Change the sq pki certify tests to use the common test framework.

This commit is contained in:
Neal H. Walfield 2024-06-05 12:48:12 +02:00
parent c8c7e24fe9
commit 028983d40f
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
2 changed files with 371 additions and 403 deletions

View File

@ -3,6 +3,8 @@
use std::path::Path;
use std::path::PathBuf;
use std::process::Output;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use anyhow::anyhow;
use assert_cmd::Command;
@ -100,6 +102,7 @@ pub struct Sq {
base: TempDir,
home: PathBuf,
now: std::time::SystemTime,
scratch: AtomicUsize,
}
impl Sq {
@ -114,6 +117,7 @@ impl Sq {
base,
home,
now,
scratch: 0.into(),
}
}
@ -141,6 +145,49 @@ impl Sq {
&self.home
}
/// Returns the scratch directory.
pub fn scratch_dir(&self) -> PathBuf {
let dir = self.home.join("scratch");
std::fs::create_dir_all(&dir)
.expect("can create scratch directory");
dir
}
/// Returns a new scratch file.
///
/// The file is guaranteed to not exist, but it isn't actually
/// created.
pub fn scratch_file<'a, S>(&self, name: S) -> PathBuf
where S: Into<Option<&'a str>>
{
let name = name.into();
let name_;
let name = if let Some(name) = name {
name_ = name.chars()
.map(|c| {
if c.is_ascii_alphanumeric() {
c
} else {
'-'
}
})
.collect::<String>();
&name_
} else {
name.unwrap_or("scratch-file")
};
let dir = self.scratch_dir();
loop {
let i = self.scratch.fetch_add(1, Ordering::Relaxed);
let file = dir.join(format!("{}-{}", i, name));
if ! file.exists() {
return file;
}
}
}
/// Returns the current time.
pub fn now(&self) -> std::time::SystemTime {
self.now
@ -195,6 +242,49 @@ impl Sq {
output
}
/// Generates a new key.
///
/// The certificate is not imported into the cert store or key
/// store, but saved in a file.
///
/// Returns the certificate, the certificate's filename, and the
/// revocation certificate's filename.
pub fn key_generate(&self,
extra_args: &[&str],
userids: &[&str])
-> (Cert, PathBuf, PathBuf)
{
let mut cmd = self.command();
cmd.args([ "key", "generate" ]);
for arg in extra_args {
cmd.arg(arg);
}
if userids.is_empty() {
cmd.arg("--no-userids");
} else {
for userid in userids {
cmd.arg("--userid").arg(userid);
}
}
let cert_filename = self.scratch_file(
userids.get(0).map(|u| format!("{}-cert", u)).as_deref());
cmd.arg("--output").arg(&cert_filename);
let rev_filename = self.scratch_file(
userids.get(0).map(|u| format!("{}-rev", u)).as_deref());
cmd.arg("--rev-cert").arg(&rev_filename);
let output = self.run(cmd, Some(true));
let cert = Cert::from_file(&cert_filename)
.expect("can parse certificate");
assert!(cert.is_tsk());
(cert, cert_filename, rev_filename)
}
/// Run `sq inspect` and return stdout.
pub fn inspect<H>(&self, handle: H) -> String
where H: Into<FileOrKeyHandle>
{
@ -222,6 +312,15 @@ impl Sq {
self.run(cmd, Some(true));
}
/// Imports the specified certificate into the keystore.
pub fn cert_import<P>(&self, path: P)
where P: AsRef<Path>
{
let mut cmd = self.command();
cmd.arg("cert").arg("import").arg(path.as_ref());
self.run(cmd, Some(true));
}
/// Exports the specified certificate.
pub fn cert_export(&self, kh: KeyHandle) -> Cert {
let mut cmd = self.command();
@ -231,6 +330,50 @@ impl Sq {
Cert::from_bytes(&output.stdout)
.expect("can parse certificate")
}
/// Try to certify the user ID binding.
pub fn pki_certify_p<P, Q>(&self, extra_args: &[&str],
certifier: P,
cert: Q,
userid: &str,
success: bool)
-> Result<Cert>
where P: AsRef<Path>, Q: AsRef<Path>
{
let certifier = certifier.as_ref();
let cert = cert.as_ref();
let mut cmd = self.command();
cmd.args([ "pki", "certify" ]);
for arg in extra_args {
cmd.arg(arg);
}
cmd.arg("--certifier-file").arg(certifier)
.arg(cert).arg(userid)
.arg("--output").arg("-");
let output = self.run(cmd, Some(success));
if output.status.success() {
Ok(Cert::from_bytes(&output.stdout)
.expect("can parse certificate"))
} else {
Err(anyhow::anyhow!(format!(
"Failed (expected):\n{}",
String::from_utf8_lossy(&output.stderr))))
}
}
/// Certify the user ID binding.
pub fn pki_certify<P, Q>(&self, extra_args: &[&str],
certifier: P,
cert: Q,
userid: &str)
-> Cert
where P: AsRef<Path>, Q: AsRef<Path>
{
self.pki_certify_p(extra_args, certifier, cert, userid, true)
.expect("success")
}
}
/// Generate a new key in a temporary directory and return its TempDir,

View File

@ -2,14 +2,11 @@ use std::fs::File;
use std::time;
use std::time::Duration;
use tempfile::TempDir;
use assert_cmd::Command;
use predicates::prelude::*;
use sequoia_openpgp as openpgp;
use openpgp::Result;
use openpgp::cert::prelude::*;
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;
@ -17,252 +14,197 @@ use openpgp::parse::Parse;
use openpgp::policy::StandardPolicy;
use openpgp::serialize::{Serialize, SerializeInto};
mod common;
use common::Sq;
const P: &StandardPolicy = &StandardPolicy::new();
#[test]
fn sq_certify() -> Result<()> {
let tmp_dir = TempDir::new().unwrap();
let alice_pgp = tmp_dir.path().join("alice.pgp");
let bob_pgp = tmp_dir.path().join("bob.pgp");
let (alice, _) =
CertBuilder::general_purpose(None, Some("alice@example.org"))
.generate()?;
let mut file = File::create(&alice_pgp)?;
alice.as_tsk().serialize(&mut file)?;
let (bob, _) =
CertBuilder::general_purpose(None, Some("bob@example.org"))
.generate()?;
let mut file = File::create(&bob_pgp)?;
bob.serialize(&mut file)?;
let 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>"]);
// A simple certification.
Command::cargo_bin("sq")
.unwrap()
.arg("--no-cert-store")
.arg("--no-key-store")
.arg("pki").arg("certify")
.arg("--certifier-file").arg(alice_pgp.to_str().unwrap())
.arg(bob_pgp.to_str().unwrap())
.arg("bob@example.org")
.assert()
.success()
.stdout(predicate::function(|output: &[u8]| -> bool {
let cert = Cert::from_bytes(output).unwrap();
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
let cert = sq.pki_certify(&[], &alice_pgp, &bob_pgp, "<bob@example.org>");
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 vc = cert.with_policy(P, None).unwrap();
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];
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());
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());
return true;
}
}
false
},
));
ok = true;
break;
}
}
assert!(ok, "Didn't find user id");
// No expiry.
Command::cargo_bin("sq")
.unwrap()
.arg("--no-cert-store")
.arg("--no-key-store")
.arg("pki").arg("certify")
.arg("--certifier-file").arg(alice_pgp.to_str().unwrap())
.arg(bob_pgp.to_str().unwrap())
.arg("bob@example.org")
.args(["--expiry", "never"])
.assert()
.success()
.stdout(predicate::function(|output: &[u8]| -> bool {
let cert = Cert::from_bytes(output).unwrap();
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
let cert = sq.pki_certify(&["--expiry", "never"],
&alice_pgp, &bob_pgp, "<bob@example.org>");
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 vc = cert.with_policy(P, None).unwrap();
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];
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);
assert!(c.signature_validity_period().is_none());
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());
return true;
}
}
ok = true;
break;
}
}
assert!(ok, "Didn't find user id");
false
}));
// Have alice certify <bob@example.org> for 0xB0B.
let cert = sq.pki_certify(
&["--depth", "10",
"--amount", "5",
"--regex", "a",
"--regex", "b",
"--local",
"--non-revocable",
"--expiry", "1d",
],
&alice_pgp, &bob_pgp, "<bob@example.org>");
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
// Have alice certify bob@example.org for 0xB0B.
Command::cargo_bin("sq")
.unwrap()
.arg("--no-cert-store")
.arg("--no-key-store")
.arg("pki").arg("certify")
.arg("--certifier-file").arg(alice_pgp.to_str().unwrap())
.arg(bob_pgp.to_str().unwrap())
.arg("bob@example.org")
.args(["--depth", "10"])
.args(["--amount", "5"])
.args(["--regex", "a"])
.args(["--regex", "b"])
.arg("--local")
.arg("--non-revocable")
.args(["--expiry", "1d"])
.assert()
.success()
.stdout(predicate::function(|output: &[u8]| -> bool {
let cert = Cert::from_bytes(output).unwrap();
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 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];
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(), 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)));
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)));
return true;
}
}
false
}));
ok = true;
break;
}
}
assert!(ok, "Didn't find user id");
// It should fail if the User ID doesn't exist.
Command::cargo_bin("sq")
.unwrap()
.arg("--no-cert-store")
.arg("--no-key-store")
.arg("pki").arg("certify")
.arg("--certifier-file").arg(alice_pgp.to_str().unwrap())
.arg(bob_pgp.to_str().unwrap())
.arg("bob")
.assert()
.failure();
assert!(sq.pki_certify_p(&[], &alice_pgp, &bob_pgp, "bob", false).is_err());
// With a notation.
Command::cargo_bin("sq")
.unwrap()
.arg("--no-cert-store")
.arg("--no-key-store")
.arg("pki").arg("certify")
.args(["--notation", "foo", "bar"])
.args(["--notation", "!foo", "xyzzy"])
.args(["--notation", "hello@example.org", "1234567890"])
.arg("--certifier-file").arg(alice_pgp.to_str().unwrap())
.arg(bob_pgp.to_str().unwrap())
.arg("bob@example.org")
.assert()
.success()
.stdout(predicate::function(|output: &[u8]| -> bool {
let cert = Cert::from_bytes(output).unwrap();
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
let cert = sq.pki_certify(
&[
"--notation", "foo", "bar",
"--notation", "!foo", "xyzzy",
"--notation", "hello@example.org", "1234567890",
],
&alice_pgp, &bob_pgp, "<bob@example.org>");
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().certifications().len(), 1);
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(certifications.len(), 0);
// 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().certifications().len(), 1);
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(certifications.len(), 0);
}
}
// 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>" {
// There should be a single signature.
assert_eq!(ua.bundle().certifications().len(), 1);
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());
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;
}
}
}
// Accept the critical notation.
let p = &mut StandardPolicy::new();
p.good_critical_notations(&["foo"]);
let vc = cert.with_policy(p, None).unwrap();
for ua in vc.userids() {
if ua.userid().value() == b"bob@example.org" {
// There should be a single signature.
assert_eq!(ua.bundle().certifications().len(), 1);
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());
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);
}
return true;
}
for (n, found) in notations.iter() {
assert!(found, "Missing: {:?}", n);
}
false
}));
ok = true;
break;
}
}
assert!(ok, "Didn't find user id");
Ok(())
}
@ -271,53 +213,24 @@ fn sq_certify() -> Result<()> {
fn sq_certify_creation_time() -> Result<()>
{
// $ date +'%Y%m%dT%H%M%S%z'; date +'%s'
let iso8601 = "20220120T163236+0100";
let t = 1642692756;
let t = time::UNIX_EPOCH + time::Duration::new(t, 0);
let dir = TempDir::new()?;
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 ]);
let gen = |userid: &str| {
let builder = CertBuilder::new()
.add_signing_subkey()
.set_creation_time(t)
.add_userid(userid);
builder.generate().map(|(key, _rev)| key)
};
// Alice certifies bob's key.
let alice = "<alice@example.org>";
let alice_key = gen(alice)?;
let cert = sq.pki_certify(&[], &alice_pgp, &bob_pgp, bob);
let alice_pgp = dir.path().join("alice.pgp");
{
let mut file = File::create(&alice_pgp)?;
alice_key.as_tsk().serialize(&mut file)?;
}
let bob = "<bob@other.org>";
let bob_key = gen(bob)?;
let bob_pgp = dir.path().join("bob.pgp");
{
let mut file = File::create(&bob_pgp)?;
bob_key.serialize(&mut file)?;
}
// Build up the command line.
let mut cmd = Command::cargo_bin("sq")?;
cmd.args(["--no-cert-store",
"--no-key-store",
"pki", "certify",
"--certifier-file", &alice_pgp.to_string_lossy(),
&bob_pgp.to_string_lossy(), bob,
"--time", iso8601 ]);
let assertion = cmd.assert().try_success()?;
let stdout = String::from_utf8_lossy(&assertion.get_output().stdout);
let cert = Cert::from_bytes(&*stdout)?;
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
@ -355,66 +268,35 @@ fn sq_certify_with_expired_key() -> Result<()>
{
let seconds_in_day = 24 * 60 * 60;
let validity = time::Duration::new(30 * seconds_in_day, 0);
// 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 dir = TempDir::new()?;
// Alice's expired key certifies bob's not expired key.
let mut sq = Sq::at(creation_time);
let alice = "<alice@example.org>";
let alice_key = CertBuilder::new()
.add_signing_subkey()
.set_creation_time(creation_time)
.set_validity_period(validity)
.add_userid(alice)
.generate()
.map(|(key, _rev)| key)?;
let (alice_key, alice_pgp, _) = sq.key_generate(
&["--expiry", &format!("{}s", validity_seconds) ],
&[ alice ]);
let alice_pgp = dir.path().join("alice.pgp");
{
let mut file = File::create(&alice_pgp)?;
alice_key.as_tsk().serialize(&mut file)?;
}
// Bob's key has the same creation time, but it does not expire.
// Bob's certificate has the same creation time, but it does not
// expire.
let bob = "<bob@other.org>";
let bob_key = CertBuilder::new()
.add_signing_subkey()
.set_creation_time(creation_time)
.add_userid(bob)
.generate()
.map(|(key, _rev)| key)?;
let (_bob_key, bob_pgp, _) = sq.key_generate(&[], &[ bob ]);
let bob_pgp = dir.path().join("bob.pgp");
{
let mut file = File::create(&bob_pgp)?;
bob_key.serialize(&mut file)?;
}
// Alice's expired key certifies bob's not expired key.
sq.tick(validity_seconds + 1);
// Make sure using an expired key fails by default.
let mut cmd = Command::cargo_bin("sq")?;
cmd.args(["--no-cert-store",
"--no-key-store",
"pki", "certify",
"--certifier-file", &alice_pgp.to_string_lossy(),
&bob_pgp.to_string_lossy(), bob ]);
cmd.assert().failure();
assert!(sq.pki_certify_p(
&[], &alice_pgp, &bob_pgp, bob, false).is_err());
// Try again.
let mut cmd = Command::cargo_bin("sq")?;
cmd.args(["--no-cert-store",
"--no-key-store",
"pki", "certify",
"--allow-not-alive-certifier",
"--certifier-file", &alice_pgp.to_string_lossy(),
&bob_pgp.to_string_lossy(), bob ]);
let cert = sq.pki_certify(
&["--allow-not-alive-certifier"],
&alice_pgp, &bob_pgp, bob);
let assertion = cmd.assert().try_success()?;
let stdout = String::from_utf8_lossy(&assertion.get_output().stdout);
let cert = Cert::from_bytes(&*stdout)?;
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
@ -452,65 +334,52 @@ fn sq_certify_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(seconds_in_day, 0);
time::SystemTime::now() - time::Duration::new(delta, 0);
let dir = TempDir::new()?;
// Alice's revoked key certifies bob's not expired key.
let mut sq = Sq::at(creation_time);
// Create a certificate for alice and immediately revoke it.
let alice = "<alice@example.org>";
let (alice_key, revocation) = CertBuilder::new()
.add_signing_subkey()
.set_creation_time(creation_time)
.add_userid(alice)
.generate()?;
let alice_key = alice_key.insert_packets(revocation)?;
let (alice_key, alice_pgp, revocation)
= sq.key_generate(&[], &[ alice ]);
let alice_pgp = dir.path().join("alice.pgp");
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));
// Bob's key has the same creation time, but it does not expire.
let bob = "<bob@other.org>";
let bob_key = CertBuilder::new()
.add_signing_subkey()
.set_creation_time(creation_time)
.add_userid(bob)
.generate()
.map(|(key, _rev)| key)?;
let (_bob_key, bob_pgp, _) = sq.key_generate(&[], &[ bob ]);
eprintln!("Bob:\n{}", sq.inspect(&bob_pgp));
let bob_pgp = dir.path().join("bob.pgp");
{
let mut file = File::create(&bob_pgp)?;
bob_key.serialize(&mut file)?;
}
sq.tick(delta);
// Make sure using an expired key fails by default.
let mut cmd = Command::cargo_bin("sq")?;
cmd.args(["--no-cert-store",
"--no-key-store",
"pki", "certify",
"--certifier-file", &alice_pgp.to_string_lossy(),
&bob_pgp.to_string_lossy(), bob ]);
cmd.assert().failure();
assert!(sq.pki_certify_p(
&[], &alice_pgp, &bob_pgp, bob, false).is_err());
// Try again.
let mut cmd = Command::cargo_bin("sq")?;
cmd.args(["--no-cert-store",
"--no-key-store",
"pki", "certify",
"--allow-revoked-certifier",
"--certifier-file", &alice_pgp.to_string_lossy(),
&bob_pgp.to_string_lossy(), bob ]);
let cert = sq.pki_certify(
&["--allow-revoked-certifier"],
&alice_pgp, &bob_pgp, bob);
let assertion = cmd.assert().try_success()?;
let stdout = String::from_utf8_lossy(&assertion.get_output().stdout);
let cert = Cert::from_bytes(&*stdout)?;
assert_eq!(cert.bad_signatures().count(), 0,
"Bad signatures in cert\n\n{}",
String::from_utf8(cert.armored().to_vec().unwrap()).unwrap());
@ -547,67 +416,23 @@ fn sq_certify_with_revoked_key() -> Result<()>
#[test]
fn sq_certify_using_cert_store() -> Result<()>
{
let dir = TempDir::new()?;
let sq = Sq::new();
let certd = dir.path().join("cert.d").display().to_string();
std::fs::create_dir(&certd).expect("mkdir works");
let alice_pgp = dir.path().join("alice.pgp").display().to_string();
let bob_pgp = dir.path().join("bob.pgp").display().to_string();
// Generate keys.
let mut cmd = Command::cargo_bin("sq")?;
cmd.args(["--cert-store", &certd,
"key", "generate",
"--expiry", "never",
"--userid", "<alice@example.org>",
"--output", &alice_pgp]);
cmd.assert().success();
let alice = Cert::from_file(&alice_pgp)?;
let mut cmd = Command::cargo_bin("sq")?;
cmd.args(["--cert-store", &certd,
"key", "generate",
"--expiry", "never",
"--userid", "<bob@example.org>",
"--output", &bob_pgp]);
cmd.assert().success();
let bob = Cert::from_file(&bob_pgp)?;
let (alice, alice_pgp, _alice_rev)
= sq.key_generate(&[], &["<alice@example.org>"]);
let (bob, bob_pgp, _bob_rev)
= sq.key_generate(&[], &["<bob@example.org>"]);
// Import bob's (but not alice's!).
let mut cmd = Command::cargo_bin("sq")?;
cmd.args(["--cert-store", &certd,
"cert", "import", &bob_pgp]);
cmd.assert().success();
sq.cert_import(&bob_pgp);
// Have alice certify bob.
let mut cmd = Command::cargo_bin("sq")?;
cmd.args(["--cert-store", &certd,
"pki", "certify",
"--certifier-file", &alice_pgp,
&bob.fingerprint().to_string(),
"<bob@example.org>"]);
let output = cmd.output().expect("success");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(output.status.success());
let found = sq.pki_certify(
&[], &alice_pgp,
&bob.fingerprint().to_string(), "<bob@example.org>");
// Make sure the certificate on stdout is bob and that alice
// signed it.
let parser = CertParser::from_bytes(stdout.as_bytes())
.expect("valid");
let found = parser.collect::<Result<Vec<Cert>>>()
.expect("valid");
assert_eq!(found.len(), 1,
"stdout:\n{}\nstderr:\n{}",
stdout, stderr);
let found = found.into_iter().next().expect("have one");
assert_eq!(found.fingerprint(), bob.fingerprint());
assert_eq!(found.userids().count(), 1);