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