sequoia-sq/tests/integration/sq_pki_link.rs
Justus Winter 012e762d38
Align user ID designators in sq pki link retract.
- User IDs have to be explicitly given, or `--all` has to be used to
    select them all (this was previously the default).

  - This aligns the retract subcommand with the other link and vouch
    management commands.

  - Fixes #442.
2024-11-28 18:07:30 +01:00

858 lines
30 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use std::process::ExitStatus;
use std::sync::{Mutex, OnceLock};
use tempfile::TempDir;
use sequoia_openpgp as openpgp;
use openpgp::KeyHandle;
use openpgp::Result;
use openpgp::Cert;
use openpgp::parse::Parse;
use super::common::FileOrKeyHandle;
use super::common::NO_USERIDS;
use super::common::Sq;
use super::common::STANDARD_POLICY;
use super::common::UserIDArg;
use super::common::artifact;
// We are going to replace certifications, and we want to make sure
// that the newest one is the active one. This means ensuring that
// the newer one has a newer timestamp. To avoid sleeping for a
// second, the resolution of the time stamp, we pass an explicit time
// to each operation.
//
// This function drives the clock forward, and ensures that every
// operation "happens" at a different point in time.
static TIME: OnceLock<Mutex<chrono::DateTime<chrono::Utc>>> = OnceLock::new();
fn tick() -> String {
let t = TIME.get_or_init(|| Mutex::new(chrono::Utc::now()));
let mut t = t.lock().unwrap();
*t = *t + chrono::Duration::seconds(10);
t.format("%Y-%m-%dT%H:%M:%SZ").to_string()
}
// Returns the "current" time.
fn now() -> chrono::DateTime<chrono::Utc> {
*TIME.get_or_init(|| Mutex::new(chrono::Utc::now())).lock().unwrap()
}
// Verifies a signed message.
fn sq_verify(sq: &Sq,
time: Option<chrono::DateTime<chrono::Utc>>,
trust_roots: &[&str],
signer_files: &[&str],
msg_pgp: &str,
authenticated_sigs: usize, unauthenticated_sigs: usize)
{
let mut cmd = sq.command();
for trust_root in trust_roots {
cmd.args(&["--trust-root", trust_root]);
}
let time = if let Some(time) = time {
time.format("%Y-%m-%dT%H:%M:%SZ").to_string()
} else {
tick()
};
cmd.args(["verify", "--message", "--time", &time]);
for signer_file in signer_files {
cmd.args(&["--signer-file", signer_file]);
}
cmd.arg(msg_pgp);
eprintln!("{:?}", cmd);
let output = sq.run(cmd, None);
let status = output.status;
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
if authenticated_sigs > 0 {
assert!(status.success(),
"\nstdout:\n{}\nstderr:\n{}",
stdout, stderr);
assert!(stderr.contains(&format!("{} authenticated signature",
authenticated_sigs)),
"stdout:\n{}\nstderr:\n{}",
stdout, stderr);
} else {
assert!(! status.success(),
"\nstdout:\n{}\nstderr:\n{}",
stdout, stderr);
}
if unauthenticated_sigs > 0 {
assert!(stderr.contains(&format!("{} unauthenticated signature",
unauthenticated_sigs)),
"stdout:\n{}\nstderr:\n{}", stdout, stderr);
}
}
// Links a User ID and a certificate.
fn sq_link(sq: &Sq,
cert: &str, userids: &[&str], emails: &[&str], more_args: &[&str],
success: bool)
-> (ExitStatus, String, String)
{
let mut cmd = sq.command();
cmd.args(&["pki", "link", "add", "--time", &tick()]);
cmd.arg("--cert").arg(cert);
for userid in userids {
cmd.arg("--userid").arg(userid);
}
for email in emails {
cmd.arg("--email").arg(email);
}
cmd.args(more_args);
let output = sq.run(cmd, None);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
if success {
assert!(output.status.success(),
"'sq pki link add' failed unexpectedly\
\nstdout:\n{}\nstderr:\n{}",
stdout, stderr);
} else {
assert!(! output.status.success(),
"'sq pki link add' succeeded unexpectedly\
\nstdout:\n{}\nstderr:\n{}",
stdout, stderr);
}
(output.status, stdout, stderr)
}
fn sq_retract(sq: &Sq, cert: &str, userids: &[&str], emails: &[&str])
-> (ExitStatus, String, String)
{
let mut cmd = sq.command();
cmd.args(&["pki", "link", "retract", "--time", &tick(), "--cert", cert]);
for userid in userids {
cmd.arg("--userid").arg(userid);
}
for email in emails {
cmd.arg("--email").arg(email);
}
if userids.is_empty() && emails.is_empty() {
cmd.arg("--all");
}
eprintln!("{:?}", cmd);
let output = sq.run(cmd, true);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
(output.status, stdout, stderr)
}
// Certifies a binding.
//
// The certification is imported into the cert store.
fn sq_certify(sq: &Sq,
certifier: &str, cert: &str, userid: &str,
trust_amount: Option<usize>)
{
let mut extra_args = Vec::new();
let trust_amount_;
if let Some(trust_amount) = trust_amount {
extra_args.push("--amount");
trust_amount_ = format!("{}", trust_amount);
extra_args.push(&trust_amount_);
}
let certification = sq.scratch_file(Some(&format!(
"certification {} {} by {}", cert, userid, certifier)[..]));
let cert = if let Ok(kh) = cert.parse::<KeyHandle>() {
kh.into()
} else {
FileOrKeyHandle::FileOrStdin(cert.into())
};
sq.pki_vouch_add(&extra_args, certifier, cert, &[userid],
Some(certification.as_path()));
sq.cert_import(&certification);
}
fn sq_authorize(sq: &Sq,
certifier: &str, cert: &str, userid: &str,
trust_amount: Option<usize>, depth: Option<usize>)
{
let mut extra_args = vec![ "--unconstrained" ];
let trust_amount_;
if let Some(trust_amount) = trust_amount {
extra_args.push("--amount");
trust_amount_ = format!("{}", trust_amount);
extra_args.push(&trust_amount_);
}
let depth_;
if let Some(depth) = depth {
extra_args.push("--depth");
depth_ = format!("{}", depth);
extra_args.push(&depth_);
}
let certification = sq.scratch_file(Some(&format!(
"certification {} {} by {}", cert, userid, certifier)[..]));
let cert = if let Ok(kh) = cert.parse::<KeyHandle>() {
kh.into()
} else {
FileOrKeyHandle::FileOrStdin(cert.into())
};
sq.pki_vouch_authorize(&extra_args, certifier, cert, &[userid],
Some(certification.as_path()));
sq.cert_import(&certification);
}
// Verify signatures using the acceptance machinery.
#[test]
fn sq_pki_link_add_retract() -> Result<()> {
let sq = Sq::new();
let dir = TempDir::new()?;
let certd = dir.path().join("cert.d").display().to_string();
std::fs::create_dir(&certd).expect("mkdir works");
struct Data {
key_file: String,
cert: Cert, // unused
sig_file: String,
}
// Four certificates.
let alice_userid = "<alice@example.org>";
let (alice, alice_pgp, _) = sq.key_generate(&[], &[alice_userid]);
sq.cert_import(&alice_pgp);
let alice = Data {
key_file: alice_pgp.display().to_string(),
cert: alice,
sig_file: dir.path().join("alice.sig").display().to_string(),
};
let alice_fpr = alice.cert.fingerprint().to_string();
let bob_userid = "<bob@example.org>";
let (bob, bob_pgp, _) = sq.key_generate(&[], &[bob_userid]);
sq.cert_import(&bob_pgp);
let bob = Data {
key_file: bob_pgp.display().to_string(),
cert: bob,
sig_file: dir.path().join("bob.sig").display().to_string(),
};
let bob_fpr = bob.cert.fingerprint().to_string();
let carol_userid = "<carol@example.org>";
let (carol, carol_pgp, _) = sq.key_generate(&[], &[carol_userid]);
sq.cert_import(&carol_pgp);
let carol = Data {
key_file: carol_pgp.display().to_string(),
cert: carol,
sig_file: dir.path().join("carol.sig").display().to_string(),
};
let carol_fpr = carol.cert.fingerprint().to_string();
let dave_userid = "<dave@other.org>";
let (dave, dave_pgp, _) = sq.key_generate(&[], &[dave_userid]);
sq.cert_import(&dave_pgp);
let dave = Data {
key_file: dave_pgp.display().to_string(),
cert: dave,
sig_file: dir.path().join("dave.sig").display().to_string(),
};
let dave_fpr = dave.cert.fingerprint().to_string();
let data: &[&Data] = &[ &alice, &bob, &carol, &dave ][..];
// Have each certificate sign a message.
for data in data.iter() {
sq.sign_args(
&["--time", &tick()],
PathBuf::from(data.key_file.as_str()), None,
&artifact("messages/a-cypherpunks-manifesto.txt"),
PathBuf::from(data.sig_file.clone()).as_path());
}
// None of the certificates can be authenticated so verifying the
// messages should fail.
for data in data.iter() {
sq_verify(&sq, None, &[], &[], &data.sig_file, 0, 1);
}
// Have Alice certify Bob as a trusted introducer and have Bob
// certify Carol.
sq_authorize(&sq, &alice.key_file,
&bob.cert.fingerprint().to_string(), bob_userid,
None, Some(1));
sq_certify(&sq, &bob.key_file,
&carol.cert.fingerprint().to_string(), carol_userid,
None);
// We should be able to verify Alice's, Bob's and Carol's
// signatures Alice as the trust root. And Bob's and Carols' with
// Bob as the trust root.
sq_verify(&sq, None, &[&alice_fpr], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[&alice_fpr], &[], &bob.sig_file, 1, 0);
sq_verify(&sq, None, &[&alice_fpr], &[], &carol.sig_file, 1, 0);
sq_verify(&sq, None, &[&alice_fpr], &[], &dave.sig_file, 0, 1);
sq_verify(&sq, None, &[&bob_fpr], &[], &alice.sig_file, 0, 1);
sq_verify(&sq, None, &[&bob_fpr], &[], &bob.sig_file, 1, 0);
sq_verify(&sq, None, &[&bob_fpr], &[], &carol.sig_file, 1, 0);
sq_verify(&sq, None, &[&bob_fpr], &[], &dave.sig_file, 0, 1);
sq_verify(&sq, None, &[&carol_fpr], &[], &alice.sig_file, 0, 1);
sq_verify(&sq, None, &[&carol_fpr], &[], &bob.sig_file, 0, 1);
sq_verify(&sq, None, &[&carol_fpr], &[], &carol.sig_file, 1, 0);
sq_verify(&sq, None, &[&carol_fpr], &[], &dave.sig_file, 0, 1);
sq_verify(&sq, None, &[&dave_fpr], &[], &alice.sig_file, 0, 1);
sq_verify(&sq, None, &[&dave_fpr], &[], &bob.sig_file, 0, 1);
sq_verify(&sq, None, &[&dave_fpr], &[], &carol.sig_file, 0, 1);
sq_verify(&sq, None, &[&dave_fpr], &[], &dave.sig_file, 1, 0);
// Let's accept Alice, but not (yet) as a trusted introducer. We
// should now be able to verify Alice's signature, but not Bob's.
sq_link(&sq, &alice_fpr, &[ &alice_userid ], &[], &[], true);
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.sig_file, 0, 1);
// Accept Alice as a trusted introducer. We should be able to
// verify Alice, Bob, and Carol's signatures.
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained"],
alice.cert.key_handle(),
&[ &alice_userid ]);
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &carol.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &dave.sig_file, 0, 1);
// Retract the acceptance for Alice. If we don't specify a trust
// root, none of the signatures should verify.
sq_retract(&sq, &alice_fpr, &[ &alice_userid ], &[]);
for data in data.iter() {
sq_verify(&sq, None, &[], &[], &data.sig_file, 0, 1);
}
// Accept Alice as a trusted introducer again. We should be able
// to verify Alice, Bob, and Carol's signatures.
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained"],
alice.cert.key_handle(),
&[ &alice_userid ]);
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &carol.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &dave.sig_file, 0, 1);
// Have Bob certify Dave. Now Dave's signature should also
// verify.
sq_certify(&sq, &bob.key_file,
&dave.cert.fingerprint().to_string(), dave_userid,
None);
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &carol.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &dave.sig_file, 1, 0);
// Change Alice's acceptance to just be a normal certification.
// We should only be able to verify her signature.
sq_link(&sq, &alice_fpr, &[ &alice_userid ], &[], &[], true);
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.sig_file, 0, 1);
sq_verify(&sq, None, &[], &[], &carol.sig_file, 0, 1);
sq_verify(&sq, None, &[], &[], &dave.sig_file, 0, 1);
// Change Alice's acceptance to be a ca, but only for example.org,
// i.e., not for Dave.
sq.pki_link_authorize(&["--time", &tick(), "--domain", "example.org"],
alice.cert.key_handle(),
&[ &alice_userid ]);
sq_verify(&sq, None, &[], &[], &alice.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &bob.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &carol.sig_file, 1, 0);
sq_verify(&sq, None, &[], &[], &dave.sig_file, 0, 1);
// A fifth certificate.
let (ed, ed_pgp, _) = sq.key_generate(
&["--allow-non-canonical-userids"],
&[
"Ed <ed@example.org>",
"Eddie <ed@example.org>",
// This is not considered to be an email address as
// it is not wrapped in angle brackets.
"ed@some.org",
// But this is.
"<ed@other.org>",
]);
sq.cert_import(&ed_pgp);
let ed_fpr = ed.fingerprint().to_string();
let ed_sig_file = dir.path().join("ed.sig");
sq.sign_args(
&["--time", &tick()],
&ed_pgp, None,
&artifact("messages/a-cypherpunks-manifesto.txt"),
ed_sig_file.as_path());
// If we don't use --petname, than a self-signed User ID must
// exist.
sq_link(&sq, &ed_fpr, &[ "bob@example.com" ], &[], &[], false);
let ed_sig_file = ed_sig_file.display().to_string();
sq_verify(&sq, None, &[], &[], &ed_sig_file, 0, 1);
sq_link(&sq, &ed_fpr, &[], &[ "bob@example.com" ], &[], false);
sq_verify(&sq, None, &[], &[], &ed_sig_file, 0, 1);
// We should only create links if all the supplied User IDs are
// valid.
sq_link(&sq, &ed_fpr, &["ed@some.org", "bob@example.com"], &[], &[], false);
sq_verify(&sq, None, &[], &[], &ed_sig_file, 0, 1);
sq_link(&sq, &ed_fpr, &["ed@some.org"], &["bob@example.com"], &[], false);
sq_verify(&sq, None, &[], &[], &ed_sig_file, 0, 1);
// Pass an email address to --userid. This shouldn't match
// either.
sq_link(&sq, &ed_fpr, &["ed@other.org"], &[], &[], false);
sq_verify(&sq, None, &[], &[], &ed_sig_file, 0, 1);
// Link all User IDs individually.
sq_link(&sq, &ed_fpr,
&["ed@some.org", "Ed <ed@example.org>", "Eddie <ed@example.org>"],
&["ed@other.org"],
&[], true);
sq_verify(&sq, None, &[], &[], &ed_sig_file, 1, 0);
// Retract the links one at a time.
sq_retract(&sq, &ed_fpr, &[], &[ "ed@other.org" ]);
sq_verify(&sq, None, &[], &[], &ed_sig_file, 1, 0);
sq_retract(&sq, &ed_fpr, &[ "Ed <ed@example.org>" ], &[]);
sq_verify(&sq, None, &[], &[], &ed_sig_file, 1, 0);
sq_retract(&sq, &ed_fpr, &[ "Eddie <ed@example.org>" ], &[]);
sq_verify(&sq, None, &[], &[], &ed_sig_file, 1, 0);
sq_retract(&sq, &ed_fpr, &[ "ed@some.org" ], &[]);
// Now the certificate should no longer be authenticated.
sq_verify(&sq, None, &[], &[], &ed_sig_file, 0, 1);
Ok(())
}
// Set the different parameters. When the parameters are the same,
// make sure no certifications are written; when they are different
// make sure the file changed.
#[test]
fn sq_pki_link_update_detection() -> Result<()> {
let sq = Sq::new();
let alice_userid = "<alice@example.org>";
let (alice, alice_pgp, _) = sq.key_generate(&[], &[alice_userid]);
sq.cert_import(&alice_pgp);
let alice_fpr = alice.fingerprint().to_string();
let alice_cert_pgp = sq.certd()
.join(&alice_fpr[0..2].to_ascii_lowercase())
.join(&alice_fpr[2..].to_ascii_lowercase());
// Reads and returns file. Asserts that old and the new contexts
// are the same (or not).
let compare = |old: Vec<u8>, file: &Path, same: bool| -> Vec<u8> {
let new = std::fs::read(file).unwrap();
if same {
assert_eq!(old, new, "file unexpectedly changed");
} else {
assert_ne!(old, new, "file unexpectedly stayed the same");
}
new
};
let bytes = std::fs::read(&alice_cert_pgp).unwrap();
// Retract it. There is nothing to retract (but this doesn't fail).
let output = sq_retract(&sq, &alice_fpr, &[], &[]);
assert!(output.2.contains("You never certified"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, true);
// Link it.
sq_link(&sq, &alice_fpr, &[], &[], &["--all"], true);
let bytes = compare(bytes, &alice_cert_pgp, false);
// As no parameters changed, this should succeeded, but no
// certification should be written.
let output = sq_link(&sq, &alice_fpr, &[], &[], &["--all"], true);
assert!(output.2.contains("Certification parameters are unchanged"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, true);
// Make Alice a CA.
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained", "--all"],
alice.key_handle(),
NO_USERIDS);
let bytes = compare(bytes, &alice_cert_pgp, false);
sq.pki_link_authorize(&["--time", &tick(), "--unconstrained", "--all"],
alice.key_handle(),
NO_USERIDS);
let bytes = compare(bytes, &alice_cert_pgp, true);
// Make her a partially trusted CA.
let output = sq_link(&sq, &alice_fpr, &[], &[],
&["--amount", "30", "--all"], true);
assert!(output.2.contains("was previously"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, false);
let output = sq_link(&sq, &alice_fpr, &[], &[],
&["--amount", "30", "--all"], true);
assert!(output.2.contains("Certification parameters are unchanged"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, true);
// Retract the link.
let output = sq_retract(&sq, &alice_fpr, &[], &[]);
assert!(output.2.contains("was previously"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, false);
let output = sq_retract(&sq, &alice_fpr, &[], &[]);
assert!(output.2.contains("Certification parameters are unchanged"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, true);
// Link it again.
let output = sq_link(&sq, &alice_fpr, &[], &[],
&["--amount", "10", "--all"], true);
assert!(output.2.contains("was retracted"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, false);
let output = sq_link(&sq, &alice_fpr, &[], &[],
&["--amount", "10", "--all"], true);
assert!(output.2.contains("Certification parameters are unchanged"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, true);
// Use a notation.
let output = sq_link(&sq, &alice_fpr, &[], &[],
&["--signature-notation", "foo", "10", "--all"], true);
assert!(output.2.contains("was previously"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, false);
let output = sq_link(&sq, &alice_fpr, &[], &[],
&["--signature-notation", "foo", "10", "--all"], true);
assert!(output.2.contains("Certification parameters are unchanged"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, true);
// The default link again.
let output = sq_link(&sq, &alice_fpr, &[], &[], &["--all"], true);
assert!(output.2.contains("was previously"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, false);
let output = sq_link(&sq, &alice_fpr, &[], &[], &["--all"], true);
assert!(output.2.contains("Certification parameters are unchanged"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, true);
let _ = bytes;
Ok(())
}
// Check that sq pki link add --temporary works.
#[test]
fn sq_pki_link_add_temporary() -> Result<()> {
let sq = Sq::new();
let alice_userid = "<alice@example.org>";
let (alice, alice_pgp, _) = sq.key_generate(&[], &[alice_userid]);
sq.cert_import(&alice_pgp);
let alice_fpr = alice.fingerprint().to_string();
let alice_cert_pgp = sq.certd()
.join(&alice_fpr[0..2].to_ascii_lowercase())
.join(&alice_fpr[2..].to_ascii_lowercase());
let alice_sig_file = sq.base().join("alice.sig");
sq.sign_args(
&["--time", &tick()],
&alice_pgp, None,
&artifact("messages/a-cypherpunks-manifesto.txt"),
alice_sig_file.as_path());
// Reads and returns file. Asserts that old and the new contexts
// are the same (or not).
let compare = |old: Vec<u8>, file: &Path, same: bool| -> Vec<u8> {
let new = std::fs::read(file).unwrap();
if same {
assert_eq!(old, new, "file unexpectedly changed");
} else {
assert_ne!(old, new, "file unexpectedly stayed the same");
}
new
};
let bytes = std::fs::read(&alice_cert_pgp).unwrap();
let alice_sig_file = alice_sig_file.display().to_string();
sq_verify(&sq, None, &[], &[], &alice_sig_file, 0, 1);
let output = sq_link(&sq, &alice_fpr, &[], &[], &["--temporary", "--all"], true);
assert!(output.2.contains("Certifying "),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
let bytes = compare(bytes, &alice_cert_pgp, false);
// Now it is fully trusted.
sq_verify(&sq, None, &[], &[], &alice_sig_file, 1, 0);
// In 6 days, too.
sq_verify(&sq,
Some(now() + chrono::Duration::seconds(6 * 24 * 60 * 60)),
&[], &[], &alice_sig_file, 1, 0);
// But in 8 days it will only be partially trusted.
sq_verify(&sq,
Some(now() + chrono::Duration::seconds(8 * 24 * 60 * 60)),
&[], &[], &alice_sig_file, 0, 1);
// Now mark it as fully trusted. It should be trusted now, in 6
// days and in 8 days.
let output = sq_link(&sq, &alice_fpr, &[], &[], &["--all"], true);
assert!(output.2.contains("was previously"),
"stdout:\n{}\nstderr:\n{}", output.1, output.2);
eprintln!("{:?}", output);
let bytes = compare(bytes, &alice_cert_pgp, false);
sq_verify(&sq, None, &[], &[], &alice_sig_file, 1, 0);
sq_verify(&sq,
Some(now() + chrono::Duration::seconds(6 * 24 * 60 * 60)),
&[], &[], &alice_sig_file, 1, 0);
sq_verify(&sq,
Some(now() + chrono::Duration::seconds(8 * 24 * 60 * 60)),
&[], &[], &alice_sig_file, 1, 0);
let _bytes = bytes;
Ok(())
}
#[test]
fn retract_non_self_signed() {
// Make sure we can retract non-self signed user IDs.
let mut sq = Sq::new();
let alice_userid = "Alice <alice@example.org>";
let (alice, alice_pgp, _) = sq.key_generate(&[], &[alice_userid]);
sq.key_import(&alice_pgp);
let petname = "Mon chouchou";
let msg = artifact("messages/a-cypherpunks-manifesto.txt");
let sig_msg = sq.scratch_file(None);
let sig_msg = sig_msg.as_path();
let sig_msg_str = sig_msg.display().to_string();
sq.sign(alice.key_handle(), None, &msg, sig_msg);
// Verifying should fail: alice's certificate is not linked at all.
sq_verify(&sq, None, &[], &[], &sig_msg_str, 0, 1);
// Link a non-self-signed user ID.
sq.tick(1);
sq.pki_link_add(&[], alice.key_handle(),
&[UserIDArg::AddUserID(petname)]);
// Now it should work.
sq_verify(&sq, None, &[], &[], &sig_msg_str, 1, 0);
// Retract the link.
sq.tick(1);
sq_retract(&sq, &alice.fingerprint().to_string(), &[petname], &[]);
// Now it should fail.
sq_verify(&sq, None, &[], &[], &sig_msg_str, 0, 1);
}
#[test]
fn retract_weak() {
// Make sure we can retract signed user IDs whose binding
// signatures rely on weak cryptography from a valid certificate.
let sq = Sq::new();
let cert_path = sq.test_data()
.join("keys")
.join("sha1-userid-priv.pgp");
sq.key_import(&cert_path);
let cert = Cert::from_file(&cert_path).expect("can read");
// Make sure the user ID is there and really uses SHA-1.
let vc = cert.with_policy(STANDARD_POLICY, sq.now())
.expect("valid cert");
let valid_userids: BTreeSet<_> = vc.userids()
.map(|ua| ua.userid())
.collect();
let all_userids: BTreeSet<_> = cert.userids()
.map(|ua| ua.userid())
.collect();
assert!(valid_userids.len() < all_userids.len());
let weak_userids: Vec<_>
= all_userids.difference(&valid_userids)
.map(|u| {
String::from_utf8_lossy(u.value()).to_string()
})
.collect();
let weak_userids: Vec<&String> = weak_userids.iter().collect();
// The current policy doesn't allow SHA-1.
assert!(
sq.pki_link_add_maybe(&[], cert.key_handle(), &weak_userids)
.is_err());
// But the policy as of 2003 did.
sq.pki_link_add(&["--policy-as-of", "2003-01-01"],
cert.key_handle(), &weak_userids);
// Retract.
sq.pki_link_retract(&[], cert.key_handle(), &weak_userids[..]);
}
#[test]
fn retract_all() {
// Link all self-signed user IDs and a non-self-signed user ID.
// When we retract all, make sure they are all retracted.
let mut sq = Sq::new();
let alice_userid = "Alice <alice@example.org>";
let (alice, alice_pgp, _) = sq.key_generate(&[], &[alice_userid]);
sq.key_import(&alice_pgp);
let petname = "Mon chouchou";
let msg = artifact("messages/a-cypherpunks-manifesto.txt");
let sig_msg = sq.scratch_file(None);
let sig_msg = sig_msg.as_path();
let sig_msg_str = sig_msg.display().to_string();
sq.sign(alice.key_handle(), None, &msg, sig_msg);
// Verifying should fail: alice's certificate is not linked at all.
sq_verify(&sq, None, &[], &[], &sig_msg_str, 0, 1);
// Link a non-self-signed user ID.
sq.tick(1);
sq.pki_link_add(&[], alice.key_handle(), &[UserIDArg::AddUserID(petname)]);
// Now it should work.
sq_verify(&sq, None, &[], &[], &sig_msg_str, 1, 0);
// Retract *all* links.
sq.tick(1);
sq_retract(&sq, &alice.fingerprint().to_string(), &[], &[]);
// Now it should fail.
sq_verify(&sq, None, &[], &[], &sig_msg_str, 0, 1);
}
#[test]
fn no_ambiguous_email() {
// Check that we can't address self-signed user IDs by an
// ambiguous email address.
let mut sq = Sq::new();
let alice_userids = &["Alice <alice@example.org>",
"Alice Lovelace <alice@example.org>"][..];
let (alice, alice_pgp, _) = sq.key_generate(&[], alice_userids);
sq.key_import(&alice_pgp);
sq.tick(1);
// Ambiguous.
assert!(
sq.pki_link_add_maybe(
&[], alice.key_handle(), &[UserIDArg::Email("alice@example.org")])
.is_err());
assert!(
sq.pki_link_add_maybe(
&[], alice.key_handle(), &[UserIDArg::AddEmail("alice@example.org")])
.is_err());
// Not a self-signed user ID.
assert!(
sq.pki_link_add_maybe(
&[], alice.key_handle(), &[UserIDArg::UserID("alice@example.org")])
.is_err());
// Fully qualified is okay.
sq.pki_link_add(
&[], alice.key_handle(),
&[UserIDArg::UserID("Alice <alice@example.org>")]);
// As well as adding as a user ID.
sq.pki_link_add(
&[], alice.key_handle(),
&[UserIDArg::AddUserID("<alice@example.org>")]);
}
#[test]
fn special_names() {
// Check that --cert-special works.
let sq = Sq::new();
let check = |cmd: &str, args: &[&str], name: &str, success: bool| {
let mut c = sq.command();
c.args([ "pki", "link", cmd, "--cert-special", name ]);
c.args(args);
sq.run(c, Some(success));
};
const SPECIAL_STRINGS: &'static [&'static str] = &[
"public-directories",
"keys.openpgp.org",
"keys.mailvelope.com",
"proton.me",
"wkd",
"dane",
"autocrypt",
"web",
];
for name in SPECIAL_STRINGS.iter() {
check("add", &["--all"], name, true);
}
check("add", &["--all"], "xxx", false);
for name in SPECIAL_STRINGS.iter() {
check("retract", &["--all"], name, true);
}
check("retract", &["--all"], "xxx", false);
for name in SPECIAL_STRINGS.iter() {
check("authorize", &["--all", "--unconstrained"], name, true);
}
check("authorize", &["--all", "--unconstrained"], "xxx", false);
for name in SPECIAL_STRINGS.iter() {
check("retract", &["--all"], name, true);
}
check("retract", &["--all"], "xxx", false);
}