variant_ssl/
kdf.rs

1#[cfg(ossl300)]
2struct EvpKdf(*mut ffi::EVP_KDF);
3
4#[cfg(ossl300)]
5impl Drop for EvpKdf {
6    fn drop(&mut self) {
7        unsafe {
8            ffi::EVP_KDF_free(self.0);
9        }
10    }
11}
12
13#[cfg(ossl300)]
14struct EvpKdfCtx(*mut ffi::EVP_KDF_CTX);
15
16#[cfg(ossl300)]
17impl Drop for EvpKdfCtx {
18    fn drop(&mut self) {
19        unsafe {
20            ffi::EVP_KDF_CTX_free(self.0);
21        }
22    }
23}
24
25#[cfg(ossl300)]
26#[derive(Copy, Clone)]
27pub enum HkdfMode {
28    ExtractAndExpand,
29    ExtractOnly,
30    ExpandOnly,
31}
32
33cfg_if::cfg_if! {
34    if #[cfg(ossl300)] {
35        use std::ffi::CStr;
36        use std::ptr;
37        use foreign_types::ForeignTypeRef;
38        use libc::c_char;
39        use crate::{cvt, cvt_p};
40        use crate::lib_ctx::LibCtxRef;
41        use crate::error::ErrorStack;
42        use crate::ossl_param::{OsslParamBuilder, OsslParamArray};
43        use crate::md::MdRef;
44
45        const OSSL_KDF_PARAM_DIGEST: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"digest\0") };
46        const OSSL_KDF_PARAM_KEY: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"key\0") };
47        const OSSL_KDF_PARAM_SALT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"salt\0") };
48        const OSSL_KDF_PARAM_INFO: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"info\0") };
49        const OSSL_KDF_PARAM_MODE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"mode\0") };
50
51        // Safety: these all have null terminators.
52        // We cen remove these CStr::from_bytes_with_nul_unchecked calls
53        // when we upgrade to Rust 1.77+ with literal c"" syntax.
54
55        /// Derives a key using a KDF.
56        fn kdf_digest(
57            kdf_identifier: &CStr,
58            ctx: Option<&LibCtxRef>,
59            params: &OsslParamArray,
60            out: &mut [u8],
61        ) -> Result<(), ErrorStack> {
62            let libctx = ctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr);
63            unsafe {
64                let kdf = EvpKdf(cvt_p(ffi::EVP_KDF_fetch(
65                    libctx,
66                    kdf_identifier.as_ptr() as *const c_char,
67                    ptr::null(),
68                ))?);
69                let ctx = EvpKdfCtx(cvt_p(ffi::EVP_KDF_CTX_new(kdf.0))?);
70                cvt(ffi::EVP_KDF_derive(
71                    ctx.0,
72                    out.as_mut_ptr(),
73                    out.len(),
74                    params.as_ptr(),
75                ))
76                .map(|_| ())
77            }
78        }
79
80        pub fn hkdf(
81            digest: &MdRef,
82            key: &[u8],
83            salt: Option<&[u8]>,
84            info: Option<&[u8]>,
85            mode: HkdfMode,
86            ctx: Option<&LibCtxRef>,
87            out: &mut [u8],
88        ) -> Result<(), ErrorStack> {
89            let mut bld = OsslParamBuilder::new()?;
90            let sn = digest.type_().short_name()?;
91            bld.add_utf8_string(OSSL_KDF_PARAM_DIGEST, sn.as_bytes())?;
92            bld.add_octet_string(OSSL_KDF_PARAM_KEY, key)?;
93            if let Some(salt) = salt {
94                bld.add_octet_string(OSSL_KDF_PARAM_SALT, salt)?;
95            }
96            if let Some(info) = info {
97                bld.add_octet_string(OSSL_KDF_PARAM_INFO, info)?;
98            }
99            let mode_value = match mode {
100                HkdfMode::ExtractAndExpand => ffi::EVP_KDF_HKDF_MODE_EXTRACT_AND_EXPAND,
101                HkdfMode::ExtractOnly => ffi::EVP_KDF_HKDF_MODE_EXTRACT_ONLY,
102                HkdfMode::ExpandOnly => ffi::EVP_KDF_HKDF_MODE_EXPAND_ONLY,
103            };
104            bld.add_int(OSSL_KDF_PARAM_MODE, mode_value)?;
105            let params = bld.to_param()?;
106            kdf_digest(CStr::from_bytes_with_nul(b"HKDF\0").unwrap(), ctx, &params, out)
107        }
108    }
109}
110
111cfg_if::cfg_if! {
112    if #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] {
113        use std::cmp;
114
115        const OSSL_KDF_PARAM_PASSWORD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"pass\0") };
116        const OSSL_KDF_PARAM_SECRET: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"secret\0") };
117        const OSSL_KDF_PARAM_ITER: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"iter\0") };
118        const OSSL_KDF_PARAM_SIZE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"size\0") };
119        const OSSL_KDF_PARAM_THREADS: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"threads\0") };
120        const OSSL_KDF_PARAM_ARGON2_AD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"ad\0") };
121        const OSSL_KDF_PARAM_ARGON2_LANES: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"lanes\0") };
122        const OSSL_KDF_PARAM_ARGON2_MEMCOST: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"memcost\0") };
123
124        #[allow(clippy::too_many_arguments)]
125        pub fn argon2d(
126            ctx: Option<&LibCtxRef>,
127            pass: &[u8],
128            salt: &[u8],
129            ad: Option<&[u8]>,
130            secret: Option<&[u8]>,
131            iter: u32,
132            lanes: u32,
133            memcost: u32,
134            out: &mut [u8],
135        ) -> Result<(), ErrorStack> {
136            argon2_helper(CStr::from_bytes_with_nul(b"ARGON2D\0").unwrap(), ctx, pass, salt, ad, secret, iter, lanes, memcost, out)
137        }
138
139        #[allow(clippy::too_many_arguments)]
140        pub fn argon2i(
141            ctx: Option<&LibCtxRef>,
142            pass: &[u8],
143            salt: &[u8],
144            ad: Option<&[u8]>,
145            secret: Option<&[u8]>,
146            iter: u32,
147            lanes: u32,
148            memcost: u32,
149            out: &mut [u8],
150        ) -> Result<(), ErrorStack> {
151            argon2_helper(CStr::from_bytes_with_nul(b"ARGON2I\0").unwrap(), ctx, pass, salt, ad, secret, iter, lanes, memcost, out)
152        }
153
154        #[allow(clippy::too_many_arguments)]
155        pub fn argon2id(
156            ctx: Option<&LibCtxRef>,
157            pass: &[u8],
158            salt: &[u8],
159            ad: Option<&[u8]>,
160            secret: Option<&[u8]>,
161            iter: u32,
162            lanes: u32,
163            memcost: u32,
164            out: &mut [u8],
165        ) -> Result<(), ErrorStack> {
166            argon2_helper(CStr::from_bytes_with_nul(b"ARGON2ID\0").unwrap(), ctx, pass, salt, ad, secret, iter, lanes, memcost, out)
167        }
168
169        /// Derives a key using the argon2* algorithms.
170        ///
171        /// To use multiple cores to process the lanes in parallel you must
172        /// set a global max thread count using `OSSL_set_max_threads`. On
173        /// builds with no threads all lanes will be processed sequentially.
174        ///
175        /// Requires OpenSSL 3.2.0 or newer.
176        #[allow(clippy::too_many_arguments)]
177        fn argon2_helper(
178            kdf_identifier: &CStr,
179            ctx: Option<&LibCtxRef>,
180            pass: &[u8],
181            salt: &[u8],
182            ad: Option<&[u8]>,
183            secret: Option<&[u8]>,
184            iter: u32,
185            lanes: u32,
186            memcost: u32,
187            out: &mut [u8],
188        ) -> Result<(), ErrorStack> {
189            let libctx = ctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr);
190            let max_threads = unsafe {
191                ffi::init();
192                ffi::OSSL_get_max_threads(libctx)
193            };
194            let mut threads = 1;
195            // If max_threads is 0, then this isn't a threaded build.
196            // If max_threads is > u32::MAX we need to clamp since
197            // argon2id's threads parameter is a u32.
198            if max_threads > 0 {
199                threads = cmp::min(lanes, cmp::min(max_threads, u32::MAX as u64) as u32);
200            }
201            let mut bld = OsslParamBuilder::new()?;
202            bld.add_octet_string(OSSL_KDF_PARAM_PASSWORD, pass)?;
203            bld.add_octet_string(OSSL_KDF_PARAM_SALT, salt)?;
204            bld.add_uint(OSSL_KDF_PARAM_THREADS, threads)?;
205            bld.add_uint(OSSL_KDF_PARAM_ARGON2_LANES, lanes)?;
206            bld.add_uint(OSSL_KDF_PARAM_ARGON2_MEMCOST, memcost)?;
207            bld.add_uint(OSSL_KDF_PARAM_ITER, iter)?;
208            let size = out.len() as u32;
209            bld.add_uint(OSSL_KDF_PARAM_SIZE, size)?;
210            if let Some(ad) = ad {
211                bld.add_octet_string(OSSL_KDF_PARAM_ARGON2_AD, ad)?;
212            }
213            if let Some(secret) = secret {
214                bld.add_octet_string(OSSL_KDF_PARAM_SECRET, secret)?;
215            }
216            let params = bld.to_param()?;
217            kdf_digest(kdf_identifier, ctx, &params, out)
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    #[cfg(ossl300)]
225    fn hkdf_test_helper(
226        digest: &crate::md::MdRef,
227        key: &[u8],
228        salt: Option<&[u8]>,
229        info: &[u8],
230        expected_prk_hex: &str,
231        expected_hex: &str,
232    ) {
233        let expected_prk = hex::decode(expected_prk_hex).unwrap();
234        let expected = hex::decode(expected_hex).unwrap();
235        let mut prk = vec![0; expected_prk.len()];
236        let mut actual = vec![0; expected.len()];
237
238        // Test separate Extract then Expand to check PRK
239        super::hkdf(
240            digest,
241            key,
242            salt,
243            None,
244            crate::kdf::HkdfMode::ExtractOnly,
245            None,
246            &mut prk,
247        )
248        .unwrap();
249        assert_eq!(prk, expected_prk);
250        super::hkdf(
251            digest,
252            &prk,
253            None,
254            Some(info),
255            crate::kdf::HkdfMode::ExpandOnly,
256            None,
257            &mut actual,
258        )
259        .unwrap();
260        assert_eq!(actual, expected);
261
262        // Test Extract+Expand
263        super::hkdf(
264            digest,
265            key,
266            salt,
267            Some(info),
268            crate::kdf::HkdfMode::ExtractAndExpand,
269            None,
270            &mut actual,
271        )
272        .unwrap();
273        assert_eq!(actual, expected);
274    }
275
276    #[test]
277    #[cfg(ossl300)]
278    fn hkdf_rfc_case_1() {
279        // RFC 5869 Test Case 1 test vector (SHA-256 basic)
280        let digest = crate::md::Md::sha256();
281        let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
282        let salt = hex::decode("000102030405060708090a0b0c").unwrap();
283        let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap();
284        let expected_prk = concat!(
285            "077709362c2e32df0ddc3f0dc47bba63",
286            "90b6c73bb50f9c3122ec844ad7c2b3e5"
287        );
288        let expected = concat!(
289            "3cb25f25faacd57a90434f64d0362f2a",
290            "2d2d0a90cf1a5a4c5db02d56ecc4c5bf",
291            "34007208d5b887185865"
292        );
293        hkdf_test_helper(digest, &key, Some(&salt), &info, expected_prk, expected);
294    }
295
296    #[test]
297    #[cfg(ossl300)]
298    fn hkdf_rfc_case_2() {
299        // RFC 5869 Test Case 2 test vector (SHA-256 longer)
300        let digest = crate::md::Md::sha256();
301        let key = hex::decode(concat!(
302            "000102030405060708090a0b0c0d0e0f",
303            "101112131415161718191a1b1c1d1e1f",
304            "202122232425262728292a2b2c2d2e2f",
305            "303132333435363738393a3b3c3d3e3f",
306            "404142434445464748494a4b4c4d4e4f"
307        ))
308        .unwrap();
309        let salt = hex::decode(concat!(
310            "606162636465666768696a6b6c6d6e6f",
311            "707172737475767778797a7b7c7d7e7f",
312            "808182838485868788898a8b8c8d8e8f",
313            "909192939495969798999a9b9c9d9e9f",
314            "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
315        ))
316        .unwrap();
317        let info = hex::decode(concat!(
318            "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf",
319            "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf",
320            "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf",
321            "e0e1e2e3e4e5e6e7e8e9eaebecedeeef",
322            "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
323        ))
324        .unwrap();
325        let expected_prk = concat!(
326            "06a6b88c5853361a06104c9ceb35b45c",
327            "ef760014904671014a193f40c15fc244",
328        );
329        let expected = concat!(
330            "b11e398dc80327a1c8e7f78c596a4934",
331            "4f012eda2d4efad8a050cc4c19afa97c",
332            "59045a99cac7827271cb41c65e590e09",
333            "da3275600c2f09b8367793a9aca3db71",
334            "cc30c58179ec3e87c14c01d5c1f3434f",
335            "1d87",
336        );
337        hkdf_test_helper(digest, &key, Some(&salt), &info, expected_prk, expected);
338    }
339
340    #[test]
341    #[cfg(ossl300)]
342    fn hkdf_rfc_case_3() {
343        // RFC 5869 Test Case 3 test vector (SHA-256 zero-length salt/info)
344        let digest = crate::md::Md::sha256();
345        let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
346        let salt = [0u8; 0];
347        let info = [0u8; 0];
348        let expected_prk = concat!(
349            "19ef24a32c717b167f33a91d6f648bdf",
350            "96596776afdb6377ac434c1c293ccb04",
351        );
352        let expected = concat!(
353            "8da4e775a563c18f715f802a063c5a31",
354            "b8a11f5c5ee1879ec3454e5f3c738d2d",
355            "9d201395faa4b61a96c8",
356        );
357        hkdf_test_helper(digest, &key, Some(&salt), &info, expected_prk, expected);
358    }
359
360    #[test]
361    #[cfg(ossl300)]
362    fn hkdf_rfc_case_4() {
363        // RFC 5869 Test Case 4 test vector (SHA-1 basic)
364        let digest = crate::md::Md::sha1();
365        let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b").unwrap();
366        let salt = hex::decode("000102030405060708090a0b0c").unwrap();
367        let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap();
368        let expected_prk = "9b6c18c432a7bf8f0e71c8eb88f4b30baa2ba243";
369        let expected = concat!(
370            "085a01ea1b10f36933068b56efa5ad81",
371            "a4f14b822f5b091568a9cdd4f155fda2",
372            "c22e422478d305f3f896"
373        );
374        hkdf_test_helper(digest, &key, Some(&salt), &info, expected_prk, expected);
375    }
376
377    #[test]
378    #[cfg(ossl300)]
379    fn hkdf_rfc_case_5() {
380        // RFC 5869 Test Case 5 test vector (SHA-1 longer)
381        let digest = crate::md::Md::sha1();
382        let key = hex::decode(concat!(
383            "000102030405060708090a0b0c0d0e0f",
384            "101112131415161718191a1b1c1d1e1f",
385            "202122232425262728292a2b2c2d2e2f",
386            "303132333435363738393a3b3c3d3e3f",
387            "404142434445464748494a4b4c4d4e4f"
388        ))
389        .unwrap();
390        let salt = hex::decode(concat!(
391            "606162636465666768696a6b6c6d6e6f",
392            "707172737475767778797a7b7c7d7e7f",
393            "808182838485868788898a8b8c8d8e8f",
394            "909192939495969798999a9b9c9d9e9f",
395            "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
396        ))
397        .unwrap();
398        let info = hex::decode(concat!(
399            "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf",
400            "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf",
401            "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf",
402            "e0e1e2e3e4e5e6e7e8e9eaebecedeeef",
403            "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
404        ))
405        .unwrap();
406        let expected_prk = "8adae09a2a307059478d309b26c4115a224cfaf6";
407        let expected = concat!(
408            "0bd770a74d1160f7c9f12cd5912a06eb",
409            "ff6adcae899d92191fe4305673ba2ffe",
410            "8fa3f1a4e5ad79f3f334b3b202b2173c",
411            "486ea37ce3d397ed034c7f9dfeb15c5e",
412            "927336d0441f4c4300e2cff0d0900b52",
413            "d3b4",
414        );
415        hkdf_test_helper(digest, &key, Some(&salt), &info, expected_prk, expected);
416    }
417
418    #[test]
419    #[cfg(ossl300)]
420    fn hkdf_rfc_case_6() {
421        // RFC 5869 Test Case 6 test vector (SHA-1 zero-length salt/info)
422        let digest = crate::md::Md::sha1();
423        let key = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
424        let salt = [0u8; 0];
425        let info = [0u8; 0];
426        let expected_prk = "da8c8a73c7fa77288ec6f5e7c297786aa0d32d01";
427        let expected = concat!(
428            "0ac1af7002b3d761d1e55298da9d0506",
429            "b9ae52057220a306e07b6b87e8df21d0",
430            "ea00033de03984d34918",
431        );
432        hkdf_test_helper(digest, &key, Some(&salt), &info, expected_prk, expected);
433    }
434
435    #[test]
436    #[cfg(ossl300)]
437    fn hkdf_rfc_case_7() {
438        // RFC 5869 Test Case 7 test vector (SHA-1 no salt, zero-length info)
439        let digest = crate::md::Md::sha1();
440        let key = hex::decode("0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c").unwrap();
441        let salt = None;
442        let info = [0u8; 0];
443        let expected_prk = "2adccada18779e7c2077ad2eb19d3f3e731385dd";
444        let expected = concat!(
445            "2c91117204d745f3500d636a62f64f0a",
446            "b3bae548aa53d423b0d1f27ebba6f5e5",
447            "673a081d70cce7acfc48",
448        );
449        hkdf_test_helper(digest, &key, salt, &info, expected_prk, expected);
450    }
451
452    #[test]
453    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
454    fn argon2id() {
455        // RFC 9106 test vector for argon2id
456        let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101")
457            .unwrap();
458        let salt = hex::decode("02020202020202020202020202020202").unwrap();
459        let secret = hex::decode("0303030303030303").unwrap();
460        let ad = hex::decode("040404040404040404040404").unwrap();
461        let expected = "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659";
462
463        let mut actual = [0u8; 32];
464        super::argon2id(
465            None,
466            &pass,
467            &salt,
468            Some(&ad),
469            Some(&secret),
470            3,
471            4,
472            32,
473            &mut actual,
474        )
475        .unwrap();
476        assert_eq!(hex::encode(&actual[..]), expected);
477    }
478
479    #[test]
480    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
481    fn argon2d() {
482        // RFC 9106 test vector for argon2d
483        let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101")
484            .unwrap();
485        let salt = hex::decode("02020202020202020202020202020202").unwrap();
486        let secret = hex::decode("0303030303030303").unwrap();
487        let ad = hex::decode("040404040404040404040404").unwrap();
488        let expected = "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb";
489
490        let mut actual = [0u8; 32];
491        super::argon2d(
492            None,
493            &pass,
494            &salt,
495            Some(&ad),
496            Some(&secret),
497            3,
498            4,
499            32,
500            &mut actual,
501        )
502        .unwrap();
503        assert_eq!(hex::encode(&actual[..]), expected);
504    }
505
506    #[test]
507    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
508    fn argon2i() {
509        // RFC 9106 test vector for argon2i
510        let pass = hex::decode("0101010101010101010101010101010101010101010101010101010101010101")
511            .unwrap();
512        let salt = hex::decode("02020202020202020202020202020202").unwrap();
513        let secret = hex::decode("0303030303030303").unwrap();
514        let ad = hex::decode("040404040404040404040404").unwrap();
515        let expected = "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8";
516
517        let mut actual = [0u8; 32];
518        super::argon2i(
519            None,
520            &pass,
521            &salt,
522            Some(&ad),
523            Some(&secret),
524            3,
525            4,
526            32,
527            &mut actual,
528        )
529        .unwrap();
530        assert_eq!(hex::encode(&actual[..]), expected);
531    }
532
533    #[test]
534    #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))]
535    fn argon2id_no_ad_secret() {
536        // Test vector from OpenSSL
537        let pass = b"";
538        let salt = hex::decode("02020202020202020202020202020202").unwrap();
539        let expected = "0a34f1abde67086c82e785eaf17c68382259a264f4e61b91cd2763cb75ac189a";
540
541        let mut actual = [0u8; 32];
542        super::argon2id(None, pass, &salt, None, None, 3, 4, 32, &mut actual).unwrap();
543        assert_eq!(hex::encode(&actual[..]), expected);
544    }
545}