Skip to main content

wlan_rsn/key/
igtk.rs

1// Copyright 2020 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 crate::key::Tk;
6use crate::key_data::kde;
7use crate::{Error, rsn_ensure};
8use mundane::bytes;
9use wlan_common::ie::rsn::cipher::Cipher;
10
11/// This IGTK provider does not support key rotations yet.
12#[derive(Debug)]
13pub struct IgtkProvider {
14    key: Box<[u8]>,
15    tk_bytes: usize,
16    cipher: Cipher,
17}
18
19// IEEE 802.11-2016 12.7.1.5 - The Authenticator shall select the IGTK
20// as a random value each time it is generated.
21fn generate_random_igtk(len: usize) -> Box<[u8]> {
22    let mut key = vec![0; len];
23    bytes::rand(&mut key[..]);
24    key.into_boxed_slice()
25}
26
27impl IgtkProvider {
28    pub fn new(cipher: Cipher) -> Result<IgtkProvider, anyhow::Error> {
29        let tk_bytes: usize =
30            cipher.tk_bytes().ok_or(Error::IgtkHierarchyUnsupportedCipherError)?.into();
31        Ok(IgtkProvider { key: generate_random_igtk(tk_bytes), cipher, tk_bytes })
32    }
33
34    pub fn cipher(&self) -> Cipher {
35        self.cipher
36    }
37
38    pub fn rotate_key(&mut self) {
39        self.key = generate_random_igtk(self.tk_bytes);
40    }
41
42    pub fn get_igtk(&self) -> Igtk {
43        Igtk { igtk: self.key.to_vec(), key_id: 0, ipn: [0u8; 6], cipher: self.cipher.clone() }
44    }
45}
46
47#[derive(Debug, Clone, Eq)]
48pub struct Igtk {
49    pub igtk: Vec<u8>,
50    pub key_id: u16,
51    pub ipn: [u8; 6],
52    pub cipher: Cipher,
53}
54
55/// PartialEq implementation explicitly excludes the IPN (Integrity Packet Number).
56/// Both PartialEq and Hash ignore the IPN to prevent key re-installation (KRACK) on retransmissions.
57impl PartialEq for Igtk {
58    fn eq(&self, other: &Self) -> bool {
59        self.igtk == other.igtk && self.key_id == other.key_id && self.cipher == other.cipher
60    }
61}
62
63impl std::hash::Hash for Igtk {
64    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
65        self.igtk.hash(state);
66        self.key_id.hash(state);
67        self.cipher.hash(state);
68    }
69}
70
71impl Igtk {
72    #[allow(clippy::result_large_err, reason = "large Error enum")]
73    pub fn from_kde(element: kde::Igtk, cipher: Cipher) -> Result<Self, Error> {
74        let tk_len: usize =
75            cipher.tk_bytes().ok_or(Error::IgtkHierarchyUnsupportedCipherError)?.into();
76        // TODO(https://fxbug.dev/523310267): Handle the case where `element.igtk.len() > tk_len`
77        rsn_ensure!(
78            element.igtk.len() >= tk_len,
79            Error::InvalidKeyLength(element.igtk.len(), tk_len)
80        );
81        Ok(Self { igtk: element.igtk, key_id: element.id, ipn: element.ipn, cipher })
82    }
83}
84
85impl Tk for Igtk {
86    fn tk(&self) -> &[u8] {
87        &self.igtk[..]
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use wlan_common::ie::rsn::suite_filter::DEFAULT_GROUP_MGMT_CIPHER;
95
96    #[test]
97    fn test_igtk_generation() {
98        let mut igtk_provider =
99            IgtkProvider::new(DEFAULT_GROUP_MGMT_CIPHER).expect("failed creating IgtkProvider");
100
101        let first_igtk = igtk_provider.get_igtk().tk().to_vec();
102        for _ in 0..3 {
103            igtk_provider.rotate_key();
104            if first_igtk != igtk_provider.get_igtk().tk().to_vec() {
105                return;
106            }
107        }
108        panic!("IGTK key rotation always generates the same key!");
109    }
110
111    #[test]
112    fn test_igtk_from_kde_validation() {
113        // DEFAULT_GROUP_MGMT_CIPHER is BIP-CMAC-128, which expects a 16-byte key.
114        let ipn = [0u8; 6];
115
116        // 1. Correct key size (16 bytes)
117        let exact_key = vec![0xaa; 16];
118        let element = kde::Igtk::new(1, &ipn[..], &exact_key[..]);
119        let igtk = Igtk::from_kde(element, DEFAULT_GROUP_MGMT_CIPHER);
120        assert!(igtk.is_ok());
121        let igtk = igtk.unwrap();
122        assert_eq!(igtk.igtk, exact_key);
123
124        // 2. Shorter key size (e.g. 15 bytes) -> should fail.
125        let short_key = vec![0xcc; 15];
126        let element = kde::Igtk::new(1, &ipn[..], &short_key[..]);
127        let igtk = Igtk::from_kde(element, DEFAULT_GROUP_MGMT_CIPHER);
128        assert!(igtk.is_err());
129    }
130}