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            .get(0)
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 {
156        self.params.is_password_based()
157    }
158
159    pub fn is_ssh_agent(&self) -> bool {
160        self.params.is_ssh_agent()
161    }
162}
163
164impl std::fmt::Display for EncryptedKey {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        write!(f, "EncryptedKey({})", self.params)
167    }
168}
169
170impl CBORTagged for EncryptedKey {
171    fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_ENCRYPTED_KEY]) }
172}
173
174impl From<EncryptedKey> for CBOR {
175    fn from(value: EncryptedKey) -> Self { value.tagged_cbor() }
176}
177
178impl CBORTaggedEncodable for EncryptedKey {
179    fn untagged_cbor(&self) -> CBOR {
180        return self.encrypted_message().clone().into();
181    }
182}
183
184impl TryFrom<CBOR> for EncryptedKey {
185    type Error = dcbor::Error;
186
187    fn try_from(value: CBOR) -> dcbor::Result<Self> {
188        Self::from_tagged_cbor(value)
189    }
190}
191
192impl CBORTaggedDecodable for EncryptedKey {
193    fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
194        let encrypted_key: EncryptedMessage = untagged_cbor.try_into()?;
195        let params_cbor = CBOR::try_from_data(encrypted_key.aad())?;
196        let params = params_cbor.try_into()?;
197        Ok(Self { params, encrypted_message: encrypted_key })
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    fn test_secret() -> &'static [u8] { b"correct horse battery staple" }
206
207    fn test_content_key() -> SymmetricKey { SymmetricKey::new() }
208
209    #[test]
210    fn test_encrypted_key_hkdf_roundtrip() {
211        crate::register_tags();
212        let secret = test_secret();
213        let content_key = test_content_key();
214
215        let encrypted =
216            EncryptedKey::lock(KeyDerivationMethod::HKDF, secret, &content_key)
217                .unwrap();
218        assert_eq!(format!("{}", encrypted), "EncryptedKey(HKDF(SHA256))");
219        let cbor = encrypted.clone().to_cbor();
220        let encrypted2 = EncryptedKey::try_from(cbor).unwrap();
221        let decrypted = EncryptedKey::unlock(&encrypted2, secret).unwrap();
222
223        assert_eq!(content_key, decrypted);
224    }
225
226    #[test]
227    fn test_encrypted_key_pbkdf2_roundtrip() {
228        let secret = test_secret();
229        let content_key = test_content_key();
230
231        let encrypted = EncryptedKey::lock(
232            KeyDerivationMethod::PBKDF2,
233            secret,
234            &content_key,
235        )
236        .unwrap();
237        assert_eq!(format!("{}", encrypted), "EncryptedKey(PBKDF2(SHA256))");
238        let cbor = encrypted.clone().to_cbor();
239        let encrypted2 = EncryptedKey::try_from(cbor).unwrap();
240        let decrypted = EncryptedKey::unlock(&encrypted2, secret).unwrap();
241
242        assert_eq!(content_key, decrypted);
243    }
244
245    #[test]
246    fn test_encrypted_key_scrypt_roundtrip() {
247        let secret = test_secret();
248        let content_key = test_content_key();
249
250        let encrypted = EncryptedKey::lock(
251            KeyDerivationMethod::Scrypt,
252            secret,
253            &content_key,
254        )
255        .unwrap();
256        assert_eq!(format!("{}", encrypted), "EncryptedKey(Scrypt)");
257        let cbor = encrypted.clone().to_cbor();
258        let encrypted2 = EncryptedKey::try_from(cbor).unwrap();
259        let decrypted = EncryptedKey::unlock(&encrypted2, secret).unwrap();
260
261        assert_eq!(content_key, decrypted);
262    }
263
264    #[test]
265    fn test_encrypted_key_argon2id_roundtrip() {
266        let secret = test_secret();
267        let content_key = test_content_key();
268
269        let argon2id = EncryptedKey::lock(
270            KeyDerivationMethod::Argon2id,
271            secret,
272            &content_key,
273        )
274        .unwrap();
275        assert_eq!(format!("{}", argon2id), "EncryptedKey(Argon2id)");
276        let cbor = argon2id.clone().to_cbor();
277        let argon2id2 = EncryptedKey::try_from(cbor).unwrap();
278        let decrypted = EncryptedKey::unlock(&argon2id2, secret).unwrap();
279
280        assert_eq!(content_key, decrypted);
281    }
282
283    #[test]
284    fn test_encrypted_key_wrong_secret_fails() {
285        let secret = test_secret();
286        let wrong_secret = b"wrong secret";
287        let content_key = test_content_key();
288
289        let encrypted =
290            EncryptedKey::lock(KeyDerivationMethod::HKDF, secret, &content_key)
291                .unwrap();
292        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
293        assert!(result.is_err());
294
295        let encrypted = EncryptedKey::lock(
296            KeyDerivationMethod::PBKDF2,
297            secret,
298            &content_key,
299        )
300        .unwrap();
301        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
302        assert!(result.is_err());
303
304        let encrypted = EncryptedKey::lock(
305            KeyDerivationMethod::Scrypt,
306            secret,
307            &content_key,
308        )
309        .unwrap();
310        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
311        assert!(result.is_err());
312
313        let encrypted = EncryptedKey::lock(
314            KeyDerivationMethod::Argon2id,
315            secret,
316            &content_key,
317        )
318        .unwrap();
319        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
320        assert!(result.is_err());
321    }
322
323    #[test]
324    fn test_encrypted_key_params_variant() {
325        let secret = test_secret();
326        let content_key = test_content_key();
327
328        let hkdf =
329            EncryptedKey::lock(KeyDerivationMethod::HKDF, secret, &content_key)
330                .unwrap();
331        matches!(hkdf.params, KeyDerivationParams::HKDF(_));
332
333        let pbkdf2 = EncryptedKey::lock(
334            KeyDerivationMethod::PBKDF2,
335            secret,
336            &content_key,
337        )
338        .unwrap();
339        matches!(pbkdf2.params, KeyDerivationParams::PBKDF2(_));
340
341        let scrypt = EncryptedKey::lock(
342            KeyDerivationMethod::Scrypt,
343            secret,
344            &content_key,
345        )
346        .unwrap();
347        matches!(scrypt.params, KeyDerivationParams::Scrypt(_));
348
349        let argon2id = EncryptedKey::lock(
350            KeyDerivationMethod::Argon2id,
351            secret,
352            &content_key,
353        )
354        .unwrap();
355        matches!(argon2id.params, KeyDerivationParams::Argon2id(_));
356    }
357}