1use 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#[non_exhaustive]
34#[derive(Clone, PartialEq, Eq, Hash, Debug)]
35pub enum S2K {
36 Argon2 {
38 salt: [u8; 16],
40 t: u8,
42 p: u8,
44 m: u8,
46 },
47
48 Iterated {
50 hash: HashAlgorithm,
52 salt: [u8; 8],
54 hash_bytes: u32,
62 },
63
64 #[deprecated(note = "Use `S2K::Iterated`.")]
70 Salted {
71 hash: HashAlgorithm,
73 salt: [u8; 8],
75 },
76
77 #[deprecated(note = "Use `S2K::Iterated`.")]
86 Simple {
87 hash: HashAlgorithm
89 },
90
91 #[deprecated(note = "Use `S2K::Iterated`.")]
101 Implicit,
102
103 Private {
105 tag: u8,
109
110 parameters: Option<Box<[u8]>>,
119 },
120
121 Unknown {
123 tag: u8,
125
126 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 HashAlgorithm::SHA256,
148 0x3e00000,
152 ).expect("0x3e00000 is representable")
153 }
154}
155
156impl S2K {
157 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 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 let mut blocks = Blocks::new(¶ms)?;
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 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 hash.update(&salt[..]);
254 hash.update(string);
255 },
256 &S2K::Iterated { ref salt, hash_bytes, .. } => {
257 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 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 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 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 pub(crate) fn encode_count(hash_bytes: u32) -> Result<u8> {
363 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
495struct 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 #[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 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}