#include "opt_wlan.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/socket.h>
#include <net/if.h>
#include <net/if_media.h>
#include <net/ethernet.h>
#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_crypto_gcm.h>
#include <crypto/rijndael/rijndael.h>
#define AES_BLOCK_LEN 16
#define GCMP_PN_LEN 6
#define GCMP_IV_LEN 12
struct gcmp_ctx {
struct ieee80211vap *cc_vap;
struct ieee80211com *cc_ic;
rijndael_ctx cc_aes;
};
static void *gcmp_attach(struct ieee80211vap *, struct ieee80211_key *);
static void gcmp_detach(struct ieee80211_key *);
static int gcmp_setkey(struct ieee80211_key *);
static void gcmp_setiv(struct ieee80211_key *, uint8_t *);
static int gcmp_encap(struct ieee80211_key *, struct mbuf *);
static int gcmp_decap(struct ieee80211_key *, struct mbuf *, int);
static int gcmp_enmic(struct ieee80211_key *, struct mbuf *, int);
static int gcmp_demic(struct ieee80211_key *, struct mbuf *, int);
static const struct ieee80211_cipher gcmp = {
.ic_name = "AES-GCMP",
.ic_cipher = IEEE80211_CIPHER_AES_GCM_128,
.ic_header = IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN +
IEEE80211_WEP_EXTIVLEN,
.ic_trailer = GCMP_MIC_LEN,
.ic_miclen = 0,
.ic_attach = gcmp_attach,
.ic_detach = gcmp_detach,
.ic_setkey = gcmp_setkey,
.ic_setiv = gcmp_setiv,
.ic_encap = gcmp_encap,
.ic_decap = gcmp_decap,
.ic_enmic = gcmp_enmic,
.ic_demic = gcmp_demic,
};
static const struct ieee80211_cipher gcmp_256 = {
.ic_name = "AES-GCMP-256",
.ic_cipher = IEEE80211_CIPHER_AES_GCM_256,
.ic_header = IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN +
IEEE80211_WEP_EXTIVLEN,
.ic_trailer = GCMP_MIC_LEN,
.ic_miclen = 0,
.ic_attach = gcmp_attach,
.ic_detach = gcmp_detach,
.ic_setkey = gcmp_setkey,
.ic_setiv = gcmp_setiv,
.ic_encap = gcmp_encap,
.ic_decap = gcmp_decap,
.ic_enmic = gcmp_enmic,
.ic_demic = gcmp_demic,
};
static int gcmp_encrypt(struct ieee80211_key *, struct mbuf *, int hdrlen);
static int gcmp_decrypt(struct ieee80211_key *, u_int64_t pn,
struct mbuf *, int hdrlen);
static int nrefs = 0;
static void *
gcmp_attach(struct ieee80211vap *vap, struct ieee80211_key *k)
{
struct gcmp_ctx *ctx;
ctx = (struct gcmp_ctx *) IEEE80211_MALLOC(sizeof(struct gcmp_ctx),
M_80211_CRYPTO, IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
if (ctx == NULL) {
vap->iv_stats.is_crypto_nomem++;
return (NULL);
}
ctx->cc_vap = vap;
ctx->cc_ic = vap->iv_ic;
nrefs++;
return (ctx);
}
static void
gcmp_detach(struct ieee80211_key *k)
{
struct gcmp_ctx *ctx = k->wk_private;
IEEE80211_FREE(ctx, M_80211_CRYPTO);
KASSERT(nrefs > 0, ("imbalanced attach/detach"));
nrefs--;
}
static int
gcmp_get_trailer_len(struct ieee80211_key *k)
{
return (k->wk_cipher->ic_trailer);
}
static int
gcmp_get_header_len(struct ieee80211_key *k)
{
return (k->wk_cipher->ic_header);
}
static int
gcmp_setkey(struct ieee80211_key *k)
{
uint32_t keylen;
struct gcmp_ctx *ctx = k->wk_private;
switch (k->wk_cipher->ic_cipher) {
case IEEE80211_CIPHER_AES_GCM_128:
keylen = 128;
break;
case IEEE80211_CIPHER_AES_GCM_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, keylen/NBBY);
return (0);
}
if (k->wk_flags & IEEE80211_KEY_SWENCRYPT)
rijndael_set_key(&ctx->cc_aes, k->wk_key, k->wk_keylen*NBBY);
return (1);
}
static void
gcmp_setiv(struct ieee80211_key *k, uint8_t *ivp)
{
struct gcmp_ctx *ctx = k->wk_private;
struct ieee80211vap *vap = ctx->cc_vap;
uint8_t keyid;
keyid = ieee80211_crypto_get_keyid(vap, k) << 6;
k->wk_keytsc++;
ivp[0] = k->wk_keytsc >> 0;
ivp[1] = k->wk_keytsc >> 8;
ivp[2] = 0;
ivp[3] = keyid | IEEE80211_WEP_EXTIV;
ivp[4] = k->wk_keytsc >> 16;
ivp[5] = k->wk_keytsc >> 24;
ivp[6] = k->wk_keytsc >> 32;
ivp[7] = k->wk_keytsc >> 40;
}
static int
gcmp_encap(struct ieee80211_key *k, struct mbuf *m)
{
const struct ieee80211_frame *wh;
struct gcmp_ctx *ctx = k->wk_private;
struct ieee80211com *ic = ctx->cc_ic;
uint8_t *ivp;
int hdrlen;
int is_mgmt;
hdrlen = ieee80211_hdrspace(ic, mtod(m, void *));
wh = mtod(m, const struct ieee80211_frame *);
is_mgmt = IEEE80211_IS_MGMT(wh);
if (is_mgmt && (k->wk_flags & IEEE80211_KEY_NOIVMGT))
return (1);
if (!is_mgmt && (k->wk_flags & IEEE80211_KEY_NOIV))
return (1);
M_PREPEND(m, gcmp_get_header_len(k), IEEE80211_M_NOWAIT);
if (m == NULL)
return (0);
ivp = mtod(m, uint8_t *);
ovbcopy(ivp + gcmp_get_header_len(k), ivp, hdrlen);
ivp += hdrlen;
gcmp_setiv(k, ivp);
if ((k->wk_flags & IEEE80211_KEY_SWENCRYPT) &&
!gcmp_encrypt(k, m, hdrlen))
return (0);
return (1);
}
static int
gcmp_enmic(struct ieee80211_key *k, struct mbuf *m, int force)
{
return (1);
}
static __inline uint64_t
READ_6(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4, uint8_t b5)
{
uint32_t iv32 = (b0 << 0) | (b1 << 8) | (b2 << 16) | (b3 << 24);
uint16_t iv16 = (b4 << 0) | (b5 << 8);
return ((((uint64_t)iv16) << 32) | iv32);
}
static int
gcmp_decap(struct ieee80211_key *k, struct mbuf *m, int hdrlen)
{
const struct ieee80211_rx_stats *rxs;
struct gcmp_ctx *ctx = k->wk_private;
struct ieee80211vap *vap = ctx->cc_vap;
struct ieee80211_frame *wh;
uint8_t *ivp, tid;
uint64_t pn;
bool noreplaycheck;
rxs = ieee80211_get_rx_params_ptr(m);
if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_IV_STRIP) != 0)
goto finish;
wh = mtod(m, struct ieee80211_frame *);
ivp = mtod(m, uint8_t *) + hdrlen;
if ((ivp[IEEE80211_WEP_IVLEN] & IEEE80211_WEP_EXTIV) == 0) {
IEEE80211_NOTE_MAC(vap, IEEE80211_MSG_CRYPTO, wh->i_addr2,
"%s", "missing ExtIV for AES-GCM cipher");
vap->iv_stats.is_rx_gcmpformat++;
return (0);
}
tid = ieee80211_gettid(wh);
pn = READ_6(ivp[0], ivp[1], ivp[4], ivp[5], ivp[6], ivp[7]);
noreplaycheck = (k->wk_flags & IEEE80211_KEY_NOREPLAY) != 0;
noreplaycheck |= (rxs != NULL) &&
(rxs->c_pktflags & IEEE80211_RX_F_PN_VALIDATED) != 0;
if (pn <= k->wk_keyrsc[tid] && !noreplaycheck) {
ieee80211_notify_replay_failure(vap, wh, k, pn, tid);
vap->iv_stats.is_rx_gcmpreplay++;
return (0);
}
if ((k->wk_flags & IEEE80211_KEY_SWDECRYPT) &&
!gcmp_decrypt(k, pn, m, hdrlen))
return (0);
finish:
if ((rxs == NULL) || (rxs->c_pktflags & IEEE80211_RX_F_IV_STRIP) == 0) {
ovbcopy(mtod(m, void *), mtod(m, uint8_t *) +
gcmp_get_header_len(k), hdrlen);
m_adj(m, gcmp_get_header_len(k));
}
if ((rxs == NULL) || (rxs->c_pktflags & IEEE80211_RX_F_MIC_STRIP) == 0)
m_adj(m, -gcmp_get_trailer_len(k));
if ((rxs == NULL) || (rxs->c_pktflags & IEEE80211_RX_F_IV_STRIP) == 0) {
if (pn > k->wk_keyrsc[tid])
k->wk_keyrsc[tid] = pn;
}
return (1);
}
static int
gcmp_demic(struct ieee80211_key *k, struct mbuf *m, int force)
{
return (1);
}
static int
gcmp_init_iv(uint8_t *iv, const struct ieee80211_frame *wh, u_int64_t pn)
{
uint8_t j_pn[GCMP_PN_LEN];
j_pn[0] = pn >> 40;
j_pn[1] = pn >> 32;
j_pn[2] = pn >> 24;
j_pn[3] = pn >> 16;
j_pn[4] = pn >> 8;
j_pn[5] = pn >> 0;
memcpy(iv, wh->i_addr2, IEEE80211_ADDR_LEN);
memcpy(iv + IEEE80211_ADDR_LEN, j_pn, GCMP_PN_LEN);
return (GCMP_IV_LEN);
}
static int
gcmp_encrypt(struct ieee80211_key *key, struct mbuf *m0, int hdrlen)
{
struct gcmp_ctx *ctx = key->wk_private;
struct ieee80211_frame *wh;
struct mbuf *m = m0;
int data_len, aad_len, iv_len, ret;
uint8_t aad[GCM_AAD_LEN];
uint8_t T[GCMP_MIC_LEN];
uint8_t iv[GCMP_IV_LEN];
uint8_t *p_pktbuf = NULL;
uint8_t *c_pktbuf = NULL;
wh = mtod(m, struct ieee80211_frame *);
data_len = m->m_pkthdr.len - (hdrlen + gcmp_get_header_len(key));
ctx->cc_vap->iv_stats.is_crypto_gcmp++;
p_pktbuf = IEEE80211_MALLOC(data_len, M_TEMP,
IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
if (p_pktbuf == NULL) {
IEEE80211_NOTE_MAC(ctx->cc_vap, IEEE80211_MSG_CRYPTO,
wh->i_addr2, "%s",
"AES-GCM encrypt failed; couldn't allocate buffer");
ctx->cc_vap->iv_stats.is_crypto_gcmp_nomem++;
return (0);
}
c_pktbuf = IEEE80211_MALLOC(data_len, M_TEMP,
IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
if (c_pktbuf == NULL) {
IEEE80211_NOTE_MAC(ctx->cc_vap, IEEE80211_MSG_CRYPTO,
wh->i_addr2, "%s",
"AES-GCM encrypt failed; couldn't allocate buffer");
ctx->cc_vap->iv_stats.is_crypto_gcmp_nomem++;
IEEE80211_FREE(p_pktbuf, M_TEMP);
return (0);
}
aad_len = ieee80211_crypto_init_aad(wh, aad, GCM_AAD_LEN);
iv_len = gcmp_init_iv(iv, wh, key->wk_keytsc);
m_copydata(m0, hdrlen + gcmp_get_header_len(key), data_len,
p_pktbuf);
ieee80211_crypto_aes_gcm_ae(&ctx->cc_aes, iv, iv_len,
p_pktbuf, data_len, aad + 2, aad_len, c_pktbuf, T);
m_copyback(m0, hdrlen + gcmp_get_header_len(key), data_len,
c_pktbuf);
ret = m_append(m0, gcmp_get_trailer_len(key), T);
if (ret == 0) {
IEEE80211_NOTE_MAC(ctx->cc_vap, IEEE80211_MSG_CRYPTO,
wh->i_addr2, "%s",
"AES-GCM encrypt failed; couldn't append T");
ctx->cc_vap->iv_stats.is_crypto_gcmp_nospc++;
}
IEEE80211_FREE(p_pktbuf, M_TEMP);
IEEE80211_FREE(c_pktbuf, M_TEMP);
return (ret);
}
static int
gcmp_decrypt(struct ieee80211_key *key, u_int64_t pn, struct mbuf *m,
int hdrlen)
{
const struct ieee80211_rx_stats *rxs;
struct gcmp_ctx *ctx = key->wk_private;
struct ieee80211_frame *wh;
int data_len, aad_len, iv_len, ret;
uint8_t aad[GCM_AAD_LEN];
uint8_t T[GCMP_MIC_LEN];
uint8_t iv[GCMP_IV_LEN];
uint8_t *p_pktbuf = NULL;
uint8_t *c_pktbuf = NULL;
rxs = ieee80211_get_rx_params_ptr(m);
if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_DECRYPTED) != 0)
return (1);
wh = mtod(m, struct ieee80211_frame *);
data_len = m->m_pkthdr.len -
(hdrlen + gcmp_get_header_len(key) + GCMP_MIC_LEN);
ctx->cc_vap->iv_stats.is_crypto_gcmp++;
p_pktbuf = IEEE80211_MALLOC(data_len, M_TEMP,
IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
if (p_pktbuf == NULL) {
ctx->cc_vap->iv_stats.is_crypto_gcmp_nomem++;
return (0);
}
c_pktbuf = IEEE80211_MALLOC(data_len, M_TEMP,
IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
if (c_pktbuf == NULL) {
ctx->cc_vap->iv_stats.is_crypto_gcmp_nomem++;
IEEE80211_FREE(p_pktbuf, M_TEMP);
return (0);
}
aad_len = ieee80211_crypto_init_aad(wh, aad, GCM_AAD_LEN);
iv_len = gcmp_init_iv(iv, wh, pn);
m_copydata(m, hdrlen + gcmp_get_header_len(key), data_len,
c_pktbuf);
m_copydata(m, hdrlen + gcmp_get_header_len(key) + data_len,
GCMP_MIC_LEN, T);
ret = ieee80211_crypto_aes_gcm_ad(&ctx->cc_aes, iv, iv_len,
c_pktbuf, data_len, aad + 2, aad_len, T, p_pktbuf);
if ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_MIC_STRIP) != 0)
goto skip_ok;
if (ret != 0) {
ctx->cc_vap->iv_stats.is_rx_gcmpmic++;
IEEE80211_NOTE_MAC(ctx->cc_vap, IEEE80211_MSG_CRYPTO,
wh->i_addr2, "%s", "AES-GCM decrypt failed; MIC mismatch");
IEEE80211_FREE(p_pktbuf, M_TEMP);
IEEE80211_FREE(c_pktbuf, M_TEMP);
return (0);
}
skip_ok:
m_copyback(m, hdrlen + gcmp_get_header_len(key), data_len,
p_pktbuf);
IEEE80211_FREE(p_pktbuf, M_TEMP);
IEEE80211_FREE(c_pktbuf, M_TEMP);
return (1);
}
IEEE80211_CRYPTO_MODULE(gcmp, 1);
IEEE80211_CRYPTO_MODULE_ADD(gcmp_256);