use std::time::{SystemTime, UNIX_EPOCH};
use super::{Data, Store, StoreError, format};
use crate::certificate::{self, FoundRoot, Trust, fingerprint, parse_fingerprint};
use crate::tbk::Backup;
pub const MAX_ROOTS: usize = 16;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RootSource {
BuiltIn,
Tq6,
P12,
Tbk,
Part,
}
impl RootSource {
pub fn name(self) -> &'static str {
match self {
Self::BuiltIn => "built in",
Self::Tq6 => "tq6",
Self::P12 => "p12",
Self::Tbk => "tbk",
Self::Part => "part",
}
}
pub(super) fn from_name(name: &str) -> Option<Self> {
Some(match name {
"tq6" => Self::Tq6,
"p12" => Self::P12,
"tbk" => Self::Tbk,
"part" => Self::Part,
_ => return None,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RootKind {
Root,
Ca,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrustedRoot {
pub kind: RootKind,
pub subject: String,
pub serial: String,
pub not_before: SystemTime,
pub not_after: SystemTime,
pub fingerprint: String,
pub source: RootSource,
pub accepted_at: Option<u64>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct AcceptedRoot {
pub der: Vec<u8>,
pub kind: RootKind,
pub source: RootSource,
pub accepted_at: u64,
}
impl AcceptedRoot {
fn describe(&self) -> Option<TrustedRoot> {
describe(&self.der, self.kind, self.source, Some(self.accepted_at))
}
}
fn describe(
der: &[u8],
kind: RootKind,
source: RootSource,
accepted_at: Option<u64>,
) -> Option<TrustedRoot> {
let d = certificate::describe(der)?;
Some(TrustedRoot {
kind,
subject: d.subject,
serial: d.serial,
not_before: d.not_before,
not_after: d.not_after,
fingerprint: fingerprint(der),
source,
accepted_at,
})
}
fn now() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_secs())
}
pub(super) fn extend(base: &Trust, data: &Data) -> Trust {
base.extended(
data.roots
.iter()
.filter(|r| r.kind == RootKind::Root)
.map(|r| r.der.as_slice()),
data.roots
.iter()
.filter(|r| r.kind == RootKind::Ca)
.map(|r| r.der.as_slice()),
)
}
pub(super) fn add(
data: &mut Data,
found: &[FoundRoot],
source: RootSource,
) -> Result<Vec<TrustedRoot>, StoreError> {
let accepted_at = now();
let mut added = Vec::new();
for f in found {
let entries = std::iter::once((RootKind::Root, &f.root))
.chain(f.authorities.iter().map(|a| (RootKind::Ca, a)));
for (kind, der) in entries {
if data.roots.iter().any(|r| &r.der == der) {
continue;
}
if data.roots.iter().filter(|r| r.kind == kind).count() >= MAX_ROOTS {
return Err(StoreError::Limit("accepted roots"));
}
let entry = AcceptedRoot {
der: der.clone(),
kind,
source,
accepted_at,
};
if kind == RootKind::Root {
added.extend(entry.describe());
}
data.roots.push(entry);
}
}
Ok(added)
}
impl Store {
pub fn trust(&self) -> Result<Trust, StoreError> {
self.trust_with(&Trust::lotw())
}
pub fn trust_with(&self, base: &Trust) -> Result<Trust, StoreError> {
let _lock = self.lock()?;
Ok(extend(base, &self.read()?))
}
pub fn trusted_roots(&self) -> Result<Vec<TrustedRoot>, StoreError> {
let data = {
let _lock = self.lock()?;
self.read()?
};
let (roots, authorities) = Trust::lotw().certificates();
let built_in = roots
.iter()
.map(|d| (d, RootKind::Root))
.chain(authorities.iter().map(|d| (d, RootKind::Ca)))
.filter_map(|(d, kind)| describe(d, kind, RootSource::BuiltIn, None));
Ok(built_in
.chain(data.roots.iter().filter_map(AcceptedRoot::describe))
.collect())
}
pub fn remove_root(&self, fingerprint: &str) -> Result<TrustedRoot, StoreError> {
let given = parse_fingerprint(fingerprint).ok_or(StoreError::Invalid(
"a root is named by its SHA-256 fingerprint",
))?;
let lotw = Trust::lotw();
let (roots, authorities) = lotw.certificates();
if roots
.iter()
.chain(&authorities)
.any(|d| certificate::fingerprint(d) == given)
{
return Err(StoreError::BuiltInRoot);
}
let _lock = self.lock()?;
let mut data = self.read()?;
let i = data
.roots
.iter()
.position(|r| certificate::fingerprint(&r.der) == given)
.ok_or(StoreError::NotFound)?;
let removed = data.roots.remove(i);
loop {
let trust = extend(&lotw, &data);
let Some(orphan) = data.roots.iter().position(|r| {
r.kind == RootKind::Ca
&& certificate::verify_imported(&r.der, &trust, &[], true).is_err()
}) else {
break;
};
data.roots.remove(orphan);
}
self.replace(&format::write(&data)?)?;
removed
.describe()
.ok_or(StoreError::Invalid("accepted root"))
}
pub fn trust_backup_roots(
&self,
backup: &Backup,
trust: &Trust,
) -> Result<Vec<TrustedRoot>, StoreError> {
let _lock = self.lock()?;
let mut data = self.read()?;
let found = backup.found_roots(&extend(trust, &data));
let added = add(&mut data, &found, RootSource::Tbk)?;
if !found.is_empty() {
self.replace(&format::write(&data)?)?;
}
Ok(added)
}
}