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, CoseExtractError, extract_bytes, extract_integer,
34 },
35 keys::KeyId,
36 xchacha20,
37};
38
39const ENVELOPE_ARGON2_SALT_SIZE: usize = 16;
42const ENVELOPE_ARGON2_OUTPUT_KEY_SIZE: usize = 32;
44
45pub struct PasswordProtectedKeyEnvelope {
54 cose_encrypt: coset::CoseEncrypt,
55}
56
57impl PasswordProtectedKeyEnvelope {
58 pub fn seal<Ids: KeyIds>(
63 key_to_seal: Ids::Symmetric,
64 password: &str,
65 ctx: &KeyStoreContext<Ids>,
66 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
67 #[allow(deprecated)]
68 let key_ref = ctx
69 .dangerous_get_symmetric_key(key_to_seal)
70 .map_err(|_| PasswordProtectedKeyEnvelopeError::KeyMissing)?;
71 Self::seal_ref(key_ref, password)
72 }
73
74 fn seal_ref(
77 key_to_seal: &SymmetricCryptoKey,
78 password: &str,
79 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
80 Self::seal_ref_with_settings(
81 key_to_seal,
82 password,
83 &Argon2RawSettings::local_kdf_settings(),
84 )
85 }
86
87 fn seal_ref_with_settings(
91 key_to_seal: &SymmetricCryptoKey,
92 password: &str,
93 kdf_settings: &Argon2RawSettings,
94 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
95 let envelope_key = derive_key(kdf_settings, password)
103 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
104
105 let (content_format, key_to_seal_bytes) = match key_to_seal.to_encoded_raw() {
106 EncodedSymmetricKey::BitwardenLegacyKey(key_bytes) => {
107 (ContentFormat::BitwardenLegacyKey, key_bytes.to_vec())
108 }
109 EncodedSymmetricKey::CoseKey(key_bytes) => (ContentFormat::CoseKey, key_bytes.to_vec()),
110 };
111
112 let mut nonce = [0u8; crate::xchacha20::NONCE_SIZE];
113
114 let mut cose_encrypt = coset::CoseEncryptBuilder::new()
118 .add_recipient({
119 let mut recipient = coset::CoseRecipientBuilder::new()
120 .unprotected(kdf_settings.into())
121 .build();
122 recipient.protected.header.alg = Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13));
123 recipient
124 })
125 .protected({
126 let mut hdr = HeaderBuilder::from(content_format);
127 if let Some(key_id) = key_to_seal.key_id() {
128 hdr = hdr.value(CONTAINED_KEY_ID, Value::from(Vec::from(&key_id)));
129 }
130 hdr.build()
131 })
132 .create_ciphertext(&key_to_seal_bytes, &[], |data, aad| {
133 let ciphertext = xchacha20::encrypt_xchacha20_poly1305(&envelope_key, data, aad);
134 nonce.copy_from_slice(&ciphertext.nonce());
135 ciphertext.encrypted_bytes().to_vec()
136 })
137 .build();
138 cose_encrypt.unprotected.iv = nonce.into();
139
140 Ok(PasswordProtectedKeyEnvelope { cose_encrypt })
141 }
142
143 pub fn unseal<Ids: KeyIds>(
146 &self,
147 password: &str,
148 ctx: &mut KeyStoreContext<Ids>,
149 ) -> Result<Ids::Symmetric, PasswordProtectedKeyEnvelopeError> {
150 let key = self.unseal_ref(password)?;
151 Ok(ctx.add_local_symmetric_key(key))
152 }
153
154 fn unseal_ref(
155 &self,
156 password: &str,
157 ) -> Result<SymmetricCryptoKey, PasswordProtectedKeyEnvelopeError> {
158 let recipient = self
161 .cose_encrypt
162 .recipients
163 .first()
164 .filter(|_| self.cose_encrypt.recipients.len() == 1)
165 .ok_or_else(|| {
166 PasswordProtectedKeyEnvelopeError::Parsing(
167 "Invalid number of recipients".to_string(),
168 )
169 })?;
170
171 if recipient.protected.header.alg != Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13)) {
172 return Err(PasswordProtectedKeyEnvelopeError::Parsing(
173 "Unknown or unsupported KDF algorithm".to_string(),
174 ));
175 }
176
177 let kdf_settings: Argon2RawSettings =
178 (&recipient.unprotected).try_into().map_err(|_| {
179 PasswordProtectedKeyEnvelopeError::Parsing(
180 "Invalid or missing KDF parameters".to_string(),
181 )
182 })?;
183 let envelope_key = derive_key(&kdf_settings, password)
184 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
185 let nonce: [u8; crate::xchacha20::NONCE_SIZE] = self
186 .cose_encrypt
187 .unprotected
188 .iv
189 .clone()
190 .try_into()
191 .map_err(|_| PasswordProtectedKeyEnvelopeError::Parsing("Invalid IV".to_string()))?;
192
193 let key_bytes = self
194 .cose_encrypt
195 .decrypt_ciphertext(
196 &[],
197 || CryptoError::MissingField("ciphertext"),
198 |data, aad| xchacha20::decrypt_xchacha20_poly1305(&nonce, &envelope_key, data, aad),
199 )
200 .map_err(|_| PasswordProtectedKeyEnvelopeError::WrongPassword)?;
203
204 SymmetricCryptoKey::try_from(
205 match ContentFormat::try_from(&self.cose_encrypt.protected.header).map_err(|_| {
206 PasswordProtectedKeyEnvelopeError::Parsing("Invalid content format".to_string())
207 })? {
208 ContentFormat::BitwardenLegacyKey => EncodedSymmetricKey::BitwardenLegacyKey(
209 BitwardenLegacyKeyBytes::from(key_bytes),
210 ),
211 ContentFormat::CoseKey => {
212 EncodedSymmetricKey::CoseKey(CoseKeyBytes::from(key_bytes))
213 }
214 _ => {
215 return Err(PasswordProtectedKeyEnvelopeError::Parsing(
216 "Unknown or unsupported content format".to_string(),
217 ));
218 }
219 },
220 )
221 .map_err(|_| PasswordProtectedKeyEnvelopeError::Parsing("Failed to decode key".to_string()))
222 }
223
224 pub fn reseal(
226 &self,
227 password: &str,
228 new_password: &str,
229 ) -> Result<Self, PasswordProtectedKeyEnvelopeError> {
230 let unsealed = self.unseal_ref(password)?;
231 Self::seal_ref(&unsealed, new_password)
232 }
233
234 pub fn contained_key_id(&self) -> Result<Option<KeyId>, PasswordProtectedKeyEnvelopeError> {
237 let key_id_bytes = extract_bytes(
238 &self.cose_encrypt.protected.header,
239 CONTAINED_KEY_ID,
240 "key id",
241 );
242
243 if let Ok(bytes) = key_id_bytes {
244 let key_id_array: [u8; KEY_ID_SIZE] = bytes.as_slice().try_into().map_err(|_| {
245 PasswordProtectedKeyEnvelopeError::Parsing("Invalid key id".to_string())
246 })?;
247 Ok(Some(KeyId::from(key_id_array)))
248 } else {
249 Ok(None)
250 }
251 }
252}
253
254impl From<&PasswordProtectedKeyEnvelope> for Vec<u8> {
255 fn from(val: &PasswordProtectedKeyEnvelope) -> Self {
256 val.cose_encrypt
257 .clone()
258 .to_vec()
259 .expect("Serialization to cose should not fail")
260 }
261}
262
263impl TryFrom<&Vec<u8>> for PasswordProtectedKeyEnvelope {
264 type Error = CoseError;
265
266 fn try_from(value: &Vec<u8>) -> Result<Self, Self::Error> {
267 let cose_encrypt = coset::CoseEncrypt::from_slice(value)?;
268 Ok(PasswordProtectedKeyEnvelope { cose_encrypt })
269 }
270}
271
272impl std::fmt::Debug for PasswordProtectedKeyEnvelope {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 f.debug_struct("PasswordProtectedKeyEnvelope")
275 .field("cose_encrypt", &self.cose_encrypt)
276 .finish()
277 }
278}
279
280impl FromStr for PasswordProtectedKeyEnvelope {
281 type Err = PasswordProtectedKeyEnvelopeError;
282
283 fn from_str(s: &str) -> Result<Self, Self::Err> {
284 let data = B64::try_from(s).map_err(|_| {
285 PasswordProtectedKeyEnvelopeError::Parsing(
286 "Invalid PasswordProtectedKeyEnvelope Base64 encoding".to_string(),
287 )
288 })?;
289 Self::try_from(&data.into_bytes()).map_err(|_| {
290 PasswordProtectedKeyEnvelopeError::Parsing(
291 "Failed to parse PasswordProtectedKeyEnvelope".to_string(),
292 )
293 })
294 }
295}
296
297impl From<PasswordProtectedKeyEnvelope> for String {
298 fn from(val: PasswordProtectedKeyEnvelope) -> Self {
299 let serialized: Vec<u8> = (&val).into();
300 B64::from(serialized).to_string()
301 }
302}
303
304impl<'de> Deserialize<'de> for PasswordProtectedKeyEnvelope {
305 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
306 where
307 D: serde::Deserializer<'de>,
308 {
309 deserializer.deserialize_str(FromStrVisitor::new())
310 }
311}
312
313impl Serialize for PasswordProtectedKeyEnvelope {
314 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
315 where
316 S: serde::Serializer,
317 {
318 let serialized: Vec<u8> = self.into();
319 serializer.serialize_str(&B64::from(serialized).to_string())
320 }
321}
322
323struct Argon2RawSettings {
328 iterations: u32,
329 memory: u32,
331 parallelism: u32,
332 salt: [u8; ENVELOPE_ARGON2_SALT_SIZE],
333}
334
335impl Argon2RawSettings {
336 fn local_kdf_settings() -> Self {
339 if cfg!(target_os = "ios") {
342 Self {
344 iterations: 6,
345 memory: 32 * 1024, parallelism: 4,
347 salt: make_salt(),
348 }
349 } else {
350 Self {
354 iterations: 3,
355 memory: 64 * 1024, parallelism: 4,
357 salt: make_salt(),
358 }
359 }
360 }
361}
362
363impl From<&Argon2RawSettings> for Header {
364 fn from(settings: &Argon2RawSettings) -> Header {
365 let builder = HeaderBuilder::new()
366 .value(ARGON2_ITERATIONS, Integer::from(settings.iterations).into())
367 .value(ARGON2_MEMORY, Integer::from(settings.memory).into())
368 .value(
369 ARGON2_PARALLELISM,
370 Integer::from(settings.parallelism).into(),
371 )
372 .value(ARGON2_SALT, Value::from(settings.salt.to_vec()));
373
374 let mut header = builder.build();
375 header.alg = Some(coset::Algorithm::PrivateUse(ALG_ARGON2ID13));
376 header
377 }
378}
379
380impl TryInto<Params> for &Argon2RawSettings {
381 type Error = PasswordProtectedKeyEnvelopeError;
382
383 fn try_into(self) -> Result<Params, PasswordProtectedKeyEnvelopeError> {
384 Params::new(
385 self.memory,
386 self.iterations,
387 self.parallelism,
388 Some(ENVELOPE_ARGON2_OUTPUT_KEY_SIZE),
389 )
390 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)
391 }
392}
393
394impl TryInto<Argon2RawSettings> for &Header {
395 type Error = PasswordProtectedKeyEnvelopeError;
396
397 fn try_into(self) -> Result<Argon2RawSettings, PasswordProtectedKeyEnvelopeError> {
398 Ok(Argon2RawSettings {
399 iterations: extract_integer(self, ARGON2_ITERATIONS, "iterations")?.try_into()?,
400 memory: extract_integer(self, ARGON2_MEMORY, "memory")?.try_into()?,
401 parallelism: extract_integer(self, ARGON2_PARALLELISM, "parallelism")?.try_into()?,
402 salt: extract_bytes(self, ARGON2_SALT, "salt")?
403 .try_into()
404 .map_err(|_| {
405 PasswordProtectedKeyEnvelopeError::Parsing("Invalid Argon2 salt".to_string())
406 })?,
407 })
408 }
409}
410
411fn make_salt() -> [u8; ENVELOPE_ARGON2_SALT_SIZE] {
412 let mut salt = [0u8; ENVELOPE_ARGON2_SALT_SIZE];
413 rand::thread_rng().fill_bytes(&mut salt);
414 salt
415}
416
417fn derive_key(
418 argon2_settings: &Argon2RawSettings,
419 password: &str,
420) -> Result<[u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE], PasswordProtectedKeyEnvelopeError> {
421 use argon2::*;
422
423 let mut hash = [0u8; ENVELOPE_ARGON2_OUTPUT_KEY_SIZE];
424 Argon2::new(
425 Algorithm::Argon2id,
426 Version::V0x13,
427 argon2_settings.try_into()?,
428 )
429 .hash_password_into(password.as_bytes(), &argon2_settings.salt, &mut hash)
430 .map_err(|_| PasswordProtectedKeyEnvelopeError::Kdf)?;
431
432 Ok(hash)
433}
434
435#[derive(Debug, Error)]
437pub enum PasswordProtectedKeyEnvelopeError {
438 #[error("Wrong password")]
440 WrongPassword,
441 #[error("Parsing error {0}")]
443 Parsing(String),
444 #[error("Kdf error")]
447 Kdf,
448 #[error("Key missing error")]
450 KeyMissing,
451 #[error("Could not write to key store")]
453 KeyStore,
454}
455
456impl From<CoseExtractError> for PasswordProtectedKeyEnvelopeError {
457 fn from(err: CoseExtractError) -> Self {
458 let CoseExtractError::MissingValue(label) = err;
459 PasswordProtectedKeyEnvelopeError::Parsing(format!("Missing value for {}", label))
460 }
461}
462
463impl From<TryFromIntError> for PasswordProtectedKeyEnvelopeError {
464 fn from(err: TryFromIntError) -> Self {
465 PasswordProtectedKeyEnvelopeError::Parsing(format!("Invalid integer: {}", err))
466 }
467}
468
469#[cfg(feature = "wasm")]
470#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
471const TS_CUSTOM_TYPES: &'static str = r#"
472export type PasswordProtectedKeyEnvelope = Tagged<string, "PasswordProtectedKeyEnvelope">;
473"#;
474
475#[cfg(feature = "wasm")]
476impl wasm_bindgen::describe::WasmDescribe for PasswordProtectedKeyEnvelope {
477 fn describe() {
478 <String as wasm_bindgen::describe::WasmDescribe>::describe();
479 }
480}
481
482#[cfg(feature = "wasm")]
483impl FromWasmAbi for PasswordProtectedKeyEnvelope {
484 type Abi = <String as FromWasmAbi>::Abi;
485
486 unsafe fn from_abi(abi: Self::Abi) -> Self {
487 use wasm_bindgen::UnwrapThrowExt;
488 let string = unsafe { String::from_abi(abi) };
489 PasswordProtectedKeyEnvelope::from_str(&string).unwrap_throw()
490 }
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496 use crate::{KeyStore, SymmetricKeyAlgorithm, traits::tests::TestIds};
497
498 const TEST_UNSEALED_COSEKEY_ENCODED: &[u8] = &[
499 165, 1, 4, 2, 80, 63, 208, 189, 183, 204, 37, 72, 170, 179, 236, 190, 208, 22, 65, 227,
500 183, 3, 58, 0, 1, 17, 111, 4, 132, 3, 4, 5, 6, 32, 88, 32, 88, 25, 68, 85, 205, 28, 133,
501 28, 90, 147, 160, 145, 48, 3, 178, 184, 30, 11, 122, 132, 64, 59, 51, 233, 191, 117, 159,
502 117, 23, 168, 248, 36, 1,
503 ];
504 const TESTVECTOR_COSEKEY_ENVELOPE: &[u8] = &[
505 132, 68, 161, 3, 24, 101, 161, 5, 88, 24, 1, 31, 58, 230, 10, 92, 195, 233, 212, 7, 166,
506 252, 67, 115, 221, 58, 3, 191, 218, 188, 181, 192, 28, 11, 88, 84, 141, 183, 137, 167, 166,
507 161, 33, 82, 30, 255, 23, 10, 179, 149, 88, 24, 39, 60, 74, 232, 133, 44, 90, 98, 117, 31,
508 41, 69, 251, 76, 250, 141, 229, 83, 191, 6, 237, 107, 127, 93, 238, 110, 49, 125, 201, 37,
509 162, 120, 157, 32, 116, 195, 208, 143, 83, 254, 223, 93, 97, 158, 0, 24, 95, 197, 249, 35,
510 240, 3, 20, 71, 164, 97, 180, 29, 203, 69, 31, 151, 249, 244, 197, 91, 101, 174, 129, 131,
511 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,
512 90, 26, 0, 1, 0, 0, 58, 0, 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 165, 253, 56, 243, 255, 54,
513 246, 252, 231, 230, 33, 252, 49, 175, 1, 111, 246,
514 ];
515 const TEST_UNSEALED_LEGACYKEY_ENCODED: &[u8] = &[
516 135, 114, 97, 155, 115, 209, 215, 224, 175, 159, 231, 208, 15, 244, 40, 171, 239, 137, 57,
517 98, 207, 167, 231, 138, 145, 254, 28, 136, 236, 60, 23, 163, 4, 246, 219, 117, 104, 246,
518 86, 10, 152, 52, 90, 85, 58, 6, 70, 39, 111, 128, 93, 145, 143, 180, 77, 129, 178, 242, 82,
519 72, 57, 61, 192, 64,
520 ];
521 const TESTVECTOR_LEGACYKEY_ENVELOPE: &[u8] = &[
522 132, 88, 38, 161, 3, 120, 34, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 120,
523 46, 98, 105, 116, 119, 97, 114, 100, 101, 110, 46, 108, 101, 103, 97, 99, 121, 45, 107,
524 101, 121, 161, 5, 88, 24, 218, 72, 22, 79, 149, 30, 12, 36, 180, 212, 44, 21, 167, 208,
525 214, 221, 7, 91, 178, 12, 104, 17, 45, 219, 88, 80, 114, 38, 14, 165, 85, 229, 103, 108,
526 17, 175, 41, 43, 203, 175, 119, 125, 227, 127, 163, 214, 213, 138, 12, 216, 163, 204, 38,
527 222, 47, 11, 44, 231, 239, 170, 63, 8, 249, 56, 102, 18, 134, 34, 232, 193, 44, 19, 228,
528 17, 187, 199, 238, 187, 2, 13, 30, 112, 103, 110, 5, 31, 238, 58, 4, 24, 19, 239, 135, 57,
529 206, 190, 144, 83, 128, 204, 59, 155, 21, 80, 180, 34, 129, 131, 71, 161, 1, 58, 0, 1, 21,
530 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,
531 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 212, 91, 185, 112, 92, 177, 108, 33, 182, 202, 26, 141,
532 11, 133, 95, 235, 246,
533 ];
534
535 const TESTVECTOR_PASSWORD: &str = "test_password";
536
537 #[test]
538 fn test_testvector_cosekey() {
539 let key_store = KeyStore::<TestIds>::default();
540 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
541 let envelope =
542 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_COSEKEY_ENVELOPE.to_vec())
543 .expect("Key envelope should be valid");
544 let key = envelope
545 .unseal(TESTVECTOR_PASSWORD, &mut ctx)
546 .expect("Unsealing should succeed");
547 #[allow(deprecated)]
548 let unsealed_key = ctx
549 .dangerous_get_symmetric_key(key)
550 .expect("Key should exist in the key store");
551 assert_eq!(
552 unsealed_key.to_encoded().to_vec(),
553 TEST_UNSEALED_COSEKEY_ENCODED
554 );
555 }
556
557 #[test]
558 fn test_testvector_legacykey() {
559 let key_store = KeyStore::<TestIds>::default();
560 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
561 let envelope =
562 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_LEGACYKEY_ENVELOPE.to_vec())
563 .expect("Key envelope should be valid");
564 let key = envelope
565 .unseal(TESTVECTOR_PASSWORD, &mut ctx)
566 .expect("Unsealing should succeed");
567 #[allow(deprecated)]
568 let unsealed_key = ctx
569 .dangerous_get_symmetric_key(key)
570 .expect("Key should exist in the key store");
571 assert_eq!(
572 unsealed_key.to_encoded().to_vec(),
573 TEST_UNSEALED_LEGACYKEY_ENCODED
574 );
575 }
576
577 #[test]
578 fn test_make_envelope() {
579 let key_store = KeyStore::<TestIds>::default();
580 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
581 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
582
583 let password = "test_password";
584
585 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
587 let serialized: Vec<u8> = (&envelope).into();
588
589 let deserialized: PasswordProtectedKeyEnvelope =
591 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
592 let key = deserialized.unseal(password, &mut ctx).unwrap();
593
594 #[allow(deprecated)]
596 let unsealed_key = ctx
597 .dangerous_get_symmetric_key(key)
598 .expect("Key should exist in the key store");
599
600 #[allow(deprecated)]
601 let key_before_sealing = ctx
602 .dangerous_get_symmetric_key(test_key)
603 .expect("Key should exist in the key store");
604
605 assert_eq!(unsealed_key, key_before_sealing);
606 }
607
608 #[test]
609 fn test_make_envelope_legacy_key() {
610 let key_store = KeyStore::<TestIds>::default();
611 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
612 let test_key = ctx.generate_symmetric_key();
613
614 let password = "test_password";
615
616 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
618 let serialized: Vec<u8> = (&envelope).into();
619
620 let deserialized: PasswordProtectedKeyEnvelope =
622 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
623 let key = deserialized.unseal(password, &mut ctx).unwrap();
624
625 #[allow(deprecated)]
627 let unsealed_key = ctx
628 .dangerous_get_symmetric_key(key)
629 .expect("Key should exist in the key store");
630
631 #[allow(deprecated)]
632 let key_before_sealing = ctx
633 .dangerous_get_symmetric_key(test_key)
634 .expect("Key should exist in the key store");
635
636 assert_eq!(unsealed_key, key_before_sealing);
637 }
638
639 #[test]
640 fn test_reseal_envelope() {
641 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
642 let password = "test_password";
643 let new_password = "new_test_password";
644
645 let envelope: PasswordProtectedKeyEnvelope =
647 PasswordProtectedKeyEnvelope::seal_ref(&key, password).expect("Sealing should work");
648
649 let envelope = envelope
651 .reseal(password, new_password)
652 .expect("Resealing should work");
653 let unsealed = envelope
654 .unseal_ref(new_password)
655 .expect("Unsealing should work");
656
657 assert_eq!(unsealed, key);
659 }
660
661 #[test]
662 fn test_wrong_password() {
663 let key_store = KeyStore::<TestIds>::default();
664 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
665 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
666
667 let password = "test_password";
668 let wrong_password = "wrong_password";
669
670 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
672
673 let deserialized: PasswordProtectedKeyEnvelope =
675 PasswordProtectedKeyEnvelope::try_from(&(&envelope).into()).unwrap();
676 assert!(matches!(
677 deserialized.unseal(wrong_password, &mut ctx),
678 Err(PasswordProtectedKeyEnvelopeError::WrongPassword)
679 ));
680 }
681
682 #[test]
683 fn test_key_id() {
684 let key_store = KeyStore::<TestIds>::default();
685 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
686 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
687 #[allow(deprecated)]
688 let key_id = ctx
689 .dangerous_get_symmetric_key(test_key)
690 .unwrap()
691 .key_id()
692 .unwrap();
693
694 let password = "test_password";
695
696 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
698 let contained_key_id = envelope.contained_key_id().unwrap();
699 assert_eq!(Some(key_id), contained_key_id);
700 }
701
702 #[test]
703 fn test_no_key_id() {
704 let key_store = KeyStore::<TestIds>::default();
705 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
706 let test_key = ctx.generate_symmetric_key();
707
708 let password = "test_password";
709
710 let envelope = PasswordProtectedKeyEnvelope::seal(test_key, password, &ctx).unwrap();
712 let contained_key_id = envelope.contained_key_id().unwrap();
713 assert_eq!(None, contained_key_id);
714 }
715}