11140ae5c1
- Extend `sq cert import` to import bare revocation certificates. - See #50.
166 lines
4.9 KiB
Rust
166 lines
4.9 KiB
Rust
use std::fs::File;
|
|
|
|
use sequoia_openpgp as openpgp;
|
|
use openpgp::Packet;
|
|
use openpgp::PacketPile;
|
|
use openpgp::Result;
|
|
use openpgp::cert::prelude::*;
|
|
use openpgp::packet::Tag;
|
|
use openpgp::parse::Parse;
|
|
use openpgp::serialize::Serialize;
|
|
use openpgp::types::RevocationStatus;
|
|
|
|
use super::common::Sq;
|
|
use super::common::STANDARD_POLICY;
|
|
|
|
#[test]
|
|
fn sq_cert_import() -> Result<()>
|
|
{
|
|
let sq = Sq::new();
|
|
|
|
// Generate keys.
|
|
let (_cert, alice_pgp, _rev) =
|
|
sq.key_generate(&["--expiration", "never"], &["<alice@example.org>"]);
|
|
|
|
let alice_bytes = std::fs::read(&alice_pgp)?;
|
|
|
|
let (_cert, bob_pgp, _rev) =
|
|
sq.key_generate(&["--expiration", "never"], &["<bob@example.org>"]);
|
|
|
|
let (_cert, carol_pgp, _rev) =
|
|
sq.key_generate(&["--expiration", "never"], &["<carol@example.org>"]);
|
|
|
|
let alice_pgp = alice_pgp.display().to_string();
|
|
let alice_pgp = &alice_pgp[..];
|
|
let bob_pgp = bob_pgp.display().to_string();
|
|
let bob_pgp = &bob_pgp[..];
|
|
let carol_pgp = carol_pgp.display().to_string();
|
|
let carol_pgp = &carol_pgp[..];
|
|
|
|
let files = &[ alice_pgp, bob_pgp, carol_pgp ];
|
|
|
|
let check = |files: &[&str], stdin: Option<&[u8]>, expected: usize|
|
|
{
|
|
// Use a fresh certd.
|
|
let sq = Sq::new();
|
|
|
|
// Import.
|
|
let mut cmd = sq.command();
|
|
cmd.args(["cert", "import"]);
|
|
cmd.args(files);
|
|
if let Some(stdin) = stdin {
|
|
cmd.write_stdin(stdin);
|
|
}
|
|
eprintln!("sq cert import {}{}",
|
|
files.join(" "),
|
|
if stdin.is_some() { "<BYTES" } else { "" });
|
|
cmd.assert().success();
|
|
|
|
|
|
// Export.
|
|
let mut cmd = sq.command();
|
|
cmd.args(["cert", "export", "--all"]);
|
|
|
|
eprintln!("sq cert export...");
|
|
|
|
let output = cmd.output().expect("success");
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(output.status.success(),
|
|
"sq cert export should succeed\n\
|
|
stdout:\n{}\nstderr:\n{}",
|
|
stdout, stderr);
|
|
|
|
let parser = CertParser::from_bytes(stdout.as_bytes())
|
|
.expect("valid");
|
|
let found = parser.collect::<Result<Vec<Cert>>>()
|
|
.expect("valid");
|
|
|
|
assert_eq!(expected, found.len(),
|
|
"expected: {}\nfound: {} ({})\n\
|
|
stdout:\n{}\nstderr:\n{}",
|
|
expected, found.len(),
|
|
found.iter().map(|c| c.fingerprint().to_string())
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
stdout, stderr);
|
|
};
|
|
|
|
// Import from N files.
|
|
for i in 1..=files.len() {
|
|
check(&files[0..i], None, i);
|
|
}
|
|
|
|
// Import from stdin.
|
|
check(&[], Some(&alice_bytes[..]), 1);
|
|
|
|
// Specify "-".
|
|
check(&["-"], Some(&alice_bytes[..]), 1);
|
|
|
|
// Provide stdin and a file. Only the file should be read.
|
|
check(&[bob_pgp], Some(&alice_bytes[..]), 1);
|
|
|
|
// Provide stdin explicitly and a file. Both should be read.
|
|
check(&[bob_pgp, "-"], Some(&alice_bytes[..]), 2);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn sq_cert_import_rev() -> Result<()>
|
|
{
|
|
let sq = Sq::new();
|
|
|
|
// Generate a key. (We don't use sq on purpose: we want to make
|
|
// sure we have a bare revocation certificate.)
|
|
let (cert, rev) = CertBuilder::general_purpose(
|
|
None, Some("alice@example.org"))
|
|
.set_creation_time(sq.now())
|
|
.generate()?;
|
|
|
|
let cert_file = sq.scratch_file("cert");
|
|
cert.as_tsk().serialize(&mut File::create(&cert_file)?)?;
|
|
|
|
sq.key_import(&cert_file);
|
|
|
|
|
|
// We shouldn't be able to import a signature over a data file.
|
|
|
|
// Create a detached signature.
|
|
let sig_file = sq.scratch_file("sig");
|
|
sq.sign_detached(&[], cert.fingerprint(),
|
|
cert_file.as_path(), sig_file.as_path());
|
|
|
|
// Be extra sure that it is a single packet.
|
|
let pp = PacketPile::from_file(&sig_file)?;
|
|
let packets = pp.into_children().collect::<Vec<_>>();
|
|
assert_eq!(packets.len(), 1);
|
|
assert_eq!(packets[0].tag(), Tag::Signature);
|
|
|
|
// Assert that it can't be imported.
|
|
assert!(sq.cert_import_maybe(&sig_file).is_err());
|
|
|
|
|
|
// We should be able to import a bare revocation certificate.
|
|
|
|
// Assert that the certificate is not revoked.
|
|
let cert = sq.cert_export(cert.key_handle());
|
|
assert!(! matches!(
|
|
cert.revocation_status(STANDARD_POLICY, sq.now()),
|
|
RevocationStatus::Revoked(_)));
|
|
|
|
// Import the revocation certificate.
|
|
let rev_file = sq.scratch_file("rev");
|
|
Packet::from(rev).serialize(&mut File::create(&rev_file)?)?;
|
|
sq.cert_import(rev_file);
|
|
|
|
// Assert that the certificate is now revoked.
|
|
let cert = sq.cert_export(cert.key_handle());
|
|
assert!(matches!(
|
|
cert.revocation_status(STANDARD_POLICY, sq.now()),
|
|
RevocationStatus::Revoked(_)));
|
|
|
|
Ok(())
|
|
}
|