Skip to content

Commit

Permalink
Implemented PasswordRecipientInfoBuilder for cms
Browse files Browse the repository at this point in the history
  • Loading branch information
bkstein committed Nov 30, 2023
1 parent 70cef1d commit c0c3195
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 25 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
target/
**/*.rs.bk

# CLion IDE
# IDEs
.idea
.vscode

# Artifacts
*.orig
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions cms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
124 changes: 103 additions & 21 deletions cms/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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};
Expand All @@ -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;
Expand Down Expand Up @@ -705,32 +704,109 @@ 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<AlgorithmIdentifierOwned>;
/// 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<Vec<u8>>;
}

/// 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
/// token such as a smart card.
pub key_derivation_alg: Option<AlgorithmIdentifierOwned>,
/// 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<AlgorithmIdentifierOwned>,
key_enc_alg: AlgorithmIdentifierOwned,
) -> Result<PasswordRecipientInfoBuilder> {
key_encryptor: P,
rng: &'r mut R,
) -> Result<PasswordRecipientInfoBuilder<'r, P, R>> {
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<Vec<u8>> {
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<u8> = 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
Expand All @@ -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<RecipientInfo> {
Err(Error::Builder(String::from(
"Building PasswordRecipientInfo is not implemented, yet.",
)))
fn build(&mut self, content_encryption_key: &[u8]) -> Result<RecipientInfo> {
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,
}))
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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())?),
},
))
}};
Expand Down Expand Up @@ -1085,7 +1167,7 @@ pub fn create_content_type_attribute(content_type: ObjectIdentifier) -> Result<A
pub fn create_message_digest_attribute(message_digest: &[u8]) -> Result<Attribute> {
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 {
Expand Down
113 changes: 111 additions & 2 deletions cms/tests/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<cbc::Encryptor<Aes128>>,
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::<Aes128>::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<Vec<u8>, cms::builder::Error> {
// Derive a key-encryption key from the challenge password.
let mut key_encryption_key = [0_u8; 16];
pbkdf2::pbkdf2_hmac::<Sha256>(
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::<cbc::Encryptor<Aes128>>::from_slice(&key_encryption_key).to_owned();
let mut encryptor = cbc::Encryptor::<Aes128>::new(&key.into(), &self.key_encryption_iv);
let tmp = encryptor.encrypt_padded_vec_mut::<Pkcs7>(wrapped_content_encryption_key);

// Encrypt result again (see RFC 3211)
encryptor = cbc::Encryptor::<Aes128>::new(
&key.into(),
aes::Block::from_slice(&tmp[tmp.len() - self.key_encryption_iv.len()..]),
);
Ok(encryptor.encrypt_padded_vec_mut::<Pkcs7>(tmp.as_slice()))
}

fn key_derivation_algorithm(&self) -> Option<AlgorithmIdentifierOwned> {
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")
);
}
4 changes: 3 additions & 1 deletion der/src/asn1/octet_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<PrintableStringRef<'_>>().unwrap();
assert_eq!(AsRef::<str>::as_ref(&res), "hi");
Expand Down

0 comments on commit c0c3195

Please sign in to comment.