diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2023-01-13 12:34:22 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2023-01-13 12:34:22 +0100 |
commit | 92e8b887662cdbc251722438c8195b8e40f4fc2f (patch) | |
tree | 471ad4035c0918694b58299f74ca1d647b1ddb94 | |
parent | fc24b33e41dcab7c798e3e4415c25a1955170f30 (diff) | |
parent | d257d46258b00b1368582832242c6265cbeb3268 (diff) |
Merge branch 'justus/openpgp-next-argon2' into justus/openpgp-next
-rw-r--r-- | Cargo.lock | 42 | ||||
-rw-r--r-- | openpgp/Cargo.toml | 1 | ||||
-rw-r--r-- | openpgp/examples/test-vectors-argon2.rs | 60 | ||||
-rw-r--r-- | openpgp/src/crypto/s2k.rs | 42 | ||||
-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 |
7 files changed, 174 insertions, 0 deletions
@@ -73,6 +73,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" [[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] name = "ascii-canvas" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -191,6 +203,17 @@ dependencies = [ ] [[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] name = "block-buffer" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -494,6 +517,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279bc8fc53f788a75c7804af68237d1fce02cde1e275a886a4b320604dc2aeda" [[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] name = "core-foundation" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2612,6 +2641,18 @@ dependencies = [ ] [[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + +[[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2815,6 +2856,7 @@ dependencies = [ "ripemd160", "rpassword", "rsa", + "rust-argon2", "sha-1 0.9.8", "sha1collisiondetection", "sha2", diff --git a/openpgp/Cargo.toml b/openpgp/Cargo.toml index 0a164285..dafd3b4d 100644 --- a/openpgp/Cargo.toml +++ b/openpgp/Cargo.toml @@ -41,6 +41,7 @@ memsec = { version = ">=0.5", default-features = false } nettle = { git = "https://gitlab.com/sequoia-pgp/nettle-rs", branch = "justus/ocb", optional = true } regex = "1" regex-syntax = "0.6" +rust-argon2 = "0.8" 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/examples/test-vectors-argon2.rs b/openpgp/examples/test-vectors-argon2.rs new file mode 100644 index 00000000..1e691d76 --- /dev/null +++ b/openpgp/examples/test-vectors-argon2.rs @@ -0,0 +1,60 @@ +use std::io::{self, Write}; + +use anyhow::Context; + +use sequoia_openpgp as openpgp; + +use openpgp::{ + crypto::*, + fmt::hex, + packet::prelude::*, + serialize::{Serialize, stream::*}, + types::*, +}; + +const PASSWORD: &str = "password"; +const MESSAGE: &[u8] = b"Hello, world!"; + +fn main() -> openpgp::Result<()> { + let password = PASSWORD.into(); + + for sym_algo in &[SymmetricAlgorithm::AES128, + SymmetricAlgorithm::AES192, + SymmetricAlgorithm::AES256] { + let sk = SessionKey::new(sym_algo.key_size()?); + + let mut sink = io::stdout(); + let message = Message::new(&mut sink); + let mut message = Armorer::new(message) + .add_header("Comment", format!("Encrypted using {}", sym_algo)) + .add_header("Comment", format!("Session key: {}", hex::encode(&sk))) + .build()?; + + let mut salt = Default::default(); + openpgp::crypto::random(&mut salt); + let skesk4 = SKESK4::with_password(*sym_algo, + *sym_algo, + S2K::Argon2 { + salt, + t: 1, + p: 4, + m: 21, + }, + &sk, &password)?; + Packet::from(skesk4).serialize(&mut message)?; + + let message = Encryptor::with_session_key(message, *sym_algo, sk)? + .build().context("Failed to create encryptor")?; + + let mut message = LiteralWriter::new(message).build() + .context("Failed to create literal writer")?; + + message.write_all(MESSAGE)?; + + // Finally, finalize the OpenPGP message by tearing down the + // writer stack. + message.finalize()?; + } + + Ok(()) +} diff --git a/openpgp/src/crypto/s2k.rs b/openpgp/src/crypto/s2k.rs index c31d6fa2..90d7c540 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,27 @@ impl S2K { -> Result<SessionKey> { #[allow(deprecated)] match self { + &S2K::Argon2 { salt, t, p, m, } => { + let config = argon2::Config { + time_cost: t.into(), + lanes: p.into(), + mem_cost: 2u32.checked_pow(m.into()) + .ok_or_else(|| Error::InvalidArgument( + format!("Argon2 memory parameter out of bounds: {}", + m)))?, + hash_length: key_size.try_into() + .map_err(|_| Error::InvalidArgument( + format!("key size parameter out of bounds: {}", + key_size)))?, + thread_mode: argon2::ThreadMode::Parallel, + variant: argon2::Variant::Argon2id, + ..argon2::Config::default() + }; + password.map(|password| { + Ok(argon2::hash_raw(password, &salt, &config)? + .into()) + }) + }, &S2K::Simple { hash } | &S2K::Salted { hash, .. } | &S2K::Iterated { hash, .. } => password.map(|string| { let mut hash = hash.context()?; @@ -183,6 +218,7 @@ impl S2K { hash.update(&zeros[..]); match self { + &S2K::Argon2 { .. } => unreachable!("handled above"), &S2K::Simple { .. } => { hash.update(string); } @@ -248,6 +284,7 @@ impl S2K { Simple { .. } | Salted { .. } | Iterated { .. } + | Argon2 { .. } => true, S2K::Private { .. } | S2K::Unknown { .. } @@ -353,6 +390,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 533860e3..a1579a88 100644 --- a/openpgp/src/parse.rs +++ b/openpgp/src/parse.rs @@ -1044,6 +1044,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 c48d7805..56c2117d 100644 --- a/openpgp/src/serialize.rs +++ b/openpgp/src/serialize.rs @@ -1285,6 +1285,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])?; @@ -1305,6 +1310,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 8873aba0..e2b385bd 100644 --- a/sq/src/commands/dump.rs +++ b/sq/src/commands/dump.rs @@ -941,6 +941,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)?; |