Support addressing recipients by email address and User ID
- Extend `sq encrypt` with the `--recipient-email` and `--recipient-userid` arguments to allow the caller to designate a certificate by email address or User ID, respectively. An email address or User ID is considered to designate a certificate, if the binding between the email address or User ID and the certificate can be authenticated using the web of trust. - Add support for the web of trust using the `sequoia-wot` crate. - Add a top-level option, `--trust-root`, to allow the user to specify trust roots.
This commit is contained in:
parent
62e6b4cb8b
commit
6c7b0de5c0
74
Cargo.lock
generated
74
Cargo.lock
generated
@ -477,6 +477,16 @@ dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_mangen"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc872a4bca8ddf10be882b81d36f1c2817e43c5c59862ac25f401af581dc4181"
|
||||
dependencies = [
|
||||
"clap 4.0.32",
|
||||
"roff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmac"
|
||||
version = "0.5.1"
|
||||
@ -818,6 +828,12 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dot-writer"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d1b11bd5e7e98406c6ff39fbc94d6e910a489b978ce7f17c19fce91a1195b7a"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.5"
|
||||
@ -920,6 +936,16 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumber"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa35b49b30d8f4219e279f22c4b7c899aa7f98f475da4eff84b75f17ba11ed19"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.9.0"
|
||||
@ -2821,6 +2847,20 @@ dependencies = [
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-policy-config"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12f41f8b29fdc21666e6a49d7d7a9c4396f83b11052de0e5434b35aebf302075"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"sequoia-openpgp",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-sq"
|
||||
version = "0.28.0"
|
||||
@ -2843,6 +2883,7 @@ dependencies = [
|
||||
"sequoia-cert-store",
|
||||
"sequoia-net",
|
||||
"sequoia-openpgp",
|
||||
"sequoia-wot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"subplot-build",
|
||||
@ -2852,6 +2893,30 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-wot"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb08c5484e6265aa8cd19d523c09e88f575fc040bf9e6dfdbbeeff3ed0ba07d0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap 4.0.32",
|
||||
"clap_complete",
|
||||
"clap_mangen",
|
||||
"crossbeam",
|
||||
"dot-writer",
|
||||
"enumber",
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
"openpgp-cert-d",
|
||||
"sequoia-cert-store",
|
||||
"sequoia-openpgp",
|
||||
"sequoia-policy-config",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.137"
|
||||
@ -3430,6 +3495,15 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.1"
|
||||
|
@ -34,13 +34,13 @@ dirs = "4"
|
||||
sequoia-openpgp = { version = "1.13", default-features = false, features = ["compression-deflate"] }
|
||||
sequoia-autocrypt = { version = "0.25", default-features = false, optional = true }
|
||||
sequoia-net = { version = "0.26", default-features = false }
|
||||
#sequoia-wot = { version = "0.6" }
|
||||
anyhow = "1.0.18"
|
||||
chrono = "0.4.10"
|
||||
clap = { version = "4", features = ["derive", "env", "wrap_help"] }
|
||||
itertools = "0.10"
|
||||
once_cell = "1.17"
|
||||
sequoia-cert-store = "0.2"
|
||||
sequoia-wot = "0.7"
|
||||
tempfile = "3.1"
|
||||
term_size = "0.3"
|
||||
tokio = { version = "1.13.1" }
|
||||
|
5
NEWS
5
NEWS
@ -34,6 +34,11 @@
|
||||
option replaces the various subcommand's `--time` argument as
|
||||
well as `sq key generate` and `sq key userid add`'s
|
||||
`--creation-time` arguments.
|
||||
- Add top-level option, `--trust-root`, to allow the user to
|
||||
specify trust roots.
|
||||
- Extend `sq encrypt` to allow addressing recipients by User ID
|
||||
(`--recipient-userid`) or email address (`--recipient-email`).
|
||||
Only User IDs that can be fully authenticated are considered.
|
||||
* Deprecated functionality
|
||||
- `sq key generate --creation-time TIME` is deprecated in favor of
|
||||
`sq key generate --time TIME`.
|
||||
|
249
src/sq.rs
249
src/sq.rs
@ -27,6 +27,7 @@ use openpgp::{
|
||||
};
|
||||
use openpgp::{armor, Cert};
|
||||
use openpgp::crypto::Password;
|
||||
use openpgp::Fingerprint;
|
||||
use openpgp::packet::prelude::*;
|
||||
use openpgp::parse::{Parse, PacketParser, PacketParserResult};
|
||||
use openpgp::packet::signature::subpacket::NotationData;
|
||||
@ -35,10 +36,15 @@ use openpgp::serialize::{Serialize, stream::{Message, Armorer}};
|
||||
use openpgp::cert::prelude::*;
|
||||
use openpgp::policy::StandardPolicy as P;
|
||||
use openpgp::types::KeyFlags;
|
||||
use openpgp::types::RevocationStatus;
|
||||
|
||||
use sequoia_cert_store as cert_store;
|
||||
use cert_store::Store;
|
||||
use cert_store::store::StoreError;
|
||||
use cert_store::store::UserIDQueryParams;
|
||||
|
||||
use sequoia_wot as wot;
|
||||
use wot::store::Store as _;
|
||||
|
||||
use clap::FromArgMatches;
|
||||
use crate::sq_cli::packet;
|
||||
@ -572,9 +578,9 @@ impl<'store> Config<'store> {
|
||||
|
||||
if let Some(keyflags) = keyflags.as_ref() {
|
||||
certs.retain(|cert| {
|
||||
// XXX: Respect any subcommand-specific
|
||||
// reference time.
|
||||
let vc = match cert.with_policy(&self.policy, None) {
|
||||
let vc = match cert.with_policy(
|
||||
&self.policy, self.time)
|
||||
{
|
||||
Ok(vc) => vc,
|
||||
Err(err) => {
|
||||
let err = err.context(
|
||||
@ -636,6 +642,235 @@ impl<'store> Config<'store> {
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
/// Looks up certificates by User ID or email address.
|
||||
///
|
||||
/// This only returns certificates that can be authenticate for
|
||||
/// the specified User ID (or email address, if `email` is true).
|
||||
/// If no certificate can be authenticated for some User ID,
|
||||
/// returns an error. If multiple certificates can be
|
||||
/// authenticated for a given User ID or email address, then
|
||||
/// returns them all.
|
||||
fn lookup_by_userid(&self, trust_roots: &[Fingerprint],
|
||||
userid: &[String], email: bool)
|
||||
-> Result<Vec<Cert>>
|
||||
{
|
||||
if userid.is_empty() {
|
||||
return Ok(Vec::new())
|
||||
}
|
||||
|
||||
let cert_store = self.cert_store_or_else()?;
|
||||
|
||||
// Build a WoT network.
|
||||
|
||||
let cert_store = wot::store::CertStore::from_store(
|
||||
cert_store, &self.policy, self.time);
|
||||
let n = wot::Network::new(&cert_store)?;
|
||||
let mut q = wot::QueryBuilder::new(&n);
|
||||
q.roots(wot::Roots::new(trust_roots.iter().cloned()));
|
||||
let q = q.build();
|
||||
|
||||
let mut results: Vec<Cert> = Vec::new();
|
||||
// We try hard to not just stop at the first error, but lint
|
||||
// the input so that the user gets as much feedback as
|
||||
// possible. The first error that we encounter is saved here,
|
||||
// and returned. The rest are printed directly.
|
||||
let mut error: Option<anyhow::Error> = None;
|
||||
|
||||
// Iterate over each User ID address, find any certificates
|
||||
// associated with the User ID, validate the certificates, and
|
||||
// finally authenticate them for the User ID.
|
||||
for userid in userid.iter() {
|
||||
let matches: Vec<(Fingerprint, UserID)> = if email {
|
||||
if let Err(err) = UserIDQueryParams::is_email(userid) {
|
||||
eprintln!("{:?} is not a valid email address", userid);
|
||||
if error.is_none() {
|
||||
error = Some(err);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get all certificates that are associated with the email
|
||||
// address.
|
||||
cert_store.lookup_synopses_by_email(userid)
|
||||
} else {
|
||||
let userid = UserID::from(&userid[..]);
|
||||
cert_store.lookup_synopses_by_userid(userid.clone())
|
||||
.into_iter()
|
||||
.map(|fpr| (fpr, userid.clone()))
|
||||
.collect()
|
||||
};
|
||||
|
||||
if matches.is_empty() {
|
||||
if error.is_none() {
|
||||
error = Some(anyhow::anyhow!(
|
||||
"No certificates are associated with {:?}",
|
||||
userid));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
fpr: Fingerprint,
|
||||
userid: UserID,
|
||||
cert: Result<Cert>,
|
||||
}
|
||||
let entries = matches.into_iter().map(|(fpr, userid)| {
|
||||
// We've got a match, or two, or three... Lookup the certs.
|
||||
let cert = match cert_store.lookup_by_cert_fpr(&fpr) {
|
||||
Ok(cert) => cert,
|
||||
Err(err) => {
|
||||
let err = err.context(format!(
|
||||
"Error fetching {} ({:?})",
|
||||
fpr, String::from_utf8_lossy(userid.value())));
|
||||
return Entry { fpr, userid, cert: Err(err), };
|
||||
}
|
||||
};
|
||||
|
||||
// Parse the LazyCerts.
|
||||
let cert = match cert.into_owned().into_cert() {
|
||||
Ok(cert) => cert,
|
||||
Err(err) => {
|
||||
let err = err.context(format!(
|
||||
"Error parsing {} ({:?})",
|
||||
fpr, String::from_utf8_lossy(userid.value())));
|
||||
return Entry { fpr, userid, cert: Err(err), };
|
||||
}
|
||||
};
|
||||
|
||||
// Check the certs for validity.
|
||||
let vc = match cert.with_policy(&self.policy, self.time) {
|
||||
Ok(vc) => vc,
|
||||
Err(err) => {
|
||||
let err = err.context(format!(
|
||||
"Certificate {} ({:?}) is invalid",
|
||||
fpr, String::from_utf8_lossy(userid.value())));
|
||||
return Entry { fpr, userid, cert: Err(err) };
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = vc.alive() {
|
||||
let err = err.context(format!(
|
||||
"Certificate {} ({:?}) is invalid",
|
||||
fpr, String::from_utf8_lossy(userid.value())));
|
||||
return Entry { fpr, userid, cert: Err(err), };
|
||||
}
|
||||
|
||||
if let RevocationStatus::Revoked(_) = vc.revocation_status() {
|
||||
let err = anyhow::anyhow!(
|
||||
"Certificate {} ({:?}) is revoked",
|
||||
fpr, String::from_utf8_lossy(userid.value()));
|
||||
return Entry { fpr, userid, cert: Err(err), };
|
||||
}
|
||||
|
||||
if let Some(ua) = vc.userids().find(|ua| {
|
||||
ua.userid() == &userid
|
||||
})
|
||||
{
|
||||
if let RevocationStatus::Revoked(_) = ua.revocation_status() {
|
||||
let err = anyhow::anyhow!(
|
||||
"User ID {:?} on certificate {} is revoked",
|
||||
String::from_utf8_lossy(userid.value()), fpr);
|
||||
return Entry { fpr, userid, cert: Err(err), };
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate the bindings.
|
||||
let paths = q.authenticate(
|
||||
&userid, cert.fingerprint(),
|
||||
// XXX: Make this user configurable.
|
||||
wot::FULLY_TRUSTED);
|
||||
let r = if paths.amount() < wot::FULLY_TRUSTED {
|
||||
Err(anyhow::anyhow!(
|
||||
"{}, {:?} cannot be authenticated at the \
|
||||
required level ({} of {}). After checking \
|
||||
that {} really controls {}, you could certify \
|
||||
their certificate by running \
|
||||
`sq certify MY_KEY.pgp {} {}`.",
|
||||
cert.fingerprint(),
|
||||
String::from_utf8_lossy(userid.value()),
|
||||
paths.amount(), wot::FULLY_TRUSTED,
|
||||
String::from_utf8_lossy(userid.value()),
|
||||
cert.fingerprint(),
|
||||
cert.fingerprint(),
|
||||
String::from_utf8_lossy(userid.value())))
|
||||
} else {
|
||||
Ok(cert)
|
||||
};
|
||||
|
||||
Entry { fpr, userid, cert: r, }
|
||||
});
|
||||
|
||||
// Partition into good (successfully authenticated) and
|
||||
// bad (an error occurred).
|
||||
let (good, bad): (Vec<Entry>, _)
|
||||
= entries.partition(|entry| entry.cert.is_ok());
|
||||
|
||||
if good.is_empty() {
|
||||
// We've only got errors.
|
||||
|
||||
let err = if bad.is_empty() {
|
||||
// We got nothing :/.
|
||||
if email {
|
||||
anyhow::anyhow!(
|
||||
"No known certificates have the email address {:?}",
|
||||
userid)
|
||||
} else {
|
||||
anyhow::anyhow!(
|
||||
"No known certificates have the User ID {:?}",
|
||||
userid)
|
||||
}
|
||||
} else {
|
||||
if email {
|
||||
anyhow::anyhow!(
|
||||
"None of the certificates with the email \
|
||||
address {:?} can be authenticated using \
|
||||
the configured trust model",
|
||||
userid)
|
||||
} else {
|
||||
anyhow::anyhow!(
|
||||
"None of the certificates with the User ID \
|
||||
{:?} can be authenticated using \
|
||||
the configured trust model",
|
||||
userid)
|
||||
}
|
||||
};
|
||||
|
||||
eprintln!("{:?}:\n", err);
|
||||
if error.is_none() {
|
||||
error = Some(err);
|
||||
}
|
||||
|
||||
// Print the errors.
|
||||
for (i, Entry { fpr, userid, cert }) in bad.into_iter().enumerate() {
|
||||
eprintln!("{}. When considering {} ({}):",
|
||||
i + 1, fpr,
|
||||
String::from_utf8_lossy(userid.value()));
|
||||
let err = match cert {
|
||||
Ok(_) => unreachable!(),
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
print_error_chain(&err);
|
||||
}
|
||||
} else {
|
||||
// We have at least one authenticated certificate.
|
||||
// Silently ignore any errors.
|
||||
results.extend(
|
||||
good.into_iter().filter_map(|Entry { cert, .. }| {
|
||||
cert.ok()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(error) = error {
|
||||
Err(error)
|
||||
} else {
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Looks up a certificate.
|
||||
///
|
||||
/// Like `lookup`, but looks up a certificate, which must be
|
||||
@ -765,6 +1000,14 @@ fn main() -> Result<()> {
|
||||
true,
|
||||
false)
|
||||
.context("--recipient-cert")?);
|
||||
recipients.extend(
|
||||
config.lookup_by_userid(&c.trust_roots,
|
||||
&command.recipients_email, true)
|
||||
.context("--recipient-email")?);
|
||||
recipients.extend(
|
||||
config.lookup_by_userid(&c.trust_roots,
|
||||
&command.recipients_userid, false)
|
||||
.context("--recipient-userid")?);
|
||||
let mut input = open_or_stdin(command.io.input.as_deref())?;
|
||||
|
||||
let output = config.create_or_stdout_pgp(
|
||||
|
@ -45,6 +45,21 @@ pub struct Command {
|
||||
help = "Emits binary data",
|
||||
)]
|
||||
pub binary: bool,
|
||||
|
||||
#[clap(
|
||||
long = "recipient-email",
|
||||
value_name = "EMAIL",
|
||||
help = "Encrypts to all certificates that can be authenticated \
|
||||
for the specified email address",
|
||||
)]
|
||||
pub recipients_email: Vec<String>,
|
||||
#[clap(
|
||||
long = "recipient-userid",
|
||||
value_name = "USERID",
|
||||
help = "Encrypts to all certificates that can be authenticated \
|
||||
for the specified User ID",
|
||||
)]
|
||||
pub recipients_userid: Vec<String>,
|
||||
#[clap(
|
||||
long = "recipient-cert",
|
||||
value_name = "FINGERPRINT|KEYID",
|
||||
@ -57,6 +72,7 @@ pub struct Command {
|
||||
help = "Encrypts to all certificates in CERT_RING_FILE",
|
||||
)]
|
||||
pub recipients_file: Vec<String>,
|
||||
|
||||
#[clap(
|
||||
long = "signer-file",
|
||||
value_name = "KEY_FILE",
|
||||
|
@ -6,6 +6,9 @@ use clap::{Command, CommandFactory, Parser, Subcommand};
|
||||
#[cfg(feature = "autocrypt")]
|
||||
pub mod autocrypt;
|
||||
|
||||
use sequoia_openpgp as openpgp;
|
||||
use openpgp::Fingerprint;
|
||||
|
||||
pub mod armor;
|
||||
pub mod certify;
|
||||
pub mod dane;
|
||||
@ -149,6 +152,15 @@ $ sq --time 20130721T0550+0200 verify msg.pgp
|
||||
",
|
||||
)]
|
||||
pub time: Option<String>,
|
||||
#[clap(
|
||||
long = "trust-root",
|
||||
value_name = "FINGERPRINT|KEYID",
|
||||
help = "Considers the specified certificate to be a trust root",
|
||||
long_help = "Considers the specified certificate to be a trust root. \
|
||||
Trust roots are used by trust models, e.g., the web of \
|
||||
trust, to authenticate certificates and User IDs."
|
||||
)]
|
||||
pub trust_roots: Vec<Fingerprint>,
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: SqSubcommands,
|
||||
}
|
||||
|
@ -87,4 +87,211 @@ mod integration {
|
||||
|
||||
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",
|
||||
"--expires", "never",
|
||||
"--userid", "<alice@example.org>",
|
||||
"--export", &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",
|
||||
"--expires", "never",
|
||||
"--export", &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(())
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user