mod error;
mod name_space;
mod policy;
pub use error::{BuildError, BuildResult};
use crate::algorithm::AlgorithmIdentifier;
use crate::certificate::{Certificate, Validity};
use crate::constraints::CertificateCount;
use crate::error::{Result, gate};
use crate::extension::Recognizer;
use crate::general_name::GeneralName;
use crate::key::PublicKeyInfo;
use crate::name::Name;
use name_space::{Admission, NameSpace};
use structured_asn1::memory::{Memory, OwnedMap, OwnedVec};
use structured_asn1::{BitString, Oid};
#[derive(Debug, Clone, Copy)]
pub struct Anchor<'a> {
name: &'a Name<'a>,
key: PublicKeyInfo<'a>,
certificate: Option<&'a Certificate<'a>>,
}
impl<'a> Anchor<'a> {
#[must_use]
pub fn new(name: &'a Name<'a>, key: PublicKeyInfo<'a>) -> Self {
Self {
name,
key,
certificate: None,
}
}
#[must_use]
pub fn from_certificate(certificate: &'a Certificate<'a>) -> Self {
Self {
name: certificate.tbs().subject(),
key: certificate.tbs().public_key(),
certificate: Some(certificate),
}
}
#[must_use]
pub fn name(&self) -> &'a Name<'a> {
self.name
}
#[must_use]
pub fn key(&self) -> PublicKeyInfo<'a> {
self.key
}
#[must_use]
pub fn certificate(&self) -> Option<&'a Certificate<'a>> {
self.certificate
}
fn same(self, other: Self, execution: &Memory) -> Result<bool> {
execution
.charge_work(self.key.as_bytes().len().max(other.key.as_bytes().len()) as u64)
.map_err(|reason| self.name.resource(reason))?;
if self.key.as_bytes() != other.key.as_bytes() {
return Ok(false);
}
self.name.matches(other.name, execution)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Verified {
Yes,
No,
Unsupported,
}
#[derive(Debug, Clone, Copy)]
pub struct WorkingKey<'a> {
key: PublicKeyInfo<'a>,
inherited: Option<(Oid<'a>, &'a [u8])>,
}
impl<'a> WorkingKey<'a> {
#[must_use]
pub fn standing(key: PublicKeyInfo<'a>) -> Self {
Self {
key,
inherited: None,
}
}
#[must_use]
pub fn key(self) -> PublicKeyInfo<'a> {
self.key
}
#[must_use]
pub fn parameters(self) -> Option<&'a [u8]> {
if !self.key.algorithm().has_no_parameters() {
return self.key.algorithm().parameters();
}
let (algorithm, inherited) = self.inherited?;
(algorithm == self.key.algorithm().algorithm()).then_some(inherited)
}
#[must_use]
fn below(self, key: PublicKeyInfo<'a>) -> Self {
let algorithm = self.key.algorithm().algorithm();
Self {
key,
inherited: self.parameters().map(|held| (algorithm, held)),
}
}
}
pub trait Verify {
type Error;
fn verify(
&self,
algorithm: &AlgorithmIdentifier<'_>,
key: WorkingKey<'_>,
signed: &[u8],
signature: BitString<'_>,
execution: &Memory,
) -> core::result::Result<Verified, Self::Error>;
}
impl Verify for () {
type Error = core::convert::Infallible;
fn verify(
&self,
_algorithm: &AlgorithmIdentifier<'_>,
_key: WorkingKey<'_>,
_signed: &[u8],
_signature: BitString<'_>,
_execution: &Memory,
) -> core::result::Result<Verified, Self::Error> {
Ok(Verified::Unsupported)
}
}
pub trait Watch {
fn out_of_time(&self) -> bool;
}
impl Watch for () {
fn out_of_time(&self) -> bool {
false
}
}
pub(crate) trait Meter {
fn spend(&mut self, work: u64) -> Result<bool>;
}
impl Meter for () {
fn spend(&mut self, _work: u64) -> Result<bool> {
Ok(true)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Bounds {
pub depth: usize,
pub visited: usize,
pub edges: usize,
pub work: u64,
pub paths: usize,
pub policies: usize,
}
impl Default for Bounds {
fn default() -> Self {
Self {
depth: 12,
visited: 4_096,
edges: 16_384,
work: 1_000_000,
paths: 8,
policies: 1_024,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Truncation {
Depth,
Visited,
Edges,
Work,
Time,
Paths,
Policies,
}
impl Truncation {
fn halts(self) -> bool {
matches!(self, Self::Work | Self::Time | Self::Visited | Self::Edges)
}
}
#[derive(Debug)]
pub struct Path<'a> {
certificates: OwnedVec<&'a Certificate<'a>>,
anchor: Anchor<'a>,
policies: OwnedVec<Oid<'a>>,
}
impl<'a> Path<'a> {
#[must_use]
pub fn certificates(&self) -> &[&'a Certificate<'a>] {
&self.certificates
}
#[must_use]
pub fn anchor(&self) -> Anchor<'a> {
self.anchor
}
#[must_use]
pub fn policies(&self) -> &[Oid<'a>] {
&self.policies
}
#[must_use]
pub fn len(&self) -> usize {
self.certificates.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.certificates.is_empty()
}
fn same_route(
&self,
chain: &[&'a Certificate<'a>],
anchor: Anchor<'a>,
execution: &Memory,
) -> Result<bool> {
if self.certificates.len() != chain.len() || !self.anchor.same(anchor, execution)? {
return Ok(false);
}
for (ours, theirs) in self.certificates.iter().zip(chain) {
execution
.charge_work(
ours.tbs()
.public_key()
.as_bytes()
.len()
.max(theirs.tbs().public_key().as_bytes().len()) as u64,
)
.map_err(|reason| ours.tbs().subject().resource(reason))?;
if ours.tbs().public_key().as_bytes() != theirs.tbs().public_key().as_bytes()
|| !ours
.tbs()
.subject()
.matches(theirs.tbs().subject(), execution)?
{
return Ok(false);
}
}
Ok(true)
}
fn same_meaning(
&self,
anchor: Anchor<'a>,
policies: &[Oid<'a>],
execution: &Memory,
) -> Result<bool> {
if !self.anchor.same(anchor, execution)? || self.policies.len() != policies.len() {
return Ok(false);
}
for (ours, theirs) in self.policies.iter().zip(policies) {
execution
.charge_work(ours.as_bytes().len().max(theirs.as_bytes().len()) as u64)
.map_err(|reason| self.anchor.name.resource(reason))?;
if ours != theirs {
return Ok(false);
}
}
Ok(true)
}
}
#[derive(Debug)]
pub struct Outcome<'a> {
paths: OwnedVec<Path<'a>>,
truncated: Option<Truncation>,
}
impl<'a> Outcome<'a> {
fn empty(execution: &Memory) -> Self {
Self {
paths: execution.vector(),
truncated: None,
}
}
#[must_use]
pub fn paths(&self) -> &[Path<'a>] {
&self.paths
}
#[must_use]
pub fn truncated(&self) -> Option<Truncation> {
self.truncated
}
#[must_use]
pub fn is_truncated(&self) -> bool {
self.truncated.is_some()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Interval {
from: i64,
to: i64,
}
impl Interval {
#[must_use]
pub fn between(first: i64, second: i64) -> Self {
Self {
from: first.min(second),
to: first.max(second),
}
}
#[must_use]
pub fn at(moment: i64) -> Self {
Self {
from: moment,
to: moment,
}
}
#[must_use]
pub fn from(self) -> i64 {
self.from
}
#[must_use]
pub fn to(self) -> i64 {
self.to
}
fn inside(self, validity: Validity) -> bool {
let from = crate::temporal::Instant::new(self.from, 0).expect("zero nanoseconds fits");
let to = crate::temporal::Instant::new(self.to, 0).expect("zero nanoseconds fits");
validity.not_before().instant() <= from && to <= validity.not_after().instant()
}
}
#[derive(Debug, Clone, Copy)]
pub struct Policy<'a> {
pub wanted: &'a [Oid<'a>],
pub explicit: bool,
pub mapping: bool,
pub any: bool,
}
impl Default for Policy<'_> {
fn default() -> Self {
Self {
wanted: &[],
explicit: false,
mapping: true,
any: true,
}
}
}
mod cost {
pub(super) const EXTENSION: u64 = 8;
pub(super) const NAME: u64 = 16;
pub(super) const COMPARED: u64 = 16;
pub(super) const SIGNATURE: u64 = 1_024;
pub(super) const CHECK: u64 = 32;
pub(super) const POLICY: u64 = 4;
pub(super) const ENTRY: u64 = 4;
pub(super) const HASHED: u64 = 64;
}
pub struct Context<'a, 'p, V: Verify, W: Watch, R: Recognizer> {
pub execution: &'p Memory,
pub anchors: &'p [Anchor<'a>],
pub interval: Interval,
pub bounds: Bounds,
pub policy: Policy<'a>,
pub verifier: &'p V,
pub watch: &'p W,
pub recognizer: &'p R,
}
struct Search<'a, 'p, V: Verify, W: Watch, R: Recognizer> {
execution: &'p Memory,
supplied: &'p [&'a Certificate<'a>],
target: &'a Certificate<'a>,
pool: Option<OwnedVec<&'a Certificate<'a>>>,
anchors: &'p [Anchor<'a>],
interval: Interval,
bounds: Bounds,
policy: Policy<'a>,
verifier: &'p V,
watch: &'p W,
recognizer: &'p R,
visited: usize,
edges: usize,
spent: u64,
halted: Option<Truncation>,
noted: Option<Truncation>,
found: OwnedVec<Path<'a>>,
signatures: OwnedMap<(usize, usize, usize), Verified>,
properties: OwnedMap<usize, Properties>,
}
#[derive(Debug, Clone, Copy)]
struct Properties {
usable: bool,
issues: Option<crate::constraints::BasicConstraints>,
self_issued: bool,
publishes_itself: bool,
}
impl<V: Verify, W: Watch, R: Recognizer> Meter for Search<'_, '_, V, W, R> {
fn spend(&mut self, work: u64) -> Result<bool> {
Self::spend(self, work)
}
}
impl<V: Verify, W: Watch, R: Recognizer> Search<'_, '_, V, W, R> {
fn spend(&mut self, work: u64) -> Result<bool> {
debug_assert!(self.spent <= self.bounds.work);
if work > self.bounds.work - self.spent {
self.stop(Truncation::Work);
return Ok(false);
}
self.execution
.charge_work(work)
.map_err(|reason| self.target.tbs().subject().resource(reason))?;
self.spent += work;
if self.watch.out_of_time() {
self.stop(Truncation::Time);
return Ok(false);
}
Ok(true)
}
fn spend_names(&mut self, left: &Name<'_>, right: &Name<'_>) -> Result<bool> {
let parts = left.parts().len() as u64 + right.parts().len() as u64;
if !self.spend(parts)? {
return Ok(false);
}
let bytes = left.as_bytes().len() as u64 + right.as_bytes().len() as u64;
let rounds = sorting_rounds(left).max(sorting_rounds(right));
let work = rounds
.checked_mul(bytes / cost::COMPARED)
.and_then(|work| work.checked_add(cost::NAME))
.ok_or_else(|| left.resource(structured_asn1::memory::MemoryError::WorkExceeded))?;
self.spend(work)
}
fn policy<T>(
&mut self,
result: core::result::Result<T, policy::Overflow>,
) -> Result<Option<T>> {
match result {
Ok(value) => Ok(Some(value)),
Err(policy::Overflow::Limit) => {
self.stop(Truncation::Policies);
Ok(None)
}
Err(policy::Overflow::Resource(error)) => Err(error),
}
}
fn stop(&mut self, at: Truncation) {
let recorded = if at.halts() {
&mut self.halted
} else {
&mut self.noted
};
if recorded.is_none() {
*recorded = Some(at);
}
}
fn stopped(&self) -> bool {
self.halted.is_some()
}
fn truncated(&self) -> Option<Truncation> {
self.halted.or(self.noted)
}
fn verified(
&mut self,
certificate: &Certificate<'_>,
key: WorkingKey<'_>,
) -> BuildResult<Option<Verified>, V::Error, R::Error> {
let at = (
certificate.signed_bytes().as_ptr() as usize,
key.key().as_bytes().as_ptr() as usize,
key.parameters().map_or(0, |held| held.as_ptr() as usize),
);
if let Some(answer) = self
.signatures
.get(&at, self.execution)
.map_err(|reason| certificate.tbs().subject().resource(reason))?
{
return Ok(Some(*answer));
}
if !self.spend(cost::SIGNATURE)? {
return Ok(None);
}
self.signatures
.try_insert(at, Verified::Unsupported)
.map_err(|reason| certificate.tbs().subject().resource(reason))?;
let answer = self
.verifier
.verify(
&certificate.signature_algorithm(),
key,
certificate.signed_bytes(),
certificate.signature(),
self.execution,
)
.map_err(BuildError::Verification)?;
*self
.signatures
.get_mut(&at, self.execution)
.map_err(|reason| certificate.tbs().subject().resource(reason))?
.expect("the admitted signature cache entry") = answer;
Ok(Some(answer))
}
}
fn spend_anchor(
context: &Context<'_, '_, impl Verify, impl Watch, impl Recognizer>,
spent: &mut u64,
halted: &mut Option<Truncation>,
origin: &Name<'_>,
work: u64,
) -> Result<bool> {
debug_assert!(*spent <= context.bounds.work);
if work > context.bounds.work - *spent {
*halted = Some(Truncation::Work);
return Ok(false);
}
if context.watch.out_of_time() {
*halted = Some(Truncation::Time);
return Ok(false);
}
context
.execution
.charge_work(work)
.map_err(|reason| origin.resource(reason))?;
*spent += work;
Ok(true)
}
fn fold_anchors<'a>(
context: &Context<'a, '_, impl Verify, impl Watch, impl Recognizer>,
spent: &mut u64,
halted: &mut Option<Truncation>,
) -> Result<OwnedVec<Anchor<'a>>> {
let mut keyed: OwnedMap<&[u8], OwnedVec<usize>> = context.execution.map();
let mut anchors: OwnedVec<Anchor<'a>> = context.execution.vector();
'anchors: for anchor in context.anchors {
let key = anchor.key().as_bytes();
if !spend_anchor(
context,
spent,
halted,
anchor.name(),
cost::CHECK + key.len() as u64 / cost::HASHED,
)? {
break;
}
let fail = |reason| anchor.name().resource(reason);
if keyed.get(&key, context.execution).map_err(fail)?.is_none() {
keyed
.try_insert(key, context.execution.vector())
.map_err(fail)?;
}
let group = keyed
.get_mut(&key, context.execution)
.map_err(fail)?
.expect("the group was inserted before lookup");
for at in group.iter() {
let theirs: &Name<'_> = anchors[*at].name();
let bytes = (theirs.as_bytes().len() as u64)
.checked_add(anchor.name().as_bytes().len() as u64)
.ok_or_else(|| {
anchor
.name()
.resource(structured_asn1::memory::MemoryError::WorkExceeded)
})?;
if !spend_anchor(
context,
spent,
halted,
anchor.name(),
cost::NAME + bytes / cost::COMPARED,
)? {
break 'anchors;
}
if !theirs.matches(anchor.name(), context.execution)? {
continue;
}
let held = anchors[*at].certificate();
let bytes = published_bytes(held)
.checked_add(published_bytes(anchor.certificate()))
.ok_or_else(|| {
anchor
.name()
.resource(structured_asn1::memory::MemoryError::WorkExceeded)
})?;
if !spend_anchor(
context,
spent,
halted,
anchor.name(),
cost::CHECK + bytes / cost::HASHED,
)? {
break 'anchors;
}
if published_alike(held, anchor.certificate(), context.execution)? {
continue 'anchors;
}
}
group.try_push(anchors.len()).map_err(fail)?;
anchors.try_push(*anchor).map_err(fail)?;
}
Ok(anchors)
}
fn fold_pool<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
pool: &[&'a Certificate<'a>],
target: &'a Certificate<'a>,
) -> BuildResult<OwnedVec<&'a Certificate<'a>>, V::Error, R::Error> {
let mut named: OwnedMap<usize, ()> = search.execution.map();
let mut seen: OwnedMap<&[u8], ()> = search.execution.map();
let mut unique = search.execution.vector();
for candidate in pool {
let bytes = candidate.as_bytes();
if !search.spend(cost::CHECK)? {
break;
}
if named
.try_insert(bytes.as_ptr() as usize, ())
.map_err(|reason| candidate.tbs().subject().resource(reason))?
.is_some()
{
continue;
}
if !search.spend(bytes.len() as u64 / cost::HASHED)? {
break;
}
search
.execution
.charge_work(bytes.len().max(target.as_bytes().len()) as u64)
.map_err(|reason| candidate.tbs().subject().resource(reason))?;
if bytes != target.as_bytes()
&& seen
.try_insert(bytes, ())
.map_err(|reason| candidate.tbs().subject().resource(reason))?
.is_none()
{
unique
.try_push(*candidate)
.map_err(|reason| candidate.tbs().subject().resource(reason))?;
}
}
Ok(unique)
}
pub fn build<'a, V: Verify, W: Watch, R: Recognizer>(
target: &'a Certificate<'a>,
pool: &[&'a Certificate<'a>],
context: &Context<'a, '_, V, W, R>,
) -> BuildResult<Outcome<'a>, V::Error, R::Error> {
if context.anchors.is_empty() {
return Ok(Outcome::empty(context.execution));
}
if !context.interval.inside(target.tbs().validity()) {
return Ok(Outcome::empty(context.execution));
}
let mut spent = 0u64;
let mut halted = None;
let anchors = fold_anchors(context, &mut spent, &mut halted)?;
if halted.is_some() {
return Ok(Outcome {
paths: context.execution.vector(),
truncated: halted,
});
}
let mut search = Search {
execution: context.execution,
supplied: pool,
target,
pool: None,
anchors: &anchors,
interval: context.interval,
bounds: context.bounds,
policy: context.policy,
verifier: context.verifier,
watch: context.watch,
recognizer: context.recognizer,
visited: 0,
edges: 0,
spent,
halted,
noted: None,
found: context.execution.vector(),
signatures: context.execution.map(),
properties: context.execution.map(),
};
let known = properties(&mut search, target)?;
let mut chain = context.execution.vector();
if known.usable && !known.publishes_itself {
walk(&mut search, target, &mut chain)?;
}
let mut ordered = context.execution.vector();
let found = core::mem::replace(&mut search.found, context.execution.vector());
for (position, path) in found.into_iter().enumerate() {
ordered
.try_push((position, path))
.map_err(|reason| target.tbs().subject().resource(reason))?;
}
ordered
.try_sort_unstable_by(|left, right| {
Ok::<_, structured_asn1::memory::MemoryError>(
(left.1.len(), left.0).cmp(&(right.1.len(), right.0)),
)
})
.map_err(|error| target.tbs().subject().resource(error.into()))?;
for (_, path) in ordered {
search
.found
.try_push(path)
.map_err(|reason| target.tbs().subject().resource(reason))?;
}
let truncated = search.truncated();
Ok(Outcome {
paths: search.found,
truncated,
})
}
fn walk<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
certificate: &'a Certificate<'a>,
chain: &mut OwnedVec<&'a Certificate<'a>>,
) -> BuildResult<(), V::Error, R::Error> {
let mut cursors = search.execution.vector();
let mut entering = certificate;
'enter: loop {
if search.stopped() {
break;
}
if search.visited >= search.bounds.visited {
search.stop(Truncation::Visited);
break;
}
search.visited += 1;
if !search.spend(cost::CHECK)? {
break;
}
if chain.len() >= search.bounds.depth {
search.stop(Truncation::Depth);
break;
}
chain
.try_push(entering)
.map_err(|reason| entering.tbs().subject().resource(reason))?;
cursors
.try_push(0usize)
.map_err(|reason| entering.tbs().subject().resource(reason))?;
settle(search, entering, chain)?;
loop {
if search.stopped() {
break 'enter;
}
let current = *chain.last().expect("the active DFS certificate");
let cursor = cursors.last_mut().expect("its candidate cursor");
if let Some(candidate) = next_issuer(
search,
current,
chain,
chain.len() >= search.bounds.depth,
cursor,
)? {
entering = candidate;
continue 'enter;
}
chain.pop();
cursors.pop();
if chain.is_empty() {
break 'enter;
}
}
}
chain.clear();
Ok(())
}
fn settle<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
certificate: &'a Certificate<'a>,
chain: &[&'a Certificate<'a>],
) -> BuildResult<(), V::Error, R::Error> {
for at in 0..search.anchors.len() {
let anchor = search.anchors[at];
if search.stopped() {
break;
}
if search.edges >= search.bounds.edges {
search.stop(Truncation::Edges);
break;
}
search.edges += 1;
if !search.spend_names(certificate.tbs().issuer(), anchor.name())? {
break;
}
if !certificate
.tbs()
.issuer()
.matches(anchor.name(), search.execution)?
{
continue;
}
let Some(policies) = validate(search, chain, anchor)? else {
continue;
};
keep(search, chain, anchor, policies)?;
}
Ok(())
}
fn keep<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
chain: &[&'a Certificate<'a>],
anchor: Anchor<'a>,
policies: OwnedVec<Oid<'a>>,
) -> BuildResult<(), V::Error, R::Error> {
let held = search.found.len() as u64;
if held > 0 {
if !search.spend(chain.len() as u64)? {
return Ok(());
}
let overflow = || {
anchor
.name
.resource(structured_asn1::memory::MemoryError::WorkExceeded)
};
let mut read = 0u64;
for certificate in chain {
let tbs = certificate.tbs();
let bytes =
tbs.subject().as_bytes().len() as u64 + tbs.public_key().as_bytes().len() as u64;
read = read.checked_add(bytes).ok_or_else(overflow)?;
}
let each = (read / cost::COMPARED)
.checked_add(cost::NAME)
.and_then(|work| work.checked_add(policies.len() as u64))
.ok_or_else(overflow)?;
let work = each.checked_mul(held).ok_or_else(overflow)?;
if !search.spend(work)? {
return Ok(());
}
}
for held in &search.found {
if held.same_route(chain, anchor, search.execution)?
&& held.same_meaning(anchor, &policies, search.execution)?
{
return Ok(());
}
}
if search.found.len() < search.bounds.paths {
let mut certificates = search.execution.vector();
certificates
.try_extend_from_slice(chain)
.map_err(|reason| anchor.name.resource(reason))?;
search
.found
.try_push(Path {
certificates,
anchor,
policies,
})
.map_err(|reason| anchor.name.resource(reason))?;
return Ok(());
}
search.stop(Truncation::Paths);
for held in &search.found {
if held.same_meaning(anchor, &policies, search.execution)? {
return Ok(());
}
}
let mut repeat = None;
for at in (0..search.found.len()).rev() {
let candidate = &search.found[at];
let mut count = 0;
for held in &search.found {
if held.same_meaning(candidate.anchor, &candidate.policies, search.execution)? {
count += 1;
if count > 1 {
break;
}
}
}
if count > 1 {
repeat = Some(at);
break;
}
}
if let Some(at) = repeat {
let moves = core::mem::size_of_val(&search.found[at..]);
search
.execution
.charge_work(moves as u64)
.map_err(|reason| anchor.name.resource(reason))?;
search.found[at..].rotate_left(1);
search.found.pop();
let mut certificates = search.execution.vector();
certificates
.try_extend_from_slice(chain)
.map_err(|reason| anchor.name.resource(reason))?;
search
.found
.try_push(Path {
certificates,
anchor,
policies,
})
.map_err(|reason| anchor.name.resource(reason))?;
}
Ok(())
}
fn next_issuer<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
certificate: &'a Certificate<'a>,
chain: &[&'a Certificate<'a>],
deepest: bool,
at: &mut usize,
) -> BuildResult<Option<&'a Certificate<'a>>, V::Error, R::Error> {
if search.pool.is_none() {
let supplied = search.supplied;
let target = search.target;
search.pool = Some(fold_pool(search, supplied, target)?);
}
while let Some(candidate) = search.pool.as_ref().and_then(|pool| pool.get(*at)).copied() {
*at += 1;
if search.stopped() {
break;
}
if search.edges >= search.bounds.edges {
search.stop(Truncation::Edges);
break;
}
search.edges += 1;
if !search.spend_names(certificate.tbs().issuer(), candidate.tbs().subject())? {
break;
}
let mut repeated = false;
for held in chain {
search
.execution
.charge_work(1)
.map_err(|reason| certificate.tbs().subject().resource(reason))?;
if core::ptr::eq(held.as_bytes(), candidate.as_bytes()) {
repeated = true;
break;
}
}
if repeated {
continue;
}
if !certificate
.tbs()
.issuer()
.matches(candidate.tbs().subject(), search.execution)?
{
continue;
}
let known = properties(search, candidate)?;
if known.publishes_itself || !known.usable || known.issues.is_none() {
continue;
}
if deepest {
search.stop(Truncation::Depth);
break;
}
let key = WorkingKey::standing(candidate.tbs().public_key());
let Some(answer) = search.verified(certificate, key)? else {
break;
};
if answer == Verified::No {
continue;
}
return Ok(Some(candidate));
}
Ok(None)
}
fn published_alike(
left: Option<&Certificate<'_>>,
right: Option<&Certificate<'_>>,
execution: &Memory,
) -> Result<bool> {
match (left, right) {
(None, None) => Ok(true),
(Some(ours), Some(theirs)) => binds_alike(ours, theirs, execution),
_ => Ok(false),
}
}
fn binds_alike(
ours: &Certificate<'_>,
theirs: &Certificate<'_>,
execution: &Memory,
) -> Result<bool> {
if same(ours, theirs) {
return Ok(true);
}
let ours = ours
.tbs()
.extensions()
.get(crate::oid::NAME_CONSTRAINTS, execution)?;
let theirs = theirs
.tbs()
.extensions()
.get(crate::oid::NAME_CONSTRAINTS, execution)?;
Ok(match (ours, theirs) {
(None, None) => true,
(Some(ours), Some(theirs)) => {
ours.is_critical() == theirs.is_critical() && ours.value() == theirs.value()
}
_ => false,
})
}
fn sorting_rounds(name: &Name<'_>) -> u64 {
let widest = name
.parts()
.iter()
.map(|part| part.attributes().len())
.max()
.unwrap_or(1)
.max(1);
u64::from(widest.ilog2()) + 1
}
fn published_bytes(certificate: Option<&Certificate<'_>>) -> u64 {
certificate.map_or(0, |certificate| certificate.as_bytes().len() as u64)
}
fn same(left: &Certificate<'_>, right: &Certificate<'_>) -> bool {
core::ptr::eq(left.as_bytes(), right.as_bytes()) || left.as_bytes() == right.as_bytes()
}
fn publishes_anchor<V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'_, '_, V, W, R>,
certificate: &Certificate<'_>,
) -> BuildResult<bool, V::Error, R::Error> {
let anchors = search.anchors;
for anchor in anchors {
if !search.spend_names(certificate.tbs().subject(), anchor.name())? {
break;
}
if !certificate
.tbs()
.subject()
.matches(anchor.name(), search.execution)?
{
continue;
}
let key = certificate.tbs().public_key().as_bytes();
let against = anchor.key().as_bytes();
let read = (key.len() as u64 + against.len() as u64) / cost::COMPARED;
if !search.spend(read)? {
break;
}
if key == against {
return Ok(true);
}
}
Ok(false)
}
fn properties<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
certificate: &'a Certificate<'a>,
) -> BuildResult<Properties, V::Error, R::Error> {
let at = certificate.as_bytes().as_ptr() as usize;
if let Some(known) = search
.properties
.get(&at, search.execution)
.map_err(|reason| certificate.tbs().subject().resource(reason))?
{
return Ok(*known);
}
let self_issued = search
.spend_names(certificate.tbs().subject(), certificate.tbs().issuer())?
&& self_issued(certificate, search.execution)?;
let publishes_anchor = publishes_anchor(search, certificate)?;
let issuing = issues(search, certificate)?;
let named =
issuing.is_some_and(|issuing| !issuing.asserted || !certificate.tbs().subject().is_empty());
let usable = named && usable(search, certificate)?;
let publishes_itself = usable
&& publishes_anchor
&& self_issued
&& search.verified(
certificate,
WorkingKey::standing(certificate.tbs().public_key()),
)? == Some(Verified::Yes);
let known = Properties {
usable,
issues: issuing.and_then(|issuing| issuing.constraints),
self_issued,
publishes_itself,
};
search
.properties
.try_insert(at, known)
.map_err(|reason| certificate.tbs().subject().resource(reason))?;
Ok(known)
}
fn usable<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
certificate: &'a Certificate<'a>,
) -> BuildResult<bool, V::Error, R::Error> {
if !search.spend(cost::CHECK)? {
return Ok(false);
}
if !search.interval.inside(certificate.tbs().validity()) {
return Ok(false);
}
let extensions = certificate.tbs().extensions();
for demand in extensions.criticals(search.execution) {
let demand = demand?;
if !search.spend(cost::EXTENSION)? {
return Ok(false);
}
if extensions.understands(demand, search.recognizer, search.execution)? {
continue;
}
if demand.kind() == crate::oid::NAME_CONSTRAINTS {
if !search.spend(cost::EXTENSION)? {
return Ok(false);
}
if gate(crate::constraints::NameConstraints::read(
demand,
search.execution,
))?
.is_some()
{
continue;
}
}
return Ok(false);
}
Ok(true)
}
#[derive(Debug, Clone, Copy, Default)]
struct Issuing {
asserted: bool,
constraints: Option<crate::constraints::BasicConstraints>,
}
fn issues<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
certificate: &'a Certificate<'a>,
) -> BuildResult<Option<Issuing>, V::Error, R::Error> {
let extensions = certificate.tbs().extensions();
let Ok(carried) = read(search, |execution| extensions.basic_constraints(execution))?.enforced()
else {
return Ok(None);
};
let Some(basic) = carried else {
return Ok(Some(Issuing::default()));
};
if !basic.is_authority() {
return Ok(Some(Issuing::default()));
}
let asserted = Issuing {
asserted: true,
constraints: None,
};
if extensions
.get(crate::oid::KEY_USAGE, search.execution)?
.is_some()
{
let Some(usage) = read(search, |execution| extensions.key_usage(execution))?.value() else {
return Ok(Some(asserted));
};
if !usage.key_cert_sign() {
return Ok(Some(asserted));
}
}
Ok(Some(Issuing {
constraints: Some(basic),
..asserted
}))
}
fn validate<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
chain: &[&'a Certificate<'a>],
anchor: Anchor<'a>,
) -> BuildResult<Option<OwnedVec<Oid<'a>>>, V::Error, R::Error> {
let length = chain.len();
let mut space = NameSpace::new(search.execution);
let Some(mut policies) = initial_policies(search, length)? else {
return Ok(None);
};
if let Some(certificate) = anchor.certificate()
&& !narrow_published(search, &mut space, certificate.tbs().extensions())?
{
return Ok(None);
}
let mut allowed = u32::try_from(length).unwrap_or(u32::MAX);
let mut last_constraints = None;
let mut working = WorkingKey::standing(anchor.key());
for (position, certificate) in chain.iter().rev().enumerate() {
if !search.spend(cost::CHECK)? {
return Ok(None);
}
let last = position + 1 == length;
let Some(answer) = search.verified(certificate, working)? else {
return Ok(None);
};
if answer != Verified::Yes {
return Ok(None);
}
working = working.below(certificate.tbs().public_key());
let known = properties(search, certificate)?;
let self_issued = known.self_issued;
let extensions = certificate.tbs().extensions();
if !space.is_open() && (last || !self_issued) && !within(search, &space, certificate)? {
return Ok(None);
}
let Ok(named) = read(search, |execution| {
extensions.certificate_policies(execution)
})?
.enforced() else {
return Ok(None);
};
let presented = policies.present(named.as_ref(), self_issued, last, search);
let Some(satisfied) = search.policy(presented)? else {
return Ok(None);
};
if !satisfied {
return Ok(None);
}
if !narrow(search, &mut space, extensions)? {
return Ok(None);
}
if last {
let Ok(constraints) =
read(search, |execution| extensions.policy_constraints(execution))?.enforced()
else {
return Ok(None);
};
last_constraints = constraints;
break;
}
let Some(restrictions) = restrictions(search, extensions)? else {
return Ok(None);
};
let carried = policies.carry(
restrictions.mappings.as_ref(),
restrictions.constraints,
restrictions.inhibit,
self_issued,
search,
);
if search.policy(carried)?.is_none() {
return Ok(None);
}
let Some(basic) = known.issues else {
return Ok(None);
};
if !self_issued {
if allowed == 0 {
return Ok(None);
}
allowed -= 1;
}
if let Some(limit) = basic.path_length().and_then(CertificateCount::certificates) {
allowed = allowed.min(limit);
}
}
if !spend_policies(search, policies.nodes())? {
return Ok(None);
}
let settled = policies.settle(last_constraints, search.policy.wanted, search);
let Some(settled) = search.policy(settled)? else {
return Ok(None);
};
if !settled.is_satisfied() {
return Ok(None);
}
Ok(Some(settled.into_policies()))
}
fn spend_policies(
search: &mut Search<'_, '_, impl Verify, impl Watch, impl Recognizer>,
nodes: usize,
) -> Result<bool> {
let work = cost::POLICY.checked_mul(nodes as u64).ok_or_else(|| {
search
.target
.tbs()
.subject()
.resource(structured_asn1::memory::MemoryError::WorkExceeded)
})?;
search.spend(work)
}
fn initial_policies<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
length: usize,
) -> Result<Option<policy::Processing<'a>>> {
let origin = search
.target
.tbs()
.subject()
.resource(structured_asn1::memory::MemoryError::CapacityOverflow);
let length = u32::try_from(length).map_err(|_| origin.clone())?;
let initial = policy::Processing::new(
length,
policy::Initial {
explicit_policy: search.policy.explicit,
policy_mapping: search.policy.mapping,
any_policy: search.policy.any,
},
search.bounds.policies,
search.execution,
origin.offset(),
);
search.policy(initial)
}
fn narrow<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
space: &mut NameSpace<'a>,
extensions: &'a crate::extension::Extensions<'a>,
) -> BuildResult<bool, V::Error, R::Error> {
if extensions
.get(crate::oid::NAME_CONSTRAINTS, search.execution)?
.is_none()
{
return Ok(true);
}
let authority = read(search, |execution| extensions.basic_constraints(execution))?
.value()
.is_some_and(crate::constraints::BasicConstraints::is_authority);
if !authority {
return Ok(false);
}
narrowed(search, space, extensions)
}
fn narrow_published<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
space: &mut NameSpace<'a>,
extensions: &'a crate::extension::Extensions<'a>,
) -> BuildResult<bool, V::Error, R::Error> {
if extensions
.get(crate::oid::NAME_CONSTRAINTS, search.execution)?
.is_none()
{
return Ok(true);
}
narrowed(search, space, extensions)
}
fn narrowed<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
space: &mut NameSpace<'a>,
extensions: &'a crate::extension::Extensions<'a>,
) -> BuildResult<bool, V::Error, R::Error> {
let Some(constraints) =
read(search, |execution| extensions.name_constraints(execution))?.value()
else {
return Ok(false);
};
let origin = extensions
.get(crate::oid::NAME_CONSTRAINTS, search.execution)?
.expect("the decoded name constraints extension")
.span()
.start;
space
.narrow(constraints)
.map_err(|reason| crate::Error::resource(reason, "nameConstraints", origin))?;
Ok(true)
}
fn within<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
space: &NameSpace<'a>,
certificate: &'a Certificate<'a>,
) -> BuildResult<bool, V::Error, R::Error> {
let subject = certificate.tbs().subject();
let Ok(carried) = read(search, |execution| {
certificate.tbs().extensions().subject_alt_name(execution)
})?
.enforced() else {
return Ok(false);
};
let alternatives = carried.as_deref().unwrap_or(&[]);
if !charge_names(search, space, subject, alternatives)? {
return Ok(false);
}
Ok(
space.admits(subject, alternatives, search.recognizer, search.execution)?
== Admission::Admitted,
)
}
fn charge_names<V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'_, '_, V, W, R>,
space: &NameSpace<'_>,
subject: &Name<'_>,
alternatives: &[GeneralName<'_>],
) -> Result<bool> {
space.charge(subject, alternatives, search, search.execution)
}
fn self_issued(certificate: &Certificate<'_>, execution: &Memory) -> Result<bool> {
certificate
.tbs()
.subject()
.matches(certificate.tbs().issuer(), execution)
}
enum Read<T> {
Absent,
Value(T),
Unreadable,
}
impl<T> Read<T> {
fn value(self) -> Option<T> {
match self {
Self::Value(value) => Some(value),
Self::Absent | Self::Unreadable => None,
}
}
fn enforced(self) -> core::result::Result<Option<T>, Unreadable> {
match self {
Self::Absent => Ok(None),
Self::Value(value) => Ok(Some(value)),
Self::Unreadable => Err(Unreadable),
}
}
}
struct Unreadable;
struct Restrictions<'a> {
mappings: Option<crate::policy::PolicyMappings<'a>>,
constraints: Option<crate::constraints::PolicyConstraints>,
inhibit: Option<crate::constraints::InhibitAnyPolicy>,
}
fn restrictions<'a, V: Verify, W: Watch, R: Recognizer>(
search: &mut Search<'a, '_, V, W, R>,
extensions: &'a crate::extension::Extensions<'a>,
) -> BuildResult<Option<Restrictions<'a>>, V::Error, R::Error> {
let mappings = read(search, |execution| extensions.policy_mappings(execution))?.enforced();
let constraints =
read(search, |execution| extensions.policy_constraints(execution))?.enforced();
let inhibit = read(search, |execution| extensions.inhibit_any_policy(execution))?.enforced();
let (Ok(mappings), Ok(constraints), Ok(inhibit)) = (mappings, constraints, inhibit) else {
return Ok(None);
};
Ok(Some(Restrictions {
mappings,
constraints,
inhibit,
}))
}
fn read<T>(
search: &mut Search<'_, '_, impl Verify, impl Watch, impl Recognizer>,
outcome: impl FnOnce(&Memory) -> Result<Option<T>>,
) -> Result<Read<T>> {
if !search.spend(cost::EXTENSION)? {
return Ok(Read::Unreadable);
}
match gate(outcome(search.execution))? {
Some(Some(value)) => Ok(Read::Value(value)),
Some(None) => Ok(Read::Absent),
None => Ok(Read::Unreadable),
}
}
#[cfg(test)]
mod tests;