diff --git a/Cargo.lock b/Cargo.lock index b2621cdbd..f676e79ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "aes-kw" +version = "0.3.0-pre" +source = "git+https://github.com/RustCrypto/key-wraps.git#bb4402822ec6b876d87b85f6495b8a4ca9a295bd" +dependencies = [ + "aes", + "const-oid", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -66,6 +75,14 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "ansi-x963-kdf" +version = "0.1.0" +source = "git+https://github.com/RustCrypto/KDFs.git#6f0b04d3fc1e6b7b7fa1ebca4d2be52d9e107dc9" +dependencies = [ + "digest", +] + [[package]] name = "anstyle" version = "1.0.10" @@ -309,12 +326,16 @@ name = "cms" version = "0.3.0-pre.0" dependencies = [ "aes", + "aes-kw", + "ansi-x963-kdf", "async-signature", "cbc", "cipher", "const-oid", "der", + "digest", "ecdsa", + "elliptic-curve", "getrandom 0.3.1", "hex-literal", "p256", @@ -533,6 +554,7 @@ dependencies = [ "digest", "ff", "group", + "hkdf", "hybrid-array", "pem-rfc7468", "pkcs8", @@ -723,6 +745,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hkdf" +version = "0.13.0-pre.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00176ff81091018d42ff82e8324f8e5adb0b7e0468d1358f653972562dbff031" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.13.0-pre.4" diff --git a/Cargo.toml b/Cargo.toml index 85cc8f141..291f3a790 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,3 +58,13 @@ tls_codec_derive = { path = "./tls_codec/derive" } x509-tsp = { path = "./x509-tsp" } x509-cert = { path = "./x509-cert" } x509-ocsp = { path = "./x509-ocsp" } + +# https://github.com/RustCrypto/key-wraps/pull/34 +# https://github.com/RustCrypto/key-wraps/pull/35 +# https://github.com/RustCrypto/key-wraps/pull/39 +aes-kw = { git = "https://github.com/RustCrypto/key-wraps.git" } + + +# https://github.com/RustCrypto/KDFs/pull/102 +ansi-x963-kdf = { git = "https://github.com/RustCrypto/KDFs.git" } + diff --git a/cms/Cargo.toml b/cms/Cargo.toml index d1ea5ac5e..da97689df 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -22,9 +22,13 @@ x509-cert = { version = "=0.3.0-pre.0", default-features = false } # optional dependencies aes = { version = "=0.9.0-pre.2", optional = true } +aes-kw = { version ="=0.3.0-pre", optional = true } +ansi-x963-kdf = { version = "0.1.0", optional = true } async-signature = { version = "=0.6.0-pre.4", features = ["digest", "rand_core"], optional = true } cbc = { version = "=0.2.0-pre.2", optional = true } cipher = { version = "=0.5.0-pre.7", features = ["alloc", "block-padding", "rand_core"], optional = true } +digest = { version = "0.11.0-pre.9", optional = true } +elliptic-curve = { version = "=0.14.0-rc.1", optional = true } rsa = { version = "=0.10.0-pre.3", optional = true } sha1 = { version = "=0.11.0-pre.4", optional = true } sha2 = { version = "=0.11.0-pre.4", optional = true } @@ -50,9 +54,14 @@ x509-cert = { version = "=0.3.0-pre.0", features = ["pem"] } std = ["der/std", "spki/std"] builder = [ "dep:aes", + "dep:aes-kw", + "dep:ansi-x963-kdf", "dep:async-signature", "dep:cbc", "dep:cipher", + "dep:digest", + "elliptic-curve/ecdh", + "elliptic-curve/pkcs8", "dep:rsa", "dep:sha1", "dep:sha2", diff --git a/cms/src/builder.rs b/cms/src/builder.rs index 89bbcdc63..57650b338 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, PasswordRecipientInfo, RecipientIdentifier, - RecipientInfo, RecipientInfos, UserKeyingMaterial, + OriginatorInfo, PasswordRecipientInfo, RecipientIdentifier, RecipientInfo, RecipientInfos, + UserKeyingMaterial, }; use crate::revocation::{RevocationInfoChoice, RevocationInfoChoices}; use crate::signed_data::{ @@ -47,6 +47,16 @@ use x509_cert::attr::{Attribute, AttributeValue, Attributes}; use x509_cert::builder::{self, AsyncBuilder, Builder}; use zeroize::Zeroize; +// Modules +mod kari; +mod utils; + +// Exports +pub use kari::{ + DhSinglePassStdDhKdf, EcKeyEncryptionInfo, KeyAgreeRecipientInfoBuilder, KeyAgreementAlgorithm, +}; +pub use utils::kw::KeyWrapAlgorithm; + /// Error type #[derive(Debug)] #[non_exhaustive] @@ -695,64 +705,6 @@ where } } -/// Builds a `KeyAgreeRecipientInfo` according to RFC 5652 § 6. -/// 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 { - /// 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 { - /// Creates a `KeyAgreeRecipientInfoBuilder` - pub fn new( - originator: OriginatorIdentifierOrKey, - ukm: Option, - key_enc_alg: AlgorithmIdentifierOwned, - ) -> Result { - Ok(KeyAgreeRecipientInfoBuilder { - originator, - ukm, - key_enc_alg, - _rng: PhantomData, - }) - } -} - -impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder -where - R: CryptoRngCore, -{ - type Rng = R; - - /// Returns the RecipientInfoType - fn recipient_info_type(&self) -> RecipientInfoType { - RecipientInfoType::Kari - } - - /// Returns the `CMSVersion` for this `RecipientInfo` - fn recipient_info_version(&self) -> CmsVersion { - CmsVersion::V3 - } - - /// Build a `KeyAgreeRecipientInfoBuilder`. See RFC 5652 § 6.2.1 - 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.", - ))) - } -} - /// 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. diff --git a/cms/src/builder/kari.rs b/cms/src/builder/kari.rs new file mode 100644 index 000000000..3d541ccbc --- /dev/null +++ b/cms/src/builder/kari.rs @@ -0,0 +1,468 @@ +//! Key Agreement Recipient Info (Kari) Builder +//! +//! This module contains the building logic for Key Agreement Recipient Info. +//! It partially implements [RFC 5753]. +//! +//! [RFC 5753]: https://datatracker.ietf.org/doc/html/rfc5753 +//! + +// Super imports +use super::{ + utils::kw::{KeyWrapAlgorithm, WrappedKey}, + AlgorithmIdentifierOwned, CryptoRngCore, RecipientInfoBuilder, RecipientInfoType, Result, + UserKeyingMaterial, +}; + +// Crate imports +#[cfg(doc)] +use crate::enveloped_data::EnvelopedData; +use crate::{ + content_info::CmsVersion, + enveloped_data::{ + EncryptedKey, KeyAgreeRecipientIdentifier, KeyAgreeRecipientInfo, + OriginatorIdentifierOrKey, OriginatorPublicKey, RecipientEncryptedKey, RecipientInfo, + }, +}; + +// Internal imports +use const_oid::{db::rfc5753, AssociatedOid, ObjectIdentifier}; +use der::{ + asn1::{BitString, OctetString}, + Any, Decode, Encode, Sequence, +}; + +// Core imports +use core::{marker::PhantomData, ops::Add}; + +// Alloc imports +use alloc::{string::String, vec, vec::Vec}; + +// RustCrypto imports +use aes::cipher::{ + array::ArraySize, + typenum::{Sum, U8}, + KeySizeUser, +}; +use digest::{Digest, FixedOutputReset}; +use elliptic_curve::{ + ecdh::{EphemeralSecret, SharedSecret}, + point::PointCompression, + sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, + AffinePoint, Curve, CurveArithmetic, FieldBytesSize, PublicKey, +}; + +/// The `EccCmsSharedInfo` type is defined in [RFC 5753 Section 7.2]. +/// +/// ```text +/// EccCmsSharedInfo ::= SEQUENCE { +/// keyInfo AlgorithmIdentifier, +/// entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL, +/// suppPubInfo [2] EXPLICIT OCTET STRING } +/// ``` +/// +/// [RFC 5753 Section 7.2]: https://www.rfc-editor.org/rfc/rfc5753#section-7.2 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct EccCmsSharedInfo { + /// Object identifier of the key-encryption algorithm + pub key_info: AlgorithmIdentifierOwned, + /// Additional keying material - optional + #[asn1( + context_specific = "0", + tag_mode = "EXPLICIT", + constructed = "true", + optional = "true" + )] + pub entity_u_info: Option, + /// Length of the generated KEK, in bits, represented as a 32-bit number + #[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")] + pub supp_pub_info: OctetString, +} + +/// Represents supported key agreement algorithm for ECC - as defined in [RFC 5753 Section 7.1.4]. +/// +/// As per [RFC 5753 Section 8]: +/// ```text +/// Implementations that support EnvelopedData with the ephemeral-static +/// ECDH standard primitive: +/// +/// - MUST support the dhSinglePass-stdDH-sha256kdf-scheme key +/// agreement algorithm, the id-aes128-wrap key wrap algorithm, and +/// the id-aes128-cbc content encryption algorithm; and +/// - MAY support the dhSinglePass-stdDH-sha1kdf-scheme, dhSinglePass- +/// stdDH-sha224kdf-scheme, dhSinglePass-stdDH-sha384kdf-scheme, and +/// dhSinglePass-stdDH-sha512kdf-scheme key agreement algorithms; +/// the id-alg-CMS3DESwrap, id-aes192-wrap, and id-aes256-wrap key +/// wrap algorithms; and the des-ede3-cbc, id-aes192-cbc, and id- +/// aes256-cbc content encryption algorithms; other algorithms MAY +/// also be supported. +/// ``` +/// +/// As such the following are currently supported: +/// - dhSinglePass-stdDH-sha224kdf-scheme +/// - dhSinglePass-stdDH-sha256kdf-scheme +/// - dhSinglePass-stdDH-sha384kdf-scheme +/// - dhSinglePass-stdDH-sha512kdf-scheme +/// +/// [RFC 5753 Section 7.1.4]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.4 +/// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 +pub trait KeyAgreementAlgorithm { + /// Compute a shared key with the provided parameters + fn kdf( + secret: &SharedSecret, + shared_info: &EccCmsSharedInfo, + key_wrapper: &mut WrappedKey, + ) -> Result<()> + where + C: Curve, + Enc: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add; +} + +/// Support for EnvelopedData with the ephemeral-static ECDH cofactor primitive +pub struct DhSinglePassStdDhKdf { + digest: PhantomData, +} + +impl KeyAgreementAlgorithm for DhSinglePassStdDhKdf +where + D: Digest + FixedOutputReset, +{ + fn kdf( + secret: &SharedSecret, + shared_info: &EccCmsSharedInfo, + key_wrapper: &mut WrappedKey, + ) -> Result<()> + where + C: Curve, + Enc: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, + { + let shared_info_der = shared_info.to_der()?; + let secret_bytes = secret.raw_secret_bytes(); + + ansi_x963_kdf::derive_key_into::(secret_bytes, &shared_info_der, key_wrapper.as_mut()) + .map_err(|_| { + super::Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + })?; + Ok(()) + } +} + +impl AssociatedOid for DhSinglePassStdDhKdf { + const OID: ObjectIdentifier = rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME; +} + +impl AssociatedOid for DhSinglePassStdDhKdf { + const OID: ObjectIdentifier = rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME; +} + +impl AssociatedOid for DhSinglePassStdDhKdf { + const OID: ObjectIdentifier = rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME; +} + +impl AssociatedOid for DhSinglePassStdDhKdf { + const OID: ObjectIdentifier = rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME; +} + +/// Contains information required to encrypt the content encryption key with a method based on ECC key agreement +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EcKeyEncryptionInfo +where + C: CurveArithmetic, +{ + /// Encrypt key with EC + Ec(PublicKey), +} +impl EcKeyEncryptionInfo +where + C: CurveArithmetic + AssociatedOid, +{ + /// Returns the OID associated with the curve used. + pub fn get_oid(&self) -> ObjectIdentifier { + C::OID + } +} +impl From> for AlgorithmIdentifierOwned +where + C: CurveArithmetic + AssociatedOid, +{ + fn from(ec_key_encryption_info: EcKeyEncryptionInfo) -> Self { + let parameters = Some(Any::from(&ec_key_encryption_info.get_oid())); + AlgorithmIdentifierOwned { + oid: elliptic_curve::ALGORITHM_OID, // id-ecPublicKey + parameters, // Curve OID + } + } +} + +/// Builds a `KeyAgreeRecipientInfo` according to RFC 5652 § 6. +/// 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 +where + R: CryptoRngCore, + C: CurveArithmetic, + KA: KeyAgreementAlgorithm, + KW: KeyWrapAlgorithm, + Enc: KeySizeUser, +{ + /// Optional information which helps generating different keys every time. + pub ukm: Option, + /// Recipient identifier + pub rid: KeyAgreeRecipientIdentifier, + /// Recipient key info + pub eckey_encryption_info: EcKeyEncryptionInfo, + /// Key agreement algorithm + key_agreement_algorithm: PhantomData, + /// Key wrap algorithm + key_wrap_algorithm: PhantomData, + /// Content encryption cipher + enc_cipher: PhantomData, + /// Rng + _rng: PhantomData, +} + +impl KeyAgreeRecipientInfoBuilder +where + R: CryptoRngCore, + C: CurveArithmetic, + KA: KeyAgreementAlgorithm, + KW: KeyWrapAlgorithm, + Enc: KeySizeUser, +{ + /// Creates a `KeyAgreeRecipientInfoBuilder` + pub fn new( + ukm: Option, + rid: KeyAgreeRecipientIdentifier, + eckey_encryption_info: EcKeyEncryptionInfo, + ) -> Result { + Ok(KeyAgreeRecipientInfoBuilder { + ukm, + eckey_encryption_info, + rid, + key_agreement_algorithm: PhantomData, + key_wrap_algorithm: PhantomData, + enc_cipher: PhantomData, + _rng: PhantomData, + }) + } +} +impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder +where + R: CryptoRngCore, + KA: KeyAgreementAlgorithm + AssociatedOid, + C: CurveArithmetic + AssociatedOid + PointCompression, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, + KW: KeyWrapAlgorithm, + Enc: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, +{ + /// Associated Rng type + type Rng = R; + + /// Returns the RecipientInfoType + fn recipient_info_type(&self) -> RecipientInfoType { + RecipientInfoType::Kari + } + + /// Returns the `CMSVersion` for this `RecipientInfo` + fn recipient_info_version(&self) -> CmsVersion { + CmsVersion::V3 + } + + /// Build a `KeyAgreeRecipientInfo` as per [RFC 5652 Section 6.2.2] and [RFC 5753 Section 3]. + /// + /// For now only [EnvelopedData] using `(ephemeral-static) ECDH` is supported - [RFC 5753 Section 3.1.1] + /// + /// We follow the flow outlined in - [RFC 5753 Section 3.1.2]: + /// + /// Todo: + /// - Add support for `'Co-factor' ECDH` - see [RFC 5753 Section 3.1.1] + /// - Add support for `1-Pass ECMQV` - see [RFC 5753 Section 3.2.1] + /// + /// [RFC 5753 Section 3]: https://datatracker.ietf.org/doc/html/rfc5753#section-3 + /// [RFC 5652 Section 6.2.2]: https://datatracker.ietf.org/doc/html/rfc5652#section-6.2.2 + /// [RFC 5753 Section 3.1.1]: https://datatracker.ietf.org/doc/html/rfc5753#section-3.1.1 + /// [RFC 5753 Section 3.1.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-3.1.2 + /// [RFC 5753 Section 3.2.1]: https://datatracker.ietf.org/doc/html/rfc5753#section-3.2.1 + fn build_with_rng( + &mut self, + content_encryption_key: &[u8], + rng: &mut Self::Rng, + ) -> Result { + // Encrypt key + let ( + encrypted_key, + ephemeral_pubkey_encoded_point, + originator_algorithm_identifier, + key_encryption_algorithm_identifier, + ) = match self.eckey_encryption_info { + EcKeyEncryptionInfo::Ec(recipient_public_key) => { + // Generate ephemeral key using ecdh + let ephemeral_secret = EphemeralSecret::random(rng); + let ephemeral_public_key_encoded_point = + ephemeral_secret.public_key().to_encoded_point(false); + + // Compute a shared secret with recipient public key. Non-uniformly random, but will be used as input for KDF later. + let non_uniformly_random_shared_secret = + ephemeral_secret.diffie_hellman(&recipient_public_key); + + // Generate shared info for KDF + // As per https://datatracker.ietf.org/doc/html/rfc5753#section-7.2" + // ``` + // keyInfo contains the object identifier of the key-encryption + // algorithm (used to wrap the CEK) and associated parameters. In + // this specification, 3DES wrap has NULL parameters while the AES + // wraps have absent parameters. + // ``` + let key_wrap_algorithm_identifier = KW::algorithm_identifier(); + let key_wrap_algorithm_der = key_wrap_algorithm_identifier.to_der()?; + + // As per https://datatracker.ietf.org/doc/html/rfc5753#section-7.2" + // ``` + // entityUInfo optionally contains additional keying material + // supplied by the sending agent. When used with ECDH and CMS, the + // entityUInfo field contains the octet string ukm. When used with + // ECMQV and CMS, the entityUInfo contains the octet string addedukm + // (encoded in MQVuserKeyingMaterial). + // ``` + let entity_u_info = self.ukm.clone(); + + // As per https://datatracker.ietf.org/doc/html/rfc5753#section-7.2" + // ``` + // suppPubInfo contains the length of the generated KEK, in bits, + // represented as a 32-bit number, as in [CMS-DH] and [CMS-AES]. + // (For example, for AES-256 it would be 00 00 01 00.) + // ``` + let key_wrap_algo_keysize_bits_in_be_bytes: [u8; 4] = + KW::key_size_in_bits().to_be_bytes(); + + let shared_info = EccCmsSharedInfo { + key_info: key_wrap_algorithm_identifier, + entity_u_info, + supp_pub_info: OctetString::new(key_wrap_algo_keysize_bits_in_be_bytes)?, + }; + + // Init a wrapping key (KEK) based on KeyWrapAlgorithm and on CEK (i.e. key to wrap) size + let kek = KW::init_kek(); + let mut wrapped: WrappedKey = KW::init_wrapped(); + + // Derive the Key Encryption Key (KEK) from Shared Secret using ANSI X9.63 KDF + KA::kdf( + &non_uniformly_random_shared_secret, + &shared_info, + &mut wrapped, + )?; + + // Wrap the Content Encryption Key (CEK) with the KEK + KW::try_wrap(&kek, content_encryption_key, wrapped.as_mut())?; + + // Return data + ( + Vec::from(wrapped), + ephemeral_public_key_encoded_point, + self.eckey_encryption_info.into(), + AlgorithmIdentifierOwned { + oid: KA::OID, + parameters: Some(Any::from_der(&key_wrap_algorithm_der)?), + }, + ) + } + }; + + // Build RecipientInfo + Ok(RecipientInfo::Kari(KeyAgreeRecipientInfo { + originator: OriginatorIdentifierOrKey::OriginatorKey(OriginatorPublicKey { + algorithm: originator_algorithm_identifier, + public_key: BitString::from_bytes(ephemeral_pubkey_encoded_point.as_bytes())?, + }), + version: self.recipient_info_version(), + ukm: self.ukm.clone(), + key_enc_alg: key_encryption_algorithm_identifier, + recipient_enc_keys: vec![RecipientEncryptedKey { + rid: self.rid.clone(), + enc_key: EncryptedKey::new(encrypted_key)?, + }], + })) + } +} + +#[cfg(test)] +mod tests { + use std::eprintln; + + use super::*; + use p256::{pkcs8::DecodePublicKey, NistP256, PublicKey}; + + /// Generate a test P256 EcKeyEncryptionInfo + fn get_test_ec_key_info() -> EcKeyEncryptionInfo { + // Public key der bytes: + // ```rust + // let public_key_der_bytes = include_bytes!("../../tests/examples/p256-pub.der"); + // ``` + // OR + // ```bash + // od -An -vtu1 cms/tests/examples/p256-pub.der | tr -s ' ' | tr -d '\n' | sed 's/ /, /g' | sed 's/^, //' |xargs + // ``` + let public_key_der_bytes: &[u8] = &[ + 48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, + 3, 66, 0, 4, 28, 172, 255, 181, 95, 47, 44, 239, 216, 157, 137, 235, 55, 75, 38, 129, + 21, 36, 82, 128, 45, 238, 160, 153, 22, 6, 129, 55, 216, 57, 207, 127, 196, 129, 164, + 68, 146, 48, 77, 126, 246, 106, 193, 23, 190, 254, 131, 168, 208, 143, 21, 95, 43, 82, + 249, 246, 24, 221, 68, 112, 41, 4, 142, 15, + ]; + let p256_public_key = PublicKey::from_public_key_der(public_key_der_bytes) + .map_err(|e| eprintln!("{}", e)) + .expect("Getting PublicKey failed"); + EcKeyEncryptionInfo::Ec(p256_public_key) + } + + #[test] + fn test_keyagreementalgorithm_oid() { + assert_eq!( + DhSinglePassStdDhKdf::::OID, + rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME + ); + assert_eq!( + DhSinglePassStdDhKdf::::OID, + rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME + ); + assert_eq!( + DhSinglePassStdDhKdf::::OID, + rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME + ); + assert_eq!( + DhSinglePassStdDhKdf::::OID, + rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME + ); + } + + #[test] + fn test_eckeyencryptioninfo_get_oid() { + let ec_key_encryption_info = get_test_ec_key_info(); + assert_eq!( + ec_key_encryption_info.get_oid(), + ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7") + ); + } + + #[test] + fn test_algorithmidentifierowned_from_eckeyencryptioninfo() { + let ec_key_encryption_info = get_test_ec_key_info(); + + assert_eq!( + AlgorithmIdentifierOwned { + oid: ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"), + parameters: Some(ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7").into()), + }, + AlgorithmIdentifierOwned::from(ec_key_encryption_info) + ) + } +} diff --git a/cms/src/builder/utils.rs b/cms/src/builder/utils.rs new file mode 100644 index 000000000..311db1534 --- /dev/null +++ b/cms/src/builder/utils.rs @@ -0,0 +1,8 @@ +//! Utilities module +//! +//! Contains various utilities used during KARI building. +//! It currently contains: +//! - kw: AES Key Wrap +//! - kdf: KDF using ANSI-x9.63 Key Derivation Function + +pub(super) mod kw; diff --git a/cms/src/builder/utils/kw.rs b/cms/src/builder/utils/kw.rs new file mode 100644 index 000000000..d9b87d382 --- /dev/null +++ b/cms/src/builder/utils/kw.rs @@ -0,0 +1,444 @@ +//! Key wrap module +//! +//! This module contains the key wrapping logic based on aes-kw algorithms +//! + +// Self imports +use crate::builder::{Error, Result}; + +// Internal imports +use const_oid::AssociatedOid; +use spki::AlgorithmIdentifierOwned; + +// Alloc imports +use alloc::{string::String, vec::Vec}; + +// Core imports +use core::ops::Add; + +// Rust crypto imports +use aes::cipher::{ + array::{Array, ArraySize}, + typenum::{Sum, Unsigned, U16, U8}, + BlockCipherDecrypt, BlockCipherEncrypt, BlockSizeUser, Key, KeyInit, KeySizeUser, +}; +use aes_kw::AesKw; + +/// Represents supported key wrap algorithm for ECC - as defined in [RFC 5753 Section 7.1.5]. +/// +/// As per [RFC 5753 Section 8]: +/// ```text +/// Implementations that support EnvelopedData with the ephemeral-static +/// ECDH standard primitive: +/// +/// - MUST support the dhSinglePass-stdDH-sha256kdf-scheme key +/// agreement algorithm, the id-aes128-wrap key wrap algorithm, and +/// the id-aes128-cbc content encryption algorithm; and +/// - MAY support the dhSinglePass-stdDH-sha1kdf-scheme, dhSinglePass- +/// stdDH-sha224kdf-scheme, dhSinglePass-stdDH-sha384kdf-scheme, and +/// dhSinglePass-stdDH-sha512kdf-scheme key agreement algorithms; +/// the id-alg-CMS3DESwrap, id-aes192-wrap, and id-aes256-wrap key +/// wrap algorithms; and the des-ede3-cbc, id-aes192-cbc, and id- +/// aes256-cbc content encryption algorithms; other algorithms MAY +/// also be supported. +/// ``` +/// +/// As such the following algorithm are currently supported +/// - id-aes128-wrap +/// - id-aes192-wrap +/// - id-aes256-wrap +/// +/// [RFC 5753 Section 8]: https://datatracker.ietf.org/doc/html/rfc5753#section-8 +/// [RFC 5753 Section 7.1.5]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.1.5 +/// +/// Represents key wrap algorithms methods. +pub trait KeyWrapAlgorithm: AssociatedOid + KeySizeUser { + /// Return key size of the key-wrap algorithm in bits + fn key_size_in_bits() -> u32; + + /// Return algorithm identifier AlgorithmIdentifierOwned` associated with the key-wrap algorithm + /// + /// [RFC 3565 Section 2.3.2]: + /// ```text + /// NIST has assigned the following OIDs to define the AES key wrap + /// algorithm. + /// + /// id-aes128-wrap OBJECT IDENTIFIER ::= { aes 5 } + /// id-aes192-wrap OBJECT IDENTIFIER ::= { aes 25 } + /// id-aes256-wrap OBJECT IDENTIFIER ::= { aes 45 } + /// + /// In all cases the parameters field MUST be absent. + /// ``` + /// + /// [RFC 3565 Section 2.3.2]: https://datatracker.ietf.org/doc/html/rfc3565#section-2.3.2 + /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 + fn algorithm_identifier() -> AlgorithmIdentifierOwned; + + /// Return an empty wrapping key (KEK) with the adequate size to be used with aes-key-wrap + fn init_kek() -> Key; + + /// Return an empty wrapped key with the adequate size to be used with aes-key-wrap + fn init_wrapped() -> WrappedKey + where + T: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add; + + /// Try to wrap some data using given wrapping key + fn try_wrap(key: &Key, data: &[u8], out: &mut [u8]) -> Result<()>; +} + +/// Struct representing a wrapped key +/// +/// Can be used to abstract wrapped key over different incoming key sizes. +pub struct WrappedKey +where + T: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, +{ + inner: Array>, +} + +impl AsMut<[u8]> for WrappedKey +where + T: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, +{ + fn as_mut(&mut self) -> &mut [u8] { + self.inner.as_mut() + } +} + +impl From> for Vec +where + T: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, +{ + fn from(wrapped_key: WrappedKey) -> Self { + wrapped_key.inner.to_vec() + } +} + +impl KeyWrapAlgorithm for AesKw +where + AesWrap: KeyInit + BlockSizeUser + BlockCipherEncrypt + BlockCipherDecrypt, + AesKw: AssociatedOid + KeyInit, +{ + fn key_size_in_bits() -> u32 { + AesWrap::KeySize::U32 * 8u32 + } + + fn algorithm_identifier() -> AlgorithmIdentifierOwned { + AlgorithmIdentifierOwned { + oid: Self::OID, + parameters: None, + } + } + + fn init_kek() -> Key { + Key::::default() + } + + fn init_wrapped() -> WrappedKey + where + AesEnc: KeySizeUser, + Sum: ArraySize, + ::KeySize: Add, + { + WrappedKey:: { + inner: Array::>::default(), + } + } + + fn try_wrap(key: &Key, data: &[u8], out: &mut [u8]) -> Result<()> { + let kek = AesKw::new(key); + let res = kek + .wrap_key(data, out) + .map_err(|_| Error::Builder(String::from("could not wrap key")))?; + if res.len() != out.len() { + return Err(Error::Builder(String::from("output buffer invalid size"))); + } + Ok(()) + } +} + +/* +#[cfg(test)] +mod tests { + + use super::*; + use alloc::string::ToString; + + #[test] + fn test_keywrapalgorithm_oid() { + assert_eq!( + KeyWrapAlgorithm::Aes128.oid(), + const_oid::db::rfc5911::ID_AES_128_WRAP + ); + assert_eq!( + KeyWrapAlgorithm::Aes192.oid(), + const_oid::db::rfc5911::ID_AES_192_WRAP + ); + assert_eq!( + KeyWrapAlgorithm::Aes256.oid(), + const_oid::db::rfc5911::ID_AES_256_WRAP + ); + } + + #[test] + fn test_keywrapalgorithm_parameters() { + assert_eq!(KeyWrapAlgorithm::Aes128.parameters(), None); + assert_eq!(KeyWrapAlgorithm::Aes192.parameters(), None); + assert_eq!(KeyWrapAlgorithm::Aes256.parameters(), None); + } + + #[test] + fn test_keywrapalgorithm_key_size_in_bits() { + assert_eq!(KeyWrapAlgorithm::Aes128.key_size_in_bits(), 128); + assert_eq!(KeyWrapAlgorithm::Aes192.key_size_in_bits(), 192); + assert_eq!(KeyWrapAlgorithm::Aes256.key_size_in_bits(), 256); + } + + #[test] + fn test_algorithmidentifierowned_from_keywrapalgorithm() { + assert_eq!( + AlgorithmIdentifierOwned::from(KeyWrapAlgorithm::Aes128), + AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_AES_128_WRAP, + parameters: None, + } + ); + assert_eq!( + AlgorithmIdentifierOwned::from(KeyWrapAlgorithm::Aes192), + AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_AES_192_WRAP, + parameters: None, + } + ); + assert_eq!( + AlgorithmIdentifierOwned::from(KeyWrapAlgorithm::Aes256), + AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5911::ID_AES_256_WRAP, + parameters: None, + } + ) + } + #[test] + fn test_keywrapalgorithm_from_contentencryptionalgorithm() { + assert_eq!( + KeyWrapAlgorithm::from(ContentEncryptionAlgorithm::Aes128Cbc), + KeyWrapAlgorithm::Aes128 + ); + assert_eq!( + KeyWrapAlgorithm::from(ContentEncryptionAlgorithm::Aes192Cbc), + KeyWrapAlgorithm::Aes192 + ); + assert_eq!( + KeyWrapAlgorithm::from(ContentEncryptionAlgorithm::Aes256Cbc), + KeyWrapAlgorithm::Aes256 + ); + } + + #[test] + fn test_keywrapper_try_new() { + assert!(KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 10).is_err()); + assert!(KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 16).is_ok()); + assert!(KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 24).is_ok()); + assert!(KeyWrapper::try_new(&KeyWrapAlgorithm::Aes256, 32).is_ok()); + } + + fn fake_kdf(_in: &[u8], _out: &mut impl AsMut<[u8]>) {} + + #[test] + fn test_keywrapper_try_wrap() { + // Key to wrap + let key_to_wrap = [0u8; 16]; + + // Shared secret + let shared_secret = [1u8; 16]; + + // Define a key wrapper from Aes128-key-wrap and key-to-wrap size + let mut key_wrapper = + KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, key_to_wrap.len()).unwrap(); + + // Derive shared key - one can call .as_mut() on key_wrapper + fake_kdf(&shared_secret, &mut key_wrapper); + + // Wrap the key + let r: core::result::Result<(), Error> = key_wrapper.try_wrap(&key_to_wrap); + + assert!(r.is_ok()); + let wrapped_key = Vec::from(key_wrapper); + assert_eq!( + wrapped_key, + alloc::vec![ + 191, 59, 119, 181, 233, 12, 170, 159, 80, 9, 254, 150, 38, 228, 239, 226, 13, 237, + 117, 238, 59, 26, 192, 213 + ] + ) + } + + #[test] + fn test_keywrapper_try_wrap_error() { + // Key to wrap + let key_to_wrap = [0u8; 16]; + + // Define a key wrapper with unsupported key size + assert_eq!( + KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 15) + .unwrap_err() + .to_string(), + "builder error: could not wrap key: key size is not supported" + ); + + // Define a key wrapper from Aes128-key-wrap but with a different size than key-to-wrap + let mut key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 24).unwrap(); + + // Wrap the key + assert_eq!( + key_wrapper.try_wrap(&key_to_wrap).unwrap_err().to_string(), + "builder error: could not wrap key with Aes128 key wrap algorithm" + ); + } + + #[test] + fn test_keywrapper_as_mut() { + let mut key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 16).unwrap(); + let slice_a128 = key_wrapper.as_mut(); + assert_eq!(slice_a128.len(), 16); + assert_eq!(slice_a128[0], 0); + } + + #[test] + fn test_vecu8_from_keywrapper() { + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 16).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 24); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 16).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 24); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes256, 16).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 24); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 24).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 32); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 24).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 32); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 24).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 32); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes128, 32).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 40); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes192, 32).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 40); + assert_eq!(vec[0], 0); + + let key_wrapper = KeyWrapper::try_new(&KeyWrapAlgorithm::Aes256, 32).unwrap(); + let vec = Vec::from(key_wrapper); + assert_eq!(vec.len(), 40); + assert_eq!(vec[0], 0); + } + + #[test] + fn test_wrappingkey_from_keywrapalgorithm() { + assert_eq!( + WrappingKey::from(&KeyWrapAlgorithm::Aes128), + WrappingKey::Aes128([0u8; 16]) + ); + assert_eq!( + WrappingKey::from(&KeyWrapAlgorithm::Aes192), + WrappingKey::Aes192([0u8; 24]) + ); + assert_eq!( + WrappingKey::from(&KeyWrapAlgorithm::Aes256), + WrappingKey::Aes256([0u8; 32]) + ); + } + + #[test] + fn test_wrappingkey_as_mut() { + let mut key_a128 = WrappingKey::Aes128([0; 16]); + let slice_a128 = key_a128.as_mut(); + assert_eq!(slice_a128.len(), 16); + assert_eq!(slice_a128[0], 0); + + let mut key_a192 = WrappingKey::Aes192([0; 24]); + let slice_a192 = key_a192.as_mut(); + assert_eq!(slice_a192.len(), 24); + assert_eq!(slice_a192[0], 0); + + let mut key_a256 = WrappingKey::Aes256([0; 32]); + let slice_a256 = key_a256.as_mut(); + assert_eq!(slice_a256.len(), 32); + assert_eq!(slice_a256[0], 0); + } + + #[test] + fn test_wrappedkey_from_usize() { + let key_size: usize = 16; + assert!(WrappedKey::try_from(key_size).is_ok()); + + let key_size: usize = 24; + assert!(WrappedKey::try_from(key_size).is_ok()); + + let key_size: usize = 32; + assert!(WrappedKey::try_from(key_size).is_ok()); + + let key_size: usize = 0; + assert!(WrappedKey::try_from(key_size).is_err()); + } + #[test] + fn test_wrappedkey_as_mut() { + let mut key_a128 = WrappedKey::Aes128([0; 24]); + let slice_a128 = key_a128.as_mut(); + assert_eq!(slice_a128.len(), 24); + assert_eq!(slice_a128[0], 0); + + let mut key_a192 = WrappedKey::Aes192([0; 32]); + let slice_a192 = key_a192.as_mut(); + assert_eq!(slice_a192.len(), 32); + assert_eq!(slice_a192[0], 0); + + let mut key_a256 = WrappedKey::Aes256([0; 40]); + let slice_a256 = key_a256.as_mut(); + assert_eq!(slice_a256.len(), 40); + assert_eq!(slice_a256[0], 0); + } + + #[test] + fn test_vecu8_from_wrappedkey() { + let key_a128 = WrappedKey::Aes128([0; 24]); + let vec = Vec::from(key_a128); + assert_eq!(vec.len(), 24); + + let key_a192 = WrappedKey::Aes192([0; 32]); + let vec = Vec::from(key_a192); + assert_eq!(vec.len(), 32); + + let key_a256 = WrappedKey::Aes256([0; 40]); + let vec = Vec::from(key_a256); + assert_eq!(vec.len(), 40); + } +} +*/ diff --git a/cms/tests/builder.rs b/cms/tests/builder.rs index ac3863e83..2f27b9afe 100644 --- a/cms/tests/builder.rs +++ b/cms/tests/builder.rs @@ -32,6 +32,10 @@ use spki::AlgorithmIdentifierOwned; use x509_cert::attr::{Attribute, AttributeValue}; use x509_cert::serial_number::SerialNumber; +// Modules +#[path = "builder/kari.rs"] +mod kari; + // TODO bk replace this by const_oid definitions as soon as released const RFC8894_ID_MESSAGE_TYPE: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.113733.1.9.2"); diff --git a/cms/tests/builder/kari.rs b/cms/tests/builder/kari.rs new file mode 100644 index 000000000..1e7e6927d --- /dev/null +++ b/cms/tests/builder/kari.rs @@ -0,0 +1,90 @@ +use aes_kw::AesKw; +use cms::{ + builder::{ + ContentEncryptionAlgorithm, DhSinglePassStdDhKdf, EcKeyEncryptionInfo, + EnvelopedDataBuilder, KeyAgreeRecipientInfoBuilder, + }, + cert::IssuerAndSerialNumber, + content_info::ContentInfo, + enveloped_data::KeyAgreeRecipientIdentifier, +}; +use der::{Any, AnyRef, Encode}; +use p256::{pkcs8::DecodePrivateKey, SecretKey}; +use pem_rfc7468::LineEnding; +use rand::rngs::OsRng; +use x509_cert::serial_number::SerialNumber; + +fn key_agreement_recipient_identifier(id: i32) -> KeyAgreeRecipientIdentifier { + let issuer = format!("CN=test client {id}").parse().unwrap(); + KeyAgreeRecipientIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber { + issuer, + serial_number: SerialNumber::new(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]) + .expect("failed to create a serial number"), + }) +} + +/// Generate a CMS message encrypted with recipient public EC key +/// +/// Can be decrypted using: +/// ```bash +/// openssl cms -decrypt -inkey cms/tests/examples/p256-priv.der -inform PEM +/// ``` +#[test] +fn test_build_enveloped_data_ec() { + // Recipient identifier + let key_agreement_recipient_identifier = key_agreement_recipient_identifier(1); + + // Recipient key material + let recipient_private_key_der = include_bytes!("../examples/p256-priv.der"); + let recipient_private_key = SecretKey::from_pkcs8_der(recipient_private_key_der) + .expect("could not parse in private key"); + let recipient_public_key = recipient_private_key.public_key(); + + // KARI builder + let kari_builder = KeyAgreeRecipientInfoBuilder::< + OsRng, + _, + DhSinglePassStdDhKdf, + AesKw, + aes::Aes128, + >::new( + None, + key_agreement_recipient_identifier, + EcKeyEncryptionInfo::Ec(recipient_public_key), + ) + .expect("Could not create a KeyAgreeRecipientInfoBuilder"); + + // Enveloped data builder + let mut rng = OsRng; + let mut builder = EnvelopedDataBuilder::new( + None, + "Arbitrary unencrypted content, encrypted using ECC".as_bytes(), + ContentEncryptionAlgorithm::Aes128Cbc, + None, + ) + .expect("Could not create an EnvelopedData builder."); + + // Enveloped data + let enveloped_data = builder + .add_recipient_info(kari_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."); + + // Content info + let content = AnyRef::try_from(enveloped_data_der.as_slice()).unwrap(); + let content_info = ContentInfo { + content_type: const_oid::db::rfc5911::ID_ENVELOPED_DATA, + content: Any::from(content), + }; + let content_info_der = content_info.to_der().unwrap(); + + println!( + "{}", + pem_rfc7468::encode_string("CMS", LineEnding::LF, &content_info_der) + .expect("PEM encoding of enveloped data DER failed") + ); +} diff --git a/cms/tests/examples/p256-pub.der b/cms/tests/examples/p256-pub.der new file mode 100644 index 000000000..67c719c76 Binary files /dev/null and b/cms/tests/examples/p256-pub.der differ