Skip to main content

sequoia_openpgp/packet/skesk/
v6.rs

1//! Symmetric-Key Encrypted Session Key Packets.
2//!
3//! SKESK packets hold symmetrically encrypted session keys.  The
4//! session key is needed to decrypt the actual ciphertext.  See
5//! [Section 5.3 of RFC 9580] for details.
6//!
7//! [Section 5.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.3
8
9#[cfg(test)]
10use quickcheck::{Arbitrary, Gen};
11
12use crate::Result;
13use crate::crypto::{
14    self,
15    S2K,
16    Password,
17    SessionKey,
18    backend::{Backend, interface::Kdf},
19};
20use crate::Error;
21use crate::types::{
22    AEADAlgorithm,
23    SymmetricAlgorithm,
24};
25use crate::packet::{
26    Packet,
27    SKESK,
28    skesk::SKESK4,
29};
30
31/// Holds a symmetrically encrypted session key version 6.
32///
33/// Holds a symmetrically encrypted session key.  The session key is
34/// needed to decrypt the actual ciphertext.  See [Version 6 Symmetric
35/// Key Encrypted Session Key Packet Format] for details.
36///
37/// [Version 6 Symmetric Key Encrypted Session Key Packet Format]: https://www.rfc-editor.org/rfc/rfc9580.html#name-version-6-symmetric-key-enc
38#[derive(Clone, Debug, PartialEq, Eq, Hash)]
39pub struct SKESK6 {
40    /// Common fields.
41    pub(crate) skesk4: SKESK4,
42
43    /// AEAD algorithm.
44    aead_algo: AEADAlgorithm,
45
46    /// Initialization vector for the AEAD algorithm.
47    aead_iv: Box<[u8]>,
48}
49assert_send_and_sync!(SKESK6);
50
51impl SKESK6 {
52    /// Creates a new SKESK version 6 packet.
53    ///
54    /// The given symmetric algorithm is the one used to encrypt the
55    /// session key.
56    pub fn new(sym_algo: SymmetricAlgorithm,
57               aead_algo: AEADAlgorithm,
58               s2k: S2K,
59               aead_iv: Box<[u8]>,
60               esk: Box<[u8]>)
61               -> Result<Self> {
62        Ok(SKESK6 {
63            skesk4: SKESK4 {
64                common: Default::default(),
65                version: 6,
66                sym_algo,
67                s2k,
68                esk: Ok(Some(esk)),
69            },
70            aead_algo,
71            aead_iv,
72        })
73    }
74
75    /// Creates a new SKESK version 6 packet with the given password.
76    ///
77    /// This function takes two [`SymmetricAlgorithm`] arguments: The
78    /// first, `payload_algo`, is the algorithm used to encrypt the
79    /// message's payload (i.e. the one used in the [`SEIP`]), and the
80    /// second, `esk_algo`, is used to encrypt the session key.
81    /// Usually, one should use the same algorithm, but if they
82    /// differ, the `esk_algo` should be at least as strong as the
83    /// `payload_algo` as not to weaken the security of the payload
84    /// encryption.
85    ///
86    ///   [`SymmetricAlgorithm`]: crate::types::SymmetricAlgorithm
87    ///   [`SEIP`]: crate::packet::SEIP
88    pub fn with_password(payload_algo: SymmetricAlgorithm,
89                         esk_algo: SymmetricAlgorithm,
90                         esk_aead: AEADAlgorithm, s2k: S2K,
91                         session_key: &SessionKey, password: &Password)
92                         -> Result<Self> {
93        if session_key.len() != payload_algo.key_size()? {
94            return Err(Error::InvalidArgument(format!(
95                "Invalid size of session key, got {} want {}",
96                session_key.len(), payload_algo.key_size()?)).into());
97        }
98
99        // Derive key and make a cipher.
100        let ad = [0xc3, 6, esk_algo.into(), esk_aead.into()];
101        let key = s2k.derive_key(password, esk_algo.key_size()?)?;
102
103        let mut kek: SessionKey = vec![0; esk_algo.key_size()?].into();
104        Backend::hkdf_sha256(&key, None, &ad, &mut kek)?;
105
106
107        // Encrypt the session key with the KEK.
108        let mut iv = vec![0u8; esk_aead.nonce_size()?];
109        crypto::random(&mut iv)?;
110        let mut ctx =
111            esk_aead.context(esk_algo, &kek, &ad, &iv)?.for_encryption()?;
112        let mut esk_digest =
113            vec![0u8; session_key.len() + esk_aead.digest_size()?];
114        ctx.encrypt_seal(&mut esk_digest, session_key)?;
115
116        // Attach digest to the ESK, we model it as one.
117        SKESK6::new(esk_algo, esk_aead, s2k, iv.into_boxed_slice(),
118                    esk_digest.into())
119    }
120
121    /// Derives the key inside this `SKESK6` from `password`.
122    ///
123    /// Returns a tuple containing a placeholder symmetric cipher and
124    /// the key itself.  `SKESK6` packets do not contain the symmetric
125    /// cipher algorithm and instead rely on the `SEIPDv2` packet that
126    /// contains it.
127    pub fn decrypt(&self, password: &Password)
128                   -> Result<SessionKey> {
129        let key = self.s2k().derive_key(password,
130                                        self.symmetric_algo().key_size()?)?;
131
132        let mut kek: SessionKey =
133            vec![0; self.symmetric_algo().key_size()?].into();
134        let ad = [0xc3,
135                  6 /* Version.  */,
136                  self.symmetric_algo().into(),
137                  self.aead_algo.into()];
138        Backend::hkdf_sha256(&key, None, &ad, &mut kek)?;
139
140        // Use the derived key to decrypt the ESK.
141        let mut cipher = self.aead_algo.context(
142            self.symmetric_algo(), &kek, &ad, self.aead_iv())?
143            .for_decryption()?;
144
145        let digest_size = self.aead_algo.digest_size()?;
146
147        if self.esk().len() < digest_size {
148            return Err(Error::InvalidArgument(format!(
149                "Encryption session key too small, got {} bytes",
150                self.esk().len())).into());
151        }
152
153        let mut plain: SessionKey =
154            vec![0; self.esk().len() - digest_size].into();
155        cipher.decrypt_verify(&mut plain, self.esk())?;
156        Ok(plain)
157    }
158
159    /// Gets the symmetric encryption algorithm.
160    pub fn symmetric_algo(&self) -> SymmetricAlgorithm {
161        self.skesk4.sym_algo
162    }
163
164    /// Sets the symmetric encryption algorithm.
165    pub fn set_symmetric_algo(&mut self, algo: SymmetricAlgorithm) -> SymmetricAlgorithm {
166        ::std::mem::replace(&mut self.skesk4.sym_algo, algo)
167    }
168
169    /// Gets the key derivation method.
170    pub fn s2k(&self) -> &S2K {
171        &self.skesk4.s2k
172    }
173
174    /// Sets the key derivation method.
175    pub fn set_s2k(&mut self, s2k: S2K) -> S2K {
176        ::std::mem::replace(&mut self.skesk4.s2k, s2k)
177    }
178
179    /// Gets the AEAD algorithm.
180    pub fn aead_algo(&self) -> AEADAlgorithm {
181        self.aead_algo
182    }
183
184    /// Sets the AEAD algorithm.
185    pub fn set_aead_algo(&mut self, algo: AEADAlgorithm) -> AEADAlgorithm {
186        ::std::mem::replace(&mut self.aead_algo, algo)
187    }
188
189    /// Gets the AEAD initialization vector.
190    pub fn aead_iv(&self) -> &[u8] {
191        &self.aead_iv
192    }
193
194    /// Sets the AEAD initialization vector.
195    pub fn set_aead_iv(&mut self, iv: Box<[u8]>) -> Box<[u8]> {
196        ::std::mem::replace(&mut self.aead_iv, iv)
197    }
198
199    /// Gets the encrypted session key.
200    pub fn esk(&self) -> &[u8] {
201        self.skesk4.raw_esk()
202    }
203
204    /// Sets the encrypted session key.
205    pub fn set_esk(&mut self, esk: Box<[u8]>) -> Box<[u8]> {
206        ::std::mem::replace(&mut self.skesk4.esk, Ok(Some(esk)))
207            .expect("v6 SKESK can always be parsed")
208            .expect("v6 SKESK packets always have an ESK")
209    }
210}
211
212impl From<SKESK6> for super::SKESK {
213    fn from(p: SKESK6) -> Self {
214        super::SKESK::V6(p)
215    }
216}
217
218impl From<SKESK6> for Packet {
219    fn from(s: SKESK6) -> Self {
220        Packet::SKESK(SKESK::V6(s))
221    }
222}
223
224#[cfg(test)]
225impl Arbitrary for SKESK6 {
226    fn arbitrary(g: &mut Gen) -> Self {
227        let symm = SymmetricAlgorithm::arbitrary(g);
228        let aead = AEADAlgorithm::arbitrary(g);
229        let mut iv = vec![0u8; aead.nonce_size().unwrap_or(16)];
230        for b in iv.iter_mut() {
231            *b = u8::arbitrary(g);
232        }
233        let esk_len =
234            symm.key_size().unwrap_or(16) + aead.digest_size().unwrap_or(16);
235        let mut esk = vec![0u8; esk_len];
236        for b in esk.iter_mut() {
237            *b = u8::arbitrary(g);
238        }
239        SKESK6::new(symm,
240                    aead,
241                    S2K::arbitrary(g),
242                    iv.into(),
243                    esk.into())
244            .unwrap()
245    }
246}
247
248#[cfg(test)]
249mod test {
250    use super::*;
251    use crate::PacketPile;
252    use crate::parse::Parse;
253    use crate::serialize::MarshalInto;
254
255    quickcheck! {
256        fn roundtrip_v6(p: SKESK6) -> bool {
257            let p = SKESK::from(p);
258            let q = SKESK::from_bytes(&p.to_vec().unwrap()).unwrap();
259            assert_eq!(p, q);
260            true
261        }
262    }
263
264    /// This sample packet is from RFC9580.
265    #[test]
266    fn v6skesk_aes128_ocb() -> Result<()> {
267        sample_skesk6_packet(
268            SymmetricAlgorithm::AES128,
269            AEADAlgorithm::OCB,
270            "crypto-refresh/v6skesk-aes128-ocb.pgp",
271            b"\xe8\x0d\xe2\x43\xa3\x62\xd9\x3b\
272              \x9d\xc6\x07\xed\xe9\x6a\x73\x56",
273            b"\x28\xe7\x9a\xb8\x23\x97\xd3\xc6\
274              \x3d\xe2\x4a\xc2\x17\xd7\xb7\x91")
275    }
276
277    /// This sample packet is from RFC9580.
278    #[test]
279    fn v6skesk_aes128_eax() -> Result<()> {
280        sample_skesk6_packet(
281            SymmetricAlgorithm::AES128,
282            AEADAlgorithm::EAX,
283            "crypto-refresh/v6skesk-aes128-eax.pgp",
284            b"\x15\x49\x67\xe5\x90\xaa\x1f\x92\
285              \x3e\x1c\x0a\xc6\x4c\x88\xf2\x3d",
286            b"\x38\x81\xba\xfe\x98\x54\x12\x45\
287              \x9b\x86\xc3\x6f\x98\xcb\x9a\x5e")
288    }
289
290    /// This sample packet is from RFC9580.
291    #[test]
292    fn v6skesk_aes128_gcm() -> Result<()> {
293        sample_skesk6_packet(
294            SymmetricAlgorithm::AES128,
295            AEADAlgorithm::GCM,
296            "crypto-refresh/v6skesk-aes128-gcm.pgp",
297            b"\x25\x02\x81\x71\x5b\xba\x78\x28\
298              \xef\x71\xef\x64\xc4\x78\x47\x53",
299            b"\x19\x36\xfc\x85\x68\x98\x02\x74\
300              \xbb\x90\x0d\x83\x19\x36\x0c\x77")
301    }
302
303    fn sample_skesk6_packet(cipher: SymmetricAlgorithm,
304                            aead: AEADAlgorithm,
305                            name: &str,
306                            derived_key: &[u8],
307                            session_key: &[u8])
308                            -> Result<()> {
309        let password: Password = String::from("password").into();
310        let packets: Vec<Packet> =
311            PacketPile::from_bytes(
312                crate::tests::file(name))?
313            .into_children().collect();
314        assert_eq!(packets.len(), 2);
315        if let Packet::SKESK(SKESK::V6(ref s)) = packets[0] {
316            let derived = s.s2k().derive_key(
317                &password, s.symmetric_algo().key_size()?)?;
318            eprintln!("derived: {:x?}", &derived[..]);
319            assert_eq!(&derived[..], derived_key);
320
321            use crate::crypto::backend::{Backend, interface::Aead};
322            if Backend::supports_algo_with_symmetric(aead, cipher)
323            {
324                let sk = s.decrypt(&password)?;
325                eprintln!("sk: {:x?}", &sk[..]);
326                assert_eq!(&sk[..], session_key);
327            } else {
328                eprintln!("{}-{} is not supported, skipping decryption.",
329                          cipher, aead);
330            }
331        } else {
332            panic!("bad packet, expected v6 SKESK: {:?}", packets[0]);
333        }
334
335        Ok(())
336    }
337
338    // Check that we error out (and don't panic) when the ESK is too
339    // short.
340    #[test]
341    fn short_esk() {
342        use crate::packet::SKESK;
343
344        // Create an SKESK packet with an ESK that is too short.
345        let sym_algo = SymmetricAlgorithm::AES128;
346        let aead_algo = AEADAlgorithm::OCB;
347        if ! aead_algo.is_supported() {
348            eprintln!("{} not supported, skipping test", aead_algo);
349        }
350        let s2k = S2K::Argon2 { salt: [0u8; 16], t: 1, p: 1, m: 8 };
351
352        let iv_len = aead_algo.nonce_size()
353            .expect("algorithm is supported");
354        let digest_len = aead_algo.digest_size()
355            .expect("algorithm is supported");
356        let iv = vec![0u8; iv_len].into_boxed_slice();
357        // Make the ESK too short.
358        let esk = vec![0u8; digest_len - 1].into_boxed_slice();
359
360        let skesk6 = SKESK6::new(sym_algo, aead_algo, s2k, iv, esk)
361            .expect("Can create SKESK6");
362        let skesk = SKESK::V6(skesk6);
363
364        // Decrypting the too-short session key should fail (not
365        // panic).
366        skesk.decrypt(&Password::from("pw"))
367            .expect_err("Should fail to decrypt");
368    }
369}