优化数据对象decode + profile validate;benchmark对比routinator geomean 0.8
This commit is contained in:
19
src/audit.rs
19
src/audit.rs
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
}],
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user