bitwarden_crypto/
content_format.rs

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