use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc}; use crate::data_model::oid::{ OID_AD_SIGNED_OBJECT, OID_CMS_ATTR_CONTENT_TYPE, OID_CMS_ATTR_MESSAGE_DIGEST, OID_CMS_ATTR_SIGNING_TIME, OID_RSA_ENCRYPTION, OID_SHA256, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SIGNED_DATA, OID_SUBJECT_INFO_ACCESS, }; use crate::data_model::rc::{ResourceCertificate, SubjectInfoAccess}; use der_parser::ber::Class; use der_parser::der::{DerObject, Tag, parse_der}; use ring::digest; use x509_parser::prelude::FromDer; use x509_parser::public_key::PublicKey; use x509_parser::x509::SubjectPublicKeyInfo; #[derive(Clone, Debug, PartialEq, Eq)] pub struct ResourceEeCertificate { pub raw_der: Vec, pub subject_key_identifier: Vec, pub spki_der: Vec, pub sia_signed_object_uris: Vec, pub resource_cert: ResourceCertificate, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RpkiSignedObject { pub raw_der: Vec, pub content_info_content_type: String, pub signed_data: SignedDataProfiled, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignedDataProfiled { pub version: u32, pub digest_algorithms: Vec, pub encap_content_info: EncapsulatedContentInfo, pub certificates: Vec, pub crls_present: bool, pub signer_infos: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct EncapsulatedContentInfo { pub econtent_type: String, pub econtent: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignerInfoProfiled { pub version: u32, pub sid_ski: Vec, pub digest_algorithm: String, pub signature_algorithm: String, pub signed_attrs: SignedAttrsProfiled, pub unsigned_attrs_present: bool, pub signature: Vec, pub signed_attrs_der_for_signature: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignedAttrsProfiled { pub content_type: String, pub message_digest: Vec, pub signing_time: Asn1TimeUtc, pub other_attrs_present: bool, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RpkiSignedObjectParsed { pub raw_der: Vec, pub content_info_content_type: String, pub signed_data: SignedDataParsed, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignedDataParsed { pub version: u64, pub digest_algorithms: Vec, pub encap_content_info: EncapsulatedContentInfoParsed, pub certificates: Option>>, pub crls_present: bool, pub signer_infos: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct AlgorithmIdentifierParsed { pub oid: String, pub params_ok: bool, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct EncapsulatedContentInfoParsed { pub econtent_type: String, pub econtent: Option>, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignerInfoParsed { pub version: u64, pub sid: SignerIdentifierParsed, pub digest_algorithm: AlgorithmIdentifierParsed, pub signature_algorithm: AlgorithmIdentifierParsed, pub signed_attrs_content: Option>, pub signed_attrs_der_for_signature: Option>, pub unsigned_attrs_present: bool, pub signature: Vec, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum SignerIdentifierParsed { SubjectKeyIdentifier(Vec), Other, } #[derive(Debug, thiserror::Error)] pub enum SignedObjectParseError { #[error("DER parse error: {0} (RFC 6488 §2; RFC 6488 §3(1l); RFC 5652 §3/§5)")] Parse(String), #[error("trailing bytes after DER object: {0} bytes (DER; RFC 6488 §3(1l))")] TrailingBytes(usize), } #[derive(Debug, thiserror::Error)] pub enum SignedObjectValidateError { #[error( "ContentInfo.contentType must be SignedData ({OID_SIGNED_DATA}), got {0} (RFC 6488 §3(1a); RFC 5652 §3)" )] InvalidContentInfoContentType(String), #[error( "SignedData.version must be 3, got {0} (RFC 6488 §2.1.1; RFC 6488 §3(1b); RFC 5652 §5.1)" )] InvalidSignedDataVersion(u64), #[error( "SignedData.digestAlgorithms must contain exactly one AlgorithmIdentifier, got {0} (RFC 6488 §2.1.2; RFC 6488 §3(1b); RFC 5652 §5.1)" )] InvalidDigestAlgorithmsCount(usize), #[error( "digest algorithm must be id-sha256 ({OID_SHA256}), got {0} (RFC 6488 §2.1.2; RFC 6488 §3(1b); RFC 7935 §2)" )] InvalidDigestAlgorithm(String), #[error("SignedData.certificates MUST be present (RFC 6488 §3(1c); RFC 5652 §5.1)")] CertificatesMissing, #[error( "SignedData.certificates must contain exactly one EE certificate, got {0} (RFC 6488 §3(1c))" )] InvalidCertificatesCount(usize), #[error("SignedData.crls MUST be omitted (RFC 6488 §3(1d))")] CrlsPresent, #[error( "SignedData.signerInfos must contain exactly one SignerInfo, got {0} (RFC 6488 §2.1; RFC 6488 §3(1e); RFC 5652 §5.1)" )] InvalidSignerInfosCount(usize), #[error("SignerInfo.version must be 3, got {0} (RFC 6488 §3(1e); RFC 5652 §5.3)")] InvalidSignerInfoVersion(u64), #[error("SignerInfo.sid must be subjectKeyIdentifier [0] (RFC 6488 §3(1c); RFC 5652 §5.3)")] InvalidSignerIdentifier, #[error( "SignerInfo.digestAlgorithm must be id-sha256 ({OID_SHA256}), got {0} (RFC 6488 §3(1j); RFC 7935 §2)" )] InvalidSignerInfoDigestAlgorithm(String), #[error("SignerInfo.signedAttrs MUST be present (RFC 9589 §4; RFC 6488 §3(1f))")] SignedAttrsMissing, #[error("SignerInfo.unsignedAttrs MUST be omitted (RFC 6488 §3(1i))")] UnsignedAttrsPresent, #[error( "SignerInfo.signatureAlgorithm must be rsaEncryption ({OID_RSA_ENCRYPTION}) or \ sha256WithRSAEncryption ({OID_SHA256_WITH_RSA_ENCRYPTION}), got {0} (RFC 6488 §3(1k); RFC 7935 §2)" )] InvalidSignatureAlgorithm(String), #[error( "SignerInfo.signatureAlgorithm parameters must be absent or NULL (RFC 5280 §4.1.1.2; RFC 7935 §2)" )] InvalidSignatureAlgorithmParameters, #[error("signedAttrs contains unsupported attribute OID {0} (RFC 9589 §4; RFC 6488 §2.1.6.4)")] UnsupportedSignedAttribute(String), #[error("signedAttrs contains duplicate attribute OID {0} (RFC 6488 §2.1.6.4; RFC 9589 §4)")] DuplicateSignedAttribute(String), #[error("signedAttrs parse error: {0} (RFC 5652 §5.3; RFC 6488 §3(1f); RFC 9589 §4)")] SignedAttrsParse(String), #[error( "signedAttrs attribute {oid} attrValues must contain exactly one value, got {count} (RFC 6488 §2.1.6.4; RFC 5652 §5.3)" )] InvalidSignedAttributeValuesCount { oid: String, count: usize }, #[error( "signedAttrs missing content-type attribute (RFC 9589 §4; RFC 5652 §11.1; RFC 6488 §2.1.6.4)" )] SignedAttrsContentTypeMissing, #[error( "signedAttrs missing message-digest attribute (RFC 9589 §4; RFC 5652 §11.2; RFC 6488 §2.1.6.4)" )] SignedAttrsMessageDigestMissing, #[error( "signedAttrs missing signing-time attribute (RFC 9589 §4; RFC 5652 §11.3; RFC 6488 §2.1.6.4)" )] SignedAttrsSigningTimeMissing, #[error( "signedAttrs.content-type attrValues must equal eContentType ({econtent_type}), got {attr_content_type} (RFC 6488 §3(1h); RFC 9589 §4)" )] ContentTypeAttrMismatch { econtent_type: String, attr_content_type: String, }, #[error("EncapsulatedContentInfo.eContent MUST be present (RFC 6488 §2.1.3; RFC 5652 §5.2)")] EContentMissing, #[error( "signedAttrs.message-digest does not match SHA-256(eContent) (RFC 6488 §3(1f); RFC 5652 §11.2)" )] MessageDigestMismatch, #[error("EE certificate parse error: {0} (RFC 6488 §3(1c); RFC 6487 §4)")] EeCertificateParse(String), #[error( "EE certificate missing SubjectKeyIdentifier extension (RFC 6488 §3(1c); RFC 6487 §4.8.2)" )] EeCertificateMissingSki, #[error( "EE certificate missing SubjectInfoAccess extension ({OID_SUBJECT_INFO_ACCESS}) (RFC 6487 §4.8.8.2)" )] EeCertificateMissingSia, #[error( "EE certificate SIA missing id-ad-signedObject access method ({OID_AD_SIGNED_OBJECT}) (RFC 6487 §4.8.8.2)" )] EeCertificateMissingSignedObjectSia, #[error( "EE certificate SIA id-ad-signedObject accessLocation must be a URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)" )] EeCertificateSignedObjectSiaNotUri, #[error( "EE certificate SIA id-ad-signedObject must include at least one rsync:// URI (RFC 6487 §4.8.8.2)" )] EeCertificateSignedObjectSiaNoRsync, #[error( "SignerInfo.sid SKI does not match EE certificate SKI (RFC 6488 §3(1c); RFC 5652 §5.3)" )] SidSkiMismatch, #[error( "invalid signing-time attribute value (expected UTCTime or GeneralizedTime) (RFC 5652 §11.3; RFC 9589 §4)" )] InvalidSigningTimeValue, } #[derive(Debug, thiserror::Error)] pub enum SignedObjectDecodeError { #[error("SignedObject parse error: {0}")] Parse(#[from] SignedObjectParseError), #[error("SignedObject validate error: {0}")] Validate(#[from] SignedObjectValidateError), } #[derive(Debug, thiserror::Error)] pub enum SignedObjectVerifyError { #[error("EE SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7)")] EeSpkiParse(String), #[error("trailing bytes after EE SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 5280 §4.1.2.7)")] EeSpkiTrailingBytes(usize), #[error("unsupported EE public key algorithm (only RSA supported in M3) (RFC 7935 §2)")] UnsupportedEePublicKeyAlgorithm, #[error("EE RSA public exponent invalid (RFC 8017 §A.1.1; RFC 7935 §2)")] InvalidEeRsaExponent, #[error("signature verification failed (RFC 6488 §3(2)-(3); RFC 5652 §5.3; RFC 7935 §2)")] InvalidSignature, } impl RpkiSignedObject { /// Parse a DER-encoded RPKI Signed Object (CMS ContentInfo wrapping SignedData). /// /// This performs encoding/structure parsing only. Profile constraints are enforced by /// `RpkiSignedObjectParsed::validate_profile`. pub fn parse_der(der: &[u8]) -> Result { let (rem, obj) = parse_der(der).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; if !rem.is_empty() { return Err(SignedObjectParseError::TrailingBytes(rem.len())); } let content_info_seq = obj .as_sequence() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; if content_info_seq.len() != 2 { return Err(SignedObjectParseError::Parse( "ContentInfo must be a SEQUENCE of 2 elements".into(), )); } let content_type = oid_to_string_parse(&content_info_seq[0])?; let signed_data = parse_signed_data_from_contentinfo_parse(&content_info_seq[1])?; Ok(RpkiSignedObjectParsed { raw_der: der.to_vec(), content_info_content_type: content_type, signed_data, }) } /// Decode a DER-encoded RPKI Signed Object (CMS ContentInfo wrapping SignedData) and enforce /// the profile constraints from RFC 6488 §2-§3 and RFC 9589 §4. pub fn decode_der(der: &[u8]) -> Result { let parsed = Self::parse_der(der)?; Ok(parsed.validate_profile()?) } /// Scheme-A naming for signature verification. pub fn verify(&self) -> Result<(), SignedObjectVerifyError> { self.verify_signature() } /// Verify the CMS signature using the embedded EE certificate public key. pub fn verify_signature(&self) -> Result<(), SignedObjectVerifyError> { let ee = &self.signed_data.certificates[0]; self.verify_signature_with_ee_spki_der(&ee.spki_der) } /// Verify the CMS signature using a DER-encoded SubjectPublicKeyInfo. pub fn verify_signature_with_ee_spki_der( &self, ee_spki_der: &[u8], ) -> Result<(), SignedObjectVerifyError> { let (rem, spki) = SubjectPublicKeyInfo::from_der(ee_spki_der) .map_err(|e| SignedObjectVerifyError::EeSpkiParse(e.to_string()))?; if !rem.is_empty() { return Err(SignedObjectVerifyError::EeSpkiTrailingBytes(rem.len())); } self.verify_signature_with_ee_spki(&spki) } /// Verify the CMS signature using a parsed SubjectPublicKeyInfo. pub fn verify_signature_with_ee_spki( &self, ee_spki: &SubjectPublicKeyInfo<'_>, ) -> Result<(), SignedObjectVerifyError> { let pk = ee_spki .parsed() .map_err(|_e| SignedObjectVerifyError::UnsupportedEePublicKeyAlgorithm)?; let (n, e) = match pk { PublicKey::RSA(rsa) => { let n = strip_leading_zeros(rsa.modulus).to_vec(); let e = strip_leading_zeros(rsa.exponent).to_vec(); let _exp = rsa .try_exponent() .map_err(|_e| SignedObjectVerifyError::InvalidEeRsaExponent)?; (n, e) } _ => return Err(SignedObjectVerifyError::UnsupportedEePublicKeyAlgorithm), }; let signer = &self.signed_data.signer_infos[0]; // The message to be verified is the DER encoding of SignedAttributes (SET OF Attribute). let msg = &signer.signed_attrs_der_for_signature; let pk = ring::signature::RsaPublicKeyComponents { n, e }; pk.verify( &ring::signature::RSA_PKCS1_2048_8192_SHA256, msg, &signer.signature, ) .map_err(|_e| SignedObjectVerifyError::InvalidSignature) } } impl RpkiSignedObjectParsed { pub fn validate_profile(self) -> Result { if self.content_info_content_type != OID_SIGNED_DATA { return Err(SignedObjectValidateError::InvalidContentInfoContentType( self.content_info_content_type, )); } let signed_data = validate_signed_data_profile(self.signed_data)?; Ok(RpkiSignedObject { raw_der: self.raw_der, content_info_content_type: OID_SIGNED_DATA.to_string(), signed_data, }) } } fn parse_signed_data_from_contentinfo_parse( obj: &DerObject<'_>, ) -> Result { // ContentInfo.content is `[0] EXPLICIT`, but `der-parser` will represent unknown tagged // objects as `Unknown(Any)`. For EXPLICIT tags, the content octets are the full encoding of // the inner object, so we parse it from the object's slice. if obj.class() != Class::ContextSpecific || obj.tag() != Tag(0) { return Err(SignedObjectParseError::Parse( "ContentInfo.content must be [0] EXPLICIT".into(), )); } let inner_der = obj .as_slice() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; let (rem, inner_obj) = parse_der(inner_der).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; if !rem.is_empty() { return Err(SignedObjectParseError::Parse( "trailing bytes inside ContentInfo.content".into(), )); } parse_signed_data_parse(&inner_obj) } fn parse_signed_data_parse( obj: &DerObject<'_>, ) -> Result { let seq = obj .as_sequence() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; if seq.len() < 4 || seq.len() > 6 { return Err(SignedObjectParseError::Parse( "SignedData must be a SEQUENCE of 4..6 elements".into(), )); } let version = seq[0] .as_u64() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; let digest_set = seq[1] .as_set() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; let mut digest_algorithms: Vec = Vec::with_capacity(digest_set.len()); for item in digest_set { let (oid, params_ok) = parse_algorithm_identifier_parse(item)?; digest_algorithms.push(AlgorithmIdentifierParsed { oid, params_ok }); } let encap_content_info = parse_encapsulated_content_info_parse(&seq[2])?; let mut certificates: Option>> = None; let mut crls_present = false; let mut signer_infos_obj: Option<&DerObject<'_>> = None; for item in &seq[3..] { if item.class() == Class::ContextSpecific && item.tag() == Tag(0) { if certificates.is_some() { return Err(SignedObjectParseError::Parse( "SignedData.certificates appears more than once".into(), )); } certificates = Some(parse_certificate_set_implicit_parse(item)?); } else if item.class() == Class::ContextSpecific && item.tag() == Tag(1) { crls_present = true; } else if item.class() == Class::Universal && item.tag() == Tag::Set { signer_infos_obj = Some(item); } else { return Err(SignedObjectParseError::Parse( "unexpected field in SignedData".into(), )); } } let signer_infos_obj = signer_infos_obj .ok_or_else(|| SignedObjectParseError::Parse("SignedData.signerInfos missing".into()))?; let signer_infos_set = signer_infos_obj .as_set() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; let mut signer_infos: Vec = Vec::with_capacity(signer_infos_set.len()); for si in signer_infos_set { signer_infos.push(parse_signer_info_parse(si)?); } Ok(SignedDataParsed { version, digest_algorithms, encap_content_info, certificates, crls_present, signer_infos, }) } fn parse_encapsulated_content_info_parse( obj: &DerObject<'_>, ) -> Result { let seq = obj .as_sequence() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; if seq.is_empty() || seq.len() > 2 { return Err(SignedObjectParseError::Parse( "EncapsulatedContentInfo must be SEQUENCE of 1..2".into(), )); } let econtent_type = oid_to_string_parse(&seq[0])?; let econtent = match seq.get(1) { None => None, Some(econtent_tagged) => { if econtent_tagged.class() != Class::ContextSpecific || econtent_tagged.tag() != Tag(0) { return Err(SignedObjectParseError::Parse( "EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into(), )); } let inner_der = econtent_tagged .as_slice() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; let (rem, inner_obj) = parse_der(inner_der).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; if !rem.is_empty() { return Err(SignedObjectParseError::Parse( "trailing bytes inside EncapsulatedContentInfo.eContent".into(), )); } Some( inner_obj .as_slice() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))? .to_vec(), ) } }; Ok(EncapsulatedContentInfoParsed { econtent_type, econtent, }) } fn parse_certificate_set_implicit_parse( obj: &DerObject<'_>, ) -> Result>, SignedObjectParseError> { let content = obj .as_slice() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; let mut input = content; let mut certs = Vec::new(); while !input.is_empty() { let (rem, _any_obj) = parse_der(input).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; let consumed = input.len() - rem.len(); certs.push(input[..consumed].to_vec()); input = rem; } Ok(certs) } fn validate_ee_certificate(der: &[u8]) -> Result { let rc = match ResourceCertificate::from_der(der) { Ok(v) => v, Err(e) => { return match e { crate::data_model::rc::ResourceCertificateDecodeError::Validate( crate::data_model::rc::ResourceCertificateProfileError::SignedObjectSiaNotUri, ) => Err(SignedObjectValidateError::EeCertificateSignedObjectSiaNotUri), crate::data_model::rc::ResourceCertificateDecodeError::Validate( crate::data_model::rc::ResourceCertificateProfileError::SignedObjectSiaNoRsync, ) => Err(SignedObjectValidateError::EeCertificateSignedObjectSiaNoRsync), _ => Err(SignedObjectValidateError::EeCertificateParse(e.to_string())), }; } }; let ski = rc .tbs .extensions .subject_key_identifier .clone() .ok_or(SignedObjectValidateError::EeCertificateMissingSki)?; let spki_der = rc.tbs.subject_public_key_info.clone(); let sia = rc .tbs .extensions .subject_info_access .as_ref() .ok_or(SignedObjectValidateError::EeCertificateMissingSia)?; let signed_object_uris: Vec = match sia { SubjectInfoAccess::Ee(ee) => ee .signed_object_uris .iter() .map(|u| u.as_str().to_string()) .collect(), SubjectInfoAccess::Ca(_ca) => Vec::new(), }; if signed_object_uris.is_empty() { return Err(SignedObjectValidateError::EeCertificateMissingSignedObjectSia); } if !signed_object_uris.iter().any(|u| u.starts_with("rsync://")) { return Err(SignedObjectValidateError::EeCertificateSignedObjectSiaNoRsync); } Ok(ResourceEeCertificate { raw_der: der.to_vec(), subject_key_identifier: ski, spki_der, sia_signed_object_uris: signed_object_uris, resource_cert: rc, }) } fn parse_signer_info_parse( obj: &DerObject<'_>, ) -> Result { let seq = obj .as_sequence() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; if seq.len() < 5 || seq.len() > 7 { return Err(SignedObjectParseError::Parse( "SignerInfo must be a SEQUENCE of 5..7 elements".into(), )); } let version = seq[0] .as_u64() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; let sid = &seq[1]; let sid = if sid.class() == Class::ContextSpecific && sid.tag() == Tag(0) { let ski = sid .as_slice() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))? .to_vec(); SignerIdentifierParsed::SubjectKeyIdentifier(ski) } else { SignerIdentifierParsed::Other }; let (digest_oid, digest_params_ok) = parse_algorithm_identifier_parse(&seq[2])?; let digest_algorithm = AlgorithmIdentifierParsed { oid: digest_oid, params_ok: digest_params_ok, }; let mut idx = 3; let mut signed_attrs_content: Option> = None; let mut signed_attrs_der_for_signature: Option> = None; if seq[idx].class() == Class::ContextSpecific && seq[idx].tag() == Tag(0) { let signed_attrs_obj = &seq[idx]; let content = signed_attrs_obj .as_slice() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))? .to_vec(); signed_attrs_content = Some(content); signed_attrs_der_for_signature = Some(make_signed_attrs_der_for_signature_parse(signed_attrs_obj)?); idx += 1; } let (signature_oid, signature_params_ok) = parse_algorithm_identifier_parse(&seq[idx])?; let signature_algorithm = AlgorithmIdentifierParsed { oid: signature_oid, params_ok: signature_params_ok, }; idx += 1; let signature = seq[idx] .as_slice() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))? .to_vec(); idx += 1; let unsigned_attrs_present = seq.get(idx).is_some(); Ok(SignerInfoParsed { version, sid, digest_algorithm, signature_algorithm, signed_attrs_content, unsigned_attrs_present, signature, signed_attrs_der_for_signature, }) } fn validate_signed_data_profile( signed_data: SignedDataParsed, ) -> Result { if signed_data.version != 3 { return Err(SignedObjectValidateError::InvalidSignedDataVersion( signed_data.version, )); } if signed_data.digest_algorithms.len() != 1 { return Err(SignedObjectValidateError::InvalidDigestAlgorithmsCount( signed_data.digest_algorithms.len(), )); } let digest_alg = &signed_data.digest_algorithms[0]; if digest_alg.oid != OID_SHA256 { return Err(SignedObjectValidateError::InvalidDigestAlgorithm( digest_alg.oid.clone(), )); } if signed_data.crls_present { return Err(SignedObjectValidateError::CrlsPresent); } let econtent = signed_data .encap_content_info .econtent .clone() .ok_or(SignedObjectValidateError::EContentMissing)?; if econtent.is_empty() { return Err(SignedObjectValidateError::EContentMissing); } let encap_content_info = EncapsulatedContentInfo { econtent_type: signed_data.encap_content_info.econtent_type.clone(), econtent: econtent.clone(), }; let certs = signed_data .certificates .as_ref() .ok_or(SignedObjectValidateError::CertificatesMissing)?; if certs.len() != 1 { return Err(SignedObjectValidateError::InvalidCertificatesCount( certs.len(), )); } let ee = validate_ee_certificate(&certs[0])?; if signed_data.signer_infos.len() != 1 { return Err(SignedObjectValidateError::InvalidSignerInfosCount( signed_data.signer_infos.len(), )); } let signer = &signed_data.signer_infos[0]; if signer.version != 3 { return Err(SignedObjectValidateError::InvalidSignerInfoVersion( signer.version, )); } let sid_ski = match &signer.sid { SignerIdentifierParsed::SubjectKeyIdentifier(ski) => ski.clone(), SignerIdentifierParsed::Other => { return Err(SignedObjectValidateError::InvalidSignerIdentifier); } }; if signer.digest_algorithm.oid != OID_SHA256 { return Err(SignedObjectValidateError::InvalidSignerInfoDigestAlgorithm( signer.digest_algorithm.oid.clone(), )); } let signed_attrs_content = signer .signed_attrs_content .as_deref() .ok_or(SignedObjectValidateError::SignedAttrsMissing)?; let signed_attrs_der_for_signature = signer .signed_attrs_der_for_signature .clone() .ok_or(SignedObjectValidateError::SignedAttrsMissing)?; let signed_attrs = parse_signed_attrs_implicit(signed_attrs_content)?; if signer.unsigned_attrs_present { return Err(SignedObjectValidateError::UnsignedAttrsPresent); } if !signer.signature_algorithm.params_ok { return Err(SignedObjectValidateError::InvalidSignatureAlgorithmParameters); } let signature_algorithm = signer.signature_algorithm.oid.clone(); if signature_algorithm != OID_RSA_ENCRYPTION && signature_algorithm != OID_SHA256_WITH_RSA_ENCRYPTION { return Err(SignedObjectValidateError::InvalidSignatureAlgorithm( signature_algorithm, )); } if sid_ski != ee.subject_key_identifier { return Err(SignedObjectValidateError::SidSkiMismatch); } if signed_attrs.content_type != encap_content_info.econtent_type { return Err(SignedObjectValidateError::ContentTypeAttrMismatch { econtent_type: encap_content_info.econtent_type.clone(), attr_content_type: signed_attrs.content_type.clone(), }); } let computed = digest::digest(&digest::SHA256, &encap_content_info.econtent); if computed.as_ref() != signed_attrs.message_digest.as_slice() { return Err(SignedObjectValidateError::MessageDigestMismatch); } Ok(SignedDataProfiled { version: 3, digest_algorithms: vec![OID_SHA256.to_string()], encap_content_info, certificates: vec![ee.clone()], crls_present: false, signer_infos: vec![SignerInfoProfiled { version: 3, sid_ski, digest_algorithm: OID_SHA256.to_string(), signature_algorithm: signer.signature_algorithm.oid.clone(), signed_attrs, unsigned_attrs_present: false, signature: signer.signature.clone(), signed_attrs_der_for_signature, }], }) } fn parse_signed_attrs_implicit( input: &[u8], ) -> Result { let mut content_type: Option = None; let mut message_digest: Option> = None; let mut signing_time: Option = None; let mut remaining = input; while !remaining.is_empty() { let (rem, attr_obj) = parse_der(remaining) .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; remaining = rem; let attr_seq = attr_obj .as_sequence() .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; if attr_seq.len() != 2 { return Err(SignedObjectValidateError::SignedAttrsParse( "Attribute must be SEQUENCE of 2".into(), )); } let oid = oid_to_string_parse(&attr_seq[0]) .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; let values_set = attr_seq[1] .as_set() .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; if values_set.len() != 1 { return Err( SignedObjectValidateError::InvalidSignedAttributeValuesCount { oid, count: values_set.len(), }, ); } match oid.as_str() { OID_CMS_ATTR_CONTENT_TYPE => { if content_type.is_some() { return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid)); } let v = oid_to_string_parse(&values_set[0]) .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?; content_type = Some(v); } OID_CMS_ATTR_MESSAGE_DIGEST => { if message_digest.is_some() { return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid)); } let v = values_set[0] .as_slice() .map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))? .to_vec(); message_digest = Some(v); } OID_CMS_ATTR_SIGNING_TIME => { if signing_time.is_some() { return Err(SignedObjectValidateError::DuplicateSignedAttribute(oid)); } signing_time = Some(parse_signing_time_value(&values_set[0])?); } _ => { return Err(SignedObjectValidateError::UnsupportedSignedAttribute(oid)); } } } Ok(SignedAttrsProfiled { content_type: content_type .ok_or(SignedObjectValidateError::SignedAttrsContentTypeMissing)?, message_digest: message_digest .ok_or(SignedObjectValidateError::SignedAttrsMessageDigestMissing)?, signing_time: signing_time .ok_or(SignedObjectValidateError::SignedAttrsSigningTimeMissing)?, other_attrs_present: false, }) } fn parse_signing_time_value(obj: &DerObject<'_>) -> Result { match &obj.content { der_parser::ber::BerObjectContent::UTCTime(dt) => Ok(Asn1TimeUtc { utc: dt .to_datetime() .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?, encoding: Asn1TimeEncoding::UtcTime, }), der_parser::ber::BerObjectContent::GeneralizedTime(dt) => Ok(Asn1TimeUtc { utc: dt .to_datetime() .map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?, encoding: Asn1TimeEncoding::GeneralizedTime, }), _ => Err(SignedObjectValidateError::InvalidSigningTimeValue), } } fn make_signed_attrs_der_for_signature_parse( obj: &DerObject<'_>, ) -> Result, SignedObjectParseError> { // We need the DER encoding of SignedAttributes (SET OF Attribute) as signature input. // The SignedAttributes field in SignerInfo is `[0] IMPLICIT`, so the on-wire bytes start with // a context-specific constructed tag (0xA0 for tag 0). For signature verification, this tag // is replaced with the universal SET tag (0x31), leaving length+content unchanged. // let mut cs_der = obj .to_vec() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; if cs_der.is_empty() { return Err(SignedObjectParseError::Parse( "signedAttrs encoding is empty".into(), )); } // The first byte should be the context-specific tag (0xA0) for [0] constructed. // Replace it with universal SET (0x31) for signature input. cs_der[0] = 0x31; Ok(cs_der) } fn oid_to_string_parse(obj: &DerObject<'_>) -> Result { let oid = obj .as_oid() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; Ok(oid.to_id_string()) } fn parse_algorithm_identifier_parse( obj: &DerObject<'_>, ) -> Result<(String, bool), SignedObjectParseError> { let seq = obj .as_sequence() .map_err(|e| SignedObjectParseError::Parse(e.to_string()))?; if seq.is_empty() || seq.len() > 2 { return Err(SignedObjectParseError::Parse( "AlgorithmIdentifier must be SEQUENCE of 1..2".into(), )); } let oid = oid_to_string_parse(&seq[0])?; let params_ok = match seq.get(1) { None => true, Some(p) => matches!(p.content, der_parser::ber::BerObjectContent::Null), }; Ok((oid, params_ok)) } fn strip_leading_zeros(bytes: &[u8]) -> &[u8] { let mut idx = 0; while idx < bytes.len() && bytes[idx] == 0 { idx += 1; } if idx == bytes.len() { &bytes[bytes.len() - 1..] } else { &bytes[idx..] } }