pgp/composed/signed_key/
secret.rs

1use std::{io, ops::Deref};
2
3use chrono::{DateTime, Utc};
4use generic_array::GenericArray;
5use log::{debug, warn};
6use rand::{CryptoRng, Rng};
7
8use crate::{
9    armor,
10    composed::{
11        key::{PublicKey, PublicSubkey},
12        signed_key::{SignedKeyDetails, SignedPublicSubKey},
13        ArmorOptions, PlainSessionKey, SignedPublicKey,
14    },
15    crypto::hash::KnownDigest,
16    errors::{ensure, Result},
17    packet::{self, Packet, PacketTrait, SignatureType, SubpacketData},
18    ser::Serialize,
19    types::{EskType, Imprint, Password, PkeskBytes, PublicKeyTrait, Tag},
20};
21
22/// Represents a secret signed PGP key.
23#[derive(Debug, PartialEq, Eq, Clone)]
24pub struct SignedSecretKey {
25    pub primary_key: packet::SecretKey,
26    pub details: SignedKeyDetails,
27    pub public_subkeys: Vec<SignedPublicSubKey>,
28    pub secret_subkeys: Vec<SignedSecretSubKey>,
29}
30
31/// Parse OpenPGP secret keys ("Transferable Secret Keys") from the given packets.
32/// Ref: <https://www.rfc-editor.org/rfc/rfc9580.html#name-transferable-secret-keys>
33pub struct SignedSecretKeyParser<
34    I: Sized + Iterator<Item = crate::errors::Result<crate::packet::Packet>>,
35> {
36    inner: std::iter::Peekable<I>,
37}
38
39impl<I: Sized + Iterator<Item = crate::errors::Result<crate::packet::Packet>>>
40    SignedSecretKeyParser<I>
41{
42    pub fn into_inner(self) -> std::iter::Peekable<I> {
43        self.inner
44    }
45
46    pub fn from_packets(packets: std::iter::Peekable<I>) -> Self {
47        SignedSecretKeyParser { inner: packets }
48    }
49}
50
51impl<I: Sized + Iterator<Item = Result<Packet>>> Iterator for SignedSecretKeyParser<I> {
52    type Item = Result<SignedSecretKey>;
53
54    fn next(&mut self) -> Option<Self::Item> {
55        match super::key_parser::next::<_, packet::SecretKey>(&mut self.inner, Tag::SecretKey, true)
56        {
57            Some(Err(err)) => Some(Err(err)),
58            None => None,
59            Some(Ok((primary_key, details, public_subkeys, secret_subkeys))) => Some(Ok(
60                SignedSecretKey::new(primary_key, details, public_subkeys, secret_subkeys),
61            )),
62        }
63    }
64}
65
66impl crate::composed::Deserializable for SignedSecretKey {
67    /// Parse a transferable key from packets.
68    /// Ref: <https://www.rfc-editor.org/rfc/rfc9580.html#name-transferable-secret-keys>
69    fn from_packets<'a, I: Iterator<Item = Result<Packet>> + 'a>(
70        packets: std::iter::Peekable<I>,
71    ) -> Box<dyn Iterator<Item = Result<Self>> + 'a> {
72        Box::new(SignedSecretKeyParser::from_packets(packets))
73    }
74
75    fn matches_block_type(typ: armor::BlockType) -> bool {
76        matches!(typ, armor::BlockType::PrivateKey | armor::BlockType::File)
77    }
78}
79
80impl SignedSecretKey {
81    pub fn new(
82        primary_key: packet::SecretKey,
83        details: SignedKeyDetails,
84        mut public_subkeys: Vec<SignedPublicSubKey>,
85        mut secret_subkeys: Vec<SignedSecretSubKey>,
86    ) -> Self {
87        public_subkeys.retain(|key| {
88            if key.signatures.is_empty() {
89                warn!("ignoring unsigned {:?}", key.key);
90                false
91            } else {
92                true
93            }
94        });
95
96        secret_subkeys.retain(|key| {
97            if key.signatures.is_empty() {
98                warn!("ignoring unsigned {:?}", key.key);
99                false
100            } else {
101                true
102            }
103        });
104
105        SignedSecretKey {
106            primary_key,
107            details,
108            public_subkeys,
109            secret_subkeys,
110        }
111    }
112
113    /// Get the secret key expiration as a date.
114    pub fn expires_at(&self) -> Option<DateTime<Utc>> {
115        let expiration = self.details.key_expiration_time()?;
116        Some(*self.primary_key.public_key().created_at() + expiration)
117    }
118
119    fn verify_public_subkeys(&self) -> Result<()> {
120        for subkey in &self.public_subkeys {
121            subkey.verify(self.primary_key.public_key())?;
122        }
123
124        Ok(())
125    }
126
127    fn verify_secret_subkeys(&self) -> Result<()> {
128        for subkey in &self.secret_subkeys {
129            subkey.verify(self.primary_key.public_key())?;
130        }
131
132        Ok(())
133    }
134
135    pub fn verify(&self) -> Result<()> {
136        self.details.verify(self.primary_key.public_key())?;
137        self.verify_public_subkeys()?;
138        self.verify_secret_subkeys()?;
139
140        Ok(())
141    }
142
143    pub fn to_armored_writer(
144        &self,
145        writer: &mut impl io::Write,
146        opts: ArmorOptions<'_>,
147    ) -> Result<()> {
148        armor::write(
149            self,
150            armor::BlockType::PrivateKey,
151            writer,
152            opts.headers,
153            opts.include_checksum,
154        )
155    }
156
157    pub fn to_armored_bytes(&self, opts: ArmorOptions<'_>) -> Result<Vec<u8>> {
158        let mut buf = Vec::new();
159
160        self.to_armored_writer(&mut buf, opts)?;
161
162        Ok(buf)
163    }
164
165    pub fn to_armored_string(&self, opts: ArmorOptions<'_>) -> Result<String> {
166        let res = String::from_utf8(self.to_armored_bytes(opts)?).map_err(|e| e.utf8_error())?;
167        Ok(res)
168    }
169
170    pub fn encrypt<R: Rng + CryptoRng>(
171        &self,
172        rng: R,
173        plain: &[u8],
174        typ: EskType,
175    ) -> Result<PkeskBytes> {
176        self.primary_key.encrypt(rng, plain, typ)
177    }
178
179    pub fn public_key(&self) -> PublicKey {
180        let mut subkeys: Vec<PublicSubkey> = self
181            .public_subkeys
182            .iter()
183            .map(SignedPublicSubKey::as_unsigned)
184            .collect();
185        let sec_subkeys = self.secret_subkeys.iter().map(|k| k.public_key());
186        subkeys.extend(sec_subkeys);
187
188        PublicKey::new(
189            self.primary_key.public_key().clone(),
190            self.details.as_unsigned(),
191            subkeys,
192        )
193    }
194
195    /// Drops the secret key material in both the primary key and all secret subkeys.
196    /// All other components of the key remain as they are.
197    pub fn signed_public_key(&self) -> SignedPublicKey {
198        let mut public_subkeys: Vec<SignedPublicSubKey> = self.public_subkeys.to_vec();
199        let sec_subkeys: Vec<SignedPublicSubKey> = self
200            .secret_subkeys
201            .iter()
202            .map(SignedSecretSubKey::signed_public_key)
203            .collect();
204        public_subkeys.extend(sec_subkeys);
205
206        SignedPublicKey::new(
207            self.primary_key.public_key().clone(),
208            self.details.clone(),
209            public_subkeys,
210        )
211    }
212
213    /// Decrypts session key using this key.
214    pub fn decrypt_session_key(
215        &self,
216        key_pw: &Password,
217        values: &PkeskBytes,
218        typ: EskType,
219    ) -> Result<Result<PlainSessionKey>> {
220        debug!("decrypt session key");
221
222        self.unlock(key_pw, |pub_params, priv_key| {
223            priv_key.decrypt(pub_params, values, typ, self.primary_key.public_key())
224        })
225    }
226}
227
228impl Serialize for SignedSecretKey {
229    fn to_writer<W: io::Write>(&self, writer: &mut W) -> Result<()> {
230        self.primary_key.to_writer_with_header(writer)?;
231        self.details.to_writer(writer)?;
232        for ps in &self.public_subkeys {
233            ps.to_writer(writer)?;
234        }
235
236        for ps in &self.secret_subkeys {
237            ps.to_writer(writer)?;
238        }
239
240        Ok(())
241    }
242
243    fn write_len(&self) -> usize {
244        let mut sum = self.primary_key.write_len_with_header();
245        sum += self.details.write_len();
246        sum += self.public_subkeys.write_len();
247        sum += self.secret_subkeys.write_len();
248        sum
249    }
250}
251
252impl Deref for SignedSecretKey {
253    type Target = packet::SecretKey;
254
255    fn deref(&self) -> &Self::Target {
256        &self.primary_key
257    }
258}
259
260impl Imprint for SignedSecretKey {
261    fn imprint<D: KnownDigest>(&self) -> Result<GenericArray<u8, D::OutputSize>> {
262        self.primary_key.imprint::<D>()
263    }
264}
265
266/// Represents a composed secret PGP SubKey.
267#[derive(Debug, PartialEq, Eq, Clone)]
268pub struct SignedSecretSubKey {
269    pub key: packet::SecretSubkey,
270    pub signatures: Vec<packet::Signature>,
271}
272
273impl SignedSecretSubKey {
274    pub fn new(key: packet::SecretSubkey, mut signatures: Vec<packet::Signature>) -> Self {
275        signatures.retain(|sig| {
276            if sig.typ() != Some(SignatureType::SubkeyBinding)
277                && sig.typ() != Some(SignatureType::SubkeyRevocation)
278            {
279                warn!(
280                    "ignoring unexpected signature {:?} after Subkey packet",
281                    sig.typ()
282                );
283                false
284            } else {
285                true
286            }
287        });
288
289        SignedSecretSubKey { key, signatures }
290    }
291
292    pub fn verify<P>(&self, key: &P) -> Result<()>
293    where
294        P: PublicKeyTrait + Serialize,
295    {
296        ensure!(!self.signatures.is_empty(), "missing subkey bindings");
297
298        for sig in &self.signatures {
299            sig.verify_subkey_binding(key, self.key.public_key())?;
300        }
301
302        Ok(())
303    }
304
305    pub fn encrypt<R: Rng + CryptoRng>(
306        &self,
307        rng: R,
308        plain: &[u8],
309        typ: EskType,
310    ) -> Result<PkeskBytes> {
311        self.key.encrypt(rng, plain, typ)
312    }
313
314    /// Drops the secret key material in this subkey.
315    /// All binding signatures remain as they are.
316    pub fn signed_public_key(&self) -> SignedPublicSubKey {
317        SignedPublicSubKey::new(self.key.public_key().clone(), self.signatures.clone())
318    }
319
320    pub fn public_key(&self) -> PublicSubkey {
321        let sig = self.signatures.first().expect("invalid signed subkey");
322
323        let keyflags = sig.key_flags();
324
325        let embedded = sig.config().and_then(|c| {
326            c.hashed_subpackets().find_map(|p| match &p.data {
327                SubpacketData::EmbeddedSignature(backsig) => Some(*backsig.clone()),
328                _ => None,
329            })
330        });
331
332        PublicSubkey::new(self.key.public_key().clone(), keyflags, embedded)
333    }
334
335    /// Decrypts session key using this key.
336    pub fn decrypt_session_key(
337        &self,
338        key_pw: &Password,
339        values: &PkeskBytes,
340        typ: EskType,
341    ) -> Result<Result<PlainSessionKey>> {
342        debug!("decrypt session key");
343
344        self.unlock(key_pw, |pub_params, priv_key| {
345            priv_key.decrypt(pub_params, values, typ, self.key.public_key())
346        })
347    }
348}
349
350impl Deref for SignedSecretSubKey {
351    type Target = packet::SecretSubkey;
352
353    fn deref(&self) -> &Self::Target {
354        &self.key
355    }
356}
357
358impl Serialize for SignedSecretSubKey {
359    fn to_writer<W: io::Write>(&self, writer: &mut W) -> Result<()> {
360        self.key.to_writer_with_header(writer)?;
361        for sig in &self.signatures {
362            sig.to_writer_with_header(writer)?;
363        }
364
365        Ok(())
366    }
367
368    fn write_len(&self) -> usize {
369        let mut sum = self.key.write_len_with_header();
370        for sig in &self.signatures {
371            sum += sig.write_len_with_header()
372        }
373        sum
374    }
375}
376
377impl Imprint for SignedSecretSubKey {
378    fn imprint<D: KnownDigest>(&self) -> Result<GenericArray<u8, D::OutputSize>> {
379        self.key.imprint::<D>()
380    }
381}
382
383impl From<SignedSecretKey> for SignedPublicKey {
384    fn from(value: SignedSecretKey) -> Self {
385        let primary = value.primary_key.public_key();
386        let details = value.details;
387
388        let mut subkeys = value.public_subkeys;
389
390        value
391            .secret_subkeys
392            .into_iter()
393            .for_each(|key| subkeys.push(key.into()));
394
395        SignedPublicKey::new(primary.clone(), details, subkeys)
396    }
397}
398
399impl From<SignedSecretSubKey> for SignedPublicSubKey {
400    fn from(value: SignedSecretSubKey) -> Self {
401        SignedPublicSubKey::new(value.key.public_key().clone(), value.signatures)
402    }
403}
404
405#[cfg(test)]
406mod tests {
407    #![allow(clippy::unwrap_used)]
408
409    use rand::SeedableRng;
410    use rand_chacha::ChaCha8Rng;
411
412    use super::*;
413    use crate::{
414        composed::{shared::Deserializable, Message, MessageBuilder},
415        crypto::hash::HashAlgorithm,
416        types::{KeyVersion, Password, S2kParams},
417    };
418
419    #[test]
420    fn test_v6_annex_a_4() -> Result<()> {
421        let _ = pretty_env_logger::try_init();
422
423        // A.4. Sample v6 Secret Key (Transferable Secret Key)
424
425        let tsk = "-----BEGIN PGP PRIVATE KEY BLOCK-----
426
427xUsGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laMAGXKB
428exK+cH6NX1hs5hNhIB00TrJmosgv3mg1ditlsLfCsQYfGwoAAABCBYJjh3/jAwsJ
429BwUVCg4IDAIWAAKbAwIeCSIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6
4302azJBScJAgcCAAAAAK0oIBA+LX0ifsDm185Ecds2v8lwgyU2kCcUmKfvBXbAf6rh
431RYWzuQOwEn7E/aLwIwRaLsdry0+VcallHhSu4RN6HWaEQsiPlR4zxP/TP7mhfVEe
4327XWPxtnMUMtf15OyA51YBMdLBmOHf+MZAAAAIIaTJINn+eUBXbki+PSAld2nhJh/
433LVmFsS+60WyvXkQ1AE1gCk95TUR3XFeibg/u/tVY6a//1q0NWC1X+yui3O24wpsG
434GBsKAAAALAWCY4d/4wKbDCIhBssYbE8GCaaX5NUt+mxyKwwfHifBilZwj2Ul7Ce6
4352azJAAAAAAQBIKbpGG2dWTX8j+VjFM21J0hqWlEg+bdiojWnKfA5AQpWUWtnNwDE
436M0g12vYxoWM8Y81W+bHBw805I8kWVkXU6vFOi+HWvv/ira7ofJu16NnoUkhclkUr
437k0mXubZvyl4GBg==
438-----END PGP PRIVATE KEY BLOCK-----";
439
440        let (ssk, _) = SignedSecretKey::from_armor_single(io::Cursor::new(tsk))?;
441
442        // eprintln!("ssk: {:#02x?}", ssk);
443
444        ssk.verify()?;
445
446        let mut rng = ChaCha8Rng::seed_from_u64(0);
447        let pri = &ssk.primary_key;
448
449        let mut builder = crate::composed::MessageBuilder::from_bytes("", &b"Hello world"[..]);
450        builder.sign(pri, Password::empty(), HashAlgorithm::Sha256);
451        let signed = builder.to_armored_string(&mut rng, ArmorOptions::default())?;
452
453        eprintln!("{signed}");
454
455        let (mut message, _) = Message::from_armor(signed.as_bytes())?;
456        message.verify_read(&pri.public_key())?;
457
458        Ok(())
459    }
460
461    // A.5. Sample locked v6 Secret Key (Transferable Secret Key)
462    const ANNEX_A_5: &str = "-----BEGIN PGP PRIVATE KEY BLOCK-----
463
464xYIGY4d/4xsAAAAg+U2nu0jWCmHlZ3BqZYfQMxmZu52JGggkLq2EVD34laP9JgkC
465FARdb9ccngltHraRe25uHuyuAQQVtKipJ0+r5jL4dacGWSAheCWPpITYiyfyIOPS
4663gIDyg8f7strd1OB4+LZsUhcIjOMpVHgmiY/IutJkulneoBYwrEGHxsKAAAAQgWC
467Y4d/4wMLCQcFFQoOCAwCFgACmwMCHgkiIQbLGGxPBgmml+TVLfpscisMHx4nwYpW
468cI9lJewnutmsyQUnCQIHAgAAAACtKCAQPi19In7A5tfORHHbNr/JcIMlNpAnFJin
4697wV2wH+q4UWFs7kDsBJ+xP2i8CMEWi7Ha8tPlXGpZR4UruETeh1mhELIj5UeM8T/
4700z+5oX1RHu11j8bZzFDLX9eTsgOdWATHggZjh3/jGQAAACCGkySDZ/nlAV25Ivj0
471gJXdp4SYfy1ZhbEvutFsr15ENf0mCQIUBA5hhGgp2oaavg6mFUXcFMwBBBUuE8qf
4729Ock+xwusd+GAglBr5LVyr/lup3xxQvHXFSjjA2haXfoN6xUGRdDEHI6+uevKjVR
473v5oAxgu7eJpaXNjCmwYYGwoAAAAsBYJjh3/jApsMIiEGyxhsTwYJppfk1S36bHIr
474DB8eJ8GKVnCPZSXsJ7rZrMkAAAAABAEgpukYbZ1ZNfyP5WMUzbUnSGpaUSD5t2Ki
475Nacp8DkBClZRa2c3AMQzSDXa9jGhYzxjzVb5scHDzTkjyRZWRdTq8U6L4da+/+Kt
476ruh8m7Xo2ehSSFyWRSuTSZe5tm/KXgYG
477-----END PGP PRIVATE KEY BLOCK-----";
478
479    const ANNEX_A_5_PASSPHRASE: &str = "correct horse battery staple";
480
481    #[test]
482    #[ignore] // slow in debug mode (argon2)
483    fn test_v6_annex_a_5() -> Result<()> {
484        let _ = pretty_env_logger::try_init();
485
486        let (ssk, _) = SignedSecretKey::from_armor_single(io::Cursor::new(ANNEX_A_5))?;
487        ssk.verify()?;
488
489        let mut rng = ChaCha8Rng::seed_from_u64(0);
490        let mut builder = MessageBuilder::from_bytes("", &b"Hello world"[..]);
491        builder.sign(
492            &ssk.primary_key,
493            ANNEX_A_5_PASSPHRASE.into(),
494            HashAlgorithm::Sha256,
495        );
496        let msg = builder.to_vec(&mut rng)?;
497        let mut msg = Message::from_bytes(&msg[..])?;
498        msg.verify_read(&ssk.primary_key.public_key())?;
499        Ok(())
500    }
501
502    #[test]
503    #[ignore] // slow in debug mode
504    fn secret_key_protection_v6() -> Result<()> {
505        let _ = pretty_env_logger::try_init();
506
507        let file_name = "";
508        let text = b"Hello world";
509        let mut rng = ChaCha8Rng::seed_from_u64(0);
510
511        let (ssk, _) = SignedSecretKey::from_armor_single(io::Cursor::new(ANNEX_A_5))?;
512        ssk.verify()?;
513
514        // we will test unlock/lock on the primary key
515        let mut pri = ssk.primary_key;
516
517        // remove passphrase
518        pri.remove_password(&ANNEX_A_5_PASSPHRASE.into())?;
519
520        // try signing without pw
521        let mut builder = MessageBuilder::from_bytes(file_name, &text[..]);
522        builder.sign(&pri, Password::empty(), HashAlgorithm::Sha256);
523        let msg = builder.to_vec(&mut rng)?;
524
525        let mut msg = Message::from_bytes(&msg[..])?;
526        msg.verify_read(pri.public_key())?;
527
528        // set passphrase with default s2k
529        pri.set_password(&mut rng, &ANNEX_A_5_PASSPHRASE.into())?;
530
531        // try signing with pw
532        let mut builder = MessageBuilder::from_bytes(file_name, &text[..]);
533        builder.sign(&pri, ANNEX_A_5_PASSPHRASE.into(), HashAlgorithm::Sha256);
534        let msg = builder.to_vec(&mut rng)?;
535
536        let mut msg = Message::from_bytes(&msg[..])?;
537        msg.verify_read(pri.public_key())?;
538
539        // remove passphrase
540        pri.remove_password(&ANNEX_A_5_PASSPHRASE.into())?;
541
542        // set passphrase with Cfb s2k (default for KeyVersion::V4)
543        pri.set_password_with_s2k(
544            &ANNEX_A_5_PASSPHRASE.into(),
545            S2kParams::new_default(&mut rng, KeyVersion::V4),
546        )?;
547
548        // try signing with pw
549        let mut builder = MessageBuilder::from_bytes(file_name, &text[..]);
550        builder.sign(&pri, ANNEX_A_5_PASSPHRASE.into(), HashAlgorithm::Sha256);
551        let msg = builder.to_vec(&mut rng)?;
552
553        let mut msg = Message::from_bytes(&msg[..])?;
554        msg.verify_read(pri.public_key())?;
555        Ok(())
556    }
557}