Change the sq pki certify tests to use the common test framework.
This commit is contained in:
parent
c8c7e24fe9
commit
028983d40f
143
tests/common.rs
143
tests/common.rs
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user