use super::AlgorithmIdentifier;
use crate::error::{Error, ErrorCode, Located, Result};
use structured_asn1::memory::{OwnedVec, Work};
use structured_asn1::{Element, Integer, Oid, ScopedReader as Reader, Tag, universal};
use crate::oid::SHA1;
const MGF1: Oid<'static> = Oid::from_bytes(&[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, 8]);
const FIELD: &str = "algorithm.pss";
#[derive(Debug, Clone, Copy)]
pub struct Written<'a, T> {
element: Element<'a>,
value: T,
}
impl<'a, T: Copy> Written<'a, T> {
#[must_use]
pub fn element(self) -> Element<'a> {
self.element
}
#[must_use]
pub fn value(self) -> T {
self.value
}
}
#[derive(Debug, Clone, Copy)]
pub enum Number<'a> {
Default(u64),
Written(Written<'a, Integer<'a>>),
}
impl Number<'_> {
#[must_use]
pub fn as_u64(self) -> Option<u64> {
match self {
Self::Default(value) => Some(value),
Self::Written(value) => value.value.as_u64(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Mask<'a> {
DefaultMgf1Sha1,
Mgf1 {
function: Written<'a, AlgorithmIdentifier<'a>>,
hash: AlgorithmIdentifier<'a>,
},
Other(Written<'a, AlgorithmIdentifier<'a>>),
}
#[derive(Debug, Clone, Copy)]
pub struct Parameters<'a> {
element: Element<'a>,
hash: Option<Written<'a, AlgorithmIdentifier<'a>>>,
mask: Mask<'a>,
salt: Number<'a>,
trailer: Number<'a>,
}
impl<'a> Parameters<'a> {
#[must_use]
pub fn element(self) -> Element<'a> {
self.element
}
#[must_use]
pub fn hash(self) -> Option<Written<'a, AlgorithmIdentifier<'a>>> {
self.hash
}
#[must_use]
pub fn mask(self) -> Mask<'a> {
self.mask
}
#[must_use]
pub fn salt(self) -> Number<'a> {
self.salt
}
#[must_use]
pub fn trailer(self) -> Number<'a> {
self.trailer
}
}
#[derive(Debug)]
pub struct Reading<'a> {
parameters: Parameters<'a>,
faults: OwnedVec<Error>,
}
impl<'a> Reading<'a> {
#[must_use]
pub fn parameters(&self) -> Parameters<'a> {
self.parameters
}
#[must_use]
pub fn faults(&self) -> &[Error] {
&self.faults
}
}
pub fn read_in<'a>(
identifier: AlgorithmIdentifier<'a>,
parent: &Reader<'a, '_>,
) -> Result<Option<Parameters<'a>>> {
let execution = parent.execution();
let Some((reader, element)) = open(identifier, parent, &execution)? else {
return Ok(None);
};
decode(&reader, element, Err).map(Some)
}
pub fn inspect_in<'a>(
identifier: AlgorithmIdentifier<'a>,
parent: &Reader<'a, '_>,
) -> Result<Option<Reading<'a>>> {
let execution = parent.execution();
let Some((reader, element)) = open(identifier, parent, &execution)? else {
return Ok(None);
};
let mut faults = execution.vector();
let parameters = decode(&reader, element, |fault| {
let at = fault.offset();
let field = fault.field();
faults
.try_push(fault)
.map_err(|reason| Error::resource(reason, field, at))
})?;
Ok(Some(Reading {
parameters,
faults: faults.into_owned(),
}))
}
fn open<'a: 'operation, 'operation, W: Work>(
identifier: AlgorithmIdentifier<'a>,
parent: &Reader<'a, '_>,
execution: &'operation W,
) -> Result<Option<(Reader<'a, 'operation>, Element<'a>)>> {
if !identifier.same_session(parent) {
return Err(Error::new(
ErrorCode::ForeignReader,
FIELD,
identifier.offset(),
));
}
if !oid_is(
parent,
identifier.algorithm(),
crate::oid::RSASSA_PSS,
identifier.offset(),
)? {
return Err(Error::new(
ErrorCode::UnexpectedField,
FIELD,
identifier.offset(),
));
}
let value = open_parameter(identifier, parent, execution, FIELD)?;
if let Some((_, element)) = &value
&& element.tag() != Tag::constructed(universal::SEQUENCE)
{
return Err(Error::new(
ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
FIELD,
element.span().start,
));
}
Ok(value)
}
fn open_parameter<'a: 'operation, 'operation, W: Work>(
identifier: AlgorithmIdentifier<'a>,
parent: &Reader<'a, '_>,
execution: &'operation W,
field: &'static str,
) -> Result<Option<(Reader<'a, 'operation>, Element<'a>)>> {
charge(parent, 1, identifier.offset())?;
let Some(parameters) = identifier.parameters_nested() else {
return Ok(None);
};
parameters.open(execution, field).map(Some)
}
fn decode<'a>(
parent: &Reader<'a, '_>,
element: Element<'a>,
mut fault: impl FnMut(Error) -> Result<()>,
) -> Result<Parameters<'a>> {
let mut fields = parent.enter(&element).field(FIELD)?;
let mut parameters = Parameters {
element,
hash: None,
mask: Mask::DefaultMgf1Sha1,
salt: Number::Default(20),
trailer: Number::Default(1),
};
let mut previous = None;
while let Some(element) = fields.next_element().field(FIELD)? {
fields.charge(1).field(FIELD)?;
let Some(number) = element.tag().number() else {
return Err(Error::new(
ErrorCode::UnexpectedField,
FIELD,
element.span().start,
));
};
if number > 3
|| element.tag() != Tag::context(true, number)
|| previous.is_some_and(|last| number <= last)
{
return Err(Error::new(
ErrorCode::UnexpectedField,
FIELD,
element.span().start,
));
}
previous = Some(number);
let mut inside = fields.enter(&element).field(FIELD)?;
let value = inside
.next_element()
.field(FIELD)?
.ok_or_else(|| Error::new(ErrorCode::MissingField, FIELD, inside.offset()))?;
if !inside.is_empty() {
return Err(Error::new(ErrorCode::TrailingData, FIELD, inside.offset()));
}
let defaulted = match number {
0 => {
let hash = super::read(&inside, &value, "algorithm.pss.hash")?;
parameters.hash = Some(Written {
element,
value: hash,
});
default_hash(&inside, hash)?
}
1 => {
let (mask, defaulted) = read_mask(&inside, element, value)?;
parameters.mask = mask;
defaulted
}
_ => {
if value.tag() != Tag::primitive(universal::INTEGER) {
return Err(Error::new(
ErrorCode::Encoding(structured_asn1::ErrorCode::UnexpectedTag),
FIELD,
value.span().start,
));
}
let integer = inside.integer(&value).field(FIELD)?;
if number == 2 && integer.is_negative() {
return Err(Error::new(
ErrorCode::MalformedValue,
FIELD,
value.span().start,
));
}
charge(&inside, 9, value.span().start)?;
let written = Number::Written(Written {
element,
value: integer,
});
if number == 2 {
parameters.salt = written;
integer.as_u64() == Some(20)
} else {
parameters.trailer = written;
integer.as_u64() == Some(1)
}
}
};
if defaulted && fields.encoding().is_der() {
fault(Error::new(
ErrorCode::DefaultWritten,
FIELD,
element.span().start,
))?;
}
}
Ok(parameters)
}
fn default_hash(parent: &Reader<'_, '_>, identifier: AlgorithmIdentifier<'_>) -> Result<bool> {
Ok(
oid_is(parent, identifier.algorithm(), SHA1, identifier.offset())?
&& identifier.has_null_parameter(),
)
}
fn oid_is(parent: &Reader<'_, '_>, left: Oid<'_>, right: Oid<'_>, at: u64) -> Result<bool> {
charge(parent, 1, at)?;
if left.as_bytes().len() != right.as_bytes().len() {
return Ok(false);
}
charge(parent, left.as_bytes().len() as u64, at)?;
Ok(left == right)
}
fn charge(parent: &Reader<'_, '_>, amount: u64, at: u64) -> Result<()> {
parent
.charge(amount)
.map_err(|error| Error::new(ErrorCode::Encoding(error.code()), FIELD, at))
}
fn read_mask<'a>(
parent: &Reader<'a, '_>,
element: Element<'a>,
value: Element<'a>,
) -> Result<(Mask<'a>, bool)> {
let algorithm = super::read(parent, &value, "algorithm.pss.mask")?;
let function = Written {
element,
value: algorithm,
};
if !oid_is(parent, algorithm.algorithm(), MGF1, algorithm.offset())? {
return Ok((Mask::Other(function), false));
}
let execution = parent.execution();
let (reader, element) =
open_parameter(algorithm, parent, &execution, "algorithm.pss.mask.hash")?.ok_or_else(
|| {
Error::new(
ErrorCode::MissingField,
"algorithm.pss.mask.hash",
algorithm.offset(),
)
},
)?;
let hash = super::read(&reader, &element, "algorithm.pss.mask.hash")?;
Ok((Mask::Mgf1 { function, hash }, default_hash(&reader, hash)?))
}