From 2d340cb6b0df569ebd8ffe03a7cd75cd2c1efbf0 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 31 Aug 2024 13:28:39 -0700 Subject: [PATCH 1/9] add argon2id support for ossl 3.2+ --- openssl/src/argon2.rs | 147 ++++++++++++++++++++++++++++++++++++++++++ openssl/src/lib.rs | 2 + 2 files changed, 149 insertions(+) create mode 100644 openssl/src/argon2.rs diff --git a/openssl/src/argon2.rs b/openssl/src/argon2.rs new file mode 100644 index 000000000..af9fda00d --- /dev/null +++ b/openssl/src/argon2.rs @@ -0,0 +1,147 @@ +use libc::c_void; +use std::ffi::CStr; +use std::ptr; + +use crate::error::ErrorStack; +use crate::{cvt, cvt_p}; + +/// Derives a key using the argon2id algorithm. +/// +/// Requires OpenSSL 3.2.0 or newer. +#[allow(clippy::too_many_arguments)] +pub fn argon2id( + pass: &[u8], + salt: &[u8], + ad: Option<&[u8]>, + secret: Option<&[u8]>, + mut iter: u32, + mut threads: u32, + mut lanes: u32, + mut memcost: u32, + out: &mut [u8], +) -> Result<(), ErrorStack> { + // We only support single-threaded operation for now since rust-openssl doesn't + // bind OSSL_set_max_threads + assert!(threads == 1); + let pass_field = CStr::from_bytes_with_nul(b"pass\0").unwrap(); + let salt_field = CStr::from_bytes_with_nul(b"salt\0").unwrap(); + let ad_field = CStr::from_bytes_with_nul(b"ad\0").unwrap(); + let secret_field = CStr::from_bytes_with_nul(b"secret\0").unwrap(); + let iter_field = CStr::from_bytes_with_nul(b"iter\0").unwrap(); + let size_field = CStr::from_bytes_with_nul(b"size\0").unwrap(); + let threads_field = CStr::from_bytes_with_nul(b"threads\0").unwrap(); + let lanes_field = CStr::from_bytes_with_nul(b"lanes\0").unwrap(); + let memcost_field = CStr::from_bytes_with_nul(b"memcost\0").unwrap(); + unsafe { + ffi::init(); + let mut params = vec![]; + let param_pass = ffi::OSSL_PARAM_construct_octet_string( + pass_field.as_ptr(), + pass.as_ptr() as *mut c_void, + pass.len(), + ); + params.push(param_pass); + let param_salt = ffi::OSSL_PARAM_construct_octet_string( + salt_field.as_ptr(), + salt.as_ptr() as *mut c_void, + salt.len(), + ); + params.push(param_salt); + if let Some(ad) = ad { + let param_ad = ffi::OSSL_PARAM_construct_octet_string( + ad_field.as_ptr(), + ad.as_ptr() as *mut c_void, + ad.len(), + ); + params.push(param_ad); + } + if let Some(secret) = secret { + let param_secret = ffi::OSSL_PARAM_construct_octet_string( + secret_field.as_ptr(), + secret.as_ptr() as *mut c_void, + secret.len(), + ); + params.push(param_secret); + } + let param_threads = ffi::OSSL_PARAM_construct_uint(threads_field.as_ptr(), &mut threads); + params.push(param_threads); + let param_lanes = ffi::OSSL_PARAM_construct_uint(lanes_field.as_ptr(), &mut lanes); + params.push(param_lanes); + let param_memcost = ffi::OSSL_PARAM_construct_uint(memcost_field.as_ptr(), &mut memcost); + params.push(param_memcost); + let param_iter = ffi::OSSL_PARAM_construct_uint(iter_field.as_ptr(), &mut iter); + params.push(param_iter); + let mut size = out.len() as u32; + let param_size = ffi::OSSL_PARAM_construct_uint(size_field.as_ptr(), &mut size); + params.push(param_size); + let param_end = ffi::OSSL_PARAM_construct_end(); + params.push(param_end); + + let argon2id_field = CStr::from_bytes_with_nul(b"ARGON2ID\0").unwrap(); + let argon2 = cvt_p(ffi::EVP_KDF_fetch( + ptr::null_mut(), + argon2id_field.as_ptr(), + ptr::null(), + ))?; // This needs to be freed + let ctx = cvt_p(ffi::EVP_KDF_CTX_new(argon2))?; // this also needs to be freed + cvt(ffi::EVP_KDF_derive( + ctx, + out.as_mut_ptr(), + out.len(), + params.as_ptr(), + )) + .map(|_| ()) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn argon2id() { + // RFC 9106 test vector for argon2id + let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101") + .unwrap(); + let salt = hex::decode("02020202020202020202020202020202").unwrap(); + let secret = hex::decode("0303030303030303").unwrap(); + let ad = hex::decode("040404040404040404040404").unwrap(); + let expected = "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659"; + + let mut actual = [0 as u8; 32]; + super::argon2id( + &pass, + &salt, + Some(&ad), + Some(&secret), + 3, + 1, + 4, + 32, + &mut actual, + ) + .unwrap(); + assert_eq!(hex::encode(&actual[..]), expected); + } + + #[test] + fn argon2id_no_ad_secret() { + // Test vector from OpenSSL + let pass = ""; + let salt = hex::decode("02020202020202020202020202020202").unwrap(); + let expected = "0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a"; + + let mut actual = [0 as u8; 32]; + super::argon2id( + &pass.as_bytes(), + &salt, + None, + None, + 3, + 1, + 4, + 32, + &mut actual, + ) + .unwrap(); + assert_eq!(hex::encode(&actual[..]), expected); + } +} diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 555eda972..1ba33349b 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -147,6 +147,8 @@ mod bio; #[macro_use] mod util; pub mod aes; +#[cfg(ossl320)] +pub mod argon2; pub mod asn1; pub mod base64; pub mod bn; From 6ed35b28f8d6a54a4103858fbc7db24655e07822 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 31 Aug 2024 19:29:01 -0700 Subject: [PATCH 2/9] review feedback, support OPENSSL_NO_ARGON2 --- openssl/build.rs | 2 +- openssl/src/argon2.rs | 70 +++++++++++++++++++++++++++---------------- openssl/src/lib.rs | 1 + 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/openssl/build.rs b/openssl/build.rs index 41a047d97..33372efd5 100644 --- a/openssl/build.rs +++ b/openssl/build.rs @@ -7,7 +7,7 @@ use std::env; fn main() { - println!("cargo:rustc-check-cfg=cfg(osslconf, values(\"OPENSSL_NO_OCB\", \"OPENSSL_NO_SM4\", \"OPENSSL_NO_SEED\", \"OPENSSL_NO_CHACHA\", \"OPENSSL_NO_CAST\", \"OPENSSL_NO_IDEA\", \"OPENSSL_NO_CAMELLIA\", \"OPENSSL_NO_RC4\", \"OPENSSL_NO_BF\", \"OPENSSL_NO_PSK\", \"OPENSSL_NO_DEPRECATED_3_0\", \"OPENSSL_NO_SCRYPT\", \"OPENSSL_NO_SM3\", \"OPENSSL_NO_RMD160\", \"OPENSSL_NO_EC2M\", \"OPENSSL_NO_OCSP\", \"OPENSSL_NO_CMS\", \"OPENSSL_NO_EC\"))"); + println!("cargo:rustc-check-cfg=cfg(osslconf, values(\"OPENSSL_NO_OCB\", \"OPENSSL_NO_SM4\", \"OPENSSL_NO_SEED\", \"OPENSSL_NO_CHACHA\", \"OPENSSL_NO_CAST\", \"OPENSSL_NO_IDEA\", \"OPENSSL_NO_CAMELLIA\", \"OPENSSL_NO_RC4\", \"OPENSSL_NO_BF\", \"OPENSSL_NO_PSK\", \"OPENSSL_NO_DEPRECATED_3_0\", \"OPENSSL_NO_SCRYPT\", \"OPENSSL_NO_SM3\", \"OPENSSL_NO_RMD160\", \"OPENSSL_NO_EC2M\", \"OPENSSL_NO_OCSP\", \"OPENSSL_NO_CMS\", \"OPENSSL_NO_EC\", \"OPENSSL_NO_ARGON2\"))"); println!("cargo:rustc-check-cfg=cfg(libressl)"); println!("cargo:rustc-check-cfg=cfg(boringssl)"); diff --git a/openssl/src/argon2.rs b/openssl/src/argon2.rs index af9fda00d..1e955b6ad 100644 --- a/openssl/src/argon2.rs +++ b/openssl/src/argon2.rs @@ -1,10 +1,33 @@ -use libc::c_void; -use std::ffi::CStr; +use std::ffi::c_void; use std::ptr; use crate::error::ErrorStack; use crate::{cvt, cvt_p}; +struct EvpKdf { + kdf: *mut ffi::EVP_KDF, +} + +impl Drop for EvpKdf { + fn drop(&mut self) { + unsafe { + ffi::EVP_KDF_free(self.kdf); + } + } +} + +struct EvpKdfCtx { + ctx: *mut ffi::EVP_KDF_CTX, +} + +impl Drop for EvpKdfCtx { + fn drop(&mut self) { + unsafe { + ffi::EVP_KDF_CTX_free(self.ctx); + } + } +} + /// Derives a key using the argon2id algorithm. /// /// Requires OpenSSL 3.2.0 or newer. @@ -23,33 +46,24 @@ pub fn argon2id( // We only support single-threaded operation for now since rust-openssl doesn't // bind OSSL_set_max_threads assert!(threads == 1); - let pass_field = CStr::from_bytes_with_nul(b"pass\0").unwrap(); - let salt_field = CStr::from_bytes_with_nul(b"salt\0").unwrap(); - let ad_field = CStr::from_bytes_with_nul(b"ad\0").unwrap(); - let secret_field = CStr::from_bytes_with_nul(b"secret\0").unwrap(); - let iter_field = CStr::from_bytes_with_nul(b"iter\0").unwrap(); - let size_field = CStr::from_bytes_with_nul(b"size\0").unwrap(); - let threads_field = CStr::from_bytes_with_nul(b"threads\0").unwrap(); - let lanes_field = CStr::from_bytes_with_nul(b"lanes\0").unwrap(); - let memcost_field = CStr::from_bytes_with_nul(b"memcost\0").unwrap(); unsafe { ffi::init(); let mut params = vec![]; let param_pass = ffi::OSSL_PARAM_construct_octet_string( - pass_field.as_ptr(), + b"pass\0".as_ptr() as *const i8, pass.as_ptr() as *mut c_void, pass.len(), ); params.push(param_pass); let param_salt = ffi::OSSL_PARAM_construct_octet_string( - salt_field.as_ptr(), + b"salt\0".as_ptr() as *const i8, salt.as_ptr() as *mut c_void, salt.len(), ); params.push(param_salt); if let Some(ad) = ad { let param_ad = ffi::OSSL_PARAM_construct_octet_string( - ad_field.as_ptr(), + b"ad\0".as_ptr() as *const i8, ad.as_ptr() as *mut c_void, ad.len(), ); @@ -57,35 +71,39 @@ pub fn argon2id( } if let Some(secret) = secret { let param_secret = ffi::OSSL_PARAM_construct_octet_string( - secret_field.as_ptr(), + b"secret\0".as_ptr() as *const i8, secret.as_ptr() as *mut c_void, secret.len(), ); params.push(param_secret); } - let param_threads = ffi::OSSL_PARAM_construct_uint(threads_field.as_ptr(), &mut threads); + let param_threads = + ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const i8, &mut threads); params.push(param_threads); - let param_lanes = ffi::OSSL_PARAM_construct_uint(lanes_field.as_ptr(), &mut lanes); + let param_lanes = + ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const i8, &mut lanes); params.push(param_lanes); - let param_memcost = ffi::OSSL_PARAM_construct_uint(memcost_field.as_ptr(), &mut memcost); + let param_memcost = + ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const i8, &mut memcost); params.push(param_memcost); - let param_iter = ffi::OSSL_PARAM_construct_uint(iter_field.as_ptr(), &mut iter); + let param_iter = ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const i8, &mut iter); params.push(param_iter); let mut size = out.len() as u32; - let param_size = ffi::OSSL_PARAM_construct_uint(size_field.as_ptr(), &mut size); + let param_size = ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const i8, &mut size); params.push(param_size); let param_end = ffi::OSSL_PARAM_construct_end(); params.push(param_end); - let argon2id_field = CStr::from_bytes_with_nul(b"ARGON2ID\0").unwrap(); - let argon2 = cvt_p(ffi::EVP_KDF_fetch( + let argon2_p = cvt_p(ffi::EVP_KDF_fetch( ptr::null_mut(), - argon2id_field.as_ptr(), + b"ARGON2ID\0".as_ptr() as *const i8, ptr::null(), - ))?; // This needs to be freed - let ctx = cvt_p(ffi::EVP_KDF_CTX_new(argon2))?; // this also needs to be freed + ))?; + let argon2 = EvpKdf { kdf: argon2_p }; + let ctx_p = cvt_p(ffi::EVP_KDF_CTX_new(argon2.kdf))?; + let ctx = EvpKdfCtx { ctx: ctx_p }; cvt(ffi::EVP_KDF_derive( - ctx, + ctx.ctx, out.as_mut_ptr(), out.len(), params.as_ptr(), diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 1ba33349b..d758d5085 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -148,6 +148,7 @@ mod bio; mod util; pub mod aes; #[cfg(ossl320)] +#[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] pub mod argon2; pub mod asn1; pub mod base64; From 05d9f2ef6e70f6b610b0f94ee308e87ad00a7c39 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 31 Aug 2024 20:20:10 -0700 Subject: [PATCH 3/9] simplify, use CStr to hopefully handle i8/u8 nonsense --- openssl/src/argon2.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/openssl/src/argon2.rs b/openssl/src/argon2.rs index 1e955b6ad..f46bdc2af 100644 --- a/openssl/src/argon2.rs +++ b/openssl/src/argon2.rs @@ -1,29 +1,25 @@ -use std::ffi::c_void; +use std::ffi::{c_void, CStr}; use std::ptr; use crate::error::ErrorStack; use crate::{cvt, cvt_p}; -struct EvpKdf { - kdf: *mut ffi::EVP_KDF, -} +struct EvpKdf(*mut ffi::EVP_KDF); impl Drop for EvpKdf { fn drop(&mut self) { unsafe { - ffi::EVP_KDF_free(self.kdf); + ffi::EVP_KDF_free(self.0); } } } -struct EvpKdfCtx { - ctx: *mut ffi::EVP_KDF_CTX, -} +struct EvpKdfCtx(*mut ffi::EVP_KDF_CTX); impl Drop for EvpKdfCtx { fn drop(&mut self) { unsafe { - ffi::EVP_KDF_CTX_free(self.ctx); + ffi::EVP_KDF_CTX_free(self.0); } } } @@ -94,16 +90,17 @@ pub fn argon2id( let param_end = ffi::OSSL_PARAM_construct_end(); params.push(param_end); + let argon2id = CStr::from_bytes_with_nul(b"ARGON2ID\0").unwrap(); let argon2_p = cvt_p(ffi::EVP_KDF_fetch( ptr::null_mut(), - b"ARGON2ID\0".as_ptr() as *const i8, + argon2id.as_ptr(), ptr::null(), ))?; - let argon2 = EvpKdf { kdf: argon2_p }; - let ctx_p = cvt_p(ffi::EVP_KDF_CTX_new(argon2.kdf))?; - let ctx = EvpKdfCtx { ctx: ctx_p }; + let argon2 = EvpKdf(argon2_p); + let ctx_p = cvt_p(ffi::EVP_KDF_CTX_new(argon2.0))?; + let ctx = EvpKdfCtx(ctx_p); cvt(ffi::EVP_KDF_derive( - ctx.ctx, + ctx.0, out.as_mut_ptr(), out.len(), params.as_ptr(), From 04ffcf1e10d267a716376531ccbb0605a555bc70 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sat, 31 Aug 2024 21:50:12 -0700 Subject: [PATCH 4/9] speeeeeeed --- openssl/src/argon2.rs | 51 ++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/openssl/src/argon2.rs b/openssl/src/argon2.rs index f46bdc2af..f28c17794 100644 --- a/openssl/src/argon2.rs +++ b/openssl/src/argon2.rs @@ -1,4 +1,5 @@ use std::ffi::{c_void, CStr}; +use std::mem::MaybeUninit; use std::ptr; use crate::error::ErrorStack; @@ -44,51 +45,51 @@ pub fn argon2id( assert!(threads == 1); unsafe { ffi::init(); - let mut params = vec![]; - let param_pass = ffi::OSSL_PARAM_construct_octet_string( + let mut params: [ffi::OSSL_PARAM; 10] = + core::array::from_fn(|_| MaybeUninit::::zeroed().assume_init()); + let mut idx = 0; + params[idx] = ffi::OSSL_PARAM_construct_octet_string( b"pass\0".as_ptr() as *const i8, pass.as_ptr() as *mut c_void, pass.len(), ); - params.push(param_pass); - let param_salt = ffi::OSSL_PARAM_construct_octet_string( + idx += 1; + params[idx] = ffi::OSSL_PARAM_construct_octet_string( b"salt\0".as_ptr() as *const i8, salt.as_ptr() as *mut c_void, salt.len(), ); - params.push(param_salt); + idx += 1; + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const i8, &mut threads); + idx += 1; + params[idx] = ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const i8, &mut lanes); + idx += 1; + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const i8, &mut memcost); + idx += 1; + params[idx] = ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const i8, &mut iter); + idx += 1; + let mut size = out.len() as u32; + params[idx] = ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const i8, &mut size); + idx += 1; if let Some(ad) = ad { - let param_ad = ffi::OSSL_PARAM_construct_octet_string( + params[idx] = ffi::OSSL_PARAM_construct_octet_string( b"ad\0".as_ptr() as *const i8, ad.as_ptr() as *mut c_void, ad.len(), ); - params.push(param_ad); + idx += 1; } if let Some(secret) = secret { - let param_secret = ffi::OSSL_PARAM_construct_octet_string( + params[idx] = ffi::OSSL_PARAM_construct_octet_string( b"secret\0".as_ptr() as *const i8, secret.as_ptr() as *mut c_void, secret.len(), ); - params.push(param_secret); + idx += 1; } - let param_threads = - ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const i8, &mut threads); - params.push(param_threads); - let param_lanes = - ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const i8, &mut lanes); - params.push(param_lanes); - let param_memcost = - ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const i8, &mut memcost); - params.push(param_memcost); - let param_iter = ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const i8, &mut iter); - params.push(param_iter); - let mut size = out.len() as u32; - let param_size = ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const i8, &mut size); - params.push(param_size); - let param_end = ffi::OSSL_PARAM_construct_end(); - params.push(param_end); + params[idx] = ffi::OSSL_PARAM_construct_end(); let argon2id = CStr::from_bytes_with_nul(b"ARGON2ID\0").unwrap(); let argon2_p = cvt_p(ffi::EVP_KDF_fetch( From 826f2a0a82e57ccd16941f75daa5e7bc24b5e89e Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 1 Sep 2024 06:56:40 -0700 Subject: [PATCH 5/9] use c_char --- openssl/src/argon2.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/openssl/src/argon2.rs b/openssl/src/argon2.rs index f28c17794..836b45dce 100644 --- a/openssl/src/argon2.rs +++ b/openssl/src/argon2.rs @@ -1,4 +1,4 @@ -use std::ffi::{c_void, CStr}; +use std::ffi::{c_char, c_void}; use std::mem::MaybeUninit; use std::ptr; @@ -49,33 +49,36 @@ pub fn argon2id( core::array::from_fn(|_| MaybeUninit::::zeroed().assume_init()); let mut idx = 0; params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"pass\0".as_ptr() as *const i8, + b"pass\0".as_ptr() as *const c_char, pass.as_ptr() as *mut c_void, pass.len(), ); idx += 1; params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"salt\0".as_ptr() as *const i8, + b"salt\0".as_ptr() as *const c_char, salt.as_ptr() as *mut c_void, salt.len(), ); idx += 1; params[idx] = - ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const i8, &mut threads); + ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const c_char, &mut threads); idx += 1; - params[idx] = ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const i8, &mut lanes); + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const c_char, &mut lanes); idx += 1; params[idx] = - ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const i8, &mut memcost); + ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const c_char, &mut memcost); idx += 1; - params[idx] = ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const i8, &mut iter); + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const c_char, &mut iter); idx += 1; let mut size = out.len() as u32; - params[idx] = ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const i8, &mut size); + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const c_char, &mut size); idx += 1; if let Some(ad) = ad { params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"ad\0".as_ptr() as *const i8, + b"ad\0".as_ptr() as *const c_char, ad.as_ptr() as *mut c_void, ad.len(), ); @@ -83,7 +86,7 @@ pub fn argon2id( } if let Some(secret) = secret { params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"secret\0".as_ptr() as *const i8, + b"secret\0".as_ptr() as *const c_char, secret.as_ptr() as *mut c_void, secret.len(), ); @@ -91,10 +94,9 @@ pub fn argon2id( } params[idx] = ffi::OSSL_PARAM_construct_end(); - let argon2id = CStr::from_bytes_with_nul(b"ARGON2ID\0").unwrap(); let argon2_p = cvt_p(ffi::EVP_KDF_fetch( ptr::null_mut(), - argon2id.as_ptr(), + b"ARGON2ID\0".as_ptr() as *const c_char, ptr::null(), ))?; let argon2 = EvpKdf(argon2_p); From 5ff8594e9c98f2ffbaf677c663dce8d4ff72fa88 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 1 Sep 2024 14:49:20 -0700 Subject: [PATCH 6/9] rename to kdf, remove thread arg --- openssl/src/argon2.rs | 165 ------------------------------------------ openssl/src/kdf.rs | 162 +++++++++++++++++++++++++++++++++++++++++ openssl/src/lib.rs | 3 +- 3 files changed, 163 insertions(+), 167 deletions(-) delete mode 100644 openssl/src/argon2.rs create mode 100644 openssl/src/kdf.rs diff --git a/openssl/src/argon2.rs b/openssl/src/argon2.rs deleted file mode 100644 index 836b45dce..000000000 --- a/openssl/src/argon2.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::ffi::{c_char, c_void}; -use std::mem::MaybeUninit; -use std::ptr; - -use crate::error::ErrorStack; -use crate::{cvt, cvt_p}; - -struct EvpKdf(*mut ffi::EVP_KDF); - -impl Drop for EvpKdf { - fn drop(&mut self) { - unsafe { - ffi::EVP_KDF_free(self.0); - } - } -} - -struct EvpKdfCtx(*mut ffi::EVP_KDF_CTX); - -impl Drop for EvpKdfCtx { - fn drop(&mut self) { - unsafe { - ffi::EVP_KDF_CTX_free(self.0); - } - } -} - -/// Derives a key using the argon2id algorithm. -/// -/// Requires OpenSSL 3.2.0 or newer. -#[allow(clippy::too_many_arguments)] -pub fn argon2id( - pass: &[u8], - salt: &[u8], - ad: Option<&[u8]>, - secret: Option<&[u8]>, - mut iter: u32, - mut threads: u32, - mut lanes: u32, - mut memcost: u32, - out: &mut [u8], -) -> Result<(), ErrorStack> { - // We only support single-threaded operation for now since rust-openssl doesn't - // bind OSSL_set_max_threads - assert!(threads == 1); - unsafe { - ffi::init(); - let mut params: [ffi::OSSL_PARAM; 10] = - core::array::from_fn(|_| MaybeUninit::::zeroed().assume_init()); - let mut idx = 0; - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"pass\0".as_ptr() as *const c_char, - pass.as_ptr() as *mut c_void, - pass.len(), - ); - idx += 1; - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"salt\0".as_ptr() as *const c_char, - salt.as_ptr() as *mut c_void, - salt.len(), - ); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const c_char, &mut threads); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const c_char, &mut lanes); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const c_char, &mut memcost); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const c_char, &mut iter); - idx += 1; - let mut size = out.len() as u32; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const c_char, &mut size); - idx += 1; - if let Some(ad) = ad { - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"ad\0".as_ptr() as *const c_char, - ad.as_ptr() as *mut c_void, - ad.len(), - ); - idx += 1; - } - if let Some(secret) = secret { - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"secret\0".as_ptr() as *const c_char, - secret.as_ptr() as *mut c_void, - secret.len(), - ); - idx += 1; - } - params[idx] = ffi::OSSL_PARAM_construct_end(); - - let argon2_p = cvt_p(ffi::EVP_KDF_fetch( - ptr::null_mut(), - b"ARGON2ID\0".as_ptr() as *const c_char, - ptr::null(), - ))?; - let argon2 = EvpKdf(argon2_p); - let ctx_p = cvt_p(ffi::EVP_KDF_CTX_new(argon2.0))?; - let ctx = EvpKdfCtx(ctx_p); - cvt(ffi::EVP_KDF_derive( - ctx.0, - out.as_mut_ptr(), - out.len(), - params.as_ptr(), - )) - .map(|_| ()) - } -} - -#[cfg(test)] -mod tests { - #[test] - fn argon2id() { - // RFC 9106 test vector for argon2id - let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101") - .unwrap(); - let salt = hex::decode("02020202020202020202020202020202").unwrap(); - let secret = hex::decode("0303030303030303").unwrap(); - let ad = hex::decode("040404040404040404040404").unwrap(); - let expected = "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659"; - - let mut actual = [0 as u8; 32]; - super::argon2id( - &pass, - &salt, - Some(&ad), - Some(&secret), - 3, - 1, - 4, - 32, - &mut actual, - ) - .unwrap(); - assert_eq!(hex::encode(&actual[..]), expected); - } - - #[test] - fn argon2id_no_ad_secret() { - // Test vector from OpenSSL - let pass = ""; - let salt = hex::decode("02020202020202020202020202020202").unwrap(); - let expected = "0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a"; - - let mut actual = [0 as u8; 32]; - super::argon2id( - &pass.as_bytes(), - &salt, - None, - None, - 3, - 1, - 4, - 32, - &mut actual, - ) - .unwrap(); - assert_eq!(hex::encode(&actual[..]), expected); - } -} diff --git a/openssl/src/kdf.rs b/openssl/src/kdf.rs new file mode 100644 index 000000000..4bd89cc6d --- /dev/null +++ b/openssl/src/kdf.rs @@ -0,0 +1,162 @@ +#[cfg(ossl300)] +struct EvpKdf(*mut ffi::EVP_KDF); + +#[cfg(ossl300)] +impl Drop for EvpKdf { + fn drop(&mut self) { + unsafe { + ffi::EVP_KDF_free(self.0); + } + } +} + +#[cfg(ossl300)] +struct EvpKdfCtx(*mut ffi::EVP_KDF_CTX); + +#[cfg(ossl300)] +impl Drop for EvpKdfCtx { + fn drop(&mut self) { + unsafe { + ffi::EVP_KDF_CTX_free(self.0); + } + } +} + +cfg_if::cfg_if! { + if #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] { + use std::ffi::{c_char, c_void}; + use std::mem::MaybeUninit; + use std::ptr; + use crate::{cvt, cvt_p}; + use crate::error::ErrorStack; + + /// Derives a key using the argon2id algorithm. + /// + /// This function currently does not support multi-threaded operation, so + /// lanes greater than 1 will be processed sequentially. + /// + /// Requires OpenSSL 3.2.0 or newer. + #[allow(clippy::too_many_arguments)] + pub fn argon2id( + pass: &[u8], + salt: &[u8], + ad: Option<&[u8]>, + secret: Option<&[u8]>, + mut iter: u32, + mut lanes: u32, + mut memcost: u32, + out: &mut [u8], + ) -> Result<(), ErrorStack> { + unsafe { + ffi::init(); + let mut threads = 1; + let mut params: [ffi::OSSL_PARAM; 10] = + core::array::from_fn(|_| MaybeUninit::::zeroed().assume_init()); + let mut idx = 0; + params[idx] = ffi::OSSL_PARAM_construct_octet_string( + b"pass\0".as_ptr() as *const c_char, + pass.as_ptr() as *mut c_void, + pass.len(), + ); + idx += 1; + params[idx] = ffi::OSSL_PARAM_construct_octet_string( + b"salt\0".as_ptr() as *const c_char, + salt.as_ptr() as *mut c_void, + salt.len(), + ); + idx += 1; + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const c_char, &mut threads); + idx += 1; + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const c_char, &mut lanes); + idx += 1; + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const c_char, &mut memcost); + idx += 1; + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const c_char, &mut iter); + idx += 1; + let mut size = out.len() as u32; + params[idx] = + ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const c_char, &mut size); + idx += 1; + if let Some(ad) = ad { + params[idx] = ffi::OSSL_PARAM_construct_octet_string( + b"ad\0".as_ptr() as *const c_char, + ad.as_ptr() as *mut c_void, + ad.len(), + ); + idx += 1; + } + if let Some(secret) = secret { + params[idx] = ffi::OSSL_PARAM_construct_octet_string( + b"secret\0".as_ptr() as *const c_char, + secret.as_ptr() as *mut c_void, + secret.len(), + ); + idx += 1; + } + params[idx] = ffi::OSSL_PARAM_construct_end(); + + let argon2_p = cvt_p(ffi::EVP_KDF_fetch( + ptr::null_mut(), + b"ARGON2ID\0".as_ptr() as *const c_char, + ptr::null(), + ))?; + let argon2 = EvpKdf(argon2_p); + let ctx_p = cvt_p(ffi::EVP_KDF_CTX_new(argon2.0))?; + let ctx = EvpKdfCtx(ctx_p); + cvt(ffi::EVP_KDF_derive( + ctx.0, + out.as_mut_ptr(), + out.len(), + params.as_ptr(), + )) + .map(|_| ()) + } + } + } +} + +#[cfg(test)] +mod tests { + #[test] + #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] + fn argon2id() { + // RFC 9106 test vector for argon2id + let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101") + .unwrap(); + let salt = hex::decode("02020202020202020202020202020202").unwrap(); + let secret = hex::decode("0303030303030303").unwrap(); + let ad = hex::decode("040404040404040404040404").unwrap(); + let expected = "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659"; + + let mut actual = [0 as u8; 32]; + super::argon2id( + &pass, + &salt, + Some(&ad), + Some(&secret), + 3, + 4, + 32, + &mut actual, + ) + .unwrap(); + assert_eq!(hex::encode(&actual[..]), expected); + } + + #[test] + #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] + fn argon2id_no_ad_secret() { + // Test vector from OpenSSL + let pass = ""; + let salt = hex::decode("02020202020202020202020202020202").unwrap(); + let expected = "0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a"; + + let mut actual = [0 as u8; 32]; + super::argon2id(&pass.as_bytes(), &salt, None, None, 3, 4, 32, &mut actual).unwrap(); + assert_eq!(hex::encode(&actual[..]), expected); + } +} diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index d758d5085..5942734f5 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -148,8 +148,6 @@ mod bio; mod util; pub mod aes; #[cfg(ossl320)] -#[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] -pub mod argon2; pub mod asn1; pub mod base64; pub mod bn; @@ -171,6 +169,7 @@ pub mod ex_data; #[cfg(not(any(libressl, ossl300)))] pub mod fips; pub mod hash; +pub mod kdf; #[cfg(ossl300)] pub mod lib_ctx; pub mod md; From 3e57d9a1d735f81268a2468c2efdb990fd6013c2 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 1 Sep 2024 14:55:59 -0700 Subject: [PATCH 7/9] oops + clippy --- openssl/src/kdf.rs | 6 +++--- openssl/src/lib.rs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openssl/src/kdf.rs b/openssl/src/kdf.rs index 4bd89cc6d..f60ee2e05 100644 --- a/openssl/src/kdf.rs +++ b/openssl/src/kdf.rs @@ -132,7 +132,7 @@ mod tests { let ad = hex::decode("040404040404040404040404").unwrap(); let expected = "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659"; - let mut actual = [0 as u8; 32]; + let mut actual = [0u8; 32]; super::argon2id( &pass, &salt, @@ -155,8 +155,8 @@ mod tests { let salt = hex::decode("02020202020202020202020202020202").unwrap(); let expected = "0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a"; - let mut actual = [0 as u8; 32]; - super::argon2id(&pass.as_bytes(), &salt, None, None, 3, 4, 32, &mut actual).unwrap(); + let mut actual = [0u8; 32]; + super::argon2id(pass.as_bytes(), &salt, None, None, 3, 4, 32, &mut actual).unwrap(); assert_eq!(hex::encode(&actual[..]), expected); } } diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 5942734f5..c58e5bf59 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -147,7 +147,6 @@ mod bio; #[macro_use] mod util; pub mod aes; -#[cfg(ossl320)] pub mod asn1; pub mod base64; pub mod bn; From bef571b737f24a0732e7e64bbd5d7f6fc01d7b5b Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 1 Sep 2024 15:02:33 -0700 Subject: [PATCH 8/9] EvpKdf and EvpKdfCtx are available in 3.0.0, but mark them ossl320 We don't use them anywhere that isn't 3.2.0+ right now and that makes clippy angry. It can be changed if and when these get used for methods on older versions --- openssl/src/kdf.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openssl/src/kdf.rs b/openssl/src/kdf.rs index f60ee2e05..ff0c3e68a 100644 --- a/openssl/src/kdf.rs +++ b/openssl/src/kdf.rs @@ -1,7 +1,7 @@ -#[cfg(ossl300)] +#[cfg(ossl320)] struct EvpKdf(*mut ffi::EVP_KDF); -#[cfg(ossl300)] +#[cfg(ossl320)] impl Drop for EvpKdf { fn drop(&mut self) { unsafe { @@ -10,10 +10,10 @@ impl Drop for EvpKdf { } } -#[cfg(ossl300)] +#[cfg(ossl320)] struct EvpKdfCtx(*mut ffi::EVP_KDF_CTX); -#[cfg(ossl300)] +#[cfg(ossl320)] impl Drop for EvpKdfCtx { fn drop(&mut self) { unsafe { From 98521685baa9ef9e1330fdccd6be40bf88f81f64 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 1 Sep 2024 16:53:17 -0700 Subject: [PATCH 9/9] Apply suggestions from code review Co-authored-by: Alex Gaynor --- openssl/src/kdf.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/openssl/src/kdf.rs b/openssl/src/kdf.rs index ff0c3e68a..dbe568025 100644 --- a/openssl/src/kdf.rs +++ b/openssl/src/kdf.rs @@ -99,14 +99,12 @@ cfg_if::cfg_if! { } params[idx] = ffi::OSSL_PARAM_construct_end(); - let argon2_p = cvt_p(ffi::EVP_KDF_fetch( + let argon2 = EvpKdf(cvt_p(ffi::EVP_KDF_fetch( ptr::null_mut(), b"ARGON2ID\0".as_ptr() as *const c_char, ptr::null(), - ))?; - let argon2 = EvpKdf(argon2_p); - let ctx_p = cvt_p(ffi::EVP_KDF_CTX_new(argon2.0))?; - let ctx = EvpKdfCtx(ctx_p); + ))?); + let ctx = EvpKdfCtx(cvt_p(ffi::EVP_KDF_CTX_new(argon2.0))?); cvt(ffi::EVP_KDF_derive( ctx.0, out.as_mut_ptr(), @@ -151,12 +149,12 @@ mod tests { #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] fn argon2id_no_ad_secret() { // Test vector from OpenSSL - let pass = ""; + let pass = b""; let salt = hex::decode("02020202020202020202020202020202").unwrap(); let expected = "0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a"; let mut actual = [0u8; 32]; - super::argon2id(pass.as_bytes(), &salt, None, None, 3, 4, 32, &mut actual).unwrap(); + super::argon2id(pass, &salt, None, None, 3, 4, 32, &mut actual).unwrap(); assert_eq!(hex::encode(&actual[..]), expected); } }