use super::TokenInfo;
use crate::{Error, ErrorCode};
use structured_asn1::{Reader, Tag, memory::Work, universal};
use structured_pkix::{
Certificate,
general_name::{self, GeneralName},
oid,
usage::ExtendedKeyUsage,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthoritySource {
Token,
Certificate,
}
#[derive(Debug)]
pub struct AuthorityError {
pub source: AuthoritySource,
pub cause: Error,
}
#[derive(Debug)]
pub enum TsaUsage {
TimeStampingOnly,
Missing,
NotCritical,
OtherPurposes,
Unreadable(Error),
}
#[derive(Debug)]
pub enum TsaIdentity {
Absent,
Matches,
Differs,
Unreadable,
CertificateUnreadable(Error),
}
#[derive(Debug)]
pub struct AuthorityFacts {
pub usage: TsaUsage,
pub identity: TsaIdentity,
}
impl TokenInfo<'_> {
pub fn authority(
&self,
certificate: &Certificate<'_>,
token_parent: &Reader<'_>,
certificate_parent: &Reader<'_>,
) -> Result<AuthorityFacts, AuthorityError> {
self.imprint()
.require_session(token_parent)
.map_err(token_error)?;
if !certificate
.tbs()
.public_key()
.algorithm()
.same_session(certificate_parent)
{
return Err(cert_error(Error::pkix(structured_pkix::Error::new(
structured_pkix::ErrorCode::ForeignReader,
"certificate",
certificate.span().start,
))));
}
Ok(AuthorityFacts {
usage: usage(certificate, token_parent)?,
identity: identity(self, certificate, token_parent)?,
})
}
}
fn token_error(cause: Error) -> AuthorityError {
AuthorityError {
source: AuthoritySource::Token,
cause,
}
}
fn cert_error(cause: Error) -> AuthorityError {
AuthorityError {
source: AuthoritySource::Certificate,
cause,
}
}
fn usage(certificate: &Certificate<'_>, query: &Reader<'_>) -> Result<TsaUsage, AuthorityError> {
let Some(extension) = certificate
.tbs()
.extensions()
.get(oid::EXT_KEY_USAGE, &query.execution())
.map_err(Error::pkix)
.map_err(cert_error)?
else {
return Ok(TsaUsage::Missing);
};
if !extension.is_critical() {
return Ok(TsaUsage::NotCritical);
}
let purposes = match ExtendedKeyUsage::read_in(extension, query).map_err(Error::pkix) {
Ok(purposes) => purposes,
Err(error) if error.is_terminal() => return Err(cert_error(error)),
Err(error) => return Ok(TsaUsage::Unreadable(error)),
};
if purposes.purposes().len() != 1 {
return Ok(TsaUsage::OtherPurposes);
}
query
.execution()
.charge_work(1 + purposes.purposes()[0].as_bytes().len() as u64)
.map_err(|reason| {
cert_error(Error::resource(
reason,
"extensions.extKeyUsage",
extension.span().start,
))
})?;
Ok(if purposes.purposes()[0] == oid::TIME_STAMPING {
TsaUsage::TimeStampingOnly
} else {
TsaUsage::OtherPurposes
})
}
fn identity(
info: &TokenInfo<'_>,
certificate: &Certificate<'_>,
query: &Reader<'_>,
) -> Result<TsaIdentity, AuthorityError> {
let Some(tsa) = info.tsa() else {
return Ok(TsaIdentity::Absent);
};
let Some(name) = tsa.name() else {
return Ok(TsaIdentity::Unreadable);
};
if let GeneralName::Directory(name) = name
&& name
.matches_in(certificate.tbs().subject(), query)
.map_err(|cause| {
Error::pkix(structured_pkix::Error::new(
cause.code(),
"tstInfo.tsa",
tsa.span().start,
))
})
.map_err(token_error)?
{
return Ok(TsaIdentity::Matches);
}
let Some(extension) = certificate
.tbs()
.extensions()
.get(oid::SUBJECT_ALT_NAME, &query.execution())
.map_err(Error::pkix)
.map_err(cert_error)?
else {
return Ok(TsaIdentity::Differs);
};
match san_matches(extension, name, tsa.span().start, query) {
Ok(true) => Ok(TsaIdentity::Matches),
Ok(false) => Ok(TsaIdentity::Differs),
Err(error) if error.field() == "tstInfo.tsa" => Err(token_error(error)),
Err(error) if error.is_terminal() => Err(cert_error(error)),
Err(error) => Ok(TsaIdentity::CertificateUnreadable(error)),
}
}
fn san_matches(
extension: &structured_pkix::Extension<'_>,
name: &GeneralName<'_>,
at: u64,
query: &Reader<'_>,
) -> crate::Result<bool> {
const FIELD: &str = "extensions.subjectAltName";
let mut reader = extension
.reader_in(query)
.map_err(|source| Error::encoding(source, FIELD))?;
let sequence = reader
.expect(Tag::constructed(universal::SEQUENCE))
.map_err(|source| Error::encoding(source, FIELD))?;
if !reader.is_empty() {
return Err(Error::schema(
ErrorCode::TrailingData,
FIELD,
reader.offset(),
));
}
let mut inside = reader
.enter(&sequence)
.map_err(|source| Error::encoding(source, FIELD))?;
let mut matched = false;
while let Some(element) = inside
.next_element()
.map_err(|source| Error::encoding(source, FIELD))?
{
let alternative = general_name::read(&inside, &element, FIELD).map_err(Error::pkix)?;
if !matched
&& general_name::matches(query, name, &alternative, at, "tstInfo.tsa")
.map_err(Error::pkix)?
{
matched = true;
}
}
Ok(matched)
}