rpki/src/data_model/signed_object.rs

993 lines
34 KiB
Rust

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<u8>,
pub subject_key_identifier: Vec<u8>,
pub spki_der: Vec<u8>,
pub sia_signed_object_uris: Vec<String>,
pub resource_cert: ResourceCertificate,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RpkiSignedObject {
pub raw_der: Vec<u8>,
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<String>,
pub encap_content_info: EncapsulatedContentInfo,
pub certificates: Vec<ResourceEeCertificate>,
pub crls_present: bool,
pub signer_infos: Vec<SignerInfoProfiled>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EncapsulatedContentInfo {
pub econtent_type: String,
pub econtent: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignerInfoProfiled {
pub version: u32,
pub sid_ski: Vec<u8>,
pub digest_algorithm: String,
pub signature_algorithm: String,
pub signed_attrs: SignedAttrsProfiled,
pub unsigned_attrs_present: bool,
pub signature: Vec<u8>,
pub signed_attrs_der_for_signature: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignedAttrsProfiled {
pub content_type: String,
pub message_digest: Vec<u8>,
pub signing_time: Asn1TimeUtc,
pub other_attrs_present: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RpkiSignedObjectParsed {
pub raw_der: Vec<u8>,
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<AlgorithmIdentifierParsed>,
pub encap_content_info: EncapsulatedContentInfoParsed,
pub certificates: Option<Vec<Vec<u8>>>,
pub crls_present: bool,
pub signer_infos: Vec<SignerInfoParsed>,
}
#[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<Vec<u8>>,
}
#[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<Vec<u8>>,
pub signed_attrs_der_for_signature: Option<Vec<u8>>,
pub unsigned_attrs_present: bool,
pub signature: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SignerIdentifierParsed {
SubjectKeyIdentifier(Vec<u8>),
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<RpkiSignedObjectParsed, SignedObjectParseError> {
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<Self, SignedObjectDecodeError> {
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<RpkiSignedObject, SignedObjectValidateError> {
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<SignedDataParsed, SignedObjectParseError> {
// 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<SignedDataParsed, SignedObjectParseError> {
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<AlgorithmIdentifierParsed> =
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<Vec<Vec<u8>>> = 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<SignerInfoParsed> = 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<EncapsulatedContentInfoParsed, 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(
"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<Vec<Vec<u8>>, 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<ResourceEeCertificate, SignedObjectValidateError> {
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<String> = 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<SignerInfoParsed, SignedObjectParseError> {
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<Vec<u8>> = None;
let mut signed_attrs_der_for_signature: Option<Vec<u8>> = 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<SignedDataProfiled, SignedObjectValidateError> {
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<SignedAttrsProfiled, SignedObjectValidateError> {
let mut content_type: Option<String> = None;
let mut message_digest: Option<Vec<u8>> = None;
let mut signing_time: Option<Asn1TimeUtc> = 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<Asn1TimeUtc, SignedObjectValidateError> {
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<Vec<u8>, 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<String, SignedObjectParseError> {
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..]
}
}