database: serve first-party attested third-party certifications
This implements support for third-party userid certifications. To prevent denial-of-service attacks, we only merge those certifications that are attested by the key holder. The key holder attests the certifications using an Attested Key Signature containing the digests of the certifications in an Attested Certifications subpacket as specified in RFC4880bis-10. Fixes #124.
This commit is contained in:
parent
3ecd264c59
commit
39c0e12ac6
|
@ -185,11 +185,6 @@ name = "base64"
|
|||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.51.1"
|
||||
|
@ -274,7 +269,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "buffered-reader"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2072,8 +2067,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
dependencies = [
|
||||
"anyhow 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"backtrace 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"buffered-reader 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"buffered-reader 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dyn-clone 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"eax 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -2778,7 +2773,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
|
||||
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
||||
"checksum base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||
"checksum base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
|
||||
"checksum bindgen 0.51.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ebd71393f1ec0509b553aa012b9b58e81dadbdff7130bd3b8cba576e69b32f75"
|
||||
"checksum bit-set 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
|
||||
|
@ -2789,7 +2783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
|
||||
"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||
"checksum buf_redux 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
|
||||
"checksum buffered-reader 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5711ccfa79a8167779ad2176d3334078f03b1579ddf8f42aa556196eba60a42"
|
||||
"checksum buffered-reader 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5f76f15096822ca97dcc626a98ce3eb93c8afc795f33994a63e8d4ed767007e4"
|
||||
"checksum bumpalo 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
||||
"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
|
|
@ -928,4 +928,12 @@ mod tests {
|
|||
Some(fp.clone()));
|
||||
db.check_consistency().expect("inconsistent database");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn attested_key_signatures() -> Result<()> {
|
||||
let (_tmp_dir, mut db, log_path) = open_db();
|
||||
test::attested_key_signatures(&mut db, &log_path)?;
|
||||
db.check_consistency()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ use std::convert::TryFrom;
|
|||
use openpgp::{
|
||||
Cert,
|
||||
types::RevocationStatus,
|
||||
cert::prelude::*,
|
||||
serialize::SerializeInto as _,
|
||||
policy::StandardPolicy,
|
||||
};
|
||||
|
@ -50,6 +51,17 @@ pub fn tpk_clean(tpk: &Cert) -> Result<Cert> {
|
|||
for s in uidb.self_signatures() { acc.push(s.clone().into()) }
|
||||
for s in uidb.self_revocations() { acc.push(s.clone().into()) }
|
||||
for s in uidb.other_revocations() { acc.push(s.clone().into()) }
|
||||
|
||||
// Reasoning about the currently attested certifications
|
||||
// requires a policy.
|
||||
if let Ok(vuid) = uidb.with_policy(&POLICY, None) {
|
||||
for s in vuid.attestation_key_signatures() {
|
||||
acc.push(s.clone().into());
|
||||
}
|
||||
for s in vuid.attested_certifications() {
|
||||
acc.push(s.clone().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cert::from_packets(acc.into_iter())
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::str::FromStr;
|
||||
use anyhow::Result;
|
||||
|
||||
use Database;
|
||||
use Query;
|
||||
|
@ -1117,6 +1118,124 @@ pub fn test_no_selfsig(db: &mut impl Database, log_path: &Path) {
|
|||
}, tpk_status);
|
||||
}
|
||||
|
||||
/// Makes sure that attested key signatures are correctly handled.
|
||||
pub fn attested_key_signatures(db: &mut impl Database, log_path: &Path)
|
||||
-> Result<()> {
|
||||
use std::time::{SystemTime, Duration};
|
||||
use openpgp::{
|
||||
packet::signature::SignatureBuilder,
|
||||
types::*,
|
||||
};
|
||||
let t0 = SystemTime::now() - Duration::new(5 * 60, 0);
|
||||
let t1 = SystemTime::now() - Duration::new(4 * 60, 0);
|
||||
|
||||
let (alice, _) = CertBuilder::new()
|
||||
.set_creation_time(t0)
|
||||
.add_userid("alice@foo.com")
|
||||
.generate()?;
|
||||
let mut alice_signer =
|
||||
alice.primary_key().key().clone().parts_into_secret()?
|
||||
.into_keypair()?;
|
||||
|
||||
let (bob, _) = CertBuilder::new()
|
||||
.set_creation_time(t0)
|
||||
.add_userid("bob@bar.com")
|
||||
.generate()?;
|
||||
let bobs_fp = Fingerprint::try_from(bob.fingerprint())?;
|
||||
let mut bob_signer =
|
||||
bob.primary_key().key().clone().parts_into_secret()?
|
||||
.into_keypair()?;
|
||||
|
||||
// Have Alice certify the binding between "bob@bar.com" and
|
||||
// Bob's key.
|
||||
let alice_certifies_bob
|
||||
= bob.userids().nth(0).unwrap().userid().bind(
|
||||
&mut alice_signer, &bob,
|
||||
SignatureBuilder::new(SignatureType::GenericCertification)
|
||||
.set_signature_creation_time(t1)?)?;
|
||||
|
||||
// Have Bob attest that certification.
|
||||
let attestations =
|
||||
bob.userids().next().unwrap().attest_certifications(
|
||||
&POLICY,
|
||||
&mut bob_signer,
|
||||
vec![&alice_certifies_bob])?;
|
||||
assert_eq!(attestations.len(), 1);
|
||||
let attestation = attestations[0].clone();
|
||||
|
||||
// Now for the test. First, import Bob's cert as is.
|
||||
db.merge(bob.clone())?;
|
||||
check_log_entry(log_path, &bobs_fp);
|
||||
|
||||
// Confirm the email so that we can inspect the userid component.
|
||||
db.set_email_published(&bobs_fp, &Email::from_str("bob@bar.com")?)?;
|
||||
|
||||
// Then, add the certification, merge into the db, check that the
|
||||
// certification is stripped.
|
||||
let bob = bob.insert_packets(vec![
|
||||
alice_certifies_bob.clone(),
|
||||
])?;
|
||||
db.merge(bob.clone())?;
|
||||
check_log_entry(log_path, &bobs_fp);
|
||||
let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?;
|
||||
assert_eq!(bob_.bad_signatures().count(), 0);
|
||||
assert_eq!(bob_.userids().nth(0).unwrap().certifications().count(), 0);
|
||||
|
||||
// Add the attestation, merge into the db, check that the
|
||||
// certification is now included.
|
||||
let bob_attested = bob.clone().insert_packets(vec![
|
||||
attestation.clone(),
|
||||
])?;
|
||||
db.merge(bob_attested.clone())?;
|
||||
check_log_entry(log_path, &bobs_fp);
|
||||
let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?;
|
||||
assert_eq!(bob_.bad_signatures().count(), 0);
|
||||
assert_eq!(bob_.userids().nth(0).unwrap().certifications().count(), 1);
|
||||
assert_eq!(bob_.with_policy(&POLICY, None)?
|
||||
.userids().nth(0).unwrap().attestation_key_signatures().count(), 1);
|
||||
assert_eq!(bob_.with_policy(&POLICY, None)?
|
||||
.userids().nth(0).unwrap().attested_certifications().count(), 1);
|
||||
|
||||
// Make a random merge with Bob's unattested cert, demonstrating
|
||||
// that the attestation still works.
|
||||
db.merge(bob.clone())?;
|
||||
check_log_entry(log_path, &bobs_fp);
|
||||
let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?;
|
||||
assert_eq!(bob_.bad_signatures().count(), 0);
|
||||
assert_eq!(bob_.userids().nth(0).unwrap().certifications().count(), 1);
|
||||
|
||||
// Finally, withdraw consent by overriding the attestation, merge
|
||||
// into the db, check that the certification is now gone.
|
||||
let attestations =
|
||||
bob_attested.userids().next().unwrap().attest_certifications(
|
||||
&POLICY,
|
||||
&mut bob_signer,
|
||||
&[])?;
|
||||
assert_eq!(attestations.len(), 1);
|
||||
let clear_attestation = attestations[0].clone();
|
||||
|
||||
let bob = bob.insert_packets(vec![
|
||||
clear_attestation.clone(),
|
||||
])?;
|
||||
assert_eq!(bob.userids().nth(0).unwrap().certifications().count(), 1);
|
||||
assert_eq!(bob.with_policy(&POLICY, None)?
|
||||
.userids().nth(0).unwrap().attestation_key_signatures().count(), 1);
|
||||
assert_eq!(bob.with_policy(&POLICY, None)?
|
||||
.userids().nth(0).unwrap().attested_certifications().count(), 0);
|
||||
|
||||
db.merge(bob.clone())?;
|
||||
check_log_entry(log_path, &bobs_fp);
|
||||
let bob_ = Cert::from_bytes(&db.by_fpr(&bobs_fp).unwrap())?;
|
||||
assert_eq!(bob_.bad_signatures().count(), 0);
|
||||
assert_eq!(bob_.userids().nth(0).unwrap().certifications().count(), 0);
|
||||
assert_eq!(bob_.with_policy(&POLICY, None)?
|
||||
.userids().nth(0).unwrap().attestation_key_signatures().count(), 1);
|
||||
assert_eq!(bob_.with_policy(&POLICY, None)?
|
||||
.userids().nth(0).unwrap().attested_certifications().count(), 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_log_entry(log_path: &Path, fpr: &Fingerprint) {
|
||||
let log_data = fs::read_to_string(log_path).unwrap();
|
||||
let last_entry = log_data
|
||||
|
|
Loading…
Reference in New Issue