diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2022-02-21 16:00:45 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2022-03-25 12:58:42 +0100 |
commit | 122a0b4fc0484046d8015444baa171d5431b9a8c (patch) | |
tree | dbe636dd76c2f6948889439b21eabf075508c4e1 | |
parent | e47e91fb5de347b86d2a84c881a41fab482eea60 (diff) |
openpgp: Implement S2K::Argon2 using RustCrypto's argon2 crate.justus/alternative-argon2
- See https://openpgp-wg.gitlab.io/rfc4880bis/#name-argon2
-rw-r--r-- | Cargo.lock | 68 | ||||
-rw-r--r-- | openpgp/Cargo.toml | 1 | ||||
-rw-r--r-- | openpgp/src/crypto/s2k.rs | 46 | ||||
-rw-r--r-- | openpgp/src/parse.rs | 12 | ||||
-rw-r--r-- | openpgp/src/serialize.rs | 6 | ||||
-rw-r--r-- | sq/src/commands/dump.rs | 11 |
6 files changed, 144 insertions, 0 deletions
@@ -91,6 +91,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" [[package]] +name = "argon2" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25df3c03f1040d0069fcd3907e24e36d59f9b6fa07ba49be0eb25a794f036ba7" +dependencies = [ + "base64ct", + "blake2", + "password-hash", +] + +[[package]] name = "ascii-canvas" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -158,6 +169,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] +name = "base64ct" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b" + +[[package]] name = "bindgen" version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -210,6 +227,15 @@ dependencies = [ ] [[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.3", +] + +[[package]] name = "block-buffer" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -231,6 +257,15 @@ dependencies = [ ] [[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.4", +] + +[[package]] name = "block-modes" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -616,6 +651,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array 0.14.4", + "typenum", +] + +[[package]] name = "crypto-mac" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -757,6 +802,17 @@ dependencies = [ ] [[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + +[[package]] name = "dirs" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1963,6 +2019,17 @@ dependencies = [ ] [[package]] +name = "password-hash" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8" +dependencies = [ + "base64ct", + "rand_core 0.6.3", + "subtle", +] + +[[package]] name = "peeking_take_while" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2686,6 +2753,7 @@ version = "1.8.0" dependencies = [ "aes", "anyhow", + "argon2", "backtrace", "base64", "block-modes", diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index c6f3568b..8392727e 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -41,6 +41,7 @@ memsec = { version = ">=0.5", default-features = false } nettle = { version = "7.0.2", optional = true } regex = "1" regex-syntax = "0.6" +argon2 = { version = "0.3", features = ["std"] } sha1collisiondetection = { version = "0.2.3", default-features = false, features = ["std"] } thiserror = "1.0.2" xxhash-rust = { version = "0.8", features = ["xxh3"] } diff --git a/openpgp/src/crypto/s2k.rs b/openpgp/src/crypto/s2k.rs index c31d6fa2..f9b936bb 100644 --- a/openpgp/src/crypto/s2k.rs +++ b/openpgp/src/crypto/s2k.rs @@ -6,6 +6,8 @@ //! //! [Section 3.7 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-3.7 +use std::convert::TryInto; + use crate::Error; use crate::Result; use crate::HashAlgorithm; @@ -35,6 +37,18 @@ use quickcheck::{Arbitrary, Gen}; #[non_exhaustive] #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub enum S2K { + /// Argon2 Memory-Hard Password Hashing Function. + Argon2 { + /// The salt. + salt: [u8; 16], + /// Number of passes. + t: u8, + /// Degree of parallelism. + p: u8, + /// Exponent of memory size. + m: u8, + }, + /// Repeatently hashes the password with a public `salt` value. Iterated { /// Hash used for key derivation. @@ -167,6 +181,31 @@ impl S2K { -> Result<SessionKey> { #[allow(deprecated)] match self { + &S2K::Argon2 { salt, t, p, m, } => { + let mut config = argon2::ParamsBuilder::new(); + config.t_cost(t.into())?; + config.p_cost(p.into())?; + config.m_cost( + 2u32.checked_pow(m.into()) + .ok_or_else(|| Error::InvalidArgument( + format!("Argon2 memory parameter out of bounds: {}", + m)))?)?; + config.output_len( + key_size.try_into() + .map_err(|_| Error::InvalidArgument( + format!("key size parameter out of bounds: {}", + key_size)))?)?; + let params = config.params()?; + let argon2 = argon2::Argon2::new( + argon2::Algorithm::Argon2id, + argon2::Version::V0x13, + params); + let mut sk: SessionKey = vec![0; key_size].into(); + password.map(|password| { + argon2.hash_password_into(password, &salt, &mut sk) + })?; + Ok(sk) + }, &S2K::Simple { hash } | &S2K::Salted { hash, .. } | &S2K::Iterated { hash, .. } => password.map(|string| { let mut hash = hash.context()?; @@ -183,6 +222,7 @@ impl S2K { hash.update(&zeros[..]); match self { + &S2K::Argon2 { .. } => unreachable!("handled above"), &S2K::Simple { .. } => { hash.update(string); } @@ -248,6 +288,7 @@ impl S2K { Simple { .. } | Salted { .. } | Iterated { .. } + | Argon2 { .. } => true, S2K::Private { .. } | S2K::Unknown { .. } @@ -353,6 +394,11 @@ impl fmt::Display for S2K { salt[4], salt[5], salt[6], salt[7], hash_bytes)) } + S2K::Argon2 { salt, t, p, m, } => { + write!(f, + "Argon2id with t: {}, p: {}, m: 2^{}, salt: {}", + t, p, m, crate::fmt::hex::encode(salt)) + }, S2K::Private { tag, parameters } => if let Some(p) = parameters.as_ref() { write!(f, "Private/Experimental S2K {}:{:?}", tag, p) diff --git a/openpgp/src/parse.rs b/openpgp/src/parse.rs index a7fbb085..a02ba9a3 100644 --- a/openpgp/src/parse.rs +++ b/openpgp/src/parse.rs @@ -1117,6 +1117,18 @@ impl S2K { hash_bytes: S2K::decode_count(php.parse_u8("s2k_count")?), } }, + 4 => S2K::Argon2 { + salt: { + let mut b = [0u8; 16]; + let b_len = b.len(); + b.copy_from_slice( + &php.parse_bytes("argon2_salt", b_len)?); + b + }, + t: php.parse_u8("argon2_t")?, + p: php.parse_u8("argon2_p")?, + m: php.parse_u8("argon2_m")?, + }, 100..=110 => S2K::Private { tag: s2k, parameters: if let Some(l) = s2k_len { diff --git a/openpgp/src/serialize.rs b/openpgp/src/serialize.rs index afbf1646..997b6f5c 100644 --- a/openpgp/src/serialize.rs +++ b/openpgp/src/serialize.rs @@ -1284,6 +1284,11 @@ impl Marshal for S2K { w.write_all(&salt[..])?; w.write_all(&[S2K::encode_count(hash_bytes)?])?; } + S2K::Argon2 { salt, t, p, m, } => { + w.write_all(&[4])?; + w.write_all(salt)?; + w.write_all(&[*t, *p, *m])?; + }, S2K::Private { tag, parameters } | S2K::Unknown { tag, parameters} => { w.write_all(&[*tag])?; @@ -1304,6 +1309,7 @@ impl MarshalInto for S2K { &S2K::Simple{ .. } => 2, &S2K::Salted{ .. } => 2 + 8, &S2K::Iterated{ .. } => 2 + 8 + 1, + S2K::Argon2 { .. } => 20, S2K::Private { parameters, .. } | S2K::Unknown { parameters, .. } => 1 + parameters.as_ref().map(|p| p.len()).unwrap_or(0), diff --git a/sq/src/commands/dump.rs b/sq/src/commands/dump.rs index a9aae2cc..af742cec 100644 --- a/sq/src/commands/dump.rs +++ b/sq/src/commands/dump.rs @@ -926,6 +926,17 @@ impl PacketDumper { writeln!(output, "{} Salt: {}", i, hex::encode(salt))?; writeln!(output, "{} Hash bytes: {}", i, hash_bytes)?; }, + Argon2 { salt, t, p, m, } => { + writeln!(output, "Argon2id")?; + writeln!(output, "{} Number of passes (t): {}", i, t)?; + writeln!(output, "{} Degree of parallelism (p): {}", i, p)?; + writeln!(output, "{} Memory size (m): {}", i, + 2u32.checked_pow(*m as u32) + .map(|b| format!("{} bytes", b)) + .unwrap_or_else( + || format!("Invalid parameter: {}", m)))?; + writeln!(output, "{} Salt: {}", i, hex::encode(salt))?; + }, Private { tag, parameters } => { writeln!(output, "Private")?; writeln!(output, "{} Tag: {}", i, tag)?; |