Skip to main content

gpg_inspector_lib/packet/
subpackets.rs

1//! Signature subpacket parsing.
2//!
3//! This module parses the subpackets embedded in Signature packets.
4//! Subpackets carry additional signature metadata like creation time,
5//! key expiration, preferred algorithms, and issuer identification.
6
7use crate::error::{Error, Result};
8use crate::lookup::{
9    lookup_compression_algorithm, lookup_hash_algorithm, lookup_key_flags,
10    lookup_public_key_algorithm, lookup_revocation_reason, lookup_subpacket_type,
11    lookup_symmetric_algorithm,
12};
13use crate::packet::Field;
14use crate::stream::ByteStream;
15use chrono::{DateTime, TimeZone, Utc};
16
17fn format_timestamp(ts: u32) -> Result<String> {
18    Utc.timestamp_opt(ts as i64, 0)
19        .single()
20        .map(|dt: DateTime<Utc>| dt.to_rfc3339())
21        .ok_or(Error::InvalidTimestamp(ts))
22}
23
24/// A parsed signature subpacket.
25#[derive(Debug, Clone)]
26pub struct Subpacket {
27    /// Subpacket type identifier.
28    pub packet_type: u8,
29    /// Whether this subpacket is critical (must be understood).
30    pub critical: bool,
31    /// The parsed subpacket data.
32    pub data: SubpacketData,
33}
34
35/// The typed content of a signature subpacket.
36///
37/// Each variant corresponds to a specific subpacket type defined in
38/// RFC 4880 and RFC 9580.
39#[derive(Debug, Clone)]
40pub enum SubpacketData {
41    /// Signature creation time (type 2).
42    SignatureCreationTime(u32),
43    /// Signature expiration time as seconds after creation (type 3).
44    SignatureExpirationTime(u32),
45    /// Key expiration time as seconds after key creation (type 9).
46    KeyExpirationTime(u32),
47    /// Whether the certification is exportable (type 4).
48    Exportable(bool),
49    /// Trust signature level and amount (type 5).
50    Trust {
51        /// Trust level (0 = ordinary, 1 = introducer, etc.).
52        level: u8,
53        /// Trust amount (0-255).
54        amount: u8,
55    },
56    /// Whether the signature is revocable (type 7).
57    Revocable(bool),
58    /// Preferred symmetric algorithms (type 11).
59    PreferredSymmetric(Vec<u8>),
60    /// Revocation key designation (type 12).
61    RevocationKey {
62        /// Revocation class.
63        class: u8,
64        /// Public-key algorithm of the revocation key.
65        algo: u8,
66        /// Fingerprint of the revocation key.
67        fingerprint: String,
68    },
69    /// Issuer key ID (type 16).
70    IssuerKeyId(String),
71    /// Notation data (type 20).
72    NotationData {
73        /// Notation name.
74        name: String,
75        /// Notation value.
76        value: String,
77    },
78    /// Preferred hash algorithms (type 21).
79    PreferredHash(Vec<u8>),
80    /// Preferred compression algorithms (type 22).
81    PreferredCompression(Vec<u8>),
82    /// Key server preferences (type 23).
83    KeyServerPreferences(Vec<u8>),
84    /// Preferred key server URL (type 24).
85    PreferredKeyServer(String),
86    /// Whether this is the primary user ID (type 25).
87    PrimaryUserId(bool),
88    /// Policy URI (type 26).
89    PolicyUri(String),
90    /// Key usage flags (type 27).
91    KeyFlags(Vec<u8>),
92    /// Signer's user ID (type 28).
93    SignerUserId(String),
94    /// Reason for revocation (type 29).
95    RevocationReason {
96        /// Reason code.
97        code: u8,
98        /// Human-readable reason string.
99        reason: String,
100    },
101    /// Supported features (type 30).
102    Features(Vec<u8>),
103    /// Signature target (type 31).
104    SignatureTarget {
105        /// Public-key algorithm.
106        algo: u8,
107        /// Hash algorithm.
108        hash_algo: u8,
109        /// Hash of the target.
110        hash: String,
111    },
112    /// Embedded signature (type 32).
113    EmbeddedSignature(Vec<u8>),
114    /// Issuer fingerprint (type 33).
115    IssuerFingerprint {
116        /// Key version.
117        version: u8,
118        /// Key fingerprint.
119        fingerprint: String,
120    },
121    /// Preferred AEAD algorithms (type 34).
122    PreferredAead(Vec<u8>),
123    /// Intended recipient fingerprint (type 35).
124    IntendedRecipient {
125        /// Key version.
126        version: u8,
127        /// Recipient's key fingerprint.
128        fingerprint: String,
129    },
130    /// Unknown or unsupported subpacket type.
131    Unknown(Vec<u8>),
132}
133
134/// Parses subpackets from a stream.
135///
136/// # Arguments
137///
138/// * `stream` - Stream containing the subpacket data
139/// * `fields` - Output field list
140/// * `prefix` - Label prefix ("Hashed" or "Unhashed")
141/// * `base_offset` - Byte offset for span calculation
142pub fn parse_subpackets(
143    stream: &mut ByteStream,
144    fields: &mut Vec<Field>,
145    prefix: &str,
146    base_offset: usize,
147) -> Result<Vec<Subpacket>> {
148    let mut subpackets = Vec::new();
149    let mut count = 0;
150
151    while !stream.is_empty() {
152        count += 1;
153        let sp_start = base_offset + stream.pos();
154
155        let len = stream.variable_length()?;
156        if len == 0 {
157            continue;
158        }
159
160        let type_byte = stream.octet()?;
161        let critical = (type_byte & 0x80) != 0;
162        let packet_type = type_byte & 0x7F;
163
164        let data_len = len - 1;
165        let mut sp_stream = stream.slice(stream.pos(), stream.pos() + data_len);
166        stream.skip(data_len)?;
167        let sp_end = base_offset + stream.pos();
168
169        let type_info = lookup_subpacket_type(packet_type);
170        let field_name = format!("{} #{}: {}", prefix, count, type_info.name);
171
172        let (data, value) = parse_subpacket_data(packet_type, &mut sp_stream)?;
173
174        fields.push(Field::subfield(field_name, value, (sp_start, sp_end)));
175
176        subpackets.push(Subpacket {
177            packet_type,
178            critical,
179            data,
180        });
181    }
182
183    Ok(subpackets)
184}
185
186fn parse_subpacket_data(
187    packet_type: u8,
188    stream: &mut ByteStream,
189) -> Result<(SubpacketData, String)> {
190    let (data, value) = match packet_type {
191        2 => {
192            let time = stream.uint32()?;
193            (
194                SubpacketData::SignatureCreationTime(time),
195                format_timestamp(time)?,
196            )
197        }
198        3 => {
199            let time = stream.uint32()?;
200            (
201                SubpacketData::SignatureExpirationTime(time),
202                format_duration(time),
203            )
204        }
205        4 => {
206            let exportable = stream.octet()? != 0;
207            (
208                SubpacketData::Exportable(exportable),
209                exportable.to_string(),
210            )
211        }
212        5 => {
213            let level = stream.octet()?;
214            let amount = stream.octet()?;
215            (
216                SubpacketData::Trust { level, amount },
217                format!("level={}, amount={}", level, amount),
218            )
219        }
220        7 => {
221            let revocable = stream.octet()? != 0;
222            (SubpacketData::Revocable(revocable), revocable.to_string())
223        }
224        9 => {
225            let time = stream.uint32()?;
226            (
227                SubpacketData::KeyExpirationTime(time),
228                format_duration(time),
229            )
230        }
231        11 => {
232            let prefs = stream.rest();
233            let names: Vec<String> = prefs
234                .iter()
235                .map(|&id| lookup_symmetric_algorithm(id).name)
236                .collect();
237            (SubpacketData::PreferredSymmetric(prefs), names.join(", "))
238        }
239        12 => {
240            let class = stream.octet()?;
241            let algo = stream.octet()?;
242            let fingerprint = stream.hex(20)?;
243            let algo_name = lookup_public_key_algorithm(algo).name;
244            (
245                SubpacketData::RevocationKey {
246                    class,
247                    algo,
248                    fingerprint: fingerprint.clone(),
249                },
250                format!("{} ({})", fingerprint, algo_name),
251            )
252        }
253        16 => {
254            let key_id = stream.hex(8)?;
255            (SubpacketData::IssuerKeyId(key_id.clone()), key_id)
256        }
257        20 => {
258            let flags = stream.uint32()?;
259            let name_len = stream.uint16()? as usize;
260            let value_len = stream.uint16()? as usize;
261            let name = stream.utf8(name_len)?;
262            let value = if flags & 0x80000000 != 0 {
263                stream.utf8(value_len)?
264            } else {
265                stream.hex(value_len)?
266            };
267            let display = format!("{}={}", name, value);
268            (SubpacketData::NotationData { name, value }, display)
269        }
270        21 => {
271            let prefs = stream.rest();
272            let names: Vec<String> = prefs
273                .iter()
274                .map(|&id| lookup_hash_algorithm(id).name)
275                .collect();
276            (SubpacketData::PreferredHash(prefs), names.join(", "))
277        }
278        22 => {
279            let prefs = stream.rest();
280            let names: Vec<String> = prefs
281                .iter()
282                .map(|&id| lookup_compression_algorithm(id).name)
283                .collect();
284            (SubpacketData::PreferredCompression(prefs), names.join(", "))
285        }
286        23 => {
287            let prefs = stream.rest();
288            (
289                SubpacketData::KeyServerPreferences(prefs.clone()),
290                format!("{} bytes", prefs.len()),
291            )
292        }
293        24 => {
294            let server = stream.utf8(stream.remaining())?;
295            (SubpacketData::PreferredKeyServer(server.clone()), server)
296        }
297        25 => {
298            let primary = stream.octet()? != 0;
299            (SubpacketData::PrimaryUserId(primary), primary.to_string())
300        }
301        26 => {
302            let uri = stream.utf8(stream.remaining())?;
303            (SubpacketData::PolicyUri(uri.clone()), uri)
304        }
305        27 => {
306            let flags_data = stream.rest();
307            let flags = if !flags_data.is_empty() {
308                flags_data[0]
309            } else {
310                0
311            };
312            let flag_names = lookup_key_flags(flags);
313            (SubpacketData::KeyFlags(flags_data), flag_names.join(", "))
314        }
315        28 => {
316            let user_id = stream.utf8(stream.remaining())?;
317            (SubpacketData::SignerUserId(user_id.clone()), user_id)
318        }
319        29 => {
320            let code = stream.octet()?;
321            let reason = stream.utf8(stream.remaining())?;
322            let code_name = lookup_revocation_reason(code);
323            (
324                SubpacketData::RevocationReason {
325                    code,
326                    reason: reason.clone(),
327                },
328                format!("{}: {}", code_name, reason),
329            )
330        }
331        30 => {
332            let features = stream.rest();
333            let mut feat_names = Vec::new();
334            if !features.is_empty() {
335                if features[0] & 0x01 != 0 {
336                    feat_names.push("Modification Detection");
337                }
338                if features[0] & 0x02 != 0 {
339                    feat_names.push("AEAD");
340                }
341                if features[0] & 0x04 != 0 {
342                    feat_names.push("Version 5 Public Keys");
343                }
344            }
345            (SubpacketData::Features(features), feat_names.join(", "))
346        }
347        31 => {
348            let algo = stream.octet()?;
349            let hash_algo = stream.octet()?;
350            let hash = stream.rest_as_hex();
351            let algo_name = lookup_public_key_algorithm(algo).name;
352            let hash_name = lookup_hash_algorithm(hash_algo).name;
353            (
354                SubpacketData::SignatureTarget {
355                    algo,
356                    hash_algo,
357                    hash: hash.clone(),
358                },
359                format!("{}/{}: {}", algo_name, hash_name, hash),
360            )
361        }
362        32 => {
363            let sig_data = stream.rest();
364            (
365                SubpacketData::EmbeddedSignature(sig_data.clone()),
366                format!("{} bytes", sig_data.len()),
367            )
368        }
369        33 => {
370            let version = stream.octet()?;
371            let fp_len = if version == 4 { 20 } else { 32 };
372            let fingerprint = stream.hex(fp_len.min(stream.remaining()))?;
373            (
374                SubpacketData::IssuerFingerprint {
375                    version,
376                    fingerprint: fingerprint.clone(),
377                },
378                format!("v{}: {}", version, fingerprint),
379            )
380        }
381        34 => {
382            let prefs = stream.rest();
383            (
384                SubpacketData::PreferredAead(prefs.clone()),
385                format!("{} algorithms", prefs.len()),
386            )
387        }
388        35 => {
389            let version = stream.octet()?;
390            let fp_len = if version == 4 { 20 } else { 32 };
391            let fingerprint = stream.hex(fp_len.min(stream.remaining()))?;
392            (
393                SubpacketData::IntendedRecipient {
394                    version,
395                    fingerprint: fingerprint.clone(),
396                },
397                format!("v{}: {}", version, fingerprint),
398            )
399        }
400        _ => {
401            let data = stream.rest();
402            (
403                SubpacketData::Unknown(data.clone()),
404                format!("{} bytes", data.len()),
405            )
406        }
407    };
408
409    Ok((data, value))
410}
411
412fn format_duration(seconds: u32) -> String {
413    if seconds == 0 {
414        return "never expires".to_string();
415    }
416
417    let days = seconds / 86400;
418    let years = days / 365;
419
420    if years > 0 {
421        format!("{} years", years)
422    } else if days > 0 {
423        format!("{} days", days)
424    } else {
425        format!("{} seconds", seconds)
426    }
427}