diff options
author | Neal H. Walfield <neal@pep.foundation> | 2021-01-21 17:29:14 +0100 |
---|---|---|
committer | Neal H. Walfield <neal@pep.foundation> | 2021-01-21 17:41:12 +0100 |
commit | c8d0d8a08d2ad8101e545eb866e095392a88a624 (patch) | |
tree | c6df5647708058c4fe904912c7ed324c5a8a1b29 | |
parent | 8ea5c7a74d3b9d15f130c1e49b7a98249b782731 (diff) |
sq: Add new command 'sq certring merge'
- Unlike 'sq certring join', this merges multiple version of the
same certificate.
-rw-r--r-- | sq/src/commands/certring.rs | 59 | ||||
-rw-r--r-- | sq/src/sq-usage.rs | 56 | ||||
-rw-r--r-- | sq/src/sq_cli.rs | 26 |
3 files changed, 133 insertions, 8 deletions
diff --git a/sq/src/commands/certring.rs b/sq/src/commands/certring.rs index f102eaf1..4b5572a7 100644 --- a/sq/src/commands/certring.rs +++ b/sq/src/commands/certring.rs @@ -1,4 +1,6 @@ use std::{ + collections::HashMap, + collections::hash_map::Entry, fs::File, io, path::PathBuf, @@ -13,6 +15,7 @@ use openpgp::{ Cert, CertParser, }, + Fingerprint, packet::{ UserID, UserAttribute, @@ -128,6 +131,14 @@ pub fn dispatch(m: &clap::ArgMatches, force: bool) -> Result<()> { filter(m.values_of("input"), &mut output, |c| Some(c))?; output.finalize() }, + ("merge", Some(m)) => { + let mut output = create_or_stdout_pgp(m.value_of("output"), + force, + m.is_present("binary"), + armor::Kind::PublicKey)?; + merge(m.values_of("input"), &mut output)?; + output.finalize() + }, ("list", Some(m)) => { let mut input = open_or_stdin(m.value_of("input"))?; list(&mut input) @@ -236,6 +247,54 @@ fn split(input: &mut (dyn io::Read + Sync + Send), prefix: &str) Ok(()) } +/// Merge multiple certrings. +fn merge(inputs: Option<clap::Values>, output: &mut dyn io::Write) + -> Result<()> +{ + let mut certs: HashMap<Fingerprint, Option<Cert>> = HashMap::new(); + + if let Some(inputs) = inputs { + for name in inputs { + for cert in CertParser::from_file(name)? { + let cert = cert.context( + format!("Malformed certificate in certring {:?}", name))?; + match certs.entry(cert.fingerprint()) { + e @ Entry::Vacant(_) => { + e.or_insert(Some(cert)); + } + Entry::Occupied(mut e) => { + let e = e.get_mut(); + let curr = e.take().unwrap(); + *e = Some(curr.merge_public_and_secret(cert) + .expect("Same certificate")); + } + } + } + } + } else { + for cert in CertParser::from_reader(io::stdin())? { + let cert = cert.context("Malformed certificate in certring")?; + match certs.entry(cert.fingerprint()) { + e @ Entry::Vacant(_) => { + e.or_insert(Some(cert)); + } + Entry::Occupied(mut e) => { + let e = e.get_mut(); + let curr = e.take().unwrap(); + *e = Some(curr.merge_public_and_secret(cert) + .expect("Same certificate")); + } + } + } + } + + for (_, cert) in certs.iter_mut() { + cert.take().unwrap().as_tsk().serialize(output)?; + } + + Ok(()) +} + /// Sanitizes a string to a safe filename fragment. fn to_filename_fragment<S: AsRef<str>>(s: S) -> Option<String> { let mut r = String::with_capacity(s.as_ref().len()); diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs index 6a6df264..1185dfce 100644 --- a/sq/src/sq-usage.rs +++ b/sq/src/sq-usage.rs @@ -328,8 +328,9 @@ //! SUBCOMMANDS: //! 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 +//! join Joins certs or certrings into a single certring //! list Lists certs in a certring +//! merge Merges certs or certrings into a single certring //! split Splits a certring into individual certs //! ``` //! @@ -367,21 +368,31 @@ //! ### Subcommand certring join //! //! ```text -//! Joins certs into a certring +//! Unlike 'sq certring merge', multiple versions of the same certificate are not +//! merged together. //! //! USAGE: //! sq certring join [FLAGS] [OPTIONS] [FILE]... //! //! FLAGS: -//! -B, --binary Emits binary data -//! -h, --help Prints help information -//! -V, --version Prints version information +//! -B, --binary +//! Don't ASCII-armor the certring +//! +//! -h, --help +//! Prints help information +//! +//! -V, --version +//! Prints version information +//! //! //! OPTIONS: -//! -o, --output <FILE> Writes to FILE or stdout if omitted +//! -o, --output <FILE> +//! Sets the output file to use +//! //! //! ARGS: -//! <FILE>... Reads from FILE +//! <FILE>... +//! Sets the input files to use //! ``` //! //! ### Subcommand certring list @@ -400,6 +411,37 @@ //! <FILE> Reads from FILE or stdin if omitted //! ``` //! +//! ### Subcommand certring merge +//! +//! ```text +//! Unlike 'sq certring join', the certificates are buffered and multiple versions +//! of the same certificate are merged together. Where data is replaced (e.g., +//! secret key material), data from the later certificate is preferred. +//! +//! USAGE: +//! sq certring merge [FLAGS] [OPTIONS] [FILE]... +//! +//! FLAGS: +//! -B, --binary +//! Emits binary data +//! +//! -h, --help +//! Prints help information +//! +//! -V, --version +//! Prints version information +//! +//! +//! OPTIONS: +//! -o, --output <FILE> +//! Writes to FILE or stdout if omitted +//! +//! +//! ARGS: +//! <FILE>... +//! Reads from FILE +//! ``` +//! //! ### Subcommand certring split //! //! ```text diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs index bd37788c..6fa3201a 100644 --- a/sq/src/sq_cli.rs +++ b/sq/src/sq_cli.rs @@ -459,7 +459,31 @@ pub fn configure(app: App<'static, 'static>) -> App<'static, 'static> { ) .subcommand( SubCommand::with_name("join") - .about("Joins certs into a certring") + .about("Joins certs or certrings into a single certring") + .long_about( + "Unlike 'sq certring merge', multiple versions \ + of the same certificate are not merged \ + together.") + .arg(Arg::with_name("input") + .value_name("FILE") + .multiple(true) + .help("Sets the input files to use")) + .arg(Arg::with_name("output") + .short("o").long("output").value_name("FILE") + .help("Sets the output file to use")) + .arg(Arg::with_name("binary") + .short("B").long("binary") + .help("Don't ASCII-armor the certring")) + ) + .subcommand( + SubCommand::with_name("merge") + .about("Merges certs or certrings into a single certring") + .long_about( + "Unlike 'sq certring join', the certificates \ + are buffered and multiple versions of the same \ + certificate are merged together. Where data \ + is replaced (e.g., secret key material), data \ + from the later certificate is preferred.") .arg(Arg::with_name("input") .value_name("FILE") .multiple(true) |