Skip to main content

gpg_inspector_lib/packet/
secret_key.rs

1//! Secret key packet parsing.
2//!
3//! This module parses Secret Key (tag 5) and Secret Subkey (tag 7) packets,
4//! which contain the public key data plus encrypted (or unencrypted) private
5//! key material.
6
7use crate::error::Result;
8use crate::lookup::{lookup_s2k_type, lookup_symmetric_algorithm};
9use crate::packet::Field;
10use crate::packet::public_key::{PublicKeyPacket, parse_public_key};
11use crate::stream::ByteStream;
12
13/// A parsed secret key or secret subkey packet.
14///
15/// Contains the public key portion plus the secret key data,
16/// which may be encrypted with a passphrase.
17#[derive(Debug, Clone)]
18pub struct SecretKeyPacket {
19    /// The public key portion (version, creation time, algorithm, key material).
20    pub public_key: PublicKeyPacket,
21    /// String-to-Key usage byte indicating encryption method.
22    pub s2k_usage: u8,
23    /// Encryption parameters, if the key is encrypted.
24    pub encryption_info: Option<EncryptionInfo>,
25    /// The secret key data (encrypted or plaintext).
26    pub secret_key_data: Vec<u8>,
27}
28
29/// Encryption parameters for a secret key.
30///
31/// Describes how the secret key material is encrypted, including
32/// the cipher, S2K (string-to-key) parameters, and IV.
33#[derive(Debug, Clone)]
34pub struct EncryptionInfo {
35    /// Symmetric cipher algorithm used for encryption.
36    pub cipher_algo: u8,
37    /// S2K specifier type (Simple, Salted, Iterated, Argon2).
38    pub s2k_type: u8,
39    /// Hash algorithm used in S2K.
40    pub s2k_hash: u8,
41    /// 8-byte salt for Salted and Iterated S2K.
42    pub s2k_salt: Option<Vec<u8>>,
43    /// Iteration count byte for Iterated S2K.
44    pub s2k_count: Option<u8>,
45    /// Initialization vector for the cipher.
46    pub iv: Vec<u8>,
47}
48
49/// Parses a secret key packet body.
50///
51/// This function is called for both Secret Key (tag 5) and Secret Subkey (tag 7)
52/// packets, as they share the same format.
53pub fn parse_secret_key(
54    stream: &mut ByteStream,
55    fields: &mut Vec<Field>,
56    offset: usize,
57) -> Result<SecretKeyPacket> {
58    let public_key = parse_public_key(stream, fields, offset)?;
59
60    let s2k_start = offset + stream.pos();
61    let s2k_usage = stream.octet()?;
62    let s2k_end = offset + stream.pos();
63
64    let s2k_desc = match s2k_usage {
65        0 => "Unencrypted".to_string(),
66        254 => "SHA-1 checksum, encrypted".to_string(),
67        255 => "Checksum, encrypted".to_string(),
68        n => format!("Symmetric algorithm {}", n),
69    };
70    fields.push(Field::field("S2K Usage", s2k_desc, (s2k_start, s2k_end)));
71
72    let encryption_info = if s2k_usage == 254 || s2k_usage == 255 {
73        let cipher_start = offset + stream.pos();
74        let cipher_algo = stream.octet()?;
75        let cipher_end = offset + stream.pos();
76        let cipher_info = lookup_symmetric_algorithm(cipher_algo);
77        fields.push(Field::field(
78            "Cipher",
79            cipher_info.display(),
80            (cipher_start, cipher_end),
81        ));
82
83        let s2k_type_start = offset + stream.pos();
84        let s2k_type = stream.octet()?;
85        let s2k_type_end = offset + stream.pos();
86        fields.push(Field::field(
87            "S2K Type",
88            lookup_s2k_type(s2k_type).to_string(),
89            (s2k_type_start, s2k_type_end),
90        ));
91
92        let _s2k_hash_start = offset + stream.pos();
93        let s2k_hash = stream.octet()?;
94        let _s2k_hash_end = offset + stream.pos();
95
96        let (s2k_salt, s2k_count) = if s2k_type == 1 || s2k_type == 3 {
97            let salt_start = offset + stream.pos();
98            let salt = stream.bytes(8)?;
99            let salt_end = offset + stream.pos();
100            fields.push(Field::field("S2K Salt", "8 bytes", (salt_start, salt_end)));
101
102            let count = if s2k_type == 3 {
103                let count_start = offset + stream.pos();
104                let c = stream.octet()?;
105                let count_end = offset + stream.pos();
106                let iterations = (16u32 + (c as u32 & 15)) << ((c >> 4) + 6);
107                fields.push(Field::field(
108                    "S2K Iterations",
109                    iterations.to_string(),
110                    (count_start, count_end),
111                ));
112                Some(c)
113            } else {
114                None
115            };
116
117            (Some(salt), count)
118        } else {
119            (None, None)
120        };
121
122        let iv_size = match cipher_algo {
123            7..=9 => 16,
124            _ => 8,
125        };
126        let iv_start = offset + stream.pos();
127        let iv = stream.bytes(iv_size)?;
128        let iv_end = offset + stream.pos();
129        fields.push(Field::field(
130            "IV",
131            format!("{} bytes", iv.len()),
132            (iv_start, iv_end),
133        ));
134
135        Some(EncryptionInfo {
136            cipher_algo,
137            s2k_type,
138            s2k_hash,
139            s2k_salt,
140            s2k_count,
141            iv,
142        })
143    } else if s2k_usage != 0 {
144        let iv_size = match s2k_usage {
145            7..=9 => 16,
146            _ => 8,
147        };
148        let _iv_start = offset + stream.pos();
149        let iv = stream.bytes(iv_size)?;
150        let _iv_end = offset + stream.pos();
151
152        Some(EncryptionInfo {
153            cipher_algo: s2k_usage,
154            s2k_type: 0,
155            s2k_hash: 0,
156            s2k_salt: None,
157            s2k_count: None,
158            iv,
159        })
160    } else {
161        None
162    };
163
164    let data_start = offset + stream.pos();
165    let secret_key_data = stream.rest();
166    if !secret_key_data.is_empty() {
167        let data_end = offset + stream.pos();
168        fields.push(Field::field(
169            "Secret Key Data",
170            format!("{} bytes (encrypted)", secret_key_data.len()),
171            (data_start, data_end),
172        ));
173    }
174
175    Ok(SecretKeyPacket {
176        public_key,
177        s2k_usage,
178        encryption_info,
179        secret_key_data,
180    })
181}