bc_components/encrypted_key/
encrypted_key_impl.rs

1//! This module provides functionality to securely lock (encrypt) and unlock
2//! (decrypt) a symmetric content key using secret-based key derivation.
3//! Multiple derivation methods are supported, ensuring extensibility and
4//! security.
5
6use anyhow::{Error, Result};
7use dcbor::prelude::*;
8
9use super::SSHAgentParams;
10use crate::{
11    Argon2idParams, EncryptedMessage, HKDFParams, KeyDerivation,
12    KeyDerivationMethod, KeyDerivationParams, PBKDF2Params, ScryptParams,
13    SymmetricKey, tags,
14};
15
16/// # Overview
17/// Provides symmetric encryption and decryption of content keys using various
18/// key derivation methods (HKDF, PBKDF2, Scrypt, Argon2id). This module
19/// implements types and traits to wrap the encryption mechanisms, and encodes
20/// methods and parameters in CBOR according to the defined CDDL schemas.
21///
22/// # Usage
23/// - Call `EncryptedKey::lock` with a chosen key derivation method, secret, and
24///   content key to produce an encrypted key.
25/// - Retrieve the original content key by calling `EncryptedKey::unlock` with
26///   the correct secret.
27///
28/// # Encoding
29/// The form of an `EncryptedKey` is an `EncryptedMessage` that contains the
30/// encrypted content key, with its Additional Authenticated Data (AAD) being
31/// the CBOR encoding of the key derivation method and parameters used for key
32/// derivation. The same key derivation method and parameters must be used to
33/// unlock the content key.
34///
35/// CDDL:
36/// ```cddl
37/// EncryptedKey = #6.40027(EncryptedMessage) ; TAG_ENCRYPTED_KEY
38///
39/// EncryptedMessage =
40///     #6.40002([ ciphertext: bstr, nonce: bstr, auth: bstr, aad: bstr .cbor KeyDerivation ]) ; TAG_ENCRYPTED
41///
42/// KeyDerivation = HKDFParams / PBKDF2Params / ScryptParams / Argon2idParams / SSHAgentParams
43///
44/// HKDFParams = [HKDF, Salt, HashType]
45/// PBKDF2Params = [PBKDF2, Salt, iterations: uint, HashType]
46/// ScryptParams = [Scrypt, Salt, log_n: uint, r: uint, p: uint]
47/// Argon2idParams = [Argon2id, Salt]
48/// SSHAgentParams = [SSHAgent, Salt, id: tstr]
49///
50/// KeyDerivationMethod = HKDF / PBKDF2 / Scrypt / Argon2id / SSHAgent
51///
52/// HKDF = 0
53/// PBKDF2 = 1
54/// Scrypt = 2
55/// Argon2id = 3
56/// SSHAgent = 4
57///
58/// HashType = SHA256 / SHA512
59///
60/// SHA256 = 0
61/// SHA512 = 1
62/// ```
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct EncryptedKey {
65    params: KeyDerivationParams,
66    encrypted_message: EncryptedMessage,
67}
68
69impl EncryptedKey {
70    pub fn lock_opt(
71        mut params: KeyDerivationParams,
72        secret: impl AsRef<[u8]>,
73        content_key: &SymmetricKey,
74    ) -> Result<Self> {
75        let encrypted_message = params.lock(content_key, secret)?;
76        Ok(Self { params, encrypted_message })
77    }
78
79    pub fn lock(
80        method: KeyDerivationMethod,
81        secret: impl AsRef<[u8]>,
82        content_key: &SymmetricKey,
83    ) -> Result<Self> {
84        match method {
85            KeyDerivationMethod::HKDF => Self::lock_opt(
86                KeyDerivationParams::HKDF(HKDFParams::new()),
87                secret,
88                content_key,
89            ),
90            KeyDerivationMethod::PBKDF2 => Self::lock_opt(
91                KeyDerivationParams::PBKDF2(PBKDF2Params::new()),
92                secret,
93                content_key,
94            ),
95            KeyDerivationMethod::Scrypt => Self::lock_opt(
96                KeyDerivationParams::Scrypt(ScryptParams::new()),
97                secret,
98                content_key,
99            ),
100            KeyDerivationMethod::Argon2id => Self::lock_opt(
101                KeyDerivationParams::Argon2id(Argon2idParams::new()),
102                secret,
103                content_key,
104            ),
105            KeyDerivationMethod::SSHAgent => Self::lock_opt(
106                KeyDerivationParams::SSHAgent(SSHAgentParams::new()),
107                secret,
108                content_key,
109            ),
110        }
111    }
112
113    pub fn encrypted_message(&self) -> &EncryptedMessage {
114        &self.encrypted_message
115    }
116
117    pub fn aad_cbor(&self) -> Result<CBOR> {
118        self.encrypted_message()
119            .aad_cbor()
120            .ok_or_else(|| Error::msg("Missing AAD CBOR in EncryptedMessage"))
121    }
122
123    pub fn unlock(&self, secret: impl AsRef<[u8]>) -> Result<SymmetricKey> {
124        let encrypted_message = &self.encrypted_message();
125        let cbor = self.aad_cbor()?;
126        let array = cbor.clone().try_into_array()?;
127        let method = array
128            .first()
129            .ok_or_else(|| Error::msg("Missing method"))?
130            .try_into()?;
131        match method {
132            KeyDerivationMethod::HKDF => {
133                let params = HKDFParams::try_from(cbor)?;
134                params.unlock(encrypted_message, secret)
135            }
136            KeyDerivationMethod::PBKDF2 => {
137                let params = PBKDF2Params::try_from(cbor)?;
138                params.unlock(encrypted_message, secret)
139            }
140            KeyDerivationMethod::Scrypt => {
141                let params = ScryptParams::try_from(cbor)?;
142                params.unlock(encrypted_message, secret)
143            }
144            KeyDerivationMethod::Argon2id => {
145                let params = Argon2idParams::try_from(cbor)?;
146                params.unlock(encrypted_message, secret)
147            }
148            KeyDerivationMethod::SSHAgent => {
149                let params = SSHAgentParams::try_from(cbor)?;
150                params.unlock(encrypted_message, secret)
151            }
152        }
153    }
154
155    pub fn is_password_based(&self) -> bool { self.params.is_password_based() }
156
157    pub fn is_ssh_agent(&self) -> bool { self.params.is_ssh_agent() }
158}
159
160impl std::fmt::Display for EncryptedKey {
161    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162        write!(f, "EncryptedKey({})", self.params)
163    }
164}
165
166impl CBORTagged for EncryptedKey {
167    fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_ENCRYPTED_KEY]) }
168}
169
170impl From<EncryptedKey> for CBOR {
171    fn from(value: EncryptedKey) -> Self { value.tagged_cbor() }
172}
173
174impl CBORTaggedEncodable for EncryptedKey {
175    fn untagged_cbor(&self) -> CBOR { self.encrypted_message().clone().into() }
176}
177
178impl TryFrom<CBOR> for EncryptedKey {
179    type Error = dcbor::Error;
180
181    fn try_from(value: CBOR) -> dcbor::Result<Self> {
182        Self::from_tagged_cbor(value)
183    }
184}
185
186impl CBORTaggedDecodable for EncryptedKey {
187    fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
188        let encrypted_key: EncryptedMessage = untagged_cbor.try_into()?;
189        let params_cbor = CBOR::try_from_data(encrypted_key.aad())?;
190        let params = params_cbor.try_into()?;
191        Ok(Self { params, encrypted_message: encrypted_key })
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    fn test_secret() -> &'static [u8] { b"correct horse battery staple" }
200
201    fn test_content_key() -> SymmetricKey { SymmetricKey::new() }
202
203    #[test]
204    fn test_encrypted_key_hkdf_roundtrip() {
205        crate::register_tags();
206        let secret = test_secret();
207        let content_key = test_content_key();
208
209        let encrypted =
210            EncryptedKey::lock(KeyDerivationMethod::HKDF, secret, &content_key)
211                .unwrap();
212        assert_eq!(format!("{}", encrypted), "EncryptedKey(HKDF(SHA256))");
213        let cbor = encrypted.clone().to_cbor();
214        let encrypted2 = EncryptedKey::try_from(cbor).unwrap();
215        let decrypted = EncryptedKey::unlock(&encrypted2, secret).unwrap();
216
217        assert_eq!(content_key, decrypted);
218    }
219
220    #[test]
221    fn test_encrypted_key_pbkdf2_roundtrip() {
222        let secret = test_secret();
223        let content_key = test_content_key();
224
225        let encrypted = EncryptedKey::lock(
226            KeyDerivationMethod::PBKDF2,
227            secret,
228            &content_key,
229        )
230        .unwrap();
231        assert_eq!(format!("{}", encrypted), "EncryptedKey(PBKDF2(SHA256))");
232        let cbor = encrypted.clone().to_cbor();
233        let encrypted2 = EncryptedKey::try_from(cbor).unwrap();
234        let decrypted = EncryptedKey::unlock(&encrypted2, secret).unwrap();
235
236        assert_eq!(content_key, decrypted);
237    }
238
239    #[test]
240    fn test_encrypted_key_scrypt_roundtrip() {
241        let secret = test_secret();
242        let content_key = test_content_key();
243
244        let encrypted = EncryptedKey::lock(
245            KeyDerivationMethod::Scrypt,
246            secret,
247            &content_key,
248        )
249        .unwrap();
250        assert_eq!(format!("{}", encrypted), "EncryptedKey(Scrypt)");
251        let cbor = encrypted.clone().to_cbor();
252        let encrypted2 = EncryptedKey::try_from(cbor).unwrap();
253        let decrypted = EncryptedKey::unlock(&encrypted2, secret).unwrap();
254
255        assert_eq!(content_key, decrypted);
256    }
257
258    #[test]
259    fn test_encrypted_key_argon2id_roundtrip() {
260        let secret = test_secret();
261        let content_key = test_content_key();
262
263        let argon2id = EncryptedKey::lock(
264            KeyDerivationMethod::Argon2id,
265            secret,
266            &content_key,
267        )
268        .unwrap();
269        assert_eq!(format!("{}", argon2id), "EncryptedKey(Argon2id)");
270        let cbor = argon2id.clone().to_cbor();
271        let argon2id2 = EncryptedKey::try_from(cbor).unwrap();
272        let decrypted = EncryptedKey::unlock(&argon2id2, secret).unwrap();
273
274        assert_eq!(content_key, decrypted);
275    }
276
277    #[test]
278    fn test_encrypted_key_wrong_secret_fails() {
279        let secret = test_secret();
280        let wrong_secret = b"wrong secret";
281        let content_key = test_content_key();
282
283        let encrypted =
284            EncryptedKey::lock(KeyDerivationMethod::HKDF, secret, &content_key)
285                .unwrap();
286        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
287        assert!(result.is_err());
288
289        let encrypted = EncryptedKey::lock(
290            KeyDerivationMethod::PBKDF2,
291            secret,
292            &content_key,
293        )
294        .unwrap();
295        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
296        assert!(result.is_err());
297
298        let encrypted = EncryptedKey::lock(
299            KeyDerivationMethod::Scrypt,
300            secret,
301            &content_key,
302        )
303        .unwrap();
304        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
305        assert!(result.is_err());
306
307        let encrypted = EncryptedKey::lock(
308            KeyDerivationMethod::Argon2id,
309            secret,
310            &content_key,
311        )
312        .unwrap();
313        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
314        assert!(result.is_err());
315    }
316
317    #[test]
318    fn test_encrypted_key_params_variant() {
319        let secret = test_secret();
320        let content_key = test_content_key();
321
322        let hkdf =
323            EncryptedKey::lock(KeyDerivationMethod::HKDF, secret, &content_key)
324                .unwrap();
325        matches!(hkdf.params, KeyDerivationParams::HKDF(_));
326
327        let pbkdf2 = EncryptedKey::lock(
328            KeyDerivationMethod::PBKDF2,
329            secret,
330            &content_key,
331        )
332        .unwrap();
333        matches!(pbkdf2.params, KeyDerivationParams::PBKDF2(_));
334
335        let scrypt = EncryptedKey::lock(
336            KeyDerivationMethod::Scrypt,
337            secret,
338            &content_key,
339        )
340        .unwrap();
341        matches!(scrypt.params, KeyDerivationParams::Scrypt(_));
342
343        let argon2id = EncryptedKey::lock(
344            KeyDerivationMethod::Argon2id,
345            secret,
346            &content_key,
347        )
348        .unwrap();
349        matches!(argon2id.params, KeyDerivationParams::Argon2id(_));
350    }
351}