diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2020-10-14 11:43:23 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-10-14 12:16:15 +0200 |
commit | 653960de3dfe19c9b46daad38efbc4a49a7676f2 (patch) | |
tree | dd1c0c5963667dd06b717e69a0dd40df0a94cd68 /tool | |
parent | 2079449be080b4da3cdc8c6f9972f4ac48e58aaf (diff) |
sq: Rename module to 'sequoia-sq'.
Diffstat (limited to 'tool')
-rw-r--r-- | tool/Cargo.toml | 62 | ||||
-rw-r--r-- | tool/Makefile | 45 | ||||
-rw-r--r-- | tool/build.rs | 22 | ||||
-rw-r--r-- | tool/make-usage.sh | 54 | ||||
-rw-r--r-- | tool/src/commands/decrypt.rs | 354 | ||||
-rw-r--r-- | tool/src/commands/dump.rs | 946 | ||||
-rw-r--r-- | tool/src/commands/inspect.rs | 412 | ||||
-rw-r--r-- | tool/src/commands/key.rs | 248 | ||||
-rw-r--r-- | tool/src/commands/mod.rs | 515 | ||||
-rw-r--r-- | tool/src/commands/sign.rs | 347 | ||||
-rw-r--r-- | tool/src/sq-usage.rs | 768 | ||||
-rw-r--r-- | tool/src/sq.rs | 739 | ||||
-rw-r--r-- | tool/src/sq_cli.rs | 601 | ||||
-rw-r--r-- | tool/tests/sq-sign.rs | 813 |
14 files changed, 0 insertions, 5926 deletions
diff --git a/tool/Cargo.toml b/tool/Cargo.toml deleted file mode 100644 index ada5b3fe..00000000 --- a/tool/Cargo.toml +++ /dev/null @@ -1,62 +0,0 @@ -[package] -name = "sequoia-tool" -description = "Command-line frontends for Sequoia" -version = "0.19.0" -authors = [ - "Justus Winter <justus@sequoia-pgp.org>", - "Kai Michaelis <kai@sequoia-pgp.org>", - "Neal H. Walfield <neal@sequoia-pgp.org>", -] -build = "build.rs" -documentation = "https://docs.sequoia-pgp.org/0.19.0/sq" -homepage = "https://sequoia-pgp.org/" -repository = "https://gitlab.com/sequoia-pgp/sequoia" -readme = "../README.md" -keywords = ["cryptography", "openpgp", "pgp", "encryption", "signing"] -categories = ["cryptography", "authentication", "command-line-utilities"] -license = "GPL-2.0-or-later" -edition = "2018" - -[badges] -gitlab = { repository = "sequoia-pgp/sequoia" } -maintenance = { status = "actively-developed" } - -[dependencies] -buffered-reader = { path = "../buffered-reader", version = "0.18", default-features = false } -sequoia-openpgp = { path = "../openpgp", version = "0.19", default-features = false } -sequoia-autocrypt = { path = "../autocrypt", version = "0.19", default-features = false } -sequoia-core = { path = "../core", version = "0.19", default-features = false } -sequoia-ipc = { path = "../ipc", version = "0.19", default-features = false } -sequoia-net = { path = "../net", version = "0.19", default-features = false } -sequoia-store = { path = "../store", version = "0.19", default-features = false } -anyhow = "1" -chrono = "0.4" -clap = "2.32.0" -itertools = "0.9" -prettytable-rs = "0.8.0" -tempfile = "3.0.4" -crossterm = "0.13" -tokio-core = "0.1" -rpassword = "5.0" - -[build-dependencies] -clap = "2.32.0" - -[dev-dependencies] -assert_cli = "0.6" - -[[bin]] -name = "sq" -path = "src/sq-usage.rs" - -[features] -default = [ - "buffered-reader/compression", - "sequoia-openpgp/default", - "sequoia-store/background-services" -] -crypto-nettle = ["sequoia-openpgp/crypto-nettle"] -crypto-cng = ["sequoia-openpgp/crypto-cng"] -compression = ["buffered-reader/compression", "sequoia-openpgp/compression"] -compression-deflate = ["buffered-reader/compression-deflate", "sequoia-openpgp/compression-deflate"] -compression-bzip2 = ["buffered-reader/compression-bzip2", "sequoia-openpgp/compression-bzip2"] diff --git a/tool/Makefile b/tool/Makefile deleted file mode 100644 index 44428c96..00000000 --- a/tool/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -# Configuration. -CARGO_TARGET_DIR ?= $(shell pwd)/../target -# We currently only support absolute paths. -CARGO_TARGET_DIR := $(abspath $(CARGO_TARGET_DIR)) -SQ ?= $(CARGO_TARGET_DIR)/debug/sq - -# Tools. -CARGO ?= cargo - -ifneq ($(filter Darwin BSD,$(shell uname -s)),"") - INSTALL ?= ginstall -else - INSTALL ?= install -endif - -all: src/sq-usage.rs - -# Installation. -.PHONY: build-release -build-release: - CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \ - $(CARGO) build $(CARGO_FLAGS) --release --package sequoia-tool - $(MAKE) -C../store build-release - -.PHONY: install -install: build-release - $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -t $(DESTDIR)$(PREFIX)/bin $(CARGO_TARGET_DIR)/release/sq - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/zsh/site-functions - $(INSTALL) -t $(DESTDIR)$(PREFIX)/share/zsh/site-functions \ - $(CARGO_TARGET_DIR)/_sq - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/bash-completion/completions - $(INSTALL) $(CARGO_TARGET_DIR)/sq.bash \ - $(DESTDIR)$(PREFIX)/share/bash-completion/completions/sq - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/fish/completions - $(INSTALL) -t $(DESTDIR)$(PREFIX)/share/fish/completions \ - $(CARGO_TARGET_DIR)/sq.fish - $(MAKE) -C../store install - -# Maintenance. -.PHONY: update-usage -update-usage: src/sq-usage.rs - -src/sq-usage.rs: make-usage.sh $(SQ) - sh make-usage.sh $(SQ) >$@~ && mv $@~ $@ diff --git a/tool/build.rs b/tool/build.rs deleted file mode 100644 index b7d3147d..00000000 --- a/tool/build.rs +++ /dev/null @@ -1,22 +0,0 @@ -use clap; - -use std::env; -use std::fs; -use clap::Shell; - -mod sq_cli { - include!("src/sq_cli.rs"); -} - -fn main() { - let outdir = match env::var_os("CARGO_TARGET_DIR") { - None => return, - Some(outdir) => outdir, - }; - fs::create_dir_all(&outdir).unwrap(); - let mut sq = sq_cli::build(); - for shell in &[Shell::Bash, Shell::Fish, Shell::Zsh, Shell::PowerShell, - Shell::Elvish] { - sq.gen_completions("sq", *shell, &outdir); - } -} diff --git a/tool/make-usage.sh b/tool/make-usage.sh deleted file mode 100644 index 7bcbfcf2..00000000 --- a/tool/make-usage.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh - -tool=$1 - -quote() { - sed 's@^@//! @' | sed 's/ $//' -} - -begin_code() { - printf '```text\n' -} - -end_code() { - printf '```\n' -} - -dump_help() { # subcommand, indentation - if [ -z "$1" ] - then - printf "\n# Usage\n\n" - set "" "#" - else - printf "\n$2 Subcommand$1\n\n" - fi - - help="`$tool $1 --help`" - - begin_code - printf "$help\n" | tail -n +2 - end_code - - if echo $help | fgrep -q SUBCOMMANDS - then - printf "$help\n" | - sed -n '/^SUBCOMMANDS:/,$p' | - tail -n+2 | - grep '^ [^ ]' | - while read subcommand desc - do - if [ "$subcommand" = help ]; then - continue - fi - - dump_help "$1 $subcommand" "#$2" - done - fi -} - -( - printf "A command-line frontend for Sequoia.\n" - dump_help -) | quote - -printf '\ninclude!("'"$(basename $tool)"'.rs");\n' diff --git a/tool/src/commands/decrypt.rs b/tool/src/commands/decrypt.rs deleted file mode 100644 index 07adabe6..00000000 --- a/tool/src/commands/decrypt.rs +++ /dev/null @@ -1,354 +0,0 @@ -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(()) -} diff --git a/tool/src/commands/dump.rs b/tool/src/commands/dump.rs deleted file mode 100644 index 6f35f56d..00000000 --- a/tool/src/commands/dump.rs +++ /dev/null @@ -1,946 +0,0 @@ -use std::io::{self, Read}; - -use sequoia_openpgp as openpgp; -use self::openpgp::types::{Duration, Timestamp, SymmetricAlgorithm}; -use self::openpgp::fmt::hex; -use self::openpgp::crypto::mpi; -use self::openpgp::{Packet, Result}; -use self::openpgp::packet::prelude::*; -use self::openpgp::packet::header::CTB; -use self::openpgp::packet::{Header, header::BodyLength, Signature}; -use self::openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue}; -use self::openpgp::crypto::{SessionKey, S2K}; -use self::openpgp::parse::{map::Map, Parse, PacketParserResult}; - -#[derive(Debug)] -pub enum Kind { - Message { - encrypted: bool, - }, - Keyring, - Cert, - Unknown, -} - -/// Converts sequoia_openpgp types for rendering. -pub trait Convert<T> { - /// Performs the conversion. - fn convert(self) -> T; -} - -impl Convert<chrono::Duration> for std::time::Duration { - fn convert(self) -> chrono::Duration { - chrono::Duration::seconds(self.as_secs() as i64) - } -} - -impl Convert<chrono::Duration> for Duration { - fn convert(self) -> chrono::Duration { - chrono::Duration::seconds(self.as_secs() as i64) - } -} - -impl Convert<chrono::DateTime<chrono::offset::Utc>> for std::time::SystemTime { - fn convert(self) -> chrono::DateTime<chrono::offset::Utc> { - chrono::DateTime::<chrono::offset::Utc>::from(self) - } -} - -impl Convert<chrono::DateTime<chrono::offset::Utc>> for Timestamp { - fn convert(self) -> chrono::DateTime<chrono::offset::Utc> { - std::time::SystemTime::from(self).convert() - } -} - -pub fn dump<W>(input: &mut dyn io::Read, output: &mut dyn io::Write, - mpis: bool, hex: bool, sk: Option<&SessionKey>, - width: W) - -> Result<Kind> - where W: Into<Option<usize>> -{ - let mut ppr - = self::openpgp::parse::PacketParserBuilder::from_reader(input)? - .map(hex).build()?; - let mut message_encrypted = false; - let width = width.into().unwrap_or(80); - let mut dumper = PacketDumper::new(width, mpis); - - while let PacketParserResult::Some(mut pp) = ppr { - let additional_fields = match pp.packet { - Packet::Literal(_) => { - let mut prefix = vec![0; 40]; - let n = pp.read(&mut prefix)?; - Some(vec![ - format!("Content: {:?}{}", - String::from_utf8_lossy(&prefix[..n]), - if n == prefix.len() { "..." } else { "" }), - ]) - }, - Packet::SEIP(_) if sk.is_none() => { - message_encrypted = true; - Some(vec!["No session key supplied".into()]) - } - Packet::SEIP(_) if sk.is_some() => { - message_encrypted = true; - let sk = sk.as_ref().unwrap(); - let mut decrypted_with = None; - for algo in 1..20 { - let algo = SymmetricAlgorithm::from(algo); - if let Ok(size) = algo.key_size() { - if size != sk.len() { continue; } - } else { - continue; - } - - if let Ok(_) = pp.decrypt(algo, sk) { - decrypted_with = Some(algo); - break; - } - } - let mut fields = Vec::new(); - fields.push(format!("Session key: {}", hex::encode(sk))); - if let Some(algo) = decrypted_with { - fields.push(format!("Symmetric algo: {}", algo)); - fields.push("Decryption successful".into()); - } else { - fields.push("Decryption failed".into()); - } - Some(fields) - }, - Packet::AED(_) if sk.is_none() => { - message_encrypted = true; - Some(vec!["No session key supplied".into()]) - } - Packet::AED(_) if sk.is_some() => { - message_encrypted = true; - let sk = sk.as_ref().unwrap(); - let algo = if let Packet::AED(ref aed) = pp.packet { - aed.symmetric_algo() - } else { - unreachable!() - }; - - let |