Skip to content

Cose Content Type #203

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 6 commits into
base: km/cose
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions bitwarden_license/bitwarden-sm/src/projects/create.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bitwarden_api_api::models::ProjectCreateRequestModel;
use bitwarden_core::{key_management::SymmetricKeyId, Client};
use bitwarden_crypto::Encryptable;
use bitwarden_crypto::{ContentFormat, Encryptable};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
Expand Down Expand Up @@ -34,7 +34,7 @@
.name
.clone()
.trim()
.encrypt(&mut key_store.context(), key)?
.encrypt(&mut key_store.context(), key, ContentFormat::Utf8)?

Check warning on line 37 in bitwarden_license/bitwarden-sm/src/projects/create.rs

View check run for this annotation

Codecov / codecov/patch

bitwarden_license/bitwarden-sm/src/projects/create.rs#L37

Added line #L37 was not covered by tests
.to_string(),
});

Expand Down
4 changes: 2 additions & 2 deletions bitwarden_license/bitwarden-sm/src/projects/update.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bitwarden_api_api::models::ProjectUpdateRequestModel;
use bitwarden_core::{key_management::SymmetricKeyId, Client};
use bitwarden_crypto::Encryptable;
use bitwarden_crypto::{ContentFormat, Encryptable};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
Expand Down Expand Up @@ -36,7 +36,7 @@
.name
.clone()
.trim()
.encrypt(&mut key_store.context(), key)?
.encrypt(&mut key_store.context(), key, ContentFormat::Utf8)?

Check warning on line 39 in bitwarden_license/bitwarden-sm/src/projects/update.rs

View check run for this annotation

Codecov / codecov/patch

bitwarden_license/bitwarden-sm/src/projects/update.rs#L39

Added line #L39 was not covered by tests
.to_string(),
});

Expand Down
17 changes: 13 additions & 4 deletions bitwarden_license/bitwarden-sm/src/secrets/create.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bitwarden_api_api::models::SecretCreateRequestModel;
use bitwarden_core::{key_management::SymmetricKeyId, Client};
use bitwarden_crypto::Encryptable;
use bitwarden_crypto::{ContentFormat, Encryptable};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
Expand Down Expand Up @@ -40,13 +40,22 @@
let secret = {
let mut ctx = key_store.context();
Some(SecretCreateRequestModel {
key: input.key.clone().trim().encrypt(&mut ctx, key)?.to_string(),
value: input.value.clone().encrypt(&mut ctx, key)?.to_string(),
key: input
.key
.clone()
.trim()
.encrypt(&mut ctx, key, ContentFormat::Utf8)?
.to_string(),
value: input
.value
.clone()
.encrypt(&mut ctx, key, ContentFormat::Utf8)?
.to_string(),

Check warning on line 53 in bitwarden_license/bitwarden-sm/src/secrets/create.rs

View check run for this annotation

Codecov / codecov/patch

bitwarden_license/bitwarden-sm/src/secrets/create.rs#L43-L53

Added lines #L43 - L53 were not covered by tests
note: input
.note
.clone()
.trim()
.encrypt(&mut ctx, key)?
.encrypt(&mut ctx, key, ContentFormat::Utf8)?

Check warning on line 58 in bitwarden_license/bitwarden-sm/src/secrets/create.rs

View check run for this annotation

Codecov / codecov/patch

bitwarden_license/bitwarden-sm/src/secrets/create.rs#L58

Added line #L58 was not covered by tests
.to_string(),
project_ids: input.project_ids.clone(),
access_policies_requests: None,
Expand Down
17 changes: 13 additions & 4 deletions bitwarden_license/bitwarden-sm/src/secrets/update.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bitwarden_api_api::models::SecretUpdateRequestModel;
use bitwarden_core::{key_management::SymmetricKeyId, Client};
use bitwarden_crypto::Encryptable;
use bitwarden_crypto::{ContentFormat, Encryptable};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
Expand Down Expand Up @@ -39,13 +39,22 @@
let secret = {
let mut ctx = key_store.context();
Some(SecretUpdateRequestModel {
key: input.key.clone().trim().encrypt(&mut ctx, key)?.to_string(),
value: input.value.clone().encrypt(&mut ctx, key)?.to_string(),
key: input
.key
.clone()
.trim()
.encrypt(&mut ctx, key, ContentFormat::Utf8)?
.to_string(),
value: input
.value
.clone()
.encrypt(&mut ctx, key, ContentFormat::Utf8)?
.to_string(),

Check warning on line 52 in bitwarden_license/bitwarden-sm/src/secrets/update.rs

View check run for this annotation

Codecov / codecov/patch

bitwarden_license/bitwarden-sm/src/secrets/update.rs#L42-L52

Added lines #L42 - L52 were not covered by tests
note: input
.note
.clone()
.trim()
.encrypt(&mut ctx, key)?
.encrypt(&mut ctx, key, ContentFormat::Utf8)?

Check warning on line 57 in bitwarden_license/bitwarden-sm/src/secrets/update.rs

View check run for this annotation

Codecov / codecov/patch

bitwarden_license/bitwarden-sm/src/secrets/update.rs#L57

Added line #L57 was not covered by tests
.to_string(),
project_ids: input.project_ids.clone(),
access_policies_requests: None,
Expand Down
6 changes: 3 additions & 3 deletions crates/bitwarden-core/src/mobile/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use base64::{engine::general_purpose::STANDARD, Engine};
use bitwarden_crypto::{
AsymmetricCryptoKey, AsymmetricEncString, CryptoError, EncString, Kdf, KeyDecryptable,
KeyEncryptable, MasterKey, SymmetricCryptoKey, UserKey,
MasterKey, SymmetricCryptoKey, TypedKeyEncryptable, UserKey,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -517,7 +517,7 @@ pub fn verify_asymmetric_keys(
mod tests {
use std::num::NonZeroU32;

use bitwarden_crypto::RsaKeyPair;
use bitwarden_crypto::{ContentFormat, KeyEncryptable, RsaKeyPair};

use super::*;
use crate::Client;
Expand Down Expand Up @@ -831,7 +831,7 @@ mod tests {
let invalid_private_key = "bad_key"
.to_string()
.into_bytes()
.encrypt_with_key(&user_key.0)
.encrypt_with_key(&user_key.0, ContentFormat::Utf8)
.unwrap();

let request = VerifyAsymmetricKeysRequest {
Expand Down
2 changes: 1 addition & 1 deletion crates/bitwarden-core/src/secrets_manager/state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{fmt::Debug, path::Path};

use bitwarden_crypto::{EncString, KeyDecryptable, KeyEncryptable};
use bitwarden_crypto::{EncString, KeyDecryptable, TypedKeyEncryptable};
use serde::{Deserialize, Serialize};

use crate::auth::AccessToken;
Expand Down
2 changes: 1 addition & 1 deletion crates/bitwarden-crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ secure.
## Example:

```rust
use bitwarden_crypto::{SymmetricCryptoKey, KeyEncryptable, KeyDecryptable, CryptoError};
use bitwarden_crypto::{SymmetricCryptoKey, TypedKeyEncryptable, KeyDecryptable, CryptoError};

async fn example() -> Result<(), CryptoError> {
let key = SymmetricCryptoKey::generate();
Expand Down
17 changes: 17 additions & 0 deletions crates/bitwarden-crypto/src/cose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,24 @@
//! Standardized values from <https://www.iana.org/assignments/cose/cose.xhtml> should always be preferred
//! unless there is a specific reason to use a private-use value.

use serde::{Deserialize, Serialize};
#[cfg(feature = "wasm")]
use tsify_next::Tsify;

// XChaCha20 <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03> is used over ChaCha20
// to be able to randomly generate nonces, and to not have to worry about key wearout. Since
// the draft was never published as an RFC, we use a private-use value for the algorithm.
pub(crate) const XCHACHA20_POLY1305: i64 = -70000;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
pub enum ContentFormat {
Utf8,
Pkcs8,
CoseKey,
OctetStream,
Unknown,
// This should never be serialized. It is used to indicate when we call an encrypt operation
// on a complex object that consists of multiple, individually encrypted fields
DomainObject,
}
84 changes: 71 additions & 13 deletions crates/bitwarden-crypto/src/enc_string/symmetric.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use std::{fmt::Display, str::FromStr};

use base64::{engine::general_purpose::STANDARD, Engine};
use coset::CborSerializable;
use coset::{iana::CoapContentFormat, CborSerializable, ContentType};
use serde::Deserialize;

use super::{check_length, from_b64, from_b64_vec, split_enc_string};
use crate::{
cose,
cose::{self, ContentFormat},
error::{CryptoError, EncStringParseError, Result, UnsupportedOperation},
Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, XChaCha20Poly1305Key,
Aes256CbcHmacKey, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, TypedKeyEncryptable,
XChaCha20Poly1305Key,
};

#[cfg(feature = "wasm")]
Expand Down Expand Up @@ -223,6 +224,8 @@
}

impl EncString {
const XCHACHA20_TEXT_PAD_BLOCK_SIZE: usize = 24;

pub(crate) fn encrypt_aes256_hmac(
data_dec: &[u8],
key: &Aes256CbcHmacKey,
Expand All @@ -235,14 +238,40 @@
pub(crate) fn encrypt_xchacha20_poly1305(
data_dec: &[u8],
key: &XChaCha20Poly1305Key,
content_format: ContentFormat,
) -> Result<EncString> {
let mut protected_header = coset::HeaderBuilder::new().build();
let mut protected_header = coset::HeaderBuilder::new();
match content_format {
ContentFormat::Utf8 => {
protected_header =
protected_header.content_type("application/utf8-padded".to_string());
}
ContentFormat::Pkcs8 => {
protected_header = protected_header.content_format(CoapContentFormat::Pkcs8);
}
ContentFormat::CoseKey => {
protected_header = protected_header.content_format(CoapContentFormat::CoseKey);
}
ContentFormat::OctetStream => {
protected_header = protected_header.content_format(CoapContentFormat::OctetStream);
}
ContentFormat::Unknown => todo!(),
ContentFormat::DomainObject => unreachable!(),

Check warning on line 259 in crates/bitwarden-crypto/src/enc_string/symmetric.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-crypto/src/enc_string/symmetric.rs#L249-L259

Added lines #L249 - L259 were not covered by tests
}

let mut data = data_dec.to_vec();
if content_format == ContentFormat::Utf8 {
// Pad the data to a block size in order to hide plaintext length
pad_bytes(&mut data, Self::XCHACHA20_TEXT_PAD_BLOCK_SIZE);
}

let mut protected_header = protected_header.build();
protected_header.alg = Some(coset::Algorithm::PrivateUse(cose::XCHACHA20_POLY1305));

let mut nonce = [0u8; 24];
let cose_encrypt0 = coset::CoseEncrypt0Builder::new()
.protected(protected_header)
.try_create_ciphertext(data_dec, &[], |data, aad| {
.try_create_ciphertext(&data, &[], |data, aad| {
let ciphertext = crate::xchacha20::encrypt_xchacha20_poly1305(
key.enc_key
.as_slice()
Expand Down Expand Up @@ -276,11 +305,15 @@
}

impl KeyEncryptable<SymmetricCryptoKey, EncString> for &[u8] {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
fn encrypt_with_key(
self,
key: &SymmetricCryptoKey,
content_format: ContentFormat,
) -> Result<EncString> {
match key {
SymmetricCryptoKey::Aes256CbcHmacKey(key) => EncString::encrypt_aes256_hmac(self, key),
SymmetricCryptoKey::XChaCha20Poly1305Key(inner_key) => {
EncString::encrypt_xchacha20_poly1305(self, inner_key)
EncString::encrypt_xchacha20_poly1305(self, inner_key, content_format)
}
SymmetricCryptoKey::Aes256CbcKey(_) => Err(CryptoError::OperationNotSupported(
UnsupportedOperation::EncryptionNotImplementedForKey,
Expand All @@ -303,9 +336,10 @@
EncString::XChaCha20_Poly1305_Cose_B64 { data },
SymmetricCryptoKey::XChaCha20Poly1305Key(key),
) => {
// parse cose
let msg = coset::CoseEncrypt0::from_slice(data.as_slice())
.map_err(|_| CryptoError::EncString(EncStringParseError::InvalidEncoding))?;
let decrypted_message = msg
let mut decrypted_message = msg
.decrypt(&[], |data, aad| {
let nonce = msg.unprotected.iv.as_slice();
crate::xchacha20::decrypt_xchacha20_poly1305(
Expand All @@ -320,22 +354,31 @@
.map_err(|_| CryptoError::EncodingError)
})
.map_err(|_| CryptoError::EncodingError)?;

if let Some(ContentType::Text(content_type)) = msg.protected.header.content_type {
if content_type == "application/utf8-padded" {
decrypted_message = unpad_bytes(decrypted_message.as_slice()).to_vec();
} else {
return Err(CryptoError::EncodingError);

Check warning on line 362 in crates/bitwarden-crypto/src/enc_string/symmetric.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-crypto/src/enc_string/symmetric.rs#L362

Added line #L362 was not covered by tests
}
}

Check warning on line 364 in crates/bitwarden-crypto/src/enc_string/symmetric.rs

View check run for this annotation

Codecov / codecov/patch

crates/bitwarden-crypto/src/enc_string/symmetric.rs#L364

Added line #L364 was not covered by tests

Ok(decrypted_message)
}
_ => Err(CryptoError::WrongKeyType),
}
}
}

impl KeyEncryptable<SymmetricCryptoKey, EncString> for String {
impl TypedKeyEncryptable<SymmetricCryptoKey, EncString> for String {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
self.as_bytes().encrypt_with_key(key)
self.as_bytes().encrypt_with_key(key, ContentFormat::Utf8)
}
}

impl KeyEncryptable<SymmetricCryptoKey, EncString> for &str {
impl TypedKeyEncryptable<SymmetricCryptoKey, EncString> for &str {
fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result<EncString> {
self.as_bytes().encrypt_with_key(key)
self.as_bytes().encrypt_with_key(key, ContentFormat::Utf8)
}
}

Expand All @@ -358,14 +401,29 @@
}
}

/// Pads bytes to a minimum length using PKCS7-like padding
fn pad_bytes(bytes: &mut Vec<u8>, block_size: usize) {
let padding_len = block_size - (bytes.len() % block_size);
let padded_length = padding_len + bytes.len();
bytes.resize(padded_length, padding_len as u8);
}

// Unpads the bytes
fn unpad_bytes(bytes: &[u8]) -> &[u8] {
// this unwrap is safe, the input is always at least 1 byte long
#[allow(clippy::unwrap_used)]
let pad_len = *bytes.last().unwrap() as usize;
bytes[..bytes.len() - pad_len].as_ref()
}

#[cfg(test)]
mod tests {
use generic_array::GenericArray;
use schemars::schema_for;

use super::EncString;
use crate::{
derive_symmetric_key, CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey,
derive_symmetric_key, CryptoError, KeyDecryptable, SymmetricCryptoKey, TypedKeyEncryptable,
};

#[test]
Expand Down
4 changes: 2 additions & 2 deletions crates/bitwarden-crypto/src/keys/device_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ impl DeviceKey {

let protected_device_public_key = device_private_key
.to_public_der()?
.encrypt_with_key(user_key)?;
.encrypt_with_key(user_key, crate::cose::ContentFormat::OctetStream)?;

let protected_device_private_key = device_private_key
.to_der()?
.encrypt_with_key(&device_key.0)?;
.encrypt_with_key(&device_key.0, crate::cose::ContentFormat::Pkcs8)?;

Ok(TrustDeviceResponse {
device_key: device_key.to_base64(),
Expand Down
Loading
Loading