Change sq pki certify to support the cert store and key store.

- Change `sq pki certify` to support the cert store and key store.

  - See #205.
This commit is contained in:
Neal H. Walfield 2024-06-05 15:23:21 +02:00
parent 028983d40f
commit a82d9908f9
No known key found for this signature in database
GPG Key ID: 6863C9AD5B4D22D3
6 changed files with 587 additions and 320 deletions

1
NEWS
View File

@ -55,6 +55,7 @@
it should read the certificate from stdin.
- In `sq pki certify`, change the certifier file parameter from a
positional parameter to a named parameter, `--certifier-file`.
- `sq pki certify` can now use the cert store and the key store.
* Changes in 0.36.0
- Missing
* Changes in 0.35.0

View File

@ -1,7 +1,11 @@
//! Command-line parser for `sq pki certify`.
use clap::ArgGroup;
use clap::Parser;
use sequoia_openpgp as openpgp;
use openpgp::KeyHandle;
use crate::cli::THIRD_PARTY_CERTIFICATION_VALIDITY_DURATION;
use crate::cli::THIRD_PARTY_CERTIFICATION_VALIDITY_IN_YEARS;
@ -53,15 +57,15 @@ $ sq pki certify --time 20130721 --certifier-file neal.pgp
ada.pgp Ada
",
)]
#[clap(group(ArgGroup::new("certifier_input").args(&["certifier_file", "certifier"]).required(true)))]
pub struct Command {
#[clap(
default_value_t = FileOrStdout::default(),
help = FileOrStdout::HELP_OPTIONAL,
long,
short,
value_name = FileOrStdout::VALUE_NAME,
)]
pub output: FileOrStdout,
pub output: Option<FileOrStdout>,
#[clap(
short = 'B',
long,
@ -197,13 +201,19 @@ pub struct Command {
certification is revoked.",
)]
pub allow_revoked_certifier: bool,
#[clap(
long,
help = "Create the certification using CERTIFIER-KEY.",
value_name = FileOrStdin::VALUE_NAME,
)]
pub certifier: Option<KeyHandle>,
#[clap(
long,
value_name = "CERTIFIER-FILE",
required = true,
help = "Create the certification using CERTIFIER-KEY.",
)]
pub certifier_file: FileOrStdin,
pub certifier_file: Option<FileOrStdin>,
#[clap(
value_name = "KEY_ID|FINGERPRINT|FILE",
required = true,

View File

@ -251,6 +251,7 @@ impl ClapData for FileOrCertStore {
/// Designates a certificate by path, by stdin, or by key handle.
///
/// Use [`Sq::lookup_one`] to read the certificate.
#[derive(Debug)]
pub enum FileStdinOrKeyHandle {
FileOrStdin(FileOrStdin),
KeyHandle(KeyHandle),
@ -262,6 +263,12 @@ impl From<FileOrStdin> for FileStdinOrKeyHandle {
}
}
impl From<&str> for FileStdinOrKeyHandle {
fn from(path: &str) -> Self {
PathBuf::from(path).into()
}
}
impl From<&Path> for FileStdinOrKeyHandle {
fn from(path: &Path) -> Self {
path.to_path_buf().into()

View File

@ -1,4 +1,5 @@
use std::fmt;
use std::sync::Arc;
use chrono::DateTime;
use chrono::Utc;
@ -6,34 +7,57 @@ use chrono::Utc;
use sequoia_openpgp as openpgp;
use openpgp::KeyHandle;
use openpgp::Result;
use openpgp::cert::prelude::*;
use openpgp::packet::prelude::*;
use openpgp::packet::signature::subpacket::NotationDataFlags;
use openpgp::parse::Parse;
use openpgp::serialize::Serialize;
use openpgp::types::SignatureType;
use openpgp::types::KeyFlags;
use sequoia_cert_store as cert_store;
use cert_store::StoreUpdate;
use crate::Sq;
use crate::parse_notations;
use crate::sq::GetKeysOptions;
use crate::cli::pki::certify;
use crate::cli::types::FileOrStdin;
use crate::cli::types::FileStdinOrKeyHandle;
use crate::commands::FileOrStdout;
pub fn certify(sq: Sq, c: certify::Command)
pub fn certify(sq: Sq, mut c: certify::Command)
-> Result<()>
{
let cert = c.certificate;
let certifier: FileStdinOrKeyHandle = if let Some(file) = c.certifier_file {
assert!(c.certifier.is_none());
file.into()
} else if let Some(kh) = c.certifier {
kh.into()
} else {
panic!("clap enforces --certifier or --certifier-file is set");
};
// XXX: Change this interface: it's dangerous to guess whether an
// identifier is a file or a key handle.
let cert = if let Ok(kh) = c.certificate.parse::<KeyHandle>() {
FileStdinOrKeyHandle::KeyHandle(kh)
} else {
FileStdinOrKeyHandle::FileOrStdin(
FileOrStdin::new(Some(c.certificate.into())))
};
if cert.is_file() {
// If the cert is read from a file, we default to stdout.
// (None means write to the cert store.)
if c.output.is_none() {
c.output = Some(FileOrStdout::new(None));
}
}
let userid = c.userid;
let certifier = sq.lookup_one(
c.certifier_file, Some(KeyFlags::empty().set_certification()), true)?;
// XXX: Change this interface: it's dangerous to guess whether an
// identifier is a file or a key handle.
let cert = if let Ok(kh) = cert.parse::<KeyHandle>() {
sq.lookup_one(&kh, None, true)?
} else {
Cert::from_file(cert)?
};
certifier, Some(KeyFlags::empty().set_certification()), true)?;
let cert = sq.lookup_one(cert, None, true)?;
let trust_depth: u8 = c.depth;
let trust_amount: u8 = c.amount.amount();
@ -173,15 +197,35 @@ pub fn certify(sq: Sq, c: certify::Command)
new_packets.push(certification.into());
}
// And export it.
let cert = cert.insert_packets(new_packets)?;
let mut message = c.output.create_pgp_safe(
sq.force,
c.binary,
sequoia_openpgp::armor::Kind::PublicKey,
)?;
cert.serialize(&mut message)?;
message.finalize()?;
if let Some(output) = c.output {
// And export it.
let mut message = output.create_pgp_safe(
sq.force,
c.binary,
sequoia_openpgp::armor::Kind::PublicKey,
)?;
cert.serialize(&mut message)?;
message.finalize()?;
} else {
// Import it.
let cert_store = sq.cert_store_or_else()?;
let keyid = cert.keyid();
if let Err(err) = cert_store.update(Arc::new(cert.into())) {
wprintln!("Error importing updated cert: {}", err);
return Err(err);
} else {
sq.hint(format_args!(
"Imported updated cert into the cert store. \
To make the update effective, it has to be published \
so that others can find it, for example using:"))
.command(format_args!(
"sq cert export --cert {} | sq network keyserver publish",
keyid));
}
}
Ok(())
}

View File

@ -1,5 +1,7 @@
#![allow(unused)]
use std::ffi::OsStr;
use std::ffi::OsString;
use std::path::Path;
use std::path::PathBuf;
use std::process::Output;
@ -7,6 +9,8 @@ use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use anyhow::anyhow;
use anyhow::Context;
use assert_cmd::Command;
use chrono::DateTime;
@ -45,9 +49,22 @@ pub fn time_as_string(t: DateTime<Utc>) -> String {
}
/// Designates a certificate by path, or by key handle.
#[derive(Clone, Debug)]
pub enum FileOrKeyHandle {
FileOrStdin(PathBuf),
KeyHandle(KeyHandle),
KeyHandle((KeyHandle, OsString)),
}
impl From<&str> for FileOrKeyHandle {
fn from(path: &str) -> Self {
PathBuf::from(path).into()
}
}
impl From<String> for FileOrKeyHandle {
fn from(path: String) -> Self {
PathBuf::from(path).into()
}
}
impl From<&Path> for FileOrKeyHandle {
@ -70,13 +87,29 @@ impl From<PathBuf> for FileOrKeyHandle {
impl From<&KeyHandle> for FileOrKeyHandle {
fn from(kh: &KeyHandle) -> Self {
FileOrKeyHandle::KeyHandle(kh.clone())
FileOrKeyHandle::KeyHandle((kh.clone(), kh.to_string().into()))
}
}
impl From<KeyHandle> for FileOrKeyHandle {
fn from(kh: KeyHandle) -> Self {
FileOrKeyHandle::KeyHandle(kh)
let s = kh.to_string().into();
FileOrKeyHandle::KeyHandle((kh, s))
}
}
impl From<&FileOrKeyHandle> for FileOrKeyHandle {
fn from(h: &FileOrKeyHandle) -> Self {
h.clone()
}
}
impl AsRef<OsStr> for FileOrKeyHandle {
fn as_ref(&self) -> &OsStr {
match self {
FileOrKeyHandle::FileOrStdin(file) => file.as_os_str(),
FileOrKeyHandle::KeyHandle((kh, s)) => s.as_os_str(),
}
}
}
@ -224,8 +257,34 @@ impl Sq {
let output = cmd.output().expect("can run command");
if let Some(expect) = expect.into() {
match (output.status.success(), expect) {
(true, true) => (),
(false, false) => (),
(true, true) | (false, false) => {
eprintln!("Exit status:");
let dump = |id, stream| {
let limit = 70;
let data = String::from_utf8_lossy(stream)
.chars()
.collect::<Vec<_>>();
if data.is_empty() {
eprintln!("{}: empty", id);
} else {
eprintln!("{}: {}{}",
id,
data.iter().take(limit).collect::<String>(),
if data.len() > limit {
format!("... {} more bytes",
data.len() - limit)
} else {
"".to_string()
});
}
};
dump("stdout", &output.stdout);
dump("stderr", &output.stderr);
}
(got, expected) => {
panic!(
"Running {:?}: {}, but should have {}:\n\
@ -294,8 +353,8 @@ impl Sq {
FileOrKeyHandle::FileOrStdin(path) => {
cmd.arg(path);
}
FileOrKeyHandle::KeyHandle(kh) => {
cmd.args(["--cert", &kh.to_string()]);
FileOrKeyHandle::KeyHandle((_kh, s)) => {
cmd.arg("--cert").arg(&s);
}
};
@ -332,30 +391,74 @@ impl Sq {
}
/// 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)
///
/// If `output_file` is `Some`, then the output is written to that
/// file. Otherwise, the default behavior is followed.
pub fn pki_certify_p<'a, H, C, Q>(&self, extra_args: &[&str],
certifier: H,
cert: C,
userid: &str,
output_file: Q,
success: bool)
-> Result<Cert>
where P: AsRef<Path>, Q: AsRef<Path>
where H: Into<FileOrKeyHandle>,
C: Into<FileOrKeyHandle>,
Q: Into<Option<&'a Path>>,
{
let certifier = certifier.as_ref();
let cert = cert.as_ref();
let certifier = certifier.into();
let cert = cert.into();
let output_file = output_file.into();
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("-");
match &certifier {
FileOrKeyHandle::FileOrStdin(file) => {
cmd.arg("--certifier-file").arg(file);
}
FileOrKeyHandle::KeyHandle((_kh, s)) => {
cmd.arg("--certifier").arg(s);
}
}
cmd.arg(&cert).arg(userid);
if let Some(output_file) = output_file {
cmd.arg("--force").arg("--output").arg(output_file);
}
let output = self.run(cmd, Some(success));
if output.status.success() {
Ok(Cert::from_bytes(&output.stdout)
.expect("can parse certificate"))
if let Some(output_file) = output_file {
// The output was explicitly written to a file.
if output_file == &PathBuf::from("-") {
Ok(Cert::from_bytes(&output.stdout)
.expect("can parse certificate"))
} else {
Ok(Cert::from_file(&output_file)
.expect("can parse certificate"))
}
} else {
match cert {
FileOrKeyHandle::FileOrStdin(_) => {
// When the cert is from a file, the output is
// written to stdout by default.
Ok(Cert::from_bytes(&output.stdout)
.with_context(|| {
format!("Importing result from the file {:?}",
cert)
})
.expect("can parse certificate"))
}
FileOrKeyHandle::KeyHandle((kh, _s)) => {
// When the cert is from the cert store, the
// output is written to the cert store by
// default.
Ok(self.cert_export(kh.clone()))
}
}
}
} else {
Err(anyhow::anyhow!(format!(
"Failed (expected):\n{}",
@ -364,14 +467,18 @@ impl Sq {
}
/// Certify the user ID binding.
pub fn pki_certify<P, Q>(&self, extra_args: &[&str],
certifier: P,
cert: Q,
userid: &str)
pub fn pki_certify<'a, H, C, Q>(&self, extra_args: &[&str],
certifier: H,
cert: C,
userid: &str,
output_file: Q)
-> Cert
where P: AsRef<Path>, Q: AsRef<Path>
where H: Into<FileOrKeyHandle>,
C: Into<FileOrKeyHandle>,
Q: Into<Option<&'a Path>>,
{
self.pki_certify_p(extra_args, certifier, cert, userid, true)
self.pki_certify_p(
extra_args, certifier, cert, userid, output_file, true)
.expect("success")
}
}

View File

@ -15,197 +15,243 @@ use openpgp::policy::StandardPolicy;
use openpgp::serialize::{Serialize, SerializeInto};
mod common;
use common::FileOrKeyHandle;
use common::Sq;
const P: &StandardPolicy = &StandardPolicy::new();
#[test]
fn sq_certify() -> Result<()> {
let sq = Sq::new();
let mut sq = Sq::new();
let (_alice, alice_pgp, _alice_rev)
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.
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 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;
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
}
assert!(ok, "Didn't find user id");
// No expiry.
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 alice_handle: FileOrKeyHandle = if keystore {
alice.key_handle().into()
} else {
alice_pgp.clone().into()
};
let vc = cert.with_policy(P, None).unwrap();
let mut bob_pgp = vec![ bob_pgp.clone() ];
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];
let mut certification_count = 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());
// A simple certification.
sq.tick(1);
let bob_pgp_new = sq.scratch_file("bob");
let cert = sq.pki_certify(
&[], &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());
ok = true;
break;
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");
assert!(ok, "Didn't find user id");
// 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());
// No expiry.
sq.tick(1);
let bob_pgp_new = sq.scratch_file(None);
let cert = sq.pki_certify(
&["--expiry", "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 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];
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(), 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(), 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;
ok = true;
break;
}
}
}
assert!(ok, "Didn't find user id");
assert!(ok, "Didn't find user id");
// It should fail if the User ID doesn't exist.
assert!(sq.pki_certify_p(&[], &alice_pgp, &bob_pgp, "bob", false).is_err());
// Have alice certify <bob@example.org> for 0xB0B.
sq.tick(1);
let bob_pgp_new = sq.scratch_file(None);
let cert = sq.pki_certify(
&["--depth", "10",
"--amount", "5",
"--regex", "a",
"--regex", "b",
"--local",
"--non-revocable",
"--expiry", "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());
// With a notation.
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());
let vc = cert.with_policy(P, None).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);
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");
// Accept the critical notation.
let p = &mut StandardPolicy::new();
p.good_critical_notations(&["foo"]);
let vc = cert.with_policy(p, None).unwrap();
// It should fail if the User ID doesn't exist.
assert!(sq.pki_certify_p(
&[], &alice_handle, bob_pgp.last().unwrap(), "bob",
None, false).is_err());
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);
// With a notation.
sq.tick(1);
let bob_pgp_new = sq.scratch_file(None);
let cert = sq.pki_certify(
&[
"--notation", "foo", "bar",
"--notation", "!foo", "xyzzy",
"--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());
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(certifications.len(), 1);
// 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(),
certification_count);
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(
certifications.len(),
// Subtract the bad one.
certification_count - 1);
}
}
let c = certifications[0];
// Accept the critical notation.
let p = &mut StandardPolicy::new();
p.good_critical_notations(&["foo"]);
let vc = cert.with_policy(p, None).unwrap();
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 mut ok = false;
for ua in vc.userids() {
if ua.userid().value() == b"<bob@example.org>" {
assert_eq!(ua.bundle().certifications().len(),
certification_count);
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)
];
let certifications: Vec<_>
= ua.certifications().collect();
assert_eq!(certifications.len(), certification_count);
for n in c.notation_data() {
if n.name() == "salt@notations.sequoia-pgp.org" {
continue;
}
let c = certifications[0];
for (m, found) in notations.iter_mut() {
if n == m {
assert!(!*found);
*found = true;
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);
}
for (n, found) in notations.iter() {
assert!(found, "Missing: {:?}", n);
}
ok = true;
break;
ok = true;
break;
}
}
assert!(ok, "Didn't find user id");
}
assert!(ok, "Didn't find user id");
Ok(())
}
@ -227,37 +273,48 @@ fn sq_certify_creation_time() -> Result<()>
= sq.key_generate(&[], &[ bob ]);
// Alice certifies bob's key.
let cert = sq.pki_certify(&[], &alice_pgp, &bob_pgp, bob);
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;
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
}
if let Some(userid) = userid {
let certifications: Vec<_> = userid.certifications().collect();
assert_eq!(certifications.len(), 1);
let certification = certifications.into_iter().next().unwrap();
let alice_handle: FileOrKeyHandle = if keystore {
alice_key.key_handle().into()
} else {
alice_pgp.clone().into()
};
assert_eq!(certification.get_issuers().into_iter().next(),
Some(KeyHandle::from(alice_key.fingerprint())));
// Alice certifies bob's key.
let cert = sq.pki_certify(&[], &alice_handle, &bob_pgp, bob, None);
assert_eq!(certification.signature_creation_time(), Some(t));
} else {
panic!("missing user id");
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(())
@ -285,45 +342,57 @@ fn sq_certify_with_expired_key() -> Result<()>
let bob = "<bob@other.org>";
let (_bob_key, bob_pgp, _) = sq.key_generate(&[], &[ bob ]);
// 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.pki_certify_p(
&[], &alice_pgp, &bob_pgp, bob, false).is_err());
// Try again.
let cert = sq.pki_certify(
&["--allow-not-alive-certifier"],
&alice_pgp, &bob_pgp, bob);
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)?;
assert!(
creation_time.duration_since(vc.primary_key().creation_time()).unwrap()
< time::Duration::new(1, 0));
let mut userid = None;
for u in vc.userids() {
if u.userid().value() == bob.as_bytes() {
userid = Some(u);
break;
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
}
if let Some(userid) = userid {
let certifications: Vec<_> = userid.certifications().collect();
assert_eq!(certifications.len(), 1);
let certification = certifications.into_iter().next().unwrap();
let alice_handle: FileOrKeyHandle = if keystore {
alice_key.key_handle().into()
} else {
alice_pgp.clone().into()
};
assert_eq!(certification.get_issuers().into_iter().next(),
Some(KeyHandle::from(alice_key.fingerprint())));
} else {
panic!("missing user id");
// 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.pki_certify_p(
&[], &alice_handle, &bob_pgp, bob, Some(&*bob_pgp), false).is_err());
// Try again.
let cert = sq.pki_certify(
&["--allow-not-alive-certifier"],
&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, None)?;
assert!(
creation_time.duration_since(vc.primary_key().creation_time()).unwrap()
< time::Duration::new(1, 0));
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())));
} else {
panic!("missing user id");
}
}
Ok(())
@ -369,44 +438,56 @@ fn sq_certify_with_revoked_key() -> Result<()>
let (_bob_key, bob_pgp, _) = sq.key_generate(&[], &[ bob ]);
eprintln!("Bob:\n{}", sq.inspect(&bob_pgp));
sq.tick(delta);
// Make sure using an expired key fails by default.
assert!(sq.pki_certify_p(
&[], &alice_pgp, &bob_pgp, bob, false).is_err());
// Try again.
let cert = sq.pki_certify(
&["--allow-revoked-certifier"],
&alice_pgp, &bob_pgp, bob);
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)?;
assert!(
creation_time.duration_since(vc.primary_key().creation_time()).unwrap()
< time::Duration::new(1, 0));
let mut userid = None;
for u in vc.userids() {
if u.userid().value() == bob.as_bytes() {
userid = Some(u);
break;
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
}
if let Some(userid) = userid {
let certifications: Vec<_> = userid.certifications().collect();
assert_eq!(certifications.len(), 1);
let certification = certifications.into_iter().next().unwrap();
let alice_handle: FileOrKeyHandle = if keystore {
alice_key.key_handle().into()
} else {
alice_pgp.clone().into()
};
assert_eq!(certification.get_issuers().into_iter().next(),
Some(KeyHandle::from(alice_key.fingerprint())));
} else {
panic!("missing user id");
sq.tick(delta);
// Make sure using an expired key fails by default.
assert!(sq.pki_certify_p(
&[], &alice_handle, &bob_pgp, bob, None, false).is_err());
// Try again.
let cert = sq.pki_certify(
&["--allow-revoked-certifier"],
&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, None)?;
assert!(
creation_time.duration_since(vc.primary_key().creation_time()).unwrap()
< time::Duration::new(1, 0));
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())));
} else {
panic!("missing user id");
}
}
Ok(())
@ -416,38 +497,55 @@ fn sq_certify_with_revoked_key() -> Result<()>
#[test]
fn sq_certify_using_cert_store() -> Result<()>
{
let sq = Sq::new();
let mut sq = Sq::new();
let (alice, alice_pgp, _alice_rev)
let (alice_key, alice_pgp, _alice_rev)
= sq.key_generate(&[], &["<alice@example.org>"]);
let (bob, bob_pgp, _bob_rev)
let (bob_key, bob_pgp, _bob_rev)
= sq.key_generate(&[], &["<bob@example.org>"]);
// Import bob's (but not alice's!).
// Import bob's (but not yet alice's!).
sq.cert_import(&bob_pgp);
// Have alice certify bob.
let found = sq.pki_certify(
&[], &alice_pgp,
&bob.fingerprint().to_string(), "<bob@example.org>");
let mut certification_count = 0;
for keystore in [false, true] {
if keystore {
sq.key_import(&alice_pgp);
}
// Make sure the certificate on stdout is bob and that alice
// signed it.
assert_eq!(found.fingerprint(), bob.fingerprint());
assert_eq!(found.userids().count(), 1);
let alice_handle: FileOrKeyHandle = if keystore {
alice_key.key_handle().into()
} else {
alice_pgp.clone().into()
};
let ua = found.userids().next().expect("have one");
let certifications: Vec<_> = ua.certifications().collect();
assert_eq!(certifications.len(), 1);
let certification = certifications.into_iter().next().unwrap();
sq.tick(1);
assert_eq!(certification.get_issuers().into_iter().next(),
Some(KeyHandle::from(alice.fingerprint())));
certification.clone().verify_userid_binding(
alice.primary_key().key(),
bob.primary_key().key(),
&UserID::from("<bob@example.org>"))
.expect("valid certification");
// Have alice certify bob.
let found = sq.pki_certify(
&[], &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(())
}