diff options
Diffstat (limited to 'net/mac80211/wpa.c')
| -rw-r--r-- | net/mac80211/wpa.c | 170 | 
1 files changed, 142 insertions, 28 deletions
diff --git a/net/mac80211/wpa.c b/net/mac80211/wpa.c index c9edfcb7a13..9b3dcc20114 100644 --- a/net/mac80211/wpa.c +++ b/net/mac80211/wpa.c @@ -127,7 +127,7 @@ ieee80211_rx_h_michael_mic_verify(struct ieee80211_rx_data *rx)  		 * APs with pairwise keys should never receive Michael MIC  		 * errors for non-zero keyidx because these are reserved for  		 * group keys and only the AP is sending real multicast -		 * frames in the BSS. ( +		 * frames in the BSS.  		 */  		return RX_DROP_UNUSABLE;  	} @@ -301,22 +301,15 @@ ieee80211_crypto_tkip_decrypt(struct ieee80211_rx_data *rx)  } -static void ccmp_special_blocks(struct sk_buff *skb, u8 *pn, u8 *scratch, -				int encrypted) +static void ccmp_special_blocks(struct sk_buff *skb, u8 *pn, u8 *b_0, u8 *aad)  {  	__le16 mask_fc;  	int a4_included, mgmt;  	u8 qos_tid; -	u8 *b_0, *aad; -	u16 data_len, len_a; +	u16 len_a;  	unsigned int hdrlen;  	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; -	memset(scratch, 0, 6 * AES_BLOCK_SIZE); - -	b_0 = scratch + 3 * AES_BLOCK_SIZE; -	aad = scratch + 4 * AES_BLOCK_SIZE; -  	/*  	 * Mask FC: zero subtype b4 b5 b6 (if not mgmt)  	 * Retry, PwrMgt, MoreData; set Protected @@ -338,20 +331,21 @@ static void ccmp_special_blocks(struct sk_buff *skb, u8 *pn, u8 *scratch,  	else  		qos_tid = 0; -	data_len = skb->len - hdrlen - IEEE80211_CCMP_HDR_LEN; -	if (encrypted) -		data_len -= IEEE80211_CCMP_MIC_LEN; +	/* In CCM, the initial vectors (IV) used for CTR mode encryption and CBC +	 * mode authentication are not allowed to collide, yet both are derived +	 * from this vector b_0. We only set L := 1 here to indicate that the +	 * data size can be represented in (L+1) bytes. The CCM layer will take +	 * care of storing the data length in the top (L+1) bytes and setting +	 * and clearing the other bits as is required to derive the two IVs. +	 */ +	b_0[0] = 0x1; -	/* First block, b_0 */ -	b_0[0] = 0x59; /* flags: Adata: 1, M: 011, L: 001 */  	/* Nonce: Nonce Flags | A2 | PN  	 * Nonce Flags: Priority (b0..b3) | Management (b4) | Reserved (b5..b7)  	 */  	b_0[1] = qos_tid | (mgmt << 4);  	memcpy(&b_0[2], hdr->addr2, ETH_ALEN);  	memcpy(&b_0[8], pn, IEEE80211_CCMP_PN_LEN); -	/* l(m) */ -	put_unaligned_be16(data_len, &b_0[14]);  	/* AAD (extra authenticate-only data) / masked 802.11 header  	 * FC | A1 | A2 | A3 | SC | [A4] | [QC] */ @@ -407,11 +401,15 @@ static int ccmp_encrypt_skb(struct ieee80211_tx_data *tx, struct sk_buff *skb)  	u8 *pos;  	u8 pn[6];  	u64 pn64; -	u8 scratch[6 * AES_BLOCK_SIZE]; +	u8 aad[2 * AES_BLOCK_SIZE]; +	u8 b_0[AES_BLOCK_SIZE];  	if (info->control.hw_key &&  	    !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_GENERATE_IV) && -	    !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE)) { +	    !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE) && +	    !((info->control.hw_key->flags & +	       IEEE80211_KEY_FLAG_GENERATE_IV_MGMT) && +	      ieee80211_is_mgmt(hdr->frame_control))) {  		/*  		 * hwaccel has no need for preallocated room for CCMP  		 * header or MIC fields @@ -460,9 +458,9 @@ static int ccmp_encrypt_skb(struct ieee80211_tx_data *tx, struct sk_buff *skb)  		return 0;  	pos += IEEE80211_CCMP_HDR_LEN; -	ccmp_special_blocks(skb, pn, scratch, 0); -	ieee80211_aes_ccm_encrypt(key->u.ccmp.tfm, scratch, pos, len, -				  pos, skb_put(skb, IEEE80211_CCMP_MIC_LEN)); +	ccmp_special_blocks(skb, pn, b_0, aad); +	ieee80211_aes_ccm_encrypt(key->u.ccmp.tfm, b_0, aad, pos, len, +				  skb_put(skb, IEEE80211_CCMP_MIC_LEN));  	return 0;  } @@ -499,7 +497,7 @@ ieee80211_crypto_ccmp_decrypt(struct ieee80211_rx_data *rx)  	hdrlen = ieee80211_hdrlen(hdr->frame_control);  	if (!ieee80211_is_data(hdr->frame_control) && -	    !ieee80211_is_robust_mgmt_frame(hdr)) +	    !ieee80211_is_robust_mgmt_frame(skb))  		return RX_CONTINUE;  	data_len = skb->len - hdrlen - IEEE80211_CCMP_HDR_LEN - @@ -525,16 +523,16 @@ ieee80211_crypto_ccmp_decrypt(struct ieee80211_rx_data *rx)  	}  	if (!(status->flag & RX_FLAG_DECRYPTED)) { -		u8 scratch[6 * AES_BLOCK_SIZE]; +		u8 aad[2 * AES_BLOCK_SIZE]; +		u8 b_0[AES_BLOCK_SIZE];  		/* hardware didn't decrypt/verify MIC */ -		ccmp_special_blocks(skb, pn, scratch, 1); +		ccmp_special_blocks(skb, pn, b_0, aad);  		if (ieee80211_aes_ccm_decrypt( -			    key->u.ccmp.tfm, scratch, +			    key->u.ccmp.tfm, b_0, aad,  			    skb->data + hdrlen + IEEE80211_CCMP_HDR_LEN,  			    data_len, -			    skb->data + skb->len - IEEE80211_CCMP_MIC_LEN, -			    skb->data + hdrlen + IEEE80211_CCMP_HDR_LEN)) +			    skb->data + skb->len - IEEE80211_CCMP_MIC_LEN))  			return RX_DROP_UNUSABLE;  	} @@ -549,6 +547,106 @@ ieee80211_crypto_ccmp_decrypt(struct ieee80211_rx_data *rx)  	return RX_CONTINUE;  } +static ieee80211_tx_result +ieee80211_crypto_cs_encrypt(struct ieee80211_tx_data *tx, +			    struct sk_buff *skb) +{ +	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; +	struct ieee80211_key *key = tx->key; +	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); +	const struct ieee80211_cipher_scheme *cs = key->sta->cipher_scheme; +	int hdrlen; +	u8 *pos; + +	if (info->control.hw_key && +	    !(info->control.hw_key->flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE)) { +		/* hwaccel has no need for preallocated head room */ +		return TX_CONTINUE; +	} + +	if (unlikely(skb_headroom(skb) < cs->hdr_len && +		     pskb_expand_head(skb, cs->hdr_len, 0, GFP_ATOMIC))) +		return TX_DROP; + +	hdrlen = ieee80211_hdrlen(hdr->frame_control); + +	pos = skb_push(skb, cs->hdr_len); +	memmove(pos, pos + cs->hdr_len, hdrlen); +	skb_set_network_header(skb, skb_network_offset(skb) + cs->hdr_len); + +	return TX_CONTINUE; +} + +static inline int ieee80211_crypto_cs_pn_compare(u8 *pn1, u8 *pn2, int len) +{ +	int i; + +	/* pn is little endian */ +	for (i = len - 1; i >= 0; i--) { +		if (pn1[i] < pn2[i]) +			return -1; +		else if (pn1[i] > pn2[i]) +			return 1; +	} + +	return 0; +} + +static ieee80211_rx_result +ieee80211_crypto_cs_decrypt(struct ieee80211_rx_data *rx) +{ +	struct ieee80211_key *key = rx->key; +	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data; +	const struct ieee80211_cipher_scheme *cs = NULL; +	int hdrlen = ieee80211_hdrlen(hdr->frame_control); +	struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb); +	int data_len; +	u8 *rx_pn; +	u8 *skb_pn; +	u8 qos_tid; + +	if (!rx->sta || !rx->sta->cipher_scheme || +	    !(status->flag & RX_FLAG_DECRYPTED)) +		return RX_DROP_UNUSABLE; + +	if (!ieee80211_is_data(hdr->frame_control)) +		return RX_CONTINUE; + +	cs = rx->sta->cipher_scheme; + +	data_len = rx->skb->len - hdrlen - cs->hdr_len; + +	if (data_len < 0) +		return RX_DROP_UNUSABLE; + +	if (ieee80211_is_data_qos(hdr->frame_control)) +		qos_tid = *ieee80211_get_qos_ctl(hdr) & +				IEEE80211_QOS_CTL_TID_MASK; +	else +		qos_tid = 0; + +	if (skb_linearize(rx->skb)) +		return RX_DROP_UNUSABLE; + +	hdr = (struct ieee80211_hdr *)rx->skb->data; + +	rx_pn = key->u.gen.rx_pn[qos_tid]; +	skb_pn = rx->skb->data + hdrlen + cs->pn_off; + +	if (ieee80211_crypto_cs_pn_compare(skb_pn, rx_pn, cs->pn_len) <= 0) +		return RX_DROP_UNUSABLE; + +	memcpy(rx_pn, skb_pn, cs->pn_len); + +	/* remove security header and MIC */ +	if (pskb_trim(rx->skb, rx->skb->len - cs->mic_len)) +		return RX_DROP_UNUSABLE; + +	memmove(rx->skb->data + cs->hdr_len, rx->skb->data, hdrlen); +	skb_pull(rx->skb, cs->hdr_len); + +	return RX_CONTINUE; +}  static void bip_aad(struct sk_buff *skb, u8 *aad)  { @@ -689,6 +787,7 @@ ieee80211_crypto_hw_encrypt(struct ieee80211_tx_data *tx)  {  	struct sk_buff *skb;  	struct ieee80211_tx_info *info = NULL; +	ieee80211_tx_result res;  	skb_queue_walk(&tx->skbs, skb) {  		info  = IEEE80211_SKB_CB(skb); @@ -696,9 +795,24 @@ ieee80211_crypto_hw_encrypt(struct ieee80211_tx_data *tx)  		/* handle hw-only algorithm */  		if (!info->control.hw_key)  			return TX_DROP; + +		if (tx->key->sta->cipher_scheme) { +			res = ieee80211_crypto_cs_encrypt(tx, skb); +			if (res != TX_CONTINUE) +				return res; +		}  	}  	ieee80211_tx_set_protected(tx);  	return TX_CONTINUE;  } + +ieee80211_rx_result +ieee80211_crypto_hw_decrypt(struct ieee80211_rx_data *rx) +{ +	if (rx->sta->cipher_scheme) +		return ieee80211_crypto_cs_decrypt(rx); + +	return RX_DROP_UNUSABLE; +}  | 
