ring/
hkdf.rs

1// Copyright 2015 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15//! HMAC-based Extract-and-Expand Key Derivation Function.
16//!
17//! HKDF is specified in [RFC 5869].
18//!
19//! [RFC 5869]: https://tools.ietf.org/html/rfc5869
20
21use crate::{error, hmac};
22
23/// An HKDF algorithm.
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
25pub struct Algorithm(hmac::Algorithm);
26
27impl Algorithm {
28    /// The underlying HMAC algorithm.
29    #[inline]
30    pub fn hmac_algorithm(&self) -> hmac::Algorithm {
31        self.0
32    }
33}
34
35/// HKDF using HMAC-SHA-1. Obsolete.
36pub static HKDF_SHA1_FOR_LEGACY_USE_ONLY: Algorithm =
37    Algorithm(hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY);
38
39/// HKDF using HMAC-SHA-256.
40pub static HKDF_SHA256: Algorithm = Algorithm(hmac::HMAC_SHA256);
41
42/// HKDF using HMAC-SHA-384.
43pub static HKDF_SHA384: Algorithm = Algorithm(hmac::HMAC_SHA384);
44
45/// HKDF using HMAC-SHA-512.
46pub static HKDF_SHA512: Algorithm = Algorithm(hmac::HMAC_SHA512);
47
48impl KeyType for Algorithm {
49    fn len(&self) -> usize {
50        self.0.digest_algorithm().output_len()
51    }
52}
53
54/// A salt for HKDF operations.
55#[derive(Debug)]
56pub struct Salt(hmac::Key);
57
58impl Salt {
59    /// Constructs a new `Salt` with the given value based on the given digest
60    /// algorithm.
61    ///
62    /// Constructing a `Salt` is relatively expensive so it is good to reuse a
63    /// `Salt` object instead of re-constructing `Salt`s with the same value.
64    pub fn new(algorithm: Algorithm, value: &[u8]) -> Self {
65        Self(hmac::Key::new(algorithm.0, value))
66    }
67
68    /// The [HKDF-Extract] operation.
69    ///
70    /// [HKDF-Extract]: https://tools.ietf.org/html/rfc5869#section-2.2
71    pub fn extract(&self, secret: &[u8]) -> Prk {
72        // The spec says that if no salt is provided then a key of
73        // `digest_alg.output_len` bytes of zeros is used. But, HMAC keys are
74        // already zero-padded to the block length, which is larger than the output
75        // length of the extract step (the length of the digest). Consequently the
76        // `Key` constructor will automatically do the right thing for a
77        // zero-length string.
78        let salt = &self.0;
79        let prk = hmac::sign(salt, secret);
80        Prk(hmac::Key::new(salt.algorithm(), prk.as_ref()))
81    }
82
83    /// The algorithm used to derive this salt.
84    #[inline]
85    pub fn algorithm(&self) -> Algorithm {
86        Algorithm(self.0.algorithm())
87    }
88}
89
90impl From<Okm<'_, Algorithm>> for Salt {
91    fn from(okm: Okm<'_, Algorithm>) -> Self {
92        Self(hmac::Key::from(Okm {
93            prk: okm.prk,
94            info: okm.info,
95            len: okm.len().0,
96            len_cached: okm.len_cached,
97        }))
98    }
99}
100
101/// The length of the OKM (Output Keying Material) for a `Prk::expand()` call.
102pub trait KeyType {
103    /// The length that `Prk::expand()` should expand its input to.
104    fn len(&self) -> usize;
105}
106
107/// A HKDF PRK (pseudorandom key).
108#[derive(Clone, Debug)]
109pub struct Prk(hmac::Key);
110
111impl Prk {
112    /// Construct a new `Prk` directly with the given value.
113    ///
114    /// Usually one can avoid using this. It is useful when the application
115    /// intentionally wants to leak the PRK secret, e.g. to implement
116    /// `SSLKEYLOGFILE` functionality.
117    pub fn new_less_safe(algorithm: Algorithm, value: &[u8]) -> Self {
118        Self(hmac::Key::new(algorithm.hmac_algorithm(), value))
119    }
120
121    /// The [HKDF-Expand] operation.
122    ///
123    /// [HKDF-Expand]: https://tools.ietf.org/html/rfc5869#section-2.3
124    ///
125    /// Fails if (and only if) `len` is too large.
126    #[inline]
127    pub fn expand<'a, L: KeyType>(
128        &'a self,
129        info: &'a [&'a [u8]],
130        len: L,
131    ) -> Result<Okm<'a, L>, error::Unspecified> {
132        let len_cached = len.len();
133        if len_cached > 255 * self.0.algorithm().digest_algorithm().output_len() {
134            return Err(error::Unspecified);
135        }
136        Ok(Okm {
137            prk: self,
138            info,
139            len,
140            len_cached,
141        })
142    }
143}
144
145impl From<Okm<'_, Algorithm>> for Prk {
146    fn from(okm: Okm<Algorithm>) -> Self {
147        Self(hmac::Key::from(Okm {
148            prk: okm.prk,
149            info: okm.info,
150            len: okm.len().0,
151            len_cached: okm.len_cached,
152        }))
153    }
154}
155
156/// An HKDF OKM (Output Keying Material)
157///
158/// Intentionally not `Clone` or `Copy` as an OKM is generally only safe to
159/// use once.
160#[derive(Debug)]
161pub struct Okm<'a, L: KeyType> {
162    prk: &'a Prk,
163    info: &'a [&'a [u8]],
164    len: L,
165    len_cached: usize,
166}
167
168impl<L: KeyType> Okm<'_, L> {
169    /// The `OkmLength` given to `Prk::expand()`.
170    #[inline]
171    pub fn len(&self) -> &L {
172        &self.len
173    }
174
175    /// Fills `out` with the output of the HKDF-Expand operation for the given
176    /// inputs.
177    ///
178    /// Fails if (and only if) the requested output length is larger than 255
179    /// times the size of the digest algorithm's output. (This is the limit
180    /// imposed by the HKDF specification due to the way HKDF's counter is
181    /// constructed.)
182    #[inline]
183    pub fn fill(self, out: &mut [u8]) -> Result<(), error::Unspecified> {
184        fill_okm(self.prk, self.info, out, self.len_cached)
185    }
186}
187
188fn fill_okm(
189    prk: &Prk,
190    info: &[&[u8]],
191    out: &mut [u8],
192    len: usize,
193) -> Result<(), error::Unspecified> {
194    if out.len() != len {
195        return Err(error::Unspecified);
196    }
197
198    let digest_alg = prk.0.algorithm().digest_algorithm();
199    assert!(digest_alg.block_len() >= digest_alg.output_len());
200
201    let mut ctx = hmac::Context::with_key(&prk.0);
202
203    let mut n = 1u8;
204    let mut out = out;
205    loop {
206        for info in info {
207            ctx.update(info);
208        }
209        ctx.update(&[n]);
210
211        let t = ctx.sign();
212        let t = t.as_ref();
213
214        // Append `t` to the output.
215        out = if out.len() < digest_alg.output_len() {
216            let len = out.len();
217            out.copy_from_slice(&t[..len]);
218            &mut []
219        } else {
220            let (this_chunk, rest) = out.split_at_mut(digest_alg.output_len());
221            this_chunk.copy_from_slice(t);
222            rest
223        };
224
225        if out.is_empty() {
226            return Ok(());
227        }
228
229        ctx = hmac::Context::with_key(&prk.0);
230        ctx.update(t);
231        n = n.checked_add(1).unwrap();
232    }
233}