From 89cda4d93ec016af99351823888d562fa0dc25ac Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Mon, 22 May 2023 18:45:57 +0800 Subject: [PATCH 1/7] ssh-key: Add more AES cipher --- Cargo.lock | 20 +++++ ssh-key/Cargo.toml | 4 +- ssh-key/src/cipher.rs | 183 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 193 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e23969e..8f2ffb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,15 @@ 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" @@ -91,6 +100,15 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -338,6 +356,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array", ] @@ -681,6 +700,7 @@ dependencies = [ "aes", "aes-gcm", "bcrypt-pbkdf", + "cbc", "ctr", "dsa", "ed25519-dalek", diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index c36a433..3bb3a6e 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -26,6 +26,7 @@ zeroize = { version = "1", default-features = false } # optional dependencies aes = { version = "0.8", optional = true, default-features = false } aes-gcm = { version = "0.10", optional = true, default-features = false, features = ["aes"] } +cbc = { version = "0.1.2", optional = true } ctr = { version = "0.9", optional = true, default-features = false } bcrypt-pbkdf = { version = "0.10", optional = true, default-features = false, features = ["alloc"] } bigint = { package = "num-bigint-dig", version = "0.8", optional = true, default-features = false } @@ -60,12 +61,13 @@ std = [ "signature/std" ] +aes-cbc = ["dep:cbc", "encryption"] aes-gcm = ["dep:aes-gcm", "encryption"] crypto = ["aes-gcm", "ed25519", "p256", "p384", "rsa"] # NOTE: `dsa` is obsolete/weak dsa = ["dep:bigint", "dep:dsa", "dep:sha1", "alloc", "signature/rand_core"] ecdsa = ["dep:sec1"] ed25519 = ["dep:ed25519-dalek", "rand_core"] -encryption = [ "alloc", "dep:aes", "dep:bcrypt-pbkdf", "dep:ctr", "rand_core"] +encryption = [ "alloc", "dep:aes", "dep:bcrypt-pbkdf", "dep:cbc", "dep:ctr", "rand_core"] getrandom = ["rand_core/getrandom"] p256 = ["dep:p256", "ecdsa"] p384 = ["dep:p384", "ecdsa"] diff --git a/ssh-key/src/cipher.rs b/ssh-key/src/cipher.rs index fd51d69..869bc14 100644 --- a/ssh-key/src/cipher.rs +++ b/ssh-key/src/cipher.rs @@ -8,16 +8,36 @@ use encoding::Label; #[cfg(feature = "encryption")] use aes::{ - cipher::{InnerIvInit, KeyInit, StreamCipherCore}, - Aes256, + cipher::{BlockCipher, BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit, StreamCipherCore}, + Aes128, Aes192, Aes256, }; +#[cfg(feature = "encryption")] +use cbc::{cipher::block_padding::NoPadding, Decryptor, Encryptor}; #[cfg(feature = "aes-gcm")] -use aes_gcm::{aead::AeadInPlace, Aes256Gcm}; +use aes_gcm::{aead::AeadInPlace, Aes128Gcm, Aes256Gcm}; + +/// AES-128 in block chaining (CBC) mode +const AES128_CBC: &str = "aes128-cbc"; + +/// AES-192 in block chaining (CBC) mode +const AES192_CBC: &str = "aes192-cbc"; + +/// AES-256 in block chaining (CBC) mode +const AES256_CBC: &str = "aes256-cbc"; + +/// AES-128 in counter (CTR) mode +const AES128_CTR: &str = "aes128-ctr"; + +/// AES-192 in counter (CTR) mode +const AES192_CTR: &str = "aes192-ctr"; /// AES-256 in counter (CTR) mode const AES256_CTR: &str = "aes256-ctr"; +/// AES-128 in Galois/Counter Mode (GCM). +const AES128_GCM: &str = "aes128-gcm@openssh.com"; + /// AES-256 in Galois/Counter Mode (GCM). const AES256_GCM: &str = "aes256-gcm@openssh.com"; @@ -41,10 +61,28 @@ pub enum Cipher { /// No cipher (unencrypted key). None, + /// AES-128 in block chaining (CBC) mode. + Aes128Cbc, + + /// AES-192 in block chaining (CBC) mode. + Aes192Cbc, + + /// AES-256 in block chaining (CBC) mode. + Aes256Cbc, + + /// AES-128 in counter (CTR) mode. + Aes128Ctr, + + /// AES-192 in counter (CTR) mode. + Aes192Ctr, + /// AES-256 in counter (CTR) mode. #[default] Aes256Ctr, + /// AES-128 in Galois/Counter Mode (GCM). + Aes128Gcm, + /// AES-256 in Galois/Counter Mode (GCM). Aes256Gcm, } @@ -57,7 +95,13 @@ impl Cipher { pub fn new(ciphername: &str) -> Result { match ciphername { "none" => Ok(Self::None), + AES128_CBC => Ok(Self::Aes128Cbc), + AES192_CBC => Ok(Self::Aes192Cbc), + AES256_CBC => Ok(Self::Aes256Cbc), + AES128_CTR => Ok(Self::Aes128Ctr), + AES192_CTR => Ok(Self::Aes192Ctr), AES256_CTR => Ok(Self::Aes256Ctr), + AES128_GCM => Ok(Self::Aes128Gcm), AES256_GCM => Ok(Self::Aes256Gcm), _ => Err(Error::AlgorithmUnknown), } @@ -67,7 +111,13 @@ impl Cipher { pub fn as_str(self) -> &'static str { match self { Self::None => "none", + Self::Aes128Cbc => AES128_CBC, + Self::Aes192Cbc => AES192_CBC, + Self::Aes256Cbc => AES256_CBC, + Self::Aes128Ctr => AES128_CTR, + Self::Aes192Ctr => AES192_CTR, Self::Aes256Ctr => AES256_CTR, + Self::Aes128Gcm => AES128_GCM, Self::Aes256Gcm => AES256_GCM, } } @@ -76,7 +126,13 @@ impl Cipher { pub fn key_and_iv_size(self) -> Option<(usize, usize)> { match self { Self::None => None, + Self::Aes128Cbc => Some((16, 16)), + Self::Aes192Cbc => Some((24, 16)), + Self::Aes256Cbc => Some((32, 16)), + Self::Aes128Ctr => Some((16, 16)), + Self::Aes192Ctr => Some((24, 16)), Self::Aes256Ctr => Some((32, 16)), + Self::Aes128Gcm => Some((16, 12)), Self::Aes256Gcm => Some((32, 12)), } } @@ -85,7 +141,14 @@ impl Cipher { pub fn block_size(self) -> usize { match self { Self::None => 8, - Self::Aes256Ctr | Self::Aes256Gcm => 16, + Self::Aes128Cbc + | Self::Aes192Cbc + | Self::Aes256Cbc + | Self::Aes128Ctr + | Self::Aes192Ctr + | Self::Aes256Ctr + | Self::Aes128Gcm + | Self::Aes256Gcm => 16, } } @@ -101,7 +164,7 @@ impl Cipher { /// Does this cipher have an authentication tag? (i.e. is it an AEAD mode?) pub fn has_tag(self) -> bool { - matches!(self, Self::Aes256Gcm) + matches!(self, Self::Aes128Gcm | Self::Aes256Gcm) } /// Is this cipher `none`? @@ -118,7 +181,25 @@ impl Cipher { #[cfg(feature = "encryption")] pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8], tag: Option) -> Result<()> { match self { - Self::Aes256Ctr => { + Self::Aes128Cbc => { + if tag.is_some() { + return Err(Error::Crypto); + } + cbc_decrypt::(key, iv, buffer) + } + Self::Aes192Cbc => { + if tag.is_some() { + return Err(Error::Crypto); + } + cbc_decrypt::(key, iv, buffer) + } + Self::Aes256Cbc => { + if tag.is_some() { + return Err(Error::Crypto); + } + cbc_decrypt::(key, iv, buffer) + } + Self::Aes128Ctr | Self::Aes192Ctr | Self::Aes256Ctr => { if tag.is_some() { return Err(Error::Crypto); } @@ -128,6 +209,17 @@ impl Cipher { Ok(()) } #[cfg(feature = "aes-gcm")] + Self::Aes128Gcm => { + let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::Crypto)?; + let nonce = AeadNonce::try_from(iv).map_err(|_| Error::Crypto)?; + let tag = tag.ok_or(Error::Crypto)?; + cipher + .decrypt_in_place_detached(&nonce.into(), &[], buffer, &tag.into()) + .map_err(|_| Error::Crypto)?; + + Ok(()) + } + #[cfg(feature = "aes-gcm")] Self::Aes256Gcm => { let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::Crypto)?; let nonce = AeadNonce::try_from(iv).map_err(|_| Error::Crypto)?; @@ -146,16 +238,39 @@ impl Cipher { #[cfg(feature = "encryption")] pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result> { match self { + Self::Aes128Cbc => { + cbc_encrypt::(key, iv, buffer)?; + Ok(None) + } + Self::Aes192Cbc => { + cbc_encrypt::(key, iv, buffer)?; + Ok(None) + } + Self::Aes256Cbc => { + cbc_encrypt::(key, iv, buffer)?; + Ok(None) + } + Self::Aes128Ctr => { + ctr_encrypt::>(key, iv, buffer)?; + Ok(None) + } + Self::Aes192Ctr => { + ctr_encrypt::>(key, iv, buffer)?; + Ok(None) + } Self::Aes256Ctr => { - let cipher = Aes256::new_from_slice(key) - .and_then(|aes| Ctr128BE::inner_iv_slice_init(aes, iv)) - .map_err(|_| Error::Crypto)?; - - cipher - .try_apply_keystream_partial(buffer.into()) + ctr_encrypt::>(key, iv, buffer)?; + Ok(None) + } + #[cfg(feature = "aes-gcm")] + Self::Aes128Gcm => { + let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::Crypto)?; + let nonce = AeadNonce::try_from(iv).map_err(|_| Error::Crypto)?; + let tag = cipher + .encrypt_in_place_detached(&nonce.into(), &[], buffer) .map_err(|_| Error::Crypto)?; - Ok(None) + Ok(Some(tag.into())) } #[cfg(feature = "aes-gcm")] Self::Aes256Gcm => { @@ -195,3 +310,45 @@ impl str::FromStr for Cipher { Self::new(id) } } + +#[cfg(feature = "encryption")] +fn cbc_encrypt(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()> +where + C: BlockEncryptMut + BlockCipher + KeyInit, +{ + let cipher = Encryptor::::new_from_slices(key, iv).map_err(|_| Error::Crypto)?; + + // Since the passed in buffer is already padded, using NoPadding here + cipher + .encrypt_padded_mut::(buffer, buffer.len()) + .map_err(|_| Error::Crypto)?; + Ok(()) +} + +#[cfg(feature = "encryption")] +fn cbc_decrypt(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()> +where + C: BlockDecryptMut + BlockCipher + KeyInit, +{ + let cipher = Decryptor::::new_from_slices(key, iv).map_err(|_| Error::Crypto)?; + + // Since the passed in buffer is already padded, using NoPadding here + cipher + .decrypt_padded_mut::(buffer) + .map_err(|_| Error::Crypto)?; + Ok(()) +} + +#[cfg(feature = "encryption")] +fn ctr_encrypt(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()> +where + C: StreamCipherCore + KeyIvInit +{ + let cipher = C::new_from_slices(key, iv) + .map_err(|_| Error::Crypto)?; + + cipher + .try_apply_keystream_partial(buffer.into()) + .map_err(|_| Error::Crypto)?; + Ok(()) +} From 6bc5f71d8724079ed4a1d265b4d8e7d0ecc40c26 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Mon, 22 May 2023 19:20:04 +0800 Subject: [PATCH 2/7] ssh-key: Create tests with newly added ciphers --- ssh-key/tests/encrypted_private_key.rs | 264 ++++++++++++++++-- .../tests/examples/id_ed25519.aes128-cbc.enc | 8 + .../tests/examples/id_ed25519.aes128-ctr.enc | 8 + .../tests/examples/id_ed25519.aes128-gcm.enc | 9 + .../tests/examples/id_ed25519.aes192-cbc.enc | 8 + .../tests/examples/id_ed25519.aes192-ctr.enc | 8 + .../tests/examples/id_ed25519.aes256-cbc.enc | 8 + ....aes-ctr.enc => id_ed25519.aes256-ctr.enc} | 0 ....aes-gcm.enc => id_ed25519.aes256-gcm.enc} | 0 9 files changed, 293 insertions(+), 20 deletions(-) create mode 100644 ssh-key/tests/examples/id_ed25519.aes128-cbc.enc create mode 100644 ssh-key/tests/examples/id_ed25519.aes128-ctr.enc create mode 100644 ssh-key/tests/examples/id_ed25519.aes128-gcm.enc create mode 100644 ssh-key/tests/examples/id_ed25519.aes192-cbc.enc create mode 100644 ssh-key/tests/examples/id_ed25519.aes192-ctr.enc create mode 100644 ssh-key/tests/examples/id_ed25519.aes256-cbc.enc rename ssh-key/tests/examples/{id_ed25519.aes-ctr.enc => id_ed25519.aes256-ctr.enc} (100%) rename ssh-key/tests/examples/{id_ed25519.aes-gcm.enc => id_ed25519.aes256-gcm.enc} (100%) diff --git a/ssh-key/tests/encrypted_private_key.rs b/ssh-key/tests/encrypted_private_key.rs index 12cef98..d99e022 100644 --- a/ssh-key/tests/encrypted_private_key.rs +++ b/ssh-key/tests/encrypted_private_key.rs @@ -9,23 +9,53 @@ use ssh_key::{Algorithm, Cipher, Kdf, KdfAlg, PrivateKey}; #[cfg(all(feature = "encryption"))] const OPENSSH_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519"); -/// AES-CTR encrypted Ed25519 OpenSSH-formatted private key. +/// AES128-CBC encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. -const OPENSSH_AES_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes-ctr.enc"); +const OPENSSH_AES128_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-cbc.enc"); -/// AES-GCM encrypted Ed25519 OpenSSH-formatted private key. +/// AES192-CBC encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. -const OPENSSH_AES_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes-gcm.enc"); +const OPENSSH_AES192_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes192-cbc.enc"); + +/// AES256-CBC encrypted Ed25519 OpenSSH-formatted private key. +/// +/// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +const OPENSSH_AES256_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes256-cbc.enc"); + +/// AES128-CTR encrypted Ed25519 OpenSSH-formatted private key. +/// +/// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +const OPENSSH_AES128_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-ctr.enc"); + +/// AES192-CTR encrypted Ed25519 OpenSSH-formatted private key. +/// +/// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +const OPENSSH_AES192_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes192-ctr.enc"); + +/// AES256-CTR encrypted Ed25519 OpenSSH-formatted private key. +/// +/// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +const OPENSSH_AES256_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes256-ctr.enc"); + +/// AES256-GCM encrypted Ed25519 OpenSSH-formatted private key. +/// +/// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +const OPENSSH_AES128_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-gcm.enc"); + +/// AES256-GCM encrypted Ed25519 OpenSSH-formatted private key. +/// +/// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +const OPENSSH_AES256_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes256-gcm.enc"); /// Bad password; don't actually use outside tests! #[cfg(all(feature = "encryption"))] const PASSWORD: &[u8] = b"hunter42"; #[test] -fn decode_openssh_aes_ctr() { - let key = PrivateKey::from_openssh(OPENSSH_AES_CTR_ED25519_EXAMPLE).unwrap(); +fn decode_openssh_aes256_ctr() { + let key = PrivateKey::from_openssh(OPENSSH_AES256_CTR_ED25519_EXAMPLE).unwrap(); assert_eq!(Algorithm::Ed25519, key.algorithm()); assert_eq!(Cipher::Aes256Ctr, key.cipher()); assert_eq!(KdfAlg::Bcrypt, key.kdf().algorithm()); @@ -45,8 +75,8 @@ fn decode_openssh_aes_ctr() { } #[test] -fn decode_openssh_aes_gcm() { - let key = PrivateKey::from_openssh(OPENSSH_AES_GCM_ED25519_EXAMPLE).unwrap(); +fn decode_openssh_aes256_gcm() { + let key = PrivateKey::from_openssh(OPENSSH_AES256_GCM_ED25519_EXAMPLE).unwrap(); assert_eq!(Algorithm::Ed25519, key.algorithm()); assert_eq!(Cipher::Aes256Gcm, key.cipher()); assert_eq!(KdfAlg::Bcrypt, key.kdf().algorithm()); @@ -64,11 +94,71 @@ fn decode_openssh_aes_gcm() { key.public_key().key_data().ed25519().unwrap().as_ref(), ); } +#[cfg(all(feature = "encryption"))] +#[test] +fn decrypt_openssh_aes128_ctr() { + let key_enc = PrivateKey::from_openssh(OPENSSH_AES128_CTR_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::Aes128Ctr, key_enc.cipher()); + let key_dec = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!( + PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), + key_dec + ); +} + +#[cfg(all(feature = "encryption"))] +#[test] +fn decrypt_openssh_aes192_ctr() { + let key_enc = PrivateKey::from_openssh(OPENSSH_AES192_CTR_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::Aes192Ctr, key_enc.cipher()); + let key_dec = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!( + PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), + key_dec + ); +} + +#[cfg(all(feature = "encryption"))] +#[test] +fn decrypt_openssh_aes256_ctr() { + let key_enc = PrivateKey::from_openssh(OPENSSH_AES256_CTR_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::Aes256Ctr, key_enc.cipher()); + let key_dec = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!( + PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), + key_dec + ); +} + +#[cfg(all(feature = "encryption"))] +#[test] +fn decrypt_openssh_aes128_cbc() { + let key_enc = PrivateKey::from_openssh(OPENSSH_AES128_CBC_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::Aes128Cbc, key_enc.cipher()); + let key_dec = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!( + PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), + key_dec + ); +} #[cfg(all(feature = "encryption"))] #[test] -fn decrypt_openssh_aes_ctr() { - let key_enc = PrivateKey::from_openssh(OPENSSH_AES_CTR_ED25519_EXAMPLE).unwrap(); +fn decrypt_openssh_aes192_cbc() { + let key_enc = PrivateKey::from_openssh(OPENSSH_AES192_CBC_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::Aes192Cbc, key_enc.cipher()); + let key_dec = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!( + PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), + key_dec + ); +} + +#[cfg(all(feature = "encryption"))] +#[test] +fn decrypt_openssh_aes256_cbc() { + let key_enc = PrivateKey::from_openssh(OPENSSH_AES256_CBC_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::Aes256Cbc, key_enc.cipher()); let key_dec = key_enc.decrypt(PASSWORD).unwrap(); assert_eq!( PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), @@ -78,8 +168,9 @@ fn decrypt_openssh_aes_ctr() { #[cfg(all(feature = "aes-gcm"))] #[test] -fn decrypt_openssh_aes_gcm() { - let key_enc = PrivateKey::from_openssh(OPENSSH_AES_GCM_ED25519_EXAMPLE).unwrap(); +fn decrypt_openssh_aes128_gcm() { + let key_enc = PrivateKey::from_openssh(OPENSSH_AES128_GCM_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::Aes128Gcm, key_enc.cipher()); let key_dec = key_enc.decrypt(PASSWORD).unwrap(); assert_eq!( PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), @@ -87,27 +178,139 @@ fn decrypt_openssh_aes_gcm() { ); } +#[cfg(all(feature = "aes-gcm"))] #[test] -fn encode_openssh_aes_ctr() { - let key = PrivateKey::from_openssh(OPENSSH_AES_CTR_ED25519_EXAMPLE).unwrap(); +fn decrypt_openssh_aes256_gcm() { + let key_enc = PrivateKey::from_openssh(OPENSSH_AES256_GCM_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::Aes256Gcm, key_enc.cipher()); + let key_dec = key_enc.decrypt(PASSWORD).unwrap(); assert_eq!( - OPENSSH_AES_CTR_ED25519_EXAMPLE.trim_end(), + PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), + key_dec + ); +} + +#[test] +fn encode_openssh_aes256_ctr() { + let key = PrivateKey::from_openssh(OPENSSH_AES256_CTR_ED25519_EXAMPLE).unwrap(); + assert_eq!( + OPENSSH_AES256_CTR_ED25519_EXAMPLE.trim_end(), key.to_openssh(Default::default()).unwrap().trim_end() ); } #[test] -fn encode_openssh_aes_gcm() { - let key = PrivateKey::from_openssh(OPENSSH_AES_GCM_ED25519_EXAMPLE).unwrap(); +fn encode_openssh_aes256_gcm() { + let key = PrivateKey::from_openssh(OPENSSH_AES256_GCM_ED25519_EXAMPLE).unwrap(); assert_eq!( - OPENSSH_AES_GCM_ED25519_EXAMPLE.trim_end(), + OPENSSH_AES256_GCM_ED25519_EXAMPLE.trim_end(), key.to_openssh(Default::default()).unwrap().trim_end() ); } #[cfg(all(feature = "encryption", feature = "getrandom"))] #[test] -fn encrypt_openssh_aes_ctr() { +fn encrypt_openssh_aes128_cbc() { + use rand_core::OsRng; + + let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); + let key_enc = key_dec + .encrypt_with_cipher(&mut OsRng, Cipher::Aes128Cbc, PASSWORD) + .unwrap(); + + // Ensure encrypted key round trips through encoder/decoder + let key_enc_str = key_enc.to_openssh(Default::default()).unwrap(); + let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap(); + assert_eq!(key_enc, key_enc2); + + // Ensure decrypted key matches the original + let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!(key_dec, key_dec2); +} + +#[cfg(all(feature = "encryption", feature = "getrandom"))] +#[test] +fn encrypt_openssh_aes192_cbc() { + use rand_core::OsRng; + + let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); + let key_enc = key_dec + .encrypt_with_cipher(&mut OsRng, Cipher::Aes192Cbc, PASSWORD) + .unwrap(); + + // Ensure encrypted key round trips through encoder/decoder + let key_enc_str = key_enc.to_openssh(Default::default()).unwrap(); + let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap(); + assert_eq!(key_enc, key_enc2); + + // Ensure decrypted key matches the original + let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!(key_dec, key_dec2); +} + +#[cfg(all(feature = "encryption", feature = "getrandom"))] +#[test] +fn encrypt_openssh_aes256_cbc() { + use rand_core::OsRng; + + let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); + let key_enc = key_dec + .encrypt_with_cipher(&mut OsRng, Cipher::Aes256Cbc, PASSWORD) + .unwrap(); + + // Ensure encrypted key round trips through encoder/decoder + let key_enc_str = key_enc.to_openssh(Default::default()).unwrap(); + let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap(); + assert_eq!(key_enc, key_enc2); + + // Ensure decrypted key matches the original + let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!(key_dec, key_dec2); +} + +#[cfg(all(feature = "encryption", feature = "getrandom"))] +#[test] +fn encrypt_openssh_aes128_ctr() { + use rand_core::OsRng; + + let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); + let key_enc = key_dec + .encrypt_with_cipher(&mut OsRng, Cipher::Aes128Ctr, PASSWORD) + .unwrap(); + + // Ensure encrypted key round trips through encoder/decoder + let key_enc_str = key_enc.to_openssh(Default::default()).unwrap(); + let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap(); + assert_eq!(key_enc, key_enc2); + + // Ensure decrypted key matches the original + let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!(key_dec, key_dec2); +} + +#[cfg(all(feature = "encryption", feature = "getrandom"))] +#[test] +fn encrypt_openssh_aes192_ctr() { + use rand_core::OsRng; + + let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); + let key_enc = key_dec + .encrypt_with_cipher(&mut OsRng, Cipher::Aes192Ctr, PASSWORD) + .unwrap(); + + // Ensure encrypted key round trips through encoder/decoder + let key_enc_str = key_enc.to_openssh(Default::default()).unwrap(); + let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap(); + assert_eq!(key_enc, key_enc2); + + // Ensure decrypted key matches the original + let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!(key_dec, key_dec2); +} + +#[cfg(all(feature = "encryption", feature = "getrandom"))] +#[test] +fn encrypt_openssh_aes256_ctr() { use rand_core::OsRng; let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); @@ -125,7 +328,28 @@ fn encrypt_openssh_aes_ctr() { #[cfg(all(feature = "aes-gcm", feature = "getrandom"))] #[test] -fn encrypt_openssh_aes_gcm() { +fn encrypt_openssh_aes128_gcm() { + use rand_core::OsRng; + + let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); + + let key_enc = key_dec + .encrypt_with_cipher(&mut OsRng, Cipher::Aes128Gcm, PASSWORD) + .unwrap(); + + // Ensure encrypted key round trips through encoder/decoder + let key_enc_str = key_enc.to_openssh(Default::default()).unwrap(); + let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap(); + assert_eq!(key_enc, key_enc2); + + // Ensure decrypted key matches the original + let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!(key_dec, key_dec2); +} + +#[cfg(all(feature = "aes-gcm", feature = "getrandom"))] +#[test] +fn encrypt_openssh_aes256_gcm() { use rand_core::OsRng; let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); diff --git a/ssh-key/tests/examples/id_ed25519.aes128-cbc.enc b/ssh-key/tests/examples/id_ed25519.aes128-cbc.enc new file mode 100644 index 0000000..97b7cc7 --- /dev/null +++ b/ssh-key/tests/examples/id_ed25519.aes128-cbc.enc @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDfKCxn9E +flgqvP7Uxtpmp5AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN +796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoEVNJyfF6Jecws0dSosXLdcRh4doAv07rra0K7 +4BxIFyzyoMqFpEdb0jNgyNlMiiIzglU3W9Nd5JQgA06YMtv5bdfJKu9Z9/nxmfTOmPSMlN +hjsXJdmKJjJmJ9ZeMeh2wzOFRewbQ3mtyroK1dXPbiIH601aW00/LYc/HqxnoA0HRkXqwj +nKUyE9vGurvcGySgo1+n7/1u+b9NbqEmt9f+g= +-----END OPENSSH PRIVATE KEY----- diff --git a/ssh-key/tests/examples/id_ed25519.aes128-ctr.enc b/ssh-key/tests/examples/id_ed25519.aes128-ctr.enc new file mode 100644 index 0000000..70bc12b --- /dev/null +++ b/ssh-key/tests/examples/id_ed25519.aes128-ctr.enc @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jdHIAAAAGYmNyeXB0AAAAGAAAABDQaLR2sa +YpWEjw/HUGyEGmAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN +796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoN+GP4jh5KCPGL/0OcFfYZBkS1vtY0QsSd++FF +qMaww+pOEru2QYaEu6VYDeHNDsYRbvSgLRTP5pdyIzY2WSxmKMkRAYTj57LQmokRjLwmHV +mKQKOQ901sAocIq/40ZCwxVXBNu49seU8u6kEgifckpjjraZ/p908QlQFtArVZrwSYvCQT +q2bFsEkyp+3e5QvkidZWbL3/f41uxirt3L28Y= +-----END OPENSSH PRIVATE KEY----- diff --git a/ssh-key/tests/examples/id_ed25519.aes128-gcm.enc b/ssh-key/tests/examples/id_ed25519.aes128-gcm.enc new file mode 100644 index 0000000..9135d3d --- /dev/null +++ b/ssh-key/tests/examples/id_ed25519.aes128-gcm.enc @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAAFmFlczEyOC1nY21Ab3BlbnNzaC5jb20AAAAGYmNyeXB0AA +AAGAAAABCiZCxATdLothFeOkNhDdB0AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAA +ILM+rvN+ot98qgEN796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoAni5wCWsx/J4HXd/6UeSH ++me0c0b5CtvJiHk37UXf+tH2f+byjhNqWSyJLgrBlR7waruc5XtsM6HPRo+HeA6VjcYqnP +r8SIipPyLAPtmNttsnc0KZRnzAri4xVIoLLRCJOmL6lzdDBCEKzrXmHZBd/MfLpZRCCoT6 +rKuxoSbFrteXAQ6bLHSLid+I+Zm2RgrT+Q9oxLHgrKifLwS1pdE9ER2FYC79hshy83fAlG +i05w +-----END OPENSSH PRIVATE KEY----- diff --git a/ssh-key/tests/examples/id_ed25519.aes192-cbc.enc b/ssh-key/tests/examples/id_ed25519.aes192-cbc.enc new file mode 100644 index 0000000..9b66f15 --- /dev/null +++ b/ssh-key/tests/examples/id_ed25519.aes192-cbc.enc @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczE5Mi1jYmMAAAAGYmNyeXB0AAAAGAAAABCAILpRu7 +oyyiJZTIy4qfJTAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN +796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoC6RbA0+5zoA0g4f/g04BxpoymbgU2wX83rrdW +E3C+IGJTdYrf/wTJ+ubtQ4JpfESA7Gr2i3mIv9NTtd54y5XzvLpe9S/NfBoFfGz2INvbD8 +BnFovA0tPHUrmy6BiUatdpoFEeBwwScB3nw+65DhngCydM2WIsUe1juIq1qpsyZRy0UI0Q +hVgHTGTC4QadWtWtanI93tSyObzke4LClIoiw= +-----END OPENSSH PRIVATE KEY----- diff --git a/ssh-key/tests/examples/id_ed25519.aes192-ctr.enc b/ssh-key/tests/examples/id_ed25519.aes192-ctr.enc new file mode 100644 index 0000000..5d0debe --- /dev/null +++ b/ssh-key/tests/examples/id_ed25519.aes192-ctr.enc @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczE5Mi1jdHIAAAAGYmNyeXB0AAAAGAAAABDI/chiIV +ViHJOdrglsLV1pAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN +796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoNAcEUO0a1eQXgZ6S/m4XoPuo3o+F9S7P795C+ +qggI+pDNisImcblRTLhpEBwT9JErq5+zj8qZ2rhCSN+UuvLa28AF2tSZmqAcRXcUNrvgCw +VcqKDR3SgHtifeCB5i32+PT9kLQc7jKQIgZNFv4zF0bmYtEZ9fboDNQZgwvu36Gegv+WNa +g2RecYJ3E7Nmte1H0qjk2aPYKtEfoo/CGl7Dw= +-----END OPENSSH PRIVATE KEY----- diff --git a/ssh-key/tests/examples/id_ed25519.aes256-cbc.enc b/ssh-key/tests/examples/id_ed25519.aes256-cbc.enc new file mode 100644 index 0000000..968e3ea --- /dev/null +++ b/ssh-key/tests/examples/id_ed25519.aes256-cbc.enc @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABC596ibeu +zBIwgxU2VhAfNPAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILM+rvN+ot98qgEN +796jTiQfZfG1KaT0PtFDJ/XFSqtiAAAAoBfqsNSTaDbJdvexzf9ykDWUG6XCqprKijfHka +YCIWuNs/8rjXwRWolpUIGTdThAlZVyrG7t7wjyr3m2jZQ8Xr4KesY7ODJ25Cz4dVBoqj9c +DdPMlIuimygqHZQgpNG7YVie669nyNuHJHFLhfAcNQCz03hhAB2NpEidzIqWZOgqch0qst +tbX5XgVeGiK6YZqYK53vAA44pFkYMGdP/iEBY= +-----END OPENSSH PRIVATE KEY----- diff --git a/ssh-key/tests/examples/id_ed25519.aes-ctr.enc b/ssh-key/tests/examples/id_ed25519.aes256-ctr.enc similarity index 100% rename from ssh-key/tests/examples/id_ed25519.aes-ctr.enc rename to ssh-key/tests/examples/id_ed25519.aes256-ctr.enc diff --git a/ssh-key/tests/examples/id_ed25519.aes-gcm.enc b/ssh-key/tests/examples/id_ed25519.aes256-gcm.enc similarity index 100% rename from ssh-key/tests/examples/id_ed25519.aes-gcm.enc rename to ssh-key/tests/examples/id_ed25519.aes256-gcm.enc From d6764b1d5c20e3a7f1c645ffff0fda566c5de9e4 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Mon, 29 May 2023 15:02:11 +0800 Subject: [PATCH 3/7] ssh-key: Add support for decoding ChaCha20-Poly1305 & 3DES-CBC private keys --- ssh-key/src/cipher.rs | 30 +++++++++-- ssh-key/tests/encrypted_private_key.rs | 53 +++++++++++++++++++ .../tests/examples/id_ed25519.3des-cbc.enc | 8 +++ .../examples/id_ed25519.chacha20-poly1305.enc | 9 ++++ 4 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 ssh-key/tests/examples/id_ed25519.3des-cbc.enc create mode 100644 ssh-key/tests/examples/id_ed25519.chacha20-poly1305.enc diff --git a/ssh-key/src/cipher.rs b/ssh-key/src/cipher.rs index 869bc14..af7a287 100644 --- a/ssh-key/src/cipher.rs +++ b/ssh-key/src/cipher.rs @@ -41,6 +41,12 @@ const AES128_GCM: &str = "aes128-gcm@openssh.com"; /// AES-256 in Galois/Counter Mode (GCM). const AES256_GCM: &str = "aes256-gcm@openssh.com"; +/// ChaCha20-Poly1305 +const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com"; + +/// Triple-DES in block chaining (CBC) mode +const TDES_CBC: &str = "3des-cbc"; + /// Nonces for AEAD modes. #[cfg(feature = "aes-gcm")] type AeadNonce = [u8; 12]; @@ -85,6 +91,12 @@ pub enum Cipher { /// AES-256 in Galois/Counter Mode (GCM). Aes256Gcm, + + /// ChaCha20-Poly1305 + ChaCha20Poly1305, + + /// TripleDES in block chaining (CBC) mode + TDesCbc, } impl Cipher { @@ -103,6 +115,8 @@ impl Cipher { AES256_CTR => Ok(Self::Aes256Ctr), AES128_GCM => Ok(Self::Aes128Gcm), AES256_GCM => Ok(Self::Aes256Gcm), + CHACHA20_POLY1305 => Ok(Self::ChaCha20Poly1305), + TDES_CBC => Ok(Self::TDesCbc), _ => Err(Error::AlgorithmUnknown), } } @@ -119,6 +133,8 @@ impl Cipher { Self::Aes256Ctr => AES256_CTR, Self::Aes128Gcm => AES128_GCM, Self::Aes256Gcm => AES256_GCM, + Self::ChaCha20Poly1305 => CHACHA20_POLY1305, + Self::TDesCbc => TDES_CBC, } } @@ -134,13 +150,15 @@ impl Cipher { Self::Aes256Ctr => Some((32, 16)), Self::Aes128Gcm => Some((16, 12)), Self::Aes256Gcm => Some((32, 12)), + Self::ChaCha20Poly1305 => Some((64, 0)), + Self::TDesCbc => Some((24, 8)), } } /// Get the block size for this cipher in bytes. pub fn block_size(self) -> usize { match self { - Self::None => 8, + Self::None | Self::ChaCha20Poly1305 | Self::TDesCbc => 8, Self::Aes128Cbc | Self::Aes192Cbc | Self::Aes256Cbc @@ -164,7 +182,10 @@ impl Cipher { /// Does this cipher have an authentication tag? (i.e. is it an AEAD mode?) pub fn has_tag(self) -> bool { - matches!(self, Self::Aes128Gcm | Self::Aes256Gcm) + matches!( + self, + Self::Aes128Gcm | Self::Aes256Gcm | Self::ChaCha20Poly1305 + ) } /// Is this cipher `none`? @@ -342,10 +363,9 @@ where #[cfg(feature = "encryption")] fn ctr_encrypt(key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()> where - C: StreamCipherCore + KeyIvInit + C: StreamCipherCore + KeyIvInit, { - let cipher = C::new_from_slices(key, iv) - .map_err(|_| Error::Crypto)?; + let cipher = C::new_from_slices(key, iv).map_err(|_| Error::Crypto)?; cipher .try_apply_keystream_partial(buffer.into()) diff --git a/ssh-key/tests/encrypted_private_key.rs b/ssh-key/tests/encrypted_private_key.rs index d99e022..84bf572 100644 --- a/ssh-key/tests/encrypted_private_key.rs +++ b/ssh-key/tests/encrypted_private_key.rs @@ -49,6 +49,16 @@ const OPENSSH_AES128_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed255 /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. const OPENSSH_AES256_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes256-gcm.enc"); +/// ChaCha20-Poly1305 encrypted Ed25519 OpenSSH-formatted private key. +/// +/// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +const OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.chacha20-poly1305.enc"); + +/// TripleDES-CBC encrypted Ed25519 OpenSSH-formatted private key. +/// +/// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +const OPENSSH_3DES_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.3des-cbc.enc"); + /// Bad password; don't actually use outside tests! #[cfg(all(feature = "encryption"))] const PASSWORD: &[u8] = b"hunter42"; @@ -94,6 +104,49 @@ fn decode_openssh_aes256_gcm() { key.public_key().key_data().ed25519().unwrap().as_ref(), ); } + +#[test] +fn decode_openssh_chacha20_poly1305() { + let key = PrivateKey::from_openssh(OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE).unwrap(); + assert_eq!(Algorithm::Ed25519, key.algorithm()); + assert_eq!(Cipher::ChaCha20Poly1305, key.cipher()); + assert_eq!(KdfAlg::Bcrypt, key.kdf().algorithm()); + + match key.kdf() { + Kdf::Bcrypt { salt, rounds } => { + assert_eq!(salt, &hex!("f651ca3efb15904d05c216a5041ea89a")); + assert_eq!(*rounds, 16); + } + other => panic!("unexpected KDF algorithm: {:?}", other), + } + + assert_eq!( + &hex!("b33eaef37ea2df7caa010defdea34e241f65f1b529a4f43ed14327f5c54aab62"), + key.public_key().key_data().ed25519().unwrap().as_ref(), + ); +} + +#[test] +fn decode_openssh_3des_cbc() { + let key = PrivateKey::from_openssh(OPENSSH_3DES_CBC_ED25519_EXAMPLE).unwrap(); + assert_eq!(Algorithm::Ed25519, key.algorithm()); + assert_eq!(Cipher::TDesCbc, key.cipher()); + assert_eq!(KdfAlg::Bcrypt, key.kdf().algorithm()); + + match key.kdf() { + Kdf::Bcrypt { salt, rounds } => { + assert_eq!(salt, &hex!("1afcebea3c598c277e7edc2b78db1e94")); + assert_eq!(*rounds, 16); + } + other => panic!("unexpected KDF algorithm: {:?}", other), + } + + assert_eq!( + &hex!("b33eaef37ea2df7caa010defdea34e241f65f1b529a4f43ed14327f5c54aab62"), + key.public_key().key_data().ed25519().unwrap().as_ref(), + ); +} + #[cfg(all(feature = "encryption"))] #[test] fn decrypt_openssh_aes128_ctr() { diff --git a/ssh-key/tests/examples/id_ed25519.3des-cbc.enc b/ssh-key/tests/examples/id_ed25519.3des-cbc.enc new file mode 100644 index 0000000..e9d3be5 --- /dev/null +++ b/ssh-key/tests/examples/id_ed25519.3des-cbc.enc @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAACDNkZXMtY2JjAAAABmJjcnlwdAAAABgAAAAQGvzr6jxZjC +d+ftwreNselAAAABAAAAABAAAAMwAAAAtzc2gtZWQyNTUxOQAAACCzPq7zfqLffKoBDe/e +o04kH2XxtSmk9D7RQyf1xUqrYgAAAJg/4GdFw1boDlKQK5tYsHQUUIyHIYR91BH6LfVhG1 +gj+b6Cj3dm1ESAUdg1qhKac4oZS6AYx6L3m7M4rHXYi2SncRqPoa+DODCxcvUqUNQOrT54 +6PjbIx6P7Ewam4PtXENekmx0gOsBshNkQyzP8XA86DtorA+kW0VU1YUTmJrqwAzZrrg2Nf +Rueif44KC5+spnhieuaDf6Lg== +-----END OPENSSH PRIVATE KEY----- diff --git a/ssh-key/tests/examples/id_ed25519.chacha20-poly1305.enc b/ssh-key/tests/examples/id_ed25519.chacha20-poly1305.enc new file mode 100644 index 0000000..445e75e --- /dev/null +++ b/ssh-key/tests/examples/id_ed25519.chacha20-poly1305.enc @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAAHWNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc2guY29tAAAABm +JjcnlwdAAAABgAAAAQ9lHKPvsVkE0FwhalBB6omgAAABAAAAABAAAAMwAAAAtzc2gtZWQy +NTUxOQAAACCzPq7zfqLffKoBDe/eo04kH2XxtSmk9D7RQyf1xUqrYgAAAJiRvYDd00XU/W +BkZ93ZW52HNwvM2m3z/MHuqD8q/tk16rKKtBNOc95wo4gyRzkdGYhKnF1RFCJYcdvlw6zo +kctfmmhQ6W54G6u9Eh9bIJtHt3l4FQgzriuIsBTUKZIlvvk6Fo5ItNPHM00r2ehuX81lcZ +QHMaims6Blw8Esl6G3NYCAa2NKyqlmM5LIfkga/Ymydvrbc7EQmN2hbii0c0aMUdYQclyk +F4o= +-----END OPENSSH PRIVATE KEY----- From deddf28d6959ef081cfee38192d3994d96ea454a Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 30 May 2023 17:23:36 +0800 Subject: [PATCH 4/7] ssh-key: Add support for chacha20-poly1305@openssh.com --- Cargo.lock | 24 ++++++++ ssh-key/Cargo.toml | 3 + ssh-key/src/cipher.rs | 76 +++++++++++++++++++++++++- ssh-key/tests/encrypted_private_key.rs | 42 +++++++++++++- 4 files changed, 143 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f2ffb1..2d54ed1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,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" @@ -520,6 +531,17 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +[[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.0" @@ -701,6 +723,7 @@ dependencies = [ "aes-gcm", "bcrypt-pbkdf", "cbc", + "chacha20", "ctr", "dsa", "ed25519-dalek", @@ -708,6 +731,7 @@ dependencies = [ "num-bigint-dig", "p256", "p384", + "poly1305", "rand_chacha", "rand_core", "rsa", diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index 3bb3a6e..7190ecb 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -28,6 +28,8 @@ aes = { version = "0.8", optional = true, default-features = false } aes-gcm = { version = "0.10", optional = true, default-features = false, features = ["aes"] } cbc = { version = "0.1.2", optional = true } ctr = { version = "0.9", optional = true, default-features = false } +chacha20 = { version = "0.9.1", optional = true, default-features = false } +poly1305 = { version = "0.8.0", optional = true, default-features = false } bcrypt-pbkdf = { version = "0.10", optional = true, default-features = false, features = ["alloc"] } bigint = { package = "num-bigint-dig", version = "0.8", optional = true, default-features = false } dsa = { version = "0.6", optional = true, default-features = false } @@ -63,6 +65,7 @@ std = [ aes-cbc = ["dep:cbc", "encryption"] aes-gcm = ["dep:aes-gcm", "encryption"] +chacha20poly1305 = ["dep:chacha20", "dep:poly1305", "encryption"] crypto = ["aes-gcm", "ed25519", "p256", "p384", "rsa"] # NOTE: `dsa` is obsolete/weak dsa = ["dep:bigint", "dep:dsa", "dep:sha1", "alloc", "signature/rand_core"] ecdsa = ["dep:sec1"] diff --git a/ssh-key/src/cipher.rs b/ssh-key/src/cipher.rs index af7a287..914f75c 100644 --- a/ssh-key/src/cipher.rs +++ b/ssh-key/src/cipher.rs @@ -48,7 +48,7 @@ const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com"; const TDES_CBC: &str = "3des-cbc"; /// Nonces for AEAD modes. -#[cfg(feature = "aes-gcm")] +#[cfg(any(feature = "aes-gcm", feature = "chacha20poly1305"))] type AeadNonce = [u8; 12]; /// Authentication tag for ciphertext data. @@ -251,6 +251,10 @@ impl Cipher { Ok(()) } + #[cfg(feature = "chacha20poly1305")] + Self::ChaCha20Poly1305 => { + chacha20_poly1305_openssh::chacha20poly1305_decrypt(key, buffer, tag) + } _ => Err(Error::Crypto), } } @@ -303,6 +307,10 @@ impl Cipher { Ok(Some(tag.into())) } + #[cfg(feature = "chacha20poly1305")] + Self::ChaCha20Poly1305 => { + chacha20_poly1305_openssh::chacha20poly1305_encrypt(key, buffer).map(Some) + } _ => Err(Error::Crypto), } } @@ -372,3 +380,69 @@ where .map_err(|_| Error::Crypto)?; Ok(()) } + +/// There are some differences between `chacha20-poly1305@openssh.com` and +/// RFC 8439 `chacha20-poly1305`. Therefore, this module implements the cipher +/// required by the sshkey. +/// +/// - The input of Poly1305 is not padded +/// - The lengths of ciphertext and AAD do not authenticate with Poly1305 +/// - There are two ChaCha20 keys derived from KDF +/// - IV is not generated from KDF +#[cfg(feature = "chacha20poly1305")] +mod chacha20_poly1305_openssh { + use super::*; + + #[cfg(feature = "encryption")] + use aes::cipher::{StreamCipher, StreamCipherSeek}; + use chacha20::ChaCha20; + use poly1305::Poly1305; + use subtle::ConstantTimeEq; + + type ChaCha20Key = [u8; 32]; + + #[inline] + fn chacha20poly1305_init(key: &[u8]) -> Result<(ChaCha20, Poly1305)> { + // The key here is actually concatenation of two chacha20 keys. + if key.len() != 64 { + return Err(Error::Crypto); + } + let k_main = ChaCha20Key::try_from(&key[..32]).map_err(|_| Error::Crypto)?; + let _k_header = ChaCha20Key::try_from(&key[32..]).map_err(|_| Error::Crypto)?; + // The nonce is from packet seq, but the value is alway 0 in sshkey. + let nonce: AeadNonce = [0u8; 12]; + + let mut main_cipher = ChaCha20::new(&k_main.into(), &nonce.into()); + let mut poly1305_key = poly1305::Key::default(); + main_cipher.apply_keystream(&mut poly1305_key); + let poly1305 = Poly1305::new(&poly1305_key); + // Seek to block 1 + main_cipher.seek(64); + + Ok((main_cipher, poly1305)) + } + + #[inline] + pub fn chacha20poly1305_encrypt(key: &[u8], buffer: &mut [u8]) -> Result { + let (mut cipher, poly1305) = chacha20poly1305_init(key)?; + + cipher.apply_keystream(buffer); + let tag = poly1305.compute_unpadded(buffer); + + Ok(tag.into()) + } + + #[inline] + pub fn chacha20poly1305_decrypt(key: &[u8], buffer: &mut [u8], tag: Option) -> Result<()> { + let (mut cipher, poly1305) = chacha20poly1305_init(key)?; + let tag = tag.ok_or(Error::Crypto)?; + + let expect_tag = poly1305.compute_unpadded(buffer); + if expect_tag.ct_eq(&tag).into() { + cipher.apply_keystream(buffer); + Ok(()) + } else { + Err(Error::Crypto) + } + } +} diff --git a/ssh-key/tests/encrypted_private_key.rs b/ssh-key/tests/encrypted_private_key.rs index 84bf572..464d3d7 100644 --- a/ssh-key/tests/encrypted_private_key.rs +++ b/ssh-key/tests/encrypted_private_key.rs @@ -12,26 +12,31 @@ const OPENSSH_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519"); /// AES128-CBC encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +#[cfg(all(feature = "encryption"))] const OPENSSH_AES128_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-cbc.enc"); /// AES192-CBC encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +#[cfg(all(feature = "encryption"))] const OPENSSH_AES192_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes192-cbc.enc"); /// AES256-CBC encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +#[cfg(all(feature = "encryption"))] const OPENSSH_AES256_CBC_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes256-cbc.enc"); /// AES128-CTR encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +#[cfg(all(feature = "encryption"))] const OPENSSH_AES128_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-ctr.enc"); /// AES192-CTR encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +#[cfg(all(feature = "encryption"))] const OPENSSH_AES192_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes192-ctr.enc"); /// AES256-CTR encrypted Ed25519 OpenSSH-formatted private key. @@ -42,6 +47,7 @@ const OPENSSH_AES256_CTR_ED25519_EXAMPLE: &str = include_str!("examples/id_ed255 /// AES256-GCM encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. +#[cfg(all(feature = "aes-gcm"))] const OPENSSH_AES128_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.aes128-gcm.enc"); /// AES256-GCM encrypted Ed25519 OpenSSH-formatted private key. @@ -52,7 +58,8 @@ const OPENSSH_AES256_GCM_ED25519_EXAMPLE: &str = include_str!("examples/id_ed255 /// ChaCha20-Poly1305 encrypted Ed25519 OpenSSH-formatted private key. /// /// Plaintext is `OPENSSH_ED25519_EXAMPLE`. -const OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE: &str = include_str!("examples/id_ed25519.chacha20-poly1305.enc"); +const OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE: &str = + include_str!("examples/id_ed25519.chacha20-poly1305.enc"); /// TripleDES-CBC encrypted Ed25519 OpenSSH-formatted private key. /// @@ -243,6 +250,18 @@ fn decrypt_openssh_aes256_gcm() { ); } +#[cfg(all(feature = "chacha20poly1305"))] +#[test] +fn decrypt_openssh_chacha20_poly1305() { + let key_enc = PrivateKey::from_openssh(OPENSSH_CHACHA20_POLY1305_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::ChaCha20Poly1305, key_enc.cipher()); + let key_dec = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!( + PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), + key_dec + ); +} + #[test] fn encode_openssh_aes256_ctr() { let key = PrivateKey::from_openssh(OPENSSH_AES256_CTR_ED25519_EXAMPLE).unwrap(); @@ -420,3 +439,24 @@ fn encrypt_openssh_aes256_gcm() { let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); assert_eq!(key_dec, key_dec2); } + +#[cfg(all(feature = "chacha20poly1305", feature = "getrandom"))] +#[test] +fn encrypt_openssh_chacha20_poly1305() { + use rand_core::OsRng; + + let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); + + let key_enc = key_dec + .encrypt_with_cipher(&mut OsRng, Cipher::ChaCha20Poly1305, PASSWORD) + .unwrap(); + + // Ensure encrypted key round trips through encoder/decoder + let key_enc_str = key_enc.to_openssh(Default::default()).unwrap(); + let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap(); + assert_eq!(key_enc, key_enc2); + + // Ensure decrypted key matches the original + let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!(key_dec, key_dec2); +} From 86821df4f7473ce5e6d73c708a6a85b89244dc45 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 30 May 2023 17:34:10 +0800 Subject: [PATCH 5/7] ssh-key: Add reference of chacha20-poly1305@openssh.com --- ssh-key/src/cipher.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ssh-key/src/cipher.rs b/ssh-key/src/cipher.rs index 914f75c..084cde6 100644 --- a/ssh-key/src/cipher.rs +++ b/ssh-key/src/cipher.rs @@ -389,6 +389,8 @@ where /// - The lengths of ciphertext and AAD do not authenticate with Poly1305 /// - There are two ChaCha20 keys derived from KDF /// - IV is not generated from KDF +/// +/// [PROTOCOL.chacha20poly1305]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD #[cfg(feature = "chacha20poly1305")] mod chacha20_poly1305_openssh { use super::*; From 0f9dabd892d239c5941e74622b4b835eda18eb85 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Wed, 14 Jun 2023 11:51:02 +0800 Subject: [PATCH 6/7] ssh-key: Add support for triple DES --- Cargo.lock | 10 ++++++++ ssh-key/Cargo.toml | 2 ++ ssh-key/src/cipher.rs | 15 ++++++++++++ ssh-key/tests/encrypted_private_key.rs | 33 ++++++++++++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 2d54ed1..9ea12e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,6 +206,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "digest" version = "0.10.6" @@ -725,6 +734,7 @@ dependencies = [ "cbc", "chacha20", "ctr", + "des", "dsa", "ed25519-dalek", "hex-literal", diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index 7190ecb..272e2a7 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -33,6 +33,7 @@ poly1305 = { version = "0.8.0", optional = true, default-features = false } bcrypt-pbkdf = { version = "0.10", optional = true, default-features = false, features = ["alloc"] } bigint = { package = "num-bigint-dig", version = "0.8", optional = true, default-features = false } dsa = { version = "0.6", optional = true, default-features = false } +des = { version = "0.8.1", optional = true, default-features = false } ed25519-dalek = { version = "=2.0.0-rc.2", optional = true, default-features = false } p256 = { version = "0.13", optional = true, default-features = false, features = ["ecdsa"] } p384 = { version = "0.13", optional = true, default-features = false, features = ["ecdsa"] } @@ -75,6 +76,7 @@ getrandom = ["rand_core/getrandom"] p256 = ["dep:p256", "ecdsa"] p384 = ["dep:p384", "ecdsa"] rsa = ["dep:bigint", "dep:rsa", "alloc", "rand_core"] +des = ["dep:des", "encryption"] [package.metadata.docs.rs] all-features = true diff --git a/ssh-key/src/cipher.rs b/ssh-key/src/cipher.rs index 084cde6..d547d8b 100644 --- a/ssh-key/src/cipher.rs +++ b/ssh-key/src/cipher.rs @@ -17,6 +17,9 @@ use cbc::{cipher::block_padding::NoPadding, Decryptor, Encryptor}; #[cfg(feature = "aes-gcm")] use aes_gcm::{aead::AeadInPlace, Aes128Gcm, Aes256Gcm}; +#[cfg(feature = "des")] +use des::TdesEde3; + /// AES-128 in block chaining (CBC) mode const AES128_CBC: &str = "aes128-cbc"; @@ -255,6 +258,13 @@ impl Cipher { Self::ChaCha20Poly1305 => { chacha20_poly1305_openssh::chacha20poly1305_decrypt(key, buffer, tag) } + #[cfg(feature = "des")] + Self::TDesCbc => { + if tag.is_some() { + return Err(Error::Crypto); + } + cbc_decrypt::(key, iv, buffer) + } _ => Err(Error::Crypto), } } @@ -311,6 +321,11 @@ impl Cipher { Self::ChaCha20Poly1305 => { chacha20_poly1305_openssh::chacha20poly1305_encrypt(key, buffer).map(Some) } + #[cfg(feature = "des")] + Self::TDesCbc => { + cbc_encrypt::(key, iv, buffer)?; + Ok(None) + } _ => Err(Error::Crypto), } } diff --git a/ssh-key/tests/encrypted_private_key.rs b/ssh-key/tests/encrypted_private_key.rs index 464d3d7..c36eb6d 100644 --- a/ssh-key/tests/encrypted_private_key.rs +++ b/ssh-key/tests/encrypted_private_key.rs @@ -262,6 +262,18 @@ fn decrypt_openssh_chacha20_poly1305() { ); } +#[cfg(all(feature = "des"))] +#[test] +fn decrypt_openssh_3des() { + let key_enc = PrivateKey::from_openssh(OPENSSH_3DES_CBC_ED25519_EXAMPLE).unwrap(); + assert_eq!(Cipher::TDesCbc, key_enc.cipher()); + let key_dec = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!( + PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(), + key_dec + ); +} + #[test] fn encode_openssh_aes256_ctr() { let key = PrivateKey::from_openssh(OPENSSH_AES256_CTR_ED25519_EXAMPLE).unwrap(); @@ -460,3 +472,24 @@ fn encrypt_openssh_chacha20_poly1305() { let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); assert_eq!(key_dec, key_dec2); } + +#[cfg(all(feature = "des", feature = "getrandom"))] +#[test] +fn encrypt_openssh_3des() { + use rand_core::OsRng; + + let key_dec = PrivateKey::from_openssh(OPENSSH_ED25519_EXAMPLE).unwrap(); + + let key_enc = key_dec + .encrypt_with_cipher(&mut OsRng, Cipher::TDesCbc, PASSWORD) + .unwrap(); + + // Ensure encrypted key round trips through encoder/decoder + let key_enc_str = key_enc.to_openssh(Default::default()).unwrap(); + let key_enc2 = PrivateKey::from_openssh(&*key_enc_str).unwrap(); + assert_eq!(key_enc, key_enc2); + + // Ensure decrypted key matches the original + let key_dec2 = key_enc.decrypt(PASSWORD).unwrap(); + assert_eq!(key_dec, key_dec2); +} From a83e74015410451dca65bd67dafec5f91f014e13 Mon Sep 17 00:00:00 2001 From: Leo Chen Date: Tue, 20 Jun 2023 02:03:16 +0800 Subject: [PATCH 7/7] ssh-key: Rename des feature to tdes --- ssh-key/Cargo.toml | 2 +- ssh-key/src/cipher.rs | 6 +++--- ssh-key/tests/encrypted_private_key.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index 272e2a7..5d4ed69 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -76,7 +76,7 @@ getrandom = ["rand_core/getrandom"] p256 = ["dep:p256", "ecdsa"] p384 = ["dep:p384", "ecdsa"] rsa = ["dep:bigint", "dep:rsa", "alloc", "rand_core"] -des = ["dep:des", "encryption"] +tdes = ["dep:des", "encryption"] [package.metadata.docs.rs] all-features = true diff --git a/ssh-key/src/cipher.rs b/ssh-key/src/cipher.rs index d547d8b..3f37b03 100644 --- a/ssh-key/src/cipher.rs +++ b/ssh-key/src/cipher.rs @@ -17,7 +17,7 @@ use cbc::{cipher::block_padding::NoPadding, Decryptor, Encryptor}; #[cfg(feature = "aes-gcm")] use aes_gcm::{aead::AeadInPlace, Aes128Gcm, Aes256Gcm}; -#[cfg(feature = "des")] +#[cfg(feature = "tdes")] use des::TdesEde3; /// AES-128 in block chaining (CBC) mode @@ -258,7 +258,7 @@ impl Cipher { Self::ChaCha20Poly1305 => { chacha20_poly1305_openssh::chacha20poly1305_decrypt(key, buffer, tag) } - #[cfg(feature = "des")] + #[cfg(feature = "tdes")] Self::TDesCbc => { if tag.is_some() { return Err(Error::Crypto); @@ -321,7 +321,7 @@ impl Cipher { Self::ChaCha20Poly1305 => { chacha20_poly1305_openssh::chacha20poly1305_encrypt(key, buffer).map(Some) } - #[cfg(feature = "des")] + #[cfg(feature = "tdes")] Self::TDesCbc => { cbc_encrypt::(key, iv, buffer)?; Ok(None) diff --git a/ssh-key/tests/encrypted_private_key.rs b/ssh-key/tests/encrypted_private_key.rs index c36eb6d..b6e4aa3 100644 --- a/ssh-key/tests/encrypted_private_key.rs +++ b/ssh-key/tests/encrypted_private_key.rs @@ -262,7 +262,7 @@ fn decrypt_openssh_chacha20_poly1305() { ); } -#[cfg(all(feature = "des"))] +#[cfg(all(feature = "tdes"))] #[test] fn decrypt_openssh_3des() { let key_enc = PrivateKey::from_openssh(OPENSSH_3DES_CBC_ED25519_EXAMPLE).unwrap(); @@ -473,7 +473,7 @@ fn encrypt_openssh_chacha20_poly1305() { assert_eq!(key_dec, key_dec2); } -#[cfg(all(feature = "des", feature = "getrandom"))] +#[cfg(all(feature = "tdes", feature = "getrandom"))] #[test] fn encrypt_openssh_3des() { use rand_core::OsRng;