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 X25519 and X448 we have to prepend
141        // the cipher octet to the ciphertext instead of
142        // encrypting it.
143        PublicKeyAlgorithm::X25519 |
144        PublicKeyAlgorithm::X448 =>
145            Ok((false, seipdv1, false)),
146
147        a @ PublicKeyAlgorithm::RSASign |
148        a @ PublicKeyAlgorithm::DSA |
149        a @ PublicKeyAlgorithm::ECDSA |
150        a @ PublicKeyAlgorithm::EdDSA |
151        a @ PublicKeyAlgorithm::Ed25519 |
152        a @ PublicKeyAlgorithm::Ed448 |
153        a @ PublicKeyAlgorithm::Private(_) |
154        a @ PublicKeyAlgorithm::Unknown(_) =>
155            Err(Error::UnsupportedPublicKeyAlgorithm(a).into()),
156    }
157}
158
159
160impl packet::PKESK {
161    fn encrypt_common(algo: Option<SymmetricAlgorithm>,
162                      session_key: &SessionKey,
163                      recipient: &Key<key::UnspecifiedParts,
164                                      key::UnspecifiedRole>)
165                      -> Result<Ciphertext>
166    {
167        let (checksummed, unencrypted_cipher_octet, encrypted_cipher_octet) =
168            classify_pk_algo(recipient.pk_algo(), algo.is_some())?;
169
170        // We may need to prefix the cipher specifier to the session
171        // key, and we may add a two-octet checksum.
172        let mut psk = Vec::with_capacity(
173            encrypted_cipher_octet.then(|| 1).unwrap_or(0)
174                + session_key.len()
175                + checksummed.then(|| 2).unwrap_or(0));
176        if let Some(algo) = algo {
177            if encrypted_cipher_octet {
178                psk.push(algo.into());
179            }
180        }
181        psk.extend_from_slice(session_key);
182
183        if checksummed {
184            // Compute the sum modulo 65536, i.e. as u16.
185            let checksum = session_key
186                .iter()
187                .cloned()
188                .map(u16::from)
189                .fold(0u16, u16::wrapping_add);
190
191            psk.extend_from_slice(&checksum.to_be_bytes());
192        }
193
194        // Make sure it is cleaned up when dropped.
195        let psk: SessionKey = psk.into();
196        let mut esk = recipient.encrypt(&psk)?;
197
198        if let Some(algo) = algo {
199            if unencrypted_cipher_octet {
200                match esk {
201                    Ciphertext::X25519 { ref mut key, .. } |
202                    Ciphertext::X448 { ref mut key, .. } => {
203                        let mut new_key = Vec::with_capacity(1 + key.len());
204                        new_key.push(algo.into());
205                        new_key.extend_from_slice(key);
206                        *key = new_key.into();
207                    },
208                    _ => unreachable!("We only prepend the cipher octet \
209                                       for X25519 and X448"),
210                };
211            }
212        }
213
214        Ok(esk)
215    }
216
217    fn decrypt_common(ciphertext: &Ciphertext,
218                      decryptor: &mut dyn Decryptor,
219                      sym_algo_hint: Option<SymmetricAlgorithm>,
220                      seipdv1: bool)
221                      -> Result<(Option<SymmetricAlgorithm>, SessionKey)>
222    {
223        let (checksummed, unencrypted_cipher_octet, encrypted_cipher_octet) =
224            classify_pk_algo(decryptor.public().pk_algo(), seipdv1)?;
225
226        //dbg!((checksummed, unencrypted_cipher_octet, encrypted_cipher_octet));
227
228        let mut sym_algo: Option<SymmetricAlgorithm> = None;
229        let modified_ciphertext;
230        let esk;
231        if unencrypted_cipher_octet {
232            match ciphertext {
233                Ciphertext::X25519 { e, key, } => {
234                    sym_algo =
235                        Some((*key.get(0).ok_or_else(
236                            || Error::MalformedPacket("Short ESK".into()))?)
237                             .into());
238                    modified_ciphertext = Ciphertext::X25519 {
239                        e: e.clone(),
240                        key: key[1..].into(),
241                    };
242                    esk = &modified_ciphertext;
243                },
244                Ciphertext::X448 { e, key, } => {
245                    sym_algo =
246                        Some((*key.get(0).ok_or_else(
247                            || Error::MalformedPacket("Short ESK".into()))?)
248                             .into());
249                    modified_ciphertext = Ciphertext::X448 {
250                        e: e.clone(),
251                        key: key[1..].into(),
252                    };
253                    esk = &modified_ciphertext;
254                },
255
256                _ => {
257                    // We only prepend the cipher octet for X25519 and
258                    // X448, yet we're trying to decrypt a ciphertext
259                    // that uses a different algorithm, clearly
260                    // something has gone wrong and will fail when we
261                    // try to decrypt it downstream.
262                    esk = ciphertext;
263                },
264            }
265        } else {
266            esk = ciphertext;
267        }
268
269        let plaintext_len = if let Some(s) = sym_algo_hint {
270            Some(encrypted_cipher_octet.then(|| 1).unwrap_or(0)
271                 + s.key_size()?
272                 + checksummed.then(|| 2).unwrap_or(0))
273        } else {
274            None
275        };
276        let plain = decryptor.decrypt(esk, plaintext_len)?;
277        let key_rgn = encrypted_cipher_octet.then(|| 1).unwrap_or(0)
278            ..plain.len().saturating_sub(checksummed.then(|| 2).unwrap_or(0));
279        if encrypted_cipher_octet {
280            sym_algo = Some(plain[0].into());
281        }
282        let sym_algo = sym_algo.or(sym_algo_hint);
283
284        if let Some(sym_algo) = sym_algo {
285            if key_rgn.len() != sym_algo.key_size()? {
286                return Err(Error::MalformedPacket(
287                    format!("session key has the wrong size (got: {}, expected: {})",
288                            key_rgn.len(), sym_algo.key_size()?)).into())
289            }
290        }
291
292        let mut key: SessionKey = vec![0u8; key_rgn.len()].into();
293        key.copy_from_slice(&plain[key_rgn]);
294
295        if checksummed {
296            let our_checksum
297                = key.iter().map(|&x| x as usize).sum::<usize>() & 0xffff;
298            let their_checksum = (plain[plain.len() - 2] as usize) << 8
299                | (plain[plain.len() - 1] as usize);
300
301            if their_checksum != our_checksum {
302                return Err(Error::MalformedPacket(
303                    "key checksum wrong".to_string()).into());
304            }
305        }
306        Ok((sym_algo, key))
307    }
308}
309
310#[cfg(test)]
311impl Arbitrary for super::PKESK {
312    fn arbitrary(g: &mut Gen) -> Self {
313        if bool::arbitrary(g) {
314            PKESK3::arbitrary(g).into()
315        } else {
316            PKESK6::arbitrary(g).into()
317        }
318    }
319}