use std::borrow::Cow; use std::str; use std::sync::Arc; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; use anyhow::Context; use sequoia_openpgp as openpgp; use openpgp::Fingerprint; use openpgp::KeyHandle; use openpgp::Result; use openpgp::cert::Cert; use openpgp::cert::ValidCert; use openpgp::packet::UserID; pub use openpgp_cert_d; mod userid_index; pub use userid_index::UserIDIndex; pub mod certd; pub use certd::CertD; pub mod certs; pub use certs::Certs; // The keyserver backend is optional. #[cfg(feature = "keyserver")] pub mod keyserver; #[cfg(feature = "keyserver")] pub use keyserver::KeyServer; // If it is disabled, we swap in a dummy to reduce changes to the rest // of the code. pub mod no_keyserver; #[cfg(not(feature = "keyserver"))] use no_keyserver as keyserver; #[cfg(not(feature = "keyserver"))] pub(crate) use keyserver::KeyServer; pub mod pep; pub use pep::Pep; use super::TRACE; use crate::LazyCert; #[derive(Debug, Clone)] pub struct UserIDQueryParams { anchor_start: bool, anchor_end: bool, email: bool, ignore_case: bool, } assert_send_and_sync!(UserIDQueryParams); impl UserIDQueryParams { /// Returns a new `UserIDQueryParams`. /// /// By default, the query is configured to perform an exact match /// on the User ID. That is, the pattern must match the start and /// end of the User ID, and case is considered significant. pub fn new() -> Self { Self { anchor_start: true, anchor_end: true, email: false, ignore_case: false, } } /// Sets whether the pattern must match the start of the User ID /// or email address. pub fn set_anchor_start(&mut self, anchor_start: bool) -> &mut Self { self.anchor_start = anchor_start; self } /// Returns whether the pattern must match the start of the User /// ID or email address. pub fn anchor_start(&self) -> bool { self.anchor_start } /// Sets whether the pattern must match the end of the User /// ID or email address. pub fn set_anchor_end(&mut self, anchor_end: bool) -> &mut Self { self.anchor_end = anchor_end; self } /// Returns whether the pattern must match the end of the User /// ID or email address. pub fn anchor_end(&self) -> bool { self.anchor_end } /// Sets whether the pattern must match the User ID or the /// normalized email address. /// /// The email address to check the pattern against is extracted /// from the User ID using [`UserID::email_normalized`]. /// /// Note: the pattern is *not* normalized, even if the anchors are /// set. If you want to search by email address you need to /// normalize it your yourself. /// /// [`UserID::email_normalized`]: https://docs.rs/sequoia-openpgp/latest/sequoia_openpgp/packet/prelude/struct.UserID.html#method.email_normalized pub fn set_email(&mut self, email: bool) -> &mut Self { self.email = email; self } /// Returns whether the pattern must match the User ID or the /// normalized email address. /// /// See [`UserIDQueryParams::set_email`] for more details. pub fn email(&self) -> bool { self.email } /// Sets whether to ignore the case when matching the User ID or /// email address. /// /// Uses the empty local. /// /// When matching an email address, the domain is always matched /// in a case insensitive manner. The localpart, however, is /// matched in a case sensitive manner by default. pub fn set_ignore_case(&mut self, ignore_case: bool) -> &mut Self { self.ignore_case = ignore_case; self } /// Returns whether to ignore the case when matching the User ID /// or email address. /// /// See [`UserIDQueryParams::set_ignore_case`] for more details. pub fn ignore_case(&self) -> bool { self.ignore_case } /// Checks that the User ID satisfies the constraints. pub fn check(&self, userid: &UserID, pattern: &str) -> bool { tracer!(TRACE, "UserIDQueryParams::check"); // XXX: If you change this function, // UserIDIndex::select_userid contains similar code. Update // that too. match self { UserIDQueryParams { anchor_start: true, anchor_end: true, email: false, ignore_case: false, } => { // Exact User ID match. userid.value() == pattern.as_bytes() } UserIDQueryParams { anchor_start: true, anchor_end: true, email: true, ignore_case: false, } => { // Exact email match. if let Ok(Some(email)) = userid.email_normalized() { email == pattern } else { false } } UserIDQueryParams { anchor_start, anchor_end, email, ignore_case, } => { t!("Considering if {:?} matches {:?} \ (anchors: {}, {}, ignore case: {})", pattern, userid, anchor_start, anchor_end, ignore_case); // Substring search. let mut userid = if *email { if let Ok(Some(email)) = userid.email_normalized() { Cow::Owned(email) } else { t!("User ID does not contain a valid email address"); return false; } } else { String::from_utf8_lossy(userid.value()) }; if *ignore_case { userid = Cow::Owned(userid.to_lowercase()); } let mut pattern = pattern; let _pattern; if *ignore_case { // Convert to lowercase without tailoring, // i.e. without taking any locale into account. // See: // // - https://www.w3.org/International/wiki/Case_folding // - https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase // - http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G33992 _pattern = pattern.to_lowercase(); pattern = &_pattern[..]; } if match (*anchor_start, *anchor_end) { (true, true) => userid == pattern, (true, false) => userid.starts_with(pattern), (false, true) => userid.ends_with(pattern), (false, false) => userid.contains(pattern), } { t!("*** {:?} matches {:?} (anchors: {}, {})", pattern, userid, *anchor_start, anchor_end); true } else { false } } } } /// Checks that at least one User ID satisfies the constraints. pub fn check_lazy_cert(&self, cert: &LazyCert, pattern: &str) -> bool { cert.userids().any(|userid| self.check(&userid, pattern)) } /// Checks that at least one User ID satisfies the constraints. pub fn check_cert(&self, cert: &Cert, pattern: &str) -> bool { cert.userids().any(|ua| self.check(ua.userid(), pattern)) } /// Checks that at least one User ID satisfies the constraints. pub fn check_valid_cert(&self, vc: &ValidCert, pattern: &str) -> bool { vc.userids().any(|ua| self.check(ua.userid(), pattern)) } /// Returns whether the supplied email address is actually a valid /// email address. /// /// If it is valid, returns the normalized email address. pub fn is_email(email: &str) -> Result { let email_check = UserID::from(format!("<{}>", email)); match email_check.email2() { Ok(Some(email_check)) => { if email != email_check { return Err(StoreError::InvalidEmail( email.to_string(), None).into()); } } Ok(None) => { return Err(StoreError::InvalidEmail( email.to_string(), None).into()); } Err(err) => { return Err(StoreError::InvalidEmail( email.to_string(), Some(err)).into()); } } match UserID::from(&email[..]).email_normalized() { Err(err) => { Err(StoreError::InvalidEmail( email.to_string(), Some(err)).into()) } Ok(None) => { Err(StoreError::InvalidEmail( email.to_string(), None).into()) } Ok(Some(email)) => { Ok(email) } } } /// Returns whether the supplied domain address is actually a /// valid domain for an email address. /// /// Returns the normalized domain. pub fn is_domain(domain: &str) -> Result { let localpart = "user@"; let email = format!("{}{}", localpart, domain); let email = Self::is_email(&email)?; // We get the normalized email address back. Chop off the // username and the @. assert!(email.starts_with(localpart)); Ok(email[localpart.len()..].to_string()) } } /// [`Store`] specific error codes. #[non_exhaustive] #[derive(thiserror::Error, Debug)] pub enum StoreError { /// No certificate was found. #[error("{0} was not found")] NotFound(KeyHandle), /// No certificate matches the search criteria. #[error("No certificates matched {0}")] NoMatches(String), /// The email address does not appear to be a valid email address. #[error("{0:?} does not appear to be a valid email address")] InvalidEmail(String, #[source] Option), } /// Status messages. /// /// Status messages sent by [`StatusListener::update`]. /// /// The transaction id allows messages to be grouped together. For /// instance, `LookupStarted` will return a new transaction id and /// further messages related to that lookup including `LookupFinished` /// and `LookupFailed` will use the same transaction id. #[non_exhaustive] #[derive(Debug)] pub enum StatusUpdate<'a, 'c> { /// Sent when a lookup is starting. /// /// usize is the transaction id. /// /// The first `&str` is a short human-readable description of the /// backend. /// /// `KeyHandle` is what is being looked up. /// /// The last `&str` is a short human-readable message describing /// the look up. LookupStarted(usize, &'a str, &'a KeyHandle, Option<&'a str>), /// Sent while a lookup is on-going. /// /// usize is the transaction id. It will match the transaction id /// sent in the `LookupStart` message. /// /// The first `&str` is a short human-readable description of the /// backend. /// /// `KeyHandle` is what was being looked up. /// /// The last `&str` is a short human-readable message describing /// something that happened, e.g., "WKD returned FPR", etc. LookupStatus(usize, &'a str, &'a KeyHandle, &'a str), /// Sent when a lookup has been successful. /// /// usize is the transaction id. It will match the transaction id /// sent in the `LookupStart` message. /// /// The first `&str` is a short human-readable description of the /// backend. /// /// `KeyHandle` is what was being looked up. /// /// The certificates are the returned results. /// /// The last `&str` is a short human-readable message describing /// what happened, e.g., "found in cache", etc. LookupFinished(usize, &'a str, &'a KeyHandle, &'a [Arc>], Option<&'a str>), /// Sent when a lookup has failed. /// /// usize is the transaction id. It will match the transaction id /// sent in the `LookupStart` message. /// /// The first `&str` is a short human-readable description of the /// backend. /// /// `KeyHandle` is what was being looked up. /// /// The error is the reason that the lookup failed. A backend /// should set this to `None` if no certificate was present. LookupFailed(usize, &'a str, &'a KeyHandle, Option<&'a anyhow::Error>), /// Sent when a search is started. /// /// usize is the transaction id. /// /// The first `&str` is a short human-readable description of the /// backend. /// /// The second `&str` is the pattern being searched for. /// /// The last `&str` is a short human-readable message describing /// what is being looked up. SearchStarted(usize, &'a str, &'a str, Option<&'a str>), /// Sent while a search is on-going. /// /// usize is the transaction id. It will match the transaction id /// sent in the `LookupStart` message. /// /// The first `&str` is a short human-readable description of the /// backend. /// /// The second `&str` is the pattern being searched for. /// /// The last `&str` is a short human-readable message describing /// something that happened, e.g., "WKD returned FPR", etc. SearchStatus(usize, &'a str, &'a str, &'a str), /// Sent when a search is successful. /// /// usize is the transaction id. It will match the transaction id /// sent in the `LookupStart` message. /// /// The first `&str` is a short human-readable description of the /// backend. /// /// The second `&str` is the pattern being searched for. /// /// The certificates are the returned results. /// /// The last `&str` is a short human-readable message describing /// what happened, e.g., "found in cache", "found 5 matching /// certificates", etc. SearchFinished(usize, &'a str, &'a str, &'a [Arc>], Option<&'a str>), /// Sent whenever something has been lookup successfully. /// /// usize is the transaction id. It will match the transaction id /// sent in the `LookupStart` message. /// /// The first `&str` is a short human-readable description of the /// backend. /// /// The second `&str` is the pattern being searched for. /// /// The error is the reason that the search failed. A backend /// should set this to `None` if no matching certificate was /// found. SearchFailed(usize, &'a str, &'a str, Option<&'a anyhow::Error>), } impl<'a, 'c> std::fmt::Display for StatusUpdate<'a, 'c> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { use StatusUpdate::*; match self { LookupStarted(_tx, backend, kh, msg) => { if let Some(msg) = msg { write!(fmt, "{}: Looking up {}: {}...", backend, kh, msg) } else { write!(fmt, "{}: Looking up {}...", backend, kh) } } LookupStatus(_tx, backend, kh, msg) => { write!(fmt, "{}: Looking up {}: {}", backend, kh, msg) } LookupFinished(_tx, backend, kh, results, msg) => { if let Some(msg) = msg { write!(fmt, "{}: Looking up {}, returned {} results: {}", backend, kh, results.len(), msg) } else { write!(fmt, "{}: Looking up {}, returned {} results", backend, kh, results.len()) } } LookupFailed(_tx, backend, kh, err) => { if let Some(err) = err { write!(fmt, "{}: Looking up {}, failed: {}", backend, kh, err) } else { write!(fmt, "{}: Looking up {}, returned no results", backend, kh) } } SearchStarted(_tx, backend, pattern, msg) => { if let Some(msg) = msg { write!(fmt, "{}: Searching for {:?}: {}...", backend, pattern, msg) } else { write!(fmt, "{}: Searching for {:?}...", backend, pattern) } } SearchStatus(_tx, backend, pattern, msg) => { write!(fmt, "{}: Searching for {:?}: {}", backend, pattern, msg) } SearchFinished(_tx, backend, pattern, results, msg) => { if let Some(msg) = msg { write!(fmt, "{}: Searching for {:?}, returned {} results: {}", backend, pattern, results.len(), msg) } else { write!(fmt, "{}: Searching for {:?}, returned {} results", backend, pattern, results.len()) } } SearchFailed(_tx, backend, pattern, err) => { if let Some(err) = err { write!(fmt, "{}: Searching for {:?} failed: {}", backend, pattern, err) } else { write!(fmt, "{}: Searching for {:?}, returned no results", backend, pattern) } } } } } /// A callback mechanism to indicate what a backend is doing. /// /// We use an enum instead of a separate function for each message so /// that a naive listener can just print each message to stdout. /// /// This is primarily interesting for backends like a keyserver. /// /// Currently, backends are not required to implement this trait. In /// fact [`KeyServer`] is the only backend that implements it. A /// caller can add a listener to a `KeyServer` using /// [`KeyServer::add_listener`]. pub trait StatusListener { /// A status update. fn update(&self, status: &StatusUpdate); } /// Returns certificates from a backing store. pub trait Store<'a> { /// Returns the certificates whose fingerprint matches the handle. /// /// Returns [`StoreError::NotFound`] if no certificate is found. /// /// The caller may assume that looking up a fingerprint returns at /// most one certificate. fn lookup_by_cert(&self, kh: &KeyHandle) -> Result>>>; /// Returns the certificate with the specified fingerprint, if any. /// /// Returns [`StoreError::NotFound`] if the certificate is not found. /// /// The default implementation is implemented in terms of /// [`Store::lookup_by_cert`]. fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint) -> Result>> { let kh = KeyHandle::from(fingerprint.clone()); self.lookup_by_cert(&kh) .and_then(|v| { assert!(v.len() <= 1, "Looking up {} returned multiple certificates: {}", fingerprint, v.iter() .map(|c| { c.fingerprint().to_string() }) .collect::>() .join(", ")); v.into_iter().next() .ok_or(StoreError::NotFound(kh).into()) }) } /// Returns certificates that have a key with the specified /// handle, if any. /// /// Returns [`StoreError::NotFound`] if no certificate is not found. /// /// Note: even if you pass a fingerprint, this may return multiple /// certificates as the same subkey may be attached to multiple /// certificates. fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result>>>; /// Returns certificates that have a User ID matching the /// specified pattern according to the query parameters. /// /// User IDs are interpreted as UTF-8 strings. If a user ID is /// not valid UTF-8, then it will be converted to UTF-8 in a lossy /// manner using the same semantics as /// [`String::from_utf8_lossy`]. /// /// This function returns all matching user IDs, even those that /// are not self signed. It's up to the caller to authenticate /// the returned user IDs. fn select_userid(&self, query: &UserIDQueryParams, pattern: &str) -> Result>>>; /// Performs an exact match on the User ID. /// /// The pattern is anchored, and the match is case sensitive. /// /// The user ID is interpreted as a UTF-8 string. If the user ID /// is not valid UTF-8, then it will be converted to UTF-8 in a /// lossy manner using the same semantics as /// [`String::from_utf8_lossy`]. /// /// This function returns all matching user IDs, even those that /// are not self signed. It's up to the caller to authenticate /// the returned user IDs. fn lookup_by_userid(&self, userid: &UserID) -> Result>>> { self.select_userid( &UserIDQueryParams::new() .set_email(false) .set_anchor_start(true) .set_anchor_end(true) .set_ignore_case(false), &String::from_utf8_lossy(userid.value())) } /// Performs a case insenitive, substring match on the User ID. /// /// The pattern is not anchored, and it is matched case /// insensitively. /// /// User IDs are interpreted as UTF-8 strings. If a user ID is /// not valid UTF-8, then it will be converted to UTF-8 in a lossy /// manner using the same semantics as /// [`String::from_utf8_lossy`]. /// /// This function returns all matching user IDs, even those that /// are not self signed. It's up to the caller to authenticate /// the returned user IDs. fn grep_userid(&self, pattern: &str) -> Result>>> { self.select_userid( &UserIDQueryParams::new() .set_email(false) .set_anchor_start(false) .set_anchor_end(false) .set_ignore_case(true), pattern) } /// Returns certificates that have a User ID with the specified /// email address. /// /// The pattern is interpreted as an email address. It is first /// normalized, and then matched against the normalized email /// address, it is anchored, and the match is case sensitive. /// /// User IDs are interpreted as UTF-8 strings. If a user ID is /// not valid UTF-8, then it will be converted to UTF-8 in a lossy /// manner using the same semantics as /// [`String::from_utf8_lossy`]. /// /// This function returns all matching user IDs, even those that /// are not self signed. It's up to the caller to authenticate /// the returned user IDs. fn lookup_by_email(&self, email: &str) -> Result>>> { let userid = crate::email_to_userid(&email)?; let email = userid.email_normalized()?.expect("have one"); self.select_userid( &UserIDQueryParams::new() .set_email(true) .set_anchor_start(true) .set_anchor_end(true) .set_ignore_case(false), &email) } /// Performs a case insenitive, substring match on the normalized /// email address. /// /// The pattern is matched against the normalized email address, /// it is not anchored, and it is matched case insensitively. The /// pattern itself is *not* normalized. /// /// User IDs are interpreted as UTF-8 strings. If a user ID is /// not valid UTF-8, then it will be converted to UTF-8 in a lossy /// manner using the same semantics as /// [`String::from_utf8_lossy`]. /// /// This function returns all matching user IDs, even those that /// are not self signed. It's up to the caller to authenticate /// the returned user IDs. fn grep_email(&self, pattern: &str) -> Result>>> { self.select_userid( &UserIDQueryParams::new() .set_email(true) .set_anchor_start(false) .set_anchor_end(false) .set_ignore_case(true), pattern) } /// Returns certificates that have User ID with an email address /// from the specified domain. /// /// The pattern is interpreted as a domain address. It is first /// normalized, and then matched against the normalized email /// address, it is anchored, and the match is case sensitive. /// /// `domain` must be a bare domain, like `example.org`; it should /// not start with an `@`. This does not match subdomains. That /// is, it will match `alice@foo.bar.com` when searching for /// `bar.com`. /// /// User IDs are interpreted as UTF-8 strings. If a user ID is /// not valid UTF-8, then it will be converted to UTF-8 in a lossy /// manner using the same semantics as /// [`String::from_utf8_lossy`]. /// /// This function returns all matching user IDs, even those that /// are not self signed. It's up to the caller to authenticate /// the returned user IDs. fn lookup_by_email_domain(&self, domain: &str) -> Result>>> { let localpart = "localpart"; let email = format!("{}@{}", localpart, domain); let userid = crate::email_to_userid(&email)?; let email = userid.email_normalized()?.expect("have one"); let domain = &email[email.rfind('@').expect("have an @")..]; self.select_userid( &UserIDQueryParams::new() .set_email(true) .set_anchor_start(false) .set_anchor_end(true) .set_ignore_case(false), domain) } /// Lists all of the certificates. /// /// If a backend is not able to enumerate all the certificates, /// then it should return those that it knows about. For /// instance, some keyservers allow certificates to be looked up /// by fingerprint, but not to enumerate all of the certificates. /// Thus, a user must not assume that if a certificate is not /// returned by this function, it cannot be found by name. fn fingerprints<'b>(&'b self) -> Box + 'b>; /// Returns all of the certificates. /// /// The default implementation is implemented in terms of /// [`Store::fingerprints`] and [`Store::lookup_by_cert_fpr`]. Many backends /// will be able to do this more efficiently. fn certs<'b>(&'b self) -> Box>> + 'b> where 'a: 'b { Box::new(self.fingerprints() .filter_map(|fpr| { self.lookup_by_cert_fpr(&fpr).ok() })) } /// Prefills the cache. /// /// Prefilling the cache makes sense when you plan to examine most /// certificates. It doesn't make sense if you are just /// authenticating a single or a few bindings. /// /// This function may be multi-threaded. /// /// Errors should be silently ignored and propagated when the /// operation in question is executed directly. fn prefetch_all(&self) { } /// Prefetches some certificates. /// /// Prefilling the cache makes sense when you plan to examine some /// certificates in the near future. /// /// This interface is useful as it allows batching, which may be /// more efficient, especially when the certificates are accessed /// over the network. And, the function may be multi-threaded. /// /// Errors should be silently ignored and propagated when the /// operation in question is executed directly. fn prefetch_some(&self, certs: &[KeyHandle]) { let _ = certs; } } // The references in Store need a different lifetime from the contents // of the Box. Otherwise, a `Backend` that is a `&Box` would // create a self referential data structure. impl<'a: 't, 't, T> Store<'a> for Box where T: Store<'a> + ?Sized + 't { fn lookup_by_cert(&self, kh: &KeyHandle) -> Result>>> { self.as_ref().lookup_by_cert(kh) } fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint) -> Result>> { self.as_ref().lookup_by_cert_fpr(fingerprint) } fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result>>> { self.as_ref().lookup_by_cert_or_subkey(kh) } fn select_userid(&self, query: &UserIDQueryParams, pattern: &str) -> Result>>> { self.as_ref().select_userid(query, pattern) } fn lookup_by_userid(&self, userid: &UserID) -> Result>>> { self.as_ref().lookup_by_userid(userid) } fn grep_userid(&self, pattern: &str) -> Result>>> { self.as_ref().grep_userid(pattern) } fn lookup_by_email(&self, email: &str) -> Result>>> { self.as_ref().lookup_by_email(email) } fn grep_email(&self, pattern: &str) -> Result>>> { self.as_ref().grep_email(pattern) } fn lookup_by_email_domain(&self, domain: &str) -> Result>>> { self.as_ref().lookup_by_email_domain(domain) } fn fingerprints<'b>(&'b self) -> Box + 'b> { self.as_ref().fingerprints() } fn certs<'b>(&'b self) -> Box>> + 'b> where 'a: 'b { self.as_ref().certs() } fn prefetch_all(&self) { self.as_ref().prefetch_all() } fn prefetch_some(&self, certs: &[KeyHandle]) { self.as_ref().prefetch_some(certs) } } impl<'a: 't, 't, T> Store<'a> for &'t T where T: Store<'a> + ?Sized { fn lookup_by_cert(&self, kh: &KeyHandle) -> Result>>> { (*self).lookup_by_cert(kh) } fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint) -> Result>> { (*self).lookup_by_cert_fpr(fingerprint) } fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result>>> { (*self).lookup_by_cert_or_subkey(kh) } fn select_userid(&self, query: &UserIDQueryParams, pattern: &str) -> Result>>> { (*self).select_userid(query, pattern) } fn lookup_by_userid(&self, userid: &UserID) -> Result>>> { (*self).lookup_by_userid(userid) } fn grep_userid(&self, pattern: &str) -> Result>>> { (*self).grep_userid(pattern) } fn lookup_by_email(&self, email: &str) -> Result>>> { (*self).lookup_by_email(email) } fn grep_email(&self, pattern: &str) -> Result>>> { (*self).grep_email(pattern) } fn lookup_by_email_domain(&self, domain: &str) -> Result>>> { (*self).lookup_by_email_domain(domain) } fn fingerprints<'b>(&'b self) -> Box + 'b> { (*self).fingerprints() } fn certs<'b>(&'b self) -> Box>> + 'b> where 'a: 'b { (*self).certs() } } impl<'a: 't, 't, T> Store<'a> for &'t mut T where T: Store<'a> + ?Sized { fn lookup_by_cert(&self, kh: &KeyHandle) -> Result>>> { (**self).lookup_by_cert(kh) } fn lookup_by_cert_fpr(&self, fingerprint: &Fingerprint) -> Result>> { (**self).lookup_by_cert_fpr(fingerprint) } fn lookup_by_cert_or_subkey(&self, kh: &KeyHandle) -> Result>>> { (**self).lookup_by_cert_or_subkey(kh) } fn select_userid(&self, query: &UserIDQueryParams, pattern: &str) -> Result>>> { (**self).select_userid(query, pattern) } fn lookup_by_userid(&self, userid: &UserID) -> Result>>> { (**self).lookup_by_userid(userid) } fn grep_userid(&self, pattern: &str) -> Result>>> { (**self).grep_userid(pattern) } fn lookup_by_email(&self, email: &str) -> Result>>> { (**self).lookup_by_email(email) } fn grep_email(&self, pattern: &str) -> Result>>> { (**self).grep_email(pattern) } fn lookup_by_email_domain(&self, domain: &str) -> Result>>> { (**self).lookup_by_email_domain(domain) } fn fingerprints<'b>(&'b self) -> Box + 'b> { (**self).fingerprints() } fn certs<'b>(&'b self) -> Box>> + 'b> where 'a: 'b { (**self).certs() } fn prefetch_all(&self) { (**self).prefetch_all() } fn prefetch_some(&self, certs: &[KeyHandle]) { (**self).prefetch_some(certs) } } /// Merges two certificates. /// /// This is primarily useful as the `merge_strategy` callback to /// [`StoreUpdate::update_by`]. pub trait MergeCerts<'a> { /// Merges two certificates. /// /// This is primarily useful as the `merge_strategy` callback to /// [`StoreUpdate::update_by`]. /// /// The default implementation merges the two certificates using /// [`Cert::merge_public`]. This means that any secret key /// material in `disk` is preserved, any secret key material in /// `new` is ignored, and unhashed subpacket areas are merged. /// /// Note: `self` is a normal reference, not a mutable reference. /// This means that the underlying implementation has to use /// interior mutability. The advantage is that multiple threads /// can have a reference to the store, and update it in parallel. fn merge_public<'b>(&self, new: Arc>, disk: Option>>) -> Result>> { if let Some(disk) = disk { let merged = disk.to_cert()?.clone() .merge_public(new.to_cert()?.clone())?; Ok(Arc::new(LazyCert::from(merged))) } else { if new.is_tsk() { Ok(Arc::new(LazyCert::from(new.to_cert()?.clone() .strip_secret_key_material()))) } else { Ok(new) } } } } impl<'a> MergeCerts<'a> for () { } /// Provides an interface to update a backing store. pub trait StoreUpdate<'a>: Store<'a> { /// Insert a certificate. /// /// This uses the default implementation of [`MergeCerts`] to /// merge the certificate with any existing certificate. /// /// Note: `self` is a normal reference, not a mutable reference. /// This means that the underlying implementation has to use /// interior mutability. The advantage is that multiple threads /// can have a reference to the store, and update it in parallel. fn update(&self, cert: Arc>) -> Result<()> { self.update_by(cert, &mut ())?; Ok(()) } /// Inserts a certificate into the store. /// /// Inserts a certificate into the store and uses `merge_strategy` /// to merge it with the existing certificate, if any. /// /// Unless there is an error, you must call `merge_strategy`. /// This is the case even if the certificate is not on the /// backend. In that case, you must pass `None` for the on-disk /// version. This allows `merge_strategy` to generate statistics, /// and to modify the certificate before it is saved, e.g., by /// stripping third-party certifications. /// /// To use the default merge strategy, either call /// [`StoreUpdate::update`] directly, or pass `&mut ()`. /// /// Note: `self` and `merge_strategy` are normal references, not a /// mutable reference. This means that the underlying /// implementation has to use interior mutability. The advantage /// is that multiple threads can have a reference to the store, /// and update it in parallel. fn update_by(&self, cert: Arc>, merge_strategy: &dyn MergeCerts<'a>) -> Result>>; } impl<'a: 't, 't, T> StoreUpdate<'a> for Box where T: StoreUpdate<'a> + ?Sized + 't { fn update(&self, cert: Arc>) -> Result<()> { self.as_ref().update(cert) } fn update_by(&self, cert: Arc>, merge_strategy: &dyn MergeCerts<'a>) -> Result>> { self.as_ref().update_by(cert, merge_strategy) } } impl<'a: 't, 't, T> StoreUpdate<'a> for &'t T where T: StoreUpdate<'a> + ?Sized { fn update(&self, cert: Arc>) -> Result<()> { (*self).update(cert) } fn update_by(&self, cert: Arc>, merge_strategy: &dyn MergeCerts<'a>) -> Result>> { (*self).update_by(cert, merge_strategy) } } /// Merges two certificates and collects statistics. /// /// This is primarily useful as the `merge_strategy` callback to /// [`StoreUpdate::update_by`]. #[derive(Debug)] #[non_exhaustive] pub struct MergePublicCollectStats { /// Number of new certificates. new_certs: AtomicUsize, /// Number of unchanged certificates. /// /// Note: there may be false negative. That is some certificates /// may be unchanged, but the heuristic thinks that they have been /// updated. unchanged_certs: AtomicUsize, /// Number of update certificates. updated_certs: AtomicUsize, /// Number of errors. errors: AtomicUsize, } assert_send_and_sync!(MergePublicCollectStats); impl MergePublicCollectStats { /// Returns a new `MergePublicCollectStats` with all stats set to 0. pub fn new() -> Self { Self { new_certs: AtomicUsize::new(0), unchanged_certs: AtomicUsize::new(0), updated_certs: AtomicUsize::new(0), errors: AtomicUsize::new(0), } } /// Returns the number of new certificates. pub fn new_certs(&self) -> usize { self.new_certs.load(Ordering::Relaxed) } /// Returns the number of unchanged certificates. pub fn unchanged_certs(&self) -> usize { self.unchanged_certs.load(Ordering::Relaxed) } /// Returns the number of updated certificates. pub fn updated_certs(&self) -> usize { self.updated_certs.load(Ordering::Relaxed) } /// Returns the number of errors. pub fn errors(&self) -> usize { self.errors.load(Ordering::Relaxed) } /// Increments the number of new certificates by 1. pub fn inc_new_certs(&self) { self.new_certs.fetch_add(1, Ordering::Relaxed); } /// Increments the number of unchanged certificates by 1. pub fn inc_unchanged_certs(&self) { self.unchanged_certs.fetch_add(1, Ordering::Relaxed); } /// Increments the number of updated certificates by 1. pub fn inc_updated_certs(&self) { self.updated_certs.fetch_add(1, Ordering::Relaxed); } /// Increments the number of errors by 1. pub fn inc_errors(&self) { self.errors.fetch_add(1, Ordering::Relaxed); } } impl<'a> MergeCerts<'a> for MergePublicCollectStats { /// Merges two certificates. /// /// This is primarily useful as the `merge_strategy` callback to /// [`StoreUpdate::update_by`]. /// /// This implementation has the same merge semantics as the /// default implementation, but it also updates the statistics in /// `self`. /// /// # Examples /// /// ```rust /// use std::any::Any; /// use std::sync::Arc; /// /// use sequoia_openpgp as openpgp; /// # use openpgp::Result; /// use openpgp::cert::prelude::*; /// use openpgp::parse::Parse; /// /// use sequoia_cert_store as cert_store; /// use cert_store::CertStore; /// use cert_store::LazyCert; /// use cert_store::store::MergePublicCollectStats; /// use cert_store::store::StoreUpdate; /// /// # fn main() -> Result<()> { /// let (cert, _rev) = CertBuilder::new().generate()?; /// /// let mut certs = CertStore::empty(); /// /// let mut stats = MergePublicCollectStats::new(); /// /// certs.update_by(Arc::new(LazyCert::from(cert)), &mut stats) /// .expect("valid"); /// /// assert_eq!(stats.new_certs(), 1); /// # Ok(()) } /// ``` fn merge_public<'b, 'rb>(&self, new: Arc>, disk: Option>>) -> Result>> { let disk = if let Some(disk) = disk { disk } else { self.inc_new_certs(); if new.is_tsk() { return Ok(Arc::new(LazyCert::from( new.to_cert()?.clone().strip_secret_key_material()))) } else { return Ok(new); } }; let fpr = new.fingerprint(); let disk = disk.to_cert() .with_context(|| { format!("Parsing {} as returned from the cert directory", fpr) })? .clone(); let new = new.to_cert() .with_context(|| { format!("Parsing {} as being inserted into \ the cert directory", fpr) })? .clone(); if disk == new { self.inc_unchanged_certs(); Ok(Arc::new(LazyCert::from(new))) } else { // If the on-disk version has secrets, we preserve them. // If new has secrets, we ignore them. match disk.clone().merge_public(new) { Ok(merged) => { if merged == disk { self.inc_unchanged_certs(); } else { self.inc_updated_certs(); } Ok(Arc::new(LazyCert::from(merged))) } Err(err) => { self.inc_errors(); Err(err.into()) } } } } } #[cfg(test)] mod tests { use super::*; use crate::store; // Make sure we can pass a &Box where a generic type // needs to implement Store. #[test] fn store_boxed() -> Result<()> { struct Foo<'a, B> where B: Store<'a> { backend: B, _a: std::marker::PhantomData<&'a ()>, } impl<'a, B> Foo<'a, B> where B: Store<'a> { fn new(backend: B) -> Self { Foo { backend, _a: std::marker::PhantomData, } } fn count(&self) -> usize { self.backend.certs().count() } } let backend = store::Certs::empty(); let backend: Box = Box::new(backend); let foo = Foo::new(&backend); // Do something (anything) with the backend. assert_eq!(foo.count(), 0); Ok(()) } // Make sure we can pass a &Box where a generic type // needs to implement Store. #[test] fn store_update_boxed() -> Result<()> { struct Foo<'a, B> where B: StoreUpdate<'a> { backend: B, _a: std::marker::PhantomData<&'a ()>, } impl<'a, B> Foo<'a, B> where B: StoreUpdate<'a> { fn new(backend: B) -> Self { Foo { backend, _a: std::marker::PhantomData, } } fn count(&self) -> usize { self.backend.certs().count() } } let backend = store::Certs::empty(); let backend: Box = Box::new(backend); let foo = Foo::new(&backend); // Do something (anything) with the backend. assert_eq!(foo.count(), 0); Ok(()) } #[test] fn is_email() { assert!(UserIDQueryParams::is_email("foo@domain.com").is_ok()); // Need a local part. assert!(UserIDQueryParams::is_email("@domain.com").is_err()); // Need a domain. assert!(UserIDQueryParams::is_email("foo@").is_err()); // One @ assert!(UserIDQueryParams::is_email("foo").is_err()); assert!(UserIDQueryParams::is_email("foo@@domain.com").is_err()); assert!(UserIDQueryParams::is_email("foo@a@domain.com").is_err()); // Bare email address, not wrapped in angle brackets. assert!(UserIDQueryParams::is_email("").is_err()); // Whitespace is not allowed. assert!(UserIDQueryParams::is_email(" foo@domain.com").is_err()); assert!(UserIDQueryParams::is_email("foo o@domain.com").is_err()); assert!(UserIDQueryParams::is_email("foo@do main.com").is_err()); assert!(UserIDQueryParams::is_email("foo@domain.com ").is_err()); } #[test] fn is_domain() { assert!(UserIDQueryParams::is_domain("domain.com").is_ok()); // No at. assert!(UserIDQueryParams::is_domain("foo").is_ok()); assert!(UserIDQueryParams::is_domain("@domain.com").is_err()); assert!(UserIDQueryParams::is_domain("foo@").is_err()); assert!(UserIDQueryParams::is_domain("foo@@domain.com").is_err()); assert!(UserIDQueryParams::is_domain("foo@a@domain.com").is_err()); assert!(UserIDQueryParams::is_domain("").is_err()); } include!("../tests/keyring.rs"); // Check that MergePublicCollectStats works as advertised. #[test] fn store_update_merge_public_collect_stats() { use std::collections::HashSet; use openpgp::Cert; use openpgp::parse::Parse; use crate::CertStore; use crate::store::MergePublicCollectStats; let certs = CertStore::empty(); let mut stats = MergePublicCollectStats::new(); let mut seen = HashSet::new(); for (i, cert) in keyring::certs.iter().enumerate() { let cert = Cert::from_bytes(&cert.bytes()).expect("valid"); let fpr = cert.fingerprint(); seen.insert(fpr.clone()); certs.update_by(Arc::new(LazyCert::from(cert)), &mut stats) .expect("valid"); eprintln!("After inserting {} ({}), stats: {:?}", i, fpr, stats); assert_eq!(stats.new_certs(), seen.len()); assert_eq!(stats.new_certs() + stats.updated_certs() + stats.unchanged_certs(), i + 1); } let new = stats.new_certs(); let updated = stats.updated_certs(); let unchanged = stats.unchanged_certs(); // Insert again. This time nothing should change. for (i, cert) in keyring::certs.iter().enumerate() { let cert = Cert::from_bytes(&cert.bytes()).expect("valid"); let fpr = cert.fingerprint(); certs.update_by(Arc::new(LazyCert::from(cert)), &mut stats) .expect("valid"); eprintln!("After reinserting {} ({}), stats: {:?}", i, fpr, stats); // These should not change: assert_eq!(stats.new_certs(), new); // Update should also not change, but there may be false // positives. assert_eq!(stats.unchanged_certs() + stats.updated_certs(), updated + unchanged + i + 1); } } }