wlan_common/ie/wpa/
mod.rs

1// Copyright 2019 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
5pub mod fake_wpa_ies;
6
7use super::rsn::{akm, cipher, suite_selector};
8
9use crate::append::{Append, BufferTooSmall};
10use crate::organization::Oui;
11use nom::bytes::streaming::take;
12use nom::combinator::map;
13use nom::multi::length_count;
14use nom::number::streaming::le_u16;
15use nom::{IResult, Parser};
16
17// The WPA1 IE is not fully specified by IEEE. This format was derived from pcap.
18// Note that this file only parses fields specific to WPA -- IE headers and MSFT-specific fields
19// are omitted.
20// (3B) OUI
21pub const OUI: Oui = Oui::MSFT;
22// (1B) OUI-specific element type
23pub const VENDOR_SPECIFIC_TYPE: u8 = 1;
24// (2B) WPA type
25pub const WPA_TYPE: u16 = 1;
26// (4B) multicast cipher
27//     0-2 cipher suite (OUI)
28//     3   cipher type
29// (2B) unicast cipher count
30// (4B x N) unicast cipher list
31// (2B) AKM count
32// (4B x N) AKM list
33#[derive(Debug, PartialOrd, PartialEq, Eq, Clone)]
34pub struct WpaIe {
35    pub multicast_cipher: cipher::Cipher,
36    pub unicast_cipher_list: Vec<cipher::Cipher>,
37    pub akm_list: Vec<akm::Akm>,
38}
39
40impl WpaIe {
41    const FIXED_FIELDS_LENGTH: usize = 10;
42    pub fn len(&self) -> usize {
43        Self::FIXED_FIELDS_LENGTH + self.unicast_cipher_list.len() * 4 + self.akm_list.len() * 4
44    }
45
46    pub fn into_bytes(self) -> Vec<u8> {
47        let mut buf = Vec::new();
48        self.write_into(&mut buf).unwrap();
49        buf
50    }
51
52    pub fn write_into<A: Append>(&self, buf: &mut A) -> Result<(), BufferTooSmall> {
53        if !buf.can_append(self.len()) {
54            return Err(BufferTooSmall);
55        }
56
57        buf.append_value(&WPA_TYPE)?;
58
59        buf.append_bytes(&self.multicast_cipher.oui[..])?;
60        buf.append_value(&self.multicast_cipher.suite_type)?;
61
62        buf.append_value(&(self.unicast_cipher_list.len() as u16))?;
63        for cipher in &self.unicast_cipher_list {
64            buf.append_bytes(&cipher.oui[..])?;
65            buf.append_value(&cipher.suite_type)?;
66        }
67
68        buf.append_value(&(self.akm_list.len() as u16))?;
69        for akm in &self.akm_list {
70            buf.append_bytes(&akm.oui[..])?;
71            buf.append_value(&akm.suite_type)?;
72        }
73
74        Ok(())
75    }
76}
77
78fn read_suite_selector<T>(input: &[u8]) -> IResult<&[u8], T>
79where
80    T: suite_selector::Factory<Suite = T>,
81{
82    let (i1, bytes) = take(4usize).parse(input)?;
83    let oui = Oui::new([bytes[0], bytes[1], bytes[2]]);
84    return Ok((i1, T::new(oui, bytes[3])));
85}
86
87fn parse_akm(input: &[u8]) -> IResult<&[u8], akm::Akm> {
88    read_suite_selector::<akm::Akm>(input)
89}
90
91fn parse_cipher(input: &[u8]) -> IResult<&[u8], cipher::Cipher> {
92    read_suite_selector::<cipher::Cipher>(input)
93}
94
95/// Convert bytes of a WPA information element into a WpaIe representation.
96pub fn from_bytes(input: &[u8]) -> IResult<&[u8], WpaIe> {
97    map(
98        // A terminated eof is not used since this IE sometimes adds extra
99        // non-compliant bytes.
100        (le_u16, parse_cipher, length_count(le_u16, parse_cipher), length_count(le_u16, parse_akm)),
101        |(_wpa_type, multicast_cipher, unicast_cipher_list, akm_list)| WpaIe {
102            multicast_cipher,
103            unicast_cipher_list,
104            akm_list,
105        },
106    )
107    .parse(input)
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[rustfmt::skip]
115    const DEFAULT_FRAME: [u8; 18] = [
116        // WPA version
117        0x01, 0x00,
118        // Multicast cipher
119        0x00, 0x50, 0xf2, 0x02,
120        // Unicast cipher list
121        0x01, 0x00, 0x00, 0x50, 0xf2, 0x02,
122        // AKM list
123        0x01, 0x00, 0x00, 0x50, 0xf2, 0x02,
124    ];
125
126    #[rustfmt::skip]
127    const FRAME_WITH_EXTRA_BYTES: [u8; 20] = [
128        // WPA version
129        0x01, 0x00,
130        // Multicast cipher
131        0x00, 0x50, 0xf2, 0x04,
132        // Unicast cipher list
133        0x01, 0x00, 0x00, 0x50, 0xf2, 0x04,
134        // AKM list
135        0x01, 0x00, 0x00, 0x50, 0xf2, 0x02,
136        // Extra bytes
137        0x0c, 0x00,
138    ];
139
140    #[test]
141    fn test_write_into() {
142        let wpa_frame_bytes = WpaIe {
143            multicast_cipher: cipher::Cipher { oui: OUI, suite_type: cipher::TKIP },
144            unicast_cipher_list: vec![cipher::Cipher { oui: OUI, suite_type: cipher::TKIP }],
145            akm_list: vec![akm::Akm { oui: OUI, suite_type: akm::PSK }],
146        }
147        .into_bytes();
148        assert_eq!(&wpa_frame_bytes[..], &DEFAULT_FRAME[..]);
149    }
150
151    #[test]
152    fn test_write_into_roundtrip() {
153        let wpa_frame = from_bytes(&DEFAULT_FRAME[..]);
154        assert!(wpa_frame.is_ok());
155        let wpa_frame = wpa_frame.unwrap().1;
156        let wpa_frame_bytes = wpa_frame.into_bytes();
157        assert_eq!(&wpa_frame_bytes[..], &DEFAULT_FRAME[..]);
158    }
159
160    #[test]
161    fn test_parse_correct() {
162        let wpa_frame = from_bytes(&DEFAULT_FRAME[..]);
163        assert!(wpa_frame.is_ok());
164        let wpa_frame = wpa_frame.unwrap().1;
165        assert_eq!(
166            wpa_frame.multicast_cipher,
167            cipher::Cipher { oui: OUI, suite_type: cipher::TKIP }
168        );
169        assert_eq!(
170            wpa_frame.unicast_cipher_list,
171            vec![cipher::Cipher { oui: OUI, suite_type: cipher::TKIP }]
172        );
173        assert_eq!(wpa_frame.akm_list, vec![akm::Akm { oui: OUI, suite_type: akm::PSK }]);
174    }
175
176    #[test]
177    fn test_parse_bad_frame() {
178        #[rustfmt::skip]
179        let bad_frame: Vec<u8> = vec![
180            // WPA version
181            0x01, 0x00,
182            // Multicast cipher
183            0x00, 0x50, 0xf2, 0x02,
184            // Unicast cipher list (count is incorrect)
185            0x16, 0x00, 0x00, 0x50, 0xf2, 0x02,
186            // AKM list
187            0x01, 0x00, 0x00, 0x50, 0xf2, 0x02,
188        ];
189        let wpa_frame = from_bytes(&bad_frame[..]);
190        assert!(!wpa_frame.is_ok());
191    }
192
193    #[test]
194    fn test_truncated_frame() {
195        #[rustfmt::skip]
196        let bad_frame: Vec<u8> = vec![
197            // WPA version
198            0x01, 0x00,
199            // Multicast ciph... truncated frame.
200            0x00, 0x50
201        ];
202        let wpa_frame = from_bytes(&bad_frame[..]);
203        assert!(!wpa_frame.is_ok());
204    }
205
206    #[test]
207    fn test_parse_with_extra_bytes() {
208        let wpa_frame = from_bytes(&FRAME_WITH_EXTRA_BYTES[..]);
209        assert!(wpa_frame.is_ok());
210        let wpa_frame = wpa_frame.unwrap().1;
211        assert_eq!(
212            wpa_frame.multicast_cipher,
213            cipher::Cipher { oui: OUI, suite_type: cipher::CCMP_128 }
214        );
215        assert_eq!(
216            wpa_frame.unicast_cipher_list,
217            vec![cipher::Cipher { oui: OUI, suite_type: cipher::CCMP_128 }]
218        );
219        assert_eq!(wpa_frame.akm_list, vec![akm::Akm { oui: OUI, suite_type: akm::PSK }]);
220    }
221}