use super::{Mask, Number, Parameters};
use crate::algorithm::{AlgorithmIdentifier, digest};
use crate::{Error, ErrorCode, Result};
use structured_asn1::digest::Resolution;
use structured_asn1::memory::Work;
use structured_asn1::{Element, Parser};
const FIELD: &str = "algorithm.pss.profile";
#[derive(Debug, Clone, Copy)]
pub struct Profile<'a> {
parser: &'a Parser,
element: Element<'a>,
hash: digest::Algorithm,
mask_hash: digest::Algorithm,
salt: Number<'a>,
trailer: Number<'a>,
}
impl<'a> Profile<'a> {
#[must_use]
pub fn element(self) -> Element<'a> {
self.element
}
#[must_use]
pub fn hash(self) -> digest::Algorithm {
self.hash
}
#[must_use]
pub fn mask_hash(self) -> digest::Algorithm {
self.mask_hash
}
#[must_use]
pub fn salt(self) -> Number<'a> {
self.salt
}
#[must_use]
pub fn trailer(self) -> Number<'a> {
self.trailer
}
pub fn allows<W: Work>(&self, signature: &Self, execution: &W) -> Result<bool> {
self.charge(execution, 1, self.element.span().start)?;
signature.charge(execution, 1, signature.element.span().start)?;
if self.hash != signature.hash || self.mask_hash != signature.mask_hash {
return Ok(false);
}
let minimum = Salt::new(self.salt);
let used = Salt::new(signature.salt);
self.charge(
execution,
minimum.len() as u64 * 2 + 2,
number_at(self.salt, self.element),
)?;
signature.charge(
execution,
used.len() as u64 * 2 + 2,
number_at(signature.salt, signature.element),
)?;
let minimum = minimum.bytes();
let used = used.bytes();
if used.len() != minimum.len() {
return Ok(used.len() > minimum.len());
}
Ok(used >= minimum)
}
fn charge<W: Work>(&self, execution: &W, amount: u64, at: u64) -> Result<()> {
execution
.execution_for(self.parser)
.charge_work(amount)
.map_err(|reason| Error::resource(reason, FIELD, at))
}
}
impl<'a> Parameters<'a> {
pub fn profile<W: Work>(&self, execution: &W) -> Result<Resolution<Profile<'a>>> {
let query = execution.execution_for(self.parser);
query
.charge_work(1)
.map_err(|reason| Error::resource(reason, FIELD, self.element.span().start))?;
let hash = match self.hash {
None => digest::Algorithm::Sha1,
Some(written) => match read_hash(written.value(), execution)? {
Resolution::Supported(hash) => hash,
_ => return Ok(Resolution::Unsupported),
},
};
let mask_hash = match self.mask {
Mask::DefaultMgf1Sha1 => digest::Algorithm::Sha1,
Mask::Mgf1 { hash, .. } => match read_hash(hash, execution)? {
Resolution::Supported(hash) => hash,
_ => return Ok(Resolution::Unsupported),
},
Mask::Other(_) => return Ok(Resolution::Unsupported),
};
if let Number::Written(written) = self.trailer {
let at = written.element().span().start;
query
.charge_work(10)
.map_err(|reason| Error::resource(reason, FIELD, at))?;
if written.value().is_negative() {
return Err(Error::new(ErrorCode::MalformedValue, FIELD, at));
}
if written.value().as_u64() != Some(1) {
return Ok(Resolution::Unsupported);
}
}
Ok(Resolution::Supported(Profile {
parser: self.parser,
element: self.element,
hash,
mask_hash,
salt: self.salt,
trailer: self.trailer,
}))
}
}
pub fn key_allows<W: Work>(
key: AlgorithmIdentifier<'_>,
signature: &Profile<'_>,
execution: &W,
) -> Result<Resolution<bool>> {
let written = key.algorithm().as_bytes();
key.query_in(execution)
.charge_work(written.len() as u64 + 1)
.map_err(|reason| Error::resource(reason, FIELD, key.offset()))?;
signature.charge(execution, 1, signature.element.span().start)?;
match written {
[0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, last] => match last {
1 => {
if !key.has_null_parameter() {
return Err(Error::new(
ErrorCode::MalformedValue,
FIELD,
key.parameters_nested()
.map_or(key.offset(), super::super::Nested::offset),
));
}
return Ok(Resolution::Supported(true));
}
10 => {}
_ => return Ok(Resolution::Unsupported),
},
_ => return Ok(Resolution::Unsupported),
}
let Some(parameters) = super::read_known(key, execution)? else {
return Ok(Resolution::Supported(true));
};
match parameters.profile(execution)? {
Resolution::Supported(profile) => profile
.allows(signature, execution)
.map(Resolution::Supported),
_ => Ok(Resolution::Unsupported),
}
}
fn read_hash<W: Work>(
identifier: AlgorithmIdentifier<'_>,
execution: &W,
) -> Result<Resolution<digest::Algorithm>> {
match digest::algorithm(&identifier, execution)? {
Resolution::Malformed => Err(Error::new(
ErrorCode::MalformedValue,
FIELD,
identifier.offset(),
)),
value => Ok(value),
}
}
fn number_at(number: Number<'_>, enclosing: Element<'_>) -> u64 {
match number {
Number::Default(_) => enclosing.span().start,
Number::Written(value) => value.element().span().start,
}
}
enum Salt<'a> {
Written(&'a [u8]),
Default,
}
impl<'a> Salt<'a> {
fn new(number: Number<'a>) -> Self {
match number {
Number::Written(value) => Self::Written(value.value().as_bytes()),
Number::Default(value) => {
debug_assert_eq!(value, 20);
Self::Default
}
}
}
fn len(&self) -> usize {
match self {
Self::Written(bytes) => bytes.len(),
Self::Default => 1,
}
}
fn bytes(&self) -> &[u8] {
let bytes = match self {
Self::Written(bytes) => *bytes,
Self::Default => &[20],
};
let mut start = 0;
while start + 1 < bytes.len() && bytes[start] == 0 {
start += 1;
}
&bytes[start..]
}
}