rc_crypto/
hkdf.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5// This file contains code that was copied from the ring crate which is under
6// the ISC license, reproduced below:
7
8// Copyright 2015-2017 Brian Smith.
9
10// Permission to use, copy, modify, and/or distribute this software for any
11// purpose with or without fee is hereby granted, provided that the above
12// copyright notice and this permission notice appear in all copies.
13
14// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
15// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
17// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
19// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
20// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21
22use crate::{error::*, hmac};
23
24pub fn extract_and_expand(
25    salt: &hmac::SigningKey,
26    secret: &[u8],
27    info: &[u8],
28    out: &mut [u8],
29) -> Result<()> {
30    let prk = extract(salt, secret)?;
31    expand(&prk, info, out)?;
32    Ok(())
33}
34
35pub fn extract(salt: &hmac::SigningKey, secret: &[u8]) -> Result<hmac::SigningKey> {
36    let prk = hmac::sign(salt, secret)?;
37    Ok(hmac::SigningKey::new(salt.digest_algorithm(), prk.as_ref()))
38}
39
40pub fn expand(prk: &hmac::SigningKey, info: &[u8], out: &mut [u8]) -> Result<()> {
41    let mut derived =
42        nss::pk11::sym_key::hkdf_expand(prk.digest_alg, &prk.key_value, info, out.len())?;
43    out.swap_with_slice(&mut derived[0..out.len()]);
44    Ok(())
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use crate::digest;
51    use nss::ensure_initialized;
52
53    #[test]
54    fn hkdf_produces_correct_result() {
55        ensure_initialized();
56        let secret = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
57        let salt = hex::decode("000102030405060708090a0b0c").unwrap();
58        let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap();
59        let expected_out = hex::decode(
60            "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
61        )
62        .unwrap();
63        let salt = hmac::SigningKey::new(&digest::SHA256, &salt);
64        let mut out = vec![0u8; expected_out.len()];
65        extract_and_expand(&salt, &secret, &info, &mut out).unwrap();
66        assert_eq!(out, expected_out);
67    }
68
69    #[test]
70    fn hkdf_rejects_gigantic_salt() {
71        ensure_initialized();
72        if (u32::MAX as usize) < usize::MAX {
73            let salt_bytes = vec![0; (u32::MAX as usize) + 1];
74            let salt = hmac::SigningKey {
75                digest_alg: &digest::SHA256,
76                key_value: salt_bytes,
77            };
78            let mut out = vec![0u8; 8];
79            assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err());
80        }
81    }
82
83    #[test]
84    fn hkdf_rejects_gigantic_secret() {
85        ensure_initialized();
86        if (u32::MAX as usize) < usize::MAX {
87            let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
88            let secret = vec![0; (u32::MAX as usize) + 1];
89            let mut out = vec![0u8; 8];
90            assert!(extract_and_expand(&salt, secret.as_slice(), b"info", &mut out).is_err());
91        }
92    }
93
94    // N.B. the `info `parameter is a `c_ulong`, and I can't figure out how to check whether
95    // `c_ulong` is smaller than `usize` in order to write a `hkdf_rejects_gigantic_info` test.
96
97    #[test]
98    fn hkdf_rejects_gigantic_output_buffers() {
99        ensure_initialized();
100        let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
101        let mut out = vec![0u8; 8160 + 1]; // RFC maximum (hashlen * 255) + 1
102        assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err());
103    }
104
105    #[test]
106    fn hkdf_rejects_zero_length_output_buffer() {
107        ensure_initialized();
108        let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
109        let mut out = vec![0u8; 0];
110        assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err());
111    }
112
113    #[test]
114    fn hkdf_can_produce_small_output() {
115        ensure_initialized();
116        let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
117        let mut out = vec![0u8; 1];
118        assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_ok());
119    }
120
121    #[test]
122    fn hkdf_accepts_zero_length_info() {
123        ensure_initialized();
124        let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
125        let mut out = vec![0u8; 32];
126        assert!(extract_and_expand(&salt, b"secret", b"", &mut out).is_ok());
127    }
128
129    #[test]
130    fn hkdf_expand_rejects_short_prk() {
131        ensure_initialized();
132        let prk = hmac::SigningKey::new(&digest::SHA256, b"too short"); // must be >= HashLen
133        let mut out = vec![0u8; 8];
134        assert!(expand(&prk, b"info", &mut out).is_ok());
135    }
136}