ieee80211_crypt_ccmp.c 11.9 KB
Newer Older
Jeff Garzik's avatar
Jeff Garzik committed
1 2 3
/*
 * Host AP crypt: host-based CCMP encryption implementation for Host AP driver
 *
4
 * Copyright (c) 2003-2004, Jouni Malinen <j@w1.fi>
Jeff Garzik's avatar
Jeff Garzik committed
5 6 7 8 9 10 11
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation. See README and COPYING for
 * more details.
 */

12
#include <linux/kernel.h>
13
#include <linux/err.h>
Jeff Garzik's avatar
Jeff Garzik committed
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <asm/string.h>
#include <linux/wireless.h>

#include <net/ieee80211.h>

#include <linux/crypto.h>
#include <asm/scatterlist.h>

MODULE_AUTHOR("Jouni Malinen");
MODULE_DESCRIPTION("Host AP crypt: CCMP");
MODULE_LICENSE("GPL");

#define AES_BLOCK_LEN 16
#define CCMP_HDR_LEN 8
#define CCMP_MIC_LEN 8
#define CCMP_TK_LEN 16
#define CCMP_PN_LEN 6

struct ieee80211_ccmp_data {
	u8 key[CCMP_TK_LEN];
	int key_set;

	u8 tx_pn[CCMP_PN_LEN];
	u8 rx_pn[CCMP_PN_LEN];

	u32 dot11RSNAStatsCCMPFormatErrors;
	u32 dot11RSNAStatsCCMPReplays;
	u32 dot11RSNAStatsCCMPDecryptErrors;

	int key_idx;

53
	struct crypto_cipher *tfm;
Jeff Garzik's avatar
Jeff Garzik committed
54 55 56

	/* scratch buffers for virt_to_page() (crypto API) */
	u8 tx_b0[AES_BLOCK_LEN], tx_b[AES_BLOCK_LEN],
57
	    tx_e[AES_BLOCK_LEN], tx_s0[AES_BLOCK_LEN];
Jeff Garzik's avatar
Jeff Garzik committed
58 59 60
	u8 rx_b0[AES_BLOCK_LEN], rx_b[AES_BLOCK_LEN], rx_a[AES_BLOCK_LEN];
};

61 62
static inline void ieee80211_ccmp_aes_encrypt(struct crypto_cipher *tfm,
					      const u8 pt[16], u8 ct[16])
Jeff Garzik's avatar
Jeff Garzik committed
63
{
64
	crypto_cipher_encrypt_one(tfm, ct, pt);
Jeff Garzik's avatar
Jeff Garzik committed
65 66
}

67
static void *ieee80211_ccmp_init(int key_idx)
Jeff Garzik's avatar
Jeff Garzik committed
68 69 70
{
	struct ieee80211_ccmp_data *priv;

71
	priv = kzalloc(sizeof(*priv), GFP_ATOMIC);
Jeff Garzik's avatar
Jeff Garzik committed
72 73 74 75
	if (priv == NULL)
		goto fail;
	priv->key_idx = key_idx;

76 77
	priv->tfm = crypto_alloc_cipher("aes", 0, CRYPTO_ALG_ASYNC);
	if (IS_ERR(priv->tfm)) {
Jeff Garzik's avatar
Jeff Garzik committed
78 79
		printk(KERN_DEBUG "ieee80211_crypt_ccmp: could not allocate "
		       "crypto API aes\n");
80
		priv->tfm = NULL;
Jeff Garzik's avatar
Jeff Garzik committed
81 82 83 84 85
		goto fail;
	}

	return priv;

86
      fail:
Jeff Garzik's avatar
Jeff Garzik committed
87 88
	if (priv) {
		if (priv->tfm)
89
			crypto_free_cipher(priv->tfm);
Jeff Garzik's avatar
Jeff Garzik committed
90 91 92 93 94 95 96 97 98 99
		kfree(priv);
	}

	return NULL;
}

static void ieee80211_ccmp_deinit(void *priv)
{
	struct ieee80211_ccmp_data *_priv = priv;
	if (_priv && _priv->tfm)
100
		crypto_free_cipher(_priv->tfm);
Jeff Garzik's avatar
Jeff Garzik committed
101 102 103
	kfree(priv);
}

104
static inline void xor_block(u8 * b, u8 * a, size_t len)
Jeff Garzik's avatar
Jeff Garzik committed
105 106 107 108 109 110
{
	int i;
	for (i = 0; i < len; i++)
		b[i] ^= a[i];
}

111
static void ccmp_init_blocks(struct crypto_cipher *tfm,
112
			     struct ieee80211_hdr_4addr *hdr,
113
			     u8 * pn, size_t dlen, u8 * b0, u8 * auth, u8 * s0)
Jeff Garzik's avatar
Jeff Garzik committed
114 115 116 117 118 119 120 121 122 123 124
{
	u8 *pos, qc = 0;
	size_t aad_len;
	u16 fc;
	int a4_included, qc_included;
	u8 aad[2 * AES_BLOCK_LEN];

	fc = le16_to_cpu(hdr->frame_ctl);
	a4_included = ((fc & (IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS)) ==
		       (IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS));
	qc_included = ((WLAN_FC_GET_TYPE(fc) == IEEE80211_FTYPE_DATA) &&
125
		       (WLAN_FC_GET_STYPE(fc) & IEEE80211_STYPE_QOS_DATA));
Jeff Garzik's avatar
Jeff Garzik committed
126 127 128 129
	aad_len = 22;
	if (a4_included)
		aad_len += 6;
	if (qc_included) {
130
		pos = (u8 *) & hdr->addr4;
Jeff Garzik's avatar
Jeff Garzik committed
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
		if (a4_included)
			pos += 6;
		qc = *pos & 0x0f;
		aad_len += 2;
	}

	/* CCM Initial Block:
	 * Flag (Include authentication header, M=3 (8-octet MIC),
	 *       L=1 (2-octet Dlen))
	 * Nonce: 0x00 | A2 | PN
	 * Dlen */
	b0[0] = 0x59;
	b0[1] = qc;
	memcpy(b0 + 2, hdr->addr2, ETH_ALEN);
	memcpy(b0 + 8, pn, CCMP_PN_LEN);
	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)
	 */
	pos = (u8 *) hdr;
157
	aad[0] = 0;		/* aad_len >> 8 */
Jeff Garzik's avatar
Jeff Garzik committed
158 159 160 161
	aad[1] = aad_len & 0xff;
	aad[2] = pos[0] & 0x8f;
	aad[3] = pos[1] & 0xc7;
	memcpy(aad + 4, hdr->addr1, 3 * ETH_ALEN);
162
	pos = (u8 *) & hdr->seq_ctl;
Jeff Garzik's avatar
Jeff Garzik committed
163
	aad[22] = pos[0] & 0x0f;
164
	aad[23] = 0;		/* all bits masked */
Jeff Garzik's avatar
Jeff Garzik committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
	memset(aad + 24, 0, 8);
	if (a4_included)
		memcpy(aad + 24, hdr->addr4, ETH_ALEN);
	if (qc_included) {
		aad[a4_included ? 30 : 24] = qc;
		/* rest of QC masked */
	}

	/* Start with the first block and AAD */
	ieee80211_ccmp_aes_encrypt(tfm, b0, auth);
	xor_block(auth, aad, AES_BLOCK_LEN);
	ieee80211_ccmp_aes_encrypt(tfm, auth, auth);
	xor_block(auth, &aad[AES_BLOCK_LEN], AES_BLOCK_LEN);
	ieee80211_ccmp_aes_encrypt(tfm, auth, auth);
	b0[0] &= 0x07;
	b0[14] = b0[15] = 0;
	ieee80211_ccmp_aes_encrypt(tfm, b0, s0);
}

184 185
static int ieee80211_ccmp_hdr(struct sk_buff *skb, int hdr_len,
			      u8 *aeskey, int keylen, void *priv)
Jeff Garzik's avatar
Jeff Garzik committed
186 187
{
	struct ieee80211_ccmp_data *key = priv;
188 189
	int i;
	u8 *pos;
Jeff Garzik's avatar
Jeff Garzik committed
190

191
	if (skb_headroom(skb) < CCMP_HDR_LEN || skb->len < hdr_len)
Jeff Garzik's avatar
Jeff Garzik committed
192 193
		return -1;

194 195 196
	if (aeskey != NULL && keylen >= CCMP_TK_LEN)
		memcpy(aeskey, key->key, CCMP_TK_LEN);

Jeff Garzik's avatar
Jeff Garzik committed
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
	pos = skb_push(skb, CCMP_HDR_LEN);
	memmove(pos, pos + CCMP_HDR_LEN, hdr_len);
	pos += hdr_len;

	i = CCMP_PN_LEN - 1;
	while (i >= 0) {
		key->tx_pn[i]++;
		if (key->tx_pn[i] != 0)
			break;
		i--;
	}

	*pos++ = key->tx_pn[5];
	*pos++ = key->tx_pn[4];
	*pos++ = 0;
212
	*pos++ = (key->key_idx << 6) | (1 << 5) /* Ext IV included */ ;
Jeff Garzik's avatar
Jeff Garzik committed
213 214 215 216 217
	*pos++ = key->tx_pn[3];
	*pos++ = key->tx_pn[2];
	*pos++ = key->tx_pn[1];
	*pos++ = key->tx_pn[0];

218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
	return CCMP_HDR_LEN;
}

static int ieee80211_ccmp_encrypt(struct sk_buff *skb, int hdr_len, void *priv)
{
	struct ieee80211_ccmp_data *key = priv;
	int data_len, i, blocks, last, len;
	u8 *pos, *mic;
	struct ieee80211_hdr_4addr *hdr;
	u8 *b0 = key->tx_b0;
	u8 *b = key->tx_b;
	u8 *e = key->tx_e;
	u8 *s0 = key->tx_s0;

	if (skb_tailroom(skb) < CCMP_MIC_LEN || skb->len < hdr_len)
		return -1;

	data_len = skb->len - hdr_len;
236
	len = ieee80211_ccmp_hdr(skb, hdr_len, NULL, 0, priv);
237 238 239 240 241
	if (len < 0)
		return -1;

	pos = skb->data + hdr_len + CCMP_HDR_LEN;
	mic = skb_put(skb, CCMP_MIC_LEN);
242
	hdr = (struct ieee80211_hdr_4addr *)skb->data;
Jeff Garzik's avatar
Jeff Garzik committed
243 244
	ccmp_init_blocks(key->tfm, hdr, key->tx_pn, data_len, b0, b, s0);

245
	blocks = DIV_ROUND_UP(data_len, AES_BLOCK_LEN);
Jeff Garzik's avatar
Jeff Garzik committed
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
	last = data_len % AES_BLOCK_LEN;

	for (i = 1; i <= blocks; i++) {
		len = (i == blocks && last) ? last : AES_BLOCK_LEN;
		/* Authentication */
		xor_block(b, pos, len);
		ieee80211_ccmp_aes_encrypt(key->tfm, b, b);
		/* Encryption, with counter */
		b0[14] = (i >> 8) & 0xff;
		b0[15] = i & 0xff;
		ieee80211_ccmp_aes_encrypt(key->tfm, b0, e);
		xor_block(pos, e, len);
		pos += len;
	}

	for (i = 0; i < CCMP_MIC_LEN; i++)
		mic[i] = b[i] ^ s0[i];

	return 0;
}

267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
/*
 * deal with seq counter wrapping correctly.
 * refer to timer_after() for jiffies wrapping handling
 */
static inline int ccmp_replay_check(u8 *pn_n, u8 *pn_o)
{
	u32 iv32_n, iv16_n;
	u32 iv32_o, iv16_o;

	iv32_n = (pn_n[0] << 24) | (pn_n[1] << 16) | (pn_n[2] << 8) | pn_n[3];
	iv16_n = (pn_n[4] << 8) | pn_n[5];

	iv32_o = (pn_o[0] << 24) | (pn_o[1] << 16) | (pn_o[2] << 8) | pn_o[3];
	iv16_o = (pn_o[4] << 8) | pn_o[5];

	if ((s32)iv32_n - (s32)iv32_o < 0 ||
	    (iv32_n == iv32_o && iv16_n <= iv16_o))
		return 1;
	return 0;
}

Jeff Garzik's avatar
Jeff Garzik committed
288 289 290 291
static int ieee80211_ccmp_decrypt(struct sk_buff *skb, int hdr_len, void *priv)
{
	struct ieee80211_ccmp_data *key = priv;
	u8 keyidx, *pos;
292
	struct ieee80211_hdr_4addr *hdr;
Jeff Garzik's avatar
Jeff Garzik committed
293 294 295 296 297 298 299 300 301 302 303 304 305
	u8 *b0 = key->rx_b0;
	u8 *b = key->rx_b;
	u8 *a = key->rx_a;
	u8 pn[6];
	int i, blocks, last, len;
	size_t data_len = skb->len - hdr_len - CCMP_HDR_LEN - CCMP_MIC_LEN;
	u8 *mic = skb->data + skb->len - CCMP_MIC_LEN;

	if (skb->len < hdr_len + CCMP_HDR_LEN + CCMP_MIC_LEN) {
		key->dot11RSNAStatsCCMPFormatErrors++;
		return -1;
	}

306
	hdr = (struct ieee80211_hdr_4addr *)skb->data;
Jeff Garzik's avatar
Jeff Garzik committed
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
	pos = skb->data + hdr_len;
	keyidx = pos[3];
	if (!(keyidx & (1 << 5))) {
		if (net_ratelimit()) {
			printk(KERN_DEBUG "CCMP: received packet without ExtIV"
			       " flag from " MAC_FMT "\n", MAC_ARG(hdr->addr2));
		}
		key->dot11RSNAStatsCCMPFormatErrors++;
		return -2;
	}
	keyidx >>= 6;
	if (key->key_idx != keyidx) {
		printk(KERN_DEBUG "CCMP: RX tkey->key_idx=%d frame "
		       "keyidx=%d priv=%p\n", key->key_idx, keyidx, priv);
		return -6;
	}
	if (!key->key_set) {
		if (net_ratelimit()) {
			printk(KERN_DEBUG "CCMP: received packet from " MAC_FMT
			       " with keyid=%d that does not have a configured"
			       " key\n", MAC_ARG(hdr->addr2), keyidx);
		}
		return -3;
	}

	pn[0] = pos[7];
	pn[1] = pos[6];
	pn[2] = pos[5];
	pn[3] = pos[4];
	pn[4] = pos[1];
	pn[5] = pos[0];
	pos += 8;

340
	if (ccmp_replay_check(pn, key->rx_pn)) {
Jeff Garzik's avatar
Jeff Garzik committed
341
		if (net_ratelimit()) {
342
			IEEE80211_DEBUG_DROP("CCMP: replay detected: STA=" MAC_FMT
Jeff Garzik's avatar
Jeff Garzik committed
343 344 345 346 347 348 349 350 351 352 353 354
			       " previous PN %02x%02x%02x%02x%02x%02x "
			       "received PN %02x%02x%02x%02x%02x%02x\n",
			       MAC_ARG(hdr->addr2), MAC_ARG(key->rx_pn),
			       MAC_ARG(pn));
		}
		key->dot11RSNAStatsCCMPReplays++;
		return -4;
	}

	ccmp_init_blocks(key->tfm, hdr, pn, data_len, b0, a, b);
	xor_block(mic, b, CCMP_MIC_LEN);

355
	blocks = DIV_ROUND_UP(data_len, AES_BLOCK_LEN);
Jeff Garzik's avatar
Jeff Garzik committed
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
	last = data_len % AES_BLOCK_LEN;

	for (i = 1; i <= blocks; i++) {
		len = (i == blocks && last) ? last : AES_BLOCK_LEN;
		/* Decrypt, with counter */
		b0[14] = (i >> 8) & 0xff;
		b0[15] = i & 0xff;
		ieee80211_ccmp_aes_encrypt(key->tfm, b0, b);
		xor_block(pos, b, len);
		/* Authentication */
		xor_block(a, pos, len);
		ieee80211_ccmp_aes_encrypt(key->tfm, a, a);
		pos += len;
	}

	if (memcmp(mic, a, CCMP_MIC_LEN) != 0) {
		if (net_ratelimit()) {
			printk(KERN_DEBUG "CCMP: decrypt failed: STA="
			       MAC_FMT "\n", MAC_ARG(hdr->addr2));
		}
		key->dot11RSNAStatsCCMPDecryptErrors++;
		return -5;
	}

	memcpy(key->rx_pn, pn, CCMP_PN_LEN);

	/* Remove hdr and MIC */
	memmove(skb->data + CCMP_HDR_LEN, skb->data, hdr_len);
	skb_pull(skb, CCMP_HDR_LEN);
	skb_trim(skb, skb->len - CCMP_MIC_LEN);

	return keyidx;
}

390
static int ieee80211_ccmp_set_key(void *key, int len, u8 * seq, void *priv)
Jeff Garzik's avatar
Jeff Garzik committed
391 392 393
{
	struct ieee80211_ccmp_data *data = priv;
	int keyidx;
394
	struct crypto_cipher *tfm = data->tfm;
Jeff Garzik's avatar
Jeff Garzik committed
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419

	keyidx = data->key_idx;
	memset(data, 0, sizeof(*data));
	data->key_idx = keyidx;
	data->tfm = tfm;
	if (len == CCMP_TK_LEN) {
		memcpy(data->key, key, CCMP_TK_LEN);
		data->key_set = 1;
		if (seq) {
			data->rx_pn[0] = seq[5];
			data->rx_pn[1] = seq[4];
			data->rx_pn[2] = seq[3];
			data->rx_pn[3] = seq[2];
			data->rx_pn[4] = seq[1];
			data->rx_pn[5] = seq[0];
		}
		crypto_cipher_setkey(data->tfm, data->key, CCMP_TK_LEN);
	} else if (len == 0)
		data->key_set = 0;
	else
		return -1;

	return 0;
}

420
static int ieee80211_ccmp_get_key(void *key, int len, u8 * seq, void *priv)
Jeff Garzik's avatar
Jeff Garzik committed
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442
{
	struct ieee80211_ccmp_data *data = priv;

	if (len < CCMP_TK_LEN)
		return -1;

	if (!data->key_set)
		return 0;
	memcpy(key, data->key, CCMP_TK_LEN);

	if (seq) {
		seq[0] = data->tx_pn[5];
		seq[1] = data->tx_pn[4];
		seq[2] = data->tx_pn[3];
		seq[3] = data->tx_pn[2];
		seq[4] = data->tx_pn[1];
		seq[5] = data->tx_pn[0];
	}

	return CCMP_TK_LEN;
}

443
static char *ieee80211_ccmp_print_stats(char *p, void *priv)
Jeff Garzik's avatar
Jeff Garzik committed
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
{
	struct ieee80211_ccmp_data *ccmp = priv;
	p += sprintf(p, "key[%d] alg=CCMP key_set=%d "
		     "tx_pn=%02x%02x%02x%02x%02x%02x "
		     "rx_pn=%02x%02x%02x%02x%02x%02x "
		     "format_errors=%d replays=%d decrypt_errors=%d\n",
		     ccmp->key_idx, ccmp->key_set,
		     MAC_ARG(ccmp->tx_pn), MAC_ARG(ccmp->rx_pn),
		     ccmp->dot11RSNAStatsCCMPFormatErrors,
		     ccmp->dot11RSNAStatsCCMPReplays,
		     ccmp->dot11RSNAStatsCCMPDecryptErrors);

	return p;
}

static struct ieee80211_crypto_ops ieee80211_crypt_ccmp = {
460 461 462
	.name = "CCMP",
	.init = ieee80211_ccmp_init,
	.deinit = ieee80211_ccmp_deinit,
463
	.build_iv = ieee80211_ccmp_hdr,
464 465 466 467 468 469 470
	.encrypt_mpdu = ieee80211_ccmp_encrypt,
	.decrypt_mpdu = ieee80211_ccmp_decrypt,
	.encrypt_msdu = NULL,
	.decrypt_msdu = NULL,
	.set_key = ieee80211_ccmp_set_key,
	.get_key = ieee80211_ccmp_get_key,
	.print_stats = ieee80211_ccmp_print_stats,
471 472
	.extra_mpdu_prefix_len = CCMP_HDR_LEN,
	.extra_mpdu_postfix_len = CCMP_MIC_LEN,
473
	.owner = THIS_MODULE,
Jeff Garzik's avatar
Jeff Garzik committed
474 475 476 477 478 479 480 481 482 483 484 485 486 487
};

static int __init ieee80211_crypto_ccmp_init(void)
{
	return ieee80211_register_crypto_ops(&ieee80211_crypt_ccmp);
}

static void __exit ieee80211_crypto_ccmp_exit(void)
{
	ieee80211_unregister_crypto_ops(&ieee80211_crypt_ccmp);
}

module_init(ieee80211_crypto_ccmp_init);
module_exit(ieee80211_crypto_ccmp_exit);