summaryrefslogtreecommitdiffstats
path: root/sq/src/commands/decrypt.rs
diff options
context:
space:
mode:
Diffstat (limited to 'sq/src/commands/decrypt.rs')
-rw-r--r--sq/src/commands/decrypt.rs354
1 files changed, 354 insertions, 0 deletions
diff --git a/sq/src/commands/decrypt.rs b/sq/src/commands/decrypt.rs
new file mode 100644
index 00000000..07adabe6
--- /dev/null
+++ b/sq/src/commands/decrypt.rs
@@ -0,0 +1,354 @@
+use crossterm::terminal;
+use anyhow::Context as _;
+use std::collections::HashMap;
+use std::io;
+use rpassword;
+
+use sequoia_openpgp as openpgp;
+use sequoia_core::Context;
+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::{
+ Parse,
+ PacketParser,
+ PacketParserResult,
+};
+use crate::openpgp::parse::stream::{
+ VerificationHelper, DecryptionHelper, DecryptorBuilder, MessageStructure,
+};
+use crate::openpgp::policy::Policy;
+use sequoia_store as store;
+
+use super::{dump::PacketDumper, VHelper};
+
+struct Helper<'a> {
+ vhelper: VHelper<'a>,
+ secret_keys:
+ HashMap<KeyID, Key<key::SecretParts, key::UnspecifiedRole>>,
+ key_identities: HashMap<KeyID, Fingerprint>,
+ key_hints: HashMap<KeyID, String>,
+ dump_session_key: bool,
+ dumper: Option<PacketDumper>,
+}
+
+impl<'a> Helper<'a> {
+ fn new(ctx: &'a Context, policy: &'a dyn Policy,
+ mapping: &'a mut store::Mapping,
+ signatures: usize, certs: Vec<Cert>, secrets: Vec<Cert>,
+ dump_session_key: bool, dump: bool)
+ -> Self
+ {
+ let mut keys = HashMap::new();
+ let mut identities: HashMap<KeyID, Fingerprint> = HashMap::new();
+ let mut hints: HashMap<KeyID, String> = HashMap::new();
+ for tsk in secrets {
+ let hint = match tsk.with_policy(policy, None)
+ .and_then(|valid_cert| valid_cert.primary_userid()).ok()
+ {
+ Some(uid) => format!("{} ({})", uid.userid(),
+ KeyID::from(tsk.fingerprint())),
+ None => format!("{}", KeyID::from(tsk.fingerprint())),
+ };
+
+ for ka in tsk.keys()
+ // XXX: Should use the message's creation time that we do not know.
+ .with_policy(policy, None)
+ .for_transport_encryption().for_storage_encryption()
+ .secret()
+ {
+ let id: KeyID = ka.key().fingerprint().into();
+ keys.insert(id.clone(), ka.key().clone().into());
+ identities.insert(id.clone(), tsk.fingerprint());
+ hints.insert(id, hint.clone());
+ }
+ }
+
+ Helper {
+ vhelper: VHelper::new(ctx, mapping, signatures, certs),
+ secret_keys: keys,
+ key_identities: identities,
+ key_hints: hints,
+ dump_session_key: dump_session_key,
+ dumper: if dump {
+ let width = terminal::size().ok().map(|(cols, _)| cols as usize)
+ .unwrap_or(80);
+ Some(PacketDumper::new(width, false))
+ } else {
+ None
+ },
+ }
+ }
+
+ /// Tries to decrypt the given PKESK packet with `keypair` and try
+ /// to decrypt the packet parser using `decrypt`.
+ fn try_decrypt<D>(&self, pkesk: &PKESK,
+ sym_algo: Option<SymmetricAlgorithm>,
+ keypair: &mut dyn crypto::Decryptor,
+ decrypt: &mut D)
+ -> Option<Option<Fingerprint>>
+ where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool
+ {
+ let keyid = keypair.public().fingerprint().into();
+ match pkesk.decrypt(keypair, sym_algo)
+ .and_then(|(algo, sk)| {
+ if decrypt(algo, &sk) { Some(sk) } else { None }
+ })
+ {
+ Some(sk) => {
+ if self.dump_session_key {
+ eprintln!("Session key: {}", hex::encode(&sk));
+ }
+ Some(self.key_identities.get(&keyid).map(|fp| fp.clone()))
+ },
+ None => None,
+ }
+ }
+}
+
+impl<'a> VerificationHelper for Helper<'a> {
+ fn inspect(&mut self, pp: &PacketParser) -> Result<()> {
+ if let Some(dumper) = self.dumper.as_mut() {
+ dumper.packet(&mut io::stderr(),
+ pp.recursion_depth() as usize,
+ pp.header().clone(), pp.packet.clone(),
+ pp.map().map(|m| m.clone()), None)?;
+ }
+ Ok(())
+ }
+
+ fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> Result<Vec<Cert>> {
+ self.vhelper.get_certs(ids)
+ }
+ fn check(&mut self, structure: MessageStructure) -> Result<()> {
+ self.vhelper.check(structure)
+ }
+}
+
+impl<'a> DecryptionHelper for Helper<'a> {
+ fn decrypt<D>(&mut self, pkesks: &[PKESK], skesks: &[SKESK],
+ sym_algo: Option<SymmetricAlgorithm>,
+ mut decrypt: D) -> openpgp::Result<Option<Fingerprint>>
+ where D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool
+ {
+ // First, we try those keys that we can use without prompting
+ // for a password.
+ for pkesk in pkesks {
+ let keyid = pkesk.recipient();
+ if let Some(key) = self.secret_keys.get(&keyid) {
+ if ! key.secret().is_encrypted() {
+ if let Some(fp) = key.clone().into_keypair().ok()
+ .and_then(|mut k|
+ self.try_decrypt(pkesk, sym_algo, &mut k, &mut decrypt))
+ {
+ return Ok(fp);
+ }
+ }
+ }
+ }
+
+ // Second, we try those keys that are encrypted.
+ for pkesk in pkesks {
+ // Don't ask the user to decrypt a key if we don't support
+ // the algorithm.
+ if ! pkesk.pk_algo().is_supported() {
+ continue;
+ }
+
+ let keyid = pkesk.recipient();
+ if let Some(key) = self.secret_keys.get_mut(&keyid) {
+ let mut keypair = loop {
+ if ! key.secret().is_encrypted() {
+ break key.clone().into_keypair().unwrap();
+ }
+
+ let p = rpassword::read_password_from_tty(Some(
+ &format!(
+ "Enter password to decrypt key {}: ",
+ self.key_hints.get(&keyid).unwrap())))?.into();
+
+ let algo = key.pk_algo();
+ if let Some(()) =
+ key.secret_mut().decrypt_in_place(algo, &p).ok() {
+ break key.clone().into_keypair().unwrap()
+ } else {
+ eprintln!("Bad password.");
+ }
+ };
+
+ if let Some(fp) =
+ self.try_decrypt(pkesk, sym_algo, &mut keypair,
+ &mut decrypt)
+ {
+ return Ok(fp);
+ }
+ }
+ }
+
+ // Third, we try to decrypt PKESK packets with wildcard
+ // recipients using those keys that we can use without
+ // prompting for a password.
+ for pkesk in pkesks.iter().filter(|p| p.recipient().is_wildcard()) {
+ for key in self.secret_keys.values() {
+ if ! key.secret().is_encrypted() {
+ if let Some(fp) = key.clone().into_keypair().ok()
+ .and_then(|mut k|
+ self.try_decrypt(pkesk, sym_algo, &mut k, &mut decrypt))
+ {
+ return Ok(fp);
+ }
+ }
+ }
+ }
+
+ // Fourth, we try to decrypt PKESK packets with wildcard
+ // recipients using those keys that are encrypted.
+ for pkesk in pkesks.iter().filter(|p| p.recipient().is_wildcard()) {
+ // Don't ask the user to decrypt a key if we don't support
+ // the algorithm.
+ if ! pkesk.pk_algo().is_supported() {
+ continue;
+ }
+
+ // To appease the borrow checker, iterate over the
+ // hashmap, awkwardly.
+ for keyid in self.secret_keys.keys().cloned().collect::<Vec<_>>()
+ {
+ let mut keypair = loop {
+ let key = self.secret_keys.get_mut(&keyid).unwrap(); // Yuck
+
+ if ! key.secret().is_encrypted() {
+ break key.clone().into_keypair().unwrap();
+ }
+
+ let p = rpassword::read_password_from_tty(Some(
+ &format!(
+ "Enter password to decrypt key {}: ",
+ self.key_hints.get(&keyid).unwrap())))?.into();
+
+ let algo = key.pk_algo();
+ if let Some(()) =
+ key.secret_mut().decrypt_in_place(algo, &p).ok() {
+ break key.clone().into_keypair().unwrap()
+ } else {
+ eprintln!("Bad password.");
+ }
+ };
+
+ if let Some(fp) =
+ self.try_decrypt(pkesk, sym_algo, &mut keypair,
+ &mut decrypt)
+ {
+ return Ok(fp);
+ }
+ }
+ }
+
+ if skesks.is_empty() {
+ return
+ Err(anyhow::anyhow!("No key to decrypt message"));
+ }
+
+ // Finally, try to decrypt using the SKESKs.
+ loop {
+ let password =
+ rpassword::read_password_from_tty(Some(
+ "Enter password to decrypt message: "))?.into();
+
+ for skesk in skesks {
+ if let Some(sk) = skesk.decrypt(&password).ok()
+ .and_then(|(algo, sk)| { if decrypt(algo, &sk) { Some(sk) } else { None }})
+ {
+ if self.dump_session_key {
+ eprintln!("Session key: {}", hex::encode(&sk));
+ }
+ return Ok(None);
+ }
+ }
+
+ eprintln!("Bad password.");
+ }
+ }
+}
+
+pub fn decrypt(ctx: &Context, policy: &dyn Policy, mapping: &mut store::Mapping,
+ input: &mut dyn io::Read, output: &mut dyn io::Write,
+ signatures: usize, certs: Vec<Cert>, secrets: Vec<Cert>,
+ dump_session_key: bool,
+ dump: bool, hex: bool)
+ -> Result<()> {
+ let helper = Helper::new(ctx, policy, mapping, signatures, certs, secrets,
+ dump_session_key, dump || hex);
+ let mut decryptor = DecryptorBuilder::from_reader(input)?
+ .mapping(hex)
+ .with_policy(policy, None, helper)
+ .context("Decryption failed")?;
+
+ io::copy(&mut decryptor, output).context("Decryption failed")?;
+
+ let helper = decryptor.into_helper();
+ if let Some(dumper) = helper.dumper.as_ref() {
+ dumper.flush(&mut io::stderr())?;
+ }
+ helper.vhelper.print_status();
+ return Ok(());
+}
+
+pub fn decrypt_unwrap(ctx: &Context, policy: &dyn Policy,
+ mapping: &mut store::Mapping,
+ input: &mut dyn io::Read, output: &mut dyn io::Write,
+ secrets: Vec<Cert>, dump_session_key: bool)
+ -> Result<()>
+{
+ let mut helper = Helper::new(ctx, policy, mapping, 0, Vec::new(), secrets,
+ dump_session_key, false);
+
+ let mut ppr = PacketParser::from_reader(input)?;
+
+ let mut pkesks: Vec<packet::PKESK> = Vec::new();
+ let mut skesks: Vec<packet::SKESK> = Vec::new();
+ while let PacketParserResult::Some(mut pp) = ppr {
+ let sym_algo_hint = if let Packet::AED(ref aed) = pp.packet {
+ Some(aed.symmetric_algo())
+ } else {
+ None
+ };
+
+ match pp.packet {
+ Packet::SEIP(_) | Packet::AED(_) => {
+ {
+ let decrypt = |algo, secret: &SessionKey| {
+ pp.decrypt(algo, secret).is_ok()
+ };
+ helper.decrypt(&pkesks[..], &skesks[..], sym_algo_hint,
+ decrypt)?;
+ }
+ if pp.encrypted() {
+ return Err(
+ openpgp::Error::MissingSessionKey(
+ "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(())
+}