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);