From 3b42be17971f763604bcea715b717c56c5bff365 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sat, 4 Nov 2023 15:39:25 -0500 Subject: [PATCH] ca: Add support for encrypted private keys --- Cargo.lock | 161 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- src/ca.rs | 26 +++++++- src/config.rs | 2 + src/server/host.rs | 9 ++- 5 files changed, 193 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 811308e..a9446f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -176,6 +211,17 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bcrypt-pbkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -206,6 +252,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -224,6 +289,15 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.83" @@ -239,6 +313,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "cipher" version = "0.4.4" @@ -286,6 +371,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "4.1.1" @@ -593,6 +687,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.0" @@ -722,6 +826,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array", ] @@ -944,6 +1049,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "option-ext" version = "0.2.0" @@ -1014,6 +1125,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1094,6 +1214,29 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1533,8 +1676,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" dependencies = [ + "aes", + "aes-gcm", + "cbc", + "chacha20", "cipher", + "ctr", + "poly1305", "ssh-encoding", + "subtle", ] [[package]] @@ -1554,6 +1704,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2180b3bc4955efd5661a97658d3cf4c8107e0d132f619195afe9486c13cca313" dependencies = [ + "bcrypt-pbkdf", "ed25519-dalek", "p256", "p384", @@ -1831,6 +1982,16 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index b49f5fa..44908c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ jsonwebtoken = { version = "8.3.0", default-features = false } rand_core = { version = "0.6.4", features = ["getrandom"] } serde = { version = "1.0.190", features = ["derive"] } serde_json = "1.0.108" -ssh-key = { version = "0.6.2", features = ["serde", "ed25519", "getrandom"] } +ssh-key = { version = "0.6.2", features = ["serde", "ed25519", "getrandom", "encryption"] } tokio = { version = "1.33.0", features = ["rt", "macros", "net", "signal", "fs", "io-util"] } toml = "0.8.6" tracing = { version = "0.1.40", features = ["log"] } diff --git a/src/ca.rs b/src/ca.rs index 9a8a39b..f95cc45 100644 --- a/src/ca.rs +++ b/src/ca.rs @@ -7,7 +7,7 @@ use ssh_key::rand_core::OsRng; use ssh_key::{Certificate, PrivateKey, PublicKey}; use tokio::fs::File; use tokio::io::AsyncReadExt; -use tracing::debug; +use tracing::{debug, warn}; #[derive(Debug)] pub enum CertError { @@ -91,7 +91,10 @@ impl std::error::Error for LoadKeyError { } /// Load an SSH private key from a file -pub async fn load_private_key

(path: P) -> Result +pub async fn load_private_key

( + path: P, + passphrase_path: Option

, +) -> Result where P: AsRef, { @@ -99,7 +102,24 @@ where debug!("Loading private key from {}", path.as_ref().display()); let mut f = File::open(path).await?; f.read_to_end(&mut data).await?; - parse_private_key(&data) + let privkey = parse_private_key(&data)?; + if privkey.is_encrypted() { + if let Some(path) = passphrase_path { + debug!( + "Loading private key passphrase from {}", + path.as_ref().display() + ); + let mut passphrase = Vec::new(); + let mut f = File::open(path).await?; + f.read_to_end(&mut passphrase).await?; + Ok(privkey.decrypt(passphrase)?) + } else { + warn!("Private key is encrypted but no passphrase provided"); + Ok(privkey) + } + } else { + Ok(privkey) + } } /// Parse an SSH private key from a slice of bytes diff --git a/src/config.rs b/src/config.rs index f80417c..4695aa2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -48,6 +48,7 @@ pub struct HostCaConfig { /// Path to the Host CA private key file #[serde(default = "default_host_ca_key")] pub private_key_file: PathBuf, + pub private_key_passphrase_file: Option, /// Duration of issued host certificates #[serde(default = "default_host_cert_duration")] @@ -58,6 +59,7 @@ impl Default for HostCaConfig { fn default() -> Self { Self { private_key_file: default_host_ca_key(), + private_key_passphrase_file: None, cert_duration: default_host_cert_duration(), } } diff --git a/src/server/host.rs b/src/server/host.rs index 5bfd42e..2144404 100644 --- a/src/server/host.rs +++ b/src/server/host.rs @@ -115,9 +115,12 @@ pub(super) async fn sign_host_cert( let config = &ctx.config; let duration = Duration::from_secs(config.ca.host.cert_duration); - let privkey = ca::load_private_key(&config.ca.host.private_key_file) - .await - .map_err(SignKeyError::LoadPrivateKey)?; + let privkey = ca::load_private_key( + &config.ca.host.private_key_file, + config.ca.host.private_key_passphrase_file.as_ref(), + ) + .await + .map_err(SignKeyError::LoadPrivateKey)?; let pubkey = ca::parse_public_key(&body.pubkey) .map_err(SignKeyError::ParsePublicKey)?;