sequoia-sq/tests/integration/sq_pki_vouch_add.rs
Justus Winter c37bfe5e7b
Rename --notation to --signature-notation.
- This aligns with `sq encrypt --signature-notation` and makes it
    clearer that notations are being put on signatures.

  - Fixes #454.
2024-11-21 11:38:59 +01:00

505 lines
16 KiB
Rust

use std::fs::File;
use std::time;
use std::time::Duration;
use sequoia_openpgp as openpgp;
use openpgp::Result;
use openpgp::KeyHandle;
use openpgp::Packet;
use openpgp::PacketPile;
use openpgp::packet::signature::subpacket::NotationData;
use openpgp::packet::signature::subpacket::NotationDataFlags;
use openpgp::packet::UserID;
use openpgp::parse::Parse;
use openpgp::policy::StandardPolicy;
use openpgp::serialize::{Serialize, SerializeInto};
use super::common::FileOrKeyHandle;
use super::common::Sq;
const P: &StandardPolicy = &StandardPolicy::new();
#[test]
fn sq_pki_vouch_add() -> Result<()> {
let mut sq = Sq::new();
let (alice, alice_pgp, _alice_rev)
= sq.key_generate(&[], &["<alice@example.org>"]);
let (_bob, bob_pgp, _bob_rev)
= sq.key_generate(&[], &["<bob@example.org>"]);
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
let alice_handle: FileOrKeyHandle = if keystore {
alice.key_handle().into()
} else {
alice_pgp.clone().into()
};
let mut bob_pgp = vec![ bob_pgp.clone() ];
let mut certification_count = 0;
// A simple certification.
sq.tick(1);
let bob_pgp_new = sq.scratch_file("bob");
let cert = sq.pki_vouch_add(
&[], &alice_handle, bob_pgp.last().unwrap(), &["<bob@example.org>"],
Some(&*bob_pgp_new));
bob_pgp.push(bob_pgp_new);
certification_count += 1;
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
let vc = cert.with_policy(P, None).unwrap();
let mut ok = false;
for ua in vc.userids() {
if ua.userid().value() == b"<bob@example.org>" {
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(certifications.len(), 1);
let c = certifications[0];
assert_eq!(c.trust_signature(), None);
assert_eq!(c.regular_expressions().count(), 0);
assert_eq!(c.revocable().unwrap_or(true), true);
assert_eq!(c.exportable_certification().unwrap_or(true), true);
// By default, we set a duration.
assert!(c.signature_validity_period().is_some());
ok = true;
break;
}
}
assert!(ok, "Didn't find user id");
// No expiration.
sq.tick(1);
let bob_pgp_new = sq.scratch_file(None);
let cert = sq.pki_vouch_add(
&["--expiration", "never"],
&alice_handle, bob_pgp.last().unwrap(), &["<bob@example.org>"],
Some(&*bob_pgp_new));
bob_pgp.push(bob_pgp_new);
certification_count += 1;
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
let vc = cert.with_policy(P, None).unwrap();
let mut ok = false;
for ua in vc.userids() {
if ua.userid().value() == b"<bob@example.org>" {
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(certifications.len(), certification_count,
"Expected exactly one certification");
let c = certifications[0];
assert_eq!(c.trust_signature(), None);
assert_eq!(c.regular_expressions().count(), 0);
assert_eq!(c.revocable().unwrap_or(true), true);
assert_eq!(c.exportable_certification().unwrap_or(true), true);
assert!(c.signature_validity_period().is_none());
ok = true;
break;
}
}
assert!(ok, "Didn't find user id");
// Have alice certify <bob@example.org> for 0xB0B.
sq.tick(1);
let bob_pgp_new = sq.scratch_file(None);
let cert = sq.pki_vouch_authorize(
&["--depth", "10",
"--amount", "5",
"--regex", "a",
"--regex", "b",
"--local",
"--non-revocable",
"--expiration", "1d",
],
&alice_handle, bob_pgp.last().unwrap(), &["<bob@example.org>"],
Some(&*bob_pgp_new));
bob_pgp.push(bob_pgp_new);
certification_count += 1;
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
let vc = cert.with_policy(P, None).unwrap();
let mut ok = false;
for ua in vc.userids() {
if ua.userid().value() == b"<bob@example.org>" {
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(certifications.len(), certification_count);
let c = certifications[0];
assert_eq!(c.trust_signature(), Some((10, 5)));
assert_eq!(&c.regular_expressions().collect::<Vec<_>>()[..],
&[ b"a", b"b" ]);
assert_eq!(c.revocable(), Some(false));
assert_eq!(c.exportable_certification(), Some(false));
assert_eq!(c.signature_validity_period(),
Some(Duration::new(24 * 60 * 60, 0)));
ok = true;
break;
}
}
assert!(ok, "Didn't find user id");
// It should fail if the User ID doesn't exist.
assert!(sq.try_pki_vouch_add(
&[], &alice_handle, bob_pgp.last().unwrap(), &["bob"],
None, false).is_err());
// With a notation.
sq.tick(1);
let bob_pgp_new = sq.scratch_file(None);
let cert = sq.pki_vouch_add(
&[
"--signature-notation", "foo", "bar",
"--signature-notation", "!foo", "xyzzy",
"--signature-notation", "hello@example.org", "1234567890",
],
&alice_handle, bob_pgp.last().unwrap(), &["<bob@example.org>"],
Some(&*bob_pgp_new));
bob_pgp.push(bob_pgp_new);
certification_count += 1;
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
// The standard policy will reject the
// certification, because it has an unknown
// critical notation.
let vc = cert.with_policy(P, None).unwrap();
for ua in vc.userids() {
if ua.userid().value() == b"<bob@example.org>" {
assert_eq!(
ua.bundle().certifications2().count(),
certification_count);
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(
certifications.len(),
// Subtract the bad one.
certification_count - 1);
}
}
// Accept the critical notation.
let p = &mut StandardPolicy::new();
p.good_critical_notations(&["foo"]);
let vc = cert.with_policy(p, None).unwrap();
let mut ok = false;
for ua in vc.userids() {
if ua.userid().value() == b"<bob@example.org>" {
assert_eq!(ua.bundle().certifications2().count(),
certification_count);
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(certifications.len(), certification_count);
let c = certifications[0];
assert_eq!(c.trust_signature(), None);
assert_eq!(c.regular_expressions().count(), 0);
assert_eq!(c.revocable().unwrap_or(true), true);
assert_eq!(c.exportable_certification().unwrap_or(true), true);
// By default, we set a duration.
assert!(c.signature_validity_period().is_some());
let hr = NotationDataFlags::empty().set_human_readable();
let notations = &mut [
(NotationData::new("foo", "bar", hr.clone()), false),
(NotationData::new("foo", "xyzzy", hr.clone()), false),
(NotationData::new("hello@example.org", "1234567890", hr), false)
];
for n in c.notation_data() {
if n.name() == "salt@notations.sequoia-pgp.org" {
continue;
}
for (m, found) in notations.iter_mut() {
if n == m {
assert!(!*found);
*found = true;
}
}
}
for (n, found) in notations.iter() {
assert!(found, "Missing: {:?}", n);
}
ok = true;
break;
}
}
assert!(ok, "Didn't find user id");
}
Ok(())
}
#[test]
fn sq_pki_vouch_add_creation_time() -> Result<()>
{
// $ date +'%Y%m%dT%H%M%S%z'; date +'%s'
let t = 1642692756;
let t = time::UNIX_EPOCH + time::Duration::new(t, 0);
let sq = Sq::at(t);
let alice = "<alice@example.org>";
let (alice_key, alice_pgp, _alice_rev)
= sq.key_generate(&[], &[ alice ]);
let bob = "<bob@other.org>";
let (_bob_key, bob_pgp, _bob_rev)
= sq.key_generate(&[], &[ bob ]);
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
let alice_handle: FileOrKeyHandle = if keystore {
alice_key.key_handle().into()
} else {
alice_pgp.clone().into()
};
// Alice certifies bob's key.
let cert = sq.pki_vouch_add(
&[], &alice_handle, &bob_pgp, &[bob], None);
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
let vc = cert.with_policy(P, t)?;
assert_eq!(vc.primary_key().creation_time(), t);
let mut userid = None;
for u in vc.userids() {
if u.userid().value() == bob.as_bytes() {
userid = Some(u);
break;
}
}
if let Some(userid) = userid {
let certifications: Vec<_> = userid.certifications().collect();
assert_eq!(certifications.len(), 1);
let certification = certifications.into_iter().next().unwrap();
assert_eq!(certification.get_issuers().into_iter().next(),
Some(KeyHandle::from(alice_key.fingerprint())));
assert_eq!(certification.signature_creation_time(), Some(t));
} else {
panic!("missing user id");
}
}
Ok(())
}
#[test]
fn sq_pki_vouch_add_with_expired_key() -> Result<()>
{
let seconds_in_day = 24 * 60 * 60;
// Alice's certificate expires in 30 days.
let validity_seconds = 30 * seconds_in_day;
let validity = time::Duration::new(validity_seconds, 0);
let creation_time = time::SystemTime::now() - 2 * validity;
let mut sq = Sq::at(creation_time);
let alice = "<alice@example.org>";
let (alice_key, alice_pgp, _) = sq.key_generate(
&["--expiration", &format!("{}s", validity_seconds) ],
&[ alice ]);
// Bob's certificate has the same creation time, but it does not
// expire.
let bob = "<bob@other.org>";
let (_bob_key, bob_pgp, _) = sq.key_generate(&[], &[ bob ]);
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
let alice_handle: FileOrKeyHandle = if keystore {
alice_key.key_handle().into()
} else {
alice_pgp.clone().into()
};
// Alice's expired key certifies bob's not expired key.
sq.tick(validity_seconds + 1);
// Make sure using an expired key fails by default.
assert!(sq.try_pki_vouch_add(
&[], &alice_handle, &bob_pgp, &[bob], Some(&*bob_pgp), false).is_err());
}
Ok(())
}
#[test]
fn sq_pki_vouch_add_with_revoked_key() -> Result<()>
{
let seconds_in_day = 24 * 60 * 60;
let delta = seconds_in_day;
let creation_time =
time::SystemTime::now() - time::Duration::new(delta, 0);
let mut sq = Sq::at(creation_time);
// Create a certificate for alice and immediately revoke it.
let alice = "<alice@example.org>";
let (alice_key, alice_pgp, revocation)
= sq.key_generate(&[], &[ alice ]);
let revocation = PacketPile::from_file(revocation)
.expect("can parse revocation certificate");
let revocation = revocation
.descendants()
.filter(|p| {
if let Packet::Signature(_) = p {
true
} else {
false
}
})
.cloned().collect::<Vec<_>>();
let alice_key = alice_key.insert_packets(revocation)?;
{
let mut file = File::create(&alice_pgp)?;
alice_key.as_tsk().serialize(&mut file)?;
}
eprintln!("Alice:\n{}", sq.inspect(&alice_pgp));
let bob = "<bob@other.org>";
let (_bob_key, bob_pgp, _) = sq.key_generate(&[], &[ bob ]);
eprintln!("Bob:\n{}", sq.inspect(&bob_pgp));
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
let alice_handle: FileOrKeyHandle = if keystore {
alice_key.key_handle().into()
} else {
alice_pgp.clone().into()
};
sq.tick(delta);
// Make sure using an expired key fails by default.
assert!(sq.try_pki_vouch_add(
&[], &alice_handle, &bob_pgp, &[bob], None, false).is_err());
}
Ok(())
}
// Certify a certificate in the cert store.
#[test]
fn sq_pki_vouch_add_using_cert_store() -> Result<()>
{
let mut sq = Sq::new();
let (alice_key, alice_pgp, _alice_rev)
= sq.key_generate(&[], &["<alice@example.org>"]);
let (bob_key, bob_pgp, _bob_rev)
= sq.key_generate(&[], &["<bob@example.org>"]);
// Import bob's (but not yet alice's!).
sq.cert_import(&bob_pgp);
let mut certification_count = 0;
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
let alice_handle: FileOrKeyHandle = if keystore {
alice_key.key_handle().into()
} else {
alice_pgp.clone().into()
};
sq.tick(1);
// Have alice certify bob.
let found = sq.pki_vouch_add(
&[], &alice_handle,
bob_key.key_handle(), &["<bob@example.org>"],
None);
certification_count += 1;
// Make sure the certificate on stdout is bob and that alice
// signed it.
assert_eq!(found.fingerprint(), bob_key.fingerprint());
assert_eq!(found.userids().count(), 1);
let ua = found.userids().next().expect("have one");
let certifications: Vec<_> = ua.certifications().collect();
assert_eq!(certifications.len(), certification_count);
let certification = certifications.into_iter().next().unwrap();
assert_eq!(certification.get_issuers().into_iter().next(),
Some(KeyHandle::from(alice_key.fingerprint())));
certification.clone().verify_userid_binding(
alice_key.primary_key().key(),
bob_key.primary_key().key(),
&UserID::from("<bob@example.org>"))
.expect("valid certification");
}
Ok(())
}
// Make sure we reject making self signatures.
#[test]
fn sq_pki_vouch_add_no_self_signatures() -> Result<()>
{
let sq = Sq::new();
let alice_userid = "<alice@example.org>";
let (alice, alice_pgp, _alice_rev)
= sq.key_generate(&[], &[alice_userid]);
sq.key_import(&alice_pgp);
let r = sq.try_pki_vouch_add(
&[], &alice.key_handle(),
alice.key_handle(), &[alice_userid],
None,
false);
assert!(r.is_err());
Ok(())
}