summaryrefslogtreecommitdiffstats
path: root/sqv/src
diff options
context:
space:
mode:
authorNora Widdecke <nora@sequoia-pgp.org>2021-07-08 15:31:44 +0200
committerNora Widdecke <nora@sequoia-pgp.org>2021-07-09 21:54:51 +0200
commitc84b096add1d05afc41cc94b35a9db15bac0afea (patch)
tree3bcf860de8a5ea56ca55a69ca7dbebbfdd378964 /sqv/src
parentc1a03f229e61402e9418ee31609f1b22cbe13e4a (diff)
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.
Diffstat (limited to 'sqv/src')
-rw-r--r--sqv/src/sqv-usage.rs42
-rw-r--r--sqv/src/sqv.rs367
-rw-r--r--sqv/src/sqv_cli.rs50
3 files changed, 0 insertions, 459 deletions
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] <SIG-FILE> <FILE> --keyring <FILE>...
-//!
-//! FLAGS:
-//! -h, --help Prints help information
-//! -V, --version Prints version information
-//! -v, --verbose Be verbose.
-//!
-//! OPTIONS:
-//! --keyring <FILE>... A keyring. Can be given multiple times.
-//! --not-after <TIMESTAMP>
-//! Consider signatures created after TIMESTAMP as invalid. If a date
-//! is given, 23:59:59 is used for the time.
-//! [default: now]
-//! --not-before <TIMESTAMP>
-//! 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 <N>
-//! The number of valid signatures to return success. Default: 1
-//!
-//!
-//! ARGS:
-//! <SIG-FILE> File containing the detached signature.
-//! <FILE> 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<std::time::SystemTime>,
- 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<std::time::SystemTime>,
- 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<Vec<Cert>> {
- 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::<usize>() {
- 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<std::time::SystemTime> =
- 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<DateTime<Utc>>
-{
- // 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.")
-}