Coverage Report

Created: 2026-11-12 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/crypt/dot11decrypt_util.c
Line
Count
Source
1
/* dot11decrypt_util.c
2
 *
3
 * Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
4
 * Copyright (c) 2006 CACE Technologies, Davis (California)
5
 * All rights reserved.
6
 *
7
 * SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only)
8
 */
9
10
/****************************************************************************/
11
/* File includes                                                            */
12
#include "config.h"
13
14
#include "dot11decrypt_debug.h"
15
#include "dot11decrypt_int.h"
16
#include "dot11decrypt_util.h"
17
18
/****************************************************************************/
19
/*    Internal definitions                                                  */
20
21
0
#define FC0_AAD_MASK 0x8f
22
0
#define FC1_AAD_MASK 0xc7
23
0
#define FC1_AAD_QOS_MASK 0x47
24
25
/****************************************************************************/
26
/* Internal macros                                                          */
27
28
/****************************************************************************/
29
/* Internal function prototypes declarations                                */
30
31
/****************************************************************************/
32
/* Function definitions                                                     */
33
34
/* From IEEE 802.11 2016 Chapter 12.5.3.3.3 and 12.5.5.3.3 Construct AAD */
35
void dot11decrypt_construct_aad(
36
    PDOT11DECRYPT_MAC_FRAME wh,
37
    const uint8_t *A1,
38
    const uint8_t *A2,
39
    const uint8_t *A3,
40
    uint8_t *aad,
41
    size_t *aad_len)
42
0
{
43
0
    uint8_t mgmt = (DOT11DECRYPT_TYPE(wh->fc[0]) == DOT11DECRYPT_TYPE_MANAGEMENT);
44
0
    int alen = 22;
45
46
    /* AAD:
47
    * FC with bits 4..6 and 11..13 masked to zero; 14 is always one; 15 zero when QoS Control field present
48
    * A1 | A2 | A3
49
    * SC with bits 4..15 (seq#) masked to zero
50
    * A4 (if present)
51
    * QC (if present)
52
    */
53
54
    /* NB: aad[1] set below */
55
0
    if (!mgmt) {
56
0
        aad[0] = (uint8_t)(wh->fc[0] & FC0_AAD_MASK);
57
0
    } else {
58
0
        aad[0] = wh->fc[0];
59
0
    }
60
0
    if (DOT11DECRYPT_IS_QOS_DATA(wh)) {
61
0
        aad[1] = (uint8_t)((wh->fc[1] & FC1_AAD_QOS_MASK) | 0x40);
62
0
    } else {
63
0
        aad[1] = (uint8_t)((wh->fc[1] & FC1_AAD_MASK) | 0x40);
64
0
    }
65
0
    memcpy(aad + 2, A1, DOT11DECRYPT_MAC_LEN);
66
0
    memcpy(aad + 8, A2, DOT11DECRYPT_MAC_LEN);
67
0
    memcpy(aad + 14, A3, DOT11DECRYPT_MAC_LEN);
68
0
    aad[20] = (uint8_t)(wh->seq[0] & DOT11DECRYPT_SEQ_FRAG_MASK);
69
0
    aad[21] = 0; /* all bits masked */
70
71
    /*
72
    * Construct variable-length portion of AAD based
73
    * on whether this is a 4-address frame/QOS frame.
74
    */
75
0
    if (DOT11DECRYPT_IS_4ADDRESS(wh)) {
76
0
        alen += 6;
77
0
        DOT11DECRYPT_ADDR_COPY(aad + 22,
78
0
            ((PDOT11DECRYPT_MAC_FRAME_ADDR4)wh)->addr4);
79
0
        if (DOT11DECRYPT_IS_QOS_DATA(wh)) {
80
0
            PDOT11DECRYPT_MAC_FRAME_ADDR4_QOS qwh4 =
81
0
                (PDOT11DECRYPT_MAC_FRAME_ADDR4_QOS) wh;
82
0
            aad[28] = (uint8_t)(qwh4->qos[0] & 0x0f);/* just priority bits */
83
0
            aad[29] = 0;
84
0
            alen += 2;
85
0
        }
86
0
    } else {
87
0
        if (DOT11DECRYPT_IS_QOS_DATA(wh)) {
88
0
            PDOT11DECRYPT_MAC_FRAME_QOS qwh =
89
0
                (PDOT11DECRYPT_MAC_FRAME_QOS) wh;
90
0
            aad[22] = (uint8_t)(qwh->qos[0] & 0x0f); /* just priority bits */
91
0
            aad[23] = 0;
92
0
            alen += 2;
93
0
        }
94
0
    }
95
0
    *aad_len = alen;
96
0
}
97
98
/**
99
 * IEEE 802.11-2016 12.7.1.2 PRF (Pseudo Random Function)
100
 *
101
 * @param key Derivation input key.
102
 * @param key_len Length of the key in bytes.
103
 * @param label Unique label for each different purpose of the PRF (named 'A' in the standard).
104
 * @param context Provides context to identify the derived key (named 'B' in the standard).
105
 * @param context_len Length of context in bytes.
106
 * @param hash_algo Hash algorithm to use for the PRF.
107
 *        See gcrypt available hash algorithms:
108
 *        https://gnupg.org/documentation/manuals/gcrypt/Available-hash-algorithms.html
109
 * @param[out] output Derived key.
110
 * @param output_len Length of derived key in bytes.
111
 * @return false on error
112
 */
113
0
#define MAX_R_LEN 256
114
0
#define MAX_TMP_LEN 1024
115
0
#define MAX_CONTEXT_LEN 256
116
117
bool
118
dot11decrypt_prf(const uint8_t *key, size_t key_len,
119
                 const char *label,
120
                 const uint8_t *context, size_t context_len,
121
                 int hash_algo,
122
                 uint8_t *output, size_t output_len)
123
0
{
124
0
    uint8_t R[MAX_R_LEN]; /* Will hold "label || 0 || context || i" */
125
0
    size_t label_len;
126
0
    uint8_t tmp[MAX_TMP_LEN];
127
0
    uint16_t hash_len = gcry_md_get_algo_dlen(hash_algo);
128
0
    size_t offset = 0;
129
130
0
    if (!key || !label || !context || !output) {
131
0
        return false;
132
0
    }
133
134
0
    label_len = strlen(label);
135
0
    if (label_len + 1 + context_len + 1 > MAX_R_LEN ||
136
0
        output_len > 64) {
137
0
        ws_warning("Invalid input or output sizes");
138
0
        return false;
139
0
    }
140
141
    /* Fill R with "label || 0 || context || i" */
142
0
    memcpy(R + offset, label, label_len);
143
0
    offset += label_len;
144
0
    R[offset++] = 0;
145
0
    memcpy(R + offset, context, context_len);
146
0
    offset += context_len;
147
148
0
    for (size_t i = 0; i <= output_len * 8 / 160; i++)
149
0
    {
150
0
        R[offset] = (uint8_t)i;
151
0
        if (ws_hmac_buffer(hash_algo, tmp + hash_len * i, R, offset + 1, key, key_len)) {
152
0
            return false;
153
0
        }
154
0
    }
155
0
    memcpy(output, tmp, output_len);
156
0
    return true;
157
0
}
158
159
/**
160
 * 12.7.1.7.2 Key derivation function (KDF)
161
 *
162
 * @param key Derivation input key.
163
 * @param key_len Length of the key in bytes.
164
 * @param label A string identifying the purpose of the keys derived using this KDF.
165
 * @param context Provides context to identify the derived key.
166
 * @param context_len Length of context in bytes.
167
 * @param hash_algo Hash algorithm to use for the KDF.
168
 *        See gcrypt available hash algorithms:
169
 *        https://gnupg.org/documentation/manuals/gcrypt/Available-hash-algorithms.html
170
 * @param[out] output Derived key.
171
 * @param output_len Length of derived key in bytes.
172
 * @return false on error
173
 */
174
bool
175
dot11decrypt_kdf(const uint8_t *key, size_t key_len,
176
                 const char *label,
177
                 const uint8_t *context, size_t context_len,
178
                 int hash_algo,
179
                 uint8_t *output, size_t output_len)
180
0
{
181
0
    uint8_t R[MAX_R_LEN]; /* Will hold "i || Label || Context || Length" */
182
0
    uint8_t tmp[MAX_TMP_LEN];
183
0
    size_t label_len;
184
0
    size_t hash_len = gcry_md_get_algo_dlen(hash_algo);
185
0
    size_t iterations = output_len * 8 / hash_len;
186
0
    uint16_t len_le = GUINT16_TO_LE(output_len * 8);
187
0
    size_t offset = 0;
188
0
    size_t i;
189
190
0
    if (!key || !label || !context || !output) {
191
0
        return false;
192
0
    }
193
194
0
    label_len = strlen(label);
195
0
    if (2 + label_len + context_len + 2 > MAX_R_LEN ||
196
0
        iterations * hash_len > MAX_TMP_LEN) {
197
0
        ws_warning("Invalid input sizes");
198
0
        return false;
199
0
    }
200
201
    /* Fill tmp with "i || Label || Context || Length" */
202
0
    offset += 2; /* Skip "i" (will be copied in for loop below) */
203
0
    memcpy(R + offset, label, label_len);
204
0
    offset += label_len;
205
0
    memcpy(R + offset, context, context_len);
206
0
    offset += context_len;
207
0
    memcpy(R + offset, &len_le, 2);
208
0
    offset += 2;
209
210
0
    for (i = 0; i < iterations; i++)
211
0
    {
212
0
        uint16_t count_le = GUINT16_TO_LE(i + 1);
213
0
        memcpy(R, &count_le, 2);
214
215
0
        if (ws_hmac_buffer(hash_algo, tmp + hash_len * i, R, offset, key, key_len)) {
216
0
            return false;
217
0
        }
218
0
    }
219
0
    memcpy(output, tmp, output_len);
220
0
    return true;
221
0
}
222
223
static bool sha256(const uint8_t *data, size_t len, uint8_t output[32])
224
0
{
225
0
    gcry_md_hd_t ctx;
226
0
    gcry_error_t result = gcry_md_open(&ctx, GCRY_MD_SHA256, 0);
227
0
    uint8_t *digest;
228
229
0
    if (result) {
230
0
        return false;
231
0
    }
232
0
    gcry_md_write(ctx, data, len);
233
0
    digest = gcry_md_read(ctx, GCRY_MD_SHA256);
234
0
    if (!digest) {
235
0
        return false;
236
0
    }
237
0
    memcpy(output, digest, 32);
238
0
    gcry_md_close(ctx);
239
0
    return true;
240
0
}
241
242
/**
243
 * Derive PMK-R0 and PMKR0Name. See IEEE 802.11-2016 12.7.1.7.3 PMK-R0
244
 *
245
 * @param xxkey PSK / MPMK or certain part of MSK.
246
 * @param xxkey_len Length of xxkey in bytes.
247
 * @param ssid SSID
248
 * @param ssid_len Length of SSID in bytes.
249
 * @param mdid MDID (Mobility Domain Identifier).
250
 * @param r0kh_id PMK-R0 key holder identifier in the Authenticator.
251
 * @param r0kh_id_len Length of r0kh_id in bytes.
252
 * @param s0kh_id PMK-R0 key holder in the Supplicant (STA mac address)
253
 * @param hash_algo Hash algorithm to use for the KDF.
254
 *        See gcrypt available hash algorithms:
255
 *        https://gnupg.org/documentation/manuals/gcrypt/Available-hash-algorithms.html
256
 * @param[out] pmk_r0 Pairwise master key, first level
257
 * @param pmk_r0_len Length of pmk_r0 in bytes.
258
 * @param[out] pmk_r0_name Pairwise master key (PMK) R0 name.
259
 */
260
bool
261
dot11decrypt_derive_pmk_r0(const uint8_t *xxkey, size_t xxkey_len,
262
                           const uint8_t *ssid, size_t ssid_len,
263
                           const uint8_t mdid[2],
264
                           const uint8_t *r0kh_id, size_t r0kh_id_len,
265
                           const uint8_t s0kh_id[DOT11DECRYPT_MAC_LEN],
266
                           int hash_algo,
267
                           uint8_t *pmk_r0,
268
                           size_t *pmk_r0_len,
269
                           uint8_t pmk_r0_name[16])
270
0
{
271
0
    const char *ft_r0n = "FT-R0N";
272
0
    const size_t ft_r0n_len = strlen(ft_r0n);
273
0
    uint8_t context[MAX_CONTEXT_LEN];
274
0
    uint8_t r0_key_data[DOT11DECRYPT_WPA_PMK_MAX_LEN + 16];
275
0
    uint8_t sha256_res[32];
276
0
    size_t offset = 0;
277
0
    unsigned q = gcry_md_get_algo_dlen(hash_algo);
278
279
0
    if (!xxkey || !ssid || !mdid || !r0kh_id || !s0kh_id ||
280
0
        !pmk_r0 || !pmk_r0_len || !pmk_r0_name)
281
0
    {
282
0
        return false;
283
0
    }
284
0
    if (1 + ssid_len + 2 + 1 + r0kh_id_len + DOT11DECRYPT_MAC_LEN > MAX_CONTEXT_LEN)
285
0
    {
286
0
        ws_warning("Invalid input sizes");
287
0
        return false;
288
0
    }
289
290
    // R0-Key-Data =
291
    //   KDF-Hash-Length(XXKey, "FT-R0",
292
    //           SSIDlength || SSID || MDID || R0KHlength || R0KH-ID || S0KH-ID)
293
    // PMK-R0 = L(R0-Key-Data, 0, Q) * PMK-R0Name-Salt = L(R0-Key-Data, Q, 128)
294
0
    context[offset++] = (uint8_t)ssid_len;
295
0
    memcpy(context + offset, ssid, ssid_len);
296
0
    offset += ssid_len;
297
0
    memcpy(context + offset, mdid, 2);
298
0
    offset += 2;
299
0
    context[offset++] = (uint8_t)r0kh_id_len;
300
0
    memcpy(context + offset, r0kh_id, r0kh_id_len);
301
0
    offset += r0kh_id_len;
302
0
    memcpy(context + offset, s0kh_id, DOT11DECRYPT_MAC_LEN);
303
0
    offset += DOT11DECRYPT_MAC_LEN;
304
0
    dot11decrypt_kdf(xxkey, xxkey_len, "FT-R0", context, offset, hash_algo,
305
0
                     r0_key_data, q + 16);
306
0
    memcpy(pmk_r0, r0_key_data, q);
307
0
    *pmk_r0_len = q;
308
309
    // PMK-R0Name-Salt = L(R0-Key-Data, Q, 128)
310
    // PMKR0Name = Truncate-128(SHA-256("FT-R0N" || PMK-R0Name-Salt))
311
0
    offset = 0;
312
0
    memcpy(context + offset, ft_r0n, ft_r0n_len);
313
0
    offset += ft_r0n_len;
314
0
    memcpy(context + offset, r0_key_data + q, 16);
315
0
    offset += 16;
316
0
    if(!sha256(context, offset, sha256_res))
317
0
        return false;
318
0
    memcpy(pmk_r0_name, sha256_res, 16);
319
0
    return true;
320
0
}
321
322
/**
323
 * Derive PMK-R1 and PMKR1Name. See IEEE 802.11-2016 12.7.1.7.4 PMK-R1
324
 *
325
 */
326
bool
327
dot11decrypt_derive_pmk_r1(const uint8_t *pmk_r0, size_t pmk_r0_len,
328
                           const uint8_t *pmk_r0_name,
329
                           const uint8_t *r1kh_id, const uint8_t *s1kh_id,
330
                           int hash_algo,
331
                           uint8_t *pmk_r1, size_t *pmk_r1_len,
332
                           uint8_t *pmk_r1_name)
333
0
{
334
0
    const char *ft_r1n = "FT-R1N";
335
0
    const size_t ft_r1n_len = strlen(ft_r1n);
336
    // context len = MAX(R1KH-ID || S1KH-ID, "FT-R1N" || PMKR0Name || R1KH-ID || S1KH-ID)
337
0
    uint8_t context[6 + 16 + 6 + 6];
338
0
    uint8_t sha256_res[32];
339
0
    size_t offset = 0;
340
341
0
    if (!pmk_r0 || !pmk_r0_name || !r1kh_id || !s1kh_id ||
342
0
        !pmk_r1 || !pmk_r1_len || !pmk_r1_name)
343
0
    {
344
0
        return false;
345
0
    }
346
0
    *pmk_r1_len = gcry_md_get_algo_dlen(hash_algo);
347
348
    // PMK-R1 = KDF-Hash-Length(PMK-R0, "FT-R1", R1KH-ID || S1KH-ID)
349
0
    memcpy(context + offset, r1kh_id, DOT11DECRYPT_MAC_LEN);
350
0
    offset += DOT11DECRYPT_MAC_LEN;
351
0
    memcpy(context + offset, s1kh_id, DOT11DECRYPT_MAC_LEN);
352
0
    offset += DOT11DECRYPT_MAC_LEN;
353
0
    dot11decrypt_kdf(pmk_r0, pmk_r0_len, "FT-R1", context, offset, hash_algo,
354
0
                     pmk_r1, *pmk_r1_len);
355
356
    // PMKR1Name = Truncate-128(SHA-256("FT-R1N" || PMKR0Name || R1KH-ID || S1KH-ID))
357
0
    offset = 0;
358
0
    memcpy(context + offset, ft_r1n, ft_r1n_len);
359
0
    offset += ft_r1n_len;
360
0
    memcpy(context + offset, pmk_r0_name, 16);
361
0
    offset += 16;
362
0
    memcpy(context + offset, r1kh_id, DOT11DECRYPT_MAC_LEN);
363
0
    offset += DOT11DECRYPT_MAC_LEN;
364
0
    memcpy(context + offset, s1kh_id, DOT11DECRYPT_MAC_LEN);
365
0
    offset += DOT11DECRYPT_MAC_LEN;
366
0
    if(!sha256(context, offset, sha256_res))
367
0
        return false;
368
0
    memcpy(pmk_r1_name, sha256_res, 16);
369
0
    return true;
370
0
}
371
372
/**
373
 * Derive PTK for FT AKMS. See IEE 802.11-2016 12.7.1.7.5 PTK
374
 *
375
 * PTK = KDF-Hash-Length(PMK-R1, "FT-PTK", SNonce || ANonce || BSSID || STA-ADDR)
376
 * PTKName = Truncate-128(
377
 *         SHA-256(PMKR1Name || "FT-PTKN" || SNonce || ANonce || BSSID || STA-ADDR))
378
 */
379
bool
380
dot11decrypt_derive_ft_ptk(const uint8_t *pmk_r1, size_t pmk_r1_len,
381
                           const uint8_t *pmk_r1_name _U_,
382
                           const uint8_t *snonce, const uint8_t *anonce,
383
                           const uint8_t *bssid, const uint8_t *sta_addr,
384
                           int hash_algo,
385
                           uint8_t *ptk, const size_t ptk_len, uint8_t *ptk_name _U_)
386
0
{
387
0
    uint8_t context[32 + 32 + 6 + 6];
388
0
    unsigned offset = 0;
389
390
    // PTK = KDF-Hash-Length(PMK-R1, "FT-PTK", SNonce || ANonce || BSSID || STA-ADDR)
391
0
    memcpy(context + offset, snonce, 32);
392
0
    offset += 32;
393
0
    memcpy(context + offset, anonce, 32);
394
0
    offset += 32;
395
0
    memcpy(context + offset, bssid, DOT11DECRYPT_MAC_LEN);
396
0
    offset += DOT11DECRYPT_MAC_LEN;
397
0
    memcpy(context + offset, sta_addr, DOT11DECRYPT_MAC_LEN);
398
0
    offset += DOT11DECRYPT_MAC_LEN;
399
0
    dot11decrypt_kdf(pmk_r1, pmk_r1_len, "FT-PTK", context, offset, hash_algo,
400
0
                     ptk, ptk_len);
401
402
    // TODO derive PTKName
403
    return true;
404
0
}
405
406
/*
407
 * Editor modelines
408
 *
409
 * Local Variables:
410
 * c-basic-offset: 4
411
 * tab-width: 8
412
 * indent-tabs-mode: nil
413
 * End:
414
 *
415
 * ex: set shiftwidth=4 tabstop=8 expandtab:
416
 * :indentSize=4:tabSize=8:noTabs=true:
417
 */