diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2019-02-08 17:45:18 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2019-02-19 13:46:25 +0100 |
commit | 864bea75d9ca795d032cf145015068756fdf3799 (patch) | |
tree | a5713ec500f98d3727e3f465d18cf3b5f2c11c60 /tool | |
parent | 9d0cf36e464676a4a939273e88f771a6b9f9fa58 (diff) |
tool: New command sq inspect.
- sq inspect is similar to sq dump, but it tries to extract and
display the high-level structure of the packet sequence in a
format suitable for human consumption.
- Fixes #87.
Diffstat (limited to 'tool')
-rw-r--r-- | tool/src/commands/inspect.rs | 288 | ||||
-rw-r--r-- | tool/src/commands/mod.rs | 2 | ||||
-rw-r--r-- | tool/src/sq-usage.rs | 19 | ||||
-rw-r--r-- | tool/src/sq.rs | 4 | ||||
-rw-r--r-- | tool/src/sq_cli.rs | 10 |
5 files changed, 323 insertions, 0 deletions
diff --git a/tool/src/commands/inspect.rs b/tool/src/commands/inspect.rs new file mode 100644 index 00000000..ae5a4f81 --- /dev/null +++ b/tool/src/commands/inspect.rs @@ -0,0 +1,288 @@ +use std::io::{self, Read}; +use time; + +use clap; + +extern crate sequoia_openpgp as openpgp; +use openpgp::{Packet, Result}; +use openpgp::parse::{Parse, PacketParserResult}; + +use super::TIMEFMT; + +pub fn inspect(m: &clap::ArgMatches, output: &mut io::Write) + -> Result<()> { + let print_keygrips = m.is_present("keygrips"); + let print_certifications = m.is_present("certifications"); + + let input = m.value_of("input"); + let input_name = input.unwrap_or("-"); + write!(output, "{}: ", input_name)?; + + let mut type_called = false; // Did we print the type yet? + let mut encrypted = false; // Is it an encrypted message? + let mut packets = Vec::new(); // Accumulator for packets. + let mut pkesks = Vec::new(); // Accumulator for PKESKs. + let mut n_skesks = 0; // Number of SKESKs. + let mut sigs = Vec::new(); // Accumulator for signatures. + let mut literal_prefix = Vec::new(); + + let mut ppr = + openpgp::parse::PacketParser::from_reader(::open_or_stdin(input)?)?; + while let PacketParserResult::Some(mut pp) = ppr { + match pp.packet { + Packet::PublicKey(_) | Packet::SecretKey(_) => { + if ! pp.possible_tpk() && pp.possible_keyring() { + if ! type_called { + writeln!(output, "OpenPGP Keyring.")?; + writeln!(output)?; + type_called = true; + } + let pp = openpgp::PacketPile::from( + ::std::mem::replace(&mut packets, Vec::new())); + let tpk = openpgp::TPK::from_packet_pile(pp)?; + inspect_tpk(output, &tpk, print_keygrips, + print_certifications)?; + } + }, + Packet::Literal(_) => { + pp.by_ref().take(40).read_to_end(&mut literal_prefix)?; + }, + Packet::SEIP(_) | Packet::AED(_) => { + encrypted = true; + }, + _ => (), + } + + let possible_keyring = pp.possible_keyring(); + let (packet, ppr_) = pp.recurse()?; + ppr = ppr_; + + match packet { + Packet::PKESK(p) => pkesks.push(p), + Packet::SKESK(_) => n_skesks += 1, + Packet::Signature(s) => if possible_keyring { + packets.push(Packet::Signature(s)) + } else { + sigs.push(s) + }, + _ => packets.push(packet), + } + } + + if let PacketParserResult::EOF(eof) = ppr { + if eof.is_message() { + writeln!(output, "{}OpenPGP Message.", + match (encrypted, ! sigs.is_empty()) { + (false, false) => "", + (false, true) => "Signed ", + (true, false) => "Encrypted ", + (true, true) => "Encrypted and signed ", + })?; + writeln!(output)?; + if n_skesks > 0 { + writeln!(output, " Passwords: {}", n_skesks)?; + } + for pkesk in pkesks.iter() { + writeln!(output, " Recipient: {}", pkesk.recipient())?; + } + inspect_signatures(output, &sigs)?; + if ! literal_prefix.is_empty() { + writeln!(output, " Data: {:?}{}", + String::from_utf8_lossy(&literal_prefix), + if literal_prefix.len() == 40 { "..." } else { "" })?; + } + + } else if eof.is_tpk() || eof.is_keyring() { + let pp = openpgp::PacketPile::from(packets); + let tpk = openpgp::TPK::from_packet_pile(pp)?; + inspect_tpk(output, &tpk, print_keygrips, 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)?; + } else if packets.is_empty() { + writeln!(output, "No OpenPGP data.")?; + } else { + writeln!(output, "Unknown sequence of OpenPGP packets.")?; + writeln!(output)?; + writeln!(output, "Hint: Try 'sq dump {}'", input_name)?; + } + } else { + unreachable!() + } + + Ok(()) +} + +fn inspect_tpk(output: &mut io::Write, tpk: &openpgp::TPK, + print_keygrips: bool, print_certifications: bool) -> Result<()> { + writeln!(output, "Transferable {} Key.", + if tpk.is_tsk() { "Secret" } else { "Public" })?; + writeln!(output)?; + writeln!(output, " Fingerprint: {}", tpk.fingerprint())?; + inspect_revocation(output, "", tpk.revoked(None))?; + inspect_key(output, "", tpk.primary(), tpk.primary_key_signature(), + &tpk.certifications().collect::<Vec<_>>()[..], + print_keygrips, print_certifications)?; + writeln!(output)?; + + for skb in tpk.subkeys() { + writeln!(output, " Subkey: {}", skb.subkey().fingerprint())?; + inspect_revocation(output, "", skb.revoked(None))?; + inspect_key(output, "", skb.subkey(), skb.binding_signature(), + &skb.certifications().collect::<Vec<_>>()[..], + print_keygrips, print_certifications)?; + writeln!(output)?; + } + + for uidb in tpk.userids() { + writeln!(output, " UserID: {}", uidb.userid())?; + inspect_revocation(output, "", uidb.revoked(None))?; + if let Some(sig) = uidb.binding_signature() { + if sig.signature_expired() { + writeln!(output, " Expired")?; + } else if ! sig.signature_alive() { + writeln!(output, " Not yet valid")?; + } + } + inspect_certifications(output, + &uidb.certifications().collect::<Vec<_>>()[..], + print_certifications)?; + writeln!(output)?; + } + + Ok(()) +} + +fn inspect_key(output: &mut io::Write, + indent: &str, + key: &openpgp::packet::Key, + binding_signature: Option<&openpgp::packet::Signature>, + certs: &[&openpgp::packet::Signature], + print_keygrips: bool, + print_certifications: bool) + -> Result<()> { + if let Some(sig) = binding_signature { + if sig.key_expired(key) { + writeln!(output, "{} Expired", indent)?; + } else if ! sig.key_alive(key) { + writeln!(output, "{} Not yet valid", indent)?; + } + } + + if print_keygrips { + writeln!(output, "{} Keygrip: {}", indent, + key.mpis().keygrip()?)?; + } + writeln!(output, "{}Public-key algo: {}", indent, key.pk_algo())?; + writeln!(output, "{}Public-key size: {} bits", indent, key.mpis().bits())?; + writeln!(output, "{} Creation time: {}", indent, + time::strftime(TIMEFMT, key.creation_time())?)?; + if let Some(sig) = binding_signature { + if let Some(expires) = sig.key_expiration_time() { + let expiration_time = *key.creation_time() + expires; + writeln!(output, "{}Expiration time: {} (creation time + {})", + indent, + time::strftime(TIMEFMT, &expiration_time)?, + expires)?; + } + + if let Some(keyflags) = inspect_key_flags(sig.key_flags()) { + writeln!(output, "{} Keyflags: {}", indent, keyflags)?; + } + } + inspect_certifications(output, certs, print_certifications)?; + + Ok(()) +} + +fn inspect_revocation(output: &mut io::Write, + indent: &str, + revoked: openpgp::RevocationStatus) + -> Result<()> { + use openpgp::RevocationStatus::*; + match revoked { + Revoked(_) => + writeln!(output, "{} Revoked", indent)?, + CouldBe(_) => + writeln!(output, "{} Possibly revoked", indent)?, + NotAsFarAsWeKnow => (), + } + + Ok(()) +} + +fn inspect_key_flags(flags: openpgp::packet::KeyFlags) -> Option<String> { + let mut capabilities = Vec::new(); + if flags.can_certify() { + capabilities.push("certification") + } + if flags.can_sign() { + capabilities.push("signing") + } + if flags.can_authenticate() { + capabilities.push("authentication") + } + if flags.can_encrypt_for_transport() { + capabilities.push("transport encryption") + } + if flags.can_encrypt_at_rest() { + capabilities.push("data-at-rest encryption") + } + + if capabilities.len() > 0 { + Some(capabilities.join(", ")) + } else { + None + } +} + +fn inspect_signatures(output: &mut io::Write, + sigs: &[openpgp::packet::Signature]) -> Result<()> { + use openpgp::constants::SignatureType::*; + for sig in sigs { + match sig.sigtype() { + Binary | Text => (), + signature_type @ _ => + writeln!(output, " Kind: {}", signature_type)?, + } + + if let Some(fp) = sig.issuer_fingerprint() { + writeln!(output, " Signed by: {}", fp)?; + } else if let Some(kid) = sig.issuer() { + writeln!(output, " Signed by: {}", kid)?; + } + } + if ! sigs.is_empty() { + writeln!(output, " \ + Signatures have NOT been verified!")?; + } + + Ok(()) +} + +fn inspect_certifications(output: &mut io::Write, + certs: &[&openpgp::packet::Signature], + print_certifications: bool) -> Result<()> { + if print_certifications { + for sig in certs { + if let Some(fp) = sig.issuer_fingerprint() { + writeln!(output, " Certified by: {}", fp)?; + } else if let Some(kid) = sig.issuer() { + writeln!(output, " Certified by: {}", kid)?; + } + } + if ! certs.is_empty() { + writeln!(output, " \ + Certifications have NOT been verified!")?; + } + } else { + if ! certs.is_empty() { + writeln!(output, " Certifications: {}, \ + use --certifications to list", certs.len())?; + } + } + + Ok(()) +} diff --git a/tool/src/commands/mod.rs b/tool/src/commands/mod.rs index fbd6e47b..db713caa 100644 --- a/tool/src/commands/mod.rs +++ b/tool/src/commands/mod.rs @@ -30,6 +30,8 @@ mod sign; pub use self::sign::sign; mod dump; pub use self::dump::dump; +mod inspect; +pub use self::inspect::inspect; pub mod key; const TIMEFMT: &'static str = "%Y-%m-%dT%H:%M"; diff --git a/tool/src/sq-usage.rs b/tool/src/sq-usage.rs index 7c06615b..6981e439 100644 --- a/tool/src/sq-usage.rs +++ b/tool/src/sq-usage.rs @@ -31,6 +31,7 @@ //! dump Lists OpenPGP packets //! enarmor Applies ASCII Armor to a file //! help Prints this message or the help of the given subcommand(s) +//! inspect Inspects a sequence of OpenPGP packets //! key Manipulates keys //! list Lists key stores and known keys //! split Splits a message into OpenPGP packets @@ -421,6 +422,24 @@ //! <FILE> Sets the input file to use //! ``` //! +//! ## Subcommand inspect +//! +//! ```text +//! Inspects a sequence of OpenPGP packets +//! +//! USAGE: +//! sq inspect [FLAGS] [FILE] +//! +//! FLAGS: +//! --certifications Print third-party certifications +//! -h, --help Prints help information +//! --keygrips Print keygrips of keys and subkeys +//! -V, --version Prints version information +//! +//! ARGS: +//! <FILE> Sets the input file to use +//! ``` +//! //! ## Subcommand key //! //! ```text diff --git a/tool/src/sq.rs b/tool/src/sq.rs index ffce734d..3706844f 100644 --- a/tool/src/sq.rs +++ b/tool/src/sq.rs @@ -223,6 +223,10 @@ fn real_main() -> Result<(), failure::Error> { commands::dump(&mut input, &mut output, m.is_present("mpis"), m.is_present("hex"))?; }, + ("inspect", Some(m)) => { + let mut output = create_or_stdout(m.value_of("output"), force)?; + commands::inspect(m, &mut output)?; + }, ("split", Some(m)) => { let mut input = open_or_stdin(m.value_of("input"))?; let prefix = diff --git a/tool/src/sq_cli.rs b/tool/src/sq_cli.rs index d3489e6e..0da389fd 100644 --- a/tool/src/sq_cli.rs +++ b/tool/src/sq_cli.rs @@ -216,6 +216,16 @@ pub fn build() -> App<'static, 'static> { .long("hex") .short("x") .help("Print a hexdump"))) + .subcommand(SubCommand::with_name("inspect") + .about("Inspects a sequence of OpenPGP packets") + .arg(Arg::with_name("input").value_name("FILE") + .help("Sets the input file to use")) + .arg(Arg::with_name("keygrips") + .long("keygrips") + .help("Print keygrips of keys and subkeys")) + .arg(Arg::with_name("certifications") + .long("certifications") + .help("Print third-party certifications"))) .subcommand(SubCommand::with_name("split") .about("Splits a message into OpenPGP packets") .arg(Arg::with_name("input").value_name("FILE") |