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::Rng;
23use serde::{Deserialize, Serialize};
24use thiserror::Error;
25#[cfg(feature = "wasm")]
26use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi};
27
28use crate::{
29 BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CryptoError, EncodedSymmetricKey,
30 KEY_ID_SIZE, KeySlotIds, 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: KeySlotIds>(
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: KeySlotIds>(
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::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#[cfg(feature = "wasm")]
538impl OptionFromWasmAbi for PasswordProtectedKeyEnvelope {
539 fn is_none(abi: &Self::Abi) -> bool {
540 <String as OptionFromWasmAbi>::is_none(abi)
541 }
542}
543
544#[cfg(feature = "wasm")]
545impl IntoWasmAbi for PasswordProtectedKeyEnvelope {
546 type Abi = <String as IntoWasmAbi>::Abi;
547
548 fn into_abi(self) -> Self::Abi {
549 let string: String = self.into();
550 string.into_abi()
551 }
552}
553
554#[derive(Debug, Clone, Copy, PartialEq, Eq)]
556pub enum PasswordProtectedKeyEnvelopeNamespace {
557 PinUnlock = 1,
559 #[cfg(test)]
561 ExampleNamespace = -1,
562 #[cfg(test)]
564 ExampleNamespace2 = -2,
565}
566
567impl PasswordProtectedKeyEnvelopeNamespace {
568 fn as_i64(&self) -> i64 {
570 *self as i64
571 }
572}
573
574impl TryFrom<i128> for PasswordProtectedKeyEnvelopeNamespace {
575 type Error = PasswordProtectedKeyEnvelopeError;
576
577 fn try_from(value: i128) -> Result<Self, Self::Error> {
578 match value {
579 1 => Ok(PasswordProtectedKeyEnvelopeNamespace::PinUnlock),
580 #[cfg(test)]
581 -1 => Ok(PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace),
582 #[cfg(test)]
583 -2 => Ok(PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace2),
584 _ => Err(PasswordProtectedKeyEnvelopeError::InvalidNamespace),
585 }
586 }
587}
588
589impl TryFrom<i64> for PasswordProtectedKeyEnvelopeNamespace {
590 type Error = PasswordProtectedKeyEnvelopeError;
591
592 fn try_from(value: i64) -> Result<Self, Self::Error> {
593 Self::try_from(i128::from(value))
594 }
595}
596
597impl From<PasswordProtectedKeyEnvelopeNamespace> for i128 {
598 fn from(val: PasswordProtectedKeyEnvelopeNamespace) -> Self {
599 val.as_i64().into()
600 }
601}
602
603impl ContentNamespace for PasswordProtectedKeyEnvelopeNamespace {}
604
605#[cfg(test)]
606mod tests {
607 use super::*;
608 use crate::{KeyStore, SymmetricKeyAlgorithm, traits::tests::TestIds};
609
610 const TEST_UNSEALED_COSEKEY_ENCODED: &[u8] = &[
611 165, 1, 4, 2, 80, 63, 208, 189, 183, 204, 37, 72, 170, 179, 236, 190, 208, 22, 65, 227,
612 183, 3, 58, 0, 1, 17, 111, 4, 132, 3, 4, 5, 6, 32, 88, 32, 88, 25, 68, 85, 205, 28, 133,
613 28, 90, 147, 160, 145, 48, 3, 178, 184, 30, 11, 122, 132, 64, 59, 51, 233, 191, 117, 159,
614 117, 23, 168, 248, 36, 1,
615 ];
616 const TESTVECTOR_COSEKEY_ENVELOPE: &[u8] = &[
617 132, 68, 161, 3, 24, 101, 161, 5, 88, 24, 1, 31, 58, 230, 10, 92, 195, 233, 212, 7, 166,
618 252, 67, 115, 221, 58, 3, 191, 218, 188, 181, 192, 28, 11, 88, 84, 141, 183, 137, 167, 166,
619 161, 33, 82, 30, 255, 23, 10, 179, 149, 88, 24, 39, 60, 74, 232, 133, 44, 90, 98, 117, 31,
620 41, 69, 251, 76, 250, 141, 229, 83, 191, 6, 237, 107, 127, 93, 238, 110, 49, 125, 201, 37,
621 162, 120, 157, 32, 116, 195, 208, 143, 83, 254, 223, 93, 97, 158, 0, 24, 95, 197, 249, 35,
622 240, 3, 20, 71, 164, 97, 180, 29, 203, 69, 31, 151, 249, 244, 197, 91, 101, 174, 129, 131,
623 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,
624 90, 26, 0, 1, 0, 0, 58, 0, 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 165, 253, 56, 243, 255, 54,
625 246, 252, 231, 230, 33, 252, 49, 175, 1, 111, 246,
626 ];
627 const TEST_UNSEALED_LEGACYKEY_ENCODED: &[u8] = &[
628 135, 114, 97, 155, 115, 209, 215, 224, 175, 159, 231, 208, 15, 244, 40, 171, 239, 137, 57,
629 98, 207, 167, 231, 138, 145, 254, 28, 136, 236, 60, 23, 163, 4, 246, 219, 117, 104, 246,
630 86, 10, 152, 52, 90, 85, 58, 6, 70, 39, 111, 128, 93, 145, 143, 180, 77, 129, 178, 242, 82,
631 72, 57, 61, 192, 64,
632 ];
633 const TESTVECTOR_LEGACYKEY_ENVELOPE: &[u8] = &[
634 132, 88, 38, 161, 3, 120, 34, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 120,
635 46, 98, 105, 116, 119, 97, 114, 100, 101, 110, 46, 108, 101, 103, 97, 99, 121, 45, 107,
636 101, 121, 161, 5, 88, 24, 218, 72, 22, 79, 149, 30, 12, 36, 180, 212, 44, 21, 167, 208,
637 214, 221, 7, 91, 178, 12, 104, 17, 45, 219, 88, 80, 114, 38, 14, 165, 85, 229, 103, 108,
638 17, 175, 41, 43, 203, 175, 119, 125, 227, 127, 163, 214, 213, 138, 12, 216, 163, 204, 38,
639 222, 47, 11, 44, 231, 239, 170, 63, 8, 249, 56, 102, 18, 134, 34, 232, 193, 44, 19, 228,
640 17, 187, 199, 238, 187, 2, 13, 30, 112, 103, 110, 5, 31, 238, 58, 4, 24, 19, 239, 135, 57,
641 206, 190, 144, 83, 128, 204, 59, 155, 21, 80, 180, 34, 129, 131, 71, 161, 1, 58, 0, 1, 21,
642 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,
643 1, 21, 91, 4, 58, 0, 1, 21, 88, 80, 212, 91, 185, 112, 92, 177, 108, 33, 182, 202, 26, 141,
644 11, 133, 95, 235, 246,
645 ];
646
647 const TESTVECTOR_PASSWORD: &str = "test_password";
648
649 #[test]
650 #[ignore = "Manual test to verify debug format"]
651 fn test_debug() {
652 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
653 let envelope = PasswordProtectedKeyEnvelope::seal_ref(
654 &key,
655 "test_password",
656 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
657 )
658 .unwrap();
659 println!("{:?}", envelope);
660 }
661
662 #[test]
663 fn test_testvector_cosekey() {
664 let key_store = KeyStore::<TestIds>::default();
665 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
666 let envelope =
667 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_COSEKEY_ENVELOPE.to_vec())
668 .expect("Key envelope should be valid");
669 let key = envelope
670 .unseal(
671 TESTVECTOR_PASSWORD,
672 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
673 &mut ctx,
674 )
675 .expect("Unsealing should succeed");
676 #[allow(deprecated)]
677 let unsealed_key = ctx
678 .dangerous_get_symmetric_key(key)
679 .expect("Key should exist in the key store");
680 assert_eq!(
681 unsealed_key.to_encoded().to_vec(),
682 TEST_UNSEALED_COSEKEY_ENCODED
683 );
684 }
685
686 #[test]
687 fn test_testvector_legacykey() {
688 let key_store = KeyStore::<TestIds>::default();
689 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
690 let envelope =
691 PasswordProtectedKeyEnvelope::try_from(&TESTVECTOR_LEGACYKEY_ENVELOPE.to_vec())
692 .expect("Key envelope should be valid");
693 let key = envelope
694 .unseal(
695 TESTVECTOR_PASSWORD,
696 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
697 &mut ctx,
698 )
699 .expect("Unsealing should succeed");
700 #[allow(deprecated)]
701 let unsealed_key = ctx
702 .dangerous_get_symmetric_key(key)
703 .expect("Key should exist in the key store");
704 assert_eq!(
705 unsealed_key.to_encoded().to_vec(),
706 TEST_UNSEALED_LEGACYKEY_ENCODED
707 );
708 }
709
710 #[test]
711 fn test_make_envelope() {
712 let key_store = KeyStore::<TestIds>::default();
713 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
714 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
715
716 let password = "test_password";
717
718 let envelope = PasswordProtectedKeyEnvelope::seal(
720 test_key,
721 password,
722 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
723 &ctx,
724 )
725 .unwrap();
726 let serialized: Vec<u8> = (&envelope).into();
727
728 let deserialized: PasswordProtectedKeyEnvelope =
730 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
731 let key = deserialized
732 .unseal(
733 password,
734 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
735 &mut ctx,
736 )
737 .unwrap();
738
739 #[allow(deprecated)]
741 let unsealed_key = ctx
742 .dangerous_get_symmetric_key(key)
743 .expect("Key should exist in the key store");
744
745 #[allow(deprecated)]
746 let key_before_sealing = ctx
747 .dangerous_get_symmetric_key(test_key)
748 .expect("Key should exist in the key store");
749
750 assert_eq!(unsealed_key, key_before_sealing);
751 }
752
753 #[test]
754 fn test_make_envelope_legacy_key() {
755 let key_store = KeyStore::<TestIds>::default();
756 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
757 let test_key = ctx.generate_symmetric_key();
758
759 let password = "test_password";
760
761 let envelope = PasswordProtectedKeyEnvelope::seal(
763 test_key,
764 password,
765 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
766 &ctx,
767 )
768 .unwrap();
769 let serialized: Vec<u8> = (&envelope).into();
770
771 let deserialized: PasswordProtectedKeyEnvelope =
773 PasswordProtectedKeyEnvelope::try_from(&serialized).unwrap();
774 let key = deserialized
775 .unseal(
776 password,
777 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
778 &mut ctx,
779 )
780 .unwrap();
781
782 #[allow(deprecated)]
784 let unsealed_key = ctx
785 .dangerous_get_symmetric_key(key)
786 .expect("Key should exist in the key store");
787
788 #[allow(deprecated)]
789 let key_before_sealing = ctx
790 .dangerous_get_symmetric_key(test_key)
791 .expect("Key should exist in the key store");
792
793 assert_eq!(unsealed_key, key_before_sealing);
794 }
795
796 #[test]
797 fn test_reseal_envelope() {
798 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
799 let password = "test_password";
800 let new_password = "new_test_password";
801
802 let envelope: PasswordProtectedKeyEnvelope = PasswordProtectedKeyEnvelope::seal_ref(
804 &key,
805 password,
806 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
807 )
808 .expect("Sealing should work");
809
810 let envelope = envelope
812 .reseal(
813 password,
814 new_password,
815 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
816 )
817 .expect("Resealing should work");
818 let unsealed = envelope
819 .unseal_ref(
820 new_password,
821 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
822 )
823 .expect("Unsealing should work");
824
825 assert_eq!(unsealed, key);
827 }
828
829 #[test]
830 fn test_wrong_password() {
831 let key_store = KeyStore::<TestIds>::default();
832 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
833 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
834
835 let password = "test_password";
836 let wrong_password = "wrong_password";
837
838 let envelope = PasswordProtectedKeyEnvelope::seal(
840 test_key,
841 password,
842 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
843 &ctx,
844 )
845 .unwrap();
846
847 let deserialized: PasswordProtectedKeyEnvelope =
849 PasswordProtectedKeyEnvelope::try_from(&(&envelope).into()).unwrap();
850 assert!(matches!(
851 deserialized.unseal(
852 wrong_password,
853 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
854 &mut ctx
855 ),
856 Err(PasswordProtectedKeyEnvelopeError::WrongPassword)
857 ));
858 }
859
860 #[test]
861 fn test_wrong_safe_namespace() {
862 let key_store = KeyStore::<TestIds>::default();
863 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
864 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
865 let password = "test_password";
866
867 let mut envelope = PasswordProtectedKeyEnvelope::seal(
868 test_key,
869 password,
870 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
871 &ctx,
872 )
873 .expect("Seal works");
874
875 if let Some((_, value)) = envelope
876 .cose_encrypt
877 .protected
878 .header
879 .rest
880 .iter_mut()
881 .find(|(label, _)| {
882 matches!(label, coset::Label::Int(label_value) if *label_value == crate::cose::SAFE_OBJECT_NAMESPACE)
883 })
884 {
885 *value = Value::Integer((SafeObjectNamespace::DataEnvelope as i64).into());
886 }
887
888 let deserialized: PasswordProtectedKeyEnvelope =
889 PasswordProtectedKeyEnvelope::try_from(&(&envelope).into())
890 .expect("Envelope should be valid");
891
892 let a = deserialized.unseal(
893 password,
894 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
895 &mut ctx,
896 );
897 println!("Error: {a:?}");
898 assert!(matches!(
899 a,
900 Err(PasswordProtectedKeyEnvelopeError::InvalidNamespace)
901 ));
902 }
903
904 #[test]
905 fn test_key_id() {
906 let key_store = KeyStore::<TestIds>::default();
907 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
908 let test_key = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
909 #[allow(deprecated)]
910 let key_id = ctx
911 .dangerous_get_symmetric_key(test_key)
912 .unwrap()
913 .key_id()
914 .unwrap();
915
916 let password = "test_password";
917
918 let envelope = PasswordProtectedKeyEnvelope::seal(
920 test_key,
921 password,
922 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
923 &ctx,
924 )
925 .unwrap();
926 let contained_key_id = envelope.contained_key_id().unwrap();
927 assert_eq!(Some(key_id), contained_key_id);
928 }
929
930 #[test]
931 fn test_no_key_id() {
932 let key_store = KeyStore::<TestIds>::default();
933 let mut ctx: KeyStoreContext<'_, TestIds> = key_store.context_mut();
934 let test_key = ctx.generate_symmetric_key();
935
936 let password = "test_password";
937
938 let envelope = PasswordProtectedKeyEnvelope::seal(
940 test_key,
941 password,
942 PasswordProtectedKeyEnvelopeNamespace::ExampleNamespace,
943 &ctx,
944 )
945 .unwrap();
946 let contained_key_id = envelope.contained_key_id().unwrap();
947 assert_eq!(None, contained_key_id);
948 }
949}