summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2021-01-06 14:58:04 +0100
committerJustus Winter <justus@sequoia-pgp.org>2021-01-06 14:58:04 +0100
commit17253b2e7fa297426a841042fa955ca69955f00f (patch)
treea09b4115d3e604abbdd4d1f55271e41d332bf8f6
parent623b6a783184e0e8ec0f27f7cabfe8db62abb1bb (diff)
sq: Implement 'certring filter'.
-rw-r--r--sq/src/commands/certring.rs91
-rw-r--r--sq/src/sq-usage.rs59
-rw-r--r--sq/src/sq_cli.rs40
3 files changed, 184 insertions, 6 deletions
diff --git a/sq/src/commands/certring.rs b/sq/src/commands/certring.rs
index 351aad7c..f102eaf1 100644
--- a/sq/src/commands/certring.rs
+++ b/sq/src/commands/certring.rs
@@ -13,6 +13,11 @@ use openpgp::{
Cert,
CertParser,
},
+ packet::{
+ UserID,
+ UserAttribute,
+ Key,
+ },
parse::Parse,
serialize::Serialize,
};
@@ -24,6 +29,92 @@ use crate::{
pub fn dispatch(m: &clap::ArgMatches, force: bool) -> Result<()> {
match m.subcommand() {
+ ("filter", Some(m)) => {
+ let any_uid_predicates =
+ m.is_present("name")
+ || m.is_present("email")
+ || m.is_present("domain");
+ let uid_predicate = |uid: &UserID| {
+ let mut keep = false;
+
+ if let Some(names) = m.values_of("name") {
+ for name in names {
+ keep |= uid
+ .name().unwrap_or(None)
+ .map(|n| n == name)
+ .unwrap_or(false);
+ }
+ }
+
+ if let Some(emails) = m.values_of("email") {
+ for email in emails {
+ keep |= uid
+ .email().unwrap_or(None)
+ .map(|n| n == email)
+ .unwrap_or(false);
+ }
+ }
+
+ if let Some(domains) = m.values_of("domain") {
+ for domain in domains {
+ keep |= uid
+ .email().unwrap_or(None)
+ .map(|n| n.ends_with(&format!("@{}", domain)))
+ .unwrap_or(false);
+ }
+ }
+
+ keep
+ };
+
+ let any_ua_predicates = false;
+ let ua_predicate = |_ua: &UserAttribute| false;
+
+ let any_key_predicates = false;
+ let key_predicate = |_key: &Key<_, _>| false;
+
+ let filter_fn = |c: Cert| -> Option<Cert> {
+ if ! (c.userids().any(|c| uid_predicate(&c))
+ || c.user_attributes().any(|c| ua_predicate(&c))
+ || c.keys().subkeys().any(|c| key_predicate(&c))) {
+ None
+ } else if m.is_present("prune-certs") {
+ let c = c
+ .retain_userids(|c| {
+ ! any_uid_predicates || uid_predicate(&c)
+ })
+ .retain_user_attributes(|c| {
+ ! any_ua_predicates || ua_predicate(&c)
+ })
+ .retain_subkeys(|c| {
+ ! any_key_predicates || key_predicate(&c)
+ });
+ if c.userids().count() == 0
+ && c.user_attributes().count() == 0
+ && c.keys().subkeys().count() == 0
+ {
+ // We stripped all components, omit this cert.
+ None
+ } else {
+ Some(c)
+ }
+ } else {
+ Some(c)
+ }
+ };
+
+ // XXX: Armor type selection is a bit problematic. If any
+ // of the certificates contain a secret key, it would be
+ // better to use Kind::SecretKey here. However, this
+ // requires buffering all certs, which has its own
+ // problems.
+ let mut output = create_or_stdout_pgp(m.value_of("output"),
+ force,
+ m.is_present("binary"),
+ armor::Kind::PublicKey)?;
+ filter(m.values_of("input"), &mut output, filter_fn)?;
+ output.finalize()
+ },
("join", Some(m)) => {
// XXX: Armor type selection is a bit problematic. If any
// of the certificates contain a secret key, it would be
diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs
index 59844351..9519d098 100644
--- a/sq/src/sq-usage.rs
+++ b/sq/src/sq-usage.rs
@@ -438,14 +438,61 @@
//! sq certring <SUBCOMMAND>
//!
//! FLAGS:
-//! -h, --help Prints help information
-//! -V, --version Prints version information
+//! -h, --help
+//! Prints help information
+//!
+//! -V, --version
+//! Prints version information
+//!
//!
//! SUBCOMMANDS:
-//! help Prints this message or the help of the given subcommand(s)
-//! join Joins certs into a certring
-//! list Lists certs in a certring
-//! split Splits a certring into individual certs
+//! filter Joins certs into a certring applying a filter
+//! help Prints this message or the help of the given subcommand(s)
+//! join Joins certs into a certring
+//! list Lists certs in a certring
+//! split Splits a certring into individual certs
+//! ```
+//!
+//! ### Subcommand certring filter
+//!
+//! ```text
+//! If multiple predicates are given, they are or'ed, i.e. a certificate matches if any of the predicates match. To require
+//! all predicates to match, chain multiple invocations of this command.
+//!
+//! USAGE:
+//! sq certring filter [FLAGS] [OPTIONS] [--] [FILE]...
+//!
+//! FLAGS:
+//! -B, --binary
+//! Don't ASCII-armor the certring
+//!
+//! -h, --help
+//! Prints help information
+//!
+//! -P, --prune-certs
+//! Remove certificate components not matching the filter
+//!
+//! -V, --version
+//! Prints version information
+//!
+//!
+//! OPTIONS:
+//! --domain <FQDN>...
+//! Match on this email domain name
+//!
+//! --email <ADDRESS>...
+//! Match on this email address
+//!
+//! --name <NAME>...
+//! Match on this name
+//!
+//! -o, --output <FILE>
+//! Sets the output file to use
+//!
+//!
+//! ARGS:
+//! <FILE>...
+//! Sets the input files to use
//! ```
//!
//! ### Subcommand certring join
diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs
index e924b8ab..11496248 100644
--- a/sq/src/sq_cli.rs
+++ b/sq/src/sq_cli.rs
@@ -517,6 +517,46 @@ pub fn build() -> App<'static, 'static> {
.about("Manipulates certificate rings")
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
+ SubCommand::with_name("filter")
+ .about("Joins certs into a certring applying a filter")
+ .long_about(
+ "If multiple predicates are given, they are \
+ or'ed, i.e. a certificate matches if any \
+ of the predicates match. To require all \
+ predicates to match, chain multiple \
+ invocations of this command.")
+ .arg(Arg::with_name("input").value_name("FILE")
+ .multiple(true)
+ .help("Sets the input files to use"))
+ .arg(Arg::with_name("output").value_name("FILE")
+ .long("output")
+ .short("o")
+ .help("Sets the output file to use"))
+ .arg(Arg::with_name("name").value_name("NAME")
+ .long("name")
+ .multiple(true)
+ .number_of_values(1)
+ .help("Match on this name"))
+ .arg(Arg::with_name("email").value_name("ADDRESS")
+ .long("email")
+ .multiple(true)
+ .number_of_values(1)
+ .help("Match on this email address"))
+ .arg(Arg::with_name("domain").value_name("FQDN")
+ .long("domain")
+ .multiple(true)
+ .number_of_values(1)
+ .help("Match on this email domain name"))
+ .arg(Arg::with_name("prune-certs")
+ .long("prune-certs")
+ .short("P")
+ .help("Remove certificate components not matching \
+ the filter"))
+ .arg(Arg::with_name("binary")
+ .long("binary")
+ .short("B")
+ .help("Don't ASCII-armor the certring")))
+ .subcommand(
SubCommand::with_name("join")
.about("Joins certs into a certring")
.arg(Arg::with_name("input").value_name("FILE")