use std::collections::HashMap;
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};
use thiserror::Error;
use x509_validator::rfc5280::RFC5280Policy;
use x509_validator::store::CertificateStore;
use x509_validator::unverified_chain::UnverifiedCertificateChain;
use x509_validator::{Certificate, CertificateExt, Oid, PolicyEvaluationResult, PolicyFailureReason, ValidationPolicy};
#[derive(Error, Debug, PartialEq)]
pub enum ChainVerifierError {
#[error("VerificationFailure: [{0}]")]
VerificationFailure(ChainVerificationFailureReason),
#[error("InternalError: [{0}]")]
InternalError(String),
#[error("InternalDecodeError: [{0}]")]
InternalDecodeError(#[from] base64::DecodeError),
}
#[derive(Error, Debug, PartialEq)]
pub enum ChainVerificationFailureReason {
#[error("InvalidAppIdentifier")]
InvalidAppIdentifier,
#[error("InvalidCertificate")]
InvalidCertificate,
#[error("InvalidChainLength")]
InvalidChainLength,
}
const APPLE_RECEIPT_SIGNER_OID: &str = "1.2.840.113635.100.6.11.1";
const APPLE_WWDR_INTERMEDIATE_OID: &str = "1.2.840.113635.100.6.2.1";
const EXPECTED_CHAIN_LENGTH: usize = 3;
struct AppStoreOidPolicy {
wwdr_oid: Oid<'static>,
receipt_signer_oid: Oid<'static>,
}
impl AppStoreOidPolicy {
fn new() -> Self {
Self {
wwdr_oid: APPLE_WWDR_INTERMEDIATE_OID
.parse()
.expect("valid OID"),
receipt_signer_oid: APPLE_RECEIPT_SIGNER_OID
.parse()
.expect("valid OID"),
}
}
fn certificate_has_oid(certificate: &Certificate, oid: &Oid<'static>) -> bool {
certificate
.tbs_certificate
.iter_extensions()
.any(|ext| &ext.oid == oid)
}
}
impl ValidationPolicy for AppStoreOidPolicy {
fn verifying_critical_extensions(&self) -> Vec<Oid<'static>> {
vec![]
}
fn chain_meets_policy_requirements(&self, chain: &UnverifiedCertificateChain) -> PolicyEvaluationResult {
if chain.len() != EXPECTED_CHAIN_LENGTH {
return Err(PolicyFailureReason::new("chain has unexpected length"));
}
let leaf = &chain[0];
let intermediate = &chain[1];
if !Self::certificate_has_oid(intermediate, &self.wwdr_oid) {
return Err(PolicyFailureReason::new(
"intermediate certificate does not contain WWDR OID",
));
}
if !Self::certificate_has_oid(leaf, &self.receipt_signer_oid) {
return Err(PolicyFailureReason::new(
"leaf certificate does not contain Receipt Signing OID",
));
}
Ok(())
}
}
const MAXIMUM_CACHE_SIZE: usize = 32;
const CACHE_TIME_LIMIT: u64 = 15 * 60;
#[derive(PartialEq, Eq, Hash)]
struct CacheKey {
leaf: Vec<u8>,
intermediate: Vec<u8>,
}
struct CacheValue {
expiration_time: u64,
public_key: Vec<u8>,
}
pub struct ChainVerifier {
root_certificates: Vec<Vec<u8>>,
verified_public_key_cache: Mutex<HashMap<CacheKey, CacheValue>>,
}
impl ChainVerifier {
pub fn new(root_certificates: Vec<Vec<u8>>) -> Self {
Self {
root_certificates,
verified_public_key_cache: Mutex::new(HashMap::new()),
}
}
pub fn verify(
&self,
leaf: &[u8],
intermediate: &[u8],
effective_date: Option<u64>,
enable_online_checks: bool,
) -> Result<Vec<u8>, ChainVerifierError> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
self.verify_at(
leaf,
intermediate,
effective_date,
enable_online_checks,
now,
)
}
pub fn verify_at(
&self,
leaf: &[u8],
intermediate: &[u8],
effective_date: Option<u64>,
enable_online_checks: bool,
now: u64,
) -> Result<Vec<u8>, ChainVerifierError> {
if enable_online_checks {
if let Some(cached) = self.cached_public_key(leaf, intermediate, now) {
return Ok(cached);
}
}
let public_key = self.verify_without_caching(leaf, intermediate, effective_date)?;
if enable_online_checks {
self.store_public_key(leaf, intermediate, &public_key, now);
}
Ok(public_key)
}
pub fn cache_len(&self) -> usize {
self.verified_public_key_cache
.lock()
.map(|c| c.len())
.unwrap_or(0)
}
fn verify_without_caching(
&self,
leaf: &[u8],
intermediate: &[u8],
effective_date: Option<u64>,
) -> Result<Vec<u8>, ChainVerifierError> {
let leaf = parse_certificate(leaf)?;
let intermediate = parse_certificate(intermediate)?;
let mut roots = CertificateStore::new();
for root_der in &self.root_certificates {
let root = parse_certificate(root_der)?;
roots.append(root);
}
let validation_time = effective_date
.map(|d| i64::try_from(d).unwrap_or(i64::MAX))
.unwrap_or_else(|| {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
});
let policy = x509_validator::policy! {
RFC5280Policy::new(validation_time);
AppStoreOidPolicy::new()
};
let validator = x509_validator::Validator::with_policy(roots, policy);
let mut intermediates = CertificateStore::new();
intermediates.append(intermediate);
match validator.validate_with_diagnostics(&leaf, &intermediates, &mut |_| {}) {
Ok(chain) => Ok(leaf_spki_der(chain.leaf())),
Err(_reasons) => Err(ChainVerifierError::VerificationFailure(
ChainVerificationFailureReason::InvalidCertificate,
)),
}
}
fn cached_public_key(&self, leaf: &[u8], intermediate: &[u8], now: u64) -> Option<Vec<u8>> {
let key = CacheKey {
leaf: leaf.to_vec(),
intermediate: intermediate.to_vec(),
};
let cache = self
.verified_public_key_cache
.lock()
.ok()?;
let value = cache.get(&key)?;
if value.expiration_time > now {
Some(value.public_key.clone())
} else {
None
}
}
fn store_public_key(&self, leaf: &[u8], intermediate: &[u8], public_key: &[u8], now: u64) {
let Ok(mut cache) = self.verified_public_key_cache.lock() else {
return;
};
cache.insert(
CacheKey {
leaf: leaf.to_vec(),
intermediate: intermediate.to_vec(),
},
CacheValue {
expiration_time: now + CACHE_TIME_LIMIT,
public_key: public_key.to_vec(),
},
);
if cache.len() > MAXIMUM_CACHE_SIZE {
cache.retain(|_, v| v.expiration_time > now);
}
}
}
fn parse_certificate(der: &[u8]) -> Result<Certificate<'_>, ChainVerifierError> {
Certificate::parse(der)
.map_err(|_| ChainVerifierError::VerificationFailure(ChainVerificationFailureReason::InvalidCertificate))
}
fn leaf_spki_der(leaf: &Certificate) -> Vec<u8> {
leaf.tbs_certificate
.subject_pki
.raw
.to_vec()
}