From 30b37711392468161817653211754601f4f52057 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Fri, 4 Oct 2019 15:31:28 +0200 Subject: tool: Add packet decrypt that unwraps encryption containers. --- tool/src/commands/decrypt.rs | 54 +++++++++++++++++++++++++++++++++++++++++++- tool/src/commands/mod.rs | 2 +- tool/src/sq-usage.rs | 32 ++++++++++++++++++++++---- tool/src/sq.rs | 22 ++++++++++++++++++ tool/src/sq_cli.rs | 30 ++++++++++++++++++++++++ 5 files changed, 134 insertions(+), 6 deletions(-) (limited to 'tool') diff --git a/tool/src/commands/decrypt.rs b/tool/src/commands/decrypt.rs index f04b6ac1..2e2753f6 100644 --- a/tool/src/commands/decrypt.rs +++ b/tool/src/commands/decrypt.rs @@ -10,8 +10,13 @@ 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::PacketParser; +use crate::openpgp::parse::{ + Parse, + PacketParser, + PacketParserResult, +}; use crate::openpgp::parse::stream::{ VerificationHelper, DecryptionHelper, Decryptor, MessageStructure, }; @@ -319,3 +324,50 @@ pub fn decrypt(ctx: &Context, mapping: &mut store::Mapping, helper.vhelper.print_status(); return Ok(()); } + +pub fn decrypt_unwrap(ctx: &Context, mapping: &mut store::Mapping, + input: &mut dyn io::Read, output: &mut dyn io::Write, + secrets: Vec, dump_session_key: bool) + -> Result<()> { + let mut helper = Helper::new(ctx, mapping, 0, Vec::new(), secrets, + dump_session_key, false, false); + + let mut ppr = PacketParser::from_reader(input)?; + + let mut pkesks: Vec = Vec::new(); + let mut skesks: Vec = Vec::new(); + while let PacketParserResult::Some(mut pp) = ppr { + match pp.packet { + Packet::SEIP(_) | Packet::AED(_) => { + { + let decrypt = + |algo, secret: &SessionKey| pp.decrypt(algo, secret); + helper.decrypt(&pkesks[..], &skesks[..], decrypt)?; + } + if ! pp.decrypted() { + // XXX: That is not quite the right error to return. + return Err( + openpgp::Error::InvalidSessionKey( + "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/tool/src/commands/mod.rs b/tool/src/commands/mod.rs index 37254db2..3ecd65ca 100644 --- a/tool/src/commands/mod.rs +++ b/tool/src/commands/mod.rs @@ -31,7 +31,7 @@ use crate::openpgp::serialize::padding::{ }; extern crate sequoia_store as store; -mod decrypt; +pub mod decrypt; pub use self::decrypt::decrypt; mod sign; pub use self::sign::sign; diff --git a/tool/src/sq-usage.rs b/tool/src/sq-usage.rs index 19607bb1..be4b3695 100644 --- a/tool/src/sq-usage.rs +++ b/tool/src/sq-usage.rs @@ -589,10 +589,34 @@ //! -V, --version Prints version information //! //! SUBCOMMANDS: -//! dump Lists OpenPGP packets -//! help Prints this message or the help of the given subcommand(s) -//! join Joins OpenPGP packets split across files -//! split Splits a message into OpenPGP packets +//! decrypt Decrypts an OpenPGP message, dumping the content of the encryption container without further +//! processing +//! dump Lists OpenPGP packets +//! help Prints this message or the help of the given subcommand(s) +//! join Joins OpenPGP packets split across files +//! split Splits a message into OpenPGP packets +//! ``` +//! +//! ### Subcommand packet decrypt +//! +//! ```text +//! Decrypts an OpenPGP message, dumping the content of the encryption container without further processing +//! +//! USAGE: +//! sq packet decrypt [FLAGS] [OPTIONS] [--] [FILE] +//! +//! FLAGS: +//! -B, --binary Don't ASCII-armor encode the OpenPGP data +//! --dump-session-key Prints the session key to stderr +//! -h, --help Prints help information +//! -V, --version Prints version information +//! +//! OPTIONS: +//! -o, --output Sets the output file to use +//! --secret-key-file ... Secret key to decrypt with, given as a file (can be given multiple times) +//! +//! ARGS: +//! Sets the input file to use //! ``` //! //! ### Subcommand packet dump diff --git a/tool/src/sq.rs b/tool/src/sq.rs index 055cf642..950ce27a 100644 --- a/tool/src/sq.rs +++ b/tool/src/sq.rs @@ -332,6 +332,28 @@ fn real_main() -> Result<(), failure::Error> { m.is_present("mpis"), m.is_present("hex"), session_key.as_ref(), width)?; }, + + ("decrypt", Some(m)) => { + let mut input = open_or_stdin(m.value_of("input"))?; + let output = create_or_stdout(m.value_of("output"), force)?; + let mut output = if ! m.is_present("binary") { + Box::new(armor::Writer::new(output, + armor::Kind::Message, + &[])?) + } else { + output + }; + let secrets = m.values_of("secret-key-file") + .map(load_certs) + .unwrap_or(Ok(vec![]))?; + let mut mapping = Mapping::open(&ctx, realm_name, mapping_name) + .context("Failed to open the mapping")?; + commands::decrypt::decrypt_unwrap( + &ctx, &mut mapping, + &mut input, &mut output, + secrets, m.is_present("dump-session-key"))?; + }, + ("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 d247e6d7..06ccde14 100644 --- a/tool/src/sq_cli.rs +++ b/tool/src/sq_cli.rs @@ -450,6 +450,36 @@ pub fn build() -> App<'static, 'static> { .long("hex") .short("x") .help("Print a hexdump"))) + + .subcommand(SubCommand::with_name("decrypt") + .display_order(10) + .about("Decrypts an OpenPGP message, dumping \ + the content of the encryption \ + container without further processing") + .arg(Arg::with_name("input").value_name("FILE") + .help("Sets the input file to use")) + .arg(Arg::with_name("output").value_name("FILE") + .long("output") + .short("o") + .help("Sets the output file to use")) + .arg(Arg::with_name("binary") + .long("binary") + .short("B") + .help("Don't ASCII-armor encode the \ + OpenPGP data")) + .arg(Arg::with_name("secret-key-file") + .long("secret-key-file") + .multiple(true) + .takes_value(true) + .value_name("TSK-FILE") + .number_of_values(1) + .help("Secret key to decrypt with, given \ + as a file \ + (can be given multiple times)")) + .arg(Arg::with_name("dump-session-key") + .long("dump-session-key") + .help("Prints the session key to stderr"))) + .subcommand(SubCommand::with_name("split") .about("Splits a message into OpenPGP packets") .arg(Arg::with_name("input").value_name("FILE") -- cgit v1.2.3