优化数据对象decode + profile validate;benchmark对比routinator geomean 0.8

This commit is contained in:
2026-02-26 17:01:51 +08:00
parent 1cc3351bef
commit 68cbd3c500
82 changed files with 5869 additions and 609 deletions

View File

@@ -133,28 +133,23 @@ pub fn sha256_hex(bytes: &[u8]) -> String {
}
pub fn format_roa_ip_prefix(p: &crate::data_model::roa::IpPrefix) -> String {
let addr = p.addr_bytes();
match p.afi {
crate::data_model::roa::RoaAfi::Ipv4 => {
if p.addr.len() != 4 {
return format!("ipv4:{:02X?}/{}", p.addr, p.prefix_len);
}
format!(
"{}.{}.{}.{}{}",
p.addr[0],
p.addr[1],
p.addr[2],
p.addr[3],
addr[0],
addr[1],
addr[2],
addr[3],
format!("/{}", p.prefix_len)
)
}
crate::data_model::roa::RoaAfi::Ipv6 => {
if p.addr.len() != 16 {
return format!("ipv6:{:02X?}/{}", p.addr, p.prefix_len);
}
let mut parts = Vec::with_capacity(8);
for i in 0..8 {
let hi = p.addr[i * 2] as u16;
let lo = p.addr[i * 2 + 1] as u16;
let hi = addr[i * 2] as u16;
let lo = addr[i * 2 + 1] as u16;
parts.push(format!("{:x}", (hi << 8) | lo));
}
format!("{}{}", parts.join(":"), format!("/{}", p.prefix_len))

View File

@@ -612,7 +612,7 @@ mod tests {
prefix: crate::data_model::roa::IpPrefix {
afi: crate::data_model::roa::RoaAfi::Ipv4,
prefix_len: 24,
addr: vec![192, 0, 2, 0],
addr: [192, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
},
max_length: 24,
}],

View File

@@ -1,10 +1,9 @@
use crate::data_model::oid::OID_CT_ASPA;
use crate::data_model::common::{DerReader, der_take_tlv};
use crate::data_model::rc::ResourceCertificate;
use crate::data_model::signed_object::{
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
};
use der_parser::ber::Class;
use der_parser::der::{DerObject, Tag, parse_der};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AspaObject {
@@ -203,7 +202,7 @@ impl AspaObject {
impl AspaEContent {
/// Parse step of scheme A (`parse → validate → verify`).
pub fn parse_der(der: &[u8]) -> Result<AspaEContentParsed, AspaParseError> {
let (rem, _obj) = parse_der(der).map_err(|e| AspaParseError::Parse(e.to_string()))?;
let (_tag, _value, rem) = der_take_tlv(der).map_err(AspaParseError::Parse)?;
if !rem.is_empty() {
return Err(AspaParseError::TrailingBytes(rem.len()));
}
@@ -292,47 +291,66 @@ impl AspaObjectParsed {
impl AspaEContentParsed {
pub fn validate_profile(self) -> Result<AspaEContent, AspaProfileError> {
let (_rem, obj) =
parse_der(&self.der).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
let mut n = 0usize;
while !r.is_empty() {
r.skip_any()?;
n += 1;
}
Ok(n)
}
let seq = obj
.as_sequence()
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
if seq.len() != 3 {
let mut r = DerReader::new(&self.der);
let mut seq = r
.take_sequence()
.map_err(|e| AspaProfileError::ProfileDecode(e))?;
if !r.is_empty() {
return Err(AspaProfileError::ProfileDecode(
"trailing bytes after ASProviderAttestation".into(),
));
}
let elem_count =
count_elements(seq).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
if elem_count != 3 {
return Err(AspaProfileError::InvalidAttestationSequence);
}
// version [0] EXPLICIT INTEGER MUST be present and MUST be 1.
let v_obj = &seq[0];
if v_obj.class() != Class::ContextSpecific || v_obj.tag() != Tag(0) {
if seq
.peek_tag()
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?
!= 0xA0
{
return Err(AspaProfileError::VersionMustBeExplicitOne);
}
let inner_der = v_obj
.as_slice()
let (inner_tag, inner_val) = seq
.take_explicit(0xA0)
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
let (rem, inner) =
parse_der(inner_der).map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
if !rem.is_empty() {
return Err(AspaProfileError::ProfileDecode(
"trailing bytes inside ASProviderAttestation.version".into(),
));
if inner_tag != 0x02 {
return Err(AspaProfileError::VersionMustBeExplicitOne);
}
let v = inner
.as_u64()
let v = crate::data_model::common::der_uint_from_bytes(inner_val)
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
if v != 1 {
return Err(AspaProfileError::VersionMustBeExplicitOne);
}
let customer_u64 = seq[1]
.as_u64()
let customer_u64 = seq
.take_uint_u64()
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
if customer_u64 > u32::MAX as u64 {
return Err(AspaProfileError::CustomerAsIdOutOfRange(customer_u64));
}
let customer_as_id = customer_u64 as u32;
let providers = parse_providers(&seq[2], customer_as_id)?;
let providers_seq = seq
.take_sequence()
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
if !seq.is_empty() {
return Err(AspaProfileError::InvalidAttestationSequence);
}
let providers = parse_providers_cursor(providers_seq, customer_as_id)?;
Ok(AspaEContent {
version: 1,
@@ -342,19 +360,19 @@ impl AspaEContentParsed {
}
}
fn parse_providers(obj: &DerObject<'_>, customer_as_id: u32) -> Result<Vec<u32>, AspaProfileError> {
let seq = obj
.as_sequence()
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
fn parse_providers_cursor(
mut seq: DerReader<'_>,
customer_as_id: u32,
) -> Result<Vec<u32>, AspaProfileError> {
if seq.is_empty() {
return Err(AspaProfileError::EmptyProviders);
}
let mut out: Vec<u32> = Vec::with_capacity(seq.len());
let mut out: Vec<u32> = Vec::new();
let mut prev: Option<u32> = None;
for item in seq {
let v = item
.as_u64()
while !seq.is_empty() {
let v = seq
.take_uint_u64()
.map_err(|e| AspaProfileError::ProfileDecode(e.to_string()))?;
if v > u32::MAX as u64 {
return Err(AspaProfileError::ProviderAsIdOutOfRange(v));

View File

@@ -1,5 +1,6 @@
use x509_parser::asn1_rs::Tag;
use x509_parser::x509::AlgorithmIdentifier;
use x509_parser::prelude::FromDer;
pub type UtcTime = time::OffsetDateTime;
@@ -100,6 +101,190 @@ pub fn algorithm_params_absent_or_null(sig: &AlgorithmIdentifier<'_>) -> bool {
}
}
/// Take a single DER TLV (Tag-Length-Value) from the start of `input`.
///
/// This helper supports:
/// - short- and long-form lengths (up to 8 length bytes)
/// - only low-tag-number form tags (no high-tag-number form)
/// - definite length only (DER forbids indefinite length)
///
/// Returns: `(tag_byte, value_bytes, remaining_bytes)`.
pub(crate) fn der_take_tlv(input: &[u8]) -> Result<(u8, &[u8], &[u8]), String> {
if input.len() < 2 {
return Err("truncated DER (need tag+len)".into());
}
let tag = input[0];
if (tag & 0x1F) == 0x1F {
return Err("high-tag-number form not supported".into());
}
let len0 = input[1];
if len0 == 0x80 {
return Err("indefinite length not allowed in DER".into());
}
let (len, hdr_len) = if len0 & 0x80 == 0 {
(len0 as usize, 2usize)
} else {
let n = (len0 & 0x7F) as usize;
if n == 0 || n > 8 {
return Err("invalid DER length".into());
}
if input.len() < 2 + n {
return Err("truncated DER (length bytes)".into());
}
let mut l: usize = 0;
for &b in &input[2..2 + n] {
l = (l << 8) | (b as usize);
}
(l, 2 + n)
};
if input.len() < hdr_len + len {
return Err("truncated DER (value bytes)".into());
}
let value = &input[hdr_len..hdr_len + len];
let rem = &input[hdr_len + len..];
Ok((tag, value, rem))
}
/// Minimal streaming DER reader built on `der_take_tlv`.
///
/// This is intentionally small and only supports the subset of DER needed by
/// RPKI object eContent decoders (ROA/ASPA), to avoid constructing a generic AST
/// (which is expensive on large objects such as ROAs with thousands of prefixes).
#[derive(Clone, Copy)]
pub(crate) struct DerReader<'a> {
buf: &'a [u8],
}
impl<'a> DerReader<'a> {
pub(crate) fn new(buf: &'a [u8]) -> Self {
Self { buf }
}
pub(crate) fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub(crate) fn remaining_len(&self) -> usize {
self.buf.len()
}
pub(crate) fn peek_tag(&self) -> Result<u8, String> {
self.buf.first().copied().ok_or_else(|| "truncated DER".into())
}
pub(crate) fn take_any(&mut self) -> Result<(u8, &'a [u8]), String> {
let (tag, value, rem) = der_take_tlv(self.buf)?;
self.buf = rem;
Ok((tag, value))
}
pub(crate) fn take_any_full(&mut self) -> Result<(u8, &'a [u8], &'a [u8]), String> {
let (tag, value, rem) = der_take_tlv(self.buf)?;
let consumed = self.buf.len() - rem.len();
let full = &self.buf[..consumed];
self.buf = rem;
Ok((tag, full, value))
}
pub(crate) fn skip_any(&mut self) -> Result<(), String> {
let _ = self.take_any()?;
Ok(())
}
pub(crate) fn take_tag(&mut self, expected_tag: u8) -> Result<&'a [u8], String> {
let (tag, value) = self.take_any()?;
if tag != expected_tag {
return Err(format!(
"unexpected tag: got 0x{tag:02X}, expected 0x{expected_tag:02X}"
));
}
Ok(value)
}
pub(crate) fn take_sequence(&mut self) -> Result<DerReader<'a>, String> {
let value = self.take_tag(0x30)?;
Ok(DerReader::new(value))
}
pub(crate) fn take_octet_string(&mut self) -> Result<&'a [u8], String> {
self.take_tag(0x04)
}
pub(crate) fn take_bit_string(&mut self) -> Result<(u8, &'a [u8]), String> {
let v = self.take_tag(0x03)?;
if v.is_empty() {
return Err("BIT STRING content is empty".into());
}
Ok((v[0], &v[1..]))
}
pub(crate) fn take_uint_u64(&mut self) -> Result<u64, String> {
let v = self.take_tag(0x02)?;
der_uint_from_bytes(v)
}
pub(crate) 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) = der_take_tlv(inner_der)?;
if !rem.is_empty() {
return Err("trailing bytes inside EXPLICIT value".into());
}
Ok((tag, value))
}
pub(crate) 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) = der_take_tlv(inner_der)?;
if !rem.is_empty() {
return Err("trailing bytes inside EXPLICIT value".into());
}
Ok(inner_der)
}
}
pub(crate) fn der_uint_from_bytes(bytes: &[u8]) -> Result<u64, String> {
if bytes.is_empty() {
return Err("INTEGER has empty content".into());
}
// Disallow negative values.
if (bytes[0] & 0x80) != 0 {
return Err("INTEGER is negative".into());
}
// DER requires minimal encoding for INTEGER.
if bytes.len() > 1 && bytes[0] == 0x00 && (bytes[1] & 0x80) == 0 {
return Err("INTEGER not minimally encoded".into());
}
if bytes.len() > 8 {
return Err("INTEGER does not fit u64".into());
}
let mut v: u64 = 0;
for &b in bytes {
v = (v << 8) | (b as u64);
}
Ok(v)
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct X509NameDer(pub Vec<u8>);
impl X509NameDer {
pub fn as_raw(&self) -> &[u8] {
&self.0
}
}
impl std::fmt::Display for X509NameDer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Ok((rem, name)) = x509_parser::x509::X509Name::from_der(&self.0) else {
return write!(f, "<invalid X.509 Name DER>");
};
if !rem.is_empty() {
return write!(f, "<invalid X.509 Name DER (trailing bytes)>");
}
write!(f, "{name}")
}
}
/// Filename extensions registered in IANA "RPKI Repository Name Schemes".
///
/// Source: <https://www.iana.org/assignments/rpki/rpki.xhtml>

View File

@@ -1,7 +1,8 @@
pub use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc, BigUnsigned};
use crate::data_model::oid::{
OID_AUTHORITY_KEY_IDENTIFIER, OID_CRL_NUMBER, OID_SHA256_WITH_RSA_ENCRYPTION,
OID_SUBJECT_KEY_IDENTIFIER,
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTHORITY_KEY_IDENTIFIER_RAW, OID_CRL_NUMBER,
OID_CRL_NUMBER_RAW, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW,
OID_SUBJECT_KEY_IDENTIFIER_RAW,
};
use x509_parser::certificate::X509Certificate;
use x509_parser::extensions::{ParsedExtension, X509Extension};
@@ -425,8 +426,15 @@ fn algorithm_identifier_value(ai: &AlgorithmIdentifier<'_>) -> AlgorithmIdentifi
tag: p.tag(),
data: p.as_bytes().to_vec(),
});
// NOTE(perf): Avoid `to_id_string()` allocations for the signature algorithms we expect
// in RPKI CRLs. Fall back to `to_id_string()` for unexpected algorithms (mostly error paths).
let oid = if ai.algorithm.as_bytes() == OID_SHA256_WITH_RSA_ENCRYPTION_RAW {
OID_SHA256_WITH_RSA_ENCRYPTION.to_string()
} else {
ai.algorithm.to_id_string()
};
AlgorithmIdentifierValue {
oid: ai.algorithm.to_id_string(),
oid,
parameters,
}
}
@@ -434,9 +442,8 @@ fn algorithm_identifier_value(ai: &AlgorithmIdentifier<'_>) -> AlgorithmIdentifi
fn parse_extensions_parse(exts: &[X509Extension<'_>]) -> Result<Vec<CrlExtensionParsed>, String> {
let mut out = Vec::with_capacity(exts.len());
for ext in exts {
let oid = ext.oid.to_id_string();
match oid.as_str() {
OID_AUTHORITY_KEY_IDENTIFIER => {
let oid = ext.oid.as_bytes();
if oid == OID_AUTHORITY_KEY_IDENTIFIER_RAW {
let ParsedExtension::AuthorityKeyIdentifier(aki) = ext.parsed_extension() else {
return Err("AKI extension parse failed".to_string());
};
@@ -446,18 +453,19 @@ fn parse_extensions_parse(exts: &[X509Extension<'_>]) -> Result<Vec<CrlExtension
|| aki.authority_cert_serial.is_some(),
critical: ext.critical,
});
}
OID_CRL_NUMBER => match ext.parsed_extension() {
} else if oid == OID_CRL_NUMBER_RAW {
match ext.parsed_extension() {
ParsedExtension::CRLNumber(n) => out.push(CrlExtensionParsed::CrlNumber {
number: n.clone(),
critical: ext.critical,
}),
_ => return Err("CRLNumber extension parse failed".to_string()),
},
_ => out.push(CrlExtensionParsed::Other {
oid,
}
} else {
out.push(CrlExtensionParsed::Other {
oid: ext.oid.to_id_string(),
critical: ext.critical,
}),
})
}
}
Ok(out)
@@ -525,7 +533,7 @@ fn validate_extensions_profile(
fn get_subject_key_identifier(cert: &X509Certificate<'_>) -> Option<Vec<u8>> {
cert.extensions()
.iter()
.find(|ext| ext.oid.to_id_string() == OID_SUBJECT_KEY_IDENTIFIER)
.find(|ext| ext.oid.as_bytes() == OID_SUBJECT_KEY_IDENTIFIER_RAW)
.and_then(|ext| match ext.parsed_extension() {
ParsedExtension::SubjectKeyIdentifier(ki) => Some(ki.0.to_vec()),
_ => None,

View File

@@ -1,4 +1,5 @@
use crate::data_model::common::{BigUnsigned, UtcTime};
use crate::data_model::common::der_take_tlv;
use crate::data_model::oid::{OID_CT_RPKI_MANIFEST, OID_SHA256};
use crate::data_model::rc::ResourceCertificate;
use crate::data_model::signed_object::{
@@ -821,42 +822,6 @@ fn oid_content_iter(bytes: &[u8]) -> impl Iterator<Item = u64> + '_ {
}
}
fn der_take_tlv(input: &[u8]) -> Result<(u8, &[u8], &[u8]), String> {
if input.len() < 2 {
return Err("truncated DER (need tag+len)".into());
}
let tag = input[0];
if (tag & 0x1F) == 0x1F {
return Err("high-tag-number form not supported".into());
}
let len0 = input[1];
if len0 == 0x80 {
return Err("indefinite length not allowed in DER".into());
}
let (len, hdr_len) = if len0 & 0x80 == 0 {
(len0 as usize, 2usize)
} else {
let n = (len0 & 0x7F) as usize;
if n == 0 || n > 8 {
return Err("invalid DER length".into());
}
if input.len() < 2 + n {
return Err("truncated DER (length bytes)".into());
}
let mut l: usize = 0;
for &b in &input[2..2 + n] {
l = (l << 8) | (b as usize);
}
(l, 2 + n)
};
if input.len() < hdr_len + len {
return Err("truncated DER (value bytes)".into());
}
let value = &input[hdr_len..hdr_len + len];
let rem = &input[hdr_len + len..];
Ok((tag, value, rem))
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1,42 +1,72 @@
pub const OID_SHA256: &str = "2.16.840.1.101.3.4.2.1";
pub const OID_SHA256_RAW: &[u8] = &asn1_rs::oid!(raw 2.16.840.1.101.3.4.2.1);
pub const OID_SIGNED_DATA: &str = "1.2.840.113549.1.7.2";
pub const OID_SIGNED_DATA_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.7.2);
pub const OID_CMS_ATTR_CONTENT_TYPE: &str = "1.2.840.113549.1.9.3";
pub const OID_CMS_ATTR_CONTENT_TYPE_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.3);
pub const OID_CMS_ATTR_MESSAGE_DIGEST: &str = "1.2.840.113549.1.9.4";
pub const OID_CMS_ATTR_MESSAGE_DIGEST_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.4);
pub const OID_CMS_ATTR_SIGNING_TIME: &str = "1.2.840.113549.1.9.5";
pub const OID_CMS_ATTR_SIGNING_TIME_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.5);
pub const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
pub const OID_RSA_ENCRYPTION_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.1.1);
pub const OID_SHA256_WITH_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.11";
pub const OID_SHA256_WITH_RSA_ENCRYPTION_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.1.11);
// X.509 extensions (RFC 5280 / RFC 6487)
pub const OID_BASIC_CONSTRAINTS: &str = "2.5.29.19";
pub const OID_BASIC_CONSTRAINTS_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.19);
pub const OID_KEY_USAGE: &str = "2.5.29.15";
pub const OID_KEY_USAGE_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.15);
pub const OID_EXTENDED_KEY_USAGE: &str = "2.5.29.37";
pub const OID_EXTENDED_KEY_USAGE_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.37);
pub const OID_CRL_DISTRIBUTION_POINTS: &str = "2.5.29.31";
pub const OID_CRL_DISTRIBUTION_POINTS_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.31);
pub const OID_AUTHORITY_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.1";
pub const OID_AUTHORITY_INFO_ACCESS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.1);
pub const OID_CERTIFICATE_POLICIES: &str = "2.5.29.32";
pub const OID_CERTIFICATE_POLICIES_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.32);
pub const OID_AUTHORITY_KEY_IDENTIFIER: &str = "2.5.29.35";
pub const OID_AUTHORITY_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.35);
pub const OID_CRL_NUMBER: &str = "2.5.29.20";
pub const OID_CRL_NUMBER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.20);
pub const OID_SUBJECT_KEY_IDENTIFIER: &str = "2.5.29.14";
pub const OID_SUBJECT_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.14);
pub const OID_CT_RPKI_MANIFEST: &str = "1.2.840.113549.1.9.16.1.26";
pub const OID_CT_RPKI_MANIFEST_RAW: &[u8] =
&asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.26);
pub const OID_CT_ROUTE_ORIGIN_AUTHZ: &str = "1.2.840.113549.1.9.16.1.24";
pub const OID_CT_ROUTE_ORIGIN_AUTHZ_RAW: &[u8] =
&asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.24);
pub const OID_CT_ASPA: &str = "1.2.840.113549.1.9.16.1.49";
pub const OID_CT_ASPA_RAW: &[u8] = &asn1_rs::oid!(raw 1.2.840.113549.1.9.16.1.49);
// X.509 extensions / access methods (RFC 5280 / RFC 6487)
pub const OID_SUBJECT_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.11";
pub const OID_SUBJECT_INFO_ACCESS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.11);
pub const OID_AD_SIGNED_OBJECT: &str = "1.3.6.1.5.5.7.48.11";
pub const OID_AD_SIGNED_OBJECT_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.11);
pub const OID_AD_CA_ISSUERS: &str = "1.3.6.1.5.5.7.48.2";
pub const OID_AD_CA_ISSUERS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.2);
pub const OID_AD_CA_REPOSITORY: &str = "1.3.6.1.5.5.7.48.5";
pub const OID_AD_CA_REPOSITORY_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.5);
pub const OID_AD_RPKI_MANIFEST: &str = "1.3.6.1.5.5.7.48.10";
pub const OID_AD_RPKI_MANIFEST_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.10);
pub const OID_AD_RPKI_NOTIFY: &str = "1.3.6.1.5.5.7.48.13";
pub const OID_AD_RPKI_NOTIFY_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.48.13);
// RFC 3779 resource extensions (RFC 6487 profile)
pub const OID_IP_ADDR_BLOCKS: &str = "1.3.6.1.5.5.7.1.7";
pub const OID_IP_ADDR_BLOCKS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.7);
pub const OID_AUTONOMOUS_SYS_IDS: &str = "1.3.6.1.5.5.7.1.8";
pub const OID_AUTONOMOUS_SYS_IDS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.8);
// RPKI CP (RFC 6484 / RFC 6487)
pub const OID_CP_IPADDR_ASNUMBER: &str = "1.3.6.1.5.5.7.14.2";
pub const OID_CP_IPADDR_ASNUMBER_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.14.2);

View File

@@ -1,19 +1,21 @@
use der_parser::ber::{BerObjectContent, Class};
use der_parser::der::{DerObject, Tag, parse_der};
use der_parser::num_bigint::BigUint;
use url::Url;
use x509_parser::asn1_rs::{Class as Asn1Class, Tag as Asn1Tag};
use x509_parser::extensions::ParsedExtension;
use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version};
use crate::data_model::common::{
Asn1TimeUtc, InvalidTimeEncodingError, UtcTime, asn1_time_to_model,
Asn1TimeUtc, InvalidTimeEncodingError, UtcTime, X509NameDer, asn1_time_to_model,
};
use crate::data_model::oid::{
OID_AD_CA_ISSUERS, OID_AD_SIGNED_OBJECT, OID_AUTHORITY_INFO_ACCESS,
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTONOMOUS_SYS_IDS, OID_CP_IPADDR_ASNUMBER,
OID_CRL_DISTRIBUTION_POINTS, OID_IP_ADDR_BLOCKS, OID_SHA256_WITH_RSA_ENCRYPTION,
OID_SUBJECT_INFO_ACCESS, OID_SUBJECT_KEY_IDENTIFIER,
OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
OID_AD_RPKI_MANIFEST_RAW, OID_AD_RPKI_NOTIFY, OID_AD_RPKI_NOTIFY_RAW, OID_AD_SIGNED_OBJECT,
OID_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS_RAW, OID_AUTHORITY_KEY_IDENTIFIER_RAW,
OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS_RAW, OID_CERTIFICATE_POLICIES_RAW,
OID_CP_IPADDR_ASNUMBER, OID_CP_IPADDR_ASNUMBER_RAW, OID_CRL_DISTRIBUTION_POINTS_RAW,
OID_IP_ADDR_BLOCKS_RAW, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW,
OID_SUBJECT_INFO_ACCESS_RAW, OID_SUBJECT_KEY_IDENTIFIER_RAW,
};
/// Resource Certificate kind (semantic classification).
@@ -43,8 +45,8 @@ pub struct RpkixTbsCertificate {
pub version: u32,
pub serial_number: BigUint,
pub signature_algorithm: String,
pub issuer_dn: String,
pub subject_dn: String,
pub issuer_name: X509NameDer,
pub subject_name: X509NameDer,
pub validity_not_before: UtcTime,
pub validity_not_after: UtcTime,
/// DER encoding of SubjectPublicKeyInfo.
@@ -59,9 +61,9 @@ pub struct RcExtensions {
/// Authority Key Identifier (AKI) keyIdentifier value.
pub authority_key_identifier: Option<Vec<u8>>,
/// CRL Distribution Points URIs (fullName).
pub crl_distribution_points_uris: Option<Vec<Url>>,
pub crl_distribution_points_uris: Option<Vec<String>>,
/// Authority Information Access (AIA) caIssuers URIs.
pub ca_issuers_uris: Option<Vec<Url>>,
pub ca_issuers_uris: Option<Vec<String>>,
pub subject_info_access: Option<SubjectInfoAccess>,
pub certificate_policies_oid: Option<String>,
@@ -76,8 +78,8 @@ pub struct ResourceCertificateParsed {
pub serial_number: BigUint,
pub signature_algorithm: AlgorithmIdentifierValue,
pub tbs_signature_algorithm: AlgorithmIdentifierValue,
pub issuer_dn: String,
pub subject_dn: String,
pub issuer_name: X509NameDer,
pub subject_name: X509NameDer,
pub validity_not_before: Asn1TimeUtc,
pub validity_not_after: Asn1TimeUtc,
/// DER encoding of SubjectPublicKeyInfo.
@@ -130,7 +132,7 @@ pub struct AuthorityKeyIdentifierParsed {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuthorityInfoAccessParsed {
pub ca_issuers_uris: Vec<Url>,
pub ca_issuers_uris: Vec<String>,
pub ca_issuers_access_location_not_uri: bool,
}
@@ -145,7 +147,7 @@ pub struct CrlDistributionPointParsed {
pub reasons_present: bool,
pub crl_issuer_present: bool,
pub name_relative_to_crl_issuer_present: bool,
pub full_name_uris: Vec<Url>,
pub full_name_uris: Vec<String>,
pub full_name_not_uri: bool,
pub full_name_present: bool,
}
@@ -153,7 +155,7 @@ pub struct CrlDistributionPointParsed {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubjectInfoAccessParsed {
pub access_descriptions: Vec<AccessDescription>,
pub signed_object_uris: Vec<Url>,
pub signed_object_uris: Vec<String>,
pub signed_object_access_location_not_uri: bool,
}
@@ -170,7 +172,7 @@ pub struct SubjectInfoAccessCa {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubjectInfoAccessEe {
pub signed_object_uris: Vec<Url>,
pub signed_object_uris: Vec<String>,
/// The full list of access descriptions as carried in the SIA extension.
pub access_descriptions: Vec<AccessDescription>,
}
@@ -178,7 +180,7 @@ pub struct SubjectInfoAccessEe {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AccessDescription {
pub access_method_oid: String,
pub access_location: Url,
pub access_location: String,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
@@ -541,8 +543,8 @@ impl ResourceCertificate {
serial_number: cert.tbs_certificate.serial.clone(),
signature_algorithm,
tbs_signature_algorithm,
issuer_dn: cert.issuer().to_string(),
subject_dn: cert.subject().to_string(),
issuer_name: X509NameDer(cert.issuer().as_raw().to_vec()),
subject_name: X509NameDer(cert.subject().as_raw().to_vec()),
validity_not_before,
validity_not_after,
subject_public_key_info,
@@ -591,7 +593,7 @@ impl ResourceCertificateParsed {
return Err(ResourceCertificateProfileError::InvalidSignatureAlgorithmParameters);
}
let is_self_signed = self.issuer_dn == self.subject_dn;
let is_self_signed = self.issuer_name == self.subject_name;
let extensions = self.extensions.validate_profile(is_self_signed)?;
let kind = if extensions.basic_constraints_ca {
ResourceCertKind::Ca
@@ -605,8 +607,8 @@ impl ResourceCertificateParsed {
version,
serial_number: self.serial_number,
signature_algorithm: self.signature_algorithm.oid,
issuer_dn: self.issuer_dn,
subject_dn: self.subject_dn,
issuer_name: self.issuer_name,
subject_name: self.subject_name,
validity_not_before: self.validity_not_before.utc,
validity_not_after: self.validity_not_after.utc,
subject_public_key_info: self.subject_public_key_info,
@@ -622,20 +624,38 @@ impl RcExtensionsParsed {
self,
is_self_signed: bool,
) -> Result<RcExtensions, ResourceCertificateProfileError> {
if self.basic_constraints_ca.len() > 1 {
// NOTE(perf): `self` is consumed. Prefer moving decoded fields out rather than cloning,
// especially for large resource sets and URI lists.
let RcExtensionsParsed {
basic_constraints_ca,
subject_key_identifier,
authority_key_identifier,
crl_distribution_points,
authority_info_access,
subject_info_access,
certificate_policies,
ip_resources,
as_resources,
} = self;
if basic_constraints_ca.len() > 1 {
return Err(ResourceCertificateProfileError::DuplicateExtension(
"basicConstraints",
));
}
let basic_constraints_ca = self.basic_constraints_ca.first().copied().unwrap_or(false);
let basic_constraints_ca = basic_constraints_ca.first().copied().unwrap_or(false);
let subject_key_identifier = match self.subject_key_identifier.as_slice() {
[] => None,
[(ski, critical)] => {
if *critical {
let subject_key_identifier = match subject_key_identifier.len() {
0 => None,
1 => {
let (ski, critical) = subject_key_identifier
.into_iter()
.next()
.expect("len==1");
if critical {
return Err(ResourceCertificateProfileError::SkiCriticality);
}
Some(ski.clone())
Some(ski)
}
_ => {
return Err(ResourceCertificateProfileError::DuplicateExtension(
@@ -644,16 +664,20 @@ impl RcExtensionsParsed {
}
};
let authority_key_identifier = match self.authority_key_identifier.as_slice() {
[] => {
let authority_key_identifier = match authority_key_identifier.len() {
0 => {
if is_self_signed {
None
} else {
return Err(ResourceCertificateProfileError::AkiMissing);
}
}
[(aki, critical)] => {
if *critical {
1 => {
let (aki, critical) = authority_key_identifier
.into_iter()
.next()
.expect("len==1");
if critical {
return Err(ResourceCertificateProfileError::AkiCriticality);
}
if aki.has_authority_cert_issuer {
@@ -662,7 +686,7 @@ impl RcExtensionsParsed {
if aki.has_authority_cert_serial {
return Err(ResourceCertificateProfileError::AkiAuthorityCertSerialPresent);
}
let keyid = aki.key_identifier.clone();
let keyid = aki.key_identifier;
if is_self_signed {
if let (Some(keyid), Some(ski)) =
(keyid.as_ref(), subject_key_identifier.as_ref())
@@ -683,16 +707,20 @@ impl RcExtensionsParsed {
}
};
let crl_distribution_points_uris = match self.crl_distribution_points.as_slice() {
[] => {
let crl_distribution_points_uris = match crl_distribution_points.len() {
0 => {
if is_self_signed {
None
} else {
return Err(ResourceCertificateProfileError::CrlDistributionPointsMissing);
}
}
[(crldp, critical)] => {
if *critical {
1 => {
let (crldp, critical) = crl_distribution_points
.into_iter()
.next()
.expect("len==1");
if critical {
return Err(ResourceCertificateProfileError::CrlDistributionPointsCriticality);
}
if is_self_signed {
@@ -703,7 +731,11 @@ impl RcExtensionsParsed {
if crldp.distribution_points.len() != 1 {
return Err(ResourceCertificateProfileError::CrlDistributionPointsNotSingle);
}
let dp = &crldp.distribution_points[0];
let dp = crldp
.distribution_points
.into_iter()
.next()
.expect("len==1");
if dp.reasons_present {
return Err(ResourceCertificateProfileError::CrlDistributionPointsHasReasons);
}
@@ -723,10 +755,10 @@ impl RcExtensionsParsed {
ResourceCertificateProfileError::CrlDistributionPointsFullNameNotUri,
);
}
if !dp.full_name_uris.iter().any(|u| u.scheme() == "rsync") {
if !dp.full_name_uris.iter().any(|u| u.starts_with("rsync://")) {
return Err(ResourceCertificateProfileError::CrlDistributionPointsNoRsync);
}
Some(dp.full_name_uris.clone())
Some(dp.full_name_uris)
}
_ => {
return Err(ResourceCertificateProfileError::DuplicateExtension(
@@ -735,16 +767,17 @@ impl RcExtensionsParsed {
}
};
let ca_issuers_uris = match self.authority_info_access.as_slice() {
[] => {
let ca_issuers_uris = match authority_info_access.len() {
0 => {
if is_self_signed {
None
} else {
return Err(ResourceCertificateProfileError::AuthorityInfoAccessMissing);
}
}
[(aia, critical)] => {
if *critical {
1 => {
let (aia, critical) = authority_info_access.into_iter().next().expect("len==1");
if critical {
return Err(ResourceCertificateProfileError::AuthorityInfoAccessCriticality);
}
if is_self_signed {
@@ -762,10 +795,10 @@ impl RcExtensionsParsed {
ResourceCertificateProfileError::AuthorityInfoAccessMissingCaIssuers,
);
}
if !aia.ca_issuers_uris.iter().any(|u| u.scheme() == "rsync") {
if !aia.ca_issuers_uris.iter().any(|u| u.starts_with("rsync://")) {
return Err(ResourceCertificateProfileError::AuthorityInfoAccessNoRsync);
}
Some(aia.ca_issuers_uris.clone())
Some(aia.ca_issuers_uris)
}
_ => {
return Err(ResourceCertificateProfileError::DuplicateExtension(
@@ -774,28 +807,32 @@ impl RcExtensionsParsed {
}
};
let subject_info_access = match self.subject_info_access.as_slice() {
[] => None,
[(sia, critical)] => {
if *critical {
let subject_info_access = match subject_info_access.len() {
0 => None,
1 => {
let (sia, critical) = subject_info_access.into_iter().next().expect("len==1");
if critical {
return Err(ResourceCertificateProfileError::SiaCriticality);
}
if sia.signed_object_access_location_not_uri {
return Err(ResourceCertificateProfileError::SignedObjectSiaNotUri);
}
if !sia.signed_object_uris.is_empty()
&& !sia.signed_object_uris.iter().any(|u| u.scheme() == "rsync")
&& !sia
.signed_object_uris
.iter()
.any(|u| u.starts_with("rsync://"))
{
return Err(ResourceCertificateProfileError::SignedObjectSiaNoRsync);
}
if sia.signed_object_uris.is_empty() {
Some(SubjectInfoAccess::Ca(SubjectInfoAccessCa {
access_descriptions: sia.access_descriptions.clone(),
access_descriptions: sia.access_descriptions,
}))
} else {
Some(SubjectInfoAccess::Ee(SubjectInfoAccessEe {
signed_object_uris: sia.signed_object_uris.clone(),
access_descriptions: sia.access_descriptions.clone(),
signed_object_uris: sia.signed_object_uris,
access_descriptions: sia.access_descriptions,
}))
}
}
@@ -806,10 +843,11 @@ impl RcExtensionsParsed {
}
};
let certificate_policies_oid = match self.certificate_policies.as_slice() {
[] => None,
[(oids, critical)] => {
if !*critical {
let certificate_policies_oid = match certificate_policies.len() {
0 => None,
1 => {
let (oids, critical) = certificate_policies.into_iter().next().expect("len==1");
if !critical {
return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
}
if oids.len() != 1 {
@@ -817,7 +855,7 @@ impl RcExtensionsParsed {
"expected exactly one policy".into(),
));
}
let policy_oid = oids[0].clone();
let policy_oid = oids.into_iter().next().expect("len==1");
if policy_oid != OID_CP_IPADDR_ASNUMBER {
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
policy_oid,
@@ -832,13 +870,14 @@ impl RcExtensionsParsed {
}
};
let ip_resources = match self.ip_resources.as_slice() {
[] => None,
[(ip, critical)] => {
if !*critical {
let ip_resources = match ip_resources.len() {
0 => None,
1 => {
let (ip, critical) = ip_resources.into_iter().next().expect("len==1");
if !critical {
return Err(ResourceCertificateProfileError::IpResourcesCriticality);
}
Some(ip.clone())
Some(ip)
}
_ => {
return Err(ResourceCertificateProfileError::DuplicateExtension(
@@ -847,13 +886,14 @@ impl RcExtensionsParsed {
}
};
let as_resources = match self.as_resources.as_slice() {
[] => None,
[(asn, critical)] => {
if !*critical {
let as_resources = match as_resources.len() {
0 => None,
1 => {
let (asn, critical) = as_resources.into_iter().next().expect("len==1");
if !critical {
return Err(ResourceCertificateProfileError::AsResourcesCriticality);
}
Some(asn.clone())
Some(asn)
}
_ => {
return Err(ResourceCertificateProfileError::DuplicateExtension(
@@ -884,8 +924,16 @@ fn algorithm_identifier_value(
tag: p.tag(),
data: p.as_bytes().to_vec(),
});
// NOTE(perf): Avoid `to_id_string()` allocations for the algorithms we expect
// in RPKI resource certificates. Fall back to `to_id_string()` for unexpected
// algorithms (mostly error paths).
let oid = if ai.algorithm.as_bytes() == OID_SHA256_WITH_RSA_ENCRYPTION_RAW {
OID_SHA256_WITH_RSA_ENCRYPTION.to_string()
} else {
ai.algorithm.to_id_string()
};
AlgorithmIdentifierValue {
oid: ai.algorithm.to_id_string(),
oid,
parameters,
}
}
@@ -905,25 +953,22 @@ fn parse_extensions_parse(
let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new();
for ext in exts {
let oid = ext.oid.to_id_string();
match oid.as_str() {
crate::data_model::oid::OID_BASIC_CONSTRAINTS => {
let oid = ext.oid.as_bytes();
if oid == OID_BASIC_CONSTRAINTS_RAW {
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"basicConstraints parse failed".into(),
));
};
basic_constraints_ca.push(bc.ca);
}
OID_SUBJECT_KEY_IDENTIFIER => {
} else if oid == OID_SUBJECT_KEY_IDENTIFIER_RAW {
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"subjectKeyIdentifier parse failed".into(),
));
};
ski.push((s.0.to_vec(), ext.critical));
}
OID_AUTHORITY_KEY_IDENTIFIER => {
} else if oid == OID_AUTHORITY_KEY_IDENTIFIER_RAW {
let ParsedExtension::AuthorityKeyIdentifier(a) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"authorityKeyIdentifier parse failed".into(),
@@ -937,51 +982,51 @@ fn parse_extensions_parse(
},
ext.critical,
));
}
OID_CRL_DISTRIBUTION_POINTS => {
} else if oid == OID_CRL_DISTRIBUTION_POINTS_RAW {
let ParsedExtension::CRLDistributionPoints(p) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"cRLDistributionPoints parse failed".into(),
));
};
crldp.push((parse_crldp_parse(p)?, ext.critical));
}
OID_AUTHORITY_INFO_ACCESS => {
} else if oid == OID_AUTHORITY_INFO_ACCESS_RAW {
let ParsedExtension::AuthorityInfoAccess(p) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"authorityInfoAccess parse failed".into(),
));
};
aia.push((parse_aia_parse(p.accessdescs.as_slice())?, ext.critical));
}
OID_SUBJECT_INFO_ACCESS => {
} else if oid == OID_SUBJECT_INFO_ACCESS_RAW {
let ParsedExtension::SubjectInfoAccess(s) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"subjectInfoAccess parse failed".into(),
));
};
sia.push((parse_sia_parse(s.accessdescs.as_slice())?, ext.critical));
}
crate::data_model::oid::OID_CERTIFICATE_POLICIES => {
} else if oid == OID_CERTIFICATE_POLICIES_RAW {
let ParsedExtension::CertificatePolicies(cp) = ext.parsed_extension() else {
return Err(ResourceCertificateParseError::Parse(
"certificatePolicies parse failed".into(),
));
};
let oids: Vec<String> = cp.iter().map(|p| p.policy_id.to_id_string()).collect();
let mut oids: Vec<String> = Vec::with_capacity(cp.len());
for p in cp.iter() {
let b = p.policy_id.as_bytes();
if b == OID_CP_IPADDR_ASNUMBER_RAW {
oids.push(OID_CP_IPADDR_ASNUMBER.to_string());
} else {
oids.push(p.policy_id.to_id_string());
}
}
cert_policies.push((oids, ext.critical));
}
OID_IP_ADDR_BLOCKS => {
} else if oid == OID_IP_ADDR_BLOCKS_RAW {
let parsed = IpResourceSet::decode_extn_value(ext.value)
.map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
ip_resources.push((parsed, ext.critical));
}
OID_AUTONOMOUS_SYS_IDS => {
} else if oid == OID_AUTONOMOUS_SYS_IDS_RAW {
let parsed = AsResourceSet::decode_extn_value(ext.value)
.map_err(|_e| ResourceCertificateParseError::InvalidAsResourcesEncoding)?;
as_resources.push((parsed, ext.critical));
}
_ => {}
}
}
@@ -1001,12 +1046,11 @@ fn parse_extensions_parse(
fn parse_aia_parse(
access: &[x509_parser::extensions::AccessDescription<'_>],
) -> Result<AuthorityInfoAccessParsed, ResourceCertificateParseError> {
let mut ca_issuers_uris: Vec<Url> = Vec::new();
let mut ca_issuers_uris: Vec<String> = Vec::new();
let mut ca_issuers_access_location_not_uri = false;
for ad in access {
let access_method_oid = ad.access_method.to_id_string();
if access_method_oid != OID_AD_CA_ISSUERS {
if ad.access_method.as_bytes() != OID_AD_CA_ISSUERS_RAW {
continue;
}
let uri = match &ad.access_location {
@@ -1016,9 +1060,7 @@ fn parse_aia_parse(
continue;
}
};
let url = Url::parse(uri)
.map_err(|_| ResourceCertificateParseError::Parse(format!("invalid URI: {uri}")))?;
ca_issuers_uris.push(url);
ca_issuers_uris.push(uri.to_string());
}
Ok(AuthorityInfoAccessParsed {
@@ -1032,7 +1074,7 @@ fn parse_crldp_parse(
) -> Result<CrlDistributionPointsParsed, ResourceCertificateParseError> {
let mut out: Vec<CrlDistributionPointParsed> = Vec::new();
for p in crldp.iter() {
let mut full_name_uris: Vec<Url> = Vec::new();
let mut full_name_uris: Vec<String> = Vec::new();
let mut full_name_not_uri = false;
let mut full_name_present = false;
let mut name_relative_to_crl_issuer_present = false;
@@ -1046,12 +1088,7 @@ fn parse_crldp_parse(
for n in names {
match n {
x509_parser::extensions::GeneralName::URI(u) => {
let url = Url::parse(u).map_err(|_| {
ResourceCertificateParseError::Parse(format!(
"invalid URI: {u}"
))
})?;
full_name_uris.push(url);
full_name_uris.push(u.to_string());
}
_ => {
full_name_not_uri = true;
@@ -1084,28 +1121,37 @@ fn parse_sia_parse(
access: &[x509_parser::extensions::AccessDescription<'_>],
) -> Result<SubjectInfoAccessParsed, ResourceCertificateParseError> {
let mut all = Vec::with_capacity(access.len());
let mut signed_object_uris: Vec<Url> = Vec::new();
let mut signed_object_uris: Vec<String> = Vec::new();
let mut signed_object_access_location_not_uri = false;
for ad in access {
let access_method_oid = ad.access_method.to_id_string();
let access_method_oid = if ad.access_method.as_bytes() == OID_AD_CA_REPOSITORY_RAW {
OID_AD_CA_REPOSITORY.to_string()
} else if ad.access_method.as_bytes() == OID_AD_RPKI_MANIFEST_RAW {
OID_AD_RPKI_MANIFEST.to_string()
} else if ad.access_method.as_bytes() == OID_AD_RPKI_NOTIFY_RAW {
OID_AD_RPKI_NOTIFY.to_string()
} else if ad.access_method.as_bytes() == OID_AD_SIGNED_OBJECT_RAW {
OID_AD_SIGNED_OBJECT.to_string()
} else {
ad.access_method.to_id_string()
};
let is_signed_object = access_method_oid == OID_AD_SIGNED_OBJECT;
let uri = match &ad.access_location {
x509_parser::extensions::GeneralName::URI(u) => u,
_ => {
if access_method_oid == OID_AD_SIGNED_OBJECT {
if is_signed_object {
signed_object_access_location_not_uri = true;
}
continue;
}
};
let url = Url::parse(uri)
.map_err(|_| ResourceCertificateParseError::Parse(format!("invalid URI: {uri}")))?;
if access_method_oid == OID_AD_SIGNED_OBJECT {
signed_object_uris.push(url.clone());
if is_signed_object {
signed_object_uris.push(uri.to_string());
}
all.push(AccessDescription {
access_method_oid,
access_location: url,
access_location: uri.to_string(),
});
}

View File

@@ -1,10 +1,9 @@
use crate::data_model::oid::OID_CT_ROUTE_ORIGIN_AUTHZ;
use crate::data_model::common::{DerReader, der_take_tlv};
use crate::data_model::rc::{Afi as RcAfi, IpPrefix as RcIpPrefix, ResourceCertificate};
use crate::data_model::signed_object::{
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
};
use der_parser::ber::{BerObjectContent, Class};
use der_parser::der::{DerObject, Tag, parse_der};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RoaObject {
@@ -229,14 +228,25 @@ pub struct IpPrefix {
pub afi: RoaAfi,
/// Prefix length in bits.
pub prefix_len: u16,
/// Network order address bytes (IPv4 4 bytes / IPv6 16 bytes), with host bits cleared.
pub addr: Vec<u8>,
/// Network order address bytes (always 16 bytes), with host bits cleared.
///
/// For IPv4 prefixes, only the first 4 bytes are used and the remaining 12 bytes are zero.
pub addr: [u8; 16],
}
impl IpPrefix {
pub fn addr_bytes(&self) -> &[u8] {
match self.afi {
RoaAfi::Ipv4 => &self.addr[..4],
RoaAfi::Ipv6 => &self.addr[..16],
}
}
}
impl RoaEContent {
/// Parse step of scheme A (`parse → validate → verify`).
pub fn parse_der(der: &[u8]) -> Result<RoaEContentParsed, RoaParseError> {
let (rem, _obj) = parse_der(der).map_err(|e| RoaParseError::Parse(e.to_string()))?;
let (_tag, _value, rem) = der_take_tlv(der).map_err(RoaParseError::Parse)?;
if !rem.is_empty() {
return Err(RoaParseError::TrailingBytes(rem.len()));
}
@@ -293,7 +303,7 @@ impl RoaEContent {
if !ip.contains_prefix(&rc_prefix) {
return Err(RoaValidateError::PrefixNotInEeResources {
afi: entry.prefix.afi,
addr: entry.prefix.addr.clone(),
addr: entry.prefix.addr_bytes().to_vec(),
prefix_len: entry.prefix.prefix_len,
});
}
@@ -328,55 +338,75 @@ impl RoaObjectParsed {
impl RoaEContentParsed {
pub fn validate_profile(self) -> Result<RoaEContent, RoaProfileError> {
let (_rem, obj) =
parse_der(&self.der).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
let seq = obj
.as_sequence()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
if seq.len() != 2 && seq.len() != 3 {
return Err(RoaProfileError::InvalidAttestationSequenceLen(seq.len()));
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
let mut n = 0usize;
while !r.is_empty() {
r.skip_any()?;
n += 1;
}
Ok(n)
}
let mut r = DerReader::new(&self.der);
let mut seq = r
.take_sequence()
.map_err(|e| RoaProfileError::ProfileDecode(e))?;
if !r.is_empty() {
return Err(RoaProfileError::ProfileDecode(
"trailing bytes after RouteOriginAttestation".into(),
));
}
let elem_count =
count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
if elem_count != 2 && elem_count != 3 {
return Err(RoaProfileError::InvalidAttestationSequenceLen(elem_count));
}
let mut idx = 0;
let mut version: u32 = 0;
if seq.len() == 3 {
let v_obj = &seq[0];
if v_obj.class() != Class::ContextSpecific || v_obj.tag() != Tag(0) {
if elem_count == 3 {
if seq
.peek_tag()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?
!= 0xA0
{
return Err(RoaProfileError::ProfileDecode(
"RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
));
}
let inner_der = v_obj
.as_slice()
let (inner_tag, inner_val) = seq
.take_explicit(0xA0)
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
let (rem, inner) =
parse_der(inner_der).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
if !rem.is_empty() {
if inner_tag != 0x02 {
return Err(RoaProfileError::ProfileDecode(
"trailing bytes inside RouteOriginAttestation.version".into(),
"RouteOriginAttestation.version must be [0] EXPLICIT INTEGER".into(),
));
}
let v = inner
.as_u64()
let v = crate::data_model::common::der_uint_from_bytes(inner_val)
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
if v != 0 {
return Err(RoaProfileError::InvalidVersion(v));
}
version = 0;
idx = 1;
}
let as_id_u64 = seq[idx]
.as_u64()
let as_id_u64 = seq
.take_uint_u64()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
if as_id_u64 > u32::MAX as u64 {
return Err(RoaProfileError::AsIdOutOfRange(as_id_u64));
}
let as_id = as_id_u64 as u32;
idx += 1;
let ip_addr_blocks = parse_ip_addr_blocks_cursor(seq.take_sequence().map_err(|e| {
RoaProfileError::ProfileDecode(format!("ipAddrBlocks: {e}"))
})?)?;
let ip_addr_blocks = parse_ip_addr_blocks(&seq[idx])?;
if !seq.is_empty() {
// Extra elements beyond the expected 2..3.
let extra =
count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
return Err(RoaProfileError::InvalidAttestationSequenceLen(elem_count + extra));
}
let mut out = RoaEContent {
version,
@@ -396,21 +426,34 @@ fn roa_prefix_to_rc(p: &IpPrefix) -> RcIpPrefix {
RcIpPrefix {
afi,
prefix_len: p.prefix_len,
addr: p.addr.clone(),
addr: p.addr_bytes().to_vec(),
}
}
fn parse_ip_addr_blocks(obj: &DerObject<'_>) -> Result<Vec<RoaIpAddressFamily>, RoaProfileError> {
let seq = obj
.as_sequence()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
if seq.is_empty() || seq.len() > 2 {
return Err(RoaProfileError::InvalidIpAddrBlocksLen(seq.len()));
fn parse_ip_addr_blocks_cursor(
mut seq: DerReader<'_>,
) -> Result<Vec<RoaIpAddressFamily>, RoaProfileError> {
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
let mut n = 0usize;
while !r.is_empty() {
r.skip_any()?;
n += 1;
}
Ok(n)
}
let mut out: Vec<RoaIpAddressFamily> = Vec::new();
for fam in seq {
let family = parse_ip_address_family(fam)?;
let fam_count =
count_elements(seq).map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
if fam_count == 0 || fam_count > 2 {
return Err(RoaProfileError::InvalidIpAddrBlocksLen(fam_count));
}
let mut out: Vec<RoaIpAddressFamily> = Vec::with_capacity(fam_count);
while !seq.is_empty() {
let family = parse_ip_address_family_cursor(
seq.take_sequence()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?,
)?;
if out.iter().any(|f| f.afi == family.afi) {
return Err(RoaProfileError::DuplicateAfi(family.afi));
}
@@ -419,77 +462,75 @@ fn parse_ip_addr_blocks(obj: &DerObject<'_>) -> Result<Vec<RoaIpAddressFamily>,
Ok(out)
}
fn parse_ip_address_family(obj: &DerObject<'_>) -> Result<RoaIpAddressFamily, RoaProfileError> {
let seq = obj
.as_sequence()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
if seq.len() != 2 {
fn parse_ip_address_family_cursor(
mut fam: DerReader<'_>,
) -> Result<RoaIpAddressFamily, RoaProfileError> {
let afi = {
let bytes = fam
.take_octet_string()
.map_err(|_e| RoaProfileError::InvalidAddressFamily)?;
if bytes.len() != 2 {
return Err(RoaProfileError::InvalidAddressFamily);
}
match bytes {
[0x00, 0x01] => RoaAfi::Ipv4,
[0x00, 0x02] => RoaAfi::Ipv6,
_ => return Err(RoaProfileError::UnsupportedAfi(bytes.to_vec())),
}
};
let mut addrs = fam
.take_sequence()
.map_err(|_e| RoaProfileError::InvalidIpAddressFamily)?;
if !fam.is_empty() {
return Err(RoaProfileError::InvalidIpAddressFamily);
}
let afi = parse_afi(&seq[0])?;
let addresses = parse_roa_addresses(afi, &seq[1])?;
if addresses.is_empty() {
if addrs.is_empty() {
return Err(RoaProfileError::EmptyAddressList);
}
let mut addresses: Vec<RoaIpAddress> = Vec::new();
while !addrs.is_empty() {
let entry = addrs
.take_sequence()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
addresses.push(parse_roa_ip_address_cursor(afi, entry)?);
}
Ok(RoaIpAddressFamily { afi, addresses })
}
fn parse_afi(obj: &DerObject<'_>) -> Result<RoaAfi, RoaProfileError> {
let bytes = obj
.as_slice()
.map_err(|_e| RoaProfileError::InvalidAddressFamily)?;
if bytes.len() != 2 {
return Err(RoaProfileError::InvalidAddressFamily);
}
match bytes {
[0x00, 0x01] => Ok(RoaAfi::Ipv4),
[0x00, 0x02] => Ok(RoaAfi::Ipv6),
_ => Err(RoaProfileError::UnsupportedAfi(bytes.to_vec())),
}
}
fn parse_roa_addresses(
fn parse_roa_ip_address_cursor(
afi: RoaAfi,
obj: &DerObject<'_>,
) -> Result<Vec<RoaIpAddress>, RoaProfileError> {
let seq = obj
.as_sequence()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
let mut out = Vec::with_capacity(seq.len());
for entry in seq {
out.push(parse_roa_ip_address(afi, entry)?);
}
Ok(out)
}
fn parse_roa_ip_address(afi: RoaAfi, obj: &DerObject<'_>) -> Result<RoaIpAddress, RoaProfileError> {
let seq = obj
.as_sequence()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
if seq.is_empty() || seq.len() > 2 {
mut seq: DerReader<'_>,
) -> Result<RoaIpAddress, RoaProfileError> {
if seq.is_empty() {
return Err(RoaProfileError::InvalidRoaIpAddress);
}
let prefix = parse_prefix_bits(afi, &seq[0])?;
let max_length = match seq.get(1) {
None => None,
Some(m) => {
let v = m
.as_u64()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
let max_len: u16 = v
.try_into()
.map_err(|_e| RoaProfileError::InvalidMaxLength {
afi,
prefix_len: prefix.prefix_len,
max_len: u16::MAX,
})?;
Some(max_len)
}
let (unused_bits, bytes) = seq
.take_bit_string()
.map_err(|_e| RoaProfileError::InvalidPrefixBitString)?;
let prefix = parse_prefix_bits_bytes(afi, unused_bits, bytes)?;
let max_length = if !seq.is_empty() {
let v = seq
.take_uint_u64()
.map_err(|e| RoaProfileError::ProfileDecode(e.to_string()))?;
let max_len: u16 = v.try_into().map_err(|_e| RoaProfileError::InvalidMaxLength {
afi,
prefix_len: prefix.prefix_len,
max_len: u16::MAX,
})?;
Some(max_len)
} else {
None
};
if !seq.is_empty() {
return Err(RoaProfileError::InvalidRoaIpAddress);
}
if let Some(max_len) = max_length {
let ub = afi.ub();
if max_len > ub || max_len < prefix.prefix_len {
@@ -504,12 +545,11 @@ fn parse_roa_ip_address(afi: RoaAfi, obj: &DerObject<'_>) -> Result<RoaIpAddress
Ok(RoaIpAddress { prefix, max_length })
}
fn parse_prefix_bits(afi: RoaAfi, obj: &DerObject<'_>) -> Result<IpPrefix, RoaProfileError> {
let (unused_bits, bytes) = match &obj.content {
BerObjectContent::BitString(unused, bso) => (*unused, bso.data.to_vec()),
_ => return Err(RoaProfileError::InvalidPrefixBitString),
};
fn parse_prefix_bits_bytes(
afi: RoaAfi,
unused_bits: u8,
bytes: &[u8],
) -> Result<IpPrefix, RoaProfileError> {
if unused_bits > 7 {
return Err(RoaProfileError::InvalidPrefixUnusedBits);
}
@@ -531,7 +571,7 @@ fn parse_prefix_bits(afi: RoaAfi, obj: &DerObject<'_>) -> Result<IpPrefix, RoaPr
return Err(RoaProfileError::PrefixLenOutOfRange { afi, prefix_len });
}
let addr = canonicalize_prefix_addr(afi, prefix_len, &bytes);
let addr = canonicalize_prefix_addr(afi, prefix_len, bytes);
Ok(IpPrefix {
afi,
prefix_len,
@@ -539,12 +579,12 @@ fn parse_prefix_bits(afi: RoaAfi, obj: &DerObject<'_>) -> Result<IpPrefix, RoaPr
})
}
fn canonicalize_prefix_addr(afi: RoaAfi, prefix_len: u16, bytes: &[u8]) -> Vec<u8> {
fn canonicalize_prefix_addr(afi: RoaAfi, prefix_len: u16, bytes: &[u8]) -> [u8; 16] {
let full_len = match afi {
RoaAfi::Ipv4 => 4,
RoaAfi::Ipv6 => 16,
};
let mut addr = vec![0u8; full_len];
let mut addr = [0u8; 16];
let copy_len = bytes.len().min(full_len);
addr[..copy_len].copy_from_slice(&bytes[..copy_len]);
@@ -557,7 +597,7 @@ fn canonicalize_prefix_addr(afi: RoaAfi, prefix_len: u16, bytes: &[u8]) -> Vec<u
let rem = (prefix_len % 8) as u8;
if rem != 0 {
let mask: u8 = 0xFF << (8 - rem);
if last_prefix_byte < addr.len() {
if last_prefix_byte < full_len {
addr[last_prefix_byte] &= mask;
}
}

View File

@@ -1,12 +1,14 @@
use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc};
use crate::data_model::common::{der_take_tlv, Asn1TimeEncoding, Asn1TimeUtc, DerReader};
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,
OID_CMS_ATTR_CONTENT_TYPE_RAW, OID_CMS_ATTR_MESSAGE_DIGEST_RAW, OID_CMS_ATTR_SIGNING_TIME,
OID_CMS_ATTR_SIGNING_TIME_RAW, OID_CT_ASPA, OID_CT_ASPA_RAW, OID_CT_RPKI_MANIFEST,
OID_CT_RPKI_MANIFEST_RAW, OID_CT_ROUTE_ORIGIN_AUTHZ, OID_CT_ROUTE_ORIGIN_AUTHZ_RAW,
OID_RSA_ENCRYPTION, OID_RSA_ENCRYPTION_RAW, OID_SHA256, OID_SHA256_RAW,
OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SIGNED_DATA,
OID_SIGNED_DATA_RAW, 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;
@@ -303,24 +305,22 @@ 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> {
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 mut r = DerReader::new(der);
let mut content_info_seq = r
.take_sequence()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
if !r.is_empty() {
return Err(SignedObjectParseError::TrailingBytes(r.remaining_len()));
}
let content_info_seq = obj
.as_sequence()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
if content_info_seq.len() != 2 {
let content_type = take_oid_string(&mut content_info_seq)?;
let signed_data = parse_signed_data_from_contentinfo_cursor(&mut content_info_seq)?;
if !content_info_seq.is_empty() {
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,
@@ -411,90 +411,93 @@ impl RpkiSignedObjectParsed {
}
}
fn parse_signed_data_from_contentinfo_parse(
obj: &DerObject<'_>,
fn parse_signed_data_from_contentinfo_cursor(
seq: &mut DerReader<'_>,
) -> 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()
let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
SignedObjectParseError::Parse("ContentInfo.content must be [0] EXPLICIT".into())
})?;
let mut r = DerReader::new(inner_der);
let signed_data_seq = r
.take_sequence()
.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() {
if !r.is_empty() {
return Err(SignedObjectParseError::Parse(
"trailing bytes inside ContentInfo.content".into(),
));
}
parse_signed_data_parse(&inner_obj)
parse_signed_data_cursor(signed_data_seq)
}
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()
fn parse_signed_data_cursor(mut seq: DerReader<'_>) -> Result<SignedDataParsed, SignedObjectParseError> {
let version = seq
.take_uint_u64()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
let digest_set = seq[1]
.as_set()
let digest_set_bytes = seq
.take_tag(0x31)
.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)?;
let mut digest_set = DerReader::new(digest_set_bytes);
let mut digest_algorithms: Vec<AlgorithmIdentifierParsed> = Vec::new();
while !digest_set.is_empty() {
let alg = digest_set
.take_sequence()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
let (oid, params_ok) = parse_algorithm_identifier_cursor(alg)?;
digest_algorithms.push(AlgorithmIdentifierParsed { oid, params_ok });
}
let encap_content_info = parse_encapsulated_content_info_parse(&seq[2])?;
let encap_content_info = parse_encapsulated_content_info_cursor(
seq.take_sequence()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?,
)?;
let mut certificates: Option<Vec<Vec<u8>>> = None;
let mut crls_present = false;
let mut signer_infos_obj: Option<&DerObject<'_>> = None;
let mut signer_infos: Option<Vec<SignerInfoParsed>> = None;
for item in &seq[3..] {
if item.class() == Class::ContextSpecific && item.tag() == Tag(0) {
if certificates.is_some() {
while !seq.is_empty() {
let tag = seq
.peek_tag()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
match tag {
0xA0 => {
if certificates.is_some() {
return Err(SignedObjectParseError::Parse(
"SignedData.certificates appears more than once".into(),
));
}
let content = seq
.take_tag(0xA0)
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
certificates = Some(split_der_objects(content)?);
}
0xA1 => {
crls_present = true;
seq.skip_any()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
}
0x31 => {
if signer_infos.is_some() {
return Err(SignedObjectParseError::Parse(
"SignedData.signerInfos appears more than once".into(),
));
}
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)?);
}
_ => {
return Err(SignedObjectParseError::Parse(
"SignedData.certificates appears more than once".into(),
"unexpected field in SignedData".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)?);
}
let signer_infos =
signer_infos.ok_or_else(|| SignedObjectParseError::Parse("SignedData.signerInfos missing".into()))?;
Ok(SignedDataParsed {
version,
@@ -506,46 +509,39 @@ fn parse_signed_data_parse(
})
}
fn parse_encapsulated_content_info_parse(
obj: &DerObject<'_>,
fn parse_encapsulated_content_info_cursor(
mut seq: DerReader<'_>,
) -> Result<EncapsulatedContentInfoParsed, SignedObjectParseError> {
let seq = obj
.as_sequence()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
if seq.is_empty() || seq.len() > 2 {
if seq.is_empty() {
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(),
)
let econtent_type = take_oid_string(&mut seq)?;
let econtent = if seq.is_empty() {
None
} else {
let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
SignedObjectParseError::Parse("EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into())
})?;
let mut inner = DerReader::new(inner_der);
let octets = inner
.take_octet_string()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
if !inner.is_empty() {
return Err(SignedObjectParseError::Parse(
"trailing bytes inside EncapsulatedContentInfo.eContent".into(),
));
}
Some(octets.to_vec())
};
if !seq.is_empty() {
return Err(SignedObjectParseError::Parse(
"EncapsulatedContentInfo must be SEQUENCE of 1..2".into(),
));
}
Ok(EncapsulatedContentInfoParsed {
econtent_type,
@@ -553,22 +549,28 @@ fn parse_encapsulated_content_info_parse(
})
}
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();
fn split_der_objects(mut input: &[u8]) -> Result<Vec<Vec<u8>>, SignedObjectParseError> {
let mut out: Vec<Vec<u8>> = Vec::new();
while !input.is_empty() {
let (rem, _any_obj) =
parse_der(input).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
let (_tag, _value, rem) =
der_take_tlv(input).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
let consumed = input.len() - rem.len();
certs.push(input[..consumed].to_vec());
out.push(input[..consumed].to_vec());
input = rem;
}
Ok(certs)
Ok(out)
}
fn parse_signer_infos_set_cursor(set_bytes: &[u8]) -> Result<Vec<SignerInfoParsed>, SignedObjectParseError> {
let mut set = DerReader::new(set_bytes);
let mut out: Vec<SignerInfoParsed> = Vec::new();
while !set.is_empty() {
let si = set
.take_sequence()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
out.push(parse_signer_info_cursor(si)?);
}
Ok(out)
}
fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedObjectValidateError> {
@@ -603,11 +605,7 @@ fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedOb
.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::Ee(ee) => ee.signed_object_uris.clone(),
SubjectInfoAccess::Ca(_ca) => Vec::new(),
};
if signed_object_uris.is_empty() {
@@ -626,69 +624,64 @@ fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedOb
})
}
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()
fn parse_signer_info_cursor(mut seq: DerReader<'_>) -> Result<SignerInfoParsed, SignedObjectParseError> {
let version = seq
.take_uint_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)
let (sid_tag, sid_bytes) = seq
.take_any()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
let sid = if (sid_tag & 0xC0) == 0x80 && (sid_tag & 0x1F) == 0 {
SignerIdentifierParsed::SubjectKeyIdentifier(sid_bytes.to_vec())
} else {
SignerIdentifierParsed::Other
};
let (digest_oid, digest_params_ok) = parse_algorithm_identifier_parse(&seq[2])?;
let digest_alg_seq = seq
.take_sequence()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
let (digest_oid, digest_params_ok) = parse_algorithm_identifier_cursor(digest_alg_seq)?;
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;
if seq
.peek_tag()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
== 0xA0
{
let (tag, full_tlv, value) = seq
.take_any_full()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
if tag != 0xA0 {
return Err(SignedObjectParseError::Parse(
"SignerInfo.signedAttrs must be [0] IMPLICIT".into(),
));
}
signed_attrs_content = Some(value.to_vec());
signed_attrs_der_for_signature = Some(make_signed_attrs_der_for_signature(full_tlv)?);
}
let (signature_oid, signature_params_ok) = parse_algorithm_identifier_parse(&seq[idx])?;
let sig_alg_seq = seq
.take_sequence()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
let (signature_oid, signature_params_ok) = parse_algorithm_identifier_cursor(sig_alg_seq)?;
let signature_algorithm = AlgorithmIdentifierParsed {
oid: signature_oid,
params_ok: signature_params_ok,
};
idx += 1;
let signature = seq[idx]
.as_slice()
let signature = seq
.take_octet_string()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?
.to_vec();
idx += 1;
let unsigned_attrs_present = seq.get(idx).is_some();
let unsigned_attrs_present = !seq.is_empty();
Ok(SignerInfoParsed {
version,
@@ -696,9 +689,9 @@ fn parse_signer_info_parse(
digest_algorithm,
signature_algorithm,
signed_attrs_content,
signed_attrs_der_for_signature,
unsigned_attrs_present,
signature,
signed_attrs_der_for_signature,
})
}
@@ -843,58 +836,90 @@ fn parse_signed_attrs_implicit(
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;
fn count_elements(mut r: DerReader<'_>) -> Result<usize, String> {
let mut n = 0usize;
while !r.is_empty() {
r.skip_any()?;
n += 1;
}
Ok(n)
}
let attr_seq = attr_obj
.as_sequence()
let mut remaining = DerReader::new(input);
while !remaining.is_empty() {
let mut attr = remaining
.take_sequence()
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
if attr_seq.len() != 2 {
let oid_bytes = attr
.take_tag(0x06)
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
let oid = oid_value_bytes_to_string(oid_bytes);
let values_bytes = attr
.take_tag(0x31)
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
if !attr.is_empty() {
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(),
},
);
let mut values = DerReader::new(values_bytes);
let count = if values.is_empty() {
0
} else {
values
.skip_any()
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
if values.is_empty() {
1
} else {
1 + count_elements(values)
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?
}
};
if count != 1 {
return Err(SignedObjectValidateError::InvalidSignedAttributeValuesCount {
oid,
count,
});
}
// Re-parse the sole value.
let mut values = DerReader::new(values_bytes);
let (val_tag, val_bytes) = values
.take_any()
.map_err(|e| SignedObjectValidateError::SignedAttrsParse(e.to_string()))?;
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);
if val_tag != 0x06 {
return Err(SignedObjectValidateError::SignedAttrsParse(
"content-type attr value must be OBJECT IDENTIFIER".into(),
));
}
content_type = Some(oid_value_bytes_to_string(val_bytes));
}
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);
if val_tag != 0x04 {
return Err(SignedObjectValidateError::SignedAttrsParse(
"message-digest attr value must be OCTET STRING".into(),
));
}
message_digest = Some(val_bytes.to_vec());
}
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])?);
signing_time = Some(parse_signing_time_value_tlv(val_tag, val_bytes)?);
}
_ => {
return Err(SignedObjectValidateError::UnsupportedSignedAttribute(oid));
@@ -913,35 +938,27 @@ fn parse_signed_attrs_implicit(
})
}
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)?,
fn parse_signing_time_value_tlv(tag: u8, value: &[u8]) -> Result<Asn1TimeUtc, SignedObjectValidateError> {
match tag {
0x17 => Ok(Asn1TimeUtc {
utc: parse_utctime(value)?,
encoding: Asn1TimeEncoding::UtcTime,
}),
der_parser::ber::BerObjectContent::GeneralizedTime(dt) => Ok(Asn1TimeUtc {
utc: dt
.to_datetime()
.map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?,
0x18 => Ok(Asn1TimeUtc {
utc: parse_generalized_time(value)?,
encoding: Asn1TimeEncoding::GeneralizedTime,
}),
_ => Err(SignedObjectValidateError::InvalidSigningTimeValue),
}
}
fn make_signed_attrs_der_for_signature_parse(
obj: &DerObject<'_>,
) -> Result<Vec<u8>, SignedObjectParseError> {
fn make_signed_attrs_der_for_signature(full_tlv: &[u8]) -> 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()))?;
let mut cs_der = full_tlv.to_vec();
if cs_der.is_empty() {
return Err(SignedObjectParseError::Parse(
"signedAttrs encoding is empty".into(),
@@ -953,32 +970,162 @@ fn make_signed_attrs_der_for_signature_parse(
Ok(cs_der)
}
fn oid_to_string_parse(obj: &DerObject<'_>) -> Result<String, SignedObjectParseError> {
let oid = obj
.as_oid()
fn take_oid_string(seq: &mut DerReader<'_>) -> Result<String, SignedObjectParseError> {
let oid = seq
.take_tag(0x06)
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
Ok(oid.to_id_string())
Ok(oid_value_bytes_to_string(oid))
}
fn parse_algorithm_identifier_parse(
obj: &DerObject<'_>,
fn oid_value_bytes_to_string(oid_value: &[u8]) -> String {
if oid_value == OID_SHA256_RAW {
return OID_SHA256.to_string();
}
if oid_value == OID_SIGNED_DATA_RAW {
return OID_SIGNED_DATA.to_string();
}
if oid_value == OID_CMS_ATTR_CONTENT_TYPE_RAW {
return OID_CMS_ATTR_CONTENT_TYPE.to_string();
}
if oid_value == OID_CMS_ATTR_MESSAGE_DIGEST_RAW {
return OID_CMS_ATTR_MESSAGE_DIGEST.to_string();
}
if oid_value == OID_CMS_ATTR_SIGNING_TIME_RAW {
return OID_CMS_ATTR_SIGNING_TIME.to_string();
}
if oid_value == OID_RSA_ENCRYPTION_RAW {
return OID_RSA_ENCRYPTION.to_string();
}
if oid_value == OID_SHA256_WITH_RSA_ENCRYPTION_RAW {
return OID_SHA256_WITH_RSA_ENCRYPTION.to_string();
}
if oid_value == OID_CT_RPKI_MANIFEST_RAW {
return OID_CT_RPKI_MANIFEST.to_string();
}
if oid_value == OID_CT_ROUTE_ORIGIN_AUTHZ_RAW {
return OID_CT_ROUTE_ORIGIN_AUTHZ.to_string();
}
if oid_value == OID_CT_ASPA_RAW {
return OID_CT_ASPA.to_string();
}
decode_oid_to_dotted_string(oid_value)
}
fn decode_oid_to_dotted_string(value: &[u8]) -> String {
if value.is_empty() {
return "<empty-oid>".into();
}
let first = value[0];
let a = (first / 40) as u32;
let b = (first % 40) as u32;
let mut out = String::new();
out.push_str(&a.to_string());
out.push('.');
out.push_str(&b.to_string());
let mut idx = 1usize;
while idx < value.len() {
let mut v: u32 = 0;
loop {
if idx >= value.len() {
out.push_str(".<truncated>");
return out;
}
let byte = value[idx];
idx += 1;
v = (v << 7) | (byte as u32 & 0x7F);
if (byte & 0x80) == 0 {
break;
}
}
out.push('.');
out.push_str(&v.to_string());
}
out
}
fn parse_algorithm_identifier_cursor(
mut seq: DerReader<'_>,
) -> Result<(String, bool), SignedObjectParseError> {
let seq = obj
.as_sequence()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
if seq.is_empty() || seq.len() > 2 {
if seq.is_empty() {
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),
let oid = take_oid_string(&mut seq)?;
let params_ok = if seq.is_empty() {
true
} else {
let (tag, value) = seq
.take_any()
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
tag == 0x05 && value.is_empty()
};
if !seq.is_empty() {
return Err(SignedObjectParseError::Parse(
"AlgorithmIdentifier must be SEQUENCE of 1..2".into(),
));
}
Ok((oid, params_ok))
}
fn parse_utctime(value: &[u8]) -> Result<time::OffsetDateTime, SignedObjectValidateError> {
let s = std::str::from_utf8(value).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
if !s.ends_with('Z') {
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
}
let digits = &s[..s.len() - 1];
if digits.len() != 10 && digits.len() != 12 {
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
}
if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) {
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
}
let yy: i32 = digits[0..2].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let year = if yy <= 49 { 2000 + yy } else { 1900 + yy };
let mon: u8 = digits[2..4].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let day: u8 = digits[4..6].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let hour: u8 = digits[6..8].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let min: u8 = digits[8..10].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let sec: u8 = if digits.len() == 12 {
digits[10..12].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?
} else {
0
};
let month = time::Month::try_from(mon).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let date = time::Date::from_calendar_date(year, month, day).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let time = time::Time::from_hms(hour, min, sec).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
Ok(time::OffsetDateTime::new_utc(date, time))
}
fn parse_generalized_time(value: &[u8]) -> Result<time::OffsetDateTime, SignedObjectValidateError> {
let s = std::str::from_utf8(value).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
if !s.ends_with('Z') {
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
}
let digits = &s[..s.len() - 1];
if digits.len() != 12 && digits.len() != 14 {
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
}
if !digits.as_bytes().iter().all(|b| b.is_ascii_digit()) {
return Err(SignedObjectValidateError::InvalidSigningTimeValue);
}
let year: i32 = digits[0..4].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let mon: u8 = digits[4..6].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let day: u8 = digits[6..8].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let hour: u8 = digits[8..10].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let min: u8 = digits[10..12].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let sec: u8 = if digits.len() == 14 {
digits[12..14].parse().map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?
} else {
0
};
let month = time::Month::try_from(mon).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let date = time::Date::from_calendar_date(year, month, day).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
let time = time::Time::from_hms(hour, min, sec).map_err(|_| SignedObjectValidateError::InvalidSigningTimeValue)?;
Ok(time::OffsetDateTime::new_utc(date, time))
}
fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
let mut idx = 0;
while idx < bytes.len() && bytes[idx] == 0 {

View File

@@ -211,7 +211,7 @@ impl TaCertificateParsed {
return Err(TaCertificateProfileError::NotCa);
}
if rc_ca.tbs.issuer_dn != rc_ca.tbs.subject_dn {
if rc_ca.tbs.issuer_name != rc_ca.tbs.subject_name {
return Err(TaCertificateProfileError::NotSelfSignedIssuerSubject);
}

View File

@@ -78,23 +78,23 @@ pub fn ca_instance_uris_from_ca_certificate(
for ad in access_descriptions {
if ad.access_method_oid == OID_AD_CA_REPOSITORY {
let u = ad.access_location.to_string();
if ad.access_location.scheme() != "rsync" {
return Err(CaInstanceUrisError::CaRepositoryNotRsync(u));
let u = ad.access_location.as_str();
if !u.starts_with("rsync://") {
return Err(CaInstanceUrisError::CaRepositoryNotRsync(u.to_string()));
}
ca_repo.get_or_insert(u);
ca_repo.get_or_insert(u.to_string());
} else if ad.access_method_oid == OID_AD_RPKI_MANIFEST {
let u = ad.access_location.to_string();
if ad.access_location.scheme() != "rsync" {
return Err(CaInstanceUrisError::RpkiManifestNotRsync(u));
let u = ad.access_location.as_str();
if !u.starts_with("rsync://") {
return Err(CaInstanceUrisError::RpkiManifestNotRsync(u.to_string()));
}
manifest.get_or_insert(u);
manifest.get_or_insert(u.to_string());
} else if ad.access_method_oid == OID_AD_RPKI_NOTIFY {
let u = ad.access_location.to_string();
if ad.access_location.scheme() != "https" {
return Err(CaInstanceUrisError::RpkiNotifyNotHttps(u));
let u = ad.access_location.as_str();
if !u.starts_with("https://") {
return Err(CaInstanceUrisError::RpkiNotifyNotHttps(u.to_string()));
}
notify.get_or_insert(u);
notify.get_or_insert(u.to_string());
}
}

View File

@@ -1,6 +1,6 @@
use crate::data_model::common::BigUnsigned;
use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
use crate::data_model::oid::OID_KEY_USAGE;
use crate::data_model::oid::OID_KEY_USAGE_RAW;
use crate::data_model::rc::{
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
ResourceCertificate, ResourceCertificateDecodeError,
@@ -135,10 +135,10 @@ pub fn validate_subordinate_ca_cert(
return Err(CaPathError::IssuerNotCa);
}
if child_ca.tbs.issuer_dn != issuer_ca.tbs.subject_dn {
if child_ca.tbs.issuer_name != issuer_ca.tbs.subject_name {
return Err(CaPathError::IssuerSubjectMismatch {
child_issuer_dn: child_ca.tbs.issuer_dn.clone(),
issuer_subject_dn: issuer_ca.tbs.subject_dn.clone(),
child_issuer_dn: child_ca.tbs.issuer_name.to_string(),
issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
});
}
@@ -247,7 +247,7 @@ fn validate_child_ca_key_usage(child_ca_der: &[u8]) -> Result<(), CaPathError> {
let mut ku_critical: Option<bool> = None;
for ext in cert.extensions() {
if ext.oid.to_id_string() == OID_KEY_USAGE {
if ext.oid.as_bytes() == OID_KEY_USAGE_RAW {
ku_critical = Some(ext.critical);
break;
}
@@ -695,9 +695,8 @@ mod tests {
use crate::data_model::rc::{
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
};
use crate::data_model::common::X509NameDer;
use der_parser::num_bigint::BigUint;
use url::Url;
fn dummy_cert(
kind: ResourceCertKind,
subject_dn: &str,
@@ -707,16 +706,8 @@ mod tests {
aia: Option<Vec<&str>>,
crldp: Option<Vec<&str>>,
) -> ResourceCertificate {
let aia = aia.map(|v| {
v.into_iter()
.map(|s| Url::parse(s).expect("url"))
.collect::<Vec<_>>()
});
let crldp = crldp.map(|v| {
v.into_iter()
.map(|s| Url::parse(s).expect("url"))
.collect::<Vec<_>>()
});
let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
ResourceCertificate {
raw_der: Vec::new(),
@@ -725,8 +716,8 @@ mod tests {
version: 2,
serial_number: BigUint::from(1u8),
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
issuer_dn: issuer_dn.to_string(),
subject_dn: subject_dn.to_string(),
issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()),
subject_name: X509NameDer(subject_dn.as_bytes().to_vec()),
validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
subject_public_key_info: Vec::new(),

View File

@@ -112,10 +112,10 @@ pub fn validate_ee_cert_path(
return Err(CertPathError::IssuerNotCa);
}
if ee.tbs.issuer_dn != issuer_ca.tbs.subject_dn {
if ee.tbs.issuer_name != issuer_ca.tbs.subject_name {
return Err(CertPathError::IssuerSubjectMismatch {
ee_issuer_dn: ee.tbs.issuer_dn.clone(),
issuer_subject_dn: issuer_ca.tbs.subject_dn.clone(),
ee_issuer_dn: ee.tbs.issuer_name.to_string(),
issuer_subject_dn: issuer_ca.tbs.subject_name.to_string(),
});
}
@@ -212,7 +212,7 @@ fn validate_ee_key_usage(ee_cert_der: &[u8]) -> Result<(), CertPathError> {
let mut ku_critical: Option<bool> = None;
for ext in cert.extensions() {
if ext.oid.to_id_string() == crate::data_model::oid::OID_KEY_USAGE {
if ext.oid.as_bytes() == crate::data_model::oid::OID_KEY_USAGE_RAW {
ku_critical = Some(ext.critical);
break;
}
@@ -302,8 +302,8 @@ mod tests {
use crate::data_model::rc::{
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
};
use crate::data_model::common::X509NameDer;
use der_parser::num_bigint::BigUint;
use url::Url;
fn dummy_cert(
kind: ResourceCertKind,
@@ -314,16 +314,8 @@ mod tests {
aia: Option<Vec<&str>>,
crldp: Option<Vec<&str>>,
) -> ResourceCertificate {
let aia = aia.map(|v| {
v.into_iter()
.map(|s| Url::parse(s).expect("url"))
.collect::<Vec<_>>()
});
let crldp = crldp.map(|v| {
v.into_iter()
.map(|s| Url::parse(s).expect("url"))
.collect::<Vec<_>>()
});
let aia = aia.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
let crldp = crldp.map(|v| v.into_iter().map(|s| s.to_string()).collect::<Vec<_>>());
ResourceCertificate {
raw_der: Vec::new(),
kind,
@@ -331,8 +323,8 @@ mod tests {
version: 2,
serial_number: BigUint::from(1u8),
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
issuer_dn: issuer_dn.to_string(),
subject_dn: subject_dn.to_string(),
issuer_name: X509NameDer(issuer_dn.as_bytes().to_vec()),
subject_name: X509NameDer(subject_dn.as_bytes().to_vec()),
validity_not_before: time::OffsetDateTime::UNIX_EPOCH,
validity_not_after: time::OffsetDateTime::UNIX_EPOCH,
subject_public_key_info: Vec::new(),

View File

@@ -462,7 +462,7 @@ fn process_aspa_with_issuer(
}
fn choose_crl_for_certificate(
crldp_uris: Option<&Vec<url::Url>>,
crldp_uris: Option<&Vec<String>>,
crl_files: &[(String, Vec<u8>)],
) -> Result<(String, Vec<u8>), ObjectValidateError> {
if crl_files.is_empty() {