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
8#[cfg(feature = "ssh-agent")]
9use super::SSHAgentParams;
10use crate::{
11    Argon2idParams, EncryptedMessage, Error, HKDFParams, KeyDerivation,
12    KeyDerivationMethod, KeyDerivationParams, PBKDF2Params, Result,
13    ScryptParams, 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            #[cfg(feature = "ssh-agent")]
106            KeyDerivationMethod::SSHAgent => Self::lock_opt(
107                KeyDerivationParams::SSHAgent(SSHAgentParams::new()),
108                secret,
109                content_key,
110            ),
111        }
112    }
113
114    pub fn encrypted_message(&self) -> &EncryptedMessage {
115        &self.encrypted_message
116    }
117
118    pub fn aad_cbor(&self) -> Result<CBOR> {
119        self.encrypted_message().aad_cbor().ok_or_else(|| {
120            Error::general("Missing AAD CBOR in EncryptedMessage")
121        })
122    }
123
124    pub fn unlock(&self, secret: impl AsRef<[u8]>) -> Result<SymmetricKey> {
125        let encrypted_message = &self.encrypted_message();
126        let cbor = self.aad_cbor()?;
127        let array = cbor.clone().try_into_array()?;
128        let method = array
129            .first()
130            .ok_or_else(|| Error::general("Missing method"))?
131            .try_into()?;
132        match method {
133            KeyDerivationMethod::HKDF => {
134                let params = HKDFParams::try_from(cbor)?;
135                params.unlock(encrypted_message, secret)
136            }
137            KeyDerivationMethod::PBKDF2 => {
138                let params = PBKDF2Params::try_from(cbor)?;
139                params.unlock(encrypted_message, secret)
140            }
141            KeyDerivationMethod::Scrypt => {
142                let params = ScryptParams::try_from(cbor)?;
143                params.unlock(encrypted_message, secret)
144            }
145            KeyDerivationMethod::Argon2id => {
146                let params = Argon2idParams::try_from(cbor)?;
147                params.unlock(encrypted_message, secret)
148            }
149            #[cfg(feature = "ssh-agent")]
150            KeyDerivationMethod::SSHAgent => {
151                let params = SSHAgentParams::try_from(cbor)?;
152                params.unlock(encrypted_message, secret)
153            }
154        }
155    }
156
157    pub fn is_password_based(&self) -> bool { self.params.is_password_based() }
158
159    pub fn is_ssh_agent(&self) -> bool { self.params.is_ssh_agent() }
160}
161
162impl std::fmt::Display for EncryptedKey {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        write!(f, "EncryptedKey({})", self.params)
165    }
166}
167
168impl CBORTagged for EncryptedKey {
169    fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_ENCRYPTED_KEY]) }
170}
171
172impl From<EncryptedKey> for CBOR {
173    fn from(value: EncryptedKey) -> Self { value.tagged_cbor() }
174}
175
176impl CBORTaggedEncodable for EncryptedKey {
177    fn untagged_cbor(&self) -> CBOR { self.encrypted_message().clone().into() }
178}
179
180impl TryFrom<CBOR> for EncryptedKey {
181    type Error = dcbor::Error;
182
183    fn try_from(value: CBOR) -> dcbor::Result<Self> {
184        Self::from_tagged_cbor(value)
185    }
186}
187
188impl CBORTaggedDecodable for EncryptedKey {
189    fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
190        let encrypted_key: EncryptedMessage = untagged_cbor.try_into()?;
191        let params_cbor = CBOR::try_from_data(encrypted_key.aad())?;
192        let params = params_cbor.try_into()?;
193        Ok(Self { params, encrypted_message: encrypted_key })
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    fn test_secret() -> &'static [u8] { b"correct horse battery staple" }
202
203    fn test_content_key() -> SymmetricKey { SymmetricKey::new() }
204
205    #[test]
206    fn test_encrypted_key_hkdf_roundtrip() {
207        crate::register_tags();
208        let secret = test_secret();
209        let content_key = test_content_key();
210
211        let encrypted =
212            EncryptedKey::lock(KeyDerivationMethod::HKDF, secret, &content_key)
213                .unwrap();
214        assert_eq!(format!("{}", encrypted), "EncryptedKey(HKDF(SHA256))");
215        let cbor = encrypted.clone().to_cbor();
216        let encrypted2 = EncryptedKey::try_from(cbor).unwrap();
217        let decrypted = EncryptedKey::unlock(&encrypted2, secret).unwrap();
218
219        assert_eq!(content_key, decrypted);
220    }
221
222    #[test]
223    fn test_encrypted_key_pbkdf2_roundtrip() {
224        let secret = test_secret();
225        let content_key = test_content_key();
226
227        let encrypted = EncryptedKey::lock(
228            KeyDerivationMethod::PBKDF2,
229            secret,
230            &content_key,
231        )
232        .unwrap();
233        assert_eq!(format!("{}", encrypted), "EncryptedKey(PBKDF2(SHA256))");
234        let cbor = encrypted.clone().to_cbor();
235        let encrypted2 = EncryptedKey::try_from(cbor).unwrap();
236        let decrypted = EncryptedKey::unlock(&encrypted2, secret).unwrap();
237
238        assert_eq!(content_key, decrypted);
239    }
240
241    #[test]
242    fn test_encrypted_key_scrypt_roundtrip() {
243        let secret = test_secret();
244        let content_key = test_content_key();
245
246        let encrypted = EncryptedKey::lock(
247            KeyDerivationMethod::Scrypt,
248            secret,
249            &content_key,
250        )
251        .unwrap();
252        assert_eq!(format!("{}", encrypted), "EncryptedKey(Scrypt)");
253        let cbor = encrypted.clone().to_cbor();
254        let encrypted2 = EncryptedKey::try_from(cbor).unwrap();
255        let decrypted = EncryptedKey::unlock(&encrypted2, secret).unwrap();
256
257        assert_eq!(content_key, decrypted);
258    }
259
260    #[test]
261    fn test_encrypted_key_argon2id_roundtrip() {
262        let secret = test_secret();
263        let content_key = test_content_key();
264
265        let argon2id = EncryptedKey::lock(
266            KeyDerivationMethod::Argon2id,
267            secret,
268            &content_key,
269        )
270        .unwrap();
271        assert_eq!(format!("{}", argon2id), "EncryptedKey(Argon2id)");
272        let cbor = argon2id.clone().to_cbor();
273        let argon2id2 = EncryptedKey::try_from(cbor).unwrap();
274        let decrypted = EncryptedKey::unlock(&argon2id2, secret).unwrap();
275
276        assert_eq!(content_key, decrypted);
277    }
278
279    #[test]
280    fn test_encrypted_key_wrong_secret_fails() {
281        let secret = test_secret();
282        let wrong_secret = b"wrong secret";
283        let content_key = test_content_key();
284
285        let encrypted =
286            EncryptedKey::lock(KeyDerivationMethod::HKDF, secret, &content_key)
287                .unwrap();
288        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
289        assert!(result.is_err());
290
291        let encrypted = EncryptedKey::lock(
292            KeyDerivationMethod::PBKDF2,
293            secret,
294            &content_key,
295        )
296        .unwrap();
297        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
298        assert!(result.is_err());
299
300        let encrypted = EncryptedKey::lock(
301            KeyDerivationMethod::Scrypt,
302            secret,
303            &content_key,
304        )
305        .unwrap();
306        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
307        assert!(result.is_err());
308
309        let encrypted = EncryptedKey::lock(
310            KeyDerivationMethod::Argon2id,
311            secret,
312            &content_key,
313        )
314        .unwrap();
315        let result = EncryptedKey::unlock(&encrypted, wrong_secret);
316        assert!(result.is_err());
317    }
318
319    #[test]
320    fn test_encrypted_key_params_variant() {
321        let secret = test_secret();
322        let content_key = test_content_key();
323
324        let hkdf =
325            EncryptedKey::lock(KeyDerivationMethod::HKDF, secret, &content_key)
326                .unwrap();
327        matches!(hkdf.params, KeyDerivationParams::HKDF(_));
328
329        let pbkdf2 = EncryptedKey::lock(
330            KeyDerivationMethod::PBKDF2,
331            secret,
332            &content_key,
333        )
334        .unwrap();
335        matches!(pbkdf2.params, KeyDerivationParams::PBKDF2(_));
336
337        let scrypt = EncryptedKey::lock(
338            KeyDerivationMethod::Scrypt,
339            secret,
340            &content_key,
341        )
342        .unwrap();
343        matches!(scrypt.params, KeyDerivationParams::Scrypt(_));
344
345        let argon2id = EncryptedKey::lock(
346            KeyDerivationMethod::Argon2id,
347            secret,
348            &content_key,
349        )
350        .unwrap();
351        matches!(argon2id.params, KeyDerivationParams::Argon2id(_));
352    }
353}