Skip to main content

bitwarden_crypto/safe/
mod.rs

1#![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
27/// Failure modes when decoding the raw key bytes recovered from a key envelope back into a
28/// [`SymmetricCryptoKey`]. See [`decode_sealed_symmetric_key`].
29pub(super) enum DecodeSealedKeyError {
30    /// The protected header did not declare a valid content format.
31    InvalidContentFormat,
32    /// The declared content format is not a supported symmetric key encoding.
33    UnsupportedContentFormat,
34    /// The decoded bytes do not form a valid symmetric key.
35    InvalidKey,
36}
37
38/// Decodes the raw key bytes recovered from a key envelope into a [`SymmetricCryptoKey`], using the
39/// content format declared in the envelope's protected `header`.
40///
41/// Shared by the key envelopes
42/// ([`PasswordProtectedKeyEnvelope`], [`SecretProtectedKeyEnvelope`], and
43/// [`SymmetricKeyEnvelope`]), which all store the wrapped key using the same content-format-tagged
44/// encoding.
45pub(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
61/// Extract the single recipient from a [`coset::CoseEncrypt`].
62///
63/// The COSE objects used by this module's envelopes always carry exactly one recipient (holding the
64/// KDF parameters). Returns an error if there is not exactly one recipient.
65pub(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
74/// Extract the contained key ID from a COSE header, if present.
75/// Only COSE keys have a key ID; legacy keys do not.
76pub(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
87/// Set the contained key ID on a COSE header, if present.
88pub(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}