sequoia_openpgp/packet/skesk/
v4.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    S2K,
15    Password,
16    SessionKey,
17};
18
19use crate::Error;
20use crate::types::{
21    SymmetricAlgorithm,
22};
23use crate::packet::{self, SKESK};
24use crate::Packet;
25
26/// Holds a symmetrically encrypted session key version 4.
27///
28/// Holds a symmetrically encrypted session key.  The session key is
29/// needed to decrypt the actual ciphertext.  See [Section 5.3 of RFC
30/// 9580] for details.
31///
32/// [Section 5.3 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.3
33#[derive(Clone, Debug)]
34pub struct SKESK4 {
35    /// CTB header fields.
36    pub(crate) common: packet::Common,
37
38    /// Packet version. Must be 4 or 5.
39    ///
40    /// This struct is also used by SKESK6, hence we have a version
41    /// field.
42    pub(crate) version: u8,
43
44    /// Symmetric algorithm used to encrypt the session key.
45    pub(crate) sym_algo: SymmetricAlgorithm,
46
47    /// Key derivation method for the symmetric key.
48    pub(crate) s2k: S2K,
49
50    /// The encrypted session key.
51    ///
52    /// If we recognized the S2K object during parsing, we can
53    /// successfully parse the data into S2K and ciphertext.  However,
54    /// if we do not recognize the S2K type, we do not know how large
55    /// its parameters are, so we cannot cleanly parse it, and have to
56    /// accept that the S2K's body bleeds into the rest of the data.
57    pub(crate) esk: std::result::Result<Option<Box<[u8]>>, // optional ciphertext.
58                                        Box<[u8]>>,        // S2K body + maybe ciphertext.
59}
60assert_send_and_sync!(SKESK4);
61
62// Because the S2K and ESK cannot be cleanly separated at parse time,
63// we need to carefully compare and hash SKESK4 packets.
64
65impl PartialEq for SKESK4 {
66    fn eq(&self, other: &SKESK4) -> bool {
67        self.version == other.version
68            && self.sym_algo == other.sym_algo
69            // Treat S2K and ESK as opaque blob.
70            && {
71                // XXX: This would be nicer without the allocations.
72                use crate::serialize::MarshalInto;
73                let mut a = self.s2k.to_vec().unwrap();
74                let mut b = other.s2k.to_vec().unwrap();
75                a.extend_from_slice(self.raw_esk());
76                b.extend_from_slice(other.raw_esk());
77                a == b
78            }
79    }
80}
81
82impl Eq for SKESK4 {}
83
84impl std::hash::Hash for SKESK4 {
85    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
86        self.version.hash(state);
87        self.sym_algo.hash(state);
88        // Treat S2K and ESK as opaque blob.
89        // XXX: This would be nicer without the allocations.
90        use crate::serialize::MarshalInto;
91        let mut a = self.s2k.to_vec().unwrap();
92        a.extend_from_slice(self.raw_esk());
93        a.hash(state);
94    }
95}
96
97impl SKESK4 {
98    /// Creates a new SKESK version 4 packet.
99    ///
100    /// The given symmetric algorithm is the one used to encrypt the
101    /// session key.
102    pub fn new(esk_algo: SymmetricAlgorithm, s2k: S2K,
103               esk: Option<Box<[u8]>>) -> Result<SKESK4> {
104        Self::new_raw(esk_algo, s2k, Ok(esk.and_then(|esk| {
105            if esk.len() == 0 { None } else { Some(esk) }
106        })))
107    }
108
109    /// Creates a new SKESK version 4 packet.
110    ///
111    /// The given symmetric algorithm is the one used to encrypt the
112    /// session key.
113    pub(crate) fn new_raw(esk_algo: SymmetricAlgorithm, s2k: S2K,
114                          esk: std::result::Result<Option<Box<[u8]>>,
115                                                   Box<[u8]>>)
116                          -> Result<SKESK4> {
117        Ok(SKESK4{
118            common: Default::default(),
119            version: 4,
120            sym_algo: esk_algo,
121            s2k,
122            esk,
123        })
124    }
125
126    /// Creates a new SKESK4 packet with the given password.
127    ///
128    /// This function takes two [`SymmetricAlgorithm`] arguments: The
129    /// first, `payload_algo`, is the algorithm used to encrypt the
130    /// message's payload (i.e. the one used in the [`SEIP`]), and the
131    /// second, `esk_algo`, is used to encrypt the session key.
132    /// Usually, one should use the same algorithm, but if they
133    /// differ, the `esk_algo` should be at least as strong as the
134    /// `payload_algo` as not to weaken the security of the payload
135    /// encryption.
136    ///
137    ///   [`SymmetricAlgorithm`]: crate::types::SymmetricAlgorithm
138    ///   [`SEIP`]: crate::packet::SEIP
139    pub fn with_password(payload_algo: SymmetricAlgorithm,
140                         esk_algo: SymmetricAlgorithm,
141                         s2k: S2K,
142                         session_key: &SessionKey, password: &Password)
143                         -> Result<SKESK4> {
144        if session_key.len() != payload_algo.key_size()? {
145            return Err(Error::InvalidArgument(format!(
146                "Invalid size of session key, got {} want {}",
147                session_key.len(), payload_algo.key_size()?)).into());
148        }
149
150        // Derive key and make a cipher.
151        let key = s2k.derive_key(password, esk_algo.key_size()?)?;
152        let block_size = esk_algo.block_size()?;
153
154        use crate::crypto::symmetric::BlockCipherMode;
155        use crate::crypto::backend::{Backend, interface::Symmetric};
156        let mut cipher = Backend::encryptor(esk_algo, BlockCipherMode::CFB,
157                                            key.as_protected(), None)?;
158
159        // We need to prefix the cipher specifier to the session key.
160        let mut psk: SessionKey = vec![0; 1 + session_key.len()].into();
161        psk[0] = payload_algo.into();
162        psk[1..].copy_from_slice(session_key);
163        let mut esk = vec![0u8; psk.len()];
164
165        for (pt, ct) in psk[..].chunks(block_size)
166            .zip(esk.chunks_mut(block_size)) {
167                cipher.encrypt(ct, pt)?;
168        }
169
170        SKESK4::new(esk_algo, s2k, Some(esk.into()))
171    }
172
173    /// Gets the symmetric encryption algorithm.
174    pub fn symmetric_algo(&self) -> SymmetricAlgorithm {
175        self.sym_algo
176    }
177
178    /// Sets the symmetric encryption algorithm.
179    pub fn set_symmetric_algo(&mut self, algo: SymmetricAlgorithm) -> SymmetricAlgorithm {
180        ::std::mem::replace(&mut self.sym_algo, algo)
181    }
182
183    /// Gets the key derivation method.
184    pub fn s2k(&self) -> &S2K {
185        &self.s2k
186    }
187
188    /// Sets the key derivation method.
189    pub fn set_s2k(&mut self, s2k: S2K) -> S2K {
190        ::std::mem::replace(&mut self.s2k, s2k)
191    }
192
193    /// Gets the encrypted session key.
194    ///
195    /// If the [`S2K`] mechanism is not supported by Sequoia, this
196    /// function will fail.  Note that the information is not lost,
197    /// but stored in the packet.  If the packet is serialized again,
198    /// it is written out.
199    ///
200    ///   [`S2K`]: crate::crypto::S2K
201    pub fn esk(&self) -> Result<Option<&[u8]>> {
202        self.esk.as_ref()
203            .map(|esko| esko.as_ref().map(|esk| &esk[..]))
204            .map_err(|_| Error::MalformedPacket(
205                format!("Unknown S2K: {:?}", self.s2k)).into())
206    }
207
208    /// Returns the encrypted session key, possibly including the body
209    /// of the S2K object.
210    pub(crate) fn raw_esk(&self) -> &[u8] {
211        match self.esk.as_ref() {
212            Ok(Some(esk)) => &esk[..],
213            Ok(None) => &[][..],
214            Err(s2k_esk) => &s2k_esk[..],
215        }
216    }
217
218    /// Sets the encrypted session key.
219    pub fn set_esk(&mut self, esk: Option<Box<[u8]>>) -> Option<Box<[u8]>> {
220        ::std::mem::replace(
221            &mut self.esk,
222            Ok(esk.and_then(|esk| {
223                if esk.len() == 0 { None } else { Some(esk) }
224            })))
225            .unwrap_or(None)
226    }
227
228    /// Derives the key inside this SKESK4 from `password`.
229    ///
230    /// Returns a tuple of the symmetric cipher to use with the key
231    /// and the key itself.
232    pub fn decrypt(&self, password: &Password)
233        -> Result<(SymmetricAlgorithm, SessionKey)>
234    {
235        let key = self.s2k.derive_key(password, self.sym_algo.key_size()?)?;
236
237        if let Some(esk) = self.esk()? {
238            // Use the derived key to decrypt the ESK. Unlike SEP &
239            // SEIP we have to use plain CFB here.
240            let blk_sz = self.sym_algo.block_size()?;
241
242            use crate::crypto::symmetric::BlockCipherMode;
243            use crate::crypto::backend::{Backend, interface::Symmetric};
244            let mut dec = Backend::decryptor(self.sym_algo, BlockCipherMode::CFB,
245                                             key.as_protected(), None)?;
246
247            let mut plain: SessionKey = vec![0u8; esk.len()].into();
248            let cipher = esk;
249
250            for (pl, ct)
251                in plain[..].chunks_mut(blk_sz).zip(cipher.chunks(blk_sz))
252            {
253                dec.decrypt(pl, ct)?;
254            }
255
256            // Get the algorithm from the front.
257            let sym = SymmetricAlgorithm::from(plain[0]);
258            Ok((sym, plain[1..].into()))
259        } else {
260            // No ESK, we return the derived key.
261            Ok((self.sym_algo, key))
262        }
263    }
264}
265
266impl From<SKESK4> for super::SKESK {
267    fn from(p: SKESK4) -> Self {
268        super::SKESK::V4(p)
269    }
270}
271
272impl From<SKESK4> for Packet {
273    fn from(s: SKESK4) -> Self {
274        Packet::SKESK(SKESK::V4(s))
275    }
276}
277
278#[cfg(test)]
279impl Arbitrary for SKESK4 {
280    fn arbitrary(g: &mut Gen) -> Self {
281        SKESK4::new(SymmetricAlgorithm::arbitrary(g),
282                    S2K::arbitrary(g),
283                    Option::<Vec<u8>>::arbitrary(g).map(|v| v.into()))
284            .unwrap()
285    }
286}
287
288#[cfg(test)]
289mod test {
290    use super::*;
291    use crate::parse::Parse;
292    use crate::serialize::MarshalInto;
293
294    quickcheck! {
295        fn roundtrip_v4(p: SKESK4) -> bool {
296            let p = SKESK::from(p);
297            let q = SKESK::from_bytes(&p.to_vec().unwrap()).unwrap();
298            assert_eq!(p, q);
299            true
300        }
301    }
302
303    /// Tests various S2K methods, with and without encrypted session
304    /// key.
305    #[test]
306    fn skesk4_s2k_variants() -> Result<()> {
307        use std::io::Read;
308        use crate::{
309            Cert,
310            packet::{SKESK, PKESK},
311            parse::stream::*,
312        };
313
314        struct H();
315        impl VerificationHelper for H {
316            fn get_certs(&mut self, _ids: &[crate::KeyHandle])
317                         -> Result<Vec<Cert>> {
318                Ok(Vec::new())
319            }
320
321            fn check(&mut self, _m: MessageStructure)
322                     -> Result<()> {
323                Ok(())
324            }
325        }
326
327        impl DecryptionHelper for H {
328            fn decrypt(&mut self, _: &[PKESK], skesks: &[SKESK],
329                       _: Option<SymmetricAlgorithm>,
330                       decrypt: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool)
331                       -> Result<Option<Cert>>
332            {
333                assert_eq!(skesks.len(), 1);
334                let (cipher, sk) = skesks[0].decrypt(&"password".into())?;
335                assert_eq!(cipher, Some(SymmetricAlgorithm::AES256));
336                let r = decrypt(cipher, &sk);
337                assert!(r);
338                Ok(None)
339            }
340        }
341
342        let p = &crate::policy::StandardPolicy::new();
343        for variant in &["simple", "salted", "iterated.min", "iterated.max"] {
344            for esk in &["", ".esk"] {
345                let name = format!("s2k/{}{}.pgp", variant, esk);
346                eprintln!("{}", name);
347                let mut verifier = DecryptorBuilder::from_bytes(
348                    crate::tests::message(&name))?
349                    .with_policy(p, None, H())?;
350                let mut b = Vec::new();
351                verifier.read_to_end(&mut b)?;
352                assert_eq!(&b, b"Hello World :)");
353            }
354        }
355
356        Ok(())
357    }
358}