sequoia-sq/tests/integration/sq_pki_link.rs

858 lines
30 KiB
Rust
Raw Normal View History

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(),
2024-01-17 12:47:41 +03:00
"'sq pki link add' failed unexpectedly\
\nstdout:\n{}\nstderr:\n{}",
stdout, stderr);
} else {
assert!(! output.status.success(),
2024-01-17 12:47:41 +03:00
"'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);
}