gpg_inspector_lib/packet/
subpackets.rs1use 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#[derive(Debug, Clone)]
26pub struct Subpacket {
27 pub packet_type: u8,
29 pub critical: bool,
31 pub data: SubpacketData,
33}
34
35#[derive(Debug, Clone)]
40pub enum SubpacketData {
41 SignatureCreationTime(u32),
43 SignatureExpirationTime(u32),
45 KeyExpirationTime(u32),
47 Exportable(bool),
49 Trust {
51 level: u8,
53 amount: u8,
55 },
56 Revocable(bool),
58 PreferredSymmetric(Vec<u8>),
60 RevocationKey {
62 class: u8,
64 algo: u8,
66 fingerprint: String,
68 },
69 IssuerKeyId(String),
71 NotationData {
73 name: String,
75 value: String,
77 },
78 PreferredHash(Vec<u8>),
80 PreferredCompression(Vec<u8>),
82 KeyServerPreferences(Vec<u8>),
84 PreferredKeyServer(String),
86 PrimaryUserId(bool),
88 PolicyUri(String),
90 KeyFlags(Vec<u8>),
92 SignerUserId(String),
94 RevocationReason {
96 code: u8,
98 reason: String,
100 },
101 Features(Vec<u8>),
103 SignatureTarget {
105 algo: u8,
107 hash_algo: u8,
109 hash: String,
111 },
112 EmbeddedSignature(Vec<u8>),
114 IssuerFingerprint {
116 version: u8,
118 fingerprint: String,
120 },
121 PreferredAead(Vec<u8>),
123 IntendedRecipient {
125 version: u8,
127 fingerprint: String,
129 },
130 Unknown(Vec<u8>),
132}
133
134pub 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}