bitwarden_crypto/safe/
mod.rs1#![doc = include_str!("./README.md")]
2
3mod password_protected_key_envelope;
4pub use password_protected_key_envelope::*;
5mod high_entropy_secret;
6pub use high_entropy_secret::*;
7mod secret_protected_key_envelope;
8pub use secret_protected_key_envelope::*;
9mod symmetric_key_envelope;
10pub use symmetric_key_envelope::*;
11mod data_envelope;
12pub use data_envelope::*;
13mod key_hierarchy;
14pub use key_hierarchy::*;
15mod helpers;
16
17use ciborium::Value;
18
19use crate::{
20 BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, EncodedSymmetricKey, KEY_ID_SIZE,
21 SymmetricCryptoKey,
22 cose::{CONTAINED_KEY_ID, extract_bytes},
23 keys::KeyId,
24 safe::helpers::set_header_value,
25};
26
27pub(super) enum DecodeSealedKeyError {
30 InvalidContentFormat,
32 UnsupportedContentFormat,
34 InvalidKey,
36}
37
38pub(super) fn decode_sealed_symmetric_key(
46 header: &coset::Header,
47 key_bytes: Vec<u8>,
48) -> Result<SymmetricCryptoKey, DecodeSealedKeyError> {
49 let encoded_key = match ContentFormat::try_from(header)
50 .map_err(|_| DecodeSealedKeyError::InvalidContentFormat)?
51 {
52 ContentFormat::BitwardenLegacyKey => {
53 EncodedSymmetricKey::BitwardenLegacyKey(BitwardenLegacyKeyBytes::from(key_bytes))
54 }
55 ContentFormat::CoseKey => EncodedSymmetricKey::CoseKey(CoseKeyBytes::from(key_bytes)),
56 _ => return Err(DecodeSealedKeyError::UnsupportedContentFormat),
57 };
58 SymmetricCryptoKey::try_from(encoded_key).map_err(|_| DecodeSealedKeyError::InvalidKey)
59}
60
61pub(super) fn extract_single_recipient(
66 cose_encrypt: &coset::CoseEncrypt,
67) -> Result<&coset::CoseRecipient, ()> {
68 match cose_encrypt.recipients.as_slice() {
69 [recipient] => Ok(recipient),
70 _ => Err(()),
71 }
72}
73
74pub(super) fn extract_key_id(header: &coset::Header) -> Result<Option<KeyId>, ()> {
77 let key_id_bytes = extract_bytes(header, CONTAINED_KEY_ID, "key id");
78
79 if let Ok(bytes) = key_id_bytes {
80 let key_id_array: [u8; KEY_ID_SIZE] = bytes.as_slice().try_into().map_err(|_| ())?;
81 Ok(Some(KeyId::from(key_id_array)))
82 } else {
83 Ok(None)
84 }
85}
86
87pub(super) fn set_contained_key_id(header: &mut coset::Header, key_id: Option<KeyId>) {
89 if let Some(key_id) = key_id {
90 set_header_value(header, CONTAINED_KEY_ID, Value::from(Vec::from(&key_id)));
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn set_contained_key_id_round_trips_through_extract_key_id() {
100 let key_id = KeyId::from([7u8; KEY_ID_SIZE]);
101 let mut header = coset::HeaderBuilder::new().build();
102
103 set_contained_key_id(&mut header, Some(key_id.clone()));
104
105 assert_eq!(extract_key_id(&header), Ok(Some(key_id)));
106 }
107
108 #[test]
109 fn extract_key_id_returns_none_when_absent() {
110 let header = coset::HeaderBuilder::new().build();
111
112 assert_eq!(extract_key_id(&header), Ok(None));
113 }
114
115 #[test]
116 fn set_contained_key_id_with_none_leaves_header_without_key_id() {
117 let mut header = coset::HeaderBuilder::new().build();
118
119 set_contained_key_id(&mut header, None);
120
121 assert_eq!(extract_key_id(&header), Ok(None));
122 }
123}