From c84b096add1d05afc41cc94b35a9db15bac0afea Mon Sep 17 00:00:00 2001 From: Nora Widdecke Date: Thu, 8 Jul 2021 15:31:44 +0200 Subject: sqv: Move to https://gitlab.com/sequoia-pgp/sequoia-sqv - From this point on, the crate sequoia-sqv will be maintained in its own repository. --- .Makefile | 2 - .gitlab-ci.yml | 6 +- Cargo.lock | 11 - Cargo.toml | 1 - sqv/Cargo.toml | 43 -- sqv/LICENSE.txt | 1 - sqv/Makefile | 34 -- sqv/NEWS | 7 - sqv/README.md | 8 - sqv/build.rs | 101 ----- sqv/src/sqv-usage.rs | 42 -- sqv/src/sqv.rs | 367 ---------------- sqv/src/sqv_cli.rs | 50 --- sqv/tests/bad-subkey.rs | 16 - sqv/tests/data/bad-subkey-keyring.pgp | Bin 18064 -> 0 bytes sqv/tests/data/bad-subkey-keyring.txt | 12 - sqv/tests/data/bad-subkey.txt | 1 - sqv/tests/data/bad-subkey.txt.sig | Bin 310 -> 0 bytes sqv/tests/data/msg.txt | 1 - sqv/tests/data/no-signing-caps.key | Bin 1718 -> 0 bytes sqv/tests/data/no-signing-caps.sig | Bin 438 -> 0 bytes sqv/tests/data/revoked-key-cert-not-revoked.pgp | Bin 985 -> 0 bytes .../data/revoked-key-cert-revoked-compromised.pgp | Bin 1140 -> 0 bytes .../revoked-key-cert-revoked-compromised.sk.pgp | Bin 1140 -> 0 bytes .../data/revoked-key-cert-revoked-key_retired.pgp | Bin 1140 -> 0 bytes .../revoked-key-cert-revoked-key_retired.sk.pgp | Bin 1140 -> 0 bytes .../data/revoked-key-cert-revoked-no_subpacket.pgp | Bin 1104 -> 0 bytes .../revoked-key-cert-revoked-no_subpacket.sk.pgp | Bin 1104 -> 0 bytes .../data/revoked-key-cert-revoked-private.pgp | Bin 1149 -> 0 bytes .../data/revoked-key-cert-revoked-private.sk.pgp | Bin 1149 -> 0 bytes .../data/revoked-key-cert-revoked-superseded.pgp | Bin 1124 -> 0 bytes .../revoked-key-cert-revoked-superseded.sk.pgp | Bin 1124 -> 0 bytes .../data/revoked-key-cert-revoked-uid_retired.pgp | Bin 1145 -> 0 bytes .../revoked-key-cert-revoked-uid_retired.sk.pgp | Bin 1145 -> 0 bytes .../data/revoked-key-cert-revoked-unknown.pgp | Bin 1136 -> 0 bytes .../data/revoked-key-cert-revoked-unknown.sk.pgp | Bin 1136 -> 0 bytes .../data/revoked-key-cert-revoked-unspecified.pgp | Bin 1126 -> 0 bytes .../revoked-key-cert-revoked-unspecified.sk.pgp | Bin 1126 -> 0 bytes sqv/tests/data/revoked-key-sig-t0.pgp | Bin 119 -> 0 bytes sqv/tests/data/revoked-key-sig-t0.sk.pgp | Bin 119 -> 0 bytes sqv/tests/data/revoked-key-sig-t1-t2.pgp | Bin 119 -> 0 bytes sqv/tests/data/revoked-key-sig-t1-t2.sk.pgp | Bin 119 -> 0 bytes sqv/tests/data/revoked-key-sig-t2-t3.pgp | Bin 119 -> 0 bytes sqv/tests/data/revoked-key-sig-t2-t3.sk.pgp | Bin 119 -> 0 bytes sqv/tests/data/revoked-key-sig-t3-now.pgp | Bin 119 -> 0 bytes sqv/tests/data/revoked-key-sig-t3-now.sk.pgp | Bin 119 -> 0 bytes sqv/tests/duplicate-signatures.rs | 30 -- sqv/tests/multiple-signatures.rs | 23 - sqv/tests/not-before-after.rs | 60 --- sqv/tests/revoked-key.rs | 488 --------------------- sqv/tests/wrong-key-flags.rs | 66 --- 51 files changed, 2 insertions(+), 1368 deletions(-) delete mode 100644 sqv/Cargo.toml delete mode 120000 sqv/LICENSE.txt delete mode 100644 sqv/Makefile delete mode 100644 sqv/NEWS delete mode 100644 sqv/README.md delete mode 100644 sqv/build.rs delete mode 100644 sqv/src/sqv-usage.rs delete mode 100644 sqv/src/sqv.rs delete mode 100644 sqv/src/sqv_cli.rs delete mode 100644 sqv/tests/bad-subkey.rs delete mode 100644 sqv/tests/data/bad-subkey-keyring.pgp delete mode 100644 sqv/tests/data/bad-subkey-keyring.txt delete mode 100644 sqv/tests/data/bad-subkey.txt delete mode 100644 sqv/tests/data/bad-subkey.txt.sig delete mode 100644 sqv/tests/data/msg.txt delete mode 100644 sqv/tests/data/no-signing-caps.key delete mode 100644 sqv/tests/data/no-signing-caps.sig delete mode 100644 sqv/tests/data/revoked-key-cert-not-revoked.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-compromised.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-compromised.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-key_retired.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-key_retired.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-no_subpacket.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-no_subpacket.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-private.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-private.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-superseded.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-superseded.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-uid_retired.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-uid_retired.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-unknown.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-unknown.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-unspecified.pgp delete mode 100644 sqv/tests/data/revoked-key-cert-revoked-unspecified.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-sig-t0.pgp delete mode 100644 sqv/tests/data/revoked-key-sig-t0.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-sig-t1-t2.pgp delete mode 100644 sqv/tests/data/revoked-key-sig-t1-t2.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-sig-t2-t3.pgp delete mode 100644 sqv/tests/data/revoked-key-sig-t2-t3.sk.pgp delete mode 100644 sqv/tests/data/revoked-key-sig-t3-now.pgp delete mode 100644 sqv/tests/data/revoked-key-sig-t3-now.sk.pgp delete mode 100644 sqv/tests/duplicate-signatures.rs delete mode 100644 sqv/tests/multiple-signatures.rs delete mode 100644 sqv/tests/not-before-after.rs delete mode 100644 sqv/tests/revoked-key.rs delete mode 100644 sqv/tests/wrong-key-flags.rs diff --git a/.Makefile b/.Makefile index f3a6a654..7e8eb636 100644 --- a/.Makefile +++ b/.Makefile @@ -106,7 +106,6 @@ build-release: $(MAKE) -Copenpgp-ffi build-release $(MAKE) -Cffi build-release $(MAKE) -Csq build-release - $(MAKE) -Csqv build-release # "install" needs "build-release" as it builds the project # with optimizations enabled. @@ -115,7 +114,6 @@ install: build-release $(MAKE) -Copenpgp-ffi install $(MAKE) -Cffi install $(MAKE) -Csq install - $(MAKE) -Csqv install # Infrastructure for creating source distributions. .PHONY: dist diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2adabc14..e8d7e3b7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,7 +26,6 @@ build-bullseye: - $MAKE_TOP - if ! git diff --quiet Cargo.lock ; then echo "Cargo.lock changed. Please add the change to the corresponding commit." ; false ; fi - if ! git diff --quiet sq ; then echo "Please commit the changes to sq/src/sq-usage.rs." ; false ; fi - - if ! git diff --quiet sqv ; then echo "Please commit the changes to sqv/src/sqv-usage.rs." ; false ; fi - if ! git diff --quiet ; then echo "The build changed the source. Please investigate." ; git diff ; fi variables: CARGO_TARGET_DIR: /target @@ -50,7 +49,6 @@ build-bullseye:arm64: - $MAKE_TOP - if ! git diff --quiet Cargo.lock ; then echo "Cargo.lock changed. Please add the change to the corresponding commit." ; false ; fi - if ! git diff --quiet sq ; then echo "Please commit the changes to sq/src/sq-usage.rs." ; false ; fi - - if ! git diff --quiet sqv ; then echo "Please commit the changes to sqv/src/sqv-usage.rs." ; false ; fi - if ! git diff --quiet ; then echo "The build changed the source. Please investigate." ; git diff ; fi variables: CARGO_TARGET_DIR: /target @@ -198,7 +196,7 @@ rust-stable: - cargo --version - clang --version script: - - CARGO_PACKAGES="-p buffered-reader -p sequoia-openpgp -p sequoia-sqv" $MAKE_TOP test + - CARGO_PACKAGES="-p buffered-reader -p sequoia-openpgp" $MAKE_TOP test variables: CARGO_TARGET_DIR: /target CARGO_HOME: /cargo @@ -222,7 +220,7 @@ rust-stable:arm64: - cargo --version - clang --version script: - - CARGO_PACKAGES="-p buffered-reader -p sequoia-openpgp -p sequoia-sqv" $MAKE_TOP test + - CARGO_PACKAGES="-p buffered-reader -p sequoia-openpgp" $MAKE_TOP test variables: CARGO_TARGET_DIR: /target CARGO_HOME: /cargo diff --git a/Cargo.lock b/Cargo.lock index a6aab920..0e4a5580 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2204,17 +2204,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "sequoia-sqv" -version = "1.0.0" -dependencies = [ - "anyhow", - "assert_cli", - "chrono", - "clap", - "sequoia-openpgp", -] - [[package]] name = "sequoia-store" version = "0.22.0" diff --git a/Cargo.toml b/Cargo.toml index a391ad61..a4f48520 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ members = [ "openpgp", "openpgp-ffi", "sq", - "sqv", "store", ] diff --git a/sqv/Cargo.toml b/sqv/Cargo.toml deleted file mode 100644 index 79663083..00000000 --- a/sqv/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "sequoia-sqv" -description = "A simple OpenPGP signature verification program" -version = "1.0.0" -authors = [ - "Justus Winter ", - "Kai Michaelis ", - "Neal H. Walfield ", -] -documentation = "https://docs.sequoia-pgp.org/0.21.0/sqv" -homepage = "https://sequoia-pgp.org/" -repository = "https://gitlab.com/sequoia-pgp/sequoia" -readme = "README.md" -keywords = ["cryptography", "openpgp", "pgp", "signature", "verification"] -categories = ["cryptography", "command-line-utilities"] -license = "GPL-2.0-or-later" -edition = "2018" - -[badges] -gitlab = { repository = "sequoia-pgp/sequoia" } -maintenance = { status = "actively-developed" } - -[dependencies] -sequoia-openpgp = { path = "../openpgp", version = "1.0.0", default-features = false } -anyhow = "1.0.18" -chrono = "0.4.10" -clap = { version = "2.33", features = ["wrap_help"] } - -[build-dependencies] -clap = "2.33" - -[dev-dependencies] -assert_cli = "0.6" - -[[bin]] -name = "sqv" -path = "src/sqv-usage.rs" -bench = false - -[features] -default = ["crypto-nettle"] -crypto-nettle = ["sequoia-openpgp/crypto-nettle"] -crypto-cng = ["sequoia-openpgp/crypto-cng"] diff --git a/sqv/LICENSE.txt b/sqv/LICENSE.txt deleted file mode 120000 index 4ab43736..00000000 --- a/sqv/LICENSE.txt +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.txt \ No newline at end of file diff --git a/sqv/Makefile b/sqv/Makefile deleted file mode 100644 index 1ec2c975..00000000 --- a/sqv/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -# Configuration. -CARGO_TARGET_DIR ?= $(shell pwd)/../target -# We currently only support absolute paths. -CARGO_TARGET_DIR := $(abspath $(CARGO_TARGET_DIR)) -SQV ?= $(CARGO_TARGET_DIR)/debug/sqv - -# Tools. -CARGO ?= cargo - -ifneq ($(filter Darwin %BSD,$(shell uname -s)),) - INSTALL ?= ginstall -else - INSTALL ?= install -endif - -# Installation. -.PHONY: build-release -build-release: - CARGO_TARGET_DIR=$(CARGO_TARGET_DIR) \ - $(CARGO) build $(CARGO_FLAGS) --release --package sequoia-sqv - -.PHONY: install -install: build-release - $(INSTALL) -d $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -t $(DESTDIR)$(PREFIX)/bin $(CARGO_TARGET_DIR)/release/sqv - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/zsh/site-functions - $(INSTALL) -t $(DESTDIR)$(PREFIX)/share/zsh/site-functions \ - $(CARGO_TARGET_DIR)/_sqv - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/bash-completion/completions - $(INSTALL) $(CARGO_TARGET_DIR)/sqv.bash \ - $(DESTDIR)$(PREFIX)/share/bash-completion/completions/sqv - $(INSTALL) -d $(DESTDIR)$(PREFIX)/share/fish/completions - $(INSTALL) -t $(DESTDIR)$(PREFIX)/share/fish/completions \ - $(CARGO_TARGET_DIR)/sqv.fish diff --git a/sqv/NEWS b/sqv/NEWS deleted file mode 100644 index 6169330a..00000000 --- a/sqv/NEWS +++ /dev/null @@ -1,7 +0,0 @@ - -*- org -*- -#+TITLE: sequoia-sqv NEWS – history of user-visible changes -#+STARTUP: content hidestars - -* Changes in 1.0.0 - -This is the initial stable release. diff --git a/sqv/README.md b/sqv/README.md deleted file mode 100644 index 7cfb9c04..00000000 --- a/sqv/README.md +++ /dev/null @@ -1,8 +0,0 @@ -A simple signature verification program. - -`sqv` verifies detached OpenPGP signatures. It is a replacement for -`gpgv`. Unlike `gpgv`, it can take additional constraints on the -signature into account. - -See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=872271 for -the motivation. diff --git a/sqv/build.rs b/sqv/build.rs deleted file mode 100644 index 921e71ff..00000000 --- a/sqv/build.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::env; -use std::fs; -use std::io::{self, Write}; -use clap::Shell; - -mod sqv_cli { - include!("src/sqv_cli.rs"); -} - -fn main() { - println!("cargo:rerun-if-changed=build.rs"); - - // XXX: Revisit once - // https://github.com/rust-lang/rust/issues/44732 is stabilized. - - let mut sqv = sqv_cli::configure(clap::App::new("sqv").set_term_width(80)); - let mut main = fs::File::create("src/sqv-usage.rs").unwrap(); - dump_help(&mut main, - &mut sqv, - vec![], - "#").unwrap(); - - writeln!(main, "\n#![doc(html_favicon_url = \"https://docs.sequoia-pgp.org/favicon.png\")]") - .unwrap(); - writeln!(main, "#![doc(html_logo_url = \"https://docs.sequoia-pgp.org/logo.svg\")]") - .unwrap(); - writeln!(main, "\ninclude!(\"sqv.rs\");").unwrap(); - - let outdir = match env::var_os("CARGO_TARGET_DIR") { - None => return, - Some(outdir) => outdir, - }; - fs::create_dir_all(&outdir).unwrap(); - let mut sqv = sqv_cli::build(); - for shell in &[Shell::Bash, Shell::Fish, Shell::Zsh, Shell::PowerShell, - Shell::Elvish] { - sqv.gen_completions("sqv", *shell, &outdir); - } -} - -fn dump_help(sink: &mut dyn io::Write, - sqv: &mut clap::App, - cmd: Vec, - heading: &str) - -> io::Result<()> -{ - - if cmd.is_empty() { - writeln!(sink, "//! A command-line frontend for Sequoia.")?; - writeln!(sink, "//!")?; - writeln!(sink, "//! # Usage")?; - } else { - writeln!(sink, "//!")?; - writeln!(sink, "//! {} Subcommand {}", heading, cmd.join(" "))?; - } - - writeln!(sink, "//!")?; - - let args = std::iter::once("sqv") - .chain(cmd.iter().map(|s| s.as_str())) - .chain(std::iter::once("--help")) - .collect::>(); - - let help = sqv.get_matches_from_safe_borrow(&args) - .unwrap_err().to_string(); - - writeln!(sink, "//! ```text")?; - for line in help.trim_end().split('\n').skip(1) { - if line.is_empty() { - writeln!(sink, "//!")?; - } else { - writeln!(sink, "//! {}", line.trim_end())?; - } - } - writeln!(sink, "//! ```")?; - - // Recurse. - let mut found_subcommands = false; - for subcmd in help.split('\n').filter_map(move |line| { - if line == "SUBCOMMANDS:" { - found_subcommands = true; - None - } else if found_subcommands { - if line.chars().nth(4).map(|c| ! c.is_ascii_whitespace()) - .unwrap_or(false) - { - line.trim_start().split(' ').next() - } else { - None - } - } else { - None - } - }).filter(|subcmd| *subcmd != "help") { - let mut c = cmd.clone(); - c.push(subcmd.into()); - dump_help(sink, sqv, c, &format!("{}#", heading))?; - } - - Ok(()) -} diff --git a/sqv/src/sqv-usage.rs b/sqv/src/sqv-usage.rs deleted file mode 100644 index 1838d6cd..00000000 --- a/sqv/src/sqv-usage.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! A command-line frontend for Sequoia. -//! -//! # Usage -//! -//! ```text -//! sqv is a command-line OpenPGP signature verification tool. -//! -//! USAGE: -//! sqv [FLAGS] [OPTIONS] --keyring ... -//! -//! FLAGS: -//! -h, --help Prints help information -//! -V, --version Prints version information -//! -v, --verbose Be verbose. -//! -//! OPTIONS: -//! --keyring ... A keyring. Can be given multiple times. -//! --not-after -//! Consider signatures created after TIMESTAMP as invalid. If a date -//! is given, 23:59:59 is used for the time. -//! [default: now] -//! --not-before -//! Consider signatures created before TIMESTAMP as invalid. If a date -//! is given, 00:00:00 is used for the time. -//! [default: no constraint] -//! -n, --signatures -//! The number of valid signatures to return success. Default: 1 -//! -//! -//! ARGS: -//! File containing the detached signature. -//! File to verify. -//! -//! TIMESTAMPs must be given in ISO 8601 format (e.g. '2017-03-04T13:25:35Z', '2017- -//! 03-04T13:25', '20170304T1325+0830', '2017-03-04', '2017031', ...). If no -//! timezone is specified, UTC is assumed. -//! ``` - -#![doc(html_favicon_url = "https://docs.sequoia-pgp.org/favicon.png")] -#![doc(html_logo_url = "https://docs.sequoia-pgp.org/logo.svg")] - -include!("sqv.rs"); diff --git a/sqv/src/sqv.rs b/sqv/src/sqv.rs deleted file mode 100644 index 08beef3c..00000000 --- a/sqv/src/sqv.rs +++ /dev/null @@ -1,367 +0,0 @@ -/// A simple signature verification program. -/// -/// See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=872271 for -/// the motivation. - -use std::process::exit; - -use chrono::{DateTime, offset::Utc}; -use anyhow::Context; - -use sequoia_openpgp as openpgp; - -use crate::openpgp::{ - Cert, - KeyHandle, - Result, - parse::Parse, -}; -use crate::openpgp::parse::stream::{ - DetachedVerifierBuilder, - MessageLayer, - MessageStructure, - VerificationHelper, - GoodChecksum, - VerificationError, -}; -use crate::openpgp::cert::prelude::*; -use crate::openpgp::policy::StandardPolicy as P; - -mod sqv_cli; - -struct VHelper<'a> { - not_before: Option, - not_after: std::time::SystemTime, - - good: usize, - total: usize, - threshold: usize, - - keyrings: clap::OsValues<'a>, -} - -impl<'a> std::fmt::Debug for VHelper<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("VHelper") - .field("not_before", &self.not_before) - .field("not_after", &self.not_after) - .field("good", &self.good) - .field("total", &self.total) - .field("threshold", &self.threshold) - .field("keyrings", &self.keyrings) - .finish() - } -} - -impl<'a> VHelper<'a> { - fn new(threshold: usize, - not_before: Option, - not_after: std::time::SystemTime, - keyrings: clap::OsValues<'a>) -> Self { - VHelper { - not_before: not_before, - not_after: not_after, - good: 0, - total: 0, - threshold: threshold, - keyrings: keyrings, - } - } -} - -impl<'a> VerificationHelper for VHelper<'a> { - fn get_certs(&mut self, ids: &[crate::KeyHandle]) -> Result> { - let mut certs = Vec::with_capacity(ids.len()); - - // Load relevant keys from the keyring. - for filename in self.keyrings.clone() { - for cert in CertParser::from_file(filename) - .with_context(|| format!("Failed to parse keyring {:?}", - filename))? - .unvalidated_cert_filter(|cert, _| { - // We don't skip keys that are valid (not revoked, - // alive, etc.) so that - cert.keys().key_handles(ids.iter()).next().is_some() - }) - { - certs.push(cert.with_context(|| { - format!("Malformed certificate in keyring {:?}", filename) - })?); - } - } - - // Dedup. To avoid cloning the certificates, we don't use - // Vec::dedup. - certs.sort_by(|a, b| a.fingerprint().cmp(&b.fingerprint())); - let count = certs.len(); - let (certs, errs) = certs.into_iter().fold( - (Vec::with_capacity(count), Vec::new()), - |(mut certs, mut errs), a| { - if certs.is_empty() { - certs.push(a); - } else if certs[certs.len() - 1].fingerprint() == a.fingerprint() { - // Merge `a` into the last element. - match certs.pop().expect("non-empty vec").merge_public(a) { - Ok(cert) => certs.push(cert), - Err(err) => errs.push(err), - } - } else { - certs.push(a); - } - - (certs, errs) - }); - - if !errs.is_empty() { - eprintln!("Error merging duplicate keys:"); - for err in errs.iter() { - eprintln!(" {}", err); - } - Err(errs.into_iter().next().expect("non-empty vec")) - } else { - Ok(certs) - } - } - - fn check(&mut self, structure: MessageStructure) -> Result<()> { - use self::VerificationError::*; - - let mut signers = Vec::with_capacity(2); - let mut verification_err = None; - - for layer in structure.into_iter() { - match layer { - MessageLayer::SignatureGroup { results } => - for result in results { - self.total += 1; - match result { - Ok(GoodChecksum { sig, ka, .. }) => { - match (sig.signature_creation_time(), - self.not_before, - self.not_after) - { - (None, _, _) => { - eprintln!("Malformed signature:"); - print_error_chain(&anyhow::anyhow!( - "no signature creation time")); - }, - (Some(t), Some(not_before), not_after) => { - if t < not_before { - eprintln!( - "Signature by {:X} was created before \ - the --not-before date.", - ka.key().fingerprint()); - } else if t > not_after { - eprintln!( - "Signature by {:X} was created after \ - the --not-after date.", - ka.key().fingerprint()); - } else { - signers.push(ka.cert().fingerprint()); - } - } - (Some(t), None, not_after) => { - if t > not_after { - eprintln!( - "Signature by {:X} was created after \ - the --not-after date.", - ka.key().fingerprint()); - } else { - signers.push(ka.cert().fingerprint()); - } - } - }; - } - Err(MalformedSignature { error, .. }) => { - eprintln!("Signature is malformed:"); - print_error_chain(&error); - } - Err(MissingKey { sig, .. }) => { - let issuers = sig.get_issuers(); - eprintln!("Missing key {:X}, which is needed to \ - verify signature.", - issuers.first().unwrap()); - } - Err(UnboundKey { cert, error, .. }) => { - eprintln!("Signing key on {:X} is not bound:", - cert.fingerprint()); - print_error_chain(&error); - } - Err(BadKey { ka, error, .. }) => { - eprintln!("Signing key on {:X} is bad:", - ka.cert().fingerprint()); - print_error_chain(&error); - } - Err(BadSignature { error, .. }) => { - eprintln!("Verifying signature:"); - print_error_chain(&error); - if verification_err.is_none() { - verification_err = Some(error) - } - } - } - } - MessageLayer::Compression { .. } => (), - _ => unreachable!(), - } - } - - // Dedup the keys so that it is not possible to exceed the - // threshold by duplicating signatures or by using the same - // key. - signers.sort(); - signers.dedup(); - - self.good = signers.len(); - for signer in signers { - println!("{:X}", signer); - } - - Ok(()) - } -} - -fn print_error_chain(err: &anyhow::Error) { - eprintln!(" {}", err); - err.chain().skip(1).for_each(|cause| eprintln!(" because: {}", cause)); -} - - -fn main() -> Result<()> { - let p = &P::new(); - - let matches = sqv_cli::build().get_matches(); - - let verbose = matches.is_present("verbose"); - - let good_threshold - = if let Some(good_threshold) = matches.value_of("signatures") { - match good_threshold.parse::() { - Ok(good_threshold) => good_threshold, - Err(err) => { - eprintln!("Value passed to --signatures must be numeric: \ - {} (got: {:?}).", - err, good_threshold); - exit(2); - }, - } - } else { - 1 - }; - if good_threshold < 1 { - eprintln!("Value passed to --signatures must be >= 1 (got: {:?}).", - good_threshold); - exit(2); - } - - let file = matches.value_of_os("file").expect("'file' is required"); - let sig_file = matches.value_of_os("sig-file") - .expect("'sig-file' is required"); - - let not_before: Option = - if let Some(t) = matches.value_of("not-before") { - Some(parse_iso8601(t, chrono::NaiveTime::from_hms(0, 0, 0)) - .context(format!("Bad value passed to --not-before: {:?}", t))? - .into()) - } else { - None - }; - let not_after: std::time::SystemTime = - if let Some(t) = matches.value_of("not-after") { - Some(parse_iso8601(t, chrono::NaiveTime::from_hms(23, 59, 59)) - .context(format!("Bad value passed to --not-after: {:?}", t))? - .into()) - } else { - None - }.unwrap_or_else(std::time::SystemTime::now); - - let keyrings = matches.values_of_os("keyring") - .expect("No keyring specified."); - - let h = VHelper::new(good_threshold, not_before, not_after, keyrings); - - let mut v = - DetachedVerifierBuilder::from_file(sig_file)?.with_policy(p, None, h)?; - v.verify_file(file)?; - - let h = v.into_helper(); - - if verbose { - eprintln!("{} of {} signatures are valid (threshold is: {}).", - h.good, h.total, good_threshold); - } - - exit(if h.good >= good_threshold { 0 } else { 1 }); -} - -/// Parses the given string depicting a ISO 8601 timestamp. -fn parse_iso8601(s: &str, pad_date_with: chrono::NaiveTime) - -> Result> -{ - // If you modify this function this function, synchronize the - // changes with the copy in sqv.rs! - for f in &[ - "%Y-%m-%dT%H:%M:%S%#z", - "%Y-%m-%dT%H:%M:%S", - "%Y-%m-%dT%H:%M%#z", - "%Y-%m-%dT%H:%M", - "%Y-%m-%dT%H%#z", - "%Y-%m-%dT%H", - "%Y%m%dT%H%M%S%#z", - "%Y%m%dT%H%M%S", - "%Y%m%dT%H%M%#z", - "%Y%m%dT%H%M", - "%Y%m%dT%H%#z", - "%Y%m%dT%H", - ] { - if f.ends_with("%#z") { - if let Ok(d) = DateTime::parse_from_str(s, *f) { - return Ok(d.into()); - } - } else { - if let Ok(d) = chrono::NaiveDateTime::parse_from_str(s, *f) { - return Ok(DateTime::from_utc(d, Utc)); - } - } - } - for f in &[ - "%Y-%m-%d", - "%Y-%m", - "%Y-%j", - "%Y%m%d", - "%Y%m", - "%Y%j", - "%Y", - ] { - if let Ok(d) = chrono::NaiveDate::parse_from_str(s, *f) { - return Ok(DateTime::from_utc(d.and_time(pad_date_with), Utc)); - } - } - Err(anyhow::anyhow!("Malformed ISO8601 timestamp: {}", s)) -} - -#[test] -fn test_parse_iso8601() { - let z = chrono::NaiveTime::from_hms(0, 0, 0); - parse_iso8601("2017-03-04T13:25:35Z", z).unwrap(); - parse_iso8601("2017-03-04T13:25:35+08:30", z).unwrap(); - parse_iso8601("2017-03-04T13:25:35", z).unwrap(); - parse_iso8601("2017-03-04T13:25Z", z).unwrap(); - parse_iso8601("2017-03-04T13:25", z).unwrap(); - // parse_iso8601("2017-03-04T13Z", z).unwrap(); // XXX: chrono doesn't like - // parse_iso8601("2017-03-04T13", z).unwrap(); // ditto - parse_iso8601("2017-03-04", z).unwrap(); - // parse_iso8601("2017-03", z).unwrap(); // ditto - parse_iso8601("2017-031", z).unwrap(); - parse_iso8601("20170304T132535Z", z).unwrap(); - parse_iso8601("20170304T132535+0830", z).unwrap(); - parse_iso8601("20170304T132535", z).unwrap(); - parse_iso8601("20170304T1325Z", z).unwrap(); - parse_iso8601("20170304T1325", z).unwrap(); - // parse_iso8601("20170304T13Z", z).unwrap(); // ditto - // parse_iso8601("20170304T13", z).unwrap(); // ditto - parse_iso8601("20170304", z).unwrap(); - // parse_iso8601("201703", z).unwrap(); // ditto - parse_iso8601("2017031", z).unwrap(); - // parse_iso8601("2017", z).unwrap(); // ditto -} diff --git a/sqv/src/sqv_cli.rs b/sqv/src/sqv_cli.rs deleted file mode 100644 index f1022319..00000000 --- a/sqv/src/sqv_cli.rs +++ /dev/null @@ -1,50 +0,0 @@ -/// Command-line parser for sqv. - -use clap::{App, Arg, AppSettings}; - -// The argument parser. -pub fn build() -> App<'static, 'static> { - configure(App::new("sqv")) -} - -pub fn configure(app: App<'static, 'static>) -> App<'static, 'static> { - app - .version(env!("CARGO_PKG_VERSION")) - .about("sqv is a command-line OpenPGP signature verification tool.") - .setting(AppSettings::ArgRequiredElseHelp) - .arg(Arg::with_name("keyring").value_name("FILE") - .help("A keyring. Can be given multiple times.") - .long("keyring") - .required(true) - .number_of_values(1) - .multiple(true)) - .arg(Arg::with_name("signatures").value_name("N") - .help("The number of valid signatures to return success. Default: 1") - .long("signatures") - .short("n")) - .arg(Arg::with_name("not-before").value_name("TIMESTAMP") - .help("Consider signatures created before TIMESTAMP as invalid. \ - If a date is given, 00:00:00 is used for the time. \ - \n[default: no constraint]") - .long("not-before")) - .arg(Arg::with_name("not-after").value_name("TIMESTAMP") - .help("Consider signatures created after TIMESTAMP as invalid. \ - If a date is given, 23:59:59 is used for the time. \ - \n[default: now]") - .long("not-after")) - .arg(Arg::with_name("sig-file").value_name("SIG-FILE") - .help("File containing the detached signature.") - .required(true)) - .arg(Arg::with_name("file").value_name("FILE") - .help("File to verify.") - .required(true)) - .arg(Arg::with_name("verbose") - .help("Be verbose.") - .long("verbose") - .short("v")) - .after_help( - "TIMESTAMPs must be given in ISO 8601 format \ - (e.g. '2017-03-04T13:25:35Z', '2017-03-04T13:25', \ - '20170304T1325+0830', '2017-03-04', '2017031', ...). \ - If no timezone is specified, UTC is assumed.") -} diff --git a/sqv/tests/bad-subkey.rs b/sqv/tests/bad-subkey.rs deleted file mode 100644 index 7e5a4fbd..00000000 --- a/sqv/tests/bad-subkey.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[cfg(test)] -mod integration { - use std::path; - - use assert_cli::Assert; - - #[test] - fn bad_subkey() { - Assert::cargo_binary("sqv") - .current_dir(path::Path::new("tests").join("data")) - .with_args(&["--keyring", "bad-subkey-keyring.pgp", - "bad-subkey.txt.sig", "bad-subkey.txt"]) - .stdout().is("8F17777118A33DDA9BA48E62AACB3243630052D9") - .unwrap(); - } -} diff --git a/sqv/tests/data/bad-subkey-keyring.pgp b/sqv/tests/data/bad-subkey-keyring.pgp deleted file mode 100644 index 72acf4de..00000000 Binary files a/sqv/tests/data/bad-subkey-keyring.pgp and /dev/null differ diff --git a/sqv/tests/data/bad-subkey-keyring.txt b/sqv/tests/data/bad-subkey-keyring.txt deleted file mode 100644 index d98d157b..00000000 --- a/sqv/tests/data/bad-subkey-keyring.txt +++ /dev/null @@ -1,12 +0,0 @@ -This key keyring contains two keys in the following order: "Justus", -"Neal". - -Justus's key includes all of Neal's subkeys. When Justus's key is -canonicalized, Neal's subkeys should be dropped. - -If an application looks for Neal's signing subkey and either doesn't -validate the keys or only filters on the unvalidated keys, then it -will not find the right key. - -This was fixed in sqv in commit -1d63e71a839bf68f50cb7f4c1942f0d0b1eccfca. diff --git a/sqv/tests/data/bad-subkey.txt b/sqv/tests/data/bad-subkey.txt deleted file mode 100644 index 257cc564..00000000 --- a/sqv/tests/data/bad-subkey.txt +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/sqv/tests/data/bad-subkey.txt.sig b/sqv/tests/data/bad-subkey.txt.sig deleted file mode 100644 index 65fc578c..00000000 Binary files a/sqv/tests/data/bad-subkey.txt.sig and /dev/null differ diff --git a/sqv/tests/data/msg.txt b/sqv/tests/data/msg.txt deleted file mode 100644 index 1856e9be..00000000 --- a/sqv/tests/data/msg.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, World \ No newline at end of file diff --git a/sqv/tests/data/no-signing-caps.key b/sqv/tests/data/no-signing-caps.key deleted file mode 100644 index 7aabd532..00000000 Binary files a/sqv/tests/data/no-signing-caps.key and /dev/null differ diff --git a/sqv/tests/data/no-signing-caps.sig b/sqv/tests/data/no-signing-caps.sig deleted file mode 100644 index c32210d4..00000000 Binary files a/sqv/tests/data/no-signing-caps.sig and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-not-revoked.pgp b/sqv/tests/data/revoked-key-cert-not-revoked.pgp deleted file mode 100644 index d14e954e..00000000 Binary files a/sqv/tests/data/revoked-key-cert-not-revoked.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-compromised.pgp b/sqv/tests/data/revoked-key-cert-revoked-compromised.pgp deleted file mode 100644 index 7d43d847..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-compromised.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-compromised.sk.pgp b/sqv/tests/data/revoked-key-cert-revoked-compromised.sk.pgp deleted file mode 100644 index 8952867a..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-compromised.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-key_retired.pgp b/sqv/tests/data/revoked-key-cert-revoked-key_retired.pgp deleted file mode 100644 index 9478494e..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-key_retired.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-key_retired.sk.pgp b/sqv/tests/data/revoked-key-cert-revoked-key_retired.sk.pgp deleted file mode 100644 index 9ec67628..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-key_retired.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-no_subpacket.pgp b/sqv/tests/data/revoked-key-cert-revoked-no_subpacket.pgp deleted file mode 100644 index 530347b2..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-no_subpacket.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-no_subpacket.sk.pgp b/sqv/tests/data/revoked-key-cert-revoked-no_subpacket.sk.pgp deleted file mode 100644 index d302429f..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-no_subpacket.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-private.pgp b/sqv/tests/data/revoked-key-cert-revoked-private.pgp deleted file mode 100644 index 59f4b5ba..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-private.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-private.sk.pgp b/sqv/tests/data/revoked-key-cert-revoked-private.sk.pgp deleted file mode 100644 index 7a0c04b5..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-private.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-superseded.pgp b/sqv/tests/data/revoked-key-cert-revoked-superseded.pgp deleted file mode 100644 index 61125f28..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-superseded.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-superseded.sk.pgp b/sqv/tests/data/revoked-key-cert-revoked-superseded.sk.pgp deleted file mode 100644 index 891dfe8e..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-superseded.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-uid_retired.pgp b/sqv/tests/data/revoked-key-cert-revoked-uid_retired.pgp deleted file mode 100644 index fea734b9..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-uid_retired.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-uid_retired.sk.pgp b/sqv/tests/data/revoked-key-cert-revoked-uid_retired.sk.pgp deleted file mode 100644 index 3bd4d9fd..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-uid_retired.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-unknown.pgp b/sqv/tests/data/revoked-key-cert-revoked-unknown.pgp deleted file mode 100644 index 83d9e299..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-unknown.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-unknown.sk.pgp b/sqv/tests/data/revoked-key-cert-revoked-unknown.sk.pgp deleted file mode 100644 index a4baef41..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-unknown.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-unspecified.pgp b/sqv/tests/data/revoked-key-cert-revoked-unspecified.pgp deleted file mode 100644 index 0d77cfd1..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-unspecified.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-cert-revoked-unspecified.sk.pgp b/sqv/tests/data/revoked-key-cert-revoked-unspecified.sk.pgp deleted file mode 100644 index c6d7644e..00000000 Binary files a/sqv/tests/data/revoked-key-cert-revoked-unspecified.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-sig-t0.pgp b/sqv/tests/data/revoked-key-sig-t0.pgp deleted file mode 100644 index 1c87b4c5..00000000 Binary files a/sqv/tests/data/revoked-key-sig-t0.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-sig-t0.sk.pgp b/sqv/tests/data/revoked-key-sig-t0.sk.pgp deleted file mode 100644 index 80732cbb..00000000 Binary files a/sqv/tests/data/revoked-key-sig-t0.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-sig-t1-t2.pgp b/sqv/tests/data/revoked-key-sig-t1-t2.pgp deleted file mode 100644 index a1ae9e93..00000000 Binary files a/sqv/tests/data/revoked-key-sig-t1-t2.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-sig-t1-t2.sk.pgp b/sqv/tests/data/revoked-key-sig-t1-t2.sk.pgp deleted file mode 100644 index 9d54de30..00000000 Binary files a/sqv/tests/data/revoked-key-sig-t1-t2.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-sig-t2-t3.pgp b/sqv/tests/data/revoked-key-sig-t2-t3.pgp deleted file mode 100644 index 47970df3..00000000 Binary files a/sqv/tests/data/revoked-key-sig-t2-t3.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-sig-t2-t3.sk.pgp b/sqv/tests/data/revoked-key-sig-t2-t3.sk.pgp deleted file mode 100644 index b8b36ed6..00000000 Binary files a/sqv/tests/data/revoked-key-sig-t2-t3.sk.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-sig-t3-now.pgp b/sqv/tests/data/revoked-key-sig-t3-now.pgp deleted file mode 100644 index 7bb16851..00000000 Binary files a/sqv/tests/data/revoked-key-sig-t3-now.pgp and /dev/null differ diff --git a/sqv/tests/data/revoked-key-sig-t3-now.sk.pgp b/sqv/tests/data/revoked-key-sig-t3-now.sk.pgp deleted file mode 100644 index dad31e65..00000000 Binary files a/sqv/tests/data/revoked-key-sig-t3-now.sk.pgp and /dev/null differ diff --git a/sqv/tests/duplicate-signatures.rs b/sqv/tests/duplicate-signatures.rs deleted file mode 100644 index e48cb3ad..00000000 --- a/sqv/tests/duplicate-signatures.rs +++ /dev/null @@ -1,30 +0,0 @@ - -use assert_cli::Assert; - -fn p(filename: &str) -> String { - format!("../openpgp/tests/data/{}", filename) -} - -/// Asserts that duplicate signatures are properly ignored. -#[test] -fn ignore_duplicates() { - // Duplicate is ignored, but remaining one is ok. - Assert::cargo_binary("sqv") - .with_args( - &["--keyring", - &p("keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp"), - &p("messages/a-cypherpunks-manifesto.txt.ed25519.sig.duplicated"), - &p("messages/a-cypherpunks-manifesto.txt")]) - .unwrap(); - - // Duplicate is ignored, and fails to meet the threshold. - Assert::cargo_binary("sqv") - .with_args( - &["--keyring", - &p("keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp"), - "--signatures=2", - &p("messages/a-cypherpunks-manifesto.txt.ed25519.sig.duplicated"), - &p("messages/a-cypherpunks-manifesto.txt")]) - .fails() - .unwrap(); -} diff --git a/sqv/tests/multiple-signatures.rs b/sqv/tests/multiple-signatures.rs deleted file mode 100644 index 01065e4b..00000000 --- a/sqv/tests/multiple-signatures.rs +++ /dev/null @@ -1,23 +0,0 @@ - -use assert_cli::Assert; - -fn p(filename: &str) -> String { - format!("../openpgp/tests/data/{}", filename) -} - -/// Asserts that multiple signatures from the same Cert are properly -/// ignored. -#[test] -fn ignore_multiple_signatures() { - // Multiple signatures from the same Cert are ignored, and fails to - // meet the threshold. - Assert::cargo_binary("sqv") - .with_args( - &["--keyring", - &p("keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp"), - "--signatures=2", - &p("messages/a-cypherpunks-manifesto.txt.ed25519.sig.duplicated"), - &p("messages/a-cypherpunks-manifesto.txt")]) - .fails() - .unwrap(); -} diff --git a/sqv/tests/not-before-after.rs b/sqv/tests/not-before-after.rs deleted file mode 100644 index 6b2ed805..00000000 --- a/sqv/tests/not-before-after.rs +++ /dev/null @@ -1,60 +0,0 @@ -#[cfg(test)] -mod integration { - use assert_cli::Assert; - - fn p(filename: &str) -> String { - format!("../openpgp/tests/data/{}", filename) - } - - #[test] - fn unconstrained() { - Assert::cargo_binary("sqv") - .with_args( - &["--keyring", - &p("keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp"), - &p("messages/a-cypherpunks-manifesto.txt.ed25519.sig"), - &p("messages/a-cypherpunks-manifesto.txt")]) - .stdout().is("8E8C33FA4626337976D97978069C0C348DD82C19") - .unwrap(); - } - - #[test] - fn in_interval() { - Assert::cargo_binary("sqv") - .with_args( - &["--keyring", - &p("keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp"), - "--not-before", "2018-08-14", - "--not-after", "2018-08-15", - &p("messages/a-cypherpunks-manifesto.txt.ed25519.sig"), - &p("messages/a-cypherpunks-manifesto.txt")]) - .stdout().is("8E8C33FA4626337976D97978069C0C348DD82C19") - .unwrap(); - } - - #[test] - fn before() { - Assert::cargo_binary("sqv") - .with_args( - &["--keyring", - &p("keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp"), - "--not-before", "2018-08-15", - &p("messages/a-cypherpunks-manifesto.txt.ed25519.sig"), - &p("messages/a-cypherpunks-manifesto.txt")]) - .fails() - .unwrap(); - } - - #[test] - fn after() { - Assert::cargo_binary("sqv") - .with_args( - &["--keyring", - &p("keys/emmelie-dorothea-dina-samantha-awina-ed25519.pgp"), - "--not-after", "2018-08-13", - &p("messages/a-cypherpunks-manifesto.txt.ed25519.sig"), - &p("messages/a-cypherpunks-manifesto.txt")]) - .fails() - .unwrap(); - } -} diff --git a/sqv/tests/revoked-key.rs b/sqv/tests/revoked-key.rs deleted file mode 100644 index 86caceae..00000000 --- a/sqv/tests/revoked-key.rs +++ /dev/null @@ -1,488 +0,0 @@ -//! Tests revocations and binding signatures over time. -//! -//! These tests create a certificate with a signing capable primary -//! key (subkey), and revoke it later on, then re-legitimize it using -//! a new signature. We then ask sqv to verify a signature at -//! different points in time. Hard revocations of the key invalidate -//! the signature at any point in time, whereas in the case of soft -//! revocations, the keys can be re-legitimized. -//! -//! All tests are run in three flavors: -//! -//! 0. The primary key makes the signatures and is revoked. -//! 1. The subkey makes the signatures, primary key is revoked. -//! 2. The subkey makes the signatures and is revoked. -//! -//! As extra subtlety, we bind the subkey *after* the t1-t2 signature. -//! -//! Timeline: v -//! | -//! t0 -| - Signature revoked-key-sig-t0.pgp -//! | -//! t1 -| - Primary key creation -//! | -//! | - Subkey creation -//! | -//! | - Signature revoked-key-sig-t1-t2.pgp -//! | -//! | - Subkey is bound -//! | -//! t2 -| - Revocation of (sub)key -//! | -//! | - Signature revoked-key-sig-t2-t3.pgp -//! | -//! t3 -| - New direct/binding signature -//! | -//! | - Signature revoked-key-sig-t3-now.pgp -//! | -//! now -| -//! v - -#[cfg(test)] -mod integration { - use assert_cli::Assert; - use std::path; - - fn sqv(keyring: &str, sig: &str) -> Assert { - Assert::cargo_binary("sqv") - .current_dir(path::Path::new("tests").join("data")) - .with_args( - &["--keyring", - &format!("revoked-key-cert-{}.pgp", keyring), - &format!("revoked-key-sig-{}.pgp", sig), - "msg.txt"]) - } - - /// Tests flavor 0, primary key signs and is revoked. - fn f0(keyring: &str, sig: &str) -> Assert { - sqv(keyring, sig) - } - - /// Tests flavor 1, subkey signs and primary key is revoked. - fn f1(keyring: &str, sig: &str) -> Assert { - sqv(keyring, &format!("{}.sk", sig)) - } - - /// Tests flavor 2, subkey signs and is revoked. - fn f2(keyring: &str, sig: &str) -> Assert { - sqv(&format!("{}.sk", keyring), &format!("{}.sk", sig)) - } - - /// Base case, cert is not revoked. - #[test] - fn not_revoked() { - let c = "not-revoked"; - f0(c, "t0").fails().unwrap(); - f0(c, "t1-t2").unwrap(); - f0(c, "t2-t3").unwrap(); - f0(c, "t3-now").unwrap(); - - f1(c, "t0").fails().unwrap(); - f1(c, "t1-t2").fails().unwrap(); - f1(c, "t2-t3").unwrap(); - f1(c, "t3-now").unwrap(); - - // f2 is not used here, because we don't have any revocations. - } - - /// The hard revocation reasons. All signatures are invalid. - #[test] - fn revoked_no_subpacket() { - let c = "revoked-no_subpacket"; - f0(c, "t0").fails().unwrap(); - f0(c, "t1-t2").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f1(c, "t0").fails().unwrap(); - f1(c, "t1-t2").fails().unwrap(); - f1(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f1(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f2(c, "t0").fails().unwrap(); - f2(c, "t1-t2").fails().unwrap(); - f2(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f2(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - } - - #[test] - fn revoked_unspecified() { - let c = "revoked-unspecified"; - f0(c, "t0").fails().unwrap(); - f0(c, "t1-t2").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f1(c, "t0").fails().unwrap(); - f1(c, "t1-t2").fails().unwrap(); - f1(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f1(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f2(c, "t0").fails().unwrap(); - f2(c, "t1-t2").fails().unwrap(); - f2(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f2(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - } - - #[test] - fn revoked_compromised() { - let c = "revoked-compromised"; - f0(c, "t0").fails().unwrap(); - f0(c, "t1-t2").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f1(c, "t0").fails().unwrap(); - f1(c, "t1-t2").fails().unwrap(); - f1(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f1(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f2(c, "t0").fails().unwrap(); - f2(c, "t1-t2").fails().unwrap(); - f2(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f2(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - } - - #[test] - fn revoked_private() { - let c = "revoked-private"; - f0(c, "t0").fails().unwrap(); - f0(c, "t1-t2").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f1(c, "t0").fails().unwrap(); - f1(c, "t1-t2").fails().unwrap(); - f1(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f1(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f2(c, "t0").fails().unwrap(); - f2(c, "t1-t2").fails().unwrap(); - f2(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f2(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - } - - #[test] - fn revoked_unknown() { - let c = "revoked-unknown"; - f0(c, "t0").fails().unwrap(); - f0(c, "t1-t2").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f1(c, "t0").fails().unwrap(); - f1(c, "t1-t2").fails().unwrap(); - f1(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f1(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - - f2(c, "t0").fails().unwrap(); - f2(c, "t1-t2").fails().unwrap(); - f2(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f2(c, "t3-now").fails().and().stderr().contains("revoked").unwrap(); - } - - /// The soft revocation reasons. Only the signature dated prior - /// to the key creation and the one directly after the revocation - /// are invalid. - #[test] - fn revoked_superseded() { - let c = "revoked-superseded"; - f0(c, "t0").fails().unwrap(); - f0(c, "t1-t2").unwrap(); - f0(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t3-now").unwrap(); - - f1(c, "t0").fails().unwrap(); - f1(c, "t1-t2").fails().unwrap(); - f1(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f1(c, "t3-now").unwrap(); - - f2(c, "t0").fails().unwrap(); - f2(c, "t1-t2").fails().unwrap(); - f2(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f2(c, "t3-now").unwrap(); - } - - #[test] - fn revoked_key_retired() { - let c = "revoked-key_retired"; - f0(c, "t0").fails().unwrap(); - f0(c, "t1-t2").unwrap(); - f0(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t3-now").unwrap(); - - f1(c, "t0").fails().unwrap(); - f1(c, "t1-t2").fails().unwrap(); - f1(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f1(c, "t3-now").unwrap(); - - f2(c, "t0").fails().unwrap(); - f2(c, "t1-t2").fails().unwrap(); - f2(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f2(c, "t3-now").unwrap(); - } - - /// XXX: This is an odd one. - #[test] - fn revoked_uid_retired() { - let c = "revoked-uid_retired"; - f0(c, "t0").fails().unwrap(); - f0(c, "t1-t2").unwrap(); - f0(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f0(c, "t3-now").unwrap(); - - f1(c, "t0").fails().unwrap(); - f1(c, "t1-t2").fails().unwrap(); - f1(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f1(c, "t3-now").unwrap(); - - f2(c, "t0").fails().unwrap(); - f2(c, "t1-t2").fails().unwrap(); - f2(c, "t2-t3").fails().and().stderr().contains("revoked").unwrap(); - f2(c, "t3-now").unwrap(); - } -} - -// Code to create the data for the test cases above -//#[test] -#[allow(dead_code)] -fn create_key() { - use std::fs::File; - use std::convert::TryFrom; - use sequoia_openpgp::{ - Cert, - Packet, - packet::{ - signature, - Key, - key::{ - Key4, - SecretParts, - PrimaryRole, - SubordinateRole, - }, - UserID, - }, - serialize::Serialize, - types::{ - Curve, - Features, - KeyFlags, - SignatureType, - HashAlgorithm, - ReasonForRevocation, - } - }; - use chrono::offset::TimeZone; - - let msg = b"Hello, World"; - let t0 = chrono::offset::Utc.timestamp(1483228800, 0); // 2017-01-01 - let t1 = chrono::offset::Utc.timestamp(1514764800, 0); // 2018-01-01 - let t2 = chrono::offset::Utc.timestamp(1546300800, 0); // 2019-01-01 - let t3 = chrono::offset::Utc.timestamp(1577836800, 0); // 2020-01-01 - let f1: f32 = 0.4; // Chosen by fair dice roll. - let f2: f32 = 0.7; // Likewise. - let t12 = t1 + chrono::Duration::days((300.0 * f1) as i64); - let t_sk_binding = t12 + chrono::Duration::days(1); - let t23 = t2 + chrono::Duration::days((300.0 * f2) as i64); - let compatible = false; - - let make_key = || -> Key { - if compatible { - Key4::generate_rsa(2048).unwrap().into() - } else { - Key4::generate_ecc(true, Curve::Ed25519).unwrap().into() - } - }; - - // Create primary key. - let mut key: Key<_, PrimaryRole> = make_key(); - key.set_creation_time(t1).unwrap(); - let mut signer = key.clone().into_keypair().unwrap(); - - // Create a UserID. - let uid: UserID = "juliet@example.org".into(); - let uid_binding = - signature::SignatureBuilder::new(SignatureType::PositiveCertification) - .set_signature_creation_time(t1).unwrap() - .sign_userid_binding(&mut signer, None, &uid).unwrap(); - - // Create subkey. - let mut subkey: Key<_, SubordinateRole> = make_key().into(); - subkey.set_creation_time(t1 + chrono::Duration::days(1)).unwrap(); - let mut sk_signer = subkey.clone().into_keypair().unwrap(); - - // 1st direct key signature valid from t1 on - let mut b = signature::SignatureBuilder::new(SignatureType::DirectKey) - .set_features(Features::sequoia()).unwrap() - .set_key_flags(KeyFlags::empty() - .set_signing().set_certification()).unwrap() - .set_signature_creation_time(t1).unwrap() - .set_preferred_hash_algorithms(vec![HashAlgorithm::SHA512]) - .unwrap(); - let direct1 = b.sign_direct_key(&mut signer, None).unwrap(); - - // 1st subkey binding signature valid from t_sk_binding on - b = signature::SignatureBuilder::new(SignatureType::SubkeyBinding) - .set_key_flags(KeyFlags::empty().set_signing()).unwrap() - .set_signature_creation_time(t_sk_binding).unwrap() - .set_embedded_signature( - signature::SignatureBuilder::new(SignatureType::PrimaryKeyBinding) - .set_signature_creation_time(t_sk_binding).unwrap() - .sign_primary_key_binding(&mut sk_signer, &key, &subkey).unwrap()) - .unwrap(); - let sk_bind1 = b.sign_subkey_binding(&mut signer, None, &subkey).unwrap(); - - // 2nd direct key signature valid from t3 on - b = signature::SignatureBuilder::new(SignatureType::DirectKey) - .set_features(Features::sequoia()).unwrap() - .set_key_flags(KeyFlags::empty() - .set_signing().set_certification()).unwrap() - .set_signature_creation_time(t3).unwrap() - .set_preferred_hash_algorithms(vec![HashAlgorithm::SHA512]) - .unwrap(); - let direct2 = b.sign_direct_key(&mut signer, None).unwrap(); - - // 2nd subkey binding signature valid from t3 on - let mut b = signature::SignatureBuilder::new(SignatureType::SubkeyBinding) - .set_key_flags(KeyFlags::empty().set_signing()).unwrap() - .set_signature_creation_time(t3).unwrap() - .set_embedded_signature( - signature::SignatureBuilder::new(SignatureType::PrimaryKeyBinding) - .set_signature_creation_time(t3).unwrap() - .sign_primary_key_binding(&mut sk_signer, &key, &subkey).unwrap()) - .unwrap(); - let sk_bind2 = b.sign_subkey_binding(&mut signer, None, &subkey).unwrap(); - - let cert = Cert::try_from(vec![ - key.clone().into(), - direct1.clone().into(), - direct2.clone().into(), - uid.clone().into(), - uid_binding.clone().into(), - subkey.clone().into(), - sk_bind1.clone().into(), - sk_bind2.clone().into(), - ]).unwrap(); - let mut fd = File::create("revoked-key-cert-not-revoked.pgp").unwrap(); - cert.serialize(&mut fd).unwrap(); - - for (slug, reason) in &[ - ("no_subpacket", None), - ("unspecified", Some(ReasonForRevocation::Unspecified)), - ("superseded", Some(ReasonForRevocation::KeySuperseded)), - ("compromised", Some(ReasonForRevocation::KeyCompromised)), - ("key_retired", Some(ReasonForRevocation::KeyRetired)), - ("uid_retired", Some(ReasonForRevocation::UIDRetired)), - ("private", Some(ReasonForRevocation::Private(100))), - ("unknown", Some(ReasonForRevocation::Unknown(200))), - ] { - // Revocation sig valid from t2 on - let mut b = signature::SignatureBuilder::new(SignatureType::KeyRevocation) - .set_signature_creation_time(t2).unwrap(); - - if let Some(r) = reason { - b = b.set_reason_for_revocation(*r, r.to_string().as_bytes()) - .unwrap(); - } - - let rev = b.sign_direct_key(&mut signer, None).unwrap(); - let cert = Cert::try_from(vec![ - key.clone().into(), - direct1.clone().into(), - rev.clone().into(), - direct2.clone().into(), - uid.clone().into(), - uid_binding.clone().into(), - subkey.clone().into(), - sk_bind1.clone().into(), - sk_bind2.clone().into(), - ]).unwrap(); - - let mut fd = - File::create(format!("revoked-key-cert-revoked-{}.pgp", slug)) - .unwrap(); - cert.serialize(&mut fd).unwrap(); - - // Again, this time we revoke the subkey. - let mut b = signature::SignatureBuilder::new(SignatureType::SubkeyRevocation) - .set_signature_creation_time(t2).unwrap(); - - if let Some(r) = reason { - b = b.set_reason_for_revocation(*r, r.to_string().as_bytes()) - .unwrap(); - } - - let rev = b.sign_subkey_binding(&mut signer, None, &subkey).unwrap(); - let cert = Cert::try_from(vec![ - key.clone().into(), - direct1.clone().into(), - direct2.clone().into(), - uid.clone().into(), - uid_binding.clone().into(), - subkey.clone().into(), - sk_bind1.clone().into(), - rev.clone().into(), - sk_bind2.clone().into(), - ]).unwrap(); - - let mut fd = - File::create(format!("revoked-key-cert-revoked-{}.sk.pgp", slug)) - .unwrap(); - cert.serialize(&mut fd).unwrap(); - } - - // 0th message sig before t1 - let sig0 = signature::SignatureBuilder::new(SignatureType::Binary) - .set_signature_creation_time(t0).unwrap() - .sign_message(&mut signer, msg).unwrap(); - let mut fd = File::create("revoked-key-sig-t0.pgp").unwrap(); - Packet::from(sig0).serialize(&mut fd).u