cmail_rpgp/packet/
user_id.rs

1use std::{fmt, io, str};
2
3use aes_gcm::aead::rand_core::CryptoRng;
4use bstr::{BStr, BString};
5use chrono::{SubsecRound, Utc};
6use rand::Rng;
7
8use crate::errors::Result;
9use crate::packet::{
10    PacketTrait, Signature, SignatureConfig, SignatureType, Subpacket, SubpacketData,
11};
12use crate::ser::Serialize;
13use crate::types::{KeyVersion, PublicKeyTrait, SecretKeyTrait, SignedUser, Tag, Version};
14
15/// User ID Packet
16/// <https://www.rfc-editor.org/rfc/rfc9580.html#name-user-id-packet-type-id-13>
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct UserId {
19    packet_version: Version,
20    id: BString,
21}
22
23impl UserId {
24    /// Parses a `UserId` packet from the given slice.
25    pub fn from_slice(packet_version: Version, input: &[u8]) -> Result<Self> {
26        Ok(UserId {
27            packet_version,
28            id: BString::from(input),
29        })
30    }
31
32    pub fn from_str(packet_version: Version, input: &str) -> Self {
33        UserId {
34            packet_version,
35            id: BString::from(input),
36        }
37    }
38
39    pub fn id(&self) -> &BStr {
40        self.id.as_ref()
41    }
42
43    /// Create a self-signature
44    pub fn sign<R, F>(&self, rng: R, key: &impl SecretKeyTrait, key_pw: F) -> Result<SignedUser>
45    where
46        R: CryptoRng + Rng,
47        F: FnOnce() -> String,
48    {
49        self.sign_third_party(rng, key, key_pw, key)
50    }
51
52    /// Create a third-party signature
53    pub fn sign_third_party<R, F>(
54        &self,
55        mut rng: R,
56        signer: &impl SecretKeyTrait,
57        signer_pw: F,
58        signee: &impl PublicKeyTrait,
59    ) -> Result<SignedUser>
60    where
61        R: CryptoRng + Rng,
62        F: FnOnce() -> String,
63    {
64        let hashed_subpackets = vec![Subpacket::regular(SubpacketData::SignatureCreationTime(
65            Utc::now().trunc_subsecs(0),
66        ))];
67        let unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(signer.key_id()))];
68
69        let mut config = match signer.version() {
70            KeyVersion::V4 => SignatureConfig::v4(
71                SignatureType::CertGeneric,
72                signer.algorithm(),
73                signer.hash_alg(),
74            ),
75            KeyVersion::V6 => SignatureConfig::v6(
76                &mut rng,
77                SignatureType::CertGeneric,
78                signer.algorithm(),
79                signer.hash_alg(),
80            )?,
81            v => unsupported_err!("unsupported key version: {:?}", v),
82        };
83
84        config.hashed_subpackets = hashed_subpackets;
85        config.unhashed_subpackets = unhashed_subpackets;
86
87        let sig =
88            config.sign_certification_third_party(signer, signer_pw, signee, self.tag(), &self)?;
89
90        Ok(SignedUser::new(self.clone(), vec![sig]))
91    }
92
93    pub fn into_signed(self, sig: Signature) -> SignedUser {
94        SignedUser::new(self, vec![sig])
95    }
96}
97
98impl Serialize for UserId {
99    fn to_writer<W: io::Write>(&self, writer: &mut W) -> Result<()> {
100        writer.write_all(&self.id)?;
101
102        Ok(())
103    }
104}
105
106impl fmt::Display for UserId {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        write!(f, "User ID: \"{}\"", self.id)
109    }
110}
111
112impl PacketTrait for UserId {
113    fn packet_version(&self) -> Version {
114        self.packet_version
115    }
116
117    fn tag(&self) -> Tag {
118        Tag::UserId
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    #![allow(clippy::unwrap_used)]
125
126    use rand::SeedableRng;
127    use rand_chacha::ChaCha8Rng;
128
129    use super::*;
130    use crate::types::KeyVersion;
131    use crate::{packet, KeyType};
132
133    #[test]
134    fn test_user_id_certification() {
135        let key_type = KeyType::EdDSALegacy;
136        let mut rng = ChaCha8Rng::seed_from_u64(0);
137
138        let (public_params, secret_params) = key_type.generate(&mut rng).unwrap();
139
140        let alice_sec = packet::SecretKey::new(
141            packet::PublicKey::new(
142                Version::New,
143                KeyVersion::V4,
144                key_type.to_alg(),
145                Utc::now().trunc_subsecs(0),
146                None,
147                public_params,
148            )
149            .unwrap(),
150            secret_params,
151        );
152
153        let alice_pub = alice_sec.public_key();
154
155        let alice_uid = UserId::from_str(Version::New, "<alice@example.org>");
156
157        // test self-signature
158        let self_signed = alice_uid
159            .sign(&mut rng, &alice_sec, String::default)
160            .unwrap();
161        self_signed
162            .verify(&alice_pub)
163            .expect("self signature verification failed");
164
165        // test third-party signature
166        let (public_params, secret_params) = key_type.generate(&mut rng).unwrap();
167
168        let signer_sec = packet::SecretKey::new(
169            packet::PublicKey::new(
170                Version::New,
171                KeyVersion::V4,
172                key_type.to_alg(),
173                Utc::now().trunc_subsecs(0),
174                None,
175                public_params,
176            )
177            .unwrap(),
178            secret_params,
179        );
180
181        let signer_pub = signer_sec.public_key();
182
183        let third_signed = alice_uid
184            .sign_third_party(&mut rng, &signer_sec, String::default, &alice_pub)
185            .unwrap();
186        third_signed
187            .verify_third_party(&alice_pub, &signer_pub)
188            .expect("self signature verification failed");
189    }
190}