Skip to main content

ring_native_ossl/
hkdf.rs

1//! HKDF key derivation, mirroring `ring::hkdf`.
2//!
3//! Implements the two-step HKDF construction (RFC 5869): [`Salt::extract`]
4//! runs HKDF-Extract to produce a [`Prk`], and [`Prk::expand`] runs
5//! HKDF-Expand to fill a caller-supplied buffer with output keying material.
6//! PRK bytes are stored in [`native_ossl::util::SecretBuf`] and erased on drop.
7
8use crate::error::Unspecified;
9use native_ossl::digest::DigestAlg;
10use native_ossl::kdf::{HkdfBuilder, HkdfMode};
11use native_ossl::util::SecretBuf;
12
13/// HKDF algorithm, wrapping a digest algorithm (mirrors `ring::hkdf::Algorithm`).
14#[derive(Clone, Copy, Debug)]
15pub struct Algorithm(pub(crate) &'static crate::digest::Algorithm);
16
17pub static HKDF_SHA256: Algorithm = Algorithm(&crate::digest::SHA256);
18pub static HKDF_SHA384: Algorithm = Algorithm(&crate::digest::SHA384);
19pub static HKDF_SHA512: Algorithm = Algorithm(&crate::digest::SHA512);
20
21impl Algorithm {
22    #[must_use]
23    pub fn hmac_algorithm(&self) -> crate::hmac::Algorithm {
24        crate::hmac::Algorithm(self.0)
25    }
26}
27
28/// Trait for HKDF output key types (mirrors `ring::hkdf::KeyType`).
29pub trait KeyType {
30    fn len(&self) -> usize;
31    fn is_empty(&self) -> bool {
32        self.len() == 0
33    }
34}
35
36/// HKDF Salt — holds salt bytes and algorithm for the extract step
37/// (mirrors `ring::hkdf::Salt`).
38#[derive(Debug)]
39pub struct Salt {
40    alg: Algorithm,
41    salt: Vec<u8>,
42}
43
44impl Salt {
45    #[must_use]
46    pub fn new(algorithm: Algorithm, value: &[u8]) -> Self {
47        Self {
48            alg: algorithm,
49            salt: value.to_vec(),
50        }
51    }
52
53    /// HKDF-Extract: combine salt + IKM → PRK.
54    ///
55    /// # Panics
56    ///
57    /// Panics if OpenSSL cannot load the digest algorithm or the HKDF-Extract operation fails.
58    #[must_use]
59    pub fn extract(self, secret: &[u8]) -> Prk {
60        let digest_alg = DigestAlg::fetch(self.alg.0.name, None)
61            .unwrap_or_else(|e| panic!("OpenSSL digest unavailable: {e}"));
62        let prk = HkdfBuilder::new(&digest_alg)
63            .mode(HkdfMode::ExtractOnly)
64            .key(secret)
65            .salt(&self.salt)
66            .derive_to_vec(self.alg.0.output_len)
67            .unwrap_or_else(|e| panic!("HKDF extract failed: {e}"));
68        Prk {
69            alg: self.alg,
70            prk: SecretBuf::new(prk),
71        }
72    }
73
74    #[must_use]
75    pub fn algorithm(&self) -> Algorithm {
76        self.alg
77    }
78}
79
80/// HKDF Pseudorandom Key — result of the extract step
81/// (mirrors `ring::hkdf::Prk`).
82pub struct Prk {
83    alg: Algorithm,
84    prk: SecretBuf,
85}
86
87impl std::fmt::Debug for Prk {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.debug_struct("Prk")
90            .field("alg", &self.alg)
91            .finish_non_exhaustive()
92    }
93}
94
95impl Prk {
96    /// Create a `Prk` directly from pre-existing PRK bytes (e.g. for `ExpandOnly` use).
97    #[must_use]
98    pub fn new_less_safe(algorithm: Algorithm, value: &[u8]) -> Self {
99        Self {
100            alg: algorithm,
101            prk: SecretBuf::from_slice(value),
102        }
103    }
104
105    /// HKDF-Expand: PRK + info → OKM of the given length.
106    ///
107    /// # Errors
108    ///
109    /// Returns `Unspecified` if the requested length is invalid.
110    pub fn expand<'a, L: KeyType>(
111        &'a self,
112        info: &'a [&'a [u8]],
113        len: L,
114    ) -> Result<Okm<'a, L>, Unspecified> {
115        Ok(Okm {
116            prk: self,
117            info,
118            len,
119        })
120    }
121}
122
123/// HKDF Output Keying Material — lazy, fills on demand
124/// (mirrors `ring::hkdf::Okm`).
125pub struct Okm<'a, L: KeyType> {
126    prk: &'a Prk,
127    info: &'a [&'a [u8]],
128    len: L,
129}
130
131impl<L: KeyType> Okm<'_, L> {
132    #[must_use]
133    pub fn len(&self) -> &L {
134        &self.len
135    }
136
137    /// Fill `out` with the derived key material.
138    ///
139    /// # Errors
140    ///
141    /// Returns `Unspecified` if `out.len()` does not match the declared key length,
142    /// or if the underlying OpenSSL HKDF-Expand operation fails.
143    pub fn fill(self, out: &mut [u8]) -> Result<(), Unspecified> {
144        if out.len() != self.len.len() {
145            return Err(Unspecified);
146        }
147        let digest_alg = DigestAlg::fetch(self.prk.alg.0.name, None).map_err(|_| Unspecified)?;
148
149        // Concatenate info slices.
150        let info_concat: Vec<u8> = self.info.iter().flat_map(|s| s.iter().copied()).collect();
151
152        HkdfBuilder::new(&digest_alg)
153            .mode(HkdfMode::ExpandOnly)
154            .key(self.prk.prk.as_ref())
155            .info(&info_concat)
156            .derive(out)
157            .map_err(|_| Unspecified)
158    }
159}