Skip to main content

sequoia_openpgp/packet/
pkesk.rs

1//! PublicKey-Encrypted Session Key packets.
2//!
3//! The session key is needed to decrypt the actual ciphertext.  See
4//! [Section 5.1 of RFC 9580] for details.
5//!
6//!   [Section 5.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.1
7
8#[cfg(test)]
9use quickcheck::{Arbitrary, Gen};
10
11use crate::Error;
12use crate::KeyHandle;
13use crate::packet::key;
14use crate::packet::Key;
15use crate::packet::Packet;
16use crate::crypto::Decryptor;
17use crate::crypto::mpi::Ciphertext;
18use crate::PublicKeyAlgorithm;
19use crate::Result;
20use crate::SymmetricAlgorithm;
21use crate::crypto::SessionKey;
22use crate::packet;
23
24mod v3;
25pub use v3::PKESK3;
26mod v6;
27pub use v6::PKESK6;
28
29/// Holds an asymmetrically encrypted session key.
30///
31/// The session key is used to decrypt the actual ciphertext, which is
32/// typically stored in a [`SEIP`] packet.  See [Section 5.1 of
33/// RFC 9580] for details.
34///
35/// A PKESK packet is not normally instantiated directly.  In most
36/// cases, you'll create one as a side effect of encrypting a message
37/// using the [streaming serializer], or parsing an encrypted message
38/// using the [`PacketParser`].
39///
40/// [`SEIP`]: crate::packet::SEIP
41/// [Section 5.1 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.1
42/// [streaming serializer]: crate::serialize::stream
43/// [`PacketParser`]: crate::parse::PacketParser
44#[non_exhaustive]
45#[derive(PartialEq, Eq, Hash, Clone, Debug)]
46pub enum PKESK {
47    /// PKESK packet version 3.
48    V3(PKESK3),
49    /// PKESK packet version 6.
50    V6(PKESK6),
51}
52assert_send_and_sync!(PKESK);
53
54impl PKESK {
55    /// Gets the version.
56    pub fn version(&self) -> u8 {
57        match self {
58            PKESK::V3(_) => 3,
59            PKESK::V6(_) => 6,
60        }
61    }
62
63    /// Gets the recipient.
64    pub fn recipient(&self) -> Option<KeyHandle> {
65        match self {
66            PKESK::V3(p) => p.recipient().map(Into::into),
67            PKESK::V6(p) => p.recipient().map(Into::into),
68        }
69    }
70
71    /// Gets the public key algorithm.
72    pub fn pk_algo(&self) -> PublicKeyAlgorithm {
73        match self {
74            PKESK::V3(p) => p.pk_algo(),
75            PKESK::V6(p) => p.pk_algo(),
76        }
77    }
78
79    /// Gets the encrypted session key.
80    pub fn esk(&self) -> &crate::crypto::mpi::Ciphertext {
81        match self {
82            PKESK::V3(p) => p.esk(),
83            PKESK::V6(p) => p.esk(),
84        }
85    }
86
87    /// Decrypts the encrypted session key.
88    ///
89    /// If the symmetric algorithm used to encrypt the message is
90    /// known in advance, it should be given as argument.  This allows
91    /// us to reduce the side-channel leakage of the decryption
92    /// operation for RSA.
93    ///
94    /// Returns the session key and symmetric algorithm used to
95    /// encrypt the following payload.
96    ///
97    /// Returns `None` on errors.  This prevents leaking information
98    /// to an attacker, which could lead to compromise of secret key
99    /// material with certain algorithms (RSA).  See [Section 13 of
100    /// RFC 9580].
101    ///
102    ///   [Section 13 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-13
103    pub fn decrypt(&self, decryptor: &mut dyn Decryptor,
104                   sym_algo_hint: Option<SymmetricAlgorithm>)
105        -> Option<(Option<SymmetricAlgorithm>, SessionKey)>
106    {
107        match self {
108            PKESK::V3(p) => p.decrypt(decryptor, sym_algo_hint)
109                .map(|(s, k)| (Some(s), k)),
110            PKESK::V6(p) => p.decrypt(decryptor, sym_algo_hint)
111                .map(|k| (None, k)),
112        }
113    }
114}
115
116impl From<PKESK> for Packet {
117    fn from(p: PKESK) -> Self {
118        Packet::PKESK(p)
119    }
120}
121
122/// Returns whether the given `algo` requires checksumming, and
123/// whether the cipher octet is prepended to the encrypted session
124/// key, or it is prepended to the plain session key and then
125/// encrypted.
126fn classify_pk_algo(algo: PublicKeyAlgorithm, seipdv1: bool)
127                    -> Result<(bool, bool, bool)>
128{
129    #[allow(deprecated)]
130    match algo {
131        // Classical encryption: plaintext includes the cipher
132        // octet and is checksummed.
133        PublicKeyAlgorithm::RSAEncryptSign |
134        PublicKeyAlgorithm::RSAEncrypt |
135        PublicKeyAlgorithm::ElGamalEncrypt |
136        PublicKeyAlgorithm::ElGamalEncryptSign |
137        PublicKeyAlgorithm::ECDH =>
138            Ok((true, false, seipdv1)),
139
140        // Corner case: for the modern algorithms like X25519 and X448
141        // we have to prepend the cipher octet to the ciphertext
142        // instead of encrypting it.
143        PublicKeyAlgorithm::X25519 |
144        PublicKeyAlgorithm::X448 |
145        PublicKeyAlgorithm::MLKEM768_X25519 =>
146            Ok((false, seipdv1, false)),
147
148        // MLKEM1024+X448 must not be used seipv1 packets.
149        a @ PublicKeyAlgorithm::MLKEM1024_X448 =>
150            if seipdv1 {
151                Err(Error::UnsupportedPublicKeyAlgorithm(a).into())
152            } else {
153                Ok((false, seipdv1, false))
154            },
155
156        a @ PublicKeyAlgorithm::RSASign |
157        a @ PublicKeyAlgorithm::DSA |
158        a @ PublicKeyAlgorithm::ECDSA |
159        a @ PublicKeyAlgorithm::EdDSA |
160        a @ PublicKeyAlgorithm::Ed25519 |
161        a @ PublicKeyAlgorithm::Ed448 |
162        a @ PublicKeyAlgorithm::MLDSA65_Ed25519 |
163        a @ PublicKeyAlgorithm::MLDSA87_Ed448 |
164        a @ PublicKeyAlgorithm::SLHDSA128s |
165        a @ PublicKeyAlgorithm::SLHDSA128f |
166        a @ PublicKeyAlgorithm::SLHDSA256s |
167        a @ PublicKeyAlgorithm::Private(_) |
168        a @ PublicKeyAlgorithm::Unknown(_) =>
169            Err(Error::UnsupportedPublicKeyAlgorithm(a).into()),
170    }
171}
172
173
174impl packet::PKESK {
175    fn encrypt_common(algo: Option<SymmetricAlgorithm>,
176                      session_key: &SessionKey,
177                      recipient: &Key<key::UnspecifiedParts,
178                                      key::UnspecifiedRole>)
179                      -> Result<Ciphertext>
180    {
181        let (checksummed, unencrypted_cipher_octet, encrypted_cipher_octet) =
182            classify_pk_algo(recipient.pk_algo(), algo.is_some())?;
183
184        // We may need to prefix the cipher specifier to the session
185        // key, and we may add a two-octet checksum.
186        let mut psk = Vec::with_capacity(
187            encrypted_cipher_octet.then(|| 1).unwrap_or(0)
188                + session_key.len()
189                + checksummed.then(|| 2).unwrap_or(0));
190        if let Some(algo) = algo {
191            if encrypted_cipher_octet {
192                psk.push(algo.into());
193            }
194        }
195        psk.extend_from_slice(session_key);
196
197        if checksummed {
198            // Compute the sum modulo 65536, i.e. as u16.
199            let checksum = session_key
200                .iter()
201                .cloned()
202                .map(u16::from)
203                .fold(0u16, u16::wrapping_add);
204
205            psk.extend_from_slice(&checksum.to_be_bytes());
206        }
207
208        // Make sure it is cleaned up when dropped.
209        let psk: SessionKey = psk.into();
210        let mut esk = recipient.encrypt(&psk)?;
211
212        if let Some(algo) = algo {
213            if unencrypted_cipher_octet {
214                match esk {
215                    Ciphertext::X25519 { ref mut key, .. } |
216                    Ciphertext::X448 { ref mut key, .. } |
217                    Ciphertext::MLKEM768_X25519 { esk: ref mut key, .. } => {
218                        let mut new_key = Vec::with_capacity(1 + key.len());
219                        new_key.push(algo.into());
220                        new_key.extend_from_slice(key);
221                        *key = new_key.into();
222                    },
223                    _ => unreachable!(
224                        "We only prepend the cipher octet \
225                         for X25519, X448 and ML-KEM-768+X25519"),
226                };
227            }
228        }
229
230        Ok(esk)
231    }
232
233    fn decrypt_common(ciphertext: &Ciphertext,
234                      decryptor: &mut dyn Decryptor,
235                      sym_algo_hint: Option<SymmetricAlgorithm>,
236                      seipdv1: bool)
237                      -> Result<(Option<SymmetricAlgorithm>, SessionKey)>
238    {
239        let (checksummed, unencrypted_cipher_octet, encrypted_cipher_octet) =
240            classify_pk_algo(decryptor.public().pk_algo(), seipdv1)?;
241
242        //dbg!((checksummed, unencrypted_cipher_octet, encrypted_cipher_octet));
243
244        let mut sym_algo: Option<SymmetricAlgorithm> = None;
245        let modified_ciphertext;
246        let esk;
247        if unencrypted_cipher_octet {
248            match ciphertext {
249                Ciphertext::X25519 { e, key, } => {
250                    sym_algo =
251                        Some((*key.get(0).ok_or_else(
252                            || Error::MalformedPacket("Short ESK".into()))?)
253                             .into());
254                    modified_ciphertext = Ciphertext::X25519 {
255                        e: e.clone(),
256                        key: key[1..].into(),
257                    };
258                    esk = &modified_ciphertext;
259                },
260                Ciphertext::X448 { e, key, } => {
261                    sym_algo =
262                        Some((*key.get(0).ok_or_else(
263                            || Error::MalformedPacket("Short ESK".into()))?)
264                             .into());
265                    modified_ciphertext = Ciphertext::X448 {
266                        e: e.clone(),
267                        key: key[1..].into(),
268                    };
269                    esk = &modified_ciphertext;
270                },
271
272                Ciphertext::MLKEM768_X25519 { ecdh, mlkem, esk: key, } => {
273                    sym_algo =
274                        Some((*key.get(0).ok_or_else(
275                            || Error::MalformedPacket("Short ESK".into()))?)
276                             .into());
277                    modified_ciphertext = Ciphertext::MLKEM768_X25519 {
278                        ecdh: ecdh.clone(),
279                        mlkem: mlkem.clone(),
280                        esk: key[1..].into(),
281                    };
282                    esk = &modified_ciphertext;
283                },
284
285                Ciphertext::MLKEM1024_X448 { ecdh, mlkem, esk: key, } => {
286                    sym_algo =
287                        Some((*key.get(0).ok_or_else(
288                            || Error::MalformedPacket("Short ESK".into()))?)
289                             .into());
290                    modified_ciphertext = Ciphertext::MLKEM1024_X448 {
291                        ecdh: ecdh.clone(),
292                        mlkem: mlkem.clone(),
293                        esk: key[1..].into(),
294                    };
295                    esk = &modified_ciphertext;
296                },
297
298                _ => {
299                    // We only prepend the cipher octet for X25519 and
300                    // X448, yet we're trying to decrypt a ciphertext
301                    // that uses a different algorithm, clearly
302                    // something has gone wrong and will fail when we
303                    // try to decrypt it downstream.
304                    esk = ciphertext;
305                },
306            }
307        } else {
308            esk = ciphertext;
309        }
310
311        let plaintext_len = if let Some(s) = sym_algo_hint {
312            Some(encrypted_cipher_octet.then(|| 1).unwrap_or(0)
313                 + s.key_size()?
314                 + checksummed.then(|| 2).unwrap_or(0))
315        } else {
316            None
317        };
318        let plain = decryptor.decrypt(esk, plaintext_len)?;
319        let key_rgn = encrypted_cipher_octet.then(|| 1).unwrap_or(0)
320            ..plain.len().saturating_sub(checksummed.then(|| 2).unwrap_or(0));
321        if encrypted_cipher_octet {
322            sym_algo = Some(plain[0].into());
323        }
324        let sym_algo = sym_algo.or(sym_algo_hint);
325
326        if let Some(sym_algo) = sym_algo {
327            if key_rgn.len() != sym_algo.key_size()? {
328                return Err(Error::MalformedPacket(
329                    format!("session key has the wrong size (got: {}, expected: {})",
330                            key_rgn.len(), sym_algo.key_size()?)).into())
331            }
332        }
333
334        let mut key: SessionKey = vec![0u8; key_rgn.len()].into();
335        key.copy_from_slice(&plain[key_rgn]);
336
337        if checksummed {
338            let our_checksum
339                = key.iter().map(|&x| x as usize).sum::<usize>() & 0xffff;
340            let their_checksum = (plain[plain.len() - 2] as usize) << 8
341                | (plain[plain.len() - 1] as usize);
342
343            if their_checksum != our_checksum {
344                return Err(Error::MalformedPacket(
345                    "key checksum wrong".to_string()).into());
346            }
347        }
348        Ok((sym_algo, key))
349    }
350}
351
352#[cfg(test)]
353impl Arbitrary for super::PKESK {
354    fn arbitrary(g: &mut Gen) -> Self {
355        if bool::arbitrary(g) {
356            PKESK3::arbitrary(g).into()
357        } else {
358            PKESK6::arbitrary(g).into()
359        }
360    }
361}