use std::path::Path; 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 super::common::artifact; use super::common::Sq; use super::common::FileOrKeyHandle; // 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>> = 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 { *TIME.get_or_init(|| Mutex::new(chrono::Utc::now())).lock().unwrap() } // Verifies a signed message. fn sq_verify(sq: &Sq, time: Option>, 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", "--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); } 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) { 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::() { kh.into() } else { FileOrKeyHandle::FileOrStdin(cert.into()) }; sq.pki_vouch_certify(&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, depth: Option) { 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::() { 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 = ""; 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 = ""; 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 = ""; 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 = ""; 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.command() .arg("sign") .args(["--signer-file", &data.key_file]) .args(["--output", &data.sig_file]) .args(["--time", &tick()]) .arg(&artifact("messages/a-cypherpunks-manifesto.txt")) .assert() .success(); } // 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 ", "Eddie ", // This is not considered to be an email address as // it is not wrapped in angle brackets. "ed@some.org", // But this is. "", ]); sq.cert_import(&ed_pgp); let ed_pgp = ed_pgp.display().to_string(); let ed_fpr = ed.fingerprint().to_string(); let ed_sig_file = dir.path().join("ed.sig").display().to_string(); sq.command() .arg("sign") .args(["--signer-file", &ed_pgp]) .args(["--output", &ed_sig_file]) .args(["--time", &tick()]) .arg(&artifact("messages/a-cypherpunks-manifesto.txt")) .assert() .success(); // If we don't use --petname, than a self-signed User ID must // exist. sq_link(&sq, &ed_fpr, &[ "bob@example.com" ], &[], &[], false); 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@other.org", "ed@example.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 " ], &[]); sq_verify(&sq, None, &[], &[], &ed_sig_file, 1, 0); sq_retract(&sq, &ed_fpr, &[ "Eddie " ], &[]); 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 = ""; 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, file: &Path, same: bool| -> Vec { 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"], alice.key_handle(), &[]); let bytes = compare(bytes, &alice_cert_pgp, false); sq.pki_link_authorize(&["--time", &tick(), "--unconstrained", "--all"], alice.key_handle(), &[]); 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, &[], &[], &["--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, &[], &[], &["--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 = ""; let (alice, alice_pgp, _) = sq.key_generate(&[], &[alice_userid]); sq.cert_import(&alice_pgp); let alice_pgp = alice_pgp.display().to_string(); 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").display().to_string(); sq.command() .arg("sign") .args(["--signer-file", &alice_pgp]) .args(["--output", &alice_sig_file]) .args(["--time", &tick()]) .arg(&artifact("messages/a-cypherpunks-manifesto.txt")) .assert() .success(); // Reads and returns file. Asserts that old and the new contexts // are the same (or not). let compare = |old: Vec, file: &Path, same: bool| -> Vec { 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(); 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 "; 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(&["--add-userid"], alice.key_handle(), &[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_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 "; 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(&["--add-userid"], alice.key_handle(), &[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); }