use crate::attribute::{Attribute, Attributes};
use crate::signer::SignerInfo;
use crate::{Error, ErrorCode, Octets, Result, oid};
use structured_asn1::memory::Work;
use structured_asn1::{Oid, Reader, Span, Tag, universal};
use structured_pkix::AlgorithmIdentifier;
#[derive(Debug)]
pub enum DigestBinding {
Matches,
Differs,
Malformed(Error),
}
#[derive(Debug)]
pub enum BindError<E> {
Processing(Error),
Callback(E),
OutputWidth {
expected: usize,
actual: usize,
},
}
pub fn matches_digest<'a, P: crate::provider::DigestProvider<'a>, W: Work>(
provider: &P,
digest: crate::provider::Prehash<'_, '_, P, P::Identity>,
expected: &[u8],
execution: &W,
field: &'static str,
span: Span,
) -> core::result::Result<DigestBinding, BindError<P::Error>> {
crate::provider::require_instance(provider, field, span.start)
.map_err(BindError::Processing)?;
if !digest.from_provider(provider) {
return Err(BindError::Processing(Error::schema(
ErrorCode::ProviderMismatch,
field,
span.start,
)));
}
execution
.charge_work(1)
.map_err(|reason| BindError::Processing(Error::resource(reason, field, span.start)))?;
let actual = digest.as_bytes().len();
if let Some(width) = provider.digest_width(digest.identity())
&& width != actual
{
return Err(BindError::OutputWidth {
expected: width,
actual,
});
}
if expected.len() != actual {
return Ok(DigestBinding::Malformed(Error::schema(
ErrorCode::DigestWidth,
field,
span.start,
)));
}
if provider
.matches_digest(digest, expected, execution)
.map_err(BindError::Callback)?
{
Ok(DigestBinding::Matches)
} else {
Ok(DigestBinding::Differs)
}
}
#[derive(Debug, Clone, Copy)]
pub enum Scope<'a> {
Content(Oid<'a>),
CounterSignature,
}
#[derive(Debug, Clone, Copy)]
pub struct Protection<'a> {
digest: AlgorithmIdentifier<'a>,
signature: AlgorithmIdentifier<'a>,
}
impl<'a> Protection<'a> {
#[must_use]
pub fn digest(&self) -> AlgorithmIdentifier<'a> {
self.digest
}
#[must_use]
pub fn signature(&self) -> AlgorithmIdentifier<'a> {
self.signature
}
}
#[derive(Debug)]
pub enum Binding<'a> {
Direct,
Attributes {
digest: &'a [u8],
digest_span: Span,
protection: Option<Protection<'a>>,
},
}
pub fn inspect<'a>(
signer: &SignerInfo<'a>,
parent: &Reader<'a>,
scope: Scope<'a>,
) -> Result<Binding<'a>> {
signer.require_session(parent, "signerInfo")?;
for attributes in [signer.signed_attributes(), signer.unsigned_attributes()]
.into_iter()
.flatten()
{
if let Some(fault) = attributes.fault() {
return Err(fault);
}
}
check_placements(signer, parent)?;
let Some(attributes) = signer.signed_attributes() else {
if let Scope::Content(kind) = scope {
parent
.execution()
.charge_work(kind.as_bytes().len() as u64)
.map_err(|reason| {
Error::resource(reason, "signerInfo.signedAttrs", signer.span().start)
})?;
if kind != oid::DATA {
return Err(Error::schema(
ErrorCode::MissingField,
"signerInfo.signedAttrs",
signer.span().start,
));
}
}
return Ok(Binding::Direct);
};
let content_type = attributes.unique_in(oid::CONTENT_TYPE, parent)?;
match scope {
Scope::CounterSignature => {
if let Some(attribute) = content_type {
return Err(Error::schema(
ErrorCode::AttributePlacement,
"signerInfo.signedAttrs.contentType",
attribute.span().start,
));
}
}
Scope::Content(expected) => {
let attribute = content_type.ok_or_else(|| {
Error::schema(
ErrorCode::MissingField,
"signerInfo.signedAttrs.contentType",
attributes.element().span().start,
)
})?;
attribute.single("signerInfo.signedAttrs.contentType")?;
let mut reader = value_reader(attribute, parent, "signerInfo.signedAttrs.contentType")?;
let declared = reader
.read_oid()
.map_err(|error| Error::encoding(error, "signerInfo.signedAttrs.contentType"))?;
parent
.execution()
.charge_work(declared.as_bytes().len() as u64)
.map_err(|reason| {
Error::resource(
reason,
"signerInfo.signedAttrs.contentType",
attribute.span().start,
)
})?;
if declared != expected {
return Err(Error::schema(
ErrorCode::ContentTypeMismatch,
"signerInfo.signedAttrs.contentType",
attribute.span().start,
));
}
}
}
let attribute = attributes
.unique_in(oid::MESSAGE_DIGEST, parent)?
.ok_or_else(|| {
Error::schema(
ErrorCode::MissingField,
"signerInfo.signedAttrs.messageDigest",
attributes.element().span().start,
)
})?;
attribute.single("signerInfo.signedAttrs.messageDigest")?;
let mut reader = value_reader(attribute, parent, "signerInfo.signedAttrs.messageDigest")?;
let element = crate::signer::required(&mut reader, "signerInfo.signedAttrs.messageDigest")?;
let digest = Octets::read(&reader, &element, "signerInfo.signedAttrs.messageDigest")?;
let digest = digest.as_one().ok_or_else(|| {
Error::schema(
ErrorCode::UnexpectedTag,
"signerInfo.signedAttrs.messageDigest",
element.span().start,
)
})?;
check_signing_time(attributes, parent)?;
let protection = read_protection(attributes, parent)?;
Ok(Binding::Attributes {
digest,
digest_span: element.span(),
protection,
})
}
fn check_placements(signer: &SignerInfo<'_>, parent: &Reader<'_>) -> Result<()> {
if let Some(unsigned) = signer.unsigned_attributes() {
unsigned.placement_faults_in(parent, Err)?;
}
if let Some(signed) = signer.signed_attributes() {
signed.placement_faults_in(parent, Err)?;
}
Ok(())
}
fn check_signing_time(attributes: &Attributes<'_>, parent: &Reader<'_>) -> Result<()> {
const FIELD: &str = "signerInfo.signedAttrs.signingTime";
let Some(attribute) = attributes.unique_in(oid::SIGNING_TIME, parent)? else {
return Ok(());
};
let value = attribute.single(FIELD)?;
parent
.execution()
.charge_work(9)
.map_err(|reason| Error::resource(reason, FIELD, value.span().start))?;
let valid = if value.tag() == Tag::primitive(universal::UTC_TIME) {
value.contents().len() == 13
} else if value.tag() == Tag::primitive(universal::GENERALIZED_TIME) {
value.contents().len() == 15
&& !(value.contents()[..4] >= b"1950"[..] && value.contents()[..4] <= b"2049"[..])
} else {
false
};
if !valid {
return Err(Error::schema(
ErrorCode::AttributeSyntax,
FIELD,
value.span().start,
));
}
Ok(())
}
fn value_reader<'a, 'query>(
attribute: &Attribute<'a>,
parent: &Reader<'query>,
field: &'static str,
) -> Result<Reader<'query>>
where
'a: 'query,
{
attribute.value_reader_in(0, parent, field)?.ok_or_else(|| {
Error::schema(
ErrorCode::AttributeCardinality,
field,
attribute.span().start,
)
})
}
fn read_protection<'a>(
attributes: &Attributes<'a>,
parent: &Reader<'a>,
) -> Result<Option<Protection<'a>>> {
const FIELD: &str = "signerInfo.signedAttrs.cmsAlgorithmProtection";
let Some(attribute) = attributes.unique_in(oid::ALGORITHM_PROTECTION, parent)? else {
return Ok(None);
};
attribute.single(FIELD)?;
let mut reader = value_reader(attribute, parent, FIELD)?;
let element = reader
.expect(Tag::constructed(universal::SEQUENCE))
.map_err(|error| Error::encoding(error, FIELD))?;
let mut inside = reader
.enter(&element)
.map_err(|error| Error::encoding(error, FIELD))?;
let digest = crate::signer::required(&mut inside, FIELD)?;
let digest = structured_pkix::algorithm::read(&inside, &digest, FIELD).map_err(Error::pkix)?;
let signature = crate::signer::required(&mut inside, FIELD)?;
let signature = structured_pkix::algorithm::read_implicit(&inside, &signature, 1, FIELD)
.map_err(Error::pkix)?;
if !inside.is_empty() {
return Err(Error::schema(
ErrorCode::TrailingData,
FIELD,
inside.offset(),
));
}
Ok(Some(Protection { digest, signature }))
}