diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2018-03-26 15:03:36 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2018-03-26 15:17:48 +0200 |
commit | 30604553c08564e27621ce1e022cfe7602056646 (patch) | |
tree | b0ada51e11b2de2b0098263be2d09d10ceebd5dd /tool | |
parent | 1a70e0e46279e326862da345ef0d0c9f821b707a (diff) |
tool: Add decryption subcommand.
- Add support for decrypting messages. For now, only symmetrically
encrypted messages are supported.
Diffstat (limited to 'tool')
-rw-r--r-- | tool/Cargo.toml | 1 | ||||
-rw-r--r-- | tool/src/cli.rs | 17 | ||||
-rw-r--r-- | tool/src/main.rs | 93 | ||||
-rw-r--r-- | tool/src/usage.rs | 20 |
4 files changed, 129 insertions, 2 deletions
diff --git a/tool/Cargo.toml b/tool/Cargo.toml index a909d812..d395706d 100644 --- a/tool/Cargo.toml +++ b/tool/Cargo.toml @@ -12,6 +12,7 @@ sequoia-store = { path = "../store" } clap = "2.27.1" failure = "0.1.1" prettytable-rs = "0.6.7" +rpassword = "2.0.0" time = "0.1.38" [build-dependencies] diff --git a/tool/src/cli.rs b/tool/src/cli.rs index ee627d27..be98bd6a 100644 --- a/tool/src/cli.rs +++ b/tool/src/cli.rs @@ -13,6 +13,23 @@ pub fn build() -> App<'static, 'static> { .long("policy") .short("p") .help("Sets the network policy to use")) + .subcommand(SubCommand::with_name("decrypt") + .about("Decrypts an OpenPGP message") + .arg(Arg::with_name("input").value_name("FILE") + .long("input") + .short("i") + .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("dearmor") + .long("dearmor") + .short("A") + .help("Remove ASCII Armor from input")) + .arg(Arg::with_name("dump") + .long("dump") + .help("Print a packet dump to stderr"))) .subcommand(SubCommand::with_name("enarmor") .about("Applies ASCII Armor to a file") .arg(Arg::with_name("input").value_name("FILE") diff --git a/tool/src/main.rs b/tool/src/main.rs index 39d501fc..0f9af6d6 100644 --- a/tool/src/main.rs +++ b/tool/src/main.rs @@ -4,6 +4,7 @@ extern crate clap; extern crate failure; #[macro_use] extern crate prettytable; +extern crate rpassword; extern crate time; use failure::ResultExt; @@ -19,8 +20,7 @@ extern crate sequoia_core; extern crate sequoia_net; extern crate sequoia_store; -use openpgp::{armor, Fingerprint}; -use openpgp::TPK; +use openpgp::{armor, Fingerprint, TPK, Packet, Tag}; use sequoia_core::{Context, NetworkPolicy}; use sequoia_net::KeyServer; use sequoia_store::{Store, LogIter}; @@ -45,6 +45,84 @@ fn create_or_stdout(f: Option<&str>) -> Result<Box<io::Write>, failure::Error> { const INDENT: &'static str = " "; +fn decrypt(input: &mut io::Read, output: &mut io::Write, dump: bool) + -> Result<(), failure::Error> { + #[derive(PartialEq)] + enum State { + Start, + Decrypted(u8, Vec<u8>), + Deciphered, + Done, + } + let mut state = State::Start; + let mut ppo = openpgp::parse::PacketParser::from_reader(input)?; + + while let Some(mut pp) = ppo { + state = match state { + // Look for an PKESK or SKESK packet. + State::Start => + match pp.packet { + Packet::Unknown(ref u) => { + match u.tag { + Tag::PKESK => + eprintln!("Decryption using PKESK not yet \ + supported."), + _ => (), + } + State::Start + }, + Packet::SKESK(ref skesk) => { + let pass = rpassword::prompt_password_stderr( + "Enter passphrase to decrypt message: ")?; + match skesk.decrypt(pass.into_bytes().as_ref()) { + Ok((algo, key)) => State::Decrypted(algo, key), + Err(e) => { + eprintln!("Decryption failed: {}", e); + State::Start + }, + } + }, + _ => State::Start, + }, + + // Look for an SEIP packet. + State::Decrypted(algo, key) => + if let Packet::SEIP(_) = pp.packet { + pp.decrypt(algo, &key[..])?; + State::Deciphered + } else { + State::Decrypted(algo, key) + }, + + // Look for the literal data packet. + State::Deciphered => + if let Packet::Literal(_) = pp.packet { + io::copy(&mut pp, output)?; + State::Done + } else { + State::Deciphered + }, + + // We continue to parse, useful for dumping + // encrypted packets. + State::Done => State::Done, + }; + + if dump { + eprintln!("{}{:?}", + &INDENT[0..pp.recursion_depth as usize], pp.packet); + } + + let (_, _, ppo_tmp, _) = pp.recurse()?; + ppo = ppo_tmp; + } + + if state != State::Done { + return Err(failure::err_msg("Decryption failed.")); + } + Ok(()) +} + fn real_main() -> Result<(), failure::Error> { let matches = cli::build().get_matches(); @@ -63,6 +141,17 @@ fn real_main() -> Result<(), failure::Error> { .network_policy(policy).build()?; match matches.subcommand() { + ("decrypt", Some(m)) => { + let dump = m.is_present("dump"); + let mut input = open_or_stdin(m.value_of("input"))?; + let mut output = create_or_stdout(m.value_of("output"))?; + let mut input = if m.is_present("dearmor") { + Box::new(armor::Reader::new(&mut input, armor::Kind::Any)) + } else { + input + }; + return decrypt(&mut input, &mut output, dump); + }, ("enarmor", Some(m)) => { let mut input = open_or_stdin(m.value_of("input"))?; let mut output = create_or_stdout(m.value_of("output"))?; diff --git a/tool/src/usage.rs b/tool/src/usage.rs index 3b5cc2a5..81ae0f07 100644 --- a/tool/src/usage.rs +++ b/tool/src/usage.rs @@ -18,6 +18,7 @@ //! //! SUBCOMMANDS: //! dearmor Removes ASCII Armor from a file +//! decrypt Decrypts an OpenPGP message //! dump Lists OpenPGP packets //! enarmor Applies ASCII Armor to a file //! help Prints this message or the help of the given subcommand(s) @@ -43,6 +44,25 @@ //! -o, --output <FILE> Sets the output file to use //! ``` //! +//! ## Subcommand decrypt +//! +//! ```text +//! Decrypts an OpenPGP message +//! +//! USAGE: +//! sq decrypt [FLAGS] [OPTIONS] +//! +//! FLAGS: +//! -A, --dearmor Remove ASCII Armor from input +//! --dump Print a packet dump to stderr +//! -h, --help Prints help information +//! -V, --version Prints version information +//! +//! OPTIONS: +//! -i, --input <FILE> Sets the input file to use +//! -o, --output <FILE> Sets the output file to use +//! ``` +//! //! ## Subcommand dump //! //! ```text |