Skip to main content

ssh_cipher/
encryptor.rs

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