summaryrefslogtreecommitdiffstats
path: root/sq
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2020-10-14 11:43:23 +0200
committerJustus Winter <justus@sequoia-pgp.org>2020-10-14 12:16:15 +0200
commit653960de3dfe19c9b46daad38efbc4a49a7676f2 (patch)
treedd1c0c5963667dd06b717e69a0dd40df0a94cd68 /sq
parent2079449be080b4da3cdc8c6f9972f4ac48e58aaf (diff)
sq: Rename module to 'sequoia-sq'.
Diffstat (limited to 'sq')
-rw-r--r--sq/Cargo.toml62
-rw-r--r--sq/Makefile45
-rw-r--r--sq/build.rs22
-rw-r--r--sq/make-usage.sh54
-rw-r--r--sq/src/commands/decrypt.rs354
-rw-r--r--sq/src/commands/dump.rs946
-rw-r--r--sq/src/commands/inspect.rs412
-rw-r--r--sq/src/commands/key.rs248
-rw-r--r--sq/src/commands/mod.rs515
-rw-r--r--sq/src/commands/sign.rs347
-rw-r--r--sq/src/sq-usage.rs768
-rw-r--r--sq/src/sq.rs739
-rw-r--r--sq/src/sq_cli.rs601
-rw-r--r--sq/tests/sq-sign.rs813
14 files changed, 5926 insertions, 0 deletions
diff --git a/sq/Cargo.toml b/sq/Cargo.toml
new file mode 100644
index 00000000..9023b02b
--- /dev/null
+++ b/sq/Cargo.toml
@@ -0,0 +1,62 @@
+[package]
+name = "sequoia-sq"
+description = "Command-line frontends for Sequoia"
+version = "0.19.0"
+authors = [
+ "Justus Winter <justus@sequoia-pgp.org>",
+ "Kai Michaelis <kai@sequoia-pgp.org>",
+ "Neal H. Walfield <neal@sequoia-pgp.org>",
+]
+build = "build.rs"
+documentation = "https://docs.sequoia-pgp.org/0.19.0/sq"
+homepage = "https://sequoia-pgp.org/"
+repository = "https://gitlab.com/sequoia-pgp/sequoia"
+readme = "../README.md"
+keywords = ["cryptography", "openpgp", "pgp", "encryption", "signing"]
+categories = ["cryptography", "authentication", "command-line-utilities"]
+license = "GPL-2.0-or-later"
+edition = "2018"
+
+[badges]
+gitlab = { repository = "sequoia-pgp/sequoia" }
+maintenance = { status = "actively-developed" }
+
+[dependencies]
+buffered-reader = { path = "../buffered-reader", version = "0.18", default-features = false }
+sequoia-openpgp = { path = "../openpgp", version = "0.19", default-features = false }
+sequoia-autocrypt = { path = "../autocrypt", version = "0.19", default-features = false }
+sequoia-core = { path = "../core", version = "0.19", default-features = false }
+sequoia-ipc = { path = "../ipc", version = "0.19", default-features = false }
+sequoia-net = { path = "../net", version = "0.19", default-features = false }
+sequoia-store = { path = "../store", version = "0.19", default-features = false }
+anyhow = "1"
+chrono = "0.4"
+clap = "2.32.0"
+itertools = "0.9"
+prettytable-rs = "0.8.0"
+tempfile = "3.0.4"
+crossterm = "0.13"
+tokio-core = "0.1"
+rpassword = "5.0"
+
+[build-dependencies]
+clap = "2.32.0"
+
+[dev-dependencies]
+assert_cli = "0.6"
+
+[[bin]]
+name = "sq"
+path = "src/sq-usage.rs"
+
+[features]
+default = [
+ "buffered-reader/compression",
+ "sequoia-openpgp/default",
+ "sequoia-store/background-services"
+]
+crypto-nettle = ["sequoia-openpgp/crypto-nettle"]
+crypto-cng = ["sequoia-openpgp/crypto-cng"]
+compression = ["buffered-reader/compression", "sequoia-openpgp/compression"]
+compression-deflate = ["buffered-reader/compression-deflate", "sequoia-openpgp/compression-deflate"]
+compression-bzip2 = ["buffered-reader/compression-bzip2", "sequoia-openpgp/compression-bzip2"]
diff --git a/sq/Makefile b/sq/Makefile
new file mode 100644
index 00000000..8b7e11c1
--- /dev/null
+++ b/sq/Makefile
@@ -0,0 +1,45 @@
+# Configuration.
+CARGO_TARGET_DIR ?= $(shell pwd)/../target
+# We currently only support absolute paths.
+CARGO_TARGET_DIR := $(abspath $(CARGO_TARGET_DIR))
+SQ ?= $(CARGO_TARGET_DIR)/debug/sq
+
+# Tools.
+CARGO ?= cargo
+
+ifneq ($(filter Darwin BSD,$(shell uname -s)),"")
+ INSTALL ?= ginstall
+else
+ INSTALL ?= install
+endif
+
+all: src/sq-usage.rs
+
+# Installation.
+.PHONY: build-release
+build-release:
+ CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \
+ $(CARGO) build $(CARGO_FLAGS) --release --package sequoia-sq
+ $(MAKE) -C../store build-release
+
+.PHONY: install
+install: build-release
+ $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin
+ $(INSTALL) -t $(DESTDIR)$(PREFIX)/bin $(CARGO_TARGET_DIR)/release/sq
+ $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/zsh/site-functions
+ $(INSTALL) -t $(DESTDIR)$(PREFIX)/share/zsh/site-functions \
+ $(CARGO_TARGET_DIR)/_sq
+ $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/bash-completion/completions
+ $(INSTALL) $(CARGO_TARGET_DIR)/sq.bash \
+ $(DESTDIR)$(PREFIX)/share/bash-completion/completions/sq
+ $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/fish/completions
+ $(INSTALL) -t $(DESTDIR)$(PREFIX)/share/fish/completions \
+ $(CARGO_TARGET_DIR)/sq.fish
+ $(MAKE) -C../store install
+
+# Maintenance.
+.PHONY: update-usage
+update-usage: src/sq-usage.rs
+
+src/sq-usage.rs: make-usage.sh $(SQ)
+ sh make-usage.sh $(SQ) >$@~ && mv $@~ $@
diff --git a/sq/build.rs b/sq/build.rs
new file mode 100644
index 00000000..b7d3147d
--- /dev/null
+++ b/sq/build.rs
@@ -0,0 +1,22 @@
+use clap;
+
+use std::env;
+use std::fs;
+use clap::Shell;
+
+mod sq_cli {
+ include!("src/sq_cli.rs");
+}
+
+fn main() {
+ let outdir = match env::var_os("CARGO_TARGET_DIR") {
+ None => return,
+ Some(outdir) => outdir,
+ };
+ fs::create_dir_all(&outdir).unwrap();
+ let mut sq = sq_cli::build();
+ for shell in &[Shell::Bash, Shell::Fish, Shell::Zsh, Shell::PowerShell,
+ Shell::Elvish] {
+ sq.gen_completions("sq", *shell, &outdir);
+ }
+}
diff --git a/sq/make-usage.sh b/sq/make-usage.sh
new file mode 100644
index 00000000..7bcbfcf2
--- /dev/null
+++ b/sq/make-usage.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+tool=$1
+
+quote() {
+ sed 's@^@//! @' | sed 's/ $//'
+}
+
+begin_code() {
+ printf '```text\n'
+}
+
+end_code() {
+ printf '```\n'
+}
+
+dump_help() { # subcommand, indentation
+ if [ -z "$1" ]
+ then
+ printf "\n# Usage\n\n"
+ set "" "#"
+ else
+ printf "\n$2 Subcommand$1\n\n"
+ fi
+
+ help="`$tool $1 --help`"
+
+ begin_code
+ printf "$help\n" | tail -n +2
+ end_code
+
+ if echo $help | fgrep -q SUBCOMMANDS
+ then
+ printf "$help\n" |
+ sed -n '/^SUBCOMMANDS:/,$p' |
+ tail -n+2 |
+ grep '^ [^ ]' |
+ while read subcommand desc
+ do
+ if [ "$subcommand" = help ]; then
+ continue
+ fi
+
+ dump_help "$1 $subcommand" "#$2"
+ done
+ fi
+}
+
+(
+ printf "A command-line frontend for Sequoia.\n"
+ dump_help
+) | quote
+
+printf '\ninclude!("'"$(basename $tool)"'.rs");\n'
diff --git a/sq/src/commands/decrypt.rs b/sq/src/commands/decrypt.rs
new file mode 100644
index 00000000..07adabe6
--- /dev/null
+++ b/sq/src/commands/decrypt.rs
@@ -0,0 +1,354 @@
+use crossterm::terminal;
+use anyhow::Context as _;
+use std::collections::HashMap;
+use std::io;
+use rpassword;
+
+use sequoia_openpgp as openpgp;
+use sequoia_core::Context;
+use crate::openpgp::types::SymmetricAlgorithm;
+use crate::openpgp::fmt::hex;
+use crate::openpgp::crypto::{self, SessionKey};
+use crate::openpgp::{Fingerprint, Cert, KeyID, Result};
+use crate::openpgp::packet;
+use crate::openpgp::packet::prelude::*;
+use crate::openpgp::parse::{
+ Parse,
+ PacketParser,
+ PacketParserResult,
+};
+use crate::openpgp::parse::stream::{
+ VerificationHelper, DecryptionHelper, DecryptorBuilder, MessageStructure,
+};
+use crate::openpgp::policy::Policy;
+use sequoia_store as store;
+
+use super::{dump::PacketDumper, VHelper};
+
+struct Helper<'a> {
+ vhelper: VHelper<'a>,
+ secret_keys:
+ HashMap<KeyID, Key<key::SecretParts, key::UnspecifiedRole>>,
+ key_identities: HashMap<KeyID, Fingerprint>,
+ key_hints: HashMap<KeyID, String>,
+ dump_session_key: bool,
+ dumper: Option<PacketDumper>,
+}
+
+impl<'a> Helper<'a> {
+ fn new(ctx: &'a Context, policy: &'a dyn Policy,
+ mapping: &'a mut store::Mapping,
+ signatures: usize, certs: Vec<Cert>, secrets: Vec<Cert>,
+ dump_session_key: bool, dump: bool)
+ -> Self
+ {
+ let mut keys = HashMap::new();
+ let mut identities: HashMap<KeyID, Fingerprint> = HashMap::new();
+ let mut hints: HashMap<KeyID, String> = HashMap::new();
+ for tsk in secrets {
+ let hint = match tsk.with_policy(policy, None)
+ .and_then(|valid_cert| valid_cert.primary_userid()).ok()
+ {
+ Some(uid) => format!("{} ({})", uid.userid(),
+ KeyID::from(tsk.fingerprint())),
+ None => format!("{}", KeyID::from(tsk.fingerprint())),
+ };
+
+ for ka in tsk.keys()
+ // XXX: Should use the message's creation time that we do not know.
+ .with_policy(policy, None)
+ .for_transport_encryption().for_storage_encryption()
+ .secret()
+ {
+ let id: KeyID = ka.key().fingerprint().into();
+ keys.insert(id.clone(), ka.key().clone().into());
+ identities.insert(id.clone(), tsk.fingerprint());
+ hints.insert(id, hint.clone());
+ }
+ }
+
+ Helper {
+ vhelper: VHelper::new(ctx, mapping, signatures, certs),
+ secret_keys: keys,
+ key_identities: identities,
+ key_hints: hints,
+ dump_session_key: dump_session_key,
+ dumper: if dump {
+ let width = terminal::size().ok().map(|(cols, _)| cols as usize)
+ .unwrap_or(80);
+ Some(PacketDumper::new(width, false))
+ } else {
+ None
+ },
+ }
+ }
+
+ /// Tries to decrypt the given PKESK packet with `keypair` and try
+ /// to decrypt the packet parser using `decrypt`.
+ fn try_decrypt<D>(&self, pkesk: &PKESK,
+ sym_algo: Option<SymmetricAlgorithm>,
+ keypair: &mut dyn crypto::Decryptor,
+ decrypt: &mut D)
+ -> Option<Option<Fingerprint>>
+ where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool
+ {
+ let keyid = keypair.public().fingerprint().into();
+ match pkesk.decrypt(keypair, sym_algo)
+ .and_then(|(algo, sk)| {
+ if decrypt(algo, &sk) { Some(sk) } else { None }
+ })
+ {
+ Some(sk) => {
+ if self.dump_session_key {
+ eprintln!("Session key: {}", hex::encode(&sk));
+ }
+ Some(self.key_identities.get(&keyid).map(|fp| fp.clone()))
+ },
+ None => None,
+ }
+ }
+}
+
+impl<'a> VerificationHelper for Helper<'a> {
+ fn inspect(&mut self, pp: &PacketParser) -> Result<()> {
+ if let Some(dumper) = self.dumper.as_mut() {
+ dumper.packet(&mut io::stderr(),
+ pp.recursion_depth() as usize,
+ pp.header().clone(), pp.packet.clone(),
+ pp.map().map(|m| m.clone()), None)?;
+ }
+ Ok(())
+ }
+
+ fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> Result<Vec<Cert>> {
+ self.vhelper.get_certs(ids)
+ }
+ fn check(&mut self, structure: MessageStructure) -> Result<()> {
+ self.vhelper.check(structure)
+ }
+}
+
+impl<'a> DecryptionHelper for Helper<'a> {
+ fn decrypt<D>(&mut self, pkesks: &[PKESK], skesks: &[SKESK],
+ sym_algo: Option<SymmetricAlgorithm>,
+ mut decrypt: D) -> openpgp::Result<Option<Fingerprint>>
+ where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool
+ {
+ // First, we try those keys that we can use without prompting
+ // for a password.
+ for pkesk in pkesks {
+ let keyid = pkesk.recipient();
+ if let Some(key) = self.secret_keys.get(&keyid) {
+ if ! key.secret().is_encrypted() {
+ if let Some(fp) = key.clone().into_keypair().ok()
+ .and_then(|mut k|
+ self.try_decrypt(pkesk, sym_algo, &mut k, &mut decrypt))
+ {
+ return Ok(fp);
+ }
+ }
+ }
+ }
+
+ // Second, we try those keys that are encrypted.
+ for pkesk in pkesks {
+ // Don't ask the user to decrypt a key if we don't support
+ // the algorithm.
+ if ! pkesk.pk_algo().is_supported() {
+ continue;
+ }
+
+ let keyid = pkesk.recipient();
+ if let Some(key) = self.secret_keys.get_mut(&keyid) {
+ let mut keypair = loop {
+ if ! key.secret().is_encrypted() {
+ break key.clone().into_keypair().unwrap();
+ }
+
+ let p = rpassword::read_password_from_tty(Some(
+ &format!(
+ "Enter password to decrypt key {}: ",
+ self.key_hints.get(&keyid).unwrap())))?.into();
+
+ let algo = key.pk_algo();
+ if let Some(()) =
+ key.secret_mut().decrypt_in_place(algo, &p).ok() {
+ break key.clone().into_keypair().unwrap()
+ } else {
+ eprintln!("Bad password.");
+ }
+ };
+
+ if let Some(fp) =
+ self.try_decrypt(pkesk, sym_algo, &mut keypair,
+ &mut decrypt)
+ {
+ return Ok(fp);
+ }
+ }
+ }
+
+ // Third, we try to decrypt PKESK packets with wildcard
+ // recipients using those keys that we can use without
+ // prompting for a password.
+ for pkesk in pkesks.iter().filter(|p| p.recipient().is_wildcard()) {
+ for key in self.secret_keys.values() {
+ if ! key.secret().is_encrypted() {
+ if let Some(fp) = key.clone().into_keypair().ok()
+ .and_then(|mut k|
+ self.try_decrypt(pkesk, sym_algo, &mut k, &mut decrypt))
+ {
+ return Ok(fp);
+ }
+ }
+ }
+ }
+
+ // Fourth, we try to decrypt PKESK packets with wildcard
+ // recipients using those keys that are encrypted.
+ for pkesk in pkesks.iter().filter(|p| p.recipient().is_wildcard()) {
+ // Don't ask the user to decrypt a key if we don't support
+ // the algorithm.
+ if ! pkesk.pk_algo().is_supported() {
+ continue;
+ }
+
+ // To appease the borrow checker, iterate over the
+ // hashmap, awkwardly.
+ for keyid in self.secret_keys.keys().cloned().collect::<Vec<_>>()
+ {
+ let mut keypair = loop {
+ let key = self.secret_keys.get_mut(&keyid).unwrap(); // Yuck
+
+ if ! key.secret().is_encrypted() {
+ break key.clone().into_keypair().unwrap();
+ }
+
+ let p = rpassword::read_password_from_tty(Some(
+ &format!(
+ "Enter password to decrypt key {}: ",
+ self.key_hints.get(&keyid).unwrap())))?.into();
+
+ let algo = key.pk_algo();
+ if let Some(()) =
+ key.secret_mut().decrypt_in_place(algo, &p).ok() {
+ break key.clone().into_keypair().unwrap()
+ } else {
+ eprintln!("Bad password.");
+ }
+ };
+
+ if let Some(fp) =
+ self.try_decrypt(pkesk, sym_algo, &mut keypair,
+ &mut decrypt)
+ {
+ return Ok(fp);
+ }
+ }
+ }
+
+ if skesks.is_empty() {
+ return
+ Err(anyhow::anyhow!("No key to decrypt message"));
+ }
+
+ // Finally, try to decrypt using the SKESKs.
+ loop {
+ let password =
+ rpassword::read_password_from_tty(Some(
+ "Enter password to decrypt message: "))?.into();
+
+ for skesk in skesks {
+ if let Some(sk) = skesk.decrypt(&password).ok()
+ .and_then(|(algo, sk)| { if decrypt(algo, &sk) { Some(sk) } else { None }})
+ {
+ if self.dump_session_key {
+ eprintln!("Session key: {}", hex::encode(&sk));
+ }
+ return Ok(None);
+ }
+ }
+
+ eprintln!("Bad password.");
+ }
+ }
+}
+
+pub fn decrypt(ctx: &Context, policy: &dyn Policy, mapping: &mut store::Mapping,
+ input: &mut dyn io::Read, output: &mut dyn io::Write,
+ signatures: usize, certs: Vec<Cert>, secrets: Vec<Cert>,
+ dump_session_key: bool,
+ dump: bool, hex: bool)
+ -> Result<()> {
+ let helper = Helper::new(ctx, policy, mapping, signatures, certs, secrets,
+ dump_session_key, dump || hex);
+ let mut decryptor = DecryptorBuilder::from_reader(input)?
+ .mapping(hex)
+ .with_policy(policy, None, helper)
+ .context("Decryption failed")?;
+
+ io::copy(&mut decryptor, output).context("Decryption failed")?;
+
+ let helper = decryptor.into_helper();
+ if let Some(dumper) = helper.dumper.as_ref() {
+ dumper.flush(&mut io::stderr())?;
+ }
+ helper.vhelper.print_status();
+ return Ok(());
+}
+
+pub fn decrypt_unwrap(ctx: &Context, policy: &dyn Policy,
+ mapping: &mut store::Mapping,
+ input: &mut dyn io::Read, output: &mut dyn io::Write,
+ secrets: Vec<Cert>, dump_session_key: bool)
+ -> Result<()>
+{
+ let mut helper = Helper::new(ctx, policy, mapping, 0, Vec::new(), secrets,
+ dump_session_key, false);
+
+ let mut ppr = PacketParser::from_reader(input)?;
+
+ let mut pkesks: Vec<packet::PKESK> = Vec::new();
+ let mut skesks: Vec<packet::SKESK> = Vec::new();
+ while let PacketParserResult::Some(mut pp) = ppr {
+ let sym_algo_hint = if let Packet::AED(ref aed) = pp.packet {
+ Some(aed.symmetric_algo())
+ } else {
+ None
+ };
+
+ match pp.packet {
+ Packet::SEIP(_) | Packet::AED(_) => {
+ {
+ let decrypt = |algo, secret: &SessionKey| {
+ pp.decrypt(algo, secret).is_ok()
+ };
+ helper.decrypt(&pkesks[..], &skesks[..], sym_algo_hint,
+ decrypt)?;
+ }
+ if pp.encrypted() {
+ return Err(
+ openpgp::Error::MissingSessionKey(
+ "No session key".into()).into());
+ }
+
+ io::copy(&mut pp, output)?;
+ return Ok(());
+ },
+ Packet::MDC(ref mdc) => if ! mdc.valid() {
+ return Err(openpgp::Error::ManipulatedMessage.into());
+ },
+ _ => (),
+ }
+
+ let (p, ppr_tmp) = pp.recurse()?;
+ match p {
+ Packet::PKESK(pkesk) => pkesks.push(pkesk),
+ Packet::SKESK(skesk) => skesks.push(skesk),
+ _ => (),
+ }
+ ppr = ppr_tmp;
+ }
+
+ Ok(())
+}
diff --git a/sq/src/commands/dump.rs b/sq/src/commands/dump.rs
new file mode 100644
index 00000000..6f35f56d
--- /dev/null
+++ b/sq/src/commands/dump.rs
@@ -0,0 +1,946 @@
+use std::io::{self, Read};
+
+use sequoia_openpgp as openpgp;
+use self::openpgp::types::{Duration, Timestamp, SymmetricAlgorithm};
+use self::openpgp::fmt::hex;
+use self::openpgp::crypto::mpi;
+use self::openpgp::{Packet, Result};
+use self::openpgp::packet::prelude::*;
+use self::openpgp::packet::header::CTB;
+use self::openpgp::packet::{Header, header::BodyLength, Signature};
+use self::openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue};
+use self::openpgp::crypto::{SessionKey, S2K};
+use self::openpgp::parse::{map::Map, Parse, PacketParserResult};
+
+#[derive(Debug)]
+pub enum Kind {
+ Message {
+ encrypted: bool,
+ },
+ Keyring,
+ Cert,
+ Unknown,
+}
+
+/// Converts sequoia_openpgp types for rendering.
+pub trait Convert<T> {
+ /// Performs the conversion.
+ fn convert(self) -> T;
+}
+
+impl Convert<chrono::Duration> for std::time::Duration {
+ fn convert(self) -> chrono::Duration {
+ chrono::Duration::seconds(self.as_secs() as i64)
+ }
+}
+
+impl Convert<chrono::Duration> for Duration {
+ fn convert(self) -> chrono::Duration {
+ chrono::Duration::seconds(self.as_secs() as i64)
+ }
+}
+
+impl Convert<chrono::DateTime<chrono::offset::Utc>> for std::time::SystemTime {
+ fn convert(self) -> chrono::DateTime<chrono::offset::Utc> {
+ chrono::DateTime::<chrono::offset::Utc>::from(self)
+ }
+}
+
+impl Convert<chrono::DateTime<chrono::offset::Utc>> for Timestamp {
+ fn convert(self) -> chrono::DateTime<chrono::offset::Utc> {
+ std::time::SystemTime::from(self).convert()
+ }
+}
+
+pub fn dump<W>(input: &mut dyn io::Read, output: &mut dyn io::Write,
+ mpis: bool, hex: bool, sk: Option<&SessionKey>,
+ width: W)
+ -> Result<Kind>
+ where W: Into<Option<usize>>
+{
+ let mut ppr
+ = self::openpgp::parse::PacketParserBuilder::from_reader(input)?
+ .map(hex).build()?;
+ let mut message_encrypted = false;
+ let width = width.into().unwrap_or(80);
+ let mut dumper = PacketDumper::new(width, mpis);
+
+ while let PacketParserResult::Some(mut pp) = ppr {
+ let additional_fields = match pp.packet {
+ Packet::Literal(_) => {
+ let mut prefix = vec![0; 40];
+ let n = pp.read(&mut prefix)?;
+ Some(vec![
+ format!("Content: {:?}{}",
+ String::from_utf8_lossy(&prefix[..n]),
+ if n == prefix.len() { "..." } else { "" }),
+ ])
+ },
+ Packet::SEIP(_) if sk.is_none() => {
+ message_encrypted = true;
+ Some(vec!["No session key supplied".into()])
+ }
+ Packet::SEIP(_) if sk.is_some() => {
+ message_encrypted = true;
+ let sk = sk.as_ref().unwrap();
+ let mut decrypted_with = None;
+ for algo in 1..20 {
+ let algo = SymmetricAlgorithm::from(algo);
+ if let Ok(size) = algo.key_size() {
+ if size != sk.len() { continue; }
+ } else {
+ continue;
+ }
+
+ if let Ok(_) = pp.decrypt(algo, sk) {
+ decrypted_with = Some(algo);
+ break;
+ }
+ }
+ let mut fields = Vec::new();
+ fields.push(format!("Session key: {}", hex::encode(sk)));
+ if let Some(algo) = decrypted_with {
+ fields.push(format!("Symmetric algo: {}", algo));
+ fields.push("Decryption successful".into());
+ } else {
+ fields.push("Decryption failed".into());
+ }
+ Some(fields)
+ },
+ Packet::AED(_) if sk.is_none() => {
+ message_encrypted = true;
+ Some(vec!["No session key supplied".into()])
+ }
+ Packet::AED(_) if sk.is_some() => {
+ message_encrypted = true;
+ let sk = sk.as_ref().unwrap();
+ let algo = if let Packet::AED(ref aed) = pp.packet {
+ aed.symmetric_algo()
+ } else {
+ unreachable!()
+ };
+
+ let _ = pp.decrypt(algo, sk);
+
+ let mut fields = Vec::new();
+ fields.push(format!("Session key: {}", hex::encode(sk)));
+ if pp.encrypted() {
+ fields.push("Decryption failed".into());
+ } else {
+ fields.push("Decryption successful".into());
+ }
+ Some(fields)
+ },
+ _ => None,
+ };
+
+ let header = pp.header().clone();
+ let map = pp.take_map();
+
+ let recursion_depth = pp.recursion_depth();
+ let packet = pp.packet.clone();
+
+ dumper.packet(output, recursion_depth as usize,
+ header, packet, map, additional_fields)?;
+
+ let (_, ppr_) = match pp.recurse() {
+ Ok(v) => Ok(v),
+ Err(e) => {
+ let _ = dumper.flush(output);
+ Err(e)
+ },
+ }?;
+ ppr = ppr_;
+ }
+
+ dumper.flush(output)?;
+
+ if let PacketParserResult::EOF(eof) = ppr {
+ if eof.is_message().is_ok() {
+ Ok(Kind::Message {
+ encrypted: message_encrypted,
+ })
+ } else if eof.is_cert().is_ok() {
+ Ok(Kind::Cert)
+ } else if eof.is_keyring().is_ok() {
+ Ok(Kind::Keyring)
+ } else {
+ Ok(Kind::Unknown)
+ }
+ } else {
+ unreachable!()
+ }
+}
+
+struct Node {