Skip to main content

gpg_inspector_lib/
lookup.rs

1//! Algorithm and format lookup tables for OpenPGP identifiers.
2//!
3//! This module provides functions to convert numeric algorithm IDs and
4//! other OpenPGP identifiers to human-readable names. These lookups are
5//! based on RFC 4880 and RFC 9580.
6//!
7//! # Example
8//!
9//! ```
10//! use gpg_inspector_lib::lookup::lookup_public_key_algorithm;
11//!
12//! let result = lookup_public_key_algorithm(1);
13//! assert_eq!(result.name, "RSA (Encrypt or Sign)");
14//! println!("{}", result.display()); // "1 (RSA (Encrypt or Sign))"
15//! ```
16
17/// The result of a lookup operation, containing both the raw value and its name.
18///
19/// This struct pairs the original identifier with its human-readable name,
20/// allowing display in formats like "1 (RSA)" or just the name.
21pub struct LookupResult<T> {
22    /// The original identifier value.
23    pub value: T,
24    /// The human-readable name for this identifier.
25    pub name: String,
26}
27
28impl<T: std::fmt::Display> LookupResult<T> {
29    /// Returns a formatted string combining value and name.
30    ///
31    /// Format: `"{value} ({name})"`, e.g., `"1 (RSA (Encrypt or Sign))"`.
32    pub fn display(&self) -> String {
33        format!("{} ({})", self.value, self.name)
34    }
35}
36
37/// Looks up a public-key algorithm by its RFC 4880/9580 identifier.
38///
39/// # Supported Algorithms
40///
41/// - 1: RSA (Encrypt or Sign)
42/// - 2: RSA Encrypt-Only
43/// - 3: RSA Sign-Only
44/// - 16: Elgamal (Encrypt-Only)
45/// - 17: DSA
46/// - 18: ECDH
47/// - 19: ECDSA
48/// - 22: EdDSA (legacy)
49/// - 25: X25519
50/// - 27: Ed25519
51/// - 100-110: Private/Experimental
52pub fn lookup_public_key_algorithm(id: u8) -> LookupResult<u8> {
53    let name = match id {
54        1 => "RSA (Encrypt or Sign)",
55        2 => "RSA Encrypt-Only",
56        3 => "RSA Sign-Only",
57        16 => "Elgamal (Encrypt-Only)",
58        17 => "DSA",
59        18 => "ECDH",
60        19 => "ECDSA",
61        20 => "Reserved (formerly Elgamal)",
62        21 => "Reserved for Diffie-Hellman",
63        22 => "EdDSA",
64        23 => "Reserved for AEDH",
65        24 => "Reserved for AEDSA",
66        25 => "X25519",
67        26 => "X448",
68        27 => "Ed25519",
69        28 => "Ed448",
70        100..=110 => "Private/Experimental",
71        _ => "Unknown",
72    };
73    LookupResult {
74        value: id,
75        name: name.to_string(),
76    }
77}
78
79/// Looks up a symmetric-key algorithm by its RFC 4880/9580 identifier.
80///
81/// # Supported Algorithms
82///
83/// - 0: Plaintext (unencrypted)
84/// - 1: IDEA
85/// - 2: TripleDES
86/// - 3: CAST5
87/// - 4: Blowfish
88/// - 7-9: AES (128/192/256)
89/// - 10: Twofish
90/// - 11-13: Camellia (128/192/256)
91pub fn lookup_symmetric_algorithm(id: u8) -> LookupResult<u8> {
92    let name = match id {
93        0 => "Plaintext",
94        1 => "IDEA",
95        2 => "TripleDES",
96        3 => "CAST5",
97        4 => "Blowfish",
98        5 => "Reserved",
99        6 => "Reserved",
100        7 => "AES-128",
101        8 => "AES-192",
102        9 => "AES-256",
103        10 => "Twofish",
104        11 => "Camellia-128",
105        12 => "Camellia-192",
106        13 => "Camellia-256",
107        100..=110 => "Private/Experimental",
108        _ => "Unknown",
109    };
110    LookupResult {
111        value: id,
112        name: name.to_string(),
113    }
114}
115
116/// Looks up a hash algorithm by its RFC 4880/9580 identifier.
117///
118/// # Supported Algorithms
119///
120/// - 1: MD5 (deprecated)
121/// - 2: SHA-1 (deprecated for signatures)
122/// - 3: RIPEMD-160
123/// - 8-11: SHA-2 family (256/384/512/224)
124/// - 12, 14: SHA-3 family (256/512)
125pub fn lookup_hash_algorithm(id: u8) -> LookupResult<u8> {
126    let name = match id {
127        1 => "MD5",
128        2 => "SHA-1",
129        3 => "RIPEMD-160",
130        4 => "Reserved",
131        5 => "Reserved",
132        6 => "Reserved",
133        7 => "Reserved",
134        8 => "SHA-256",
135        9 => "SHA-384",
136        10 => "SHA-512",
137        11 => "SHA-224",
138        12 => "SHA3-256",
139        13 => "Reserved",
140        14 => "SHA3-512",
141        100..=110 => "Private/Experimental",
142        _ => "Unknown",
143    };
144    LookupResult {
145        value: id,
146        name: name.to_string(),
147    }
148}
149
150/// Looks up a compression algorithm by its RFC 4880 identifier.
151///
152/// # Supported Algorithms
153///
154/// - 0: Uncompressed
155/// - 1: ZIP (RFC 1951)
156/// - 2: ZLIB (RFC 1950)
157/// - 3: BZip2
158pub fn lookup_compression_algorithm(id: u8) -> LookupResult<u8> {
159    let name = match id {
160        0 => "Uncompressed",
161        1 => "ZIP",
162        2 => "ZLIB",
163        3 => "BZip2",
164        100..=110 => "Private/Experimental",
165        _ => "Unknown",
166    };
167    LookupResult {
168        value: id,
169        name: name.to_string(),
170    }
171}
172
173/// Looks up a signature type by its RFC 4880 identifier.
174///
175/// # Signature Types
176///
177/// - 0x00: Binary document signature
178/// - 0x01: Canonical text document signature
179/// - 0x10-0x13: Key certifications (generic to positive)
180/// - 0x18: Subkey binding signature
181/// - 0x19: Primary key binding signature
182/// - 0x1F: Direct key signature
183/// - 0x20, 0x28, 0x30: Revocation signatures
184/// - 0x40: Timestamp signature
185pub fn lookup_signature_type(id: u8) -> LookupResult<u8> {
186    let name = match id {
187        0x00 => "Binary document",
188        0x01 => "Canonical text document",
189        0x02 => "Standalone",
190        0x10 => "Generic certification",
191        0x11 => "Persona certification",
192        0x12 => "Casual certification",
193        0x13 => "Positive certification",
194        0x18 => "Subkey Binding",
195        0x19 => "Primary Key Binding",
196        0x1F => "Direct key",
197        0x20 => "Key revocation",
198        0x28 => "Subkey revocation",
199        0x30 => "Certification revocation",
200        0x40 => "Timestamp",
201        0x50 => "Third-Party Confirmation",
202        _ => "Unknown",
203    };
204    LookupResult {
205        value: id,
206        name: name.to_string(),
207    }
208}
209
210/// Looks up a signature subpacket type by its RFC 4880/9580 identifier.
211///
212/// Subpackets carry additional signature metadata like creation time,
213/// key expiration, preferred algorithms, and issuer identification.
214pub fn lookup_subpacket_type(id: u8) -> LookupResult<u8> {
215    let name = match id {
216        0 => "Reserved",
217        1 => "Reserved",
218        2 => "Signature Creation Time",
219        3 => "Signature Expiration Time",
220        4 => "Exportable Certification",
221        5 => "Trust Signature",
222        6 => "Regular Expression",
223        7 => "Revocable",
224        8 => "Reserved",
225        9 => "Key Expiration Time",
226        10 => "Placeholder for backward compatibility",
227        11 => "Preferred Symmetric Algorithms",
228        12 => "Revocation Key",
229        13..=15 => "Reserved",
230        16 => "Issuer Key ID",
231        17..=19 => "Reserved",
232        20 => "Notation Data",
233        21 => "Preferred Hash Algorithms",
234        22 => "Preferred Compression Algorithms",
235        23 => "Key Server Preferences",
236        24 => "Preferred Key Server",
237        25 => "Primary User ID",
238        26 => "Policy URI",
239        27 => "Key Flags",
240        28 => "Signer's User ID",
241        29 => "Reason for Revocation",
242        30 => "Features",
243        31 => "Signature Target",
244        32 => "Embedded Signature",
245        33 => "Issuer Fingerprint",
246        34 => "Preferred AEAD Algorithms",
247        35 => "Intended Recipient Fingerprint",
248        37 => "Attested Certifications",
249        38 => "Key Block",
250        39 => "Preferred AEAD Ciphersuites",
251        100..=110 => "Private/Experimental",
252        _ => "Unknown/Reserved",
253    };
254    LookupResult {
255        value: id,
256        name: name.to_string(),
257    }
258}
259
260/// Converts an elliptic curve OID to its human-readable name.
261///
262/// # Supported Curves
263///
264/// - NIST P-256, P-384, P-521
265/// - Brainpool P256r1, P384r1, P512r1
266/// - Curve25519, Ed25519
267///
268/// # Returns
269///
270/// Returns the curve name if recognized, or `"Unknown OID (hex)"` otherwise.
271pub fn lookup_curve_oid(oid: &[u8]) -> String {
272    match oid {
273        [0x2B, 0x81, 0x04, 0x00, 0x22] => "secp384r1 (NIST P-384)".to_string(),
274        [0x2B, 0x81, 0x04, 0x00, 0x23] => "secp521r1 (NIST P-521)".to_string(),
275        [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07] => "secp256r1 (NIST P-256)".to_string(),
276        [0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07] => "brainpoolP256r1".to_string(),
277        [0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B] => "brainpoolP384r1".to_string(),
278        [0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D] => "brainpoolP512r1".to_string(),
279        [0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01] => "Ed25519".to_string(),
280        [0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01] => "Curve25519".to_string(),
281        _ => {
282            let hex: String = oid.iter().map(|b| format!("{:02X}", b)).collect();
283            format!("Unknown OID ({})", hex)
284        }
285    }
286}
287
288/// Decodes key usage flags into a list of capability names.
289///
290/// # Flag Bits
291///
292/// - 0x01: Certify (sign other keys)
293/// - 0x02: Sign (create signatures)
294/// - 0x04: Encrypt communications
295/// - 0x08: Encrypt storage
296/// - 0x10: Split key (part of a shared key)
297/// - 0x20: Authentication
298/// - 0x80: Shared key (held by multiple parties)
299pub fn lookup_key_flags(flags: u8) -> Vec<&'static str> {
300    let mut result = Vec::new();
301    if flags & 0x01 != 0 {
302        result.push("Certify");
303    }
304    if flags & 0x02 != 0 {
305        result.push("Sign");
306    }
307    if flags & 0x04 != 0 {
308        result.push("Encrypt Communications");
309    }
310    if flags & 0x08 != 0 {
311        result.push("Encrypt Storage");
312    }
313    if flags & 0x10 != 0 {
314        result.push("Split Key");
315    }
316    if flags & 0x20 != 0 {
317        result.push("Authentication");
318    }
319    if flags & 0x80 != 0 {
320        result.push("Shared Key");
321    }
322    result
323}
324
325/// Looks up a revocation reason code.
326///
327/// # Reason Codes
328///
329/// - 0: No reason specified
330/// - 1: Key is superseded
331/// - 2: Key material has been compromised
332/// - 3: Key is retired and no longer used
333/// - 32: User ID information is no longer valid
334pub fn lookup_revocation_reason(code: u8) -> &'static str {
335    match code {
336        0 => "No reason specified",
337        1 => "Key is superseded",
338        2 => "Key material has been compromised",
339        3 => "Key is retired and no longer used",
340        32 => "User ID information is no longer valid",
341        _ => "Unknown reason",
342    }
343}
344
345/// Looks up a String-to-Key (S2K) specifier type.
346///
347/// S2K specifiers define how a passphrase is converted into a symmetric key.
348///
349/// # Types
350///
351/// - 0: Simple S2K (hash passphrase directly)
352/// - 1: Salted S2K (hash with 8-byte salt)
353/// - 3: Iterated and Salted S2K (repeated hashing)
354/// - 4: Argon2 (memory-hard KDF)
355pub fn lookup_s2k_type(id: u8) -> &'static str {
356    match id {
357        0 => "Simple S2K",
358        1 => "Salted S2K",
359        2 => "Reserved",
360        3 => "Iterated and Salted S2K",
361        4 => "Argon2",
362        100..=110 => "Private/Experimental",
363        _ => "Unknown",
364    }
365}
366
367/// Looks up an AEAD algorithm by its RFC 9580 identifier.
368///
369/// # Supported Algorithms
370///
371/// - 1: EAX
372/// - 2: OCB
373/// - 3: GCM
374pub fn lookup_aead_algorithm(id: u8) -> LookupResult<u8> {
375    let name = match id {
376        0 => "Reserved",
377        1 => "EAX",
378        2 => "OCB",
379        3 => "GCM",
380        100..=110 => "Private/Experimental",
381        _ => "Unknown",
382    };
383    LookupResult {
384        value: id,
385        name: name.to_string(),
386    }
387}
388
389/// Returns the salt length for V6 signatures based on the hash algorithm.
390///
391/// Per RFC 9580 Section 5.2.3, V6 signatures include a salt whose length
392/// depends on the hash algorithm used:
393/// - SHA-256, SHA3-256: 16 bytes
394/// - SHA-384: 24 bytes
395/// - SHA-512, SHA3-512: 32 bytes
396/// - Other hash algorithms: defaults to 16 bytes
397pub fn get_v6_signature_salt_len(hash_algo: u8) -> usize {
398    match hash_algo {
399        8 | 12 => 16,  // SHA-256, SHA3-256
400        9 => 24,       // SHA-384
401        10 | 14 => 32, // SHA-512, SHA3-512
402        _ => 16,       // Default for unknown/other algorithms
403    }
404}
405
406/// Returns the fixed signature size for algorithms with native signatures.
407///
408/// RFC 9580 algorithms Ed25519 and Ed448 use fixed-size native signatures
409/// instead of MPIs:
410/// - Ed25519 (27): 64 bytes
411/// - Ed448 (28): 114 bytes
412///
413/// Returns `None` for algorithms that use MPI-encoded signatures.
414pub fn get_raw_signature_len(pub_algo: u8) -> Option<usize> {
415    match pub_algo {
416        27 => Some(64),  // Ed25519: 64-byte signature
417        28 => Some(114), // Ed448: 114-byte signature
418        _ => None,       // MPI-encoded signatures
419    }
420}