use super::{Anchor, Bounds, Interval, Path, Policy, Truncation, Verified, Verify, build};
use crate::fixtures::{
CertificateParts, bits, boolean, cat, extension, ia5, int, oid as oid_of, printable, seq, set,
tlv, utc_time,
};
use crate::{Certificate, oid};
use alloc::vec;
use alloc::vec::Vec;
use p256::ecdsa::signature::{Signer, Verifier};
use structured_asn1::{Oid, Parser};
#[test]
fn ordinary_interval_does_not_alias_validity_leap_label() {
let parts = CertificateParts {
validity: seq(&[
crate::fixtures::generalized_time("20161231235960Z"),
crate::fixtures::generalized_time("20170101000000Z"),
]),
..CertificateParts::default()
};
let bytes = parts.build();
let parser = Parser::der(crate::fixtures::memory());
let certificate = Certificate::read(&parser, &bytes, 0).unwrap();
let validity = certificate.tbs().validity();
assert!(!Interval::at(1_483_228_799).inside(validity));
assert!(Interval::at(1_483_228_800).inside(validity));
}
#[test]
fn lossless_interval_preserves_inserted_second_and_fractional_endpoints() {
let der = CertificateParts {
validity: seq(&[
crate::fixtures::generalized_time("20161231235960Z"),
crate::fixtures::generalized_time("20170101000000Z"),
]),
..CertificateParts::default()
}
.build();
let parser = Parser::der(crate::fixtures::memory());
let certificate = Certificate::read(&parser, &der, 0).unwrap();
let validity = certificate.tbs().validity();
let leap = validity.not_before().instant();
let after = crate::temporal::Instant::leap_after(leap.seconds(), 123).unwrap();
let interval = Interval::between_instants(after, leap);
assert_eq!(interval.from_instant(), leap);
assert_eq!(interval.to_instant(), after);
assert!(interval.inside(validity));
assert!(Interval::at_instant(leap).inside(validity));
assert!(!Interval::at(leap.seconds()).inside(validity));
let after_end = crate::temporal::Instant::new(validity.not_after().seconds(), 1).unwrap();
assert!(!Interval::at_instant(after_end).inside(validity));
}
const ECDSA_SHA256: Oid<'static> =
Oid::from_bytes(&[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]);
const PRIME256V1: Oid<'static> = Oid::from_bytes(&[0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]);
fn key(seed: u8) -> p256::ecdsa::SigningKey {
let mut scalar = [1u8; 32];
scalar[31] = seed;
p256::ecdsa::SigningKey::from_slice(&scalar).expect("a scalar inside the field")
}
fn public(signing: &p256::ecdsa::SigningKey) -> Vec<u8> {
described(signing, oid_of(PRIME256V1))
}
fn keyed_over(signing: &p256::ecdsa::SigningKey, width: usize) -> Vec<u8> {
described(signing, tlv(0x04, &alloc::vec![0x41; width]))
}
fn parameterless(signing: &p256::ecdsa::SigningKey) -> Vec<u8> {
let point = signing.verifying_key().to_sec1_point(false);
seq(&[seq(&[oid_of(oid::EC_PUBLIC_KEY)]), bits(point.as_ref())])
}
fn described(signing: &p256::ecdsa::SigningKey, parameters: Vec<u8>) -> Vec<u8> {
let point = signing.verifying_key().to_sec1_point(false);
seq(&[
seq(&[oid_of(oid::EC_PUBLIC_KEY), parameters]),
bits(point.as_ref()),
])
}
fn elsewhere(signing: &p256::ecdsa::SigningKey) -> Vec<u8> {
let point = signing.verifying_key().to_sec1_point(false);
seq(&[
seq(&[oid_of(oid::RSA_ENCRYPTION), tlv(0x05, &[])]),
bits(point.as_ref()),
])
}
fn named(common: &str) -> Vec<u8> {
seq(&[set(&[seq(&[oid_of(oid::COMMON_NAME), printable(common)])])])
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Authority {
Absent,
Unlimited,
Below(u32),
}
struct Mint<'a> {
subject: &'a str,
issuer: &'a str,
key: u8,
parameters: Option<usize>,
signer: u8,
serial: u32,
authority: Authority,
usage: Option<u8>,
validity: (&'a str, &'a str),
extra: Vec<Vec<u8>>,
subject_der: Option<Vec<u8>>,
issuer_der: Option<Vec<u8>>,
inherits: bool,
foreign: bool,
}
impl Default for Mint<'_> {
fn default() -> Self {
Self {
subject: "Subject",
issuer: "Issuer",
key: 1,
parameters: None,
signer: 2,
serial: 1,
authority: Authority::Absent,
usage: None,
validity: ("240101000000Z", "300101000000Z"),
extra: Vec::new(),
subject_der: None,
issuer_der: None,
inherits: false,
foreign: false,
}
}
}
impl Mint<'_> {
fn build(&self) -> Vec<u8> {
let mut extensions = Vec::new();
match self.authority {
Authority::Absent => {}
Authority::Unlimited => extensions.push(extension(
oid::BASIC_CONSTRAINTS,
true,
&seq(&[boolean(true)]),
)),
Authority::Below(length) => extensions.push(extension(
oid::BASIC_CONSTRAINTS,
true,
&seq(&[boolean(true), int(length)]),
)),
}
if let Some(first) = self.usage {
let unused = u8::try_from(first.trailing_zeros()).expect("a byte has eight bits");
extensions.push(extension(
oid::KEY_USAGE,
true,
&tlv(0x03, &[unused, first]),
));
}
extensions.extend(self.extra.iter().cloned());
let parts = CertificateParts {
serial: int(self.serial),
inner_algorithm: seq(&[oid_of(ECDSA_SHA256)]),
outer_algorithm: seq(&[oid_of(ECDSA_SHA256)]),
issuer: self
.issuer_der
.clone()
.unwrap_or_else(|| named(self.issuer)),
subject: self
.subject_der
.clone()
.unwrap_or_else(|| named(self.subject)),
validity: seq(&[utc_time(self.validity.0), utc_time(self.validity.1)]),
key: match (self.foreign, self.inherits, self.parameters) {
(true, _, _) => elsewhere(&key(self.key)),
(false, true, _) => parameterless(&key(self.key)),
(false, false, Some(width)) => keyed_over(&key(self.key), width),
(false, false, None) => public(&key(self.key)),
},
extensions: (!extensions.is_empty()).then(|| seq(&extensions)),
signature: bits(&[]),
..CertificateParts::default()
};
let signed = parts.tbs();
let signature: p256::ecdsa::Signature = key(self.signer).sign(&signed);
CertificateParts {
signature: bits(signature.to_der().as_bytes()),
..parts
}
.build()
}
}
struct Ecdsa;
impl Verify for Ecdsa {
type Error = core::convert::Infallible;
fn verify(
&self,
algorithm: &crate::AlgorithmIdentifier<'_>,
key: super::WorkingKey<'_>,
signed: &[u8],
signature: structured_asn1::BitString<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<Verified, Self::Error> {
Ok({
if algorithm.algorithm() != ECDSA_SHA256
|| key.key().algorithm().algorithm() != oid::EC_PUBLIC_KEY
{
return Ok(Verified::Unsupported);
}
let (Some(octets), Some(value)) = (key.key().key_octets(), signature.octets()) else {
return Ok(Verified::No);
};
let (Ok(verifying), Ok(signature)) = (
p256::ecdsa::VerifyingKey::from_sec1_bytes(octets),
p256::ecdsa::Signature::from_der(value),
) else {
return Ok(Verified::No);
};
if verifying.verify(signed, &signature).is_ok() {
Verified::Yes
} else {
Verified::No
}
})
}
}
struct Curved;
impl Verify for Curved {
type Error = core::convert::Infallible;
fn verify(
&self,
algorithm: &crate::AlgorithmIdentifier<'_>,
key: super::WorkingKey<'_>,
signed: &[u8],
signature: structured_asn1::BitString<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<Verified, Self::Error> {
Ok({
if algorithm.algorithm() != ECDSA_SHA256
|| key.key().algorithm().algorithm() != oid::EC_PUBLIC_KEY
{
return Ok(Verified::Unsupported);
}
let Some(parameters) = key.parameters() else {
return Ok(Verified::Unsupported);
};
if parameters != oid_of(PRIME256V1).as_slice() {
return Ok(Verified::Unsupported);
}
let (Some(octets), Some(value)) = (key.key().key_octets(), signature.octets()) else {
return Ok(Verified::No);
};
let (Ok(verifying), Ok(signature)) = (
p256::ecdsa::VerifyingKey::from_sec1_bytes(octets),
p256::ecdsa::Signature::from_der(value),
) else {
return Ok(Verified::No);
};
if verifying.verify(signed, &signature).is_ok() {
Verified::Yes
} else {
Verified::No
}
})
}
}
fn moment() -> Interval {
Interval::at(1_767_225_600)
}
fn context<'a, 'p>(
anchors: &'p [Anchor<'a>],
bounds: Bounds,
execution: &'p structured_asn1::memory::Memory,
) -> super::Context<'a, 'p, Ecdsa, (), ()> {
asking(anchors, bounds, Policy::default(), execution)
}
fn asking<'a, 'p>(
anchors: &'p [Anchor<'a>],
bounds: Bounds,
policy: Policy<'a>,
execution: &'p structured_asn1::memory::Memory,
) -> super::Context<'a, 'p, Ecdsa, (), ()> {
super::Context {
execution,
anchors,
interval: moment(),
bounds,
policy,
verifier: &Ecdsa,
watch: &(),
recognizer: &(),
}
}
#[test]
fn anchor_folding_refuses_storage_before_allocating() {
let der = Mint {
subject: "Root",
issuer: "Root",
..Mint::default()
}
.build();
let parser = Parser::der(crate::fixtures::memory());
let root = Certificate::read(&parser, &der, 201).unwrap();
let anchors = [Anchor::from_certificate(&root)];
let execution = structured_asn1::memory::Memory::new(alloc::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(0, 1_000_000),
));
let options = context(&anchors, Bounds::default(), &execution);
let mut spent = 0;
let mut halted = None;
let error = super::fold_anchors(&options, &mut spent, &mut halted).unwrap_err();
assert_eq!(
error.code(),
crate::ErrorCode::Encoding(structured_asn1::ErrorCode::LimitExceeded(
structured_asn1::Limit::Memory
))
);
let original = root.tbs().subject().as_bytes().as_ptr() as usize - der.as_ptr() as usize;
assert_eq!(error.offset(), 201 + original as u64);
assert_eq!(halted, None);
assert_eq!(execution.used(), 0);
}
#[test]
fn anchor_work_refusal_stops_before_target_processing() {
let der = Mint::default().build();
let parser = Parser::der(crate::fixtures::memory());
let target = Certificate::read(&parser, &der, 0).unwrap();
let anchors = [Anchor::from_certificate(&target)];
let execution = crate::fixtures::memory();
let options = context(
&anchors,
Bounds {
work: 0,
..Bounds::default()
},
&execution,
);
let outcome = build(&target, &[], &options).unwrap();
assert!(outcome.paths().is_empty());
assert_eq!(outcome.truncated(), Some(Truncation::Work));
assert_eq!(execution.used(), 0);
assert_eq!(execution.work_used(), 0);
}
#[test]
fn anchor_folding_uses_the_aggregate_work_domain() {
use structured_asn1::memory::{Memory, MemoryAccount};
let der = Mint::default().build();
let parser = Parser::der(crate::fixtures::memory());
let target = Certificate::read(&parser, &der, 101).unwrap();
let anchors = [Anchor::from_certificate(&target)];
let execution = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(1_000_000, 0)));
let options = context(&anchors, Bounds::default(), &execution);
let mut spent = 0;
let mut halted = None;
let error = super::fold_anchors(&options, &mut spent, &mut halted).unwrap_err();
assert!(error.is_resource());
let at = target.tbs().subject().as_bytes().as_ptr() as usize - der.as_ptr() as usize;
assert_eq!(error.offset(), 101 + at as u64);
assert_eq!(spent, 0);
assert_eq!(halted, None);
assert_eq!(execution.used(), 0);
assert_eq!(execution.work_used(), 0);
}
#[test]
fn an_exhausted_edge_counter_never_overflows() {
let parser = Parser::der(crate::fixtures::memory());
let ders = [
Mint::default().build(),
Mint {
subject: "Root",
issuer: "Root",
..Mint::default()
}
.build(),
];
let read = parse(&parser, &ders);
let target = &read[0];
let anchors = [Anchor::from_certificate(&read[1])];
let execution = crate::fixtures::memory();
let mut pool = execution.vector();
pool.try_push(&read[1]).unwrap();
let mut search = super::Search {
execution: &execution,
supplied: &[],
target,
pool: Some(pool),
anchors: &anchors,
interval: moment(),
bounds: Bounds {
edges: usize::MAX,
..Bounds::default()
},
policy: Policy::default(),
verifier: &(),
watch: &(),
recognizer: &(),
visited: 0,
edges: usize::MAX,
spent: 0,
halted: None,
noted: None,
found: execution.vector(),
signatures: execution.map(),
properties: execution.map(),
};
super::settle(&mut search, target, &[target]).unwrap();
assert_eq!(search.halted, Some(Truncation::Edges));
assert_eq!(search.edges, usize::MAX);
search.halted = None;
let mut cursor = 0;
assert!(
super::next_issuer(&mut search, target, &[target], false, &mut cursor)
.unwrap()
.is_none()
);
assert_eq!(search.halted, Some(Truncation::Edges));
assert_eq!(search.edges, usize::MAX);
}
#[test]
fn search_work_uses_the_aggregate_domain() {
use structured_asn1::memory::{Memory, MemoryAccount};
let der = Mint::default().build();
let parser = Parser::der(crate::fixtures::memory());
let target = Certificate::read(&parser, &der, 303).unwrap();
let execution = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, 64)));
let mut search = super::Search {
execution: &execution,
supplied: &[],
target: &target,
pool: None,
anchors: &[],
interval: moment(),
bounds: Bounds::default(),
policy: Policy::default(),
verifier: &(),
watch: &(),
recognizer: &(),
visited: 0,
edges: 0,
spent: 0,
halted: None,
noted: None,
found: execution.vector(),
signatures: execution.map(),
properties: execution.map(),
};
assert!(search.spend(32).unwrap());
assert!(search.spend(32).unwrap());
assert_eq!(execution.work_used(), 64);
let error = search.spend(1).unwrap_err();
assert!(error.is_resource());
let at = target.tbs().subject().as_bytes().as_ptr() as usize - der.as_ptr() as usize;
assert_eq!(error.offset(), 303 + at as u64);
assert_eq!(execution.work_used(), 64);
assert_eq!(search.halted, None);
}
fn parse<'a>(parser: &'a Parser, ders: &'a [Vec<u8>]) -> Vec<Certificate<'a>> {
ders.iter()
.map(|der| Certificate::read(parser, der, 0).expect("a certificate this test wrote"))
.collect()
}
#[test]
fn supplied_path_and_search_verify_the_same_original_chain() {
let ders = chain(Authority::Unlimited, Some(0x04));
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchor = Anchor::from_certificate(&read[2]);
let execution = crate::fixtures::memory();
let context = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &(),
};
let super::Validation::Accepted(facts) =
super::validate(&[&read[0], &read[1]], anchor, &context).unwrap()
else {
panic!("the original signed chain is accepted")
};
assert_eq!(
facts.key.key().as_bytes().as_ptr(),
read[0].tbs().public_key().as_bytes().as_ptr()
);
let anchors = [anchor];
let search_context = super::Context {
execution: &execution,
anchors: &anchors,
interval: moment(),
bounds: Bounds::default(),
policy: Policy::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &(),
};
let outcome = build(&read[0], &[&read[1]], &search_context).unwrap();
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.paths()[0].policies(), facts.policies());
assert_eq!(outcome.paths()[0].len(), 2);
assert_eq!(facts.non_self_issued_intermediates, 1);
drop(outcome);
drop(facts);
assert_eq!(execution.used(), 0);
}
#[test]
fn supplied_intermediate_role_and_issuer_failures_are_located() {
for (authority, usage, expected) in [
(
Authority::Absent,
None,
super::RejectionReason::NotAuthority,
),
(
Authority::Unlimited,
Some(0x80),
super::RejectionReason::KeyCertSign,
),
] {
let ders = chain(authority, usage);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let execution = crate::fixtures::memory();
let context = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &(),
};
let super::Validation::Rejected(rejected) = super::validate(
&[&read[0], &read[1]],
Anchor::from_certificate(&read[2]),
&context,
)
.unwrap() else {
panic!("an unauthorized issuer is rejected")
};
assert_eq!(rejected.at, Some(super::ConstraintSource::Certificate(1)));
assert_eq!(rejected.reason, expected);
let super::Validation::Rejected(rejected) =
super::validate(&[&read[0]], Anchor::from_certificate(&read[2]), &context).unwrap()
else {
panic!("the root did not name itself as this leaf's issuer")
};
assert_eq!(rejected.at, Some(super::ConstraintSource::Certificate(0)));
assert_eq!(rejected.reason, super::RejectionReason::Issuer);
assert_eq!(execution.used(), 0);
}
}
#[test]
fn supplied_path_length_reports_the_constraining_ca_and_original_limit() {
let ders = [
Mint {
subject: "Leaf",
issuer: "Lower",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "Lower",
issuer: "Upper",
key: 2,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Upper",
issuer: "Root",
key: 3,
signer: 4,
authority: Authority::Below(0),
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 4,
signer: 4,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let execution = crate::fixtures::memory();
let context = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &(),
};
let super::Validation::Rejected(rejected) = super::validate(
&[&read[0], &read[1], &read[2]],
Anchor::from_certificate(&read[3]),
&context,
)
.unwrap() else {
panic!("the upper CA allows no intermediate below it")
};
assert_eq!(rejected.at, Some(super::ConstraintSource::Certificate(1)));
assert_eq!(
rejected.reason,
super::RejectionReason::PathLength {
source: super::ConstraintSource::Certificate(2),
limit: 0
}
);
assert_eq!(execution.used(), 0);
}
#[test]
fn supplied_effective_target_key_retains_inherited_parameters() {
let mut ders = inheriting();
ders[0] = Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
inherits: true,
..Mint::default()
}
.build();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let execution = crate::fixtures::memory();
let context = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Curved,
watch: &(),
recognizer: &(),
};
let super::Validation::Accepted(facts) = super::validate(
&[&read[0], &read[1]],
Anchor::from_certificate(&read[2]),
&context,
)
.unwrap() else {
panic!("inherited curve supports both original signatures")
};
assert_eq!(read[0].tbs().public_key().algorithm().parameters(), None);
let parameters = facts.key.parameters().unwrap();
let root_parameters = read[2].tbs().public_key().algorithm().parameters().unwrap();
assert_eq!(parameters, root_parameters);
assert_eq!(parameters.as_ptr(), root_parameters.as_ptr());
assert_eq!(
facts.key.key().as_bytes().as_ptr(),
read[0].tbs().public_key().as_bytes().as_ptr()
);
drop(facts);
assert_eq!(execution.used(), 0);
}
#[test]
fn inheritance_local_refusal_precedes_shared_work_admission() {
struct Stopped;
impl super::Meter for Stopped {
fn spend(&mut self, _work: u64) -> crate::Result<bool> {
Ok(false)
}
}
let ders = inheriting();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
for work in [0, 1_000_000] {
let execution = structured_asn1::memory::Memory::new(alloc::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(0, work),
));
let result = super::WorkingKey::standing(read[2].tbs().public_key()).below(
&read[1],
&execution,
&mut Stopped,
);
assert!(result.unwrap().is_none());
assert_eq!(execution.work_used(), 0);
assert_eq!(execution.used(), 0);
}
}
#[test]
fn public_subject_projection_borrows_effective_parameters_without_key_catalogue() {
let ders = inheriting();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let execution = structured_asn1::memory::Memory::new(alloc::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(0, 1_000_000),
));
let root = super::WorkingKey::standing(read[2].tbs().public_key());
let projected = root.subject(&read[1], &execution).unwrap();
assert_eq!(
projected.key().as_bytes().as_ptr(),
read[1].tbs().public_key().as_bytes().as_ptr()
);
assert_eq!(
projected.parameters().unwrap().as_ptr(),
root.parameters().unwrap().as_ptr()
);
assert_eq!(execution.used(), 0);
let stopped = structured_asn1::memory::Memory::new(alloc::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(0, 0),
));
assert!(root.subject(&read[1], &stopped).unwrap_err().is_resource());
assert_eq!(stopped.work_used(), 0);
}
#[test]
fn inherited_algorithm_matching_is_admitted_at_the_current_certificate() {
struct Exhaust(usize);
impl Verify for Exhaust {
type Error = core::convert::Infallible;
fn verify(
&self,
_algorithm: &crate::AlgorithmIdentifier<'_>,
_key: super::WorkingKey<'_>,
_signed: &[u8],
_signature: structured_asn1::BitString<'_>,
execution: &structured_asn1::memory::Memory,
) -> Result<Verified, Self::Error> {
execution
.charge_work((self.0 - execution.work_used()) as u64)
.unwrap();
Ok(Verified::Yes)
}
}
let ders = inheriting();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let work_limit = 1_000_000;
let execution = structured_asn1::memory::Memory::new(alloc::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(1_000_000, work_limit),
));
let context = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Exhaust(work_limit),
watch: &(),
recognizer: &(),
};
let super::BuildError::Processing(error) = super::validate(
&[&read[0], &read[1]],
Anchor::from_certificate(&read[2]),
&context,
)
.unwrap_err();
let at = read[1].tbs().subject().as_bytes().as_ptr() as usize - ders[1].as_ptr() as usize;
assert_eq!(error.offset(), at as u64);
assert_eq!(
error.code(),
crate::ErrorCode::Encoding(structured_asn1::ErrorCode::LimitExceeded(
structured_asn1::Limit::Work
))
);
assert_eq!(execution.work_used(), work_limit);
assert_eq!(execution.used(), 0);
}
#[test]
fn supplied_name_constraint_failure_names_anchor_or_ca_source() {
let excluded = extension(
oid::NAME_CONSTRAINTS,
true,
&seq(&[tlv(0xa1, &seq(&[tlv(0x82, b"blocked.example")]))]),
);
for in_anchor in [false, true] {
let ders = [
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
extra: vec![also_known_as("blocked.example")],
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
authority: Authority::Unlimited,
extra: if in_anchor {
Vec::new()
} else {
vec![excluded.clone()]
},
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
extra: if in_anchor {
vec![excluded.clone()]
} else {
Vec::new()
},
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let execution = crate::fixtures::memory();
let context = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &(),
};
let super::Validation::Rejected(rejected) = super::validate(
&[&read[0], &read[1]],
Anchor::from_certificate(&read[2]),
&context,
)
.unwrap() else {
panic!("explicit DNS exclusion")
};
assert_eq!(rejected.at, Some(super::ConstraintSource::Certificate(0)));
let source = if in_anchor {
super::ConstraintSource::Anchor
} else {
super::ConstraintSource::Certificate(1)
};
assert_eq!(
rejected.reason,
super::RejectionReason::NameConstraints {
source,
failure: super::NameConstraintFailure::Excluded
}
);
let anchors = [Anchor::from_certificate(&read[2])];
let search = build(
&read[0],
&[&read[1]],
&self::context(&anchors, Bounds::default(), &execution),
)
.unwrap();
assert_eq!(search.paths().len(), 0);
assert_eq!(search.truncated(), None);
drop(search);
assert_eq!(execution.used(), 0);
}
}
#[test]
fn supplied_policies_keep_owned_capacity_after_processing_returns() {
const POLICY: Oid<'static> = Oid::from_bytes(&[0x2a, 3, 4, 5]);
let ders = [
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
extra: vec![issued_under(POLICY)],
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let execution = crate::fixtures::memory();
let context = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &(),
};
let super::Validation::Accepted(facts) =
super::validate(&[&read[0]], Anchor::from_certificate(&read[1]), &context).unwrap()
else {
panic!("named policy")
};
assert_eq!(facts.policies(), &[POLICY]);
let retained = execution.used();
assert!(retained > 0);
let work = execution.work_used();
let policies = facts.into_policies();
assert_eq!(&*policies, &[POLICY]);
assert_eq!(execution.used(), retained);
assert_eq!(execution.work_used(), work);
assert_eq!(
retained,
policies.capacity() * core::mem::size_of::<Oid<'_>>()
);
drop(policies);
assert_eq!(execution.used(), 0);
}
#[test]
fn supplied_malformed_constraints_keep_original_decode_location() {
let ders = [
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
extra: vec![extension(oid::NAME_CONSTRAINTS, true, &tlv(0x01, &[0xff]))],
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let execution = crate::fixtures::memory();
let expected = read[0]
.tbs()
.extensions()
.name_constraints(&execution)
.unwrap_err();
let context = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &(),
};
let super::Validation::Rejected(rejected) =
super::validate(&[&read[0]], Anchor::from_certificate(&read[1]), &context).unwrap()
else {
panic!("malformed constraints")
};
assert_eq!(rejected.at, Some(super::ConstraintSource::Certificate(0)));
assert_eq!(
rejected.reason,
super::RejectionReason::MalformedExtension {
extension: oid::NAME_CONSTRAINTS,
code: expected.code(),
field: expected.field(),
offset: expected.offset()
}
);
assert_eq!(execution.used(), 0);
}
#[test]
fn a_chain_to_an_anchor_the_caller_named_is_a_path() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let (leaf, intermediate, root) = (&read[0], &read[1], &read[2]);
let anchors = alloc::vec![Anchor::from_certificate(root)];
let pool = alloc::vec![intermediate, root];
let outcome = build(
leaf,
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert!(!outcome.is_truncated());
let path: &Path<'_> = &outcome.paths()[0];
assert_eq!(path.len(), 2);
assert!(core::ptr::eq(path.certificates()[0], leaf));
assert!(core::ptr::eq(path.certificates()[1], intermediate));
assert!(path.anchor().certificate().is_some());
}
#[test]
fn returned_paths_retain_their_storage_charge() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = [Anchor::from_certificate(&read[1])];
let execution = crate::fixtures::memory();
let outcome = build(
&read[0],
&[],
&context(&anchors, Bounds::default(), &execution),
)
.unwrap();
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.paths()[0].certificates().len(), 1);
assert!(
execution.used()
>= core::mem::size_of::<Path<'_>>() + core::mem::size_of::<&Certificate<'_>>()
);
drop(outcome);
assert_eq!(execution.used(), 0);
}
#[test]
fn a_verifier_failure_preserves_the_provider_cause() {
struct Refusing<'a>(&'a structured_asn1::memory::Memory);
impl Verify for Refusing<'_> {
type Error = &'static str;
fn verify(
&self,
_: &crate::AlgorithmIdentifier<'_>,
_: super::WorkingKey<'_>,
_: &[u8],
_: structured_asn1::BitString<'_>,
execution: &structured_asn1::memory::Memory,
) -> core::result::Result<Verified, Self::Error> {
assert!(execution.same_account(self.0));
Err("provider execution failed")
}
}
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = [Anchor::from_certificate(&read[1])];
let execution = crate::fixtures::memory();
let options = super::Context {
execution: &execution,
anchors: &anchors,
interval: moment(),
bounds: Bounds::default(),
policy: Policy::default(),
verifier: &Refusing(&execution),
watch: &(),
recognizer: &(),
};
let error = build(&read[0], &[], &options).unwrap_err();
assert!(matches!(
error,
super::BuildError::Verification("provider execution failed")
));
assert_eq!(execution.used(), 0);
}
#[test]
fn a_recognizer_failure_preserves_the_policy_cause() {
struct Refusing<'a>(&'a structured_asn1::memory::Memory);
impl crate::extension::Recognizer for Refusing<'_> {
type Error = &'static str;
fn recognizes(
&self,
_: Oid<'_>,
execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
assert!(execution.same_account(self.0));
Err("scheme policy refused")
}
}
let unknown = Oid::from_bytes(&[0x2b, 6, 1, 4, 1, 127]);
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
extra: alloc::vec![extension(unknown, true, &seq(&[]))],
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = [Anchor::from_certificate(&read[1])];
let execution = crate::fixtures::memory();
let options = super::Context {
execution: &execution,
anchors: &anchors,
interval: moment(),
bounds: Bounds::default(),
policy: Policy::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &Refusing(&execution),
};
let error = build(&read[0], &[], &options).unwrap_err();
assert!(matches!(
error,
super::BuildError::Recognition("scheme policy refused")
));
assert_eq!(execution.used(), 0);
}
#[test]
fn iterative_search_keeps_a_deep_chain_in_order() {
const DEPTH: usize = 128;
let names: Vec<_> = (0..=DEPTH)
.map(|at| alloc::format!("Authority {at:04}"))
.collect();
let ders: Vec<_> = (0..=DEPTH)
.map(|at| {
Mint {
subject: &names[at],
issuer: &names[(at + 1).min(DEPTH)],
key: 1,
signer: 1,
authority: Authority::Unlimited,
..Mint::default()
}
.build()
})
.collect();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let pool: Vec<_> = read[1..DEPTH].iter().collect();
let anchors = [Anchor::from_certificate(&read[DEPTH])];
let execution = crate::fixtures::memory();
let bounds = Bounds {
depth: DEPTH,
edges: DEPTH * DEPTH + DEPTH,
work: 10_000_000,
..Bounds::default()
};
let outcome = build(&read[0], &pool, &context(&anchors, bounds, &execution)).unwrap();
assert_eq!(outcome.truncated(), None);
assert_eq!(outcome.paths().len(), 1);
let certificates = outcome.paths()[0].certificates();
assert_eq!(certificates.len(), DEPTH);
for (actual, expected) in certificates.iter().zip(&read[..DEPTH]) {
assert!(core::ptr::eq(*actual, expected));
}
drop(outcome);
assert_eq!(execution.used(), 0);
}
#[test]
fn a_root_the_caller_did_not_name_ends_nothing() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(&[], Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
}
fn chain(intermediate: Authority, usage: Option<u8>) -> Vec<Vec<u8>> {
alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
authority: intermediate,
usage,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
]
}
#[test]
fn an_issuer_that_does_not_say_it_issues_certificates_is_not_one() {
for authority in [Authority::Absent, Authority::Below(0)] {
let ders = chain(authority, None);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
if authority == Authority::Absent {
assert!(outcome.paths().is_empty());
} else {
assert!(outcome.paths().is_empty() || outcome.paths().len() == 1);
}
}
}
#[test]
fn an_authority_that_allows_nothing_below_it_ends_the_path_there() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Middle",
key: 1,
signer: 4,
..Mint::default()
}
.build(),
Mint {
subject: "Middle",
issuer: "Intermediate",
key: 4,
signer: 2,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
authority: Authority::Below(0),
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[3])];
let pool = alloc::vec![&read[1], &read[2]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
let ders = alloc::vec![
ders[0].clone(),
ders[1].clone(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
authority: Authority::Below(1),
..Mint::default()
}
.build(),
ders[3].clone(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[3])];
let pool = alloc::vec![&read[1], &read[2]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
}
#[test]
fn an_issuer_whose_key_is_not_for_signing_certificates_is_not_one() {
let signing_only = chain(Authority::Unlimited, Some(0b1000_0000));
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &signing_only);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
let allowed = chain(Authority::Unlimited, Some(0b0000_0100));
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &allowed);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
}
fn cross_key(level: usize, at: u8) -> u8 {
10 + u8::try_from(level).expect("a level of this test") * 8 + at
}
fn cross_certified(levels: usize, width: u8) -> Vec<Vec<u8>> {
const NAMES: [&str; 4] = ["Level0", "Level1", "Level2", "Level3"];
let mut ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: NAMES[0],
key: 1,
signer: cross_key(0, 0),
..Mint::default()
}
.build(),
];
let mut serial = 1;
for level in 0..levels {
let top = level + 1 == levels;
for mine in 0..width {
let parents = if top { 1 } else { width };
for parent in 0..parents {
serial += 1;
ders.push(
Mint {
subject: NAMES[level],
issuer: if top { "Root" } else { NAMES[level + 1] },
key: cross_key(level, mine),
signer: if top {
99
} else {
cross_key(level + 1, parent)
},
serial,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
}
}
}
ders.push(
Mint {
subject: "Root",
issuer: "Root",
key: 99,
signer: 99,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
ders
}
#[test]
fn a_search_that_reached_a_bound_says_which_one() {
let ders = cross_certified(3, 3);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
for (bounds, expected) in [
(
Bounds {
paths: 2,
..Bounds::default()
},
Truncation::Paths,
),
(
Bounds {
depth: 2,
..Bounds::default()
},
Truncation::Depth,
),
(
Bounds {
visited: 3,
..Bounds::default()
},
Truncation::Visited,
),
(
Bounds {
edges: 4,
..Bounds::default()
},
Truncation::Edges,
),
(
Bounds {
work: 2_048,
..Bounds::default()
},
Truncation::Work,
),
] {
let outcome = build(
&read[0],
&pool,
&context(&anchors, bounds, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.truncated(), Some(expected), "{bounds:?}");
assert!(outcome.paths().len() <= bounds.paths);
}
}
struct Passed;
impl super::Watch for Passed {
fn out_of_time(&self) -> bool {
true
}
}
#[test]
fn a_clock_the_caller_watches_stops_the_search() {
let ders = chain(Authority::Unlimited, None);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&super::Context {
execution: &crate::fixtures::memory(),
anchors: &anchors,
interval: moment(),
bounds: Bounds::default(),
policy: Policy::default(),
verifier: &Ecdsa,
watch: &Passed,
recognizer: &(),
},
)
.expect("a clock is an answer, not an error");
assert_eq!(outcome.truncated(), Some(Truncation::Time));
assert!(outcome.paths().is_empty());
}
fn spelled(outcome: &super::Outcome<'_>) -> Vec<(Vec<Vec<u8>>, Vec<u8>)> {
outcome
.paths()
.iter()
.map(|path| {
(
path.certificates()
.iter()
.map(|certificate| certificate.tbs().serial().as_bytes().to_vec())
.collect(),
path.anchor().key().as_bytes().to_vec(),
)
})
.collect()
}
#[test]
fn every_route_a_cross_certified_set_offers_is_shown() {
let ders = cross_certified(2, 3);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 3);
assert_eq!(outcome.truncated(), None);
for path in outcome.paths() {
assert_eq!(path.len(), 3);
}
let mut keys: Vec<Vec<u8>> = outcome
.paths()
.iter()
.map(|path| {
path.certificates()[2]
.tbs()
.public_key()
.as_bytes()
.to_vec()
})
.collect();
keys.sort_unstable();
keys.dedup();
assert_eq!(keys.len(), 3);
}
#[test]
fn an_authority_that_reissued_its_own_certificate_is_one_route() {
let leaf = Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
..Mint::default()
}
.build();
let older = Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
serial: 1,
authority: Authority::Unlimited,
validity: ("240101000000Z", "290101000000Z"),
..Mint::default()
}
.build();
let newer = Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
serial: 2,
authority: Authority::Unlimited,
validity: ("240102000000Z", "300101000000Z"),
..Mint::default()
}
.build();
let root = Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build();
let ders = alloc::vec![leaf, older, newer, root];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[3])];
let pool = alloc::vec![&read[1], &read[2]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
}
fn recognized_twice() -> Vec<Vec<u8>> {
alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "RootOne",
key: 2,
signer: 3,
serial: 1,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "RootTwo",
key: 2,
signer: 4,
serial: 2,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "RootOne",
issuer: "RootOne",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "RootTwo",
issuer: "RootTwo",
key: 4,
signer: 4,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
]
}
#[test]
fn a_path_to_each_anchor_the_caller_named_is_shown() {
let ders = recognized_twice();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![
Anchor::from_certificate(&read[3]),
Anchor::from_certificate(&read[4]),
];
let pool = alloc::vec![&read[1], &read[2]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 2);
let mut anchors: Vec<Vec<u8>> = outcome
.paths()
.iter()
.map(|path| path.anchor().key().as_bytes().to_vec())
.collect();
anchors.sort_unstable();
anchors.dedup();
assert_eq!(anchors.len(), 2);
}
#[test]
fn a_path_that_says_something_else_takes_the_place_of_a_repeat() {
let mut ders = cross_certified(2, 3);
let root = ders.pop().expect("the root");
ders.push(
Mint {
subject: "Level1",
issuer: "RootTwo",
key: cross_key(1, 2),
signer: 98,
serial: 100,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
let anchor_ders = alloc::vec![
root,
Mint {
subject: "RootTwo",
issuer: "RootTwo",
key: 98,
signer: 98,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let roots = parse(&parser, &anchor_ders);
let anchors = alloc::vec![
Anchor::from_certificate(&roots[0]),
Anchor::from_certificate(&roots[1]),
];
let pool: Vec<&Certificate<'_>> = read[1..].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
paths: 2,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 2);
assert_eq!(outcome.truncated(), Some(Truncation::Paths));
let mut keys: Vec<Vec<u8>> = outcome
.paths()
.iter()
.map(|path| path.anchor().key().as_bytes().to_vec())
.collect();
keys.sort_unstable();
keys.dedup();
assert_eq!(keys.len(), 2, "one path to each anchor, not two to one");
}
#[test]
fn two_certificates_that_issued_each_other_do_not_send_the_search_round() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "One",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "One",
issuer: "Two",
key: 2,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Two",
issuer: "One",
key: 3,
signer: 2,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let pool = alloc::vec![&read[1], &read[2]];
let outcome = build(
&read[0],
&pool,
&context(&[], Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
assert_eq!(outcome.truncated(), None);
let anchors = alloc::vec![Anchor::new(
read[2].tbs().subject(),
read[2].tbs().public_key()
)];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
}
#[test]
fn a_rollover_link_between_two_keys_of_one_name_is_a_path() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Authority",
key: 1,
signer: 5,
..Mint::default()
}
.build(),
Mint {
subject: "Authority",
issuer: "Authority",
key: 5,
signer: 6,
serial: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Authority",
issuer: "Authority",
key: 6,
signer: 6,
serial: 4,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![
Anchor::from_certificate(&read[1]),
Anchor::from_certificate(&read[2]),
];
let pool = alloc::vec![&read[1], &read[2]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 2);
let mut lengths: Vec<usize> = outcome.paths().iter().map(Path::len).collect();
lengths.sort_unstable();
assert_eq!(lengths, alloc::vec![1, 2]);
let execution = crate::fixtures::memory();
let validation = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &(),
};
let super::Validation::Accepted(facts) =
super::validate(&[&read[0], &read[1]], anchors[1], &validation).unwrap()
else {
panic!("the rollover is accepted against the old anchor")
};
assert_eq!(facts.non_self_issued_intermediates, 0);
}
#[test]
fn a_pool_holding_one_certificate_twice_spends_for_one() {
let ders = chain(Authority::Unlimited, None);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1], &read[1], &read[1], &read[1], &read[1]];
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
edges: 4,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
}
#[test]
fn a_pool_built_to_explode_answers_inside_its_bounds() {
let ders = cross_certified(4, 4);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let bounds = Bounds::default();
let outcome = build(
&read[0],
&pool,
&context(&anchors, bounds, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert!(outcome.paths().len() <= bounds.paths);
assert!(outcome.is_truncated());
for path in outcome.paths() {
assert_eq!(path.len(), 5);
}
}
#[test]
fn a_budget_that_runs_out_leaves_behind_what_it_had_found() {
let ders = cross_certified(3, 3);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
work: 12_000,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a spent budget is an answer, not an error");
assert!(!outcome.paths().is_empty(), "what was found is still shown");
assert_eq!(outcome.truncated(), Some(Truncation::Work));
let whole = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(whole.paths().len() > outcome.paths().len());
}
#[test]
fn the_same_pool_gives_the_same_paths_twice() {
let ders = cross_certified(3, 3);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let first = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
let second = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(spelled(&first), spelled(&second));
}
fn constrained(intermediate: Option<Vec<u8>>, root: Option<Vec<u8>>) -> Vec<Vec<u8>> {
alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
authority: Authority::Unlimited,
extra: intermediate.into_iter().collect(),
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
extra: root.into_iter().collect(),
..Mint::default()
}
.build(),
]
}
fn permitting(domain: &str) -> Vec<u8> {
extension(
oid::NAME_CONSTRAINTS,
true,
&seq(&[tlv(0xa0, &seq(&[tlv(0x82, domain.as_bytes())]))]),
)
}
fn also_known_as(host: &str) -> Vec<u8> {
extension(
oid::SUBJECT_ALT_NAME,
false,
&seq(&[tlv(0x82, host.as_bytes())]),
)
}
fn paths_under(intermediate: Option<Vec<u8>>, root: Option<Vec<u8>>, host: &str) -> usize {
let mut ders = constrained(intermediate, root);
ders[0] = Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
extra: alloc::vec![also_known_as(host)],
..Mint::default()
}
.build();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend")
.paths()
.len()
}
#[test]
fn a_name_outside_what_its_authority_allowed_is_not_a_path() {
let permitted = Some(permitting("example.com"));
assert_eq!(paths_under(permitted.clone(), None, "host.example.com"), 1);
assert_eq!(paths_under(permitted, None, "host.evil.net"), 0);
}
#[test]
fn what_the_anchor_allows_binds_the_path_below_it() {
let permitted = Some(permitting("example.com"));
assert_eq!(paths_under(None, permitted.clone(), "host.example.com"), 1);
assert_eq!(paths_under(None, permitted, "host.evil.net"), 0);
}
const HOUSE_POLICY: Oid<'static> = Oid::from_bytes(&[0x2a, 0x03, 0x04, 0x01]);
const OTHER_POLICY: Oid<'static> = Oid::from_bytes(&[0x2a, 0x03, 0x04, 0x02]);
fn issued_under(policy: Oid<'_>) -> Vec<u8> {
extension(
oid::CERTIFICATE_POLICIES,
false,
&seq(&[seq(&[oid_of(policy)])]),
)
}
fn demanding_a_policy() -> Vec<u8> {
extension(oid::POLICY_CONSTRAINTS, true, &seq(&[tlv(0x80, &[0x00])]))
}
fn under_policies(leaf: Vec<Vec<u8>>, intermediate: Vec<Vec<u8>>) -> Vec<Vec<u8>> {
alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
extra: leaf,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
authority: Authority::Unlimited,
extra: intermediate,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
]
}
#[test]
fn the_policy_the_authorities_agreed_on_travels_with_the_path() {
let ders = under_policies(
alloc::vec![issued_under(HOUSE_POLICY)],
alloc::vec![issued_under(HOUSE_POLICY)],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.paths()[0].policies(), &[HOUSE_POLICY]);
}
#[test]
fn an_authority_that_demands_a_policy_is_not_answered_by_a_path_without_one() {
let ders = under_policies(alloc::vec![], alloc::vec![demanding_a_policy()]);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
let ders = under_policies(
alloc::vec![issued_under(HOUSE_POLICY)],
alloc::vec![issued_under(HOUSE_POLICY), demanding_a_policy()],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
}
#[test]
fn a_caller_that_demands_a_policy_gets_no_path_from_certificates_naming_none() {
let ders = under_policies(alloc::vec![], alloc::vec![]);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let demanding = Policy {
explicit: true,
..Policy::default()
};
let outcome = build(
&read[0],
&pool,
&asking(
&anchors,
Bounds::default(),
demanding,
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.paths()[0].policies(), []);
}
#[test]
fn a_policy_bound_of_nothing_leaves_no_room_to_work_a_path_out() {
let ders = chain(Authority::Unlimited, None);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
policies: 0,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a bound is an answer, not an error");
assert!(outcome.paths().is_empty());
assert_eq!(outcome.truncated(), Some(Truncation::Policies));
}
#[test]
fn a_bound_that_ends_the_search_is_the_one_the_answer_names() {
let ders = cross_certified(3, 3);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
depth: 2,
visited: 3,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.truncated(), Some(Truncation::Visited));
}
#[test]
fn a_depth_of_none_leaves_no_path_to_return() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let outcome = build(
&read[0],
&[],
&context(
&anchors,
Bounds {
depth: 0,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a bound is an answer, not an error");
assert!(outcome.paths().is_empty());
assert_eq!(outcome.truncated(), Some(Truncation::Depth));
let outcome = build(
&read[0],
&[],
&context(
&anchors,
Bounds {
depth: 1,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
}
#[test]
fn an_anchors_own_identity_cross_signed_by_another_is_still_a_link() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Provider",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "Provider",
issuer: "Provider",
key: 2,
signer: 2,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Provider",
issuer: "Other",
key: 2,
signer: 4,
serial: 7,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Other",
issuer: "Other",
key: 4,
signer: 4,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![
Anchor::from_certificate(&read[1]),
Anchor::from_certificate(&read[3]),
];
let pool = alloc::vec![&read[1], &read[2]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 2);
let mut anchors: Vec<Vec<u8>> = outcome
.paths()
.iter()
.map(|path| path.anchor().key().as_bytes().to_vec())
.collect();
anchors.sort_unstable();
anchors.dedup();
assert_eq!(anchors.len(), 2);
}
#[test]
fn one_identity_issued_under_two_policies_is_two_answers() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
extra: alloc::vec![issued_under(oid::ANY_POLICY)],
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
serial: 1,
authority: Authority::Unlimited,
extra: alloc::vec![issued_under(HOUSE_POLICY)],
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
serial: 2,
authority: Authority::Unlimited,
extra: alloc::vec![issued_under(OTHER_POLICY)],
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[3])];
let pool = alloc::vec![&read[1], &read[2]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 2);
let mut policies: Vec<Vec<u8>> = outcome
.paths()
.iter()
.flat_map(super::Path::policies)
.map(|policy| policy.as_bytes().to_vec())
.collect();
policies.sort_unstable();
assert_eq!(
policies,
alloc::vec![
HOUSE_POLICY.as_bytes().to_vec(),
OTHER_POLICY.as_bytes().to_vec()
]
);
}
#[test]
fn a_policy_restriction_that_cannot_be_read_is_not_one_that_is_absent() {
let malformed = extension(
oid::POLICY_CONSTRAINTS,
false,
&cat(&[seq(&[tlv(0x80, &[0x00])]), vec![0x00]]),
);
let ders = under_policies(alloc::vec![], alloc::vec![malformed]);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
let ders = under_policies(
alloc::vec![],
alloc::vec![extension(
oid::POLICY_CONSTRAINTS,
false,
&seq(&[tlv(0x80, &[0x05])]),
)],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
}
#[test]
fn a_depth_bound_nothing_could_have_gone_past_is_not_truncation() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let outcome = build(
&read[0],
&[],
&context(
&anchors,
Bounds {
depth: 1,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
}
#[test]
fn a_path_that_takes_a_repeats_place_still_lands_in_the_order_it_was_found() {
let mut ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Mid",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
];
for upper in [31, 32, 33, 34] {
ders.push(
Mint {
subject: "Mid",
issuer: "Up",
key: 2,
signer: upper,
authority: Authority::Unlimited,
serial: u32::from(upper),
..Mint::default()
}
.build(),
);
}
for (upper, root, signer) in [
(31, "RootA", 41),
(32, "RootA", 41),
(33, "RootB", 42),
(34, "RootC", 43),
] {
ders.push(
Mint {
subject: "Up",
issuer: root,
key: upper,
signer,
authority: Authority::Unlimited,
serial: u32::from(upper),
..Mint::default()
}
.build(),
);
}
let roots: Vec<Vec<u8>> = [("RootA", 41), ("RootB", 42), ("RootC", 43)]
.into_iter()
.map(|(name, key)| {
Mint {
subject: name,
issuer: name,
key,
signer: key,
authority: Authority::Unlimited,
..Mint::default()
}
.build()
})
.collect();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let read_roots = parse(&parser, &roots);
let anchors: Vec<Anchor<'_>> = read_roots.iter().map(Anchor::from_certificate).collect();
let pool: Vec<&Certificate<'_>> = read[1..].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
paths: 3,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.truncated(), Some(Truncation::Paths));
let order: Vec<Vec<u8>> = outcome
.paths()
.iter()
.map(|path| {
path.certificates()
.last()
.expect("a path holds certificates")
.tbs()
.serial()
.as_bytes()
.to_vec()
})
.collect();
assert_eq!(
order,
alloc::vec![alloc::vec![31], alloc::vec![33], alloc::vec![34]]
);
}
fn under_names_of(width: usize) -> Vec<Vec<u8>> {
let base = "A".repeat(width);
let mut ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: &base,
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: &base,
issuer: "Root",
key: 2,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
for decoy in 0..20u8 {
let mut name = base.clone();
name.push(char::from(b'B' + decoy));
ders.push(
Mint {
subject: &name,
issuer: "Nowhere",
key: 4,
signer: 4,
serial: u32::from(decoy) + 2,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
}
ders.push(
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
ders
}
#[test]
fn comparing_two_long_names_costs_what_comparing_them_costs() {
let budget = Bounds {
work: 12_000,
..Bounds::default()
};
let short = under_names_of(4);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &short);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a budget this shape does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
let long = under_names_of(2_000);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &long);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.truncated(), Some(Truncation::Work));
}
#[test]
fn a_pool_that_holds_the_target_spends_nothing_on_it() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let bounds = Bounds {
edges: 1,
..Bounds::default()
};
let empty = build(
&read[0],
&[],
&context(&anchors, bounds, &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(empty.paths().len(), 1);
assert_eq!(empty.truncated(), None);
let pool = alloc::vec![&read[0]];
let held = build(
&read[0],
&pool,
&context(&anchors, bounds, &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(held.paths().len(), 1);
assert_eq!(held.truncated(), None);
}
fn permitting_locations(hosts: &[&str]) -> Vec<u8> {
let subtrees: Vec<Vec<u8>> = hosts
.iter()
.map(|host| seq(&[tlv(0x86, host.as_bytes())]))
.collect();
extension(
oid::NAME_CONSTRAINTS,
true,
&seq(&[tlv(0xa0, &cat(&subtrees))]),
)
}
fn located_at(location: &str) -> Vec<u8> {
extension(
oid::SUBJECT_ALT_NAME,
false,
&seq(&[tlv(0x86, location.as_bytes())]),
)
}
fn located_under(width: usize) -> Vec<Vec<u8>> {
let hosts: Vec<alloc::string::String> = (0..16u8)
.map(|which| alloc::format!("h{which}.example.com"))
.chain(core::iter::once(alloc::string::String::from(
"host.example.com",
)))
.collect();
let borrowed: Vec<&str> = hosts.iter().map(alloc::string::String::as_str).collect();
let mut ders = constrained(Some(permitting_locations(&borrowed)), None);
let location = alloc::format!("https://{}@host.example.com/", "a".repeat(width));
ders[0] = Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
extra: alloc::vec![located_at(&location)],
..Mint::default()
}
.build();
ders
}
#[test]
fn reading_a_location_out_of_a_long_authority_costs_what_it_reads() {
let budget = Bounds {
work: 20_000,
..Bounds::default()
};
let short = located_under(4);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &short);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a budget this shape does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
let long = located_under(400_000);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &long);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.truncated(), Some(Truncation::Work));
}
#[test]
fn a_search_with_no_anchor_answers_without_spending_anything() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let pool: Vec<&Certificate<'_>> = read[1..].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(
&[],
Bounds {
work: 1,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("no anchor is an answer, not an error");
assert!(outcome.paths().is_empty());
assert_eq!(outcome.truncated(), None);
}
#[test]
fn an_anchor_the_caller_named_twice_is_one_anchor() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let bounds = Bounds {
edges: 1,
..Bounds::default()
};
let once = alloc::vec![Anchor::from_certificate(&read[1])];
let outcome = build(
&read[0],
&[],
&context(&once, bounds, &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
let twice = alloc::vec![
Anchor::from_certificate(&read[1]),
Anchor::from_certificate(&read[1]),
];
let outcome = build(
&read[0],
&[],
&context(&twice, bounds, &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
}
#[test]
fn an_anchors_own_certificate_is_not_a_path_to_that_anchor() {
let ders = alloc::vec![
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[0])];
let outcome = build(
&read[0],
&[],
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
assert_eq!(outcome.truncated(), None);
}
#[test]
fn anchors_of_one_name_spelled_two_ways_are_one_anchor() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 4,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "ROOT",
issuer: "ROOT",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 4,
signer: 4,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![
Anchor::new(read[1].tbs().subject(), read[1].tbs().public_key()),
Anchor::new(read[2].tbs().subject(), read[2].tbs().public_key()),
Anchor::new(read[3].tbs().subject(), read[3].tbs().public_key()),
];
let outcome = build(
&read[0],
&[],
&context(
&anchors,
Bounds {
edges: 2,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
}
fn issued_to_names_of(width: usize) -> Vec<Vec<u8>> {
let base = "A".repeat(width);
alloc::vec![
Mint {
subject: &base,
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
]
}
#[test]
fn asking_whether_a_certificate_issued_itself_costs_what_it_reads() {
let budget = Bounds {
work: 3_000,
..Bounds::default()
};
let short = issued_to_names_of(4);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &short);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let outcome = build(
&read[0],
&[],
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a budget this shape does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
let long = issued_to_names_of(100_000);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &long);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let outcome = build(
&read[0],
&[],
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.truncated(), Some(Truncation::Work));
}
#[test]
fn anchors_of_one_key_published_differently_are_not_one_anchor() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
extra: alloc::vec![also_known_as("host.other.test")],
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
extra: alloc::vec![permitting("example.com")],
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let published = Anchor::from_certificate(&read[1]);
let bare = Anchor::new(read[1].tbs().subject(), read[1].tbs().public_key());
let first = alloc::vec![published, bare];
let outcome = build(
&read[0],
&[],
&context(&first, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1, "constrained named first");
let second = alloc::vec![bare, published];
let outcome = build(
&read[0],
&[],
&context(&second, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1, "bare named first");
}
#[test]
fn a_target_the_interval_does_not_hold_answers_without_reading_the_inputs() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
validity: ("200101000000Z", "210101000000Z"),
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let pool: Vec<&Certificate<'_>> = read[1..].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
work: 1,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("an expired target is an answer, not an error");
assert!(outcome.paths().is_empty());
assert_eq!(outcome.truncated(), None);
}
#[test]
fn a_target_claiming_to_be_an_authority_and_naming_nobody_is_not_a_path() {
let parts = CertificateParts {
serial: int(1),
inner_algorithm: seq(&[oid_of(ECDSA_SHA256)]),
outer_algorithm: seq(&[oid_of(ECDSA_SHA256)]),
issuer: named("Root"),
subject: seq(&[]),
validity: seq(&[utc_time("240101000000Z"), utc_time("300101000000Z")]),
key: public(&key(1)),
extensions: Some(seq(&[
extension(oid::BASIC_CONSTRAINTS, true, &seq(&[boolean(true)])),
extension(
oid::SUBJECT_ALT_NAME,
true,
&seq(&[tlv(0x82, b"host.example.com")]),
),
])),
signature: bits(&[]),
..CertificateParts::default()
};
let signature: p256::ecdsa::Signature = key(3).sign(&parts.tbs());
let leaf = CertificateParts {
signature: bits(signature.to_der().as_bytes()),
..parts
}
.build();
let ders = alloc::vec![
leaf,
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let outcome = build(
&read[0],
&[],
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
}
#[test]
fn two_readings_of_one_certificate_are_one_candidate() {
let one = chain(Authority::Unlimited, None);
let mut ders = alloc::vec![one[0].clone(), one[1].clone(), one[2].clone()];
ders.push(one[1].clone());
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
assert_eq!(read[1].as_bytes(), read[3].as_bytes());
assert!(!core::ptr::eq(read[1].as_bytes(), read[3].as_bytes()));
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1], &read[3]];
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
edges: 4,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
}
#[test]
fn a_target_asserting_authority_names_nobody_even_where_its_key_may_not_issue() {
let parts = CertificateParts {
serial: int(1),
inner_algorithm: seq(&[oid_of(ECDSA_SHA256)]),
outer_algorithm: seq(&[oid_of(ECDSA_SHA256)]),
issuer: named("Root"),
subject: seq(&[]),
validity: seq(&[utc_time("240101000000Z"), utc_time("300101000000Z")]),
key: public(&key(1)),
extensions: Some(seq(&[
extension(oid::BASIC_CONSTRAINTS, true, &seq(&[boolean(true)])),
extension(oid::KEY_USAGE, true, &tlv(0x03, &[7, 0x80])),
extension(
oid::SUBJECT_ALT_NAME,
true,
&seq(&[tlv(0x82, b"host.example.com")]),
),
])),
signature: bits(&[]),
..CertificateParts::default()
};
let signature: p256::ecdsa::Signature = key(3).sign(&parts.tbs());
let leaf = CertificateParts {
signature: bits(signature.to_der().as_bytes()),
..parts
}
.build();
let ders = alloc::vec![
leaf,
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let outcome = build(
&read[0],
&[],
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert!(outcome.paths().is_empty());
}
fn routes_keyed_over(width: Option<usize>) -> Vec<Vec<u8>> {
let mut ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Mid",
key: 1,
parameters: width,
signer: 2,
..Mint::default()
}
.build(),
];
for upper in [31u8, 32] {
ders.push(
Mint {
subject: "Mid",
issuer: "Up",
key: 2,
parameters: width,
signer: upper,
serial: u32::from(upper),
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
ders.push(
Mint {
subject: "Up",
issuer: "Root",
key: upper,
parameters: width,
signer: 41,
serial: u32::from(upper) + 100,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
}
ders.push(
Mint {
subject: "Root",
issuer: "Root",
key: 41,
parameters: width,
signer: 41,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
ders
}
#[test]
fn telling_two_routes_apart_costs_what_reading_their_keys_costs() {
let budget = Bounds {
work: 40_000,
..Bounds::default()
};
let short = routes_keyed_over(None);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &short);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a budget this shape does not spend");
assert_eq!(outcome.paths().len(), 2);
assert_eq!(outcome.truncated(), None);
let long = routes_keyed_over(Some(200_000));
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &long);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.truncated(), Some(Truncation::Work));
}
#[test]
fn a_pool_naming_one_certificate_over_and_over_reads_it_once() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
parameters: Some(20_000),
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let budget = Bounds {
work: 8_000,
..Bounds::default()
};
let once = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&once,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a budget this shape does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
let over: Vec<&Certificate<'_>> = core::iter::repeat_n(&read[1], 50).collect();
let outcome = build(
&read[0],
&over,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a budget this shape does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
}
#[test]
fn a_lookalike_that_could_not_stand_in_a_path_costs_no_signature() {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let decoys: Vec<Vec<u8>> = (0..8u32)
.map(|which| {
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
serial: which + 10,
validity: ("200101000000Z", "210101000000Z"),
authority: Authority::Unlimited,
..Mint::default()
}
.build()
})
.collect();
let stood = parse(&parser, &decoys);
let pool: Vec<&Certificate<'_>> = stood.iter().collect();
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
work: 4_000,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
}
fn permitting_domains(domains: &[&str]) -> Vec<u8> {
let subtrees: Vec<Vec<u8>> = domains
.iter()
.map(|domain| seq(&[tlv(0x82, domain.as_bytes())]))
.collect();
extension(
oid::NAME_CONSTRAINTS,
true,
&seq(&[tlv(0xa0, &cat(&subtrees))]),
)
}
#[test]
fn a_name_is_charged_against_the_regions_of_its_own_form() {
let long = "A".repeat(20_000);
let hosts: Vec<alloc::string::String> = (0..16u8)
.map(|which| alloc::format!("h{which}.example.com"))
.collect();
let borrowed: Vec<&str> = hosts.iter().map(alloc::string::String::as_str).collect();
let mut ders = constrained(Some(permitting_domains(&borrowed)), None);
ders[0] = Mint {
subject: &long,
issuer: "Intermediate",
key: 1,
signer: 2,
..Mint::default()
}
.build();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[2])];
let pool = alloc::vec![&read[1]];
let outcome = build(
&read[0],
&pool,
&context(
&anchors,
Bounds {
work: 10_000,
..Bounds::default()
},
&crate::fixtures::memory(),
),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
}
#[test]
fn a_location_whose_authority_is_bracketed_is_not_a_host_name() {
let ders = constrained(Some(permitting_locations(&[".example.com"])), None);
let mut ders = ders;
ders[0] = Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
extra: alloc::vec![located_at("http://[v1.example.com]/")],
..Mint::default()
}
.build();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 0);
assert_eq!(outcome.truncated(), None);
}
fn issued_under_many(count: u32) -> Vec<u8> {
let named: Vec<Vec<u8>> = (0..count)
.map(|which| {
let mut arc = Vec::new();
let mut left = which;
loop {
arc.insert(0, (left & 0x7f) as u8);
left >>= 7;
if left == 0 {
break;
}
}
let last = arc.len() - 1;
for byte in &mut arc[..last] {
*byte |= 0x80;
}
let mut identifier = alloc::vec![0x2a, 0x03, 0x05];
identifier.extend_from_slice(&arc);
seq(&[tlv(0x06, &identifier)])
})
.collect();
extension(oid::CERTIFICATE_POLICIES, false, &seq(&named))
}
#[test]
fn reading_a_long_list_of_policies_costs_what_reading_it_costs() {
let budget = Bounds {
work: 40_000,
..Bounds::default()
};
let ders = under_policies(
alloc::vec![issued_under_many(8)],
alloc::vec![issued_under(HOUSE_POLICY)],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a budget a short list does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(outcome.truncated(), None);
let ders = under_policies(
alloc::vec![issued_under_many(20_000)],
alloc::vec![issued_under(HOUSE_POLICY)],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.truncated(), Some(Truncation::Work));
}
fn mapping_one_issuer(count: u32) -> Vec<u8> {
let pairs: Vec<Vec<u8>> = (0..count)
.map(|which| {
let mut identifier = alloc::vec![0x2a, 0x03, 0x06];
let mut arc = Vec::new();
let mut left = which;
loop {
arc.insert(0, (left & 0x7f) as u8);
left >>= 7;
if left == 0 {
break;
}
}
let last = arc.len() - 1;
for byte in &mut arc[..last] {
*byte |= 0x80;
}
identifier.extend_from_slice(&arc);
seq(&[oid_of(HOUSE_POLICY), tlv(0x06, &identifier)])
})
.collect();
extension(oid::POLICY_MAPPINGS, true, &seq(&pairs))
}
#[test]
fn one_issuer_named_in_many_mappings_is_answered_once() {
let budget = Bounds {
work: 100_000,
..Bounds::default()
};
let unmapped = Policy {
mapping: false,
..Policy::default()
};
let ders = under_policies(
alloc::vec![issued_under(HOUSE_POLICY)],
alloc::vec![issued_under(HOUSE_POLICY), mapping_one_issuer(20_000)],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&asking(&anchors, budget, unmapped, &crate::fixtures::memory()),
)
.expect("a budget the sum does not spend");
assert_eq!(outcome.truncated(), None);
assert_eq!(outcome.paths().len(), 1);
}
struct Polls(core::cell::Cell<usize>);
impl super::Watch for Polls {
fn out_of_time(&self) -> bool {
self.0.set(self.0.get() + 1);
false
}
}
fn addressed(count: usize) -> Vec<Vec<u8>> {
let attributes: Vec<Vec<u8>> = (0..count)
.map(|which| {
set(&[seq(&[
oid_of(oid::EMAIL_ADDRESS),
ia5(&alloc::format!("person{which}@example.com")),
])])
})
.collect();
let mut ders = constrained(Some(permitting_mail("example.com")), None);
ders[0] = Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
subject_der: Some(seq(&attributes)),
..Mint::default()
}
.build();
ders
}
fn permitting_mail(domain: &str) -> Vec<u8> {
extension(
oid::NAME_CONSTRAINTS,
true,
&seq(&[tlv(0xa0, &seq(&[tlv(0x81, domain.as_bytes())]))]),
)
}
fn polls_while_reading(count: usize) -> usize {
let ders = addressed(count);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let clock = Polls(core::cell::Cell::new(0));
let outcome = build(
&read[0],
&pool,
&super::Context {
execution: &crate::fixtures::memory(),
anchors: &anchors,
interval: moment(),
bounds: Bounds::default(),
policy: Policy::default(),
verifier: &Ecdsa,
watch: &clock,
recognizer: &(),
},
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
clock.0.get()
}
#[test]
fn the_meter_is_consulted_while_the_names_are_formed() {
let few = polls_while_reading(4);
let many = polls_while_reading(64);
assert!(
many >= few + 50,
"the clock was asked {few} times for four names and {many} for sixty-four, \
which is not once per name"
);
}
struct Scheme;
impl crate::extension::Recognizer for Scheme {
type Error = core::convert::Infallible;
fn recognizes(
&self,
kind: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(kind.as_bytes().starts_with(&[0x2a, 0x03, 0x07]))
}
}
fn demanding(count: u32) -> Vec<Vec<u8>> {
let mut extra: Vec<Vec<u8>> = Vec::new();
for which in 0..count {
let mut arc = Vec::new();
let mut left = which;
loop {
arc.insert(0, (left & 0x7f) as u8);
left >>= 7;
if left == 0 {
break;
}
}
let last = arc.len() - 1;
for byte in &mut arc[..last] {
*byte |= 0x80;
}
let mut identifier = alloc::vec![0x2a, 0x03, 0x07];
identifier.extend_from_slice(&arc);
extra.push(extension(
Oid::from_bytes(&identifier),
true,
&boolean(true),
));
}
alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
extra,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
]
}
fn settles_reading(count: u32, work: u64) -> Option<Truncation> {
let ders = demanding(count);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let outcome = build(
&read[0],
&[],
&super::Context {
execution: &crate::fixtures::memory(),
anchors: &anchors,
interval: moment(),
bounds: Bounds {
work,
..Bounds::default()
},
policy: Policy::default(),
verifier: &Ecdsa,
watch: &(),
recognizer: &Scheme,
},
)
.expect("a bound is an answer, not an error");
outcome.truncated()
}
#[test]
fn reading_a_long_list_of_critical_extensions_costs_what_it_reads() {
assert_eq!(settles_reading(4, 40_000), None);
assert_eq!(settles_reading(20_000, 40_000), Some(Truncation::Work));
}
fn lookalikes_keyed_over(count: u8, width: Option<usize>) -> Vec<Vec<u8>> {
let mut ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
parameters: width,
signer: 3,
..Mint::default()
}
.build(),
];
for which in 0..count {
ders.push(
Mint {
subject: "Root",
issuer: "Root",
key: 51 + which,
parameters: width,
signer: 51 + which,
serial: u32::from(which) + 200,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
}
ders.push(
Mint {
subject: "Root",
issuer: "Root",
key: 3,
parameters: width,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
ders
}
fn settles_against_lookalikes(width: Option<usize>) -> Option<Truncation> {
let ders = lookalikes_keyed_over(4, width);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let budget = Bounds {
work: 40_000,
..Bounds::default()
};
build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error")
.truncated()
}
#[test]
fn asking_whether_a_certificate_publishes_an_anchor_costs_what_it_reads() {
assert_eq!(settles_against_lookalikes(None), None);
assert_eq!(
settles_against_lookalikes(Some(200_000)),
Some(Truncation::Work)
);
}
fn issued_under_alike(count: u32, prefix: usize) -> Vec<u8> {
let named: Vec<Vec<u8>> = (0..count)
.map(|which| {
let mut identifier = alloc::vec![0x2a];
identifier.extend(core::iter::repeat_n(0x03u8, prefix));
let mut arc = Vec::new();
let mut left = which;
loop {
arc.insert(0, (left & 0x7f) as u8);
left >>= 7;
if left == 0 {
break;
}
}
let last = arc.len() - 1;
for byte in &mut arc[..last] {
*byte |= 0x80;
}
identifier.extend_from_slice(&arc);
seq(&[tlv(0x06, &identifier)])
})
.collect();
extension(oid::CERTIFICATE_POLICIES, false, &seq(&named))
}
fn settles_ordering(count: u32, prefix: usize) -> Option<Truncation> {
let ders = under_policies(
alloc::vec![issued_under_alike(count, prefix)],
alloc::vec![issued_under_alike(count, prefix)],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let budget = Bounds {
work: 40_000,
policies: 4_096,
..Bounds::default()
};
build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error")
.truncated()
}
#[test]
fn ordering_the_settled_policies_costs_what_it_reads() {
assert_eq!(settles_ordering(256, 2), None);
assert_eq!(settles_ordering(256, 600), Some(Truncation::Work));
}
fn direct_beside(count: u8, width: Option<usize>) -> Vec<Vec<u8>> {
let mut ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
];
for which in 0..count {
ders.push(
Mint {
subject: "Elsewhere",
issuer: "Nobody",
key: 61 + which,
parameters: width,
signer: 61 + which,
serial: u32::from(which) + 300,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
}
ders.push(
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
ders
}
#[test]
fn a_path_the_anchors_settle_is_not_lost_to_a_pool_nobody_walks() {
let ders = direct_beside(8, Some(200_000));
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let budget = Bounds {
work: 10_000,
..Bounds::default()
};
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.paths().len(), 1);
}
#[test]
fn a_target_whose_basic_constraints_do_not_read_is_not_an_end_entity() {
let ders = alloc::vec![
Mint {
subject: "unused",
subject_der: Some(seq(&[])),
issuer: "Root",
key: 1,
signer: 3,
extra: alloc::vec![
extension(
oid::SUBJECT_ALT_NAME,
true,
&seq(&[tlv(0x82, b"host.example.com")]),
),
extension(oid::BASIC_CONSTRAINTS, false, &[0x05, 0x00]),
],
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let outcome = build(
&read[0],
&[],
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 0);
assert_eq!(outcome.truncated(), None);
}
fn routes_settled(width: Option<usize>, work: u64) -> (usize, Option<Truncation>) {
let ders = routes_keyed_over(width);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let budget = Bounds {
work,
..Bounds::default()
};
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
(outcome.paths().len(), outcome.truncated())
}
#[test]
fn a_route_is_not_shown_where_telling_it_apart_was_refused() {
let (settled, truncated) = routes_settled(Some(200_000), 40_000);
assert_eq!(truncated, Some(Truncation::Work));
assert_eq!(settled, 1);
}
fn long_identifier(width: usize, last: u8) -> Vec<u8> {
let mut content = alloc::vec![0x2a];
content.extend(core::iter::repeat_n(0x01u8, width));
content.push(last);
content
}
fn named_by(identifier: &[u8]) -> Vec<u8> {
tlv(
0xa0,
&cat(&[tlv(0x06, identifier), tlv(0xa0, &boolean(true))]),
)
}
fn permitting_others(count: u8, width: usize) -> Vec<u8> {
let subtrees: Vec<Vec<u8>> = (0..count)
.map(|which| seq(&[named_by(&long_identifier(width, which))]))
.collect();
extension(
oid::NAME_CONSTRAINTS,
true,
&seq(&[tlv(0xa0, &cat(&subtrees))]),
)
}
fn settles_against_others(count: u8, width: usize, work: u64) -> Option<Truncation> {
let mut ders = constrained(Some(permitting_others(count, width)), None);
ders[0] = Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
extra: alloc::vec![extension(
oid::SUBJECT_ALT_NAME,
false,
&seq(&[named_by(&long_identifier(width, 0x7f))]),
)],
..Mint::default()
}
.build();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let budget = Bounds {
work,
..Bounds::default()
};
build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error")
.truncated()
}
#[test]
fn comparing_names_told_apart_by_an_identifier_costs_what_it_reads() {
assert_eq!(settles_against_others(16, 8, 120_000), None);
assert_eq!(
settles_against_others(16, 200_000, 120_000),
Some(Truncation::Work)
);
}
fn mapping_many_issuers(count: u32, prefix: usize) -> Vec<u8> {
let pairs: Vec<Vec<u8>> = (0..count)
.map(|which| {
let issuer = alike_identifier(0x08, which, prefix);
let subject = alike_identifier(0x09, which, prefix);
seq(&[tlv(0x06, &issuer), tlv(0x06, &subject)])
})
.collect();
extension(oid::POLICY_MAPPINGS, true, &seq(&pairs))
}
fn issued_under_issuers(count: u32, prefix: usize) -> Vec<u8> {
let named: Vec<Vec<u8>> = (0..count)
.map(|which| seq(&[tlv(0x06, &alike_identifier(0x08, which, prefix))]))
.collect();
extension(oid::CERTIFICATE_POLICIES, false, &seq(&named))
}
fn alike_identifier(family: u8, which: u32, prefix: usize) -> Vec<u8> {
let mut content = alloc::vec![0x2a, family];
content.extend(core::iter::repeat_n(0x01u8, prefix));
let mut arc = Vec::new();
let mut left = which;
loop {
arc.insert(0, (left & 0x7f) as u8);
left >>= 7;
if left == 0 {
break;
}
}
let last = arc.len() - 1;
for byte in &mut arc[..last] {
*byte |= 0x80;
}
content.extend_from_slice(&arc);
content
}
#[test]
fn applying_mappings_looks_at_each_node_once_per_issuer_that_names_it() {
let budget = Bounds {
work: 40_000,
policies: 4_096,
..Bounds::default()
};
let ders = under_policies(
alloc::vec![issued_under_issuers(128, 4)],
alloc::vec![issued_under_issuers(128, 4), mapping_many_issuers(128, 4),],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.truncated(), None);
}
fn issued_under_subjects_and_any(count: u32, prefix: usize) -> Vec<u8> {
let mut named: Vec<Vec<u8>> = (0..count)
.map(|which| seq(&[tlv(0x06, &alike_identifier(0x09, which, prefix))]))
.collect();
named.push(seq(&[oid_of(oid::ANY_POLICY)]));
extension(oid::CERTIFICATE_POLICIES, false, &seq(&named))
}
#[test]
fn expanding_any_policy_looks_at_each_expectation_once() {
let budget = Bounds {
work: 40_000,
policies: 4_096,
..Bounds::default()
};
let ders = under_policies(
alloc::vec![issued_under_subjects_and_any(128, 4)],
alloc::vec![issued_under_issuers(128, 4), mapping_many_issuers(128, 4),],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
assert_eq!(outcome.truncated(), None);
}
#[test]
fn an_anchor_re_issued_under_one_statement_of_trust_is_one_anchor() {
let budget = Bounds {
edges: 1,
..Bounds::default()
};
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
serial: 1,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
serial: 2,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![
Anchor::from_certificate(&read[1]),
Anchor::from_certificate(&read[2]),
];
let outcome = build(
&read[0],
&[],
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a budget one anchor does not spend");
assert_eq!(outcome.truncated(), None);
assert_eq!(outcome.paths().len(), 1);
}
fn inheriting() -> Vec<Vec<u8>> {
alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
inherits: true,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
]
}
#[test]
fn a_key_written_without_parameters_stands_in_the_ones_above_it() {
let ders = inheriting();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&super::Context {
execution: &crate::fixtures::memory(),
anchors: &anchors,
interval: moment(),
bounds: Bounds::default(),
policy: Policy::default(),
verifier: &Curved,
watch: &(),
recognizer: &(),
},
)
.expect("a budget this test does not spend");
assert_eq!(outcome.truncated(), None);
assert_eq!(outcome.paths().len(), 1);
}
#[test]
fn effective_local_anchor_preserves_inherited_parameters() {
let ders = inheriting();
let execution = crate::fixtures::memory();
let parser = Parser::der(execution.clone());
let read = parse(&parser, &ders);
let key = super::WorkingKey::standing(read[2].tbs().public_key())
.subject(&read[1], &execution)
.unwrap();
assert!(key.parameters().is_some());
let storage_free = structured_asn1::memory::Memory::new(alloc::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(0, 100_000),
));
assert!(
super::EffectiveAnchor::bind(Anchor::from_certificate(&read[2]), key, &storage_free)
.unwrap()
.is_none()
);
let anchor =
super::EffectiveAnchor::bind(Anchor::from_certificate(&read[1]), key, &storage_free)
.unwrap()
.unwrap();
assert_eq!(
anchor.key().parameters().unwrap().as_ptr(),
key.parameters().unwrap().as_ptr()
);
assert_eq!(storage_free.used(), 0);
let unfunded = structured_asn1::memory::Memory::new(alloc::sync::Arc::new(
structured_asn1::memory::MemoryAccount::new(0, 0),
));
assert!(
super::EffectiveAnchor::bind(Anchor::from_certificate(&read[1]), key, &unfunded)
.unwrap_err()
.is_resource()
);
let context = super::ValidationContext {
execution: &execution,
interval: moment(),
policy: Policy::default(),
bounds: super::ValidationBounds::default(),
verifier: &Curved,
watch: &(),
recognizer: &(),
};
let result = super::validate_effective(&[&read[0]], anchor, &context).unwrap();
assert!(
matches!(result, super::Validation::Accepted(_)),
"{result:?}"
);
assert!(matches!(
super::validate(&[&read[0]], anchor.anchor(), &context).unwrap(),
super::Validation::Rejected(super::Rejection {
reason: super::RejectionReason::UnsupportedSignature,
..
})
));
}
#[test]
fn unrestricted_pss_subject_does_not_inherit_parent_restrictions() {
use crate::fixtures::{CertificateParts, bits, oid as encoded_oid, seq};
let root_bytes = CertificateParts {
key: seq(&[
seq(&[encoded_oid(oid::RSASSA_PSS), seq(&[])]),
bits(&[1, 2, 3]),
]),
..CertificateParts::default()
}
.build();
let child_bytes = CertificateParts {
key: seq(&[seq(&[encoded_oid(oid::RSASSA_PSS)]), bits(&[4, 5, 6])]),
..CertificateParts::default()
}
.build();
let execution = crate::fixtures::memory();
let parser = Parser::der(execution.clone());
let root = Certificate::read(&parser, &root_bytes, 100).unwrap();
let child = Certificate::read(&parser, &child_bytes, 500).unwrap();
let prior = super::WorkingKey::standing(root.tbs().public_key());
assert_eq!(prior.parameters(), Some([48, 0].as_slice()));
let projected = prior.subject(&child, &execution).unwrap();
assert_eq!(projected.parameters(), None);
assert_eq!(
projected.key().as_bytes().as_ptr(),
child.tbs().public_key().as_bytes().as_ptr()
);
}
#[test]
fn null_pss_subject_keeps_invalid_original_parameters_for_provider_refusal() {
let root_bytes = CertificateParts {
key: seq(&[seq(&[oid_of(oid::RSASSA_PSS), seq(&[])]), bits(&[1, 2, 3])]),
..CertificateParts::default()
}
.build();
let child_bytes = CertificateParts {
key: seq(&[
seq(&[oid_of(oid::RSASSA_PSS), crate::fixtures::null()]),
bits(&[4, 5, 6]),
]),
..CertificateParts::default()
}
.build();
let execution = crate::fixtures::memory();
let parser = Parser::der(execution.clone());
let root = Certificate::read(&parser, &root_bytes, 100).unwrap();
let child = Certificate::read(&parser, &child_bytes, 500).unwrap();
let projected = super::WorkingKey::standing(root.tbs().public_key())
.subject(&child, &execution)
.unwrap();
assert_eq!(projected.parameters(), None);
assert_eq!(
projected.key().algorithm().parameters(),
Some([5, 0].as_slice())
);
}
struct Recording<'a>(&'a core::cell::RefCell<Vec<Option<Vec<u8>>>>);
impl Verify for Recording<'_> {
type Error = core::convert::Infallible;
fn verify(
&self,
_algorithm: &crate::AlgorithmIdentifier<'_>,
key: super::WorkingKey<'_>,
_signed: &[u8],
_signature: structured_asn1::BitString<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<Verified, Self::Error> {
Ok({
self.0
.borrow_mut()
.push(key.parameters().map(<[u8]>::to_vec));
Verified::Yes
})
}
}
fn changing_algorithm() -> Vec<Vec<u8>> {
alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
..Mint::default()
}
.build(),
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
foreign: true,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
]
}
#[test]
fn parameters_are_not_carried_across_a_change_of_algorithm() {
let ders = changing_algorithm();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let asked = core::cell::RefCell::new(Vec::new());
let outcome = build(
&read[0],
&pool,
&super::Context {
execution: &crate::fixtures::memory(),
anchors: &anchors,
interval: moment(),
bounds: Bounds::default(),
policy: Policy::default(),
verifier: &Recording(&asked),
watch: &(),
recognizer: &(),
},
)
.expect("a budget this test does not spend");
assert_eq!(outcome.paths().len(), 1);
assert_eq!(
asked.into_inner(),
alloc::vec![None, Some(oid_of(PRIME256V1))],
);
}
fn published_constraints() -> Vec<Vec<u8>> {
alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
extra: alloc::vec![extension(
oid::NAME_CONSTRAINTS,
true,
&seq(&[tlv(0xa0, &seq(&[tlv(0xa4, &named("Leaf"))]))]),
)],
..Mint::default()
}
.build(),
]
}
#[test]
fn an_anchor_names_the_regions_below_it_without_claiming_to_issue() {
let ders = published_constraints();
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let outcome = build(
&read[0],
&[],
&context(&anchors, Bounds::default(), &crate::fixtures::memory()),
)
.expect("a budget this test does not spend");
assert_eq!(outcome.truncated(), None);
assert_eq!(outcome.paths().len(), 1);
}
fn permitting_directory(common: &str) -> Vec<u8> {
extension(
oid::NAME_CONSTRAINTS,
true,
&seq(&[tlv(0xa0, &seq(&[tlv(0xa4, &named(common))]))]),
)
}
#[test]
fn anchors_of_one_statement_are_one_anchor_however_they_describe_issuing() {
let budget = Bounds {
edges: 1,
..Bounds::default()
};
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Root",
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
serial: 1,
authority: Authority::Unlimited,
extra: alloc::vec![permitting_directory("Leaf")],
..Mint::default()
}
.build(),
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
serial: 2,
authority: Authority::Absent,
extra: alloc::vec![permitting_directory("Leaf")],
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![
Anchor::from_certificate(&read[1]),
Anchor::from_certificate(&read[2]),
];
let outcome = build(
&read[0],
&[],
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a budget one anchor does not spend");
assert_eq!(outcome.truncated(), None);
assert_eq!(outcome.paths().len(), 1);
}
fn crowded(count: usize, upper: bool) -> Vec<u8> {
let attributes: Vec<Vec<u8>> = (0..count)
.map(|which| {
let text = if upper {
alloc::format!("NAME{which:06}")
} else {
alloc::format!("name{which:06}")
};
seq(&[oid_of(oid::COMMON_NAME), printable(&text)])
})
.collect();
seq(&[set(&attributes)])
}
fn settles_against_a_crowded_name(count: usize, work: u64) -> Option<Truncation> {
let ders = alloc::vec![
Mint {
subject: "Leaf",
issuer_der: Some(crowded(count, false)),
key: 1,
signer: 3,
..Mint::default()
}
.build(),
Mint {
subject_der: Some(crowded(count, true)),
issuer_der: Some(crowded(count, true)),
key: 3,
signer: 3,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
];
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(&read[1])];
let budget = Bounds {
work,
..Bounds::default()
};
let outcome = build(
&read[0],
&[],
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
outcome.truncated()
}
#[test]
fn ordering_a_crowded_name_costs_what_the_ordering_reads() {
const WIDE: usize = 2_048;
assert_eq!(settles_against_a_crowded_name(WIDE, 2_000_000), None);
assert_eq!(
settles_against_a_crowded_name(WIDE, 20_000),
Some(Truncation::Work),
);
}
fn issued_under_long(count: u32, prefix: usize) -> Vec<u8> {
let named: Vec<Vec<u8>> = (0..count)
.map(|which| seq(&[tlv(0x06, &alike_identifier(0x09, which, prefix))]))
.collect();
extension(oid::CERTIFICATE_POLICIES, false, &seq(&named))
}
fn settles_under_long_policies(count: u32, prefix: usize, work: u64) -> Option<Truncation> {
let budget = Bounds {
work,
..Bounds::default()
};
let ders = under_policies(
alloc::vec![issued_under_long(count, prefix)],
alloc::vec![issued_under_issuers(4, prefix)],
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&context(&anchors, budget, &crate::fixtures::memory()),
)
.expect("a bound is an answer, not an error");
outcome.truncated()
}
#[test]
fn looking_a_policy_identifier_up_costs_what_the_lookup_reads() {
const NAMED: u32 = 64;
const WIDE: usize = 4_096;
assert_eq!(settles_under_long_policies(NAMED, WIDE, 2_000_000), None);
assert_eq!(
settles_under_long_policies(NAMED, WIDE, 4_000),
Some(Truncation::Work),
);
}
struct Registered;
impl Verify for Registered {
type Error = core::convert::Infallible;
fn verify(
&self,
_algorithm: &crate::AlgorithmIdentifier<'_>,
key: super::WorkingKey<'_>,
signed: &[u8],
signature: structured_asn1::BitString<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<Verified, Self::Error> {
Ok({
let (Some(octets), Some(value)) = (key.key().key_octets(), signature.octets()) else {
return Ok(Verified::No);
};
let (Ok(verifying), Ok(signature)) = (
p256::ecdsa::VerifyingKey::from_sec1_bytes(octets),
p256::ecdsa::Signature::from_der(value),
) else {
return Ok(Verified::No);
};
if verifying.verify(signed, &signature).is_ok() {
Verified::Yes
} else {
Verified::No
}
})
}
}
fn settles_past_lookalike_authorities(count: u8, edges: usize) -> (Option<Truncation>, usize) {
let mut ders = alloc::vec![
Mint {
subject: "Leaf",
issuer: "Intermediate",
key: 1,
signer: 2,
foreign: true,
..Mint::default()
}
.build(),
];
ders.extend((0..count).map(|which| {
Mint {
subject: "Intermediate",
issuer: "Root",
key: 16 + which,
signer: 3,
serial: u32::from(which) + 2,
foreign: true,
authority: Authority::Unlimited,
..Mint::default()
}
.build()
}));
ders.push(
Mint {
subject: "Intermediate",
issuer: "Root",
key: 2,
signer: 3,
foreign: true,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
ders.push(
Mint {
subject: "Root",
issuer: "Root",
key: 3,
signer: 3,
foreign: true,
authority: Authority::Unlimited,
..Mint::default()
}
.build(),
);
let parser = Parser::der(crate::fixtures::memory());
let read = parse(&parser, &ders);
let anchors = alloc::vec![Anchor::from_certificate(read.last().expect("the root"))];
let pool: Vec<&Certificate<'_>> = read[1..read.len() - 1].iter().collect();
let outcome = build(
&read[0],
&pool,
&super::Context {
execution: &crate::fixtures::memory(),
anchors: &anchors,
interval: moment(),
bounds: Bounds {
edges,
..Bounds::default()
},
policy: Policy::default(),
verifier: &Registered,
watch: &(),
recognizer: &(),
},
)
.expect("a bound is an answer, not an error");
(outcome.truncated(), outcome.paths().len())
}
#[test]
fn a_wrong_key_is_refused_at_the_edge_where_it_writes_a_null() {
const LOOKALIKES: u8 = 8;
assert_eq!(
settles_past_lookalike_authorities(LOOKALIKES, 30),
(None, 1)
);
}