use crate::error::{Error, ErrorCode, Located, Result, gate};
use crate::extension::{self, Extension};
use structured_asn1::memory::{Memory, OwnedVec};
use structured_asn1::{Oid, Parser, Tag, universal};
#[derive(Debug)]
pub struct PolicyInformation<'a> {
policy: Oid<'a>,
qualifiers: OwnedVec<PolicyQualifier<'a>>,
at: u64,
}
impl<'a> PolicyInformation<'a> {
#[must_use]
pub fn policy(&self) -> Oid<'a> {
self.policy
}
#[must_use]
pub fn qualifiers(&self) -> &[PolicyQualifier<'a>] {
&self.qualifiers
}
}
#[derive(Debug, Clone, Copy)]
pub struct PolicyQualifier<'a> {
kind: Oid<'a>,
value: &'a [u8],
at: u64,
outer: u32,
}
impl<'a> PolicyQualifier<'a> {
#[must_use]
pub fn kind(self) -> Oid<'a> {
self.kind
}
#[must_use]
pub fn value(self) -> &'a [u8] {
self.value
}
}
#[derive(Debug)]
pub struct CertificatePolicies<'a> {
policies: OwnedVec<PolicyInformation<'a>>,
}
impl<'a> CertificatePolicies<'a> {
#[must_use]
pub fn policies(&self) -> &[PolicyInformation<'a>] {
&self.policies
}
pub fn has(&self, policy: Oid<'_>, execution: &Memory) -> Result<bool> {
for information in &self.policies {
let work = 1 + information
.policy
.as_bytes()
.len()
.max(policy.as_bytes().len()) as u64;
execution.charge_work(work).map_err(|reason| {
Error::resource(reason, "extensions.certificatePolicies", information.at)
})?;
if information.policy == policy {
return Ok(true);
}
}
Ok(false)
}
pub fn read(extension: &Extension<'a>, execution: &Memory) -> Result<Self> {
const FIELD: &str = "extensions.certificatePolicies";
let fail = |code| Error::new(code, FIELD, extension.span().start);
let mut inside = extension::sequence(extension, FIELD, execution)?;
let mut policies = inside.memory().vector();
let mut identifiers = inside.memory().map::<Oid<'a>, ()>();
while let Some(entry) = inside.next_element().field(FIELD)? {
let at = |code| Error::new(code, FIELD, entry.span().start);
if entry.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(at(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut information = inside.enter(&entry).field(FIELD)?;
let policy = information.read_oid().field(FIELD)?;
let mut qualifiers = inside.memory().vector();
if let Some(list) = information.next_element().field(FIELD)? {
if list.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(at(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut each = information.enter(&list).field(FIELD)?;
while let Some(qualifier) = each.next_element().field(FIELD)? {
if qualifier.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(at(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut pair = each.enter(&qualifier).field(FIELD)?;
let kind = pair.read_oid().field(FIELD)?;
let value = pair
.next_element()
.field(FIELD)?
.ok_or_else(|| at(ErrorCode::MissingField))?;
if !pair.is_empty() {
return Err(at(ErrorCode::TrailingData));
}
crate::name::walk_der(&pair, &value, FIELD, crate::name::Strings::Checked)?;
qualifiers
.try_push(PolicyQualifier {
kind,
value: value.as_bytes(),
at: value.span().start,
outer: each.depth(),
})
.map_err(|reason| Error::resource(reason, FIELD, qualifier.span().start))?;
}
if qualifiers.is_empty() {
return Err(at(ErrorCode::EmptySequence));
}
}
if !information.is_empty() {
return Err(at(ErrorCode::TrailingData));
}
if identifiers
.try_insert(policy, ())
.map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?
.is_some()
{
return Err(at(ErrorCode::MalformedValue));
}
policies
.try_push(PolicyInformation {
policy,
qualifiers,
at: entry.span().start,
})
.map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?;
}
if policies.is_empty() {
return Err(fail(ErrorCode::EmptySequence));
}
Ok(Self { policies })
}
}
#[derive(Debug, Clone, Copy)]
pub struct PolicyMapping<'a> {
issuer_domain: Oid<'a>,
subject_domain: Oid<'a>,
}
impl<'a> PolicyMapping<'a> {
#[must_use]
pub fn issuer_domain(self) -> Oid<'a> {
self.issuer_domain
}
#[must_use]
pub fn subject_domain(self) -> Oid<'a> {
self.subject_domain
}
}
#[derive(Debug)]
pub struct PolicyMappings<'a> {
mappings: OwnedVec<PolicyMapping<'a>>,
}
impl<'a> PolicyMappings<'a> {
#[must_use]
pub fn mappings(&self) -> &[PolicyMapping<'a>] {
&self.mappings
}
pub fn read(extension: &Extension<'a>, execution: &Memory) -> Result<Self> {
const FIELD: &str = "extensions.policyMappings";
let fail = |code| Error::new(code, FIELD, extension.span().start);
let mut inside = extension::sequence(extension, FIELD, execution)?;
let mut mappings = inside.memory().vector();
let mut seen = inside.memory().map::<(Oid<'a>, Oid<'a>), ()>();
while let Some(entry) = inside.next_element().field(FIELD)? {
let at = |code| Error::new(code, FIELD, entry.span().start);
if entry.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(at(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut pair = inside.enter(&entry).field(FIELD)?;
let issuer_domain = pair.read_oid().field(FIELD)?;
let subject_domain = pair.read_oid().field(FIELD)?;
if !pair.is_empty() {
return Err(at(ErrorCode::TrailingData));
}
if issuer_domain == crate::oid::ANY_POLICY || subject_domain == crate::oid::ANY_POLICY {
return Err(at(ErrorCode::MalformedValue));
}
if seen
.try_insert((issuer_domain, subject_domain), ())
.map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?
.is_none()
{
mappings
.try_push(PolicyMapping {
issuer_domain,
subject_domain,
})
.map_err(|reason| Error::resource(reason, FIELD, entry.span().start))?;
}
}
if mappings.is_empty() {
return Err(fail(ErrorCode::EmptySequence));
}
Ok(Self { mappings })
}
}
impl CertificatePolicies<'_> {
pub(crate) fn understood(&self, parser: &Parser, execution: &Memory) -> Result<bool> {
for qualifier in self.policies.iter().flat_map(PolicyInformation::qualifiers) {
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, QUALIFIER_FIELD, qualifier.at))?;
if !retained_qualifier_understood(parser, *qualifier, execution)? {
return Ok(false);
}
}
Ok(true)
}
}
const QUALIFIER_FIELD: &str = "extensions.certificatePolicies";
fn retained_qualifier_understood(
parser: &Parser,
qualifier: PolicyQualifier<'_>,
execution: &Memory,
) -> Result<bool> {
let Some(mut reader) = gate(
parser
.read_nested_in(
qualifier.value,
qualifier.at,
structured_asn1::Encoding::Der,
qualifier.outer,
execution,
)
.field(QUALIFIER_FIELD),
)?
else {
return Ok(false);
};
let Some(Some(value)) = gate(reader.next_element().field(QUALIFIER_FIELD))? else {
return Ok(false);
};
if !reader.is_empty() {
return Ok(false);
}
qualifier_understood(&reader, qualifier.kind, &value)
}
#[derive(Clone, Copy)]
enum QualifierKind {
Cps,
Notice,
}
pub fn qualifier_understood<'a>(
parent: &structured_asn1::Reader<'a>,
kind: Oid<'_>,
value: &structured_asn1::Element<'a>,
) -> Result<bool> {
let work = (kind
.as_bytes()
.len()
.max(crate::oid::CPS_QUALIFIER.as_bytes().len()) as u64)
.checked_mul(2)
.and_then(|cost| cost.checked_add(1))
.ok_or_else(|| {
Error::resource(
structured_asn1::memory::MemoryError::WorkExceeded,
QUALIFIER_FIELD,
value.span().start,
)
})?;
qualifier_charge(parent, value, work)?;
let kind = if kind == crate::oid::CPS_QUALIFIER {
QualifierKind::Cps
} else if kind == crate::oid::USER_NOTICE_QUALIFIER {
QualifierKind::Notice
} else {
return Ok(false);
};
if parent.encoding().is_der() {
return qualifier_schema(parent, kind, value);
}
let Some(mut reader) = gate(
parent
.reread(value, structured_asn1::Encoding::Der)
.field(QUALIFIER_FIELD),
)?
else {
return Ok(false);
};
let Some(Some(value)) = gate(reader.next_element().field(QUALIFIER_FIELD))? else {
return Ok(false);
};
if !reader.is_empty() {
return Ok(false);
}
qualifier_schema(&reader, kind, &value)
}
fn qualifier_charge(
reader: &structured_asn1::Reader<'_>,
value: &structured_asn1::Element<'_>,
work: u64,
) -> Result<()> {
reader.charge(work).map_err(|error| {
Error::new(
ErrorCode::Encoding(error.code()),
QUALIFIER_FIELD,
value.span().start,
)
})
}
fn qualifier_schema(
reader: &structured_asn1::Reader<'_>,
kind: QualifierKind,
value: &structured_asn1::Element<'_>,
) -> Result<bool> {
Ok(match kind {
QualifierKind::Cps => {
if value.tag() != Tag::primitive(universal::IA5_STRING) {
return Ok(false);
}
let work = (value.contents().len() as u64)
.checked_mul(12)
.and_then(|cost| cost.checked_add(1))
.ok_or_else(|| {
Error::resource(
structured_asn1::memory::MemoryError::WorkExceeded,
QUALIFIER_FIELD,
value.span().start,
)
})?;
qualifier_charge(reader, value, work)?;
crate::name::is_ia5(value.contents())
&& core::str::from_utf8(value.contents()).is_ok_and(crate::general_name::is_cps_uri)
}
QualifierKind::Notice => user_notice_understood(reader, value)?,
})
}
fn user_notice_understood(
parent: &structured_asn1::Reader<'_>,
value: &structured_asn1::Element<'_>,
) -> Result<bool> {
if value.tag() != Tag::constructed(universal::SEQUENCE) {
return Ok(false);
}
let Some(mut inside) = gate(parent.enter(value).field(QUALIFIER_FIELD))? else {
return Ok(false);
};
let Some(mut next) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
return Ok(false);
};
if let Some(reference) = next
&& reference.tag() == Tag::constructed(universal::SEQUENCE)
{
if !notice_reference_understood(&inside, &reference)? {
return Ok(false);
}
let Some(following) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
return Ok(false);
};
next = following;
}
if let Some(text) = next
&& !is_display_text(&inside, &text)?
{
return Ok(false);
}
Ok(inside.is_empty())
}
fn notice_reference_understood(
parent: &structured_asn1::Reader<'_>,
reference: &structured_asn1::Element<'_>,
) -> Result<bool> {
let Some(mut inside) = gate(parent.enter(reference).field(QUALIFIER_FIELD))? else {
return Ok(false);
};
let Some(Some(organization)) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
return Ok(false);
};
if !is_display_text(&inside, &organization)? {
return Ok(false);
}
let Some(Some(numbers)) = gate(inside.next_element().field(QUALIFIER_FIELD))? else {
return Ok(false);
};
if numbers.tag() != Tag::constructed(universal::SEQUENCE) || !inside.is_empty() {
return Ok(false);
}
let Some(mut each) = gate(inside.enter(&numbers).field(QUALIFIER_FIELD))? else {
return Ok(false);
};
loop {
match gate(each.next_element().field(QUALIFIER_FIELD))? {
Some(Some(number)) => {
if number.tag() != Tag::primitive(universal::INTEGER)
|| gate(each.integer(&number).field(QUALIFIER_FIELD))?.is_none()
{
return Ok(false);
}
}
Some(None) => return Ok(true),
None => return Ok(false),
}
}
}
fn is_display_text(
reader: &structured_asn1::Reader<'_>,
element: &structured_asn1::Element<'_>,
) -> Result<bool> {
let tag = element.tag();
let contents = element.contents();
if contents.len() > 200 * 4 {
return Ok(false);
}
qualifier_charge(reader, element, (contents.len() as u64) * 2 + 1)?;
let characters = if tag == Tag::primitive(universal::IA5_STRING) {
contents.iter().all(u8::is_ascii).then_some(contents.len())
} else if tag == Tag::primitive(universal::UTF8_STRING) {
core::str::from_utf8(contents)
.ok()
.map(|text| text.chars().count())
} else if tag == Tag::primitive(26) {
contents
.iter()
.all(|byte| matches!(byte, 0x20..=0x7e))
.then_some(contents.len())
} else if tag == Tag::primitive(30) {
crate::name::wide_char_count(contents, 2)
} else {
return Ok(false);
};
Ok(characters.is_some_and(|count| (1..=200).contains(&count)))
}
#[cfg(test)]
mod tests;