ssh_key/
cipher.rs

1//! Symmetric encryption ciphers.
2//!
3//! These are used for encrypting private keys.
4
5use crate::{algorithm::AlgString, Error, Result};
6use core::{fmt, str};
7
8#[cfg(feature = "encryption")]
9use aes::{
10    cipher::{InnerIvInit, KeyInit, StreamCipherCore},
11    Aes256,
12};
13
14/// AES-256 in counter (CTR) mode
15const AES256_CTR: &str = "aes256-ctr";
16
17/// Counter mode with a 32-bit big endian counter.
18#[cfg(feature = "encryption")]
19type Ctr128BE<Cipher> = ctr::CtrCore<Cipher, ctr::flavors::Ctr128BE>;
20
21/// Cipher algorithms.
22#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
23#[non_exhaustive]
24pub enum Cipher {
25    /// No cipher (unencrypted key).
26    None,
27
28    /// AES-256 in counter (CTR) mode.
29    Aes256Ctr,
30}
31
32impl Cipher {
33    /// Decode cipher algorithm from the given `ciphername`.
34    ///
35    /// # Supported cipher names
36    /// - `aes256-ctr`
37    pub fn new(ciphername: &str) -> Result<Self> {
38        match ciphername {
39            "none" => Ok(Self::None),
40            AES256_CTR => Ok(Self::Aes256Ctr),
41            _ => Err(Error::Algorithm),
42        }
43    }
44
45    /// Get the string identifier which corresponds to this algorithm.
46    pub fn as_str(self) -> &'static str {
47        match self {
48            Self::None => "none",
49            Self::Aes256Ctr => AES256_CTR,
50        }
51    }
52
53    /// Get the key and IV size for this cipher in bytes.
54    pub fn key_and_iv_size(self) -> Option<(usize, usize)> {
55        match self {
56            Self::None => None,
57            Self::Aes256Ctr => Some((32, 16)),
58        }
59    }
60
61    /// Get the block size for this cipher in bytes.
62    pub fn block_size(self) -> usize {
63        match self {
64            Self::None => 8,
65            Self::Aes256Ctr => 16,
66        }
67    }
68
69    /// Compute the length of padding necessary to pad the given input to
70    /// the block size.
71    #[allow(clippy::integer_arithmetic)]
72    pub fn padding_len(self, input_size: usize) -> usize {
73        match input_size % self.block_size() {
74            0 => 0,
75            input_rem => self.block_size() - input_rem,
76        }
77    }
78
79    /// Is this cipher `none`?
80    pub fn is_none(self) -> bool {
81        self == Self::None
82    }
83
84    /// Is the cipher anything other than `none`?
85    pub fn is_some(self) -> bool {
86        !self.is_none()
87    }
88
89    /// Decrypt the ciphertext in the `buffer` in-place using this cipher.
90    #[cfg(feature = "encryption")]
91    #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
92    pub fn decrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()> {
93        match self {
94            Self::None => return Err(Error::Crypto),
95            // Counter mode encryption and decryption are the same operation
96            Self::Aes256Ctr => self.encrypt(key, iv, buffer)?,
97        }
98
99        Ok(())
100    }
101
102    /// Encrypt the ciphertext in the `buffer` in-place using this cipher.
103    #[cfg(feature = "encryption")]
104    #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
105    pub fn encrypt(self, key: &[u8], iv: &[u8], buffer: &mut [u8]) -> Result<()> {
106        match self {
107            Self::None => return Err(Error::Crypto),
108            Self::Aes256Ctr => {
109                let cipher = Aes256::new_from_slice(key)
110                    .and_then(|aes| Ctr128BE::inner_iv_slice_init(aes, iv))
111                    .map_err(|_| Error::Crypto)?;
112
113                cipher
114                    .try_apply_keystream_partial(buffer.into())
115                    .map_err(|_| Error::Crypto)?;
116            }
117        }
118
119        Ok(())
120    }
121}
122
123impl AsRef<str> for Cipher {
124    fn as_ref(&self) -> &str {
125        self.as_str()
126    }
127}
128
129impl AlgString for Cipher {}
130
131impl Default for Cipher {
132    fn default() -> Cipher {
133        Cipher::Aes256Ctr
134    }
135}
136
137impl fmt::Display for Cipher {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        f.write_str(self.as_str())
140    }
141}
142
143impl str::FromStr for Cipher {
144    type Err = Error;
145
146    fn from_str(id: &str) -> Result<Self> {
147        Self::new(id)
148    }
149}