Skip to content

Commit

Permalink
feat: add APIs to create and verify third-party certifications
Browse files Browse the repository at this point in the history
  • Loading branch information
hko-s committed May 12, 2024
1 parent 56505aa commit b71bc3e
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 21 deletions.
21 changes: 18 additions & 3 deletions src/packet/signature/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,29 @@ impl SignatureConfig {
Ok(Signature::from_config(self, signed_hash_value, signature))
}

/// Create a certification signature.
/// Create a certification self-signature.
pub fn sign_certification<F>(
self,
key: &impl SecretKeyTrait,
key_pw: F,
tag: Tag,
id: &impl Serialize,
) -> Result<Signature>
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<F>(
self,
signer: &impl SecretKeyTrait,
signer_pw: F,
signee: &impl PublicKeyTrait,
tag: Tag,
id: &impl Serialize,
) -> Result<Signature>
where
F: FnOnce() -> String,
{
Expand All @@ -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)?;
Expand Down Expand Up @@ -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))
}
Expand Down
23 changes: 17 additions & 6 deletions src/packet/signature/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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).
Expand Down
25 changes: 20 additions & 5 deletions src/packet/user_attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -61,23 +61,38 @@ impl UserAttribute {
}
}

/// Create a self-signature
pub fn sign<F>(&self, key: &impl SecretKeyTrait, key_pw: F) -> Result<SignedUserAttribute>
where
F: FnOnce() -> String,
{
self.sign_third_party(key, key_pw, key)
}

/// Create a third-party signature
pub fn sign_third_party<F>(
&self,
signer: &impl SecretKeyTrait,
signer_pw: F,
signee: &impl PublicKeyTrait,
) -> Result<SignedUserAttribute>
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]))
}
Expand Down
93 changes: 88 additions & 5 deletions src/packet/user_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -38,23 +39,38 @@ impl UserId {
self.id.as_ref()
}

/// Create a self-signature
pub fn sign<F>(&self, key: &impl SecretKeyTrait, key_pw: F) -> Result<SignedUser>
where
F: FnOnce() -> String,
{
self.sign_third_party(key, key_pw, key)
}

/// Create a third-party signature
pub fn sign_third_party<F>(
&self,
signer: &impl SecretKeyTrait,
signer_pw: F,
signee: &impl PublicKeyTrait,
) -> Result<SignedUser>
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]))
}
Expand Down Expand Up @@ -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, "<alice@example.org>");

// 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");
}
}
44 changes: 42 additions & 2 deletions src/types/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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)
}
Expand Down Expand Up @@ -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");
Expand All @@ -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 {
Expand Down

0 comments on commit b71bc3e

Please sign in to comment.