summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNeal H. Walfield <neal@pep.foundation>2024-01-12 08:24:51 +0100
committerNeal H. Walfield <neal@pep.foundation>2024-01-12 08:24:51 +0100
commit07f073e94dcca94a84ec9d3757e3c28cde9db9dc (patch)
tree3846b4fd84b0dc3ff8f70fadb58a5d797b9c2b69
parent2120571f235d3a43d647ab5b5794d1aa77b871ad (diff)
openpgp: Simplify working with third-party certifications.
- Add `ComponentAmalgamation::certifications_by_key`, which returns third-party certifications that appear to be made by a particular key. Specifically, this function only checks that the certificates include a matching issuer or issuer fingerprint subpacket; the certifications are not verified.
-rw-r--r--openpgp/NEWS3
-rw-r--r--openpgp/src/cert/amalgamation.rs124
2 files changed, 126 insertions, 1 deletions
diff --git a/openpgp/NEWS b/openpgp/NEWS
index ceb840f0..96c98873 100644
--- a/openpgp/NEWS
+++ b/openpgp/NEWS
@@ -3,6 +3,9 @@
#+TITLE: sequoia-openpgp NEWS – history of user-visible changes
#+STARTUP: content hidestars
+* Changes in 1.18.0
+** New functionality
+ - ComponentAmalgamation::certifications_by_key
* Changes in 1.17.0
** Notable fixes
- Sequoia now ignores some formatting errors when reading secret
diff --git a/openpgp/src/cert/amalgamation.rs b/openpgp/src/cert/amalgamation.rs
index 7de5908d..6f77e378 100644
--- a/openpgp/src/cert/amalgamation.rs
+++ b/openpgp/src/cert/amalgamation.rs
@@ -230,6 +230,7 @@ use crate::{
cert::prelude::*,
crypto::{Signer, hash::{Hash, Digest}},
Error,
+ KeyHandle,
packet::{
Signature,
Unknown,
@@ -846,6 +847,35 @@ impl<'a, C> ComponentAmalgamation<'a, C> {
self.bundle().certifications().iter()
}
+ /// Returns third-party certifications that appear to issued by
+ /// any of the specified keys.
+ ///
+ /// A certification is returned if one of the provided key handles
+ /// matches an [Issuer subpacket] or [Issuer Fingerprint
+ /// subpacket] in the certification.
+ ///
+ /// [Issuer subpacket]: https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.3.5
+ /// [Issuer Fingerprint subpacket]: https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-13.html#name-issuer-fingerprint
+ ///
+ /// This function does not check that a certification is valid.
+ /// It can't. To check that a certification was actually issued
+ /// by a specific key, we also need a policy and the public key,
+ /// which we don't have. To only get valid certifications, use
+ /// [`UserIDAmalgamation::valid_certifications_by_key`] or
+ /// [`UserIDAmalgamation::active_certifications_by_key`] instead
+ /// of this function.
+ pub fn certifications_by_key<'b>(&'b self, issuers: &'b [ KeyHandle ])
+ -> impl Iterator<Item=&'a Signature> + Send + Sync + 'b
+ {
+ self.certifications().filter(|certification| {
+ certification.get_issuers().into_iter().any(|certification_issuer| {
+ issuers.iter().any(|issuer| {
+ certification_issuer.aliases(issuer)
+ })
+ })
+ })
+ }
+
/// The component's revocations that were issued by the
/// certificate holder.
pub fn self_revocations(&self) -> impl Iterator<Item=&'a Signature> + Send + Sync {
@@ -1851,8 +1881,12 @@ impl<'a, C> crate::cert::Preferences<'a>
#[cfg(test)]
mod test {
+ use super::*;
+
use crate::policy::StandardPolicy as P;
- use crate::cert::prelude::*;
+ use crate::packet::signature::SignatureBuilder;
+ use crate::packet::UserID;
+ use crate::types::SignatureType;
// derive(Clone) doesn't work with generic parameters that don't
// implement clone. Make sure that our custom implementations
@@ -1896,4 +1930,92 @@ mod test {
let _ = cert.user_attributes().map(|ua| ua.user_attribute())
.collect::<Vec<_>>();
}
+
+ #[test]
+ fn component_amalgamation_certifications_by_key() -> Result<()> {
+ // Alice and Bob certify Carol's certificate. We then check
+ // that certifications_by_key returns them.
+ let (alice, _) = CertBuilder::new()
+ .add_userid("<alice@example.example>")
+ .generate()
+ .unwrap();
+
+ let (bob, _) = CertBuilder::new()
+ .add_userid("<bob@example.example>")
+ .generate()
+ .unwrap();
+
+ let carol_userid = "<carol@example.example>";
+ let (carol, _) = CertBuilder::new()
+ .add_userid(carol_userid)
+ .generate()
+ .unwrap();
+
+ let ua = alice.userids().next().expect("have a user id");
+ assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 0);
+
+ // Alice has not certified Bob's User ID.
+ let ua = bob.userids().next().expect("have a user id");
+ assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 0);
+
+ // Alice has not certified Carol's User ID.
+ let ua = carol.userids().next().expect("have a user id");
+ assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 0);
+
+
+ // Have Alice certify Carol's certificate.
+ let mut alice_signer = alice.primary_key()
+ .key()
+ .clone()
+ .parts_into_secret().expect("have unencrypted key material")
+ .into_keypair().expect("have unencrypted key material");
+ let certification = SignatureBuilder::new(SignatureType::GenericCertification)
+ .sign_userid_binding(
+ &mut alice_signer,
+ carol.primary_key().key(),
+ &UserID::from(carol_userid))?;
+ let carol = carol.insert_packets(certification)?;
+
+ // Check that it is returned.
+ let ua = carol.userids().next().expect("have a user id");
+ assert_eq!(ua.certifications().count(), 1);
+ assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 1);
+ assert_eq!(ua.certifications_by_key(&[ bob.key_handle() ]).count(), 0);
+
+
+ // Have Bob certify Carol's certificate.
+ let mut bob_signer = bob.primary_key()
+ .key()
+ .clone()
+ .parts_into_secret().expect("have unencrypted key material")
+ .into_keypair().expect("have unencrypted key material");
+ let certification = SignatureBuilder::new(SignatureType::GenericCertification)
+ .sign_userid_binding(
+ &mut bob_signer,
+ carol.primary_key().key(),
+ &UserID::from(carol_userid))?;
+ let carol = carol.insert_packets(certification)?;
+
+ // Check that it is returned.
+ let ua = carol.userids().next().expect("have a user id");
+ assert_eq!(ua.certifications().count(), 2);
+ assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 1);
+ assert_eq!(ua.certifications_by_key(&[ bob.key_handle() ]).count(), 1);
+
+ // Again.
+ let certification = SignatureBuilder::new(SignatureType::GenericCertification)
+ .sign_userid_binding(
+ &mut bob_signer,
+ carol.primary_key().key(),
+ &UserID::from(carol_userid))?;
+ let carol = carol.insert_packets(certification)?;
+
+ // Check that it is returned.
+ let ua = carol.userids().next().expect("have a user id");
+ assert_eq!(ua.certifications().count(), 3);
+ assert_eq!(ua.certifications_by_key(&[ alice.key_handle() ]).count(), 1);
+ assert_eq!(ua.certifications_by_key(&[ bob.key_handle() ]).count(), 2);
+
+ Ok(())
+ }
}