From c2f802dd59a71f04f6010b25a897e36a017497ef Mon Sep 17 00:00:00 2001 From: "Neal H. Walfield" Date: Wed, 20 Jan 2021 13:55:17 +0100 Subject: sq: Add sq certify. - Add the command 'sq certify' to certify a (User ID, Certificate). --- sq/src/commands/certify.rs | 142 ++++++++++++++++++++++++++++++++++++++ sq/src/commands/mod.rs | 1 + sq/src/sq-usage.rs | 49 +++++++++++++ sq/src/sq.rs | 5 ++ sq/src/sq_cli.rs | 84 +++++++++++++++++++++++ sq/tests/sq-certify.rs | 166 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 447 insertions(+) create mode 100644 sq/src/commands/certify.rs create mode 100644 sq/tests/sq-certify.rs diff --git a/sq/src/commands/certify.rs b/sq/src/commands/certify.rs new file mode 100644 index 00000000..22a0772e --- /dev/null +++ b/sq/src/commands/certify.rs @@ -0,0 +1,142 @@ +use std::time::{SystemTime, Duration}; + +use sequoia_openpgp as openpgp; +use openpgp::Result; +use openpgp::cert::prelude::*; +use openpgp::packet::prelude::*; +use openpgp::parse::Parse; +use openpgp::policy::Policy; +use openpgp::serialize::Serialize; +use openpgp::types::SignatureType; + +use crate::parse_duration; +use crate::SECONDS_IN_YEAR; + +pub fn certify(p: &impl Policy, m: &clap::ArgMatches, _force: bool) + -> Result<()> +{ + let certifier = m.value_of("certifier").unwrap(); + let cert = m.value_of("certificate").unwrap(); + let userid = m.value_of("userid").unwrap(); + + let certifier = Cert::from_file(certifier)?; + let cert = Cert::from_file(cert)?; + let vc = cert.with_policy(p, None)?; + + let trust_depth: u8 = m.value_of("depth") + .map(|s| s.parse()).unwrap_or(Ok(0))?; + let trust_amount: u8 = m.value_of("amount") + .map(|s| s.parse()).unwrap_or(Ok(120))?; + let regex = m.values_of("regex").map(|v| v.collect::>()) + .unwrap_or(vec![]); + if trust_depth == 0 && regex.len() > 0 { + return Err( + anyhow::format_err!("A regex only makes sense \ + if the trust depth is greater than 0")); + } + + let local = m.is_present("local"); + let non_revocable = m.is_present("non-revocable"); + let expires = m.value_of("expires"); + let expires_in = m.value_of("expires-in"); + + + // Find the matching User ID. + let mut u = None; + for ua in vc.userids() { + if let Ok(a_userid) = std::str::from_utf8(ua.userid().value()) { + if a_userid == userid { + u = Some(ua.userid()); + break; + } + } + } + + let userid = if let Some(userid) = u { + userid + } else { + eprintln!("User ID: '{}' not found.\nValid User IDs:", userid); + let mut have_valid = false; + for ua in vc.userids() { + if let Ok(u) = std::str::from_utf8(ua.userid().value()) { + have_valid = true; + eprintln!(" - {}", u); + } + } + if ! have_valid { + eprintln!(" - Certificate has no valid User IDs."); + } + return Err(anyhow::format_err!("No matching User ID found")); + }; + + // Create the certification. + let mut builder + = SignatureBuilder::new(SignatureType::GenericCertification); + + if trust_depth != 0 || trust_amount != 120 { + builder = builder.set_trust_signature(trust_depth, trust_amount)?; + } + + for regex in regex { + builder = builder.add_regular_expression(regex)?; + } + + if local { + builder = builder.set_exportable_certification(false)?; + } + + if non_revocable { + builder = builder.set_revocable(false)?; + } + + match (expires, expires_in) { + (None, None) => + // Default expiration. + builder = builder.set_signature_validity_period( + Duration::new(5 * SECONDS_IN_YEAR, 0))?, + (Some(t), None) if t == "never" => + // The default is no expiration; there is nothing to do. + (), + (Some(t), None) => { + let now = builder.signature_creation_time() + .unwrap_or_else(std::time::SystemTime::now); + let expiration = SystemTime::from( + crate::parse_iso8601(t, chrono::NaiveTime::from_hms(0, 0, 0))?); + let validity = expiration.duration_since(now)?; + builder = builder.set_signature_creation_time(now)? + .set_signature_validity_period(validity)?; + }, + (None, Some(d)) if d == "never" => + // The default is no expiration; there is nothing to do. + (), + (None, Some(d)) => { + let d = parse_duration(d)?; + builder = builder.set_signature_validity_period(d)?; + }, + (Some(_), Some(_)) => unreachable!("conflicting args"), + } + + + // Sign it. + let mut signer = certifier.primary_key().key().clone() + .parts_into_secret()?.into_keypair()?; + + let certification = builder + .sign_userid_binding( + &mut signer, + cert.primary_key().component(), + userid)?; + let cert = cert.insert_packets(certification.clone())?; + assert!(cert.clone().into_packets().any(|p| { + match p { + Packet::Signature(sig) => sig == certification, + _ => false, + } + })); + + + // And export it. + cert.armored().serialize(&mut std::io::stdout())?; + + Ok(()) +} diff --git a/sq/src/commands/mod.rs b/sq/src/commands/mod.rs index 83a4f398..45c1201c 100644 --- a/sq/src/commands/mod.rs +++ b/sq/src/commands/mod.rs @@ -44,6 +44,7 @@ pub use self::merge_signatures::merge_signatures; pub mod certring; #[cfg(feature = "net")] pub mod net; +pub mod certify; /// Returns suitable signing keys from a given list of Certs. fn get_signing_keys(certs: &[openpgp::Cert], p: &dyn Policy, diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs index 3c9d7d50..79b82885 100644 --- a/sq/src/sq-usage.rs +++ b/sq/src/sq-usage.rs @@ -35,6 +35,7 @@ //! dearmor Removes ASCII Armor from a file //! inspect Inspects a sequence of OpenPGP packets //! packet OpenPGP Packet manipulation +//! certify Certify a User ID for a Certificate //! help Prints this message or the help of the given //! subcommand(s) //! ``` @@ -791,6 +792,54 @@ //! ARGS: //! Sets the input file to use //! ``` +//! +//! ## Subcommand certify +//! +//! ```text +//! Certify a User ID for a Certificate +//! +//! USAGE: +//! sq certify [FLAGS] [OPTIONS] +//! +//! FLAGS: +//! -h, --help Prints help information +//! -l, --local Makes the certification a local certification. +//! Normally, local certifications are not exported. +//! --non-revocable Marks the certification as being non-revocable. That +//! is, you cannot later revoke this certification. This +//! should normally only be used with an expiration. +//! -V, --version Prints version information +//! +//! OPTIONS: +//! -a, --amount +//! The amount of trust. Values between 1 and 120 are meaningful. 120 +//! means fully trusted. Values less than 120 indicate the degree of +//! trust. 60 is usually used for partially trusted. The default is +//! 120. +//! -d, --depth +//! The trust depth (sometimes referred to as the trust level). 0 means +//! a normal certification of . 1 means +//! CERTIFICATE is also a trusted introducer, 2 means CERTIFICATE is a +//! meta-trusted introducer, etc. The default is 0. +//! --expires