diff --git a/.gitignore b/.gitignore index 8bf9ddb05..e9aa04a8b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ target/ # CLion IDE .idea + +# VS Code IDE +.vscode diff --git a/Cargo.lock b/Cargo.lock index d3831efad..34b6f2fbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -363,6 +363,7 @@ dependencies = [ "getrandom", "hex-literal", "p256", + "pbkdf2", "pem-rfc7468", "pkcs5", "rand", diff --git a/cms/Cargo.toml b/cms/Cargo.toml index d6b604e26..1ceaf952b 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -33,10 +33,12 @@ signature = { version = "=2.3.0-pre.4", features = ["digest", "alloc"], optional zeroize = { version = "1.8.1", optional = true } [dev-dependencies] +aes = "=0.9.0-pre.2" getrandom = "0.2" hex-literal = "0.4" pem-rfc7468 = "1.0.0-rc.1" pkcs5 = "0.8.0-rc.1" +pbkdf2 = "0.13.0-pre.0" rand = "0.8.5" rsa = { version = "=0.10.0-pre.3", features = ["sha2"] } ecdsa = { version = "=0.17.0-pre.9", features = ["digest", "pem"] } diff --git a/cms/src/builder.rs b/cms/src/builder.rs index 9f658ef50..693c4007e 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 async_signature::{AsyncRandomizedSigner, AsyncSigner}; use cipher::{ @@ -28,9 +28,9 @@ use cipher::{ use const_oid::ObjectIdentifier; use core::cmp::Ordering; use core::fmt; -use der::asn1::{BitString, OctetStringRef, SetOfVec}; +use core::marker::PhantomData; +use der::asn1::{BitString, Null, 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; @@ -584,6 +584,9 @@ impl<'s> SignedDataBuilder<'s> { /// Trait for builders of a `RecipientInfo`. RFC 5652 § 6 defines 5 different `RecipientInfo` /// formats. All implementations must implement this trait. pub trait RecipientInfoBuilder { + /// Associated Rng type + type Rng: CryptoRngCore; + /// Return the recipient info type fn recipient_info_type(&self) -> RecipientInfoType; @@ -592,7 +595,11 @@ pub trait RecipientInfoBuilder { /// Encrypt the `content_encryption_key` using a method, that is specific for the implementing /// builder type. Finally return a `RecipientInfo`. - fn build(&mut self, content_encryption_key: &[u8]) -> Result; + fn build_with_rng( + &mut self, + content_encryption_key: &[u8], + rng: &mut Self::Rng, + ) -> Result; } /// `RecipientInfoBuilder` must be implemented for these 5 recipient info types @@ -621,40 +628,31 @@ pub enum KeyEncryptionInfo { /// Builds a `KeyTransRecipientInfo` according to RFC 5652 § 6. /// This type uses the recipient's public key to encrypt the content-encryption key. -pub struct KeyTransRecipientInfoBuilder<'a, R> -where - R: CryptoRngCore, -{ +pub struct KeyTransRecipientInfoBuilder { /// Identifies the recipient pub rid: RecipientIdentifier, /// Info for key encryption pub key_encryption_info: KeyEncryptionInfo, - /// Rng - rng: &'a mut R, + _rng: PhantomData, } -impl<'a, R> KeyTransRecipientInfoBuilder<'a, R> -where - R: CryptoRngCore, -{ +impl KeyTransRecipientInfoBuilder { /// Creates a `KeyTransRecipientInfoBuilder` - pub fn new( - rid: RecipientIdentifier, - key_encryption_info: KeyEncryptionInfo, - rng: &'a mut R, - ) -> Result> { + pub fn new(rid: RecipientIdentifier, key_encryption_info: KeyEncryptionInfo) -> Result { Ok(KeyTransRecipientInfoBuilder { rid, key_encryption_info, - rng, + _rng: PhantomData, }) } } -impl<'a, R> RecipientInfoBuilder for KeyTransRecipientInfoBuilder<'a, R> +impl RecipientInfoBuilder for KeyTransRecipientInfoBuilder where R: CryptoRngCore, { + type Rng = R; + fn recipient_info_type(&self) -> RecipientInfoType { RecipientInfoType::Ktri } @@ -668,17 +666,21 @@ where /// Build a `KeyTransRecipientInfo`. See RFC 5652 § 6.2.1 /// `content_encryption_key` will be encrypted with the recipient's public key. - fn build(&mut self, content_encryption_key: &[u8]) -> Result { + fn build_with_rng( + &mut self, + content_encryption_key: &[u8], + rng: &mut Self::Rng, + ) -> Result { // Encrypt key let (encrypted_key, key_enc_alg) = match &self.key_encryption_info { // RSA encryption KeyEncryptionInfo::Rsa(recipient_public_key) => ( recipient_public_key - .encrypt(self.rng, Pkcs1v15Encrypt, content_encryption_key) + .encrypt(rng, Pkcs1v15Encrypt, content_encryption_key) .map_err(|_| Error::Builder(String::from("Could not encrypt key")))?, AlgorithmIdentifierOwned { oid: const_oid::db::rfc5912::RSA_ENCRYPTION, - parameters: None, + parameters: Some(Any::from(Null)), }, ), }; @@ -697,31 +699,38 @@ where /// This type uses key agreement: the recipient's public key and the sender's /// private key are used to generate a pairwise symmetric key, then /// the content-encryption key is encrypted in the pairwise symmetric key. -pub struct KeyAgreeRecipientInfoBuilder { +pub struct KeyAgreeRecipientInfoBuilder { /// A CHOICE with three alternatives specifying the sender's key agreement public key. pub originator: OriginatorIdentifierOrKey, /// Optional information which helps generating different keys every time. pub ukm: Option, /// Encryption algorithm to be used for key encryption pub key_enc_alg: AlgorithmIdentifierOwned, + _rng: PhantomData, } -impl KeyAgreeRecipientInfoBuilder { +impl KeyAgreeRecipientInfoBuilder { /// Creates a `KeyAgreeRecipientInfoBuilder` pub fn new( originator: OriginatorIdentifierOrKey, ukm: Option, key_enc_alg: AlgorithmIdentifierOwned, - ) -> Result { + ) -> Result { Ok(KeyAgreeRecipientInfoBuilder { originator, ukm, key_enc_alg, + _rng: PhantomData, }) } } -impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder { +impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder +where + R: CryptoRngCore, +{ + type Rng = R; + /// Returns the RecipientInfoType fn recipient_info_type(&self) -> RecipientInfoType { RecipientInfoType::Kari @@ -733,7 +742,11 @@ impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder { } /// Build a `KeyAgreeRecipientInfoBuilder`. See RFC 5652 § 6.2.1 - fn build(&mut self, _content_encryption_key: &[u8]) -> Result { + fn build_with_rng( + &mut self, + _content_encryption_key: &[u8], + _rng: &mut Self::Rng, + ) -> Result { Err(Error::Builder(String::from( "Building KeyAgreeRecipientInfo is not implemented, yet.", ))) @@ -743,28 +756,32 @@ impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder { /// Builds a `KekRecipientInfo` according to RFC 5652 § 6. /// Uses symmetric key-encryption keys: the content-encryption key is /// encrypted in a previously distributed symmetric key-encryption key. -pub struct KekRecipientInfoBuilder { +pub struct KekRecipientInfoBuilder { /// Specifies a symmetric key-encryption key that was previously distributed to the sender and /// one or more recipients. pub kek_id: KekIdentifier, /// Encryption algorithm to be used for key encryption pub key_enc_alg: AlgorithmIdentifierOwned, + _rng: PhantomData, } -impl KekRecipientInfoBuilder { +impl KekRecipientInfoBuilder { /// Creates a `KekRecipientInfoBuilder` - pub fn new( - kek_id: KekIdentifier, - key_enc_alg: AlgorithmIdentifierOwned, - ) -> Result { + pub fn new(kek_id: KekIdentifier, key_enc_alg: AlgorithmIdentifierOwned) -> Result { Ok(KekRecipientInfoBuilder { kek_id, key_enc_alg, + _rng: PhantomData, }) } } -impl RecipientInfoBuilder for KekRecipientInfoBuilder { +impl RecipientInfoBuilder for KekRecipientInfoBuilder +where + R: CryptoRngCore, +{ + type Rng = R; + /// Returns the RecipientInfoType fn recipient_info_type(&self) -> RecipientInfoType { RecipientInfoType::Kekri @@ -776,16 +793,45 @@ impl RecipientInfoBuilder for KekRecipientInfoBuilder { } /// Build a `KekRecipientInfoBuilder`. See RFC 5652 § 6.2.1 - fn build(&mut self, _content_encryption_key: &[u8]) -> Result { + fn build_with_rng( + &mut self, + _content_encryption_key: &[u8], + _rng: &mut Self::Rng, + ) -> Result { Err(Error::Builder(String::from( "Building KekRecipientInfo is not implemented, yet.", ))) } } -/// 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_BITS: 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) -> Result>; + /// Returns the algorithm identifier of the used encryption algorithm + /// including eventual parameters (e.g. the used iv). + fn key_encryption_algorithm(&self) -> Result; + /// Encrypt the padded content-encryption key twice following RFC 3211, § 2.3.1 + fn encrypt_rfc3211( + &mut self, + padded_content_encryption_key: &[u8], + rng: &mut impl CryptoRngCore, + ) -> 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 +where + P: PwriEncryptor, +{ /// 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 @@ -793,22 +839,85 @@ 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 + _rng: PhantomData, } -impl PasswordRecipientInfoBuilder { +impl PasswordRecipientInfoBuilder +where + P: PwriEncryptor, +{ /// Creates a `PasswordRecipientInfoBuilder` - pub fn new( - key_derivation_alg: Option, - key_enc_alg: AlgorithmIdentifierOwned, - ) -> Result { + /// `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_encryptor: P) -> 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: PhantomData, }) } } -impl RecipientInfoBuilder for PasswordRecipientInfoBuilder { +impl PasswordRecipientInfoBuilder +where + P: PwriEncryptor, + R: CryptoRngCore, +{ + /// 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) + /// + /// [RFC 3211, §2.3.1]: https://www.rfc-editor.org/rfc/rfc3211#section-2.3.1 + fn pad_content_encryption_key( + &mut self, + content_encryption_key: &[u8], + rng: &mut R, + ) -> Result> { + let content_encryption_key_length = content_encryption_key.len(); + let padded_key_length_wo_padding = 1 + 3 + content_encryption_key_length; + let key_enc_alg_blocklength_bytes = P::BLOCK_LENGTH_BITS / 8; + let padding_length = if padded_key_length_wo_padding < 2 * key_enc_alg_blocklength_bytes { + 2 * key_enc_alg_blocklength_bytes - padded_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 padded_cek: Vec = + Vec::with_capacity(4 + content_encryption_key_length + padding_length); + padded_cek.push(cek_byte_count); + padded_cek.push(0xff ^ content_encryption_key[0]); + padded_cek.push(0xff ^ content_encryption_key[1]); + padded_cek.push(0xff ^ content_encryption_key[2]); + padded_cek.extend_from_slice(content_encryption_key); + if padding_length > 0 { + let mut padding = vec![0_u8; padding_length]; + rng.fill_bytes(padding.as_mut_slice()); + padded_cek.append(&mut padding); + } + Ok(padded_cek) + } +} + +impl RecipientInfoBuilder for PasswordRecipientInfoBuilder +where + P: PwriEncryptor, + R: CryptoRngCore, +{ + type Rng = R; + /// Returns the RecipientInfoType fn recipient_info_type(&self) -> RecipientInfoType { RecipientInfoType::Pwri @@ -820,34 +929,54 @@ 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_with_rng( + &mut self, + content_encryption_key: &[u8], + rng: &mut Self::Rng, + ) -> Result { + let padded_cek = self.pad_content_encryption_key(content_encryption_key, rng)?; + let encrypted_key = self + .key_encryptor + .encrypt_rfc3211(padded_cek.as_slice(), rng)?; + 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, + })) } } /// Builds an `OtherRecipientInfo` according to RFC 5652 § 6. /// This type makes no assumption about the encryption method or the needed information. -pub struct OtherRecipientInfoBuilder { +pub struct OtherRecipientInfoBuilder { /// Identifies the key management technique. pub ori_type: ObjectIdentifier, /// Contains the protocol data elements needed by a recipient using the identified key /// management technique pub ori_value: Any, + + _rng: PhantomData, } -impl OtherRecipientInfoBuilder { +impl OtherRecipientInfoBuilder { /// Creates a `OtherRecipientInfoBuilder` - pub fn new(ori_type: ObjectIdentifier, ori_value: Any) -> Result { + pub fn new(ori_type: ObjectIdentifier, ori_value: Any) -> Result { Ok(OtherRecipientInfoBuilder { ori_type, ori_value, + _rng: PhantomData, }) } } -impl RecipientInfoBuilder for OtherRecipientInfoBuilder { +impl RecipientInfoBuilder for OtherRecipientInfoBuilder +where + R: CryptoRngCore, +{ + type Rng = R; + /// Returns the RecipientInfoType fn recipient_info_type(&self) -> RecipientInfoType { RecipientInfoType::Ori @@ -859,7 +988,11 @@ impl RecipientInfoBuilder for OtherRecipientInfoBuilder { } /// Build a `OtherRecipientInfoBuilder`. See RFC 5652 § 6.2.1 - fn build(&mut self, _content_encryption_key: &[u8]) -> Result { + fn build_with_rng( + &mut self, + _content_encryption_key: &[u8], + _rng: &mut Self::Rng, + ) -> Result { panic!("Ori has no common build method.") } } @@ -887,9 +1020,9 @@ impl ContentEncryptionAlgorithm { } /// Builds CMS `EnvelopedData` according to RFC 5652 § 6. -pub struct EnvelopedDataBuilder<'c> { +pub struct EnvelopedDataBuilder<'c, R> { originator_info: Option, - recipient_infos: Vec>, + recipient_infos: Vec + 'c>>, unencrypted_content: &'c [u8], // TODO bk Not good to offer both, `content_encryptor` and `content_encryption_algorithm`. // We should @@ -904,14 +1037,14 @@ pub struct EnvelopedDataBuilder<'c> { unprotected_attributes: Option, } -impl<'c> EnvelopedDataBuilder<'c> { +impl<'c, R> EnvelopedDataBuilder<'c, R> { /// Create a new builder for `EnvelopedData` pub fn new( originator_info: Option, unencrypted_content: &'c [u8], content_encryption_algorithm: ContentEncryptionAlgorithm, unprotected_attributes: Option, - ) -> Result> { + ) -> Result { Ok(EnvelopedDataBuilder { originator_info, recipient_infos: Vec::new(), @@ -920,12 +1053,17 @@ impl<'c> EnvelopedDataBuilder<'c> { unprotected_attributes, }) } +} +impl<'c, R> EnvelopedDataBuilder<'c, R> +where + R: CryptoRngCore, +{ /// Add recipient info. A builder is used, which generates a `RecipientInfo` according to /// RFC 5652 § 6.2, when `EnvelopedData` is built. pub fn add_recipient_info( &mut self, - recipient_info_builder: impl RecipientInfoBuilder + 'c, + recipient_info_builder: impl RecipientInfoBuilder + 'c, ) -> Result<&mut Self> { self.recipient_infos.push(Box::new(recipient_info_builder)); @@ -934,7 +1072,7 @@ impl<'c> EnvelopedDataBuilder<'c> { /// Generate an `EnvelopedData` object according to RFC 5652 § 6 using a provided /// random number generator. - pub fn build_with_rng(&mut self, rng: &mut impl CryptoRngCore) -> Result { + pub fn build_with_rng(&mut self, rng: &mut R) -> Result { // Generate content encryption key // Encrypt content // Build recipient infos @@ -945,7 +1083,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, @@ -955,7 +1093,7 @@ impl<'c> EnvelopedDataBuilder<'c> { let recipient_infos_vec = self .recipient_infos .iter_mut() - .map(|ri| ri.build(&content_encryption_key)) + .map(|ri| ri.build_with_rng(&content_encryption_key, rng)) .collect::>>()?; content_encryption_key.zeroize(); let recip_infos = RecipientInfos::try_from(recipient_infos_vec).unwrap(); @@ -1100,7 +1238,7 @@ macro_rules! encrypt_block_mode { key.to_vec(), AlgorithmIdentifierOwned { oid: $oid, - parameters: Some(Any::new(OctetString, iv.to_vec())?), + parameters: Some(Any::new(Tag::OctetString, iv.to_vec())?), }, )) }}; @@ -1164,7 +1302,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(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 b126c32e9..6462085d5 100644 --- a/cms/tests/builder.rs +++ b/cms/tests/builder.rs @@ -2,25 +2,29 @@ use aes::Aes128; use cipher::block_padding::Pkcs7; -use cipher::{BlockModeDecrypt, KeyIvInit}; +use cipher::{BlockModeDecrypt, BlockModeEncrypt, BlockSizeUser, Iv, IvSizeUser, 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; use cms::enveloped_data::RecipientInfo::Ktri; -use cms::enveloped_data::{EnvelopedData, RecipientIdentifier, RecipientInfo}; +use cms::enveloped_data::{ + EnvelopedData, PasswordRecipientInfo, RecipientIdentifier, RecipientInfo, +}; use cms::signed_data::{EncapsulatedContentInfo, SignedData, SignerIdentifier}; use const_oid::ObjectIdentifier; -use der::asn1::{OctetString, PrintableString, SetOfVec}; +use der::asn1::{OctetString, OctetStringRef, PrintableString, SetOfVec}; use der::{Any, AnyRef, Decode, DecodePem, Encode, Tag, Tagged}; use p256::{pkcs8::DecodePrivateKey, NistP256}; use pem_rfc7468::LineEnding; +use pkcs5::pbes2::Pbkdf2Params; use rand::rngs::OsRng; use rsa::pkcs1::DecodeRsaPrivateKey; -use rsa::pkcs1v15; -use rsa::pss; +use rsa::rand_core::CryptoRngCore; +use rsa::{pkcs1v15, pss}; use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey}; use sha2::Sha256; use signature::Verifier; @@ -108,7 +112,7 @@ fn test_build_signed_data() { let external_message_digest_2 = None; let signer_info_builder_2 = SignerInfoBuilder::new( signer_identifier(1), - digest_algorithm_2.clone(), + digest_algorithm_2, &content, external_message_digest_2, ) @@ -184,7 +188,6 @@ fn test_build_enveloped_data() { let recipient_info_builder = KeyTransRecipientInfoBuilder::new( recipient_identifier, KeyEncryptionInfo::Rsa(recipient_public_key), - &mut rng, ) .expect("Could not create a KeyTransRecipientInfoBuilder"); @@ -270,9 +273,6 @@ fn test_build_pkcs7_scep_pkcsreq() { // - Wrap enveloped data in `SignedData` // - Sign with sender's RSA key. - // Generate a random number generator - let mut rng = rand::thread_rng(); - // Create recipient info let recipient_identifier = recipient_identifier(42); let recipient_private_key = @@ -284,7 +284,6 @@ fn test_build_pkcs7_scep_pkcsreq() { let recipient_info_builder = KeyTransRecipientInfoBuilder::new( recipient_identifier.clone(), KeyEncryptionInfo::Rsa(recipient_public_key), - &mut rng, ) .unwrap(); @@ -667,3 +666,301 @@ async fn async_builder() { .expect("PEM encoding of signed data DER failed") ); } + +#[test] +/// This demonstrates and tests creating and receiving a CMS message using +/// PasswordRecipientInfoBuilder (pwri) according to RFC3211, +/// using Aes128Cbc for encryption of the content-encryption key (CEK). +/// Note: if you want to create a CMS message using pwri, you have to implement +/// an encryptor, that is supported by sender and receiver. The following +/// implementation uses AES128-CBC and can be used as a template for other +/// encryption methods. +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, + } + impl<'a> Aes128CbcPwriEncryptor<'a> { + pub fn new(challenge_password: &'a [u8], rng: &mut impl CryptoRngCore) -> Self { + let mut key_encryption_iv = [0u8; 16]; + rng.fill_bytes(key_encryption_iv.as_mut_slice()); + let key_encryption_iv = key_encryption_iv.into(); + + Aes128CbcPwriEncryptor { + challenge_password, + key_encryption_iv, + key_derivation_params: pkcs5::pbes2::Pbkdf2Params::hmac_with_sha256( + 60_000, // use >=600_000 in real world applications + b"salz", + ) + .unwrap(), + } + } + } + impl<'a> PwriEncryptor for Aes128CbcPwriEncryptor<'a> { + const BLOCK_LENGTH_BITS: usize = 128; // AES block length + fn encrypt_rfc3211( + &mut self, + padded_content_encryption_key: &[u8], + _rng: &mut impl CryptoRngCore, + ) -> Result, cms::builder::Error> { + if padded_content_encryption_key.len() < 2 * Self::BLOCK_LENGTH_BITS / 8 { + return Err(cms::builder::Error::Builder( + "Padded content encryption key must be at least 2 AES blocks long.".to_string(), + )); + } + // 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.as_bytes(), + self.key_derivation_params.iteration_count, + &mut key_encryption_key, + ); + + // Encrypt first time + let mut encryptor: cbc::Encryptor = + cbc::Encryptor::::new(&key_encryption_key.into(), &self.key_encryption_iv); + // Allocate memory for encrypted cek and pre-fill with unencrypted key for in-place + // encryption. + let mut encrypted_cek_blocks: Vec = padded_content_encryption_key + .chunks_exact(Self::BLOCK_LENGTH_BITS / 8) + .map(|chunk| { + let mut block = [0_u8; Self::BLOCK_LENGTH_BITS / 8]; + block.copy_from_slice(chunk); + aes::Block::from(block) + }) + .collect(); + encryptor.encrypt_blocks(encrypted_cek_blocks.as_mut_slice()); + + // Encrypt result again (see RFC 3211) taking last encrypted block as iv. + encryptor = cbc::Encryptor::::new( + &key_encryption_key.into(), + encrypted_cek_blocks + .last() + .expect("Pass 1 encrypted cek cannot be empty"), + ); + encryptor.encrypt_blocks(encrypted_cek_blocks.as_mut_slice()); + Ok(encrypted_cek_blocks + .into_iter() + .flat_map(|block| block.into_iter()) + .collect()) + } + + fn key_derivation_algorithm( + &self, + ) -> Result, cms::builder::Error> { + let key_derivation_params_der = self.key_derivation_params.to_der()?; + Ok(Some(AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_PBKDF_2, + parameters: Some(Any::from_der(key_derivation_params_der.as_slice())?), + })) + } + + fn key_encryption_algorithm( + &self, + ) -> Result { + Ok(AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_AES_128_CBC, + parameters: Some(Any::new( + der::Tag::OctetString, + self.key_encryption_iv.to_vec(), + )?), + }) + } + } + + pub fn cms_pwri_decrypt_content_encryption_key( + recipient_info: &PasswordRecipientInfo, + challenge_password: &str, + ) -> Vec { + // Derive key from challenge password. + let key_derivation_alg = recipient_info.key_derivation_alg.clone().unwrap(); + assert_eq!(key_derivation_alg.oid, const_oid::db::rfc5911::ID_PBKDF_2); + let key_derivation_parameters_any = &key_derivation_alg.parameters.unwrap(); + let key_derivation_parameters_der = key_derivation_parameters_any.to_der().unwrap(); + let kdf_parameters = + Pbkdf2Params::from_der(key_derivation_parameters_der.as_slice()).unwrap(); + let salt = kdf_parameters.salt; + let iteration_count = kdf_parameters.iteration_count; + let mut key_encryption_key = [0_u8; 16]; + pbkdf2::pbkdf2_hmac::( + challenge_password.as_bytes(), + salt.as_bytes(), + iteration_count, + &mut key_encryption_key, + ); + let key_encryption_key = cipher::Key::>::from(key_encryption_key); + + // Decrypt twice according to RFC 3211 + assert_eq!( + recipient_info.key_enc_alg.oid, + const_oid::db::rfc5911::ID_AES_128_CBC + ); + let block_size_in_bytes = cbc::Decryptor::::block_size(); + let iv_size_in_bytes = cbc::Decryptor::::iv_size(); + + let enc_key_len = u32::from(recipient_info.enc_key.len()) as usize; + assert!(enc_key_len >= 2 * block_size_in_bytes); + + // Allocate memory for the decrypted cek and pre-fill with encrypted cek. + let mut padded_cek_blocks: Vec = recipient_info + .enc_key + .as_bytes() + .chunks_exact(block_size_in_bytes) + .map(|chunk| { + let mut block = [0_u8; 16]; // 16 == block_size_in_bytes + block.copy_from_slice(chunk); + aes::Block::from(block) + }) + .collect(); + + // 1. Using the n-1'th ciphertext block as the IV, decrypt the n'th ciphertext block. + let iv_pre = + Iv::>::from(padded_cek_blocks[padded_cek_blocks.len() - 2]); + let iv_encrypted_block = padded_cek_blocks[padded_cek_blocks.len() - 1]; + let mut iv = Iv::>::from([0_u8; 16]); + cbc::Decryptor::::new(&key_encryption_key, &iv_pre) + .decrypt_block_b2b(&iv_encrypted_block, &mut iv); + + // 2. Using the decrypted n'th ciphertext block as the IV, decrypt the 1st ... n-1'th + // ciphertext blocks. This strips the outer layer of encryption. + // Decryption is in-place. + cbc::Decryptor::::new(&key_encryption_key, &iv) + .decrypt_blocks(padded_cek_blocks.as_mut_slice()); + + // 3. Decrypt the inner layer of encryption using the KEK. + // Decryption is in-place. + let iv2_bytes = get_iv_from_algorithm_identifier(&recipient_info.key_enc_alg); + assert!(iv2_bytes.as_bytes().len() == iv_size_in_bytes); + let iv2 = Iv::>::try_from(iv2_bytes.as_bytes()).unwrap(); + cbc::Decryptor::::new(&key_encryption_key, &iv2) + .decrypt_blocks(padded_cek_blocks.as_mut_slice()); + + let padded_cek: Vec = padded_cek_blocks + .into_iter() + .flat_map(|block| block.into_iter()) + .collect(); + + cms_pwri_unpad_content_encryption_key(padded_cek.as_slice(), aes::Aes128::block_size()) + } + + /// Return the IV, which is stored in the algorithm params for AES algorithm identifiers. + fn get_iv_from_algorithm_identifier( + encryption_algorithm_identifier: &AlgorithmIdentifierOwned, + ) -> OctetString { + let encryption_params = &encryption_algorithm_identifier.parameters.clone().unwrap(); + let iv_der = encryption_params.to_der().unwrap(); + let iv_octet_string = OctetString::from_der(iv_der.as_slice()).unwrap(); + + iv_octet_string + } + + /// Unpad a content-encryption key (CEK) according RFC 3211, §2.3.1 + /// The formatted CEK block looks as follows: + /// CEK byte count || check value || CEK || padding (if required) + fn cms_pwri_unpad_content_encryption_key( + padded_content_encryption_key: &[u8], + block_length: usize, + ) -> Vec { + assert!(padded_content_encryption_key.len() >= 2 * block_length); + assert!(padded_content_encryption_key.len() % block_length == 0); + + let content_encryption_key_length = padded_content_encryption_key[0] as usize; + assert!(content_encryption_key_length != 0); + assert!(padded_content_encryption_key.len() >= content_encryption_key_length + 4); + let mut content_encryption_key = vec![]; + content_encryption_key.extend_from_slice( + &padded_content_encryption_key[4..(4 + content_encryption_key_length)], + ); + assert_eq!( + 0xff ^ padded_content_encryption_key[1], + content_encryption_key[0] + ); + assert_eq!( + 0xff ^ padded_content_encryption_key[2], + content_encryption_key[1] + ); + assert_eq!( + 0xff ^ padded_content_encryption_key[3], + content_encryption_key[2] + ); + + content_encryption_key + } + + let mut the_one_and_only_rng = OsRng; + + // Encrypt the content-encryption key (CEK) using custom encryptor + // of type `Aes128CbcPwriEncryptor`: + let challenge_password = "chellange pazzw0rd"; + let key_encryptor = + Aes128CbcPwriEncryptor::new(challenge_password.as_bytes(), &mut the_one_and_only_rng); + + // Create recipient info + let recipient_info_builder = PasswordRecipientInfoBuilder::new(key_encryptor).unwrap(); + + 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 the_one_and_only_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") + ); + + // Decrypt CEK and content + let recipient_info = enveloped_data.recip_infos.0.get(0).unwrap(); + if let RecipientInfo::Pwri(recipient_info) = recipient_info { + let decrypted_content_encryption_key = + cms_pwri_decrypt_content_encryption_key(recipient_info, challenge_password); + let content_encryption_key = cipher::Key::>::try_from( + decrypted_content_encryption_key.as_slice(), + ) + .unwrap(); + let algorithm_params_der = enveloped_data + .encrypted_content + .content_enc_alg + .parameters + .unwrap() + .to_der() + .unwrap(); + let iv = Iv::>::try_from( + OctetStringRef::from_der(algorithm_params_der.as_slice()) + .unwrap() + .as_bytes(), + ) + .unwrap(); + let decryptor: cbc::Decryptor = + cbc::Decryptor::::new(&content_encryption_key, &iv); + let decrypted_content = decryptor + .decrypt_padded_vec::( + enveloped_data + .encrypted_content + .encrypted_content + .unwrap() + .as_bytes(), + ) + .unwrap(); + // This is the final test: do we get the original content? + assert_eq!( + decrypted_content, + "Arbitrary unencrypted content".as_bytes() + ) + }; +}