pgp/packet/signature/
ser.rs

1use std::io;
2
3use byteorder::{BigEndian, WriteBytesExt};
4use chrono::Duration;
5use log::debug;
6
7use crate::{
8    errors::{bail, unimplemented_err, unsupported_err, Result},
9    packet::{
10        signature::{types::*, SignatureConfig},
11        SignatureVersionSpecific, Subpacket, SubpacketData, SubpacketType,
12    },
13    ser::Serialize,
14};
15
16impl Serialize for Signature {
17    fn to_writer<W: io::Write>(&self, writer: &mut W) -> Result<()> {
18        writer.write_u8(self.version().into())?;
19
20        match &self.inner {
21            InnerSignature::Known {
22                config,
23                signed_hash_value,
24                signature,
25                ..
26            } => match config.version() {
27                SignatureVersion::V2 | SignatureVersion::V3 => {
28                    config.to_writer_v3(writer)?;
29                    // signed hash value
30                    writer.write_all(signed_hash_value)?;
31                    // the actual cryptographic signature
32                    signature.to_writer(writer)?;
33                }
34                SignatureVersion::V4 | SignatureVersion::V6 => {
35                    config.to_writer_v4_v6(writer)?;
36
37                    // signed hash value
38                    writer.write_all(signed_hash_value)?;
39
40                    // salt, if v6
41                    if let SignatureVersionSpecific::V6 { salt } = &config.version_specific {
42                        debug!("writing salt {} bytes", salt.len());
43                        writer.write_u8(salt.len().try_into()?)?;
44                        writer.write_all(salt)?;
45                    }
46
47                    // the actual cryptographic signature
48                    signature.to_writer(writer)?;
49                }
50                SignatureVersion::V5 => {
51                    unsupported_err!("crate V5 signature")
52                }
53                _ => unreachable!(),
54            },
55            InnerSignature::Unknown { data, .. } => {
56                writer.write_all(data)?;
57            }
58        }
59        Ok(())
60    }
61
62    fn write_len(&self) -> usize {
63        let mut sum = 1;
64        match &self.inner {
65            InnerSignature::Known {
66                config,
67                signed_hash_value,
68                signature,
69            } => match config.version() {
70                SignatureVersion::V2 | SignatureVersion::V3 => {
71                    sum += config.write_len_v3();
72                    sum += signed_hash_value.len();
73                    sum += signature.write_len();
74                }
75                SignatureVersion::V4 | SignatureVersion::V6 => {
76                    sum += config.write_len_v4_v6();
77                    sum += signed_hash_value.len();
78
79                    if let SignatureVersionSpecific::V6 { salt } = &config.version_specific {
80                        sum += 1;
81                        sum += salt.len();
82                    }
83
84                    sum += signature.write_len();
85                }
86                SignatureVersion::V5 => panic!("v5 signature unsupported writer"),
87                _ => unreachable!(),
88            },
89            InnerSignature::Unknown { data, .. } => {
90                sum += data.len();
91            }
92        }
93        sum
94    }
95}
96
97/// Convert expiration time "Duration" data to OpenPGP u32 format.
98/// Use u32:MAX on overflow.
99fn duration_to_u32(d: &Duration) -> u32 {
100    u32::try_from(d.num_seconds()).unwrap_or(u32::MAX)
101}
102
103impl Subpacket {
104    pub fn typ(&self) -> SubpacketType {
105        match &self.data {
106            SubpacketData::SignatureCreationTime(_) => SubpacketType::SignatureCreationTime,
107            SubpacketData::SignatureExpirationTime(_) => SubpacketType::SignatureExpirationTime,
108            SubpacketData::KeyExpirationTime(_) => SubpacketType::KeyExpirationTime,
109            SubpacketData::Issuer(_) => SubpacketType::Issuer,
110            SubpacketData::PreferredSymmetricAlgorithms(_) => {
111                SubpacketType::PreferredSymmetricAlgorithms
112            }
113            SubpacketData::PreferredHashAlgorithms(_) => SubpacketType::PreferredHashAlgorithms,
114            SubpacketData::PreferredCompressionAlgorithms(_) => {
115                SubpacketType::PreferredCompressionAlgorithms
116            }
117            SubpacketData::KeyServerPreferences(_) => SubpacketType::KeyServerPreferences,
118            SubpacketData::KeyFlags(_) => SubpacketType::KeyFlags,
119            SubpacketData::Features(_) => SubpacketType::Features,
120            SubpacketData::RevocationReason(_, _) => SubpacketType::RevocationReason,
121            SubpacketData::IsPrimary(_) => SubpacketType::PrimaryUserId,
122            SubpacketData::Revocable(_) => SubpacketType::Revocable,
123            SubpacketData::EmbeddedSignature(_) => SubpacketType::EmbeddedSignature,
124            SubpacketData::PreferredKeyServer(_) => SubpacketType::PreferredKeyServer,
125            SubpacketData::Notation(_) => SubpacketType::Notation,
126            SubpacketData::RevocationKey(_) => SubpacketType::RevocationKey,
127            SubpacketData::SignersUserID(_) => SubpacketType::SignersUserID,
128            SubpacketData::PolicyURI(_) => SubpacketType::PolicyURI,
129            SubpacketData::TrustSignature(_, _) => SubpacketType::TrustSignature,
130            SubpacketData::RegularExpression(_) => SubpacketType::RegularExpression,
131            SubpacketData::ExportableCertification(_) => SubpacketType::ExportableCertification,
132            SubpacketData::IssuerFingerprint(_) => SubpacketType::IssuerFingerprint,
133            SubpacketData::PreferredEncryptionModes(_) => SubpacketType::PreferredEncryptionModes,
134            SubpacketData::IntendedRecipientFingerprint(_) => {
135                SubpacketType::IntendedRecipientFingerprint
136            }
137            SubpacketData::PreferredAeadAlgorithms(_) => SubpacketType::PreferredAead,
138            SubpacketData::Experimental(n, _) => SubpacketType::Experimental(*n),
139            SubpacketData::Other(n, _) => SubpacketType::Other(*n),
140            SubpacketData::SignatureTarget(_, _, _) => SubpacketType::SignatureTarget,
141        }
142    }
143}
144
145impl Serialize for SubpacketData {
146    fn to_writer<W: io::Write>(&self, writer: &mut W) -> Result<()> {
147        debug!("writing subpacket: {self:?}");
148        match &self {
149            SubpacketData::SignatureCreationTime(t) => {
150                writer.write_u32::<BigEndian>(t.timestamp().try_into()?)?;
151            }
152            SubpacketData::SignatureExpirationTime(d) => {
153                writer.write_u32::<BigEndian>(duration_to_u32(d))?;
154            }
155            SubpacketData::KeyExpirationTime(d) => {
156                writer.write_u32::<BigEndian>(duration_to_u32(d))?;
157            }
158            SubpacketData::Issuer(id) => {
159                writer.write_all(id.as_ref())?;
160            }
161            SubpacketData::PreferredSymmetricAlgorithms(algs) => {
162                writer.write_all(&algs.iter().map(|&alg| u8::from(alg)).collect::<Vec<_>>())?;
163            }
164            SubpacketData::PreferredHashAlgorithms(algs) => {
165                writer.write_all(&algs.iter().map(|&alg| alg.into()).collect::<Vec<_>>())?;
166            }
167            SubpacketData::PreferredCompressionAlgorithms(algs) => {
168                writer.write_all(&algs.iter().map(|&alg| u8::from(alg)).collect::<Vec<_>>())?;
169            }
170            SubpacketData::KeyServerPreferences(prefs) => {
171                writer.write_all(prefs)?;
172            }
173            SubpacketData::KeyFlags(flags) => {
174                flags.to_writer(writer)?;
175            }
176            SubpacketData::Features(features) => {
177                features.to_writer(writer)?;
178            }
179            SubpacketData::RevocationReason(code, reason) => {
180                writer.write_u8((*code).into())?;
181                writer.write_all(reason)?;
182            }
183            SubpacketData::IsPrimary(is_primary) => {
184                writer.write_u8((*is_primary).into())?;
185            }
186            SubpacketData::Revocable(is_revocable) => {
187                writer.write_u8((*is_revocable).into())?;
188            }
189            SubpacketData::EmbeddedSignature(inner_sig) => {
190                (*inner_sig).to_writer(writer)?;
191            }
192            SubpacketData::PreferredKeyServer(server) => {
193                writer.write_all(server.as_bytes())?;
194            }
195            SubpacketData::Notation(notation) => {
196                let is_readable = if notation.readable { 0x80 } else { 0 };
197                writer.write_all(&[is_readable, 0, 0, 0])?;
198
199                writer.write_u16::<BigEndian>(notation.name.len().try_into()?)?;
200
201                writer.write_u16::<BigEndian>(notation.value.len().try_into()?)?;
202
203                writer.write_all(&notation.name)?;
204                writer.write_all(&notation.value)?;
205            }
206            SubpacketData::RevocationKey(rev_key) => {
207                writer.write_u8(rev_key.class as u8)?;
208                writer.write_u8(rev_key.algorithm.into())?;
209                writer.write_all(&rev_key.fingerprint[..])?;
210            }
211            SubpacketData::SignersUserID(body) => {
212                writer.write_all(body.as_ref())?;
213            }
214            SubpacketData::PolicyURI(uri) => {
215                writer.write_all(uri.as_bytes())?;
216            }
217            SubpacketData::TrustSignature(depth, value) => {
218                writer.write_u8(*depth)?;
219                writer.write_u8(*value)?;
220            }
221            SubpacketData::RegularExpression(regexp) => {
222                writer.write_all(regexp)?;
223            }
224            SubpacketData::ExportableCertification(is_exportable) => {
225                writer.write_u8((*is_exportable).into())?;
226            }
227            SubpacketData::IssuerFingerprint(fp) => {
228                if let Some(version) = fp.version() {
229                    writer.write_u8(version.into())?;
230                    writer.write_all(fp.as_bytes())?;
231                } else {
232                    bail!("IssuerFingerprint: needs versioned fingerprint")
233                }
234            }
235            SubpacketData::PreferredEncryptionModes(algs) => {
236                writer.write_all(&algs.iter().map(|&alg| alg.into()).collect::<Vec<_>>())?;
237            }
238            SubpacketData::IntendedRecipientFingerprint(fp) => {
239                if let Some(version) = fp.version() {
240                    writer.write_u8(version.into())?;
241                    writer.write_all(fp.as_bytes())?;
242                } else {
243                    bail!("IntendedRecipientFingerprint: needs versioned fingerprint")
244                }
245            }
246            SubpacketData::PreferredAeadAlgorithms(algs) => {
247                writer.write_all(
248                    &algs
249                        .iter()
250                        .flat_map(|&(sym_alg, aead)| [sym_alg.into(), aead.into()])
251                        .collect::<Vec<_>>(),
252                )?;
253            }
254            SubpacketData::Experimental(_, body) => {
255                writer.write_all(body)?;
256            }
257            SubpacketData::Other(_, body) => {
258                writer.write_all(body)?;
259            }
260            SubpacketData::SignatureTarget(pub_alg, hash_alg, hash) => {
261                writer.write_u8((*pub_alg).into())?;
262                writer.write_u8((*hash_alg).into())?;
263                writer.write_all(hash)?;
264            }
265        }
266
267        Ok(())
268    }
269
270    fn write_len(&self) -> usize {
271        let len = match &self {
272            SubpacketData::SignatureCreationTime(_) => 4,
273            SubpacketData::SignatureExpirationTime(_) => 4,
274            SubpacketData::KeyExpirationTime(_) => 4,
275            SubpacketData::Issuer(_) => 8,
276            SubpacketData::PreferredSymmetricAlgorithms(algs) => algs.len(),
277            SubpacketData::PreferredHashAlgorithms(algs) => algs.len(),
278            SubpacketData::PreferredCompressionAlgorithms(algs) => algs.len(),
279            SubpacketData::KeyServerPreferences(prefs) => prefs.len(),
280            SubpacketData::KeyFlags(flags) => flags.write_len(),
281            SubpacketData::Features(features) => features.write_len(),
282
283            SubpacketData::RevocationReason(_, reason) => {
284                // 1 byte for revocation code + n for the reason
285                1 + reason.len()
286            }
287            SubpacketData::IsPrimary(_) => 1,
288            SubpacketData::Revocable(_) => 1,
289            SubpacketData::EmbeddedSignature(sig) => (*sig).write_len(),
290            SubpacketData::PreferredKeyServer(server) => server.chars().count(),
291            SubpacketData::Notation(n) => {
292                // 4 for the flags, 2 for the name length, 2 for the value length, m for the name, n for the value
293                4 + 2 + 2 + n.name.len() + n.value.len()
294            }
295            SubpacketData::RevocationKey(_) => 22,
296            SubpacketData::SignersUserID(body) => {
297                let bytes: &[u8] = body.as_ref();
298                bytes.len()
299            }
300            SubpacketData::PolicyURI(uri) => uri.len(),
301            SubpacketData::TrustSignature(_, _) => 2,
302            SubpacketData::RegularExpression(regexp) => regexp.len(),
303            SubpacketData::ExportableCertification(_) => 1,
304            SubpacketData::IssuerFingerprint(fp) => 1 + fp.len(),
305            SubpacketData::PreferredEncryptionModes(algs) => algs.len(),
306            SubpacketData::IntendedRecipientFingerprint(fp) => 1 + fp.len(),
307            SubpacketData::PreferredAeadAlgorithms(algs) => algs.len() * 2,
308            SubpacketData::Experimental(_, body) => body.len(),
309            SubpacketData::Other(_, body) => body.len(),
310            SubpacketData::SignatureTarget(_, _, hash) => 2 + hash.len(),
311        };
312
313        len
314    }
315}
316
317impl Serialize for Subpacket {
318    fn to_writer<W: io::Write>(&self, writer: &mut W) -> Result<()> {
319        self.len.to_writer(writer)?;
320        writer.write_u8(self.typ().as_u8(self.is_critical))?;
321        self.data.to_writer(writer)?;
322
323        Ok(())
324    }
325
326    fn write_len(&self) -> usize {
327        let mut sum = self.len.len();
328        sum += self.len.write_len();
329        sum
330    }
331}
332
333impl SignatureConfig {
334    /// Serializes a v2 or v3 signature.
335    fn to_writer_v3<W: io::Write>(&self, writer: &mut W) -> Result<()> {
336        writer.write_u8(0x05)?; // 1-octet length of the following hashed material; it MUST be 5
337        writer.write_u8(self.typ.into())?; // type
338
339        if let SignatureVersionSpecific::V2 { created, issuer }
340        | SignatureVersionSpecific::V3 { created, issuer } = &self.version_specific
341        {
342            writer.write_u32::<BigEndian>(created.timestamp().try_into()?)?;
343            writer.write_all(issuer.as_ref())?;
344        } else {
345            bail!("expecting SignatureVersionSpecific::V3 for a v2/v3 signature")
346        }
347
348        writer.write_u8(self.pub_alg.into())?; // public algorithm
349        writer.write_u8(self.hash_alg.into())?; // hash algorithm
350
351        Ok(())
352    }
353
354    pub(super) fn write_len_v3(&self) -> usize {
355        let mut sum = 1 + 1;
356
357        if let SignatureVersionSpecific::V2 { issuer, .. }
358        | SignatureVersionSpecific::V3 { issuer, .. } = &self.version_specific
359        {
360            sum += 4;
361            sum += issuer.as_ref().len();
362        } else {
363            panic!("expecting SignatureVersionSpecific::V3 for a v2/v3 signature")
364        }
365
366        sum += 1 + 1;
367
368        sum
369    }
370
371    /// Serializes a v4 or v6 signature.
372    fn to_writer_v4_v6<W: io::Write>(&self, writer: &mut W) -> Result<()> {
373        writer.write_u8(self.typ.into())?; // type
374
375        writer.write_u8(self.pub_alg.into())?; // public algorithm
376        writer.write_u8(self.hash_alg.into())?; // hash algorithm
377
378        // hashed subpackets
379        let hashed_sub_len = self.hashed_subpackets.write_len();
380        match self.version() {
381            SignatureVersion::V4 => writer.write_u16::<BigEndian>(hashed_sub_len.try_into()?)?,
382            SignatureVersion::V6 => writer.write_u32::<BigEndian>(hashed_sub_len.try_into()?)?,
383            v => unimplemented_err!("signature version {:?}", v),
384        }
385
386        for packet in &self.hashed_subpackets {
387            packet.to_writer(writer)?;
388        }
389
390        // unhashed subpackets
391        let unhashed_sub_len = self.unhashed_subpackets.write_len();
392
393        match self.version() {
394            SignatureVersion::V4 => writer.write_u16::<BigEndian>(unhashed_sub_len.try_into()?)?,
395            SignatureVersion::V6 => writer.write_u32::<BigEndian>(unhashed_sub_len.try_into()?)?,
396            v => unimplemented_err!("signature version {:?}", v),
397        }
398
399        for packet in &self.unhashed_subpackets {
400            packet.to_writer(writer)?;
401        }
402
403        Ok(())
404    }
405
406    pub(super) fn write_len_v4_v6(&self) -> usize {
407        let mut sum = 1 + 1 + 1;
408
409        // hashed subpackets
410        sum += self.hashed_subpackets.write_len();
411
412        match self.version() {
413            SignatureVersion::V4 => {
414                sum += 2;
415            }
416            SignatureVersion::V6 => {
417                sum += 4;
418            }
419            v => panic!("signature version {v:?}"),
420        }
421
422        // unhashed subpackets
423        sum += self.unhashed_subpackets.write_len();
424        match self.version() {
425            SignatureVersion::V4 => {
426                sum += 2;
427            }
428            SignatureVersion::V6 => {
429                sum += 4;
430            }
431            v => panic!("signature version {v:?}"),
432        }
433
434        sum
435    }
436}
437
438#[cfg(test)]
439mod tests {
440    #![allow(clippy::unwrap_used)]
441
442    use std::{
443        fs::File,
444        io::{BufReader, Read},
445        path::Path,
446    };
447
448    use super::*;
449    use crate::packet::{Packet, PacketParser};
450
451    fn test_roundtrip(name: &str) {
452        let f = BufReader::new(
453            std::fs::File::open(Path::new("./tests/openpgp/samplemsgs").join(name)).unwrap(),
454        );
455
456        let packets: Vec<Packet> = PacketParser::new(f).collect::<Result<_>>().unwrap();
457        let mut serialized = Vec::new();
458
459        for p in &packets {
460            if let Packet::Signature(_) = p {
461                p.to_writer(&mut serialized).unwrap();
462            } else {
463                panic!("unexpected packet: {p:?}");
464            };
465        }
466
467        let bytes = {
468            let mut f = File::open(Path::new("./tests/openpgp/samplemsgs").join(name)).unwrap();
469            let mut bytes = Vec::new();
470            f.read_to_end(&mut bytes).unwrap();
471            bytes
472        };
473
474        assert_eq!(bytes, serialized, "failed to roundtrip");
475    }
476
477    #[test]
478    fn packet_signature_roundtrip_openpgp_sig_1_key_1() {
479        test_roundtrip("sig-1-key-1.sig");
480    }
481
482    #[test]
483    fn packet_signature_roundtrip_openpgp_sig_1_key_2() {
484        test_roundtrip("sig-1-key-2.sig");
485    }
486
487    #[test]
488    fn packet_signature_roundtrip_openpgp_sig_2_keys_1() {
489        test_roundtrip("sig-2-keys-1.sig");
490    }
491
492    #[test]
493    fn packet_signature_roundtrip_openpgp_sig_2_keys_2() {
494        test_roundtrip("sig-2-keys-2.sig");
495    }
496
497    // Tries to roundtrip a signature containing a name + E-Mail with complicated multibyte unicode characters
498    #[test]
499    fn packet_signature_roundtrip_openpgp_with_unicode() {
500        test_roundtrip("unicode.sig");
501    }
502}