diff --git a/sys/net80211/ieee80211_crypto.h b/sys/net80211/ieee80211_crypto.h --- a/sys/net80211/ieee80211_crypto.h +++ b/sys/net80211/ieee80211_crypto.h @@ -274,5 +274,11 @@ uint64_t rsc, int tid); void ieee80211_notify_michael_failure(struct ieee80211vap *, const struct ieee80211_frame *, u_int keyix); + +/* AAD assembly for CCMP/GCMP. */ +int 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.c b/sys/net80211/ieee80211_crypto.c --- a/sys/net80211/ieee80211_crypto.c +++ b/sys/net80211/ieee80211_crypto.c @@ -884,3 +884,100 @@ 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.) + */ +int +ieee80211_crypto_init_aad(const struct ieee80211_frame *wh, uint8_t *aad, + int len) +{ + int 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_ccmp.c b/sys/net80211/ieee80211_crypto_ccmp.c --- a/sys/net80211/ieee80211_crypto_ccmp.c +++ b/sys/net80211/ieee80211_crypto_ccmp.c @@ -408,6 +408,51 @@ 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)) { + if (IEEE80211_QOS_HAS_SEQ(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_QOS_HAS_SEQ(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 +473,6 @@ 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 +489,8 @@ * 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 +501,8 @@ 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 +513,6 @@ 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 --- a/sys/net80211/ieee80211_crypto_gcmp.c +++ b/sys/net80211/ieee80211_crypto_gcmp.c @@ -380,90 +380,6 @@ 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 @@ } /* 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 @@ } /* 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);