diff options
author | Wiktor Kwapisiewicz <wiktor@metacode.biz> | 2021-10-18 10:25:18 +0200 |
---|---|---|
committer | Wiktor Kwapisiewicz <wiktor@metacode.biz> | 2021-11-03 10:09:28 +0100 |
commit | c9626a20eb28428d291ae8b078830bf71633c12c (patch) | |
tree | f24c9a5d3fdbf47f2aa5f3ca40a892cdf5a3ba1e /sq | |
parent | ac08b36fd92a77adaa805f8464ed9556c2af2845 (diff) |
sq: Delegate crypto operations to private key store if set.
- If certificates contain secret keys try to decrypt the in place.
- If certificates contain only public bits and the private key store
has been set try to decrypt it using the sequoia_net::pks functions.
Diffstat (limited to 'sq')
-rw-r--r-- | sq/src/commands/decrypt.rs | 144 | ||||
-rw-r--r-- | sq/src/commands/mod.rs | 21 | ||||
-rw-r--r-- | sq/src/sq.rs | 6 |
3 files changed, 121 insertions, 50 deletions
diff --git a/sq/src/commands/decrypt.rs b/sq/src/commands/decrypt.rs index fc66c8ed..eb40fb98 100644 --- a/sq/src/commands/decrypt.rs +++ b/sq/src/commands/decrypt.rs @@ -2,10 +2,11 @@ use anyhow::Context as _; use std::collections::HashMap; use std::io; +use sequoia_net::pks; use sequoia_openpgp as openpgp; use crate::openpgp::types::SymmetricAlgorithm; use crate::openpgp::fmt::hex; -use crate::openpgp::crypto::{self, SessionKey}; +use crate::openpgp::crypto::{self, SessionKey, Decryptor, Password}; use crate::openpgp::{Fingerprint, Cert, KeyID, Result}; use crate::openpgp::packet; use crate::openpgp::packet::prelude::*; @@ -26,10 +27,70 @@ use crate::{ }, }; +trait PrivateKey { + fn get_unlocked(&self) -> Option<Box<dyn Decryptor>>; + + fn unlock(&mut self, p: &Password) -> Result<Box<dyn Decryptor>>; +} + +struct LocalPrivateKey { + key: Key<key::SecretParts, key::UnspecifiedRole>, +} + +impl LocalPrivateKey { + fn new(key: Key<key::SecretParts, key::UnspecifiedRole>) -> Self { + Self { key } + } +} + +impl PrivateKey for LocalPrivateKey { + fn get_unlocked(&self) -> Option<Box<dyn Decryptor>> { + if self.key.secret().is_encrypted() { + None + } else { + // `into_keypair` fails if the key is encrypted but we + // have already checked for that + let keypair = self.key.clone().into_keypair().unwrap(); + Some(Box::new(keypair)) + } + } + + fn unlock(&mut self, p: &Password) -> Result<Box<dyn Decryptor>> { + let algo = self.key.pk_algo(); + self.key.secret_mut().decrypt_in_place(algo, &p)?; + let keypair = self.key.clone().into_keypair()?; + Ok(Box::new(keypair)) + } +} + +struct RemotePrivateKey { + key: Key<key::PublicParts, key::UnspecifiedRole>, + store: String, +} + +impl RemotePrivateKey { + fn new(key: Key<key::PublicParts, key::UnspecifiedRole>, store: String) -> Self { + Self { + key, + store, + } + } +} + +impl PrivateKey for RemotePrivateKey { + fn get_unlocked(&self) -> Option<Box<dyn Decryptor>> { + // getting already unlocked keys is not implemented right now + None + } + + fn unlock(&mut self, p: &Password) -> Result<Box<dyn Decryptor>> { + Ok(pks::unlock_decryptor(&self.store, self.key.clone(), &p)?) + } +} + struct Helper<'a> { vhelper: VHelper<'a>, - secret_keys: - HashMap<KeyID, Key<key::SecretParts, key::UnspecifiedRole>>, + secret_keys: HashMap<KeyID, Box<dyn PrivateKey>>, key_identities: HashMap<KeyID, Fingerprint>, key_hints: HashMap<KeyID, String>, dump_session_key: bool, @@ -37,12 +98,12 @@ struct Helper<'a> { } impl<'a> Helper<'a> { - fn new(config: &Config<'a>, _private_key_store: Option<&str>, + fn new(config: &Config<'a>, private_key_store: Option<&str>, signatures: usize, certs: Vec<Cert>, secrets: Vec<Cert>, dump_session_key: bool, dump: bool) -> Self { - let mut keys = HashMap::new(); + let mut keys: HashMap<KeyID, Box<dyn PrivateKey>> = HashMap::new(); let mut identities: HashMap<KeyID, Fingerprint> = HashMap::new(); let mut hints: HashMap<KeyID, String> = HashMap::new(); for tsk in secrets { @@ -58,10 +119,18 @@ impl<'a> Helper<'a> { // XXX: Should use the message's creation time that we do not know. .with_policy(&config.policy, None) .for_transport_encryption().for_storage_encryption() - .secret() { let id: KeyID = ka.key().fingerprint().into(); - keys.insert(id.clone(), ka.key().clone()); + let key = ka.key(); + keys.insert(id.clone(), + if let Ok(key) = key.parts_as_secret() { + Box::new(LocalPrivateKey::new(key.clone())) + } else if let Some(store) = private_key_store { + Box::new(RemotePrivateKey::new(key.clone(), store.to_string())) + } else { + panic!("Cert does not contain secret keys and private-key-store option has not been set."); + } + ); identities.insert(id.clone(), tsk.fingerprint()); hints.insert(id, hint.clone()); } @@ -87,13 +156,13 @@ impl<'a> Helper<'a> { /// to decrypt the packet parser using `decrypt`. fn try_decrypt<D>(&self, pkesk: &PKESK, sym_algo: Option<SymmetricAlgorithm>, - keypair: &mut dyn crypto::Decryptor, + mut keypair: Box<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) + match pkesk.decrypt(&mut *keypair, sym_algo) .and_then(|(algo, sk)| { if decrypt(algo, &sk) { Some(sk) } else { None } }) @@ -139,14 +208,12 @@ impl<'a> DecryptionHelper for Helper<'a> { // 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); - } + if let Some(key) = self.secret_keys.get_mut(&keyid) { + if let Some(fp) = key.get_unlocked() + .and_then(|k| + self.try_decrypt(pkesk, sym_algo, k, &mut decrypt)) + { + return Ok(fp); } } } @@ -161,9 +228,9 @@ impl<'a> DecryptionHelper for Helper<'a> { 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 keypair = loop { + if let Some(keypair) = key.get_unlocked() { + break keypair; } let p = rpassword::read_password_from_tty(Some( @@ -171,17 +238,14 @@ impl<'a> DecryptionHelper for Helper<'a> { "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."); + match key.unlock(&p) { + Ok(decryptor) => break decryptor, + Err(error) => eprintln!("Could not unlock key: {:?}", error), } }; if let Some(fp) = - self.try_decrypt(pkesk, sym_algo, &mut keypair, + self.try_decrypt(pkesk, sym_algo, keypair, &mut decrypt) { return Ok(fp); @@ -194,13 +258,11 @@ impl<'a> DecryptionHelper for Helper<'a> { // 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); - } + if let Some(fp) = key.get_unlocked() + .and_then(|k| + self.try_decrypt(pkesk, sym_algo, k, &mut decrypt)) + { + return Ok(fp); } } } @@ -218,11 +280,11 @@ impl<'a> DecryptionHelper for Helper<'a> { // hashmap, awkwardly. for keyid in self.secret_keys.keys().cloned().collect::<Vec<_>>() { - let mut keypair = loop { + let keypair = loop { let key = self.secret_keys.get_mut(&keyid).unwrap(); // Yuck - if ! key.secret().is_encrypted() { - break key.clone().into_keypair().unwrap(); + if let Some(keypair) = key.get_unlocked() { + break keypair; } let p = rpassword::read_password_from_tty(Some( @@ -230,17 +292,15 @@ impl<'a> DecryptionHelper for Helper<'a> { "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() + if let Ok(decryptor) = key.unlock(&p) { + break decryptor; } else { eprintln!("Bad password."); } }; if let Some(fp) = - self.try_decrypt(pkesk, sym_algo, &mut keypair, + self.try_decrypt(pkesk, sym_algo, keypair, &mut decrypt) { return Ok(fp); diff --git a/sq/src/commands/mod.rs b/sq/src/commands/mod.rs index 001e9cea..aff04e3e 100644 --- a/sq/src/commands/mod.rs +++ b/sq/src/commands/mod.rs @@ -5,6 +5,7 @@ use std::fs::File; use std::io::{self, Write}; use std::time::SystemTime; +use sequoia_net::pks; use sequoia_openpgp as openpgp; use crate::openpgp::{ armor, @@ -54,11 +55,11 @@ pub mod certify; /// Returns suitable signing keys from a given list of Certs. #[allow(clippy::never_loop)] fn get_signing_keys(certs: &[openpgp::Cert], p: &dyn Policy, - _private_key_store: Option<&str>, + private_key_store: Option<&str>, timestamp: Option<SystemTime>) - -> Result<Vec<crypto::KeyPair>> + -> Result<Vec<Box<dyn crypto::Signer + Send + Sync>>> { - let mut keys = Vec::new(); + let mut keys: Vec<Box<dyn crypto::Signer + Send + Sync>> = Vec::new(); 'next_cert: for tsk in certs { for key in tsk.keys().with_policy(p, timestamp).alive().revoked(false) .for_signing() @@ -78,9 +79,19 @@ fn get_signing_keys(certs: &[openpgp::Cert], p: &dyn Policy, SecretKeyMaterial::Unencrypted(ref u) => u.clone(), }; - keys.push(crypto::KeyPair::new(key.clone(), unencrypted) - .unwrap()); + keys.push(Box::new(crypto::KeyPair::new(key.clone(), unencrypted) + .unwrap())); break 'next_cert; + } else if let Some(private_key_store) = private_key_store { + let password = rpassword::read_password_from_tty( + Some(&format!("Please enter password to key {}/{}: ", tsk, key))).unwrap().into(); + match pks::unlock_signer(private_key_store, key.clone(), &password) { + Ok(signer) => { + keys.push(signer); + break 'next_cert; + }, + Err(error) => eprintln!("Could not unlock signer: {:?}", error), + } } } diff --git a/sq/src/sq.rs b/sq/src/sq.rs index d49f340a..8affa51f 100644 --- a/sq/src/sq.rs +++ b/sq/src/sq.rs @@ -431,7 +431,7 @@ fn main() -> Result<()> { 1 }; let secrets = m.values_of("secret-key-file") - .map(load_keys) + .map(load_certs) .unwrap_or_else(|| Ok(vec![]))?; let private_key_store = m.value_of("private-key-store"); commands::decrypt(config, private_key_store, @@ -450,7 +450,7 @@ fn main() -> Result<()> { m.is_present("binary"), armor::Kind::Message)?; let additional_secrets = m.values_of("signer-key-file") - .map(load_keys) + .map(load_certs) .unwrap_or_else(|| Ok(vec![]))?; let mode = match m.value_of("mode").expect("has default") { "rest" => KeyFlags::empty() @@ -493,7 +493,7 @@ fn main() -> Result<()> { let notarize = m.is_present("notarize"); let private_key_store = m.value_of("private-key-store"); let secrets = m.values_of("secret-key-file") - .map(load_keys) + .map(load_certs) .unwrap_or_else(|| Ok(vec![]))?; let time = if let Some(time) = m.value_of("time") { Some(parse_iso8601(time, chrono::NaiveTime::from_hms(0, 0, 0)) |