1use std::{num::TryFromIntError, str::FromStr};
17
18use argon2::Params;
19use bitwarden_encoding::{B64, FromStrVisitor};
20use ciborium::{Value, value::Integer};
21use coset::{CborSerializable, CoseError, Header, HeaderBuilder};
22use rand::RngCore;
23use serde::{Deserialize, Serialize};
24use thiserror::Error;
25#[cfg(feature = "wasm")]
26use wasm_bindgen::convert::FromWasmAbi;
27
28use crate::{
29 BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CryptoError, EncodedSymmetricKey,
30 KEY_ID_SIZE, KeyIds, KeyStoreContext, SymmetricCryptoKey,
31 cose::{
32 ALG_ARGON2ID13, ARGON2_ITERATIONS, ARGON2_MEMORY, ARGON2_PARALLELISM, ARGON2_SALT,
33 CONTAINED_KEY_ID, ContentNamespace, CoseExtractError, SafeObjectNamespace, extract_bytes,
34 extract_integer,
35 },
36 keys::KeyId,
37 safe::helpers::{debug_fmt, set_safe_namespaces, validate_safe_namespaces},
38 xchacha20,
39};
40
41const ENVELOPE_ARGON2_SALT_SIZE: usize = 16;
44const ENVELOPE_ARGON2_OUTPUT_KEY_SIZE: usize = 32;
46
47#[derive(Clone)]
56pub struct PasswordProtectedKeyEnvelope {
57 cose_encrypt: coset::CoseEncrypt,
58}
59
60impl PasswordProtectedKeyEnvelope {
61 pub fn seal<Ids: KeyIds>(
66 key_to_seal: Ids::Symmetric,
67 password: &str,
68 namespace: PasswordProtectedKeyEnvelopeNamespace,
69 ctx: &KeyStoreContext<Ids>,
70 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
71 #[allow(deprecated)]
72 let key_ref = ctx
73 .dangerous_get_symmetric_key(key_to_seal)
74 .map_err(|_| PasswordProtectedKeyEnvelopeError::KeyMissing)?;
75 Self::seal_ref(key_ref, password, namespace)
76 }
77
78 fn seal_ref(
81 key_to_seal: &SymmetricCryptoKey,
82 password: &str,
83 namespace: PasswordProtectedKeyEnvelopeNamespace,
84 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
85 Self::seal_ref_with_settings(
86 key_to_seal,
87 password,
88 &Argon2RawSettings::local_kdf_settings(),
89 namespace,
90 )
91 }
92
93 fn seal_ref_with_settings(
97 key_to_seal: &SymmetricCryptoKey,
98 password: &str,
99 kdf_settings: &Argon2RawSettings,
100 namespace: PasswordProtectedKeyEnvelopeNamespace,
101 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
102 let envelope_key = derive_key(kdf_settings, password)
110 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
111
112 let (content_format, key_to_seal_bytes) = match key_to_seal.to_encoded_raw() {
113 EncodedSymmetricKey::BitwardenLegacyKey(key_bytes) => {
114 (ContentFormat::BitwardenLegacyKey, key_bytes.to_vec())
115 }
116 EncodedSymmetricKey::CoseKey(key_bytes) => (ContentFormat::CoseKey, key_bytes.to_vec()),
117 };
118
119 let mut nonce = [0u8; crate::xchacha20::NONCE_SIZE];
120
121 let mut cose_encrypt = coset::CoseEncryptBuilder::new()
125 .add_recipient({
126 let mut recipient = coset::CoseRecipientBuilder::new()
127 .unprotected(kdf_settings.into())
128 .build();
129 recipient.protected.header.alg = Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13));
130 recipient
131 })
132 .protected({
133 let mut hdr = HeaderBuilder::from(content_format);
134 if let Some(key_id) = key_to_seal.key_id() {
135 hdr = hdr.value(CONTAINED_KEY_ID, Value::from(Vec::from(&key_id)));
136 }
137 let mut header = hdr.build();
138 set_safe_namespaces(
139 &mut header,
140 SafeObjectNamespace::PasswordProtectedKeyEnvelope,
141 namespace,
142 );
143 header
144 })
145 .create_ciphertext(&key_to_seal_bytes, &[], |data, aad| {
146 let ciphertext = xchacha20::encrypt_xchacha20_poly1305(&envelope_key, data, aad);
147 nonce.copy_from_slice(&ciphertext.nonce());
148 ciphertext.encrypted_bytes().to_vec()
149 })
150 .build();
151 cose_encrypt.unprotected.iv = nonce.into();
152
153 Ok(PasswordProtectedKeyEnvelope { cose_encrypt })
154 }
155
156 pub fn unseal<Ids: KeyIds>(
159 &self,
160 password: &str,
161 namespace: PasswordProtectedKeyEnvelopeNamespace,
162 ctx: &mut KeyStoreContext<Ids>,
163 ) -> Result<Ids::Symmetric, PasswordProtectedKeyEnvelopeError> {
164 let key = self.unseal_ref(password, namespace)?;
165 Ok(ctx.add_local_symmetric_key(key))
166 }
167
168 fn unseal_ref(
169 &self,
170 password: &str,
171 content_namespace: PasswordProtectedKeyEnvelopeNamespace,
172 ) -> Result<SymmetricCryptoKey, PasswordProtectedKeyEnvelopeError> {
173 let recipient = self
176 .cose_encrypt
177 .recipients
178 .first()
179 .filter(|_| self.cose_encrypt.recipients.len() == 1)
180 .ok_or_else(|| {
181 PasswordProtectedKeyEnvelopeError::Parsing(
182 "Invalid number of recipients".to_string(),
183 )
184 })?;
185
186 if recipient.protected.header.alg != Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13)) {
187 return Err(PasswordProtectedKeyEnvelopeError::Parsing(
188 "Unknown or unsupported KDF algorithm".to_string(),
189 ));
190 }
191
192 validate_safe_namespaces(
193 &self.cose_encrypt.protected.header,
194 SafeObjectNamespace::PasswordProtectedKeyEnvelope,
195 content_namespace,
196 )
197 .map_err(|_| PasswordProtectedKeyEnvelopeError::InvalidNamespace)?;
198
199 let kdf_settings: Argon2RawSettings =
200 (&recipient.unprotected).try_into().map_err(|_| {
201 PasswordProtectedKeyEnvelopeError::Parsing(
202 "Invalid or missing KDF parameters".to_string(),
203 )
204 })?;
205 let envelope_key = derive_key(&kdf_settings, password)
206 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
207 let nonce: [u8; crate::xchacha20::NONCE_SIZE] = self
208 .cose_encrypt
209 .unprotected
210 .iv
211 .clone()
212 .try_into()
213 .map_err(|_| PasswordProtectedKeyEnvelopeError::Parsing("Invalid IV".to_string()))?;
214
215 let key_bytes = self
216 .cose_encrypt
217 .decrypt_ciphertext(
218 &[],
219 || CryptoError::MissingField("ciphertext"),
220 |data, aad| xchacha20::decrypt_xchacha20_poly1305(&nonce, &envelope_key, data, aad),
221 )
222 .map_err(|_| PasswordProtectedKeyEnvelopeError::WrongPassword)?;
225
226 SymmetricCryptoKey::try_from(
227 match ContentFormat::try_from(&self.cose_encrypt.protected.header).map_err(|_| {
228 PasswordProtectedKeyEnvelopeError::Parsing("Invalid content format".to_string())
229 })? {
230 ContentFormat::BitwardenLegacyKey => EncodedSymmetricKey::BitwardenLegacyKey(
231 BitwardenLegacyKeyBytes::from(key_bytes),
232 ),
233 ContentFormat::CoseKey => {
234 EncodedSymmetricKey::CoseKey(CoseKeyBytes::from(key_bytes))
235 }
236 _ => {
237 return Err(PasswordProtectedKeyEnvelopeError::Parsing(
238 "Unknown or unsupported content format".to_string(),
239 ));
240 }
241 },
242 )
243 .map_err(|_| PasswordProtectedKeyEnvelopeError::Parsing("Failed to decode key".to_string()))
244 }
245
246 pub fn reseal(
248 &self,
249 password: &str,
250 new_password: &str,
251 namespace: PasswordProtectedKeyEnvelopeNamespace,
252 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
253 let unsealed = self.unseal_ref(password, namespace)?;
254 Self::seal_ref(&unsealed, new_password, namespace)
255 }
256
257 pub fn contained_key_id(&self) -> Result<Option<KeyId>, PasswordProtectedKeyEnvelopeError> {
260 let key_id_bytes = extract_bytes(
261 &self.cose_encrypt.protected.header,
262 CONTAINED_KEY_ID,
263 "key id",
264 );
265
266 if let Ok(bytes) = key_id_bytes {
267 let key_id_array: [u8; KEY_ID_SIZE] = bytes.as_slice().try_into().map_err(|_| {
268 PasswordProtectedKeyEnvelopeError::Parsing("Invalid key id".to_string())
269 })?;
270 Ok(Some(KeyId::from(key_id_array)))
271 } else {
272 Ok(None)
273 }
274 }
275}
276
277impl From<&PasswordProtectedKeyEnvelope> for Vec<u8> {
278 fn from(val: &PasswordProtectedKeyEnvelope) -> Self {
279 val.cose_encrypt
280 .clone()
281 .to_vec()
282 .expect("Serialization to cose should not fail")
283 }
284}
285
286impl TryFrom<&Vec<u8>> for PasswordProtectedKeyEnvelope {
287 type Error = CoseError;
288
289 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
290 let cose_encrypt = coset::CoseEncrypt::from_slice(value)?;
291 Ok(PasswordProtectedKeyEnvelope { cose_encrypt })
292 }
293}
294
295impl std::fmt::Debug for PasswordProtectedKeyEnvelope {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 let mut s = f.debug_struct("PasswordProtectedKeyEnvelope");
298
299 if let Some(recipient) = self.cose_encrypt.recipients.first() {
300 let settings: Result<Argon2RawSettings, _> = (&recipient.unprotected).try_into();
301 if let Ok(settings) = settings {
302 s.field("argon2_iterations", &settings.iterations);
303 s.field("argon2_memory_kib", &settings.memory);
304 s.field("argon2_parallelism", &settings.parallelism);
305 }
306 }
307
308 debug_fmt::<PasswordProtectedKeyEnvelopeNamespace>(
309 &mut s,
310 &self.cose_encrypt.protected.header,
311 );
312
313 if let Ok(Some(key_id)) = self.contained_key_id() {
314 s.field("contained_key_id", &key_id);
315 }
316
317 s.finish()
318 }
319}
320
321impl FromStr for PasswordProtectedKeyEnvelope {
322 type Err = PasswordProtectedKeyEnvelopeError;
323
324 fn from_str(s: &str) -> Result<Self, Self::Err> {
325 let data = B64::try_from(s).map_err(|_| {
326 PasswordProtectedKeyEnvelopeError::Parsing(
327 "Invalid PasswordProtectedKeyEnvelope Base64 encoding".to_string(),
328 )
329 })?;
330 Self::try_from(&data.into_bytes()).map_err(|_| {
331 PasswordProtectedKeyEnvelopeError::Parsing(
332 "Failed to parse PasswordProtectedKeyEnvelope".to_string(),
333 )
334 })
335 }
336}
337
338impl From<PasswordProtectedKeyEnvelope> for String {
339 fn from(val: PasswordProtectedKeyEnvelope) -> Self {
340 let serialized: Vec<u8> = (&val).into();
341 B64::from(serialized).to_string()
342 }
343}
344
345impl<'de> Deserialize<'de> for PasswordProtectedKeyEnvelope {
346 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
347 where
348 D: serde::Deserializer<'de>,
349 {
350 deserializer.deserialize_str(FromStrVisitor::new())
351 }
352}
353
354impl Serialize for PasswordProtectedKeyEnvelope {
355 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
356 where
357 S: serde::Serializer,
358 {
359 let serialized: Vec<u8> = self.into();
360 serializer.serialize_str(&B64::from(serialized).to_string())
361 }
362}
363
364struct Argon2RawSettings {
369 iterations: u32,
370 memory: u32,
372 parallelism: u32,
373 salt: [u8; ENVELOPE_ARGON2_SALT_SIZE],
374}
375
376impl Argon2RawSettings {
377 fn local_kdf_settings() -> Self {
380 if cfg!(target_os = "ios") {
383 Self {
385 iterations: 6,
386 memory: 32 * 1024, parallelism: 4,
388 salt: make_salt(),
389 }
390 } else {
391 Self {
395 iterations: 3,
396 memory: 64 * 1024, parallelism: 4,
398 salt: make_salt(),
399 }
400 }
401 }
402}
403
404impl From<&Argon2RawSettings> for Header {
405 fn from(settings: &Argon2RawSettings) -> Header {
406 let builder = HeaderBuilder::new()
407 .value(ARGON2_ITERATIONS, Integer::from(settings.iterations).into())
408 .value(ARGON2_MEMORY, Integer::from(settings.memory).into())
409 .value(
410 ARGON2_PARALLELISM,
411 Integer::from(settings.parallelism).into(),
412 )
413 .value(ARGON2_SALT, Value::from(settings.salt.to_vec()));
414
415 let mut header = builder.build();
416 header.alg = Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13));
417 header
418 }
419}
420
421impl TryInto<Params> for &Argon2RawSettings {
422 type Error = PasswordProtectedKeyEnvelopeError;
423
424 fn try_into(self) -> Result<Params, PasswordProtectedKeyEnvelopeError> {
425 Params::new(
426 self.memory,
427 self.iterations,
428 self.parallelism,
429 Some(ENVELOPE_ARGON2_OUTPUT_KEY_SIZE),
430 )
431 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)
432 }
433}
434
435impl TryInto<Argon2RawSettings> for &Header {
436 type Error = PasswordProtectedKeyEnvelopeError;
437
438 fn try_into(self) -> Result<Argon2RawSettings, PasswordProtectedKeyEnvelopeError> {
439 Ok(Argon2RawSettings {
440 iterations: extract_integer(self, ARGON2_ITERATIONS, "iterations")?.try_into()?,
441 memory: extract_integer(self, ARGON2_MEMORY, "memory")?.try_into()?,
442 parallelism: extract_integer(self, ARGON2_PARALLELISM, "parallelism")?.try_into()?,
443 salt: extract_bytes(self, ARGON2_SALT, "salt")?
444 .try_into()
445 .map_err(|_| {
446 PasswordProtectedKeyEnvelopeError::Parsing("Invalid Argon2 salt".to_string())
447 })?,
448 })
449 }
450}
451
452fn make_salt() -> [u8; ENVELOPE_ARGON2_SALT_SIZE] {
453 let mut salt = [0u8; ENVELOPE_ARGON2_SALT_SIZE];
454 rand::thread_rng().fill_bytes(&mut salt);
455 salt
456}
457
458fn derive_key(
459 argon2_settings: &Argon2RawSettings,
460 password: &str,
461) -> Result<[u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE], PasswordProtectedKeyEnvelopeError> {
462 use argon2::*;
463
464 let mut hash = [0u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE];
465 Argon2::new(
466 Algorithm::Argon2id,
467 Version::V0x13,
468 argon2_settings.try_into()?,
469 )
470 .hash_password_into(password.as_bytes(), &argon2_settings.salt, &mut hash)
471 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
472
473 Ok(hash)
474}
475
476#[derive(Debug, Error)]
478pub enum PasswordProtectedKeyEnvelopeError {
479 #[error("Wrong password")]
481 WrongPassword,
482 #[error("Parsing error {0}")]
484 Parsing(String),
485 #[error("Kdf error")]
488 Kdf,
489 #[error("Key missing error")]
491 KeyMissing,
492 #[error("Could not write to key store")]
494 KeyStore,
495 #[error("Invalid namespace")]
497 InvalidNamespace,
498}
499
500impl From<CoseExtractError> for PasswordProtectedKeyEnvelopeError {
501 fn from(err: CoseExtractError) -> Self {
502 let CoseExtractError::MissingValue(label) = err;
503 PasswordProtectedKeyEnvelopeError::Parsing(format!("Missing value for {}", label))
504 }
505}
506
507impl From<TryFromIntError> for PasswordProtectedKeyEnvelopeError {
508 fn from(err: TryFromIntError) -> Self {
509 PasswordProtectedKeyEnvelopeError::Parsing(format!("Invalid integer: {}", err))
510 }
511}
512
513#[cfg(feature = "wasm")]
514#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
515const TS_CUSTOM_TYPES: &'static str = r#"
516export type PasswordProtectedKeyEnvelope = Tagged<string, "PasswordProtectedKeyEnvelope">;
517"#;
518
519#[cfg(feature = "wasm")]
520impl wasm_bindgen::describe::WasmDescribe for PasswordProtectedKeyEnvelope {
521 fn describe() {
522 <String as wasm_bindgen::describe::WasmDescribe>::describe();
523 }
524}
525
526#[cfg(feature = "wasm")]
527impl FromWasmAbi for PasswordProtectedKeyEnvelope {
528 type Abi = <String as FromWasmAbi>::Abi;
529
530 unsafe fn from_abi(abi: Self::Abi) -> Self {
531 use wasm_bindgen::UnwrapThrowExt;
532 let string = unsafe { String::from_abi(abi) };
533 PasswordProtectedKeyEnvelope::from_str(&string).unwrap_throw()
534 }
535}
536
537#[derive(Debug, Clone, Copy, PartialEq, Eq)]
539pub enum PasswordProtectedKeyEnvelopeNamespace {
540 PinUnlock = 1,
542 #[cfg(test)]
544 ExampleNamespace = -1,
545 #[cfg(test)]
547 ExampleNamespace2 = -2,
548}
549
550impl PasswordProtectedKeyEnvelopeNamespace {
551 fn as_i64(&self) -> i64 {
553 *self as i64
554 }
555}
556
557impl TryFrom<i128> for PasswordProtectedKeyEnvelopeNamespace {
558 type Error = PasswordProtectedKeyEnvelopeError;
559
560 fn try_from(value: i128) -> Result<Self, Self::Error> {
561 match value {
562 1 => Ok(PasswordProtectedKeyEnvelopeNamespace::PinUnlock),
563 #[cfg(test)]
564 -1 => Ok(PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace),
565 #[cfg(test)]
566 -2 => Ok(PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace2),
567 _ => Err(PasswordProtectedKeyEnvelopeError::InvalidNamespace),
568 }
569 }
570}
571
572impl TryFrom<i64> for PasswordProtectedKeyEnvelopeNamespace {
573 type Error = PasswordProtectedKeyEnvelopeError;
574
575 fn try_from(value: i64) -> Result<Self, Self::Error> {
576 Self::try_from(i128::from(value))
577 }
578}
579
580impl From<PasswordProtectedKeyEnvelopeNamespace> for i128 {
581 fn from(val: PasswordProtectedKeyEnvelopeNamespace) -> Self {
582 val.as_i64().into()
583 }
584}
585
586impl ContentNamespace for PasswordProtectedKeyEnvelopeNamespace {}
587
588#[cfg(test)]
589mod tests {
590 use super::*;
591 use crate::{KeyStore, SymmetricKeyAlgorithm, traits::tests::TestIds};
592
593 const TEST_UNSEALED_COSEKEY_ENCODED: &[u8] = &[
594 165, 1, 4, 2, 80, 63, 208, 189, 183, 204, 37, 72, 170, 179, 236, 190, 208, 22, 65, 227,
595 183, 3, 58, 0, 1, 17, 111, 4, 132, 3, 4, 5, 6, 32, 88, 32, 88, 25, 68, 85, 205, 28, 133,
596 28, 90, 147, 160, 145, 48, 3, 178, 184, 30, 11, 122, 132, 64, 59, 51, 233, 191, 117, 159,
597 117, 23, 168, 248, 36, 1,
598 ];
599 const TESTVECTOR_COSEKEY_ENVELOPE: &[u8] = &[
600 132, 68, 161, 3, 24, 101, 161, 5, 88, 24, 1, 31, 58, 230, 10, 92, 195, 233, 212, 7, 166,
601 252, 67, 115, 221, 58, 3, 191, 218, 188, 181, 192, 28, 11, 88, 84, 141, 183, 137, 167, 166,
602 161, 33, 82, 30, 255, 23, 10, 179, 149, 88, 24, 39, 60, 74, 232, 133, 44, 90, 98, 117, 31,
603 41, 69, 251, 76, 250, 141, 229, 83, 191, 6, 237, 107, 127, 93, 238, 110, 49, 125, 201, 37,
604 162, 120, 157, 32, 116, 195, 208, 143, 83, 254, 223, 93, 97, 158, 0, 24, 95, 197, 249, 35,
605 240, 3, 20, 71, 164, 97, 180, 29, 203, 69, 31, 151, 249, 244, 197, 91, 101, 174, 129, 131,
606 71, 161, 1, 58, 0, 1, 21, 87, 165, 1, 58, 0, 1, 21, 87, 58, 0, 1, 21, 89, 3, 58, 0, 1, 21,
607 90, 26, 0, 1, 0, 0, 58, 0, 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 165, 253, 56, 243, 255, 54,
608 246, 252, 231, 230, 33, 252, 49, 175, 1, 111, 246,
609 ];
610 const TEST_UNSEALED_LEGACYKEY_ENCODED: &[u8] = &[
611 135, 114, 97, 155, 115, 209, 215, 224, 175, 159, 231, 208, 15, 244, 40, 171, 239, 137, 57,
612 98, 207, 167, 231, 138, 145, 254, 28, 136, 236, 60, 23, 163, 4, 246, 219, 117, 104, 246,
613 86, 10, 152, 52, 90, 85, 58, 6, 70, 39, 111, 128, 93, 145, 143, 180, 77, 129, 178, 242, 82,
614 72, 57, 61, 192, 64,
615 ];
616 const TESTVECTOR_LEGACYKEY_ENVELOPE: &[u8] = &[
617 132, 88, 38, 161, 3, 120, 34, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 120,
618 46, 98, 105, 116, 119, 97, 114, 100, 101, 110, 46, 108, 101, 103, 97, 99, 121, 45, 107,
619 101, 121, 161, 5, 88, 24, 218, 72, 22, 79, 149, 30, 12, 36, 180, 212, 44, 21, 167, 208,
620 214, 221, 7, 91, 178, 12, 104, 17, 45, 219, 88, 80, 114, 38, 14, 165, 85, 229, 103, 108,
621 17, 175, 41, 43, 203, 175, 119, 125, 227, 127, 163, 214, 213, 138, 12, 216, 163, 204, 38,
622 222, 47, 11, 44, 231, 239, 170, 63, 8, 249, 56, 102, 18, 134, 34, 232, 193, 44, 19, 228,
623 17, 187, 199, 238, 187, 2, 13, 30, 112, 103, 110, 5, 31, 238, 58, 4, 24, 19, 239, 135, 57,
624 206, 190, 144, 83, 128, 204, 59, 155, 21, 80, 180, 34, 129, 131, 71, 161, 1, 58, 0, 1, 21,
625 87, 165, 1, 58, 0, 1, 21, 87, 58, 0, 1, 21, 89, 3, 58, 0, 1, 21, 90, 26, 0, 1, 0, 0, 58, 0,
626 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 212, 91, 185, 112, 92, 177, 108, 33, 182, 202, 26, 141,
627 11, 133, 95, 235, 246,
628 ];
629
630 const TESTVECTOR_PASSWORD: &str = "test_password";
631
632 #[test]
633 #[ignore = "Manual test to verify debug format"]
634 fn test_debug() {
635 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
636 let envelope = PasswordProtectedKeyEnvelope::seal_ref(
637 &key,
638 "test_password",
639 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
640 )
641 .unwrap();
642 println!("{:?}", envelope);
643 }
644
645 #[test]
646 fn test_testvector_cosekey() {
647 let key_store = KeyStore::<TestIds>::default();
648 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
649 let envelope =
650 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_COSEKEY_ENVELOPE.to_vec())
651 .expect("Key envelope should be valid");
652 let key = envelope
653 .unseal(
654 TESTVECTOR_PASSWORD,
655 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
656 &mut ctx,
657 )
658 .expect("Unsealing should succeed");
659 #[allow(deprecated)]
660 let unsealed_key = ctx
661 .dangerous_get_symmetric_key(key)
662 .expect("Key should exist in the key store");
663 assert_eq!(
664 unsealed_key.to_encoded().to_vec(),
665 TEST_UNSEALED_COSEKEY_ENCODED
666 );
667 }
668
669 #[test]
670 fn test_testvector_legacykey() {
671 let key_store = KeyStore::<TestIds>::default();
672 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
673 let envelope =
674 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_LEGACYKEY_ENVELOPE.to_vec())
675 .expect("Key envelope should be valid");
676 let key = envelope
677 .unseal(
678 TESTVECTOR_PASSWORD,
679 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
680 &mut ctx,
681 )
682 .expect("Unsealing should succeed");
683 #[allow(deprecated)]
684 let unsealed_key = ctx
685 .dangerous_get_symmetric_key(key)
686 .expect("Key should exist in the key store");
687 assert_eq!(
688 unsealed_key.to_encoded().to_vec(),
689 TEST_UNSEALED_LEGACYKEY_ENCODED
690 );
691 }
692
693 #[test]
694 fn test_make_envelope() {
695 let key_store = KeyStore::<TestIds>::default();
696 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
697 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
698
699 let password = "test_password";
700
701 let envelope = PasswordProtectedKeyEnvelope::seal(
703 test_key,
704 password,
705 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
706 &ctx,
707 )
708 .unwrap();
709 let serialized: Vec<u8> = (&envelope).into();
710
711 let deserialized: PasswordProtectedKeyEnvelope =
713 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
714 let key = deserialized
715 .unseal(
716 password,
717 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
718 &mut ctx,
719 )
720 .unwrap();
721
722 #[allow(deprecated)]
724 let unsealed_key = ctx
725 .dangerous_get_symmetric_key(key)
726 .expect("Key should exist in the key store");
727
728 #[allow(deprecated)]
729 let key_before_sealing = ctx
730 .dangerous_get_symmetric_key(test_key)
731 .expect("Key should exist in the key store");
732
733 assert_eq!(unsealed_key, key_before_sealing);
734 }
735
736 #[test]
737 fn test_make_envelope_legacy_key() {
738 let key_store = KeyStore::<TestIds>::default();
739 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
740 let test_key = ctx.generate_symmetric_key();
741
742 let password = "test_password";
743
744 let envelope = PasswordProtectedKeyEnvelope::seal(
746 test_key,
747 password,
748 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
749 &ctx,
750 )
751 .unwrap();
752 let serialized: Vec<u8> = (&envelope).into();
753
754 let deserialized: PasswordProtectedKeyEnvelope =
756 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
757 let key = deserialized
758 .unseal(
759 password,
760 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
761 &mut ctx,
762 )
763 .unwrap();
764
765 #[allow(deprecated)]
767 let unsealed_key = ctx
768 .dangerous_get_symmetric_key(key)
769 .expect("Key should exist in the key store");
770
771 #[allow(deprecated)]
772 let key_before_sealing = ctx
773 .dangerous_get_symmetric_key(test_key)
774 .expect("Key should exist in the key store");
775
776 assert_eq!(unsealed_key, key_before_sealing);
777 }
778
779 #[test]
780 fn test_reseal_envelope() {
781 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
782 let password = "test_password";
783 let new_password = "new_test_password";
784
785 let envelope: PasswordProtectedKeyEnvelope = PasswordProtectedKeyEnvelope::seal_ref(
787 &key,
788 password,
789 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
790 )
791 .expect("Sealing should work");
792
793 let envelope = envelope
795 .reseal(
796 password,
797 new_password,
798 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
799 )
800 .expect("Resealing should work");
801 let unsealed = envelope
802 .unseal_ref(
803 new_password,
804 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
805 )
806 .expect("Unsealing should work");
807
808 assert_eq!(unsealed, key);
810 }
811
812 #[test]
813 fn test_wrong_password() {
814 let key_store = KeyStore::<TestIds>::default();
815 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
816 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
817
818 let password = "test_password";
819 let wrong_password = "wrong_password";
820
821 let envelope = PasswordProtectedKeyEnvelope::seal(
823 test_key,
824 password,
825 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
826 &ctx,
827 )
828 .unwrap();
829
830 let deserialized: PasswordProtectedKeyEnvelope =
832 PasswordProtectedKeyEnvelope::try_from(&(&envelope).into()).unwrap();
833 assert!(matches!(
834 deserialized.unseal(
835 wrong_password,
836 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
837 &mut ctx
838 ),
839 Err(PasswordProtectedKeyEnvelopeError::WrongPassword)
840 ));
841 }
842
843 #[test]
844 fn test_wrong_safe_namespace() {
845 let key_store = KeyStore::<TestIds>::default();
846 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
847 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
848 let password = "test_password";
849
850 let mut envelope = PasswordProtectedKeyEnvelope::seal(
851 test_key,
852 password,
853 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
854 &ctx,
855 )
856 .expect("Seal works");
857
858 if let Some((_, value)) = envelope
859 .cose_encrypt
860 .protected
861 .header
862 .rest
863 .iter_mut()
864 .find(|(label, _)| {
865 matches!(label, coset::Label::Int(label_value) if *label_value == crate::cose::SAFE_OBJECT_NAMESPACE)
866 })
867 {
868 *value = Value::Integer((SafeObjectNamespace::DataEnvelope as i64).into());
869 }
870
871 let deserialized: PasswordProtectedKeyEnvelope =
872 PasswordProtectedKeyEnvelope::try_from(&(&envelope).into())
873 .expect("Envelope should be valid");
874
875 let a = deserialized.unseal(
876 password,
877 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
878 &mut ctx,
879 );
880 println!("Error: {a:?}");
881 assert!(matches!(
882 a,
883 Err(PasswordProtectedKeyEnvelopeError::InvalidNamespace)
884 ));
885 }
886
887 #[test]
888 fn test_key_id() {
889 let key_store = KeyStore::<TestIds>::default();
890 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
891 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
892 #[allow(deprecated)]
893 let key_id = ctx
894 .dangerous_get_symmetric_key(test_key)
895 .unwrap()
896 .key_id()
897 .unwrap();
898
899 let password = "test_password";
900
901 let envelope = PasswordProtectedKeyEnvelope::seal(
903 test_key,
904 password,
905 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
906 &ctx,
907 )
908 .unwrap();
909 let contained_key_id = envelope.contained_key_id().unwrap();
910 assert_eq!(Some(key_id), contained_key_id);
911 }
912
913 #[test]
914 fn test_no_key_id() {
915 let key_store = KeyStore::<TestIds>::default();
916 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
917 let test_key = ctx.generate_symmetric_key();
918
919 let password = "test_password";
920
921 let envelope = PasswordProtectedKeyEnvelope::seal(
923 test_key,
924 password,
925 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
926 &ctx,
927 )
928 .unwrap();
929 let contained_key_id = envelope.contained_key_id().unwrap();
930 assert_eq!(None, contained_key_id);
931 }
932}