Skip to main content

gpg_inspector_lib/packet/
misc.rs

1//! Miscellaneous simple packet parsing.
2//!
3//! This module handles several simple packet types that have straightforward
4//! structures:
5//! - Marker packet (tag 10) - obsolete, contains "PGP" magic
6//! - Symmetrically Encrypted Data (tag 9) - legacy encrypted data
7//! - AEAD Encrypted Data (tag 20) - RFC 9580 AEAD encryption
8//! - Padding packet (tag 21) - RFC 9580 padding
9
10use crate::error::Result;
11use crate::lookup::{lookup_aead_algorithm, lookup_symmetric_algorithm};
12use crate::stream::ByteStream;
13
14use super::Field;
15
16// =============================================================================
17// Marker Packet (tag 10)
18// =============================================================================
19
20/// Parsed Marker packet.
21///
22/// The Marker packet is obsolete and contains only the three bytes "PGP".
23/// It was used to indicate newer message formats to older PGP versions.
24#[derive(Debug, Clone)]
25pub struct MarkerPacket {
26    /// Whether the packet contains valid "PGP" magic bytes.
27    pub valid: bool,
28}
29
30/// Parses a Marker packet body.
31///
32/// RFC 4880 Section 5.8: Must contain exactly 0x50, 0x47, 0x50 ("PGP").
33pub fn parse_marker(
34    stream: &mut ByteStream,
35    fields: &mut Vec<Field>,
36    body_offset: usize,
37) -> Result<MarkerPacket> {
38    let marker_start = body_offset + stream.pos();
39    let data = stream.rest();
40
41    let valid = data == [0x50, 0x47, 0x50]; // "PGP"
42    let status = if valid {
43        "PGP (valid)"
44    } else if data.len() == 3 {
45        "Invalid marker"
46    } else {
47        "Invalid length"
48    };
49
50    fields.push(Field::field(
51        "Marker",
52        status,
53        (marker_start, marker_start + data.len()),
54    ));
55
56    Ok(MarkerPacket { valid })
57}
58
59// =============================================================================
60// Symmetrically Encrypted Data Packet (tag 9)
61// =============================================================================
62
63/// Parsed Symmetrically Encrypted Data packet.
64///
65/// This is the legacy encryption format without integrity protection.
66/// The encrypted data is in OpenPGP CFB mode.
67#[derive(Debug, Clone)]
68pub struct SymmetricallyEncryptedDataPacket {
69    /// The encrypted data (includes random prefix and encrypted content).
70    pub encrypted_data: Vec<u8>,
71}
72
73/// Parses a Symmetrically Encrypted Data packet body.
74///
75/// RFC 4880 Section 5.7: Contains raw CFB-encrypted data.
76pub fn parse_symmetrically_encrypted_data(
77    stream: &mut ByteStream,
78    fields: &mut Vec<Field>,
79    body_offset: usize,
80) -> Result<SymmetricallyEncryptedDataPacket> {
81    let data_start = body_offset + stream.pos();
82    let encrypted_data = stream.rest();
83    let data_end = body_offset + stream.pos();
84
85    fields.push(Field::field(
86        "Encrypted Data",
87        format!("{} bytes (legacy CFB, no MDC)", encrypted_data.len()),
88        (data_start, data_end),
89    ));
90
91    Ok(SymmetricallyEncryptedDataPacket { encrypted_data })
92}
93
94// =============================================================================
95// AEAD Encrypted Data Packet (tag 20)
96// =============================================================================
97
98/// Parsed AEAD Encrypted Data packet.
99///
100/// RFC 9580: Authenticated encryption with associated data.
101#[derive(Debug, Clone)]
102pub struct AeadEncryptedDataPacket {
103    /// Packet version (1).
104    pub version: u8,
105    /// Symmetric cipher algorithm.
106    pub cipher_algorithm: u8,
107    /// AEAD mode (EAX, OCB, GCM).
108    pub aead_algorithm: u8,
109    /// Chunk size (power of 2, encoded).
110    pub chunk_size: u8,
111    /// Initialization vector / starting nonce.
112    pub iv: Vec<u8>,
113    /// Encrypted data with authentication tags.
114    pub encrypted_data: Vec<u8>,
115}
116
117/// Parses an AEAD Encrypted Data packet body.
118pub fn parse_aead_encrypted_data(
119    stream: &mut ByteStream,
120    fields: &mut Vec<Field>,
121    body_offset: usize,
122) -> Result<AeadEncryptedDataPacket> {
123    let version_start = body_offset + stream.pos();
124    let version = stream.octet()?;
125    fields.push(Field::field(
126        "Version",
127        version.to_string(),
128        (version_start, version_start + 1),
129    ));
130
131    let cipher_start = body_offset + stream.pos();
132    let cipher_algorithm = stream.octet()?;
133    let cipher_lookup = lookup_symmetric_algorithm(cipher_algorithm);
134    fields.push(Field::field(
135        "Cipher Algorithm",
136        cipher_lookup.display(),
137        (cipher_start, cipher_start + 1),
138    ));
139
140    let aead_start = body_offset + stream.pos();
141    let aead_algorithm = stream.octet()?;
142    let aead_lookup = lookup_aead_algorithm(aead_algorithm);
143    fields.push(Field::field(
144        "AEAD Algorithm",
145        aead_lookup.display(),
146        (aead_start, aead_start + 1),
147    ));
148
149    let chunk_start = body_offset + stream.pos();
150    let chunk_size = stream.octet()?;
151    let chunk_bytes = 1u64 << (chunk_size + 6);
152    fields.push(Field::field(
153        "Chunk Size",
154        format!("{} bytes (2^{})", chunk_bytes, chunk_size + 6),
155        (chunk_start, chunk_start + 1),
156    ));
157
158    // IV length depends on AEAD mode
159    let iv_len = match aead_algorithm {
160        1 => 16, // EAX
161        2 => 15, // OCB
162        3 => 12, // GCM
163        _ => 16, // Default
164    };
165
166    let iv_start = body_offset + stream.pos();
167    let iv = stream.bytes(iv_len)?;
168    let iv_hex: String = iv.iter().map(|b| format!("{:02X}", b)).collect();
169    fields.push(Field::field(
170        "IV/Nonce",
171        iv_hex,
172        (iv_start, iv_start + iv_len),
173    ));
174
175    let data_start = body_offset + stream.pos();
176    let encrypted_data = stream.rest();
177    let data_end = body_offset + stream.pos();
178
179    if !encrypted_data.is_empty() {
180        fields.push(Field::field(
181            "Encrypted Data",
182            format!("{} bytes (with auth tags)", encrypted_data.len()),
183            (data_start, data_end),
184        ));
185    }
186
187    Ok(AeadEncryptedDataPacket {
188        version,
189        cipher_algorithm,
190        aead_algorithm,
191        chunk_size,
192        iv,
193        encrypted_data,
194    })
195}
196
197// =============================================================================
198// Padding Packet (tag 21)
199// =============================================================================
200
201/// Parsed Padding packet.
202///
203/// RFC 9580: Contains random padding bytes to obscure message size.
204#[derive(Debug, Clone)]
205pub struct PaddingPacket {
206    /// The padding bytes (should be random).
207    pub padding: Vec<u8>,
208}
209
210/// Parses a Padding packet body.
211pub fn parse_padding(
212    stream: &mut ByteStream,
213    fields: &mut Vec<Field>,
214    body_offset: usize,
215) -> Result<PaddingPacket> {
216    let padding_start = body_offset + stream.pos();
217    let padding = stream.rest();
218    let padding_end = body_offset + stream.pos();
219
220    fields.push(Field::field(
221        "Padding",
222        format!("{} bytes", padding.len()),
223        (padding_start, padding_end),
224    ));
225
226    Ok(PaddingPacket { padding })
227}