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::CStr;
29        use std::ptr;
30        use foreign_types::ForeignTypeRef;
31        use libc::c_char;
32        use crate::{cvt, cvt_p};
33        use crate::lib_ctx::LibCtxRef;
34        use crate::error::ErrorStack;
35        use crate::ossl_param::OsslParamBuilder;
36
37        // Safety: these all have null terminators.
38        // We cen remove these CStr::from_bytes_with_nul_unchecked calls
39        // when we upgrade to Rust 1.77+ with literal c"" syntax.
40
41        const OSSL_KDF_PARAM_PASSWORD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"pass\0") };
42        const OSSL_KDF_PARAM_SALT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"salt\0") };
43        const OSSL_KDF_PARAM_SECRET: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"secret\0") };
44        const OSSL_KDF_PARAM_ITER: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"iter\0") };
45        const OSSL_KDF_PARAM_SIZE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"size\0") };
46        const OSSL_KDF_PARAM_THREADS: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"threads\0") };
47        const OSSL_KDF_PARAM_ARGON2_AD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"ad\0") };
48        const OSSL_KDF_PARAM_ARGON2_LANES: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"lanes\0") };
49        const OSSL_KDF_PARAM_ARGON2_MEMCOST: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"memcost\0") };
50
51        #[allow(clippy::too_many_arguments)]
52        pub fn argon2d(
53            ctx: Option<&LibCtxRef>,
54            pass: &[u8],
55            salt: &[u8],
56            ad: Option<&[u8]>,
57            secret: Option<&[u8]>,
58            iter: u32,
59            lanes: u32,
60            memcost: u32,
61            out: &mut [u8],
62        ) -> Result<(), ErrorStack> {
63            argon2_helper(CStr::from_bytes_with_nul(b"ARGON2D\0").unwrap(), ctx, pass, salt, ad, secret, iter, lanes, memcost, out)
64        }
65
66        #[allow(clippy::too_many_arguments)]
67        pub fn argon2i(
68            ctx: Option<&LibCtxRef>,
69            pass: &[u8],
70            salt: &[u8],
71            ad: Option<&[u8]>,
72            secret: Option<&[u8]>,
73            iter: u32,
74            lanes: u32,
75            memcost: u32,
76            out: &mut [u8],
77        ) -> Result<(), ErrorStack> {
78            argon2_helper(CStr::from_bytes_with_nul(b"ARGON2I\0").unwrap(), ctx, pass, salt, ad, secret, iter, lanes, memcost, out)
79        }
80
81        #[allow(clippy::too_many_arguments)]
82        pub fn argon2id(
83            ctx: Option<&LibCtxRef>,
84            pass: &[u8],
85            salt: &[u8],
86            ad: Option<&[u8]>,
87            secret: Option<&[u8]>,
88            iter: u32,
89            lanes: u32,
90            memcost: u32,
91            out: &mut [u8],
92        ) -> Result<(), ErrorStack> {
93            argon2_helper(CStr::from_bytes_with_nul(b"ARGON2ID\0").unwrap(), ctx, pass, salt, ad, secret, iter, lanes, memcost, out)
94        }
95
96        /// Derives a key using the argon2* algorithms.
97        ///
98        /// To use multiple cores to process the lanes in parallel you must
99        /// set a global max thread count using `OSSL_set_max_threads`. On
100        /// builds with no threads all lanes will be processed sequentially.
101        ///
102        /// Requires OpenSSL 3.2.0 or newer.
103        #[allow(clippy::too_many_arguments)]
104        fn argon2_helper(
105            kdf_identifier: &CStr,
106            ctx: Option<&LibCtxRef>,
107            pass: &[u8],
108            salt: &[u8],
109            ad: Option<&[u8]>,
110            secret: Option<&[u8]>,
111            iter: u32,
112            lanes: u32,
113            memcost: u32,
114            out: &mut [u8],
115        ) -> Result<(), ErrorStack> {
116            let libctx = ctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr);
117            let max_threads = unsafe {
118                ffi::init();
119                ffi::OSSL_get_max_threads(libctx)
120            };
121            let mut threads = 1;
122            // If max_threads is 0, then this isn't a threaded build.
123            // If max_threads is > u32::MAX we need to clamp since
124            // argon2id's threads parameter is a u32.
125            if max_threads > 0 {
126                threads = cmp::min(lanes, cmp::min(max_threads, u32::MAX as u64) as u32);
127            }
128            let mut bld = OsslParamBuilder::new()?;
129            bld.add_octet_string(OSSL_KDF_PARAM_PASSWORD, pass)?;
130            bld.add_octet_string(OSSL_KDF_PARAM_SALT, salt)?;
131            bld.add_uint(OSSL_KDF_PARAM_THREADS, threads)?;
132            bld.add_uint(OSSL_KDF_PARAM_ARGON2_LANES, lanes)?;
133            bld.add_uint(OSSL_KDF_PARAM_ARGON2_MEMCOST, memcost)?;
134            bld.add_uint(OSSL_KDF_PARAM_ITER, iter)?;
135            let size = out.len() as u32;
136            bld.add_uint(OSSL_KDF_PARAM_SIZE, size)?;
137            if let Some(ad) = ad {
138                bld.add_octet_string(OSSL_KDF_PARAM_ARGON2_AD, ad)?;
139            }
140            if let Some(secret) = secret {
141                bld.add_octet_string(OSSL_KDF_PARAM_SECRET, secret)?;
142            }
143            let params = bld.to_param()?;
144            unsafe {
145                let argon2 = EvpKdf(cvt_p(ffi::EVP_KDF_fetch(
146                    libctx,
147                    kdf_identifier.as_ptr() as *const c_char,
148                    ptr::null(),
149                ))?);
150                let ctx = EvpKdfCtx(cvt_p(ffi::EVP_KDF_CTX_new(argon2.0))?);
151                cvt(ffi::EVP_KDF_derive(
152                    ctx.0,
153                    out.as_mut_ptr(),
154                    out.len(),
155                    params.as_ptr(),
156                ))
157                .map(|_| ())
158            }
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    #[test]
166    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
167    fn argon2id() {
168        // RFC 9106 test vector for argon2id
169        let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101")
170            .unwrap();
171        let salt = hex::decode("02020202020202020202020202020202").unwrap();
172        let secret = hex::decode("0303030303030303").unwrap();
173        let ad = hex::decode("040404040404040404040404").unwrap();
174        let expected = "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659";
175
176        let mut actual = [0u8; 32];
177        super::argon2id(
178            None,
179            &pass,
180            &salt,
181            Some(&ad),
182            Some(&secret),
183            3,
184            4,
185            32,
186            &mut actual,
187        )
188        .unwrap();
189        assert_eq!(hex::encode(&actual[..]), expected);
190    }
191
192    #[test]
193    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
194    fn argon2d() {
195        // RFC 9106 test vector for argon2d
196        let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101")
197            .unwrap();
198        let salt = hex::decode("02020202020202020202020202020202").unwrap();
199        let secret = hex::decode("0303030303030303").unwrap();
200        let ad = hex::decode("040404040404040404040404").unwrap();
201        let expected = "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb";
202
203        let mut actual = [0u8; 32];
204        super::argon2d(
205            None,
206            &pass,
207            &salt,
208            Some(&ad),
209            Some(&secret),
210            3,
211            4,
212            32,
213            &mut actual,
214        )
215        .unwrap();
216        assert_eq!(hex::encode(&actual[..]), expected);
217    }
218
219    #[test]
220    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
221    fn argon2i() {
222        // RFC 9106 test vector for argon2i
223        let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101")
224            .unwrap();
225        let salt = hex::decode("02020202020202020202020202020202").unwrap();
226        let secret = hex::decode("0303030303030303").unwrap();
227        let ad = hex::decode("040404040404040404040404").unwrap();
228        let expected = "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8";
229
230        let mut actual = [0u8; 32];
231        super::argon2i(
232            None,
233            &pass,
234            &salt,
235            Some(&ad),
236            Some(&secret),
237            3,
238            4,
239            32,
240            &mut actual,
241        )
242        .unwrap();
243        assert_eq!(hex::encode(&actual[..]), expected);
244    }
245
246    #[test]
247    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
248    fn argon2id_no_ad_secret() {
249        // Test vector from OpenSSL
250        let pass = b"";
251        let salt = hex::decode("02020202020202020202020202020202").unwrap();
252        let expected = "0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a";
253
254        let mut actual = [0u8; 32];
255        super::argon2id(None, pass, &salt, None, None, 3, 4, 32, &mut actual).unwrap();
256        assert_eq!(hex::encode(&actual[..]), expected);
257    }
258}