diff --git a/Cargo.lock b/Cargo.lock index 75e1e9985..e53bfe18e 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#c9437ce4933822252863fd64fc06c631405ad8b1" +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 = "anstream" version = "0.3.2" @@ -354,12 +371,15 @@ name = "cms" version = "0.3.0-pre.0" dependencies = [ "aes", + "aes-kw", + "ansi-x963-kdf", "async-signature", "cbc", "cipher", "const-oid", "der", "ecdsa", + "elliptic-curve", "getrandom", "hex-literal", "p256", @@ -583,6 +603,7 @@ dependencies = [ "digest", "ff", "group", + "hkdf", "hybrid-array", "pem-rfc7468", "pkcs8", @@ -813,6 +834,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" @@ -1865,7 +1895,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 85cc8f141..7aaa419e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,3 +58,11 @@ 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 +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 2ff032271..8758ef7c8 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -22,9 +22,12 @@ 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 } +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 } @@ -48,9 +51,13 @@ 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", + "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 9f658ef50..c269c5c59 100644 --- a/cms/src/builder.rs +++ b/cms/src/builder.rs @@ -6,8 +6,7 @@ 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, + OriginatorInfo, RecipientIdentifier, RecipientInfo, RecipientInfos, UserKeyingMaterial, }; use crate::revocation::{RevocationInfoChoice, RevocationInfoChoices}; use crate::signed_data::{ @@ -47,6 +46,14 @@ 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::{EcKeyEncryptionInfo, KeyAgreeRecipientInfoBuilder, KeyAgreementAlgorithm}; +pub use utils::kw::KeyWrapAlgorithm; + /// Error type #[derive(Debug)] #[non_exhaustive] @@ -693,53 +700,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, -} - -impl KeyAgreeRecipientInfoBuilder { - /// Creates a `KeyAgreeRecipientInfoBuilder` - pub fn new( - originator: OriginatorIdentifierOrKey, - ukm: Option, - key_enc_alg: AlgorithmIdentifierOwned, - ) -> Result { - Ok(KeyAgreeRecipientInfoBuilder { - originator, - ukm, - key_enc_alg, - }) - } -} - -impl RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder { - /// 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(&mut self, _content_encryption_key: &[u8]) -> 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..96f5b8559 --- /dev/null +++ b/cms/src/builder/kari.rs @@ -0,0 +1,446 @@ +//! 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::{try_ansi_x963_kdf, HashDigest, KeyWrapper}, + AlgorithmIdentifierOwned, CryptoRngCore, KeyWrapAlgorithm, 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::{AssociatedOid, ObjectIdentifier}; +use der::{ + asn1::{BitString, OctetString}, + Any, Decode, Encode, Sequence, +}; + +// Alloc imports +use alloc::{vec, vec::Vec}; + +// RustCrypto imports +use elliptic_curve::{ + ecdh::EphemeralSecret, + point::PointCompression, + sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint}, + AffinePoint, 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 +#[allow(clippy::enum_variant_names)] +#[derive(Clone, Copy)] +pub enum KeyAgreementAlgorithm { + /// dhSinglePass-stdDH-sha224kdf-scheme + SinglePassStdDhSha224Kdf, + /// dhSinglePass-stdDH-sha256kdf-scheme + SinglePassStdDhSha256Kdf, + /// dhSinglePass-stdDH-sha384kdf-scheme + SinglePassStdDhSha384Kdf, + /// dhSinglePass-stdDH-sh512df-scheme + SinglePassStdDhSha512Kdf, +} +impl KeyAgreementAlgorithm { + /// Return the OID of the algorithm. + fn oid(&self) -> ObjectIdentifier { + match self { + Self::SinglePassStdDhSha224Kdf => { + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME + } + Self::SinglePassStdDhSha256Kdf => { + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME + } + Self::SinglePassStdDhSha384Kdf => { + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME + } + Self::SinglePassStdDhSha512Kdf => { + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME + } + } + } +} +impl From<&KeyAgreementAlgorithm> for HashDigest { + fn from(ka_algo: &KeyAgreementAlgorithm) -> Self { + match ka_algo { + KeyAgreementAlgorithm::SinglePassStdDhSha224Kdf => Self::Sha224, + KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf => Self::Sha256, + KeyAgreementAlgorithm::SinglePassStdDhSha384Kdf => Self::Sha384, + KeyAgreementAlgorithm::SinglePassStdDhSha512Kdf => Self::Sha512, + } + } +} + +/// 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<&EcKeyEncryptionInfo> 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<'a, R, C> +where + R: CryptoRngCore, + C: CurveArithmetic, +{ + /// Optional information which helps generating different keys every time. + pub ukm: Option, + /// Encryption algorithm to be used for key encryption + pub rid: KeyAgreeRecipientIdentifier, + /// Recipient key info + pub eckey_encryption_info: EcKeyEncryptionInfo, + /// Content encryption algorithm + pub key_agreement_algorithm: KeyAgreementAlgorithm, + /// Content encryption algorithm + pub key_wrap_algorithm: KeyWrapAlgorithm, + /// Rng + rng: &'a mut R, +} + +impl<'a, R, C> KeyAgreeRecipientInfoBuilder<'a, R, C> +where + R: CryptoRngCore, + C: CurveArithmetic, +{ + /// Creates a `KeyAgreeRecipientInfoBuilder` + pub fn new( + ukm: Option, + rid: KeyAgreeRecipientIdentifier, + eckey_encryption_info: EcKeyEncryptionInfo, + key_agreement_algorithm: KeyAgreementAlgorithm, + key_wrap_algorithm: KeyWrapAlgorithm, + rng: &'a mut R, + ) -> Result> { + Ok(KeyAgreeRecipientInfoBuilder { + ukm, + eckey_encryption_info, + key_agreement_algorithm, + key_wrap_algorithm, + rid, + rng, + }) + } +} +impl<'a, R, C> RecipientInfoBuilder for KeyAgreeRecipientInfoBuilder<'a, R, C> +where + R: CryptoRngCore, + C: CurveArithmetic + AssociatedOid + PointCompression, + AffinePoint: FromEncodedPoint + ToEncodedPoint, + FieldBytesSize: ModulusSize, +{ + /// 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(&mut self, content_encryption_key: &[u8]) -> 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(self.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); + let non_uniformly_random_shared_secret_bytes = + non_uniformly_random_shared_secret.raw_secret_bytes(); + + // 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: AlgorithmIdentifierOwned = + self.key_wrap_algorithm.into(); + 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] = + self.key_wrap_algorithm.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)?, + }; + let shared_info_der = shared_info.to_der()?; + + // Init a wrapping key (KEK) based on KeyWrapAlgorithm and on CEK (i.e. key to wrap) size + let mut key_wrapper = + KeyWrapper::try_new(&self.key_wrap_algorithm, content_encryption_key.len())?; + + // Derive the Key Encryption Key (KEK) from Shared Secret using ANSI X9.63 KDF + let digest = HashDigest::from(&self.key_agreement_algorithm); + try_ansi_x963_kdf( + non_uniformly_random_shared_secret_bytes.as_slice(), + &shared_info_der, + &mut key_wrapper, + &digest, + )?; + + // Wrap the Content Encryption Key (CEK) with the KEK + key_wrapper.try_wrap(content_encryption_key)?; + + // Return data + ( + Vec::from(key_wrapper), + ephemeral_public_key_encoded_point, + AlgorithmIdentifierOwned::from(&self.eckey_encryption_info), + AlgorithmIdentifierOwned { + oid: self.key_agreement_algorithm.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!( + KeyAgreementAlgorithm::SinglePassStdDhSha224Kdf.oid(), + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_224_KDF_SCHEME + ); + assert_eq!( + KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf.oid(), + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_256_KDF_SCHEME + ); + assert_eq!( + KeyAgreementAlgorithm::SinglePassStdDhSha384Kdf.oid(), + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_384_KDF_SCHEME + ); + assert_eq!( + KeyAgreementAlgorithm::SinglePassStdDhSha512Kdf.oid(), + const_oid::db::rfc5753::DH_SINGLE_PASS_STD_DH_SHA_512_KDF_SCHEME + ); + } + + #[test] + fn test_from_keyagreementalgorithm_for_hashdigest() { + assert_eq!( + HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha224Kdf), + HashDigest::Sha224 + ); + assert_eq!( + HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf), + HashDigest::Sha256 + ); + assert_eq!( + HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha384Kdf), + HashDigest::Sha384 + ); + assert_eq!( + HashDigest::from(&KeyAgreementAlgorithm::SinglePassStdDhSha512Kdf), + HashDigest::Sha512 + ); + } + + #[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..140b2234a --- /dev/null +++ b/cms/src/builder/utils.rs @@ -0,0 +1,12 @@ +//! 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 + +mod kdf; +pub(super) mod kw; + +pub(super) use kdf::{try_ansi_x963_kdf, HashDigest}; +pub(super) use kw::KeyWrapper; diff --git a/cms/src/builder/utils/kdf.rs b/cms/src/builder/utils/kdf.rs new file mode 100644 index 000000000..a99b2399c --- /dev/null +++ b/cms/src/builder/utils/kdf.rs @@ -0,0 +1,175 @@ +//! KDF module +//! +//! This module contains KDF logic. +//! + +use crate::builder::{Error, Result}; +use alloc::string::String; + +#[derive(PartialEq, Eq, Debug)] +pub(in crate::builder) enum HashDigest { + Sha224, + Sha256, + Sha384, + Sha512, +} + +/// Wrapper for ANSI-X9.63 KDF +/// +/// This function wraps ansi_x963_kdf, applying a Hash Disgest based on the key agreement algorithm identifier +pub(in crate::builder) fn try_ansi_x963_kdf( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, + digest: &HashDigest, +) -> Result<()> { + match digest { + HashDigest::Sha224 => ansi_x963_kdf_sha224(secret, other_info, key).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf SHA-224", + )) + }), + HashDigest::Sha256 => ansi_x963_kdf_sha256(secret, other_info, key).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf SHA-256", + )) + }), + HashDigest::Sha384 => ansi_x963_kdf_sha384(secret, other_info, key).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf SHA-384", + )) + }), + HashDigest::Sha512 => ansi_x963_kdf_sha512(secret, other_info, key).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf SHA-512", + )) + }), + } +} + +/// ANSI-X9.63-KDF with SHA224 +fn ansi_x963_kdf_sha224( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, +) -> Result<()> { + ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + }) +} + +/// ANSI-X9.63-KDF with SHA256 +fn ansi_x963_kdf_sha256( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, +) -> Result<()> { + ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + }) +} + +/// ANSI-X9.63-KDF with SHA384 +fn ansi_x963_kdf_sha384( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, +) -> Result<()> { + ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + }) +} + +/// ANSI-X9.63-KDF with SHA512 +fn ansi_x963_kdf_sha512( + secret: &[u8], + other_info: &[u8], + key: &mut impl AsMut<[u8]>, +) -> Result<()> { + ansi_x963_kdf::derive_key_into::(secret, other_info, key.as_mut()).map_err(|_| { + Error::Builder(String::from( + "Could not generate a shared secret via ansi-x9.63-kdf", + )) + }) +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_try_ansi_x963_kdf() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(try_ansi_x963_kdf(&secret, &other_info, &mut key, &HashDigest::Sha224).is_ok()); + assert_eq!( + key, + [33, 35, 2, 122, 169, 122, 164, 137, 12, 5, 195, 31, 101, 142, 44, 237] + ) + } + + #[test] + fn test_try_ansi_x963_kdf_error() { + // Empty secret should trigger error of ansi_x963_kdf + let secret = []; + let other_info = []; + let mut key = [0u8; 16]; + assert!(try_ansi_x963_kdf(&secret, &other_info, &mut key, &HashDigest::Sha224).is_err()); + } + + #[test] + fn test_try_ansi_x963_kdf_sha224() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(ansi_x963_kdf_sha224(&secret, &other_info, &mut key).is_ok()); + assert_eq!( + key, + [33, 35, 2, 122, 169, 122, 164, 137, 12, 5, 195, 31, 101, 142, 44, 237] + ) + } + + #[test] + fn test_try_ansi_x963_kdf_sha256() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(ansi_x963_kdf_sha256(&secret, &other_info, &mut key).is_ok()); + assert_eq!( + key, + [233, 255, 14, 110, 109, 233, 93, 165, 111, 240, 159, 78, 62, 15, 72, 29] + ) + } + + #[test] + fn test_try_ansi_x963_kdf_sha384() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(ansi_x963_kdf_sha384(&secret, &other_info, &mut key).is_ok()); + assert_eq!( + key, + [156, 231, 52, 7, 234, 137, 225, 91, 29, 49, 193, 212, 25, 40, 137, 8] + ) + } + + #[test] + fn test_try_ansi_x963_kdf_sha512() { + let secret = [0u8; 16]; + let other_info = []; + let mut key = [0u8; 16]; + assert!(ansi_x963_kdf_sha512(&secret, &other_info, &mut key).is_ok()); + assert_eq!( + key, + [160, 237, 224, 79, 173, 198, 48, 115, 203, 162, 233, 108, 204, 185, 88, 209] + ) + } +} diff --git a/cms/src/builder/utils/kw.rs b/cms/src/builder/utils/kw.rs new file mode 100644 index 000000000..32cf3a349 --- /dev/null +++ b/cms/src/builder/utils/kw.rs @@ -0,0 +1,571 @@ +//! Key wrap module +//! +//! This module contains the key wrapping logic based on aes-kw algorithms +//! + +use alloc::{string::String, vec::Vec}; + +use crate::builder::{ContentEncryptionAlgorithm, Error, Result}; +use aes_kw::Kek; +use const_oid::ObjectIdentifier; +use der::Any; +use spki::AlgorithmIdentifierOwned; + +/// 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 +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum KeyWrapAlgorithm { + /// id-aes128-wrap + Aes128, + /// id-aes192-wrap + Aes192, + /// id-aes256-wrap + Aes256, +} +impl KeyWrapAlgorithm { + /// Return the Object Identifier (OID) of the algorithm. + /// + /// OID are defined in [RFC 3565 Section 2.3.2] + /// + /// [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 + fn oid(&self) -> ObjectIdentifier { + match self { + Self::Aes128 => const_oid::db::rfc5911::ID_AES_128_WRAP, + Self::Aes192 => const_oid::db::rfc5911::ID_AES_192_WRAP, + Self::Aes256 => const_oid::db::rfc5911::ID_AES_256_WRAP, + } + } + + /// Return parameters of the algorithm to be used in the context of `AlgorithmIdentifierOwned`. + /// + /// It should be absent as defined in [RFC 3565 Section 2.3.2] and per usage in [RFC 5753 Section 7.2]. + /// + /// [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 parameters(&self) -> Option { + match self { + Self::Aes128 => None, + Self::Aes192 => None, + Self::Aes256 => None, + } + } + + /// Return key size of the algorithm in number of bits + pub fn key_size_in_bits(&self) -> u32 { + match self { + Self::Aes128 => 128, + Self::Aes192 => 192, + Self::Aes256 => 256, + } + } +} +impl From for AlgorithmIdentifierOwned { + /// Convert a `KeyWrapAlgorithm` to the corresponding `AlgorithmIdentifierOwned`. + /// + /// Conversion is done according to [RFC 5753 Section 7.2]: + /// + /// + /// [RFC 5753 Section 7.2] + /// ```text + /// 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. + /// ``` + /// + /// [RFC 5753 Section 7.2]: https://datatracker.ietf.org/doc/html/rfc5753#section-7.2 + fn from(kw_algo: KeyWrapAlgorithm) -> Self { + Self { + oid: kw_algo.oid(), + parameters: kw_algo.parameters(), + } + } +} +impl From for KeyWrapAlgorithm { + /// Convert a `ContentEncryptionAlgorithm` to a `KeyWrapAlgorithm`. + /// + /// Conversion is done matching encryption strength. + fn from(ce_algo: ContentEncryptionAlgorithm) -> Self { + match ce_algo { + ContentEncryptionAlgorithm::Aes128Cbc => Self::Aes128, + ContentEncryptionAlgorithm::Aes192Cbc => Self::Aes192, + ContentEncryptionAlgorithm::Aes256Cbc => Self::Aes256, + } + } +} + +/// This struct can be used to perform key wrapping operation. +/// +/// It abstracts some of the key-wrapping logic over incoming wrapping-key and outgoing wrapped-key of different sizes. +/// It currently implements: +/// - try_new() - initialize a key wrapper with right sized depending on KeyWrapAlgorithm and key-to-wrap size +/// - try_wrap() - wrap a key with the corresponding aes-key-wrap algorithms +/// +/// # Note +/// For convenience KeyWrapper can: +/// - yield the inner wrapping-key as a mutable reference (e.g. to use with a KDF) +/// - convert to Vec to obtain Owned data to the wrapped key +#[derive(Debug, Clone, Copy)] +pub(in crate::builder) struct KeyWrapper { + /// Wrapping key + wrapping_key: WrappingKey, + /// Wrapped key + wrapped_key: WrappedKey, +} +impl KeyWrapper { + /// Initialize a new KeyWrapper based on `KeyWrapAlgorithm` and key-to-wrap size. + pub(in crate::builder) fn try_new(kw_algo: &KeyWrapAlgorithm, key_size: usize) -> Result { + let wrapped_key = WrappedKey::try_from(key_size)?; + let wrapping_key = WrappingKey::from(kw_algo); + + Ok(Self { + wrapping_key, + wrapped_key, + }) + } + /// Wraps a given key. + /// + /// This function attempts to wrap the provided `target_key`. + /// + /// # Arguments + /// * `target_key` - A slice of bytes representing the key to be wrapped. + pub(in crate::builder) fn try_wrap(&mut self, target_key: &[u8]) -> Result<()> { + match self.wrapping_key { + WrappingKey::Aes128(wrap_key) => Kek::from(wrap_key) + .wrap(target_key, self.wrapped_key.as_mut()) + .map_err(|_| { + Error::Builder(String::from( + "could not wrap key with Aes128 key wrap algorithm", + )) + }), + WrappingKey::Aes192(kek) => Kek::from(kek) + .wrap(target_key, self.wrapped_key.as_mut()) + .map_err(|_| { + Error::Builder(String::from( + "could not wrap key with Aes192 key wrap algorithm", + )) + }), + WrappingKey::Aes256(kek) => Kek::from(kek) + .wrap(target_key, self.wrapped_key.as_mut()) + .map_err(|_| { + Error::Builder(String::from( + "could not wrap key with Aes256 key wrap algorithm", + )) + }), + } + } +} +impl AsMut<[u8]> for KeyWrapper { + fn as_mut(&mut self) -> &mut [u8] { + self.wrapping_key.as_mut() + } +} +impl From for Vec { + fn from(wrapper: KeyWrapper) -> Self { + Self::from(wrapper.wrapped_key) + } +} + +/// Represents a wrapping key to be used by [KeyWrapper] +/// +/// This type can be used to abstract over wrapping-key material of different size. +/// The following wrapping key type are currently supported: +/// - Aes128 +/// - Aes192 +/// - Aes256 +/// +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum WrappingKey { + /// id-aes128-wrap + Aes128([u8; 16]), + /// id-aes192-wrap + Aes192([u8; 24]), + /// id-aes256-wrap + Aes256([u8; 32]), +} +impl From<&KeyWrapAlgorithm> for WrappingKey { + fn from(kw_algo: &KeyWrapAlgorithm) -> Self { + match kw_algo { + KeyWrapAlgorithm::Aes128 => Self::Aes128([0u8; 16]), + KeyWrapAlgorithm::Aes192 => Self::Aes192([0u8; 24]), + KeyWrapAlgorithm::Aes256 => Self::Aes256([0u8; 32]), + } + } +} +impl AsMut<[u8]> for WrappingKey { + fn as_mut(&mut self) -> &mut [u8] { + match self { + Self::Aes128(key) => key, + Self::Aes192(key) => key, + Self::Aes256(key) => key, + } + } +} +/// Represents a wrapped key to be used by [KeyWrapper] +/// +/// This type can be used to abstract over wrapped key of different size for aes-key-wrap algorithms. +/// It currently supports the following incoming key size: +/// - 16 +/// - 24 +/// - 32 +#[derive(Debug, Clone, Copy)] +enum WrappedKey { + Aes128([u8; 24]), + Aes192([u8; 32]), + Aes256([u8; 40]), +} +impl TryFrom for WrappedKey { + type Error = Error; + fn try_from(key_size: usize) -> Result { + match key_size { + 16 => Ok(Self::Aes128([0u8; 24])), + 24 => Ok(Self::Aes192([0u8; 32])), + 32 => Ok(Self::Aes256([0u8; 40])), + _ => Err(Error::Builder(String::from( + "could not wrap key: key size is not supported", + ))), + } + } +} +impl AsMut<[u8]> for WrappedKey { + fn as_mut(&mut self) -> &mut [u8] { + match self { + Self::Aes128(key) => key, + Self::Aes192(key) => key, + Self::Aes256(key) => key, + } + } +} +impl From for Vec { + fn from(key: WrappedKey) -> Self { + match key { + WrappedKey::Aes128(arr) => arr.to_vec(), + WrappedKey::Aes192(arr) => arr.to_vec(), + WrappedKey::Aes256(arr) => arr.to_vec(), + } + } +} + +#[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 b126c32e9..89870d530 100644 --- a/cms/tests/builder.rs +++ b/cms/tests/builder.rs @@ -28,6 +28,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..503c4c914 --- /dev/null +++ b/cms/tests/builder/kari.rs @@ -0,0 +1,87 @@ +use cms::{ + builder::{ + ContentEncryptionAlgorithm, EcKeyEncryptionInfo, EnvelopedDataBuilder, + KeyAgreeRecipientInfoBuilder, KeyAgreementAlgorithm, KeyWrapAlgorithm, + }, + 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 mut rng = OsRng; + let kari_builder = KeyAgreeRecipientInfoBuilder::new( + None, + key_agreement_recipient_identifier, + EcKeyEncryptionInfo::Ec(recipient_public_key), + KeyAgreementAlgorithm::SinglePassStdDhSha256Kdf, + KeyWrapAlgorithm::Aes128, + &mut rng, + ) + .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