Skip to main content

ssh_key/
kdf.rs

1//! Key Derivation Functions.
2//!
3//! These are used for deriving an encryption key from a password.
4
5use crate::{Error, KdfAlg, Result};
6use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
7
8#[cfg(feature = "alloc")]
9use alloc::vec::Vec;
10
11#[cfg(feature = "encryption")]
12use {crate::Cipher, bcrypt_pbkdf::bcrypt_pbkdf, rand_core::TryCryptoRng, zeroize::Zeroizing};
13
14/// Default number of rounds to use for bcrypt-pbkdf.
15#[cfg(feature = "encryption")]
16const DEFAULT_BCRYPT_ROUNDS: u32 = 16;
17
18/// Default salt size. Matches OpenSSH.
19#[cfg(feature = "encryption")]
20const DEFAULT_SALT_SIZE: usize = 16;
21
22/// Key Derivation Functions (KDF).
23#[derive(Clone, Debug, Eq, PartialEq)]
24#[non_exhaustive]
25#[derive(Default)]
26pub enum Kdf {
27    /// No KDF.
28    #[default]
29    None,
30
31    /// bcrypt-pbkdf options.
32    #[cfg(feature = "alloc")]
33    Bcrypt {
34        /// Salt
35        salt: Vec<u8>,
36
37        /// Rounds
38        rounds: u32,
39    },
40}
41
42impl Kdf {
43    /// Initialize KDF configuration for the given algorithm.
44    ///
45    /// # Errors
46    /// Returns [`Error::RngFailure`] on RNG errors.
47    #[cfg(feature = "encryption")]
48    pub fn new<R: TryCryptoRng + ?Sized>(algorithm: KdfAlg, rng: &mut R) -> Result<Self> {
49        let mut salt = vec![0u8; DEFAULT_SALT_SIZE];
50        rng.try_fill_bytes(&mut salt)
51            .map_err(|_| Error::RngFailure)?;
52
53        match algorithm {
54            KdfAlg::None => {
55                // Disallow explicit initialization with a `none` algorithm
56                Err(Error::AlgorithmUnknown)
57            }
58            KdfAlg::Bcrypt => Ok(Kdf::Bcrypt {
59                salt,
60                rounds: DEFAULT_BCRYPT_ROUNDS,
61            }),
62        }
63    }
64
65    /// Get the KDF algorithm.
66    #[must_use]
67    pub fn algorithm(&self) -> KdfAlg {
68        match self {
69            Self::None => KdfAlg::None,
70            #[cfg(feature = "alloc")]
71            Self::Bcrypt { .. } => KdfAlg::Bcrypt,
72        }
73    }
74
75    /// Derive an encryption key from the given password.
76    ///
77    /// # Errors
78    /// Returns [`Error::Crypto`] in the event the underlying password hashing primitive returns an
79    /// error.
80    #[cfg(feature = "encryption")]
81    pub fn derive(&self, password: impl AsRef<[u8]>, output: &mut [u8]) -> Result<()> {
82        match self {
83            Kdf::None => Err(Error::Decrypted),
84            Kdf::Bcrypt { salt, rounds } => {
85                bcrypt_pbkdf(password, salt, *rounds, output).map_err(|_| Error::Crypto)?;
86                Ok(())
87            }
88        }
89    }
90
91    /// Derive key and IV for the given [`Cipher`].
92    ///
93    /// Returns two byte vectors containing the key and IV respectively.
94    ///
95    /// # Errors
96    /// - Returns [`Error::Decrypted`] if `cipher` is [`Cipher::None`].
97    /// - Returns [`Error::Encoding`] in the event of a length error.
98    #[cfg(feature = "encryption")]
99    pub fn derive_key_and_iv(
100        &self,
101        cipher: Cipher,
102        password: impl AsRef<[u8]>,
103    ) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
104        let (key_size, iv_size) = cipher.key_and_iv_size().ok_or(Error::Decrypted)?;
105
106        let okm_size = key_size
107            .checked_add(iv_size)
108            .ok_or(encoding::Error::Length)?;
109
110        let mut okm = Zeroizing::new(vec![0u8; okm_size]);
111        self.derive(password, &mut okm)?;
112        let mut iv = okm.split_off(key_size);
113
114        // For whatever reason `chacha20-poly1305@openssh.com` uses a nonce of all zeros for private
115        // key encryption, relying on a unique salt used in the password-based encryption key
116        // derivation to ensure that each encryption key is only used once.
117        if cipher == Cipher::ChaCha20Poly1305 {
118            iv.copy_from_slice(&cipher::ChaChaNonce::default());
119        }
120
121        Ok((okm, iv))
122    }
123
124    /// Is the KDF configured as `none`?
125    #[must_use]
126    pub fn is_none(&self) -> bool {
127        self == &Self::None
128    }
129
130    /// Is the KDF configured as anything other than `none`?
131    #[must_use]
132    pub fn is_some(&self) -> bool {
133        !self.is_none()
134    }
135
136    /// Is the KDF configured as `bcrypt` (i.e. bcrypt-pbkdf)?
137    #[cfg(feature = "alloc")]
138    #[must_use]
139    pub fn is_bcrypt(&self) -> bool {
140        matches!(self, Self::Bcrypt { .. })
141    }
142}
143
144impl Decode for Kdf {
145    type Error = Error;
146
147    fn decode(reader: &mut impl Reader) -> Result<Self> {
148        match KdfAlg::decode(reader)? {
149            KdfAlg::None => {
150                if usize::decode(reader)? == 0 {
151                    Ok(Self::None)
152                } else {
153                    Err(Error::AlgorithmUnknown)
154                }
155            }
156            KdfAlg::Bcrypt => {
157                #[cfg(not(feature = "alloc"))]
158                return Err(Error::AlgorithmUnknown);
159
160                #[cfg(feature = "alloc")]
161                reader.read_prefixed(|reader| {
162                    Ok(Self::Bcrypt {
163                        salt: Vec::decode(reader)?,
164                        rounds: u32::decode(reader)?,
165                    })
166                })
167            }
168        }
169    }
170}
171
172impl Encode for Kdf {
173    fn encoded_len(&self) -> encoding::Result<usize> {
174        let kdfopts_prefixed_len = match self {
175            Self::None => 4,
176            #[cfg(feature = "alloc")]
177            Self::Bcrypt { salt, .. } => [12, salt.len()].checked_sum()?,
178        };
179
180        [self.algorithm().encoded_len()?, kdfopts_prefixed_len].checked_sum()
181    }
182
183    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
184        self.algorithm().encode(writer)?;
185
186        match self {
187            Self::None => 0usize.encode(writer)?,
188            #[cfg(feature = "alloc")]
189            Self::Bcrypt { salt, rounds } => {
190                [8, salt.len()].checked_sum()?.encode(writer)?;
191                salt.encode(writer)?;
192                rounds.encode(writer)?;
193            }
194        }
195
196        Ok(())
197    }
198}