Skip to main content

native_ossl/
kdf.rs

1//! Key derivation — `KdfAlg`, `KdfCtx`, and typed builders.
2//!
3//! Phase 6 delivers a low-level `EVP_KDF` wrapper and three typed builders:
4//!
5//! | Builder           | Algorithm   | RFC       |
6//! |-------------------|-------------|-----------|
7//! | [`HkdfBuilder`]   | HKDF        | RFC 5869  |
8//! | [`Pbkdf2Builder`] | PBKDF2      | PKCS #5   |
9//! | [`ScryptBuilder`] | scrypt      | RFC 7914  |
10
11use crate::error::ErrorStack;
12use native_ossl_sys as sys;
13use std::ffi::CStr;
14
15// ── KdfAlg — algorithm descriptor ────────────────────────────────────────────
16
17/// An OpenSSL KDF algorithm descriptor (`EVP_KDF*`).
18///
19/// Fetched once and reused to create [`KdfCtx`] instances.
20pub struct KdfAlg {
21    ptr: *mut sys::EVP_KDF,
22}
23
24impl KdfAlg {
25    /// Fetch a KDF algorithm from the global default library context.
26    ///
27    /// Common names: `c"HKDF"`, `c"PBKDF2"`, `c"SCRYPT"`, `c"TLS13-KDF"`.
28    ///
29    /// # Errors
30    ///
31    /// Returns `Err` if the algorithm is not available.
32    pub fn fetch(name: &CStr) -> Result<Self, ErrorStack> {
33        let ptr =
34            unsafe { sys::EVP_KDF_fetch(std::ptr::null_mut(), name.as_ptr(), std::ptr::null()) };
35        if ptr.is_null() {
36            return Err(ErrorStack::drain());
37        }
38        Ok(KdfAlg { ptr })
39    }
40
41    fn as_ptr(&self) -> *mut sys::EVP_KDF {
42        self.ptr
43    }
44}
45
46impl Drop for KdfAlg {
47    fn drop(&mut self) {
48        unsafe { sys::EVP_KDF_free(self.ptr) };
49    }
50}
51
52// SAFETY: `EVP_KDF*` is reference-counted and immutable after fetch.
53unsafe impl Send for KdfAlg {}
54unsafe impl Sync for KdfAlg {}
55
56// ── KdfCtx — stateful context ─────────────────────────────────────────────────
57
58/// A key-derivation context (`EVP_KDF_CTX*`).
59///
60/// `!Clone` — create a new context for each derive operation.
61pub struct KdfCtx {
62    ptr: *mut sys::EVP_KDF_CTX,
63}
64
65impl KdfCtx {
66    /// Create a new context from an algorithm descriptor.
67    ///
68    /// # Errors
69    pub fn new(alg: &KdfAlg) -> Result<Self, ErrorStack> {
70        let ptr = unsafe { sys::EVP_KDF_CTX_new(alg.as_ptr()) };
71        if ptr.is_null() {
72            return Err(ErrorStack::drain());
73        }
74        Ok(KdfCtx { ptr })
75    }
76
77    /// Derive key material into `out`.
78    ///
79    /// Parameters are supplied at derive time via the `params` argument.
80    ///
81    /// # Errors
82    pub fn derive(
83        &mut self,
84        out: &mut [u8],
85        params: &crate::params::Params<'_>,
86    ) -> Result<(), ErrorStack> {
87        crate::ossl_call!(sys::EVP_KDF_derive(
88            self.ptr,
89            out.as_mut_ptr(),
90            out.len(),
91            params.as_ptr()
92        ))
93    }
94}
95
96impl Drop for KdfCtx {
97    fn drop(&mut self) {
98        unsafe { sys::EVP_KDF_CTX_free(self.ptr) };
99    }
100}
101
102unsafe impl Send for KdfCtx {}
103
104// ── HKDF mode ─────────────────────────────────────────────────────────────────
105
106/// HKDF extraction/expansion mode.
107#[derive(Default, Clone, Copy, PartialEq, Eq)]
108pub enum HkdfMode {
109    /// Extract a pseudorandom key (PRK) from the IKM, then expand it.
110    ///
111    /// This is the standard two-step mode from RFC 5869 §2.
112    #[default]
113    ExtractAndExpand,
114    /// Extract phase only — output is the PRK.
115    ExtractOnly,
116    /// Expand phase only — input key must already be a PRK.
117    ExpandOnly,
118}
119
120impl HkdfMode {
121    fn as_uint(self) -> u32 {
122        match self {
123            HkdfMode::ExtractAndExpand => 0,
124            HkdfMode::ExtractOnly => 1,
125            HkdfMode::ExpandOnly => 2,
126        }
127    }
128}
129
130// ── HkdfBuilder ───────────────────────────────────────────────────────────────
131
132/// HKDF key-derivation builder (RFC 5869).
133///
134/// ```ignore
135/// let okm = HkdfBuilder::new(&sha256)
136///     .key(ikm)
137///     .salt(salt)
138///     .info(info)
139///     .derive_to_vec(32)?;
140/// ```
141pub struct HkdfBuilder<'a> {
142    digest: &'a crate::digest::DigestAlg,
143    key: Option<&'a [u8]>,
144    salt: Option<&'a [u8]>,
145    info: Option<&'a [u8]>,
146    mode: HkdfMode,
147}
148
149impl<'a> HkdfBuilder<'a> {
150    /// Create an `HKDF` builder bound to `digest`.
151    ///
152    /// The mode defaults to `ExtractAndExpand`.
153    #[must_use]
154    pub fn new(digest: &'a crate::digest::DigestAlg) -> Self {
155        HkdfBuilder {
156            digest,
157            key: None,
158            salt: None,
159            info: None,
160            mode: HkdfMode::default(),
161        }
162    }
163
164    /// Set the input keying material (IKM).
165    #[must_use]
166    pub fn key(mut self, key: &'a [u8]) -> Self {
167        self.key = Some(key);
168        self
169    }
170
171    /// Set the salt (optional; defaults to zero-length within OpenSSL).
172    #[must_use]
173    pub fn salt(mut self, salt: &'a [u8]) -> Self {
174        self.salt = Some(salt);
175        self
176    }
177
178    /// Set the context/application-specific information bytes.
179    #[must_use]
180    pub fn info(mut self, info: &'a [u8]) -> Self {
181        self.info = Some(info);
182        self
183    }
184
185    /// Override the default extract-and-expand mode.
186    #[must_use]
187    pub fn mode(mut self, mode: HkdfMode) -> Self {
188        self.mode = mode;
189        self
190    }
191
192    /// Derive key material, writing into `out`.
193    ///
194    /// # Errors
195    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
196        let name_ptr = unsafe { sys::OBJ_nid2sn(self.digest.nid()) };
197        if name_ptr.is_null() {
198            return Err(ErrorStack::drain());
199        }
200        let name = unsafe { CStr::from_ptr(name_ptr) };
201
202        let mut builder = crate::params::ParamBuilder::new()?
203            .push_utf8_string(c"digest", name)?
204            .push_uint(c"mode", self.mode.as_uint())?;
205
206        if let Some(k) = self.key {
207            builder = builder.push_octet_slice(c"key", k)?;
208        }
209        if let Some(s) = self.salt {
210            builder = builder.push_octet_slice(c"salt", s)?;
211        }
212        if let Some(i) = self.info {
213            builder = builder.push_octet_slice(c"info", i)?;
214        }
215
216        let params = builder.build()?;
217        let alg = KdfAlg::fetch(c"HKDF")?;
218        KdfCtx::new(&alg)?.derive(out, &params)
219    }
220
221    /// Derive `len` bytes of key material, returning them in a freshly allocated `Vec<u8>`.
222    ///
223    /// # Errors
224    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
225        let mut out = vec![0u8; len];
226        self.derive(&mut out)?;
227        Ok(out)
228    }
229}
230
231// ── Pbkdf2Builder ─────────────────────────────────────────────────────────────
232
233/// `PBKDF2` key-derivation builder (PKCS #5).
234///
235/// ```ignore
236/// let dk = Pbkdf2Builder::new(&sha256, b"password", b"salt")
237///     .iterations(600_000)
238///     .derive_to_vec(32)?;
239/// ```
240pub struct Pbkdf2Builder<'a> {
241    digest: &'a crate::digest::DigestAlg,
242    password: &'a [u8],
243    salt: &'a [u8],
244    iterations: u32,
245}
246
247impl<'a> Pbkdf2Builder<'a> {
248    /// Create a `PBKDF2` builder.
249    ///
250    /// The iteration count defaults to 600 000 (NIST SP 800-132 minimum for
251    /// SHA-256 as of 2023).
252    #[must_use]
253    pub fn new(digest: &'a crate::digest::DigestAlg, password: &'a [u8], salt: &'a [u8]) -> Self {
254        Pbkdf2Builder {
255            digest,
256            password,
257            salt,
258            iterations: 600_000,
259        }
260    }
261
262    /// Override the iteration count.
263    #[must_use]
264    pub fn iterations(mut self, n: u32) -> Self {
265        self.iterations = n;
266        self
267    }
268
269    /// Derive key material, writing into `out`.
270    ///
271    /// # Errors
272    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
273        let name_ptr = unsafe { sys::OBJ_nid2sn(self.digest.nid()) };
274        if name_ptr.is_null() {
275            return Err(ErrorStack::drain());
276        }
277        let name = unsafe { CStr::from_ptr(name_ptr) };
278
279        let params = crate::params::ParamBuilder::new()?
280            .push_octet_slice(c"pass", self.password)?
281            .push_octet_slice(c"salt", self.salt)?
282            .push_uint(c"iter", self.iterations)?
283            .push_utf8_string(c"digest", name)?
284            .build()?;
285
286        let alg = KdfAlg::fetch(c"PBKDF2")?;
287        KdfCtx::new(&alg)?.derive(out, &params)
288    }
289
290    /// Derive `len` bytes of key material, returning them in a freshly allocated `Vec<u8>`.
291    ///
292    /// # Errors
293    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
294        let mut out = vec![0u8; len];
295        self.derive(&mut out)?;
296        Ok(out)
297    }
298}
299
300// ── ScryptParams / ScryptBuilder ──────────────────────────────────────────────
301
302/// Scrypt cost parameters.
303///
304/// Use struct-update syntax for partial overrides:
305/// ```ignore
306/// ScryptParams { n: 1 << 20, ..ScryptParams::default() }
307/// ```
308pub struct ScryptParams {
309    /// CPU/memory cost factor — must be a power of two and greater than 1.
310    pub n: u64,
311    /// Block size factor.
312    pub r: u32,
313    /// Parallelisation factor.
314    pub p: u32,
315}
316
317impl Default for ScryptParams {
318    fn default() -> Self {
319        // RFC 7914 §2 interactive-login recommendation.
320        ScryptParams {
321            n: 16_384,
322            r: 8,
323            p: 1,
324        }
325    }
326}
327
328/// Scrypt key-derivation builder (RFC 7914).
329///
330/// ```ignore
331/// let dk = ScryptBuilder::new(b"password", b"NaCl")
332///     .params(ScryptParams { n: 1 << 20, ..ScryptParams::default() })
333///     .derive_to_vec(64)?;
334/// ```
335pub struct ScryptBuilder<'a> {
336    password: &'a [u8],
337    salt: &'a [u8],
338    params: ScryptParams,
339}
340
341impl<'a> ScryptBuilder<'a> {
342    /// Create a scrypt builder.
343    ///
344    /// Cost parameters default to `ScryptParams::default()`.
345    #[must_use]
346    pub fn new(password: &'a [u8], salt: &'a [u8]) -> Self {
347        ScryptBuilder {
348            password,
349            salt,
350            params: ScryptParams::default(),
351        }
352    }
353
354    /// Override the cost parameters.
355    #[must_use]
356    pub fn params(mut self, params: ScryptParams) -> Self {
357        self.params = params;
358        self
359    }
360
361    /// Derive key material, writing into `out`.
362    ///
363    /// # Errors
364    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
365        let params = crate::params::ParamBuilder::new()?
366            .push_octet_slice(c"pass", self.password)?
367            .push_octet_slice(c"salt", self.salt)?
368            .push_uint64(c"n", self.params.n)?
369            .push_uint(c"r", self.params.r)?
370            .push_uint(c"p", self.params.p)?
371            .build()?;
372
373        let alg = KdfAlg::fetch(c"SCRYPT")?;
374        KdfCtx::new(&alg)?.derive(out, &params)
375    }
376
377    /// Derive `len` bytes of key material, returning them in a freshly allocated `Vec<u8>`.
378    ///
379    /// # Errors
380    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
381        let mut out = vec![0u8; len];
382        self.derive(&mut out)?;
383        Ok(out)
384    }
385}
386
387// ── SSH-KDF (OpenSSL 3.5+) ────────────────────────────────────────────────────
388
389/// Purpose codes for SSH key derivation (RFC 4253 §7.2).
390#[cfg(ossl350)]
391#[derive(Clone, Copy, Debug, PartialEq, Eq)]
392pub enum SshkdfKeyType {
393    /// Initial IV, client to server.
394    InitialIvClientToServer,
395    /// Initial IV, server to client.
396    InitialIvServerToClient,
397    /// Encryption key, client to server.
398    EncryptionKeyClientToServer,
399    /// Encryption key, server to client.
400    EncryptionKeyServerToClient,
401    /// Integrity key, client to server.
402    IntegrityKeyClientToServer,
403    /// Integrity key, server to client.
404    IntegrityKeyServerToClient,
405}
406
407#[cfg(ossl350)]
408impl SshkdfKeyType {
409    fn as_cstr(self) -> &'static CStr {
410        match self {
411            Self::InitialIvClientToServer => c"A",
412            Self::InitialIvServerToClient => c"B",
413            Self::EncryptionKeyClientToServer => c"C",
414            Self::EncryptionKeyServerToClient => c"D",
415            Self::IntegrityKeyClientToServer => c"E",
416            Self::IntegrityKeyServerToClient => c"F",
417        }
418    }
419}
420
421/// SSH key-derivation builder (RFC 4253 §7.2).
422///
423/// ```ignore
424/// let iv = SshkdfBuilder::new(&sha256, &shared_secret, &exchange_hash, &session_id,
425///                             SshkdfKeyType::InitialIvClientToServer)
426///     .derive_to_vec(16)?;
427/// ```
428#[cfg(ossl350)]
429pub struct SshkdfBuilder<'a> {
430    digest: &'a crate::digest::DigestAlg,
431    key: &'a [u8],
432    xcghash: &'a [u8],
433    session_id: &'a [u8],
434    key_type: SshkdfKeyType,
435}
436
437#[cfg(ossl350)]
438impl<'a> SshkdfBuilder<'a> {
439    /// Create an SSH-KDF builder.
440    ///
441    /// - `digest` — hash algorithm (e.g. SHA-256).
442    /// - `key` — the shared secret `K` from the Diffie-Hellman exchange.
443    /// - `xcghash` — exchange hash `H`.
444    /// - `session_id` — the session identifier (= first `H` for the session).
445    /// - `key_type` — which key/IV component to derive (A–F).
446    #[must_use]
447    pub fn new(
448        digest: &'a crate::digest::DigestAlg,
449        key: &'a [u8],
450        xcghash: &'a [u8],
451        session_id: &'a [u8],
452        key_type: SshkdfKeyType,
453    ) -> Self {
454        SshkdfBuilder {
455            digest,
456            key,
457            xcghash,
458            session_id,
459            key_type,
460        }
461    }
462
463    /// Derive key material, writing into `out`.
464    ///
465    /// # Errors
466    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
467        let name_ptr = unsafe { sys::OBJ_nid2sn(self.digest.nid()) };
468        if name_ptr.is_null() {
469            return Err(ErrorStack::drain());
470        }
471        let name = unsafe { CStr::from_ptr(name_ptr) };
472
473        let params = crate::params::ParamBuilder::new()?
474            .push_utf8_string(c"digest", name)?
475            .push_octet_slice(c"key", self.key)?
476            .push_octet_slice(c"xcghash", self.xcghash)?
477            .push_octet_slice(c"session-id", self.session_id)?
478            .push_utf8_string(c"type", self.key_type.as_cstr())?
479            .build()?;
480
481        let alg = KdfAlg::fetch(c"SSHKDF")?;
482        KdfCtx::new(&alg)?.derive(out, &params)
483    }
484
485    /// Derive `len` bytes, returning them in a freshly allocated `Vec<u8>`.
486    ///
487    /// # Errors
488    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
489        let mut out = vec![0u8; len];
490        self.derive(&mut out)?;
491        Ok(out)
492    }
493}
494
495// ── KBKDF (OpenSSL 3.5+) ─────────────────────────────────────────────────────
496
497/// KBKDF derivation mode (SP 800-108).
498#[cfg(ossl350)]
499#[derive(Clone, Copy, Debug, PartialEq, Eq)]
500pub enum KbkdfMode {
501    /// Counter mode — a counter is appended to each PRF input block.
502    Counter,
503    /// Feedback mode — the previous PRF output is fed into the next block.
504    Feedback,
505}
506
507#[cfg(ossl350)]
508impl KbkdfMode {
509    fn as_cstr(self) -> &'static CStr {
510        match self {
511            KbkdfMode::Counter => c"counter",
512            KbkdfMode::Feedback => c"feedback",
513        }
514    }
515}
516
517/// Counter field length (in bits) for KBKDF counter mode.
518#[cfg(ossl350)]
519#[derive(Clone, Copy, Debug, PartialEq, Eq)]
520#[cfg_attr(ossl350, derive(Default))]
521pub enum KbkdfCounterLen {
522    /// 8-bit counter.
523    Bits8 = 8,
524    /// 16-bit counter.
525    Bits16 = 16,
526    /// 24-bit counter.
527    Bits24 = 24,
528    /// 32-bit counter (default).
529    #[cfg_attr(ossl350, default)]
530    Bits32 = 32,
531}
532
533/// KBKDF key-derivation builder (NIST SP 800-108).
534///
535/// Supports both HMAC-based and CMAC-based PRFs.
536///
537/// ```ignore
538/// let key = KbkdfBuilder::new(KbkdfMode::Counter, &hmac_alg, &master_key)
539///     .digest(&sha256)
540///     .label(b"my label")
541///     .context(b"my context")
542///     .derive_to_vec(32)?;
543/// ```
544#[cfg(ossl350)]
545pub struct KbkdfBuilder<'a> {
546    mode: KbkdfMode,
547    mac: &'a crate::mac::MacAlg,
548    digest: Option<&'a crate::digest::DigestAlg>,
549    key: &'a [u8],
550    label: Option<&'a [u8]>,
551    context: Option<&'a [u8]>,
552    /// Feedback mode only: IV / salt fed into the first PRF block.
553    salt: Option<&'a [u8]>,
554    counter_len: KbkdfCounterLen,
555    use_l: Option<bool>,
556    use_separator: Option<bool>,
557}
558
559#[cfg(ossl350)]
560impl<'a> KbkdfBuilder<'a> {
561    /// Create a KBKDF builder.
562    ///
563    /// - `mode` — counter or feedback.
564    /// - `mac` — MAC algorithm (HMAC or CMAC).
565    /// - `key` — the key derivation key (KDK).
566    #[must_use]
567    pub fn new(mode: KbkdfMode, mac: &'a crate::mac::MacAlg, key: &'a [u8]) -> Self {
568        KbkdfBuilder {
569            mode,
570            mac,
571            digest: None,
572            key,
573            label: None,
574            context: None,
575            salt: None,
576            counter_len: KbkdfCounterLen::default(),
577            use_l: None,
578            use_separator: None,
579        }
580    }
581
582    /// Set the hash digest for HMAC-based derivation.
583    #[must_use]
584    pub fn digest(mut self, digest: &'a crate::digest::DigestAlg) -> Self {
585        self.digest = Some(digest);
586        self
587    }
588
589    /// Set the label (identifies the purpose of the derived key).
590    #[must_use]
591    pub fn label(mut self, label: &'a [u8]) -> Self {
592        self.label = Some(label);
593        self
594    }
595
596    /// Set the context (caller-specific data bound into the derivation).
597    #[must_use]
598    pub fn context(mut self, context: &'a [u8]) -> Self {
599        self.context = Some(context);
600        self
601    }
602
603    /// Set the salt / feedback IV (feedback mode only).
604    #[must_use]
605    pub fn salt(mut self, salt: &'a [u8]) -> Self {
606        self.salt = Some(salt);
607        self
608    }
609
610    /// Override the counter field length (default: 32 bits).
611    #[must_use]
612    pub fn counter_len(mut self, len: KbkdfCounterLen) -> Self {
613        self.counter_len = len;
614        self
615    }
616
617    /// Control whether the length field `L` is included (default: true).
618    #[must_use]
619    pub fn use_l(mut self, enabled: bool) -> Self {
620        self.use_l = Some(enabled);
621        self
622    }
623
624    /// Control whether the zero-byte separator is included (default: true).
625    #[must_use]
626    pub fn use_separator(mut self, enabled: bool) -> Self {
627        self.use_separator = Some(enabled);
628        self
629    }
630
631    /// Derive key material, writing into `out`.
632    ///
633    /// # Errors
634    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack> {
635        let mut builder = crate::params::ParamBuilder::new()?
636            .push_utf8_string(c"mode", self.mode.as_cstr())?
637            .push_utf8_string(c"mac", self.mac.name())?
638            .push_octet_slice(c"key", self.key)?
639            .push_uint(c"r", self.counter_len as u32)?;
640
641        if let Some(d) = self.digest {
642            let name_ptr = unsafe { sys::OBJ_nid2sn(d.nid()) };
643            if name_ptr.is_null() {
644                return Err(ErrorStack::drain());
645            }
646            let name = unsafe { CStr::from_ptr(name_ptr) };
647            builder = builder.push_utf8_string(c"digest", name)?;
648        }
649        if let Some(l) = self.label {
650            builder = builder.push_octet_slice(c"label", l)?;
651        }
652        if let Some(c) = self.context {
653            builder = builder.push_octet_slice(c"data", c)?;
654        }
655        if let Some(s) = self.salt {
656            builder = builder.push_octet_slice(c"salt", s)?;
657        }
658        if let Some(v) = self.use_l {
659            builder = builder.push_int(c"use-l", i32::from(v))?;
660        }
661        if let Some(v) = self.use_separator {
662            builder = builder.push_int(c"use-separator", i32::from(v))?;
663        }
664
665        let params = builder.build()?;
666        let alg = KdfAlg::fetch(c"KBKDF")?;
667        KdfCtx::new(&alg)?.derive(out, &params)
668    }
669
670    /// Derive `len` bytes, returning them in a freshly allocated `Vec<u8>`.
671    ///
672    /// # Errors
673    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack> {
674        let mut out = vec![0u8; len];
675        self.derive(&mut out)?;
676        Ok(out)
677    }
678}
679
680// ── Pkcs12KdfBuilder (RFC 7292 Appendix B) ───────────────────────────────────
681
682/// Output type selector for the PKCS#12 (RFC 7292 Appendix B) KDF.
683///
684/// The id byte controls which key component the KDF derives from the passphrase.
685#[derive(Clone, Copy, Debug, PartialEq, Eq)]
686pub enum Pkcs12KdfId {
687    /// Derive cipher key bytes (id = 1).
688    Key = 1,
689    /// Derive cipher IV bytes (id = 2).
690    Iv = 2,
691    /// Derive MAC key bytes (id = 3).
692    Mac = 3,
693}
694
695/// PKCS#12 (RFC 7292 Appendix B) key/IV/MAC derivation builder.
696///
697/// Legacy — only needed for interoperability with PKCS#12 files encrypted
698/// with deprecated algorithms such as `PBEWithSHAAnd3-KeyTripleDES-CBC`.
699/// New PKCS#12 files should use PBES2/PBKDF2 instead.
700///
701/// ```ignore
702/// let sha1 = DigestAlg::fetch(c"SHA1", None).unwrap();
703/// let key = Pkcs12KdfBuilder::new(&sha1, b"password", &salt, Pkcs12KdfId::Key)
704///     .iterations(2048)
705///     .derive_to_vec(24)?;
706/// ```
707pub struct Pkcs12KdfBuilder<'a> {
708    md: &'a crate::digest::DigestAlg,
709    password: &'a [u8],
710    salt: &'a [u8],
711    id: Pkcs12KdfId,
712    iter: u32,
713}
714
715impl<'a> Pkcs12KdfBuilder<'a> {
716    /// Create a PKCS#12 KDF builder.
717    ///
718    /// - `md` — hash algorithm (SHA-1 for legacy 3DES; SHA-256 for PBES2).
719    /// - `password` — UTF-8 passphrase bytes.
720    /// - `salt` — random salt (typically 8 bytes per RFC 7292).
721    /// - `id` — output type: [`Key`](Pkcs12KdfId::Key), [`Iv`](Pkcs12KdfId::Iv),
722    ///   or [`Mac`](Pkcs12KdfId::Mac).
723    ///
724    /// The iteration count defaults to 2048.
725    #[must_use]
726    pub fn new(
727        md: &'a crate::digest::DigestAlg,
728        password: &'a [u8],
729        salt: &'a [u8],
730        id: Pkcs12KdfId,
731    ) -> Self {
732        Self {
733            md,
734            password,
735            salt,
736            id,
737            iter: 2048,
738        }
739    }
740
741    /// Override the iteration count.
742    #[must_use]
743    pub fn iterations(mut self, n: u32) -> Self {
744        self.iter = n;
745        self
746    }
747
748    /// Derive key material into `out`.
749    ///
750    /// # Panics
751    ///
752    /// Panics if the password, salt, or output buffer length exceeds `i32::MAX` bytes,
753    /// which is not a practical concern for cryptographic inputs.
754    ///
755    /// # Errors
756    pub fn derive(&self, out: &mut [u8]) -> Result<(), ErrorStack> {
757        // SAFETY: PKCS12_key_gen_utf8 reads pass and salt; it does not retain
758        // pointers after returning.  The salt parameter is typed `*mut u8` in
759        // the C header despite being read-only; we cast from *const u8.
760        let rc = unsafe {
761            sys::PKCS12_key_gen_utf8(
762                self.password.as_ptr().cast(),
763                i32::try_from(self.password.len()).expect("password too long"),
764                self.salt.as_ptr().cast_mut(),
765                i32::try_from(self.salt.len()).expect("salt too long"),
766                self.id as std::ffi::c_int,
767                self.iter.cast_signed(),
768                i32::try_from(out.len()).expect("output too long"),
769                out.as_mut_ptr(),
770                self.md.as_ptr(),
771            )
772        };
773        if rc != 1 {
774            return Err(ErrorStack::drain());
775        }
776        Ok(())
777    }
778
779    /// Derive `len` bytes of key material, returning them in a freshly
780    /// allocated `Vec<u8>`.
781    ///
782    /// # Errors
783    pub fn derive_to_vec(&self, len: usize) -> Result<Vec<u8>, ErrorStack> {
784        let mut out = vec![0u8; len];
785        self.derive(&mut out)?;
786        Ok(out)
787    }
788}
789
790// ── Tests ─────────────────────────────────────────────────────────────────────
791
792#[cfg(test)]
793mod tests {
794    use super::*;
795    use crate::digest::DigestAlg;
796
797    /// RFC 5869 Test Case 1 — `HKDF-SHA-256`, known-answer test.
798    ///
799    /// IKM  = 0x0b0b...0b (22 bytes)
800    /// salt = 0x000102...0c (13 bytes)
801    /// info = 0xf0f1...f9 (10 bytes)
802    /// L    = 42
803    #[test]
804    fn hkdf_sha256_rfc5869_tc1() {
805        let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
806
807        let ikm = [0x0b_u8; 22];
808        let salt = [
809            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c_u8,
810        ];
811        let info = [
812            0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9_u8,
813        ];
814
815        let okm = HkdfBuilder::new(&digest)
816            .key(&ikm)
817            .salt(&salt)
818            .info(&info)
819            .derive_to_vec(42)
820            .unwrap();
821
822        assert_eq!(
823            hex::encode(&okm),
824            "3cb25f25faacd57a90434f64d0362f2a\
825             2d2d0a90cf1a5a4c5db02d56ecc4c5bf\
826             34007208d5b887185865"
827        );
828    }
829
830    /// HKDF without salt — RFC 5869 Test Case 3.
831    ///
832    /// IKM  = 0x0b0b...0b (22 bytes)
833    /// salt = (not provided)
834    /// info = (not provided)
835    /// L    = 42
836    #[test]
837    fn hkdf_sha256_no_salt_no_info() {
838        let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
839        let ikm = [0x0b_u8; 22];
840
841        let okm = HkdfBuilder::new(&digest)
842            .key(&ikm)
843            .derive_to_vec(42)
844            .unwrap();
845
846        assert_eq!(
847            hex::encode(&okm),
848            "8da4e775a563c18f715f802a063c5a31\
849             b8a11f5c5ee1879ec3454e5f3c738d2d\
850             9d201395faa4b61a96c8"
851        );
852    }
853
854    /// `PBKDF2-SHA256` known-answer test.
855    ///
856    /// Password   = "password"
857    /// Salt       = "salt"
858    /// Iterations = 1
859    /// dkLen      = 32
860    #[test]
861    fn pbkdf2_sha256_known_answer() {
862        let digest = DigestAlg::fetch(c"SHA2-256", None).unwrap();
863
864        let dk = Pbkdf2Builder::new(&digest, b"password", b"salt")
865            .iterations(1)
866            .derive_to_vec(32)
867            .unwrap();
868
869        assert_eq!(
870            hex::encode(&dk),
871            "120fb6cffcf8b32c43e7225256c4f837\
872             a86548c92ccc35480805987cb70be17b"
873        );
874    }
875
876    /// Scrypt functional test — verify correct output length and non-zeroness.
877    ///
878    /// Uses tiny cost parameters (N=32, r=1, p=1) so the test is fast.
879    #[test]
880    fn scrypt_derives_nonzero_output() {
881        let dk = ScryptBuilder::new(b"password", b"salt")
882            .params(ScryptParams { n: 32, r: 1, p: 1 })
883            .derive_to_vec(32)
884            .unwrap();
885
886        assert_eq!(dk.len(), 32);
887        assert_ne!(dk, vec![0u8; 32]);
888    }
889
890    /// Two different passwords produce different keys.
891    #[test]
892    fn scrypt_different_passwords_differ() {
893        let p = ScryptParams { n: 32, r: 1, p: 1 };
894
895        let dk1 = ScryptBuilder::new(b"pass1", b"salt")
896            .params(ScryptParams {
897                n: p.n,
898                r: p.r,
899                p: p.p,
900            })
901            .derive_to_vec(32)
902            .unwrap();
903        let dk2 = ScryptBuilder::new(b"pass2", b"salt")
904            .params(ScryptParams {
905                n: p.n,
906                r: p.r,
907                p: p.p,
908            })
909            .derive_to_vec(32)
910            .unwrap();
911
912        assert_ne!(dk1, dk2);
913    }
914
915    /// PKCS#12 KDF derives non-zero output of the requested length.
916    ///
917    /// No published test vectors exist for RFC 7292 Appendix B, so we use a
918    /// functional check: same inputs → same output, different id → different output.
919    #[test]
920    fn pkcs12_kdf_basic() {
921        let sha1 = DigestAlg::fetch(c"SHA1", None).unwrap();
922        let salt = b"saltsalt";
923
924        let key = Pkcs12KdfBuilder::new(&sha1, b"password", salt, Pkcs12KdfId::Key)
925            .iterations(2048)
926            .derive_to_vec(24)
927            .unwrap();
928        assert_eq!(key.len(), 24);
929        assert_ne!(key, vec![0u8; 24]);
930
931        // Same inputs, same output.
932        let key2 = Pkcs12KdfBuilder::new(&sha1, b"password", salt, Pkcs12KdfId::Key)
933            .iterations(2048)
934            .derive_to_vec(24)
935            .unwrap();
936        assert_eq!(key, key2);
937
938        // Different id byte → different output.
939        let iv = Pkcs12KdfBuilder::new(&sha1, b"password", salt, Pkcs12KdfId::Iv)
940            .iterations(2048)
941            .derive_to_vec(8)
942            .unwrap();
943        assert_ne!(key[..8], iv[..]);
944    }
945
946    /// Different passwords produce different derived keys.
947    #[test]
948    fn pkcs12_kdf_different_passwords_differ() {
949        let sha1 = DigestAlg::fetch(c"SHA1", None).unwrap();
950        let salt = b"saltsalt";
951
952        let k1 = Pkcs12KdfBuilder::new(&sha1, b"pass1", salt, Pkcs12KdfId::Key)
953            .derive_to_vec(24)
954            .unwrap();
955        let k2 = Pkcs12KdfBuilder::new(&sha1, b"pass2", salt, Pkcs12KdfId::Key)
956            .derive_to_vec(24)
957            .unwrap();
958        assert_ne!(k1, k2);
959    }
960}