From ea106c49c2f625725afd6fdbb1f2d0162320e406 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Tue, 10 Aug 2021 14:23:10 +0300 Subject: sq: allow user to request JSON output for "sq inspect" Split the logic of parsing the OpenPGP package stream and the code to produce output so they're not as intermingled. Add the --output-format option to "sq inspect" to make output use the JSON format. Fixes #737 --- Cargo.lock | 5 + sq/Cargo.toml | 2 + sq/src/commands/inspect.rs | 574 ++++++++++++++++++++++++++++++++++++--------- sq/src/sq-usage.rs | 7 +- sq/src/sq_cli.rs | 5 + 5 files changed, 481 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e4a5580..16deb2a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2199,6 +2199,8 @@ dependencies = [ "sequoia-autocrypt", "sequoia-net", "sequoia-openpgp", + "serde", + "serde_json", "tempfile", "term_size", "tokio", @@ -2228,6 +2230,9 @@ name = "serde" version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_cbor" diff --git a/sq/Cargo.toml b/sq/Cargo.toml index 03cd657a..2bf3fa74 100644 --- a/sq/Cargo.toml +++ b/sq/Cargo.toml @@ -34,6 +34,8 @@ anyhow = "1.0.18" chrono = "0.4.10" clap = { version = "2.33", features = ["wrap_help"] } itertools = "0.9" +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1", features = ["std"] } tempfile = "3.1" term_size = "0.3" tokio = { version = "0.2.19", features = ["rt-core", "io-util", "io-driver"], optional = true } diff --git a/sq/src/commands/inspect.rs b/sq/src/commands/inspect.rs index e6e86d01..73c4c876 100644 --- a/sq/src/commands/inspect.rs +++ b/sq/src/commands/inspect.rs @@ -1,14 +1,22 @@ use std::convert::TryFrom; use std::io::{self, Read}; +use serde::Serialize; +use std::collections::HashMap; - +use openpgp::types::{ + PublicKeyAlgorithm, + ReasonForRevocation, + SignatureType, +}; use sequoia_openpgp as openpgp; use crate::openpgp::{KeyHandle, Packet, Result}; use crate::openpgp::cert::prelude::*; use openpgp::packet::{ Signature, key::PublicParts, + UserID, + UserAttribute, }; use crate::openpgp::parse::{Parse, PacketParserResult}; use crate::openpgp::policy::Policy; @@ -18,11 +26,13 @@ use super::dump::Convert; pub fn inspect(m: &clap::ArgMatches, policy: &dyn Policy, output: &mut dyn io::Write) -> Result<()> { + let mut buffer = Buffer::new(); + let print_certifications = m.is_present("certifications"); let input = m.value_of("input"); let input_name = input.unwrap_or("-"); - write!(output, "{}: ", input_name)?; + buffer.filename(input_name); let mut type_called = false; // Did we print the type yet? let mut encrypted = false; // Is it an encrypted message? @@ -41,14 +51,14 @@ pub fn inspect(m: &clap::ArgMatches, policy: &dyn Policy, output: &mut dyn io::W && pp.possible_keyring().is_ok() { if ! type_called { - writeln!(output, "OpenPGP Keyring.")?; - writeln!(output)?; + buffer.openpgp_keyring(); + buffer.separator(); type_called = true; } let pp = openpgp::PacketPile::from( ::std::mem::replace(&mut packets, Vec::new())); let cert = openpgp::Cert::try_from(pp)?; - inspect_cert(policy, output, &cert, + inspect_cert(policy, &mut buffer, &cert, print_certifications)?; } }, @@ -83,147 +93,152 @@ pub fn inspect(m: &clap::ArgMatches, policy: &dyn Policy, output: &mut dyn io::W let is_keyring = eof.is_keyring(); if is_message.is_ok() { - writeln!(output, "{}OpenPGP Message.", - match (encrypted, ! sigs.is_empty()) { - (false, false) => "", - (false, true) => "Signed ", - (true, false) => "Encrypted ", - (true, true) => "Encrypted and signed ", - })?; - writeln!(output)?; + match (encrypted, ! sigs.is_empty()) { + (false, false) => buffer.openpgp_message(), + (false, true) => buffer.signed_openpgp_message(), + (true, false) => buffer.encrypted_openpgp_message(), + (true, true) => buffer.encrypted_signed_openpgp_msg(), + } + buffer.separator(); if n_skesks > 0 { - writeln!(output, " Passwords: {}", n_skesks)?; + buffer.passwords(n_skesks); } for pkesk in pkesks.iter() { - writeln!(output, " Recipient: {}", pkesk.recipient())?; + buffer.recipient(pkesk.recipient()); } - inspect_signatures(output, &sigs)?; + inspect_signatures(&mut buffer, &sigs)?; if ! literal_prefix.is_empty() { - writeln!(output, " Data: {:?}{}", - String::from_utf8_lossy(&literal_prefix), - if literal_prefix.len() == 40 { "..." } else { "" })?; + let data = String::from_utf8_lossy(&literal_prefix); + let suffix = if literal_prefix.len() == 40 { "..." } else { "" }; + buffer.data(&data, suffix); } } else if is_cert.is_ok() || is_keyring.is_ok() { let pp = openpgp::PacketPile::from(packets); let cert = openpgp::Cert::try_from(pp)?; - inspect_cert(policy, output, &cert, + inspect_cert(policy, &mut buffer, &cert, print_certifications)?; } else if packets.is_empty() && ! sigs.is_empty() { - writeln!(output, "Detached signature{}.", - if sigs.len() > 1 { "s" } else { "" })?; - writeln!(output)?; - inspect_signatures(output, &sigs)?; + if sigs.len() == 1 { + buffer.detached_signature(); + } else { + buffer.detached_signatures(); + } + buffer.separator(); + inspect_signatures(&mut buffer, &sigs)?; } else if packets.is_empty() { - writeln!(output, "No OpenPGP data.")?; + buffer.no_openpgp_data(); } else { - writeln!(output, "Unknown sequence of OpenPGP packets.")?; - writeln!(output, " Message: {}", is_message.unwrap_err())?; - writeln!(output, " Cert: {}", is_cert.unwrap_err())?; - writeln!(output, " Keyring: {}", is_keyring.unwrap_err())?; - writeln!(output)?; - writeln!(output, "Hint: Try 'sq packet dump {}'", input_name)?; + buffer.unknown_packet_sequence(is_message, + is_cert, + is_keyring, + input_name); + } } else { unreachable!() } + if let Some("json") = m.value_of("output-format") { + buffer.write_json(output)?; + } else { + buffer.write_human(output)?; + } Ok(()) } fn inspect_cert(policy: &dyn Policy, - output: &mut dyn io::Write, cert: &openpgp::Cert, + buffer: &mut Buffer, + cert: &openpgp::Cert, print_certifications: bool) -> Result<()> { if cert.is_tsk() { - writeln!(output, "Transferable Secret Key.")?; + buffer.transferable_secret_key(); } else { - writeln!(output, "OpenPGP Certificate.")?; + buffer.openpgp_certificate(); } - writeln!(output)?; - writeln!(output, " Fingerprint: {}", cert.fingerprint())?; - inspect_revocation(output, "", cert.revocation_status(policy, None))?; - inspect_key(policy, output, "", cert.keys().next().unwrap(), + buffer.separator(); + buffer.fingerprint(cert.fingerprint()); + inspect_revocation(buffer, cert.revocation_status(policy, None))?; + inspect_key(policy, buffer, cert.keys().next().unwrap(), print_certifications)?; - writeln!(output)?; + buffer.separator(); for vka in cert.keys().subkeys().with_policy(policy, None) { - writeln!(output, " Subkey: {}", vka.key().fingerprint())?; - inspect_revocation(output, "", vka.revocation_status())?; - inspect_key(policy, output, "", vka.into_key_amalgamation().into(), + buffer.subkey(vka.key().fingerprint()); + inspect_revocation(buffer, vka.revocation_status())?; + inspect_key(policy, buffer, vka.into_key_amalgamation().into(), print_certifications)?; - writeln!(output)?; + buffer.separator(); } - fn print_error_chain(output: &mut dyn io::Write, err: &anyhow::Error) + fn print_error_chain(buffer: &mut Buffer, err: &anyhow::Error) -> Result<()> { - writeln!(output, " Invalid: {}", err)?; + buffer.invalid_certificate(err); for cause in err.chain().skip(1) { - writeln!(output, " because: {}", cause)?; + buffer.invalid_certificate_cause(cause); } Ok(()) } for uidb in cert.userids() { - writeln!(output, " UserID: {}", uidb.userid())?; - inspect_revocation(output, "", uidb.revocation_status(policy, None))?; + buffer.userid(uidb.userid()); + inspect_revocation(buffer, uidb.revocation_status(policy, None))?; match uidb.binding_signature(policy, None) { Ok(sig) => if let Err(e) = sig.signature_alive(None, std::time::Duration::new(0, 0)) { - print_error_chain(output, &e)?; + print_error_chain(buffer, &e)?; } - Err(e) => print_error_chain(output, &e)?, + Err(e) => print_error_chain(buffer, &e)?, } - inspect_certifications(output, + inspect_certifications(buffer, uidb.certifications(), print_certifications)?; - writeln!(output)?; + buffer.separator(); } for uab in cert.user_attributes() { - writeln!(output, " User attribute: {:?}", - uab.user_attribute())?; - inspect_revocation(output, "", uab.revocation_status(policy, None))?; + buffer.user_attribute(uab.user_attribute()); + inspect_revocation(buffer, uab.revocation_status(policy, None))?; match uab.binding_signature(policy, None) { Ok(sig) => if let Err(e) = sig.signature_alive(None, std::time::Duration::new(0, 0)) { - print_error_chain(output, &e)?; + print_error_chain(buffer, &e)?; } - Err(e) => print_error_chain(output, &e)?, + Err(e) => print_error_chain(buffer, &e)?, } - inspect_certifications(output, + inspect_certifications(buffer, uab.certifications(), print_certifications)?; - writeln!(output)?; + buffer.separator(); } for ub in cert.unknowns() { - writeln!(output, " Unknown component: {:?}", ub.unknown())?; + buffer.unknown(ub.unknown()); match ub.binding_signature(policy, None) { Ok(sig) => if let Err(e) = sig.signature_alive(None, std::time::Duration::new(0, 0)) { - print_error_chain(output, &e)?; + print_error_chain(buffer, &e)?; } - Err(e) => print_error_chain(output, &e)?, + Err(e) => print_error_chain(buffer, &e)?, } - inspect_certifications(output, + inspect_certifications(buffer, ub.certifications(), print_certifications)?; - writeln!(output)?; + buffer.separator(); } for bad in cert.bad_signatures() { - writeln!(output, " Bad Signature: {:?}", bad)?; + buffer.bad_signature(bad); } Ok(()) } fn inspect_key(policy: &dyn Policy, - output: &mut dyn io::Write, - indent: &str, + buffer: &mut Buffer, ka: ErasedKeyAmalgamation, print_certifications: bool) -> Result<()> @@ -233,76 +248,69 @@ fn inspect_key(policy: &dyn Policy, let vka = match ka.with_policy(policy, None) { Ok(vka) => { if let Err(e) = vka.alive() { - writeln!(output, "{} Invalid: {}", indent, e)?; + buffer.invalid_key(&e); } Some(vka) }, Err(e) => { - writeln!(output, "{} Invalid: {}", indent, e)?; + buffer.invalid_key(&e); None }, }; - writeln!(output, "{}Public-key algo: {}", indent, key.pk_algo())?; + buffer.pk_algo(key.pk_algo()); if let Some(bits) = key.mpis().bits() { - writeln!(output, "{}Public-key size: {} bits", indent, bits)?; + buffer.pk_bits(bits); } if let Some(secret) = key.optional_secret() { - writeln!(output, "{} Secret key: {}", - indent, - if let SecretKeyMaterial::Unencrypted(_) = secret { - "Unencrypted" - } else { - "Encrypted" - })?; - } - writeln!(output, "{} Creation time: {}", indent, - key.creation_time().convert())?; + if let SecretKeyMaterial::Unencrypted(_) = secret { + buffer.unencrypted_secret_key(); + } else { + buffer.encrypted_secret_key(); + } + } + buffer.creation_time(key.creation_time().convert().to_string()); if let Some(vka) = vka { if let Some(expires) = vka.key_validity_period() { let expiration_time = key.creation_time() + expires; - writeln!(output, "{}Expiration time: {} (creation time + {})", - indent, - expiration_time.convert(), - expires.convert())?; + buffer.expiration_time(format!("{} (creation time + {})", + expiration_time.convert(), + expires.convert())); } if let Some(flags) = vka.key_flags().and_then(inspect_key_flags) { - writeln!(output, "{} Key flags: {}", indent, flags)?; + buffer.key_flags(flags); } } - inspect_certifications(output, bundle.certifications().iter(), + inspect_certifications(buffer, bundle.certifications().iter(), print_certifications)?; Ok(()) } -fn inspect_revocation(output: &mut dyn io::Write, - indent: &str, +fn inspect_revocation(buffer: &mut Buffer, revoked: openpgp::types::RevocationStatus) -> Result<()> { use crate::openpgp::types::RevocationStatus::*; - fn print_reasons(output: &mut dyn io::Write, indent: &str, - sigs: &[&Signature]) + fn print_reasons(buffer: &mut Buffer, sigs: &[&Signature]) -> Result<()> { for sig in sigs { if let Some((r, _)) = sig.reason_for_revocation() { - writeln!(output, "{} - {}", indent, r)?; + buffer.revocation_reason(r); } else { - writeln!(output, "{} - No reason specified", - indent)?; + buffer.unknown_revocation_reason(); } } Ok(()) } match revoked { Revoked(sigs) => { - writeln!(output, "{} Revoked:", indent)?; - print_reasons(output, indent, &sigs)?; + buffer.revoked(); + print_reasons(buffer, &sigs)?; }, CouldBe(sigs) => { - writeln!(output, "{} Possibly revoked:", indent)?; - print_reasons(output, indent, &sigs)?; + buffer.maybe_revoked(); + print_reasons(buffer, &sigs)?; }, NotAsFarAsWeKnow => (), } @@ -341,14 +349,14 @@ fn inspect_key_flags(flags: openpgp::types::KeyFlags) -> Option { } } -fn inspect_signatures(output: &mut dyn io::Write, +fn inspect_signatures(buffer: &mut Buffer, sigs: &[openpgp::packet::Signature]) -> Result<()> { use crate::openpgp::types::SignatureType::*; for sig in sigs { match sig.typ() { Binary | Text => (), signature_type @ _ => - writeln!(output, " Kind: {}", signature_type)?, + buffer.signature_type(signature_type), } let mut fps: Vec<_> = sig.issuer_fingerprints().collect(); @@ -356,26 +364,25 @@ fn inspect_signatures(output: &mut dyn io::Write, fps.dedup(); let fps: Vec = fps.into_iter().map(|fp| fp.into()).collect(); for fp in fps.iter() { - writeln!(output, " Alleged signer: {}", fp)?; + buffer.alleged_signer_by_key_handle(fp); } let mut keyids: Vec<_> = sig.issuers().collect(); keyids.sort(); keyids.dedup(); for keyid in keyids { if ! fps.iter().any(|fp| fp.aliases(&keyid.into())) { - writeln!(output, " Alleged signer: {}", keyid)?; + buffer.alleged_signer_by_key_id(keyid); } } } if ! sigs.is_empty() { - writeln!(output, " Note: \ - Signatures have NOT been verified!")?; + buffer.unverified_signers(); } Ok(()) } -fn inspect_certifications<'a, A>(output: &mut dyn io::Write, +fn inspect_certifications<'a, A>(buffer: &mut Buffer, certs: A, print_certifications: bool) -> Result<()> where A: std::iter::Iterator { @@ -388,28 +395,373 @@ fn inspect_certifications<'a, A>(output: &mut dyn io::Write, fps.dedup(); let fps: Vec = fps.into_iter().map(|fp| fp.into()).collect(); for fp in fps.iter() { - writeln!(output, "Alleged certifier: {}", fp)?; + buffer.alleged_certifier_by_key_handle(fp); } let mut keyids: Vec<_> = sig.issuers().collect(); keyids.sort(); keyids.dedup(); for keyid in keyids { if ! fps.iter().any(|fp| fp.aliases(&keyid.into())) { - writeln!(output, "Alleged certifier: {}", keyid)?; + buffer.alleged_certifier_by_key_id(keyid); } } } if emit_warning { - writeln!(output, " Note: \ - Certifications have NOT been verified!")?; + buffer.not_certified(); } } else { let count = certs.count(); if count > 0 { - writeln!(output, " Certifications: {}, \ - use --certifications to list", count)?; + buffer.num_certification(count); } } Ok(()) } + +// An atomic bit of information for output. +#[derive(Serialize)] +enum Atom { + // A key/value pair. + KeyValue(String, String), + + // A note. + Note(String), + + // A separator between groups of atoms that belong together. + Separator, +} + +impl Atom { + // Creates a key/value pair. + fn pair(a: &str, b: String) -> Self { + Self::KeyValue(a.to_string(), b) + } + + // Creates a note: a value without a key. + fn note(txt: &str) -> Self { + Self::Note(txt.to_string()) + } + + // Creates a separator. + fn separator() -> Self { + Self::Separator + } +} + +// A sequence of output atoms. +// +// The sequence can be written out in different formats. The logic to +// parse a sequence of OpenPGP packets appends atoms to the output +// buffer and the buffer can then be formatted in various ways for +// output. +#[derive(Serialize)] +struct Buffer { + atoms: Vec, +} + +impl Buffer { + // Creates a new output buffer. + fn new() -> Self { + Self { atoms: vec![] } + } + + // Writes output buffer as human-readable text. + fn write_human(&self, output: &mut dyn io::Write) -> Result<()> { + for atom in self.atoms.iter() { + match atom { + Atom::KeyValue(k, v) => + write!(output, "{}: {}\n", k, v)?, + Atom::Note(note) => write!(output, "{}\n", note)?, + Atom::Separator => write!(output, "\n")?, + } + } + Ok(()) + } + + // Writes output buffer as JSON. + // + // Splits the output buffer into groups, based on separators. Each + // group results in an object (map), and the sequence of groups is + // a list. + fn write_json(&self, output: &mut dyn io::Write) -> Result<()> { + let note = "Note".to_string(); + let mut maps = vec![]; + for v in self.split_groups() { + let mut map = HashMap::new(); + for atom in v.iter() { + match atom { + Atom::KeyValue(k, v) => { + map.insert(k, v); + }, + Atom::Note(text) => { + map.insert(¬e, text); + }, + Atom::Separator => (), + } + } + maps.push(map); + } + + write!(output, "{}", serde_json::to_string(&maps)?)?; + serde_json::to_writer(output, &maps)?; + Ok(()) + } + + // Splits output buffer at separators into a groups of atoms that + // belong together. + fn split_groups(&self) -> Vec> { + let mut vecs = vec![]; + let mut cur = vec![]; + + for atom in self.atoms.iter() { + if let Atom::Separator = atom { + vecs.push(cur.clone()); + cur.clear(); + } else { + cur.push(atom.clone()); + } + } + + if ! cur.is_empty() { + vecs.push(cur); + } + + vecs + } + + // The rest of the functions append atoms of different kinds to + // the output buffer. + + fn alleged_certifier_by_key_handle(&mut self, who: &KeyHandle) { + self.atoms.push( + Atom::pair("Alleged certifier", format!("{}", who))); + } + + fn alleged_certifier_by_key_id(&mut self, who: &openpgp::KeyID) { + self.atoms.push( + Atom::pair("Alleged certifier", format!("{}", who))); + } + + fn alleged_signer_by_key_handle(&mut self, who: &KeyHandle) { + self.atoms.push( + Atom::pair("Alleged signer", format!("{}", who))); + } + + fn alleged_signer_by_key_id(&mut self, who: &openpgp::KeyID) { + self.atoms.push( + Atom::pair("Alleged signer", format!("{}", who))); + } + + fn bad_signature(&mut self, bad: &openpgp::packet::Signature) { + self.atoms.push( + Atom::pair("Bad signature", format!("{:?}", bad))); + } + + fn creation_time(&mut self, time: String) { + self.atoms.push( + Atom::pair("Creation time", format!("{}", time))); + } + + fn data(&mut self, data: &str, suffix: &str) { + self.atoms.push( + Atom::pair("Data", format!("{}{}", data, suffix))); + } + + fn detached_signature(&mut self) { + self.atoms.push( + Atom::pair("File type", "Detached signature".to_string())); + } + + fn detached_signatures(&mut self) { + self.atoms.push( + Atom::pair("File type", "Detached signatures".to_string())); + } + + fn encrypted_openpgp_message(&mut self) { + self.atoms.push( + Atom::pair("File type", + "Encrypted OpenPGP Message".to_string())); + } + + fn encrypted_secret_key(&mut self) { + self.atoms.push( + Atom::pair("Secret key", "Encrypted".to_string())); + } + + fn encrypted_signed_openpgp_msg(&mut self) { + let atom =Atom::pair( + "File type", + "Encrypted and signed OpenPGP Message" + .to_string()); + self.atoms.push(atom); + } + + fn expiration_time(&mut self, time: String) { + self.atoms.push( + Atom::pair("Expiration time", format!("{}", time))); + } + + fn filename(&mut self, filename: &str) { + self.atoms.push(Atom::pair("Filename", filename.to_string())); + } + + fn fingerprint(&mut self, fp: openpgp::Fingerprint) { + self.atoms.push(Atom::pair("Fingerprint", format!("{}", fp))); + } + + fn invalid_certificate(&mut self, err: &anyhow::Error) { + self.atoms.push(Atom::pair("Invalid", format!("{}", err))); + } + + fn invalid_certificate_cause(&mut self, + cause: &dyn std::error::Error) { + self.atoms.push(Atom::pair("because", format!("{}", cause))); + } + + fn invalid_key(&mut self, error: &anyhow::Error) { + self.atoms.push(Atom::pair("Invalid", format!("{}", error))); + } + + fn key_flags(&mut self, flags: String) { + self.atoms.push(Atom::pair("Key flags", flags.clone())); + } + + fn maybe_revoked(&mut self) { + self.atoms.push(Atom::note("Possibly revoked")); + } + + fn no_openpgp_data(&mut self) { + self.atoms.push(Atom::note("No OpenPGP data")); + } + + fn not_certified(&mut self) { + self.atoms.push( + Atom::note("Certifications have NOT been verified")); + } + + fn num_certification(&mut self, count: usize) { + self.atoms.push( + Atom::pair("Certifications", format!("{}", count))); + } + + fn openpgp_certificate(&mut self) { + self.atoms.push( + Atom::pair("File type", "OpenPGP Certificate".to_string())); + } + + fn openpgp_keyring(&mut self) { + self.atoms.push( + Atom::pair("File type", "OpenPGP keyring".to_string())); + } + + fn openpgp_message(&mut self) { + self.atoms.push( + Atom::pair("File type", "OpenPGP Message".to_string())); + } + + fn passwords(&mut self, n: usize) { + self.atoms.push(Atom::pair("Passwords", format!("{}", n))); + } + + fn pk_algo(&mut self, algo: PublicKeyAlgorithm) { + self.atoms.push( + Atom::pair("Public key algorithm", format!("{}", algo))); + } + + fn pk_bits(&mut self, bits: usize) { + self.atoms.push( + Atom::pair("Public key size", format!("{}", bits))); + } + + fn recipient(&mut self, who: &openpgp::KeyID) { + self.atoms.push(Atom::pair("Recipient", format!("{}", who))); + } + + fn revocation_reason(&mut self, reason: ReasonForRevocation) { + self.atoms.push( + Atom::pair("Revocation reason", format!("{}", reason))); + } + + fn revoked(&mut self) { + self.atoms.push(Atom::note("Revoked")); + } + + fn separator(&mut self) { + self.atoms.push(Atom::separator()); + } + + fn signature_type(&mut self, typ: SignatureType) { + self.atoms.push(Atom::pair("Kind", format!("{}", typ))); + } + + fn signed_openpgp_message(&mut self) { + self.atoms.push( + Atom::pair("File type", + "Signed OpenPGP Message".to_string())); + } + + fn subkey(&mut self, fp: openpgp::Fingerprint) { + self.atoms.push( + Atom::pair("Subkey fingerprint", format!("{}", fp))); + } + + fn transferable_secret_key(&mut self) { + self.atoms.push( + Atom::pair("File type", + "Transferable Secret Key".to_string())); + } + + fn unencrypted_secret_key(&mut self) { + self.atoms.push( + Atom::pair("Secret key", "Unencrypted".to_string())); + } + + fn unknown_packet_sequence( + &mut self, + msg: std::result::Result<(), anyhow::Error>, + cert: std::result::Result<(), anyhow::Error>, + keyring: std::result::Result<(), anyhow::Error>, + input_name: &str) + { + let msg = msg.unwrap_err(); + let cert = cert.unwrap_err(); + let keyring = keyring.unwrap_err(); + + self.atoms.push( + Atom::note("Unknown sequence of OpenPGP packets")); + self.atoms.push( + Atom::pair("Message", format!("{}", msg))); + self.atoms.push( + Atom::pair("Ceritifacte", format!("{}", cert))); + self.atoms.push( + Atom::pair("Keyring", format!("{}", keyring))); + self.atoms.push( + Atom::pair("Input name", format!("{}", input_name))); + + } + + fn unknown(&mut self, unknown: &openpgp::packet::Unknown) { + self.atoms.push( + Atom::pair("Unknown component", format!("{:?}", unknown))); + } + + fn unknown_revocation_reason(&mut self) { + self.atoms.push(Atom::note("No reason specified")); + } + + fn unverified_signers(&mut self) { + self.atoms.push( + Atom::note("Signatures have NOT been verified")); + } + + fn userid(&mut self, userid: &UserID) { + self.atoms.push( + Atom::pair("User ID", format!("{}", userid))); + } + + fn user_attribute(&mut self, attr: &UserAttribute) { + self.atoms.push( + Atom::pair("User attribute", format!("{:?}", attr))); + } +} diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs index 54da6705..98b72716 100644 --- a/sq/src/sq-usage.rs +++ b/sq/src/sq-usage.rs @@ -1357,7 +1357,7 @@ //! of it. //! //! USAGE: -//! sq inspect [FLAGS] [FILE] +//! sq inspect [FLAGS] [OPTIONS] [FILE] //! //! FLAGS: //! --certifications @@ -1367,6 +1367,11 @@ //! Prints help information //! //! +//! OPTIONS: +//! --output-format +//! Format of output [possible values: human, json] +//! +//! //! ARGS: //! //! Reads from FILE or stdin if omitted diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs index b0ce93d7..23c0d20e 100644 --- a/sq/src/sq_cli.rs +++ b/sq/src/sq_cli.rs @@ -462,6 +462,11 @@ $ sq inspect message.sig .arg(Arg::with_name("certifications") .long("certifications") .help("Prints third-party certifications")) + .arg(Arg::with_name("output-format") + .takes_value(true) + .long("output-format") + .possible_values(&["human", "json"]) + .help("Format of output")) ) .subcommand( -- cgit v1.2.3