use crate::error::{Error, ErrorCode, Located, Result};
use crate::extension::{self, Extension};
use crate::name::{self, Name};
use core::net::{Ipv4Addr, Ipv6Addr};
use structured_asn1::memory::{Memory, OwnedVec};
use structured_asn1::{Class, Element, Oid, ScopedReader as Reader, Tag, universal};
mod equality;
mod ldap;
mod uri;
#[derive(Debug)]
#[non_exhaustive]
pub enum GeneralName<'a> {
Other {
type_id: Oid<'a>,
value: &'a [u8],
},
Email(&'a str),
Dns(&'a str),
X400(&'a [u8]),
Directory(Name<'a>),
EdiParty(&'a [u8]),
Uri(&'a str),
IpAddress(&'a [u8]),
RegisteredId(Oid<'a>),
}
impl GeneralName<'_> {
pub fn matches(
&self,
other: &GeneralName<'_>,
execution: &Memory,
) -> core::result::Result<bool, ErrorCode> {
equality::matches(self, other, execution, None, 0, "generalName")
.map_err(|error| error.code())
}
}
pub fn matches(
parent: &Reader<'_, '_>,
left: &GeneralName<'_>,
right: &GeneralName<'_>,
at: u64,
field: &'static str,
) -> Result<bool> {
equality::matches(left, right, &parent.execution(), Some(parent), at, field)
}
pub(crate) fn matches_at(
left: &GeneralName<'_>,
right: &GeneralName<'_>,
execution: &impl structured_asn1::memory::Work,
at: u64,
field: &'static str,
) -> Result<bool> {
equality::matches(left, right, execution, None, at, field)
}
pub(crate) fn read_all<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<OwnedVec<GeneralName<'a>>> {
let mut inside = parent.enter(element).field(field)?;
read_list(&mut inside, field, element.span().start)
}
pub(crate) fn read_list<'a>(
inside: &mut Reader<'a, '_>,
field: &'static str,
offset: u64,
) -> Result<OwnedVec<GeneralName<'a>>> {
read_list_as(inside, field, offset, AddressForm::Single)
}
pub(crate) fn read_locations<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<OwnedVec<GeneralName<'a>>> {
let mut inside = parent.enter(element).field(field)?;
read_list_as(
&mut inside,
field,
element.span().start,
AddressForm::Location,
)
}
fn read_list_as<'a>(
inside: &mut Reader<'a, '_>,
field: &'static str,
offset: u64,
form: AddressForm,
) -> Result<OwnedVec<GeneralName<'a>>> {
let mut names = inside.memory().vector();
while let Some(entry) = inside.next_element().field(field)? {
let name = read_as(inside, &entry, field, form)?;
let work = inside.execution();
let Ok(mut scoped) = names.with_execution(&work) else {
unreachable!("the name decoder preserves its catalogue account")
};
scoped
.try_push(name)
.map_err(|reason| Error::resource(reason, field, entry.span().start))?;
names = scoped.into_owned();
}
if names.is_empty() {
return Err(Error::new(ErrorCode::EmptySequence, field, offset));
}
Ok(names)
}
pub fn read_alternative<'a, W: structured_asn1::memory::Work>(
extension: &Extension<'a>,
execution: &W,
) -> Result<OwnedVec<GeneralName<'a>>> {
let field = if extension.kind() == crate::oid::ISSUER_ALT_NAME {
"extensions.issuerAltName"
} else {
"extensions.subjectAltName"
};
let mut inside = extension::sequence(extension, field, execution)?;
read_list(&mut inside, field, extension.span().start)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum AddressForm {
Single,
Location,
WithMask,
}
impl AddressForm {
fn permits(self, length: usize) -> bool {
match self {
Self::Single | Self::Location => length == 4 || length == 16,
Self::WithMask => length == 8 || length == 32,
}
}
}
pub fn read<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<GeneralName<'a>> {
read_as(parent, element, field, AddressForm::Single)
}
fn mailbox_fits(text: &str, address_form: AddressForm) -> bool {
match address_form {
AddressForm::Single | AddressForm::Location => is_mailbox(text),
AddressForm::WithMask => {
is_mailbox(text) || is_domain(text) || text.strip_prefix('.').is_some_and(is_domain)
}
}
}
fn domain_fits(text: &str, address_form: AddressForm) -> bool {
match address_form {
AddressForm::Single | AddressForm::Location => is_domain(text),
AddressForm::WithMask => is_domain(text) || text.strip_prefix('.').is_some_and(is_domain),
}
}
pub(crate) fn read_as<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
address_form: AddressForm,
) -> Result<GeneralName<'a>> {
let fail = |code| Error::new(code, field, element.span().start);
let tag = element.tag();
if tag.class() != Class::ContextSpecific {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let Some(number) = tag.number() else {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
};
match (number, tag.is_constructed()) {
(0, true) => read_other(parent, element, field),
(1, false) => {
let email = text(parent, element.contents(), field, element)?;
if !mailbox_fits(email, address_form) {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(GeneralName::Email(email))
}
(2, false) => {
let domain = text(parent, element.contents(), field, element)?;
if !domain_fits(domain, address_form) {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(GeneralName::Dns(domain))
}
(3, true) => {
read_x400(parent, element, field)?;
Ok(GeneralName::X400(element.contents()))
}
(4, true) => {
let mut inside = parent.enter(element).field(field)?;
let inner = inside
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
let name = name::read(&inside, &inner, field)?;
if address_form != AddressForm::WithMask && name.is_empty() {
return Err(fail(ErrorCode::MalformedValue));
}
for attribute in name.attributes() {
if !name::well_formed_in(attribute, &inside, field)? {
return Err(fail(ErrorCode::MalformedValue));
}
}
if !inside.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
Ok(GeneralName::Directory(name))
}
(5, true) => {
read_edi_party(parent, element, field)?;
Ok(GeneralName::EdiParty(element.contents()))
}
(6, false) => {
let location = text(parent, element.contents(), field, element)?;
let fits = match address_form {
AddressForm::Single => {
parent.charge(location.len() as u64).field(field)?;
is_uri(location)
}
AddressForm::Location => {
location_uri(parent, element, location, field)?
}
AddressForm::WithMask => {
is_domain(location) || location.strip_prefix('.').is_some_and(is_domain)
}
};
if !fits {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(GeneralName::Uri(location))
}
(7, false) => {
let contents = element.contents();
if !address_form.permits(contents.len()) {
return Err(fail(ErrorCode::MalformedValue));
}
if address_form == AddressForm::WithMask
&& !is_contiguous_mask(&contents[contents.len() / 2..])
{
return Err(fail(ErrorCode::MalformedValue));
}
Ok(GeneralName::IpAddress(contents))
}
(8, false) => parent
.oid(element)
.field(field)
.map(GeneralName::RegisteredId),
_ => Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
))),
}
}
fn read_x400<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<()> {
let fail = |code| Error::new(code, field, element.span().start);
let mut inside = parent.enter(element).field(field)?;
let standard = inside
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
if standard.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
read_standard_attributes(&inside, &standard, field)?;
let mut next = inside.next_element().field(field)?;
if let Some(domain_defined) = next
&& domain_defined.tag() == Tag::constructed(universal::SEQUENCE)
{
read_domain_defined(&inside, &domain_defined, field)?;
next = inside.next_element().field(field)?;
}
if let Some(extensions) = next {
if extensions.tag() != Tag::constructed(universal::SET) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
read_extension_attributes(&inside, &extensions, field)?;
if !inside.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
}
Ok(())
}
const X400_NUMERIC_STRING: u32 = 18;
fn x400_string(element: &Element<'_>, field: &'static str, size: (usize, usize)) -> Result<()> {
let fail = |code| Error::new(code, field, element.span().start);
let contents = element.contents();
let fits = match (
element.tag().class(),
element.tag().number(),
element.tag().is_constructed(),
) {
(Class::Universal, Some(X400_NUMERIC_STRING), false) => crate::name::is_numeric(contents),
(Class::Universal, Some(universal::PRINTABLE_STRING), false) => {
crate::name::is_printable(contents)
}
_ => {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
};
if !fits || contents.len() < size.0 || contents.len() > size.1 {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(())
}
fn x400_printable(element: &Element<'_>, field: &'static str, size: (usize, usize)) -> Result<()> {
let fail = |code| Error::new(code, field, element.span().start);
let tag = element.tag();
if tag.class() != Class::Universal
|| tag.number() != Some(universal::PRINTABLE_STRING)
|| tag.is_constructed()
{
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let contents = element.contents();
if !crate::name::is_printable(contents) || contents.len() < size.0 || contents.len() > size.1 {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(())
}
#[derive(Clone, Copy)]
enum X400Repertoire {
Numeric,
Printable,
Teletex,
}
fn x400_implicit(
element: &Element<'_>,
field: &'static str,
repertoire: X400Repertoire,
most: usize,
) -> Result<()> {
let fail = |code| Error::new(code, field, element.span().start);
let contents = element.contents();
if contents.is_empty() || contents.len() > most {
return Err(fail(ErrorCode::MalformedValue));
}
let fits = match repertoire {
X400Repertoire::Numeric => crate::name::is_numeric(contents),
X400Repertoire::Printable => crate::name::is_printable(contents),
X400Repertoire::Teletex => true,
};
if !fits {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(())
}
fn x400_teletex(element: &Element<'_>, field: &'static str, size: (usize, usize)) -> Result<()> {
let fail = |code| Error::new(code, field, element.span().start);
if element.tag() != Tag::primitive(20) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let length = element.contents().len();
if length < size.0 || length > size.1 {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(())
}
fn x400_bare_country(value: &Element<'_>, field: &'static str) -> Result<()> {
let fail = |code| Error::new(code, field, value.span().start);
let contents = value.contents();
let fits = match (
value.tag().class(),
value.tag().number(),
value.tag().is_constructed(),
) {
(Class::Universal, Some(X400_NUMERIC_STRING), false) => {
contents.len() == 3 && contents.iter().all(u8::is_ascii_digit)
}
(Class::Universal, Some(universal::PRINTABLE_STRING), false) => {
contents.len() == 2 && contents.iter().all(u8::is_ascii_uppercase)
}
_ => {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
};
if !fits {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(())
}
fn x400_country<'a>(
parent: &Reader<'a, '_>,
wrapper: &Element<'a>,
field: &'static str,
) -> Result<()> {
let fail = |code| Error::new(code, field, wrapper.span().start);
let mut inside = parent.enter(wrapper).field(field)?;
let value = inside
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
if !inside.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
x400_bare_country(&value, field)
}
fn x400_choice<'a>(
parent: &Reader<'a, '_>,
wrapper: &Element<'a>,
field: &'static str,
numeric: (usize, usize),
printable: (usize, usize),
) -> Result<()> {
let fail = |code| Error::new(code, field, wrapper.span().start);
let mut inside = parent.enter(wrapper).field(field)?;
let value = inside
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
if !inside.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
let size = match (value.tag().class(), value.tag().number()) {
(Class::Universal, Some(X400_NUMERIC_STRING)) => numeric,
(Class::Universal, Some(universal::PRINTABLE_STRING)) => printable,
_ => {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
};
x400_string(&value, field, size)
}
fn read_standard_attributes<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<()> {
let mut inside = parent.enter(element).field(field)?;
let mut last_rank = -1i8;
while let Some(member) = inside.next_element().field(field)? {
let bad = |code| Error::new(code, field, member.span().start);
let tag = member.tag();
let Some(number) = tag.number() else {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
};
let rank: i8 = match (tag.class(), number, tag.is_constructed()) {
(Class::Application, 1, true) => {
x400_country(&inside, &member, field)?;
0
}
(Class::Application, 2, true) => {
x400_choice(&inside, &member, field, (0, 16), (0, 16))?;
1
}
(Class::ContextSpecific, 0, false) => {
x400_implicit(&member, field, X400Repertoire::Numeric, 16)?;
2
}
(Class::ContextSpecific, 1, false) => {
x400_implicit(&member, field, X400Repertoire::Printable, 24)?;
3
}
(Class::ContextSpecific, 2, true) => {
x400_choice(&inside, &member, field, (1, 16), (1, 16))?;
4
}
(Class::ContextSpecific, 3, false) => {
x400_implicit(&member, field, X400Repertoire::Printable, 64)?;
5
}
(Class::ContextSpecific, 4, false) => {
x400_implicit(&member, field, X400Repertoire::Numeric, 32)?;
6
}
(Class::ContextSpecific, 5, true) => {
read_personal_name(&inside, &member, field, X400Repertoire::Printable)?;
7
}
(Class::ContextSpecific, 6, true) => {
let mut units = inside.enter(&member).field(field)?;
let mut count = 0usize;
while let Some(unit) = units.next_element().field(field)? {
x400_printable(&unit, field, (1, 32))?;
count += 1;
}
if !(1..=4).contains(&count) {
return Err(bad(ErrorCode::MalformedValue));
}
8
}
_ => {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
};
if rank <= last_rank {
return Err(bad(ErrorCode::UnexpectedField));
}
last_rank = rank;
}
Ok(())
}
fn read_personal_name<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
repertoire: X400Repertoire,
) -> Result<()> {
let fail = |code| Error::new(code, field, element.span().start);
let mut inside = parent.enter(element).field(field)?;
let mut seen = [false; 4];
let mut last: Option<usize> = None;
while let Some(part) = inside.next_element().field(field)? {
let bad = |code| Error::new(code, field, part.span().start);
let (number, most) = match (
part.tag().class(),
part.tag().number(),
part.tag().is_constructed(),
) {
(Class::ContextSpecific, Some(0), false) => (0usize, 40),
(Class::ContextSpecific, Some(1), false) => (1, 16),
(Class::ContextSpecific, Some(2), false) => (2, 5),
(Class::ContextSpecific, Some(3), false) => (3, 3),
_ => {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
};
if seen[number] {
return Err(bad(ErrorCode::UnexpectedField));
}
if inside.encoding().is_der() && last.is_some_and(|last| number < last) {
return Err(bad(ErrorCode::UnexpectedField));
}
seen[number] = true;
last = Some(number);
x400_implicit(&part, field, repertoire, most)?;
}
if !seen[0] {
return Err(fail(ErrorCode::MissingField));
}
Ok(())
}
fn read_domain_defined<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<()> {
read_defined_pairs(parent, element, field, false)
}
fn read_defined_pairs<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
teletex: bool,
) -> Result<()> {
let fail = |code| Error::new(code, field, element.span().start);
if element.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let held_string = |element: &Element<'_>, size: (usize, usize)| {
if teletex {
x400_teletex(element, field, size)
} else {
x400_printable(element, field, size)
}
};
let mut inside = parent.enter(element).field(field)?;
let mut count = 0usize;
while let Some(pair) = inside.next_element().field(field)? {
let bad = |code| Error::new(code, field, pair.span().start);
if pair.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut two = inside.enter(&pair).field(field)?;
let kind = two
.next_element()
.field(field)?
.ok_or_else(|| bad(ErrorCode::MissingField))?;
held_string(&kind, (1, 8))?;
let value = two
.next_element()
.field(field)?
.ok_or_else(|| bad(ErrorCode::MissingField))?;
held_string(&value, (1, 128))?;
if !two.is_empty() {
return Err(bad(ErrorCode::TrailingData));
}
count += 1;
}
if !(1..=4).contains(&count) {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(())
}
fn read_extension_attributes<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<()> {
let fail = |code| Error::new(code, field, element.span().start);
let mut inside = parent.enter(element).field(field)?;
let mut count = 0usize;
let mut previous: Option<&[u8]> = None;
while let Some(entry) = inside.next_element().field(field)? {
let bad = |code| Error::new(code, field, entry.span().start);
if entry.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
if inside.encoding().is_der()
&& let Some(previous) = previous
{
inside
.charge(previous.len().max(entry.as_bytes().len()) as u64)
.field(field)?;
if entry.as_bytes() < previous {
return Err(bad(ErrorCode::UnexpectedField));
}
}
previous = Some(entry.as_bytes());
let mut two = inside.enter(&entry).field(field)?;
let kind = two
.next_element()
.field(field)?
.ok_or_else(|| bad(ErrorCode::MissingField))?;
if extension::context_of(kind.tag()) != Some((0, false)) {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let number = two.integer(&kind).field(field)?;
if number.as_u32().is_none_or(|number| number > 256) {
return Err(bad(ErrorCode::MalformedValue));
}
let value = two
.next_element()
.field(field)?
.ok_or_else(|| bad(ErrorCode::MissingField))?;
if extension::context_of(value.tag()) != Some((1, true)) {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut held = two.enter(&value).field(field)?;
let Some(declared) = held.next_element().field(field)? else {
return Err(bad(ErrorCode::MissingField));
};
let number = number
.as_u32()
.expect("bounded to 0..=256 above, which fits");
read_extension_value(&held, &declared, number, field)?;
if !held.is_empty() || !two.is_empty() {
return Err(bad(ErrorCode::TrailingData));
}
count += 1;
if count > 256 {
return Err(bad(ErrorCode::MalformedValue));
}
}
if count == 0 {
return Err(fail(ErrorCode::EmptySequence));
}
Ok(())
}
fn read_extension_value<'a>(
parent: &Reader<'a, '_>,
value: &Element<'a>,
number: u32,
field: &'static str,
) -> Result<()> {
let fail = |code| Error::new(code, field, value.span().start);
match number {
1 => x400_printable(value, field, (1, 64)),
2 | 3 => x400_teletex(value, field, (1, 64)),
4 => {
if value.tag() != Tag::constructed(universal::SET) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
read_personal_name(parent, value, field, X400Repertoire::Teletex)
}
5 => {
if value.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut units = parent.enter(value).field(field)?;
let mut count = 0usize;
while let Some(unit) = units.next_element().field(field)? {
x400_teletex(&unit, field, (1, 32))?;
count += 1;
}
if !(1..=4).contains(&count) {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(())
}
6 => read_defined_pairs(parent, value, field, true),
7 => x400_printable(value, field, (1, 16)),
8 => x400_bare_country(value, field),
9 => x400_bare_choice(value, field, (1, 16), (1, 16)),
10..=15 | 17..=21 => read_pds_parameter(parent, value, field, 30),
16 => read_unformatted_address(parent, value, field),
22 => read_extended_network_address(parent, value, field),
23 => {
if value.tag() != Tag::primitive(universal::INTEGER) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let option = parent.integer(value).field(field)?;
if option.as_u32().is_none_or(|option| option > 256) {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(())
}
_ => Err(fail(ErrorCode::MalformedValue)),
}
}
fn x400_bare_choice(
value: &Element<'_>,
field: &'static str,
numeric: (usize, usize),
printable: (usize, usize),
) -> Result<()> {
let fail = |code| Error::new(code, field, value.span().start);
let size = match (
value.tag().class(),
value.tag().number(),
value.tag().is_constructed(),
) {
(Class::Universal, Some(X400_NUMERIC_STRING), false) => numeric,
(Class::Universal, Some(universal::PRINTABLE_STRING), false) => printable,
_ => {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
};
x400_string(value, field, size)
}
fn read_pds_parameter<'a>(
parent: &Reader<'a, '_>,
value: &Element<'a>,
field: &'static str,
most: usize,
) -> Result<()> {
let fail = |code| Error::new(code, field, value.span().start);
if value.tag() != Tag::constructed(universal::SET) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut inside = parent.enter(value).field(field)?;
let mut seen_printable = false;
let mut seen_teletex = false;
while let Some(member) = inside.next_element().field(field)? {
let bad = |code| Error::new(code, field, member.span().start);
if member.tag() == Tag::primitive(universal::PRINTABLE_STRING) {
if seen_printable || (seen_teletex && inside.encoding().is_der()) {
return Err(bad(ErrorCode::UnexpectedField));
}
seen_printable = true;
x400_string(&member, field, (1, most))?;
} else if member.tag() == Tag::primitive(20) {
if seen_teletex {
return Err(bad(ErrorCode::UnexpectedField));
}
seen_teletex = true;
x400_teletex(&member, field, (1, most))?;
} else {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
}
Ok(())
}
fn read_unformatted_address<'a>(
parent: &Reader<'a, '_>,
value: &Element<'a>,
field: &'static str,
) -> Result<()> {
let fail = |code| Error::new(code, field, value.span().start);
if value.tag() != Tag::constructed(universal::SET) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut inside = parent.enter(value).field(field)?;
let mut seen_lines = false;
let mut seen_teletex = false;
while let Some(member) = inside.next_element().field(field)? {
let bad = |code| Error::new(code, field, member.span().start);
if member.tag() == Tag::constructed(universal::SEQUENCE) {
if seen_lines || (seen_teletex && inside.encoding().is_der()) {
return Err(bad(ErrorCode::UnexpectedField));
}
seen_lines = true;
let mut lines = inside.enter(&member).field(field)?;
let mut count = 0usize;
while let Some(line) = lines.next_element().field(field)? {
x400_printable(&line, field, (1, 30))?;
count += 1;
}
if !(1..=6).contains(&count) {
return Err(bad(ErrorCode::MalformedValue));
}
} else if member.tag() == Tag::primitive(20) {
if seen_teletex {
return Err(bad(ErrorCode::UnexpectedField));
}
seen_teletex = true;
x400_teletex(&member, field, (1, 180))?;
} else {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
}
Ok(())
}
fn read_e163_address<'a>(
parent: &Reader<'a, '_>,
value: &Element<'a>,
field: &'static str,
) -> Result<()> {
let fail = |code| Error::new(code, field, value.span().start);
let mut inside = parent.enter(value).field(field)?;
let number = inside
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
if extension::context_of(number.tag()) != Some((0, false)) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
x400_implicit(&number, field, X400Repertoire::Numeric, 15)?;
if let Some(sub) = inside.next_element().field(field)? {
if extension::context_of(sub.tag()) != Some((1, false)) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
x400_implicit(&sub, field, X400Repertoire::Numeric, 40)?;
}
if !inside.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
Ok(())
}
fn read_extended_network_address<'a>(
parent: &Reader<'a, '_>,
value: &Element<'a>,
field: &'static str,
) -> Result<()> {
let fail = |code| Error::new(code, field, value.span().start);
if value.tag() == Tag::constructed(universal::SEQUENCE) {
read_e163_address(parent, value, field)
} else if extension::context_of(value.tag()) == Some((0, true)) {
let mut inside = parent.enter(value).field(field)?;
let mut last: i8 = -1;
let mut saw_network = false;
while let Some(member) = inside.next_element().field(field)? {
let bad = |code| Error::new(code, field, member.span().start);
let Some((number, true)) = extension::context_of(member.tag()) else {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
};
let rank = i8::try_from(number).map_err(|_| {
bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
))
})?;
if rank > 3 {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
if rank <= last {
return Err(bad(ErrorCode::UnexpectedField));
}
last = rank;
let mut held = inside.enter(&member).field(field)?;
if rank == 3 {
let addresses = held
.next_element()
.field(field)?
.ok_or_else(|| bad(ErrorCode::MissingField))?;
if addresses.tag() != Tag::constructed(universal::SET) {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut each = held.enter(&addresses).field(field)?;
let mut count = 0usize;
let mut previous: Option<&[u8]> = None;
while let Some(address) = each.next_element().field(field)? {
if address.tag() != Tag::primitive(universal::OCTET_STRING) {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
if each.encoding().is_der()
&& let Some(previous) = previous
{
each.charge(previous.len().max(address.as_bytes().len()) as u64)
.field(field)?;
if address.as_bytes() < previous {
return Err(bad(ErrorCode::UnexpectedField));
}
}
previous = Some(address.as_bytes());
count += 1;
}
if count == 0 {
return Err(bad(ErrorCode::EmptySequence));
}
saw_network = true;
} else {
let selector = held
.next_element()
.field(field)?
.ok_or_else(|| bad(ErrorCode::MissingField))?;
if selector.tag() != Tag::primitive(universal::OCTET_STRING) {
return Err(bad(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
}
if !held.is_empty() {
return Err(bad(ErrorCode::TrailingData));
}
}
if !saw_network {
return Err(fail(ErrorCode::MissingField));
}
Ok(())
} else {
Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)))
}
}
fn read_edi_party<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<()> {
let fail = |code| Error::new(code, field, element.span().start);
let mut inside = parent.enter(element).field(field)?;
let mut next = inside.next_element().field(field)?;
if let Some(assigner) = next
&& extension::context_of(assigner.tag()) == Some((0, true))
{
require_directory_string(&inside, &assigner, field)?;
next = inside.next_element().field(field)?;
}
let party = next.ok_or_else(|| fail(ErrorCode::MissingField))?;
if extension::context_of(party.tag()) != Some((1, true)) {
return Err(Error::new(
ErrorCode::UnexpectedField,
field,
party.span().start,
));
}
require_directory_string(&inside, &party, field)?;
if !inside.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
Ok(())
}
fn require_directory_string<'a>(
parent: &Reader<'a, '_>,
wrapper: &Element<'a>,
field: &'static str,
) -> Result<()> {
const MOST: usize = 32768;
let fail = |code| Error::new(code, field, wrapper.span().start);
let mut inside = parent.enter(wrapper).field(field)?;
let value = inside
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
if !inside.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
let tag = value.tag();
let is_directory_string = tag.class() == Class::Universal
&& !tag.is_constructed()
&& matches!(tag.number(), Some(12 | 19 | 20 | 28 | 30));
let ceiling = match tag.number() {
Some(12 | 28) => MOST.saturating_mul(4),
Some(30) => MOST.saturating_mul(2),
_ => MOST,
};
if value.contents().len() > ceiling {
return Err(fail(ErrorCode::MalformedValue));
}
parent.charge(value.contents().len() as u64).field(field)?;
let decodes = match tag.number() {
Some(12) => {
core::str::from_utf8(value.contents()).is_ok_and(|text| text.chars().count() <= MOST)
}
Some(19) => crate::name::is_printable(value.contents()) && value.contents().len() <= MOST,
Some(28) => {
crate::name::wide_char_count(value.contents(), 4).is_some_and(|count| count <= MOST)
}
Some(30) => {
crate::name::wide_char_count(value.contents(), 2).is_some_and(|count| count <= MOST)
}
_ => value.contents().len() <= MOST,
};
if !is_directory_string || value.contents().is_empty() || !decodes {
return Err(fail(ErrorCode::MalformedValue));
}
Ok(())
}
fn read_other<'a>(
parent: &Reader<'a, '_>,
element: &Element<'a>,
field: &'static str,
) -> Result<GeneralName<'a>> {
let fail = |code| Error::new(code, field, element.span().start);
let mut inside = parent.enter(element).field(field)?;
let type_id = inside.read_oid().field(field)?;
let wrapper = inside
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
if wrapper.tag() != Tag::context(true, 0) {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
if !inside.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
let mut held = inside.enter(&wrapper).field(field)?;
let value = held
.next_element()
.field(field)?
.ok_or_else(|| fail(ErrorCode::MissingField))?;
if !held.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
crate::name::walk_der(&held, &value, field, crate::name::Strings::Checked)?;
Ok(GeneralName::Other {
type_id,
value: value.as_bytes(),
})
}
pub(crate) fn is_domain(text: &str) -> bool {
text.len() <= 253
&& text.split('.').all(|label| {
(1..=63).contains(&label.len())
&& label
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'-')
&& !label.starts_with('-')
&& !label.ends_with('-')
})
&& text
.rsplit('.')
.next()
.is_some_and(|top| top.bytes().any(|byte| byte.is_ascii_alphabetic()))
}
pub(crate) fn is_mailbox(text: &str) -> bool {
structured_email_address::is_smtp_mailbox(text)
}
pub(crate) fn is_absolute_uri(text: &str) -> bool {
absolute_uri::<true>(text)
}
fn absolute_uri<const CERTIFICATE_NAME: bool>(text: &str) -> bool {
let Some((scheme, rest)) = text.split_once(':') else {
return false;
};
let mut bytes = scheme.bytes();
if !(bytes.next().is_some_and(|byte| byte.is_ascii_alphabetic())
&& bytes.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'-' | b'.')))
{
return false;
}
if CERTIFICATE_NAME && rest.is_empty() {
return false;
}
let mut userinfo = "";
let mut bare_host = "";
let mut tail = rest;
if let Some(after) = rest.strip_prefix("//") {
let end = after.find(['/', '?']).unwrap_or(after.len());
let authority = &after[..end];
tail = &after[end..];
let (before, host_port) = match authority.split_once('@') {
Some((before, host_port)) => (before, host_port),
None => ("", authority),
};
if host_port.contains('@') {
return false;
}
userinfo = before;
let port = if let Some(inside) = host_port.strip_prefix('[') {
let Some((literal, past)) = inside.split_once(']') else {
return false;
};
if !is_ip_literal(literal) {
return false;
}
if past.is_empty() {
None
} else {
let Some(digits) = past.strip_prefix(':') else {
return false;
};
Some(digits)
}
} else {
let port = if let Some((host, port)) = host_port.split_once(':') {
bare_host = host;
Some(port)
} else {
bare_host = host_port;
None
};
let named = bare_host.strip_suffix('.').unwrap_or(bare_host);
if CERTIFICATE_NAME && !is_domain(named) && bare_host.parse::<Ipv4Addr>().is_err() {
return false;
}
port
};
if port.is_some_and(|digits| !digits.bytes().all(|byte| byte.is_ascii_digit())) {
return false;
}
}
uri_alphabet(userinfo) && uri_alphabet(bare_host) && uri_alphabet(tail)
}
fn uri_alphabet(segment: &str) -> bool {
let mut bytes = segment.bytes();
while let Some(byte) = bytes.next() {
let permitted = byte.is_ascii_alphanumeric()
|| matches!(
byte,
b'-' | b'.'
| b'_'
| b'~'
| b':'
| b'/'
| b'?'
| b'@'
| b'!'
| b'$'
| b'&'
| b'\''
| b'('
| b')'
| b'*'
| b'+'
| b','
| b';'
| b'='
);
if byte == b'%' {
let mut pair = [bytes.next(), bytes.next()].into_iter();
if !pair.all(|digit| digit.is_some_and(|digit| digit.is_ascii_hexdigit())) {
return false;
}
} else if !permitted {
return false;
}
}
true
}
pub(crate) fn is_uri(text: &str) -> bool {
uri::<true>(text)
}
pub(crate) fn is_cps_uri(text: &str) -> bool {
uri::<false>(text)
}
fn location_uri(
parent: &Reader<'_, '_>,
element: &Element<'_>,
text: &str,
field: &'static str,
) -> Result<bool> {
let work = u64::try_from(text.len())
.ok()
.and_then(|n| n.checked_mul(32))
.and_then(|n| n.checked_add(256))
.ok_or_else(|| {
Error::resource(
structured_asn1::memory::MemoryError::WorkExceeded,
field,
element.span().start,
)
})?;
parent.charge(work).field(field)?;
if !is_cps_uri(text) {
return Ok(false);
}
if text
.split_once(':')
.is_some_and(|(scheme, _)| scheme.eq_ignore_ascii_case("ldap"))
{
return ldap::valid(parent, element, text, field);
}
Ok(known_location_authority_fits(text))
}
fn known_location_authority_fits(text: &str) -> bool {
let (scheme, rest) = text.split_once(':').expect("URI syntax checked");
let ftp = scheme.eq_ignore_ascii_case("ftp");
if !ftp && !scheme.eq_ignore_ascii_case("http") && !scheme.eq_ignore_ascii_case("https") {
return true;
}
let Some(after) = rest.strip_prefix("//") else {
return false;
};
let end = after.find(['/', '?', '#']).unwrap_or(after.len());
let authority = &after[..end];
let host_port = authority
.rsplit_once('@')
.map_or(authority, |(_, host)| host);
if host_port.starts_with('[') {
return true;
}
let host = host_port
.split_once(':')
.map_or(host_port, |(host, _)| host);
if host.is_empty() {
return false;
}
!ftp || is_domain(host.strip_suffix('.').unwrap_or(host)) || host.parse::<Ipv4Addr>().is_ok()
}
fn uri<const CERTIFICATE_NAME: bool>(text: &str) -> bool {
match text.split_once('#') {
Some((rest, fragment)) => absolute_uri::<CERTIFICATE_NAME>(rest) && uri_alphabet(fragment),
None => absolute_uri::<CERTIFICATE_NAME>(text),
}
}
fn is_ip_literal(text: &str) -> bool {
if let Some(rest) = text.strip_prefix(['v', 'V']) {
let Some((version, suffix)) = rest.split_once('.') else {
return false;
};
return !version.is_empty()
&& version.bytes().all(|byte| byte.is_ascii_hexdigit())
&& !suffix.is_empty()
&& suffix.bytes().all(|byte| {
byte.is_ascii_alphanumeric()
|| matches!(
byte,
b'-' | b'.'
| b'_'
| b'~'
| b':'
| b'!'
| b'$'
| b'&'
| b'\''
| b'('
| b')'
| b'*'
| b'+'
| b','
| b';'
| b'='
)
});
}
text.parse::<Ipv6Addr>().is_ok()
}
fn is_contiguous_mask(mask: &[u8]) -> bool {
let mut still_ones = true;
for &byte in mask {
if still_ones {
if byte == 0xff {
continue;
}
let ones = byte.leading_ones();
let expected = if ones == 0 { 0 } else { 0xffu8 << (8 - ones) };
if byte != expected {
return false;
}
still_ones = false;
} else if byte != 0 {
return false;
}
}
true
}
fn text<'a>(
parent: &Reader<'a, '_>,
contents: &'a [u8],
field: &'static str,
element: &Element<'a>,
) -> Result<&'a str> {
parent.charge(contents.len() as u64).field(field)?;
if contents.is_empty() {
return Err(Error::new(
ErrorCode::MalformedValue,
field,
element.span().start,
));
}
if !contents.is_ascii() {
return Err(Error::new(
ErrorCode::MalformedValue,
field,
element.span().start,
));
}
core::str::from_utf8(contents)
.map_err(|_| Error::new(ErrorCode::MalformedValue, field, element.span().start))
}
#[cfg(test)]
mod tests;