git: eac4d55dba47 - main - net80211: Refactor CCMP-128 support; add CCMP-256 support

From: Adrian Chadd <adrian_at_FreeBSD.org>
Date: Tue, 08 Apr 2025 01:35:27 UTC
The branch main has been updated by adrian:

URL: https://cgit.FreeBSD.org/src/commit/?id=eac4d55dba47e3a6c45db96d32e2f66d71565543

commit eac4d55dba47e3a6c45db96d32e2f66d71565543
Author:     Adrian Chadd <adrian@FreeBSD.org>
AuthorDate: 2025-03-05 03:41:18 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2025-04-08 01:35:21 +0000

    net80211: Refactor CCMP-128 support; add CCMP-256 support
    
    Refactor CCMP-128 support and add CCMP-256 support.
    
    This has been verified against openwrt on an ath9k device with
    CCMP-256 + WPA-PSK-SHA256.
    
    (And whilst I was at it, I also tested GCMP-256.)
    
    Differential Revision:  https://reviews.freebsd.org/D49238
    Reviewed by:    thj
---
 sys/net80211/ieee80211_crypto_ccmp.c | 145 ++++++++++++++++++++++++++++-------
 1 file changed, 118 insertions(+), 27 deletions(-)

diff --git a/sys/net80211/ieee80211_crypto_ccmp.c b/sys/net80211/ieee80211_crypto_ccmp.c
index 87730679c47f..70265335fe70 100644
--- a/sys/net80211/ieee80211_crypto_ccmp.c
+++ b/sys/net80211/ieee80211_crypto_ccmp.c
@@ -54,6 +54,9 @@
 
 #define AES_BLOCK_LEN 16
 
+#define CCMP_128_MIC_LEN		8
+#define CCMP_256_MIC_LEN		16
+
 struct ccmp_ctx {
 	struct ieee80211vap *cc_vap;	/* for diagnostics+statistics */
 	struct ieee80211com *cc_ic;
@@ -74,7 +77,24 @@ static const struct ieee80211_cipher ccmp = {
 	.ic_cipher	= IEEE80211_CIPHER_AES_CCM,
 	.ic_header	= IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN +
 			  IEEE80211_WEP_EXTIVLEN,
-	.ic_trailer	= IEEE80211_WEP_MICLEN,
+	.ic_trailer	= CCMP_128_MIC_LEN,
+	.ic_miclen	= 0,
+	.ic_attach	= ccmp_attach,
+	.ic_detach	= ccmp_detach,
+	.ic_setkey	= ccmp_setkey,
+	.ic_setiv	= ccmp_setiv,
+	.ic_encap	= ccmp_encap,
+	.ic_decap	= ccmp_decap,
+	.ic_enmic	= ccmp_enmic,
+	.ic_demic	= ccmp_demic,
+};
+
+static const struct ieee80211_cipher ccmp_256 = {
+	.ic_name	= "AES-CCM-256",
+	.ic_cipher	= IEEE80211_CIPHER_AES_CCM_256,
+	.ic_header	= IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN +
+			    IEEE80211_WEP_EXTIVLEN,
+	.ic_trailer	= CCMP_256_MIC_LEN,
 	.ic_miclen	= 0,
 	.ic_attach	= ccmp_attach,
 	.ic_detach	= ccmp_detach,
@@ -120,15 +140,70 @@ ccmp_detach(struct ieee80211_key *k)
 	nrefs--;			/* NB: we assume caller locking */
 }
 
+static int
+ccmp_get_trailer_len(struct ieee80211_key *k)
+{
+	return (k->wk_cipher->ic_trailer);
+}
+
+static int
+ccmp_get_header_len(struct ieee80211_key *k)
+{
+	return (k->wk_cipher->ic_header);
+}
+
+/**
+ * @brief Return the M parameter to use for CCMP block0 initialisation.
+ *
+ * M is defined as the number of bytes in the authentication
+ * field.
+ *
+ * See RFC3610, Section 2 (CCM Mode Specification) for more
+ * information.
+ *
+ * The MIC size is defined in 802.11-2020 12.5.3
+ * (CTR with CBC-MAC Protocol (CCMP)).
+ *
+ * CCM-128 - M=8, MIC is 8 octets.
+ * CCM-256 - M=16, MIC is 16 octets.
+ *
+ * @param key	ieee80211_key to calculate M for
+ * @retval the number of bytes in the authentication field
+ */
+static int
+ccmp_get_ccm_m(struct ieee80211_key *k)
+{
+	if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_AES_CCM)
+		return (8);
+	if (k->wk_cipher->ic_cipher == IEEE80211_CIPHER_AES_CCM_256)
+		return (16);
+	return (8); /* XXX default */
+}
+
 static int
 ccmp_setkey(struct ieee80211_key *k)
 {
+	uint32_t keylen;
 	struct ccmp_ctx *ctx = k->wk_private;
 
-	if (k->wk_keylen != (128/NBBY)) {
+	switch (k->wk_cipher->ic_cipher) {
+	case IEEE80211_CIPHER_AES_CCM:
+		keylen = 128;
+		break;
+	case IEEE80211_CIPHER_AES_CCM_256:
+		keylen = 256;
+		break;
+	default:
+		IEEE80211_DPRINTF(ctx->cc_vap, IEEE80211_MSG_CRYPTO,
+		    "%s: Unexpected cipher (%u)",
+		    __func__, k->wk_cipher->ic_cipher);
+		return (0);
+	}
+
+	if (k->wk_keylen != (keylen/NBBY)) {
 		IEEE80211_DPRINTF(ctx->cc_vap, IEEE80211_MSG_CRYPTO,
 			"%s: Invalid key length %u, expecting %u\n",
-			__func__, k->wk_keylen, 128/NBBY);
+			__func__, k->wk_keylen, keylen/NBBY);
 		return 0;
 	}
 	if (k->wk_flags & IEEE80211_KEY_SWENCRYPT)
@@ -187,11 +262,11 @@ ccmp_encap(struct ieee80211_key *k, struct mbuf *m)
 	/*
 	 * Copy down 802.11 header and add the IV, KeyID, and ExtIV.
 	 */
-	M_PREPEND(m, ccmp.ic_header, IEEE80211_M_NOWAIT);
+	M_PREPEND(m, ccmp_get_header_len(k), IEEE80211_M_NOWAIT);
 	if (m == NULL)
 		return 0;
 	ivp = mtod(m, uint8_t *);
-	ovbcopy(ivp + ccmp.ic_header, ivp, hdrlen);
+	ovbcopy(ivp + ccmp_get_header_len(k), ivp, hdrlen);
 	ivp += hdrlen;
 
 	ccmp_setiv(k, ivp);
@@ -290,13 +365,14 @@ finish:
 	 * Copy up 802.11 header and strip crypto bits.
 	 */
 	if (! ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_IV_STRIP))) {
-		ovbcopy(mtod(m, void *), mtod(m, uint8_t *) + ccmp.ic_header,
+		ovbcopy(mtod(m, void *),
+		    mtod(m, uint8_t *) + ccmp_get_header_len(k),
 		    hdrlen);
-		m_adj(m, ccmp.ic_header);
+		m_adj(m, ccmp_get_header_len(k));
 	}
 
 	if ((rxs == NULL) || (rxs->c_pktflags & IEEE80211_RX_F_MIC_STRIP) == 0)
-		m_adj(m, -ccmp.ic_trailer);
+		m_adj(m, -ccmp_get_trailer_len(k));
 
 	/*
 	 * Ok to update rsc now.
@@ -348,18 +424,28 @@ xor_block(uint8_t *b, const uint8_t *a, size_t len)
 
 static void
 ccmp_init_blocks(rijndael_ctx *ctx, struct ieee80211_frame *wh,
-	u_int64_t pn, size_t dlen,
+	uint32_t m, u_int64_t pn, size_t dlen,
 	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)
+	 */
+	m = (m - 2) / 2;
+
 	/* CCM Initial Block:
-	 * Flag (Include authentication header, M=3 (8-octet MIC),
-	 *       L=1 (2-octet Dlen))
+	 *
+	 * Flag (Include authentication header,
+	 *    M=3 or 7 (8 or 16 octet auth field),
+	 *    L=1 (2-octet Dlen))
+	 *    Adata=1 (one or more auth blocks present)
 	 * Nonce: 0x00 | A2 | PN
-	 * Dlen */
-	b0[0] = 0x59;
+	 * Dlen
+	 */
+	b0[0] = 0x40 | 0x01 | (m << 3);
 	/* NB: b0[1] set below */
 	IEEE80211_ADDR_COPY(b0 + 2, wh->i_addr2);
 	b0[8] = pn >> 40;
@@ -381,6 +467,7 @@ ccmp_init_blocks(rijndael_ctx *ctx, struct ieee80211_frame *wh,
 	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);
@@ -465,14 +552,14 @@ ccmp_encrypt(struct ieee80211_key *key, struct mbuf *m0, int hdrlen)
 	ctx->cc_vap->iv_stats.is_crypto_ccmp++;
 
 	wh = mtod(m, struct ieee80211_frame *);
-	data_len = m->m_pkthdr.len - (hdrlen + ccmp.ic_header);
-	ccmp_init_blocks(&ctx->cc_aes, wh, key->wk_keytsc,
-		data_len, b0, aad, b, s0);
+	data_len = m->m_pkthdr.len - (hdrlen + ccmp_get_header_len(key));
+	ccmp_init_blocks(&ctx->cc_aes, wh, ccmp_get_ccm_m(key),
+	    key->wk_keytsc, data_len, b0, aad, b, s0);
 
 	i = 1;
-	pos = mtod(m, uint8_t *) + hdrlen + ccmp.ic_header;
+	pos = mtod(m, uint8_t *) + hdrlen + ccmp_get_header_len(key);
 	/* NB: assumes header is entirely in first mbuf */
-	space = m->m_len - (hdrlen + ccmp.ic_header);
+	space = m->m_len - (hdrlen + ccmp_get_header_len(key));
 	for (;;) {
 		if (space > data_len)
 			space = data_len;
@@ -580,8 +667,8 @@ ccmp_encrypt(struct ieee80211_key *key, struct mbuf *m0, int hdrlen)
 	}
 done:
 	/* tack on MIC */
-	xor_block(b, s0, ccmp.ic_trailer);
-	return m_append(m0, ccmp.ic_trailer, b);
+	xor_block(b, s0, ccmp_get_trailer_len(key));
+	return m_append(m0, ccmp_get_trailer_len(key), b);
 }
 #undef CCMP_ENCRYPT
 
@@ -618,14 +705,17 @@ ccmp_decrypt(struct ieee80211_key *key, u_int64_t pn, struct mbuf *m, int hdrlen
 	ctx->cc_vap->iv_stats.is_crypto_ccmp++;
 
 	wh = mtod(m, struct ieee80211_frame *);
-	data_len = m->m_pkthdr.len - (hdrlen + ccmp.ic_header + ccmp.ic_trailer);
-	ccmp_init_blocks(&ctx->cc_aes, wh, pn, data_len, b0, aad, a, b);
-	m_copydata(m, m->m_pkthdr.len - ccmp.ic_trailer, ccmp.ic_trailer, mic);
-	xor_block(mic, b, ccmp.ic_trailer);
+	data_len = m->m_pkthdr.len -
+	    (hdrlen + ccmp_get_header_len(key) + ccmp_get_trailer_len(key));
+	ccmp_init_blocks(&ctx->cc_aes, wh, ccmp_get_ccm_m(key), pn,
+	    data_len, b0, aad, a, b);
+	m_copydata(m, m->m_pkthdr.len - ccmp_get_trailer_len(key),
+	    ccmp_get_trailer_len(key), mic);
+	xor_block(mic, b, ccmp_get_trailer_len(key));
 
 	i = 1;
-	pos = mtod(m, uint8_t *) + hdrlen + ccmp.ic_header;
-	space = m->m_len - (hdrlen + ccmp.ic_header);
+	pos = mtod(m, uint8_t *) + hdrlen + ccmp_get_header_len(key);
+	space = m->m_len - (hdrlen + ccmp_get_header_len(key));
 	for (;;) {
 		if (space > data_len)
 			space = data_len;
@@ -684,7 +774,7 @@ ccmp_decrypt(struct ieee80211_key *key, u_int64_t pn, struct mbuf *m, int hdrlen
 	if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_MIC_STRIP) != 0)
 		return (1);
 
-	if (memcmp(mic, a, ccmp.ic_trailer) != 0) {
+	if (memcmp(mic, a, ccmp_get_trailer_len(key)) != 0) {
 		IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, wh->i_addr2,
 		    "%s", "AES-CCM decrypt failed; MIC mismatch");
 		vap->iv_stats.is_rx_ccmpmic++;
@@ -698,3 +788,4 @@ ccmp_decrypt(struct ieee80211_key *key, u_int64_t pn, struct mbuf *m, int hdrlen
  * Module glue.
  */
 IEEE80211_CRYPTO_MODULE(ccmp, 1);
+IEEE80211_CRYPTO_MODULE_ADD(ccmp_256);