diff --git a/.gitignore b/.gitignore index 8bf9ddb05..69581b484 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ target/ **/*.rs.bk -# CLion IDE +# IDEs .idea +.vscode + +# Artifacts +*.orig \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c3ac2b1af..438bc88d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,6 +309,7 @@ dependencies = [ "getrandom", "hex-literal 0.4.1", "p256", + "pbkdf2", "pem-rfc7468", "pkcs5", "rand", diff --git a/cms/Cargo.toml b/cms/Cargo.toml index 2729b3512..8bba08515 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -31,10 +31,12 @@ signature = { version = "2.1.0", features = ["digest", "alloc"], optional = true zeroize = { version = "1.6.0", optional = true } [dev-dependencies] +aes = "0.8.2" getrandom = "0.2" hex-literal = "0.4" pem-rfc7468 = "0.7.0" pkcs5 = { version = "0.7" } +pbkdf2 = "0.12.2" rand = { version = "0.8.5" } rsa = { version = "0.9.3", features = ["sha2"] } ecdsa = { version = "0.16.8", features = ["digest", "pem"] } diff --git a/cms/src/builder.rs b/cms/src/builder.rs index 906ed6bf7..d251de059 100644 --- a/cms/src/builder.rs +++ b/cms/src/builder.rs @@ -6,8 +6,8 @@ use crate::cert::CertificateChoices; use crate::content_info::{CmsVersion, ContentInfo}; use crate::enveloped_data::{ EncryptedContentInfo, EncryptedKey, EnvelopedData, KekIdentifier, KeyTransRecipientInfo, - OriginatorIdentifierOrKey, OriginatorInfo, RecipientIdentifier, RecipientInfo, RecipientInfos, - UserKeyingMaterial, + OriginatorIdentifierOrKey, OriginatorInfo, PasswordRecipientInfo, RecipientIdentifier, + RecipientInfo, RecipientInfos, UserKeyingMaterial, }; use crate::revocation::{RevocationInfoChoice, RevocationInfoChoices}; use crate::signed_data::{ @@ -17,7 +17,7 @@ use crate::signed_data::{ use aes::{Aes128, Aes192, Aes256}; use alloc::borrow::ToOwned; use alloc::boxed::Box; -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::vec::Vec; use cipher::block_padding::Pkcs7; use cipher::rand_core::{CryptoRng, CryptoRngCore, RngCore}; @@ -26,9 +26,8 @@ use cipher::{Key, KeyIvInit, KeySizeUser}; use const_oid::ObjectIdentifier; use core::cmp::Ordering; use core::fmt; -use der::asn1::{BitString, OctetStringRef, SetOfVec}; +use der::asn1::{BitString, OctetString, OctetStringRef, SetOfVec}; use der::oid::db::DB; -use der::Tag::OctetString; use der::{Any, AnyRef, DateTime, Decode, Encode, ErrorKind, Tag}; use digest::Digest; use rsa::Pkcs1v15Encrypt; @@ -705,9 +704,31 @@ impl RecipientInfoBuilder for KekRecipientInfoBuilder { } } -/// Builds a `PasswordRecipientInfo` according to RFC 5652 § 6. +/// Trait used for encrypting the content-encryption key for PasswordRecipientInfo. +/// This trait must be implemented by a user and which allows for greater flexibility +/// in choosing key derivation and encryption algorithms. Note, that method +/// `encrypt_rfc3211()` must follow RFC 3211 and encrypt the key twice. +pub trait PwriEncryptor { + /// Block length of the encryption algorithm. + const BLOCK_LENGTH: usize; + /// Returns the algorithm identifier of the used key derivation algorithm, + /// which is used to derive an encryption key from the secret/password + /// shared with the recipient. Includes eventual parameters (e.g. the used iv). + fn key_derivation_algorithm(&self, ) -> Option; + /// Returns the algorithm identifier of the used encryption algorithm + /// including eventual parameters (e.g. the used iv). + fn key_encryption_algorithm(&self, ) -> AlgorithmIdentifierOwned; + /// Encrypt the wrapped content-encryption key twice following RFC 3211, § 2.3.1 + fn encrypt_rfc3211(&self, wrapped_content_encryption_key: &[u8]) -> Result>; +} + +/// Builds a `PasswordRecipientInfo` according to RFC 5652 § 6 and RFC 3211. /// Uses a password or shared secret value to encrypt the content-encryption key. -pub struct PasswordRecipientInfoBuilder { +pub struct PasswordRecipientInfoBuilder<'r, P, R> +where + P: PwriEncryptor, + R: CryptoRngCore, +{ /// Identifies the key-derivation algorithm, and any associated parameters, used to derive the /// key-encryption key from the password or shared secret value. If this field is `None`, /// the key-encryption key is supplied from an external source, for example a hardware crypto @@ -715,22 +736,77 @@ pub struct PasswordRecipientInfoBuilder { pub key_derivation_alg: Option, /// Encryption algorithm to be used for key encryption pub key_enc_alg: AlgorithmIdentifierOwned, + /// Provided password encryptor + pub key_encryptor: P, + /// Random number generator + pub rng: &'r mut R, } -impl PasswordRecipientInfoBuilder { +impl<'r, P, R> PasswordRecipientInfoBuilder<'r, P, R> +where + P: PwriEncryptor, + R: CryptoRngCore, +{ /// Creates a `PasswordRecipientInfoBuilder` + /// `key_derivation_alg`: (optional) Algorithm used to derive the + /// key-encryption key from the shared secret (password) + /// `key_enc_alg`: Algorithm used to (symmetrically) encrypt the + /// content-encryption key + /// `key_encryptor`: Provided encryptor, which is used to encrypt + /// the content-encryption key + /// `rng`: Random number generator, required for padding values. pub fn new( - key_derivation_alg: Option, - key_enc_alg: AlgorithmIdentifierOwned, - ) -> Result { + key_encryptor: P, + rng: &'r mut R, + ) -> Result> { Ok(PasswordRecipientInfoBuilder { - key_derivation_alg, - key_enc_alg, + key_derivation_alg: key_encryptor.key_derivation_algorithm(), + key_enc_alg: key_encryptor.key_encryption_algorithm(), + key_encryptor, + rng, }) } + + /// Wrap the content-encryption key according to RFC 3211, §2.3.1: + /// .... + /// The formatted CEK block then looks as follows: + /// CEK byte count || check value || CEK || padding (if required) + fn wrap_content_encryption_key(&mut self, content_encryption_key: &[u8]) -> Result> { + let content_encryption_key_length = content_encryption_key.len(); + let wrapped_key_length_wo_padding = 1 + 3 + content_encryption_key_length; + let key_enc_alg_blocklength = P::BLOCK_LENGTH; + let padding_length = if wrapped_key_length_wo_padding < 2 * key_enc_alg_blocklength { + 2 * key_enc_alg_blocklength - wrapped_key_length_wo_padding + } else { + 0 + }; + + let cek_byte_count: u8 = content_encryption_key.len().try_into().map_err(|_| { + Error::Builder("Content encryption key length must not exceed 255".to_string()) + })?; + let mut check_value = vec![ + 0xff ^ content_encryption_key[0], + 0xff ^ content_encryption_key[1], + 0xff ^ content_encryption_key[2], + ]; + + let mut wrapped_cek: Vec = Vec::new(); + wrapped_cek.insert(0, cek_byte_count); + wrapped_cek.append(&mut check_value); + if padding_length > 0 { + let mut padding = vec![0_u8; padding_length]; + self.rng.fill_bytes(padding.as_mut_slice()); + wrapped_cek.append(&mut padding); + } + Ok(wrapped_cek) + } } -impl RecipientInfoBuilder for PasswordRecipientInfoBuilder { +impl<'r, P, R> RecipientInfoBuilder for PasswordRecipientInfoBuilder<'r, P, R> +where + P: PwriEncryptor, + R: CryptoRngCore, +{ /// Returns the RecipientInfoType fn recipient_info_type(&self) -> RecipientInfoType { RecipientInfoType::Pwri @@ -742,10 +818,16 @@ impl RecipientInfoBuilder for PasswordRecipientInfoBuilder { } /// Build a `PasswordRecipientInfoBuilder`. See RFC 5652 § 6.2.1 - fn build(&mut self, _content_encryption_key: &[u8]) -> Result { - Err(Error::Builder(String::from( - "Building PasswordRecipientInfo is not implemented, yet.", - ))) + fn build(&mut self, content_encryption_key: &[u8]) -> Result { + let wrapped_cek = self.wrap_content_encryption_key(content_encryption_key)?; + let encrypted_key = self.key_encryptor.encrypt_rfc3211(wrapped_cek.as_slice())?; + let enc_key = OctetString::new(encrypted_key)?; + Ok(RecipientInfo::Pwri(PasswordRecipientInfo { + version: self.recipient_info_version(), + key_derivation_alg: self.key_derivation_alg.clone(), + key_enc_alg: self.key_enc_alg.clone(), + enc_key, + })) } } @@ -867,7 +949,7 @@ impl<'c> EnvelopedDataBuilder<'c> { None, rng, )?; - let encrypted_content_octetstring = der::asn1::OctetString::new(encrypted_content)?; + let encrypted_content_octetstring = OctetString::new(encrypted_content)?; let encrypted_content_info = EncryptedContentInfo { content_type: const_oid::db::rfc5911::ID_DATA, // TODO bk should this be configurable? content_enc_alg, @@ -1021,7 +1103,7 @@ macro_rules! encrypt_block_mode { key.to_vec(), AlgorithmIdentifierOwned { oid: $oid, - parameters: Some(Any::new(OctetString, iv.to_vec())?), + parameters: Some(Any::new(der::Tag::OctetString, iv.to_vec())?), }, )) }}; @@ -1085,7 +1167,7 @@ pub fn create_content_type_attribute(content_type: ObjectIdentifier) -> Result Result { let message_digest_der = OctetStringRef::new(message_digest)?; let message_digest_attribute_value = - AttributeValue::new(OctetString, message_digest_der.as_bytes())?; + AttributeValue::new(der::Tag::OctetString, message_digest_der.as_bytes())?; let mut values = SetOfVec::new(); values.insert(message_digest_attribute_value)?; let attribute = Attribute { diff --git a/cms/tests/builder.rs b/cms/tests/builder.rs index 3273829d0..c536e7213 100644 --- a/cms/tests/builder.rs +++ b/cms/tests/builder.rs @@ -2,10 +2,11 @@ use aes::Aes128; use cipher::block_padding::Pkcs7; -use cipher::{BlockDecryptMut, KeyIvInit}; +use cipher::{BlockDecryptMut, BlockEncryptMut, Iv, KeyIvInit}; use cms::builder::{ create_signing_time_attribute, ContentEncryptionAlgorithm, EnvelopedDataBuilder, - KeyEncryptionInfo, KeyTransRecipientInfoBuilder, SignedDataBuilder, SignerInfoBuilder, + KeyEncryptionInfo, KeyTransRecipientInfoBuilder, PasswordRecipientInfoBuilder, PwriEncryptor, + SignedDataBuilder, SignerInfoBuilder, }; use cms::cert::{CertificateChoices, IssuerAndSerialNumber}; use cms::content_info::ContentInfo; @@ -580,3 +581,111 @@ fn test_create_signing_attribute() { "Invalid tag number in signing time attribute value" ); } + +#[test] +/// This demonstrates and tests PasswordRecipientInfoBuilder according to RFC3211, +/// using Aes128Cbc for encryption of the content-encryption key (CEK). +fn test_create_password_recipient_info() { + // First define an Encryptor, which is used to encrypt the content-encryption key + // for a recipient of the CMS message. + struct Aes128CbcPwriEncryptor<'a> { + challenge_password: &'a [u8], + key_encryption_iv: Iv>, + key_derivation_params: pkcs5::pbes2::Pbkdf2Params<'a>, + } + impl<'a> Aes128CbcPwriEncryptor<'a> { + pub fn new(challenge_password: &'a [u8]) -> Self { + let rng = OsRng; + Aes128CbcPwriEncryptor { + challenge_password, + key_encryption_iv: cbc::Encryptor::::generate_iv(rng), + key_derivation_params: pkcs5::pbes2::Pbkdf2Params::hmac_with_sha256( + 500_000, b"salz", + ) + .unwrap(), + } + } + } + impl<'a> PwriEncryptor for Aes128CbcPwriEncryptor<'a> { + const BLOCK_LENGTH: usize = 128; // AES block length + fn encrypt_rfc3211( + &self, + wrapped_content_encryption_key: &[u8], + ) -> Result, cms::builder::Error> { + // Derive a key-encryption key from the challenge password. + let mut key_encryption_key = [0_u8; 16]; + pbkdf2::pbkdf2_hmac::( + self.challenge_password, + self.key_derivation_params.salt, + self.key_derivation_params.iteration_count, + &mut key_encryption_key, + ); + // Encrypt first time + let key = + cipher::Key::>::from_slice(&key_encryption_key).to_owned(); + let mut encryptor = cbc::Encryptor::::new(&key.into(), &self.key_encryption_iv); + let tmp = encryptor.encrypt_padded_vec_mut::(wrapped_content_encryption_key); + + // Encrypt result again (see RFC 3211) + encryptor = cbc::Encryptor::::new( + &key.into(), + aes::Block::from_slice(&tmp[tmp.len() - self.key_encryption_iv.len()..]), + ); + Ok(encryptor.encrypt_padded_vec_mut::(tmp.as_slice())) + } + + fn key_derivation_algorithm(&self) -> Option { + Some(AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_PBKDF_2, + parameters: Some( + Any::new( + der::Tag::Sequence, + self.key_derivation_params.to_der().unwrap(), + ) + .unwrap(), + ), + }) + } + + fn key_encryption_algorithm(&self) -> AlgorithmIdentifierOwned { + AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_AES_128_CBC, + parameters: Some( + Any::new(der::Tag::OctetString, self.key_encryption_iv.to_vec()).unwrap(), + ), + } + } + } + + // Encrypt the content-encryption key using custom encryptor + // of type `Aes128CbcPwriEncryptor`: + let challenge_password = b"chellange pazzw0rd"; + let key_encryptor = Aes128CbcPwriEncryptor::new(challenge_password); + let mut rng = OsRng; + + // Create recipient info + let recipient_info_builder = + PasswordRecipientInfoBuilder::new(key_encryptor, &mut rng).unwrap(); + + let mut rng = OsRng; + let mut builder = EnvelopedDataBuilder::new( + None, + "Arbitrary unencrypted content".as_bytes(), + ContentEncryptionAlgorithm::Aes128Cbc, + None, + ) + .expect("Could not create an EnvelopedData builder."); + let enveloped_data = builder + .add_recipient_info(recipient_info_builder) + .expect("Could not add a recipient info") + .build_with_rng(&mut rng) + .expect("Building EnvelopedData failed"); + let enveloped_data_der = enveloped_data + .to_der() + .expect("conversion of enveloped data to DER failed."); + println!( + "{}", + pem_rfc7468::encode_string("ENVELOPEDDATA", LineEnding::LF, &enveloped_data_der) + .expect("PEM encoding of enveloped data DER failed") + ); +} diff --git a/der/src/asn1/octet_string.rs b/der/src/asn1/octet_string.rs index 12a1e0e4d..f44df8a57 100644 --- a/der/src/asn1/octet_string.rs +++ b/der/src/asn1/octet_string.rs @@ -244,13 +244,15 @@ mod bytes { #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use crate::asn1::{OctetStringRef, PrintableStringRef}; + use crate::asn1::{OctetString, OctetStringRef, PrintableStringRef}; #[test] fn octet_string_decode_into() { // PrintableString "hi" let der = b"\x13\x02\x68\x69"; let oct = OctetStringRef::new(der).unwrap(); + let oct_owned = OctetString::new("Halllo".as_bytes()).unwrap(); + assert!(!oct_owned.is_empty()); let res = oct.decode_into::>().unwrap(); assert_eq!(AsRef::::as_ref(&res), "hi");