use crate::error::{Error, ErrorCode, Located, Result};
use core::num::NonZeroUsize;
use structured_asn1::memory::{Memory, Work};
use structured_asn1::{Element, Encoding, Oid, Parser, Reader, Span, Tag, universal};
#[derive(Debug, Clone, Copy)]
pub struct AlgorithmIdentifier<'a> {
algorithm: Oid<'a>,
parameter_start: Option<NonZeroUsize>,
null_parameter: bool,
der: &'a [u8],
offset: u64,
field: &'static str,
parser: &'a Parser,
outer: u32,
encoding: Encoding,
indefinite: bool,
}
#[derive(Debug, Clone, Copy)]
struct AlgorithmFields<'a> {
algorithm: Oid<'a>,
parameter_start: Option<NonZeroUsize>,
null_parameter: bool,
der: &'a [u8],
offset: u64,
field: &'static str,
parser: &'a Parser,
outer: u32,
encoding: Encoding,
indefinite: bool,
}
#[derive(Debug)]
pub struct AlgorithmReading<'a> {
fields: AlgorithmFields<'a>,
fault: Option<Error>,
}
impl<'a> AlgorithmReading<'a> {
#[must_use]
pub fn algorithm(&self) -> Oid<'a> {
self.fields.algorithm
}
#[must_use]
pub fn parameters(&self) -> Option<&'a [u8]> {
parameter_bytes(
self.fields.der,
self.fields.parameter_start,
self.fields.indefinite,
)
}
#[must_use]
pub fn parameters_span(&self) -> Option<Span> {
let start = self.fields.parameter_start?.get();
Some(Span {
start: self.fields.offset + start as u64,
length: self.parameters()?.len() as u64,
})
}
#[must_use]
pub fn has_null_parameter(&self) -> bool {
self.fields.null_parameter
}
#[must_use]
pub fn as_bytes(&self) -> &'a [u8] {
self.fields.der
}
#[must_use]
pub fn offset(&self) -> u64 {
self.fields.offset
}
#[must_use]
pub fn encoding(&self) -> Encoding {
self.fields.encoding
}
#[must_use]
pub fn same_session(&self, parent: &Reader<'_>) -> bool {
core::ptr::eq(self.fields.parser, parent.parser())
}
#[must_use]
pub fn fault(&self) -> Option<&Error> {
self.fault.as_ref()
}
#[must_use]
pub fn identifier(&self) -> Option<AlgorithmIdentifier<'a>> {
if self.fault.is_some() {
return None;
}
Some(self.fields.identifier())
}
pub fn require_identifier(&self) -> Result<AlgorithmIdentifier<'a>> {
match &self.fault {
Some(error) => Err(error.clone()),
None => Ok(self.fields.identifier()),
}
}
pub fn same_written_in(&self, other: &Self, parent: &Reader<'_>) -> Result<bool> {
written_matches(&self.fields, &other.fields, parent, false)
}
fn into_identifier(self) -> Result<AlgorithmIdentifier<'a>> {
match self.fault {
Some(error) => Err(error),
None => Ok(self.fields.identifier()),
}
}
}
impl<'a> AlgorithmFields<'a> {
fn identifier(self) -> AlgorithmIdentifier<'a> {
AlgorithmIdentifier {
algorithm: self.algorithm,
parameter_start: self.parameter_start,
null_parameter: self.null_parameter,
der: self.der,
offset: self.offset,
field: self.field,
parser: self.parser,
outer: self.outer,
encoding: self.encoding,
indefinite: self.indefinite,
}
}
}
fn parameter_bytes(bytes: &[u8], start: Option<NonZeroUsize>, indefinite: bool) -> Option<&[u8]> {
let start = start?.get();
let end = bytes.len() - if indefinite { 2 } else { 0 };
Some(&bytes[start..end])
}
trait WrittenValue {
fn oid_bytes(&self) -> &[u8];
fn parameter_bytes(&self) -> Option<&[u8]>;
fn valid_null(&self) -> bool;
fn producer(&self) -> &Parser;
fn field(&self) -> &'static str;
fn offset(&self) -> u64;
}
impl WrittenValue for AlgorithmFields<'_> {
fn oid_bytes(&self) -> &[u8] {
self.algorithm.as_bytes()
}
fn parameter_bytes(&self) -> Option<&[u8]> {
parameter_bytes(self.der, self.parameter_start, self.indefinite)
}
fn valid_null(&self) -> bool {
self.null_parameter
}
fn producer(&self) -> &Parser {
self.parser
}
fn field(&self) -> &'static str {
self.field
}
fn offset(&self) -> u64 {
self.offset
}
}
impl WrittenValue for AlgorithmIdentifier<'_> {
fn oid_bytes(&self) -> &[u8] {
self.algorithm.as_bytes()
}
fn parameter_bytes(&self) -> Option<&[u8]> {
self.parameters()
}
fn valid_null(&self) -> bool {
self.null_parameter
}
fn producer(&self) -> &Parser {
self.parser
}
fn field(&self) -> &'static str {
self.field
}
fn offset(&self) -> u64 {
self.offset
}
}
fn require_written_parsers(
left: &impl WrittenValue,
right: &impl WrittenValue,
parent: &Reader<'_>,
) -> Result<()> {
if !core::ptr::eq(left.producer(), parent.parser()) {
return Err(Error::new(
ErrorCode::ForeignReader,
left.field(),
left.offset(),
));
}
if !core::ptr::eq(right.producer(), parent.parser()) {
return Err(Error::new(
ErrorCode::ForeignReader,
right.field(),
right.offset(),
));
}
Ok(())
}
fn written_matches(
left: &impl WrittenValue,
right: &impl WrittenValue,
parent: &Reader<'_>,
defaults: bool,
) -> Result<bool> {
require_written_parsers(left, right, parent)?;
written_values(left, right, parent, defaults)
}
fn written_values(
left: &impl WrittenValue,
right: &impl WrittenValue,
parent: &Reader<'_>,
defaults: bool,
) -> Result<bool> {
let charge = |amount| {
parent.charge(amount).map_err(|error| {
Error::new(
ErrorCode::Encoding(error.code()),
right.field(),
right.offset(),
)
})
};
charge(1)?;
let kind = left.oid_bytes();
if kind.len() != right.oid_bytes().len() {
return Ok(false);
}
charge(kind.len() as u64)?;
if kind != right.oid_bytes() {
return Ok(false);
}
if defaults || (left.valid_null() && right.valid_null()) {
return Ok(true);
}
let (Some(first), Some(second)) = (left.parameter_bytes(), right.parameter_bytes()) else {
return Ok(left.parameter_bytes().is_none() && right.parameter_bytes().is_none());
};
if first.len() != second.len() {
return Ok(false);
}
charge(first.len() as u64)?;
Ok(first == second)
}
impl<'a> AlgorithmIdentifier<'a> {
#[must_use]
pub fn has_null_parameter(self) -> bool {
self.null_parameter
}
pub fn same_written_in(self, other: &Self, parent: &Reader<'_>) -> Result<bool> {
self.value_matches(other, parent, false)
}
pub fn digest_matches_in(self, other: &Self, parent: &Reader<'_>) -> Result<bool> {
self.value_matches(other, parent, true)
}
fn value_matches(self, other: &Self, parent: &Reader<'_>, digest: bool) -> Result<bool> {
require_written_parsers(&self, other, parent)?;
let sha2 = if digest {
let known = self.validate_digest_params(parent)?;
other.validate_digest_params(parent)?;
known
} else {
false
};
written_values(&self, other, parent, sha2)
}
fn validate_digest_params(self, parent: &Reader<'_>) -> Result<bool> {
let charge = |amount| {
parent.charge(amount).map_err(|error| {
Error::new(ErrorCode::Encoding(error.code()), self.field, self.offset)
})
};
charge(1)?;
let kind = self.algorithm.as_bytes();
if kind.len() != 9 {
return Ok(false);
}
charge(9)?;
if kind[..8] != [0x60, 0x86, 0x48, 1, 0x65, 3, 4, 2] || kind[8] < 1 || kind[8] > 4 {
return Ok(false);
}
if self.parameter_start.is_some() && !self.null_parameter {
return Err(Error::new(
ErrorCode::MalformedValue,
self.field,
self.offset,
));
}
Ok(true)
}
#[must_use]
pub fn algorithm(self) -> Oid<'a> {
self.algorithm
}
#[must_use]
pub fn parameters(self) -> Option<&'a [u8]> {
parameter_bytes(self.der, self.parameter_start, self.indefinite)
}
#[must_use]
pub fn offset(self) -> u64 {
self.offset
}
#[must_use]
pub fn encoding(self) -> Encoding {
self.encoding
}
pub fn parameters_reader_in<'query>(
self,
parent: &Reader<'query>,
field: &'static str,
) -> Result<Option<Reader<'query>>>
where
'a: 'query,
{
if !self.same_session(parent) {
return Err(Error::new(ErrorCode::ForeignReader, field, self.offset));
}
let at = self
.parameter_start
.map_or(self.offset, |start| self.offset + start.get() as u64);
parent
.execution()
.charge_work(1)
.map_err(|reason| Error::resource(reason, field, at))?;
let Some(parameter) = self.parameters() else {
return Ok(None);
};
self.parser
.read_nested_query_in(parent, parameter, at, self.encoding, self.outer)
.field(field)
.map(Some)
}
#[must_use]
pub fn same_session(self, parent: &Reader<'_>) -> bool {
core::ptr::eq(self.parser, parent.parser())
}
#[must_use]
pub fn as_bytes(self) -> &'a [u8] {
self.der
}
#[must_use]
pub fn has_no_parameters(self) -> bool {
self.parameters()
.is_none_or(|parameters| parameters == [0x05, 0x00])
}
pub fn matches(self, other: &AlgorithmIdentifier<'_>, execution: &Memory) -> Result<bool> {
self.matches_with(other, |work| {
execution
.charge_work(work)
.map_err(|reason| Error::resource(reason, self.field, self.offset))
})
}
pub(crate) fn matches_in(
self,
other: &AlgorithmIdentifier<'_>,
parent: &Reader<'_>,
) -> Result<bool> {
self.matches_with(other, |work| {
parent.charge(work).map_err(|error| {
Error::new(ErrorCode::Encoding(error.code()), self.field, self.offset)
})
})
}
fn matches_with(
self,
other: &AlgorithmIdentifier<'_>,
mut charge: impl FnMut(u64) -> Result<()>,
) -> Result<bool> {
charge(1)?;
if self.algorithm.as_bytes().len() != other.algorithm.as_bytes().len() {
return Ok(false);
}
charge(self.algorithm.as_bytes().len() as u64)?;
if self.algorithm != other.algorithm {
return Ok(false);
}
if self.has_no_parameters() && other.has_no_parameters() {
return Ok(true);
}
let (Some(left), Some(right)) = (self.parameters(), other.parameters()) else {
return Ok(false);
};
if left.len() != right.len() {
return Ok(false);
}
charge(left.len() as u64)?;
Ok(left == right)
}
}
pub fn read<'a>(
parent: &Reader<'a>,
element: &Element<'a>,
field: &'static str,
) -> Result<AlgorithmIdentifier<'a>> {
read_tagged(
parent,
element,
Tag::constructed(universal::SEQUENCE),
field,
)
}
pub fn read_implicit<'a>(
parent: &Reader<'a>,
element: &Element<'a>,
number: u32,
field: &'static str,
) -> Result<AlgorithmIdentifier<'a>> {
read_tagged(parent, element, Tag::context(true, number), field)
}
pub fn read_inspection<'a>(
parent: &Reader<'a>,
element: &Element<'a>,
field: &'static str,
) -> Result<AlgorithmReading<'a>> {
read_fields::<true>(
parent,
element,
Tag::constructed(universal::SEQUENCE),
field,
)
}
pub fn read_implicit_inspection<'a>(
parent: &Reader<'a>,
element: &Element<'a>,
number: u32,
field: &'static str,
) -> Result<AlgorithmReading<'a>> {
read_fields::<true>(parent, element, Tag::context(true, number), field)
}
fn read_fields<'a, const INSPECTION: bool>(
parent: &Reader<'a>,
element: &Element<'a>,
expected: Tag,
field: &'static str,
) -> Result<AlgorithmReading<'a>> {
let fail = |code| Error::new(code, field, element.span().start);
if element.tag() != expected {
return Err(fail(ErrorCode::Encoding(
structured_asn1::ErrorCode::UnexpectedTag,
)));
}
let mut inside = parent.enter(element).field(field)?;
let algorithm = inside.read_oid().field(field)?;
let parameter = inside.next_element().field(field)?;
let mut fault = None;
let mut null_parameter = false;
if let Some(parameter) = ¶meter {
match crate::name::walk_der(&inside, parameter, field, crate::name::Strings::Checked) {
Ok(()) => {
null_parameter = parameter.tag() == Tag::primitive(universal::NULL);
}
Err(error) => {
if !INSPECTION || error.is_resource() || error.code() == ErrorCode::ForeignReader {
return Err(error);
}
fault = Some(error);
}
}
}
if !inside.is_empty() {
return Err(fail(ErrorCode::TrailingData));
}
let parameter_start = match parameter {
None => None,
Some(parameter) => {
let start = usize::try_from(parameter.span().start - element.span().start)
.map_err(|_| fail(ErrorCode::ValueTooLarge))?;
Some(NonZeroUsize::new(start).expect("the algorithm OID precedes its parameters"))
}
};
Ok(AlgorithmReading {
fields: AlgorithmFields {
algorithm,
parameter_start,
null_parameter,
der: element.as_bytes(),
offset: element.span().start,
field,
parser: parent.parser(),
outer: parent.depth(),
encoding: inside.encoding(),
indefinite: element.is_indefinite(),
},
fault,
})
}
fn read_tagged<'a>(
parent: &Reader<'a>,
element: &Element<'a>,
expected: Tag,
field: &'static str,
) -> Result<AlgorithmIdentifier<'a>> {
read_fields::<false>(parent, element, expected, field)?.into_identifier()
}
#[cfg(test)]
mod tests;