1use std::{pin::Pin, str::FromStr};
2
3use bitwarden_encoding::{B64, FromStrVisitor};
4use ciborium::{Value, value::Integer};
5use coset::{
6 CborSerializable, RegisteredLabelWithPrivate,
7 iana::{EnumI64, KeyOperation, KeyParameter, KeyType, SymmetricKeyParameter},
8};
9use hybrid_array::Array;
10use rand::RngExt;
11#[cfg(test)]
12use rand::SeedableRng;
13#[cfg(test)]
14use rand_chacha::ChaChaRng;
15use serde::{Deserialize, Serialize};
16#[cfg(test)]
17use sha2::Digest;
18use subtle::{Choice, ConstantTimeEq};
19use typenum::U32;
20#[cfg(feature = "wasm")]
21use wasm_bindgen::convert::{FromWasmAbi, IntoWasmAbi, OptionFromWasmAbi};
22use zeroize::{Zeroize, ZeroizeOnDrop};
23
24use super::{key_encryptable::CryptoKey, key_id::KeyId};
25use crate::{
26 BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CoseKeyThumbprint, CryptoError, cose,
27 cose::{CoseKeyThumbprintExt, thumbprint_from_required_params},
28 error::EncodingError,
29};
30
31#[cfg(feature = "wasm")]
32#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
33const TS_CUSTOM_TYPES: &'static str = r#"
34export type SymmetricKey = Tagged<string, "SymmetricKey">;
35"#;
36
37#[cfg(feature = "wasm")]
38impl wasm_bindgen::describe::WasmDescribe for SymmetricCryptoKey {
39 fn describe() {
40 <String as wasm_bindgen::describe::WasmDescribe>::describe();
41 }
42}
43
44#[cfg(feature = "wasm")]
45impl FromWasmAbi for SymmetricCryptoKey {
46 type Abi = <String as FromWasmAbi>::Abi;
47
48 unsafe fn from_abi(abi: Self::Abi) -> Self {
49 use wasm_bindgen::UnwrapThrowExt;
50 let string = unsafe { String::from_abi(abi) };
51 let b64 = B64::try_from(string).unwrap_throw();
52 SymmetricCryptoKey::try_from(b64).unwrap_throw()
53 }
54}
55
56#[cfg(feature = "wasm")]
57impl OptionFromWasmAbi for SymmetricCryptoKey {
58 fn is_none(abi: &Self::Abi) -> bool {
59 <String as OptionFromWasmAbi>::is_none(abi)
60 }
61}
62
63#[cfg(feature = "wasm")]
64impl IntoWasmAbi for SymmetricCryptoKey {
65 type Abi = <String as IntoWasmAbi>::Abi;
66
67 fn into_abi(self) -> Self::Abi {
68 let string: String = self.to_base64().to_string();
69 string.into_abi()
70 }
71}
72
73#[cfg(feature = "wasm")]
74impl TryFrom<wasm_bindgen::JsValue> for SymmetricCryptoKey {
75 type Error = CryptoError;
76
77 fn try_from(value: wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
78 let string = value.as_string().ok_or(CryptoError::InvalidKey)?;
79 Self::try_from(string)
80 }
81}
82
83#[derive(Debug, PartialEq)]
85pub enum SymmetricKeyAlgorithm {
86 Aes256CbcHmac,
88 XChaCha20Poly1305,
90 Aes256Gcm,
97}
98
99#[derive(ZeroizeOnDrop, Clone)]
103pub struct Aes256CbcKey {
104 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
106}
107
108impl ConstantTimeEq for Aes256CbcKey {
109 fn ct_eq(&self, other: &Self) -> Choice {
110 self.enc_key.ct_eq(&other.enc_key)
111 }
112}
113
114impl PartialEq for Aes256CbcKey {
115 fn eq(&self, other: &Self) -> bool {
116 self.ct_eq(other).into()
117 }
118}
119
120#[derive(ZeroizeOnDrop, Clone)]
123pub struct Aes256CbcHmacKey {
124 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
126 pub(crate) mac_key: Pin<Box<Array<u8, U32>>>,
128}
129
130impl ConstantTimeEq for Aes256CbcHmacKey {
131 fn ct_eq(&self, other: &Self) -> Choice {
132 self.enc_key.ct_eq(&other.enc_key) & self.mac_key.ct_eq(&other.mac_key)
133 }
134}
135
136impl PartialEq for Aes256CbcHmacKey {
137 fn eq(&self, other: &Self) -> bool {
138 self.ct_eq(other).into()
139 }
140}
141
142#[derive(Zeroize, Clone)]
147pub struct XChaCha20Poly1305Key {
148 pub(crate) key_id: KeyId,
149 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
150 #[zeroize(skip)]
155 pub(crate) supported_operations: Vec<KeyOperation>,
156}
157
158impl XChaCha20Poly1305Key {
159 pub fn make() -> Self {
161 let mut rng = bitwarden_random::rng();
162 let mut enc_key = Box::pin(Array::<u8, U32>::default());
163 rng.fill(enc_key.as_mut_slice());
164 let key_id = KeyId::make();
165
166 Self {
167 enc_key,
168 key_id,
169 supported_operations: vec![
170 KeyOperation::Decrypt,
171 KeyOperation::Encrypt,
172 KeyOperation::WrapKey,
173 KeyOperation::UnwrapKey,
174 ],
175 }
176 }
177}
178
179impl ConstantTimeEq for XChaCha20Poly1305Key {
180 fn ct_eq(&self, other: &Self) -> Choice {
181 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
182 }
183}
184
185impl PartialEq for XChaCha20Poly1305Key {
186 fn eq(&self, other: &Self) -> bool {
187 self.ct_eq(other).into()
188 }
189}
190
191#[derive(Zeroize, Clone)]
193pub struct Aes256GcmKey {
194 pub(crate) key_id: KeyId,
195 pub(crate) enc_key: Pin<Box<Array<u8, U32>>>,
196 #[zeroize(skip)]
199 pub(crate) supported_operations: Vec<KeyOperation>,
200}
201
202impl Aes256GcmKey {
203 pub fn make() -> Self {
205 let mut rng = bitwarden_random::rng();
206 let mut enc_key = Box::pin(Array::<u8, U32>::default());
207 rng.fill(enc_key.as_mut_slice());
208 let key_id = KeyId::make();
209
210 Self {
211 enc_key,
212 key_id,
213 supported_operations: vec![
214 KeyOperation::Decrypt,
215 KeyOperation::Encrypt,
216 KeyOperation::WrapKey,
217 KeyOperation::UnwrapKey,
218 ],
219 }
220 }
221
222 pub(crate) fn disable_key_operation(&mut self, op: KeyOperation) -> &mut Self {
223 self.supported_operations.retain(|k| *k != op);
224 self
225 }
226}
227
228impl ConstantTimeEq for Aes256GcmKey {
229 fn ct_eq(&self, other: &Self) -> Choice {
230 self.enc_key.ct_eq(&other.enc_key) & self.key_id.ct_eq(&other.key_id)
231 }
232}
233
234impl PartialEq for Aes256GcmKey {
235 fn eq(&self, other: &Self) -> bool {
236 self.ct_eq(other).into()
237 }
238}
239
240pub(crate) enum CoseKeyView<'a> {
243 Aes256Gcm(&'a Aes256GcmKey),
244 XChaCha20Poly1305(&'a XChaCha20Poly1305Key),
245}
246
247impl CoseKeyView<'_> {
248 pub(crate) fn key_id(&self) -> &KeyId {
249 match self {
250 CoseKeyView::Aes256Gcm(k) => &k.key_id,
251 CoseKeyView::XChaCha20Poly1305(k) => &k.key_id,
252 }
253 }
254
255 pub(crate) fn key_bytes(&self) -> &[u8] {
256 match self {
257 CoseKeyView::Aes256Gcm(k) => k.enc_key.as_slice(),
258 CoseKeyView::XChaCha20Poly1305(k) => k.enc_key.as_slice(),
259 }
260 }
261}
262
263#[derive(ZeroizeOnDrop, Clone)]
265pub enum SymmetricCryptoKey {
266 #[allow(missing_docs)]
267 Aes256CbcKey(Aes256CbcKey),
268 #[allow(missing_docs)]
269 Aes256CbcHmacKey(Aes256CbcHmacKey),
270 XChaCha20Poly1305Key(XChaCha20Poly1305Key),
273 Aes256GcmKey(Aes256GcmKey),
276}
277
278impl SymmetricCryptoKey {
279 const AES256_CBC_KEY_LEN: usize = 32;
281 const AES256_CBC_HMAC_KEY_LEN: usize = 64;
283
284 pub(crate) fn make_aes256_cbc_hmac_key_internal(mut rng: impl rand::CryptoRng) -> Self {
290 let mut enc_key = Box::pin(Array::<u8, U32>::default());
291 let mut mac_key = Box::pin(Array::<u8, U32>::default());
292
293 rng.fill(enc_key.as_mut_slice());
294 rng.fill(mac_key.as_mut_slice());
295
296 Self::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
297 }
298
299 pub fn make(algorithm: SymmetricKeyAlgorithm) -> Self {
301 match algorithm {
302 SymmetricKeyAlgorithm::Aes256CbcHmac => Self::make_aes256_cbc_hmac_key(),
303 SymmetricKeyAlgorithm::XChaCha20Poly1305 => Self::make_xchacha20_poly1305_key(),
304 SymmetricKeyAlgorithm::Aes256Gcm => Self::Aes256GcmKey(Aes256GcmKey::make()),
305 }
306 }
307
308 pub(crate) fn make_aes256_cbc_hmac_key() -> Self {
310 let rng = bitwarden_random::rng();
311 Self::make_aes256_cbc_hmac_key_internal(rng)
312 }
313
314 pub(crate) fn make_xchacha20_poly1305_key() -> Self {
316 let mut rng = bitwarden_random::rng();
317 let mut enc_key = Box::pin(Array::<u8, U32>::default());
318 rng.fill(enc_key.as_mut_slice());
319 Self::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
320 enc_key,
321 key_id: KeyId::make(),
322 supported_operations: vec![
323 KeyOperation::Decrypt,
324 KeyOperation::Encrypt,
325 KeyOperation::WrapKey,
326 KeyOperation::UnwrapKey,
327 ],
328 })
329 }
330
331 pub fn to_encoded(&self) -> BitwardenLegacyKeyBytes {
340 let encoded_key = self.to_encoded_raw();
341 match encoded_key {
342 EncodedSymmetricKey::BitwardenLegacyKey(_) => {
343 let encoded_key: Vec<u8> = encoded_key.into();
344 BitwardenLegacyKeyBytes::from(encoded_key)
345 }
346 EncodedSymmetricKey::CoseKey(_) => {
347 let mut encoded_key: Vec<u8> = encoded_key.into();
348 pad_key(&mut encoded_key, (Self::AES256_CBC_HMAC_KEY_LEN + 1) as u8); BitwardenLegacyKeyBytes::from(encoded_key)
350 }
351 }
352 }
353
354 #[cfg(test)]
357 pub fn generate_seeded_for_unit_tests(seed: &str) -> Self {
358 let mut seeded_rng = ChaChaRng::from_seed(sha2::Sha256::digest(seed.as_bytes()).into());
360 let mut enc_key = Box::pin(Array::<u8, U32>::default());
361 let mut mac_key = Box::pin(Array::<u8, U32>::default());
362
363 seeded_rng.fill(enc_key.as_mut_slice());
364 seeded_rng.fill(mac_key.as_mut_slice());
365
366 SymmetricCryptoKey::Aes256CbcHmacKey(Aes256CbcHmacKey { enc_key, mac_key })
367 }
368
369 pub(crate) fn to_encoded_raw(&self) -> EncodedSymmetricKey {
381 match self {
382 Self::Aes256CbcKey(key) => {
383 EncodedSymmetricKey::BitwardenLegacyKey(key.enc_key.to_vec().into())
384 }
385 Self::Aes256CbcHmacKey(key) => {
386 let mut buf = Vec::with_capacity(64);
387 buf.extend_from_slice(&key.enc_key);
388 buf.extend_from_slice(&key.mac_key);
389 EncodedSymmetricKey::BitwardenLegacyKey(buf.into())
390 }
391 Self::XChaCha20Poly1305Key(key) => {
392 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
393 let mut cose_key = builder.key_id((&key.key_id).into());
394 for op in &key.supported_operations {
395 cose_key = cose_key.add_key_op(*op);
396 }
397 let mut cose_key = cose_key.build();
398 cose_key.alg = Some(RegisteredLabelWithPrivate::PrivateUse(
399 cose::XCHACHA20_POLY1305,
400 ));
401 EncodedSymmetricKey::CoseKey(
402 cose_key
403 .to_vec()
404 .expect("cose key serialization should not fail")
405 .into(),
406 )
407 }
408 Self::Aes256GcmKey(key) => {
409 let builder = coset::CoseKeyBuilder::new_symmetric_key(key.enc_key.to_vec());
410 let mut cose_key = builder.key_id((&key.key_id).into());
411 for op in &key.supported_operations {
412 cose_key = cose_key.add_key_op(*op);
413 }
414 let mut cose_key = cose_key.build();
415 cose_key.alg = Some(RegisteredLabelWithPrivate::Assigned(
416 coset::iana::Algorithm::A256GCM,
417 ));
418 EncodedSymmetricKey::CoseKey(
419 cose_key
420 .to_vec()
421 .expect("cose key serialization should not fail")
422 .into(),
423 )
424 }
425 }
426 }
427
428 pub(crate) fn try_from_cose(serialized_key: &[u8]) -> Result<Self, CryptoError> {
429 let cose_key =
430 coset::CoseKey::from_slice(serialized_key).map_err(|_| CryptoError::InvalidKey)?;
431 let key = SymmetricCryptoKey::try_from(&cose_key)?;
432 Ok(key)
433 }
434
435 #[allow(missing_docs)]
436 pub fn to_base64(&self) -> B64 {
437 B64::from(self.to_encoded().as_ref())
438 }
439
440 pub fn key_id(&self) -> Option<KeyId> {
443 match self {
444 Self::Aes256CbcKey(_) => None,
445 Self::Aes256CbcHmacKey(_) => None,
446 Self::XChaCha20Poly1305Key(key) => Some(key.key_id.clone()),
447 Self::Aes256GcmKey(key) => Some(key.key_id.clone()),
448 }
449 }
450
451 pub(crate) fn as_cose_key_view(&self) -> Option<CoseKeyView<'_>> {
454 match self {
455 Self::Aes256GcmKey(k) => Some(CoseKeyView::Aes256Gcm(k)),
456 Self::XChaCha20Poly1305Key(k) => Some(CoseKeyView::XChaCha20Poly1305(k)),
457 Self::Aes256CbcKey(_) | Self::Aes256CbcHmacKey(_) => None,
458 }
459 }
460}
461
462impl CoseKeyThumbprintExt for SymmetricCryptoKey {
463 fn thumbprint(&self) -> Result<CoseKeyThumbprint, CryptoError> {
468 let view = self
469 .as_cose_key_view()
470 .ok_or(EncodingError::UnsupportedValue(
471 "legacy AES-CBC keys are not COSE keys and have no thumbprint",
472 ))?;
473 let params = vec![
474 (
475 KeyParameter::Kty.to_i64(),
476 Value::Integer(Integer::from(KeyType::Symmetric.to_i64())),
477 ),
478 (
479 SymmetricKeyParameter::K.to_i64(),
480 Value::Bytes(view.key_bytes().to_vec()),
481 ),
482 ];
483 Ok(thumbprint_from_required_params(params))
484 }
485}
486
487impl ConstantTimeEq for SymmetricCryptoKey {
488 fn ct_eq(&self, other: &SymmetricCryptoKey) -> Choice {
492 use SymmetricCryptoKey::*;
493 match (self, other) {
494 (Aes256CbcKey(a), Aes256CbcKey(b)) => a.ct_eq(b),
495 (Aes256CbcKey(_), _) => Choice::from(0),
496
497 (Aes256CbcHmacKey(a), Aes256CbcHmacKey(b)) => a.ct_eq(b),
498 (Aes256CbcHmacKey(_), _) => Choice::from(0),
499
500 (XChaCha20Poly1305Key(a), XChaCha20Poly1305Key(b)) => a.ct_eq(b),
501 (XChaCha20Poly1305Key(_), _) => Choice::from(0),
502
503 (Aes256GcmKey(a), Aes256GcmKey(b)) => a.ct_eq(b),
504 (Aes256GcmKey(_), _) => Choice::from(0),
505 }
506 }
507}
508
509impl PartialEq for SymmetricCryptoKey {
510 fn eq(&self, other: &Self) -> bool {
511 self.ct_eq(other).into()
512 }
513}
514
515impl TryFrom<String> for SymmetricCryptoKey {
516 type Error = CryptoError;
517
518 fn try_from(value: String) -> Result<Self, Self::Error> {
519 let bytes = B64::try_from(value).map_err(|_| CryptoError::InvalidKey)?;
520 Self::try_from(bytes)
521 }
522}
523
524impl TryFrom<B64> for SymmetricCryptoKey {
525 type Error = CryptoError;
526
527 fn try_from(value: B64) -> Result<Self, Self::Error> {
528 Self::try_from(&BitwardenLegacyKeyBytes::from(&value))
529 }
530}
531
532impl TryFrom<&BitwardenLegacyKeyBytes> for SymmetricCryptoKey {
533 type Error = CryptoError;
534
535 fn try_from(value: &BitwardenLegacyKeyBytes) -> Result<Self, Self::Error> {
536 let slice = value.as_ref();
537
538 if slice.len() == Self::AES256_CBC_HMAC_KEY_LEN || slice.len() == Self::AES256_CBC_KEY_LEN {
544 Self::try_from(EncodedSymmetricKey::BitwardenLegacyKey(value.clone()))
545 } else if slice.len() > Self::AES256_CBC_HMAC_KEY_LEN {
546 let unpadded_value = unpad_key(slice)?;
547 Ok(Self::try_from_cose(unpadded_value)?)
548 } else {
549 Err(CryptoError::InvalidKeyLen)
550 }
551 }
552}
553
554impl TryFrom<EncodedSymmetricKey> for SymmetricCryptoKey {
555 type Error = CryptoError;
556
557 fn try_from(value: EncodedSymmetricKey) -> Result<Self, Self::Error> {
558 match value {
559 EncodedSymmetricKey::BitwardenLegacyKey(key)
560 if key.as_ref().len() == Self::AES256_CBC_KEY_LEN =>
561 {
562 let mut enc_key = Box::pin(Array::<u8, U32>::default());
563 enc_key.copy_from_slice(&key.as_ref()[..Self::AES256_CBC_KEY_LEN]);
564 Ok(Self::Aes256CbcKey(Aes256CbcKey { enc_key }))
565 }
566 EncodedSymmetricKey::BitwardenLegacyKey(key)
567 if key.as_ref().len() == Self::AES256_CBC_HMAC_KEY_LEN =>
568 {
569 let mut enc_key = Box::pin(Array::<u8, U32>::default());
570 enc_key.copy_from_slice(&key.as_ref()[..32]);
571
572 let mut mac_key = Box::pin(Array::<u8, U32>::default());
573 mac_key.copy_from_slice(&key.as_ref()[32..]);
574
575 Ok(Self::Aes256CbcHmacKey(Aes256CbcHmacKey {
576 enc_key,
577 mac_key,
578 }))
579 }
580 EncodedSymmetricKey::CoseKey(key) => Self::try_from_cose(key.as_ref()),
581 _ => Err(CryptoError::InvalidKey),
582 }
583 }
584}
585
586impl CryptoKey for SymmetricCryptoKey {}
587
588impl std::fmt::Debug for SymmetricCryptoKey {
590 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
591 match self {
592 SymmetricCryptoKey::Aes256CbcKey(key) => key.fmt(f),
593 SymmetricCryptoKey::Aes256CbcHmacKey(key) => key.fmt(f),
594 SymmetricCryptoKey::XChaCha20Poly1305Key(key) => key.fmt(f),
595 SymmetricCryptoKey::Aes256GcmKey(key) => key.fmt(f),
596 }
597 }
598}
599
600impl std::fmt::Debug for Aes256CbcKey {
601 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
602 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256Cbc");
603 #[cfg(feature = "dangerous-crypto-debug")]
604 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
605 debug_struct.finish()
606 }
607}
608
609impl std::fmt::Debug for Aes256CbcHmacKey {
610 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
611 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256CbcHmac");
612 #[cfg(feature = "dangerous-crypto-debug")]
613 debug_struct
614 .field("enc_key", &hex::encode(self.enc_key.as_slice()))
615 .field("mac_key", &hex::encode(self.mac_key.as_slice()));
616 debug_struct.finish()
617 }
618}
619
620impl std::fmt::Debug for XChaCha20Poly1305Key {
621 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622 let mut debug_struct = f.debug_struct("SymmetricKey::XChaCha20Poly1305");
623 debug_struct.field("key_id", &self.key_id);
624 debug_struct.field(
625 "supported_operations",
626 &self
627 .supported_operations
628 .iter()
629 .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
630 .collect::<Vec<_>>(),
631 );
632 #[cfg(feature = "dangerous-crypto-debug")]
633 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
634 debug_struct.finish()
635 }
636}
637
638impl std::fmt::Debug for Aes256GcmKey {
639 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
640 let mut debug_struct = f.debug_struct("SymmetricKey::Aes256Gcm");
641 debug_struct.field("key_id", &self.key_id);
642 debug_struct.field(
643 "supported_operations",
644 &self
645 .supported_operations
646 .iter()
647 .map(|key_operation: &KeyOperation| cose::debug_key_operation(*key_operation))
648 .collect::<Vec<_>>(),
649 );
650 #[cfg(feature = "dangerous-crypto-debug")]
651 debug_struct.field("key", &hex::encode(self.enc_key.as_slice()));
652 debug_struct.finish()
653 }
654}
655
656fn pad_key(key_bytes: &mut Vec<u8>, min_length: u8) {
667 crate::keys::utils::pad_bytes(key_bytes, min_length as usize)
668 .expect("Padding cannot fail since the min_length is < 255")
669}
670
671fn unpad_key(key_bytes: &[u8]) -> Result<&[u8], CryptoError> {
682 crate::keys::utils::unpad_bytes(key_bytes).map_err(|_| CryptoError::InvalidKey)
683}
684
685pub enum EncodedSymmetricKey {
687 BitwardenLegacyKey(BitwardenLegacyKeyBytes),
689 CoseKey(CoseKeyBytes),
691}
692impl From<EncodedSymmetricKey> for Vec<u8> {
693 fn from(val: EncodedSymmetricKey) -> Self {
694 match val {
695 EncodedSymmetricKey::BitwardenLegacyKey(key) => key.to_vec(),
696 EncodedSymmetricKey::CoseKey(key) => key.to_vec(),
697 }
698 }
699}
700impl EncodedSymmetricKey {
701 #[allow(private_interfaces)]
703 pub fn content_format(&self) -> ContentFormat {
704 match self {
705 EncodedSymmetricKey::BitwardenLegacyKey(_) => ContentFormat::BitwardenLegacyKey,
706 EncodedSymmetricKey::CoseKey(_) => ContentFormat::CoseKey,
707 }
708 }
709}
710
711impl<'de> Deserialize<'de> for SymmetricCryptoKey {
716 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
717 where
718 D: serde::Deserializer<'de>,
719 {
720 deserializer.deserialize_str(FromStrVisitor::new())
721 }
722}
723
724impl FromStr for SymmetricCryptoKey {
725 type Err = CryptoError;
726
727 fn from_str(s: &str) -> Result<Self, Self::Err> {
728 let bytes = B64::try_from(s.to_string()).map_err(|_| CryptoError::InvalidKey)?;
729 Self::try_from(bytes).map_err(|_| CryptoError::InvalidKey)
730 }
731}
732
733impl Serialize for SymmetricCryptoKey {
734 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
735 where
736 S: serde::Serializer,
737 {
738 serializer.serialize_str(&self.to_base64().to_string())
739 }
740}
741
742#[cfg(test)]
744pub fn derive_symmetric_key(name: &str) -> Aes256CbcHmacKey {
745 use zeroize::Zeroizing;
746
747 use crate::{derive_shareable_key, generate_random_bytes};
748
749 let secret: Zeroizing<[u8; 16]> = generate_random_bytes();
750 derive_shareable_key(secret, name, None)
751}
752
753#[cfg(test)]
754mod tests {
755 use bitwarden_encoding::B64;
756 use coset::iana::KeyOperation;
757 use hybrid_array::Array;
758 use typenum::U32;
759
760 use super::{SymmetricCryptoKey, derive_symmetric_key};
761 use crate::{
762 Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, CoseKeyThumbprintExt,
763 XChaCha20Poly1305Key,
764 keys::{
765 KeyId,
766 symmetric_crypto_key::{pad_key, unpad_key},
767 },
768 };
769
770 #[test]
771 #[ignore = "Manual test to verify debug format"]
772 fn test_key_debug() {
773 let aes_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
774 println!("{:?}", aes_key);
775 let xchacha_key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
776 println!("{:?}", xchacha_key);
777 }
778
779 #[test]
780 fn test_serialize_deserialize_symmetric_crypto_key() {
781 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
782 let serialized = serde_json::to_string(&key).unwrap();
783 let deserialized: SymmetricCryptoKey = serde_json::from_str(&serialized).unwrap();
784 assert_eq!(key, deserialized);
785 }
786
787 #[test]
788 fn test_symmetric_crypto_key() {
789 let key = SymmetricCryptoKey::Aes256CbcHmacKey(derive_symmetric_key("test"));
790 let key2 = SymmetricCryptoKey::try_from(key.to_base64()).unwrap();
791
792 assert_eq!(key, key2);
793
794 let key = "UY4B5N4DA4UisCNClgZtRr6VLy9ZF5BXXC7cDZRqourKi4ghEMgISbCsubvgCkHf5DZctQjVot11/vVvN9NNHQ==".to_string();
795 let key2 = SymmetricCryptoKey::try_from(key.clone()).unwrap();
796 assert_eq!(key, key2.to_base64().to_string());
797 }
798
799 #[test]
800 fn test_encode_decode_old_symmetric_crypto_key() {
801 let key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
802 let encoded = key.to_encoded();
803 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
804 assert_eq!(key, decoded);
805 }
806
807 #[test]
808 fn test_decode_new_symmetric_crypto_key() {
809 let key: B64 = ("pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB").parse()
810 .unwrap();
811 let key = BitwardenLegacyKeyBytes::from(&key);
812 let key = SymmetricCryptoKey::try_from(&key).unwrap();
813 match key {
814 SymmetricCryptoKey::XChaCha20Poly1305Key(_) => (),
815 _ => panic!("Invalid key type"),
816 }
817 }
818
819 #[test]
820 fn test_encode_xchacha20_poly1305_key() {
821 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
822 let encoded = key.to_encoded();
823 let decoded = SymmetricCryptoKey::try_from(&encoded).unwrap();
824 assert_eq!(key, decoded);
825 }
826
827 #[test]
828 fn test_pad_unpad_key_63() {
829 let original_key = vec![1u8; 63];
830 let mut key_bytes = original_key.clone();
831 let mut encoded_bytes = vec![1u8; 65];
832 encoded_bytes[63] = 2;
833 encoded_bytes[64] = 2;
834 pad_key(&mut key_bytes, 65);
835 assert_eq!(encoded_bytes, key_bytes);
836 let unpadded_key = unpad_key(&key_bytes).unwrap();
837 assert_eq!(original_key, unpadded_key);
838 }
839
840 #[test]
841 fn test_pad_unpad_key_64() {
842 let original_key = vec![1u8; 64];
843 let mut key_bytes = original_key.clone();
844 let mut encoded_bytes = vec![1u8; 65];
845 encoded_bytes[64] = 1;
846 pad_key(&mut key_bytes, 65);
847 assert_eq!(encoded_bytes, key_bytes);
848 let unpadded_key = unpad_key(&key_bytes).unwrap();
849 assert_eq!(original_key, unpadded_key);
850 }
851
852 #[test]
853 fn test_pad_unpad_key_65() {
854 let original_key = vec![1u8; 65];
855 let mut key_bytes = original_key.clone();
856 let mut encoded_bytes = vec![1u8; 66];
857 encoded_bytes[65] = 1;
858 pad_key(&mut key_bytes, 65);
859 assert_eq!(encoded_bytes, key_bytes);
860 let unpadded_key = unpad_key(&key_bytes).unwrap();
861 assert_eq!(original_key, unpadded_key);
862 }
863
864 #[test]
865 fn test_eq_aes_cbc_hmac() {
866 let key1 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
867 let key2 = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
868 assert_ne!(key1, key2);
869 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
870 assert_eq!(key1, key3);
871 }
872
873 #[test]
874 fn test_eq_aes_cbc() {
875 let key1 =
876 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![1u8; 32])).unwrap();
877 let key2 =
878 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(vec![2u8; 32])).unwrap();
879 assert_ne!(key1, key2);
880 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
881 assert_eq!(key1, key3);
882 }
883
884 #[test]
885 fn test_eq_xchacha20_poly1305() {
886 let key1 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
887 let key2 = SymmetricCryptoKey::make_xchacha20_poly1305_key();
888 assert_ne!(key1, key2);
889 let key3 = SymmetricCryptoKey::try_from(key1.to_base64()).unwrap();
890 assert_eq!(key1, key3);
891 }
892
893 #[test]
894 fn test_neq_different_key_types() {
895 let key1 = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey {
896 enc_key: Box::pin(Array::<u8, U32>::default()),
897 });
898 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(XChaCha20Poly1305Key {
899 enc_key: Box::pin(Array::<u8, U32>::default()),
900 key_id: KeyId::from([0; 16]),
901 supported_operations: vec![
902 KeyOperation::Decrypt,
903 KeyOperation::Encrypt,
904 KeyOperation::WrapKey,
905 KeyOperation::UnwrapKey,
906 ],
907 });
908 assert_ne!(key1, key2);
909 }
910
911 #[test]
912 fn test_eq_variant_aes256_cbc() {
913 let key1 = Aes256CbcKey {
914 enc_key: Box::pin(Array::from([1u8; 32])),
915 };
916 let key2 = Aes256CbcKey {
917 enc_key: Box::pin(Array::from([1u8; 32])),
918 };
919 let key3 = Aes256CbcKey {
920 enc_key: Box::pin(Array::from([2u8; 32])),
921 };
922 assert_eq!(key1, key2);
923 assert_ne!(key1, key3);
924 }
925
926 #[test]
927 fn test_eq_variant_aes256_cbc_hmac() {
928 let key1 = Aes256CbcHmacKey {
929 enc_key: Box::pin(Array::from([1u8; 32])),
930 mac_key: Box::pin(Array::from([2u8; 32])),
931 };
932 let key2 = Aes256CbcHmacKey {
933 enc_key: Box::pin(Array::from([1u8; 32])),
934 mac_key: Box::pin(Array::from([2u8; 32])),
935 };
936 let key3 = Aes256CbcHmacKey {
937 enc_key: Box::pin(Array::from([3u8; 32])),
938 mac_key: Box::pin(Array::from([4u8; 32])),
939 };
940 assert_eq!(key1, key2);
941 assert_ne!(key1, key3);
942 }
943
944 #[test]
945 fn test_eq_variant_xchacha20_poly1305() {
946 let key1 = XChaCha20Poly1305Key {
947 enc_key: Box::pin(Array::from([1u8; 32])),
948 key_id: KeyId::from([0; 16]),
949 supported_operations: vec![
950 KeyOperation::Decrypt,
951 KeyOperation::Encrypt,
952 KeyOperation::WrapKey,
953 KeyOperation::UnwrapKey,
954 ],
955 };
956 let key2 = XChaCha20Poly1305Key {
957 enc_key: Box::pin(Array::from([1u8; 32])),
958 key_id: KeyId::from([0; 16]),
959 supported_operations: vec![
960 KeyOperation::Decrypt,
961 KeyOperation::Encrypt,
962 KeyOperation::WrapKey,
963 KeyOperation::UnwrapKey,
964 ],
965 };
966 let key3 = XChaCha20Poly1305Key {
967 enc_key: Box::pin(Array::from([2u8; 32])),
968 key_id: KeyId::from([1; 16]),
969 supported_operations: vec![
970 KeyOperation::Decrypt,
971 KeyOperation::Encrypt,
972 KeyOperation::WrapKey,
973 KeyOperation::UnwrapKey,
974 ],
975 };
976 assert_eq!(key1, key2);
977 assert_ne!(key1, key3);
978 }
979
980 #[test]
981 fn test_neq_different_key_id() {
982 let key1 = XChaCha20Poly1305Key {
983 enc_key: Box::pin(Array::<u8, U32>::default()),
984 key_id: KeyId::from([0; 16]),
985 supported_operations: vec![
986 KeyOperation::Decrypt,
987 KeyOperation::Encrypt,
988 KeyOperation::WrapKey,
989 KeyOperation::UnwrapKey,
990 ],
991 };
992 let key2 = XChaCha20Poly1305Key {
993 enc_key: Box::pin(Array::<u8, U32>::default()),
994 key_id: KeyId::from([1; 16]),
995 supported_operations: vec![
996 KeyOperation::Decrypt,
997 KeyOperation::Encrypt,
998 KeyOperation::WrapKey,
999 KeyOperation::UnwrapKey,
1000 ],
1001 };
1002 assert_ne!(key1, key2);
1003
1004 let key1 = SymmetricCryptoKey::XChaCha20Poly1305Key(key1);
1005 let key2 = SymmetricCryptoKey::XChaCha20Poly1305Key(key2);
1006 assert_ne!(key1, key2);
1007 }
1008
1009 const AES256_GCM_KEY: &str =
1010 "pQEEAlACAgICAgICAgICAgICAgICAwMEhAMEBQYgWCABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=";
1011 const AES256_GCM_KEY_THUMBPRINT: &str =
1012 "3810c7275ee292caca13d938a057a94c75210087d960d3eb6868c0ffe99b5643";
1013
1014 const XCHACHA20_POLY1305_KEY: &str = "pQEEAlDib+JxbqMBlcd3KTUesbufAzoAARFvBIQDBAUGIFggt79surJXmqhPhYuuqi9ZyPfieebmtw2OsmN5SDrb4yUB";
1015 const XCHACHA20_POLY1305_KEY_THUMBPRINT: &str =
1016 "64aec2d09ef5ba8b310ef9a70346b03422443e295b6f045e38169ae97e579d85";
1017
1018 #[test]
1019 fn test_decode_new_aes256_gcm_key() {
1020 let key: B64 = AES256_GCM_KEY.parse().unwrap();
1021 let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1022 match key {
1023 SymmetricCryptoKey::Aes256GcmKey(_) => (),
1024 _ => panic!("Invalid key type"),
1025 }
1026 }
1027
1028 #[test]
1029 fn test_thumbprint_aes256_gcm_vector() {
1030 let key: B64 = AES256_GCM_KEY.parse().unwrap();
1032 let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1033 assert_eq!(
1034 key.thumbprint().unwrap().to_hex(),
1035 AES256_GCM_KEY_THUMBPRINT
1036 );
1037 }
1038
1039 #[test]
1040 fn test_thumbprint_xchacha20_poly1305_vector() {
1041 let key: B64 = XCHACHA20_POLY1305_KEY.parse().unwrap();
1043 let key = SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(&key)).unwrap();
1044 assert_eq!(
1045 key.thumbprint().unwrap().to_hex(),
1046 XCHACHA20_POLY1305_KEY_THUMBPRINT
1047 );
1048 }
1049
1050 #[test]
1051 fn test_thumbprint_is_deterministic() {
1052 let key = SymmetricCryptoKey::make_xchacha20_poly1305_key();
1053 assert_eq!(key.thumbprint().unwrap(), key.thumbprint().unwrap());
1054 }
1055
1056 #[test]
1057 fn test_thumbprint_errors_for_legacy_aes_cbc() {
1058 assert!(
1059 SymmetricCryptoKey::make_aes256_cbc_hmac_key()
1060 .thumbprint()
1061 .is_err()
1062 );
1063 }
1064}