20260510 增加strict策略模式并隔离多TA失败
This commit is contained in:
@@ -2,7 +2,8 @@ use crate::data_model::common::{DerReader, der_take_tlv};
|
||||
use crate::data_model::oid::OID_CT_ASPA;
|
||||
use crate::data_model::rc::ResourceCertificate;
|
||||
use crate::data_model::signed_object::{
|
||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError,
|
||||
SignedObjectValidateError,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -91,6 +92,15 @@ pub enum AspaProfileError {
|
||||
ProvidersContainCustomer(u32),
|
||||
}
|
||||
|
||||
impl From<SignedObjectDecodeError> for AspaProfileError {
|
||||
fn from(value: SignedObjectDecodeError) -> Self {
|
||||
match value {
|
||||
SignedObjectDecodeError::Parse(e) => AspaProfileError::ProfileDecode(e.to_string()),
|
||||
SignedObjectDecodeError::Validate(e) => AspaProfileError::SignedObject(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AspaDecodeError {
|
||||
#[error("{0}")]
|
||||
@@ -173,6 +183,17 @@ impl AspaObject {
|
||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||
}
|
||||
|
||||
pub fn decode_der_with_strict_options(
|
||||
der: &[u8],
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
) -> Result<Self, AspaDecodeError> {
|
||||
let signed_object =
|
||||
RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name)
|
||||
.map_err(AspaProfileError::from)?;
|
||||
Self::from_signed_object(signed_object)
|
||||
}
|
||||
|
||||
pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result<Self, AspaDecodeError> {
|
||||
let econtent_type = signed_object
|
||||
.signed_data
|
||||
|
||||
@@ -3,7 +3,8 @@ use crate::data_model::common::{BigUnsigned, UtcTime};
|
||||
use crate::data_model::oid::{OID_CT_RPKI_MANIFEST, OID_SHA256};
|
||||
use crate::data_model::rc::ResourceCertificate;
|
||||
use crate::data_model::signed_object::{
|
||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError,
|
||||
SignedObjectValidateError,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -119,6 +120,15 @@ pub enum ManifestProfileError {
|
||||
InvalidHashLength(usize),
|
||||
}
|
||||
|
||||
impl From<SignedObjectDecodeError> for ManifestProfileError {
|
||||
fn from(value: SignedObjectDecodeError) -> Self {
|
||||
match value {
|
||||
SignedObjectDecodeError::Parse(e) => ManifestProfileError::ProfileDecode(e.to_string()),
|
||||
SignedObjectDecodeError::Validate(e) => ManifestProfileError::SignedObject(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ManifestDecodeError {
|
||||
#[error("{0}")]
|
||||
@@ -186,6 +196,17 @@ impl ManifestObject {
|
||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||
}
|
||||
|
||||
pub fn decode_der_with_strict_options(
|
||||
der: &[u8],
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
) -> Result<Self, ManifestDecodeError> {
|
||||
let signed_object =
|
||||
RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name)
|
||||
.map_err(ManifestProfileError::from)?;
|
||||
Self::from_signed_object(signed_object)
|
||||
}
|
||||
|
||||
pub fn from_signed_object(
|
||||
signed_object: RpkiSignedObject,
|
||||
) -> Result<Self, ManifestDecodeError> {
|
||||
|
||||
@@ -8,7 +8,7 @@ use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version}
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::data_model::common::{
|
||||
Asn1TimeUtc, InvalidTimeEncodingError, UtcTime, X509NameDer, asn1_time_to_model,
|
||||
Asn1TimeUtc, DerReader, InvalidTimeEncodingError, UtcTime, X509NameDer, asn1_time_to_model,
|
||||
};
|
||||
use crate::data_model::oid::{
|
||||
OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
|
||||
@@ -389,6 +389,11 @@ pub enum ResourceCertificateProfileError {
|
||||
#[error("invalid signature algorithm parameters (RFC 5280 §4.1.1.2)")]
|
||||
InvalidSignatureAlgorithmParameters,
|
||||
|
||||
#[error(
|
||||
"{role} Name strict validation failed: {detail} (RFC 6487 §4.4; RFC 5280 §4.1.2.4/§4.1.2.6)"
|
||||
)]
|
||||
StrictName { role: &'static str, detail: String },
|
||||
|
||||
#[error("duplicate extension: {0} (RFC 5280 §4.2; RFC 6487 §4.8)")]
|
||||
DuplicateExtension(&'static str),
|
||||
|
||||
@@ -562,17 +567,181 @@ impl ResourceCertificate {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_strict_name_profile(&self) -> Result<(), ResourceCertificateProfileError> {
|
||||
validate_strict_rpki_name(&self.tbs.issuer_name, "issuer")?;
|
||||
validate_strict_rpki_name(&self.tbs.subject_name, "subject")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decode a resource certificate (`parse + validate`).
|
||||
pub fn decode_der(der: &[u8]) -> Result<Self, ResourceCertificateDecodeError> {
|
||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||
}
|
||||
|
||||
pub fn decode_der_with_strict_name(der: &[u8]) -> Result<Self, ResourceCertificateDecodeError> {
|
||||
let cert = Self::decode_der(der)?;
|
||||
cert.validate_strict_name_profile()?;
|
||||
Ok(cert)
|
||||
}
|
||||
|
||||
/// Backwards-compatible helper (historical name).
|
||||
pub fn from_der(der: &[u8]) -> Result<Self, ResourceCertificateError> {
|
||||
Self::decode_der(der)
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_strict_rpki_name(
|
||||
name: &X509NameDer,
|
||||
role: &'static str,
|
||||
) -> Result<(), ResourceCertificateProfileError> {
|
||||
let mut name_seq = DerReader::new(name.as_raw())
|
||||
.take_sequence()
|
||||
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||
|
||||
let mut common_name_count = 0usize;
|
||||
let mut serial_number_count = 0usize;
|
||||
|
||||
while !name_seq.is_empty() {
|
||||
let set_bytes = name_seq
|
||||
.take_tag(0x31)
|
||||
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||
let mut rdn_set = DerReader::new(set_bytes);
|
||||
if rdn_set.is_empty() {
|
||||
return Err(ResourceCertificateProfileError::StrictName {
|
||||
role,
|
||||
detail: "RelativeDistinguishedName SET is empty".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
while !rdn_set.is_empty() {
|
||||
let mut attr = rdn_set
|
||||
.take_sequence()
|
||||
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||
let oid = attr
|
||||
.take_tag(0x06)
|
||||
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||
let (value_tag, _value) = attr
|
||||
.take_any()
|
||||
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||
if !attr.is_empty() {
|
||||
return Err(ResourceCertificateProfileError::StrictName {
|
||||
role,
|
||||
detail: "AttributeTypeAndValue must be SEQUENCE of 2".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
match oid {
|
||||
// 2.5.4.3 commonName
|
||||
&[0x55, 0x04, 0x03] => {
|
||||
common_name_count += 1;
|
||||
if value_tag != 0x13 {
|
||||
return Err(ResourceCertificateProfileError::StrictName {
|
||||
role,
|
||||
detail: format!(
|
||||
"commonName must be PrintableString, got tag 0x{value_tag:02X}"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
// 2.5.4.5 serialNumber
|
||||
&[0x55, 0x04, 0x05] => {
|
||||
serial_number_count += 1;
|
||||
if value_tag != 0x13 {
|
||||
return Err(ResourceCertificateProfileError::StrictName {
|
||||
role,
|
||||
detail: format!(
|
||||
"serialNumber must be PrintableString, got tag 0x{value_tag:02X}"
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if common_name_count != 1 {
|
||||
return Err(ResourceCertificateProfileError::StrictName {
|
||||
role,
|
||||
detail: format!("commonName must appear exactly once, got {common_name_count}"),
|
||||
});
|
||||
}
|
||||
if serial_number_count > 1 {
|
||||
return Err(ResourceCertificateProfileError::StrictName {
|
||||
role,
|
||||
detail: format!("serialNumber must appear at most once, got {serial_number_count}"),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod strict_name_tests {
|
||||
use super::*;
|
||||
|
||||
fn name_with_attrs(attrs: &[(&[u8], u8, &[u8])]) -> X509NameDer {
|
||||
let mut rdns = Vec::new();
|
||||
for (oid, tag, value) in attrs {
|
||||
let mut attr = Vec::new();
|
||||
attr.extend(der_tlv(0x06, oid));
|
||||
attr.extend(der_tlv(*tag, value));
|
||||
let attr = der_tlv(0x30, &attr);
|
||||
let rdn = der_tlv(0x31, &attr);
|
||||
rdns.extend(rdn);
|
||||
}
|
||||
X509NameDer(der_tlv(0x30, &rdns))
|
||||
}
|
||||
|
||||
fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
|
||||
let mut out = vec![tag];
|
||||
encode_len(value.len(), &mut out);
|
||||
out.extend_from_slice(value);
|
||||
out
|
||||
}
|
||||
|
||||
fn encode_len(len: usize, out: &mut Vec<u8>) {
|
||||
if len < 0x80 {
|
||||
out.push(len as u8);
|
||||
return;
|
||||
}
|
||||
let mut bytes = Vec::new();
|
||||
let mut value = len;
|
||||
while value > 0 {
|
||||
bytes.push((value & 0xFF) as u8);
|
||||
value >>= 8;
|
||||
}
|
||||
bytes.reverse();
|
||||
out.push(0x80 | bytes.len() as u8);
|
||||
out.extend(bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_name_accepts_printable_common_name_and_serial_number() {
|
||||
let name = name_with_attrs(&[
|
||||
(&[0x55, 0x04, 0x03], 0x13, b"CN1"),
|
||||
(&[0x55, 0x04, 0x05], 0x13, b"SN1"),
|
||||
]);
|
||||
validate_strict_rpki_name(&name, "subject").expect("strict name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_name_rejects_utf8_common_name() {
|
||||
let name = name_with_attrs(&[(&[0x55, 0x04, 0x03], 0x0C, b"CN1")]);
|
||||
let err = validate_strict_rpki_name(&name, "subject").expect_err("strict name fails");
|
||||
assert!(err.to_string().contains("PrintableString"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strict_name_rejects_duplicate_common_name() {
|
||||
let name = name_with_attrs(&[
|
||||
(&[0x55, 0x04, 0x03], 0x13, b"CN1"),
|
||||
(&[0x55, 0x04, 0x03], 0x13, b"CN2"),
|
||||
]);
|
||||
let err = validate_strict_rpki_name(&name, "subject").expect_err("strict name fails");
|
||||
assert!(err.to_string().contains("exactly once"), "{err}");
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceCertificateParsed {
|
||||
pub fn validate_profile(self) -> Result<ResourceCertificate, ResourceCertificateProfileError> {
|
||||
let version = match self.version {
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::data_model::common::{DerReader, der_take_tlv};
|
||||
use crate::data_model::oid::OID_CT_ROUTE_ORIGIN_AUTHZ;
|
||||
use crate::data_model::rc::{Afi as RcAfi, IpPrefix as RcIpPrefix, ResourceCertificate};
|
||||
use crate::data_model::signed_object::{
|
||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError,
|
||||
SignedObjectValidateError,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -106,6 +107,15 @@ pub enum RoaProfileError {
|
||||
},
|
||||
}
|
||||
|
||||
impl From<SignedObjectDecodeError> for RoaProfileError {
|
||||
fn from(value: SignedObjectDecodeError) -> Self {
|
||||
match value {
|
||||
SignedObjectDecodeError::Parse(e) => RoaProfileError::ProfileDecode(e.to_string()),
|
||||
SignedObjectDecodeError::Validate(e) => RoaProfileError::SignedObject(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum RoaDecodeError {
|
||||
#[error("{0}")]
|
||||
@@ -171,6 +181,17 @@ impl RoaObject {
|
||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||
}
|
||||
|
||||
pub fn decode_der_with_strict_options(
|
||||
der: &[u8],
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
) -> Result<Self, RoaDecodeError> {
|
||||
let signed_object =
|
||||
RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name)
|
||||
.map_err(RoaProfileError::from)?;
|
||||
Self::from_signed_object(signed_object)
|
||||
}
|
||||
|
||||
pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result<Self, RoaDecodeError> {
|
||||
let econtent_type = signed_object
|
||||
.signed_data
|
||||
|
||||
@@ -9,10 +9,10 @@ use crate::data_model::oid::{
|
||||
OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS,
|
||||
};
|
||||
use crate::data_model::rc::{ResourceCertificate, SubjectInfoAccess};
|
||||
use asn1_rs::{Any, Class, FromBer, Header, Tag};
|
||||
use asn1_rs::{Any, Class, FromBer, FromDer as Asn1FromDer, Header, Tag};
|
||||
use ring::digest;
|
||||
use x509_parser::extensions::ParsedExtension;
|
||||
use x509_parser::prelude::{FromDer, X509Certificate};
|
||||
use x509_parser::prelude::X509Certificate;
|
||||
use x509_parser::public_key::PublicKey;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
|
||||
@@ -321,7 +321,13 @@ impl RpkiSignedObject {
|
||||
/// This performs encoding/structure parsing only. Profile constraints are enforced by
|
||||
/// `RpkiSignedObjectParsed::validate_profile`.
|
||||
pub fn parse_der(der: &[u8]) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
||||
parse_signed_object_content_info(der, der)
|
||||
parse_signed_object_content_info(der, der, CmsParseMode::BerCompatible)
|
||||
}
|
||||
|
||||
pub fn parse_der_strict_cms(
|
||||
der: &[u8],
|
||||
) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
||||
parse_signed_object_content_info(der, der, CmsParseMode::DerStrict)
|
||||
}
|
||||
|
||||
/// Decode a DER-encoded RPKI Signed Object (CMS ContentInfo wrapping SignedData) and enforce
|
||||
@@ -331,6 +337,19 @@ impl RpkiSignedObject {
|
||||
Ok(parsed.validate_profile()?)
|
||||
}
|
||||
|
||||
pub fn decode_der_with_strict_options(
|
||||
der: &[u8],
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
) -> Result<Self, SignedObjectDecodeError> {
|
||||
let parsed = if strict_cms_der {
|
||||
Self::parse_der_strict_cms(der)?
|
||||
} else {
|
||||
Self::parse_der(der)?
|
||||
};
|
||||
Ok(parsed.validate_profile_with_strict_name(strict_name)?)
|
||||
}
|
||||
|
||||
/// Scheme-A naming for signature verification.
|
||||
pub fn verify(&self) -> Result<(), SignedObjectVerifyError> {
|
||||
self.verify_signature()
|
||||
@@ -399,13 +418,20 @@ impl RpkiSignedObject {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum CmsParseMode {
|
||||
BerCompatible,
|
||||
DerStrict,
|
||||
}
|
||||
|
||||
struct CmsReader<'a> {
|
||||
buf: &'a [u8],
|
||||
mode: CmsParseMode,
|
||||
}
|
||||
|
||||
impl<'a> CmsReader<'a> {
|
||||
fn new(buf: &'a [u8]) -> Self {
|
||||
Self { buf }
|
||||
fn new(buf: &'a [u8], mode: CmsParseMode) -> Self {
|
||||
Self { buf, mode }
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
@@ -417,19 +443,19 @@ impl<'a> CmsReader<'a> {
|
||||
}
|
||||
|
||||
fn peek_tag(&self) -> Result<u8, String> {
|
||||
let (_rem, any) = Any::from_ber(self.buf).map_err(|e| format!("BER parse error: {e}"))?;
|
||||
let (_rem, any) = parse_any(self.buf, self.mode)?;
|
||||
header_to_single_byte_tag(&any.header)
|
||||
}
|
||||
|
||||
fn take_any(&mut self) -> Result<(u8, &'a [u8]), String> {
|
||||
let (rem, any) = Any::from_ber(self.buf).map_err(|e| format!("BER parse error: {e}"))?;
|
||||
let (rem, any) = parse_any(self.buf, self.mode)?;
|
||||
let tag = header_to_single_byte_tag(&any.header)?;
|
||||
self.buf = rem;
|
||||
Ok((tag, any.data))
|
||||
}
|
||||
|
||||
fn take_any_full(&mut self) -> Result<(u8, &'a [u8], &'a [u8]), String> {
|
||||
let (rem, any) = Any::from_ber(self.buf).map_err(|e| format!("BER parse error: {e}"))?;
|
||||
let (rem, any) = parse_any(self.buf, self.mode)?;
|
||||
let consumed = self.buf.len() - rem.len();
|
||||
let full = &self.buf[..consumed];
|
||||
let tag = header_to_single_byte_tag(&any.header)?;
|
||||
@@ -454,16 +480,21 @@ impl<'a> CmsReader<'a> {
|
||||
|
||||
fn take_sequence(&mut self) -> Result<CmsReader<'a>, String> {
|
||||
let value = self.take_tag(0x30)?;
|
||||
Ok(CmsReader::new(value))
|
||||
Ok(CmsReader::new(value, self.mode))
|
||||
}
|
||||
|
||||
fn take_octet_string(&mut self) -> Result<Vec<u8>, String> {
|
||||
let (rem, any) = Any::from_ber(self.buf).map_err(|e| format!("BER parse error: {e}"))?;
|
||||
let (rem, any) = parse_any(self.buf, self.mode)?;
|
||||
let tag = header_to_single_byte_tag(&any.header)?;
|
||||
if self.mode == CmsParseMode::DerStrict && tag != 0x04 {
|
||||
return Err(format!(
|
||||
"unexpected tag in DER strict mode: got 0x{tag:02X}, expected 0x04"
|
||||
));
|
||||
}
|
||||
if tag != 0x04 && tag != 0x24 {
|
||||
return Err(format!("unexpected tag: got 0x{tag:02X}, expected 0x04"));
|
||||
}
|
||||
let octets = flatten_octet_string(any)?;
|
||||
let octets = flatten_octet_string(any, self.mode)?;
|
||||
self.buf = rem;
|
||||
Ok(octets)
|
||||
}
|
||||
@@ -475,7 +506,7 @@ impl<'a> CmsReader<'a> {
|
||||
|
||||
fn take_explicit(&mut self, expected_outer_tag: u8) -> Result<(u8, &'a [u8]), String> {
|
||||
let inner_der = self.take_tag(expected_outer_tag)?;
|
||||
let (tag, value, rem) = cms_take_tlv(inner_der)?;
|
||||
let (tag, value, rem) = cms_take_tlv(inner_der, self.mode)?;
|
||||
if !rem.is_empty() {
|
||||
return Err("trailing bytes inside EXPLICIT value".into());
|
||||
}
|
||||
@@ -484,7 +515,7 @@ impl<'a> CmsReader<'a> {
|
||||
|
||||
fn take_explicit_der(&mut self, expected_outer_tag: u8) -> Result<&'a [u8], String> {
|
||||
let inner_der = self.take_tag(expected_outer_tag)?;
|
||||
let (_tag, _value, rem) = cms_take_tlv(inner_der)?;
|
||||
let (_tag, _value, rem) = cms_take_tlv(inner_der, self.mode)?;
|
||||
if !rem.is_empty() {
|
||||
return Err("trailing bytes inside EXPLICIT value".into());
|
||||
}
|
||||
@@ -495,8 +526,9 @@ impl<'a> CmsReader<'a> {
|
||||
fn parse_signed_object_content_info(
|
||||
raw_der: &[u8],
|
||||
parse_der: &[u8],
|
||||
mode: CmsParseMode,
|
||||
) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
||||
let mut r = CmsReader::new(parse_der);
|
||||
let mut r = CmsReader::new(parse_der, mode);
|
||||
let mut content_info_seq = r
|
||||
.take_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
@@ -519,6 +551,17 @@ fn parse_signed_object_content_info(
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_any<'a>(input: &'a [u8], mode: CmsParseMode) -> Result<(&'a [u8], Any<'a>), String> {
|
||||
match mode {
|
||||
CmsParseMode::BerCompatible => {
|
||||
Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))
|
||||
}
|
||||
CmsParseMode::DerStrict => {
|
||||
Any::from_der(input).map_err(|e| format!("DER parse error: {e}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn header_to_single_byte_tag(header: &Header<'_>) -> Result<u8, String> {
|
||||
let tag_no = header.tag().0;
|
||||
if tag_no > 30 {
|
||||
@@ -529,24 +572,27 @@ fn header_to_single_byte_tag(header: &Header<'_>) -> Result<u8, String> {
|
||||
| tag_no as u8)
|
||||
}
|
||||
|
||||
fn cms_take_tlv(input: &[u8]) -> Result<(u8, &[u8], &[u8]), String> {
|
||||
let (rem, any) = Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))?;
|
||||
fn cms_take_tlv(input: &[u8], mode: CmsParseMode) -> Result<(u8, &[u8], &[u8]), String> {
|
||||
let (rem, any) = parse_any(input, mode)?;
|
||||
let tag = header_to_single_byte_tag(&any.header)?;
|
||||
Ok((tag, any.data, rem))
|
||||
}
|
||||
|
||||
fn flatten_octet_string(any: Any<'_>) -> Result<Vec<u8>, String> {
|
||||
fn flatten_octet_string(any: Any<'_>, mode: CmsParseMode) -> Result<Vec<u8>, String> {
|
||||
if any.class() != Class::Universal || any.tag() != Tag::OctetString {
|
||||
return Err("expected OCTET STRING".into());
|
||||
}
|
||||
if !any.header.constructed() {
|
||||
return Ok(any.data.to_vec());
|
||||
}
|
||||
if mode == CmsParseMode::DerStrict {
|
||||
return Err("constructed OCTET STRING is not allowed in DER strict mode".into());
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
let mut input = any.data;
|
||||
while !input.is_empty() {
|
||||
let (rem, child) = Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))?;
|
||||
out.extend(flatten_octet_string(child)?);
|
||||
out.extend(flatten_octet_string(child, mode)?);
|
||||
input = rem;
|
||||
}
|
||||
Ok(out)
|
||||
@@ -554,13 +600,20 @@ fn flatten_octet_string(any: Any<'_>) -> Result<Vec<u8>, String> {
|
||||
|
||||
impl RpkiSignedObjectParsed {
|
||||
pub fn validate_profile(self) -> Result<RpkiSignedObject, SignedObjectValidateError> {
|
||||
self.validate_profile_with_strict_name(false)
|
||||
}
|
||||
|
||||
pub fn validate_profile_with_strict_name(
|
||||
self,
|
||||
strict_name: bool,
|
||||
) -> 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)?;
|
||||
let signed_data = validate_signed_data_profile(self.signed_data, strict_name)?;
|
||||
|
||||
Ok(RpkiSignedObject {
|
||||
raw_der: self.raw_der,
|
||||
@@ -576,7 +629,7 @@ fn parse_signed_data_from_contentinfo_cursor(
|
||||
let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
|
||||
SignedObjectParseError::Parse("ContentInfo.content must be [0] EXPLICIT".into())
|
||||
})?;
|
||||
let mut r = CmsReader::new(inner_der);
|
||||
let mut r = CmsReader::new(inner_der, seq.mode);
|
||||
let signed_data_seq = r
|
||||
.take_sequence()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
@@ -598,7 +651,7 @@ fn parse_signed_data_cursor(
|
||||
let digest_set_bytes = seq
|
||||
.take_tag(0x31)
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let mut digest_set = CmsReader::new(digest_set_bytes);
|
||||
let mut digest_set = CmsReader::new(digest_set_bytes, seq.mode);
|
||||
let mut digest_algorithms: Vec<AlgorithmIdentifierParsed> = Vec::new();
|
||||
while !digest_set.is_empty() {
|
||||
let alg = digest_set
|
||||
@@ -631,7 +684,7 @@ fn parse_signed_data_cursor(
|
||||
let content = seq
|
||||
.take_tag(0xA0)
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
certificates = Some(split_der_objects(content)?);
|
||||
certificates = Some(split_der_objects(content, seq.mode)?);
|
||||
}
|
||||
0xA1 => {
|
||||
crls_present = true;
|
||||
@@ -647,7 +700,7 @@ fn parse_signed_data_cursor(
|
||||
let set_bytes = seq
|
||||
.take_tag(0x31)
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
signer_infos = Some(parse_signer_infos_set_cursor(set_bytes)?);
|
||||
signer_infos = Some(parse_signer_infos_set_cursor(set_bytes, seq.mode)?);
|
||||
}
|
||||
_ => {
|
||||
return Err(SignedObjectParseError::Parse(
|
||||
@@ -689,7 +742,7 @@ fn parse_encapsulated_content_info_cursor(
|
||||
"EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into(),
|
||||
)
|
||||
})?;
|
||||
let mut inner = CmsReader::new(inner_der);
|
||||
let mut inner = CmsReader::new(inner_der, seq.mode);
|
||||
let octets = inner
|
||||
.take_octet_string()
|
||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
@@ -712,11 +765,14 @@ fn parse_encapsulated_content_info_cursor(
|
||||
})
|
||||
}
|
||||
|
||||
fn split_der_objects(mut input: &[u8]) -> Result<Vec<Vec<u8>>, SignedObjectParseError> {
|
||||
fn split_der_objects(
|
||||
mut input: &[u8],
|
||||
mode: CmsParseMode,
|
||||
) -> Result<Vec<Vec<u8>>, SignedObjectParseError> {
|
||||
let mut out: Vec<Vec<u8>> = Vec::new();
|
||||
while !input.is_empty() {
|
||||
let (_tag, _value, rem) =
|
||||
cms_take_tlv(input).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
cms_take_tlv(input, mode).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||
let consumed = input.len() - rem.len();
|
||||
out.push(input[..consumed].to_vec());
|
||||
input = rem;
|
||||
@@ -726,8 +782,9 @@ fn split_der_objects(mut input: &[u8]) -> Result<Vec<Vec<u8>>, SignedObjectParse
|
||||
|
||||
fn parse_signer_infos_set_cursor(
|
||||
set_bytes: &[u8],
|
||||
mode: CmsParseMode,
|
||||
) -> Result<Vec<SignerInfoParsed>, SignedObjectParseError> {
|
||||
let mut set = CmsReader::new(set_bytes);
|
||||
let mut set = CmsReader::new(set_bytes, mode);
|
||||
let mut out: Vec<SignerInfoParsed> = Vec::new();
|
||||
while !set.is_empty() {
|
||||
let si = set
|
||||
@@ -738,7 +795,10 @@ fn parse_signer_infos_set_cursor(
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedObjectValidateError> {
|
||||
fn validate_ee_certificate(
|
||||
der: &[u8],
|
||||
strict_name: bool,
|
||||
) -> Result<ResourceEeCertificate, SignedObjectValidateError> {
|
||||
let (rem, cert) = X509Certificate::from_der(der)
|
||||
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
@@ -762,6 +822,10 @@ fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedOb
|
||||
};
|
||||
}
|
||||
};
|
||||
if strict_name {
|
||||
rc.validate_strict_name_profile()
|
||||
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
||||
}
|
||||
|
||||
let ski = rc
|
||||
.tbs
|
||||
@@ -940,6 +1004,7 @@ fn parse_signer_info_cursor(
|
||||
|
||||
fn validate_signed_data_profile(
|
||||
signed_data: SignedDataParsed,
|
||||
strict_name: bool,
|
||||
) -> Result<SignedDataProfiled, SignedObjectValidateError> {
|
||||
if signed_data.version != 3 {
|
||||
return Err(SignedObjectValidateError::InvalidSignedDataVersion(
|
||||
@@ -985,7 +1050,7 @@ fn validate_signed_data_profile(
|
||||
certs.len(),
|
||||
));
|
||||
}
|
||||
let ee = validate_ee_certificate(&certs[0])?;
|
||||
let ee = validate_ee_certificate(&certs[0], strict_name)?;
|
||||
|
||||
if signed_data.signer_infos.len() != 1 {
|
||||
return Err(SignedObjectValidateError::InvalidSignerInfosCount(
|
||||
@@ -1414,3 +1479,116 @@ fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
|
||||
&bytes[idx..]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn strict_cms_der_rejects_constructed_octet_string_fixture() {
|
||||
let der = std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.mft",
|
||||
))
|
||||
.expect("read manifest fixture");
|
||||
let parsed = RpkiSignedObject::parse_der(&der).expect("parse fixture");
|
||||
let econtent = parsed
|
||||
.signed_data
|
||||
.encap_content_info
|
||||
.econtent
|
||||
.clone()
|
||||
.expect("fixture eContent");
|
||||
assert_eq!(
|
||||
parsed.signed_data.encap_content_info.econtent.as_deref(),
|
||||
Some(econtent.as_slice())
|
||||
);
|
||||
|
||||
let primitive_octets = der_tlv(0x04, &econtent);
|
||||
let constructed_octets = same_size_constructed_octet_string(&primitive_octets, &econtent);
|
||||
let mutated = replace_first_subslice(&der, &primitive_octets, &constructed_octets)
|
||||
.expect("replace eContent OCTET STRING");
|
||||
let compatible = RpkiSignedObject::parse_der(&mutated).expect("BER-compatible parse");
|
||||
assert!(
|
||||
econtent.starts_with(
|
||||
compatible
|
||||
.signed_data
|
||||
.encap_content_info
|
||||
.econtent
|
||||
.as_deref()
|
||||
.expect("compatible eContent")
|
||||
)
|
||||
);
|
||||
let err = RpkiSignedObject::parse_der_strict_cms(&mutated)
|
||||
.expect_err("DER strict rejects constructed OCTET STRING");
|
||||
assert!(err.to_string().contains("DER"), "{err}");
|
||||
}
|
||||
|
||||
fn replace_first_subslice(input: &[u8], from: &[u8], to: &[u8]) -> Option<Vec<u8>> {
|
||||
let pos = input
|
||||
.windows(from.len())
|
||||
.position(|candidate| candidate == from)?;
|
||||
let mut out = Vec::with_capacity(input.len() - from.len() + to.len());
|
||||
out.extend_from_slice(&input[..pos]);
|
||||
out.extend_from_slice(to);
|
||||
out.extend_from_slice(&input[pos + from.len()..]);
|
||||
Some(out)
|
||||
}
|
||||
|
||||
fn same_size_constructed_octet_string(primitive: &[u8], content: &[u8]) -> Vec<u8> {
|
||||
assert_eq!(primitive[0], 0x04);
|
||||
let header_len = tlv_header_len(primitive);
|
||||
let outer_value_len = primitive.len() - header_len;
|
||||
let child_len = (0..=outer_value_len)
|
||||
.rev()
|
||||
.find(|candidate| 1 + len_len(*candidate) + *candidate == outer_value_len)
|
||||
.expect("find child length");
|
||||
let mut out = primitive[..header_len].to_vec();
|
||||
out[0] = 0x24;
|
||||
out.extend(der_tlv(0x04, &content[..child_len]));
|
||||
assert_eq!(out.len(), primitive.len());
|
||||
out
|
||||
}
|
||||
|
||||
fn tlv_header_len(tlv: &[u8]) -> usize {
|
||||
if tlv[1] & 0x80 == 0 {
|
||||
2
|
||||
} else {
|
||||
2 + (tlv[1] & 0x7F) as usize
|
||||
}
|
||||
}
|
||||
|
||||
fn len_len(len: usize) -> usize {
|
||||
if len < 0x80 {
|
||||
return 1;
|
||||
}
|
||||
let mut value = len;
|
||||
let mut n = 0usize;
|
||||
while value > 0 {
|
||||
n += 1;
|
||||
value >>= 8;
|
||||
}
|
||||
1 + n
|
||||
}
|
||||
|
||||
fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
|
||||
let mut out = vec![tag];
|
||||
encode_len(value.len(), &mut out);
|
||||
out.extend_from_slice(value);
|
||||
out
|
||||
}
|
||||
|
||||
fn encode_len(len: usize, out: &mut Vec<u8>) {
|
||||
if len < 0x80 {
|
||||
out.push(len as u8);
|
||||
return;
|
||||
}
|
||||
let mut bytes = Vec::new();
|
||||
let mut value = len;
|
||||
while value > 0 {
|
||||
bytes.push((value & 0xFF) as u8);
|
||||
value >>= 8;
|
||||
}
|
||||
bytes.reverse();
|
||||
out.push(0x80 | bytes.len() as u8);
|
||||
out.extend(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,14 @@ impl TaCertificate {
|
||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||
}
|
||||
|
||||
pub fn decode_der_with_strict_name(der: &[u8]) -> Result<Self, TaCertificateDecodeError> {
|
||||
let ta = Self::decode_der(der)?;
|
||||
ta.rc_ca
|
||||
.validate_strict_name_profile()
|
||||
.map_err(TaCertificateProfileError::from)?;
|
||||
Ok(ta)
|
||||
}
|
||||
|
||||
/// Backwards-compatible helper (historical name).
|
||||
pub fn from_der(der: &[u8]) -> Result<Self, TaCertificateError> {
|
||||
Self::decode_der(der)
|
||||
@@ -264,6 +272,15 @@ impl TrustAnchor {
|
||||
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
|
||||
}
|
||||
|
||||
pub fn bind_der_with_strict_name(
|
||||
tal: Tal,
|
||||
ta_der: &[u8],
|
||||
resolved_uri: Option<&Url>,
|
||||
) -> Result<Self, TrustAnchorError> {
|
||||
let ta_certificate = TaCertificate::decode_der_with_strict_name(ta_der)?;
|
||||
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
|
||||
}
|
||||
|
||||
pub fn bind(
|
||||
tal: Tal,
|
||||
ta_certificate: TaCertificate,
|
||||
|
||||
Reference in New Issue
Block a user