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:
parent
028983d40f
commit
a82d9908f9
1
NEWS
1
NEWS
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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(())
|
||||
}
|
||||
|
159
tests/common.rs
159
tests/common.rs
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user