use std::borrow::Borrow;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use sequoia_openpgp as openpgp;
use openpgp::cert::prelude::*;
use openpgp::cert::raw::RawCert;
use openpgp::Fingerprint;
use openpgp::KeyHandle;
use openpgp::packet::UserID;
use openpgp::policy::Policy;
use openpgp::Result;
use sequoia_cert_store as cert_store;
use cert_store::LazyCert;
use cert_store::store::MergeCerts;
use cert_store::store::Store as _;
use cert_store::store::StoreError;
use cert_store::store::UserIDQueryParams;
use crate::Depth;
use crate::CertificationSet;
use crate::CertSynopsis;
use crate::store::Backend;
use crate::store::Store;
const TRACE: bool = false;
#[allow(unused)]
use crate as wot;
pub struct CertStore<'a: 'policy, 'policy, S>
where S: cert_store::store::Store<'a>,
{
store: S,
redge_cache: Mutex<BTreeMap<Fingerprint, Arc<Vec<CertificationSet>>>>,
policy: MaybeOwnedPolicy<'policy>,
reference_time: SystemTime,
_a: std::marker::PhantomData<&'a ()>,
}
#[non_exhaustive]
pub enum MaybeOwnedPolicy<'p> {
Borrowed(&'p dyn Policy),
Arc(Arc<dyn Policy + 'p>),
Owned(Box<dyn Policy + 'p>),
}
impl MaybeOwnedPolicy<'_> {
fn as_ref(&self) -> &dyn Policy {
match self {
MaybeOwnedPolicy::Borrowed(p) => *p,
MaybeOwnedPolicy::Arc(p) => p.as_ref(),
MaybeOwnedPolicy::Owned(p) => p.as_ref(),
}
}
}
impl<'p> From<&'p dyn Policy> for MaybeOwnedPolicy<'p> {
fn from(p: &'p dyn Policy) -> Self {
MaybeOwnedPolicy::Borrowed(p)
}
}
impl<'p, P: Policy> From<&'p P> for MaybeOwnedPolicy<'p> {
fn from(p: &'p P) -> Self {
MaybeOwnedPolicy::Borrowed(&*p)
}
}
impl<'p> From<Arc<dyn Policy + 'p>> for MaybeOwnedPolicy<'p> {
fn from(p: Arc<dyn Policy + 'p>) -> Self {
MaybeOwnedPolicy::Arc(p)
}
}
impl<'p> From<Box<dyn Policy + 'p>> for MaybeOwnedPolicy<'p> {
fn from(p: Box<dyn Policy + 'p>) -> Self {
MaybeOwnedPolicy::Owned(p)
}
}
impl<'a: 'policy, 'policy, S> CertStore<'a, 'policy, S>
where S: cert_store::store::Store<'a>,
{
pub fn from_store<P, T>(store: S, policy: P, t: T)
-> Self
where
P: Into<MaybeOwnedPolicy<'policy>>,
T: Into<Option<SystemTime>>,
{
let t = t.into().unwrap_or_else(SystemTime::now);
Self {
store,
redge_cache: Default::default(),
policy: policy.into(),
reference_time: t,
_a: std::marker::PhantomData,
}
}
pub fn store(&self) -> &S {
&self.store
}
pub fn store_mut(&mut self) -> &mut S {
&mut self.store
}
pub fn into_store(self) -> S {
self.store
}
pub fn policy(&self) -> &dyn Policy {
self.policy.as_ref()
}
pub fn reference_time(&self) -> SystemTime {
self.reference_time.clone()
}
}
impl<'a: 'policy, 'policy, S> std::ops::Deref for CertStore<'a, 'policy, S>
where S: cert_store::store::Store<'a>,
{
type Target = S;
fn deref(&self) -> &Self::Target {
&self.store
}
}
impl<'a: 'policy, 'policy, S> std::ops::DerefMut for CertStore<'a, 'policy, S>
where S: cert_store::store::Store<'a>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.store
}
}
impl<'a: 'policy, 'policy> CertStore<'a, 'policy, cert_store::store::Certs<'a>>
{
pub fn from_bytes<P, T>(bytes: &'a [u8], policy: P, t: T)
-> Result<Self>
where
P: Into<MaybeOwnedPolicy<'policy>>,
T: Into<Option<SystemTime>>,
{
tracer!(TRACE, "CertStore::from_bytes");
let store = cert_store::store::Certs::from_bytes(bytes)?;
let t = t.into().unwrap_or_else(SystemTime::now);
Ok(Self {
store,
redge_cache: Default::default(),
policy: policy.into(),
reference_time: t,
_a: std::marker::PhantomData,
})
}
pub fn from_certs<P, T>(certs: impl IntoIterator<Item=Cert>,
policy: P, t: T)
-> Result<Self>
where
P: Into<MaybeOwnedPolicy<'policy>>,
T: Into<Option<SystemTime>>,
{
tracer!(TRACE, "CertStore::from_certs");
let store = cert_store::store::Certs::from_certs(certs)?;
let t = t.into().unwrap_or_else(SystemTime::now);
Ok(Self {
store,
redge_cache: Default::default(),
policy: policy.into(),
reference_time: t,
_a: std::marker::PhantomData,
})
}
pub fn from_cert_refs<P, T>(certs: impl IntoIterator<Item=&'a Cert>,
policy: P, t: T)
-> Result<Self>
where
P: Into<MaybeOwnedPolicy<'policy>>,
T: Into<Option<SystemTime>>,
{
tracer!(TRACE, "CertStore::from_cert_refs");
let store = cert_store::store::Certs::from_certs(
certs.into_iter().map(|c| {
let c: &'a Cert = c.into();
LazyCert::from(c)
}))?;
let t = t.into().unwrap_or_else(SystemTime::now);
Ok(Self {
store,
redge_cache: Default::default(),
policy: policy.into(),
reference_time: t,
_a: std::marker::PhantomData,
})
}
pub fn from_raw_certs<P, T>(certs: impl IntoIterator<Item=RawCert<'a>>,
policy: P, t: T)
-> Result<Self>
where
P: Into<MaybeOwnedPolicy<'policy>>,
T: Into<Option<SystemTime>>,
{
tracer!(TRACE, "CertStore::from_raw_certs");
let store = cert_store::store::Certs::from_certs(certs)?;
let t = t.into().unwrap_or_else(SystemTime::now);
Ok(Self {
store,
redge_cache: Default::default(),
policy: policy.into(),
reference_time: t,
_a: std::marker::PhantomData,
})
}
}
impl<'a: 'policy, 'policy, S> cert_store::Store<'a> for CertStore<'a, 'policy, S>
where S: cert_store::store::Store<'a>,
{
fn lookup_by_cert(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
self.store.lookup_by_cert(kh)
}
fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint)
-> Result<Arc<LazyCert<'a>>>
{
self.store.lookup_by_cert_fpr(fingerprint)
}
fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result<Vec<Arc<LazyCert<'a>>>> {
self.store.lookup_by_cert_or_subkey(kh)
}
fn select_userid(&self, query: &UserIDQueryParams, pattern: &str)
-> Result<Vec<Arc<LazyCert<'a>>>>
{
self.store.select_userid(query, pattern)
}
fn lookup_by_userid(&self, userid: &UserID) -> Result<Vec<Arc<LazyCert<'a>>>> {
self.store.lookup_by_userid(userid)
}
fn grep_userid(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
self.store.grep_userid(pattern)
}
fn lookup_by_email(&self, email: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
self.store.lookup_by_email(email)
}
fn grep_email(&self, pattern: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
self.store.grep_email(pattern)
}
fn lookup_by_email_domain(&self, domain: &str) -> Result<Vec<Arc<LazyCert<'a>>>> {
self.store.lookup_by_email_domain(domain)
}
fn fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
self.store.fingerprints()
}
fn certs<'b>(&'b self)
-> Box<dyn Iterator<Item=Arc<LazyCert<'a>>> + 'b>
where 'a: 'b
{
self.store.certs()
}
fn prefetch_all(&self) {
self.store.prefetch_all()
}
fn prefetch_some(&self, certs: &[KeyHandle]) {
self.store.prefetch_some(certs)
}
}
impl<'a: 'policy, 'policy, S> cert_store::StoreUpdate<'a> for CertStore<'a, 'policy, S>
where S: cert_store::store::Store<'a> + cert_store::store::StoreUpdate<'a>,
{
fn update_by(&self,
cert: Arc<LazyCert<'a>>,
merge_strategy: &dyn MergeCerts<'a>)
-> Result<Arc<LazyCert<'a>>>
{
let fingerprint = cert.fingerprint();
let r = self.store.update_by(cert, merge_strategy);
let mut redge = self.redge_cache.lock().unwrap();
redge.remove(&fingerprint);
drop(redge);
r
}
fn update(&self, cert: Arc<LazyCert<'a>>) -> Result<()> {
let fingerprint = cert.fingerprint();
let r = self.store.update(cert);
let mut redge = self.redge_cache.lock().unwrap();
redge.remove(&fingerprint);
drop(redge);
r
}
}
impl<'a: 'policy, 'policy, S> Backend<'a> for CertStore<'a, 'policy, S>
where S: cert_store::store::Store<'a> + Send + Sync,
{
fn precompute(&self) {
tracer!(TRACE, "CertStore::precompute");
t!("prefetching");
let all: BTreeSet<Fingerprint>
= BTreeSet::from_iter(self.store.fingerprints());
let done: BTreeSet<Fingerprint>
= BTreeSet::from_iter(
self.redge_cache.lock().unwrap().keys().cloned());
let todo: Vec<&Fingerprint>
= all.difference(&done).collect();
use crossbeam::thread;
use crossbeam::channel::unbounded as channel;
let result = thread::scope(|thread_scope| {
let threads = if todo.len() < 16 {
2
} else {
num_cpus::get().max(1)
};
let (work_tx, work_rx) = channel();
let mut threads_extant = Vec::new();
for fpr in todo.into_iter() {
if threads_extant.len() < threads {
let tid = threads_extant.len();
t!("Starting thread {} of {}",
tid, threads);
let mut work = Some(Ok(fpr));
let work_rx = work_rx.clone();
let backend = &*self;
let policy = self.policy();
let reference_time = self.reference_time.clone();
threads_extant.push(thread_scope.spawn(move |_| {
let mut results: Vec<(Fingerprint, Arc<Vec<CertificationSet>>)>
= Vec::new();
loop {
match work.take().unwrap_or_else(|| work_rx.recv()) {
Err(_) => break,
Ok(fpr) => {
t!("Thread {} dequeuing {}!", tid, fpr);
let cert = if let Ok(cert)
= backend.lookup_by_cert_fpr(fpr)
{
cert
} else {
continue;
};
match cert.with_policy(policy, reference_time)
{
Ok(vc) => {
results.push((
cert.fingerprint(),
backend.redges(vc, 0.into())))
}
Err(err) => {
t!("{} is not valid under \
the current policy: {}",
cert.fingerprint(), err);
results.push((
cert.fingerprint(),
Arc::new(Vec::new())))
}
}
}
}
}
t!("Thread {} exiting", tid);
results
}));
} else {
work_tx.send(fpr).unwrap();
}
}
drop(work_tx);
let redges = threads_extant.into_iter().flat_map(|t| {
let redges: Vec<(Fingerprint, Arc<Vec<CertificationSet>>)>
= t.join().unwrap();
redges
});
self.redge_cache.lock().unwrap().extend(redges.into_iter());
});
if let Err(err) = result {
t!("{:?}", err);
}
}
}
impl<'a: 'policy, 'policy, S> CertStore<'a, 'policy, S>
where S: cert_store::store::Store<'a> + Send + Sync,
{
fn certifications_of_uncached<F>(&self, target: F)
-> Result<Arc<Vec<CertificationSet>>>
where F: Borrow<Fingerprint>
{
let target = target.borrow();
let cert = self.store.lookup_by_cert_fpr(target)?;
let redges = self.redges(
cert.with_policy(self.policy(), self.reference_time)?,
0.into());
Ok(redges)
}
fn to_synopsis(&self, cert: Arc<LazyCert>) -> Option<CertSynopsis> {
cert
.to_cert()
.and_then(|c| {
c.with_policy(self.policy(), self.reference_time)
})
.map(Into::into)
.ok()
}
}
impl<'a: 'policy, 'policy, S> Store for CertStore<'a, 'policy, S>
where S: cert_store::store::Store<'a> + Send + Sync,
{
fn reference_time(&self) -> SystemTime {
self.reference_time
}
fn iter_fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
tracer!(TRACE, "CertStore::iter_fingerprints");
t!("");
self.store.fingerprints()
}
fn synopses<'b>(&'b self) -> Box<dyn Iterator<Item=CertSynopsis> + 'b> {
let certs = self.store
.certs()
.filter_map(|c| self.to_synopsis(c))
.collect::<Vec<_>>();
Box::new(certs.into_iter())
}
fn lookup_synopsis_by_fpr(&self, fingerprint: &Fingerprint)
-> Result<CertSynopsis>
{
let cert = self.store.lookup_by_cert_fpr(fingerprint)?;
self.to_synopsis(cert).ok_or_else(|| {
StoreError::NotFound(KeyHandle::from(fingerprint.clone())).into()
})
}
fn lookup_synopses(&self, kh: &KeyHandle) -> Result<Vec<CertSynopsis>> {
tracer!(TRACE, "CertStore::lookup_synopses");
t!("{}", kh);
let certs: Vec<_> = self.store.lookup_by_cert(kh)?
.into_iter()
.filter_map(|c| self.to_synopsis(c))
.collect();
if certs.is_empty() {
Err(StoreError::NotFound(kh.clone()).into())
} else {
Ok(certs)
}
}
fn certifications_of(&self, target: &Fingerprint, _min_depth: Depth)
-> Result<Arc<Vec<CertificationSet>>>
{
tracer!(TRACE, "CertStore::certifications_of");
t!("{}", target);
let redge_cache = self.redge_cache.lock().unwrap();
if let Some(redges) = redge_cache.get(target) {
t!("Cache hit!");
return Ok(redges.clone());
}
drop(redge_cache);
t!("Cache miss!");
let redges = self.certifications_of_uncached(target)?;
self.redge_cache
.lock().unwrap()
.insert(target.clone(), Arc::clone(&redges));
Ok(redges)
}
fn certified_userids_of(&self, fpr: &Fingerprint)
-> Vec<UserID>
{
let Ok(cert) = self.lookup_by_cert_fpr(fpr)
else { return Vec::new() };
cert.userids().collect()
}
fn certified_userids(&self)
-> Vec<(Fingerprint, UserID)>
{
self.certs()
.flat_map(|c| {
let fpr = c.fingerprint();
c.userids().into_iter()
.map(|u| {
(fpr.clone(), u)
})
.collect::<Vec<_>>()
.into_iter()
})
.collect()
}
fn lookup_synopses_by_userid(&self, userid: UserID) -> Vec<Fingerprint> {
self.lookup_by_userid(&userid)
.unwrap_or(Vec::new())
.into_iter()
.map(|c| c.fingerprint())
.collect()
}
fn lookup_synopses_by_email(&self, email: &str) -> Vec<(Fingerprint, UserID)> {
let email = if let Ok(email) = UserIDQueryParams::is_email(email) {
email
} else {
return Vec::new();
};
self.lookup_by_email(&email)
.unwrap_or(Vec::new())
.into_iter()
.flat_map(|cert| {
cert.userids()
.filter_map(|userid| {
if let Ok(Some(e)) = userid.email() {
if e == email {
Some((cert.fingerprint(), userid.clone()))
} else {
if let Ok(Some(e)) = userid.email_normalized() {
if e == email {
Some((cert.fingerprint(), userid.clone()))
} else {
None
}
} else {
None
}
}
} else {
None
}
})
.collect::<Vec<_>>()
.into_iter()
})
.collect()
}
}
#[cfg(test)]
mod test {
use super::*;
use sequoia_openpgp as openpgp;
use openpgp::cert::CertBuilder;
use openpgp::Result;
use openpgp::packet::UserID;
use openpgp::parse::Parse;
use openpgp::policy::StandardPolicy;
use sequoia_cert_store as cert_store;
use cert_store::Store;
use sequoia_cert_store::StoreUpdate;
use crate::NetworkBuilder;
use crate::Roots;
use crate::FULLY_TRUSTED;
const P: &StandardPolicy = &StandardPolicy::new();
#[test]
fn cert_store_lifetimes() -> Result<()> {
fn authenticate<'store: 'policy, 'policy>(
store: &CertStore<'store, 'policy,
&'policy cert_store::CertStore<'store>>,
trust_root: Fingerprint,
target_fpr: Fingerprint,
target_userid: UserID)
-> usize
{
eprintln!("trust root: {}", trust_root);
eprintln!("target: {}, {:?}", target_fpr, target_userid);
let n = NetworkBuilder::rooted(
&store, Roots::from(&[
(trust_root, FULLY_TRUSTED),
]))
.build();
let paths = n.authenticate(
target_userid,
target_fpr,
FULLY_TRUSTED);
eprintln!("paths: {:?}", paths);
paths.amount()
}
let (alice, _) = CertBuilder::general_purpose(
Some("<alice@example.org>"))
.generate()?;
let (bob, _) = CertBuilder::general_purpose(
Some("<bob@example.org>"))
.generate()?;
let cert_store = cert_store::CertStore::empty();
cert_store.update(Arc::new(alice.clone().into()))?;
cert_store.update(Arc::new(bob.clone().into()))?;
eprintln!("certificates:");
for (i, cert) in cert_store.certs().enumerate() {
eprintln!(" {}. {}, {}",
i,
cert.fingerprint(),
cert.userids().next().expect("have one"));
}
for _ in 0..2 {
let store = CertStore::from_store(&cert_store, P, None);
let amount = authenticate(
&store,
alice.fingerprint(),
bob.fingerprint(),
UserID::from("<bob@example.org>"));
assert_eq!(amount, 0);
let amount = authenticate(
&store,
alice.fingerprint(),
alice.fingerprint(),
UserID::from("<alice@example.org>"));
assert_eq!(amount, FULLY_TRUSTED);
}
assert_eq!(cert_store.fingerprints().count(), 2);
drop(cert_store);
Ok(())
}
#[test]
fn my_own_grandfather() -> Result<()> {
let ref_cert = Cert::from_bytes(
&crate::testdata::data("my-own-grandpa.pgp"))?;
let ref_fpr = ref_cert.fingerprint();
let ref_kh = ref_cert.key_handle();
assert!(ref_cert.keys().subkeys().any(|k| {
k.key().fingerprint() == ref_fpr
}));
let store = CertStore::from_cert_refs(
std::iter::once(&ref_cert), P, None)?;
store.precompute();
let cert = store.lookup_by_cert_fpr(&ref_fpr)
.expect("found cert");
assert_eq!(cert.fingerprint(), ref_fpr);
let certs = store.lookup_by_cert(&ref_kh)
.expect("found cert");
assert_eq!(certs.len(), 1);
assert_eq!(certs[0].fingerprint(), ref_fpr);
let certs = store.lookup_by_cert_or_subkey(&ref_kh)
.expect("found cert");
assert_eq!(certs.len(), 1);
assert_eq!(certs[0].fingerprint(), ref_fpr);
let certs = store.certs().collect::<Vec<_>>();
assert_eq!(certs.len(), 1);
assert_eq!(certs[0].fingerprint(), ref_fpr);
let fprs = store.fingerprints().collect::<Vec<_>>();
assert_eq!(fprs.len(), 1);
assert_eq!(fprs[0], ref_fpr);
Ok(())
}
#[test]
fn email_lookup() -> Result<()> {
use crate::store::Store as _;
let certs = CertParser::from_bytes(
&crate::testdata::data("puny-code.pgp"))?
.collect::<Result<Vec<Cert>>>()?;
let store = CertStore::from_cert_refs(
certs.iter(), P, None)?;
let certs = store.lookup_synopses_by_email("hÄNS@bücher.tld");
assert_eq!(certs.len(), 1);
let certs = store.lookup_synopses_by_email("<hÄNS@bücher.tld>");
assert_eq!(certs.len(), 0);
let certs = store.lookup_synopses_by_email("hÄns@bücher.tld");
assert_eq!(certs.len(), 1);
let certs = store.lookup_synopses_by_email("häns@xn--bcher-kva.tld");
assert_eq!(certs.len(), 1);
Ok(())
}
}