diff options
Diffstat (limited to 'sq/src/commands/mod.rs')
-rw-r--r-- | sq/src/commands/mod.rs | 515 |
1 files changed, 515 insertions, 0 deletions
diff --git a/sq/src/commands/mod.rs b/sq/src/commands/mod.rs new file mode 100644 index 00000000..6415e2cf --- /dev/null +++ b/sq/src/commands/mod.rs @@ -0,0 +1,515 @@ +use anyhow::Context as _; +use std::cmp::Ordering; +use std::collections::{HashMap, HashSet}; +use std::fs::File; +use std::io::{self, Write}; +use std::time::SystemTime; +use rpassword; + +use sequoia_openpgp as openpgp; +use sequoia_core::Context; +use crate::openpgp::types::{ + CompressionAlgorithm, +}; +use crate::openpgp::cert::prelude::*; +use crate::openpgp::crypto; +use crate::openpgp::{Cert, KeyID, Result}; +use crate::openpgp::packet::prelude::*; +use crate::openpgp::parse::{ + Parse, + PacketParserResult, +}; +use crate::openpgp::parse::stream::*; +use crate::openpgp::serialize::stream::{ + Message, Signer, LiteralWriter, Encryptor, Recipient, + Compressor, + padding::{ + Padder, + padme, + }, +}; +use crate::openpgp::policy::Policy; +use sequoia_store as store; + +pub mod decrypt; +pub use self::decrypt::decrypt; +mod sign; +pub use self::sign::sign; +pub mod dump; +use dump::Convert; +pub use self::dump::dump; +mod inspect; +pub use self::inspect::inspect; +pub mod key; + +/// Returns suitable signing keys from a given list of Certs. +fn get_signing_keys(certs: &[openpgp::Cert], p: &dyn Policy, + timestamp: Option<SystemTime>) + -> Result<Vec<crypto::KeyPair>> +{ + let mut keys = Vec::new(); + 'next_cert: for tsk in certs { + for key in tsk.keys().with_policy(p, timestamp).alive().revoked(false) + .for_signing() + .map(|ka| ka.key()) + { + if let Some(secret) = key.optional_secret() { + let unencrypted = match secret { + SecretKeyMaterial::Encrypted(ref e) => { + let password = rpassword::read_password_from_tty(Some( + &format!("Please enter password to decrypt {}/{}: ", + tsk, key))).unwrap(); + e.decrypt(key.pk_algo(), &password.into()) + .expect("decryption failed") + }, + SecretKeyMaterial::Unencrypted(ref u) => u.clone(), + }; + + keys.push(crypto::KeyPair::new(key.clone(), unencrypted) + .unwrap()); + break 'next_cert; + } + } + + return Err(anyhow::anyhow!( + format!("Found no suitable signing key on {}", tsk))); + } + + Ok(keys) +} + +pub fn encrypt<'a>(policy: &'a dyn Policy, + input: &mut dyn io::Read, message: Message<'a>, + npasswords: usize, recipients: &'a [openpgp::Cert], + signers: Vec<openpgp::Cert>, + mode: openpgp::types::KeyFlags, compression: &str, + time: Option<SystemTime>) + -> Result<()> { + let mut passwords: Vec<crypto::Password> = Vec::with_capacity(npasswords); + for n in 0..npasswords { + let nprompt = format!("Enter password {}: ", n + 1); + passwords.push(rpassword::read_password_from_tty(Some( + if npasswords > 1 { + &nprompt + } else { + "Enter password: " + }))?.into()); + } + + if recipients.len() + passwords.len() == 0 { + return Err(anyhow::anyhow!( + "Neither recipient nor password given")); + } + + let mut signers = get_signing_keys(&signers, policy, time)?; + + // Build a vector of recipients to hand to Encryptor. + let mut recipient_subkeys: Vec<Recipient> = Vec::new(); + for cert in recipients.iter() { + let mut count = 0; + for key in cert.keys().with_policy(policy, None).alive().revoked(false) + .key_flags(&mode).map(|ka| ka.key()) + { + recipient_subkeys.push(key.into()); + count += 1; + } + if count == 0 { + return Err(anyhow::anyhow!( + "Key {} has no suitable encryption key", cert)); + } + } + + // We want to encrypt a literal data packet. + let encryptor = + Encryptor::for_recipients(message, recipient_subkeys) + .add_passwords(passwords); + + let mut sink = encryptor.build() + .context("Failed to create encryptor")?; + + match compression { + "none" => (), + "pad" => sink = Padder::new(sink, padme)?, + "zip" => sink = + Compressor::new(sink).algo(CompressionAlgorithm::Zip).build()?, + "zlib" => sink = + Compressor::new(sink).algo(CompressionAlgorithm::Zlib).build()?, + "bzip2" => sink = + Compressor::new(sink).algo(CompressionAlgorithm::BZip2).build()?, + _ => unreachable!("all possible choices are handled") + } + + // Optionally sign message. + if ! signers.is_empty() { + let mut signer = Signer::new(sink, signers.pop().unwrap()); + for s in signers { + signer = signer.add_signer(s); + if let Some(time) = time { + signer = signer.creation_time(time); + } + } + for r in recipients.iter() { + signer = signer.add_intended_recipient(r); + } + sink = signer.build()?; + } + + let mut literal_writer = LiteralWriter::new(sink).build() + .context("Failed to create literal writer")?; + + // Finally, copy stdin to our writer stack to encrypt the data. + io::copy(input, &mut literal_writer) + .context("Failed to encrypt")?; + + literal_writer.finalize() + .context("Failed to encrypt")?; + + Ok(()) +} + +struct VHelper<'a> { + ctx: &'a Context, + mapping: &'a mut store::Mapping, + signatures: usize, + certs: Option<Vec<Cert>>, + labels: HashMap<KeyID, String>, + trusted: HashSet<KeyID>, + good_signatures: usize, + good_checksums: usize, + unknown_checksums: usize, + bad_signatures: usize, + bad_checksums: usize, + broken_signatures: usize, +} + +impl<'a> VHelper<'a> { + fn new(ctx: &'a Context, mapping: &'a mut store::Mapping, signatures: usize, + certs: Vec<Cert>) + -> Self { + VHelper { + ctx: ctx, + mapping: mapping, + signatures: signatures, + certs: Some(certs), + labels: HashMap::new(), + trusted: HashSet::new(), + good_signatures: 0, + good_checksums: 0, + unknown_checksums: 0, + bad_signatures: 0, + bad_checksums: 0, + broken_signatures: 0, + } + } + + fn print_status(&self) { + fn p(dirty: &mut bool, what: &str, quantity: usize) { + if quantity > 0 { + eprint!("{}{} {}{}", + if *dirty { ", " } else { "" }, + quantity, what, + if quantity == 1 { "" } else { "s" }); + *dirty = true; + } + } + + let mut dirty = false; + p(&mut dirty, "good signature", self.good_signatures); + p(&mut dirty, "good checksum", self.good_checksums); + p(&mut dirty, "unknown checksum", self.unknown_checksums); + p(&mut dirty, "bad signature", self.bad_signatures); + p(&mut dirty, "bad checksum", self.bad_checksums); + p(&mut dirty, "broken signatures", self.broken_signatures); + if dirty { + eprintln!("."); + } + } + + fn print_sigs(&mut self, results: &[VerificationResult]) { + use self::VerificationError::*; + for result in results { + let (issuer, level) = match result { + Ok(GoodChecksum { sig, ka, .. }) => + (ka.key().keyid(), sig.level()), + Err(MalformedSignature { error, .. }) => { + eprintln!("Malformed signature: {}", error); + self.broken_signatures += 1; + continue; + }, + Err(MissingKey { sig, .. }) => { + let issuer = sig.get_issuers().get(0) + .expect("missing key checksum has an issuer") + .to_string(); + let what = match sig.level() { + 0 => "checksum".into(), + n => format!("level {} notarizing checksum", n), + }; + eprintln!("No key to check {} from {}", what, issuer); + self.unknown_checksums += 1; + continue; + }, + Err(UnboundKey { cert, error, .. }) => { + eprintln!("Signing key on {} is not bound: {}", + cert.fingerprint(), error); + self.bad_checksums += 1; + continue; + }, + Err(BadKey { ka, error, .. }) => { + eprintln!("Signing key on {} is bad: {}", + ka.cert().fingerprint(), error); + self.bad_checksums += 1; + continue; + }, + Err(BadSignature { sig, ka, error }) => { + let issuer = ka.fingerprint().to_string(); + let what = match sig.level() { + 0 => "checksum".into(), + n => format!("level {} notarizing checksum", n), + }; + eprintln!("Error verifying {} from {}: {}", + what, issuer, error); + self.bad_checksums += 1; + continue; + } + }; + + let trusted = self.trusted.contains(&issuer); + let what = match (level == 0, trusted) { + (true, true) => "signature".into(), + (false, true) => format!("level {} notarization", level), + (true, false) => "checksum".into(), + (false, false) => + format!("level {} notarizing checksum", level), + }; + + let issuer_str = issuer.to_string(); + let label = self.labels.get(&issuer).unwrap_or(&issuer_str); + eprintln!("Good {} from {}", what, label); + if trusted { + self.good_signatures += 1; + } else { + self.good_checksums += 1; + } + } + } +} + +impl<'a> VerificationHelper for VHelper<'a> { + fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> Result<Vec<Cert>> { + let mut certs = self.certs.take().unwrap(); + // Get all keys. + let seen: HashSet<_> = certs.iter() + .flat_map(|cert| { + cert.keys().map(|ka| ka.key().fingerprint().into()) + }).collect(); + + // Explicitly provided keys are trusted. + self.trusted = seen.clone(); + + // Try to get missing Certs from the mapping. + for id in ids.iter().map(|i| KeyID::from(i)) + .filter(|i| !seen.contains(i)) + { + let _ = + self.mapping.lookup_by_subkeyid(&id) + .and_then(|binding| { + self.labels.insert(id.clone(), binding.label()?); + + // Keys from our mapping are trusted. + self.trusted.insert(id.clone()); + + binding.cert() + }) + .and_then(|cert| { + certs.push(cert); + Ok(()) + }); + } + + // Update seen. + let seen = self.trusted.clone(); + + // Try to get missing Certs from the pool. + for id in ids.iter().map(|i| KeyID::from(i.clone())) + .filter(|i| !seen.contains(i)) + { + let _ = + store::Store::lookup_by_subkeyid(self.ctx, &id) + .and_then(|key| { + // Keys from the pool are NOT trusted. + key.cert() + }) + .and_then(|cert| { + certs.push(cert); + Ok(()) + }); + } + Ok(certs) + } + + fn check(&mut self, structure: MessageStructure) -> Result<()> { + for layer in structure { + match layer { + MessageLayer::Compression { algo } => + eprintln!("Compressed using {}", algo), + MessageLayer::Encryption { sym_algo, aead_algo } => + if let Some(aead_algo) = aead_algo { + eprintln!("Encrypted and protected using {}/{}", + sym_algo, aead_algo); + } else { + eprintln!("Encrypted using {}", sym_algo); + }, + MessageLayer::SignatureGroup { ref results } => + self.print_sigs(results), + } + } + + if self.good_signatures >= self.signatures + && self.bad_signatures + self.bad_checksums == 0 { + Ok(()) + } else { + self.print_status(); + Err(anyhow::anyhow!("Verification failed")) + } + } +} + +pub fn verify(ctx: &Context, policy: &dyn Policy, + mapping: &mut store::Mapping, + input: &mut dyn io::Read, + detached: Option<&mut dyn io::Read>, + output: &mut dyn io::Write, + signatures: usize, certs: Vec<Cert>) + -> Result<()> { + let helper = VHelper::new(ctx, mapping, signatures, certs); + let helper = if let Some(dsig) = detached { + let mut v = DetachedVerifierBuilder::from_reader(dsig)? + .with_policy(policy, None, helper)?; + v.verify_reader(input)?; + v.into_helper() + } else { + let mut v = VerifierBuilder::from_reader(input)? + .with_policy(policy, None, helper)?; + io::copy(&mut v, output)?; + v.into_helper() + }; + + helper.print_status(); + Ok(()) +} + +pub fn split(input: &mut dyn io::Read, prefix: &str) + -> Result<()> { + // We (ab)use the mapping feature to create byte-accurate dumps of + // nested packets. + let mut ppr = + openpgp::parse::PacketParserBuilder::from_reader(input)? + .map(true).build()?; + + // This encodes our position in the tree. + let mut pos = vec![0]; + + while let PacketParserResult::Some(pp) = ppr { + if let Some(ref map) = pp.map() { + let filename = format!( + "{}{}--{}{:?}", prefix, + pos.iter().map(|n| format!("{}", n)) + .collect::<Vec<String>>().join("-"), + pp.packet.kind().map(|_| "").unwrap_or("Unknown-"), + pp.packet.tag()); + let mut sink = File::create(filename) + .context("Failed to create output file")?; + + // Write all the bytes. + for field in map.iter() { + sink.write_all(field.as_bytes())?; + } + } + + let old_depth = Some(pp.recursion_depth()); + ppr = pp.recurse()?.1; + let new_depth = ppr.as_ref().map(|pp| pp.recursion_depth()).ok(); + + // Update pos. + match old_depth.cmp(&new_depth) { + Ordering::Less => + pos.push(0), + Ordering::Equal => + *pos.last_mut().unwrap() += 1, + Ordering::Greater => { + pos.pop(); + }, + } + } + Ok(()) +} + +/// Joins the given files. +pub fn join(inputs: Option<clap::Values>, output: &mut dyn io::Write) + -> Result<()> { + /// Writes a bit-accurate copy of all top-level packets in PPR to + /// OUTPUT. + fn copy(mut ppr: PacketParserResult, output: &mut dyn io::Write) + -> Result<()> { + while let PacketParserResult::Some(pp) = ppr { + // We (ab)use the mapping feature to create byte-accurate + // copies. + for field in pp.map().expect("must be mapped").iter() { + output.write_all(field.as_bytes())?; + } + + ppr = pp.next()?.1; + } + Ok(()) + } + + if let Some(inputs) = inputs { + for name in inputs { + let ppr = + openpgp::parse::PacketParserBuilder::from_file(name)? + .map(true).build()?; + copy(ppr, output)?; + } + } else { + let ppr = + openpgp::parse::PacketParserBuilder::from_reader(io::stdin())? + .map(true).build()?; + copy(ppr, output)?; + } + Ok(()) +} + +pub fn mapping_print_stats(mapping: &store::Mapping, label: &str) -> Result<()> { + fn print_stamps(st: &store::Stamps) -> Result<()> { + println!("{} messages using this key", st.count); + if let Some(t) = st.first { + println!(" First: {}", t.convert()); + } + if let Some(t) = st.last { + println!(" Last: {}", t.convert()); + } + Ok(()) + } + + fn print_stats(st: &store::Stats) -> Result<()> { + if let Some(t) = st.created { + println!(" Created: {}", t.convert()); + } + if let Some(t) = st.updated { + println!(" Updated: {}", t.convert()); + } + print!(" Encrypted "); + print_stamps(&st.encryption)?; + print!(" Verified "); + print_stamps(&st.verification)?; + Ok(()) + } + + let binding = mapping.lookup(label)?; + println!("Binding {:?}", label); + print_stats(&binding.stats().context("Failed to get stats")?)?; + let key = binding.key().context("Failed to get key")?; + println!("Key"); + print_stats(&key.stats().context("Failed to get stats")?)?; + Ok(()) +} |