use std::collections::HashMap;
use super::x509::{time_within, Certificate};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CertRef {
IssuerAndSerial {
issuer_der: Vec<u8>,
serial: Vec<u8>,
},
SubjectKeyIdentifier(Vec<u8>),
}
#[derive(Debug, Default, Clone)]
pub struct TrustStore {
by_ias: HashMap<(Vec<u8>, Vec<u8>), Certificate>,
by_ski: HashMap<Vec<u8>, Certificate>,
by_ski_multi: Vec<(Vec<u8>, Certificate)>,
}
impl TrustStore {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, key: CertRef, cert: Certificate) {
match key {
CertRef::IssuerAndSerial { issuer_der, serial } => {
self.by_ias.insert((issuer_der, serial), cert);
}
CertRef::SubjectKeyIdentifier(ski) => {
self.by_ski_multi.push((ski.clone(), cert.clone()));
self.by_ski.insert(ski, cert);
}
}
}
pub fn insert_certificate(&mut self, cert: Certificate) {
let ias_key = (cert.issuer_der.clone(), cert.serial.clone());
if let Some(ski) = cert.subject_key_identifier() {
self.by_ski_multi.push((ski.clone(), cert.clone()));
self.by_ski.insert(ski, cert.clone());
}
self.by_ias.insert(ias_key, cert);
}
pub fn lookup(&self, key: &CertRef) -> Option<&Certificate> {
match key {
CertRef::IssuerAndSerial { issuer_der, serial } => {
self.by_ias.get(&(issuer_der.clone(), serial.clone()))
}
CertRef::SubjectKeyIdentifier(ski) => self.by_ski.get(ski),
}
}
pub fn find_with_temporal_validity(
&self,
ski: &[u8],
instant: Option<&[u8]>,
) -> Option<&Certificate> {
match instant {
Some(inst) => {
for (entry_ski, cert) in &self.by_ski_multi {
if entry_ski.as_slice() != ski {
continue;
}
if let Some((nb, na)) = cert.validity() {
if time_within(inst, nb, na) {
return Some(cert);
}
}
}
None
}
None => self.by_ski.get(ski),
}
}
pub fn len(&self) -> usize {
self.by_ias.len() + self.by_ski.len()
}
pub fn is_empty(&self) -> bool {
self.by_ias.is_empty() && self.by_ski.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn synth_cert(issuer: &[u8], serial: &[u8], spki: Option<Vec<u8>>) -> Certificate {
Certificate {
issuer_der: issuer.to_vec(),
serial: serial.to_vec(),
spki_pubkey_bits: spki,
..Default::default()
}
}
#[test]
fn ias_round_trip() {
let mut store = TrustStore::new();
let cert = synth_cert(b"O=A", &[0x01, 0x02], None);
store.insert(
CertRef::IssuerAndSerial {
issuer_der: cert.issuer_der.clone(),
serial: cert.serial.clone(),
},
cert.clone(),
);
let key = CertRef::IssuerAndSerial {
issuer_der: cert.issuer_der.clone(),
serial: cert.serial.clone(),
};
assert!(store.lookup(&key).is_some());
let key_miss = CertRef::IssuerAndSerial {
issuer_der: b"O=Other".to_vec(),
serial: vec![0x99],
};
assert!(store.lookup(&key_miss).is_none());
}
#[test]
fn ski_round_trip() {
let mut store = TrustStore::new();
let ski = vec![0xCDu8; 20];
let cert = synth_cert(b"O=B", &[0x05], Some(b"FakeSPKI".to_vec()));
store.insert(CertRef::SubjectKeyIdentifier(ski.clone()), cert);
assert!(store.lookup(&CertRef::SubjectKeyIdentifier(ski)).is_some());
assert!(store
.lookup(&CertRef::SubjectKeyIdentifier(vec![0; 20]))
.is_none());
}
#[test]
fn insert_certificate_indexes_both_forms_when_spki_present() {
let mut store = TrustStore::new();
let pubkey = b"PK-bytes-for-SKI-derivation--32!";
let cert = synth_cert(b"O=C", &[0x07], Some(pubkey.to_vec()));
let ski = cert.subject_key_identifier().expect("SKI");
store.insert_certificate(cert.clone());
assert!(store
.lookup(&CertRef::IssuerAndSerial {
issuer_der: cert.issuer_der.clone(),
serial: cert.serial.clone(),
})
.is_some());
assert!(store.lookup(&CertRef::SubjectKeyIdentifier(ski)).is_some());
assert_eq!(store.len(), 2);
}
#[test]
fn insert_certificate_skips_ski_when_spki_absent() {
let mut store = TrustStore::new();
let cert = synth_cert(b"O=D", &[0x09], None);
store.insert_certificate(cert.clone());
assert!(store
.lookup(&CertRef::IssuerAndSerial {
issuer_der: cert.issuer_der,
serial: cert.serial,
})
.is_some());
assert_eq!(store.len(), 1);
}
fn synth_cert_with_validity(
issuer: &[u8],
serial: &[u8],
spki: Vec<u8>,
not_before: &[u8],
not_after: &[u8],
) -> Certificate {
Certificate {
issuer_der: issuer.to_vec(),
serial: serial.to_vec(),
spki_pubkey_bits: Some(spki),
validity: Some((not_before.to_vec(), not_after.to_vec())),
..Default::default()
}
}
#[test]
fn find_with_temporal_validity_picks_active_generation() {
let mut store = TrustStore::new();
let pubkey = b"shared-spki-bits-32-bytes-ZZZZ!!".to_vec();
let cert_a = synth_cert_with_validity(
b"O=A 2024",
&[0x01],
pubkey.clone(),
b"20240101000000Z",
b"20241231235959Z",
);
let cert_b = synth_cert_with_validity(
b"O=B 2025",
&[0x02],
pubkey.clone(),
b"20250101000000Z",
b"20251231235959Z",
);
store.insert_certificate(cert_a.clone());
store.insert_certificate(cert_b.clone());
let ski = cert_a.subject_key_identifier().expect("SKI");
let hit_a = store
.find_with_temporal_validity(&ski, Some(b"20240601000000Z"))
.expect("temporal-A");
assert_eq!(hit_a.serial, vec![0x01]);
let hit_b = store
.find_with_temporal_validity(&ski, Some(b"20250601000000Z"))
.expect("temporal-B");
assert_eq!(hit_b.serial, vec![0x02]);
assert!(store
.find_with_temporal_validity(&ski, Some(b"20260601000000Z"))
.is_none());
let fallback = store
.find_with_temporal_validity(&ski, None)
.expect("fallback");
assert_eq!(fallback.serial, vec![0x02]);
}
#[test]
fn find_with_temporal_validity_skips_certs_without_window() {
let mut store = TrustStore::new();
let pubkey = b"some-spki-bits-32-bytes-padding!".to_vec();
let cert_no_window = Certificate {
issuer_der: b"O=No window".to_vec(),
serial: vec![0xAA],
spki_pubkey_bits: Some(pubkey.clone()),
..Default::default()
};
let cert_with_window = synth_cert_with_validity(
b"O=With window",
&[0xBB],
pubkey.clone(),
b"20260101000000Z",
b"20261231235959Z",
);
store.insert_certificate(cert_no_window);
store.insert_certificate(cert_with_window.clone());
let ski = cert_with_window.subject_key_identifier().expect("SKI");
let hit = store
.find_with_temporal_validity(&ski, Some(b"20260601000000Z"))
.expect("temporal-with-window");
assert_eq!(hit.serial, vec![0xBB]);
}
}