use crate::access::{DistributionPoints, InfoAccess};
use crate::constraints::{BasicConstraints, InhibitAnyPolicy, NameConstraints, PolicyConstraints};
use crate::error::{CallbackError, CallbackResult, Error, ErrorCode, Located, Result, gate};
use crate::general_name::{self, GeneralName};
use crate::identifier::{AuthorityKeyIdentifier, SubjectKeyIdentifier};
use crate::oid;
use crate::policy::{CertificatePolicies, PolicyMappings};
use crate::qualified::QcStatements;
use crate::usage::{ExtendedKeyUsage, KeyUsage};
use structured_asn1::memory::{Memory, MemoryError, OwnedVec, Work};
use structured_asn1::{Element, Oid, Parser, Reader, Span, Tag, universal};
mod entry;
pub use entry::{ExtensionFields, ExtensionHeader, ExtensionValue, open, read_with};
#[derive(Debug, Clone, Copy)]
pub struct Extension<'a> {
kind: Oid<'a>,
critical: bool,
value: &'a [u8],
value_span: Span,
span: Span,
parser: &'a Parser,
outer: u32,
}
impl<'a> Extension<'a> {
#[must_use]
pub fn kind(self) -> Oid<'a> {
self.kind
}
#[must_use]
pub fn is_critical(self) -> bool {
self.critical
}
#[must_use]
pub fn value(self) -> &'a [u8] {
self.value
}
#[must_use]
pub fn span(self) -> Span {
self.span
}
pub fn reader(self, execution: &Memory) -> structured_asn1::Result<Reader<'a>> {
self.parser.read_nested_in(
self.value,
self.value_span.start,
structured_asn1::Encoding::Der,
self.outer,
execution,
)
}
pub fn reader_in<'work>(self, query: &Reader<'work>) -> structured_asn1::Result<Reader<'work>>
where
'a: 'work,
{
self.parser.read_nested_query_in(
query,
self.value,
self.value_span.start,
structured_asn1::Encoding::Der,
self.outer,
)
}
#[must_use]
pub fn parser(self) -> &'a Parser {
self.parser
}
}
#[derive(Debug)]
pub struct Extensions<'a> {
items: OwnedVec<Extension<'a>>,
}
impl<'a> Extensions<'a> {
#[must_use]
pub fn memory(&self) -> &Memory {
self.items.memory()
}
pub(crate) fn empty(execution: &Memory) -> Self {
Self {
items: execution.vector(),
}
}
pub fn iter<'r, 'e, W: Work + 'e>(
&'r self,
execution: &'e W,
) -> impl Iterator<Item = Result<&'r Extension<'a>>> + 'e
where
'r: 'e,
{
let mut entries = self.items.iter();
let mut failed = false;
core::iter::from_fn(move || {
if failed || entries.as_slice().is_empty() {
return None;
}
if let Err(reason) = execution.charge_work(1) {
failed = true;
let at = entries.as_slice()[0].span.start;
return Some(Err(Error::resource(reason, "extensions", at)));
}
entries.next().map(Ok)
})
}
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn get<W: Work>(&self, kind: Oid<'_>, execution: &W) -> Result<Option<&Extension<'a>>> {
for item in &self.items {
let work = item.kind.as_bytes().len().max(kind.as_bytes().len()) as u64;
execution
.charge_work(1 + work)
.map_err(|reason| Error::resource(reason, "extensions", item.span.start))?;
if item.kind == kind {
return Ok(Some(item));
}
}
Ok(None)
}
pub fn unhandled_critical<'r, E>(
&'r self,
recognizer: &impl Recognizer<Error = E>,
execution: &Memory,
) -> CallbackResult<OwnedVec<&'r Extension<'a>>, E> {
let mut unhandled = execution.vector();
for item in self.criticals(execution) {
let item = item?;
if !self.understands(item, recognizer, execution)? {
unhandled
.try_push(item)
.map_err(|reason| Error::resource(reason, "extensions", item.span().start))?;
}
}
Ok(unhandled)
}
pub fn criticals<'r, 'e>(
&'r self,
execution: &'e Memory,
) -> impl Iterator<Item = Result<&'r Extension<'a>>> + 'e
where
'r: 'e,
{
let mut entries = self.iter(execution);
core::iter::from_fn(move || {
for item in entries.by_ref() {
let item = match item {
Ok(item) => item,
Err(error) => return Some(Err(error)),
};
if item.critical {
return Some(Ok(item));
}
}
None
})
}
pub fn understands<E>(
&self,
item: &Extension<'a>,
recognizer: &impl Recognizer<Error = E>,
execution: &Memory,
) -> CallbackResult<bool, E> {
let work = (item.kind.as_bytes().len() as u64)
.checked_mul(64)
.ok_or_else(|| {
Error::resource(MemoryError::WorkExceeded, "extensions", item.span.start)
})?;
execution
.charge_work(work)
.map_err(|reason| Error::resource(reason, "extensions", item.span.start))?;
if is_known(item.kind) {
return Self::decodes(item, recognizer, execution);
}
Ok(recognizer
.recognizes(item.kind, execution)
.map_err(CallbackError::Callback)?
&& Self::holds_one_value(item, execution)?)
}
fn holds_one_value(item: &Extension<'a>, execution: &Memory) -> Result<bool> {
const FIELD: &str = "extensions";
let read = || -> Result<()> {
let at = |code| Error::new(code, FIELD, item.value_span.start);
let mut reader = item.reader(execution).field(FIELD)?;
let element = reader
.next_element()
.field(FIELD)?
.ok_or_else(|| at(ErrorCode::MissingField))?;
crate::name::walk_der(&reader, &element, FIELD, crate::name::Strings::Checked)?;
if reader.is_empty() {
Ok(())
} else {
Err(at(ErrorCode::TrailingData))
}
};
Ok(gate(read())?.is_some())
}
fn decodes<E>(
item: &Extension<'a>,
recognizer: &impl Recognizer<Error = E>,
execution: &Memory,
) -> CallbackResult<bool, E> {
let kind = item.kind;
Ok(if kind == oid::BASIC_CONSTRAINTS {
gate(BasicConstraints::read(item, execution))?.is_some()
} else if kind == oid::KEY_USAGE {
gate(KeyUsage::read(item, execution))?.is_some()
} else if kind == oid::EXT_KEY_USAGE {
gate(ExtendedKeyUsage::read(item, execution))?.is_some()
} else if kind == oid::SUBJECT_ALT_NAME || kind == oid::ISSUER_ALT_NAME {
match gate(general_name::read_alternative(item, execution))? {
Some(names) => all(names.iter(), item, execution, |name| {
other_name_known(name, recognizer, execution)
})?,
None => false,
}
} else if kind == oid::SUBJECT_KEY_IDENTIFIER {
gate(SubjectKeyIdentifier::read(item, execution))?.is_some()
} else if kind == oid::AUTHORITY_KEY_IDENTIFIER {
match gate(AuthorityKeyIdentifier::read(item, execution))? {
Some(identifier) => all(identifier.issuer().iter(), item, execution, |name| {
other_name_known(name, recognizer, execution)
})?,
None => false,
}
} else if kind == oid::NAME_CONSTRAINTS {
match gate(NameConstraints::read(item, execution))? {
Some(constraints) => all(
constraints.permitted().iter().chain(constraints.excluded()),
item,
execution,
|subtree| {
Ok(match subtree.base() {
GeneralName::X400(_)
| GeneralName::EdiParty(_)
| GeneralName::RegisteredId(_) => false,
GeneralName::Other { type_id, .. } => recognizer
.recognizes_name_constraint(*type_id, execution)
.map_err(CallbackError::Callback)?,
_ => true,
})
},
)?,
None => false,
}
} else if kind == oid::CERTIFICATE_POLICIES {
match gate(CertificatePolicies::read(item, execution))? {
Some(policies) => policies.understood(item.parser, execution)?,
None => false,
}
} else if kind == oid::POLICY_MAPPINGS {
gate(PolicyMappings::read(item, execution))?.is_some()
} else if kind == oid::POLICY_CONSTRAINTS {
gate(PolicyConstraints::read(item, execution))?.is_some()
} else if kind == oid::INHIBIT_ANY_POLICY {
gate(InhibitAnyPolicy::read(item, execution))?.is_some()
} else if kind == oid::AUTHORITY_INFO_ACCESS || kind == oid::SUBJECT_INFO_ACCESS {
match gate(InfoAccess::read(item, execution))? {
Some(access) => all(
access.descriptions().iter(),
item,
execution,
|description| {
let method = description.method();
let named = if kind == oid::AUTHORITY_INFO_ACCESS {
[oid::OCSP, oid::CA_ISSUERS].contains(&method)
} else {
[oid::TIME_STAMPING_ACCESS, oid::CA_REPOSITORY].contains(&method)
};
Ok((named
|| recognizer
.recognizes_access_method(method, execution)
.map_err(CallbackError::Callback)?)
&& other_name_known(description.location(), recognizer, execution)?
&& (!named || method_location_fits(method, description.location())))
},
)?,
None => false,
}
} else if kind == oid::CRL_DISTRIBUTION_POINTS || kind == oid::FRESHEST_CRL {
match gate(DistributionPoints::read(item, execution))? {
Some(points) => all(points.points().iter(), item, execution, |point| {
all(
point.full_name().iter().chain(point.issuer()),
item,
execution,
|name| other_name_known(name, recognizer, execution),
)
})?,
None => false,
}
} else if kind == oid::OCSP_NO_CHECK {
gate(crate::ocsp::read_no_check(item, execution))?.is_some()
} else if kind == oid::QC_STATEMENTS {
match gate(QcStatements::read(item, execution))? {
Some(statements) => statements.understood(recognizer, execution)?,
None => false,
}
} else {
false
})
}
}
macro_rules! typed_accessors {
($($(#[$doc:meta])* $name:ident, $oid:path => $reader:path, $result:ty;)+) => {
$(
$(#[$doc])*
pub fn $name(&self, execution: &Memory) -> Result<Option<$result>> {
match self.get($oid, execution)? {
Some(item) => $reader(item, execution).map(Some),
None => Ok(None),
}
}
)+
};
}
impl<'a> Extensions<'a> {
pub fn read_element(
parent: &Reader<'a>,
element: &Element<'a>,
field: &'static str,
) -> Result<Self> {
read(parent, element, field)
}
pub fn read_implicit(
parent: &Reader<'a>,
element: &Element<'a>,
number: u32,
field: &'static str,
) -> Result<Self> {
read_tagged(parent, element, Tag::context(true, number), field)
}
typed_accessors! {
basic_constraints, oid::BASIC_CONSTRAINTS => BasicConstraints::read, BasicConstraints;
key_usage, oid::KEY_USAGE => KeyUsage::read, KeyUsage<'a>;
extended_key_usage, oid::EXT_KEY_USAGE => ExtendedKeyUsage::read, ExtendedKeyUsage<'a>;
subject_alt_name, oid::SUBJECT_ALT_NAME => general_name::read_alternative, OwnedVec<GeneralName<'a>>;
issuer_alt_name, oid::ISSUER_ALT_NAME => general_name::read_alternative, OwnedVec<GeneralName<'a>>;
subject_key_identifier, oid::SUBJECT_KEY_IDENTIFIER => SubjectKeyIdentifier::read, SubjectKeyIdentifier<'a>;
authority_key_identifier, oid::AUTHORITY_KEY_IDENTIFIER => AuthorityKeyIdentifier::read, AuthorityKeyIdentifier<'a>;
name_constraints, oid::NAME_CONSTRAINTS => NameConstraints::read, NameConstraints<'a>;
certificate_policies, oid::CERTIFICATE_POLICIES => CertificatePolicies::read, CertificatePolicies<'a>;
policy_mappings, oid::POLICY_MAPPINGS => PolicyMappings::read, PolicyMappings<'a>;
policy_constraints, oid::POLICY_CONSTRAINTS => PolicyConstraints::read, PolicyConstraints;
inhibit_any_policy, oid::INHIBIT_ANY_POLICY => InhibitAnyPolicy::read, InhibitAnyPolicy;
authority_info_access, oid::AUTHORITY_INFO_ACCESS => InfoAccess::read, InfoAccess<'a>;
subject_info_access, oid::SUBJECT_INFO_ACCESS => InfoAccess::read, InfoAccess<'a>;
crl_distribution_points, oid::CRL_DISTRIBUTION_POINTS => DistributionPoints::read, DistributionPoints<'a>;
freshest_crl, oid::FRESHEST_CRL => DistributionPoints::read, DistributionPoints<'a>;
qc_statements, oid::QC_STATEMENTS => QcStatements::read, QcStatements<'a>;
}
}
fn method_location_fits(method: Oid<'_>, location: &GeneralName<'_>) -> bool {
if method == oid::OCSP {
matches!(location, GeneralName::Uri(_))
} else if method == oid::CA_ISSUERS || method == oid::CA_REPOSITORY {
matches!(location, GeneralName::Uri(_) | GeneralName::Directory(_))
} else if method == oid::TIME_STAMPING_ACCESS {
matches!(
location,
GeneralName::Uri(_)
| GeneralName::Email(_)
| GeneralName::Dns(_)
| GeneralName::IpAddress(_)
)
} else {
true
}
}
pub(crate) fn other_name_known<E>(
name: &GeneralName<'_>,
recognizer: &impl Recognizer<Error = E>,
execution: &Memory,
) -> CallbackResult<bool, E> {
match name {
GeneralName::Other { type_id, .. } => recognizer
.recognizes_other_name(*type_id, execution)
.map_err(CallbackError::Callback),
_ => Ok(true),
}
}
fn all<T, E>(
values: impl Iterator<Item = T>,
item: &Extension<'_>,
execution: &Memory,
mut understood: impl FnMut(T) -> CallbackResult<bool, E>,
) -> CallbackResult<bool, E> {
for value in values {
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, "extensions", item.span.start))?;
if !understood(value)? {
return Ok(false);
}
}
Ok(true)
}
pub trait Recognizer {
type Error;
fn recognizes(
&self,
kind: Oid<'_>,
execution: &Memory,
) -> core::result::Result<bool, Self::Error>;
fn recognizes_other_name(
&self,
_type_id: Oid<'_>,
_execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(false)
}
fn recognizes_name_constraint(
&self,
_type_id: Oid<'_>,
_execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(false)
}
fn recognizes_access_method(
&self,
_method: Oid<'_>,
_execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(false)
}
fn recognizes_statement(
&self,
_kind: Oid<'_>,
_execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(false)
}
}
impl Recognizer for () {
type Error = core::convert::Infallible;
fn recognizes(
&self,
_kind: Oid<'_>,
_execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(false)
}
}
impl Recognizer for [Oid<'_>] {
type Error = MemoryError;
fn recognizes(
&self,
kind: Oid<'_>,
execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
for candidate in self {
execution
.charge_work(1 + candidate.as_bytes().len().max(kind.as_bytes().len()) as u64)?;
if *candidate == kind {
return Ok(true);
}
}
Ok(false)
}
}
impl<T: Recognizer + ?Sized> Recognizer for &T {
type Error = T::Error;
fn recognizes(
&self,
kind: Oid<'_>,
execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
(*self).recognizes(kind, execution)
}
fn recognizes_other_name(
&self,
type_id: Oid<'_>,
execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
(*self).recognizes_other_name(type_id, execution)
}
fn recognizes_name_constraint(
&self,
type_id: Oid<'_>,
execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
(*self).recognizes_name_constraint(type_id, execution)
}
fn recognizes_access_method(
&self,
method: Oid<'_>,
execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
(*self).recognizes_access_method(method, execution)
}
fn recognizes_statement(
&self,
kind: Oid<'_>,
execution: &Memory,
) -> core::result::Result<bool, Self::Error> {
(*self).recognizes_statement(kind, execution)
}
}
#[must_use]
pub fn is_known(kind: Oid<'_>) -> bool {
[
oid::BASIC_CONSTRAINTS,
oid::KEY_USAGE,
oid::EXT_KEY_USAGE,
oid::SUBJECT_ALT_NAME,
oid::ISSUER_ALT_NAME,
oid::SUBJECT_KEY_IDENTIFIER,
oid::AUTHORITY_KEY_IDENTIFIER,
oid::NAME_CONSTRAINTS,
oid::CERTIFICATE_POLICIES,
oid::POLICY_MAPPINGS,
oid::POLICY_CONSTRAINTS,
oid::INHIBIT_ANY_POLICY,
oid::AUTHORITY_INFO_ACCESS,
oid::SUBJECT_INFO_ACCESS,
oid::CRL_DISTRIBUTION_POINTS,
oid::FRESHEST_CRL,
oid::QC_STATEMENTS,
oid::OCSP_NO_CHECK,
]
.contains(&kind)
}
pub(crate) fn sequence<'a>(
extension: &Extension<'a>,
field: &'static str,
execution: &Memory,
) -> Result<Reader<'a>> {
sequence_reader(
extension.reader(execution).field(field)?,
field,
extension.span().start,
)
}
pub(crate) fn sequence_reader<'a>(
mut reader: Reader<'a>,
field: &'static str,
at: u64,
) -> Result<Reader<'a>> {
let fail = |code| Error::new(code, field, at);
let element = reader
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
if element.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
if !reader.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
reader.enter(&element).field(field)
}
pub(crate) fn context_of(tag: Tag) -> Option<(u32, bool)> {
(tag.class() == structured_asn1::Class::ContextSpecific)
.then(|| tag.number().map(|number| (number, tag.is_constructed())))
.flatten()
}
pub(crate) fn read<'a>(
parent: &Reader<'a>,
element: &Element<'a>,
field: &'static str,
) -> Result<Extensions<'a>> {
read_tagged(
parent,
element,
Tag::constructed(universal::SEQUENCE),
field,
)
}
fn read_tagged<'a>(
parent: &Reader<'a>,
element: &Element<'a>,
expected: Tag,
field: &'static str,
) -> Result<Extensions<'a>> {
let parser = parent.parser();
let fail = |code| Error::new(code, field, element.span().start);
if element.tag() != expected {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut sequence = parent.enter(element).field(field)?;
let mut items = parent.execution().vector();
let mut kinds = parent.execution().map::<Oid<'a>, ()>();
while let Some(entry) = sequence.next_element().field(field)? {
let at = |code| Error::new(code, field, entry.span().start);
let fields = open(&sequence, &entry, field)?;
if fields.header().default_written() && fields.reader().encoding().is_der() {
let at = fields
.header()
.critical_span()
.expect("written flag has a span")
.start;
return Err(Error::new(ErrorCode::MalformedValue, field, at));
}
let decoded = fields
.read_with(|_header, inside, value| {
if value.tag() != Tag::primitive(universal::OCTET_STRING) {
return Err(Error::new(
ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
field,
value.span().start,
));
}
if !inside.is_empty() {
return Err(at(ErrorCode::TrailingData));
}
Ok((value.contents(), value.contents_span(), inside.depth()))
})
.map_err(|error| match error {
CallbackError::Processing(error) | CallbackError::Callback(error) => error,
})?;
let header = decoded.header();
let kind = header.kind();
let critical = header.is_critical();
let (value, value_span, outer) = decoded.into_value();
if kinds
.try_insert(kind, ())
.map_err(|reason| Error::resource(reason, field, entry.span().start))?
.is_some()
{
return Err(at(ErrorCode::DuplicateExtension));
}
items
.try_push(Extension {
kind,
critical,
value,
value_span,
span: entry.span(),
parser,
outer,
})
.map_err(|reason| Error::resource(reason, field, entry.span().start))?;
}
if items.is_empty() {
return Err(fail(ErrorCode::EmptySequence));
}
Ok(Extensions {
items: items.into_owned(),
})
}
#[cfg(test)]
mod tests;