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