openssl/
kdf.rs

1#[cfg(ossl320)]
2struct EvpKdf(*mut ffi::EVP_KDF);
3
4#[cfg(ossl320)]
5impl Drop for EvpKdf {
6    fn drop(&mut self) {
7        unsafe {
8            ffi::EVP_KDF_free(self.0);
9        }
10    }
11}
12
13#[cfg(ossl320)]
14struct EvpKdfCtx(*mut ffi::EVP_KDF_CTX);
15
16#[cfg(ossl320)]
17impl Drop for EvpKdfCtx {
18    fn drop(&mut self) {
19        unsafe {
20            ffi::EVP_KDF_CTX_free(self.0);
21        }
22    }
23}
24
25cfg_if::cfg_if! {
26    if #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] {
27        use std::cmp;
28        use std::ffi::c_void;
29        use std::mem::MaybeUninit;
30        use std::ptr;
31        use foreign_types::ForeignTypeRef;
32        use libc::c_char;
33        use crate::{cvt, cvt_p};
34        use crate::lib_ctx::LibCtxRef;
35        use crate::error::ErrorStack;
36
37        /// Derives a key using the argon2id algorithm.
38        ///
39        /// To use multiple cores to process the lanes in parallel you must
40        /// set a global max thread count using `OSSL_set_max_threads`. On
41        /// builds with no threads all lanes will be processed sequentially.
42        ///
43        /// Requires OpenSSL 3.2.0 or newer.
44        #[allow(clippy::too_many_arguments)]
45        pub fn argon2id(
46            ctx: Option<&LibCtxRef>,
47            pass: &[u8],
48            salt: &[u8],
49            ad: Option<&[u8]>,
50            secret: Option<&[u8]>,
51            mut iter: u32,
52            mut lanes: u32,
53            mut memcost: u32,
54            out: &mut [u8],
55        ) -> Result<(), ErrorStack> {
56            unsafe {
57                ffi::init();
58                let libctx = ctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr);
59
60                let max_threads = ffi::OSSL_get_max_threads(libctx);
61                let mut threads = 1;
62                // If max_threads is 0, then this isn't a threaded build.
63                // If max_threads is > u32::MAX we need to clamp since
64                // argon2id's threads parameter is a u32.
65                if max_threads > 0 {
66                    threads = cmp::min(lanes, cmp::min(max_threads, u32::MAX as u64) as u32);
67                }
68                let mut params: [ffi::OSSL_PARAM; 10] =
69                    core::array::from_fn(|_| MaybeUninit::<ffi::OSSL_PARAM>::zeroed().assume_init());
70                let mut idx = 0;
71                params[idx] = ffi::OSSL_PARAM_construct_octet_string(
72                    b"pass\0".as_ptr() as *const c_char,
73                    pass.as_ptr() as *mut c_void,
74                    pass.len(),
75                );
76                idx += 1;
77                params[idx] = ffi::OSSL_PARAM_construct_octet_string(
78                    b"salt\0".as_ptr() as *const c_char,
79                    salt.as_ptr() as *mut c_void,
80                    salt.len(),
81                );
82                idx += 1;
83                params[idx] =
84                    ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const c_char, &mut threads);
85                idx += 1;
86                params[idx] =
87                    ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const c_char, &mut lanes);
88                idx += 1;
89                params[idx] =
90                    ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const c_char, &mut memcost);
91                idx += 1;
92                params[idx] =
93                    ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const c_char, &mut iter);
94                idx += 1;
95                let mut size = out.len() as u32;
96                params[idx] =
97                    ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const c_char, &mut size);
98                idx += 1;
99                if let Some(ad) = ad {
100                    params[idx] = ffi::OSSL_PARAM_construct_octet_string(
101                        b"ad\0".as_ptr() as *const c_char,
102                        ad.as_ptr() as *mut c_void,
103                        ad.len(),
104                    );
105                    idx += 1;
106                }
107                if let Some(secret) = secret {
108                    params[idx] = ffi::OSSL_PARAM_construct_octet_string(
109                        b"secret\0".as_ptr() as *const c_char,
110                        secret.as_ptr() as *mut c_void,
111                        secret.len(),
112                    );
113                    idx += 1;
114                }
115                params[idx] = ffi::OSSL_PARAM_construct_end();
116
117                let argon2 = EvpKdf(cvt_p(ffi::EVP_KDF_fetch(
118                    libctx,
119                    b"ARGON2ID\0".as_ptr() as *const c_char,
120                    ptr::null(),
121                ))?);
122                let ctx = EvpKdfCtx(cvt_p(ffi::EVP_KDF_CTX_new(argon2.0))?);
123                cvt(ffi::EVP_KDF_derive(
124                    ctx.0,
125                    out.as_mut_ptr(),
126                    out.len(),
127                    params.as_ptr(),
128                ))
129                .map(|_| ())
130            }
131        }
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    #[test]
138    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
139    fn argon2id() {
140        // RFC 9106 test vector for argon2id
141        let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101")
142            .unwrap();
143        let salt = hex::decode("02020202020202020202020202020202").unwrap();
144        let secret = hex::decode("0303030303030303").unwrap();
145        let ad = hex::decode("040404040404040404040404").unwrap();
146        let expected = "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659";
147
148        let mut actual = [0u8; 32];
149        super::argon2id(
150            None,
151            &pass,
152            &salt,
153            Some(&ad),
154            Some(&secret),
155            3,
156            4,
157            32,
158            &mut actual,
159        )
160        .unwrap();
161        assert_eq!(hex::encode(&actual[..]), expected);
162    }
163
164    #[test]
165    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
166    fn argon2id_no_ad_secret() {
167        // Test vector from OpenSSL
168        let pass = b"";
169        let salt = hex::decode("02020202020202020202020202020202").unwrap();
170        let expected = "0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a";
171
172        let mut actual = [0u8; 32];
173        super::argon2id(None, pass, &salt, None, None, 3, 4, 32, &mut actual).unwrap();
174        assert_eq!(hex::encode(&actual[..]), expected);
175    }
176}