993 lines
34 KiB
Rust
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..]
|
|
}
|
|
}
|