Skip to content

[RFC] Implement data envelope #336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 128 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
bc5900b
Implement cose content format
quexten May 9, 2025
2e905c4
Cargo fmt
quexten May 9, 2025
b41f5ef
Fix docs
quexten May 9, 2025
eb70dfc
Fix formatting
quexten May 9, 2025
1e2e95c
Merge main
quexten May 9, 2025
68be6e6
Fix formatting
quexten May 9, 2025
d6a18a4
Fix comment
quexten May 9, 2025
6e9e526
Cleanup
quexten May 21, 2025
50d8f70
Switch from pass by ref to pass by value for enum
quexten May 21, 2025
e5bd251
Add CompositeEncryptable trait
quexten May 21, 2025
b92010b
Cleanup
quexten May 21, 2025
9635098
Merge branch 'main' into km/cose-content-format
quexten May 21, 2025
b8056c2
Fix typo
quexten May 21, 2025
9440825
Typed encryptable
quexten May 21, 2025
f8cb804
Apply cargo fmt
quexten May 22, 2025
e6939e6
Fix build
quexten May 22, 2025
2a202c1
Remove unused imports
quexten May 22, 2025
ba9e2c3
Rename to primitiveencryptablewithcontenttype
quexten May 22, 2025
a42b1e7
Set correct content type for pkcs8
quexten May 22, 2025
5447cbb
Add cose keywrap
quexten May 22, 2025
66c345f
Fix keywrap
quexten May 22, 2025
9b2f6c9
Run cargo fmt
quexten May 22, 2025
88b96b7
Fix encryptable
quexten May 22, 2025
7e69b7b
Run cargo fmt
quexten May 22, 2025
b7d2bc8
Cleanup
quexten May 29, 2025
acb6d12
Rename to PrimitiveEncryptableWithoutContentFormat
quexten May 29, 2025
723ec63
Rename to PrimitiveEncryptable
quexten May 29, 2025
36d8de9
Merge branch 'main' into km/cose-content-format
quexten May 29, 2025
33da07b
Add documentation for the encryptable traits
quexten May 29, 2025
2861a9a
Merge branch 'km/cose-content-format' of github.com:bitwarden/sdk-intโ€ฆ
quexten May 29, 2025
1681df9
Fix clippy errors
quexten Jun 2, 2025
0cd85fa
Fix docs
quexten Jun 2, 2025
f91f0b8
Cargo fmt
quexten Jun 2, 2025
34ee00e
Fix docs
quexten Jun 2, 2025
485b6d8
Fix docs
quexten Jun 2, 2025
f122ff0
Fix docs
quexten Jun 2, 2025
f22531c
Update crates/bitwarden-crypto/src/keys/utils.rs
quexten Jun 4, 2025
ba10631
Update crates/bitwarden-crypto/src/keys/utils.rs
quexten Jun 4, 2025
69d8c57
Update crates/bitwarden-crypto/src/keys/utils.rs
quexten Jun 4, 2025
8833146
Merge branch 'main' into km/cose-content-format
quexten Jun 4, 2025
47ced5a
Merge branch 'main' into km/cose-content-format
quexten Jun 6, 2025
42dccb0
Add docs
quexten Jun 6, 2025
9d0ea55
Merge branch 'km/cose-content-format' of github.com:bitwarden/sdk-intโ€ฆ
quexten Jun 6, 2025
a3ed9d6
Merge branch 'main' into km/cose-content-format
quexten Jun 16, 2025
9eceb32
Cargo fmt
quexten Jun 16, 2025
f30c3ce
Apply fixes
quexten Jun 16, 2025
5f4dc3a
Cleanup
quexten Jun 16, 2025
7b17ca3
Cleanup
quexten Jun 16, 2025
38c8945
Cleanup
quexten Jun 16, 2025
b5dd862
Apply more fixes
quexten Jun 16, 2025
47c7764
Apply fixes
quexten Jun 16, 2025
b009c81
Apply fixes
quexten Jun 16, 2025
c9f6111
Update test vector to include content type
quexten Jun 17, 2025
b9b0f6e
Add bitwarden legacy content type
quexten Jun 17, 2025
67dd5e9
Move content format to separate file
quexten Jun 17, 2025
7635cd0
Remove unused import
quexten Jun 17, 2025
ec14e51
Fix missing parsing for content type
quexten Jun 17, 2025
75be6db
Merge branch 'main' into km/cose-content-format
quexten Jun 17, 2025
6d8bb8f
Fix doc error
quexten Jun 17, 2025
38b8958
Typed byte arrays
quexten Jun 19, 2025
9f334fb
Fix readme
quexten Jun 19, 2025
02cc7b3
Clippy cleanup
quexten Jun 19, 2025
f145eb3
Fix clippy errors
quexten Jun 19, 2025
99d909c
Fix clippy errors
quexten Jun 19, 2025
d47e8c0
Switch bitwarden symmetric crypto key bytes to serialized bytes generic
quexten Jun 19, 2025
1920a49
Rename to bytes
quexten Jun 19, 2025
625b830
Cargo fmt
quexten Jun 19, 2025
24f1431
Simplify encoded symmetric key
quexten Jun 19, 2025
9eb4ff8
Merge branch 'main' into km/cose-content-format
quexten Jun 19, 2025
916a46e
Remove unwrap in example
quexten Jun 19, 2025
8722077
Merge branch 'km/cose-content-format' of github.com:bitwarden/sdk-intโ€ฆ
quexten Jun 19, 2025
670c6a7
Cleanup
quexten Jun 19, 2025
6da4a0b
Fix docs
quexten Jun 19, 2025
4e5510b
Merge branch 'main' into km/cose-content-format
quexten Jun 23, 2025
b00f48b
Make content format trait sealed and add type aliases
quexten Jun 24, 2025
bf9f8c4
Merge branch 'km/cose-content-format' of github.com:bitwarden/sdk-intโ€ฆ
quexten Jun 24, 2025
235f3bc
Apply cargo fmt
quexten Jun 24, 2025
1953ba3
Apply fixes
quexten Jun 24, 2025
41bb1ad
Replace non type aliased Bytes references with type aliases
quexten Jun 24, 2025
c41e513
Apply clippy fixes
quexten Jun 24, 2025
90d2295
Update crates/bitwarden-crypto/src/enc_string/symmetric.rs
quexten Jun 25, 2025
de8f957
Update crates/bitwarden-crypto/src/keys/signed_public_key.rs
quexten Jun 25, 2025
f6ad513
Update crates/bitwarden-crypto/src/enc_string/symmetric.rs
quexten Jun 25, 2025
3c2984b
Update crates/bitwarden-crypto/src/fingerprint.rs
quexten Jun 25, 2025
bdc90b3
Update crates/bitwarden-crypto/src/signing/signed_object.rs
quexten Jun 25, 2025
80dde40
Update crates/bitwarden-crypto/src/signing/signed_object.rs
quexten Jun 25, 2025
1f30896
Update crates/bitwarden-crypto/README.md
quexten Jun 25, 2025
1817ae0
Update crates/bitwarden-core/src/key_management/crypto.rs
quexten Jun 25, 2025
416dbf5
Update crates/bitwarden-core/src/client/encryption_settings.rs
quexten Jun 25, 2025
aaad503
Fix build and cleanup
quexten Jun 25, 2025
2e21906
Remove to_vec from VerifyingKey usages
quexten Jun 25, 2025
dbdee15
Undo take
quexten Jun 25, 2025
375fd0d
Unapply allow missing docs
quexten Jun 25, 2025
fd22ded
Clean up KeyEncryptable or pin key
quexten Jun 25, 2025
7ee0278
Cleanup
quexten Jun 25, 2025
9b3549a
Apply cleanup
quexten Jun 25, 2025
a5abaae
Undo changes to crypto init
quexten Jun 25, 2025
81138ea
Apply allow private interfaces to content format
quexten Jun 25, 2025
4445aec
Cleanup
quexten Jun 25, 2025
25d1907
Typesafe base64 handling
quexten Jun 25, 2025
0d0f59b
Cleanup
quexten Jun 25, 2025
e3ee279
Cleanup cose sign1 types
quexten Jun 25, 2025
bf7bc82
Revert "Typesafe base64 handling"
quexten Jun 26, 2025
1da0173
Revert "Cleanup"
quexten Jun 26, 2025
87f87a6
Cleanup
quexten Jun 26, 2025
15ffca9
Clippy fix
quexten Jun 26, 2025
144620f
Move use under internal flag
quexten Jun 26, 2025
53aeeee
Merge branch 'main' into km/cose-content-format
quexten Jun 26, 2025
81b5a15
Update crates/bitwarden-core/src/client/encryption_settings.rs
quexten Jun 26, 2025
9427634
Update crates/bitwarden-core/src/client/encryption_settings.rs
quexten Jun 26, 2025
d1f8029
Move cose content format trait higher
quexten Jun 26, 2025
7f52fb0
Add docs
quexten Jun 26, 2025
53c528b
Update crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs
quexten Jun 26, 2025
c51e779
Update crates/bitwarden-crypto/src/keys/device_key.rs
quexten Jun 26, 2025
6c8092a
Update crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs
quexten Jun 26, 2025
954a6a5
Clean up symmetric crypto key
quexten Jun 26, 2025
948baa5
Merge branch 'km/cose-content-format' of github.com:bitwarden/sdk-intโ€ฆ
quexten Jun 26, 2025
b2f5211
Fix encryptable docs
quexten Jun 26, 2025
f0b6ec5
Cleanup
quexten Jun 26, 2025
390463c
Small cleanup
quexten Jun 26, 2025
02ce4d9
tmp
quexten Jun 27, 2025
25f50dc
Impl and add examples
quexten Jun 27, 2025
98db364
Cargo fmt
quexten Jun 27, 2025
500825b
Fix warnings
quexten Jun 27, 2025
f9ac297
Improve errors
quexten Jun 27, 2025
f5d2c72
Cleanup
quexten Jun 27, 2025
f9d4a26
Fix cargo clippy
quexten Jun 27, 2025
0c60bcc
Merge branch 'main' into km/beeep/safe-data-envelope
quexten Jul 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions crates/bitwarden-crypto/examples/seal_struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! This example demonstrates how to seal a piece of data.
//!
//! If there is a struct that should be kept secret, in can be sealed with a `DataEnvelope`. This
//! will automatically create a content-encryption-key. This is useful because the key is stored
//! separately. Rotating the encrypting key now only requires re-uploading the
//! content-encryption-key instead of the entire data. Further, server-side tampering (swapping of
//! individual fields encrypted by the same key) is prevented.
//!
//! In general, if a struct of data should be protected, the `DataEnvelope` should be used.

use bitwarden_crypto::{key_ids, SealableData};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct MyItem {
a: u64,
b: String,
}
impl SealableData for MyItem {}

fn main() {
let store = bitwarden_crypto::KeyStore::<ExampleIds>::default();
let mut ctx: bitwarden_crypto::KeyStoreContext<'_, ExampleIds> = store.context_mut();
let mut disk = MockDisk::new();

let my_item = MyItem {
a: 42,
b: "Hello, World!".to_string(),
};
// Seal the item into an encrypted blob, and store the content-encryption-key in the context.
let sealed_item = bitwarden_crypto::DataEnvelope::<ExampleIds>::seal(
my_item,
ExampleSymmetricKey::ItemKey,
&mut ctx,
)
.expect("Sealing should work");

// Store the sealed item on disk
disk.save("sealed_item", (&sealed_item).into());
let sealed_item = disk
.load("sealed_item")
.expect("Failed to load sealed item")
.clone();
let sealed_item: bitwarden_crypto::DataEnvelope<ExampleIds> =
bitwarden_crypto::DataEnvelope::from(sealed_item);

let my_item: MyItem = sealed_item
.unseal(ExampleSymmetricKey::ItemKey, &mut ctx)
.expect("Unsealing should work");
assert!(my_item.a == 42);
assert!(my_item.b == "Hello, World!");
}

pub(crate) struct MockDisk {
map: std::collections::HashMap<String, Vec<u8>>,
}

impl MockDisk {
pub(crate) fn new() -> Self {
MockDisk {
map: std::collections::HashMap::new(),
}
}

pub(crate) fn save(&mut self, key: &str, value: Vec<u8>) {
self.map.insert(key.to_string(), value);
}

pub(crate) fn load(&self, key: &str) -> Option<&Vec<u8>> {
self.map.get(key)
}
}

key_ids! {
#[symmetric]
pub enum ExampleSymmetricKey {
#[local]
ItemKey
}

#[asymmetric]
pub enum ExampleAsymmetricKey {
Key(u8),
}

#[signing]
pub enum ExampleSigningKey {
Key(u8),
}
pub ExampleIds => ExampleSymmetricKey, ExampleAsymmetricKey, ExampleSigningKey;
}
32 changes: 32 additions & 0 deletions crates/bitwarden-crypto/src/content_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
CoseKey,
/// CoseSign1 message
CoseSign1,
/// CoseEncrypt0 message
CoseEncrypt0,
/// Bitwarden Legacy Key
/// There are three permissible byte values here:
/// - `[u8; 32]` - AES-CBC (no hmac) key. This is to be removed and banned.
Expand All @@ -32,6 +34,8 @@
BitwardenLegacyKey,
/// Stream of bytes
OctetStream,
/// Cbor serialized data
Cbor,
}

mod private {
Expand Down Expand Up @@ -191,6 +195,34 @@
/// serialized COSE Sign1 messages.
pub type CoseSign1Bytes = Bytes<CoseSign1ContentFormat>;

/// CBOR serialized data
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct CborContentFormat;
impl private::Sealed for CborContentFormat {}
impl ConstContentFormat for CborContentFormat {
#[allow(private_interfaces)]
fn content_format() -> ContentFormat {
ContentFormat::Cbor
}

Check warning on line 206 in crates/bitwarden-crypto/src/content_format.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-crypto/src/content_format.rs#L204-L206

Added lines #L204 - L206 were not covered by tests
}
/// CborBytes is a type alias for Bytes with `CborContentFormat`. This is used for CBOR serialized
/// data.
pub type CborBytes = Bytes<CborContentFormat>;

/// Content format for COSE Encrypt0 messages.
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct CoseEncrypt0ContentFormat;
impl private::Sealed for CoseEncrypt0ContentFormat {}
impl ConstContentFormat for CoseEncrypt0ContentFormat {
#[allow(private_interfaces)]
fn content_format() -> ContentFormat {
ContentFormat::CoseEncrypt0
}

Check warning on line 220 in crates/bitwarden-crypto/src/content_format.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-crypto/src/content_format.rs#L218-L220

Added lines #L218 - L220 were not covered by tests
}
/// CoseEncrypt0Bytes is a type alias for Bytes with `CoseEncrypt0ContentFormat`. This is used for
/// serialized COSE Encrypt0 messages.
pub type CoseEncrypt0Bytes = Bytes<CoseEncrypt0ContentFormat>;

impl<Ids: KeyIds, T: ConstContentFormat> PrimitiveEncryptable<Ids, Ids::Symmetric, EncString>
for Bytes<T>
{
Expand Down
23 changes: 16 additions & 7 deletions crates/bitwarden-crypto/src/cose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
use crate::{
content_format::{Bytes, ConstContentFormat, CoseContentFormat},
error::{EncStringParseError, EncodingError},
xchacha20, ContentFormat, CryptoError, SymmetricCryptoKey, XChaCha20Poly1305Key,
xchacha20, ContentFormat, CoseEncrypt0Bytes, CryptoError, SymmetricCryptoKey,
XChaCha20Poly1305Key,
};

/// XChaCha20 <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03> is used over ChaCha20
Expand All @@ -38,7 +39,7 @@
plaintext: &[u8],
key: &crate::XChaCha20Poly1305Key,
content_format: ContentFormat,
) -> Result<Vec<u8>, CryptoError> {
) -> Result<CoseEncrypt0Bytes, CryptoError> {
let mut plaintext = plaintext.to_vec();

let header_builder: coset::HeaderBuilder = content_format.into();
Expand Down Expand Up @@ -68,14 +69,15 @@
cose_encrypt0
.to_vec()
.map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))
.map(CoseEncrypt0Bytes::from)
}

/// Decrypts a COSE Encrypt0 message, using a XChaCha20Poly1305 key
pub(crate) fn decrypt_xchacha20_poly1305(
cose_encrypt0_message: &[u8],
cose_encrypt0_message: &CoseEncrypt0Bytes,
key: &crate::XChaCha20Poly1305Key,
) -> Result<(Vec<u8>, ContentFormat), CryptoError> {
let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message)
let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message.as_ref())
.map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?;

let Some(ref alg) = msg.protected.header.alg else {
Expand Down Expand Up @@ -170,12 +172,16 @@
}
ContentFormat::CoseSign1 => header_builder.content_format(CoapContentFormat::CoseSign1),
ContentFormat::CoseKey => header_builder.content_format(CoapContentFormat::CoseKey),
ContentFormat::CoseEncrypt0 => {
header_builder.content_format(CoapContentFormat::CoseEncrypt0)

Check warning on line 176 in crates/bitwarden-crypto/src/cose.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-crypto/src/cose.rs#L176

Added line #L176 was not covered by tests
}
ContentFormat::BitwardenLegacyKey => {
header_builder.content_type(CONTENT_TYPE_BITWARDEN_LEGACY_KEY.to_string())
}
ContentFormat::OctetStream => {
header_builder.content_format(CoapContentFormat::OctetStream)
}
ContentFormat::Cbor => header_builder.content_format(CoapContentFormat::Cbor),
}
}
}
Expand All @@ -201,6 +207,7 @@
Some(ContentType::Assigned(CoapContentFormat::OctetStream)) => {
Ok(ContentFormat::OctetStream)
}
Some(ContentType::Assigned(CoapContentFormat::Cbor)) => Ok(ContentFormat::Cbor),
_ => Err(CryptoError::EncString(
EncStringParseError::CoseMissingContentType,
)),
Expand Down Expand Up @@ -307,7 +314,9 @@
key_id: KEY_ID,
enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)),
};
let decrypted = decrypt_xchacha20_poly1305(TEST_VECTOR_COSE_ENCRYPT0, &key).unwrap();
let decrypted =
decrypt_xchacha20_poly1305(&CoseEncrypt0Bytes::from(TEST_VECTOR_COSE_ENCRYPT0), &key)
.unwrap();
assert_eq!(
decrypted,
(TEST_VECTOR_PLAINTEXT.to_vec(), ContentFormat::OctetStream)
Expand All @@ -321,7 +330,7 @@
enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)),
};
assert!(matches!(
decrypt_xchacha20_poly1305(TEST_VECTOR_COSE_ENCRYPT0, &key),
decrypt_xchacha20_poly1305(&CoseEncrypt0Bytes::from(TEST_VECTOR_COSE_ENCRYPT0), &key),
Err(CryptoError::WrongCoseKeyId)
));
}
Expand All @@ -338,7 +347,7 @@
.create_ciphertext(&[], &[], |_, _| Vec::new())
.unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build())
.build();
let serialized_message = cose_encrypt0.to_vec().unwrap();
let serialized_message = CoseEncrypt0Bytes::from(cose_encrypt0.to_vec().unwrap());

let key = XChaCha20Poly1305Key {
key_id: KEY_ID,
Expand Down
14 changes: 9 additions & 5 deletions crates/bitwarden-crypto/src/enc_string/symmetric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use super::{check_length, from_b64, from_b64_vec, split_enc_string};
use crate::{
error::{CryptoError, EncStringParseError, Result, UnsupportedOperation},
util::FromStrVisitor,
Aes256CbcHmacKey, ContentFormat, KeyDecryptable, KeyEncryptable, KeyEncryptableWithContentType,
SymmetricCryptoKey, Utf8Bytes, XChaCha20Poly1305Key,
Aes256CbcHmacKey, ContentFormat, CoseEncrypt0Bytes, KeyDecryptable, KeyEncryptable,
KeyEncryptableWithContentType, SymmetricCryptoKey, Utf8Bytes, XChaCha20Poly1305Key,
};

#[cfg(feature = "wasm")]
Expand Down Expand Up @@ -266,7 +266,9 @@ impl EncString {
content_format: ContentFormat,
) -> Result<EncString> {
let data = crate::cose::encrypt_xchacha20_poly1305(data_dec, key, content_format)?;
Ok(EncString::Cose_Encrypt0_B64 { data })
Ok(EncString::Cose_Encrypt0_B64 {
data: data.to_vec(),
})
}

/// The numerical representation of the encryption type of the [EncString].
Expand Down Expand Up @@ -311,8 +313,10 @@ impl KeyDecryptable<SymmetricCryptoKey, Vec<u8>> for EncString {
EncString::Cose_Encrypt0_B64 { data },
SymmetricCryptoKey::XChaCha20Poly1305Key(key),
) => {
let (decrypted_message, _) =
crate::cose::decrypt_xchacha20_poly1305(data.as_slice(), key)?;
let (decrypted_message, _) = crate::cose::decrypt_xchacha20_poly1305(
&CoseEncrypt0Bytes::from(data.as_slice()),
key,
)?;
Ok(decrypted_message)
}
_ => Err(CryptoError::WrongKeyType),
Expand Down
2 changes: 2 additions & 0 deletions crates/bitwarden-crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub use store::{
};
mod cose;
pub use cose::CoseSerializable;
mod safe;
pub use safe::*;
mod signing;
pub use signing::*;
mod traits;
Expand Down
Loading
Loading