diff options
-rw-r--r-- | tool/src/commands/dump.rs | 51 | ||||
-rw-r--r-- | tool/src/sq-usage.rs | 3 | ||||
-rw-r--r-- | tool/src/sq.rs | 65 | ||||
-rw-r--r-- | tool/src/sq_cli.rs | 6 |
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")) |