summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2019-02-08 17:45:18 +0100
committerJustus Winter <justus@sequoia-pgp.org>2019-02-19 13:46:25 +0100
commit864bea75d9ca795d032cf145015068756fdf3799 (patch)
treea5713ec500f98d3727e3f465d18cf3b5f2c11c60
parent9d0cf36e464676a4a939273e88f771a6b9f9fa58 (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.
-rw-r--r--tool/src/commands/inspect.rs288
-rw-r--r--tool/src/commands/mod.rs2
-rw-r--r--tool/src/sq-usage.rs19
-rw-r--r--tool/src/sq.rs4
-rw-r--r--tool/src/sq_cli.rs10
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")