From b71bc3e1c9e0f62fb9cbf3de975597eb4295573f Mon Sep 17 00:00:00 2001 From: Heiko Schaefer Date: Sun, 12 May 2024 23:58:30 +0200 Subject: [PATCH] feat: add APIs to create and verify third-party certifications --- src/packet/signature/config.rs | 21 ++++++-- src/packet/signature/types.rs | 23 ++++++--- src/packet/user_attribute.rs | 25 +++++++-- src/packet/user_id.rs | 93 ++++++++++++++++++++++++++++++++-- src/types/user.rs | 44 +++++++++++++++- 5 files changed, 185 insertions(+), 21 deletions(-) diff --git a/src/packet/signature/config.rs b/src/packet/signature/config.rs index cf27f436..4cab25d3 100644 --- a/src/packet/signature/config.rs +++ b/src/packet/signature/config.rs @@ -71,7 +71,7 @@ impl SignatureConfig { Ok(Signature::from_config(self, signed_hash_value, signature)) } - /// Create a certification signature. + /// Create a certification self-signature. pub fn sign_certification( self, key: &impl SecretKeyTrait, @@ -79,6 +79,21 @@ impl SignatureConfig { tag: Tag, id: &impl Serialize, ) -> Result + where + F: FnOnce() -> String, + { + self.sign_certification_third_party(key, key_pw, key, tag, id) + } + + /// Create a certification third-party signature. + pub fn sign_certification_third_party( + self, + signer: &impl SecretKeyTrait, + signer_pw: F, + signee: &impl PublicKeyTrait, + tag: Tag, + id: &impl Serialize, + ) -> Result where F: FnOnce() -> String, { @@ -90,7 +105,7 @@ impl SignatureConfig { let mut hasher = self.hash_alg.new_hasher()?; - key.to_writer_old(&mut hasher)?; + signee.to_writer_old(&mut hasher)?; let mut packet_buf = Vec::new(); id.to_writer(&mut packet_buf)?; @@ -126,7 +141,7 @@ impl SignatureConfig { let hash = &hasher.finish()[..]; let signed_hash_value = [hash[0], hash[1]]; - let signature = key.create_signature(key_pw, self.hash_alg, hash)?; + let signature = signer.create_signature(signer_pw, self.hash_alg, hash)?; Ok(Signature::from_config(self, signed_hash_value, signature)) } diff --git a/src/packet/signature/types.rs b/src/packet/signature/types.rs index 9c58e50b..177b351f 100644 --- a/src/packet/signature/types.rs +++ b/src/packet/signature/types.rs @@ -139,29 +139,40 @@ impl Signature { key.verify_signature(self.config.hash_alg, hash, &self.signature) } - /// Verifies a certification signature type. + /// Verifies a certification signature type (for self-signatures). pub fn verify_certification( &self, key: &impl PublicKeyTrait, tag: Tag, id: &impl Serialize, ) -> Result<()> { - let key_id = key.key_id(); + self.verify_third_party_certification(&key, &key, tag, id) + } + + /// Verifies a certification signature type (for third-party signatures). + pub fn verify_third_party_certification( + &self, + signee: &impl PublicKeyTrait, + signer: &impl PublicKeyTrait, + tag: Tag, + id: &impl Serialize, + ) -> Result<()> { + let key_id = signee.key_id(); debug!("verifying certification {:?} {:#?}", key_id, self); ensure!( - Self::match_identity(self, key), + Self::match_identity(self, signer), "verify_certification: No matching issuer or issuer_fingerprint for Key ID: {:?}", key_id, ); let mut hasher = self.config.hash_alg.new_hasher()?; - // the key + // the key of the signee { let mut key_buf = Vec::new(); // TODO: this is different for V5 - key.to_writer_old(&mut key_buf)?; + signee.to_writer_old(&mut key_buf)?; hasher.update(&key_buf); } @@ -205,7 +216,7 @@ impl Signature { "certification: invalid signed hash value" ); - key.verify_signature(self.config.hash_alg, hash, &self.signature) + signer.verify_signature(self.config.hash_alg, hash, &self.signature) } /// Verifies a key binding (which binds a subkey to the primary key). diff --git a/src/packet/user_attribute.rs b/src/packet/user_attribute.rs index 2ba2300f..56eee105 100644 --- a/src/packet/user_attribute.rs +++ b/src/packet/user_attribute.rs @@ -12,7 +12,7 @@ use nom::sequence::pair; use crate::errors::{IResult, Result}; use crate::packet::{PacketTrait, Signature, SignatureConfigBuilder, SignatureType, Subpacket}; use crate::ser::Serialize; -use crate::types::{SecretKeyTrait, SignedUserAttribute, Tag, Version}; +use crate::types::{PublicKeyTrait, SecretKeyTrait, SignedUserAttribute, Tag, Version}; use crate::util::{packet_length, write_packet_length}; use super::SubpacketData; @@ -61,23 +61,38 @@ impl UserAttribute { } } + /// Create a self-signature pub fn sign(&self, key: &impl SecretKeyTrait, key_pw: F) -> Result + where + F: FnOnce() -> String, + { + self.sign_third_party(key, key_pw, key) + } + + /// Create a third-party signature + pub fn sign_third_party( + &self, + signer: &impl SecretKeyTrait, + signer_pw: F, + signee: &impl PublicKeyTrait, + ) -> Result where F: FnOnce() -> String, { let config = SignatureConfigBuilder::default() .typ(SignatureType::CertGeneric) - .pub_alg(key.algorithm()) - .hash_alg(key.hash_alg()) + .pub_alg(signer.algorithm()) + .hash_alg(signer.hash_alg()) .hashed_subpackets(vec![Subpacket::regular( SubpacketData::SignatureCreationTime(Utc::now().trunc_subsecs(0)), )]) .unhashed_subpackets(vec![Subpacket::regular(SubpacketData::Issuer( - key.key_id(), + signer.key_id(), ))]) .build()?; - let sig = config.sign_certification(key, key_pw, self.tag(), &self)?; + let sig = + config.sign_certification_third_party(signer, signer_pw, signee, self.tag(), &self)?; Ok(SignedUserAttribute::new(self.clone(), vec![sig])) } diff --git a/src/packet/user_id.rs b/src/packet/user_id.rs index 96f6e0cb..18773beb 100644 --- a/src/packet/user_id.rs +++ b/src/packet/user_id.rs @@ -4,11 +4,12 @@ use bstr::{BStr, BString}; use chrono::{SubsecRound, Utc}; use crate::errors::Result; + use crate::packet::{ PacketTrait, Signature, SignatureConfigBuilder, SignatureType, Subpacket, SubpacketData, }; use crate::ser::Serialize; -use crate::types::{SecretKeyTrait, SignedUser, Tag, Version}; +use crate::types::{PublicKeyTrait, SecretKeyTrait, SignedUser, Tag, Version}; /// User ID Packet /// https://tools.ietf.org/html/rfc4880.html#section-5.11 @@ -38,23 +39,38 @@ impl UserId { self.id.as_ref() } + /// Create a self-signature pub fn sign(&self, key: &impl SecretKeyTrait, key_pw: F) -> Result + where + F: FnOnce() -> String, + { + self.sign_third_party(key, key_pw, key) + } + + /// Create a third-party signature + pub fn sign_third_party( + &self, + signer: &impl SecretKeyTrait, + signer_pw: F, + signee: &impl PublicKeyTrait, + ) -> Result where F: FnOnce() -> String, { let config = SignatureConfigBuilder::default() .typ(SignatureType::CertGeneric) - .pub_alg(key.algorithm()) - .hash_alg(key.hash_alg()) + .pub_alg(signer.algorithm()) + .hash_alg(signer.hash_alg()) .hashed_subpackets(vec![Subpacket::regular( SubpacketData::SignatureCreationTime(Utc::now().trunc_subsecs(0)), )]) .unhashed_subpackets(vec![Subpacket::regular(SubpacketData::Issuer( - key.key_id(), + signer.key_id(), ))]) .build()?; - let sig = config.sign_certification(key, key_pw, self.tag(), &self)?; + let sig = + config.sign_certification_third_party(signer, signer_pw, signee, self.tag(), &self)?; Ok(SignedUser::new(self.clone(), vec![sig])) } @@ -87,3 +103,70 @@ impl PacketTrait for UserId { Tag::UserId } } + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + + use super::*; + use crate::types::{KeyVersion, S2kParams}; + use crate::{packet, KeyType}; + use rand::thread_rng; + + #[test] + fn test_user_id_certification() { + let key_type = KeyType::EdDSA; + + let (public_params, secret_params) = key_type + .generate_with_rng(thread_rng(), None, S2kParams::Unprotected) + .unwrap(); + + let alice_sec = packet::SecretKey { + details: packet::PublicKey { + packet_version: Version::New, + version: KeyVersion::V4, + algorithm: key_type.to_alg(), + created_at: Utc::now().trunc_subsecs(0), + expiration: None, + public_params, + }, + secret_params, + }; + + let alice_pub = alice_sec.public_key(); + + let alice_uid = UserId::from_str(Version::New, ""); + + // test self-signature + let self_signed = alice_uid.sign(&alice_sec, String::default).unwrap(); + self_signed + .verify(&alice_pub) + .expect("self signature verification failed"); + + // test third-party signature + let (public_params, secret_params) = key_type + .generate_with_rng(thread_rng(), None, S2kParams::Unprotected) + .unwrap(); + + let signer_sec = packet::SecretKey { + details: packet::PublicKey { + packet_version: Version::New, + version: KeyVersion::V4, + algorithm: key_type.to_alg(), + created_at: Utc::now().trunc_subsecs(0), + expiration: None, + public_params, + }, + secret_params, + }; + + let signer_pub = signer_sec.public_key(); + + let third_signed = alice_uid + .sign_third_party(&signer_sec, String::default, &alice_pub) + .unwrap(); + third_signed + .verify_third_party(&alice_pub, &signer_pub) + .expect("self signature verification failed"); + } +} diff --git a/src/types/user.rs b/src/types/user.rs index 7aaf772f..1dc7b11b 100644 --- a/src/types/user.rs +++ b/src/types/user.rs @@ -31,7 +31,7 @@ impl SignedUser { SignedUser { id, signatures } } - /// Verify all signatures. If signatures is empty, this fails. + /// Verify all signatures (for self-signatures). If signatures is empty, this fails. pub fn verify(&self, key: &impl PublicKeyTrait) -> Result<()> { debug!("verify signed user {:#?}", self); ensure!(!self.signatures.is_empty(), "no signatures found"); @@ -43,6 +43,22 @@ impl SignedUser { Ok(()) } + /// Verify all signatures (for third-party signatures). If signatures is empty, this fails. + pub fn verify_third_party( + &self, + signee: &impl PublicKeyTrait, + signer: &impl PublicKeyTrait, + ) -> Result<()> { + debug!("verify signed user {:#?} with signer {:#?}", self, signer); + ensure!(!self.signatures.is_empty(), "no signatures found"); + + for signature in &self.signatures { + signature.verify_third_party_certification(signee, signer, Tag::UserId, &self.id)?; + } + + Ok(()) + } + pub fn is_primary(&self) -> bool { self.signatures.iter().any(Signature::is_primary) } @@ -85,7 +101,7 @@ impl SignedUserAttribute { SignedUserAttribute { attr, signatures } } - /// Verify all signatures. If signatures is empty, this fails. + /// Verify all signatures (for self-signatures). If signatures is empty, this fails. pub fn verify(&self, key: &impl PublicKeyTrait) -> Result<()> { debug!("verify signed attribute {:?}", self); ensure!(!self.signatures.is_empty(), "no signatures found"); @@ -96,6 +112,30 @@ impl SignedUserAttribute { Ok(()) } + + /// Verify all signatures (for third-party signatures). If signatures is empty, this fails. + pub fn verify_third_party( + &self, + signee: &impl PublicKeyTrait, + signer: &impl PublicKeyTrait, + ) -> Result<()> { + debug!( + "verify signed attribute {:#?} with signer {:#?}", + self, signer + ); + ensure!(!self.signatures.is_empty(), "no signatures found"); + + for signature in &self.signatures { + signature.verify_third_party_certification( + signee, + signer, + Tag::UserAttribute, + &self.attr, + )?; + } + + Ok(()) + } } impl Serialize for SignedUserAttribute {