3c90428112
Instead of using a non-uniform `--export` for `sq key generate` to indicate the file path to output to, rely on the generic `--output`, provided by `sq_cli::types::FileOrStdout`.
420 lines
15 KiB
Rust
420 lines
15 KiB
Rust
use assert_cmd::Command;
|
|
use tempfile::TempDir;
|
|
|
|
use sequoia_openpgp as openpgp;
|
|
use openpgp::KeyHandle;
|
|
use openpgp::Result;
|
|
use openpgp::cert::prelude::*;
|
|
use openpgp::parse::Parse;
|
|
|
|
mod integration {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn sq_encrypt_using_cert_store() -> Result<()>
|
|
{
|
|
let dir = TempDir::new()?;
|
|
|
|
let certd = dir.path().join("cert.d").display().to_string();
|
|
std::fs::create_dir(&certd).expect("mkdir works");
|
|
let key_pgp = dir.path().join("key.pgp").display().to_string();
|
|
|
|
// Generate a key.
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"key", "generate",
|
|
"--expiry", "never",
|
|
"--userid", "<alice@example.org>",
|
|
"--output", &key_pgp]);
|
|
cmd.assert().success();
|
|
|
|
let cert = Cert::from_file(&key_pgp)?;
|
|
|
|
// Try to encrypt a message. This should fail, because we
|
|
// haven't imported the key.
|
|
for kh in cert.keys().map(|ka| KeyHandle::from(ka.fingerprint()))
|
|
.chain(cert.keys().map(|ka| KeyHandle::from(ka.keyid())))
|
|
{
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"encrypt",
|
|
"--recipient-cert",
|
|
&kh.to_string()])
|
|
.write_stdin("a secret message")
|
|
.assert().failure();
|
|
}
|
|
|
|
// Import the key.
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"import", &key_pgp]);
|
|
cmd.assert().success();
|
|
|
|
const MESSAGE: &str = "\na secret message\n\nor two\n";
|
|
|
|
// Now we should be able to encrypt a message to it, and
|
|
// decrypt it.
|
|
for kh in cert.keys().map(|ka| KeyHandle::from(ka.fingerprint()))
|
|
.chain(cert.keys().map(|ka| KeyHandle::from(ka.keyid())))
|
|
{
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"encrypt",
|
|
"--recipient-cert",
|
|
&kh.to_string()])
|
|
.write_stdin(MESSAGE);
|
|
|
|
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(),
|
|
"encryption succeeds for {}\nstdout:\n{}\nstderr:\n{}",
|
|
kh, stdout, stderr);
|
|
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["decrypt",
|
|
"--recipient-file",
|
|
&key_pgp])
|
|
.write_stdin(stdout.as_bytes());
|
|
|
|
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(),
|
|
"decryption succeeds for {}\nstdout:\n{}\nstderr:\n{}",
|
|
kh, stdout, stderr);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn sq_encrypt_recipient_userid() -> Result<()>
|
|
{
|
|
let dir = TempDir::new()?;
|
|
|
|
let certd = dir.path().join("cert.d").display().to_string();
|
|
std::fs::create_dir(&certd).expect("mkdir works");
|
|
|
|
let alice_pgp = dir.path().join("alice.pgp").display().to_string();
|
|
let bob_pgp = dir.path().join("bob.pgp").display().to_string();
|
|
|
|
// Generate the keys.
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"key", "generate",
|
|
"--expiry", "never",
|
|
"--userid", "<alice@example.org>",
|
|
"--output", &alice_pgp]);
|
|
cmd.assert().success();
|
|
let alice = Cert::from_file(&alice_pgp)?;
|
|
|
|
let bob_userids = &[
|
|
"<bob@some.org>",
|
|
"Bob <bob@other.org>",
|
|
"<bob@other.org>",
|
|
];
|
|
let bob_emails = &[
|
|
"bob@some.org",
|
|
"bob@other.org",
|
|
];
|
|
|
|
let bob_certified_userids = &[
|
|
"Bob <bob@other.org>",
|
|
];
|
|
let bob_certified_emails = &[
|
|
"bob@other.org",
|
|
];
|
|
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"key", "generate",
|
|
"--expiry", "never",
|
|
"--output", &bob_pgp]);
|
|
for userid in bob_userids.iter() {
|
|
cmd.args(["--userid", userid]);
|
|
}
|
|
cmd.assert().success();
|
|
let bob = Cert::from_file(&bob_pgp)?;
|
|
|
|
// Import the certificates.
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"import", &alice_pgp]);
|
|
cmd.assert().success();
|
|
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"import", &bob_pgp]);
|
|
cmd.assert().success();
|
|
|
|
const MESSAGE: &[u8] = &[0x42; 24 * 1024 + 23];
|
|
let encrypt = |trust_roots: &[&str],
|
|
recipients: &[(&str, &str)],
|
|
decryption_keys: &[&str]|
|
|
{
|
|
let mut cmd = Command::cargo_bin("sq").unwrap();
|
|
cmd.args(["--cert-store", &certd]);
|
|
for trust_root in trust_roots {
|
|
cmd.args(["--trust-root", trust_root]);
|
|
}
|
|
cmd.arg("encrypt");
|
|
|
|
// Make a string for debugging.
|
|
let mut cmd_display = "sq encrypt".to_string();
|
|
|
|
for (option, recipient) in recipients.iter() {
|
|
cmd.args([option, recipient]);
|
|
|
|
cmd_display.push_str(" ");
|
|
cmd_display.push_str(option);
|
|
cmd_display.push_str(" ");
|
|
cmd_display.push_str(recipient);
|
|
}
|
|
cmd.write_stdin(MESSAGE);
|
|
|
|
let output = cmd.output().expect("success");
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
if decryption_keys.is_empty() {
|
|
assert!(! output.status.success(),
|
|
"'{}' should have failed\nstdout:\n{}\nstderr:\n{}",
|
|
cmd_display, stdout, stderr);
|
|
} else {
|
|
assert!(output.status.success(),
|
|
"'{}' should have succeeded\nstdout:\n{}\nstderr:\n{}",
|
|
cmd_display, stdout, stderr);
|
|
|
|
for key in decryption_keys.iter() {
|
|
let mut cmd = Command::cargo_bin("sq").unwrap();
|
|
cmd.args(["--no-cert-store",
|
|
"decrypt",
|
|
"--recipient-file",
|
|
&key])
|
|
.write_stdin(stdout.as_bytes());
|
|
|
|
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(),
|
|
"'{}' decryption should succeed\nstdout:\n{}\nstderr:\n{}",
|
|
cmd_display, stdout, stderr);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Encryption by fingerprint should work.
|
|
encrypt(&[],
|
|
&[("--recipient-cert", &bob.fingerprint().to_string())],
|
|
&[&bob_pgp]);
|
|
|
|
// Encryption by email address and user id should fail if the
|
|
// binding can't be authenticated.
|
|
for email in bob_emails.iter() {
|
|
encrypt(&[],
|
|
&[("--recipient-email", email)],
|
|
&[]);
|
|
}
|
|
for userid in bob_userids.iter() {
|
|
encrypt(&[],
|
|
&[("--recipient-userid", userid)],
|
|
&[]);
|
|
}
|
|
|
|
// Alice certifies Bob's certificate.
|
|
for userid in bob_certified_userids {
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"certify", &alice_pgp, &bob_pgp, userid]);
|
|
|
|
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 certify {} ...' should have succeeded\
|
|
\nstdout:\n{}\nstderr:\n{}",
|
|
userid, stdout, stderr);
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"import"])
|
|
.write_stdin(stdout.as_bytes());
|
|
cmd.assert().success();
|
|
}
|
|
|
|
// Still don't use a trust root. This should still fail.
|
|
for email in bob_emails.iter() {
|
|
encrypt(&[],
|
|
&[("--recipient-email", email)],
|
|
&[]);
|
|
}
|
|
for userid in bob_userids.iter() {
|
|
encrypt(&[],
|
|
&[("--recipient-userid", userid)],
|
|
&[]);
|
|
}
|
|
|
|
// Make Alice the trust root. This should succeed.
|
|
for email in bob_emails.iter() {
|
|
if bob_certified_emails.contains(email) {
|
|
encrypt(&[&alice.fingerprint().to_string()],
|
|
&[("--recipient-email", email)],
|
|
&[ &bob_pgp ]);
|
|
} else {
|
|
encrypt(&[&alice.fingerprint().to_string()],
|
|
&[("--recipient-email", email)],
|
|
&[]);
|
|
}
|
|
}
|
|
for userid in bob_userids.iter() {
|
|
if bob_certified_userids.contains(userid) {
|
|
encrypt(&[&alice.fingerprint().to_string()],
|
|
&[("--recipient-userid", userid)],
|
|
&[ &bob_pgp ]);
|
|
} else {
|
|
encrypt(&[&alice.fingerprint().to_string()],
|
|
&[("--recipient-userid", userid)],
|
|
&[]);
|
|
}
|
|
}
|
|
|
|
// Make Bob a trust root. This should succeed for all
|
|
// self-signed user ids.
|
|
for email in bob_emails.iter() {
|
|
encrypt(&[&bob.fingerprint().to_string()],
|
|
&[("--recipient-email", email)],
|
|
&[&bob_pgp]);
|
|
}
|
|
for userid in bob_userids.iter() {
|
|
encrypt(&[&bob.fingerprint().to_string()],
|
|
&[("--recipient-userid", userid)],
|
|
&[&bob_pgp]);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Encrypt a message to two recipients: one whose certificate is
|
|
// in the certificate store, and one whose certificated is in a
|
|
// keyring.
|
|
#[test]
|
|
fn sq_encrypt_keyring() -> Result<()>
|
|
{
|
|
let dir = TempDir::new()?;
|
|
|
|
let certd = dir.path().join("cert.d").display().to_string();
|
|
std::fs::create_dir(&certd).expect("mkdir works");
|
|
|
|
let alice_pgp = dir.path().join("alice.pgp").display().to_string();
|
|
let bob_pgp = dir.path().join("bob.pgp").display().to_string();
|
|
|
|
// Generate the keys.
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"key", "generate",
|
|
"--expiry", "never",
|
|
"--userid", "<alice@example.org>",
|
|
"--output", &alice_pgp]);
|
|
cmd.assert().success();
|
|
let alice = Cert::from_file(&alice_pgp)?;
|
|
let alice_fpr = alice.fingerprint().to_string();
|
|
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"key", "generate",
|
|
"--expiry", "never",
|
|
"--userid", "<bob@example.org>",
|
|
"--output", &bob_pgp]);
|
|
cmd.assert().success();
|
|
let bob = Cert::from_file(&bob_pgp)?;
|
|
let bob_fpr = bob.keyid().to_string();
|
|
|
|
const MESSAGE: &[u8] = &[0x42; 24 * 1024 + 23];
|
|
let encrypt = |keyrings: &[&str],
|
|
recipients: &[&str],
|
|
decryption_keys: &[&str]|
|
|
{
|
|
let mut cmd = Command::cargo_bin("sq").unwrap();
|
|
cmd.args(["--cert-store", &certd]);
|
|
|
|
// Make a string for debugging.
|
|
let mut cmd_display = "sq".to_string();
|
|
|
|
for keyring in keyrings.iter() {
|
|
cmd.args(["--keyring", keyring]);
|
|
|
|
cmd_display.push_str(" --keyring ");
|
|
cmd_display.push_str(keyring);
|
|
}
|
|
|
|
cmd_display.push_str(" encrypt");
|
|
cmd.arg("encrypt");
|
|
|
|
for recipient in recipients.iter() {
|
|
cmd.args(["--recipient-cert", recipient]);
|
|
|
|
cmd_display.push_str(" --recipient-cert ");
|
|
cmd_display.push_str(recipient);
|
|
}
|
|
cmd.write_stdin(MESSAGE);
|
|
|
|
let output = cmd.output().expect("success");
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
if decryption_keys.is_empty() {
|
|
assert!(! output.status.success(),
|
|
"'{}' should have failed\nstdout:\n{}\nstderr:\n{}",
|
|
cmd_display, stdout, stderr);
|
|
} else {
|
|
assert!(output.status.success(),
|
|
"'{}' should have succeeded\nstdout:\n{}\nstderr:\n{}",
|
|
cmd_display, stdout, stderr);
|
|
|
|
for key in decryption_keys.iter() {
|
|
let mut cmd = Command::cargo_bin("sq").unwrap();
|
|
cmd.args(["--no-cert-store",
|
|
"decrypt",
|
|
"--recipient-file",
|
|
&key])
|
|
.write_stdin(stdout.as_bytes());
|
|
|
|
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(),
|
|
"'{}' decryption should succeed\nstdout:\n{}\nstderr:\n{}",
|
|
cmd_display, stdout, stderr);
|
|
}
|
|
}
|
|
};
|
|
|
|
encrypt(&[&alice_pgp, &bob_pgp],
|
|
&[&alice_fpr, &bob_fpr],
|
|
&[&alice_pgp, &bob_pgp]);
|
|
|
|
// Import Alice's certificate.
|
|
let mut cmd = Command::cargo_bin("sq")?;
|
|
cmd.args(["--cert-store", &certd,
|
|
"import", &alice_pgp]);
|
|
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 import should succeed\nstdout:\n{}\nstderr:\n{}",
|
|
stdout, stderr);
|
|
|
|
encrypt(&[&alice_pgp, &bob_pgp],
|
|
&[&alice_fpr, &bob_fpr],
|
|
&[&alice_pgp, &bob_pgp]);
|
|
|
|
encrypt(&[&bob_pgp],
|
|
&[&alice_fpr, &bob_fpr],
|
|
&[&alice_pgp, &bob_pgp]);
|
|
|
|
|
|
Ok(())
|
|
}
|
|
}
|