From 5759fc05388efaf6af64f7fcf4110a7a89ffefbf Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 12 Jan 2021 10:07:49 +0100 Subject: sq: Add 'key attest-certifications'. --- sq/src/commands/key.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++ sq/src/sq-usage.rs | 25 +++++++++++-- sq/src/sq.rs | 2 ++ sq/src/sq_cli.rs | 20 ++++++++++- 4 files changed, 141 insertions(+), 4 deletions(-) diff --git a/sq/src/commands/key.rs b/sq/src/commands/key.rs index 964f4a66..7da8d897 100644 --- a/sq/src/commands/key.rs +++ b/sq/src/commands/key.rs @@ -452,3 +452,101 @@ pub fn adopt(m: &ArgMatches, p: &dyn Policy) -> Result<()> { Ok(()) } + +pub fn attest_certifications(m: &ArgMatches, _p: &dyn Policy) -> Result<()> { + // XXX: This function has to do some steps manually, because + // Sequoia does not expose this functionality because it has not + // been standardized yet. + use sequoia_openpgp::{ + crypto::hash::{Hash, Digest}, + packet::signature::subpacket::*, + types::HashAlgorithm, + }; + #[allow(non_upper_case_globals)] + const SignatureType__AttestedKey: SignatureType = + SignatureType::Unknown(0x16); + #[allow(non_upper_case_globals)] + const SubpacketTag__AttestedCertifications: SubpacketTag = + SubpacketTag::Unknown(37); + + // Some configuration. + let hash_algo = HashAlgorithm::default(); + let digest_size = hash_algo.context()?.digest_size(); + let reserve_area_space = 256; // For the other subpackets. + let digests_per_sig = ((1usize << 16) - reserve_area_space) / digest_size; + + let key = m.value_of("key").unwrap(); + let key = Cert::from_file(key) + .context(format!("Parsing key {:?}", key))?; + + // First, remove all attestations. + let key = Cert::from_packets( + key.into_packets().filter(|p| match p { + Packet::Signature(s) if s.typ() == SignatureType__AttestedKey => + false, + _ => true, + }))?; + + + // Get a signer. + let mut passwords = Vec::new(); + let pk = key.primary_key().key(); + let mut pk_signer = + decrypt_key( + pk.clone().parts_into_secret()?, + &mut passwords)? + .into_keypair()?; + + // Now, create new attestation signatures. + let mut attestation_signatures = Vec::new(); + for uid in key.userids() { + let mut attestations = Vec::new(); + + if m.is_present("all") { + for certification in uid.certifications() { + let mut h = hash_algo.context()?; + certification.hash_for_confirmation(&mut h); + attestations.push(h.into_digest()?); + } + } + + // Hashes SHOULD be sorted. + attestations.sort(); + + // All attestation signatures we generate for this component + // should have the same creation time. Fix it now. + let t = std::time::SystemTime::now(); + + // Hash the components like in a binding signature. + let mut hash = hash_algo.context()?; + key.primary_key().hash(&mut hash); + uid.hash(&mut hash); + + for digests in attestations.chunks(digests_per_sig) { + let mut body = Vec::with_capacity(digest_size * digests.len()); + digests.iter().for_each(|d| body.extend(d)); + + attestation_signatures.push( + SignatureBuilder::new(SignatureType__AttestedKey) + .set_signature_creation_time(t)? + .modify_hashed_area(|mut a| { + a.add(Subpacket::new( + SubpacketValue::Unknown { + tag: SubpacketTag__AttestedCertifications, + body, + }, + true)?)?; + Ok(a) + })? + .sign_hash(&mut pk_signer, hash.clone())?); + } + } + + // XXX: Do the same for user attributes. + + // Finally, add the new signatures. + let key = key.insert_packets(attestation_signatures)?; + + key.as_tsk().armored().serialize(&mut std::io::stdout())?; + Ok(()) +} diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs index 9519d098..41fbbfd0 100644 --- a/sq/src/sq-usage.rs +++ b/sq/src/sq-usage.rs @@ -622,9 +622,10 @@ //! -V, --version Prints version information //! //! SUBCOMMANDS: -//! adopt Bind keys from one certificate to another. -//! generate Generates a new key -//! help Prints this message or the help of the given subcommand(s) +//! adopt Bind keys from one certificate to another. +//! attest-certifications Attests third-party certifications allowing for their distribution +//! generate Generates a new key +//! help Prints this message or the help of the given subcommand(s) //! ``` //! //! ### Subcommand key adopt @@ -648,6 +649,24 @@ //! The certificate to add keys to. //! ``` //! +//! ### Subcommand key attest-certifications +//! +//! ```text +//! Attests third-party certifications allowing for their distribution +//! +//! USAGE: +//! sq key attest-certifications [FLAGS] +//! +//! FLAGS: +//! --all Attest to all certifications +//! -h, --help Prints help information +//! --none Remove all prior attestations +//! -V, --version Prints version information +//! +//! ARGS: +//! Change attestations on this key. +//! ``` +//! //! ### Subcommand key generate //! //! ```text diff --git a/sq/src/sq.rs b/sq/src/sq.rs index 09f89a88..2f039cdf 100644 --- a/sq/src/sq.rs +++ b/sq/src/sq.rs @@ -694,6 +694,8 @@ fn main() -> Result<()> { ("key", Some(m)) => match m.subcommand() { ("generate", Some(m)) => commands::key::generate(m, force)?, ("adopt", Some(m)) => commands::key::adopt(m, policy)?, + ("attest-certifications", Some(m)) => + commands::key::attest_certifications(m, policy)?, _ => unreachable!(), }, ("wkd", Some(m)) => { diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs index 11496248..25ed86a2 100644 --- a/sq/src/sq_cli.rs +++ b/sq/src/sq_cli.rs @@ -510,8 +510,26 @@ pub fn build() -> App<'static, 'static> { .value_name("CERT") .required(true) .help("The certificate to add keys to.")) - )) + ) + .subcommand( + SubCommand::with_name("attest-certifications") + .about("Attests third-party certifications allowing \ + for their distribution") + .arg(Arg::with_name("none") + .long("none") + .conflicts_with("all") + .help("Remove all prior attestations")) + .arg(Arg::with_name("all") + .long("all") + .conflicts_with("none") + .help("Attest to all certifications")) + .arg(Arg::with_name("key") + .value_name("KEY") + .required(true) + .help("Change attestations on this key.")) + ) + ) .subcommand( SubCommand::with_name("certring") .about("Manipulates certificate rings") -- cgit v1.2.3