ieee80211 0.5.9

A parser for IEEE 802.11 frames.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
use core::cmp::Ordering;

use aes_kw::KekAes128;
use hmac::{
    Hmac, Mac,
    digest::{FixedOutput, KeyInit},
};
use llc_rs::EtherType;
use pbkdf2::pbkdf2_hmac;
use scroll::{Endian, Pread, Pwrite, ctx::TryIntoCtx};
use sha1::Sha1;
use sha2::{Sha256, Sha384};

use crate::{
    crypto::eapol::KeyInformation,
    data_frame::header::DataFrameHeader,
    elements::rsn::{IEEE80211AkmType, IEEE80211CipherSuiteSelector},
};

use super::eapol::{EapolDataFrame, EapolKeyFrame};

/// HMAC-SHA-1 function
pub type HSha1 = Hmac<Sha1>;
/// HMAC-SHA-256 function
pub type HSha256 = Hmac<Sha256>;
/// HMAC-SHA-384 function
pub type HSha384 = Hmac<Sha384>;

/// Generate the Pairwise Master Key Identifier (PMKID)
///
/// This is generic over the used HMAC function. The default is [HSha1].
pub fn generate_pmkid<H: Mac + KeyInit>(
    key: &[u8],
    authenticator_address: &[u8; 6],
    supplicant_address: &[u8; 6],
    output: &mut [u8; 16],
) {
    let mut hmac = <H as Mac>::new_from_slice(key).unwrap();
    [
        "PMK Name".as_bytes(),
        authenticator_address,
        supplicant_address,
    ]
    .iter()
    .for_each(|chunk| hmac.update(chunk));
    output.copy_from_slice(&hmac.finalize().into_bytes()[..16]);
}

/// Maps a passphrase to a PSK, as specified in Annex J of IEEE 802.11-2020.
///
/// The length of `output` is the length of the PSK.
/// This is used by WPA2.
pub fn map_passphrase_to_psk(passphrase: &str, ssid: &str, output: &mut [u8]) {
    pbkdf2_hmac::<Sha1>(passphrase.as_bytes(), ssid.as_bytes(), 4096, output);
}
/// Pseudo Random Function (PRF) with data iterator
///
/// This is exactly the same as [prf], but instead of taking in a single data slice, it takes in a
/// reference to some kind of collection of data slices. This is useful, because in IEEE 802.11 the
/// PRF is almost always used with multiple chunks of data concatenated together, which would be
/// hard to do with [prf].
pub fn prf_iter<'a, D>(key: &[u8], label: &str, data: &'a D, output: &mut [u8])
where
    &'a D: IntoIterator<Item = &'a &'a [u8]>,
    <&'a D as IntoIterator>::IntoIter: Clone,
{
    // The output size of SHA1.
    const SHA1_OUTPUT_SIZE: usize = 160 / 8;

    let data_iter = data.into_iter();
    // chunks_mut handles the clamping for us
    for (i, output_chunk) in output.chunks_mut(SHA1_OUTPUT_SIZE).enumerate() {
        // We initialize HSHA1 with the key and update it with every piece of data.
        let mut h_sha_1 = <HSha1 as Mac>::new_from_slice(key).unwrap();
        h_sha_1.update(label.as_bytes());
        h_sha_1.update(&[0x00u8]);

        // Here we update it with the data chunks
        data_iter
            .clone()
            .for_each(|data_chunk| h_sha_1.update(data_chunk));

        h_sha_1.update(&[i as u8]);
        // If the chunk is as big as SHA1's output, we can output the data directly into the output
        // buffer. Otherwise we have to put it in a variable first.
        if output_chunk.len() == SHA1_OUTPUT_SIZE {
            h_sha_1.finalize_into(output_chunk.into());
        } else {
            let output = h_sha_1.finalize().into_bytes();
            output_chunk.copy_from_slice(&output[..output_chunk.len()]);
        }
    }
}
/// Pseudo Random Function (PRF)
///
/// Implemented according to 12.7.1.2 IEEE 802.11-2020. This is the default PRF, with HMAC-SHA-1.
/// For some AKM suites, different PRF's are used.
pub fn prf(key: &[u8], label: &str, data: &[u8], output: &mut [u8]) {
    prf_iter(key, label, &[data], output)
}

/// Sort two byte slices lexicographically.
///
/// The first slice in the returned tuple is lexicographically smaller than the second one, unless
/// both are equal, in which case it's `b`.
fn sort_lexicographically<'a>(a: &'a [u8], b: &'a [u8]) -> (&'a [u8], &'a [u8]) {
    if a.iter().partial_cmp(b.iter()) == Some(Ordering::Less) {
        (a, b)
    } else {
        (b, a)
    }
}
/// Derive a Pairwise Transient Key (PTK)
///
/// This derives the PTK from a PMK and the authenticator and supplicant address and nonce.
pub fn derive_ptk(
    pmk: &[u8],
    authenticator_address: &[u8; 6],
    supplicant_address: &[u8; 6],
    authenticator_nonce: &[u8; 32],
    supplicant_nonce: &[u8; 32],
    ptk: &mut [u8],
) {
    // This combines the min max stuff together.
    // NOTE: Who the hell came up with this?
    let (min_address, max_address) =
        sort_lexicographically(authenticator_address, supplicant_address);
    let (min_nonce, max_nonce) = sort_lexicographically(authenticator_nonce, supplicant_nonce);
    prf_iter(
        pmk,
        "Pairwise key expansion",
        &[min_address, max_address, min_nonce, max_nonce],
        ptk,
    );
}
/// Partition a PTK into KCK, KEK and TK
///
/// This will return [None], if either the AKM or Cipher suite are unknown, or the provided PTK is
/// too short. If the PTK is longer than the KCK, KEK and TK together, the excess data will just be
/// truncated.
pub fn partition_ptk(
    ptk: &[u8],
    akm_suite: IEEE80211AkmType,
    cipher_suite: IEEE80211CipherSuiteSelector,
) -> Option<(&[u8], &[u8], &[u8])> {
    let kck_len = akm_suite.kck_len()?;
    let kek_len = akm_suite.kek_len()?;
    let tk_len = cipher_suite.tk_len()?;
    let (kck, kek_and_tk) = ptk.split_at_checked(kck_len)?;
    let (kek, tk) = kek_and_tk.split_at_checked(kek_len)?;
    let tk = tk.get(..tk_len)?;
    Some((kck, kek, tk))
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
/// An error related to cryptographic key management operation.
pub enum KeyManagementError {
    /// The key was too short.
    InvalidKeyLength,
    /// The output slice length didn't match the output size of the algorithm.
    InvalidOutputLength,
    /// The provided scratch buffer was too short.
    ScratchBufferTooShort,
}
/// Wrap the EAPOL key data using the NIST AES Key-Wrap algorithm.
///
/// The `key_data` slice should contain the entire Key Data segment of the EAPOL Key frame.
/// Currently this assumes a 128 bit KEK is in use, which is true for most AKM suites.
pub fn wrap_eapol_key_data(
    kek: &[u8; 16],
    key_data: &[u8],
    output: &mut [u8],
) -> Result<(), KeyManagementError> {
    let kw = KekAes128::new(kek.into());
    kw.wrap_with_padding(key_data, output)
        .map_err(|_| KeyManagementError::InvalidOutputLength)?;
    Ok(())
}

#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
/// An error related to EAPOL frame serialization.
pub enum EapolSerdeError {
    /// The provided output buffer was too short.
    BufferTooShort,
    /// The provided temporary buffer was too short.
    TemporaryBufferToShort,
    /// The data frame didn't contain a payload.
    NoPayload,
    /// A needed key wasn't provided.
    MissingKey,
    /// Deserializing the key frame failed.
    KeyFrameDeserializationFailure,
    /// The used AKM suite is unknown.
    UnknownAkmSuite,
    /// The MIC in the frame didn't match the calculated MIC.
    InvalidMic,
    /// The ether type wasn't EAPOL.
    EtherTypeNotEapol,
}
/// Serialize the provided EAPOL Key frame.
///
/// The temp_buffer needs to be at least as long as the key data field aligned to eight bytes, plus
/// eight bytes.
pub fn serialize_eapol_data_frame<
    KeyMic: AsRef<[u8]>,
    ElementContainer: TryIntoCtx<(), Error = scroll::Error>,
>(
    kck: Option<&[u8; 16]>,
    kek: Option<&[u8; 16]>,
    eapol_data_frame: EapolDataFrame<'_, KeyMic, ElementContainer>,
    buffer: &mut [u8],
    temp_buffer: &mut [u8],
) -> Result<usize, EapolSerdeError> {
    if temp_buffer.len() < 24 {
        return Err(EapolSerdeError::TemporaryBufferToShort);
    }
    let mic_range = eapol_data_frame
        .eapol_mic_range()
        .ok_or(EapolSerdeError::NoPayload)?;
    let mic_len = eapol_data_frame
        .payload
        .as_ref()
        .ok_or(EapolSerdeError::NoPayload)?
        .payload
        .key_mic
        .as_ref()
        .len();
    let key_data_length_range = eapol_data_frame
        .eapol_key_data_length_range()
        .ok_or(EapolSerdeError::NoPayload)?;
    let key_data_range = eapol_data_frame
        .eapol_key_data_range()
        .ok_or(EapolSerdeError::NoPayload)?;
    let key_information = eapol_data_frame
        .payload
        .as_ref()
        .ok_or(EapolSerdeError::NoPayload)?
        .payload
        .key_information;
    let eapol_frame_start = eapol_data_frame.header.length_in_bytes() + 8;

    let mut written = buffer
        .pwrite(eapol_data_frame, 0)
        .map_err(|_| EapolSerdeError::BufferTooShort)?;

    let key_data_length = buffer[key_data_length_range.clone()]
        .pread_with::<u16>(0, Endian::Big)
        .expect("If the TryIntoCtx impl for EapolKeyFrame is correct, this can't fail.")
        as usize;

    if key_data_length != 0 && key_information.encrypted_key_data() {
        let padded_key_data_length = if key_data_length < 16 {
            16
        } else if key_data_length % 8 != 0 {
            (key_data_length & !(0b111)) + 8
        } else {
            key_data_length
        };

        let padded_key_data = buffer[key_data_range.clone()]
            .get_mut(..padded_key_data_length)
            .ok_or(EapolSerdeError::BufferTooShort)?;
        if padded_key_data_length != key_data_length {
            padded_key_data[key_data_length] = 0xdd;
            padded_key_data[key_data_length + 1..].fill(0x00);
        }
        let padded_and_wrapped_key_data_length = padded_key_data_length + 8;

        let kw = KekAes128::new(kek.ok_or(EapolSerdeError::MissingKey)?.into());
        kw.wrap(
            padded_key_data,
            &mut temp_buffer[..padded_and_wrapped_key_data_length],
        )
        .map_err(|_| EapolSerdeError::TemporaryBufferToShort)?;

        let wrapped_key_data = buffer[key_data_range]
            .get_mut(..padded_and_wrapped_key_data_length)
            .ok_or(EapolSerdeError::BufferTooShort)?;
        wrapped_key_data.copy_from_slice(&temp_buffer[..padded_and_wrapped_key_data_length]);
        written += padded_and_wrapped_key_data_length - key_data_length;

        let _ = buffer.pwrite_with(
            padded_and_wrapped_key_data_length as u16,
            key_data_length_range.start,
            Endian::Big,
        );
        let _ = buffer.pwrite_with(
            (77 + mic_len + 2 + padded_and_wrapped_key_data_length) as u16,
            eapol_frame_start + 2,
            Endian::Big,
        );
    }
    if key_information.key_mic() {
        let mut h_sha_1 =
            <HSha1 as Mac>::new_from_slice(kck.ok_or(EapolSerdeError::MissingKey)?).unwrap();
        h_sha_1.update(&buffer[eapol_frame_start..written]);
        h_sha_1.finalize_into((&mut temp_buffer[..20]).into());
        buffer[mic_range].copy_from_slice(&temp_buffer[..16]);
    }
    Ok(written)
}
/// Decrypt, verify and deserialize an EAPOL data frame.
///
/// The temp buffer needs to be as long as the key data minus eight.
pub fn deserialize_eapol_data_frame<'a>(
    kck: Option<&[u8; 16]>,
    kek: Option<&[u8; 16]>,
    mut buffer: &'a mut [u8],
    temp_buffer: &mut [u8],
    akm_suite: IEEE80211AkmType,
    with_fcs: bool,
) -> Result<EapolKeyFrame<'a>, EapolSerdeError> {
    if with_fcs {
        buffer = buffer
            .get_mut(..buffer.len() - 4)
            .ok_or(EapolSerdeError::BufferTooShort)?;
    }
    let data_frame_header = buffer
        .pread::<DataFrameHeader>(0)
        .map_err(|_| EapolSerdeError::BufferTooShort)?;

    let payload_offset = data_frame_header.length_in_bytes();

    let llc_ether_type_offset = payload_offset + 6;
    let ether_type = buffer
        .get(llc_ether_type_offset..)
        .and_then(|bytes| bytes.pread_with::<u16>(0, Endian::Big).ok())
        .ok_or(EapolSerdeError::BufferTooShort)?;
    if ether_type != EtherType::Eapol.into() {
        return Err(EapolSerdeError::EtherTypeNotEapol);
    }

    let eapol_key_frame_offset = payload_offset + 8;
    let eapol_key_information_offset = eapol_key_frame_offset + 5;
    let eapol_key_information = KeyInformation::from_bits(
        buffer[eapol_key_information_offset..]
            .pread_with(0, Endian::Big)
            .map_err(|_| EapolSerdeError::BufferTooShort)?,
    );
    let mic_len = akm_suite
        .key_mic_len()
        .ok_or(EapolSerdeError::UnknownAkmSuite)?;
    if eapol_key_information.key_mic() {
        let mut h_sha_1 =
            <HSha1 as Mac>::new_from_slice(kck.ok_or(EapolSerdeError::MissingKey)?).unwrap();
        h_sha_1.update(
            buffer
                .get(eapol_key_frame_offset..eapol_key_frame_offset + 81)
                .ok_or(EapolSerdeError::BufferTooShort)?,
        );
        for _ in 0..mic_len / 8 {
            h_sha_1.update(&[0x00u8; 8]);
        }
        h_sha_1.update(
            buffer
                .get(eapol_key_frame_offset + 81 + mic_len..)
                .ok_or(EapolSerdeError::BufferTooShort)?,
        );
        let provided_mic = &buffer[eapol_key_frame_offset + 81..][..mic_len];

        let calculated_mic = h_sha_1.finalize().into_bytes();
        let calculated_mic = &calculated_mic.as_slice()[..mic_len];
        if calculated_mic != provided_mic {
            return Err(EapolSerdeError::InvalidMic);
        }
    }
    if eapol_key_information.encrypted_key_data() {
        let key_data_length_offset = eapol_key_frame_offset + 81 + mic_len;
        let key_data_length: u16 = buffer
            .pread_with(key_data_length_offset, Endian::Big)
            .map_err(|_| EapolSerdeError::BufferTooShort)?;

        let key_data = buffer[key_data_length_offset + 2..]
            .get_mut(..key_data_length as usize)
            .ok_or(EapolSerdeError::BufferTooShort)
            .unwrap();
        let kw = KekAes128::new(kek.ok_or(EapolSerdeError::MissingKey)?.into());
        kw.unwrap(key_data, &mut temp_buffer[..key_data_length as usize - 8])
            .map_err(|_| EapolSerdeError::TemporaryBufferToShort)?;

        buffer
            .pwrite_with(key_data_length - 8, key_data_length_offset, Endian::Big)
            .unwrap();
        buffer
            .pwrite(
                &temp_buffer[..key_data_length as usize - 8],
                key_data_length_offset + 2,
            )
            .unwrap();

        let new_buffer_len = buffer.len() - 8;
        buffer = &mut buffer[..new_buffer_len];
        buffer
            .pwrite_with(
                (new_buffer_len - eapol_key_frame_offset - 4) as u16,
                eapol_key_frame_offset + 2,
                Endian::Big,
            )
            .unwrap();
    }
    buffer
        .pread_with::<EapolKeyFrame>(eapol_key_frame_offset, akm_suite)
        .map_err(|_| EapolSerdeError::KeyFrameDeserializationFailure)
}