use super::{Extensions, Recognizer, is_known, read};
use crate::error::{ErrorCode, Result};
use crate::fixtures::{bits, boolean, extension, int, octets, oid as oid_of, seq, tlv};
use crate::oid;
use alloc::vec::Vec;
use structured_asn1::{Oid, Parser};
fn parse<'a>(parser: &'a Parser, der: &'a [u8]) -> Result<Extensions<'a>> {
let mut reader = parser.read(der, 0).unwrap();
let element = reader.next_element().unwrap().unwrap();
read(&reader, &element, "test")
}
#[test]
fn full_extension_iteration_pays_before_exposing_metadata() {
let execution = crate::fixtures::memory();
let parser = Parser::der(execution.clone());
let der = seq(&[extension(oid::BASIC_CONSTRAINTS, false, &seq(&[]))]);
let extensions = parse(&parser, &der).unwrap();
let before = execution.work_used();
let mut entries = extensions.iter(&execution);
assert_eq!(
execution.work_used(),
before,
"constructor performs no traversal"
);
let item = entries.next().unwrap().unwrap();
assert_eq!(item.kind(), oid::BASIC_CONSTRAINTS);
assert_eq!(execution.work_used(), before + 1);
assert!(entries.next().is_none());
assert!(entries.next().is_none());
assert_eq!(execution.work_used(), before + 1);
}
#[test]
fn embedded_certificate_extension_catalogue_and_duplicate_index_share_local_work() {
let der = seq(&[extension(oid::BASIC_CONSTRAINTS, false, &seq(&[]))]);
let execution = crate::fixtures::memory();
let parser = Parser::der(execution.clone());
let extensions = parse(&parser, &der).unwrap();
let full_work = u64::try_from(execution.work_used()).unwrap();
drop(extensions);
assert_eq!(execution.used(), 0);
let limited = crate::fixtures::memory();
let parser = Parser::new(
structured_asn1::Encoding::Der,
structured_asn1::Limits {
max_work: full_work - 1,
..structured_asn1::Limits::default()
},
limited.clone(),
);
let error = parse(&parser, &der)
.expect_err("every extension storage/index operation belongs to the producer");
assert!(error.is_resource());
assert_eq!(error.field(), "test");
assert_eq!(limited.used(), 0);
}
#[test]
fn critical_filter_refuses_unfunded_noncritical_scanning_and_then_stops() {
use structured_asn1::memory::{Memory, MemoryAccount};
let der = seq(&[
extension(oid::BASIC_CONSTRAINTS, false, &seq(&[])),
extension(oid::KEY_USAGE, false, &bits(&[0x80])),
]);
let parser = Parser::der(crate::fixtures::memory());
let items = parse(&parser, &der).unwrap();
let execution = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, 1)));
let mut criticals = items.criticals(&execution);
let first = criticals.next();
assert!(matches!(first, Some(Err(error)) if error.is_resource()));
assert_eq!(execution.work_used(), 1);
assert!(criticals.next().is_none());
assert!(criticals.next().is_none());
assert_eq!(execution.work_used(), 1);
}
#[test]
fn critical_filter_pays_for_the_visited_prefix_and_a_complete_miss() {
use structured_asn1::memory::{Memory, MemoryAccount};
let der = seq(&[
extension(oid::BASIC_CONSTRAINTS, false, &seq(&[])),
extension(oid::KEY_USAGE, true, &bits(&[0x80])),
extension(oid::SUBJECT_KEY_IDENTIFIER, false, &octets(b"key")),
]);
let parser = Parser::der(crate::fixtures::memory());
let items = parse(&parser, &der).unwrap();
let execution = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, 2)));
let mut entries = items.criticals(&execution);
assert_eq!(entries.next().unwrap().unwrap().kind(), oid::KEY_USAGE);
assert_eq!(execution.work_used(), 2);
assert!(entries.next().unwrap().unwrap_err().is_resource());
assert!(entries.next().is_none());
assert_eq!(execution.work_used(), 2);
let full = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, 3)));
let mut entries = items.criticals(&full);
assert!(entries.next().unwrap().is_ok());
assert!(entries.next().is_none());
assert!(entries.next().is_none());
assert_eq!(full.work_used(), 3);
let empty = Extensions::empty(parser.memory());
let denied = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, 0)));
assert!(empty.criticals(&denied).next().is_none());
assert_eq!(denied.work_used(), 0);
}
#[test]
fn extension_reader_inherits_container_depth_and_offset() {
let value = seq(&[boolean(true)]);
let der = seq(&[extension(oid::BASIC_CONSTRAINTS, false, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
let item = extensions
.get(oid::BASIC_CONSTRAINTS, extensions.memory())
.unwrap()
.unwrap();
let mut reader = item.reader(item.parser().memory()).unwrap();
assert_eq!(reader.depth(), 3);
let element = reader.next_element().unwrap().unwrap();
assert_eq!(element.span().start, item.value_span.start);
assert_eq!(element.as_bytes().as_ptr(), item.value().as_ptr());
}
#[test]
fn extension_lookup_admits_each_oid_comparison_before_matching() {
use structured_asn1::memory::{Memory, MemoryAccount};
let der = seq(&[
extension(oid::BASIC_CONSTRAINTS, false, &seq(&[])),
extension(oid::KEY_USAGE, false, &bits(&[0x80])),
]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
let first_work = 1 + oid::BASIC_CONSTRAINTS.as_bytes().len();
let execution = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, first_work)));
assert!(
extensions
.get(oid::BASIC_CONSTRAINTS, &execution)
.unwrap()
.is_some()
);
assert_eq!(execution.work_used(), first_work);
let error = extensions.get(oid::KEY_USAGE, &execution).unwrap_err();
assert!(error.is_resource());
assert_eq!(error.offset(), extensions.items[0].span.start);
assert_eq!(execution.work_used(), first_work);
let mut encoded = [0x81; 80];
encoded[79] = 1;
let long = Oid::from_bytes(&encoded);
let short = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, 80)));
assert!(extensions.get(long, &short).unwrap_err().is_resource());
assert_eq!(short.work_used(), 0);
let full = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, 162)));
assert!(extensions.get(long, &full).unwrap().is_none());
assert_eq!(full.work_used(), 162);
assert_eq!(full.used(), 0);
}
#[test]
fn lazy_extension_uses_current_work_and_storage_without_moving_old_charge() {
use structured_asn1::memory::{Memory, MemoryAccount};
let value = seq(&[crate::fixtures::implicit(2, b"example.ua")]);
let der = seq(&[extension(oid::SUBJECT_ALT_NAME, false, &value)]);
let original = crate::fixtures::memory();
let parser = Parser::der(original.clone());
let extensions = parse(&parser, &der).unwrap();
let item = extensions
.get(oid::SUBJECT_ALT_NAME, extensions.memory())
.unwrap()
.unwrap();
let before = original.work_used();
let held = original.used();
let no_work = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, 0)));
let error = extensions.subject_alt_name(&no_work).unwrap_err();
assert!(error.is_resource());
assert_eq!(error.offset(), item.span().start);
let no_storage = Memory::new(alloc::sync::Arc::new(MemoryAccount::new(0, 10_000)));
let error = extensions.subject_alt_name(&no_storage).unwrap_err();
assert!(error.is_resource());
assert_eq!(
error.code(),
ErrorCode::Encoding(structured_asn1::ErrorCode::LimitExceeded(
structured_asn1::Limit::Memory
))
);
let execution = crate::fixtures::memory();
let names = extensions.subject_alt_name(&execution).unwrap().unwrap();
assert!(names.memory().same_account(&execution));
assert!(matches!(
&*names,
[crate::general_name::GeneralName::Dns("example.ua")]
));
assert_eq!(original.work_used(), before);
assert_eq!(original.used(), held);
assert!(execution.used() > 0);
drop(names);
assert_eq!(execution.used(), 0);
drop(extensions);
assert_eq!(original.used(), 0);
}
struct Scheme(Oid<'static>);
impl 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 == self.0)
}
}
struct NameScheme(Oid<'static>);
impl Recognizer for NameScheme {
type Error = core::convert::Infallible;
fn recognizes(
&self,
_kind: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(false)
}
fn recognizes_other_name(
&self,
type_id: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(type_id == self.0)
}
}
struct ConstraintScheme(Oid<'static>);
impl Recognizer for ConstraintScheme {
type Error = core::convert::Infallible;
fn recognizes(
&self,
_kind: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(false)
}
fn recognizes_name_constraint(
&self,
type_id: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(type_id == self.0)
}
}
struct AccessScheme(Oid<'static>);
impl Recognizer for AccessScheme {
type Error = core::convert::Infallible;
fn recognizes(
&self,
_kind: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(false)
}
fn recognizes_other_name(
&self,
type_id: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(type_id == self.0)
}
fn recognizes_access_method(
&self,
method: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(method == self.0)
}
}
#[test]
fn an_extension_keeps_its_value_encoded() {
let value = seq(&[boolean(true), int(3)]);
let der = seq(&[extension(oid::BASIC_CONSTRAINTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
let found = extensions
.get(oid::BASIC_CONSTRAINTS, extensions.memory())
.unwrap()
.unwrap();
assert!(found.is_critical());
assert_eq!(found.value(), &value[..]);
assert_eq!(found.span().start, 2);
}
#[test]
fn what_this_crate_reads_needs_no_scheme_to_recognize_it() {
assert!(is_known(oid::BASIC_CONSTRAINTS));
assert!(is_known(oid::QC_STATEMENTS));
assert!(!is_known(oid::PRIVATE_KEY_USAGE_PERIOD));
}
#[test]
fn a_scheme_says_which_of_its_own_extensions_it_knows() {
let mine = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x02, 0x01]);
let other = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x02, 0x02]);
let der = seq(&[
extension(mine, true, &octets(&[0x01])),
extension(other, true, &octets(&[0x02])),
]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
let scheme = Scheme(mine);
let blocked: Vec<_> = extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.iter()
.map(|extension| extension.kind())
.collect();
assert_eq!(blocked, [other]);
assert_eq!(extensions.len(), 2);
for extension in extensions.iter(parser.memory()) {
assert!(extension.unwrap().is_critical());
}
}
#[test]
fn extensions_that_say_nothing_or_say_it_twice_are_refused() {
let parser = Parser::der(crate::fixtures::memory());
let empty = seq(&[]);
let error = parse(&parser, &empty).unwrap_err();
assert_eq!(error.code(), ErrorCode::EmptySequence);
let parser = Parser::der(crate::fixtures::memory());
let twice = seq(&[
extension(oid::KEY_USAGE, false, &bits(&[0x80])),
extension(oid::KEY_USAGE, false, &bits(&[0x04])),
]);
let error = parse(&parser, &twice).unwrap_err();
assert_eq!(error.code(), ErrorCode::DuplicateExtension);
}
#[test]
fn an_extension_that_is_not_shaped_like_one_is_refused() {
let parser = Parser::der(crate::fixtures::memory());
let der = seq(&[seq(&[oid_of(oid::KEY_USAGE), bits(&[0x80])])]);
let error = parse(&parser, &der).unwrap_err();
assert_eq!(
error.code(),
ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag)
);
let parser = Parser::der(crate::fixtures::memory());
let der = seq(&[seq(&[oid_of(oid::KEY_USAGE)])]);
let error = parse(&parser, &der).unwrap_err();
assert_eq!(error.code(), ErrorCode::MissingField);
let parser = Parser::der(crate::fixtures::memory());
let der = seq(&[seq(&[
oid_of(oid::KEY_USAGE),
octets(&bits(&[0x80])),
int(1),
])]);
let error = parse(&parser, &der).unwrap_err();
assert_eq!(error.code(), ErrorCode::TrailingData);
}
#[test]
fn a_default_written_out_is_refused_under_der_and_read_under_ber() {
let der = seq(&[seq(&[
oid_of(oid::SUBJECT_KEY_IDENTIFIER),
boolean(false),
octets(&octets(&[0x01, 0x02])),
])]);
let strict = Parser::der(crate::fixtures::memory());
let error = parse(&strict, &der).unwrap_err();
assert_eq!(error.code(), ErrorCode::MalformedValue);
let lenient = Parser::ber(crate::fixtures::memory());
let extensions = parse(&lenient, &der).unwrap();
assert!(
!extensions
.get(oid::SUBJECT_KEY_IDENTIFIER, extensions.memory())
.unwrap()
.unwrap()
.is_critical()
);
}
#[test]
fn a_malformed_known_critical_extension_still_blocks() {
let der = seq(&[extension(oid::KEY_USAGE, true, &int(3))]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
let blocked: Vec<_> = extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.iter()
.map(|item| item.kind())
.collect();
assert_eq!(blocked, [oid::KEY_USAGE]);
let der = seq(&[extension(oid::KEY_USAGE, true, &tlv(0x03, &[0x07, 0x80]))]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn extension_values_decode_as_der_whatever_parser_is_passed() {
let der = seq(&[extension(
oid::BASIC_CONSTRAINTS,
true,
&seq(&[boolean(false)]),
)]);
let parser = Parser::ber(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
let error = extensions
.basic_constraints(extensions.memory())
.unwrap_err();
assert_eq!(error.code(), ErrorCode::MalformedValue);
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn an_unknown_other_name_type_in_a_critical_name_extension_blocks() {
use crate::fixtures::{context, seq as fseq, utf8};
let other = fseq(&[context(
0,
&[oid_of(oid::USER_ID), context(0, &[utf8("uid")])],
)]);
let der = seq(&[extension(oid::SUBJECT_ALT_NAME, true, &other)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
assert!(
extensions
.subject_alt_name(extensions.memory())
.unwrap()
.is_some()
);
let scheme = NameScheme(oid::USER_ID);
assert_eq!(
extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn a_scheme_clears_a_critical_extension_only_where_its_value_is_der() {
let mine = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x02, 0x03]);
let scheme = Scheme(mine);
for value in [
Vec::new(),
tlv(0x02, &[0x00, 0x01]),
seq(&[tlv(0x02, &[0x00, 0x01])]),
[int(1), int(2)].concat(),
tlv(0x02, &[0x01]).into_iter().take(2).collect(),
] {
let der = seq(&[extension(mine, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.len(),
1,
"{value:02x?}"
);
}
let der = seq(&[extension(mine, true, &seq(&[int(1), octets(&[0x02])]))]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn an_unknown_access_method_in_a_critical_extension_blocks() {
use crate::fixtures::{implicit, seq as fseq};
let mine = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x06, 0x01]);
let value = fseq(&[fseq(&[oid_of(mine), implicit(6, b"https://x.example.ua")])]);
let der = seq(&[extension(oid::AUTHORITY_INFO_ACCESS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let scheme = AccessScheme(mine);
assert_eq!(
extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.len(),
0
);
let value = fseq(&[fseq(&[
oid_of(oid::OCSP),
implicit(6, b"https://ocsp.example.ua"),
])]);
let der = seq(&[extension(oid::AUTHORITY_INFO_ACCESS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn a_currency_code_that_is_not_alphabetic_blocks() {
use crate::fixtures::{printable, seq as fseq};
for code in ["A1?", "uah", "U-H"] {
let value = fseq(&[fseq(&[
oid_of(oid::QC_LIMIT_VALUE),
fseq(&[printable(code), int(1000), int(0)]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1,
"{code}"
);
}
}
#[test]
fn a_cps_pointer_that_is_not_a_uri_blocks() {
use crate::fixtures::{ia5, seq as fseq};
let policy = structured_asn1::Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x03, 0x01]);
for pointer in [&b"not-a-uri"[..], b"/relative", b"https:%"] {
let mut ia5_pointer = alloc::vec![0x16, u8::try_from(pointer.len()).unwrap()];
ia5_pointer.extend_from_slice(pointer);
let value = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[oid_of(oid::CPS_QUALIFIER), ia5_pointer])]),
])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1,
"{pointer:02x?}"
);
}
for pointer in [
"https://cps.example.ua",
"https://cps.example.ua/text#signing",
"https://cps.example.ua/text?v=2#signing",
"https://cps.example.ua/text#",
] {
let value = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[oid_of(oid::CPS_QUALIFIER), ia5(pointer)])]),
])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0,
"{pointer}"
);
}
let value = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[
oid_of(oid::CPS_QUALIFIER),
ia5("https://cps.example.ua/text#a#b"),
])]),
])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn recognizing_an_extension_does_not_vouch_for_an_other_name_of_the_same_oid() {
use crate::fixtures::{context, seq as fseq, utf8};
let mine = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x07, 0x01]);
let other = fseq(&[context(0, &[oid_of(mine), context(0, &[utf8("v")])])]);
let der = seq(&[extension(oid::SUBJECT_ALT_NAME, true, &other)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
let scheme = Scheme(mine);
assert_eq!(
extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn a_mailbox_location_for_a_publication_method_blocks() {
use crate::fixtures::{implicit, seq as fseq};
for method in [oid::CA_ISSUERS, oid::CA_REPOSITORY] {
let value = fseq(&[fseq(&[oid_of(method), implicit(1, b"ca@example.ua")])]);
let der = seq(&[extension(oid::AUTHORITY_INFO_ACCESS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1,
"{method:?}"
);
}
}
#[test]
fn an_attribute_outside_its_size_bounds_in_a_critical_name_blocks() {
use crate::fixtures::{context, printable, seq as fseq, set as fset};
let long_country = fseq(&[context(
4,
&[fseq(&[fset(&[fseq(&[
oid_of(oid::COUNTRY),
printable("USA"),
])])])],
)]);
let der = seq(&[extension(oid::SUBJECT_ALT_NAME, true, &long_country)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let oversized = "a".repeat(65);
let long_common = fseq(&[context(
4,
&[fseq(&[fset(&[fseq(&[
oid_of(oid::COMMON_NAME),
crate::fixtures::utf8(&oversized),
])])])],
)]);
let der = seq(&[extension(oid::SUBJECT_ALT_NAME, true, &long_common)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn an_unknown_other_name_in_a_critical_authority_key_identifier_blocks() {
use crate::fixtures::{context, implicit, seq as fseq, set as fset, utf8};
let unknown = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x06, 0x03]);
let other = context(0, &[oid_of(unknown), context(0, &[int(1)])]);
let root = fseq(&[fset(&[fseq(&[oid_of(oid::COMMON_NAME), utf8("Root")])])]);
let value = fseq(&[
implicit(0, &[0xab]),
context(1, &[other, context(4, &[root])]),
implicit(2, &[0x05]),
]);
let der = seq(&[extension(oid::AUTHORITY_KEY_IDENTIFIER, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let scheme = NameScheme(unknown);
assert_eq!(
extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn an_unknown_other_name_in_a_critical_distribution_point_blocks() {
use crate::fixtures::{context, seq as fseq};
let unknown = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x06, 0x04]);
let other = context(0, &[oid_of(unknown), context(0, &[int(1)])]);
let point = fseq(&[context(0, &[context(0, &[other])])]);
let der = seq(&[extension(
oid::CRL_DISTRIBUTION_POINTS,
true,
&fseq(&[point]),
)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let scheme = NameScheme(unknown);
assert_eq!(
extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn a_pds_location_that_is_not_a_url_blocks() {
use crate::fixtures::{printable, seq as fseq, tlv as ftlv};
let value = fseq(&[fseq(&[
oid_of(oid::QC_PDS),
fseq(&[fseq(&[ftlv(0x16, b"not-a-url"), printable("en")])]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn an_unknown_other_name_registration_authority_blocks() {
use crate::fixtures::{context, seq as fseq};
let unknown = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x06, 0x05]);
let other = context(0, &[oid_of(unknown), context(0, &[int(1)])]);
let information = fseq(&[fseq(&[other])]);
let value = fseq(&[fseq(&[oid_of(oid::PKIX_QC_SYNTAX_V2), information])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let scheme = NameScheme(unknown);
assert_eq!(
extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn an_access_location_outside_its_methods_forms_blocks() {
use crate::fixtures::{context, implicit, seq as fseq};
let value = fseq(&[fseq(&[oid_of(oid::OCSP), implicit(2, b"ocsp.example.ua")])]);
let der = seq(&[extension(oid::AUTHORITY_INFO_ACCESS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let root = fseq(&[crate::fixtures::set(&[fseq(&[
oid_of(oid::COMMON_NAME),
crate::fixtures::utf8("Root"),
])])]);
let value = fseq(&[fseq(&[oid_of(oid::CA_ISSUERS), context(4, &[root])])]);
let der = seq(&[extension(oid::AUTHORITY_INFO_ACCESS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn an_unknown_other_name_location_in_a_critical_access_extension_blocks() {
use crate::fixtures::{context, seq as fseq, utf8};
let unknown = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x06, 0x02]);
let other = context(0, &[oid_of(unknown), context(0, &[utf8("loc")])]);
let value = fseq(&[fseq(&[oid_of(oid::OCSP), other.clone()])]);
let der = seq(&[extension(oid::AUTHORITY_INFO_ACCESS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let value = fseq(&[fseq(&[oid_of(unknown), other])]);
let der = seq(&[extension(oid::AUTHORITY_INFO_ACCESS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
let scheme = AccessScheme(unknown);
assert_eq!(
extensions
.unhandled_critical(&scheme, parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn a_pds_language_that_is_not_a_language_code_blocks() {
use crate::fixtures::{printable, seq as fseq};
for language in ["A1", "EN", "1a"] {
let value = fseq(&[fseq(&[
oid_of(oid::QC_PDS),
fseq(&[fseq(&[
crate::fixtures::ia5("https://pds.example.ua/en"),
printable(language),
])]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1,
"{language}"
);
}
}
#[test]
fn a_legislation_code_that_is_not_a_country_blocks() {
use crate::fixtures::{printable, seq as fseq};
for code in ["A1", "??", "ua"] {
let value = fseq(&[fseq(&[
oid_of(oid::QC_CC_LEGISLATION),
fseq(&[printable(code)]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1,
"{code}"
);
}
}
#[test]
fn a_duplicate_key_purpose_reads_once() {
use crate::fixtures::seq as fseq;
let value = fseq(&[oid_of(oid::EMAIL_PROTECTION), oid_of(oid::EMAIL_PROTECTION)]);
let der = seq(&[extension(oid::EXT_KEY_USAGE, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
let purposes = extensions
.extended_key_usage(extensions.memory())
.unwrap()
.unwrap();
assert_eq!(purposes.purposes().len(), 1);
assert!(
purposes
.has(oid::EMAIL_PROTECTION, extensions.memory())
.unwrap()
);
}
#[test]
fn encipher_bits_without_key_agreement_block() {
let der = seq(&[extension(oid::KEY_USAGE, true, &tlv(0x03, &[0x00, 0x81]))]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let der = seq(&[extension(oid::KEY_USAGE, true, &tlv(0x03, &[0x00, 0x89]))]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn a_path_length_without_the_authority_flag_blocks() {
let der = seq(&[extension(oid::BASIC_CONSTRAINTS, true, &seq(&[int(2)]))]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn a_critical_statement_list_this_run_did_not_understand_blocks() {
use crate::fixtures::null;
let garbled_kind = seq(&[seq(&[oid_of(oid::QC_TYPE), null()])]);
let unknown_kind = seq(&[seq(&[oid_of(structured_asn1::Oid::from_bytes(&[
0x2b, 0x06, 0x01, 0x04, 0x01, 0x7e,
]))])]);
for value in [garbled_kind, unknown_kind] {
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
let understood = seq(&[
seq(&[oid_of(oid::QC_COMPLIANCE)]),
seq(&[oid_of(oid::QC_TYPE), seq(&[oid_of(oid::QCT_ESEAL)])]),
]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &understood)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn a_critical_policy_with_uninterpreted_qualifiers_blocks() {
use crate::fixtures::{ia5, null, seq as fseq};
let policy = structured_asn1::Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x03, 0x01]);
let unknown_kind = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[
oid_of(structured_asn1::Oid::from_bytes(&[
0x2a, 0x86, 0x24, 0x03, 0x02,
])),
ia5("x"),
])]),
])]);
let garbled_cps = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[oid_of(oid::CPS_QUALIFIER), null()])]),
])]);
for value in [unknown_kind, garbled_cps] {
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
let plain = fseq(&[fseq(&[oid_of(policy)])]);
let with_cps = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[
oid_of(oid::CPS_QUALIFIER),
ia5("https://cps.example.ua"),
])]),
])]);
for value in [plain, with_cps] {
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
}
#[test]
fn a_critical_statement_payload_without_its_shape_blocks() {
use crate::fixtures::{ia5, null, printable, seq as fseq};
for kind in [oid::QC_LIMIT_VALUE, oid::QC_PDS, oid::QC_CC_LEGISLATION] {
let value = fseq(&[fseq(&[oid_of(kind), null()])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1,
"{kind:?}"
);
}
let limit = fseq(&[fseq(&[
oid_of(oid::QC_LIMIT_VALUE),
fseq(&[printable("UAH"), int(1000), int(0)]),
])]);
let locations = fseq(&[fseq(&[
oid_of(oid::QC_PDS),
fseq(&[fseq(&[ia5("https://pds.example.ua/en"), printable("en")])]),
])]);
let legislation = fseq(&[fseq(&[
oid_of(oid::QC_CC_LEGISLATION),
fseq(&[printable("UA")]),
])]);
for value in [limit, locations, legislation] {
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
}
fn blocked_under_budget(der: &[u8], budget: u64) -> Option<Result<usize>> {
let limits = structured_asn1::Limits {
max_elements: budget,
..structured_asn1::Limits::default()
};
let parser = Parser::new(
structured_asn1::Encoding::Der,
limits,
crate::fixtures::memory(),
);
let mut reader = parser.read(der, 0).ok()?;
let element = reader.next_element().ok()??;
let extensions = read(&reader, &element, "test").ok()?;
Some(
extensions
.unhandled_critical(&(), parser.memory())
.map(|blocked| blocked.len())
.map_err(processing),
)
}
#[test]
fn a_limit_error_inside_a_qualifier_points_into_the_document() {
use crate::fixtures::{ia5, seq as fseq};
let policy = structured_asn1::Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x03, 0x01]);
let pointer = ia5("https://cps.example.ua");
let value = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[oid_of(oid::CPS_QUALIFIER), pointer.clone()])]),
])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let qualifier_at = u64::try_from(
der.windows(pointer.len())
.position(|window| window == &pointer[..])
.unwrap(),
)
.unwrap();
let mut deepest = None;
for budget in 1..512 {
match blocked_under_budget(&der, budget) {
Some(Ok(_)) => break,
Some(Err(error)) => deepest = Some(error),
None => {}
}
}
let error = deepest.unwrap();
assert!(error.is_limit());
assert!(
error.offset() >= qualifier_at,
"offset {} points before the qualifier at {qualifier_at}",
error.offset()
);
}
#[test]
fn a_spent_budget_is_not_an_unhandled_extension() {
let der = seq(&[extension(oid::KEY_USAGE, true, &tlv(0x03, &[0x07, 0x80]))]);
let outcome = (1..64)
.find_map(|budget| blocked_under_budget(&der, budget))
.unwrap();
assert!(outcome.unwrap_err().is_limit());
}
#[test]
fn a_notice_number_not_wearing_the_integer_tag_blocks() {
use crate::fixtures::{ia5, seq as fseq};
let policy = structured_asn1::Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x03, 0x01]);
let reference = fseq(&[ia5("org"), fseq(&[tlv(0x04, &[0x01])])]);
let value = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[
oid_of(oid::USER_NOTICE_QUALIFIER),
fseq(&[reference]),
])]),
])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn a_cps_pointer_outside_the_ia5_alphabet_blocks() {
use crate::fixtures::seq as fseq;
let policy = structured_asn1::Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x03, 0x01]);
let value = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[
oid_of(oid::CPS_QUALIFIER),
tlv(0x16, b"https://cps.example.ua/\xff"),
])]),
])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn display_text_that_does_not_decode_or_overruns_its_size_blocks() {
use crate::fixtures::seq as fseq;
let policy = structured_asn1::Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x03, 0x01]);
let notice = |text: Vec<u8>| {
fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[oid_of(oid::USER_NOTICE_QUALIFIER), fseq(&[text])])]),
])])
};
let undecodable_utf8 = notice(tlv(0x0c, b"\xff\xfe"));
let odd_bmp = notice(tlv(0x1e, &[0x00, 0x04, 0x10]));
let surrogate_bmp = notice(tlv(0x1e, &[0xd8, 0x01, 0xdc, 0x00]));
let too_long = notice(tlv(0x0c, &[b'a'; 201]));
for value in [undecodable_utf8, odd_bmp, surrogate_bmp, too_long] {
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
let at_limit = notice(tlv(0x0c, &[b'a'; 200]));
let cyrillic_bmp = notice(tlv(0x1e, &[0x04, 0x23, 0x04, 0x32]));
for value in [at_limit, cyrillic_bmp] {
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
}
#[test]
fn a_numeric_currency_not_wearing_the_integer_tag_blocks() {
use crate::fixtures::seq as fseq;
let value = fseq(&[fseq(&[
oid_of(oid::QC_LIMIT_VALUE),
fseq(&[tlv(0x04, &[0x01]), int(1000), int(0)]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn qc_strings_outside_their_declared_alphabets_block() {
use crate::fixtures::{printable, seq as fseq};
let bad_url = fseq(&[fseq(&[
oid_of(oid::QC_PDS),
fseq(&[fseq(&[
tlv(0x16, b"https://pds.example.ua/\xff"),
printable("en"),
])]),
])]);
let bad_language = fseq(&[fseq(&[
oid_of(oid::QC_PDS),
fseq(&[fseq(&[
tlv(0x16, b"https://pds.example.ua/en"),
tlv(0x13, b"e@"),
])]),
])]);
let bad_country = fseq(&[fseq(&[
oid_of(oid::QC_CC_LEGISLATION),
fseq(&[tlv(0x13, b"U@")]),
])]);
let bad_currency = fseq(&[fseq(&[
oid_of(oid::QC_LIMIT_VALUE),
fseq(&[tlv(0x13, b"U@H"), int(1000), int(0)]),
])]);
for value in [bad_url, bad_language, bad_country, bad_currency] {
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
}
#[test]
fn an_empty_type_list_in_a_critical_statement_blocks() {
use crate::fixtures::seq as fseq;
let value = fseq(&[fseq(&[oid_of(oid::QC_TYPE), fseq(&[])])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn a_type_identifier_not_wearing_the_oid_tag_blocks() {
use crate::fixtures::seq as fseq;
let full = oid_of(oid::QCT_ESIGN);
let value = fseq(&[fseq(&[
oid_of(oid::QC_TYPE),
fseq(&[tlv(0x04, &full[2..])]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn an_unknown_type_inside_a_critical_type_statement_blocks() {
use crate::fixtures::seq as fseq;
let unknown = structured_asn1::Oid::from_bytes(&[0x2b, 0x06, 0x01, 0x04, 0x01, 0x7d]);
let value = fseq(&[fseq(&[
oid_of(oid::QC_TYPE),
fseq(&[oid_of(oid::QCT_ESIGN), oid_of(unknown)]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn a_numeric_currency_outside_the_iso_4217_range_blocks() {
use crate::fixtures::seq as fseq;
for code in [int(0), tlv(0x02, &[0xFF]), int(1000)] {
let value = fseq(&[fseq(&[
oid_of(oid::QC_LIMIT_VALUE),
fseq(&[code, int(1000), int(0)]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
let value = fseq(&[fseq(&[
oid_of(oid::QC_LIMIT_VALUE),
fseq(&[int(980), int(1000), int(0)]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn empty_semantics_information_blocks() {
use crate::fixtures::seq as fseq;
let value = fseq(&[fseq(&[oid_of(oid::PKIX_QC_SYNTAX_V2), fseq(&[])])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn an_unknown_semantics_identifier_in_a_critical_statement_blocks() {
use crate::fixtures::seq as fseq;
let unknown = structured_asn1::Oid::from_bytes(&[0x2b, 0x06, 0x01, 0x04, 0x01, 0x7c]);
let value = fseq(&[fseq(&[
oid_of(oid::PKIX_QC_SYNTAX_V2),
fseq(&[oid_of(unknown)]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn every_semantics_statement_is_validated_not_just_the_preferred_one() {
use crate::fixtures::{null, seq as fseq};
let value = fseq(&[
fseq(&[oid_of(oid::PKIX_QC_SYNTAX_V2)]),
fseq(&[oid_of(oid::PKIX_QC_SYNTAX_V1), null()]),
]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let value = fseq(&[
fseq(&[
oid_of(oid::PKIX_QC_SYNTAX_V2),
fseq(&[oid_of(oid::SEMANTICS_LEGAL_PERSON)]),
]),
fseq(&[
oid_of(oid::PKIX_QC_SYNTAX_V1),
fseq(&[oid_of(oid::SEMANTICS_LEGAL_PERSON)]),
]),
]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn an_access_method_in_the_wrong_extension_blocks() {
use crate::fixtures::{implicit, seq as fseq};
let crossed = [
(oid::AUTHORITY_INFO_ACCESS, oid::TIME_STAMPING_ACCESS),
(oid::AUTHORITY_INFO_ACCESS, oid::CA_REPOSITORY),
(oid::SUBJECT_INFO_ACCESS, oid::OCSP),
(oid::SUBJECT_INFO_ACCESS, oid::CA_ISSUERS),
];
for (kind, method) in crossed {
let value = fseq(&[fseq(&[
oid_of(method),
implicit(6, b"https://service.example.ua"),
])]);
let der = seq(&[extension(kind, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
let owned = [
(oid::AUTHORITY_INFO_ACCESS, oid::CA_ISSUERS),
(oid::SUBJECT_INFO_ACCESS, oid::TIME_STAMPING_ACCESS),
(oid::SUBJECT_INFO_ACCESS, oid::CA_REPOSITORY),
];
for (kind, method) in owned {
let value = fseq(&[fseq(&[
oid_of(method),
implicit(6, b"https://service.example.ua"),
])]);
let der = seq(&[extension(kind, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
}
#[test]
fn an_opaque_name_form_in_a_constraint_blocks() {
use crate::fixtures::{context, implicit, seq as fseq, set as fset};
let attribute = fseq(&[implicit(0, &[0x17]), context(1, &[int(1)])]);
let x400 = context(3, &[fseq(&[]), fset(&[attribute])]);
let value = fseq(&[context(0, &[fseq(&[x400])])]);
let der = seq(&[extension(oid::NAME_CONSTRAINTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let value = fseq(&[context(0, &[fseq(&[implicit(2, b"example.ua")])])]);
let der = seq(&[extension(oid::NAME_CONSTRAINTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn decoding_an_other_name_does_not_vouch_for_a_constraint_on_it() {
use crate::fixtures::{context, seq as fseq, utf8};
let mine = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x07, 0x01]);
let base = context(0, &[oid_of(mine), context(0, &[utf8("branch")])]);
let value = fseq(&[context(0, &[fseq(&[base])])]);
let der = seq(&[extension(oid::NAME_CONSTRAINTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
let reader = NameScheme(mine);
assert_eq!(
extensions
.unhandled_critical(&reader, parser.memory())
.unwrap()
.len(),
1
);
let matcher = ConstraintScheme(mine);
assert_eq!(
extensions
.unhandled_critical(&matcher, parser.memory())
.unwrap()
.len(),
0
);
let value = fseq(&[context(0, &[oid_of(mine), context(0, &[utf8("branch")])])]);
let der = seq(&[extension(oid::SUBJECT_ALT_NAME, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&matcher, parser.memory())
.unwrap()
.len(),
1
);
assert_eq!(
extensions
.unhandled_critical(&reader, parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn an_empty_user_notice_is_understood() {
use crate::fixtures::seq as fseq;
let policy = structured_asn1::Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x03, 0x01]);
let value = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[oid_of(oid::USER_NOTICE_QUALIFIER), fseq(&[])])]),
])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn content_scans_are_charged_to_the_work_budget() {
use crate::fixtures::{implicit, seq as fseq, tlv as ftlv};
fn scan_error(
der: &[u8],
pick: impl FnOnce(&Extensions<'_>) -> crate::error::Error,
) -> ErrorCode {
let limits = structured_asn1::Limits {
max_work: 4096,
..structured_asn1::Limits::default()
};
let parser = Parser::new(
structured_asn1::Encoding::Der,
limits,
crate::fixtures::memory(),
);
let mut reader = parser.read(der, 0).unwrap();
let element = reader.next_element().unwrap().unwrap();
let extensions = read(&reader, &element, "test").unwrap();
pick(&extensions).code()
}
let big = alloc::vec![b'a'; 100_000];
let der = seq(&[extension(
oid::SUBJECT_ALT_NAME,
true,
&fseq(&[implicit(2, &big)]),
)]);
assert_eq!(
scan_error(&der, |list| list
.subject_alt_name(list.memory())
.unwrap_err()),
ErrorCode::Encoding(structured_asn1::ErrorCode::LimitExceeded(
structured_asn1::Limit::Work
))
);
let zeros = alloc::vec![0x00; 100_001];
let der = seq(&[extension(oid::KEY_USAGE, true, &ftlv(0x03, &zeros))]);
assert_eq!(
scan_error(&der, |list| list.key_usage(list.memory()).unwrap_err()),
ErrorCode::Encoding(structured_asn1::ErrorCode::LimitExceeded(
structured_asn1::Limit::Work
))
);
}
#[test]
fn a_registered_id_constraint_base_blocks() {
use crate::fixtures::{context, seq as fseq, tlv as ftlv};
let base = ftlv(0x88, oid::OCSP.as_bytes());
let value = fseq(&[context(0, &[fseq(&[base])])]);
let der = seq(&[extension(oid::NAME_CONSTRAINTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn the_cps_uri_scan_is_charged_to_the_work_budget() {
use crate::fixtures::{ia5, seq as fseq};
let mut url = alloc::string::String::from("https://cps.example.ua/");
url.push_str(&"a".repeat(100_000));
let policy = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x01, 0x02]);
let value = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[oid_of(oid::CPS_QUALIFIER), ia5(&url)])]),
])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let limits = structured_asn1::Limits {
max_work: 8192,
..structured_asn1::Limits::default()
};
let parser = Parser::new(
structured_asn1::Encoding::Der,
limits,
crate::fixtures::memory(),
);
let extensions = parse(&parser, &der).unwrap();
let error = processing(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap_err(),
);
assert_eq!(
error.code(),
ErrorCode::Encoding(structured_asn1::ErrorCode::LimitExceeded(
structured_asn1::Limit::Work
))
);
}
#[test]
fn the_disclosure_url_scan_is_charged_to_the_work_budget() {
use crate::fixtures::{ia5, printable, seq as fseq};
let mut url = alloc::string::String::from("https://pds.example.ua/");
url.push_str(&"a".repeat(100_000));
let value = fseq(&[fseq(&[
oid_of(oid::QC_PDS),
fseq(&[fseq(&[ia5(&url), printable("en")])]),
])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let limits = structured_asn1::Limits {
max_work: 8192,
..structured_asn1::Limits::default()
};
let parser = Parser::new(
structured_asn1::Encoding::Der,
limits,
crate::fixtures::memory(),
);
let extensions = parse(&parser, &der).unwrap();
let error = processing(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap_err(),
);
assert_eq!(
error.code(),
ErrorCode::Encoding(structured_asn1::ErrorCode::LimitExceeded(
structured_asn1::Limit::Work
))
);
}
#[test]
fn an_empty_notice_number_sequence_is_understood() {
use crate::fixtures::{ia5, seq as fseq};
let policy = structured_asn1::Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x03, 0x01]);
let reference = fseq(&[ia5("org"), fseq(&[])]);
let value = fseq(&[fseq(&[
oid_of(policy),
fseq(&[fseq(&[
oid_of(oid::USER_NOTICE_QUALIFIER),
fseq(&[reference]),
])]),
])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn a_scheme_recognized_qualified_statement_is_understood() {
use crate::fixtures::{octets, seq as fseq};
struct Statements(Oid<'static>);
impl Recognizer for Statements {
type Error = core::convert::Infallible;
fn recognizes(
&self,
_kind: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(false)
}
fn recognizes_statement(
&self,
kind: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(kind == self.0)
}
}
let national = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x07, 0x01]);
let value = fseq(&[fseq(&[oid_of(national), octets(&[0x01])])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&Statements(national), parser.memory())
.unwrap()
.len(),
0
);
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
1
);
}
#[test]
fn a_recognizer_reached_by_reference_answers_for_every_role() {
use crate::fixtures::{octets, seq as fseq};
struct Scheme(Oid<'static>);
impl 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(false)
}
fn recognizes_statement(
&self,
kind: Oid<'_>,
_execution: &structured_asn1::memory::Memory,
) -> core::result::Result<bool, Self::Error> {
Ok(kind == self.0)
}
}
let national = Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x07, 0x01]);
let value = fseq(&[fseq(&[oid_of(national), octets(&[0x01])])]);
let der = seq(&[extension(oid::QC_STATEMENTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let scheme = Scheme(national);
let by_reference: &dyn Recognizer<Error = core::convert::Infallible> = &scheme;
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&by_reference, parser.memory())
.unwrap()
.len(),
0
);
}
#[test]
fn the_display_text_scan_is_charged_to_the_work_budget() {
use crate::fixtures::{seq as fseq, utf8};
let policy = structured_asn1::Oid::from_bytes(&[0x2a, 0x86, 0x24, 0x03, 0x01]);
let text = utf8(&"a".repeat(200));
let notice = fseq(&[
oid_of(oid::USER_NOTICE_QUALIFIER),
fseq(core::slice::from_ref(&text)),
]);
let qualifiers: Vec<Vec<u8>> = core::iter::repeat_n(notice, 40).collect();
let value = fseq(&[fseq(&[oid_of(policy), fseq(&qualifiers)])]);
let der = seq(&[extension(oid::CERTIFICATE_POLICIES, true, &value)]);
let limits = structured_asn1::Limits {
max_work: 8192,
..structured_asn1::Limits::default()
};
let parser = Parser::new(
structured_asn1::Encoding::Der,
limits,
crate::fixtures::memory(),
);
let extensions = parse(&parser, &der).unwrap();
let error = processing(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap_err(),
);
assert_eq!(
error.code(),
ErrorCode::Encoding(structured_asn1::ErrorCode::LimitExceeded(
structured_asn1::Limit::Work
))
);
}
#[test]
fn a_path_length_past_any_path_does_not_block() {
use crate::fixtures::{boolean, seq as fseq};
let huge = tlv(0x02, &[0x01, 0x00, 0x00, 0x00, 0x00, 0x00]);
let value = fseq(&[boolean(true), huge]);
let der = seq(&[extension(oid::BASIC_CONSTRAINTS, true, &value)]);
let parser = Parser::der(crate::fixtures::memory());
let extensions = parse(&parser, &der).unwrap();
assert_eq!(
extensions
.unhandled_critical(&(), parser.memory())
.unwrap()
.len(),
0
);
}
fn processing(error: crate::error::CallbackError<core::convert::Infallible>) -> crate::Error {
match error {
crate::error::CallbackError::Processing(error) => error,
crate::error::CallbackError::Callback(never) => match never {},
}
}