pub(crate) mod ca;
pub(crate) mod cosign;
pub(crate) mod time;
pub(crate) mod trust;
use crate::base::der::{Element, read_element};
use crate::base::entry::{certificate_elements, entry_hash_from_certificate};
use crate::base::id::TrustAnchorId;
use crate::base::proof::MtcProof;
use crate::base::tree::{HASH_SIZE, Subtree, evaluate_inclusion_proof};
use crate::verify::trust::TrustStore;
use crate::{Error, Result};
const MTC_PROOF_OID: &str = "1.3.6.1.5.5.7.6.67";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VerificationKind {
Standalone,
LandmarkRelative,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Verification {
pub kind: VerificationKind,
pub ca_id: TrustAnchorId,
pub log_number: u16,
pub entry_index: u64,
pub subtree: Subtree,
pub subtree_hash: [u8; HASH_SIZE],
pub satisfied_cosigners: Vec<TrustAnchorId>,
}
fn check_mtc_proof_algorithm(element: Element<'_>) -> Result<()> {
if element.tag != 0x30 {
return Err(Error::NotMtc);
}
let (oid, rest) = read_element(element.content).ok_or(Error::Malformed("algorithm OID"))?;
if oid.tag != 0x06 || crate::base::id::oid_content_to_dotted(oid.content)? != MTC_PROOF_OID {
return Err(Error::NotMtc);
}
if !rest.is_empty() {
return Err(Error::Malformed("mtcProof parameters"));
}
Ok(())
}
fn serial_number(serial: Element<'_>) -> Result<u64> {
if serial.tag != 0x02 {
return Err(Error::Malformed("serial number"));
}
let reference = crate::base::proof::split_serial(serial.content).ok_or(Error::InvalidSerial)?;
if reference.log_number == 0 {
return Err(Error::InvalidSerial);
}
Ok((u64::from(reference.log_number) << 48) | reference.entry_index)
}
fn validity_unix(validity: Element<'_>) -> Result<(i64, i64)> {
if validity.tag != 0x30 {
return Err(Error::Malformed("validity"));
}
let (not_before, rest) = read_element(validity.content).ok_or(Error::Malformed("notBefore"))?;
let (not_after, rest) = read_element(rest).ok_or(Error::Malformed("notAfter"))?;
if !rest.is_empty() {
return Err(Error::Malformed("validity"));
}
let parse =
|element: Element<'_>| crate::verify::time::parse_time(element.tag, element.content);
Ok((parse(not_before)?, parse(not_after)?))
}
pub fn verify_certificate(
store: &TrustStore,
certificate_der: &[u8],
now: i64,
) -> Result<Verification> {
verify_with(store, certificate_der, Some(now))
}
pub fn verify_certificate_ignoring_validity(
store: &TrustStore,
certificate_der: &[u8],
) -> Result<Verification> {
verify_with(store, certificate_der, None)
}
fn verify_with(
store: &TrustStore,
certificate_der: &[u8],
now: Option<i64>,
) -> Result<Verification> {
let (tbs, signature_algorithm, signature_value) = certificate_elements(certificate_der)?;
check_mtc_proof_algorithm(signature_algorithm)?;
if signature_value.tag != 0x03
|| signature_value.content.is_empty()
|| signature_value.content[0] != 0
{
return Err(Error::Malformed("signature BIT STRING"));
}
let mut input = tbs.content;
if let Some((version, rest)) = read_element(input)
&& version.tag == 0xa0
{
input = rest;
}
let serial = serial_number(crate::base::der::next_element(&mut input, "serial")?)?;
check_mtc_proof_algorithm(crate::base::der::next_element(&mut input, "signature")?)?;
let issuer = crate::base::der::next_element(&mut input, "issuer")?;
let (not_before, not_after) =
validity_unix(crate::base::der::next_element(&mut input, "validity")?)?;
let ca_id = crate::verify::ca::trust_anchor_id_from_name(issuer.content)?;
let log_number = (serial >> 48) as u16;
let entry_index = serial & 0xffff_ffff_ffff;
if log_number == 0 {
return Err(Error::InvalidSerial);
}
let ca = store.ca(&ca_id).ok_or(Error::UnknownCa)?;
if ca
.revoked_ranges
.iter()
.any(|range| range.contains(&serial))
{
return Err(Error::Revoked);
}
if let Some(now) = now
&& !(not_before..=not_after).contains(&now)
{
return Err(Error::TimeInvalid);
}
let proof = MtcProof::parse_strict(&signature_value.content[1..])?;
let subtree = Subtree::new(proof.subtree.start, proof.subtree.end)
.ok_or(Error::Malformed("MTCProof subtree"))?;
if !subtree.contains(entry_index) {
return Err(Error::ProofEvaluation);
}
let entry_hash = entry_hash_from_certificate(certificate_der, proof.entry_extensions_bytes())?;
let expected_subtree_hash =
evaluate_inclusion_proof(&proof.inclusion_proof, entry_index, &entry_hash, subtree)
.ok_or(Error::ProofEvaluation)?;
if let Some(trusted) = ca
.trusted_subtrees
.iter()
.find(|t| t.log_number == log_number && t.start == subtree.start && t.end == subtree.end)
{
return if trusted.hash == expected_subtree_hash {
Ok(Verification {
kind: VerificationKind::LandmarkRelative,
ca_id,
log_number,
entry_index,
subtree,
subtree_hash: expected_subtree_hash,
satisfied_cosigners: Vec::new(),
})
} else {
Err(Error::ProofEvaluation)
};
}
let log_id = ca.ca_id.log_id(log_number);
let mut satisfied: Vec<TrustAnchorId> = Vec::new();
let mut invalid: Vec<TrustAnchorId> = Vec::new();
for cosignature in &proof.cosignatures {
let cosigner_id = TrustAnchorId::from_binary(&cosignature.cosigner_id)
.map_err(|_| Error::Malformed("cosigner ID"))?;
let info = ca.cosigner(&cosigner_id).or_else(|| {
store
.required_for_all
.iter()
.flat_map(|group| group.members().iter())
.find(|c| c.id == cosigner_id)
});
let Some(info) = info else { continue };
match crate::verify::cosign::verify_cosignature(
&info.id,
&log_id,
info.algorithm,
&info.spki_der,
subtree,
&expected_subtree_hash,
&cosignature.signature,
) {
Ok(()) => satisfied.push(cosigner_id),
Err(error @ Error::UnsupportedKey(_)) => return Err(error),
Err(_) => {
invalid.push(cosigner_id.clone());
if cosigner_id == ca.ca_cosigner.id {
return Err(Error::InvalidCosignature(ca.ca_cosigner.id.clone()));
}
}
}
}
let mut missing: Vec<TrustAnchorId> = Vec::new();
let mut note_missing = |id: &TrustAnchorId| {
if !satisfied.contains(id) && !invalid.contains(id) && !missing.contains(id) {
missing.push(id.clone());
}
};
let mut satisfied_policy = satisfied.contains(&ca.ca_cosigner.id);
if !satisfied_policy {
note_missing(&ca.ca_cosigner.id.clone());
}
let group_satisfied: Vec<TrustAnchorId> = satisfied
.iter()
.filter(|id| **id != ca.ca_cosigner.id)
.cloned()
.collect();
for group in ca
.required_groups
.iter()
.chain(store.required_for_all.iter())
{
if !group.satisfied_by(&group_satisfied) {
satisfied_policy = false;
for member in group.members() {
note_missing(&member.id);
}
}
}
if satisfied_policy {
Ok(Verification {
kind: VerificationKind::Standalone,
ca_id,
log_number,
entry_index,
subtree,
subtree_hash: expected_subtree_hash,
satisfied_cosigners: satisfied,
})
} else {
Err(Error::PolicyUnsatisfied { missing, invalid })
}
}