mod error;
mod name_space;
mod policy;
mod validation;
pub use error::{BuildError, BuildResult};
pub use validation::{
ConstraintSource, NameConstraintFailure, Rejection, RejectionReason, Validation,
ValidationBounds, ValidationContext, ValidationFacts, validate,
};
use crate::algorithm::AlgorithmIdentifier;
use crate::certificate::{Certificate, Validity};
use crate::error::Result;
use crate::extension::Recognizer;
use crate::key::PublicKeyInfo;
use crate::name::Name;
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<&'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();
}
self.inherited
}
fn below(
self,
certificate: &'a Certificate<'a>,
execution: &Memory,
meter: &mut impl Meter,
) -> Result<Option<Self>> {
let key = certificate.tbs().public_key();
let mut inherited = None;
if key.algorithm().has_no_parameters()
&& let Some(parameters) = self.parameters()
{
let previous = self.key.algorithm().algorithm().as_bytes();
let current = key.algorithm().algorithm().as_bytes();
if previous.len() == current.len() {
if core::ptr::eq(previous, current) {
inherited = Some(parameters);
} else {
let reads = (current.len() as u64).checked_mul(2).ok_or_else(|| {
certificate
.tbs()
.subject()
.resource(structured_asn1::memory::MemoryError::WorkExceeded)
})?;
if !meter.spend(reads / cost::COMPARED)? {
return Ok(None);
}
execution
.charge_work(reads)
.map_err(|reason| certificate.tbs().subject().resource(reason))?;
if previous == current {
inherited = Some(parameters);
}
}
}
}
Ok(Some(Self { key, inherited }))
}
}
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: crate::temporal::Instant,
to: crate::temporal::Instant,
}
impl Interval {
#[must_use]
pub fn between(first: i64, second: i64) -> Self {
Self::between_instants(
crate::temporal::Instant::whole(first),
crate::temporal::Instant::whole(second),
)
}
#[must_use]
pub fn at(moment: i64) -> Self {
Self::at_instant(crate::temporal::Instant::whole(moment))
}
#[must_use]
pub fn between_instants(
first: crate::temporal::Instant,
second: crate::temporal::Instant,
) -> Self {
Self {
from: first.min(second),
to: first.max(second),
}
}
#[must_use]
pub fn at_instant(moment: crate::temporal::Instant) -> Self {
Self {
from: moment,
to: moment,
}
}
#[must_use]
pub fn from(self) -> i64 {
self.from.seconds()
}
#[must_use]
pub fn to(self) -> i64 {
self.to.seconds()
}
#[must_use]
pub fn from_instant(self) -> crate::temporal::Instant {
self.from
}
#[must_use]
pub fn to_instant(self) -> crate::temporal::Instant {
self.to
}
fn inside(self, validity: Validity) -> bool {
validity.not_before().instant() <= self.from && self.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 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<'a>>,
}
#[derive(Debug, Clone, Copy)]
struct Properties<'a> {
facts: Option<validation::CertificateFacts<'a>>,
usable: bool,
issues: Option<crate::constraints::BasicConstraints>,
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<'a, V: Verify, W: Watch, R: Recognizer> validation::Driver<'a, R> for Search<'a, '_, V, W, R> {
type VerificationError = V::Error;
fn verified(
&mut self,
certificate: &'a Certificate<'a>,
key: WorkingKey<'a>,
) -> BuildResult<Option<Verified>, V::Error, R::Error> {
Self::verified(self, certificate, key)
}
fn facts(
&mut self,
certificate: &'a Certificate<'a>,
_settings: &validation::Settings<'a, '_, R>,
) -> BuildResult<Option<validation::CertificateFacts<'a>>, V::Error, R::Error> {
Ok(properties(self, certificate)?.facts)
}
}
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> {
validation::spend_names(self, left, right)
}
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_candidate(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<'a>, 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 settings = validation::Settings {
execution: search.execution,
interval: search.interval,
policy: search.policy,
policies: search.bounds.policies,
recognizer: search.recognizer,
};
let facts = validation::certificate_facts(certificate, &settings, search)?;
let usable = facts.is_some_and(|facts| facts.rejected.is_none());
let self_issued = facts.is_some_and(|facts| facts.self_issued);
let issuing = facts.and_then(|facts| {
if facts.issuing.is_none() {
facts.basic
} else {
None
}
});
let publishes_itself = usable
&& self_issued
&& publishes_anchor(search, certificate)?
&& search.verified(
certificate,
WorkingKey::standing(certificate.tbs().public_key()),
)? == Some(Verified::Yes);
let known = Properties {
facts,
usable,
issues: issuing,
publishes_itself,
};
search
.properties
.try_insert(at, known)
.map_err(|reason| certificate.tbs().subject().resource(reason))?;
Ok(known)
}
fn validate_candidate<'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 settings = validation::Settings {
execution: search.execution,
interval: search.interval,
policy: search.policy,
policies: search.bounds.policies,
recognizer: search.recognizer,
};
match validation::process(
validation::Chain::Constructed(chain),
anchor,
&settings,
search,
)? {
Validation::Accepted(facts) => Ok(Some(facts.into_policies())),
Validation::Rejected(_) => Ok(None),
Validation::Incomplete(reason) => {
if !search.stopped() {
search.stop(reason);
}
Ok(None)
}
}
}
#[cfg(test)]
mod tests;