bitwarden_crypto/
content_format.rs

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