summaryrefslogtreecommitdiffstats
path: root/tool/src
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2019-03-13 17:20:04 +0100
committerJustus Winter <justus@sequoia-pgp.org>2019-03-13 17:24:42 +0100
commit38defdd34ac927560172439c93295d3cce6f6171 (patch)
treec001b1d89622e353be9582449872e3db0ac7edfb /tool/src
parent6ca0c166e047eec89623b7edead32eb6aeeba6fa (diff)
sq: Support dumping encrypted messages.
Diffstat (limited to 'tool/src')
-rw-r--r--tool/src/commands/dump.rs51
-rw-r--r--tool/src/sq-usage.rs3
-rw-r--r--tool/src/sq.rs65
-rw-r--r--tool/src/sq_cli.rs6
4 files changed, 121 insertions, 4 deletions
diff --git a/tool/src/commands/dump.rs b/tool/src/commands/dump.rs
index 71a89141..6fc10626 100644
--- a/tool/src/commands/dump.rs
+++ b/tool/src/commands/dump.rs
@@ -2,16 +2,18 @@ use std::io::{self, Read};
use time;
extern crate sequoia_openpgp as openpgp;
+use openpgp::constants::SymmetricAlgorithm;
use openpgp::{Packet, Result};
use openpgp::packet::ctb::CTB;
use openpgp::packet::{Header, BodyLength, Signature};
use openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue};
-use openpgp::crypto::s2k::S2K;
+use openpgp::crypto::{SessionKey, s2k::S2K};
use openpgp::parse::{map::Map, Parse, PacketParserResult};
use super::TIMEFMT;
-pub fn dump(input: &mut io::Read, output: &mut io::Write, mpis: bool, hex: bool)
+pub fn dump(input: &mut io::Read, output: &mut io::Write, mpis: bool, hex: bool,
+ sk: Option<&SessionKey>)
-> Result<()> {
let mut ppr
= openpgp::parse::PacketParserBuilder::from_reader(input)?
@@ -29,6 +31,51 @@ pub fn dump(input: &mut io::Read, output: &mut io::Write, mpis: bool, hex: bool)
if n == prefix.len() { "..." } else { "" }),
])
},
+ Packet::SEIP(_) if sk.is_some() => {
+ 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: {}", to_hex(sk, false)));
+ 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_some() => {
+ let sk = sk.as_ref().unwrap();
+ let algo = if let Packet::AED(ref aed) = pp.packet {
+ aed.cipher()
+ } else {
+ unreachable!()
+ };
+
+ let _ = pp.decrypt(algo, sk);
+
+ let mut fields = Vec::new();
+ fields.push(format!("Session key: {}", to_hex(sk, false)));
+ if pp.decrypted() {
+ fields.push("Decryption successful".into());
+ } else {
+ fields.push("Decryption failed".into());
+ }
+ Some(fields)
+ },
_ => None,
};
diff --git a/tool/src/sq-usage.rs b/tool/src/sq-usage.rs
index 18c9d452..ddf4f741 100644
--- a/tool/src/sq-usage.rs
+++ b/tool/src/sq-usage.rs
@@ -573,7 +573,8 @@
//! -V, --version Prints version information
//!
//! OPTIONS:
-//! -o, --output <FILE> Sets the output file to use
+//! -o, --output <FILE> Sets the output file to use
+//! --session-key <SESSION-KEY> Session key to decrypt encryption containers
//!
//! ARGS:
//! <FILE> Sets the input file to use
diff --git a/tool/src/sq.rs b/tool/src/sq.rs
index ead02aa3..7fa375d9 100644
--- a/tool/src/sq.rs
+++ b/tool/src/sq.rs
@@ -226,8 +226,15 @@ fn real_main() -> Result<(), failure::Error> {
("dump", Some(m)) => {
let mut input = open_or_stdin(m.value_of("input"))?;
let mut output = create_or_stdout(m.value_of("output"), force)?;
+ let session_key: Option<openpgp::crypto::SessionKey> =
+ if let Some(sk) = m.value_of("session-key") {
+ Some(from_hex(sk, true)?.into())
+ } else {
+ None
+ };
commands::dump(&mut input, &mut output,
- m.is_present("mpis"), m.is_present("hex"))?;
+ m.is_present("mpis"), m.is_present("hex"),
+ session_key.as_ref())?;
},
("split", Some(m)) => {
let mut input = open_or_stdin(m.value_of("input"))?;
@@ -465,6 +472,62 @@ fn format_time(t: &time::Timespec) -> String {
.unwrap() // Only parse errors can happen.
}
+/// A helpful function for converting a hexadecimal string to binary.
+/// This function skips whitespace if `pretty` is set.
+pub(crate) fn from_hex(hex: &str, pretty: bool) -> openpgp::Result<Vec<u8>> {
+ use openpgp::Error;
+ const BAD: u8 = 255u8;
+ const X: u8 = 'x' as u8;
+
+ let mut nibbles = hex.as_bytes().iter().filter_map(|x| {
+ match *x as char {
+ '0' => Some(0u8),
+ '1' => Some(1u8),
+ '2' => Some(2u8),
+ '3' => Some(3u8),
+ '4' => Some(4u8),
+ '5' => Some(5u8),
+ '6' => Some(6u8),
+ '7' => Some(7u8),
+ '8' => Some(8u8),
+ '9' => Some(9u8),
+ 'a' | 'A' => Some(10u8),
+ 'b' | 'B' => Some(11u8),
+ 'c' | 'C' => Some(12u8),
+ 'd' | 'D' => Some(13u8),
+ 'e' | 'E' => Some(14u8),
+ 'f' | 'F' => Some(15u8),
+ 'x' | 'X' if pretty => Some(X),
+ _ if pretty && x.is_ascii_whitespace() => None,
+ _ => Some(BAD),
+ }
+ }).collect::<Vec<u8>>();
+
+ if pretty && nibbles.len() >= 2 && nibbles[0] == 0 && nibbles[1] == X {
+ // Drop '0x' prefix.
+ nibbles.remove(0);
+ nibbles.remove(0);
+ }
+
+ if nibbles.iter().any(|&b| b == BAD || b == X) {
+ // Not a hex character.
+ return
+ Err(Error::InvalidArgument("Invalid characters".into()).into());
+ }
+
+ // We need an even number of nibbles.
+ if nibbles.len() % 2 != 0 {
+ return
+ Err(Error::InvalidArgument("Odd number of nibbles".into()).into());
+ }
+
+ let bytes = nibbles.chunks(2).map(|nibbles| {
+ (nibbles[0] << 4) | nibbles[1]
+ }).collect::<Vec<u8>>();
+
+ Ok(bytes)
+}
+
fn main() {
if let Err(e) = real_main() {
let mut cause = e.as_fail();
diff --git a/tool/src/sq_cli.rs b/tool/src/sq_cli.rs
index 70d87b95..0180369f 100644
--- a/tool/src/sq_cli.rs
+++ b/tool/src/sq_cli.rs
@@ -373,6 +373,12 @@ pub fn build() -> App<'static, 'static> {
.long("output")
.short("o")
.help("Sets the output file to use"))
+ .arg(Arg::with_name("session-key")
+ .long("session-key")
+ .takes_value(true)
+ .value_name("SESSION-KEY")
+ .help("Session key to decrypt encryption \
+ containers"))
.arg(Arg::with_name("mpis")
.long("mpis")
.help("Print MPIs"))