xref: /linux/net/wireless/michael-mic.c (revision 91a4855d6c03e770e42f17c798a36a3c46e63de2)
1613c8376SEric Biggers // SPDX-License-Identifier: GPL-2.0-only
2613c8376SEric Biggers /*
3613c8376SEric Biggers  * Michael MIC implementation - optimized for TKIP MIC operations
4613c8376SEric Biggers  * Copyright 2002-2003, Instant802 Networks, Inc.
5613c8376SEric Biggers  */
6613c8376SEric Biggers #include <linux/types.h>
7613c8376SEric Biggers #include <linux/bitops.h>
8*fa489a77SEric Biggers #include <linux/export.h>
9613c8376SEric Biggers #include <linux/ieee80211.h>
10613c8376SEric Biggers #include <linux/unaligned.h>
11613c8376SEric Biggers 
12613c8376SEric Biggers struct michael_mic_ctx {
13613c8376SEric Biggers 	u32 l, r;
14613c8376SEric Biggers };
15613c8376SEric Biggers 
16613c8376SEric Biggers static void michael_block(struct michael_mic_ctx *mctx, u32 val)
17613c8376SEric Biggers {
18613c8376SEric Biggers 	mctx->l ^= val;
19613c8376SEric Biggers 	mctx->r ^= rol32(mctx->l, 17);
20613c8376SEric Biggers 	mctx->l += mctx->r;
21613c8376SEric Biggers 	mctx->r ^= ((mctx->l & 0xff00ff00) >> 8) |
22613c8376SEric Biggers 		   ((mctx->l & 0x00ff00ff) << 8);
23613c8376SEric Biggers 	mctx->l += mctx->r;
24613c8376SEric Biggers 	mctx->r ^= rol32(mctx->l, 3);
25613c8376SEric Biggers 	mctx->l += mctx->r;
26613c8376SEric Biggers 	mctx->r ^= ror32(mctx->l, 2);
27613c8376SEric Biggers 	mctx->l += mctx->r;
28613c8376SEric Biggers }
29613c8376SEric Biggers 
30613c8376SEric Biggers static void michael_mic_hdr(struct michael_mic_ctx *mctx, const u8 *key,
31613c8376SEric Biggers 			    struct ieee80211_hdr *hdr)
32613c8376SEric Biggers {
33613c8376SEric Biggers 	u8 *da, *sa, tid;
34613c8376SEric Biggers 
35613c8376SEric Biggers 	da = ieee80211_get_DA(hdr);
36613c8376SEric Biggers 	sa = ieee80211_get_SA(hdr);
37613c8376SEric Biggers 	if (ieee80211_is_data_qos(hdr->frame_control))
38613c8376SEric Biggers 		tid = ieee80211_get_tid(hdr);
39613c8376SEric Biggers 	else
40613c8376SEric Biggers 		tid = 0;
41613c8376SEric Biggers 
42613c8376SEric Biggers 	mctx->l = get_unaligned_le32(key);
43613c8376SEric Biggers 	mctx->r = get_unaligned_le32(key + 4);
44613c8376SEric Biggers 
45613c8376SEric Biggers 	/*
46613c8376SEric Biggers 	 * A pseudo header (DA, SA, Priority, 0, 0, 0) is used in Michael MIC
47613c8376SEric Biggers 	 * calculation, but it is _not_ transmitted
48613c8376SEric Biggers 	 */
49613c8376SEric Biggers 	michael_block(mctx, get_unaligned_le32(da));
50613c8376SEric Biggers 	michael_block(mctx, get_unaligned_le16(&da[4]) |
51613c8376SEric Biggers 			    (get_unaligned_le16(sa) << 16));
52613c8376SEric Biggers 	michael_block(mctx, get_unaligned_le32(&sa[2]));
53613c8376SEric Biggers 	michael_block(mctx, tid);
54613c8376SEric Biggers }
55613c8376SEric Biggers 
56613c8376SEric Biggers void michael_mic(const u8 *key, struct ieee80211_hdr *hdr,
57613c8376SEric Biggers 		 const u8 *data, size_t data_len, u8 *mic)
58613c8376SEric Biggers {
59613c8376SEric Biggers 	u32 val;
60613c8376SEric Biggers 	size_t block, blocks, left;
61613c8376SEric Biggers 	struct michael_mic_ctx mctx;
62613c8376SEric Biggers 
63613c8376SEric Biggers 	michael_mic_hdr(&mctx, key, hdr);
64613c8376SEric Biggers 
65613c8376SEric Biggers 	/* Real data */
66613c8376SEric Biggers 	blocks = data_len / 4;
67613c8376SEric Biggers 	left = data_len % 4;
68613c8376SEric Biggers 
69613c8376SEric Biggers 	for (block = 0; block < blocks; block++)
70613c8376SEric Biggers 		michael_block(&mctx, get_unaligned_le32(&data[block * 4]));
71613c8376SEric Biggers 
72613c8376SEric Biggers 	/* Partial block of 0..3 bytes and padding: 0x5a + 4..7 zeros to make
73613c8376SEric Biggers 	 * total length a multiple of 4. */
74613c8376SEric Biggers 	val = 0x5a;
75613c8376SEric Biggers 	while (left > 0) {
76613c8376SEric Biggers 		val <<= 8;
77613c8376SEric Biggers 		left--;
78613c8376SEric Biggers 		val |= data[blocks * 4 + left];
79613c8376SEric Biggers 	}
80613c8376SEric Biggers 
81613c8376SEric Biggers 	michael_block(&mctx, val);
82613c8376SEric Biggers 	michael_block(&mctx, 0);
83613c8376SEric Biggers 
84613c8376SEric Biggers 	put_unaligned_le32(mctx.l, mic);
85613c8376SEric Biggers 	put_unaligned_le32(mctx.r, mic + 4);
86613c8376SEric Biggers }
87613c8376SEric Biggers EXPORT_SYMBOL_GPL(michael_mic);
88