Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add argon2id support for ossl 3.2+ #2290

Merged
merged 9 commits into from
Sep 2, 2024
Merged
147 changes: 147 additions & 0 deletions openssl/src/argon2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use libc::c_void;
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
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,
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
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();
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
unsafe {
ffi::init();
let mut params = vec![];
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
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
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
let ctx = cvt_p(ffi::EVP_KDF_CTX_new(argon2))?; // this also needs to be freed
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
2 changes: 2 additions & 0 deletions openssl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ mod bio;
#[macro_use]
mod util;
pub mod aes;
#[cfg(ossl320)]
pub mod argon2;
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
pub mod asn1;
pub mod base64;
pub mod bn;
Expand Down