use crate::attribute::Attributes;
use crate::ess::SigningAttributes;
use crate::signer::SignerInfo;
use crate::{Error, ErrorCode, Result, oid};
use structured_asn1::memory::{OwnedVec, Work};
use structured_asn1::{Oid, Reader, Tag, universal};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Role {
Signature,
Countersignature,
ArchiveTimestamp,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Set {
Signed,
Unsigned,
Neither,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Signers {
Any,
NotCountersignature,
ArchiveTimestampOnly,
}
impl Signers {
fn admit(self, role: Role) -> bool {
match self {
Self::Any => true,
Self::NotCountersignature => role != Role::Countersignature,
Self::ArchiveTimestampOnly => role == Role::ArchiveTimestamp,
}
}
}
struct Rule {
kind: Oid<'static>,
set: Set,
single: bool,
unique: bool,
signers: Signers,
field: &'static str,
}
const fn rule(kind: Oid<'static>, set: Set, single: bool, field: &'static str) -> Rule {
Rule {
kind,
set,
single,
unique: false,
signers: Signers::Any,
field,
}
}
const fn ess(kind: Oid<'static>, field: &'static str) -> Rule {
Rule {
unique: true,
..rule(kind, Set::Signed, true, field)
}
}
const RULES: [Rule; 14] = [
rule(
oid::COMMITMENT_TYPE_INDICATION,
Set::Signed,
true,
"commitmentTypeIndication",
),
Rule {
signers: Signers::NotCountersignature,
..ess(oid::CONTENT_HINTS, "contentHints")
},
Rule {
signers: Signers::NotCountersignature,
..rule(oid::MIME_TYPE, Set::Signed, true, "mimeType")
},
rule(oid::SIGNER_LOCATION, Set::Signed, true, "signerLocation"),
rule(
oid::SIGNER_ATTRIBUTES_V2,
Set::Signed,
true,
"signerAttributesV2",
),
rule(
oid::CLAIMED_SAML_ASSERTION,
Set::Neither,
false,
"claimedSamlAssertion",
),
rule(
oid::CONTENT_TIMESTAMP,
Set::Signed,
true,
"contentTimeStamp",
),
rule(
oid::SIGNATURE_POLICY_IDENTIFIER,
Set::Signed,
true,
"signaturePolicyIdentifier",
),
rule(
oid::SIGNATURE_POLICY_STORE,
Set::Unsigned,
true,
"signaturePolicyStore",
),
ess(oid::CONTENT_REFERENCE, "contentReference"),
ess(oid::CONTENT_IDENTIFIER, "contentIdentifier"),
rule(
oid::SIGNATURE_TIMESTAMP,
Set::Unsigned,
true,
"signatureTimeStamp",
),
Rule {
unique: true,
signers: Signers::ArchiveTimestampOnly,
..rule(
oid::ATS_HASH_INDEX_V3,
Set::Unsigned,
true,
"atsHashIndexV3",
)
},
rule(
oid::ARCHIVE_TIMESTAMP_V3,
Set::Unsigned,
true,
"archiveTimeStampV3",
),
];
pub fn profile_faults<'a>(
signer: &SignerInfo<'a>,
role: Role,
parent: &Reader<'a>,
) -> Result<OwnedVec<Error>> {
signer.require_session(parent, "signerInfo")?;
let signing = SigningAttributes::read(signer, parent)?;
let execution = parent.execution();
let mut faults = execution.vector();
for rule in &RULES {
let protected = signer.signed_attributes();
let unprotected = signer.unsigned_attributes();
let (allowed, forbidden) = match rule.set {
Set::Signed => (protected, [unprotected, None]),
Set::Unsigned => (unprotected, [protected, None]),
Set::Neither => (None, [protected, unprotected]),
};
for set in forbidden.into_iter().flatten() {
report(set, rule, parent, &mut faults, |_, _| {
Some(ErrorCode::AttributePlacement)
})?;
}
let Some(set) = allowed else {
continue;
};
report(set, rule, parent, &mut faults, |attribute, instance| {
if !rule.signers.admit(role) {
Some(ErrorCode::AttributePlacement)
} else if rule.unique && instance > 0 {
Some(ErrorCode::DuplicateAttribute)
} else if rule.single && attribute.values().len() != 1 {
Some(ErrorCode::AttributeCardinality)
} else {
None
}
})?;
}
if let Some(signed) = signer.signed_attributes() {
for result in signed.all_in(oid::SIGNATURE_POLICY_IDENTIFIER, parent) {
for value in result?.values() {
execution.charge_work(1).map_err(|reason| {
Error::resource(reason, "signaturePolicyIdentifier", value.span().start)
})?;
if value.tag() == Tag::primitive(universal::NULL) {
push(
&mut faults,
Error::schema(
ErrorCode::AttributeSyntax,
"signaturePolicyIdentifier.signaturePolicyImplied",
value.span().start,
),
)?;
}
}
}
}
for (certificate, field) in [
(signing.v1(), "signingCertificate.policies"),
(signing.v2(), "signingCertificateV2.policies"),
] {
if let Some(policies) = certificate.and_then(|value| value.policies()) {
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, field, policies.span().start))?;
push(
&mut faults,
Error::schema(ErrorCode::AttributeSyntax, field, policies.span().start),
)?;
}
}
Ok(faults.into_owned())
}
fn report<W: Work>(
set: &Attributes<'_>,
rule: &Rule,
parent: &Reader<'_>,
faults: &mut OwnedVec<Error, W>,
judge: impl Fn(&crate::attribute::Attribute<'_>, usize) -> Option<ErrorCode>,
) -> Result<()> {
for (instance, result) in set.all_in(rule.kind, parent).enumerate() {
let attribute = result?;
if let Some(code) = judge(attribute, instance) {
push(
faults,
Error::schema(code, rule.field, attribute.span().start),
)?;
}
}
Ok(())
}
fn push<W: Work>(faults: &mut OwnedVec<Error, W>, fault: Error) -> Result<()> {
let field = fault.field();
let at = fault.offset();
faults
.try_push(fault)
.map_err(|reason| Error::resource(reason, field, at))
}