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