diff --git a/crates/bitwarden-crypto/src/content_format.rs b/crates/bitwarden-crypto/src/content_format.rs index 1f44dc65c..77a643481 100644 --- a/crates/bitwarden-crypto/src/content_format.rs +++ b/crates/bitwarden-crypto/src/content_format.rs @@ -1,9 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::{ - traits::PrimitiveEncryptableWithContentType, CryptoError, EncString, KeyEncryptable, - KeyEncryptableWithContentType, KeyIds, KeyStoreContext, PrimitiveEncryptable, - SymmetricCryptoKey, + CryptoError, EncString, KeyEncryptable, KeyEncryptableWithContentType, KeyIds, KeyStoreContext, + PrimitiveEncryptable, PrimitiveEncryptableWithContentType, SymmetricCryptoKey, }; /// The content format describes the format of the contained bytes. Message encryption always diff --git a/crates/bitwarden-crypto/src/cose.rs b/crates/bitwarden-crypto/src/cose.rs index b2a39ab56..12e8f1f82 100644 --- a/crates/bitwarden-crypto/src/cose.rs +++ b/crates/bitwarden-crypto/src/cose.rs @@ -5,22 +5,20 @@ use coset::{ iana::{self, CoapContentFormat}, - CborSerializable, ContentType, Label, + ContentType, Label, }; -use generic_array::GenericArray; -use typenum::U32; use crate::{ content_format::{Bytes, ConstContentFormat, CoseContentFormat}, error::{EncStringParseError, EncodingError}, - xchacha20, ContentFormat, CryptoError, SymmetricCryptoKey, XChaCha20Poly1305Key, + ContentFormat, CryptoError, }; /// XChaCha20 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; -const XCHACHA20_TEXT_PAD_BLOCK_SIZE: usize = 32; +pub(crate) const XCHACHA20_TEXT_PAD_BLOCK_SIZE: usize = 32; // Note: These are in the "unregistered" tree: https://datatracker.ietf.org/doc/html/rfc6838#section-3.4 // These are only used within Bitwarden, and not meant for exchange with other systems. @@ -33,126 +31,7 @@ const CONTENT_TYPE_SPKI_PUBLIC_KEY: &str = "application/x.bitwarden.spki-public- /// The label used for the namespace ensuring strong domain separation when using signatures. pub(crate) const SIGNING_NAMESPACE: i64 = -80000; -/// Encrypts a plaintext message using XChaCha20Poly1305 and returns a COSE Encrypt0 message -pub(crate) fn encrypt_xchacha20_poly1305( - plaintext: &[u8], - key: &crate::XChaCha20Poly1305Key, - content_format: ContentFormat, -) -> Result, CryptoError> { - let mut plaintext = plaintext.to_vec(); - - let header_builder: coset::HeaderBuilder = content_format.into(); - let mut protected_header = header_builder.key_id(key.key_id.to_vec()).build(); - // This should be adjusted to use the builder pattern once implemented in coset. - // The related coset upstream issue is: - // https://github.com/google/coset/issues/105 - protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305)); - - if should_pad_content(&content_format) { - // Pad the data to a block size in order to hide plaintext length - crate::keys::utils::pad_bytes(&mut plaintext, XCHACHA20_TEXT_PAD_BLOCK_SIZE); - } - - let mut nonce = [0u8; xchacha20::NONCE_SIZE]; - let cose_encrypt0 = coset::CoseEncrypt0Builder::new() - .protected(protected_header) - .create_ciphertext(&plaintext, &[], |data, aad| { - let ciphertext = - crate::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); - nonce = ciphertext.nonce(); - ciphertext.encrypted_bytes().to_vec() - }) - .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) - .build(); - - cose_encrypt0 - .to_vec() - .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err))) -} - -/// Decrypts a COSE Encrypt0 message, using a XChaCha20Poly1305 key -pub(crate) fn decrypt_xchacha20_poly1305( - cose_encrypt0_message: &[u8], - key: &crate::XChaCha20Poly1305Key, -) -> Result<(Vec, ContentFormat), CryptoError> { - let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message) - .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?; - - let Some(ref alg) = msg.protected.header.alg else { - return Err(CryptoError::EncString( - EncStringParseError::CoseMissingAlgorithm, - )); - }; - - if *alg != coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) { - return Err(CryptoError::WrongKeyType); - } - - let content_format = ContentFormat::try_from(&msg.protected.header) - .map_err(|_| CryptoError::EncString(EncStringParseError::CoseMissingContentType))?; - - if key.key_id != *msg.protected.header.key_id { - return Err(CryptoError::WrongCoseKeyId); - } - - let decrypted_message = msg.decrypt(&[], |data, aad| { - let nonce = msg.unprotected.iv.as_slice(); - crate::xchacha20::decrypt_xchacha20_poly1305( - nonce - .try_into() - .map_err(|_| CryptoError::InvalidNonceLength)?, - &(*key.enc_key).into(), - data, - aad, - ) - })?; - - if should_pad_content(&content_format) { - // Unpad the data to get the original plaintext - let data = crate::keys::utils::unpad_bytes(&decrypted_message)?; - return Ok((data.to_vec(), content_format)); - } - - Ok((decrypted_message, content_format)) -} - -const SYMMETRIC_KEY: Label = Label::Int(iana::SymmetricKeyParameter::K as i64); - -impl TryFrom<&coset::CoseKey> for SymmetricCryptoKey { - type Error = CryptoError; - - fn try_from(cose_key: &coset::CoseKey) -> Result { - let key_bytes = cose_key - .params - .iter() - .find_map(|(label, value)| match (label, value) { - (&SYMMETRIC_KEY, ciborium::Value::Bytes(bytes)) => Some(bytes), - _ => None, - }) - .ok_or(CryptoError::InvalidKey)?; - let alg = cose_key.alg.as_ref().ok_or(CryptoError::InvalidKey)?; - - match alg { - coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) => { - // Ensure the length is correct since `GenericArray::clone_from_slice` panics if it - // receives the wrong length. - if key_bytes.len() != xchacha20::KEY_SIZE { - return Err(CryptoError::InvalidKey); - } - let enc_key = Box::pin(GenericArray::::clone_from_slice(key_bytes)); - let key_id = cose_key - .key_id - .as_slice() - .try_into() - .map_err(|_| CryptoError::InvalidKey)?; - Ok(SymmetricCryptoKey::XChaCha20Poly1305Key( - XChaCha20Poly1305Key { enc_key, key_id }, - )) - } - _ => Err(CryptoError::InvalidKey), - } - } -} +pub(crate) const SYMMETRIC_KEY: Label = Label::Int(iana::SymmetricKeyParameter::K as i64); impl From for coset::HeaderBuilder { fn from(format: ContentFormat) -> Self { @@ -208,10 +87,6 @@ impl TryFrom<&coset::Header> for ContentFormat { } } -fn should_pad_content(format: &ContentFormat) -> bool { - matches!(format, ContentFormat::Utf8) -} - /// Trait for structs that are serializable to COSE objects. pub trait CoseSerializable { /// Serializes the struct to COSE serialization @@ -221,132 +96,3 @@ pub trait CoseSerializable { where Self: Sized; } -#[cfg(test)] -mod test { - use super::*; - - const KEY_ID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - const KEY_DATA: [u8; 32] = [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, - 0x1e, 0x1f, - ]; - const TEST_VECTOR_PLAINTEXT: &[u8] = b"Message test vector"; - const TEST_VECTOR_COSE_ENCRYPT0: &[u8] = &[ - 131, 88, 28, 163, 1, 58, 0, 1, 17, 111, 3, 24, 42, 4, 80, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, - 11, 12, 13, 14, 15, 161, 5, 88, 24, 78, 20, 28, 157, 180, 246, 131, 220, 82, 104, 72, 73, - 75, 43, 69, 139, 216, 167, 145, 220, 67, 168, 144, 173, 88, 35, 127, 234, 194, 83, 189, - 172, 65, 29, 156, 73, 98, 87, 231, 87, 129, 15, 235, 127, 125, 97, 211, 51, 212, 211, 2, - 13, 36, 123, 53, 12, 31, 191, 40, 13, 175, - ]; - - #[test] - fn test_encrypt_decrypt_roundtrip_octetstream() { - let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) = - SymmetricCryptoKey::make_xchacha20_poly1305_key() - else { - panic!("Failed to create XChaCha20Poly1305Key"); - }; - - let plaintext = b"Hello, world!"; - let encrypted = - encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::OctetStream).unwrap(); - let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap(); - assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::OctetStream)); - } - - #[test] - fn test_encrypt_decrypt_roundtrip_utf8() { - let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) = - SymmetricCryptoKey::make_xchacha20_poly1305_key() - else { - panic!("Failed to create XChaCha20Poly1305Key"); - }; - - let plaintext = b"Hello, world!"; - let encrypted = encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::Utf8).unwrap(); - let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap(); - assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::Utf8)); - } - - #[test] - fn test_encrypt_decrypt_roundtrip_pkcs8() { - let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) = - SymmetricCryptoKey::make_xchacha20_poly1305_key() - else { - panic!("Failed to create XChaCha20Poly1305Key"); - }; - - let plaintext = b"Hello, world!"; - let encrypted = - encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::Pkcs8PrivateKey).unwrap(); - let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap(); - assert_eq!( - decrypted, - (plaintext.to_vec(), ContentFormat::Pkcs8PrivateKey) - ); - } - - #[test] - fn test_encrypt_decrypt_roundtrip_cosekey() { - let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) = - SymmetricCryptoKey::make_xchacha20_poly1305_key() - else { - panic!("Failed to create XChaCha20Poly1305Key"); - }; - - let plaintext = b"Hello, world!"; - let encrypted = encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::CoseKey).unwrap(); - let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap(); - assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::CoseKey)); - } - - #[test] - fn test_decrypt_test_vector() { - let key = XChaCha20Poly1305Key { - key_id: KEY_ID, - enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)), - }; - let decrypted = decrypt_xchacha20_poly1305(TEST_VECTOR_COSE_ENCRYPT0, &key).unwrap(); - assert_eq!( - decrypted, - (TEST_VECTOR_PLAINTEXT.to_vec(), ContentFormat::OctetStream) - ); - } - - #[test] - fn test_fail_wrong_key_id() { - let key = XChaCha20Poly1305Key { - key_id: [1; 16], // Different key ID - enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)), - }; - assert!(matches!( - decrypt_xchacha20_poly1305(TEST_VECTOR_COSE_ENCRYPT0, &key), - Err(CryptoError::WrongCoseKeyId) - )); - } - - #[test] - fn test_fail_wrong_algorithm() { - let protected_header = coset::HeaderBuilder::new() - .algorithm(iana::Algorithm::A256GCM) - .key_id(KEY_ID.to_vec()) - .build(); - let nonce = [0u8; 16]; - let cose_encrypt0 = coset::CoseEncrypt0Builder::new() - .protected(protected_header) - .create_ciphertext(&[], &[], |_, _| Vec::new()) - .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) - .build(); - let serialized_message = cose_encrypt0.to_vec().unwrap(); - - let key = XChaCha20Poly1305Key { - key_id: KEY_ID, - enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)), - }; - assert!(matches!( - decrypt_xchacha20_poly1305(&serialized_message, &key), - Err(CryptoError::WrongKeyType) - )); - } -} diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs index b7ba7a9c0..1a8f2e49c 100644 --- a/crates/bitwarden-crypto/src/keys/device_key.rs +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -1,7 +1,6 @@ -use super::{AsymmetricCryptoKey, PublicKeyEncryptionAlgorithm}; use crate::{ - error::Result, CryptoError, EncString, KeyDecryptable, KeyEncryptable, Pkcs8PrivateKeyBytes, - SymmetricCryptoKey, UnsignedSharedKey, + error::Result, AsymmetricCryptoKey, CryptoError, EncString, KeyDecryptable, KeyEncryptable, + Pkcs8PrivateKeyBytes, PublicKeyEncryptionAlgorithm, SymmetricCryptoKey, UnsignedSharedKey, }; /// Device Key diff --git a/crates/bitwarden-crypto/src/keys/master_key.rs b/crates/bitwarden-crypto/src/keys/master_key.rs index 653d34b7f..ce4080a5a 100644 --- a/crates/bitwarden-crypto/src/keys/master_key.rs +++ b/crates/bitwarden-crypto/src/keys/master_key.rs @@ -12,8 +12,8 @@ use super::{ }; use crate::{ util::{self}, - BitwardenLegacyKeyBytes, CryptoError, EncString, KeyDecryptable, Result, SymmetricCryptoKey, - UserKey, + Aes256CbcKey, BitwardenLegacyKeyBytes, CryptoError, EncString, KeyDecryptable, Result, + SymmetricCryptoKey, UserKey, }; #[allow(missing_docs)] @@ -144,7 +144,7 @@ pub(super) fn decrypt_user_key( // moved to using `Aes256Cbc_HmacSha256_B64`. However, we still need to support // decrypting these old keys. EncString::Aes256Cbc_B64 { .. } => { - let legacy_key = SymmetricCryptoKey::Aes256CbcKey(super::Aes256CbcKey { + let legacy_key = SymmetricCryptoKey::Aes256CbcKey(Aes256CbcKey { enc_key: Box::pin(GenericArray::clone_from_slice(key)), }); user_key.decrypt_with_key(&legacy_key)? @@ -186,8 +186,8 @@ mod tests { use super::{make_user_key, HashPurpose, Kdf, MasterKey}; use crate::{ - keys::{master_key::KdfDerivedKeyMaterial, symmetric_crypto_key::derive_symmetric_key}, - EncString, SymmetricCryptoKey, + derive_symmetric_key, keys::master_key::KdfDerivedKeyMaterial, EncString, + SymmetricCryptoKey, }; #[test] diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index afa1b3233..1ee2ab4d5 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -1,23 +1,7 @@ -mod key_encryptable; -pub(crate) use key_encryptable::KeyEncryptableWithContentType; -pub use key_encryptable::{CryptoKey, KeyContainer, KeyDecryptable, KeyEncryptable}; mod master_key; pub use master_key::{HashPurpose, MasterKey}; mod shareable_key; pub use shareable_key::derive_shareable_key; -mod symmetric_crypto_key; -#[cfg(test)] -pub use symmetric_crypto_key::derive_symmetric_key; -pub use symmetric_crypto_key::{ - Aes256CbcHmacKey, Aes256CbcKey, SymmetricCryptoKey, XChaCha20Poly1305Key, -}; -mod asymmetric_crypto_key; -pub use asymmetric_crypto_key::{ - AsymmetricCryptoKey, AsymmetricPublicCryptoKey, PublicKeyEncryptionAlgorithm, -}; -pub(crate) use asymmetric_crypto_key::{RawPrivateKey, RawPublicKey}; -mod signed_public_key; -pub use signed_public_key::{SignedPublicKey, SignedPublicKeyMessage}; mod user_key; pub use user_key::UserKey; mod device_key; diff --git a/crates/bitwarden-crypto/src/keys/pin_key.rs b/crates/bitwarden-crypto/src/keys/pin_key.rs index a1503a5ad..1a5ebf096 100644 --- a/crates/bitwarden-crypto/src/keys/pin_key.rs +++ b/crates/bitwarden-crypto/src/keys/pin_key.rs @@ -3,9 +3,7 @@ use super::{ master_key::decrypt_user_key, utils::stretch_key, }; -use crate::{ - keys::key_encryptable::CryptoKey, EncString, KeyEncryptable, Result, SymmetricCryptoKey, -}; +use crate::{CryptoKey, EncString, KeyEncryptable, Result, SymmetricCryptoKey}; /// Pin Key. /// diff --git a/crates/bitwarden-crypto/src/keys/shareable_key.rs b/crates/bitwarden-crypto/src/keys/shareable_key.rs index 9cd731fbc..a89b26209 100644 --- a/crates/bitwarden-crypto/src/keys/shareable_key.rs +++ b/crates/bitwarden-crypto/src/keys/shareable_key.rs @@ -5,8 +5,10 @@ use hmac::Mac; use typenum::{U32, U64}; use zeroize::{Zeroize, Zeroizing}; -use super::Aes256CbcHmacKey; -use crate::util::{hkdf_expand, PbkdfSha256Hmac}; +use crate::{ + util::{hkdf_expand, PbkdfSha256Hmac}, + Aes256CbcHmacKey, +}; /// Derive a shareable key using hkdf from secret and name. /// diff --git a/crates/bitwarden-crypto/src/keys/utils.rs b/crates/bitwarden-crypto/src/keys/utils.rs index e81bf61d1..c3f78c838 100644 --- a/crates/bitwarden-crypto/src/keys/utils.rs +++ b/crates/bitwarden-crypto/src/keys/utils.rs @@ -3,8 +3,7 @@ use std::{cmp::max, pin::Pin}; use generic_array::GenericArray; use typenum::U32; -use super::Aes256CbcHmacKey; -use crate::{util::hkdf_expand, CryptoError, Result}; +use crate::{util::hkdf_expand, Aes256CbcHmacKey, CryptoError, Result}; /// Stretch the given key using HKDF. /// This can be either a kdf-derived key (PIN/Master password) or diff --git a/crates/bitwarden-crypto/src/lib.rs b/crates/bitwarden-crypto/src/lib.rs index d4cc15f45..ab7a93ff9 100644 --- a/crates/bitwarden-crypto/src/lib.rs +++ b/crates/bitwarden-crypto/src/lib.rs @@ -12,11 +12,8 @@ #[global_allocator] static ALLOC: ZeroizingAllocator = ZeroizingAllocator(std::alloc::System); -mod aes; mod content_format; pub use content_format::*; -mod enc_string; -pub use enc_string::{EncString, UnsignedSharedKey}; mod error; pub use error::CryptoError; pub(crate) use error::Result; @@ -38,11 +35,12 @@ mod cose; pub use cose::CoseSerializable; mod signing; pub use signing::*; +mod public_key_encryption; +pub use public_key_encryption::*; +mod symmetric_encryption; +pub use symmetric_encryption::*; mod traits; -mod xchacha20; -pub use traits::{ - CompositeEncryptable, Decryptable, IdentifyKey, KeyId, KeyIds, PrimitiveEncryptable, -}; +pub use traits::{IdentifyKey, KeyId, KeyIds}; pub use zeroizing_alloc::ZeroAlloc as ZeroizingAllocator; #[cfg(feature = "uniffi")] diff --git a/crates/bitwarden-crypto/src/public_key_encryption/hazmat/mod.rs b/crates/bitwarden-crypto/src/public_key_encryption/hazmat/mod.rs new file mode 100644 index 000000000..a28947461 --- /dev/null +++ b/crates/bitwarden-crypto/src/public_key_encryption/hazmat/mod.rs @@ -0,0 +1,6 @@ +//! This module contains the low-level public-key encryption implementations. +//! Any modifications to this module need to be most thoroughly reviewed. +//! +//! This module should only be referenced by the `public_key_encryption` module. +mod rsa; +pub(super) use rsa::encrypt_rsa2048_oaep_sha1; diff --git a/crates/bitwarden-crypto/src/public_key_encryption/hazmat/rsa.rs b/crates/bitwarden-crypto/src/public_key_encryption/hazmat/rsa.rs new file mode 100644 index 000000000..30e7aaa90 --- /dev/null +++ b/crates/bitwarden-crypto/src/public_key_encryption/hazmat/rsa.rs @@ -0,0 +1,17 @@ +use rsa::{Oaep, RsaPublicKey}; +use sha1::Sha1; + +use crate::CryptoError; + +/// Encrypt data using RSA-OAEP-SHA1 with a 2048 bit key +pub(crate) fn encrypt_rsa2048_oaep_sha1( + public_key: &RsaPublicKey, + data: &[u8], +) -> Result, CryptoError> { + let mut rng = rand::thread_rng(); + + let padding = Oaep::new::(); + public_key + .encrypt(&mut rng, padding, data) + .map_err(|e| CryptoError::RsaError(e.into())) +} diff --git a/crates/bitwarden-crypto/src/public_key_encryption/mod.rs b/crates/bitwarden-crypto/src/public_key_encryption/mod.rs new file mode 100644 index 000000000..8987943ac --- /dev/null +++ b/crates/bitwarden-crypto/src/public_key_encryption/mod.rs @@ -0,0 +1,9 @@ +mod private_key; +pub use private_key::AsymmetricCryptoKey; +mod public_key; +pub use public_key::*; +mod signed_public_key; +pub use signed_public_key::{SignedPublicKey, SignedPublicKeyMessage}; +mod unsigned_shared_key; +pub use unsigned_shared_key::UnsignedSharedKey; +mod hazmat; diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/public_key_encryption/private_key.rs similarity index 86% rename from crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs rename to crates/bitwarden-crypto/src/public_key_encryption/private_key.rs index d65bd1bd4..c2a645359 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/public_key_encryption/private_key.rs @@ -1,63 +1,13 @@ use std::pin::Pin; -use rsa::{pkcs8::DecodePublicKey, RsaPrivateKey, RsaPublicKey}; -use serde_repr::{Deserialize_repr, Serialize_repr}; +use rsa::RsaPrivateKey; -use super::key_encryptable::CryptoKey; use crate::{ error::{CryptoError, Result}, - Pkcs8PrivateKeyBytes, SpkiPublicKeyBytes, + public_key_encryption::public_key::{AsymmetricPublicCryptoKey, RawPublicKey}, + CryptoKey, Pkcs8PrivateKeyBytes, PublicKeyEncryptionAlgorithm, }; -/// Algorithm / public key encryption scheme used for encryption/decryption. -#[derive(Serialize_repr, Deserialize_repr)] -#[repr(u8)] -pub enum PublicKeyEncryptionAlgorithm { - /// RSA with OAEP padding and SHA-1 hashing. - RsaOaepSha1 = 0, -} - -#[derive(Clone, PartialEq)] -pub(crate) enum RawPublicKey { - RsaOaepSha1(RsaPublicKey), -} - -/// Public key of a key pair used in a public key encryption scheme. It is used for -/// encrypting data. -#[derive(Clone, PartialEq)] -pub struct AsymmetricPublicCryptoKey { - inner: RawPublicKey, -} - -impl AsymmetricPublicCryptoKey { - pub(crate) fn inner(&self) -> &RawPublicKey { - &self.inner - } - - /// Build a public key from the SubjectPublicKeyInfo DER. - pub fn from_der(der: &SpkiPublicKeyBytes) -> Result { - Ok(AsymmetricPublicCryptoKey { - inner: RawPublicKey::RsaOaepSha1( - RsaPublicKey::from_public_key_der(der.as_ref()) - .map_err(|_| CryptoError::InvalidKey)?, - ), - }) - } - - /// Makes a SubjectPublicKeyInfo DER serialized version of the public key. - pub fn to_der(&self) -> Result { - use rsa::pkcs8::EncodePublicKey; - match &self.inner { - RawPublicKey::RsaOaepSha1(public_key) => Ok(public_key - .to_public_key_der() - .map_err(|_| CryptoError::InvalidKey)? - .as_bytes() - .to_owned() - .into()), - } - } -} - #[derive(Clone)] pub(crate) enum RawPrivateKey { // RsaPrivateKey is not a Copy type so this isn't completely necessary, but @@ -167,8 +117,9 @@ mod tests { use crate::{ content_format::{Bytes, Pkcs8PrivateKeyDerContentFormat}, - AsymmetricCryptoKey, AsymmetricPublicCryptoKey, Pkcs8PrivateKeyBytes, SpkiPublicKeyBytes, - SymmetricCryptoKey, UnsignedSharedKey, + public_key_encryption::public_key::AsymmetricPublicCryptoKey, + AsymmetricCryptoKey, Pkcs8PrivateKeyBytes, SpkiPublicKeyBytes, SymmetricCryptoKey, + UnsignedSharedKey, }; #[test] diff --git a/crates/bitwarden-crypto/src/public_key_encryption/public_key.rs b/crates/bitwarden-crypto/src/public_key_encryption/public_key.rs new file mode 100644 index 000000000..d3aa02e8f --- /dev/null +++ b/crates/bitwarden-crypto/src/public_key_encryption/public_key.rs @@ -0,0 +1,171 @@ +use rsa::{pkcs8::DecodePublicKey, RsaPublicKey}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +use crate::{ + error::{CryptoError, Result}, + SpkiPublicKeyBytes, +}; + +/// Algorithm / public key encryption scheme used for encryption/decryption. +#[derive(Serialize_repr, Deserialize_repr)] +#[repr(u8)] +pub enum PublicKeyEncryptionAlgorithm { + /// RSA with OAEP padding and SHA-1 hashing. + RsaOaepSha1 = 0, +} + +#[derive(Clone, PartialEq)] +pub(crate) enum RawPublicKey { + RsaOaepSha1(RsaPublicKey), +} + +/// Public key of a key pair used in a public key encryption scheme. It is used for +/// encrypting data. +#[derive(Clone, PartialEq)] +pub struct AsymmetricPublicCryptoKey { + pub(super) inner: RawPublicKey, +} + +impl AsymmetricPublicCryptoKey { + pub(crate) fn inner(&self) -> &RawPublicKey { + &self.inner + } + + /// Build a public key from the SubjectPublicKeyInfo DER. + pub fn from_der(der: &SpkiPublicKeyBytes) -> Result { + Ok(AsymmetricPublicCryptoKey { + inner: RawPublicKey::RsaOaepSha1( + RsaPublicKey::from_public_key_der(der.as_ref()) + .map_err(|_| CryptoError::InvalidKey)?, + ), + }) + } + + /// Makes a SubjectPublicKeyInfo DER serialized version of the public key. + pub fn to_der(&self) -> Result { + use rsa::pkcs8::EncodePublicKey; + match &self.inner { + RawPublicKey::RsaOaepSha1(public_key) => Ok(public_key + .to_public_key_der() + .map_err(|_| CryptoError::InvalidKey)? + .as_bytes() + .to_owned() + .into()), + } + } +} + +#[cfg(test)] +mod tests { + use base64::{engine::general_purpose::STANDARD, Engine}; + + use crate::{ + content_format::{Bytes, Pkcs8PrivateKeyDerContentFormat}, + public_key_encryption::public_key::AsymmetricPublicCryptoKey, + AsymmetricCryptoKey, Pkcs8PrivateKeyBytes, SpkiPublicKeyBytes, SymmetricCryptoKey, + UnsignedSharedKey, + }; + + #[test] + fn test_asymmetric_crypto_key() { + let pem_key_str = "-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5 +qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYc +afeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4Cwm +qqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyv +b0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZw +P7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2fam +rEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKi +szJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx +0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+ +8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVR +jB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKach +vGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI +1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KR +J30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7 +l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQ +TjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9 +ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9Bye +KvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiN +wEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZ +UZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEA +kY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7W +pt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwN +Zy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLi +CVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzup +PFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnf +DnqOsltgPomWZ7xVfMkm9niL2OA= +-----END PRIVATE KEY-----"; + + let der_key_vec = STANDARD.decode("MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDiTQVuzhdygFz5qv14i+XFDGTnDravzUQT1hPKPGUZOUSZ1gwdNgkWqOIaOnR65BHEnL0sp4bnuiYcafeK2JAW5Sc8Z7IxBNSuAwhQmuKx3RochMIiuCkI2/p+JvUQoJu6FBNm8OoJ4CwmqqHGZESMfnpQDCuDrB3JdJEdXhtmnl0C48sGjOk3WaBMcgGqn8LbJDUlyu1zdqyvb0waJf0iV4PJm2fkUl7+57D/2TkpbCqURVnZK1FFIEg8mr6FzSN1F2pOfktkNYZwP7MSNR7o81CkRSCMr7EkIVa+MZYMBx106BMK7FXgWB7nbSpsWKxBk7ZDHkID2famrEcVtrzDAgMBAAECggEBAKwq9OssGGKgjhvUnyrLJHAZ0dqIMyzk+dotkLjX4gKiszJmyqiep6N5sStLNbsZMPtoU/RZMCW0VbJgXFhiEp2YkZU/Py5UAoqw++53J+kx0d/IkPphKbb3xUec0+1mg5O6GljDCQuiZXS1dIa/WfeZcezclW6Dz9WovY6ePjJ+8vEBR1icbNKzyeINd6MtPtpcgQPHtDwHvhPyUDbKDYGbLvjh9nui8h4+ZUlXKuVRjB0ChxiKV1xJRjkrEVoulOOicd5r597WfB2ghax3pvRZ4MdXemCXm3gQYqPVKachvGU+1cPQR/MBJZpxT+EZA97xwtFS3gqwbxJaNFcoE8ECgYEA9OaeYZhQPDo485tI1u/Z7L/3PNape9hBQIXoW7+MgcQ5NiWqYh8Jnj43EIYa0wM/ECQINr1Za8Q5e6KRJ30FcU+kfyjuQ0jeXdNELGU/fx5XXNg/vV8GevHwxRlwzqZTCg6UExUZzbYEQqd7l+wPyETGeua5xCEywA1nX/D101kCgYEA7I6aMFjhEjO71RmzNhqjKJt6DOghoOfQTjhaaanNEhLYSbenFz1mlb21mW67ulmz162saKdIYLxQNJIP8ZPmxh4ummOJI8w9ClHfo8WuCI2hCjJ19xbQJocSbTA5aJg6lA1IDVZMDbQwsnAByPRGpaLHBT/Q9ByeKvCMB+9amXsCgYEAx65yXSkP4sumPBrVHUub6MntERIGRxBgw/drKcPZEMWp0FiNwEuGUBxyUWrG3F69QK/gcqGZE6F/LSu0JvptQaKqgXQiMYJsrRvhbkFvsHpQyUcZUZL1ebFjm5HOxPAgrQaN/bEqxOwwNRjSUWEMzUImg3c06JIZCzbinvudtKECgYEAkY3JF/iIPI/yglP27lKDlCfeeHSYxI3+oTKRhzSAxx8rUGidenJAXeDGDauR/T7Wpt3pGNfddBBK9Z3uC4Iq3DqUCFE4f/taj7ADAJ1Q0Vh7/28/IJM77ojr8J1cpZwNZy2o6PPxhfkagaDjqEeN9Lrs5LD4nEvDkr5CG1vOjmMCgYEAvIBFKRm31NyF8jLiCVuPwC5PzrW5iThDmsWTaXFpB3esUsbICO2pEz872oeQS+Em4GO5vXUlpbbFPzupPFhA8iMJ8TAvemhvc7oM0OZqpU6p3K4seHf6BkwLxumoA3vDJfovu9RuXVcJVOnfDnqOsltgPomWZ7xVfMkm9niL2OA=").unwrap(); + + // Load the two different formats and check they are the same key + let pem_key = AsymmetricCryptoKey::from_pem(pem_key_str).unwrap(); + let der_key = AsymmetricCryptoKey::from_der( + &Bytes::::from(der_key_vec.clone()), + ) + .unwrap(); + assert_eq!(pem_key.to_der().unwrap(), der_key.to_der().unwrap()); + + // Check that the keys can be converted back to DER + assert_eq!(der_key.to_der().unwrap().to_vec(), der_key_vec); + assert_eq!(pem_key.to_der().unwrap().to_vec(), der_key_vec); + } + + #[test] + fn test_encrypt_public_decrypt_private() { + let private_key = STANDARD + .decode(concat!( + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH", + "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY", + "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL", + "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK", + "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2", + "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO", + "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ", + "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj", + "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B", + "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL", + "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4", + "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk", + "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro", + "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv", + "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH", + "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j", + "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU", + "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM", + "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC", + "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G", + "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/", + "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J", + "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94", + "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs", + "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R", + "GDgRMUB6cL3IRVzcR0dC6cY=", + )) + .unwrap(); + + let public_key = STANDARD + .decode(concat!( + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc", + "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X", + "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd", + "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui", + "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK", + "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS", + "XQIDAQAB", + )) + .unwrap(); + + let private_key = Pkcs8PrivateKeyBytes::from(private_key); + let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap(); + let public_key = + AsymmetricPublicCryptoKey::from_der(&SpkiPublicKeyBytes::from(public_key)).unwrap(); + + let raw_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key(); + let encrypted = UnsignedSharedKey::encapsulate_key_unsigned(&raw_key, &public_key).unwrap(); + let decrypted = encrypted.decapsulate_key_unsigned(&private_key).unwrap(); + + assert_eq!(raw_key, decrypted); + } +} diff --git a/crates/bitwarden-crypto/src/keys/signed_public_key.rs b/crates/bitwarden-crypto/src/public_key_encryption/signed_public_key.rs similarity index 96% rename from crates/bitwarden-crypto/src/keys/signed_public_key.rs rename to crates/bitwarden-crypto/src/public_key_encryption/signed_public_key.rs index 5951ccf0d..2b30dcc07 100644 --- a/crates/bitwarden-crypto/src/keys/signed_public_key.rs +++ b/crates/bitwarden-crypto/src/public_key_encryption/signed_public_key.rs @@ -9,10 +9,12 @@ use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; use serde_repr::{Deserialize_repr, Serialize_repr}; -use super::AsymmetricPublicCryptoKey; use crate::{ - cose::CoseSerializable, error::EncodingError, util::FromStrVisitor, CoseSign1Bytes, - CryptoError, PublicKeyEncryptionAlgorithm, RawPublicKey, SignedObject, SigningKey, + cose::CoseSerializable, + error::EncodingError, + public_key_encryption::public_key::{AsymmetricPublicCryptoKey, RawPublicKey}, + util::FromStrVisitor, + CoseSign1Bytes, CryptoError, PublicKeyEncryptionAlgorithm, SignedObject, SigningKey, SigningNamespace, SpkiPublicKeyBytes, VerifyingKey, }; diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/public_key_encryption/unsigned_shared_key.rs similarity index 97% rename from crates/bitwarden-crypto/src/enc_string/asymmetric.rs rename to crates/bitwarden-crypto/src/public_key_encryption/unsigned_shared_key.rs index 4f7e7c1f7..5f4daac04 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/public_key_encryption/unsigned_shared_key.rs @@ -5,14 +5,18 @@ pub use internal::UnsignedSharedKey; use rsa::Oaep; use serde::Deserialize; -use super::{from_b64_vec, split_enc_string}; use crate::{ error::{CryptoError, EncStringParseError, Result}, - rsa::encrypt_rsa2048_oaep_sha1, + from_b64_vec, + public_key_encryption::{ + private_key::RawPrivateKey, + public_key::{AsymmetricPublicCryptoKey, RawPublicKey}, + }, + split_enc_string, util::FromStrVisitor, - AsymmetricCryptoKey, AsymmetricPublicCryptoKey, BitwardenLegacyKeyBytes, RawPrivateKey, - RawPublicKey, SymmetricCryptoKey, + AsymmetricCryptoKey, BitwardenLegacyKeyBytes, SymmetricCryptoKey, }; + // This module is a workaround to avoid deprecated warnings that come from the ZeroizeOnDrop // macro expansion #[allow(deprecated)] @@ -167,7 +171,7 @@ impl UnsignedSharedKey { match encapsulation_key.inner() { RawPublicKey::RsaOaepSha1(rsa_public_key) => { Ok(UnsignedSharedKey::Rsa2048_OaepSha1_B64 { - data: encrypt_rsa2048_oaep_sha1( + data: super::hazmat::encrypt_rsa2048_oaep_sha1( rsa_public_key, encapsulated_key.to_encoded().as_ref(), )?, diff --git a/crates/bitwarden-crypto/src/rsa.rs b/crates/bitwarden-crypto/src/rsa.rs index c4c349b1a..9b2fb7837 100644 --- a/crates/bitwarden-crypto/src/rsa.rs +++ b/crates/bitwarden-crypto/src/rsa.rs @@ -1,9 +1,8 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use rsa::{ pkcs8::{EncodePrivateKey, EncodePublicKey}, - Oaep, RsaPrivateKey, RsaPublicKey, + RsaPrivateKey, RsaPublicKey, }; -use sha1::Sha1; use crate::{ error::{Result, RsaError, UnsupportedOperation}, @@ -54,13 +53,3 @@ pub(crate) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { private: protected, }) } - -/// Encrypt data using RSA-OAEP-SHA1 with a 2048 bit key -pub(super) fn encrypt_rsa2048_oaep_sha1(public_key: &RsaPublicKey, data: &[u8]) -> Result> { - let mut rng = rand::thread_rng(); - - let padding = Oaep::new::(); - public_key - .encrypt(&mut rng, padding, data) - .map_err(|e| CryptoError::RsaError(e.into())) -} diff --git a/crates/bitwarden-crypto/src/signing/namespace.rs b/crates/bitwarden-crypto/src/signing/namespace.rs index fd48bd2b9..c44ddebc4 100644 --- a/crates/bitwarden-crypto/src/signing/namespace.rs +++ b/crates/bitwarden-crypto/src/signing/namespace.rs @@ -10,7 +10,7 @@ use crate::{error::SignatureError, CryptoError}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SigningNamespace { /// The namespace for - /// [`SignedPublicKey`](crate::keys::SignedPublicKey). + /// [`SignedPublicKey`](crate::public_key_encryption::SignedPublicKey). SignedPublicKey = 1, /// This namespace is only used in tests #[cfg(test)] diff --git a/crates/bitwarden-crypto/src/store/context.rs b/crates/bitwarden-crypto/src/store/context.rs index 2df08a1f8..a7e0f4be4 100644 --- a/crates/bitwarden-crypto/src/store/context.rs +++ b/crates/bitwarden-crypto/src/store/context.rs @@ -8,10 +8,10 @@ use zeroize::Zeroizing; use super::KeyStoreInner; use crate::{ - derive_shareable_key, error::UnsupportedOperation, signing, store::backend::StoreBackend, - AsymmetricCryptoKey, BitwardenLegacyKeyBytes, ContentFormat, CryptoError, EncString, KeyId, - KeyIds, Result, Signature, SignatureAlgorithm, SignedObject, SignedPublicKey, - SignedPublicKeyMessage, SigningKey, SymmetricCryptoKey, UnsignedSharedKey, + derive_shareable_key, error::UnsupportedOperation, key_wrap, signing, + store::backend::StoreBackend, AsymmetricCryptoKey, ContentFormat, CryptoError, EncString, + KeyDecryptable, KeyId, KeyIds, Result, Signature, SignatureAlgorithm, SignedObject, + SignedPublicKey, SignedPublicKeyMessage, SigningKey, SymmetricCryptoKey, UnsignedSharedKey, }; /// The context of a crypto operation using [super::KeyStore] @@ -153,38 +153,10 @@ impl KeyStoreContext<'_, Ids> { wrapped_key: &EncString, ) -> Result { let wrapping_key = self.get_symmetric_key(wrapping_key)?; - - let key = match (wrapped_key, wrapping_key) { - (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { - SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from( - crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key)?, - ))? - } - ( - EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data }, - SymmetricCryptoKey::Aes256CbcHmacKey(key), - ) => SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from( - crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key)?, - ))?, - ( - EncString::Cose_Encrypt0_B64 { data }, - SymmetricCryptoKey::XChaCha20Poly1305Key(key), - ) => { - let (content_bytes, content_format) = - crate::cose::decrypt_xchacha20_poly1305(data, key)?; - match content_format { - ContentFormat::BitwardenLegacyKey => { - SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(content_bytes))? - } - ContentFormat::CoseKey => SymmetricCryptoKey::try_from_cose(&content_bytes)?, - _ => return Err(CryptoError::InvalidKey), - } - } - _ => return Err(CryptoError::InvalidKey), - }; + let unwrapped_key = key_wrap::unwrap_symmetric_key(wrapped_key, wrapping_key)?; #[allow(deprecated)] - self.set_symmetric_key(new_key_id, key)?; + self.set_symmetric_key(new_key_id, unwrapped_key)?; // Returning the new key identifier for convenience Ok(new_key_id) @@ -452,24 +424,8 @@ impl KeyStoreContext<'_, Ids> { data: &EncString, ) -> Result> { let key = self.get_symmetric_key(key)?; - - match (data, key) { - (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { - crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key) - } - ( - EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data }, - SymmetricCryptoKey::Aes256CbcHmacKey(key), - ) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key), - ( - EncString::Cose_Encrypt0_B64 { data }, - SymmetricCryptoKey::XChaCha20Poly1305Key(key), - ) => { - let (data, _) = crate::cose::decrypt_xchacha20_poly1305(data, key)?; - Ok(data) - } - _ => Err(CryptoError::InvalidKey), - } + let data: Vec = data.decrypt_with_key(key)?; + Ok(data) } pub(crate) fn encrypt_data_with_symmetric_key( diff --git a/crates/bitwarden-crypto/src/symmetric_encryption/cose.rs b/crates/bitwarden-crypto/src/symmetric_encryption/cose.rs new file mode 100644 index 000000000..a78fb24e0 --- /dev/null +++ b/crates/bitwarden-crypto/src/symmetric_encryption/cose.rs @@ -0,0 +1,265 @@ +use coset::CborSerializable; +use generic_array::GenericArray; +use typenum::U32; + +use crate::{ + cose::{XCHACHA20_POLY1305, XCHACHA20_TEXT_PAD_BLOCK_SIZE}, + error::EncStringParseError, + symmetric_encryption::hazmat, + ContentFormat, CryptoError, SymmetricCryptoKey, XChaCha20Poly1305Key, +}; + +/// Encrypts a plaintext message using XChaCha20Poly1305 and returns a COSE Encrypt0 message +pub(crate) fn encrypt_xchacha20_poly1305( + plaintext: &[u8], + key: &crate::XChaCha20Poly1305Key, + content_format: ContentFormat, +) -> Result, CryptoError> { + let mut plaintext = plaintext.to_vec(); + + let header_builder: coset::HeaderBuilder = content_format.into(); + let mut protected_header = header_builder.key_id(key.key_id.to_vec()).build(); + // This should be adjusted to use the builder pattern once implemented in coset. + // The related coset upstream issue is: + // https://github.com/google/coset/issues/105 + protected_header.alg = Some(coset::Algorithm::PrivateUse(XCHACHA20_POLY1305)); + + if should_pad_content(&content_format) { + // Pad the data to a block size in order to hide plaintext length + crate::keys::utils::pad_bytes(&mut plaintext, XCHACHA20_TEXT_PAD_BLOCK_SIZE); + } + + let mut nonce = [0u8; hazmat::xchacha20::NONCE_SIZE]; + let cose_encrypt0 = coset::CoseEncrypt0Builder::new() + .protected(protected_header) + .create_ciphertext(&plaintext, &[], |data, aad| { + let ciphertext = + hazmat::xchacha20::encrypt_xchacha20_poly1305(&(*key.enc_key).into(), data, aad); + nonce = ciphertext.nonce(); + ciphertext.encrypted_bytes().to_vec() + }) + .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) + .build(); + + cose_encrypt0 + .to_vec() + .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err))) +} + +/// Decrypts a COSE Encrypt0 message, using a XChaCha20Poly1305 key +pub(crate) fn decrypt_xchacha20_poly1305( + cose_encrypt0_message: &[u8], + key: &crate::XChaCha20Poly1305Key, +) -> Result<(Vec, ContentFormat), CryptoError> { + let msg = coset::CoseEncrypt0::from_slice(cose_encrypt0_message) + .map_err(|err| CryptoError::EncString(EncStringParseError::InvalidCoseEncoding(err)))?; + + let Some(ref alg) = msg.protected.header.alg else { + return Err(CryptoError::EncString( + EncStringParseError::CoseMissingAlgorithm, + )); + }; + + if *alg != coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) { + return Err(CryptoError::WrongKeyType); + } + + let content_format = ContentFormat::try_from(&msg.protected.header) + .map_err(|_| CryptoError::EncString(EncStringParseError::CoseMissingContentType))?; + + if key.key_id != *msg.protected.header.key_id { + return Err(CryptoError::WrongCoseKeyId); + } + + let decrypted_message = msg.decrypt(&[], |data, aad| { + let nonce = msg.unprotected.iv.as_slice(); + hazmat::xchacha20::decrypt_xchacha20_poly1305( + nonce + .try_into() + .map_err(|_| CryptoError::InvalidNonceLength)?, + &(*key.enc_key).into(), + data, + aad, + ) + })?; + + if should_pad_content(&content_format) { + // Unpad the data to get the original plaintext + let data = crate::keys::utils::unpad_bytes(&decrypted_message)?; + return Ok((data.to_vec(), content_format)); + } + + Ok((decrypted_message, content_format)) +} + +impl TryFrom<&coset::CoseKey> for SymmetricCryptoKey { + type Error = CryptoError; + + fn try_from(cose_key: &coset::CoseKey) -> Result { + let key_bytes = cose_key + .params + .iter() + .find_map(|(label, value)| match (label, value) { + (&crate::cose::SYMMETRIC_KEY, ciborium::Value::Bytes(bytes)) => Some(bytes), + _ => None, + }) + .ok_or(CryptoError::InvalidKey)?; + let alg = cose_key.alg.as_ref().ok_or(CryptoError::InvalidKey)?; + + match alg { + coset::Algorithm::PrivateUse(XCHACHA20_POLY1305) => { + // Ensure the length is correct since `GenericArray::clone_from_slice` panics if it + // receives the wrong length. + if key_bytes.len() != hazmat::xchacha20::KEY_SIZE { + return Err(CryptoError::InvalidKey); + } + let enc_key = Box::pin(GenericArray::::clone_from_slice(key_bytes)); + let key_id = cose_key + .key_id + .as_slice() + .try_into() + .map_err(|_| CryptoError::InvalidKey)?; + Ok(SymmetricCryptoKey::XChaCha20Poly1305Key( + XChaCha20Poly1305Key { enc_key, key_id }, + )) + } + _ => Err(CryptoError::InvalidKey), + } + } +} + +fn should_pad_content(format: &ContentFormat) -> bool { + matches!(format, ContentFormat::Utf8) +} + +#[cfg(test)] +mod test { + use coset::iana; + + use super::*; + + const KEY_ID: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + const KEY_DATA: [u8; 32] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ]; + const TEST_VECTOR_PLAINTEXT: &[u8] = b"Message test vector"; + const TEST_VECTOR_COSE_ENCRYPT0: &[u8] = &[ + 131, 88, 28, 163, 1, 58, 0, 1, 17, 111, 3, 24, 42, 4, 80, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 161, 5, 88, 24, 78, 20, 28, 157, 180, 246, 131, 220, 82, 104, 72, 73, + 75, 43, 69, 139, 216, 167, 145, 220, 67, 168, 144, 173, 88, 35, 127, 234, 194, 83, 189, + 172, 65, 29, 156, 73, 98, 87, 231, 87, 129, 15, 235, 127, 125, 97, 211, 51, 212, 211, 2, + 13, 36, 123, 53, 12, 31, 191, 40, 13, 175, + ]; + + #[test] + fn test_encrypt_decrypt_roundtrip_octetstream() { + let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) = + SymmetricCryptoKey::make_xchacha20_poly1305_key() + else { + panic!("Failed to create XChaCha20Poly1305Key"); + }; + + let plaintext = b"Hello, world!"; + let encrypted = + encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::OctetStream).unwrap(); + let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap(); + assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::OctetStream)); + } + + #[test] + fn test_encrypt_decrypt_roundtrip_utf8() { + let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) = + SymmetricCryptoKey::make_xchacha20_poly1305_key() + else { + panic!("Failed to create XChaCha20Poly1305Key"); + }; + + let plaintext = b"Hello, world!"; + let encrypted = encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::Utf8).unwrap(); + let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap(); + assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::Utf8)); + } + + #[test] + fn test_encrypt_decrypt_roundtrip_pkcs8() { + let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) = + SymmetricCryptoKey::make_xchacha20_poly1305_key() + else { + panic!("Failed to create XChaCha20Poly1305Key"); + }; + + let plaintext = b"Hello, world!"; + let encrypted = + encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::Pkcs8PrivateKey).unwrap(); + let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap(); + assert_eq!( + decrypted, + (plaintext.to_vec(), ContentFormat::Pkcs8PrivateKey) + ); + } + + #[test] + fn test_encrypt_decrypt_roundtrip_cosekey() { + let SymmetricCryptoKey::XChaCha20Poly1305Key(ref key) = + SymmetricCryptoKey::make_xchacha20_poly1305_key() + else { + panic!("Failed to create XChaCha20Poly1305Key"); + }; + + let plaintext = b"Hello, world!"; + let encrypted = encrypt_xchacha20_poly1305(plaintext, key, ContentFormat::CoseKey).unwrap(); + let decrypted = decrypt_xchacha20_poly1305(&encrypted, key).unwrap(); + assert_eq!(decrypted, (plaintext.to_vec(), ContentFormat::CoseKey)); + } + + #[test] + fn test_decrypt_test_vector() { + let key = XChaCha20Poly1305Key { + key_id: KEY_ID, + enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)), + }; + let decrypted = decrypt_xchacha20_poly1305(TEST_VECTOR_COSE_ENCRYPT0, &key).unwrap(); + assert_eq!( + decrypted, + (TEST_VECTOR_PLAINTEXT.to_vec(), ContentFormat::OctetStream) + ); + } + + #[test] + fn test_fail_wrong_key_id() { + let key = XChaCha20Poly1305Key { + key_id: [1; 16], // Different key ID + enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)), + }; + assert!(matches!( + decrypt_xchacha20_poly1305(TEST_VECTOR_COSE_ENCRYPT0, &key), + Err(CryptoError::WrongCoseKeyId) + )); + } + + #[test] + fn test_fail_wrong_algorithm() { + let protected_header = coset::HeaderBuilder::new() + .algorithm(iana::Algorithm::A256GCM) + .key_id(KEY_ID.to_vec()) + .build(); + let nonce = [0u8; 16]; + let cose_encrypt0 = coset::CoseEncrypt0Builder::new() + .protected(protected_header) + .create_ciphertext(&[], &[], |_, _| Vec::new()) + .unprotected(coset::HeaderBuilder::new().iv(nonce.to_vec()).build()) + .build(); + let serialized_message = cose_encrypt0.to_vec().unwrap(); + + let key = XChaCha20Poly1305Key { + key_id: KEY_ID, + enc_key: Box::pin(*GenericArray::from_slice(&KEY_DATA)), + }; + assert!(matches!( + decrypt_xchacha20_poly1305(&serialized_message, &key), + Err(CryptoError::WrongKeyType) + )); + } +} diff --git a/crates/bitwarden-crypto/src/traits/decryptable.rs b/crates/bitwarden-crypto/src/symmetric_encryption/decryptable.rs similarity index 100% rename from crates/bitwarden-crypto/src/traits/decryptable.rs rename to crates/bitwarden-crypto/src/symmetric_encryption/decryptable.rs diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/symmetric_encryption/enc_string.rs similarity index 96% rename from crates/bitwarden-crypto/src/enc_string/symmetric.rs rename to crates/bitwarden-crypto/src/symmetric_encryption/enc_string.rs index 5e44c81bc..815189076 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/symmetric_encryption/enc_string.rs @@ -4,9 +4,9 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use coset::CborSerializable; use serde::Deserialize; -use super::{check_length, from_b64, from_b64_vec, split_enc_string}; use crate::{ error::{CryptoError, EncStringParseError, Result, UnsupportedOperation}, + symmetric_encryption::util::{check_length, from_b64, from_b64_vec, split_enc_string}, util::FromStrVisitor, Aes256CbcHmacKey, ContentFormat, KeyDecryptable, KeyEncryptable, KeyEncryptableWithContentType, SymmetricCryptoKey, Utf8Bytes, XChaCha20Poly1305Key, @@ -256,7 +256,7 @@ impl EncString { key: &Aes256CbcHmacKey, ) -> Result { let (iv, mac, data) = - crate::aes::encrypt_aes256_hmac(data_dec, &key.mac_key, &key.enc_key)?; + super::hazmat::aes::encrypt_aes256_hmac(data_dec, &key.mac_key, &key.enc_key)?; Ok(EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data }) } @@ -265,7 +265,7 @@ impl EncString { key: &XChaCha20Poly1305Key, content_format: ContentFormat, ) -> Result { - let data = crate::cose::encrypt_xchacha20_poly1305(data_dec, key, content_format)?; + let data = super::cose::encrypt_xchacha20_poly1305(data_dec, key, content_format)?; Ok(EncString::Cose_Encrypt0_B64 { data }) } @@ -301,18 +301,24 @@ impl KeyDecryptable> for EncString { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result> { match (self, key) { (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { - crate::aes::decrypt_aes256(iv, data.clone(), &key.enc_key) + super::hazmat::aes::decrypt_aes256(iv, data.clone(), &key.enc_key) } ( EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data }, SymmetricCryptoKey::Aes256CbcHmacKey(key), - ) => crate::aes::decrypt_aes256_hmac(iv, mac, data.clone(), &key.mac_key, &key.enc_key), + ) => super::hazmat::aes::decrypt_aes256_hmac( + iv, + mac, + data.clone(), + &key.mac_key, + &key.enc_key, + ), ( EncString::Cose_Encrypt0_B64 { data }, SymmetricCryptoKey::XChaCha20Poly1305Key(key), ) => { let (decrypted_message, _) = - crate::cose::decrypt_xchacha20_poly1305(data.as_slice(), key)?; + super::cose::decrypt_xchacha20_poly1305(data.as_slice(), key)?; Ok(decrypted_message) } _ => Err(CryptoError::WrongKeyType), @@ -357,8 +363,8 @@ mod tests { use super::EncString; use crate::{ - derive_symmetric_key, CryptoError, KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, - KEY_ID_SIZE, + symmetric_encryption::symmetric_crypto_key::derive_symmetric_key, CryptoError, + KeyDecryptable, KeyEncryptable, SymmetricCryptoKey, KEY_ID_SIZE, }; #[test] diff --git a/crates/bitwarden-crypto/src/traits/encryptable.rs b/crates/bitwarden-crypto/src/symmetric_encryption/encryptable.rs similarity index 97% rename from crates/bitwarden-crypto/src/traits/encryptable.rs rename to crates/bitwarden-crypto/src/symmetric_encryption/encryptable.rs index 654a1fa90..a985ab80f 100644 --- a/crates/bitwarden-crypto/src/traits/encryptable.rs +++ b/crates/bitwarden-crypto/src/symmetric_encryption/encryptable.rs @@ -169,9 +169,9 @@ impl KeyStore { diff --git a/crates/bitwarden-crypto/src/aes.rs b/crates/bitwarden-crypto/src/symmetric_encryption/hazmat/aes.rs similarity index 100% rename from crates/bitwarden-crypto/src/aes.rs rename to crates/bitwarden-crypto/src/symmetric_encryption/hazmat/aes.rs diff --git a/crates/bitwarden-crypto/src/symmetric_encryption/hazmat/mod.rs b/crates/bitwarden-crypto/src/symmetric_encryption/hazmat/mod.rs new file mode 100644 index 000000000..fd6699839 --- /dev/null +++ b/crates/bitwarden-crypto/src/symmetric_encryption/hazmat/mod.rs @@ -0,0 +1,6 @@ +//! This module contains the low-level symmetric encryption implementations. +//! Any modifications to this module need to be most thoroughly reviewed. +//! +//! This module should only be referenced by the `symmetric_encryption` module. +pub(super) mod aes; +pub(super) mod xchacha20; diff --git a/crates/bitwarden-crypto/src/xchacha20.rs b/crates/bitwarden-crypto/src/symmetric_encryption/hazmat/xchacha20.rs similarity index 99% rename from crates/bitwarden-crypto/src/xchacha20.rs rename to crates/bitwarden-crypto/src/symmetric_encryption/hazmat/xchacha20.rs index e3d2dbbff..4ffc37844 100644 --- a/crates/bitwarden-crypto/src/xchacha20.rs +++ b/crates/bitwarden-crypto/src/symmetric_encryption/hazmat/xchacha20.rs @@ -84,9 +84,9 @@ pub(crate) fn decrypt_xchacha20_poly1305( Ok(buffer) } +#[cfg(test)] mod tests { - #[cfg(test)] - use crate::xchacha20::*; + use super::*; #[test] fn test_encrypt_decrypt_xchacha20() { diff --git a/crates/bitwarden-crypto/src/keys/key_encryptable.rs b/crates/bitwarden-crypto/src/symmetric_encryption/key_encryptable.rs similarity index 100% rename from crates/bitwarden-crypto/src/keys/key_encryptable.rs rename to crates/bitwarden-crypto/src/symmetric_encryption/key_encryptable.rs diff --git a/crates/bitwarden-crypto/src/symmetric_encryption/key_wrap.rs b/crates/bitwarden-crypto/src/symmetric_encryption/key_wrap.rs new file mode 100644 index 000000000..5fb8eeef3 --- /dev/null +++ b/crates/bitwarden-crypto/src/symmetric_encryption/key_wrap.rs @@ -0,0 +1,40 @@ +use crate::{BitwardenLegacyKeyBytes, ContentFormat, CryptoError, EncString, SymmetricCryptoKey}; + +/// Unwraps an encrypted symmetric key using the provided wrapping key +pub(crate) fn unwrap_symmetric_key( + wrapped_key: &EncString, + wrapping_key: &SymmetricCryptoKey, +) -> Result { + let key = match (wrapped_key, wrapping_key) { + (EncString::Aes256Cbc_B64 { iv, data }, SymmetricCryptoKey::Aes256CbcKey(key)) => { + SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from( + super::hazmat::aes::decrypt_aes256(iv, data.clone(), &key.enc_key)?, + ))? + } + ( + EncString::Aes256Cbc_HmacSha256_B64 { iv, mac, data }, + SymmetricCryptoKey::Aes256CbcHmacKey(key), + ) => SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from( + super::hazmat::aes::decrypt_aes256_hmac( + iv, + mac, + data.clone(), + &key.mac_key, + &key.enc_key, + )?, + ))?, + (EncString::Cose_Encrypt0_B64 { data }, SymmetricCryptoKey::XChaCha20Poly1305Key(key)) => { + let (content_bytes, content_format) = + super::cose::decrypt_xchacha20_poly1305(data, key)?; + match content_format { + ContentFormat::BitwardenLegacyKey => { + SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(content_bytes))? + } + ContentFormat::CoseKey => SymmetricCryptoKey::try_from_cose(&content_bytes)?, + _ => return Err(CryptoError::InvalidKey), + } + } + _ => return Err(CryptoError::InvalidKey), + }; + Ok(key) +} diff --git a/crates/bitwarden-crypto/src/symmetric_encryption/mod.rs b/crates/bitwarden-crypto/src/symmetric_encryption/mod.rs new file mode 100644 index 000000000..f5df2e004 --- /dev/null +++ b/crates/bitwarden-crypto/src/symmetric_encryption/mod.rs @@ -0,0 +1,20 @@ +mod cose; +mod decryptable; +pub use decryptable::*; +mod encryptable; +pub use encryptable::*; +mod enc_string; +pub use enc_string::*; +mod symmetric_crypto_key; +pub use symmetric_crypto_key::{ + Aes256CbcHmacKey, Aes256CbcKey, SymmetricCryptoKey, XChaCha20Poly1305Key, +}; +mod key_encryptable; +pub(crate) use key_encryptable::KeyEncryptableWithContentType; +pub use key_encryptable::{CryptoKey, KeyContainer, KeyDecryptable, KeyEncryptable}; +mod hazmat; +pub(crate) mod key_wrap; +mod util; +#[cfg(test)] +pub use symmetric_crypto_key::derive_symmetric_key; +pub(crate) use util::*; diff --git a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs b/crates/bitwarden-crypto/src/symmetric_encryption/symmetric_crypto_key.rs similarity index 99% rename from crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs rename to crates/bitwarden-crypto/src/symmetric_encryption/symmetric_crypto_key.rs index de2b54a8a..4fe947e1b 100644 --- a/crates/bitwarden-crypto/src/keys/symmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/symmetric_encryption/symmetric_crypto_key.rs @@ -14,11 +14,11 @@ use subtle::{Choice, ConstantTimeEq}; use typenum::U32; use zeroize::{Zeroize, ZeroizeOnDrop}; -use super::{ - key_encryptable::CryptoKey, - key_id::{KeyId, KEY_ID_SIZE}, +use super::key_encryptable::CryptoKey; +use crate::{ + cose, keys::KeyId, BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CryptoError, + KEY_ID_SIZE, }; -use crate::{cose, BitwardenLegacyKeyBytes, ContentFormat, CoseKeyBytes, CryptoError}; /// [Aes256CbcKey] is a symmetric encryption key, consisting of one 256-bit key, /// used to decrypt legacy type 0 enc strings. The data is not authenticated @@ -435,7 +435,7 @@ mod tests { use super::{derive_symmetric_key, SymmetricCryptoKey}; use crate::{ - keys::symmetric_crypto_key::{pad_key, unpad_key}, + symmetric_encryption::symmetric_crypto_key::{pad_key, unpad_key}, Aes256CbcHmacKey, Aes256CbcKey, BitwardenLegacyKeyBytes, XChaCha20Poly1305Key, }; diff --git a/crates/bitwarden-crypto/src/enc_string/mod.rs b/crates/bitwarden-crypto/src/symmetric_encryption/util.rs similarity index 76% rename from crates/bitwarden-crypto/src/enc_string/mod.rs rename to crates/bitwarden-crypto/src/symmetric_encryption/util.rs index 51daf4fb0..594628040 100644 --- a/crates/bitwarden-crypto/src/enc_string/mod.rs +++ b/crates/bitwarden-crypto/src/symmetric_encryption/util.rs @@ -1,21 +1,8 @@ -//! Encrypted string types -//! -//! [EncString] and [UnsignedSharedKey] are Bitwarden specific primitive that represents a -//! encrypted string. They are are used together with the [KeyDecryptable][crate::KeyDecryptable] -//! and [KeyEncryptable][crate::KeyEncryptable] traits to encrypt and decrypt data using -//! [SymmetricCryptoKey][crate::SymmetricCryptoKey] and -//! [AsymmetricCryptoKey][crate::AsymmetricCryptoKey]s. - -mod asymmetric; -mod symmetric; - -pub use asymmetric::UnsignedSharedKey; use base64::{engine::general_purpose::STANDARD, Engine}; -pub use symmetric::EncString; use crate::error::{EncStringParseError, Result}; -fn check_length(buf: &[u8], expected: usize) -> Result<()> { +pub(super) fn check_length(buf: &[u8], expected: usize) -> Result<()> { if buf.len() < expected { return Err(EncStringParseError::InvalidLength { expected, @@ -26,13 +13,13 @@ fn check_length(buf: &[u8], expected: usize) -> Result<()> { Ok(()) } -fn from_b64_vec(s: &str) -> Result> { +pub(crate) fn from_b64_vec(s: &str) -> Result> { Ok(STANDARD .decode(s) .map_err(EncStringParseError::InvalidBase64)?) } -fn from_b64(s: &str) -> Result<[u8; N]> { +pub(super) fn from_b64(s: &str) -> Result<[u8; N]> { Ok(from_b64_vec(s)? .try_into() .map_err(|e: Vec<_>| EncStringParseError::InvalidLength { @@ -41,7 +28,7 @@ fn from_b64(s: &str) -> Result<[u8; N]> { })?) } -fn split_enc_string(s: &str) -> (&str, Vec<&str>) { +pub(crate) fn split_enc_string(s: &str) -> (&str, Vec<&str>) { let header_parts: Vec<_> = s.split('.').collect(); if header_parts.len() == 2 { diff --git a/crates/bitwarden-crypto/src/traits/mod.rs b/crates/bitwarden-crypto/src/traits/mod.rs index 4b9bab2b6..0ffa3e8a7 100644 --- a/crates/bitwarden-crypto/src/traits/mod.rs +++ b/crates/bitwarden-crypto/src/traits/mod.rs @@ -1,9 +1,3 @@ -mod encryptable; -pub(crate) use encryptable::PrimitiveEncryptableWithContentType; -pub use encryptable::{CompositeEncryptable, PrimitiveEncryptable}; -mod decryptable; -pub use decryptable::Decryptable; - pub(crate) mod key_id; pub use key_id::{KeyId, KeyIds};