git: 6d21920e6d2e - main - net80211: refactor out the AAD init code shared between GCMP and CCMP
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Tue, 08 Apr 2025 01:35:28 UTC
The branch main has been updated by adrian: URL: https://cgit.FreeBSD.org/src/commit/?id=6d21920e6d2e03c6ed7360c432b855ca189db305 commit 6d21920e6d2e03c6ed7360c432b855ca189db305 Author: Adrian Chadd <adrian@FreeBSD.org> AuthorDate: 2025-03-14 23:30:33 +0000 Commit: Adrian Chadd <adrian@FreeBSD.org> CommitDate: 2025-04-08 01:35:21 +0000 net80211: refactor out the AAD init code shared between GCMP and CCMP The 802.11 specification specifically calls out that the GCMP AES uses the CCMP AES specification, so we can re-use this here. Changes during refactoring: * the first block (b0) needs the priority field populated, which was being done inline with the CCMP AAD assembly. So write a new function that implements just that. * Use IEEE80211_IS_QOS_ANY() in the AAD assembly; 802.11-2020 12.5.3.3.3 (Construct AAD) talks about frames with QoS control field, not specifically QoS data frames (ie, not avoiding PS-POLL.) * Use IEEE80211_IS_QOS_ANY() in the CCM nonce assembly. Leave some verbose comments about the definition of "MPDU priority". * mask the HTC/ORDER bit in the FC if the data frame contains a QoS control field. Again, see 802.11-2020 12.5.3.3.3. * Add extra comments about missing items and functionality for later implementation. Differential Revision: https://reviews.freebsd.org/D49367 Reviewed by: bz --- sys/net80211/ieee80211_crypto.c | 97 ++++++++++++++++++++++++ sys/net80211/ieee80211_crypto.h | 5 ++ sys/net80211/ieee80211_crypto_ccmp.c | 138 +++++++++++++++++++---------------- sys/net80211/ieee80211_crypto_gcmp.c | 88 +--------------------- 4 files changed, 181 insertions(+), 147 deletions(-) diff --git a/sys/net80211/ieee80211_crypto.c b/sys/net80211/ieee80211_crypto.c index 744d69ce3d1d..84cf1d02e408 100644 --- a/sys/net80211/ieee80211_crypto.c +++ b/sys/net80211/ieee80211_crypto.c @@ -900,3 +900,100 @@ ieee80211_crypto_set_deftxkey(struct ieee80211vap *vap, ieee80211_keyix kid) vap->iv_update_deftxkey(vap, kid); } + +/** + * @brief Calculate the AAD required for this frame for AES-GCM/AES-CCM. + * + * The contents are described in 802.11-2020 12.5.3.3.3 (Construct AAD) + * under AES-CCM and are shared with AES-GCM as covered in 12.5.5.3.3 + * (Construct AAD) (AES-GCM). + * + * NOTE: the first two bytes are a 16 bit big-endian length, which are used + * by AES-CCM as part of the Adata field (RFC 3610, section 2.2 + * (Authentication)) to indicate the length of the Adata field itself. + * Since this is small and fits in 0xfeff bytes, the length field + * uses the two byte big endian option. + * + * AES-GCM doesn't require the length at the beginning and will need to + * skip it. + * + * TODO: net80211 currently doesn't support negotiating SPP (Signaling + * and Payload Protected A-MSDUs) and thus bit 7 of the QoS control field + * is always masked. + * + * TODO: net80211 currently doesn't support DMG (802.11ad) so bit 7 + * (A-MSDU present) and bit 8 (A-MSDU type) are always masked. + * + * @param wh 802.11 frame to calculate the AAD over + * @param aad AAD (additional authentication data) buffer + * @param len The AAD buffer length in bytes. + * @returns The number of AAD payload bytes (ignoring the first two + * bytes, which are the AAD payload length in big-endian). + */ +uint16_t +ieee80211_crypto_init_aad(const struct ieee80211_frame *wh, uint8_t *aad, + int len) +{ + uint16_t aad_len; + + memset(aad, 0, len); + + /* + * AAD for PV0 MPDUs: + * + * FC with bits 4..6 and 11..13 masked to zero; 14 is always one + * A1 | A2 | A3 + * SC with bits 4..15 (seq#) masked to zero + * A4 (if present) + * QC (if present) + */ + aad[0] = 0; /* AAD length >> 8 */ + /* NB: aad[1] set below */ + aad[2] = wh->i_fc[0] & 0x8f; /* see above for bitfields */ + aad[3] = wh->i_fc[1] & 0xc7; /* see above for bitfields */ + /* mask aad[3] b7 if frame is data frame w/ QoS control field */ + if (IEEE80211_IS_QOS_ANY(wh)) + aad[3] &= 0x7f; + + /* NB: we know 3 addresses are contiguous */ + memcpy(aad + 4, wh->i_addr1, 3 * IEEE80211_ADDR_LEN); + aad[22] = wh->i_seq[0] & IEEE80211_SEQ_FRAG_MASK; + aad[23] = 0; /* all bits masked */ + /* + * Construct variable-length portion of AAD based + * on whether this is a 4-address frame/QOS frame. + * We always zero-pad to 32 bytes before running it + * through the cipher. + */ + if (IEEE80211_IS_DSTODS(wh)) { + IEEE80211_ADDR_COPY(aad + 24, + ((const struct ieee80211_frame_addr4 *)wh)->i_addr4); + if (IEEE80211_IS_QOS_ANY(wh)) { + const struct ieee80211_qosframe_addr4 *qwh4 = + (const struct ieee80211_qosframe_addr4 *) wh; + /* TODO: SPP A-MSDU / A-MSDU present bit */ + aad[30] = qwh4->i_qos[0] & 0x0f;/* just priority bits */ + aad[31] = 0; + aad_len = aad[1] = 22 + IEEE80211_ADDR_LEN + 2; + } else { + *(uint16_t *)&aad[30] = 0; + aad_len = aad[1] = 22 + IEEE80211_ADDR_LEN; + } + } else { + if (IEEE80211_IS_QOS_ANY(wh)) { + const struct ieee80211_qosframe *qwh = + (const struct ieee80211_qosframe*) wh; + /* TODO: SPP A-MSDU / A-MSDU present bit */ + aad[24] = qwh->i_qos[0] & 0x0f; /* just priority bits */ + aad[25] = 0; + aad_len = aad[1] = 22 + 2; + } else { + *(uint16_t *)&aad[24] = 0; + aad_len = aad[1] = 22; + } + *(uint16_t *)&aad[26] = 0; + *(uint32_t *)&aad[28] = 0; + } + + return (aad_len); +} diff --git a/sys/net80211/ieee80211_crypto.h b/sys/net80211/ieee80211_crypto.h index fa0d3fc3272a..89b8b4f9daa4 100644 --- a/sys/net80211/ieee80211_crypto.h +++ b/sys/net80211/ieee80211_crypto.h @@ -295,5 +295,10 @@ void ieee80211_notify_replay_failure(struct ieee80211vap *, uint64_t rsc, int tid); void ieee80211_notify_michael_failure(struct ieee80211vap *, const struct ieee80211_frame *, ieee80211_keyix keyix); + +/* AAD assembly for CCMP/GCMP. */ +uint16_t ieee80211_crypto_init_aad(const struct ieee80211_frame *, + uint8_t *, int); + #endif /* defined(__KERNEL__) || defined(_KERNEL) */ #endif /* _NET80211_IEEE80211_CRYPTO_H_ */ diff --git a/sys/net80211/ieee80211_crypto_ccmp.c b/sys/net80211/ieee80211_crypto_ccmp.c index 70265335fe70..01bc50f885f2 100644 --- a/sys/net80211/ieee80211_crypto_ccmp.c +++ b/sys/net80211/ieee80211_crypto_ccmp.c @@ -408,6 +408,79 @@ xor_block(uint8_t *b, const uint8_t *a, size_t len) b[i] ^= a[i]; } +/** + * @brief Initialise the AES-CCM nonce flag field in the b0 CCMP block. + * + * The B_0 block is defined in RFC 3610 section 2.2 (Authentication). + * b0[0] is the CCM flags field, so the nonce used for B_0 starts at + * b0[1]. Amusingly, b0[1] is also flags, but it's the 802.11 AES-CCM + * nonce flags field, NOT the CCM flags field. + * + * The AES-CCM nonce flags field is defined in 802.11-2020 12.5.3.3.4 + * (Construct CCM nonce). + * + * TODO: net80211 currently doesn't support MFP (management frame protection) + * and so bit 4 is never set. This routine and ccmp_init_blocks() will + * need a pointer to the ieee80211_node or a flag that explicitly states + * the frame will be sent w/ MFP encryption / received w/ MFP decryption. + * + * @param wh the 802.11 header to populate + * @param b0 the CCM nonce to update (remembering b0[0] is the CCM + * nonce flags, and b0[1] is the AES-CCM nonce flags). + */ +static void +ieee80211_crypto_ccmp_init_nonce_flags(const struct ieee80211_frame *wh, + char *b0) +{ + if (IEEE80211_IS_DSTODS(wh)) { + /* + * 802.11-2020 12.5.33.3.4 (Construct CCM nonce) mentions + * that the low four bits of this byte are the "MPDU priority." + * This is defined in 5.1.1.2 (Determination of UP) and + * 5.1.1.3 (Interpretation of Priority Parameter in MAC + * service primitives). + * + * The former says "The QoS facility supports eight priority + * values, referred to as UPs. The values a UP may take are + * the integer values from 0 to 7 and are identical to the + * 802.11D priority tags." + * + * The latter specifically calls out that "Priority parameter + * and TID subfield values 0 to 7 are interpreted aas UPs for + * the MSDUs" .. and " .. TID subfield values 8 to 15 specify + * TIDs that are TS identifiers (TSIDs)" which are used for + * TSPEC. There's a bunch of extra work to be done with frames + * received in TIDs 8..15 with no TSPEC, "then the MSDU shall + * be sent with priority parameter set to 0." + * + * All QoS frames (not just QoS data) have TID fields and + * thus priorities. However, the code straight up + * copies the 4 bit TID field, rather than a 3 bit MPDU + * priority value. For now, as net80211 doesn't specifically + * support TSPEC negotiation, this likely never gets checked. + * However as part of any future TSPEC work, this will likely + * need to be looked at and checked with interoperability + * with other stacks. + */ + if (IEEE80211_IS_QOS_ANY(wh)) { + const struct ieee80211_qosframe_addr4 *qwh4 = + (const struct ieee80211_qosframe_addr4 *) wh; + b0[1] = qwh4->i_qos[0] & 0x0f; /* prio bits */ + } else { + b0[1] = 0; + } + } else { + if (IEEE80211_IS_QOS_ANY(wh)) { + const struct ieee80211_qosframe *qwh = + (const struct ieee80211_qosframe *) wh; + b0[1] = qwh->i_qos[0] & 0x0f; /* prio bits */ + } else { + b0[1] = 0; + } + } + /* TODO: populate MFP flag */ +} + /* * Host AP crypt: host-based CCMP encryption implementation for Host AP driver * @@ -428,8 +501,6 @@ ccmp_init_blocks(rijndael_ctx *ctx, struct ieee80211_frame *wh, uint8_t b0[AES_BLOCK_LEN], uint8_t aad[2 * AES_BLOCK_LEN], uint8_t auth[AES_BLOCK_LEN], uint8_t s0[AES_BLOCK_LEN]) { -#define IS_QOS_DATA(wh) IEEE80211_QOS_HAS_SEQ(wh) - /* * Map M parameter to encoding * RFC3610, Section 2 (CCM Mode Specification) @@ -446,7 +517,8 @@ ccmp_init_blocks(rijndael_ctx *ctx, struct ieee80211_frame *wh, * Dlen */ b0[0] = 0x40 | 0x01 | (m << 3); - /* NB: b0[1] set below */ + /* Init b0[1] (CCM nonce flags) */ + ieee80211_crypto_ccmp_init_nonce_flags(wh, b0); IEEE80211_ADDR_COPY(b0 + 2, wh->i_addr2); b0[8] = pn >> 40; b0[9] = pn >> 32; @@ -457,63 +529,8 @@ ccmp_init_blocks(rijndael_ctx *ctx, struct ieee80211_frame *wh, b0[14] = (dlen >> 8) & 0xff; b0[15] = dlen & 0xff; - /* AAD: - * FC with bits 4..6 and 11..13 masked to zero; 14 is always one - * A1 | A2 | A3 - * SC with bits 4..15 (seq#) masked to zero - * A4 (if present) - * QC (if present) - */ - aad[0] = 0; /* AAD length >> 8 */ - /* NB: aad[1] set below */ - aad[2] = wh->i_fc[0] & 0x8f; /* XXX magic #s */ - /* TODO: 802.11-2016 12.5.3.3.3 - QoS control field mask */ - aad[3] = wh->i_fc[1] & 0xc7; /* XXX magic #s */ - /* NB: we know 3 addresses are contiguous */ - memcpy(aad + 4, wh->i_addr1, 3 * IEEE80211_ADDR_LEN); - aad[22] = wh->i_seq[0] & IEEE80211_SEQ_FRAG_MASK; - aad[23] = 0; /* all bits masked */ - /* - * Construct variable-length portion of AAD based - * on whether this is a 4-address frame/QOS frame. - * We always zero-pad to 32 bytes before running it - * through the cipher. - * - * We also fill in the priority bits of the CCM - * initial block as we know whether or not we have - * a QOS frame. - */ - if (IEEE80211_IS_DSTODS(wh)) { - IEEE80211_ADDR_COPY(aad + 24, - ((struct ieee80211_frame_addr4 *)wh)->i_addr4); - if (IS_QOS_DATA(wh)) { - struct ieee80211_qosframe_addr4 *qwh4 = - (struct ieee80211_qosframe_addr4 *) wh; - aad[30] = qwh4->i_qos[0] & 0x0f;/* just priority bits */ - aad[31] = 0; - b0[1] = aad[30]; - aad[1] = 22 + IEEE80211_ADDR_LEN + 2; - } else { - *(uint16_t *)&aad[30] = 0; - b0[1] = 0; - aad[1] = 22 + IEEE80211_ADDR_LEN; - } - } else { - if (IS_QOS_DATA(wh)) { - struct ieee80211_qosframe *qwh = - (struct ieee80211_qosframe*) wh; - aad[24] = qwh->i_qos[0] & 0x0f; /* just priority bits */ - aad[25] = 0; - b0[1] = aad[24]; - aad[1] = 22 + 2; - } else { - *(uint16_t *)&aad[24] = 0; - b0[1] = 0; - aad[1] = 22; - } - *(uint16_t *)&aad[26] = 0; - *(uint32_t *)&aad[28] = 0; - } + /* Init AAD */ + (void) ieee80211_crypto_init_aad(wh, aad, 2 * AES_BLOCK_LEN); /* Start with the first block and AAD */ rijndael_encrypt(ctx, b0, auth); @@ -524,7 +541,6 @@ ccmp_init_blocks(rijndael_ctx *ctx, struct ieee80211_frame *wh, b0[0] &= 0x07; b0[14] = b0[15] = 0; rijndael_encrypt(ctx, b0, s0); -#undef IS_QOS_DATA } #define CCMP_ENCRYPT(_i, _b, _b0, _pos, _e, _len) do { \ diff --git a/sys/net80211/ieee80211_crypto_gcmp.c b/sys/net80211/ieee80211_crypto_gcmp.c index 290111235b4e..28e1cc5dab46 100644 --- a/sys/net80211/ieee80211_crypto_gcmp.c +++ b/sys/net80211/ieee80211_crypto_gcmp.c @@ -380,90 +380,6 @@ gcmp_demic(struct ieee80211_key *k, struct mbuf *m, int force) return (1); } -/** - * @brief Calculate the AAD required for this frame for AES-GCM. - * - * Note: This code was first copied over from ieee80211_crypto_ccmp.c, so - * it has some CCMP-isms. - * - * NOTE: the first two bytes are a 16 bit big-endian length, which are used - * by AES-CCM. AES-GCM doesn't require the length at the beginning. - * - * @param wh 802.11 frame to calculate the AAD over - * @param aad AAD buffer, GCM_AAD_LEN bytes - * @param The AAD length in bytes. - */ -static int -gcmp_init_aad(const struct ieee80211_frame *wh, uint8_t *aad) -{ - int aad_len; - - memset(aad, 0, GCM_AAD_LEN); - -#define IS_QOS_DATA(wh) IEEE80211_QOS_HAS_SEQ(wh) - /* AAD: - * FC with bits 4..6 and 11..13 masked to zero; 14 is always one - * A1 | A2 | A3 - * SC with bits 4..15 (seq#) masked to zero - * A4 (if present) - * QC (if present) - */ - aad[0] = 0; /* AAD length >> 8 */ - /* NB: aad[1] set below */ - - /* - * TODO: go back over this in 802.11-2020 and triple check - * the AAD assembly with regards to packet flags. - */ - - aad[2] = wh->i_fc[0] & 0x8f; /* XXX magic #s */ - /* - * TODO: 12.5.3.3.3 - bit 14 should always be set; bit 15 masked to 0 - * if QoS control field, unmasked otherwise - */ - aad[3] = wh->i_fc[1] & 0xc7; /* XXX magic #s */ - /* NB: we know 3 addresses are contiguous */ - memcpy(aad + 4, wh->i_addr1, 3 * IEEE80211_ADDR_LEN); - aad[22] = wh->i_seq[0] & IEEE80211_SEQ_FRAG_MASK; - aad[23] = 0; /* all bits masked */ - /* - * Construct variable-length portion of AAD based - * on whether this is a 4-address frame/QOS frame. - * We always zero-pad to 32 bytes before running it - * through the cipher. - */ - if (IEEE80211_IS_DSTODS(wh)) { - IEEE80211_ADDR_COPY(aad + 24, - ((const struct ieee80211_frame_addr4 *)wh)->i_addr4); - if (IS_QOS_DATA(wh)) { - const struct ieee80211_qosframe_addr4 *qwh4 = - (const struct ieee80211_qosframe_addr4 *) wh; - aad[30] = qwh4->i_qos[0] & 0x0f;/* just priority bits */ - aad[31] = 0; - aad_len = aad[1] = 22 + IEEE80211_ADDR_LEN + 2; - } else { - *(uint16_t *)&aad[30] = 0; - aad_len = aad[1] = 22 + IEEE80211_ADDR_LEN; - } - } else { - if (IS_QOS_DATA(wh)) { - const struct ieee80211_qosframe *qwh = - (const struct ieee80211_qosframe*) wh; - aad[24] = qwh->i_qos[0] & 0x0f; /* just priority bits */ - aad[25] = 0; - aad_len = aad[1] = 22 + 2; - } else { - *(uint16_t *)&aad[24] = 0; - aad_len = aad[1] = 22; - } - *(uint16_t *)&aad[26] = 0; - *(uint32_t *)&aad[28] = 0; - } -#undef IS_QOS_DATA - - return (aad_len); -} - /* * Populate the 12 byte / 96 bit IV buffer. */ @@ -538,7 +454,7 @@ gcmp_encrypt(struct ieee80211_key *key, struct mbuf *m0, int hdrlen) } /* Initialise AAD */ - aad_len = gcmp_init_aad(wh, aad); + aad_len = ieee80211_crypto_init_aad(wh, aad, GCM_AAD_LEN); /* Initialise local Nonce to work on */ /* TODO: rename iv stuff here to nonce */ @@ -629,7 +545,7 @@ gcmp_decrypt(struct ieee80211_key *key, u_int64_t pn, struct mbuf *m, } /* Initialise AAD */ - aad_len = gcmp_init_aad(wh, aad); + aad_len = ieee80211_crypto_init_aad(wh, aad, GCM_AAD_LEN); /* Initialise local IV copy to work on */ iv_len = gcmp_init_iv(iv, wh, pn);