use crate::provider::{DigestAlgorithm, ImpliedDigest};
use crate::signer::{SignerInfo, required};
use crate::{Error, ErrorCode, Octets, Result, oid};
use structured_asn1::memory::{OwnedVec, Work};
use structured_asn1::{Element, Integer, Reader, Span, Tag, universal};
use structured_pkix::general_name::{self, GeneralName};
mod check;
pub use check::{Binding, BindingError, Bindings};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Version {
V1,
V2,
}
impl Version {
#[must_use]
pub fn identifier(self) -> structured_asn1::Oid<'static> {
match self {
Self::V1 => oid::SIGNING_CERTIFICATE,
Self::V2 => oid::SIGNING_CERTIFICATE_V2,
}
}
fn field(self) -> &'static str {
match self {
Self::V1 => "signerInfo.signedAttrs.signingCertificate",
Self::V2 => "signerInfo.signedAttrs.signingCertificateV2",
}
}
}
#[derive(Debug)]
pub enum IssuerReading<'a> {
Absent,
Present(IssuerSerial<'a>),
Unreadable(Element<'a>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssuerAgreement {
Unstated,
Matches,
Differs,
Unreadable,
}
#[derive(Debug)]
pub struct IssuerSerial<'a> {
names: OwnedVec<GeneralName<'a>>,
serial: Integer<'a>,
names_element: Element<'a>,
element: Element<'a>,
}
impl<'a> IssuerSerial<'a> {
#[must_use]
pub fn names(&self) -> &[GeneralName<'a>] {
&self.names
}
#[must_use]
pub fn serial(&self) -> Integer<'a> {
self.serial
}
#[must_use]
pub fn names_element(&self) -> &Element<'a> {
&self.names_element
}
#[must_use]
pub fn span(&self) -> Span {
self.element.span()
}
}
#[derive(Debug)]
pub struct CertificateId<'a> {
digest: DigestAlgorithm<'a>,
hashed: Octets<'a>,
issuer: IssuerReading<'a>,
element: Element<'a>,
field: &'static str,
}
impl<'a> CertificateId<'a> {
#[must_use]
pub fn digest_algorithm(&self) -> DigestAlgorithm<'a> {
self.digest
}
#[must_use]
pub fn hashed(&self) -> &Octets<'a> {
&self.hashed
}
#[must_use]
pub fn issuer_serial(&self) -> &IssuerReading<'a> {
&self.issuer
}
#[must_use]
pub fn element(&self) -> &Element<'a> {
&self.element
}
pub fn matches_issuer(
&self,
certificate: &structured_pkix::Certificate<'_>,
parent: &Reader<'_>,
) -> Result<IssuerAgreement> {
self.matches_issuer_with(certificate, parent, &mut None)
}
fn matches_issuer_with<'c, 'p>(
&self,
certificate: &structured_pkix::Certificate<'c>,
parent: &'p Reader<'_>,
prepared: &mut Option<structured_pkix::Ordered<'c, structured_asn1::memory::Execution<'p>>>,
) -> Result<IssuerAgreement> {
let execution = parent.execution();
let field = self.field;
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, field, self.element.span().start))?;
let issuer = match &self.issuer {
IssuerReading::Absent => return Ok(IssuerAgreement::Unstated),
IssuerReading::Unreadable(_) => return Ok(IssuerAgreement::Unreadable),
IssuerReading::Present(issuer) => issuer,
};
let at = issuer.element.span().start;
let serial = issuer.serial.as_bytes();
let actual = certificate.tbs().serial().as_bytes();
execution
.charge_work(1 + serial.len().max(actual.len()) as u64)
.map_err(|reason| Error::resource(reason, field, at))?;
if serial != actual {
return Ok(IssuerAgreement::Differs);
}
for name in &issuer.names {
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, field, at))?;
if let GeneralName::Directory(name) = name {
let actual = match &*prepared {
Some(actual) => actual,
None => prepared.insert(
certificate
.tbs()
.issuer()
.ordered_in(parent)
.map_err(Error::pkix)?,
),
};
let asked = name.ordered_in(parent).map_err(Error::pkix)?;
if asked.order(actual, &execution).map_err(Error::pkix)?
== core::cmp::Ordering::Equal
{
return Ok(IssuerAgreement::Matches);
}
}
}
Ok(IssuerAgreement::Differs)
}
}
#[derive(Debug)]
pub enum CertificateReading<'a> {
Read(usize),
Unreadable(Element<'a>),
}
#[derive(Debug)]
pub struct SigningCertificate<'a> {
version: Version,
certificates: OwnedVec<CertificateReading<'a>>,
identifiers: OwnedVec<CertificateId<'a>>,
policies: Option<Element<'a>>,
faults: OwnedVec<Error>,
element: Element<'a>,
}
#[derive(Debug)]
pub struct SigningAttributes<'a> {
signed_attributes_present: bool,
v1: Option<SigningCertificate<'a>>,
v2: Option<SigningCertificate<'a>>,
faults: OwnedVec<Error>,
}
impl<'a> SigningAttributes<'a> {
pub fn read(signer: &SignerInfo<'a>, parent: &Reader<'a>) -> Result<Self> {
signer.require_session(parent, "signerInfo.signedAttrs")?;
let execution = parent.execution();
execution.charge_work(1).map_err(|reason| {
Error::resource(reason, "signerInfo.signedAttrs", signer.span().start)
})?;
let mut faults = execution.vector();
if let Some(unsigned) = signer.unsigned_attributes() {
for version in [Version::V1, Version::V2] {
for result in unsigned.all_in(version.identifier(), parent) {
let attribute = result?;
let field = match version {
Version::V1 => "signerInfo.unsignedAttrs.signingCertificate",
Version::V2 => "signerInfo.unsignedAttrs.signingCertificateV2",
};
record(
Error::schema(ErrorCode::AttributePlacement, field, attribute.span().start),
&mut faults,
field,
)?;
}
}
}
let v1 = read_attribute(signer, parent, Version::V1, &mut faults)?;
let v2 = read_attribute(signer, parent, Version::V2, &mut faults)?;
if v1.is_none()
&& let Some(v2) = &v2
{
for identifier in v2.identifiers() {
execution.charge_work(1).map_err(|reason| {
Error::resource(reason, Version::V2.field(), identifier.element.span().start)
})?;
if let DigestAlgorithm::Written(algorithm) = identifier.digest {
execution
.charge_work(
algorithm
.algorithm()
.as_bytes()
.len()
.min(oid::SHA1.as_bytes().len()) as u64,
)
.map_err(|reason| {
Error::resource(reason, Version::V2.field(), algorithm.offset())
})?;
if algorithm.algorithm() == oid::SHA1 {
record(
Error::schema(
ErrorCode::MissingField,
Version::V1.field(),
identifier.element.span().start,
),
&mut faults,
Version::V1.field(),
)?;
break;
}
}
}
}
Ok(Self {
signed_attributes_present: signer.signed_attributes().is_some(),
v1,
v2,
faults: faults.into_owned(),
})
}
#[must_use]
pub fn signed_attributes_present(&self) -> bool {
self.signed_attributes_present
}
#[must_use]
pub fn v1(&self) -> Option<&SigningCertificate<'a>> {
self.v1.as_ref()
}
#[must_use]
pub fn v2(&self) -> Option<&SigningCertificate<'a>> {
self.v2.as_ref()
}
#[must_use]
pub fn faults(&self) -> &[Error] {
&self.faults
}
}
fn read_attribute<'a, W: Work>(
signer: &SignerInfo<'a>,
parent: &Reader<'a>,
version: Version,
faults: &mut OwnedVec<Error, W>,
) -> Result<Option<SigningCertificate<'a>>> {
let Some(attributes) = signer.signed_attributes() else {
return Ok(None);
};
let field = version.field();
let mut first: Option<&crate::attribute::Attribute<'a>> = None;
for result in attributes.all_in(version.identifier(), parent) {
let attribute = result?;
if let Some(earlier) = first {
if !same_encoding(
parent,
earlier.as_bytes(),
attribute.as_bytes(),
field,
attribute.span().start,
)? {
return Err(Error::schema(
ErrorCode::AttributeCardinality,
field,
attribute.span().start,
));
}
record(
Error::schema(
ErrorCode::AttributeCardinality,
field,
attribute.span().start,
),
faults,
field,
)?;
} else {
first = Some(attribute);
}
}
let Some(attribute) = first else {
return Ok(None);
};
let Some(value) = attribute.values().first() else {
return Err(Error::schema(
ErrorCode::AttributeCardinality,
field,
attribute.span().start,
));
};
for extra in &attribute.values()[1..] {
if !same_encoding(
parent,
value.as_bytes(),
extra.as_bytes(),
field,
extra.span().start,
)? {
return Err(Error::schema(
ErrorCode::AttributeCardinality,
field,
extra.span().start,
));
}
record(
Error::schema(ErrorCode::AttributeCardinality, field, extra.span().start),
faults,
field,
)?;
}
let mut reader = attribute
.value_reader_in(0, parent, field)?
.ok_or_else(|| Error::schema(ErrorCode::MissingField, field, attribute.span().start))?;
let original = required(&mut reader, field)?;
SigningCertificate::read(&reader, &original, version).map(Some)
}
fn same_encoding(
parent: &Reader<'_>,
left: &[u8],
right: &[u8],
field: &'static str,
at: u64,
) -> Result<bool> {
let execution = parent.execution();
execution
.charge_work(1)
.map_err(|reason| Error::resource(reason, field, at))?;
if left.len() != right.len() {
return Ok(false);
}
execution
.charge_work(left.len() as u64)
.map_err(|reason| Error::resource(reason, field, at))?;
Ok(left == right)
}
impl<'a> SigningCertificate<'a> {
pub fn read(parent: &Reader<'a>, element: &Element<'a>, version: Version) -> Result<Self> {
let field = version.field();
require_sequence(element, field)?;
let mut outer = parent
.enter(element)
.map_err(|error| Error::encoding(error, field))?;
let certs = required(&mut outer, field)?;
require_sequence(&certs, field)?;
let mut list = outer
.enter(&certs)
.map_err(|error| Error::encoding(error, field))?;
let execution = parent.execution();
let mut faults = execution.vector();
let mut certificates = execution.vector();
let mut identifiers = execution.vector();
let first = required(&mut list, field)?;
let signer = read_identifier(&list, &first, version, &mut faults)?;
identifiers
.try_push(signer)
.map_err(|reason| Error::resource(reason, field, first.span().start))?;
certificates
.try_push(CertificateReading::Read(0))
.map_err(|reason| Error::resource(reason, field, first.span().start))?;
while let Some(entry) = list
.next_element()
.map_err(|error| Error::encoding(error, field))?
{
let reading = match read_identifier(&list, &entry, version, &mut faults) {
Ok(identifier) => {
let index = identifiers.len();
identifiers
.try_push(identifier)
.map_err(|reason| Error::resource(reason, field, entry.span().start))?;
CertificateReading::Read(index)
}
Err(error) => {
record(error, &mut faults, field)?;
CertificateReading::Unreadable(entry)
}
};
certificates
.try_push(reading)
.map_err(|reason| Error::resource(reason, field, entry.span().start))?;
}
let policies = outer
.next_element()
.map_err(|error| Error::encoding(error, field))?;
if let Some(policies) = &policies
&& let Err(error) = validate_policies(&outer, policies, field)
{
record(error, &mut faults, field)?;
}
if !outer.is_empty() {
record(
Error::schema(ErrorCode::TrailingData, field, outer.offset()),
&mut faults,
field,
)?;
}
Ok(Self {
version,
certificates: certificates.into_owned(),
identifiers: identifiers.into_owned(),
policies,
faults: faults.into_owned(),
element: *element,
})
}
#[must_use]
pub fn version(&self) -> Version {
self.version
}
#[must_use]
pub fn certificates(&self) -> &[CertificateReading<'a>] {
&self.certificates
}
#[must_use]
pub fn identifiers(&self) -> &[CertificateId<'a>] {
&self.identifiers
}
#[must_use]
pub fn signer(&self) -> &CertificateId<'a> {
&self.identifiers[0]
}
#[must_use]
pub fn policies(&self) -> Option<&Element<'a>> {
self.policies.as_ref()
}
#[must_use]
pub fn faults(&self) -> &[Error] {
&self.faults
}
#[must_use]
pub fn element(&self) -> &Element<'a> {
&self.element
}
}
fn read_identifier<'a, W: Work>(
parent: &Reader<'a>,
element: &Element<'a>,
version: Version,
faults: &mut OwnedVec<Error, W>,
) -> Result<CertificateId<'a>> {
let field = version.field();
require_sequence(element, field)?;
let mut inside = parent
.enter(element)
.map_err(|error| Error::encoding(error, field))?;
let mut next = required(&mut inside, field)?;
let mut digest = DigestAlgorithm::Implied {
algorithm: match version {
Version::V1 => ImpliedDigest::EssSha1,
Version::V2 => ImpliedDigest::EssV2Sha256,
},
field,
span: element.span(),
};
if version == Version::V2 && next.tag() == Tag::constructed(universal::SEQUENCE) {
let algorithm =
structured_pkix::algorithm::read(&inside, &next, field).map_err(Error::pkix)?;
inside
.charge(
1 + algorithm
.algorithm()
.as_bytes()
.len()
.max(oid::SHA256.as_bytes().len()) as u64,
)
.map_err(|error| Error::encoding(error, field))?;
if algorithm.algorithm() == oid::SHA256 && algorithm.parameters().is_none() {
record(
Error::schema(ErrorCode::DefaultWritten, field, next.span().start),
faults,
field,
)?;
}
digest = DigestAlgorithm::Written(algorithm);
next = required(&mut inside, field)?;
} else if version == Version::V2 && next.tag() == Tag::primitive(universal::OID) {
let kind = inside
.oid(&next)
.map_err(|error| Error::encoding(error, field))?;
inside
.charge(1 + kind.as_bytes().len().max(oid::SHA256.as_bytes().len()) as u64)
.map_err(|error| Error::encoding(error, field))?;
if kind != oid::SHA256 {
return Err(Error::schema(
ErrorCode::AttributeSyntax,
field,
next.span().start,
));
}
record(
Error::schema(ErrorCode::AttributeSyntax, field, next.span().start),
faults,
field,
)?;
next = required(&mut inside, field)?;
}
let hashed = Octets::read(&inside, &next, field)?;
let issuer = match inside
.next_element()
.map_err(|error| Error::encoding(error, field))?
{
None => IssuerReading::Absent,
Some(issuer) => match read_issuer_serial(&inside, &issuer, field, faults) {
Ok(value) => IssuerReading::Present(value),
Err(error) => {
record(error, faults, field)?;
IssuerReading::Unreadable(issuer)
}
},
};
if !inside.is_empty() {
record(
Error::schema(ErrorCode::TrailingData, field, inside.offset()),
faults,
field,
)?;
}
Ok(CertificateId {
digest,
hashed,
issuer,
element: *element,
field,
})
}
fn read_issuer_serial<'a, W: Work>(
parent: &Reader<'a>,
element: &Element<'a>,
field: &'static str,
faults: &mut OwnedVec<Error, W>,
) -> Result<IssuerSerial<'a>> {
require_sequence(element, field)?;
let mut inside = parent
.enter(element)
.map_err(|error| Error::encoding(error, field))?;
let names_element = required(&mut inside, field)?;
require_sequence(&names_element, field)?;
let serial = inside
.read_integer()
.map_err(|error| Error::encoding(error, field))?;
if !inside.is_empty() {
record(
Error::schema(ErrorCode::TrailingData, field, inside.offset()),
faults,
field,
)?;
}
let mut list = inside
.enter(&names_element)
.map_err(|error| Error::encoding(error, field))?;
let mut names = parent.execution().vector();
while let Some(name) = list
.next_element()
.map_err(|error| Error::encoding(error, field))?
{
let value = general_name::read(&list, &name, field).map_err(Error::pkix)?;
names
.try_push(value)
.map_err(|reason| Error::resource(reason, field, name.span().start))?;
}
if !matches!(&*names, [GeneralName::Directory(_)]) {
record(
Error::schema(
ErrorCode::AttributeSyntax,
field,
names_element.span().start,
),
faults,
field,
)?;
}
Ok(IssuerSerial {
names: names.into_owned(),
serial,
names_element,
element: *element,
})
}
fn record<W: Work>(
error: Error,
faults: &mut OwnedVec<Error, W>,
field: &'static str,
) -> Result<()> {
if error.is_terminal() {
return Err(error);
}
let at = error.offset();
faults
.try_push(error)
.map_err(|reason| Error::resource(reason, field, at))
}
fn require_sequence(element: &Element<'_>, field: &'static str) -> Result<()> {
if element.tag() != Tag::constructed(universal::SEQUENCE) {
return Err(Error::schema(
ErrorCode::UnexpectedTag,
field,
element.span().start,
));
}
Ok(())
}
fn validate_policies(
parent: &Reader<'_>,
element: &Element<'_>,
field: &'static str,
) -> Result<()> {
require_sequence(element, field)?;
let mut list = parent
.enter(element)
.map_err(|error| Error::encoding(error, field))?;
while let Some(policy) = list
.next_element()
.map_err(|error| Error::encoding(error, field))?
{
require_sequence(&policy, field)?;
let mut information = list
.enter(&policy)
.map_err(|error| Error::encoding(error, field))?;
information
.read_oid()
.map_err(|error| Error::encoding(error, field))?;
if let Some(qualifiers) = information
.next_element()
.map_err(|error| Error::encoding(error, field))?
{
require_sequence(&qualifiers, field)?;
let mut members = information
.enter(&qualifiers)
.map_err(|error| Error::encoding(error, field))?;
if members.is_empty() {
return Err(Error::schema(
ErrorCode::AttributeSyntax,
field,
qualifiers.span().start,
));
}
while let Some(member) = members
.next_element()
.map_err(|error| Error::encoding(error, field))?
{
require_sequence(&member, field)?;
let mut pair = members
.enter(&member)
.map_err(|error| Error::encoding(error, field))?;
let kind = pair
.read_oid()
.map_err(|error| Error::encoding(error, field))?;
let qualifier = required(&mut pair, field)?;
let work = 2
+ kind
.as_bytes()
.len()
.min(structured_pkix::oid::CPS_QUALIFIER.as_bytes().len())
as u64
+ kind
.as_bytes()
.len()
.min(structured_pkix::oid::USER_NOTICE_QUALIFIER.as_bytes().len())
as u64;
pair.charge(work)
.map_err(|error| Error::encoding(error, field))?;
if (kind == structured_pkix::oid::CPS_QUALIFIER
|| kind == structured_pkix::oid::USER_NOTICE_QUALIFIER)
&& !structured_pkix::policy::qualifier_understood(&pair, kind, &qualifier)
.map_err(Error::pkix)?
{
return Err(Error::schema(
ErrorCode::AttributeSyntax,
field,
qualifier.span().start,
));
}
if !pair.is_empty() {
return Err(Error::schema(ErrorCode::TrailingData, field, pair.offset()));
}
}
}
if !information.is_empty() {
return Err(Error::schema(
ErrorCode::TrailingData,
field,
information.offset(),
));
}
}
Ok(())
}