Skip to main content

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