Skip to main content

bitwarden_crypto/
content_format.rs

1use bitwarden_encoding::B64;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    CryptoError, EncString, KeyEncryptable, KeyEncryptableWithContentType, KeySlotIds,
6    KeyStoreContext, PrimitiveEncryptable, SymmetricCryptoKey,
7    traits::PrimitiveEncryptableWithContentType,
8};
9
10/// The content format describes the format of the contained bytes. Message encryption always
11/// happens on the byte level, and this allows determining what format the contained data has. For
12/// instance, an `EncString` in most cases contains UTF-8 encoded text. In some cases it may contain
13/// a Pkcs8 private key, or a COSE key. Specifically, for COSE keys, this allows distinguishing
14/// between the old symmetric key format, represented as `ContentFormat::OctetStream`, and the new
15/// COSE key format, represented as `ContentFormat::CoseKey`.
16#[derive(Clone, Copy, Debug, PartialEq)]
17pub(crate) enum ContentFormat {
18    /// UTF-8 encoded text
19    Utf8,
20    /// Pkcs8 private key DER
21    Pkcs8PrivateKey,
22    /// SPKI public key DER
23    SPKIPublicKeyDer,
24    /// COSE serialized CoseKey
25    CoseKey,
26    /// CoseSign1 message
27    CoseSign1,
28    /// CoseEncrypt0 message
29    CoseEncrypt0,
30    /// Bitwarden Legacy Key
31    /// There are three permissible byte values here:
32    /// - `[u8; 32]` - AES-CBC (no hmac) key. This is to be removed and banned.
33    /// - `[u8; 64]` - AES-CBC with HMAC key. This is the v1 userkey key type
34    /// - `[u8; >64]` - COSE key. Padded to be larger than 64 bytes.
35    BitwardenLegacyKey,
36    /// Stream of bytes
37    OctetStream,
38    /// Cbor serialized data
39    Cbor,
40}
41
42mod private {
43    /// This trait is used to seal the `ConstContentFormat` trait, preventing external
44    /// implementations.
45    pub trait Sealed {}
46}
47
48/// This trait is used to instantiate different typed byte vectors with a specific content format,
49/// using `SerializedBytes<C>`. This allows for compile-time guarantees about the content format
50/// of the serialized bytes. The exception here is the escape hatch using e.g. `from(Vec<u8>)`,
51/// which can still be mis-used, but has to be misused explicitly.
52pub trait ConstContentFormat: private::Sealed {
53    /// Returns the content format as a `ContentFormat` enum.
54    #[allow(private_interfaces)]
55    fn content_format() -> ContentFormat;
56}
57
58/// A serialized byte array with a specific content format. This is used to represent data that has
59/// a specific format, such as UTF-8 encoded text, raw bytes, or COSE keys. The content
60/// format is used to determine how the bytes should be interpreted when encrypting or decrypting
61/// the data.
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct Bytes<C: ConstContentFormat> {
64    inner: Vec<u8>,
65    _marker: std::marker::PhantomData<C>,
66}
67
68impl<C: ConstContentFormat> From<Vec<u8>> for Bytes<C> {
69    fn from(inner: Vec<u8>) -> Self {
70        Self {
71            inner,
72            _marker: std::marker::PhantomData,
73        }
74    }
75}
76
77impl<C: ConstContentFormat> From<&[u8]> for Bytes<C> {
78    fn from(inner: &[u8]) -> Self {
79        Self::from(inner.to_vec())
80    }
81}
82
83impl<C: ConstContentFormat + FromB64ContentFormat> From<&B64> for Bytes<C> {
84    fn from(val: &B64) -> Self {
85        Self::from(val.as_bytes())
86    }
87}
88
89impl<C: ConstContentFormat + FromB64ContentFormat> From<Bytes<C>> for B64 {
90    fn from(val: Bytes<C>) -> Self {
91        B64::from(val.as_ref())
92    }
93}
94
95impl<C: ConstContentFormat> AsRef<[u8]> for Bytes<C> {
96    fn as_ref(&self) -> &[u8] {
97        &self.inner
98    }
99}
100
101impl<C: ConstContentFormat> Bytes<C> {
102    /// Returns the serialized bytes as a `Vec<u8>`.
103    pub fn to_vec(&self) -> Vec<u8> {
104        self.inner.clone()
105    }
106}
107
108/// Content format for UTF-8 encoded text. Used for most text messages.
109#[derive(PartialEq, Eq, Clone, Debug)]
110pub(crate) struct Utf8ContentFormat;
111impl private::Sealed for Utf8ContentFormat {}
112impl ConstContentFormat for Utf8ContentFormat {
113    fn content_format() -> ContentFormat {
114        ContentFormat::Utf8
115    }
116}
117/// Utf8Bytes is a type alias for Bytes with `Utf8ContentFormat`, which is used for any textual
118/// data.
119pub(crate) type Utf8Bytes = Bytes<Utf8ContentFormat>;
120
121/// Content format for raw bytes. Used for attachments and send seed keys.
122#[derive(PartialEq, Eq, Clone, Debug)]
123pub struct OctetStreamContentFormat;
124impl private::Sealed for OctetStreamContentFormat {}
125impl ConstContentFormat for OctetStreamContentFormat {
126    #[allow(private_interfaces)]
127    fn content_format() -> ContentFormat {
128        ContentFormat::OctetStream
129    }
130}
131/// OctetStreamBytes is a type alias for Bytes with `OctetStreamContentFormat`. This should be used
132/// for e.g. attachments and other data without an explicit content format.
133pub type OctetStreamBytes = Bytes<OctetStreamContentFormat>;
134
135/// Content format for PKCS8 private keys in DER format.
136#[derive(PartialEq, Eq, Clone, Debug)]
137pub struct Pkcs8PrivateKeyDerContentFormat;
138impl private::Sealed for Pkcs8PrivateKeyDerContentFormat {}
139impl ConstContentFormat for Pkcs8PrivateKeyDerContentFormat {
140    #[allow(private_interfaces)]
141    fn content_format() -> ContentFormat {
142        ContentFormat::Pkcs8PrivateKey
143    }
144}
145/// Pkcs8PrivateKeyBytes is a type alias for Bytes with `Pkcs8PrivateKeyDerContentFormat`. This is
146/// used for PKCS8 private keys in DER format.
147pub type Pkcs8PrivateKeyBytes = Bytes<Pkcs8PrivateKeyDerContentFormat>;
148
149/// Content format for SPKI public keys in DER format.
150#[derive(PartialEq, Eq, Clone, Debug)]
151pub struct SpkiPublicKeyDerContentFormat;
152impl private::Sealed for SpkiPublicKeyDerContentFormat {}
153impl ConstContentFormat for SpkiPublicKeyDerContentFormat {
154    #[allow(private_interfaces)]
155    fn content_format() -> ContentFormat {
156        ContentFormat::SPKIPublicKeyDer
157    }
158}
159impl FromB64ContentFormat for SpkiPublicKeyDerContentFormat {}
160/// SpkiPublicKeyBytes is a type alias for Bytes with `SpkiPublicKeyDerContentFormat`. This is used
161/// for SPKI public keys in DER format.
162pub type SpkiPublicKeyBytes = Bytes<SpkiPublicKeyDerContentFormat>;
163
164/// A marker trait for COSE content formats.
165pub trait CoseContentFormat {}
166
167/// A marker trait for content formats that can be converted from B64.
168pub trait FromB64ContentFormat {}
169
170/// Content format for COSE keys.
171#[derive(PartialEq, Eq, Clone, Debug)]
172pub struct CoseKeyContentFormat;
173impl private::Sealed for CoseKeyContentFormat {}
174impl ConstContentFormat for CoseKeyContentFormat {
175    #[allow(private_interfaces)]
176    fn content_format() -> ContentFormat {
177        ContentFormat::CoseKey
178    }
179}
180impl CoseContentFormat for CoseKeyContentFormat {}
181impl FromB64ContentFormat for CoseKeyContentFormat {}
182/// CoseKeyBytes is a type alias for Bytes with `CoseKeyContentFormat`. This is used for serialized
183/// CoseKey objects.
184pub type CoseKeyBytes = Bytes<CoseKeyContentFormat>;
185
186/// A legacy content format for Bitwarden keys. See `ContentFormat::BitwardenLegacyKey`
187#[derive(PartialEq, Eq, Clone, Debug)]
188pub struct BitwardenLegacyKeyContentFormat;
189impl private::Sealed for BitwardenLegacyKeyContentFormat {}
190impl ConstContentFormat for BitwardenLegacyKeyContentFormat {
191    #[allow(private_interfaces)]
192    fn content_format() -> ContentFormat {
193        ContentFormat::BitwardenLegacyKey
194    }
195}
196impl FromB64ContentFormat for BitwardenLegacyKeyContentFormat {}
197/// BitwardenLegacyKeyBytes is a type alias for Bytes with `BitwardenLegacyKeyContentFormat`. This
198/// is used for the legacy format for symmetric keys. A description of the format is available in
199/// the `ContentFormat::BitwardenLegacyKey` documentation.
200pub type BitwardenLegacyKeyBytes = Bytes<BitwardenLegacyKeyContentFormat>;
201
202/// Content format for COSE Sign1 messages.
203#[derive(PartialEq, Eq, Clone, Debug)]
204pub struct CoseSign1ContentFormat;
205impl private::Sealed for CoseSign1ContentFormat {}
206impl ConstContentFormat for CoseSign1ContentFormat {
207    #[allow(private_interfaces)]
208    fn content_format() -> ContentFormat {
209        ContentFormat::CoseSign1
210    }
211}
212impl CoseContentFormat for CoseSign1ContentFormat {}
213impl FromB64ContentFormat for CoseSign1ContentFormat {}
214/// CoseSign1Bytes is a type alias for Bytes with `CoseSign1ContentFormat`. This is used for
215/// serialized COSE Sign1 messages.
216pub type CoseSign1Bytes = Bytes<CoseSign1ContentFormat>;
217
218/// CBOR serialized data
219#[derive(PartialEq, Eq, Clone, Debug)]
220pub struct CborContentFormat;
221impl private::Sealed for CborContentFormat {}
222impl ConstContentFormat for CborContentFormat {
223    #[allow(private_interfaces)]
224    fn content_format() -> ContentFormat {
225        ContentFormat::Cbor
226    }
227}
228/// CborBytes is a type alias for Bytes with `CborContentFormat`. This is used for CBOR serialized
229/// data.
230pub type CborBytes = Bytes<CborContentFormat>;
231
232/// Content format for COSE Encrypt0 messages.
233#[derive(PartialEq, Eq, Clone, Debug)]
234pub struct CoseEncrypt0ContentFormat;
235impl private::Sealed for CoseEncrypt0ContentFormat {}
236impl ConstContentFormat for CoseEncrypt0ContentFormat {
237    #[allow(private_interfaces)]
238    fn content_format() -> ContentFormat {
239        ContentFormat::CoseEncrypt0
240    }
241}
242/// CoseEncrypt0Bytes is a type alias for Bytes with `CoseEncrypt0ContentFormat`. This is used for
243/// serialized COSE Encrypt0 messages.
244pub type CoseEncrypt0Bytes = Bytes<CoseEncrypt0ContentFormat>;
245
246impl<Ids: KeySlotIds, T: ConstContentFormat> PrimitiveEncryptable<Ids, Ids::Symmetric, EncString>
247    for Bytes<T>
248{
249    fn encrypt(
250        &self,
251        ctx: &mut KeyStoreContext<Ids>,
252        key: Ids::Symmetric,
253    ) -> Result<EncString, CryptoError> {
254        self.inner.encrypt(ctx, key, T::content_format())
255    }
256}
257
258impl<T: ConstContentFormat> KeyEncryptable<SymmetricCryptoKey, EncString> for &Bytes<T> {
259    fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString, CryptoError> {
260        self.as_ref().encrypt_with_key(key, T::content_format())
261    }
262}
263
264impl From<String> for Bytes<Utf8ContentFormat> {
265    fn from(val: String) -> Self {
266        Bytes::from(val.into_bytes())
267    }
268}
269
270impl From<&str> for Bytes<Utf8ContentFormat> {
271    fn from(val: &str) -> Self {
272        Bytes::from(val.as_bytes().to_vec())
273    }
274}