summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2021-01-12 10:07:49 +0100
committerJustus Winter <justus@sequoia-pgp.org>2021-01-14 09:59:44 +0100
commit5759fc05388efaf6af64f7fcf4110a7a89ffefbf (patch)
treec5d7db2f129e64aad84b5989299078d668181708
parente917d35d115ae83e39e94167bfd8a3fd64ca4781 (diff)
sq: Add 'key attest-certifications'.justus/1pa3pc
-rw-r--r--sq/src/commands/key.rs98
-rw-r--r--sq/src/sq-usage.rs25
-rw-r--r--sq/src/sq.rs2
-rw-r--r--sq/src/sq_cli.rs20
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 @@
//! <CERT> 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] <KEY>
+//!
+//! FLAGS:
+//! --all Attest to all certifications
+//! -h, --help Prints help information
+//! --none Remove all prior attestations
+//! -V, --version Prints version information
+//!
+//! ARGS:
+//! <KEY> 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")