Don't create a provenence record when it already exists

- When importing a certificate downloaded from a known verifying
    keyserver, a WKD or DANE, we certify the User IDs that the server
    (probably) authenticated.

  - If we download the certificate again from the same source, don't
    create another certification.  That's just redundant.
This commit is contained in:
Neal H. Walfield 2023-03-30 09:21:15 +02:00
parent 427487b76c
commit ee96205df9
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
2 changed files with 114 additions and 7 deletions

View File

@ -16,13 +16,14 @@ use openpgp::types::{
};
use openpgp::cert::prelude::*;
use openpgp::crypto;
use openpgp::{Cert, KeyID, Result};
use openpgp::{Cert, Fingerprint, KeyID, Result};
use openpgp::packet::prelude::*;
use openpgp::parse::{
Parse,
PacketParserResult,
};
use openpgp::parse::stream::*;
use openpgp::policy::HashAlgoSecurity;
use openpgp::serialize::stream::{
Message, Signer, LiteralWriter, Encryptor, Recipient,
Compressor,
@ -258,6 +259,96 @@ fn get_certification_keys<C>(certs: &[C], p: &dyn Policy,
options)
}
/// Returns the active certification, if any, for the specified bindings.
///
/// The certificate is looked up in the certificate store.
///
/// Note: if `n` User IDs are provided, then the returned vector has
/// `n` elements.
fn active_certification(config: &Config,
cert: &Fingerprint, userids: Vec<UserID>,
issuer: &Key<openpgp::packet::key::PublicParts,
openpgp::packet::key::UnspecifiedRole>)
-> Vec<(UserID, Option<Signature>)>
{
// Look up the cert and find the certifications for the specified
// User ID, if any.
let lc = config.cert_store_or_else()
.and_then(|cert_store| cert_store.lookup_by_cert_fpr(cert));
let lc = match lc {
Ok(lc) => lc,
Err(_) => {
return userids.into_iter().map(|userid| (userid, None)).collect();
}
};
let cert = match lc.to_cert() {
Ok(cert) => cert,
Err(_) => {
return userids.into_iter().map(|userid| (userid, None)).collect();
}
};
let issuer_kh = issuer.key_handle();
userids.into_iter().map(|userid| {
let ua = match cert.userids()
.filter(|ua| ua.userid() == &userid).next()
{
Some(ua) => ua,
None => return (userid, None),
};
// Get certifications that:
//
// - Have a creation time,
// - Are not younger than the reference time,
// - Are not expired,
// - Alias the issuer, and
// - Satisfy the policy.
let mut certifications = ua.bundle().certifications()
.iter()
.filter(|sig| {
if let Some(ct) = sig.signature_creation_time() {
ct <= config.time
&& sig.signature_validity_period()
.map(|vp| {
config.time < ct + vp
})
.unwrap_or(true)
&& sig.get_issuers().iter().any(|i| i.aliases(&issuer_kh))
&& config.policy.signature(
sig, HashAlgoSecurity::CollisionResistance).is_ok()
} else {
false
}
})
.collect::<Vec<&Signature>>();
// Sort so the newest signature is first.
certifications.sort_unstable_by(|a, b| {
a.signature_creation_time().unwrap()
.cmp(&b.signature_creation_time().unwrap())
.reverse()
.then(a.mpis().cmp(&b.mpis()))
});
// Return the first valid signature, which is the most recent one
// that is no younger than config.time.
let pk = ua.cert().primary_key().key();
let certification = certifications.into_iter()
.filter_map(|sig| {
let mut sig = sig.clone();
if sig.verify_userid_binding(issuer, pk, &userid).is_ok() {
Some(sig)
} else {
None
}
})
.next();
(userid, certification)
}).collect()
}
// Returns the smallest valid certificate.
//
// Given a certificate, returns the smallest valid certificate that is

View File

@ -36,7 +36,10 @@ use cert_store::StoreUpdate;
use cert_store::store::UserIDQueryParams;
use crate::{
commands::get_certification_keys,
commands::{
active_certification,
get_certification_keys,
},
Config,
Model,
open_or_stdin,
@ -102,7 +105,8 @@ fn import_certs(config: &mut Config, certs: Vec<Cert>) -> Result<()> {
///
/// This does not import the certification or the certificate into
/// the certificate store.
fn certify(signer: &mut dyn Signer, cert: &Cert, userids: &[UserID],
fn certify(config: &Config,
signer: &mut dyn Signer, cert: &Cert, userids: &[UserID],
creation_time: Option<SystemTime>, depth: u8, amount: usize)
-> Result<Cert>
{
@ -118,8 +122,20 @@ fn certify(signer: &mut dyn Signer, cert: &Cert, userids: &[UserID],
builder = builder.set_signature_creation_time(creation_time)?;
}
let certifications = userids.iter()
.map(|userid| {
let certifications = active_certification(
config, &cert.fingerprint(),
userids.iter().cloned().collect(),
signer.public())
.into_iter()
.map(|(userid, active_certification)| {
if let Some(_) = active_certification {
eprintln!("Provenance information for {}, {:?} \
exists and is current, not updating it",
cert.fingerprint(),
String::from_utf8_lossy(userid.value()));
return vec![];
}
match builder.clone().sign_userid_binding(
signer,
cert.primary_key().key(),
@ -193,7 +209,7 @@ fn get_ca(config: &mut Config,
assert_eq!(signers.len(), 1);
let mut signer = signers.into_iter().next().unwrap();
match certify(&mut signer, &ca, &[UserID::from(ca_userid)],
match certify(config, &mut signer, &ca, &[UserID::from(ca_userid)],
Some(config.time), 1, ca_trust_amount)
{
Err(err) => {
@ -308,7 +324,7 @@ fn certify_downloads(config: &mut Config,
};
match certify(
&mut ca_signer, &cert, &userids[..],
config, &mut ca_signer, &cert, &userids[..],
Some(config.time), 0, sequoia_wot::FULLY_TRUSTED)
{
Ok(cert) => cert,