Skip to main content

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        let size = l.size();
535        unsafe {
536            let ptr = self.blocks as *mut u8;
537            memsec::memzero(ptr, size);
538            std::alloc::dealloc(ptr, l)
539        };
540    }
541}
542
543impl AsMut<[argon2::Block]> for Blocks {
544    fn as_mut(&mut self) -> &mut [argon2::Block] {
545        unsafe {
546            std::slice::from_raw_parts_mut(
547                self.blocks, self.count)
548        }
549    }
550}
551
552#[cfg(test)]
553mod tests {
554    use super::*;
555
556    use crate::fmt::to_hex;
557    use crate::SymmetricAlgorithm;
558    use crate::Packet;
559    use crate::parse::{Parse, PacketParser};
560
561    #[test]
562    fn s2k_parser_test() {
563        use crate::packet::SKESK;
564
565        struct Test<'a> {
566            filename: &'a str,
567            s2k: S2K,
568            cipher_algo: SymmetricAlgorithm,
569            password: Password,
570            key_hex: &'a str,
571        }
572
573        // Note: this test only works with SK-ESK packets that don't
574        // contain an encrypted session key, i.e., the session key is
575        // the result of the s2k function.  gpg generates this type of
576        // SK-ESK packet when invoked with -c, but not -e.  (When
577        // invoked with -c and -e, it generates SK-ESK packets that
578        // include an encrypted session key.)
579        #[allow(deprecated)]
580        let tests = [
581            Test {
582                filename: "mode-0-password-1234.pgp",
583                cipher_algo: SymmetricAlgorithm::AES256,
584                s2k: S2K::Simple{ hash: HashAlgorithm::SHA1, },
585                password: "1234".into(),
586                key_hex: "7110EDA4D09E062AA5E4A390B0A572AC0D2C0220F352B0D292B65164C2A67301",
587            },
588            Test {
589                filename: "mode-1-password-123456-1.pgp",
590                cipher_algo: SymmetricAlgorithm::AES256,
591                s2k: S2K::Salted{
592                    hash: HashAlgorithm::SHA1,
593                    salt: [0xa8, 0x42, 0xa7, 0xa9, 0x59, 0xfa, 0x42, 0x2a],
594                },
595                password: "123456".into(),
596                key_hex: "8B79077CA448F6FB3D3AD2A264D3B938D357C9FB3E41219FD962DF960A9AFA08",
597            },
598            Test {
599                filename: "mode-1-password-foobar-2.pgp",
600                cipher_algo: SymmetricAlgorithm::AES256,
601                s2k: S2K::Salted{
602                    hash: HashAlgorithm::SHA1,
603                    salt: [0xbc, 0x95, 0x58, 0x45, 0x81, 0x3c, 0x7c, 0x37],
604                },
605                password: "foobar".into(),
606                key_hex: "B7D48AAE9B943B22A4D390083E8460B5EDFA118FE1688BF0C473B8094D1A8D10",
607            },
608            Test {
609                filename: "mode-3-password-qwerty-1.pgp",
610                cipher_algo: SymmetricAlgorithm::AES256,
611                s2k: S2K::Iterated {
612                    hash: HashAlgorithm::SHA1,
613                    salt: [0x78, 0x45, 0xf0, 0x5b, 0x55, 0xf7, 0xb4, 0x9e],
614                    hash_bytes: S2K::decode_count(241),
615                },
616                password: "qwerty".into(),
617                key_hex: "575AD156187A3F8CEC11108309236EB499F1E682F0D1AFADFAC4ECF97613108A",
618            },
619            Test {
620                filename: "mode-3-password-9876-2.pgp",
621                cipher_algo: SymmetricAlgorithm::AES256,
622                s2k: S2K::Iterated {
623                    hash: HashAlgorithm::SHA1,
624                    salt: [0xb9, 0x67, 0xea, 0x96, 0x53, 0xdb, 0x6a, 0xc8],
625                    hash_bytes: S2K::decode_count(43),
626                },
627                password: "9876".into(),
628                key_hex: "736C226B8C64E4E6D0325C6C552EF7C0738F98F48FED65FD8C93265103EFA23A",
629            },
630            Test {
631                filename: "mode-3-aes192-password-123.pgp",
632                cipher_algo: SymmetricAlgorithm::AES192,
633                s2k: S2K::Iterated {
634                    hash: HashAlgorithm::SHA1,
635                    salt: [0x8f, 0x81, 0x74, 0xc5, 0xd9, 0x61, 0xc7, 0x79],
636                    hash_bytes: S2K::decode_count(238),
637                },
638                password: "123".into(),
639                key_hex: "915E96FC694E7F90A6850B740125EA005199C725F3BD27E3",
640            },
641            Test {
642                filename: "mode-3-twofish-password-13-times-0123456789.pgp",
643                cipher_algo: SymmetricAlgorithm::Twofish,
644                s2k: S2K::Iterated {
645                    hash: HashAlgorithm::SHA1,
646                    salt: [0x51, 0xed, 0xfc, 0x15, 0x45, 0x40, 0x65, 0xac],
647                    hash_bytes: S2K::decode_count(238),
648                },
649                password: "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".into(),
650                key_hex: "EA264FADA5A859C40D88A159B344ECF1F51FF327FDB3C558B0A7DC299777173E",
651            },
652            Test {
653                filename: "mode-3-aes128-password-13-times-0123456789.pgp",
654                cipher_algo: SymmetricAlgorithm::AES128,
655                s2k: S2K::Iterated {
656                    hash: HashAlgorithm::SHA1,
657                    salt: [0x06, 0xe4, 0x61, 0x5c, 0xa4, 0x48, 0xf9, 0xdd],
658                    hash_bytes: S2K::decode_count(238),
659                },
660                password: "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789".into(),
661                key_hex: "F3D0CE52ED6143637443E3399437FD0F",
662            },
663        ];
664
665        for test in tests.iter().filter(|t| t.cipher_algo.is_supported()) {
666            let path = crate::tests::message(&format!("s2k/{}", test.filename));
667            let pp = PacketParser::from_bytes(path).unwrap().unwrap();
668            if let Packet::SKESK(SKESK::V4(ref skesk)) = pp.packet {
669                assert_eq!(skesk.symmetric_algo(), test.cipher_algo);
670                assert_eq!(skesk.s2k(), &test.s2k);
671
672                let key = skesk.s2k().derive_key(
673                    &test.password,
674                    skesk.symmetric_algo().key_size().unwrap());
675                if let Ok(key) = key {
676                    let key = to_hex(&key[..], false);
677                    assert_eq!(key, test.key_hex);
678                } else {
679                    panic!("Session key: None!");
680                }
681            } else {
682                panic!("Wrong packet!");
683            }
684
685            // Get the next packet.
686            let (_, ppr) = pp.next().unwrap();
687            assert!(ppr.is_eof());
688        }
689    }
690
691    quickcheck! {
692        fn s2k_display(s2k: S2K) -> bool {
693            let s = format!("{}", s2k);
694            !s.is_empty()
695        }
696    }
697
698    quickcheck! {
699        fn s2k_parse(s2k: S2K) -> bool {
700            match s2k {
701                S2K::Unknown { tag, .. } =>
702                    (tag > 3 && tag < 100) || tag == 2 || tag > 110,
703                S2K::Private { tag, .. } =>
704                    (100..=110).contains(&tag),
705                _ => true
706            }
707        }
708    }
709
710    #[test]
711    fn s2k_coded_count_roundtrip() {
712        for cc in 0..0x100usize {
713            let hash_bytes = S2K::decode_count(cc as u8);
714            assert!(hash_bytes >= 1024
715                    && S2K::encode_count(hash_bytes).unwrap() == cc as u8);
716        }
717    }
718
719    quickcheck!{
720        fn s2k_coded_count_approx(i: u32) -> bool {
721            let approx = S2K::nearest_hash_count(i as usize);
722            let cc = S2K::encode_count(approx).unwrap();
723
724            (approx >= i || i > 0x3e00000) && S2K::decode_count(cc) == approx
725        }
726    }
727
728    #[test]
729    fn s2k_coded_count_approx_1025() {
730        let i = 1025;
731        let approx = S2K::nearest_hash_count(i);
732        let cc = S2K::encode_count(approx).unwrap();
733
734        assert!(approx as usize >= i || i > 0x3e00000);
735        assert_eq!(S2K::decode_count(cc), approx);
736    }
737
738    #[test]
739    fn s2k_coded_count_approx_0x3e00000() {
740        let i = 0x3e00000;
741        let approx = S2K::nearest_hash_count(i);
742        let cc = S2K::encode_count(approx).unwrap();
743
744        assert!(approx as usize >= i || i > 0x3e00000);
745        assert_eq!(S2K::decode_count(cc), approx);
746    }
747}