use std::cell::Cell;
use std::sync::Arc;
use structured_asn1::memory::{Memory, MemoryAccount, MemoryError, Work};
use structured_asn1::{BitString, Oid, Parser};
use structured_pkix::path::{
self, Anchor, BuildError, ConstraintSource, Interval, Policy, RejectionReason, Truncation,
Validation, ValidationBounds, ValidationContext, Verified, Verify, WorkingKey,
};
use structured_pkix::{AlgorithmIdentifier, Certificate, ErrorCode, oid};
fn memory(storage: usize, work: usize) -> Memory {
Memory::new(Arc::new(MemoryAccount::new(storage, work)))
}
fn tlv(tag: u8, contents: &[u8]) -> Vec<u8> {
let mut output = vec![tag];
if contents.len() < 128 {
output.push(u8::try_from(contents.len()).unwrap());
} else {
output.extend_from_slice(&[0x81, u8::try_from(contents.len()).unwrap()]);
}
output.extend_from_slice(contents);
output
}
fn seq(parts: &[Vec<u8>]) -> Vec<u8> {
tlv(0x30, &parts.concat())
}
fn name(text: &str) -> Vec<u8> {
if text.is_empty() {
return seq(&[]);
}
seq(&[tlv(
0x31,
&seq(&[
tlv(0x06, oid::COMMON_NAME.as_bytes()),
tlv(0x0c, text.as_bytes()),
]),
)])
}
fn certificate(subject: &str, critical: bool) -> Vec<u8> {
let extensions = if critical {
vec![seq(&[
tlv(0x06, &[0x2a, 3, 4, 5]),
tlv(0x01, &[0xff]),
tlv(0x04, &[]),
])]
} else {
Vec::new()
};
certificate_with_extensions(subject, &extensions)
}
fn certificate_with_extensions(subject: &str, extensions: &[Vec<u8>]) -> Vec<u8> {
let algorithm = seq(&[tlv(0x06, oid::RSA_ENCRYPTION.as_bytes()), tlv(0x05, &[])]);
let mut fields = vec![
tlv(0xa0, &tlv(0x02, &[2])),
tlv(0x02, &[1]),
algorithm.clone(),
name("Root"),
seq(&[tlv(0x17, b"240101000000Z"), tlv(0x17, b"300101000000Z")]),
name(subject),
seq(&[algorithm.clone(), tlv(0x03, &[0, 1])]),
];
if !extensions.is_empty() {
fields.push(tlv(0xa3, &seq(extensions)));
}
seq(&[seq(&fields), algorithm, tlv(0x03, &[0, 1])])
}
struct Provider<'a> {
signed: &'a [u8],
calls: Cell<usize>,
error: bool,
}
struct ScopedProvider {
calls: Cell<usize>,
cost: u64,
}
impl<E: Work> Verify<E> for ScopedProvider {
type Error = MemoryError;
fn verify(
&self,
_: &AlgorithmIdentifier<'_>,
_: WorkingKey<'_>,
_: &[u8],
_: BitString<'_>,
execution: &E,
) -> Result<Verified, Self::Error> {
self.calls.set(self.calls.get() + 1);
execution.charge_work(self.cost)?;
Ok(Verified::Yes)
}
}
fn validation_with_temporary_operation<'a>(
leaf: &'a Certificate<'a>,
root: &'a Certificate<'a>,
wanted: &'a [Oid<'a>],
current: &Memory,
) -> (Validation<'a>, usize) {
let operation = Parser::der(current.clone());
let query = operation.read(&[], 1200).unwrap();
let work = query.execution();
let verifier = ScopedProvider {
calls: Cell::new(0),
cost: 1,
};
let context = ValidationContext {
execution: &work,
interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
policy: Policy {
wanted,
..Policy::default()
},
bounds: ValidationBounds::default(),
verifier: &verifier,
watch: &(),
recognizer: &(),
};
let result = path::validate(
&[leaf],
Anchor::new(root.tbs().subject(), root.tbs().public_key()),
&context,
)
.unwrap();
(result, verifier.calls.get())
}
#[test]
fn scoped_validation_results_outlive_temporary_execution_but_keep_original_sources() {
let policy = Oid::from_bytes(&[42, 3]);
let named = seq(&[
tlv(6, oid::CERTIFICATE_POLICIES.as_bytes()),
tlv(4, &seq(&[seq(&[tlv(6, policy.as_bytes())])])),
]);
let root_bytes = certificate("Root", false);
let leaf_bytes = certificate_with_extensions("Leaf", &[named]);
let source = Parser::der(memory(1 << 20, 1 << 24));
let root = Certificate::read(&source, &root_bytes, 400).unwrap();
let leaf = Certificate::read(&source, &leaf_bytes, 800).unwrap();
let wanted = [policy];
let current = memory(1 << 20, 1 << 24);
for requested in [&wanted[..], &[][..]] {
let (Validation::Accepted(facts), calls) =
validation_with_temporary_operation(&leaf, &root, requested, ¤t)
else {
panic!("original supplied chain")
};
assert_eq!(calls, 1);
assert_eq!(
facts.key.key().as_bytes().as_ptr(),
leaf.tbs().public_key().as_bytes().as_ptr()
);
assert_eq!(facts.policies(), &wanted);
let pointer = facts.policies()[0].as_bytes().as_ptr();
if requested.is_empty() {
assert!((leaf.as_bytes().as_ptr_range()).contains(&pointer));
} else {
assert_eq!(pointer, wanted[0].as_bytes().as_ptr());
}
assert!(current.used() > 0);
drop(facts);
assert_eq!(current.used(), 0);
}
}
#[test]
fn scoped_rejection_outlives_operation_and_keeps_original_extension_identifier() {
let kind = Oid::from_bytes(&[42, 99]);
let extension = seq(&[tlv(6, kind.as_bytes()), tlv(1, &[0xff]), tlv(4, &seq(&[]))]);
let root_bytes = certificate("Root", false);
let leaf_bytes = certificate_with_extensions("Leaf", &[extension]);
let source = Parser::der(memory(1 << 20, 1 << 24));
let root = Certificate::read(&source, &root_bytes, 400).unwrap();
let leaf = Certificate::read(&source, &leaf_bytes, 800).unwrap();
let current = memory(1 << 20, 1 << 24);
let (Validation::Rejected(rejection), calls) =
validation_with_temporary_operation(&leaf, &root, &[], ¤t)
else {
panic!("unknown critical demand")
};
let RejectionReason::CriticalExtension(original) = rejection.reason else {
panic!("preserve exact semantic cause")
};
assert_eq!(original, kind);
assert!((leaf.as_bytes().as_ptr_range()).contains(&original.as_bytes().as_ptr()));
assert_eq!(calls, 0);
assert_eq!(current.used(), 0);
}
#[test]
fn public_supplied_path_retains_scoped_work_in_graph_lazy_values_and_verifier() {
let policy = Oid::from_bytes(&[42, 3]);
let named = seq(&[
tlv(6, oid::CERTIFICATE_POLICIES.as_bytes()),
tlv(4, &seq(&[seq(&[tlv(6, policy.as_bytes())])])),
]);
let root_bytes = certificate("Root", false);
let leaf_bytes = certificate_with_extensions("Leaf", &[named]);
let source = Parser::der(memory(1 << 20, 1 << 24));
let root = Certificate::read(&source, &root_bytes, 400).unwrap();
let leaf = Certificate::read(&source, &leaf_bytes, 800).unwrap();
let anchor = Anchor::new(root.tbs().subject(), root.tbs().public_key());
let source_work = source.memory().work_used();
let current = memory(1 << 20, 1 << 24);
let operation = Parser::der(current.clone());
let query = operation.read(&[], 1200).unwrap();
let execution = query.execution();
let verifier = ScopedProvider {
calls: Cell::new(0),
cost: 1,
};
let wanted = [policy];
let context = ValidationContext {
execution: &execution,
interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
policy: Policy {
wanted: &wanted,
..Policy::default()
},
bounds: ValidationBounds::default(),
verifier: &verifier,
watch: &(),
recognizer: &(),
};
let Validation::Accepted(facts) = path::validate(&[&leaf], anchor, &context).unwrap() else {
panic!("the scoped validator must use the same semantic policy graph");
};
assert_eq!(facts.policies(), &[policy]);
assert_eq!(verifier.calls.get(), 1);
assert_eq!(source.memory().work_used(), source_work);
assert!(current.used() > 0);
drop(facts);
assert_eq!(current.used(), 0);
for (work, depth, callback_cost, callback_expected) in [
(0, 32, 1, false),
(1_000_000, 0, 1, true),
(1_000_000, 32, 1_000_000, true),
] {
let account = memory(1 << 20, 1 << 24);
let operation = Parser::new(
structured_asn1::Encoding::Der,
structured_asn1::Limits {
max_work: work,
max_depth: depth,
..structured_asn1::Limits::default()
},
account.clone(),
);
let query = operation.read(&[], 1200).unwrap();
let work = query.execution();
let provider = ScopedProvider {
calls: Cell::new(0),
cost: callback_cost,
};
let context = ValidationContext {
execution: &work,
verifier: &provider,
..context
};
let error = path::validate(&[&leaf], anchor, &context).unwrap_err();
if callback_cost == 1_000_000 {
assert!(matches!(
error,
BuildError::Verification(MemoryError::WorkExceeded)
));
} else {
assert!(matches!(error, BuildError::Processing(ref error) if error.is_resource()));
}
assert_eq!(provider.calls.get(), usize::from(callback_expected));
assert_eq!(account.used(), 0);
}
}
impl Verify for Provider<'_> {
type Error = &'static str;
fn verify(
&self,
_algorithm: &AlgorithmIdentifier<'_>,
_key: WorkingKey<'_>,
signed: &[u8],
_signature: BitString<'_>,
_execution: &Memory,
) -> Result<Verified, Self::Error> {
assert_eq!(signed.as_ptr(), self.signed.as_ptr());
assert_eq!(signed, self.signed);
self.calls.set(self.calls.get() + 1);
if self.error {
return Err("provider original cause");
}
Ok(Verified::Yes)
}
}
#[test]
fn public_supplied_path_keeps_signed_bytes_and_callback_error_identity() {
let root_der = certificate("Root", false);
let leaf_der = certificate("Leaf", false);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
let anchor = Anchor::new(root.tbs().subject(), root.tbs().public_key());
for error in [false, true] {
let execution = memory(1_000_000, 1_000_000);
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at_instant(leaf.tbs().validity().not_before().instant()),
policy: Policy::default(),
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
match path::validate(&[&leaf], anchor, &context) {
Ok(Validation::Accepted(facts)) if !error => assert_eq!(
facts.key.key().as_bytes().as_ptr(),
leaf.tbs().public_key().as_bytes().as_ptr()
),
Err(BuildError::Verification(cause)) if error => {
assert_eq!(cause, "provider original cause");
}
other => panic!("unexpected outcome: {other:?}"),
}
assert_eq!(provider.calls.get(), 1);
assert_eq!(execution.used(), 0);
}
}
#[test]
fn public_current_domain_refusal_is_located_and_precedes_the_provider() {
let root_der = certificate("Root", false);
let leaf_der = certificate("Leaf", false);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
for (storage, work, limit) in [
(0, 1_000_000, structured_asn1::Limit::Memory),
(1_000_000, 0, structured_asn1::Limit::Work),
] {
let execution = memory(storage, work);
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
policy: Policy::default(),
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
let error = path::validate(
&[&leaf],
Anchor::new(root.tbs().subject(), root.tbs().public_key()),
&context,
)
.unwrap_err();
let BuildError::Processing(error) = error else {
panic!("a shared refusal remains a processing error")
};
assert_eq!(
error.code(),
ErrorCode::Encoding(structured_asn1::ErrorCode::LimitExceeded(limit))
);
let subject_at =
leaf.tbs().subject().as_bytes().as_ptr() as usize - leaf_der.as_ptr() as usize;
assert_eq!(error.offset(), 700 + subject_at as u64);
assert_eq!(provider.calls.get(), 0);
assert_eq!(execution.used(), 0);
}
}
#[test]
fn public_duplicates_and_explicit_anchor_inclusion_are_semantic_failures() {
let root_der = certificate("Root", false);
let leaf_der = certificate("Leaf", false);
let copy_der = leaf_der.clone();
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
let copy = Certificate::read(&parser, ©_der, 900).unwrap();
let execution = memory(1_000_000, 1_000_000);
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
policy: Policy::default(),
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
for (chain, reason) in [
(
[&leaf, ©],
RejectionReason::DuplicateCertificate { earlier: 0 },
),
([&leaf, &root], RejectionReason::AnchorIncluded),
] {
let Validation::Rejected(rejected) =
path::validate(&chain, Anchor::from_certificate(&root), &context).unwrap()
else {
panic!("invalid path membership must not reach verification")
};
assert_eq!(rejected.at, Some(ConstraintSource::Certificate(1)));
assert_eq!(rejected.reason, reason);
}
assert_eq!(provider.calls.get(), 0);
assert_eq!(execution.used(), 0);
}
#[test]
fn public_pure_anchor_does_not_make_its_own_certificate_a_path() {
let root_der = certificate("Root", false);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let execution = memory(1_000_000, 1_000_000);
let provider = Provider {
signed: root.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at(root.tbs().validity().not_before().seconds()),
policy: Policy::default(),
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
let Validation::Rejected(rejected) = path::validate(
&[&root],
Anchor::new(root.tbs().subject(), root.tbs().public_key()),
&context,
)
.unwrap() else {
panic!("a verified self-published anchor is not a certificate path")
};
assert_eq!(rejected.at, Some(ConstraintSource::Certificate(0)));
assert_eq!(rejected.reason, RejectionReason::AnchorIncluded);
assert_eq!(provider.calls.get(), 1);
assert_eq!(execution.used(), 0);
}
struct RegistryFailure;
impl structured_pkix::Recognizer for RegistryFailure {
type Error = &'static str;
fn recognizes(&self, _kind: Oid<'_>, _execution: &Memory) -> Result<bool, Self::Error> {
Err("registry original cause")
}
}
struct Deadline(bool);
impl path::Watch for Deadline {
fn out_of_time(&self) -> bool {
self.0
}
}
#[test]
fn public_duplicate_extension_error_stays_at_the_decoding_boundary() {
let extension = seq(&[tlv(0x06, &[0x2a, 3, 4, 5]), tlv(0x04, &[])]);
let der = certificate_with_extensions("Leaf", &[extension.clone(), extension]);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let error = Certificate::read(&parser, &der, 700).unwrap_err();
assert_eq!(error.code(), ErrorCode::DuplicateExtension);
assert_eq!(error.field(), "certificate.tbsCertificate.extensions");
assert!(error.offset() >= 700);
assert!(error.offset() < 700 + der.len() as u64);
assert!(!error.is_resource());
}
#[test]
fn critical_uri_san_with_fragment_identifies_an_empty_subject() {
let value = "https://operator@example.com:8443/path?q=1#id";
let san = seq(&[
tlv(0x06, oid::SUBJECT_ALT_NAME.as_bytes()),
tlv(0x01, &[0xff]),
tlv(0x04, &seq(&[tlv(0x86, value.as_bytes())])),
]);
let root_der = certificate("Root", false);
let leaf_der = certificate_with_extensions("", &[san]);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
assert!(leaf.tbs().subject().is_empty());
let execution = memory(1_000_000, 1_000_000);
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at_instant(leaf.tbs().validity().not_before().instant()),
policy: Policy::default(),
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
let outcome = path::validate(
&[&leaf],
Anchor::new(root.tbs().subject(), root.tbs().public_key()),
&context,
)
.unwrap();
assert!(matches!(outcome, Validation::Accepted(_)), "{outcome:?}");
assert_eq!(provider.calls.get(), 1);
drop(outcome);
assert_eq!(execution.used(), 0);
}
#[test]
fn critical_policy_qualifiers_require_interpretation() {
for (qualifier, value, interpreted) in [
(Oid::from_bytes(&[0x2a, 3, 5]), tlv(0x05, &[]), false),
(oid::CPS_QUALIFIER, tlv(0x05, &[]), false),
(oid::USER_NOTICE_QUALIFIER, tlv(0x05, &[]), false),
(
oid::CPS_QUALIFIER,
tlv(0x16, b"https://example.test/policy#section"),
true,
),
] {
for critical in [false, true] {
let information = seq(&[
tlv(0x06, &[0x2a, 3, 4]),
seq(&[seq(&[tlv(0x06, qualifier.as_bytes()), value.clone()])]),
]);
let mut parts = vec![tlv(0x06, oid::CERTIFICATE_POLICIES.as_bytes())];
if critical {
parts.push(tlv(0x01, &[0xff]));
}
parts.push(tlv(0x04, &seq(&[information])));
let root_der = certificate("Root", false);
let leaf_der = certificate_with_extensions("Leaf", &[seq(&parts)]);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
let execution = memory(1_000_000, 1_000_000);
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at_instant(leaf.tbs().validity().not_before().instant()),
policy: Policy::default(),
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
let outcome = path::validate(
&[&leaf],
Anchor::new(root.tbs().subject(), root.tbs().public_key()),
&context,
)
.unwrap();
if critical && !interpreted {
let Validation::Rejected(rejected) = &outcome else {
panic!("uninterpreted critical qualifier: {outcome:?}")
};
assert_eq!(rejected.at, Some(ConstraintSource::Certificate(0)));
assert_eq!(
rejected.reason,
RejectionReason::CriticalExtension(oid::CERTIFICATE_POLICIES)
);
assert_eq!(provider.calls.get(), 0);
} else {
assert!(matches!(outcome, Validation::Accepted(_)), "{outcome:?}");
assert_eq!(provider.calls.get(), 1);
}
drop(outcome);
assert_eq!(execution.used(), 0);
}
}
}
#[test]
fn malformed_critical_policy_extensions_keep_decoder_details() {
for (kind, critical) in [
(structured_pkix::oid::CERTIFICATE_POLICIES, false),
(structured_pkix::oid::CERTIFICATE_POLICIES, true),
(structured_pkix::oid::POLICY_CONSTRAINTS, false),
(structured_pkix::oid::POLICY_CONSTRAINTS, true),
(structured_pkix::oid::POLICY_MAPPINGS, true),
(structured_pkix::oid::INHIBIT_ANY_POLICY, true),
] {
let root_der = certificate("Root", false);
let mut components = vec![tlv(0x06, kind.as_bytes())];
if critical {
components.push(tlv(0x01, &[0xff]));
}
components.push(tlv(0x04, &[0x05, 0x00]));
let leaf_der = certificate_with_extensions("Leaf", &[seq(&components)]);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
let execution = memory(1_000_000, 1_000_000);
let extensions = leaf.tbs().extensions();
let expected = if kind == structured_pkix::oid::CERTIFICATE_POLICIES {
extensions.certificate_policies(&execution).map(|_| ())
} else if kind == structured_pkix::oid::POLICY_CONSTRAINTS {
extensions.policy_constraints(&execution).map(|_| ())
} else if kind == structured_pkix::oid::POLICY_MAPPINGS {
extensions.policy_mappings(&execution).map(|_| ())
} else {
extensions.inhibit_any_policy(&execution).map(|_| ())
}
.unwrap_err();
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at_instant(leaf.tbs().validity().not_before().instant()),
policy: Policy::default(),
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
let Validation::Rejected(rejected) = path::validate(
&[&leaf],
Anchor::new(root.tbs().subject(), root.tbs().public_key()),
&context,
)
.unwrap() else {
panic!("malformed policy extension is a completed rejection")
};
assert_eq!(rejected.at, Some(ConstraintSource::Certificate(0)));
assert_eq!(
rejected.reason,
RejectionReason::MalformedExtension {
extension: kind,
code: expected.code(),
field: expected.field(),
offset: expected.offset(),
}
);
assert_eq!(execution.used(), 0);
}
}
#[test]
fn public_policy_failure_and_local_policy_limit_are_distinct() {
let root_der = certificate("Root", false);
let leaf_der = certificate("Leaf", false);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
let execution = memory(1_000_000, 1_000_000);
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
policy: Policy {
explicit: true,
..Policy::default()
},
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
let anchor = Anchor::new(root.tbs().subject(), root.tbs().public_key());
let Validation::Rejected(rejected) = path::validate(&[&leaf], anchor, &context).unwrap() else {
panic!("absent required policies")
};
assert_eq!(rejected.at, Some(ConstraintSource::Certificate(0)));
assert_eq!(rejected.reason, RejectionReason::Policies);
assert_eq!(provider.calls.get(), 1);
let context = ValidationContext {
bounds: ValidationBounds {
policies: 0,
..context.bounds
},
..context
};
assert!(matches!(
path::validate(&[&leaf], anchor, &context).unwrap(),
Validation::Incomplete(Truncation::Policies)
));
assert_eq!(provider.calls.get(), 1);
assert_eq!(execution.used(), 0);
}
#[test]
fn public_local_limits_and_empty_input_never_produce_accepted_facts() {
let root_der = certificate("Root", false);
let leaf_der = certificate("Leaf", false);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
for (depth, work, expired, expected) in [
(0, 1_000_000, false, Truncation::Depth),
(12, 0, false, Truncation::Work),
(12, 1_000_000, true, Truncation::Time),
] {
let execution = memory(1_000_000, 1_000_000);
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let deadline = Deadline(expired);
let context = ValidationContext {
execution: &execution,
interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
policy: Policy::default(),
bounds: ValidationBounds {
depth,
work,
policies: 1024,
},
verifier: &provider,
watch: &deadline,
recognizer: &(),
};
let anchor = Anchor::new(root.tbs().subject(), root.tbs().public_key());
assert!(
matches!(path::validate(&[&leaf], anchor, &context).unwrap(), Validation::Incomplete(reason) if reason == expected)
);
let Validation::Rejected(rejected) = path::validate(&[], anchor, &context).unwrap() else {
panic!("empty input is a completed rejection")
};
assert_eq!(rejected.at, None);
assert_eq!(rejected.reason, RejectionReason::EmptyPath);
assert_eq!(provider.calls.get(), 0);
assert_eq!(execution.used(), 0);
assert_eq!(execution.work_used(), 0);
}
}
#[test]
fn public_validity_rejection_precedes_signature_verification() {
let root_der = certificate("Root", false);
let leaf_der = certificate("Leaf", false);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
let execution = memory(1_000_000, 1_000_000);
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at(leaf.tbs().validity().not_after().seconds() + 1),
policy: Policy::default(),
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
let Validation::Rejected(rejected) = path::validate(
&[&leaf],
Anchor::new(root.tbs().subject(), root.tbs().public_key()),
&context,
)
.unwrap() else {
panic!("expired certificate")
};
assert_eq!(rejected.at, Some(ConstraintSource::Certificate(0)));
assert_eq!(rejected.reason, RejectionReason::Validity);
assert_eq!(provider.calls.get(), 0);
assert_eq!(execution.used(), 0);
}
#[test]
fn public_unknown_critical_and_recognizer_failure_remain_distinct() {
let root_der = certificate("Root", false);
let leaf_der = certificate("Leaf", true);
let parser = Parser::der(memory(1_000_000, 1_000_000));
let root = Certificate::read(&parser, &root_der, 0).unwrap();
let leaf = Certificate::read(&parser, &leaf_der, 700).unwrap();
let execution = memory(1_000_000, 1_000_000);
let provider = Provider {
signed: leaf.signed_bytes(),
calls: Cell::new(0),
error: false,
};
let context = ValidationContext {
execution: &execution,
interval: Interval::at(leaf.tbs().validity().not_before().seconds()),
policy: Policy::default(),
bounds: ValidationBounds::default(),
verifier: &provider,
watch: &(),
recognizer: &(),
};
let anchor = Anchor::new(root.tbs().subject(), root.tbs().public_key());
let Validation::Rejected(rejected) = path::validate(&[&leaf], anchor, &context).unwrap() else {
panic!("unknown critical extension")
};
assert_eq!(
rejected.reason,
RejectionReason::CriticalExtension(Oid::from_bytes(&[0x2a, 3, 4, 5]))
);
let context = ValidationContext {
recognizer: &RegistryFailure,
execution: &execution,
interval: context.interval,
policy: context.policy,
bounds: context.bounds,
verifier: &provider,
watch: &(),
};
assert!(matches!(
path::validate(&[&leaf], anchor, &context),
Err(BuildError::Recognition("registry original cause"))
));
assert_eq!(provider.calls.get(), 0);
assert_eq!(execution.used(), 0);
}