From ba022d618602ed4e97f3540ee523c4da0f1635d9 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 22 May 2026 08:26:30 -0600 Subject: [PATCH] ssh-cipher: consolidate `aes` feature; extract `Aes` type Combines the `aes-cbc`, `aes-ctr`, and `aes-gcm` features into a single `aes` feature which provides them all. Additionally refactors the internals of `Encryptor` and `Decryptor` around a new `Aes` type which provides an enum over the 128-bit, 192-bit, and 256-bit key sizes, and dynamically selects which one based on the provided key size. This type impls the `BlockCipherDecrypt`, `BlockCipherEncrypt`, and `BlockSizeUser` traits which delegate to the inner cipher, since once initialized with a key the API provided by the cipher is the same. These delegate to the respective AES implementations for various key sizes. Because it impls these traits, we're able to use it directly with types like `cbc::{Encryptor, Decryptor}` and `Ctr128BE`, collapsing the combinatorial explosion of key sizes and block modes down to just one enum variant per block mode. --- ssh-cipher/Cargo.toml | 4 +- ssh-cipher/src/aes.rs | 81 +++++++++++++++++++ ssh-cipher/src/decryptor.rs | 150 +++++++++++------------------------- ssh-cipher/src/encryptor.rs | 107 ++++++++----------------- ssh-cipher/src/error.rs | 6 ++ ssh-cipher/src/lib.rs | 46 +++++------ ssh-cipher/tests/lib.rs | 16 ++-- ssh-key/Cargo.toml | 6 +- 8 files changed, 196 insertions(+), 220 deletions(-) create mode 100644 ssh-cipher/src/aes.rs diff --git a/ssh-cipher/Cargo.toml b/ssh-cipher/Cargo.toml index 325e35a4..1de1ec75 100644 --- a/ssh-cipher/Cargo.toml +++ b/ssh-cipher/Cargo.toml @@ -38,9 +38,7 @@ zeroize = { version = "1", optional = true, default-features = false } hex-literal = "1" [features] -aes-cbc = ["dep:aes", "dep:cbc"] -aes-ctr = ["dep:aes", "dep:ctr"] -aes-gcm = ["dep:aead", "dep:aes", "dep:aes-gcm"] +aes = ["dep:aead", "dep:aes", "dep:aes-gcm", "dep:cbc", "dep:ctr"] chacha20poly1305 = ["dep:aead", "dep:chacha20", "dep:poly1305", "dep:ctutils"] tdes = ["dep:des", "dep:cbc"] zeroize = [ diff --git a/ssh-cipher/src/aes.rs b/ssh-cipher/src/aes.rs new file mode 100644 index 00000000..383541fb --- /dev/null +++ b/ssh-cipher/src/aes.rs @@ -0,0 +1,81 @@ +//! AES block cipher. + +use ::aes::{Aes128, Aes192, Aes256, Block}; +use ::cipher::{ + BlockCipherDecClosure, BlockCipherDecrypt, BlockCipherEncClosure, BlockCipherEncrypt, + BlockSizeUser, InvalidLength, KeyInit, array::sizes::U16, +}; + +/// Advanced Encryption Standard (AES) low-level block cipher. +/// +/// Supports 128-bit, 192-bit, and 256-bit key sizes. +pub(crate) enum Aes { + Aes128(Aes128), + Aes192(Aes192), + Aes256(Aes256), +} + +impl Aes { + /// Create a new AES block cipher instance. + /// + /// Supports `key` whose length is 16-bytes (128-bit), 24-bytes (192-bits), or 32-bytes + /// (256-bits). + /// + /// # Errors + /// Returns [`InvalidLength`] if the length of `key` is not any of the above. + pub(crate) fn new(key: &[u8]) -> Result { + if let Ok(cipher) = Aes128::new_from_slice(key) { + return Ok(Aes::Aes128(cipher)); + } + + if let Ok(cipher) = Aes192::new_from_slice(key) { + return Ok(Aes::Aes192(cipher)); + } + + if let Ok(cipher) = Aes256::new_from_slice(key) { + return Ok(Aes::Aes256(cipher)); + } + + Err(InvalidLength) + } +} + +impl BlockCipherDecrypt for Aes { + fn decrypt_blocks(&self, blocks: &mut [Block]) { + match self { + Aes::Aes128(aes) => aes.decrypt_blocks(blocks), + Aes::Aes192(aes) => aes.decrypt_blocks(blocks), + Aes::Aes256(aes) => aes.decrypt_blocks(blocks), + } + } + + fn decrypt_with_backend(&self, f: impl BlockCipherDecClosure) { + match self { + Aes::Aes128(aes) => aes.decrypt_with_backend(f), + Aes::Aes192(aes) => aes.decrypt_with_backend(f), + Aes::Aes256(aes) => aes.decrypt_with_backend(f), + } + } +} + +impl BlockCipherEncrypt for Aes { + fn encrypt_blocks(&self, blocks: &mut [Block]) { + match self { + Aes::Aes128(aes) => aes.encrypt_blocks(blocks), + Aes::Aes192(aes) => aes.encrypt_blocks(blocks), + Aes::Aes256(aes) => aes.encrypt_blocks(blocks), + } + } + + fn encrypt_with_backend(&self, f: impl BlockCipherEncClosure) { + match self { + Aes::Aes128(aes) => aes.encrypt_with_backend(f), + Aes::Aes192(aes) => aes.encrypt_with_backend(f), + Aes::Aes256(aes) => aes.encrypt_with_backend(f), + } + } +} + +impl BlockSizeUser for Aes { + type BlockSize = U16; +} diff --git a/ssh-cipher/src/decryptor.rs b/ssh-cipher/src/decryptor.rs index 1ca708c3..fcc62a59 100644 --- a/ssh-cipher/src/decryptor.rs +++ b/ssh-cipher/src/decryptor.rs @@ -1,22 +1,19 @@ //! Stateful decryptor object. use crate::{Cipher, Error, Result}; -use cipher::KeyIvInit; -use core::fmt::{self, Debug}; - -#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))] -use aes::{Aes128, Aes192, Aes256}; -#[cfg(any(feature = "aes-cbc", feature = "tdes"))] -use cipher::SetIvState; -#[cfg(any(feature = "aes-cbc", feature = "tdes"))] use cipher::{ - Block, IvState, + Block, IvState, SetIvState, block::{BlockCipherDecrypt, BlockModeDecrypt}, }; +use core::fmt::{self, Debug}; +#[cfg(feature = "aes")] +use { + crate::aes::Aes, + cipher::{InnerIvInit, StreamCipher, StreamCipherSeek}, + ctr::{Ctr128BE, CtrCore}, +}; #[cfg(feature = "tdes")] -use des::TdesEde3; -#[cfg(feature = "aes-ctr")] -use {crate::encryptor::ctr_encrypt as ctr_decrypt, cipher::StreamCipherSeek, ctr::Ctr128BE}; +use {cipher::KeyIvInit, des::TdesEde3}; /// Stateful decryptor object for unauthenticated SSH symmetric ciphers. /// @@ -25,31 +22,26 @@ use {crate::encryptor::ctr_encrypt as ctr_decrypt, cipher::StreamCipherSeek, ctr pub struct Decryptor { /// Inner enum over possible decryption ciphers. inner: Inner, + + /// Cipher in use by this `Encryptor`. + cipher: Cipher, } /// Inner decryptor enum which is deliberately kept out of the public API. enum Inner { - #[cfg(feature = "aes-cbc")] - Aes128Cbc(cbc::Decryptor), - #[cfg(feature = "aes-cbc")] - Aes192Cbc(cbc::Decryptor), - #[cfg(feature = "aes-cbc")] - Aes256Cbc(cbc::Decryptor), - #[cfg(feature = "aes-ctr")] - Aes128Ctr(Ctr128BE), - #[cfg(feature = "aes-ctr")] - Aes192Ctr(Ctr128BE), - #[cfg(feature = "aes-ctr")] - Aes256Ctr(Ctr128BE), + #[cfg(feature = "aes")] + AesCbc(cbc::Decryptor), + #[cfg(feature = "aes")] + AesCtr(Ctr128BE), #[cfg(feature = "tdes")] TDesCbc(cbc::Decryptor), } /// Current IV state or position within the cipher. enum State { - #[cfg(feature = "aes-cbc")] + #[cfg(feature = "aes")] AesCbc(aes::Block), - #[cfg(feature = "aes-ctr")] + #[cfg(feature = "aes")] AesCtr(u64), #[cfg(feature = "tdes")] TDesCbc(Block), @@ -67,46 +59,28 @@ impl Decryptor { cipher.check_key_and_iv(key, iv)?; let inner = match cipher { - #[cfg(feature = "aes-cbc")] - Cipher::Aes128Cbc => cbc::Decryptor::new_from_slices(key, iv).map(Inner::Aes128Cbc), - #[cfg(feature = "aes-cbc")] - Cipher::Aes192Cbc => cbc::Decryptor::new_from_slices(key, iv).map(Inner::Aes192Cbc), - #[cfg(feature = "aes-cbc")] - Cipher::Aes256Cbc => cbc::Decryptor::new_from_slices(key, iv).map(Inner::Aes256Cbc), - #[cfg(feature = "aes-ctr")] - Cipher::Aes128Ctr => Ctr128BE::new_from_slices(key, iv).map(Inner::Aes128Ctr), - #[cfg(feature = "aes-ctr")] - Cipher::Aes192Ctr => Ctr128BE::new_from_slices(key, iv).map(Inner::Aes192Ctr), - #[cfg(feature = "aes-ctr")] - Cipher::Aes256Ctr => Ctr128BE::new_from_slices(key, iv).map(Inner::Aes256Ctr), + #[cfg(feature = "aes")] + Cipher::Aes128Cbc | Cipher::Aes192Cbc | Cipher::Aes256Cbc => { + cbc::Decryptor::inner_iv_slice_init(Aes::new(key)?, iv).map(Inner::AesCbc) + } + #[cfg(feature = "aes")] + Cipher::Aes128Ctr | Cipher::Aes192Ctr | Cipher::Aes256Ctr => { + let core = CtrCore::inner_iv_slice_init(Aes::new(key)?, iv)?; + Ok(Inner::AesCtr(Ctr128BE::from_core(core))) + } #[cfg(feature = "tdes")] Cipher::TDesCbc => cbc::Decryptor::new_from_slices(key, iv).map(Inner::TDesCbc), _ => return Err(cipher.unsupported()), } .map_err(|_| Error::Length)?; - Ok(Self { inner }) + Ok(Self { inner, cipher }) } /// Get the cipher for this decryptor. #[must_use] pub fn cipher(&self) -> Cipher { - match &self.inner { - #[cfg(feature = "aes-cbc")] - Inner::Aes128Cbc(_) => Cipher::Aes128Cbc, - #[cfg(feature = "aes-cbc")] - Inner::Aes192Cbc(_) => Cipher::Aes192Cbc, - #[cfg(feature = "aes-cbc")] - Inner::Aes256Cbc(_) => Cipher::Aes256Cbc, - #[cfg(feature = "aes-ctr")] - Inner::Aes128Ctr(_) => Cipher::Aes128Ctr, - #[cfg(feature = "aes-ctr")] - Inner::Aes192Ctr(_) => Cipher::Aes192Ctr, - #[cfg(feature = "aes-ctr")] - Inner::Aes256Ctr(_) => Cipher::Aes256Ctr, - #[cfg(feature = "tdes")] - Inner::TDesCbc(_) => Cipher::TDesCbc, - } + self.cipher } /// Decrypt the given buffer in place. @@ -115,24 +89,16 @@ impl Decryptor { /// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's /// block size. pub fn decrypt(&mut self, buffer: &mut [u8]) -> Result<()> { - #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] match &mut self.inner { - #[cfg(feature = "aes-cbc")] - Inner::Aes128Cbc(cipher) => cbc_decrypt(cipher, buffer), - #[cfg(feature = "aes-cbc")] - Inner::Aes192Cbc(cipher) => cbc_decrypt(cipher, buffer), - #[cfg(feature = "aes-cbc")] - Inner::Aes256Cbc(cipher) => cbc_decrypt(cipher, buffer), - #[cfg(feature = "aes-ctr")] - Inner::Aes128Ctr(cipher) => ctr_decrypt(cipher, buffer), - #[cfg(feature = "aes-ctr")] - Inner::Aes192Ctr(cipher) => ctr_decrypt(cipher, buffer), - #[cfg(feature = "aes-ctr")] - Inner::Aes256Ctr(cipher) => ctr_decrypt(cipher, buffer), + #[cfg(feature = "aes")] + Inner::AesCbc(cipher) => cbc_decrypt(cipher, buffer)?, + #[cfg(feature = "aes")] + Inner::AesCtr(cipher) => cipher + .try_apply_keystream(buffer) + .map_err(|_| Error::Crypto)?, #[cfg(feature = "tdes")] - Inner::TDesCbc(cipher) => cbc_decrypt(cipher, buffer), + Inner::TDesCbc(cipher) => cbc_decrypt(cipher, buffer)?, } - .map_err(|_| Error::Length)?; Ok(()) } @@ -155,18 +121,10 @@ impl Decryptor { /// Get the current cipher state, i.e. IV or position within the stream cipher. fn state(&self) -> State { match &self.inner { - #[cfg(feature = "aes-cbc")] - Inner::Aes128Cbc(cipher) => State::AesCbc(cipher.iv_state()), - #[cfg(feature = "aes-cbc")] - Inner::Aes192Cbc(cipher) => State::AesCbc(cipher.iv_state()), - #[cfg(feature = "aes-cbc")] - Inner::Aes256Cbc(cipher) => State::AesCbc(cipher.iv_state()), - #[cfg(feature = "aes-ctr")] - Inner::Aes128Ctr(cipher) => State::AesCtr(cipher.current_pos()), - #[cfg(feature = "aes-ctr")] - Inner::Aes192Ctr(cipher) => State::AesCtr(cipher.current_pos()), - #[cfg(feature = "aes-ctr")] - Inner::Aes256Ctr(cipher) => State::AesCtr(cipher.current_pos()), + #[cfg(feature = "aes")] + Inner::AesCbc(cipher) => State::AesCbc(cipher.iv_state()), + #[cfg(feature = "aes")] + Inner::AesCtr(cipher) => State::AesCtr(cipher.current_pos()), #[cfg(feature = "tdes")] Inner::TDesCbc(cipher) => State::TDesCbc(cipher.iv_state()), } @@ -175,31 +133,13 @@ impl Decryptor { /// Set the current cipher state. fn set_state(&mut self, state: State) -> Result<()> { match (&mut self.inner, state) { - #[cfg(feature = "aes-cbc")] - (Inner::Aes128Cbc(cipher), State::AesCbc(iv)) => { + #[cfg(feature = "aes")] + (Inner::AesCbc(cipher), State::AesCbc(iv)) => { cipher.set_iv(&iv); Ok(()) } - #[cfg(feature = "aes-cbc")] - (Inner::Aes192Cbc(cipher), State::AesCbc(iv)) => { - cipher.set_iv(&iv); - Ok(()) - } - #[cfg(feature = "aes-cbc")] - (Inner::Aes256Cbc(cipher), State::AesCbc(iv)) => { - cipher.set_iv(&iv); - Ok(()) - } - #[cfg(feature = "aes-ctr")] - (Inner::Aes128Ctr(cipher), State::AesCtr(pos)) => { - cipher.try_seek(pos).map_err(|_| Error::Crypto) - } - #[cfg(feature = "aes-ctr")] - (Inner::Aes192Ctr(cipher), State::AesCtr(pos)) => { - cipher.try_seek(pos).map_err(|_| Error::Crypto) - } - #[cfg(feature = "aes-ctr")] - (Inner::Aes256Ctr(cipher), State::AesCtr(pos)) => { + #[cfg(feature = "aes")] + (Inner::AesCtr(cipher), State::AesCtr(pos)) => { cipher.try_seek(pos).map_err(|_| Error::Crypto) } #[cfg(feature = "tdes")] @@ -222,7 +162,7 @@ impl Debug for Decryptor { } /// CBC mode decryption helper which assumes the input is unpadded and block-aligned. -#[cfg(any(feature = "aes-cbc", feature = "tdes"))] +#[cfg(any(feature = "aes", feature = "tdes"))] fn cbc_decrypt(decryptor: &mut cbc::Decryptor, buffer: &mut [u8]) -> Result<()> where C: BlockCipherDecrypt, diff --git a/ssh-cipher/src/encryptor.rs b/ssh-cipher/src/encryptor.rs index b744851f..2d5226f3 100644 --- a/ssh-cipher/src/encryptor.rs +++ b/ssh-cipher/src/encryptor.rs @@ -1,20 +1,17 @@ //! Stateful encryptor object. use crate::{Cipher, Error, Result}; -use cipher::{BlockCipherEncrypt, KeyIvInit}; +use cipher::{Block, BlockCipherEncrypt, BlockModeEncrypt}; use core::fmt::{self, Debug}; -#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))] -use aes::{Aes128, Aes192, Aes256}; -#[cfg(any(feature = "aes-cbc", feature = "tdes"))] -use cipher::{Block, BlockModeEncrypt}; -#[cfg(feature = "tdes")] -use des::TdesEde3; -#[cfg(feature = "aes-ctr")] +#[cfg(feature = "aes")] use { - cipher::{BlockSizeUser, StreamCipher, array::sizes::U16}, - ctr::Ctr128BE, + crate::aes::Aes, + cipher::{InnerIvInit, StreamCipher}, + ctr::{Ctr128BE, CtrCore}, }; +#[cfg(feature = "tdes")] +use {cipher::KeyIvInit, des::TdesEde3}; /// Stateful encryptor object for unauthenticated SSH symmetric ciphers. /// @@ -23,22 +20,17 @@ use { pub struct Encryptor { /// Inner enum over possible encryption ciphers. inner: Inner, + + /// Cipher in use by this `Encryptor`. + cipher: Cipher, } /// Inner encryptor enum which is deliberately kept out of the public API. enum Inner { - #[cfg(feature = "aes-cbc")] - Aes128Cbc(cbc::Encryptor), - #[cfg(feature = "aes-cbc")] - Aes192Cbc(cbc::Encryptor), - #[cfg(feature = "aes-cbc")] - Aes256Cbc(cbc::Encryptor), - #[cfg(feature = "aes-ctr")] - Aes128Ctr(Ctr128BE), - #[cfg(feature = "aes-ctr")] - Aes192Ctr(Ctr128BE), - #[cfg(feature = "aes-ctr")] - Aes256Ctr(Ctr128BE), + #[cfg(feature = "aes")] + AesCbc(cbc::Encryptor), + #[cfg(feature = "aes")] + AesCtr(Ctr128BE), #[cfg(feature = "tdes")] TDesCbc(cbc::Encryptor), } @@ -55,46 +47,28 @@ impl Encryptor { cipher.check_key_and_iv(key, iv)?; let inner = match cipher { - #[cfg(feature = "aes-cbc")] - Cipher::Aes128Cbc => cbc::Encryptor::new_from_slices(key, iv).map(Inner::Aes128Cbc), - #[cfg(feature = "aes-cbc")] - Cipher::Aes192Cbc => cbc::Encryptor::new_from_slices(key, iv).map(Inner::Aes192Cbc), - #[cfg(feature = "aes-cbc")] - Cipher::Aes256Cbc => cbc::Encryptor::new_from_slices(key, iv).map(Inner::Aes256Cbc), - #[cfg(feature = "aes-ctr")] - Cipher::Aes128Ctr => Ctr128BE::new_from_slices(key, iv).map(Inner::Aes128Ctr), - #[cfg(feature = "aes-ctr")] - Cipher::Aes192Ctr => Ctr128BE::new_from_slices(key, iv).map(Inner::Aes192Ctr), - #[cfg(feature = "aes-ctr")] - Cipher::Aes256Ctr => Ctr128BE::new_from_slices(key, iv).map(Inner::Aes256Ctr), + #[cfg(feature = "aes")] + Cipher::Aes128Cbc | Cipher::Aes192Cbc | Cipher::Aes256Cbc => { + cbc::Encryptor::inner_iv_slice_init(Aes::new(key)?, iv).map(Inner::AesCbc) + } + #[cfg(feature = "aes")] + Cipher::Aes128Ctr | Cipher::Aes192Ctr | Cipher::Aes256Ctr => { + let core = CtrCore::inner_iv_slice_init(Aes::new(key)?, iv)?; + Ok(Inner::AesCtr(Ctr128BE::from_core(core))) + } #[cfg(feature = "tdes")] Cipher::TDesCbc => cbc::Encryptor::new_from_slices(key, iv).map(Inner::TDesCbc), _ => return Err(cipher.unsupported()), } .map_err(|_| Error::Length)?; - Ok(Self { inner }) + Ok(Self { inner, cipher }) } /// Get the cipher for this encryptor. #[must_use] pub fn cipher(&self) -> Cipher { - match &self.inner { - #[cfg(feature = "aes-cbc")] - Inner::Aes128Cbc(_) => Cipher::Aes128Cbc, - #[cfg(feature = "aes-cbc")] - Inner::Aes192Cbc(_) => Cipher::Aes192Cbc, - #[cfg(feature = "aes-cbc")] - Inner::Aes256Cbc(_) => Cipher::Aes256Cbc, - #[cfg(feature = "aes-ctr")] - Inner::Aes128Ctr(_) => Cipher::Aes128Ctr, - #[cfg(feature = "aes-ctr")] - Inner::Aes192Ctr(_) => Cipher::Aes192Ctr, - #[cfg(feature = "aes-ctr")] - Inner::Aes256Ctr(_) => Cipher::Aes256Ctr, - #[cfg(feature = "tdes")] - Inner::TDesCbc(_) => Cipher::TDesCbc, - } + self.cipher } /// Encrypt the given buffer in place. @@ -104,18 +78,12 @@ impl Encryptor { /// block size. pub fn encrypt(&mut self, buffer: &mut [u8]) -> Result<()> { match &mut self.inner { - #[cfg(feature = "aes-cbc")] - Inner::Aes128Cbc(cipher) => cbc_encrypt(cipher, buffer)?, - #[cfg(feature = "aes-cbc")] - Inner::Aes192Cbc(cipher) => cbc_encrypt(cipher, buffer)?, - #[cfg(feature = "aes-cbc")] - Inner::Aes256Cbc(cipher) => cbc_encrypt(cipher, buffer)?, - #[cfg(feature = "aes-ctr")] - Inner::Aes128Ctr(cipher) => ctr_encrypt(cipher, buffer)?, - #[cfg(feature = "aes-ctr")] - Inner::Aes192Ctr(cipher) => ctr_encrypt(cipher, buffer)?, - #[cfg(feature = "aes-ctr")] - Inner::Aes256Ctr(cipher) => ctr_encrypt(cipher, buffer)?, + #[cfg(feature = "aes")] + Inner::AesCbc(cipher) => cbc_encrypt(cipher, buffer)?, + #[cfg(feature = "aes")] + Inner::AesCtr(cipher) => cipher + .try_apply_keystream(buffer) + .map_err(|_| Error::Crypto)?, #[cfg(feature = "tdes")] Inner::TDesCbc(cipher) => cbc_encrypt(cipher, buffer)?, } @@ -133,7 +101,7 @@ impl Debug for Encryptor { } /// CBC mode encryption helper which assumes the input is unpadded and block-aligned. -#[cfg(any(feature = "aes-cbc", feature = "tdes"))] +#[cfg(any(feature = "aes", feature = "tdes"))] fn cbc_encrypt(encryptor: &mut cbc::Encryptor, buffer: &mut [u8]) -> Result<()> where C: BlockCipherEncrypt, @@ -148,14 +116,3 @@ where encryptor.encrypt_blocks(blocks); Ok(()) } - -/// CTR mode encryption helper which assumes the input is unpadded and block-aligned. -#[cfg(feature = "aes-ctr")] -pub(crate) fn ctr_encrypt(encryptor: &mut Ctr128BE, buffer: &mut [u8]) -> Result<()> -where - C: BlockCipherEncrypt + BlockSizeUser, -{ - encryptor - .try_apply_keystream(buffer) - .map_err(|_| Error::Crypto) -} diff --git a/ssh-cipher/src/error.rs b/ssh-cipher/src/error.rs index 11cd02e5..ecfcb77d 100644 --- a/ssh-cipher/src/error.rs +++ b/ssh-cipher/src/error.rs @@ -43,3 +43,9 @@ impl fmt::Display for Error { } impl core::error::Error for Error {} + +impl From for Error { + fn from(_: cipher::InvalidLength) -> Error { + Error::Length + } +} diff --git a/ssh-cipher/src/lib.rs b/ssh-cipher/src/lib.rs index 58ba3e17..241ef3c3 100644 --- a/ssh-cipher/src/lib.rs +++ b/ssh-cipher/src/lib.rs @@ -8,17 +8,19 @@ mod error; +#[cfg(feature = "aes")] +mod aes; #[cfg(feature = "chacha20poly1305")] mod chacha20poly1305; -#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] +#[cfg(any(feature = "aes", feature = "tdes"))] mod decryptor; -#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] +#[cfg(any(feature = "aes", feature = "tdes"))] mod encryptor; pub use crate::error::{Error, Result}; pub use cipher; -#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] +#[cfg(any(feature = "aes", feature = "tdes"))] pub use crate::{decryptor::Decryptor, encryptor::Encryptor}; #[cfg(feature = "chacha20poly1305")] @@ -28,13 +30,13 @@ use cipher::array::{Array, typenum::U16}; use core::{fmt, str}; use encoding::{Label, LabelError}; -#[cfg(feature = "aes-gcm")] +#[cfg(feature = "aes")] use { aead::array::typenum::U12, aes_gcm::{Aes128Gcm, Aes256Gcm}, }; -#[cfg(any(feature = "aes-gcm", feature = "chacha20poly1305"))] +#[cfg(any(feature = "aes", feature = "chacha20poly1305"))] use aead::{AeadInOut, KeyInit}; /// AES-128 in block chaining (CBC) mode @@ -68,7 +70,7 @@ const CHACHA20_POLY1305: &str = "chacha20-poly1305@openssh.com"; const TDES_CBC: &str = "3des-cbc"; /// Nonce for `aes128-gcm@openssh.com`/`aes256-gcm@openssh.com`. -#[cfg(feature = "aes-gcm")] +#[cfg(feature = "aes")] pub type AesGcmNonce = Array; /// Authentication tag for ciphertext data. @@ -262,13 +264,10 @@ impl Cipher { /// # Errors /// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's /// block size. - #[cfg_attr( - not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")), - allow(unused_variables) - )] + #[cfg_attr(not(any(feature = "aes", feature = "tdes")), allow(unused_variables))] pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8], tag: Option) -> Result<()> { match self { - #[cfg(feature = "aes-gcm")] + #[cfg(feature = "aes")] Self::Aes128Gcm => { let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?; let nonce = iv.try_into().map_err(|_| Error::IvSize)?; @@ -279,7 +278,7 @@ impl Cipher { Ok(()) } - #[cfg(feature = "aes-gcm")] + #[cfg(feature = "aes")] Self::Aes256Gcm => { let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?; let nonce = iv.try_into().map_err(|_| Error::IvSize)?; @@ -300,7 +299,7 @@ impl Cipher { .map_err(|_| Error::Crypto) } // Use `Decryptor` for non-AEAD modes - #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] + #[cfg(any(feature = "aes", feature = "tdes"))] _ => { // Non-AEAD modes don't take a tag. if tag.is_some() { @@ -309,7 +308,7 @@ impl Cipher { self.decryptor(key, iv)?.decrypt(buffer) } - #[cfg(not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")))] + #[cfg(not(any(feature = "aes", feature = "tdes")))] _ => Err(self.unsupported()), } } @@ -321,7 +320,7 @@ impl Cipher { /// /// # Errors /// Propagates errors from [`Decryptor::new`]. - #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] + #[cfg(any(feature = "aes", feature = "tdes"))] pub fn decryptor(self, key: &[u8], iv: &[u8]) -> Result { Decryptor::new(self, key, iv) } @@ -331,13 +330,10 @@ impl Cipher { /// # Errors /// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's /// block size. - #[cfg_attr( - not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")), - allow(unused_variables) - )] + #[cfg_attr(not(any(feature = "aes", feature = "tdes")), allow(unused_variables))] pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result> { match self { - #[cfg(feature = "aes-gcm")] + #[cfg(feature = "aes")] Self::Aes128Gcm => { let cipher = Aes128Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?; let nonce = iv.try_into().map_err(|_| Error::IvSize)?; @@ -347,7 +343,7 @@ impl Cipher { Ok(Some(tag)) } - #[cfg(feature = "aes-gcm")] + #[cfg(feature = "aes")] Self::Aes256Gcm => { let cipher = Aes256Gcm::new_from_slice(key).map_err(|_| Error::KeySize)?; let nonce = iv.try_into().map_err(|_| Error::IvSize)?; @@ -367,12 +363,12 @@ impl Cipher { Ok(Some(tag)) } // Use `Encryptor` for non-AEAD modes - #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] + #[cfg(any(feature = "aes", feature = "tdes"))] _ => { self.encryptor(key, iv)?.encrypt(buffer)?; Ok(None) } - #[cfg(not(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes")))] + #[cfg(not(any(feature = "aes", feature = "tdes")))] _ => Err(self.unsupported()), } } @@ -384,13 +380,13 @@ impl Cipher { /// /// # Errors /// Propagates errors from [`Encryptor::new`]. - #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] + #[cfg(any(feature = "aes", feature = "tdes"))] pub fn encryptor(self, key: &[u8], iv: &[u8]) -> Result { Encryptor::new(self, key, iv) } /// Check that the key and IV are the expected length for this cipher. - #[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))] + #[cfg(any(feature = "aes", feature = "tdes"))] fn check_key_and_iv(self, key: &[u8], iv: &[u8]) -> Result<()> { let (key_size, iv_size) = self .key_and_iv_size() diff --git a/ssh-cipher/tests/lib.rs b/ssh-cipher/tests/lib.rs index dc3b6207..b8a14a40 100644 --- a/ssh-cipher/tests/lib.rs +++ b/ssh-cipher/tests/lib.rs @@ -7,21 +7,21 @@ use ssh_cipher::Cipher; fn round_trip() { const MSG: &[u8] = b"Testing 1 2 3..."; const CIPHERS: &[Cipher] = &[ - #[cfg(feature = "aes-cbc")] + #[cfg(feature = "aes")] Cipher::Aes128Cbc, - #[cfg(feature = "aes-cbc")] + #[cfg(feature = "aes")] Cipher::Aes192Cbc, - #[cfg(feature = "aes-cbc")] + #[cfg(feature = "aes")] Cipher::Aes256Cbc, - #[cfg(feature = "aes-ctr")] + #[cfg(feature = "aes")] Cipher::Aes128Ctr, - #[cfg(feature = "aes-ctr")] + #[cfg(feature = "aes")] Cipher::Aes192Ctr, - #[cfg(feature = "aes-ctr")] + #[cfg(feature = "aes")] Cipher::Aes256Ctr, - #[cfg(feature = "aes-gcm")] + #[cfg(feature = "aes")] Cipher::Aes128Gcm, - #[cfg(feature = "aes-gcm")] + #[cfg(feature = "aes")] Cipher::Aes256Gcm, #[cfg(feature = "chacha20poly1305")] Cipher::ChaCha20Poly1305, diff --git a/ssh-key/Cargo.toml b/ssh-key/Cargo.toml index bc01567c..e2aabaff 100644 --- a/ssh-key/Cargo.toml +++ b/ssh-key/Cargo.toml @@ -67,16 +67,14 @@ ed25519 = ["dep:ed25519-dalek", "rand_core"] encryption = [ "dep:bcrypt-pbkdf", "alloc", - "cipher/aes-cbc", - "cipher/aes-ctr", - "cipher/aes-gcm", + "cipher/aes", "cipher/chacha20poly1305", "rand_core" ] p256 = ["dep:p256", "ecdsa"] p384 = ["dep:p384", "ecdsa"] p521 = ["dep:p521", "ecdsa"] -ppk = ["dep:hex", "alloc", "cipher/aes-cbc", "dep:hmac", "dep:argon2", "dep:sha1"] +ppk = ["dep:hex", "alloc", "cipher/aes", "dep:hmac", "dep:argon2", "dep:sha1"] rsa = ["dep:rsa", "alloc", "encoding/bigint", "rand_core"] sha1 = ["dep:sha1"] tdes = ["cipher/tdes", "encryption"]