diff options
author | Lars Wirzenius <liw@liw.fi> | 2021-08-10 14:23:10 +0300 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2021-08-19 17:28:28 +0300 |
commit | ea106c49c2f625725afd6fdbb1f2d0162320e406 (patch) | |
tree | 2186f32dc035c92aca9ccc3c1a195d665351ca9e | |
parent | e09134c41b3ed37e31a3cd8727119d427daadf0c (diff) |
sq: allow user to request JSON output for "sq inspect"liw/inspect-json
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
-rw-r--r-- | Cargo.lock | 5 | ||||
-rw-r--r-- | sq/Cargo.toml | 2 | ||||
-rw-r--r-- | sq/src/commands/inspect.rs | 574 | ||||
-rw-r--r-- | sq/src/sq-usage.rs | 7 | ||||
-rw-r--r-- | sq/src/sq_cli.rs | 5 |
5 files changed, 481 insertions, 112 deletions
@@ -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<PublicParts>, 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<String> { } } -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<KeyHandle> = 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<Item=&'a openpgp::packet::Signature> { @@ -388,28 +395,373 @@ fn inspect_certifications<'a, A>(output: &mut dyn io::Write, fps.dedup(); let fps: Vec<KeyHandle> = 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<Atom>, +} + +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<Vec<&Atom>> { + 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())); |