ssh_cipher/
encryptor.rs

1//! Stateful encryptor object.
2
3use crate::{Cipher, Error, Result};
4use cipher::{Block, BlockCipherEncrypt, KeyIvInit};
5
6#[cfg(feature = "aes-ctr")]
7use {
8    crate::Ctr128BE,
9    cipher::{BlockSizeUser, StreamCipherCore, array::sizes::U16},
10};
11
12#[cfg(feature = "tdes")]
13use des::TdesEde3;
14
15#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
16use aes::{Aes128, Aes192, Aes256};
17
18#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
19use cipher::block::BlockModeEncrypt;
20
21/// Stateful encryptor object for unauthenticated SSH symmetric ciphers.
22///
23/// Note that this deliberately does not support AEAD modes such as AES-GCM and ChaCha20Poly1305,
24/// which are one-shot by design.
25pub struct Encryptor {
26    /// Inner enum over possible encryption ciphers.
27    inner: Inner,
28}
29
30/// Inner encryptor enum which is deliberately kept out of the public API.
31enum Inner {
32    #[cfg(feature = "aes-cbc")]
33    Aes128Cbc(cbc::Encryptor<Aes128>),
34    #[cfg(feature = "aes-cbc")]
35    Aes192Cbc(cbc::Encryptor<Aes192>),
36    #[cfg(feature = "aes-cbc")]
37    Aes256Cbc(cbc::Encryptor<Aes256>),
38    #[cfg(feature = "aes-ctr")]
39    Aes128Ctr(Ctr128BE<Aes128>),
40    #[cfg(feature = "aes-ctr")]
41    Aes192Ctr(Ctr128BE<Aes192>),
42    #[cfg(feature = "aes-ctr")]
43    Aes256Ctr(Ctr128BE<Aes256>),
44    #[cfg(feature = "tdes")]
45    TDesCbc(cbc::Encryptor<TdesEde3>),
46}
47
48impl Encryptor {
49    /// Create a new encryptor object with the given [`Cipher`], key, and IV.
50    pub fn new(cipher: Cipher, key: &[u8], iv: &[u8]) -> Result<Self> {
51        cipher.check_key_and_iv(key, iv)?;
52
53        let inner = match cipher {
54            #[cfg(feature = "aes-cbc")]
55            Cipher::Aes128Cbc => cbc::Encryptor::new_from_slices(key, iv).map(Inner::Aes128Cbc),
56            #[cfg(feature = "aes-cbc")]
57            Cipher::Aes192Cbc => cbc::Encryptor::new_from_slices(key, iv).map(Inner::Aes192Cbc),
58            #[cfg(feature = "aes-cbc")]
59            Cipher::Aes256Cbc => cbc::Encryptor::new_from_slices(key, iv).map(Inner::Aes256Cbc),
60            #[cfg(feature = "aes-ctr")]
61            Cipher::Aes128Ctr => Ctr128BE::new_from_slices(key, iv).map(Inner::Aes128Ctr),
62            #[cfg(feature = "aes-ctr")]
63            Cipher::Aes192Ctr => Ctr128BE::new_from_slices(key, iv).map(Inner::Aes192Ctr),
64            #[cfg(feature = "aes-ctr")]
65            Cipher::Aes256Ctr => Ctr128BE::new_from_slices(key, iv).map(Inner::Aes256Ctr),
66            #[cfg(feature = "tdes")]
67            Cipher::TDesCbc => cbc::Encryptor::new_from_slices(key, iv).map(Inner::TDesCbc),
68            _ => return Err(cipher.unsupported()),
69        }
70        .map_err(|_| Error::Length)?;
71
72        Ok(Self { inner })
73    }
74
75    /// Get the cipher for this encryptor.
76    pub fn cipher(&self) -> Cipher {
77        match &self.inner {
78            #[cfg(feature = "aes-cbc")]
79            Inner::Aes128Cbc(_) => Cipher::Aes128Cbc,
80            #[cfg(feature = "aes-cbc")]
81            Inner::Aes192Cbc(_) => Cipher::Aes192Cbc,
82            #[cfg(feature = "aes-cbc")]
83            Inner::Aes256Cbc(_) => Cipher::Aes256Cbc,
84            #[cfg(feature = "aes-ctr")]
85            Inner::Aes128Ctr(_) => Cipher::Aes128Ctr,
86            #[cfg(feature = "aes-ctr")]
87            Inner::Aes192Ctr(_) => Cipher::Aes192Ctr,
88            #[cfg(feature = "aes-ctr")]
89            Inner::Aes256Ctr(_) => Cipher::Aes256Ctr,
90            #[cfg(feature = "tdes")]
91            Inner::TDesCbc(_) => Cipher::TDesCbc,
92        }
93    }
94
95    /// Encrypt the given buffer in place.
96    ///
97    /// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's
98    /// block size.
99    pub fn encrypt(&mut self, buffer: &mut [u8]) -> Result<()> {
100        match &mut self.inner {
101            #[cfg(feature = "aes-cbc")]
102            Inner::Aes128Cbc(cipher) => cbc_encrypt(cipher, buffer)?,
103            #[cfg(feature = "aes-cbc")]
104            Inner::Aes192Cbc(cipher) => cbc_encrypt(cipher, buffer)?,
105            #[cfg(feature = "aes-cbc")]
106            Inner::Aes256Cbc(cipher) => cbc_encrypt(cipher, buffer)?,
107            #[cfg(feature = "aes-ctr")]
108            Inner::Aes128Ctr(cipher) => ctr_encrypt(cipher, buffer)?,
109            #[cfg(feature = "aes-ctr")]
110            Inner::Aes192Ctr(cipher) => ctr_encrypt(cipher, buffer)?,
111            #[cfg(feature = "aes-ctr")]
112            Inner::Aes256Ctr(cipher) => ctr_encrypt(cipher, buffer)?,
113            #[cfg(feature = "tdes")]
114            Inner::TDesCbc(cipher) => cbc_encrypt(cipher, buffer)?,
115        }
116
117        Ok(())
118    }
119}
120
121/// CBC mode encryption helper which assumes the input is unpadded and block-aligned.
122#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
123fn cbc_encrypt<C>(encryptor: &mut cbc::Encryptor<C>, buffer: &mut [u8]) -> Result<()>
124where
125    C: BlockCipherEncrypt,
126{
127    let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer);
128
129    // Ensure input is block-aligned.
130    if !remaining.is_empty() {
131        return Err(Error::Length);
132    }
133
134    encryptor.encrypt_blocks(blocks);
135    Ok(())
136}
137
138/// CTR mode encryption helper which assumes the input is unpadded and block-aligned.
139#[cfg(feature = "aes-ctr")]
140pub(crate) fn ctr_encrypt<C>(encryptor: &mut Ctr128BE<C>, buffer: &mut [u8]) -> Result<()>
141where
142    C: BlockCipherEncrypt + BlockSizeUser<BlockSize = U16>,
143{
144    let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer);
145
146    // Ensure input is block-aligned.
147    if !remaining.is_empty() {
148        return Err(Error::Length);
149    }
150
151    encryptor.apply_keystream_blocks(blocks);
152    Ok(())
153}