wlan_sae/
hmac_utils.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use mundane::hash::{Digest, Hasher};
6use mundane::hmac::Hmac;
7
8// IEEE Std. 802.11-2020 12.4: Simultaneous Authentication of Equals (SAE)
9// SAE uses a few constructions using HMACs, where the underlying cryptographic hash algorithm is
10// parameterized based on properties of the connection.  This file contains implementations of
11// these constructions, as well as a generic implementing a trait that allows passing around a set
12// of parameterized function calls as a trait object.
13
14fn div_ceil(x: usize, y: usize) -> usize {
15    (x + (y - 1)) / y
16}
17
18/// IEEE Std. 802.11-2020 12.7.1.6.2
19/// The KDF function (named "KDF-Hash-Length" in the specification).
20pub fn kdf_hash_length<H>(key: &[u8], label: &str, context: &[u8], bits: usize) -> Vec<u8>
21where
22    H: Hasher,
23    <<H as Hasher>::Digest as Digest>::Bytes: AsRef<[u8]> + AsMut<[u8]>,
24{
25    let byte_length = div_ceil(bits, 8);
26    let iterations = div_ceil(bits, H::Digest::DIGEST_LEN * 8);
27    let mut result = Vec::with_capacity(byte_length);
28    let mut copied: usize = 0;
29    for i in 1..=iterations {
30        let to_copy = std::cmp::min(H::Digest::DIGEST_LEN, byte_length - copied);
31        {
32            let mut hmac: Hmac<H> = Hmac::new(key);
33            hmac.update(&(i as u16).to_le_bytes());
34            hmac.update(label.as_bytes());
35            hmac.update(context);
36            hmac.update(&(bits as u16).to_le_bytes());
37            let mut digest = hmac.finish().bytes();
38            // IEEE Std. 802.11-2020 12.7.1.6.2: unused bits should be "irretrievably deleted".
39            digest.as_mut()[to_copy..H::Digest::DIGEST_LEN].fill(0);
40            result.extend_from_slice(&digest.as_ref()[..to_copy]);
41        }
42        copied += to_copy;
43    }
44    result
45}
46
47/// IEEE Std. 802.11-2020 12.4.4.2.3/12.4.4.3.3, IETF RFC 5869
48/// A KDF to "extract" entropy from a short key (named "H" or "HKDF-Extract" in the specification).
49pub fn hkdf_extract<H>(salt: &[u8], ikm: &[u8]) -> Vec<u8>
50where
51    H: Hasher,
52    <<H as Hasher>::Digest as Digest>::Bytes: AsRef<[u8]>,
53{
54    // The HMAC is used to extract entropy from `ikm`, the input keying material; thus it is keyed
55    // with `salt` and `ikm` is used as the input message.
56    let mut hmac: Hmac<H> = Hmac::new(salt);
57    hmac.update(ikm);
58    hmac.finish().bytes().as_ref().to_vec()
59}
60
61/// IEEE Std. 802.11-2020 12.4.4.2.3/12.4.4.3.3, IETF RFC 5869
62/// A KDF to "expand" entropy into a long key (named "H" and "HKDF-Expand" in the specification).
63pub fn hkdf_expand<H>(prk: &[u8], info: &str, length: usize) -> Vec<u8>
64where
65    H: Hasher,
66    <<H as Hasher>::Digest as Digest>::Bytes: AsRef<[u8]>,
67{
68    let mut result: Vec<u8> = Vec::with_capacity(length);
69    let mut copied: usize = 0;
70    let mut prev_digest: Option<H::Digest> = None;
71    let digest_count = div_ceil(length, H::Digest::DIGEST_LEN);
72    for counter in 1..digest_count + 1 {
73        let to_copy = std::cmp::min(H::Digest::DIGEST_LEN, length - copied);
74        let digest = {
75            let mut hmac: Hmac<H> = Hmac::new(prk);
76            if let Some(prev_digest) = &prev_digest {
77                hmac.update(prev_digest.bytes().as_ref());
78            }
79            hmac.update(info.as_bytes());
80            hmac.update(&(counter as u8).to_le_bytes());
81            hmac.finish()
82        };
83        result.extend_from_slice(&digest.bytes().as_ref()[..to_copy]);
84        copied += to_copy;
85        let _ = prev_digest.insert(digest);
86    }
87    result
88}
89
90/// IEEE Std. 802.11-2020 12.4.2
91/// The "confirm function" (named "CN" in the specification).
92pub fn confirm<H>(key: &[u8], counter: u16, data: &[&[u8]]) -> Vec<u8>
93where
94    H: Hasher,
95    <<H as Hasher>::Digest as Digest>::Bytes: AsRef<[u8]>,
96{
97    let mut hmac: Hmac<H> = Hmac::new(key);
98    hmac.update(&counter.to_le_bytes());
99    for data_part in data {
100        hmac.update(data_part);
101    }
102    hmac.finish().bytes().as_ref().to_vec()
103}
104
105/// Trait encapsulating implementations of the HMAC constructions for a given hasher type.
106pub trait HmacUtils {
107    /// The number of bits in the digest length of the HMAC type.
108    fn bits(&self) -> usize;
109    /// See: [kdf_hash_length()]
110    fn kdf_hash_length(&self, key: &[u8], label: &str, context: &[u8], bits: usize) -> Vec<u8>;
111    /// See: [hkdf_extract()]
112    fn hkdf_extract(&self, salt: &[u8], ikm: &[u8]) -> Vec<u8>;
113    /// See: [hkdf_expand()]
114    fn hkdf_expand(&self, prk: &[u8], info: &str, length: usize) -> Vec<u8>;
115    /// See: [confirm()]
116    fn confirm(&self, key: &[u8], counter: u16, data: &[&[u8]]) -> Vec<u8>;
117}
118
119/// Generic implementation of [HmacUtils] parameterized on a [Hasher] type.
120#[derive(Debug, Clone)]
121pub struct HmacUtilsImpl<H>
122where
123    H: Hasher,
124    <<H as Hasher>::Digest as Digest>::Bytes: AsRef<[u8]>,
125{
126    // Declared as PhantoimData<fn(H)> so that HmacUtilsImpl can implement Send.
127    hasher_type: std::marker::PhantomData<fn(H)>,
128}
129
130impl<H> HmacUtilsImpl<H>
131where
132    H: Hasher,
133    <<H as Hasher>::Digest as Digest>::Bytes: AsRef<[u8]>,
134{
135    pub fn new() -> Self {
136        Self { hasher_type: std::marker::PhantomData }
137    }
138}
139
140impl<H> HmacUtils for HmacUtilsImpl<H>
141where
142    H: Hasher,
143    <<H as Hasher>::Digest as Digest>::Bytes: AsMut<[u8]> + AsRef<[u8]>,
144{
145    fn bits(&self) -> usize {
146        H::Digest::DIGEST_LEN * 8
147    }
148
149    fn kdf_hash_length(&self, key: &[u8], label: &str, context: &[u8], bits: usize) -> Vec<u8> {
150        kdf_hash_length::<H>(key, label, context, bits)
151    }
152
153    fn hkdf_extract(&self, salt: &[u8], ikm: &[u8]) -> Vec<u8> {
154        hkdf_extract::<H>(salt, ikm)
155    }
156
157    fn hkdf_expand(&self, prk: &[u8], info: &str, length: usize) -> Vec<u8> {
158        hkdf_expand::<H>(prk, info, length)
159    }
160
161    fn confirm(&self, key: &[u8], counter: u16, data: &[&[u8]]) -> Vec<u8> {
162        confirm::<H>(key, counter, data)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::boringssl::{Bignum, BignumCtx, EcGroup, EcGroupId};
170
171    use mundane::hash::Sha256;
172    use std::convert::TryFrom;
173
174    // IEEE Std. 802.11-2020 J.10
175    // The 802.11 spec does not specify test vectors for the individual HMAC constructions
176    // explicitly; instead we test them against the test vectors for a step of an example SAE
177    // exchange that exercises the construction.
178    const TEST_GROUP: EcGroupId = EcGroupId::P256;
179    const TEST_LABEL: &'static str = "SAE Hunting and Pecking";
180    const TEST_H_1: &'static str =
181        "a9025368ef78f7d65e8d4d556f0d1d0d758f2f7f1e116eb1d11307a7e8a9621a";
182    const TEST_CAND_1: &'static str =
183        "b8e89a725c57f18e8f68a7f72613e15f1c904938c38800efa01f1306f5e454b5";
184    const TEST_H_2: &'static str =
185        "954bbbf8923284e4ca164e3af0b9520ce53aa35be39020e9ccb23aff86df2226";
186    const TEST_CAND_2: &'static str =
187        "da6eb7b06a1ac5624974f90afdd6a8e9d5722634cf987c34defc91a9874e5658";
188    const TEST_H_40: &'static str =
189        "cde7b81eb539c87af5bf1be2402d315c45ad4c3db06c9c56b7f8b7daae5e5842";
190    const TEST_CAND_40: &'static str =
191        "2e12a1d615647963fd7aa4a905fd51b6f49a902fd917ef8f0ff200102699ecdb";
192    const TEST_SSID: &'static str = "byteme";
193    const TEST_IDENTIFIER: &'static str = "psk4internet";
194    const TEST_PASSWORD: &'static str = "mekmitasdigoat";
195    const TEST_INFO_1: &'static str = "SAE Hash to Element u1 P1";
196    const TEST_INFO_2: &'static str = "SAE Hash to Element u2 P2";
197    const TEST_PRK: &'static str =
198        "3bd53fe9223dc0280fbfce17d7a3564064e20f48c6ec72246ce367b5569a22af";
199    const TEST_OKM_1: &'static str =
200        "a5044469ab16f25b6abf1e0e37a36b56f50be73369053df8db87989a6b66fd1a\
201         491f1cdacbd07931620f83008ffc0ecc";
202    const TEST_OKM_2: &'static str =
203        "9b4e0d5b1879f253c5319615099b05aec5b06fa5e788bcfd1e9ea60d33436927\
204         190814c322a62585c93c577bbaa3d307";
205
206    // IEEE Std. 802.11-18/1104r0: "New Test Vectors for SAE"
207    // Neither the 802.11-2016 nor the 802.11-2020 specs define a test vector result for the
208    // confirm construction.  For this we use the proposed new test vectors in 802.11-18/1104r0.
209    const TEST_LABEL_2: &'static str = "SAE KCK and PMK";
210    const TEST_LOCAL_COMMIT: &'static str =
211        "1300eb3bab1964e4a0ab05925ddf3339519138bc65d6cdc0f813dd6fd4344eb4\
212         bfe44b5c21597658f4e3eddfb4b99f25b4d6540f32ff1fd5c530c60a79444861\
213         0bc6de3d92bdbbd47d935980ca6cf8988ab6630be6764c885ceb9793970f6952\
214         17eeff0d2170736b34696e7465726e6574";
215    const TEST_PEER_COMMIT: &'static str =
216        "13005564f045b2ea1e566cf1dd741f70d9be35d2df5b9a5502946ee03cf8dae2\
217         7e1e05b8430eb7a99e24877ce69baf3dc580e309633d6b385f83ee1c3ec3591f\
218         1a5393c06e805ddceb2fde50930dd7cfebb987c6ff9666af164eb5184d8e6662\
219         ed6aff0d2170736b34696e7465726e6574";
220    const TEST_KEYSEED: &'static str =
221        "7457a00754dcc4e3dc2850c124d6bb8fa1699d7fa33bb0667d9c34eeb513deb9";
222    const TEST_KCK: &'static str =
223        "599d6f1e27548be8499dceed2feccf94818ce1c79f1b4eb3d6a53228a09bf3ed";
224    const TEST_LOCAL_CONFIRM: &'static str =
225        "010012d9d5c78c500526d36c41dbc56aedf2914cedddd7cad4a58c48f83dbde9\
226         fc77";
227    const TEST_PEER_CONFIRM: &'static str =
228        "010002871cf906898b8060ec184143be77b8c08a8019b13eb6d0aef0d8383dfa\
229         c2fd";
230    const TEST_PMK: &'static str =
231        "7aead86fba4c3221fc437f5f14d70d854ea5d5aac1690116793081eda4d557c5";
232
233    #[test]
234    fn test_kdf_sha256_256() {
235        let bignumctx = BignumCtx::new().unwrap();
236        let group = EcGroup::new(TEST_GROUP).unwrap();
237        let p = group.get_params(&bignumctx).unwrap().p;
238        let p_vec = p.to_be_vec(p.len());
239        let p_bits = p.bits();
240
241        let cand_1 =
242            kdf_hash_length::<Sha256>(&hex::decode(TEST_H_1).unwrap(), TEST_LABEL, &p_vec, p_bits);
243        assert_eq!(hex::encode(&cand_1), TEST_CAND_1);
244        let cand_2 =
245            kdf_hash_length::<Sha256>(&hex::decode(TEST_H_2).unwrap(), TEST_LABEL, &p_vec, p_bits);
246        assert_eq!(hex::encode(&cand_2), TEST_CAND_2);
247        let cand_40 =
248            kdf_hash_length::<Sha256>(&hex::decode(TEST_H_40).unwrap(), TEST_LABEL, &p_vec, p_bits);
249        assert_eq!(hex::encode(&cand_40), TEST_CAND_40);
250    }
251
252    #[test]
253    fn test_kdf_sha256_512() {
254        let bignumctx = BignumCtx::new().unwrap();
255        let group = EcGroup::new(TEST_GROUP).unwrap();
256        let r = group.get_order(&bignumctx).unwrap();
257
258        // IEEE Std. 802.11-2020 12.4.5.4
259        // TEST_LOCAL_COMMIT and TEST_PEER_COMMIT contain constructed SAE commit messages; we parse
260        // their fields here.
261        let local_commit_scalar = hex::decode(TEST_LOCAL_COMMIT).unwrap();
262        let local_commit_scalar = &local_commit_scalar[2..2 + r.len()];
263        let local_commit_scalar = Bignum::new_from_slice(local_commit_scalar).unwrap();
264        let peer_commit_scalar = hex::decode(TEST_PEER_COMMIT).unwrap();
265        let peer_commit_scalar = &peer_commit_scalar[2..2 + r.len()];
266        let peer_commit_scalar = Bignum::new_from_slice(peer_commit_scalar).unwrap();
267
268        let context = local_commit_scalar.mod_add(&peer_commit_scalar, &r, &bignumctx).unwrap();
269        let q = 256;
270        let kck_and_pmk = kdf_hash_length::<Sha256>(
271            &hex::decode(TEST_KEYSEED).unwrap(),
272            TEST_LABEL_2,
273            &context.to_be_vec(r.len()),
274            q + 256,
275        );
276        assert_eq!(kck_and_pmk.len(), (q + 256) / 8);
277        assert_eq!(hex::encode(&kck_and_pmk[0..q / 8]), TEST_KCK);
278        assert_eq!(hex::encode(&kck_and_pmk[q / 8..(q + 256) / 8]), TEST_PMK);
279    }
280
281    #[test]
282    fn test_kdf_sha256_short() {
283        let key = hex::decode("f0f0f0f0").unwrap();
284        let label = "LABELED!";
285        let context = hex::decode("babababa").unwrap();
286        let hash = kdf_hash_length::<Sha256>(&key[..], label, &context[..], 128);
287        assert_eq!(hash.len(), 16);
288    }
289
290    #[test]
291    fn test_kdf_sha256_empty_data() {
292        let key = hex::decode("f0f0f0f0").unwrap();
293        let label = "LABELED!";
294        let context = hex::decode("babababa").unwrap();
295        let hash = kdf_hash_length::<Sha256>(&key[..], label, &context[..], 0);
296        assert_eq!(hash.len(), 0);
297    }
298
299    #[test]
300    fn test_kdf_sha256_all_empty() {
301        let key = vec![];
302        let label = "";
303        let context = vec![];
304        let hash = kdf_hash_length::<Sha256>(&key[..], label, &context[..], 0);
305        assert_eq!(hash.len(), 0);
306    }
307
308    #[test]
309    fn test_hkdf_extract() {
310        let mut password_with_id: String = String::from(TEST_PASSWORD);
311        password_with_id.push_str(TEST_IDENTIFIER);
312        let pwd_seed = hkdf_extract::<Sha256>(TEST_SSID.as_bytes(), password_with_id.as_bytes());
313        assert_eq!(hex::encode(pwd_seed), TEST_PRK);
314    }
315
316    #[test]
317    fn test_hkdf_expand() {
318        let bignumctx = BignumCtx::new().unwrap();
319        let group = EcGroup::new(TEST_GROUP).unwrap();
320        let p = group.get_params(&bignumctx).unwrap().p;
321        let p_len = p.len();
322
323        let okm_1 = hkdf_expand::<Sha256>(
324            &hex::decode(TEST_PRK).unwrap(),
325            TEST_INFO_1,
326            p_len + (p_len / 2),
327        );
328        assert_eq!(hex::encode(&okm_1), TEST_OKM_1);
329        let okm_2 = hkdf_expand::<Sha256>(
330            &hex::decode(TEST_PRK).unwrap(),
331            TEST_INFO_2,
332            p_len + (p_len / 2),
333        );
334        assert_eq!(hex::encode(&okm_2), TEST_OKM_2);
335    }
336
337    #[test]
338    fn test_confirm() {
339        let bignumctx = BignumCtx::new().unwrap();
340        let group = EcGroup::new(TEST_GROUP).unwrap();
341        let r = group.get_order(&bignumctx).unwrap();
342
343        // IEEE Std. 802.11-2020 12.4.7.4
344        // TEST_LOCAL_COMMIT and TEST_PEER_COMMIT contain constructed SAE commit messages; we parse
345        // their fields here.
346        let local_commit_bytes = hex::decode(TEST_LOCAL_COMMIT).unwrap();
347        let local_commit_scalar = &local_commit_bytes[2..2 + r.len()];
348        let local_commit_element = &local_commit_bytes[2 + r.len()..2 + r.len() * 3];
349        let peer_commit_bytes = hex::decode(TEST_PEER_COMMIT).unwrap();
350        let peer_commit_scalar = &peer_commit_bytes[2..2 + r.len()];
351        let peer_commit_element = &peer_commit_bytes[2 + r.len()..2 + r.len() * 3];
352
353        // IEEE Std. 802.11-2020 12.4.7.5
354        // TEST_LOCAL_CONFIRM and TEST_PEER_CONFIRM contain constructed SAE confirm messages; we
355        // parse their fields here.
356        let local_confirm_bytes = hex::decode(TEST_LOCAL_CONFIRM).unwrap();
357        let local_send_confirm =
358            u16::from_le_bytes(*<&[u8; 2]>::try_from(&local_confirm_bytes[0..2]).unwrap());
359        let local_confirm_element = &local_confirm_bytes[2..2 + r.len()];
360        let peer_confirm_bytes = hex::decode(TEST_PEER_CONFIRM).unwrap();
361        let peer_send_confirm =
362            u16::from_le_bytes(*<&[u8; 2]>::try_from(&peer_confirm_bytes[0..2]).unwrap());
363        let peer_confirm_element = &peer_confirm_bytes[2..2 + r.len()];
364
365        let local_confirm = confirm::<Sha256>(
366            &hex::decode(TEST_KCK).unwrap(),
367            local_send_confirm,
368            &[local_commit_scalar, local_commit_element, peer_commit_scalar, peer_commit_element],
369        );
370        assert_eq!(hex::encode(&local_confirm), hex::encode(local_confirm_element));
371
372        let peer_confirm = confirm::<Sha256>(
373            &hex::decode(TEST_KCK).unwrap(),
374            peer_send_confirm,
375            &[peer_commit_scalar, peer_commit_element, local_commit_scalar, local_commit_element],
376        );
377        assert_eq!(hex::encode(&peer_confirm), hex::encode(peer_confirm_element));
378    }
379}