GitLab Commit is coming up on August 3-4. Learn how to innovate together using GitLab, the DevOps platform. Register for free: gitlabcommitvirtual2021.com

Commit 39c0e12a authored by Justus Winter's avatar Justus Winter Committed by Vincent Breitmoser
Browse files

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.
parent 3ecd264c
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment