ssh_cipher/
decryptor.rs

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