Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PasswordRecipientInfoBuilder for CMS #1273

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c0c3195
Implemented PasswordRecipientInfoBuilder for cms
bkstein Nov 30, 2023
d819cd4
Clippy and rustfmt
bkstein Nov 30, 2023
a3a6708
Link RFC 3211
bkstein Dec 4, 2023
ee851f5
Improve code readability
bkstein Dec 4, 2023
7b23a0f
Fix construction of `wrapped_cek`
bkstein Dec 4, 2023
d0a4f8e
Changed `PwriEncryptor` return types to Result<>
bkstein Dec 5, 2023
b505f04
Merge branch 'master' of https://github.com/RustCrypto/formats into c…
bkstein Dec 5, 2023
0abe441
Fixed type declaration
bkstein Dec 5, 2023
572d2d0
Removed debugging code.
bkstein Dec 6, 2023
7cd04da
Fixed key derivation for PwriEncryptor in test
bkstein Dec 7, 2023
33f7abb
Adding CEK to wrapped key.
bkstein Dec 8, 2023
86c7e2e
Fixed appending CEK to wrapped CEK.
bkstein Dec 8, 2023
483f977
Fixed padded key length calculation
bkstein Dec 11, 2023
7126d8e
Added decryption in Pwri test
bkstein Dec 13, 2023
32fd0e1
Clippy...
bkstein Dec 13, 2023
f56f075
Fixed invalid encoding of rsaEncryption AlgorithmIdentifier
bkstein Dec 19, 2023
603ca6d
cms: do not hold a mutable reference to Rng
baloo Dec 3, 2023
14ca4dd
Merge branch 'master' into cms/pwri-builder
bkstein Apr 22, 2024
ed0aa1c
Fixed a clippy
bkstein Apr 25, 2024
49d443c
Trigger build
bkstein May 2, 2024
b29c6c8
Merge branch 'master' into cms/pwri-builder
bkstein May 2, 2024
ffb8356
Cargo.lock update
bkstein May 2, 2024
88e3f59
Merge branch 'master' into cms/pwri-builder
bkstein Jul 16, 2024
ad8f402
Saving work
bkstein Jul 24, 2024
c624f74
Update to current master
kletterstein Jul 27, 2024
ebf849d
Updated pwri changes in cms.
kletterstein Jul 27, 2024
66a4e7c
Fixed Cargo.lock
kletterstein Jul 27, 2024
4117ae4
Merge remote-tracking branch 'rustcrypto/master' into cms/pwri-builder
kletterstein Jul 27, 2024
a51145c
Merge remote-tracking branch 'rustcrypto/master' into cms/pwri-builder
kletterstein Jul 28, 2024
b62fc35
Merge remote-tracking branch 'rustcrypto/master' into cms/pwri-builder
kletterstein Jul 28, 2024
8044862
Fixing dependency versions (aes)
kletterstein Jul 28, 2024
201ddbb
Fixed clippies
kletterstein Jul 28, 2024
d57c3ab
Before update to official master
kletterstein Oct 13, 2024
cbd8f27
Merge remote-tracking branch 'rustcrypto/master' into cms/pwri-builder
kletterstein Oct 13, 2024
5fdf587
Saving work before baloo merge
kletterstein Oct 13, 2024
765e447
Merge remote-tracking branch 'baloolocal/cms/pwri-builder' into cms/p…
kletterstein Oct 15, 2024
05c23aa
Fixed some merge errors
kletterstein Oct 15, 2024
33cf7a6
PwriRecipientInfoBuilder test completed
kletterstein Oct 16, 2024
85845d7
Formatting
kletterstein Oct 16, 2024
48e0a4e
Rustfmt
kletterstein Oct 16, 2024
e374257
Merge remote-tracking branch 'rustcrypto/master' into cms/pwri-builder
kletterstein Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
121 changes: 99 additions & 22 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,104 @@ 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) -> Result<Option<AlgorithmIdentifierOwned>>;
/// Returns the algorithm identifier of the used encryption algorithm
/// including eventual parameters (e.g. the used iv).
fn key_encryption_algorithm(&self) -> Result<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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be worth changing the signature of RecipientInfoBuilder::build trait function to build_with_rng, just so we don't have to bind the rng like that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe not, it's not object-safe anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we leave it as it is? Or what kind of binding would you prefer?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too fan of the &mut R in an object. I think that makes it harder to use. You need two instances of the RNG: for example in the context of HSM, you might not have two references to the RNG easily (kough pkcs11 kough).

If we could bring in the rng "late" (it's already in the EnvelopedDataBuilder::build_with_rng context), that be great. But the Vec<Box<dyn RecipientInfoBuilder>> requires it to be object-safe, which makes it impossible.

I don't have a good solution to offer, but that's my concern here (not a blocker obviously).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only solution I could come up with is this one: bkstein#1
But this introduces some API breakage in the builder (but we can just push that in a cms-0.3)

}

impl PasswordRecipientInfoBuilder {
impl<'r, P, R> PasswordRecipientInfoBuilder<'r, P, R>
where
P: PwriEncryptor,
R: CryptoRngCore,
{
/// Creates a `PasswordRecipientInfoBuilder`
pub fn new(
key_derivation_alg: Option<AlgorithmIdentifierOwned>,
key_enc_alg: AlgorithmIdentifierOwned,
) -> Result<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_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)
///
/// [RFC 3211, §2.3.1]: https://www.rfc-editor.org/rfc/rfc3211#section-2.3.1
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 wrapped_cek: Vec<u8> = Vec::with_capacity(4 + padding_length);
wrapped_cek.push(cek_byte_count);
wrapped_cek.push(0xff ^ content_encryption_key[0]);
wrapped_cek.push(0xff ^ content_encryption_key[1]);
wrapped_cek.push(0xff ^ content_encryption_key[2]);
if padding_length > 0 {
bkstein marked this conversation as resolved.
Show resolved Hide resolved
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 +813,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 +944,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 +1098,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 +1162,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
117 changes: 114 additions & 3 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 @@ -121,7 +122,7 @@ fn test_build_signed_data() {
let signer_info_builder_2 = SignerInfoBuilder::new(
&signer_2,
signer_identifier(1),
digest_algorithm_2.clone(),
digest_algorithm_2,
&content,
external_message_digest_2,
)
Expand Down Expand Up @@ -580,3 +581,113 @@ 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, &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,
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,
) -> Result<Option<AlgorithmIdentifierOwned>, cms::builder::Error> {
Ok(Some(AlgorithmIdentifierOwned {
oid: const_oid::db::rfc5911::ID_PBKDF_2,
parameters: Some(Any::new(
der::Tag::Sequence,
self.key_derivation_params.to_der()?,
)?),
}))
}

fn key_encryption_algorithm(
&self,
) -> Result<AlgorithmIdentifierOwned, cms::builder::Error> {
Ok(AlgorithmIdentifierOwned {
oid: const_oid::db::rfc5911::ID_AES_128_CBC,
parameters: Some(Any::new(
der::Tag::OctetString,
self.key_encryption_iv.to_vec(),
)?),
})
}
}

// 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};
baloo marked this conversation as resolved.
Show resolved Hide resolved

#[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
Loading