From cadf42a1f43bcbd19ec5a34e60a21596a737ba74 Mon Sep 17 00:00:00 2001 From: Bernd Krietenstein Date: Sat, 26 Aug 2023 16:28:47 +0200 Subject: [PATCH] cms: `EnvelopedData` (#1115) --- Cargo.lock | 7 + cms/Cargo.toml | 21 +- cms/src/builder.rs | 633 ++++++++++++++++-- cms/tests/builder.rs | 411 +++++++++++- .../examples/sceptest_cert-selfsigned.pem | 27 + cms/tests/examples/sceptest_csr.der | Bin 0 -> 933 bytes cms/tests/examples/sceptest_key.pem | 28 + 7 files changed, 1073 insertions(+), 54 deletions(-) create mode 100644 cms/tests/examples/sceptest_cert-selfsigned.pem create mode 100644 cms/tests/examples/sceptest_csr.der create mode 100644 cms/tests/examples/sceptest_key.pem diff --git a/Cargo.lock b/Cargo.lock index 9ad6dfa4f..360fa8a6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,13 +270,18 @@ dependencies = [ name = "cms" version = "0.2.2" dependencies = [ + "aes", + "cbc", + "cipher", "const-oid 0.9.5", "der", "ecdsa", + "getrandom", "hex-literal", "p256", "pem-rfc7468", "pkcs5", + "rand", "rsa", "sha1", "sha2", @@ -284,6 +289,7 @@ dependencies = [ "signature", "spki", "x509-cert", + "zeroize", ] [[package]] @@ -421,6 +427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] diff --git a/cms/Cargo.toml b/cms/Cargo.toml index 4c66eaad4..554f2b0c2 100644 --- a/cms/Cargo.toml +++ b/cms/Cargo.toml @@ -14,21 +14,28 @@ edition = "2021" rust-version = "1.65" [dependencies] -der = { version = "0.7.6", features = ["alloc", "derive", "oid", "pem"] } +der = { version = "0.7.7", features = ["alloc", "derive", "oid", "pem"] } spki = { version = "0.7" } x509-cert = { version = "0.2.3", default-features = false, features = ["pem"] } -const-oid = { version = "0.9", features = ["db"] } # TODO: path = "../const-oid" +const-oid = { version = "0.9.4", features = ["db"] } # TODO: path = "../const-oid" # optional dependencies -sha1 = { version = "0.10", optional = true} -sha2 = { version = "0.10", optional = true} -sha3 = { version = "0.10", optional = true} -signature = { version = "2.1.0", features = ["digest", "alloc"], optional = true} +aes = { version = "0.8.2", optional = true } +cbc = { version = "0.1.2", optional = true } +cipher = { version = "0.4.4", features = ["alloc", "block-padding", "rand_core"], optional = true } +rsa = { version = "0.9.2", optional = true } +sha1 = { version = "0.10", optional = true } +sha2 = { version = "0.10", optional = true } +sha3 = { version = "0.10", optional = true } +signature = { version = "2.1.0", features = ["digest", "alloc"], optional = true } +zeroize = { version = "1.6.0", optional = true } [dev-dependencies] +getrandom = "0.2" hex-literal = "0.4" pem-rfc7468 = "0.7.0" pkcs5 = { version = "0.7" } +rand = { version = "0.8.5" } rsa = { version = "0.9.2", features = ["sha2"] } ecdsa = { version = "0.16.8", features = ["digest", "pem"] } p256 = "0.13.0" @@ -36,7 +43,7 @@ p256 = "0.13.0" [features] alloc = ["der/alloc"] std = ["der/std", "spki/std"] -builder = ["sha1", "sha2", "sha3", "signature", "std", "spki/alloc", "x509-cert/builder"] +builder = ["aes", "cbc", "cipher", "rsa", "sha1", "sha2", "sha3", "signature", "std", "spki/alloc", "x509-cert/builder", "zeroize"] pem = ["alloc", "der/pem"] [package.metadata.docs.rs] diff --git a/cms/src/builder.rs b/cms/src/builder.rs index de484f5c5..906ed6bf7 100644 --- a/cms/src/builder.rs +++ b/cms/src/builder.rs @@ -4,33 +4,43 @@ 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, +}; use crate::revocation::{RevocationInfoChoice, RevocationInfoChoices}; use crate::signed_data::{ CertificateSet, DigestAlgorithmIdentifiers, EncapsulatedContentInfo, SignatureValue, SignedAttributes, SignedData, SignerIdentifier, SignerInfo, SignerInfos, UnsignedAttributes, }; +use aes::{Aes128, Aes192, Aes256}; use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; +use cipher::block_padding::Pkcs7; +use cipher::rand_core::{CryptoRng, CryptoRngCore, RngCore}; +use cipher::BlockEncryptMut; +use cipher::{Key, KeyIvInit, KeySizeUser}; use const_oid::ObjectIdentifier; use core::cmp::Ordering; use core::fmt; use der::asn1::{BitString, 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; use sha2::digest; use signature::digest::DynDigest; use signature::{Keypair, Signer}; -use spki::{ - AlgorithmIdentifierOwned, DynSignatureAlgorithmIdentifier, EncodePublicKey, - SignatureBitStringEncoding, -}; +use spki::{AlgorithmIdentifierOwned, DynSignatureAlgorithmIdentifier, SignatureBitStringEncoding}; use std::time::SystemTime; use std::vec; -use x509_cert::attr::{Attribute, AttributeValue}; +use x509_cert::attr::{Attribute, AttributeValue, Attributes}; use x509_cert::builder::Builder; +use zeroize::Zeroize; /// Error type #[derive(Debug)] @@ -99,7 +109,6 @@ pub struct SignerInfoBuilder<'s, S> { impl<'s, S> SignerInfoBuilder<'s, S> where S: Keypair + DynSignatureAlgorithmIdentifier, - S::VerifyingKey: EncodePublicKey, { /// Create a new `SignerInfoBuilder`. This is used for adding `SignerInfo`s to `SignedData` /// structures. @@ -161,7 +170,6 @@ where impl<'s, S> Builder for SignerInfoBuilder<'s, S> where S: Keypair + DynSignatureAlgorithmIdentifier, - S::VerifyingKey: EncodePublicKey, { type Signer = S; type Output = SignerInfo; @@ -182,12 +190,13 @@ where // Encapsulated content must be empty, if external digest is given. return Err(der::Error::from(ErrorKind::Failed)); } - external_content_digest.to_vec() + Some(external_content_digest.to_vec()) } None => match &self.encapsulated_content_info.econtent { None => { - // Content missing, cannot sign - return Err(der::Error::from(ErrorKind::Failed)); + // This case is allowed. E.g. for degenerate certificates-only messages. + // See RFC 5652 § 5.2 or RFC 8894 § 3.4. + None } Some(content) => { let mut hasher = get_hasher(&self.digest_algorithm).ok_or_else(|| { @@ -199,48 +208,51 @@ where // or the length octets. let content_value = content.value(); hasher.update(content_value); - hasher.finalize_reset().to_vec() + Some(hasher.finalize_reset().to_vec()) } }, }; - // This implementation uses signed attributes. + // This implementation uses signed attributes to store the message digest. if self.signed_attributes.is_none() { self.signed_attributes = Some(vec![]); } - // Add digest attribute to (to be) signed attributes let signed_attributes = self .signed_attributes .as_mut() .expect("Signed attributes must be present."); - signed_attributes.push( - create_message_digest_attribute(&message_digest) - .map_err(|_| der::Error::from(ErrorKind::Failed))?, - ); - - // The content-type attribute type specifies the content type of the - // ContentInfo within signed-data or authenticated-data. The content- - // type attribute type MUST be present whenever signed attributes are - // present in signed-data or authenticated attributes present in - // authenticated-data. The content-type attribute value MUST match the - // encapContentInfo eContentType value in the signed-data or - // authenticated-data. - let econtent_type = self.encapsulated_content_info.econtent_type; - let signed_attributes_content_type = signed_attributes - .iter() - .find(|attr| attr.oid.cmp(&const_oid::db::rfc5911::ID_CONTENT_TYPE) == Ordering::Equal); - if let Some(signed_attributes_content_type) = signed_attributes_content_type { - // Check against `eContentType` - if signed_attributes_content_type.oid != econtent_type { - // Mismatch between content types: encapsulated content info <-> signed attributes. - return Err(der::Error::from(ErrorKind::Failed)); - } - } else { + + if let Some(message_digest) = message_digest { + // Add digest attribute to (to be) signed attributes signed_attributes.push( - create_content_type_attribute(econtent_type) + create_message_digest_attribute(&message_digest) .map_err(|_| der::Error::from(ErrorKind::Failed))?, ); + + // The content-type attribute type specifies the content type of the + // ContentInfo within signed-data or authenticated-data. The content- + // type attribute type MUST be present whenever signed attributes are + // present in signed-data or authenticated attributes present in + // authenticated-data. The content-type attribute value MUST match the + // encapContentInfo eContentType value in the signed-data or + // authenticated-data. + let econtent_type = self.encapsulated_content_info.econtent_type; + let signed_attributes_content_type = signed_attributes.iter().find(|attr| { + attr.oid.cmp(&const_oid::db::rfc5911::ID_CONTENT_TYPE) == Ordering::Equal + }); + if let Some(signed_attributes_content_type) = signed_attributes_content_type { + // Check against `eContentType` + if signed_attributes_content_type.oid != econtent_type { + // Mismatch between content types: encapsulated content info <-> signed attributes. + return Err(der::Error::from(ErrorKind::Failed)); + } + } else { + signed_attributes.push( + create_content_type_attribute(econtent_type) + .map_err(|_| der::Error::from(ErrorKind::Failed))?, + ); + } } // Now use `signer` to sign the DER encoded signed attributes @@ -371,7 +383,6 @@ impl<'s> SignedDataBuilder<'s> { ) -> Result<&mut Self> where S: Keypair + DynSignatureAlgorithmIdentifier, - S::VerifyingKey: EncodePublicKey, S: Signer, Signature: SignatureBitStringEncoding, { @@ -492,6 +503,482 @@ impl<'s> SignedDataBuilder<'s> { } } +/// Trait for builders of a `RecipientInfo`. RFC 5652 § 6 defines 5 different `RecipientInfo` +/// formats. All implementations must implement this trait. +pub trait RecipientInfoBuilder { + /// Return the recipient info type + fn recipient_info_type(&self) -> RecipientInfoType; + + /// Return the recipient info version + fn recipient_info_version(&self) -> CmsVersion; + + /// Encrypt the `content_encryption_key` using a method, that is specific for the implementing + /// builder type. Finally return a `RecipientInfo`. + fn build(&mut self, content_encryption_key: &[u8]) -> Result; +} + +/// `RecipientInfoBuilder` must be implemented for these 5 recipient info types +/// as defined in RFC 5652 § 6: +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum RecipientInfoType { + /// KeyTransRecipientInfo + Ktri, + /// KeyAgreeRecipientInfo + Kari, + /// KekRecipientInfo + Kekri, + /// PasswordRecipientInfo + Pwri, + /// OtherRecipientInfo + Ori, +} + +/// Contains information required to encrypt the content encryption key with a specific method +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum KeyEncryptionInfo { + /// Encrypt key with RSA + Rsa(rsa::RsaPublicKey), + // to be extended here with other asymmetric encryption algorithms +} + +/// Builds a `KeyTransRecipientInfo` according to RFC 5652 § 6. +/// This type uses the recipient's public key to encrypt the content-encryption key. +pub struct KeyTransRecipientInfoBuilder<'a, R> +where + R: CryptoRngCore, +{ + /// Identifies the recipient + pub rid: RecipientIdentifier, + /// Info for key encryption + pub key_encryption_info: KeyEncryptionInfo, + /// Rng + rng: &'a mut R, +} + +impl<'a, R> KeyTransRecipientInfoBuilder<'a, R> +where + R: CryptoRngCore, +{ + /// Creates a `KeyTransRecipientInfoBuilder` + pub fn new( + rid: RecipientIdentifier, + key_encryption_info: KeyEncryptionInfo, + rng: &'a mut R, + ) -> Result> { + Ok(KeyTransRecipientInfoBuilder { + rid, + key_encryption_info, + rng, + }) + } +} + +impl<'a, R> RecipientInfoBuilder for KeyTransRecipientInfoBuilder<'a, R> +where + R: CryptoRngCore, +{ + fn recipient_info_type(&self) -> RecipientInfoType { + RecipientInfoType::Ktri + } + + fn recipient_info_version(&self) -> CmsVersion { + match self.rid { + RecipientIdentifier::IssuerAndSerialNumber(_) => CmsVersion::V0, + RecipientIdentifier::SubjectKeyIdentifier(_) => CmsVersion::V2, + } + } + + /// Build a `KeyTransRecipientInfo`. See RFC 5652 § 6.2.1 + /// `content_encryption_key` will be encrypted with the recipient's public key. + fn build(&mut self, content_encryption_key: &[u8]) -> Result { + // Encrypt key + let (encrypted_key, key_enc_alg) = match &self.key_encryption_info { + // RSA encryption + KeyEncryptionInfo::Rsa(recipient_public_key) => ( + recipient_public_key + .encrypt(self.rng, Pkcs1v15Encrypt, content_encryption_key) + .map_err(|_| Error::Builder(String::from("Could not encrypt key")))?, + AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5912::RSA_ENCRYPTION, + parameters: None, + }, + ), + }; + let enc_key = EncryptedKey::new(encrypted_key)?; + + Ok(RecipientInfo::Ktri(KeyTransRecipientInfo { + version: self.recipient_info_version(), + rid: self.rid.clone(), + key_enc_alg, + enc_key, + })) + } +} + +/// 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. +pub struct KekRecipientInfoBuilder { + /// Specifies a symmetric key-encryption key that was previously distributed to the sender and + /// one or more recipients. + pub kek_id: KekIdentifier, + /// Encryption algorithm to be used for key encryption + pub key_enc_alg: AlgorithmIdentifierOwned, +} + +impl KekRecipientInfoBuilder { + /// Creates a `KekRecipientInfoBuilder` + pub fn new( + kek_id: KekIdentifier, + key_enc_alg: AlgorithmIdentifierOwned, + ) -> Result { + Ok(KekRecipientInfoBuilder { + kek_id, + key_enc_alg, + }) + } +} + +impl RecipientInfoBuilder for KekRecipientInfoBuilder { + /// Returns the RecipientInfoType + fn recipient_info_type(&self) -> RecipientInfoType { + RecipientInfoType::Kekri + } + + /// Returns the `CMSVersion` for this `RecipientInfo` + fn recipient_info_version(&self) -> CmsVersion { + CmsVersion::V4 + } + + /// Build a `KekRecipientInfoBuilder`. See RFC 5652 § 6.2.1 + fn build(&mut self, _content_encryption_key: &[u8]) -> Result { + Err(Error::Builder(String::from( + "Building KekRecipientInfo is not implemented, yet.", + ))) + } +} + +/// Builds a `PasswordRecipientInfo` according to RFC 5652 § 6. +/// Uses a password or shared secret value to encrypt the content-encryption key. +pub struct PasswordRecipientInfoBuilder { + /// 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, + /// Encryption algorithm to be used for key encryption + pub key_enc_alg: AlgorithmIdentifierOwned, +} + +impl PasswordRecipientInfoBuilder { + /// Creates a `PasswordRecipientInfoBuilder` + pub fn new( + key_derivation_alg: Option, + key_enc_alg: AlgorithmIdentifierOwned, + ) -> Result { + Ok(PasswordRecipientInfoBuilder { + key_derivation_alg, + key_enc_alg, + }) + } +} + +impl RecipientInfoBuilder for PasswordRecipientInfoBuilder { + /// Returns the RecipientInfoType + fn recipient_info_type(&self) -> RecipientInfoType { + RecipientInfoType::Pwri + } + + /// Returns the `CMSVersion` for this `RecipientInfo` + fn recipient_info_version(&self) -> CmsVersion { + CmsVersion::V0 + } + + /// Build a `PasswordRecipientInfoBuilder`. See RFC 5652 § 6.2.1 + fn build(&mut self, _content_encryption_key: &[u8]) -> Result { + Err(Error::Builder(String::from( + "Building PasswordRecipientInfo is not implemented, yet.", + ))) + } +} + +/// Builds an `OtherRecipientInfo` according to RFC 5652 § 6. +/// This type makes no assumption about the encryption method or the needed information. +pub struct OtherRecipientInfoBuilder { + /// Identifies the key management technique. + pub ori_type: ObjectIdentifier, + /// Contains the protocol data elements needed by a recipient using the identified key + /// management technique + pub ori_value: Any, +} + +impl OtherRecipientInfoBuilder { + /// Creates a `OtherRecipientInfoBuilder` + pub fn new(ori_type: ObjectIdentifier, ori_value: Any) -> Result { + Ok(OtherRecipientInfoBuilder { + ori_type, + ori_value, + }) + } +} + +impl RecipientInfoBuilder for OtherRecipientInfoBuilder { + /// Returns the RecipientInfoType + fn recipient_info_type(&self) -> RecipientInfoType { + RecipientInfoType::Ori + } + + /// Returns the `CMSVersion` for this `RecipientInfo` + fn recipient_info_version(&self) -> CmsVersion { + panic!("Ori has no CMSVersion") + } + + /// Build a `OtherRecipientInfoBuilder`. See RFC 5652 § 6.2.1 + fn build(&mut self, _content_encryption_key: &[u8]) -> Result { + panic!("Ori has no common build method.") + } +} + +/// Supported content encryption algorithms. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ContentEncryptionAlgorithm { + /// AES-128 CBC + Aes128Cbc, + /// AES-192 CBC + Aes192Cbc, + /// AES-256 CBC + Aes256Cbc, +} + +impl ContentEncryptionAlgorithm { + /// Return the OID of the algorithm. + pub fn oid(&self) -> ObjectIdentifier { + match self { + ContentEncryptionAlgorithm::Aes128Cbc => const_oid::db::rfc5911::ID_AES_128_CBC, + ContentEncryptionAlgorithm::Aes192Cbc => const_oid::db::rfc5911::ID_AES_192_CBC, + ContentEncryptionAlgorithm::Aes256Cbc => const_oid::db::rfc5911::ID_AES_256_CBC, + } + } +} + +/// Builds CMS `EnvelopedData` according to RFC 5652 § 6. +pub struct EnvelopedDataBuilder<'c> { + originator_info: Option, + recipient_infos: Vec>, + unencrypted_content: &'c [u8], + // TODO bk Not good to offer both, `content_encryptor` and `content_encryption_algorithm`. + // We should + // (1) either derive `content_encryption_algorithm` from `content_encryptor` (but this is not + // yet supported by RustCrypto), + // (2) or pass `content_encryption_algorithm` and create an encryptor for it. + // In the first case, we might need a new trait here, e.g. `DynEncryptionAlgorithmIdentifier` in + // analogy to `DynSignatureAlgorithmIdentifier`. + // Going for (2) + // content_encryptor: E, + content_encryption_algorithm: ContentEncryptionAlgorithm, + unprotected_attributes: Option, +} + +impl<'c> EnvelopedDataBuilder<'c> { + /// Create a new builder for `EnvelopedData` + pub fn new( + originator_info: Option, + unencrypted_content: &'c [u8], + content_encryption_algorithm: ContentEncryptionAlgorithm, + unprotected_attributes: Option, + ) -> Result> { + Ok(EnvelopedDataBuilder { + originator_info, + recipient_infos: Vec::new(), + unencrypted_content, + content_encryption_algorithm, + unprotected_attributes, + }) + } + + /// Add recipient info. A builder is used, which generates a `RecipientInfo` according to + /// RFC 5652 § 6.2, when `EnvelopedData` is built. + pub fn add_recipient_info( + &mut self, + recipient_info_builder: impl RecipientInfoBuilder + 'c, + ) -> Result<&mut Self> { + self.recipient_infos.push(Box::new(recipient_info_builder)); + + Ok(self) + } + + /// Generate an `EnvelopedData` object according to RFC 5652 § 6 using a provided + /// random number generator. + pub fn build_with_rng(&mut self, rng: &mut impl CryptoRngCore) -> Result { + // Generate content encryption key + // Encrypt content + // Build recipient infos + // Make sure, content encryption key is securely destroyed + let (encrypted_content, mut content_encryption_key, content_enc_alg) = encrypt_data( + self.unencrypted_content, + &self.content_encryption_algorithm, + None, + rng, + )?; + let encrypted_content_octetstring = der::asn1::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, + encrypted_content: Some(encrypted_content_octetstring), // TODO bk `None` (external content) should also be possible + }; + + let recipient_infos_vec = self + .recipient_infos + .iter_mut() + .map(|ri| ri.build(&content_encryption_key)) + .collect::>>()?; + content_encryption_key.zeroize(); + let recip_infos = RecipientInfos::try_from(recipient_infos_vec).unwrap(); + + Ok(EnvelopedData { + version: self.calculate_version(), + originator_info: self.originator_info.clone(), + recip_infos, + encrypted_content: encrypted_content_info, + unprotected_attrs: self.unprotected_attributes.clone(), + }) + } + + /// Calculate the `CMSVersion` of the `EnvelopedData` according to RFC 5652 § 6.1 + fn calculate_version(&self) -> CmsVersion { + // IF (originatorInfo is present) AND + // ((any certificates with a type of other are present) OR + // (any crls with a type of other are present)) + // THEN version is 4 + // ELSE + // IF ((originatorInfo is present) AND + // (any version 2 attribute certificates are present)) OR + // (any RecipientInfo structures include pwri) OR + // (any RecipientInfo structures include ori) + // THEN version is 3 + // ELSE + // IF (originatorInfo is absent) AND + // (unprotectedAttrs is absent) AND + // (all RecipientInfo structures are version 0) + // THEN version is 0 + // ELSE version is 2 + let originator_info_present = self.originator_info.is_some(); + let other_certificates_present = if let Some(originator_info) = &self.originator_info { + if let Some(certificates) = &originator_info.certs { + certificates + .0 + .iter() + .any(|certificate| matches!(certificate, CertificateChoices::Other(_))) + } else { + false + } + } else { + false + }; + let other_crls_present = if let Some(originator_info) = &self.originator_info { + if let Some(crls) = &originator_info.crls { + crls.0 + .iter() + .any(|crl| matches!(crl, RevocationInfoChoice::Other(_))) + } else { + false + } + } else { + false + }; + // v2 certificates currently not supported + // let v2_certificates_present = if let Some(certificate_option) = &self.originator_info { + // if let Some(certificates) = certificate_option { + // certificates + // .iter() + // .any(|certificate| matches!(certificate, CertificateChoices::V2AttrCert)) + // } else { + // false + // } + // } else { + // false + // }; + let v2_certificates_present = false; + let pwri_recipient_info_present = self.recipient_infos.iter().any(|recipient_info| { + matches!( + recipient_info.recipient_info_type(), + RecipientInfoType::Pwri + ) + }); + let ori_recipient_info_present = self.recipient_infos.iter().any(|recipient_info| { + matches!(recipient_info.recipient_info_type(), RecipientInfoType::Ori) + }); + let unprotected_attributes_present = self.unprotected_attributes.is_some(); + let all_recipient_infos_are_v0 = self + .recipient_infos + .iter() + .all(|ri| ri.recipient_info_version() == CmsVersion::V0); + + if originator_info_present && (other_certificates_present || other_crls_present) { + CmsVersion::V4 + } else if (originator_info_present && v2_certificates_present) + || pwri_recipient_info_present + || ori_recipient_info_present + { + CmsVersion::V3 + } else if !originator_info_present + && !unprotected_attributes_present + && all_recipient_infos_are_v0 + { + CmsVersion::V0 + } else { + CmsVersion::V2 + } + } +} + /// Get a hasher for a given digest algorithm fn get_hasher( digest_algorithm_identifier: &AlgorithmIdentifierOwned, @@ -511,6 +998,74 @@ fn get_hasher( } } +/// Helps encrypting. +macro_rules! encrypt_block_mode { + ($data:expr, $block_mode:ident::$typ:ident<$alg:ident>, $key:expr, $rng:expr, $oid:expr) => {{ + let (key, iv) = match $key { + None => $block_mode::$typ::<$alg>::generate_key_iv($rng), + Some(key) => { + if key.len() != $alg::key_size() { + return Err(Error::Builder(String::from( + "Invalid key size for chosen algorithm", + ))); + } + ( + Key::<$block_mode::$typ<$alg>>::from_slice(key).to_owned(), + $block_mode::$typ::<$alg>::generate_iv($rng), + ) + } + }; + let encryptor = $block_mode::$typ::<$alg>::new(&key.into(), &iv.into()); + Ok(( + encryptor.encrypt_padded_vec_mut::($data), + key.to_vec(), + AlgorithmIdentifierOwned { + oid: $oid, + parameters: Some(Any::new(OctetString, iv.to_vec())?), + }, + )) + }}; +} + +/// Symmetrically encrypt data. +/// Returns encrypted content, content-encryption key and the used algorithm identifier (including +/// the used algorithm parameters). +/// +/// TODO Which encryption algorithms shall also be supported? +fn encrypt_data( + data: &[u8], + encryption_algorithm_identifier: &ContentEncryptionAlgorithm, + key: Option<&[u8]>, + rng: &mut R, +) -> Result<(Vec, Vec, AlgorithmIdentifierOwned)> +where + R: CryptoRng + RngCore, +{ + match encryption_algorithm_identifier { + ContentEncryptionAlgorithm::Aes128Cbc => encrypt_block_mode!( + data, + cbc::Encryptor, + key, + rng, + encryption_algorithm_identifier.oid() + ), + ContentEncryptionAlgorithm::Aes192Cbc => encrypt_block_mode!( + data, + cbc::Encryptor, + key, + rng, + encryption_algorithm_identifier.oid() + ), + ContentEncryptionAlgorithm::Aes256Cbc => encrypt_block_mode!( + data, + cbc::Encryptor, + key, + rng, + encryption_algorithm_identifier.oid() + ), + } +} + /// Create a content-type attribute according to /// [RFC 5652 § 11.1](https://datatracker.ietf.org/doc/html/rfc5652#section-11.1) pub fn create_content_type_attribute(content_type: ObjectIdentifier) -> Result { @@ -530,7 +1085,7 @@ pub fn create_content_type_attribute(content_type: ObjectIdentifier) -> Result Result { let message_digest_der = OctetStringRef::new(message_digest)?; let message_digest_attribute_value = - AttributeValue::new(Tag::OctetString, message_digest_der.as_bytes())?; + AttributeValue::new(OctetString, message_digest_der.as_bytes())?; let mut values = SetOfVec::new(); values.insert(message_digest_attribute_value)?; let attribute = Attribute { diff --git a/cms/tests/builder.rs b/cms/tests/builder.rs index 17c45d7ff..10e0bf8f8 100644 --- a/cms/tests/builder.rs +++ b/cms/tests/builder.rs @@ -1,20 +1,41 @@ #![cfg(feature = "builder")] -use cms::builder::{create_signing_time_attribute, SignedDataBuilder, SignerInfoBuilder}; +use aes::Aes128; +use cipher::block_padding::Pkcs7; +use cipher::{BlockDecryptMut, KeyIvInit}; +use cms::builder::{ + create_signing_time_attribute, ContentEncryptionAlgorithm, EnvelopedDataBuilder, + KeyEncryptionInfo, KeyTransRecipientInfoBuilder, SignedDataBuilder, SignerInfoBuilder, +}; use cms::cert::{CertificateChoices, IssuerAndSerialNumber}; -use cms::signed_data::{EncapsulatedContentInfo, SignerIdentifier}; -use der::asn1::{OctetString, SetOfVec, Utf8StringRef}; -use der::{Any, DecodePem, Encode, Tag, Tagged}; +use cms::content_info::ContentInfo; +use cms::enveloped_data::RecipientInfo::Ktri; +use cms::enveloped_data::{EnvelopedData, RecipientIdentifier, RecipientInfo}; +use cms::signed_data::{EncapsulatedContentInfo, SignedData, SignerIdentifier}; +use const_oid::ObjectIdentifier; +use der::asn1::{OctetString, PrintableString, SetOfVec, Utf8StringRef}; +use der::{Any, AnyRef, Decode, DecodePem, Encode, Tag, Tagged}; use p256::{pkcs8::DecodePrivateKey, NistP256}; use pem_rfc7468::LineEnding; +use rand::rngs::OsRng; use rsa::pkcs1::DecodeRsaPrivateKey; -use rsa::pkcs1v15::SigningKey; +use rsa::pkcs1v15::{SigningKey, VerifyingKey}; +use rsa::{Pkcs1v15Encrypt, RsaPrivateKey, RsaPublicKey}; use sha2::Sha256; +use signature::Verifier; use spki::AlgorithmIdentifierOwned; -use x509_cert::attr::{Attribute, AttributeTypeAndValue}; +use x509_cert::attr::{Attribute, AttributeTypeAndValue, AttributeValue}; use x509_cert::name::{RdnSequence, RelativeDistinguishedName}; use x509_cert::serial_number::SerialNumber; +// 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"); +const RFC8894_ID_SENDER_NONCE: ObjectIdentifier = + ObjectIdentifier::new_unwrap("2.16.840.1.113733.1.9.5"); +const RFC8894_ID_TRANSACTION_ID: ObjectIdentifier = + ObjectIdentifier::new_unwrap("2.16.840.1.113733.1.9.7"); + const RSA_2048_PRIV_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv.der"); const PKCS8_PRIVATE_KEY_DER: &[u8] = include_bytes!("examples/p256-priv.der"); @@ -45,6 +66,23 @@ fn signer_identifier(id: i32) -> SignerIdentifier { }) } +fn recipient_identifier(id: i32) -> RecipientIdentifier { + let mut rdn_sequence = RdnSequence::default(); + let rdn = &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::CN, + value: Any::from(Utf8StringRef::new(&format!("test client {id}")).unwrap()), + }]; + let set_of_vector = SetOfVec::try_from(rdn.to_vec()).unwrap(); + rdn_sequence + .0 + .push(RelativeDistinguishedName::from(set_of_vector)); + RecipientIdentifier::IssuerAndSerialNumber(IssuerAndSerialNumber { + issuer: rdn_sequence, + serial_number: SerialNumber::new(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06]) + .expect("failed to create a serial number"), + }) +} + #[test] fn test_build_signed_data() { // Make some content @@ -104,7 +142,7 @@ fn test_build_signed_data() { .add_signer_info::, p256::ecdsa::DerSignature>( signer_info_builder_2, ) - .expect("error adding RSA signer info") + .expect("error adding P256 signer info") .build() .expect("building signed data failed"); let signed_data_pkcs7_der = signed_data_pkcs7 @@ -121,9 +159,366 @@ fn test_build_signed_data() { // - external message // - PKCS #7 message: // - different encapsulated content ASN.1 encoding -// - enveloped data content // - additional signed attributes +#[test] +fn test_build_enveloped_data() { + let recipient_identifier = recipient_identifier(1); + let mut rng = OsRng; + let bits = 2048; + let recipient_private_key = + RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); + let recipient_public_key = RsaPublicKey::from(&recipient_private_key); + + let recipient_info_builder = KeyTransRecipientInfoBuilder::new( + recipient_identifier, + KeyEncryptionInfo::Rsa(recipient_public_key), + &mut rng, + ) + .expect("Could not create a KeyTransRecipientInfoBuilder"); + + 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") + ); +} + +#[test] +fn test_build_pkcs7_scep_pkcsreq() { + // This test demonstrates how to build a PKCS7 message for the SCEP PKCSReq pkiMessage + // according to RFC 8894. + // We use the key transport mechanism in this example, which means, we have the recipient + // public (RSA) key. + // Prerequisites are + // - the recipients public RSA key, + // - an RSA key pair of the sender and + // - a CSR (PKCS #10) signed with the sender's key + // + // A CMS `SignedData` message is roughly structured as follows: + // cms_message: ContentInfo ::= SEQUENCE + // contentType: ContentType = id-signed-data + // content: ANY == SignedData + // version: CMSVersion + // digestAlgorithms*: DigestAlgorithmIdentifiers + // encapContentInfo: EncapsulatedContentInfo ::= SEQUENCE + // eContentType: ContentType = id-data + // eContent: OCTET STRING + // value_of_econtent_without_tag_and_length_bytes: ContentInfo + // contentType: ContentType = id-enveloped-data + // content: ANY == EnvelopeData ::= SEQUENCE + // version: CMSVersion + // [originatorInfo]: OriginatorInfo + // recipientInfos: RecipientInfos ::= SET OF RecipientInfo + // e.g. KeyTransRecipientInfo ::= SEQUENCE + // version: CMSVersion + // rid: RecipientIdentifier + // keyEncryptionAlgorithm: KeyEncryptionAlgorithmIdentifier + // encryptedKey: EncryptedKey + // encryptedContentInfo: EncryptedContentInfo ::= SEQUENCE + // contentType: ContentType + // contentEncryptionAlgorithm: ContentEncryptionAlgorithmIdentifier + // [encryptedContent]: EncryptedContent == OCTET STRING + // [unprotectedAttrs*] + // [certificates*] + // [crls*] + // signerInfos*: SET OF SignerInfo + // version: CMSVersion + // sid: SignerIdentifier + // digestAlgorithm: DigestAlgorithmIdentifier + // [signedAttrs*]: SignedAttributes + // signatureAlgorithm: SignatureAlgorithmIdentifier + // signature: SignatureValue + // [unsignedAttrs*]: UnsignedAttributes + // + // 4 builders are involved in the procedure: + // - `SignedDataBuilder` + // - `SignerInfoBuilder` + // - `EnvelopedDataBuilder` + // - `RecipientInfoBuilder` (trait) + // - `KeyTransRecipientInfoBuilder` (implementation used here) + // The procedure can be broken down to 4 steps: + // - Wrap CSR in `EnvelopedData`. + // - Add recipient information to `Enveloped data`. + // - Wrap enveloped data in `SignedData` + // - Sign with sender's RSA key. + + // Generate a random number generator + let mut rng = rand::thread_rng(); + + // Create recipient info + let recipient_identifier = recipient_identifier(42); + let recipient_private_key = + rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER_EXAMPLE).unwrap(); + let recipient_public_key = RsaPublicKey::from(&recipient_private_key); + + //---------------------------------------------------------------------------------------------- + // Create enveloped data + let recipient_info_builder = KeyTransRecipientInfoBuilder::new( + recipient_identifier.clone(), + KeyEncryptionInfo::Rsa(recipient_public_key), + &mut rng, + ) + .unwrap(); + + // Build `EnvelopedData` + let csr_der = include_bytes!("examples/sceptest_csr.der"); // The CSR to be signed + let mut enveloped_data_builder = EnvelopedDataBuilder::new( + None, + csr_der, // data to be encrypted... + ContentEncryptionAlgorithm::Aes128Cbc, // ... with this algorithm + None, + ) + .unwrap(); + + let mut rng = rand::thread_rng(); + + // Add recipient info. Multiple recipients are possible, but not used here. + let enveloped_data = enveloped_data_builder + .add_recipient_info(recipient_info_builder) + .unwrap() + .build_with_rng(&mut rng) + .unwrap(); + + let enveloped_data_der = enveloped_data.to_der().unwrap(); + 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), + }; + + //---------------------------------------------------------------------------------------------- + // Create signed data + + // Encapsulate the `EnvelopedData` + let content_info_der = content_info.to_der().unwrap(); + let content = EncapsulatedContentInfo { + econtent_type: const_oid::db::rfc5911::ID_DATA, + econtent: Some(Any::new(Tag::OctetString, content_info_der).unwrap()), + }; + + // Create a signer info. Multiple signers are possible, but not used here. + let signer = { + let sender_rsa_key_pem = include_str!("examples/sceptest_key.pem"); + let sender_rsa_key = RsaPrivateKey::from_pkcs8_pem(sender_rsa_key_pem).unwrap(); + SigningKey::::new(sender_rsa_key) + }; + let digest_algorithm = AlgorithmIdentifierOwned { + oid: const_oid::db::rfc5912::ID_SHA_256, + parameters: None, + }; + let mut signer_info_builder = SignerInfoBuilder::new( + &signer, + signer_identifier(1), + digest_algorithm.clone(), + &content, + None, + ) + .unwrap(); + + // For a SCEP pkiMessage, we need to add signed the following attributes: + // - messageType + // - senderNonce + // - transactionID + let mut message_type_value: SetOfVec = Default::default(); + let message_type = PrintableString::try_from("19".to_string()).unwrap(); + message_type_value.insert(Any::from(&message_type)).unwrap(); + let message_type = Attribute { + oid: RFC8894_ID_MESSAGE_TYPE, + values: message_type_value, + }; + let mut sender_nonce_value: SetOfVec = Default::default(); + let nonce = OctetString::new(*&[42; 32]).unwrap(); + sender_nonce_value + .insert(Any::new(Tag::OctetString, nonce.as_bytes()).unwrap()) + .unwrap(); + let sender_nonce = Attribute { + oid: RFC8894_ID_SENDER_NONCE, + values: sender_nonce_value, + }; + let mut transaction_id_value: SetOfVec = Default::default(); + let id = PrintableString::try_from(String::from("Test Transaction ID")).unwrap(); + transaction_id_value.insert(Any::from(&id)).unwrap(); + let transaction_id = Attribute { + oid: RFC8894_ID_TRANSACTION_ID, + values: transaction_id_value, + }; + + signer_info_builder + .add_signed_attribute(message_type) + .unwrap(); + signer_info_builder + .add_signed_attribute(sender_nonce) + .unwrap(); + signer_info_builder + .add_signed_attribute(transaction_id) + .unwrap(); + + let certificate_buf = include_bytes!("examples/sceptest_cert-selfsigned.pem"); + let certificate = x509_cert::Certificate::from_pem(certificate_buf).unwrap(); + + let mut builder = SignedDataBuilder::new(&content); + + let signed_data_pkcs7 = builder + .add_digest_algorithm(digest_algorithm) + .unwrap() + .add_certificate(CertificateChoices::Certificate(certificate)) + .unwrap() + .add_signer_info::, rsa::pkcs1v15::Signature>(signer_info_builder) + .unwrap() + .build() + .unwrap(); + let signed_data_pkcs7_der = signed_data_pkcs7.to_der().unwrap(); + println!( + "{}", + pem_rfc7468::encode_string("PKCS7", LineEnding::LF, &signed_data_pkcs7_der).unwrap() + ); + + //---------------------------------------------------------------------------------------------- + // Verify + + // Decode Message including decrypted enveloped content + // Check signature + // Decrypt content-encryption key + // Decrypt content + let ci = ContentInfo::from_der(signed_data_pkcs7_der.as_slice()).unwrap(); + assert_eq!(ci.content_type, const_oid::db::rfc5911::ID_SIGNED_DATA); + + // Decode CMS message (by converting `Any` to `SignedData`) + let signed_data_der = ci.content.to_der().unwrap(); + let signed_data = SignedData::from_der(signed_data_der.as_slice()).unwrap(); + + // Check signatures (only one in this test) + for signer_info in signed_data.signer_infos.0.iter() { + let signature = + rsa::pkcs1v15::Signature::try_from(signer_info.signature.as_bytes()).unwrap(); + let signed_attributes_der = signer_info.signed_attrs.clone().unwrap().to_der().unwrap(); + let verifier = { + let verifier_rsa_key_pem = include_str!("examples/sceptest_key.pem"); + let verifier_rsa_key = RsaPrivateKey::from_pkcs8_pem(verifier_rsa_key_pem).unwrap(); + VerifyingKey::::new(RsaPublicKey::from(verifier_rsa_key)) + }; + assert!(verifier + .verify(signed_attributes_der.as_slice(), &signature) + .is_ok()); + } + + // Decode contained enveloped data + let encap_content_info = signed_data.encap_content_info; + assert_eq!( + encap_content_info.econtent_type, + const_oid::db::rfc5911::ID_DATA + ); + let econtent = encap_content_info + .econtent + .expect("this cms must contain content"); + // let octet_string = OctetString::from_der(econtent.value()).unwrap(); + // let ci = ContentInfo::from_der(octet_string.as_bytes()).unwrap(); + let ci = ContentInfo::from_der(econtent.value()).unwrap(); + assert_eq!(ci.content_type, const_oid::db::rfc5911::ID_ENVELOPED_DATA); + let enveloped_data_der = ci.content.to_der().unwrap(); + let enveloped_data = EnvelopedData::from_der(enveloped_data_der.as_slice()).unwrap(); + let my_recipient_info: &RecipientInfo = enveloped_data + .recip_infos + .0 + .iter() + .find(|&recipient_info| match recipient_info { + Ktri(ri) => ri.rid == recipient_identifier, + _ => false, + }) + .unwrap(); + let key_trans_recipient_info = if let Ktri(recipient_info) = my_recipient_info { + recipient_info // this must succeed + } else { + panic!(); + }; + let encrypted_key = &key_trans_recipient_info.enc_key; + + // Decrypt the content-encryption key + let content_encryption_key = recipient_private_key + .decrypt(Pkcs1v15Encrypt, encrypted_key.as_bytes()) + .unwrap(); + + // Decrypt the CSR + let encryption_info = enveloped_data.encrypted_content; + assert_eq!( + encryption_info.content_enc_alg.oid, + const_oid::db::rfc5911::ID_AES_128_CBC + ); + let iv_octet_string = OctetString::from_der( + encryption_info + .content_enc_alg + .parameters + .unwrap() + .to_der() + .unwrap() + .as_slice(), + ) + .unwrap(); + let iv = iv_octet_string.as_bytes(); + let encrypted_content_octet_string = encryption_info.encrypted_content.unwrap(); + let encrypted_content = encrypted_content_octet_string.as_bytes(); + let csr_der_decrypted = + cbc::Decryptor::::new(content_encryption_key.as_slice().into(), iv.into()) + .decrypt_padded_vec_mut::(encrypted_content) + .unwrap(); + assert_eq!(csr_der_decrypted.as_slice(), csr_der) +} + +#[test] +fn test_degenerate_certificates_only_cms() { + let cert_buf = include_bytes!("examples/ValidCertificatePathTest1EE.pem"); + let cert = x509_cert::Certificate::from_pem(cert_buf).unwrap(); + let certs = vec![cert]; + + let encapsulated_content_info = EncapsulatedContentInfo { + econtent_type: const_oid::db::rfc5911::ID_DATA, + econtent: None, + }; + let mut signed_data_builder = SignedDataBuilder::new(&encapsulated_content_info); + + for cert in certs { + signed_data_builder + .add_certificate(CertificateChoices::Certificate(cert.clone())) + .unwrap(); + } + + let degenerate_certificates_only_cms = signed_data_builder.build().unwrap(); + + // Extract certificates from `degenerate_certificates_only_cms` + let signed_data = SignedData::from_der( + degenerate_certificates_only_cms + .content + .to_der() + .unwrap() + .as_slice(), + ) + .unwrap(); + let certs = signed_data.certificates.unwrap(); + let CertificateChoices::Certificate(extracted_cert) = certs.0.get(0).unwrap() else { + panic!("Invalid certificate choice encountered"); + }; + + let original_cert = x509_cert::Certificate::from_pem(cert_buf).unwrap(); + assert_eq!(original_cert.signature, extracted_cert.signature) +} + #[test] fn test_create_signing_attribute() { let attribute: Attribute = diff --git a/cms/tests/examples/sceptest_cert-selfsigned.pem b/cms/tests/examples/sceptest_cert-selfsigned.pem new file mode 100644 index 000000000..584f02286 --- /dev/null +++ b/cms/tests/examples/sceptest_cert-selfsigned.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEfjCCA2agAwIBAgIUA8L9xXyJY/x08+LY54vPBfGUGygwDQYJKoZIhvcNAQEL +BQAwgcgxFDASBgNVBAMMC0NvbW1vbiBOYW1lMQswCQYDVQQGEwJERTEfMB0GA1UE +CAwWVGVzdCBTdGF0ZSBvciBQcm92aW5jZTEWMBQGA1UEBwwNVGVzdCBMb2NhbGl0 +eTEfMB0GA1UECgwWVGVzdCBPcmdhbml6YXRpb24gTmFtZTEmMCQGA1UECwwdVGVz +dCBPcmdhbml6YXRpb25hbCBVbml0IE5hbWUxITAfBgkqhkiG9w0BCQEWEnRlc3RA +ZW1haWwuYWRkcmVzczAeFw0yMzA2MjMxMzUxMTBaFw0yNDA2MjIxMzUxMTBaMIHI +MRQwEgYDVQQDDAtDb21tb24gTmFtZTELMAkGA1UEBhMCREUxHzAdBgNVBAgMFlRl +c3QgU3RhdGUgb3IgUHJvdmluY2UxFjAUBgNVBAcMDVRlc3QgTG9jYWxpdHkxHzAd +BgNVBAoMFlRlc3QgT3JnYW5pemF0aW9uIE5hbWUxJjAkBgNVBAsMHVRlc3QgT3Jn +YW5pemF0aW9uYWwgVW5pdCBOYW1lMSEwHwYJKoZIhvcNAQkBFhJ0ZXN0QGVtYWls +LmFkZHJlc3MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdnH2Fsby+ +YBJGIRUgK3iMkbTyoJkJV4KjlMQTZlRm5YnO3buP5Ewa3I07vypsSR+GucAwiSnD +yg+kqkSS1AmrhQyBGWhHdt49yFwEYQXFSofe8sewcPbPv5YnueTzuFvs86r9LBS5 +2CVFJ9xsgBE9VANCBFxiePYs1nLvSztlSyqMDyl1+XstdXx47XxQjiJwzN5z5dVD +47WrTHI1YDQ8+ptMz1tqbTfmQs1f0XZ//X4tkkmpVKMt2xxIqa5v45JTrfH/UHyR +oK5kmGjtdJn15ELooDRBK87XKuVOElAbvWYAVR1eSfnTMRayslt0riJwzlhUkPWR +x3+1gEGbO3otAgMBAAGjXjBcMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggr +BgEFBQcDATAdBgNVHQ4EFgQUTxTNwOCSbXT5SlCu9SVPkJe95jcwFgYDVR0RBA8w +DYILZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBAJFEScTz6RrcYxYehF6T +ig47n47nR+zCUevaxX6jS8vGQTf5Y0Aw037qoMIycWsqYooDGIHrB2frkON1EfJx +vJQQFeNF5XCSF6hVdZrcCEGfqZ9OKV34Z+Xq341iZeDDJij/IT153EtjouQGHNEK +8kl1idl42b1RiSXpP6R+n8Dsf0gGts4rihdj3b54udtaN0GE9rIyr5JaXlrflXxj +KhHQl2MiTsrDOqjpUU5t2LO/aoz88BqzhPJyAg5W1X5WrUYRKNMK/hXnOSLKX+6g +whtkXkuN3A3b/YbceprupuriF5elwe7NHwe8HlDdh3ps6sBrXGH2rg+UcaxGjiQt +6PQ= +-----END CERTIFICATE----- diff --git a/cms/tests/examples/sceptest_csr.der b/cms/tests/examples/sceptest_csr.der new file mode 100644 index 0000000000000000000000000000000000000000..5289dc26aeb9a867be83aa9ff95988414c7236fc GIT binary patch literal 933 zcmXqLVqR#_#MH^e$Y9WT!cfFOh>bavg_(!jIX^cyKTp9gF*nta+kg`!$|lU@;%X>w zAPeGf@Q8(^7MCalmn4>?D&!X_1Qg|$W#%QP8j2Z+fE2Lv@PZZiTQWR_IIG;%>T z`WK}q=4DnTmSn=LQ8Q2hY2@aS1#3iBkeH(onwMDuagL&aJR7H0n@8JsUPewvF`*Km zI~-DT6Ek!45>rx&Qj3cXni!P~c;Si|8Ce;an;7{SfZ|+CO^l2TbLZ5yZrrmkLC8%} zR6)C^^MFC8=HXNPOIEo| zy281-m8VfM!@caD?THwcMAoBT?e{(%-%#-F{Qha`JD+^s5&h=#s=qoSJ8!7Es^7_J z5VQ?pc4CQ1s`#dJt?0eCb*i^k55H#V&uZP$nu@nI0ewmZXYLh0z3TjU>uR4O(*zTn zU$cGAM`z`lKXW=8f3d9oZ=LQW&y^vIb#KdftX!A>cvA4%kN*Q|CN5Z)G9%+{$;_`$ zoL($2anwF{UF)fzP=NH_G=@;wIM1J#4aGKXiY{5FRB$dLWWv{p$LqH?IL@}N(q&?1 zWMEv-ILSa69!Q+*hGIM-jta>ci8(o`dFiPN1&PJQ<@rS^2H6Nje1>rbF$R3Vc#`F3 zWc<&8}K%9r&c897UZPrCFkcNM;kXd+87xk^*8?9ne#Ec`^uf>1sZab z9Zw#s+?o<_mL=Nw<(H00&Pg2RpOpT8O`Nc0s-e$G|Jzfo+^?=>W?HwRlP5@Q%9omF zm%j;!xR^5E)1A3eMdJGoO_p6PZIxT@{nC+$JG#SqvIFh9{Mu~GoXy_qD>#Ok_&YNHC8-+G!F|Cr(-?(Mdkplo+k!aum literal 0 HcmV?d00001 diff --git a/cms/tests/examples/sceptest_key.pem b/cms/tests/examples/sceptest_key.pem new file mode 100644 index 000000000..69ea3681b --- /dev/null +++ b/cms/tests/examples/sceptest_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCdnH2Fsby+YBJG +IRUgK3iMkbTyoJkJV4KjlMQTZlRm5YnO3buP5Ewa3I07vypsSR+GucAwiSnDyg+k +qkSS1AmrhQyBGWhHdt49yFwEYQXFSofe8sewcPbPv5YnueTzuFvs86r9LBS52CVF +J9xsgBE9VANCBFxiePYs1nLvSztlSyqMDyl1+XstdXx47XxQjiJwzN5z5dVD47Wr +THI1YDQ8+ptMz1tqbTfmQs1f0XZ//X4tkkmpVKMt2xxIqa5v45JTrfH/UHyRoK5k +mGjtdJn15ELooDRBK87XKuVOElAbvWYAVR1eSfnTMRayslt0riJwzlhUkPWRx3+1 +gEGbO3otAgMBAAECggEADvZTrjT0y4fYREMQu9iUnZeZe20Gii3+D2RohsTwn0IM +JpDDJQJRvT0RxUm9D8GTVhldJt5mkhklCgdF8kBh2KANU1YjoaElsOzL23iQcS3F +n0Mh9NyMeaVg7k2F8CMgyupI4bblEs1zoFCL6trviAhpNMOwN4LvL8t95ryYG2H0 +j4tawzKh2Mqsi1kp0+wTHasQ1YDewto0+m4mpM2/ztpjrzhBPN9U6gsLn8LpIPH7 +dwb5bUuE5rm7NpG2eCiQ40sicJsth/asjzBYjagDYSkrLAXDvWvvvfbTsKCiaLEj +t6iqETwCGR4bFBBkbXkkCzmTH9F00wOogEzQxseZWwKBgQC3Jlud0lBJpUQgfO/P +JlpIJYF4NyQ+4FrT8CUBwTgyHLZb167KU7LtCY63q++EjgiKU73VTHegZNIiMi9z +HOZjwgWEprzhxoYGudq9psyxvjNdp7FIBLgzzHPLWsRdKkrJzVIlVegV5PXtZC77 +7TRCGcno8OpN4LG1IiicTkmdvwKBgQDcTZzm0f9a06gePcxKwbuTnPHM4JO89J9g +O9vJSxhopCL6fVayvgf3pWxbicQ9tDCO/2ZyvzVYxhZqBH++ckfClTIY9n8Q+/Ab +e4xnxfF/h+LAtOkW8JFuUYcI2v5TqvW10mwiBy+dZfufEHF0JlLeH5tuvD0QebN9 +Lbca5YZ7EwKBgFK3qUMjPI22bYl6w9g8CyRwhAPma6FWNM9ps10Shi3j19ytEc1h +dfsmiOWdasTkXSkUXzVZnuG7B0jYf6Ou1sMRWuqpX79cqSWahReIoQRZ0dsnpKLR +Ntx2J4odiXhGZJa1+7bPEM3qpcO8rperbyG3ggCJ5lib9cbIEa1eklMDAoGAdwmp +Aj/uTtGXQd/6h1pvVK+1KBMhQTSc+Y7ej8H4CnLGQ7t+IU71VycXic7DLuQyaNIw +NUiENteyPM75h5qQk9+yFjL7Ld40O2Vi1J/sghCWwbH/UNnke0uqP2q1idgStJBi +xXBEljQI5kYoR659kHBbuFYWWNSp27Xb/riPFekCgYBq/3D7D5Ly5HH6tFADJaNj +WH1UlbiByoBunRKLE5O8CyxDyU+7QsFw0unPxhewY5maCu/x+a8PXwXW0aTfmBf+ +PBX+3BJ5p+5CCj6Po7kpM2tBR9cwZyQ9bE4IF3AjJeGauCD4g1d/BOatAZsK2hlW +jQWfShnP6KqzjFiIymmjBQ== +-----END PRIVATE KEY-----