summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tool/Cargo.toml1
-rw-r--r--tool/src/cli.rs17
-rw-r--r--tool/src/main.rs93
-rw-r--r--tool/src/usage.rs20
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