sequoia_openpgp/crypto/
s2k.rs

1//! String-to-Key (S2K) specifiers.
2//!
3//! String-to-key (S2K) specifiers are used to convert password
4//! strings into symmetric-key encryption/decryption keys.  See
5//! [Section 3.7 of RFC 9580].
6//!
7//!   [Section 3.7 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7
8
9use std::convert::TryInto;
10
11use crate::Error;
12use crate::Result;
13use crate::HashAlgorithm;
14use crate::crypto::Password;
15use crate::crypto::SessionKey;
16
17use std::fmt;
18
19#[cfg(test)]
20use quickcheck::{Arbitrary, Gen};
21
22/// String-to-Key (S2K) specifiers.
23///
24/// String-to-key (S2K) specifiers are used to convert password
25/// strings into symmetric-key encryption/decryption keys.  See
26/// [Section 3.7 of RFC 9580].  This is used to encrypt messages with
27/// a password (see [`SKESK`]), and to protect secret keys (see
28/// [`key::Encrypted`]).
29///
30///   [Section 3.7 of RFC 9580]: https://www.rfc-editor.org/rfc/rfc9580.html#section-3.7
31///   [`SKESK`]: crate::packet::SKESK
32///   [`key::Encrypted`]: crate::packet::key::Encrypted
33#[non_exhaustive]
34#[derive(Clone, PartialEq, Eq, Hash, Debug)]
35pub enum S2K {
36    /// Argon2 Memory-Hard Password Hashing Function.
37    Argon2 {
38        /// The salt.
39        salt: [u8; 16],
40        /// Number of passes.
41        t: u8,
42        /// Degree of parallelism.
43        p: u8,
44        /// Exponent of memory size.
45        m: u8,
46    },
47
48    /// Repeatently hashes the password with a public `salt` value.
49    Iterated {
50        /// Hash used for key derivation.
51        hash: HashAlgorithm,
52        /// Public salt value mixed into the password.
53        salt: [u8; 8],
54        /// Number of bytes to hash.
55        ///
56        /// This parameter increases the workload for an attacker
57        /// doing a dictionary attack.  Note that not all values are
58        /// representable.  See [`S2K::new_iterated`].
59        ///
60        ///   [`S2K::new_iterated`]: S2K::new_iterated()
61        hash_bytes: u32,
62    },
63
64    /// Hashes the password with a public `salt` value.
65    ///
66    /// This mechanism does not use iteration to increase the time it
67    /// takes to derive the key from the password.  This makes
68    /// dictionary attacks more feasible.  Do not use this variant.
69    #[deprecated(note = "Use `S2K::Iterated`.")]
70    Salted {
71        /// Hash used for key derivation.
72        hash: HashAlgorithm,
73        /// Public salt value mixed into the password.
74        salt: [u8; 8],
75    },
76
77    /// Simply hashes the password.
78    ///
79    /// This mechanism uses neither iteration to increase the time it
80    /// takes to derive the key from the password nor does it salt the
81    /// password.  This makes dictionary attacks more feasible.
82    ///
83    /// This mechanism has been deprecated in RFC 4880. Do not use this
84    /// variant.
85    #[deprecated(note = "Use `S2K::Iterated`.")]
86    Simple {
87        /// Hash used for key derivation.
88        hash: HashAlgorithm
89    },
90
91    /// Simply hashes the password using MD5
92    ///
93    /// This mechanism uses neither iteration to increase the time it
94    /// takes to derive the key from the password nor does it salt the
95    /// password, as well as using a very weak and fast hash
96    /// algorithm.  This makes dictionary attacks more feasible.
97    ///
98    /// This mechanism has been deprecated in RFC 2440. Do not use
99    /// this variant.
100    #[deprecated(note = "Use `S2K::Iterated`.")]
101    Implicit,
102
103    /// Private S2K algorithm.
104    Private {
105        /// Tag identifying the private algorithm.
106        ///
107        /// Tags 100 to 110 are reserved for private use.
108        tag: u8,
109
110        /// The parameters for the private algorithm.
111        ///
112        /// This is optional, because when we parse a packet
113        /// containing an unknown S2K algorithm, we do not know how
114        /// many octets to attribute to the S2K's parameters.  In this
115        /// case, `parameters` is set to `None`.  Note that the
116        /// information is not lost, but stored in the packet.  If the
117        /// packet is serialized again, it is written out.
118        parameters: Option<Box<[u8]>>,
119    },
120
121    /// Unknown S2K algorithm.
122    Unknown {
123        /// Tag identifying the unknown algorithm.
124        tag: u8,
125
126        /// The parameters for the unknown algorithm.
127        ///
128        /// This is optional, because when we parse a packet
129        /// containing an unknown S2K algorithm, we do not know how
130        /// many octets to attribute to the S2K's parameters.  In this
131        /// case, `parameters` is set to `None`.  Note that the
132        /// information is not lost, but stored in the packet.  If the
133        /// packet is serialized again, it is written out.
134        parameters: Option<Box<[u8]>>,
135    },
136}
137assert_send_and_sync!(S2K);
138
139impl Default for S2K {
140    fn default() -> Self {
141        S2K::new_iterated(
142            // SHA2-256, being optimized for implementations on
143            // architectures with a word size of 32 bit, has a more
144            // consistent runtime across different architectures than
145            // SHA2-512.  Furthermore, the digest size is large enough
146            // for every cipher algorithm currently in use.
147            HashAlgorithm::SHA256,
148            // This is the largest count that OpenPGP can represent.
149            // On moderate machines, like my Intel(R) Core(TM) i5-2400
150            // CPU @ 3.10GHz, it takes ~354ms to derive a key.
151            0x3e00000,
152        ).expect("0x3e00000 is representable")
153    }
154}
155
156impl S2K {
157    /// Creates a new iterated `S2K` object.
158    ///
159    /// Usually, you should use `S2K`s [`Default`] implementation to
160    /// create `S2K` objects with sane default parameters.  The
161    /// parameters are chosen with contemporary machines in mind, and
162    /// should also be usable on lower-end devices like smartphones.
163    ///
164    ///   [`Default`]: std::default::Default
165    ///
166    /// Using this method, you can tune the parameters for embedded
167    /// devices.  Note, however, that this also decreases the work
168    /// factor for attackers doing dictionary attacks.
169    pub fn new_iterated(hash: HashAlgorithm, approx_hash_bytes: u32)
170                        -> Result<Self> {
171        if approx_hash_bytes > 0x3e00000 {
172            Err(Error::InvalidArgument(format!(
173                "Number of bytes to hash not representable: {}",
174                approx_hash_bytes)).into())
175        } else {
176            let mut salt = [0u8; 8];
177            crate::crypto::random(&mut salt)?;
178            Ok(S2K::Iterated {
179                hash,
180                salt,
181                hash_bytes:
182                Self::nearest_hash_count(approx_hash_bytes as usize),
183            })
184        }
185    }
186
187    /// Derives a key of the given size from a password.
188    pub fn derive_key(&self, password: &Password, key_size: usize)
189    -> Result<SessionKey> {
190        #[allow(deprecated)]
191        match self {
192            &S2K::Argon2 { salt, t, p, m, } => {
193                let mut config = argon2::ParamsBuilder::new();
194                config.t_cost(t.into());
195                config.p_cost(p.into());
196                config.m_cost(
197                    2u32.checked_pow(m.into())
198                        .ok_or_else(|| Error::InvalidArgument(
199                            format!("Argon2 memory parameter out of bounds: {}",
200                                    m)))?);
201                config.output_len(
202                    key_size.try_into()
203                        .map_err(|_| Error::InvalidArgument(
204                            format!("key size parameter out of bounds: {}",
205                                    key_size)))?);
206                let params = config.build()
207                    .map_err(|e| Error::InvalidOperation(e.to_string()))?;
208
209                // Allocate the blocks for the Argon2 computation.
210                let mut blocks = Blocks::new(&params)?;
211
212                let argon2 = argon2::Argon2::new(
213                    argon2::Algorithm::Argon2id,
214                    argon2::Version::V0x13,
215                    params);
216                let mut sk: SessionKey = vec![0; key_size].into();
217                password.map(|password| {
218                    argon2.hash_password_into_with_memory(
219                        password, &salt, &mut sk, blocks.as_mut())
220                }).map_err(|e| Error::InvalidOperation(e.to_string()))?;
221
222                Ok(sk)
223            },
224            &S2K::Simple { hash } | &S2K::Salted { hash, .. }
225            | &S2K::Iterated { hash, .. } => password.map(|string| {
226                let mut hash = hash.context()?.for_digest();
227
228                // If the digest length is shorter than the key length,
229                // then we need to concatenate multiple hashes, each
230                // preloaded with i 0s.
231                let hash_sz = hash.digest_size();
232                let num_contexts = (key_size + hash_sz - 1) / hash_sz;
233                let mut zeros = Vec::with_capacity(num_contexts + 1);
234                let mut ret = vec![0u8; key_size];
235
236                for data in ret.chunks_mut(hash_sz) {
237                    hash.update(&zeros[..]);
238
239                    match self {
240                        &S2K::Argon2 { .. } => unreachable!("handled above"),
241                        &S2K::Simple { .. } => {
242                            hash.update(string);
243                        }
244                        &S2K::Salted { ref salt, .. } => {
245                            hash.update(salt);
246                            hash.update(string);
247                        }
248                        &S2K::Iterated { ref salt, hash_bytes, .. }
249                        if (hash_bytes as usize) < salt.len() + string.len() =>
250                        {
251                            // Independent of what the hash count is, we
252                            // always hash the whole salt and password once.
253                            hash.update(&salt[..]);
254                            hash.update(string);
255                        },
256                        &S2K::Iterated { ref salt, hash_bytes, .. } => {
257                            // Unroll the processing loop N times.
258                            const N: usize = 16;
259                            let data_len = salt.len() + string.len();
260                            let octs_per_iter = N * data_len;
261                            let mut data: SessionKey =
262                                vec![0u8; octs_per_iter].into();
263                            let full = hash_bytes as usize / octs_per_iter;
264                            let tail = hash_bytes as usize - (full * octs_per_iter);
265
266                            for i in 0..N {
267                                let o = data_len * i;
268                                data[o..o + salt.len()]
269                                    .clone_from_slice(salt);
270                                data[o + salt.len()..o + data_len]
271                                    .clone_from_slice(string);
272                            }
273
274                            for _ in 0..full {
275                                hash.update(&data);
276                            }
277
278                            if tail != 0 {
279                                hash.update(&data[0..tail]);
280                            }
281                        }
282                        S2K::Implicit |
283                        S2K::Unknown { .. } | &S2K::Private { .. } =>
284                            unreachable!(),
285                    }
286
287                    let _ = hash.digest(data);
288                    zeros.push(0);
289                }
290
291                Ok(ret.into())
292            }),
293            S2K::Implicit => S2K::Simple {
294                hash: HashAlgorithm::MD5,
295            }.derive_key(password, key_size),
296            S2K::Unknown { tag, .. } | S2K::Private { tag, .. } =>
297                Err(Error::MalformedPacket(
298                        format!("Unknown S2K type {:#x}", tag)).into()),
299        }
300    }
301
302    /// Returns whether this S2K mechanism is supported.
303    pub fn is_supported(&self) -> bool {
304        use self::S2K::*;
305        #[allow(deprecated)]
306        match self {
307            Simple { .. }
308            | Salted { .. }
309            | Iterated { .. }
310            | Implicit
311            | Argon2 { .. }
312            => true,
313            S2K::Private { .. }
314            | S2K::Unknown { .. }
315            => false,
316        }
317    }
318
319    /// This function returns an encodable iteration count.
320    ///
321    /// Not all iteration counts are encodable as *Iterated and Salted
322    /// S2K*.  The largest encodable hash count is `0x3e00000`.
323    ///
324    /// The returned value is larger or equal `hash_bytes`, or
325    /// `0x3e00000` if `hash_bytes` is larger than or equal
326    /// `0x3e00000`.
327    fn nearest_hash_count(hash_bytes: usize) -> u32 {
328        use std::usize;
329
330        match hash_bytes {
331            0..=1024 => 1024,
332            0x3e00001..=usize::MAX => 0x3e00000,
333            hash_bytes => {
334                for i in 0..256 {
335                    let n = Self::decode_count(i as u8);
336                    if n as usize >= hash_bytes {
337                        return n;
338                    }
339                }
340                0x3e00000
341            }
342        }
343     }
344
345    /// Decodes the OpenPGP encoding of the number of bytes to hash.
346    pub(crate) fn decode_count(coded: u8) -> u32 {
347        use std::cmp;
348
349        let mantissa = 16 + (coded as u32 & 15);
350        let exp = (coded as u32 >> 4) + 6;
351
352        mantissa << cmp::min(32 - 5, exp)
353    }
354
355    /// Converts `hash_bytes` into coded count representation.
356    ///
357    /// # Errors
358    ///
359    /// Fails with `Error::InvalidArgument` if `hash_bytes` cannot be
360    /// encoded. See also [`S2K::nearest_hash_count()`].
361    ///
362    pub(crate) fn encode_count(hash_bytes: u32) -> Result<u8> {
363        // eeee.mmmm -> (16 + mmmm) * 2^(6 + e)
364
365        let msb = 32 - hash_bytes.leading_zeros();
366        let (mantissa_mask, tail_mask) = match msb {
367            0..=10 => {
368                return Err(Error::InvalidArgument(
369                    format!("S2K: cannot encode iteration count of {}",
370                            hash_bytes)).into());
371            }
372            11..=32 => {
373                let m = 0b11_1100_0000 << (msb - 11);
374                let t = 1 << (msb - 11);
375
376                (m, t - 1)
377            }
378            _ => unreachable!()
379        };
380        let exp = if msb < 11 { 0 } else { msb - 11 };
381        let mantissa = (hash_bytes & mantissa_mask) >> (msb - 5);
382
383        if tail_mask & hash_bytes != 0 {
384            return Err(Error::InvalidArgument(
385                format!("S2K: cannot encode iteration count of {}",
386                        hash_bytes)).into());
387        }
388
389        Ok(mantissa as u8 | (exp as u8) << 4)
390    }
391}
392
393impl fmt::Display for S2K {
394    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
395        #[allow(deprecated)]
396        match self {
397            S2K::Simple{ hash } =>
398                f.write_fmt(format_args!("Simple S2K with {}", hash)),
399            S2K::Salted{ hash, salt } => {
400                f.write_fmt(
401                    format_args!("Salted S2K with {} and salt\
402                        {:x}{:x}{:x}{:x}{:x}{:x}{:x}{:x}",
403                    hash,
404                    salt[0], salt[1], salt[2], salt[3],
405                    salt[4], salt[5], salt[6], salt[7]))
406            }
407            S2K::Iterated{ hash, salt, hash_bytes, } => {
408                f.write_fmt(
409                    format_args!("Iterated and Salted S2K with {}, \
410                      salt {:x}{:x}{:x}{:x}{:x}{:x}{:x}{:x} and \
411                      {} bytes to hash",
412                    hash,
413                    salt[0], salt[1], salt[2], salt[3],
414                    salt[4], salt[5], salt[6], salt[7],
415                    hash_bytes))
416            }
417            S2K::Implicit => f.write_str("Implicit S2K"),
418            S2K::Argon2 { salt, t, p, m, } => {
419                write!(f,
420                       "Argon2id with t: {}, p: {}, m: 2^{}, salt: {}",
421                       t, p, m, crate::fmt::hex::encode(salt))
422            },
423            S2K::Private { tag, parameters } =>
424                if let Some(p) = parameters.as_ref() {
425                    write!(f, "Private/Experimental S2K {}:{:?}", tag, p)
426                } else {
427                    write!(f, "Private/Experimental S2K {}", tag)
428                },
429            S2K::Unknown { tag, parameters } =>
430                if let Some(p) = parameters.as_ref() {
431                    write!(f, "Unknown S2K {}:{:?}", tag, p)
432                } else {
433                    write!(f, "Unknown S2K {}", tag)
434                },
435        }
436    }
437}
438
439#[cfg(test)]
440impl Arbitrary for S2K {
441    fn arbitrary(g: &mut Gen) -> Self {
442        use crate::arbitrary_helper::*;
443
444        #[allow(deprecated)]
445        match gen_arbitrary_from_range(0..8, g) {
446            0 => S2K::Simple{ hash: HashAlgorithm::arbitrary(g) },
447            1 => S2K::Salted{
448                hash: HashAlgorithm::arbitrary(g),
449                salt: {
450                    let mut salt = [0u8; 8];
451                    arbitrary_slice(g, &mut salt);
452                    salt
453                },
454            },
455            2 => S2K::Iterated{
456                hash: HashAlgorithm::arbitrary(g),
457                salt: {
458                    let mut salt = [0u8; 8];
459                    arbitrary_slice(g, &mut salt);
460                    salt
461                },
462                hash_bytes: S2K::nearest_hash_count(Arbitrary::arbitrary(g)),
463            },
464            7 => S2K::Argon2 {
465                salt: {
466                    let mut salt = [0u8; 16];
467                    arbitrary_slice(g, &mut salt);
468                    salt
469                },
470                t: Arbitrary::arbitrary(g),
471                p: Arbitrary::arbitrary(g),
472                m: Arbitrary::arbitrary(g),
473            },
474            3 => S2K::Private {
475                tag: gen_arbitrary_from_range(100..111, g),
476                parameters: Some(arbitrary_bounded_vec(g, 200).into()),
477            },
478            4 => S2K::Unknown {
479                tag: 2,
480                parameters: Some(arbitrary_bounded_vec(g, 200).into()),
481            },
482            5 => S2K::Unknown {
483                tag: gen_arbitrary_from_range(5..100, g),
484                parameters: Some(arbitrary_bounded_vec(g, 200).into()),
485            },
486            6 => S2K::Unknown {
487                tag: gen_arbitrary_from_range(111..256, g) as u8,
488                parameters: Some(arbitrary_bounded_vec(g, 200).into()),
489            },
490            _ => unreachable!(),
491        }
492    }
493}
494
495/// Memory for the Argon2 computation.
496///
497/// We use fallible allocation to gracefully fail if we cannot
498/// allocate the required space.
499struct Blocks {
500    blocks: *mut argon2::Block,
501    count: usize,
502}
503
504impl Blocks {
505    fn new(p: &argon2::Params) -> Result<Self> {
506        use std::alloc::Layout;
507
508        let error = || anyhow::Error::from(
509            Error::InvalidOperation(
510                "failed to allocate memory for key derivation"
511                    .into()));
512
513        let count = p.block_count();
514        let l = Layout::array::<argon2::Block>(count)
515            .map_err(|_| error())?;
516        let blocks = unsafe {
517            std::alloc::alloc_zeroed(l)
518                as *mut argon2::Block
519        };
520        if blocks.is_null() {
521            Err(error())
522        } else {
523            Ok(Blocks { blocks, count, })
524        }
525    }
526}
527
528impl Drop for Blocks {
529    fn drop(&mut self) {
530        use std::alloc::Layout;
531
532        let l = Layout::array::<argon2::Block>(self.count)
533            .expect("was valid before");
534        unsafe {
535            std::alloc::dealloc(self.blocks as *mut _, l)
536        };
537    }
538}
539
540impl AsMut<[argon2::Block]> for Blocks {
541    fn as_mut(&mut self) -> &mut [argon2::Block] {
542        unsafe {
543            std::slice::from_raw_parts_mut(
544                self.blocks, self.count)
545        }
546    }
547}
548
549#[cfg(test)]
550mod tests {
551    use super::*;
552
553    use crate::fmt::to_hex;
554    use crate::SymmetricAlgorithm;
555    use crate::Packet;
556    use crate::parse::{Parse, PacketParser};
557
558    #[test]
559    fn s2k_parser_test() {
560        use crate::packet::SKESK;
561
562        struct Test<'a> {
563            filename: &'a str,
564            s2k: S2K,
565            cipher_algo: SymmetricAlgorithm,
566            password: Password,
567            key_hex: &'a str,
568        }
569
570        // Note: this test only works with SK-ESK packets that don't
571        // contain an encrypted session key, i.e., the session key is
572        // the result of the s2k function.  gpg generates this type of
573        // SK-ESK packet when invoked with -c, but not -e.  (When
574        // invoked with -c and -e, it generates SK-ESK packets that
575        // include an encrypted session key.)
576        #[allow(deprecated)]
577        let tests = [
578            Test {
579                filename: "mode-0-password-1234.pgp",
580                cipher_algo: SymmetricAlgorithm::AES256,
581                s2k: S2K::Simple{ hash: HashAlgorithm::SHA1, },
582                password: "1234".into(),
583                key_hex: "7110EDA4D09E062AA5E4A390B0A572AC0D2C0220F352B0D292B65164C2A67301",
584            },
585            Test {
586                filename: "mode-1-password-123456-1.pgp",
587                cipher_algo: SymmetricAlgorithm::AES256,
588                s2k: S2K::Salted{
589                    hash: HashAlgorithm::SHA1,
590                    salt: [0xa8, 0x42, 0xa7, 0xa9, 0x59, 0xfa, 0x42, 0x2a],
591                },
592                password: "123456".into(),
593                key_hex: "8B79077CA448F6FB3D3AD2A264D3B938D357C9FB3E41219FD962DF960A9AFA08",
594            },
595            Test {
596                filename: "mode-1-password-foobar-2.pgp",
597                cipher_algo: SymmetricAlgorithm::AES256,
598                s2k: S2K::Salted{
599                    hash: HashAlgorithm::SHA1,
600                    salt: [0xbc, 0x95, 0x58, 0x45, 0x81, 0x3c, 0x7c, 0x37],
601                },
602                password: "foobar".into(),
603                key_hex: "B7D48AAE9B943B22A4D390083E8460B5EDFA118FE1688BF0C473B8094D1A8D10",
604            },
605            Test {
606                filename: "mode-3-password-qwerty-1.pgp",
607                cipher_algo: SymmetricAlgorithm::AES256,
608                s2k: S2K::Iterated {
609                    hash: HashAlgorithm::SHA1,
610                    salt: [0x78, 0x45, 0xf0, 0x5b, 0x55, 0xf7, 0xb4, 0x9e],
611                    hash_bytes: S2K::decode_count(241),
612                },
613                password: "qwerty".into(),
614                key_hex: "575AD156187A3F8CEC11108309236EB499F1E682F0D1AFADFAC4ECF97613108A",
615            },
616            Test {
617                filename: "mode-3-password-9876-2.pgp",
618                cipher_algo: SymmetricAlgorithm::AES256,
619                s2k: S2K::Iterated {
620                    hash: HashAlgorithm::SHA1,
621                    salt: [0xb9, 0x67, 0xea, 0x96, 0x53, 0xdb, 0x6a, 0xc8],
622                    hash_bytes: S2K::decode_count(43),
623                },
624                password: "9876".into(),
625                key_hex: "736C226B8C64E4E6D0325C6C552EF7C0738F98F48FED65FD8C93265103EFA23A",
626            },
627            Test {
628                filename: "mode-3-aes192-password-123.pgp",
629                cipher_algo: SymmetricAlgorithm::AES192,
630                s2k: S2K::Iterated {
631                    hash: HashAlgorithm::SHA1,
632                    salt: [0x8f, 0x81, 0x74, 0xc5, 0xd9, 0x61, 0xc7, 0x79],
633                    hash_bytes: S2K::decode_count(238),
634                },
635                password: "123".into(),
636                key_hex: "915E96FC694E7F90A6850B740125EA005199C725F3BD27E3",
637            },
638            Test {
639                filename: "mode-3-twofish-password-13-times-0123456789.pgp",
640                cipher_algo: SymmetricAlgorithm::Twofish,
641                s2k: S2K::Iterated {
642                    hash: HashAlgorithm::SHA1,
643                    salt: [0x51, 0xed, 0xfc, 0x15, 0x45, 0x40, 0x65, 0xac],
644                    hash_bytes: S2K::decode_count(238),
645                },
646                password: "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".into(),
647                key_hex: "EA264FADA5A859C40D88A159B344ECF1F51FF327FDB3C558B0A7DC299777173E",
648            },
649            Test {
650                filename: "mode-3-aes128-password-13-times-0123456789.pgp",
651                cipher_algo: SymmetricAlgorithm::AES128,
652                s2k: S2K::Iterated {
653                    hash: HashAlgorithm::SHA1,
654                    salt: [0x06, 0xe4, 0x61, 0x5c, 0xa4, 0x48, 0xf9, 0xdd],
655                    hash_bytes: S2K::decode_count(238),
656                },
657                password: "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".into(),
658                key_hex: "F3D0CE52ED6143637443E3399437FD0F",
659            },
660        ];
661
662        for test in tests.iter().filter(|t| t.cipher_algo.is_supported()) {
663            let path = crate::tests::message(&format!("s2k/{}", test.filename));
664            let pp = PacketParser::from_bytes(path).unwrap().unwrap();
665            if let Packet::SKESK(SKESK::V4(ref skesk)) = pp.packet {
666                assert_eq!(skesk.symmetric_algo(), test.cipher_algo);
667                assert_eq!(skesk.s2k(), &test.s2k);
668
669                let key = skesk.s2k().derive_key(
670                    &test.password,
671                    skesk.symmetric_algo().key_size().unwrap());
672                if let Ok(key) = key {
673                    let key = to_hex(&key[..], false);
674                    assert_eq!(key, test.key_hex);
675                } else {
676                    panic!("Session key: None!");
677                }
678            } else {
679                panic!("Wrong packet!");
680            }
681
682            // Get the next packet.
683            let (_, ppr) = pp.next().unwrap();
684            assert!(ppr.is_eof());
685        }
686    }
687
688    quickcheck! {
689        fn s2k_display(s2k: S2K) -> bool {
690            let s = format!("{}", s2k);
691            !s.is_empty()
692        }
693    }
694
695    quickcheck! {
696        fn s2k_parse(s2k: S2K) -> bool {
697            match s2k {
698                S2K::Unknown { tag, .. } =>
699                    (tag > 3 && tag < 100) || tag == 2 || tag > 110,
700                S2K::Private { tag, .. } =>
701                    (100..=110).contains(&tag),
702                _ => true
703            }
704        }
705    }
706
707    #[test]
708    fn s2k_coded_count_roundtrip() {
709        for cc in 0..0x100usize {
710            let hash_bytes = S2K::decode_count(cc as u8);
711            assert!(hash_bytes >= 1024
712                    && S2K::encode_count(hash_bytes).unwrap() == cc as u8);
713        }
714    }
715
716    quickcheck!{
717        fn s2k_coded_count_approx(i: u32) -> bool {
718            let approx = S2K::nearest_hash_count(i as usize);
719            let cc = S2K::encode_count(approx).unwrap();
720
721            (approx >= i || i > 0x3e00000) && S2K::decode_count(cc) == approx
722        }
723    }
724
725    #[test]
726    fn s2k_coded_count_approx_1025() {
727        let i = 1025;
728        let approx = S2K::nearest_hash_count(i);
729        let cc = S2K::encode_count(approx).unwrap();
730
731        assert!(approx as usize >= i || i > 0x3e00000);
732        assert_eq!(S2K::decode_count(cc), approx);
733    }
734
735    #[test]
736    fn s2k_coded_count_approx_0x3e00000() {
737        let i = 0x3e00000;
738        let approx = S2K::nearest_hash_count(i);
739        let cc = S2K::encode_count(approx).unwrap();
740
741        assert!(approx as usize >= i || i > 0x3e00000);
742        assert_eq!(S2K::decode_count(cc), approx);
743    }
744}