/* * Copyright (c) 2017-2018 The Linux Foundation. All rights reserved. * * Permission to use, copy, modify, and/or distribute this software for * any purpose with or without fee is hereby granted, provided that the * above copyright notice and this permission notice appear in all * copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. */ #include "lim_process_fils.h" #include #include #include #include #include #include #include #include #ifdef WLAN_FEATURE_FILS_SK #ifdef WLAN_FILS_DEBUG /** * lim_fils_data_dump()- dump fils data * @type: Data name * @data: pointer to data buffer * @len: data len * * Return: None */ static void lim_fils_data_dump(char *type, uint8_t *data, uint32_t len) { QDF_TRACE(QDF_MODULE_ID_PE, QDF_TRACE_LEVEL_DEBUG, ("%s : length %d"), type, len); qdf_trace_hex_dump(QDF_MODULE_ID_PE, QDF_TRACE_LEVEL_INFO, data, len); } #else static void lim_fils_data_dump(char *type, uint8_t *data, uint32_t len) { } #endif /** * lim_get_crypto_digest_len()- Returns hash length based on crypto type * @type: Crypto type * * Return: hash length */ static int lim_get_crypto_digest_len(uint8_t *type) { if (!strcmp(type, HMAC_SHA386_CRYPTO_TYPE)) return SHA384_DIGEST_SIZE; else if (!strcmp(type, HMAC_SHA256_CRYPTO_TYPE)) return SHA256_DIGEST_SIZE; return -EINVAL; } /** * lim_get_auth_tag_len()- This API returns auth tag len based on crypto suit * used to encrypt erp packet. * @crypto_suite: Crtpto suit * * Return: tag length */ static uint8_t lim_get_auth_tag_len(enum fils_erp_cryptosuite crypto_suite) { switch (crypto_suite) { case HMAC_SHA256_64: return -EINVAL; case HMAC_SHA256_128: return FILS_SHA256_128_AUTH_TAG; case HMAC_SHA256_256: return FILS_SHA256_256_AUTH_TAG; default: return -EINVAL; } } /** * lim_get_crypto_type()- This API returns crypto type based on akm suite used * @akm: akm used for authentication * * Return: Crypto type */ static uint8_t *lim_get_crypto_type(uint8_t akm) { switch (akm) { case eCSR_AUTH_TYPE_FILS_SHA384: case eCSR_AUTH_TYPE_FT_FILS_SHA384: return FILS_SHA384_CRYPTO_TYPE; case eCSR_AUTH_TYPE_FILS_SHA256: case eCSR_AUTH_TYPE_FT_FILS_SHA256: default: return FILS_SHA256_CRYPTO_TYPE; } } /** * lim_get_pmk_length()- This API returns pmk length based on akm used * @akm: akm used for authentication * * Return: PMK length */ static uint8_t lim_get_pmk_length(int akm_type) { switch (akm_type) { case eCSR_AUTH_TYPE_FILS_SHA256: case eCSR_AUTH_TYPE_FT_FILS_SHA256: return FILS_SHA256_PKM_LEN; case eCSR_AUTH_TYPE_FILS_SHA384: case eCSR_AUTH_TYPE_FT_FILS_SHA384: return FILS_SHA384_PKM_LEN; default: return FILS_SHA256_PKM_LEN; } } /** * lim_get_kek_len()- This API returns kek length based on akm used * @akm: akm used for authentication * * Return: KEK length */ static uint8_t lim_get_kek_len(uint8_t akm) { switch (akm) { case eCSR_AUTH_TYPE_FILS_SHA384: case eCSR_AUTH_TYPE_FT_FILS_SHA384: return FILS_SHA384_KEK_LEN; case eCSR_AUTH_TYPE_FILS_SHA256: case eCSR_AUTH_TYPE_FT_FILS_SHA256: return FILS_SHA256_KEK_LEN; default: return FILS_SHA256_KEK_LEN; } } /** * lim_get_tk_len()- This API returns tk length based on cypher used * @akm: cypher used * * Return: TK length */ static uint8_t lim_get_tk_len(int cypher_suite) { switch (cypher_suite) { case eSIR_ED_TKIP: return TK_LEN_TKIP; case eSIR_ED_CCMP: return TK_LEN_CCMP; case eSIR_ED_GCMP: return TK_LEN_GCMP; case eSIR_ED_GCMP_256: return TK_LEN_GCMP_256; case eSIR_ED_AES_128_CMAC: return TK_LEN_AES_128_CMAC; default: return 0; } } /** * lim_get_ick_len()- This API returns ick length based on akm used * @akm: akm used for authentication * * Return: ICK length */ static int lim_get_ick_len(uint8_t akm) { switch (akm) { case eCSR_AUTH_TYPE_FILS_SHA384: case eCSR_AUTH_TYPE_FT_FILS_SHA384: return FILS_SHA384_ICK_LEN; case eCSR_AUTH_TYPE_FILS_SHA256: case eCSR_AUTH_TYPE_FT_FILS_SHA256: default: return FILS_SHA256_ICK_LEN; } } /** * lim_get_key_from_prf()- This API returns key data using PRF-X as defined in * 11.6.1.7.2 ieee-80211-2012. * @type: crypto type needs to be used * @secret: key which needs to be used in crypto * @secret_len: key_len of secret * @label: PRF label * @optional_data: Data used for hash * @optional_data_len: data length * @key: key data output * @keylen: key data length * * Return: QDF_STATUS */ static QDF_STATUS lim_get_key_from_prf(uint8_t *type, uint8_t *secret, uint32_t secret_len, uint8_t *label, uint8_t *optional_data, uint32_t optional_data_len, uint8_t *key, uint32_t keylen) { uint8_t count[2]; uint8_t *addr[4]; uint32_t len[4]; uint16_t key_bit_length = keylen * 8; uint8_t key_length[2]; uint32_t i = 0, remain_len; uint16_t interation; uint8_t crypto_digest_len = lim_get_crypto_digest_len(type); uint8_t tmp_hash[SHA384_DIGEST_SIZE] = {0}; addr[0] = count; len[0] = sizeof(count); addr[1] = label; len[1] = strlen(label); addr[2] = optional_data; len[2] = optional_data_len; qdf_mem_copy(key_length, &key_bit_length, sizeof(key_bit_length)); addr[3] = key_length; len[3] = sizeof(key_length); for (interation = 1; i < keylen; interation++) { remain_len = keylen - i; qdf_mem_copy(count, &interation, sizeof(interation)); if (remain_len >= crypto_digest_len) remain_len = crypto_digest_len; if (qdf_get_hmac_hash(type, secret, secret_len, 4, addr, len, tmp_hash) < 0) { pe_err("qdf_get_hmac_hash failed"); return QDF_STATUS_E_FAILURE; } qdf_mem_copy(&key[i], tmp_hash, remain_len); i += crypto_digest_len; } return QDF_STATUS_SUCCESS; } /** * lim_default_hmac_sha256_kdf()- This API calculates key data using default kdf * defined in RFC4306. * @secret: key which needs to be used in crypto * @secret_len: key_len of secret * @label: PRF label * @optional_data: Data used for hash * @optional_data_len: data length * @key: key data output * @keylen: key data length * * This API creates default KDF as defined in RFC4306 * PRF+ (K,S) = T1 | T2 | T3 | T4 | ... * T1 = PRF (K, S | 0x01) * T2 = PRF (K, T1 | S | 0x02) * T3 = PRF (K, T2 | S | 0x03) * T4 = PRF (K, T3 | S | 0x04) * * for every iteration its creates 32 bit of hash * * Return: QDF_STATUS */ static QDF_STATUS lim_default_hmac_sha256_kdf(uint8_t *secret, uint32_t secret_len, uint8_t *label, uint8_t *optional_data, uint32_t optional_data_len, uint8_t *key, uint32_t keylen) { uint8_t tmp_hash[SHA256_DIGEST_SIZE] = {0}; uint8_t count = 1; uint8_t *addr[4]; uint32_t len[4]; uint32_t current_position = 0, remaining_data = SHA256_DIGEST_SIZE; addr[0] = tmp_hash; len[0] = SHA256_DIGEST_SIZE; addr[1] = label; len[1] = strlen(label) + 1; addr[2] = optional_data; len[2] = optional_data_len; addr[3] = &count; len[3] = 1; if (keylen == 0 || (keylen > (MAX_PRF_INTERATIONS_COUNT * SHA256_DIGEST_SIZE))) { pe_err("invalid key length %d", keylen); return QDF_STATUS_E_FAILURE; } /* Create T1 */ if (qdf_get_hmac_hash(FILS_SHA256_CRYPTO_TYPE, secret, secret_len, 3, &addr[1], &len[1], tmp_hash) < 0) { pe_err("failed to get hmac hash"); return QDF_STATUS_E_FAILURE; } /* Update hash from tmp_hash */ qdf_mem_copy(key + current_position, tmp_hash, remaining_data); current_position += remaining_data; for (count = 2; current_position < keylen; count++) { remaining_data = keylen - current_position; if (remaining_data > SHA256_DIGEST_SIZE) remaining_data = SHA256_DIGEST_SIZE; /* Create T-n */ if (qdf_get_hmac_hash(FILS_SHA256_CRYPTO_TYPE, secret, secret_len, 4, addr, len, tmp_hash) < 0) { pe_err("failed to get hmac hash"); return QDF_STATUS_E_FAILURE; } /* Update hash from tmp_hash */ qdf_mem_copy(key + current_position, tmp_hash, remaining_data); current_position += remaining_data; } return QDF_STATUS_SUCCESS; } /** * lim_process_fils_eap_tlv()- This API process eap tlv available in auth resp * and returns remaining length. * @pe_session: PE session * @wrapped_data: wrapped data * @data_len: length of tlv * * Return: remaining length */ static uint32_t lim_process_fils_eap_tlv(tpPESession pe_session, uint8_t *wrapped_data, uint32_t data_len) { struct fils_eap_tlv *tlv; struct fils_auth_rsp_info *auth_info; uint8_t auth_tag_len; auth_info = &pe_session->fils_info->auth_info; /* Minimum */ auth_tag_len = lim_get_auth_tag_len(HMAC_SHA256_128); while (data_len > (auth_tag_len + 1)) { tlv = (struct fils_eap_tlv *) wrapped_data; pe_debug("tlv type %x len %u total %u", tlv->type, tlv->length, data_len); if (tlv->length > (data_len - 2)) { pe_err("tlv len %d greater data_len %d", tlv->length, data_len); return 0; } switch (tlv->type) { case SIR_FILS_EAP_TLV_KEYNAME_NAI: auth_info->keyname = qdf_mem_malloc(tlv->length); if (!auth_info->keyname) { pe_err("failed to alloc memory"); return 0; } qdf_mem_copy(auth_info->keyname, tlv->data, tlv->length); auth_info->keylength = tlv->length; data_len -= (tlv->length + 2); wrapped_data += (tlv->length + 2); break; case SIR_FILS_EAP_TLV_R_RK_LIFETIME: /* TODO check this */ auth_info->r_rk_lifetime = lim_get_u32(tlv->data); data_len -= (tlv->length + 2); wrapped_data += (tlv->length + 2); break; case SIR_FILS_EAP_TLV_R_MSK_LIFETIME: /* TODO check this */ auth_info->r_msk_lifetime = lim_get_u32(tlv->data); data_len -= (tlv->length + 2); wrapped_data += (tlv->length + 2); break; case SIR_FILS_EAP_TLV_DOMAIN_NAME: auth_info->domain_name = qdf_mem_malloc(tlv->length); if (!auth_info->domain_name) { pe_err("failed to alloc memory"); return 0; } qdf_mem_copy(auth_info->domain_name, tlv->data, tlv->length); auth_info->domain_len = tlv->length; data_len -= (tlv->length + 2); wrapped_data += (tlv->length + 2); break; /* TODO process these now */ case SIR_FILS_EAP_TLV_CRYPTO_LIST: case SIR_FILS_EAP_TLV_AUTH_INDICATION: data_len -= (tlv->length + 2); wrapped_data += (tlv->length + 2); break; default: pe_debug("Unknown type"); return data_len; } } return data_len; } /** * lim_generate_key_data()- This API generates key data using prf * FILS-Key-Data = KDF-X(PMK, "FILS PTK Derivation", SPA||AA||SNonce||ANonce) * @fils_info: fils session info * @key_label: label used * @data: data buffer * @data_len: data buffer len * @key_data: hash data needs to be generated * @key_data_len: hash data len * * Return: QDF_STATUS */ static QDF_STATUS lim_generate_key_data(struct pe_fils_session *fils_info, uint8_t *key_label, uint8_t *data, uint32_t data_len, uint8_t *key_data, uint32_t key_data_len) { QDF_STATUS status; if (!fils_info) return QDF_STATUS_E_FAILURE; status = lim_get_key_from_prf(lim_get_crypto_type(fils_info->akm), fils_info->fils_pmk, fils_info->fils_pmk_len, key_label, data, data_len, key_data, key_data_len); if (status != QDF_STATUS_SUCCESS) pe_err("failed to generate keydata"); return status; } /** * lim_generate_ap_key_auth()- This API generates ap auth data which needs to be * verified in assoc response. * @pe_session: pe session pointer * * Return: None */ static void lim_generate_ap_key_auth(tpPESession pe_session) { uint8_t *buf, *addr[1]; uint32_t len; struct pe_fils_session *fils_info = pe_session->fils_info; uint8_t data[SIR_FILS_NONCE_LENGTH + SIR_FILS_NONCE_LENGTH + QDF_MAC_ADDR_SIZE + QDF_MAC_ADDR_SIZE] = {0}; if (!fils_info) return; len = SIR_FILS_NONCE_LENGTH + SIR_FILS_NONCE_LENGTH + QDF_MAC_ADDR_SIZE + QDF_MAC_ADDR_SIZE; addr[0] = data; buf = data; qdf_mem_copy(buf, fils_info->auth_info.fils_nonce, SIR_FILS_NONCE_LENGTH); buf += SIR_FILS_NONCE_LENGTH; qdf_mem_copy(buf, fils_info->fils_nonce, SIR_FILS_NONCE_LENGTH); buf += SIR_FILS_NONCE_LENGTH; qdf_mem_copy(buf, pe_session->bssId, QDF_MAC_ADDR_SIZE); buf += QDF_MAC_ADDR_SIZE; qdf_mem_copy(buf, pe_session->selfMacAddr, QDF_MAC_ADDR_SIZE); buf += QDF_MAC_ADDR_SIZE; if (qdf_get_hmac_hash(lim_get_crypto_type(fils_info->akm), fils_info->ick, fils_info->ick_len, 1, &addr[0], &len, fils_info->ap_key_auth_data) < 0) pe_err("failed to generate PMK id"); fils_info->ap_key_auth_len = lim_get_crypto_digest_len( lim_get_crypto_type(fils_info->akm)); lim_fils_data_dump("AP Key Auth", fils_info->ap_key_auth_data, fils_info->ap_key_auth_len); } /** * lim_generate_key_auth()- This API generates sta auth data which needs to be * send to AP in assoc request, AP will generate the same the verify it. * @pe_session: pe session pointer * * Return: None */ static void lim_generate_key_auth(tpPESession pe_session) { uint8_t *buf, *addr[1]; uint32_t len; struct pe_fils_session *fils_info = pe_session->fils_info; uint8_t data[SIR_FILS_NONCE_LENGTH + SIR_FILS_NONCE_LENGTH + QDF_MAC_ADDR_SIZE + QDF_MAC_ADDR_SIZE] = {0}; if (!fils_info) return; len = SIR_FILS_NONCE_LENGTH + SIR_FILS_NONCE_LENGTH + QDF_MAC_ADDR_SIZE + QDF_MAC_ADDR_SIZE; addr[0] = data; buf = data; qdf_mem_copy(buf, fils_info->fils_nonce, SIR_FILS_NONCE_LENGTH); buf += SIR_FILS_NONCE_LENGTH; qdf_mem_copy(buf, fils_info->auth_info.fils_nonce, SIR_FILS_NONCE_LENGTH); buf += SIR_FILS_NONCE_LENGTH; qdf_mem_copy(buf, pe_session->selfMacAddr, QDF_MAC_ADDR_SIZE); buf += QDF_MAC_ADDR_SIZE; qdf_mem_copy(buf, pe_session->bssId, QDF_MAC_ADDR_SIZE); buf += QDF_MAC_ADDR_SIZE; if (qdf_get_hmac_hash(lim_get_crypto_type(fils_info->akm), fils_info->ick, fils_info->ick_len, 1, &addr[0], &len, fils_info->key_auth) < 0) pe_err("failed to generate key auth"); fils_info->key_auth_len = lim_get_crypto_digest_len( lim_get_crypto_type(fils_info->akm)); lim_fils_data_dump("STA Key Auth", fils_info->key_auth, fils_info->key_auth_len); } /** * lim_get_keys()- This API generates keys keydata which is generated after * parsing of auth response. * KCK = L(FILS-Key-Data, 0, KCK_bits) * KEK = L(FILS-Key-Data, KCK_bits, KEK_bits) * TK = L(FILS-Key-Data, KCK_bits + KEK_bits, TK_bits) * FILS-FT = L(FILS-Key-Data, KCK_bits + KEK_bits + TK_bits, FILS-FT_bits) * @pe_session: pe session pointer * * Return: None */ static void lim_get_keys(tpPESession pe_session) { uint8_t key_label[] = PTK_KEY_LABEL; uint8_t *data; uint8_t data_len; struct pe_fils_session *fils_info = pe_session->fils_info; uint8_t key_data[MAX_ICK_LEN + MAX_KEK_LEN + MAX_TK_LEN] = {0}; uint8_t key_data_len; uint8_t ick_len; uint8_t kek_len; uint8_t tk_len = lim_get_tk_len(pe_session->encryptType); uint8_t *buf; if (!fils_info) return; ick_len = lim_get_ick_len(fils_info->akm); kek_len = lim_get_kek_len(fils_info->akm); key_data_len = ick_len + kek_len + tk_len; data_len = 2 * SIR_FILS_NONCE_LENGTH + 2 * QDF_MAC_ADDR_SIZE; data = qdf_mem_malloc(data_len); if (!data) { pe_err("failed to alloc memory"); return; } /* Update data */ buf = data; qdf_mem_copy(buf, pe_session->selfMacAddr, QDF_MAC_ADDR_SIZE); buf += QDF_MAC_ADDR_SIZE; qdf_mem_copy(buf, pe_session->bssId, QDF_MAC_ADDR_SIZE); buf += QDF_MAC_ADDR_SIZE; qdf_mem_copy(buf, fils_info->fils_nonce, SIR_FILS_NONCE_LENGTH); buf += SIR_FILS_NONCE_LENGTH; qdf_mem_copy(buf, fils_info->auth_info.fils_nonce, SIR_FILS_NONCE_LENGTH); lim_generate_key_data(fils_info, key_label, data, data_len, key_data, key_data_len); buf = key_data; qdf_mem_copy(fils_info->ick, buf, ick_len); fils_info->ick_len = ick_len; buf += ick_len; qdf_mem_copy(fils_info->kek, buf, kek_len); fils_info->kek_len = kek_len; buf += kek_len; qdf_mem_copy(fils_info->tk, buf, tk_len); fils_info->tk_len = tk_len; qdf_mem_free(data); } /** * lim_generate_pmkid()- This API generates PMKID using hash of erp auth packet * parsing of auth response. * PMKID = Truncate-128(Hash(EAP-Initiate/Reauth)) * @pe_session: pe session pointer * * Return: None */ static void lim_generate_pmkid(tpPESession pe_session) { uint8_t hash[SHA384_DIGEST_SIZE]; struct pe_fils_session *fils_info = pe_session->fils_info; if (!fils_info) return; qdf_get_hash(lim_get_crypto_type(fils_info->akm), 1, &fils_info->fils_erp_reauth_pkt, &fils_info->fils_erp_reauth_pkt_len, hash); qdf_mem_copy(fils_info->fils_pmkid, hash, PMKID_LEN); lim_fils_data_dump("PMKID", fils_info->fils_pmkid, PMKID_LEN); } /** * lim_generate_pmk()- This API generates PMK using hmac hash of rmsk data * anonce, snonce will be used as key for this * PMK = HMAC-Hash(SNonce || ANonce, rMSK [ || DHss ]) * @pe_session: pe session pointer * * Return: None */ static void lim_generate_pmk(tpPESession pe_session) { uint8_t nounce[2 * SIR_FILS_NONCE_LENGTH] = {0}; uint8_t nounce_len = 2 * SIR_FILS_NONCE_LENGTH; uint8_t *addr[1]; uint32_t len[1]; struct pe_fils_session *fils_info = pe_session->fils_info; /* Snounce */ qdf_mem_copy(nounce, fils_info->fils_nonce, SIR_FILS_NONCE_LENGTH); /* anounce */ qdf_mem_copy(nounce + SIR_FILS_NONCE_LENGTH, fils_info->auth_info.fils_nonce, SIR_FILS_NONCE_LENGTH); fils_info->fils_pmk_len = lim_get_pmk_length(fils_info->akm); if (fils_info->fils_pmk) qdf_mem_free(fils_info->fils_pmk); fils_info->fils_pmk = qdf_mem_malloc(fils_info->fils_pmk_len); if (!fils_info->fils_pmk) { pe_err("failed to alloc memory"); return; } addr[0] = fils_info->fils_rmsk; len[0] = fils_info->fils_rmsk_len; lim_fils_data_dump("Nonce", nounce, nounce_len); if (qdf_get_hmac_hash(lim_get_crypto_type(fils_info->akm), nounce, nounce_len, 1, &addr[0], &len[0], fils_info->fils_pmk) < 0) pe_err("failed to generate PMK"); } /** * lim_generate_rmsk_data()- This API generates RMSK data using * default kdf as defined in RFC4306. * @pe_session: pe session pointer * * Return: None */ static void lim_generate_rmsk_data(tpPESession pe_session) { uint8_t optional_data[4] = {0}; uint8_t rmsk_label[] = RMSK_LABEL; struct pe_fils_session *fils_info = pe_session->fils_info; struct fils_auth_rsp_info *auth_info; if (!fils_info) return; auth_info = &(pe_session->fils_info->auth_info); fils_info->fils_rmsk_len = fils_info->fils_rrk_len; fils_info->fils_rmsk = qdf_mem_malloc(fils_info->fils_rrk_len); if (!fils_info->fils_rmsk) { pe_err("failed to alloc memory"); return; } /* * Sequence number sent in EAP-INIT packet, * it should be in network byte order */ lim_copy_u16_be(&optional_data[0], fils_info->sequence_number); lim_copy_u16_be(&optional_data[2], fils_info->fils_rrk_len); lim_default_hmac_sha256_kdf(fils_info->fils_rrk, fils_info->fils_rrk_len, rmsk_label, optional_data, sizeof(optional_data), fils_info->fils_rmsk, fils_info->fils_rmsk_len); } /** * lim_process_auth_wrapped_data()- This API process wrapped data element * of auth response. * @pe_session: pe session pointer * @wrapped_data: wrapped data pointer * @data_len: wrapped data len * * Return: None */ static QDF_STATUS lim_process_auth_wrapped_data(tpPESession pe_session, uint8_t *wrapped_data, uint32_t data_len) { uint8_t code; uint8_t identifier; uint16_t length; uint8_t type; unsigned long flags; struct pe_fils_session *fils_info; uint8_t hash[32] = {0}, crypto; uint32_t remaining_len = data_len, new_len; uint8_t *input_data[1]; uint32_t input_len[1]; uint8_t auth_tag_len; fils_info = pe_session->fils_info; if (!fils_info) return QDF_STATUS_E_FAILURE; input_data[0] = wrapped_data; input_len[0] = data_len; pe_debug("trying to process the wrappped data"); code = *wrapped_data; wrapped_data++; remaining_len--; identifier = *wrapped_data; wrapped_data++; remaining_len--; length = lim_get_u16(wrapped_data); wrapped_data += sizeof(uint16_t); remaining_len -= sizeof(uint16_t); type = *wrapped_data; /* val should be 2 here */ wrapped_data++; remaining_len--; flags = *wrapped_data; if (test_bit(7, &flags)) { pe_err("R bit is set in flag, error"); return QDF_STATUS_E_FAILURE; } wrapped_data++; remaining_len--; fils_info->auth_info.sequence = lim_get_u16_be(wrapped_data); wrapped_data += sizeof(uint16_t); remaining_len -= sizeof(uint16_t); /* Validate Auth sequence number */ if (fils_info->auth_info.sequence < fils_info->sequence_number) { pe_err("sequence EAP-finish:%d is less than EAP-init:%d", fils_info->auth_info.sequence, fils_info->sequence_number); return QDF_STATUS_E_FAILURE; } /* Parse attached TLVs */ new_len = lim_process_fils_eap_tlv(pe_session, wrapped_data, remaining_len); wrapped_data += remaining_len - new_len; remaining_len = new_len; /* Remove cryptosuite */ crypto = *wrapped_data; wrapped_data++; remaining_len--; auth_tag_len = lim_get_auth_tag_len(crypto); input_len[0] -= auth_tag_len; /* if we have auth tag remaining */ if (remaining_len == auth_tag_len) { qdf_get_hmac_hash(FILS_SHA256_CRYPTO_TYPE, fils_info->fils_rik, fils_info->fils_rik_len, SINGLE_ELEMENT_HASH_CNT, input_data, input_len, hash); } else { pe_err("invalid remaining len %d", remaining_len); } if (qdf_mem_cmp(wrapped_data, hash, auth_tag_len)) { pe_err("integratity check failed for auth, crypto %d", crypto); return QDF_STATUS_E_FAILURE; } lim_generate_rmsk_data(pe_session); lim_generate_pmk(pe_session); lim_generate_pmkid(pe_session); return QDF_STATUS_SUCCESS; } /** * lim_is_valid_fils_auth_frame()- This API check whether auth frame is a * valid frame. * @mac_ctx: mac context * @pe_session: pe session pointer * @rx_auth_frm_body: pointer to autherntication frame * * Return: true if frame is valid or fils is disable, false otherwise */ bool lim_is_valid_fils_auth_frame(tpAniSirGlobal mac_ctx, tpPESession pe_session, tSirMacAuthFrameBody *rx_auth_frm_body) { if (!pe_session->fils_info) return true; if (pe_session->fils_info->is_fils_connection == false) return true; if (qdf_mem_cmp(rx_auth_frm_body->session, pe_session->fils_info->fils_session, SIR_FILS_SESSION_LENGTH)) { lim_fils_data_dump("Current FILS session", pe_session->fils_info->fils_session, SIR_FILS_SESSION_LENGTH); lim_fils_data_dump("FILS Session in pkt", rx_auth_frm_body->session, SIR_FILS_SESSION_LENGTH); return false; } qdf_mem_copy(pe_session->fils_info->auth_info.fils_nonce, rx_auth_frm_body->nonce, SIR_FILS_NONCE_LENGTH); pe_session->fils_info->auth_info.assoc_delay = rx_auth_frm_body->assoc_delay_info; return true; } QDF_STATUS lim_create_fils_rik(uint8_t *rrk, uint8_t rrk_len, uint8_t *rik, uint32_t *rik_len) { uint8_t optional_data[SIR_FILS_OPTIONAL_DATA_LEN]; uint8_t label[] = SIR_FILS_RIK_LABEL; if (!rrk || !rik) return QDF_STATUS_E_FAILURE; optional_data[0] = HMAC_SHA256_128; /* basic validation */ if (rrk_len <= 0) { pe_err("invalid r_rk length %d", rrk_len); return QDF_STATUS_E_FAILURE; } lim_copy_u16_be(&optional_data[1], rrk_len); if (lim_default_hmac_sha256_kdf(rrk, rrk_len, label, optional_data, sizeof(optional_data), rik, rrk_len) != QDF_STATUS_SUCCESS) { pe_err("failed to create rik"); return QDF_STATUS_E_FAILURE; } *rik_len = rrk_len; return QDF_STATUS_SUCCESS; } /** * lim_create_fils_wrapper_data()- This API create warpped data which will be * sent in auth request. * @fils_info: fils session info * * Return: length of the created wrapped data */ static int lim_create_fils_wrapper_data(struct pe_fils_session *fils_info) { uint8_t *buf; uint8_t auth_tag[FILS_AUTH_TAG_MAX_LENGTH] = {0}; uint32_t length = 0; QDF_STATUS status; int buf_len; if (!fils_info) return 0; buf_len = /* code + identifier */ sizeof(uint8_t) * 2 + /* length */ sizeof(uint16_t) + /* type + flags */ sizeof(uint8_t) * 2 + /* sequence */ sizeof(uint16_t) + /* tlv : type, length, data */ sizeof(uint8_t) * 2 + fils_info->keyname_nai_length + /* cryptosuite + auth_tag */ sizeof(uint8_t) + lim_get_auth_tag_len(HMAC_SHA256_128); fils_info->fils_erp_reauth_pkt = qdf_mem_malloc(buf_len); if (!fils_info->fils_erp_reauth_pkt) { pe_err("failed to allocate memory"); return -EINVAL; } buf = fils_info->fils_erp_reauth_pkt; *buf = 5; buf++; /* Identifier */ *buf = 0; buf++; /* Length */ lim_copy_u16_be(buf, buf_len); buf += sizeof(uint16_t); /* type */ *buf = SIR_FILS_EAP_INIT_PACKET_TYPE; buf++; /** * flag * 0 1 2 <-- 5 --> * ---------------- * |R|B|L| Reserved| * ----------------- */ *buf = 0x20; /* l=1, b=0, r=0 */ buf++; /* sequence */ lim_copy_u16_be(buf, fils_info->sequence_number); buf += sizeof(uint16_t); /* tlv */ /* Type */ *buf = SIR_FILS_EAP_TLV_KEYNAME_NAI; buf++; /* NAI Length */ *buf = fils_info->keyname_nai_length; buf++; /* NAI Data */ qdf_mem_copy(buf, fils_info->keyname_nai_data, fils_info->keyname_nai_length); buf += fils_info->keyname_nai_length; /* cryptosuite */ *buf = HMAC_SHA256_128; buf++; /* * This should be moved to just after sending probe to save time * lim_process_switch_channel_join_req ?? */ fils_info->fils_rik = qdf_mem_malloc(fils_info->fils_rrk_len); if (!fils_info->fils_rik) { qdf_mem_free(fils_info->fils_erp_reauth_pkt); fils_info->fils_erp_reauth_pkt = NULL; pe_err("failed to alloc memory"); return -EINVAL; } status = lim_create_fils_rik(fils_info->fils_rrk, fils_info->fils_rrk_len, fils_info->fils_rik, &fils_info->fils_rik_len); if (QDF_IS_STATUS_ERROR(status)) { pe_err("RIK create fails"); qdf_mem_free(fils_info->fils_erp_reauth_pkt); qdf_mem_free(fils_info->fils_rik); fils_info->fils_erp_reauth_pkt = NULL; fils_info->fils_rik = NULL; return -EINVAL; } fils_info->fils_erp_reauth_pkt_len = buf_len; length = fils_info->fils_erp_reauth_pkt_len - lim_get_auth_tag_len(HMAC_SHA256_128); qdf_get_hmac_hash(FILS_SHA256_CRYPTO_TYPE, fils_info->fils_rik, fils_info->fils_rik_len, 1, &fils_info->fils_erp_reauth_pkt, &length, auth_tag); lim_fils_data_dump("Auth tag", auth_tag, lim_get_auth_tag_len(HMAC_SHA256_128)); lim_fils_data_dump("EAP init pkt", fils_info->fils_erp_reauth_pkt, fils_info->fils_erp_reauth_pkt_len); qdf_mem_copy(buf, auth_tag, lim_get_auth_tag_len(HMAC_SHA256_128)); buf += lim_get_auth_tag_len(HMAC_SHA256_128); return buf_len; } /** * lim_add_fils_data_to_auth_frame()- This API add fils data to auth frame. * Following will be added in this. * RSNIE * SNonce * Session * Wrapped data * @session: PE session * @body: pointer to auth frame where data needs to be added * * Return: None */ void lim_add_fils_data_to_auth_frame(tpPESession session, uint8_t *body) { struct pe_fils_session *fils_info; fils_info = session->fils_info; if (!fils_info) return; /* RSN IE */ qdf_mem_copy(body, fils_info->rsn_ie, fils_info->rsn_ie_len); body += fils_info->rsn_ie_len; lim_fils_data_dump("FILS RSN", fils_info->rsn_ie, fils_info->rsn_ie_len); /* ***Nounce*** */ /* Add element id */ *body = SIR_MAX_ELEMENT_ID; body++; /* Add nounce length + 1 for ext element id */ *body = SIR_FILS_NONCE_LENGTH + 1; body++; /* Add ext element */ *body = SIR_FILS_NONCE_EXT_EID; body++; /* Add data */ cds_rand_get_bytes(0, fils_info->fils_nonce, SIR_FILS_NONCE_LENGTH); qdf_mem_copy(body, fils_info->fils_nonce, SIR_FILS_NONCE_LENGTH); body = body + SIR_FILS_NONCE_LENGTH; /* Dump data */ lim_fils_data_dump("fils anonce", fils_info->fils_nonce, SIR_FILS_NONCE_LENGTH); /* *** Session *** */ /* Add element id */ *body = SIR_MAX_ELEMENT_ID; body++; /* Add nounce length + 1 for ext element id */ *body = SIR_FILS_SESSION_LENGTH + 1; body++; /* Add ext element */ *body = SIR_FILS_SESSION_EXT_EID; body++; /* Add data */ cds_rand_get_bytes(0, fils_info->fils_session, SIR_FILS_SESSION_LENGTH); qdf_mem_copy(body, fils_info->fils_session, SIR_FILS_SESSION_LENGTH); body = body + SIR_FILS_SESSION_LENGTH; /* dump data */ lim_fils_data_dump("Fils Session", fils_info->fils_session, SIR_FILS_SESSION_LENGTH); /* ERP Packet */ /* Add element id */ *body = SIR_MAX_ELEMENT_ID; body++; /* Add packet length + 1 for ext element id */ *body = fils_info->fils_erp_reauth_pkt_len + 1; body++; /* Add ext element */ *body = SIR_FILS_WRAPPED_DATA_EXT_EID; body++; /* Copy data */ qdf_mem_copy(body, fils_info->fils_erp_reauth_pkt, fils_info->fils_erp_reauth_pkt_len); lim_fils_data_dump("Fils ERP reauth Pkt", fils_info->fils_erp_reauth_pkt, fils_info->fils_erp_reauth_pkt_len); body = body + fils_info->fils_erp_reauth_pkt_len; } /** * lim_process_fils_auth_frame2()- This API process fils data from auth response * @mac_ctx: mac context * @session: PE session * @rx_auth_frm_body: pointer to auth frame * * Return: true if fils data needs to be processed else false */ bool lim_process_fils_auth_frame2(tpAniSirGlobal mac_ctx, tpPESession pe_session, tSirMacAuthFrameBody *rx_auth_frm_body) { int i; uint32_t ret; bool pmkid_found = false; tDot11fIERSN dot11f_ie_rsn = {0}; if (rx_auth_frm_body->authAlgoNumber != eSIR_FILS_SK_WITHOUT_PFS) return false; if (!pe_session->fils_info) return false; ret = dot11f_unpack_ie_rsn(mac_ctx, &rx_auth_frm_body->rsn_ie.info[0], rx_auth_frm_body->rsn_ie.length, &dot11f_ie_rsn, 0); if (!DOT11F_SUCCEEDED(ret)) { pe_err("unpack failed, ret: %d", ret); return false; } for (i = 0; i < dot11f_ie_rsn.pmkid_count; i++) { if (qdf_mem_cmp(dot11f_ie_rsn.pmkid[i], pe_session->fils_info->fils_pmkid, PMKID_LEN) == 0) { pmkid_found = true; pe_debug("pmkid match in rsn ie total_count %d", dot11f_ie_rsn.pmkid_count); break; } } if (!pmkid_found) { if (QDF_STATUS_SUCCESS != lim_process_auth_wrapped_data(pe_session, rx_auth_frm_body->wrapped_data, rx_auth_frm_body->wrapped_data_len)) return false; } lim_get_keys(pe_session); lim_generate_key_auth(pe_session); lim_generate_ap_key_auth(pe_session); return true; } /** * lim_update_fils_config()- This API update fils session info to csr config * from join request. * @session: PE session * @sme_join_req: pointer to join request * * Return: None */ void lim_update_fils_config(tpPESession session, tpSirSmeJoinReq sme_join_req) { struct pe_fils_session *csr_fils_info; struct cds_fils_connection_info *fils_config_info; fils_config_info = &sme_join_req->fils_con_info; csr_fils_info = session->fils_info; if (!csr_fils_info) return; if (fils_config_info->is_fils_connection == false) return; csr_fils_info->is_fils_connection = fils_config_info->is_fils_connection; csr_fils_info->keyname_nai_length = fils_config_info->key_nai_length; csr_fils_info->fils_rrk_len = fils_config_info->r_rk_length; csr_fils_info->akm = fils_config_info->akm_type; csr_fils_info->auth = fils_config_info->auth_type; csr_fils_info->sequence_number = fils_config_info->sequence_number; if (fils_config_info->key_nai_length > FILS_MAX_KEYNAME_NAI_LENGTH) { pe_err("Restricting the key_nai_length of %d to max %d", fils_config_info->key_nai_length, FILS_MAX_KEYNAME_NAI_LENGTH); fils_config_info->key_nai_length = FILS_MAX_KEYNAME_NAI_LENGTH; } csr_fils_info->keyname_nai_data = qdf_mem_malloc(fils_config_info->key_nai_length); if (!csr_fils_info->keyname_nai_data) { pe_err("failed to alloc memory"); return; } qdf_mem_copy(csr_fils_info->keyname_nai_data, fils_config_info->keyname_nai, fils_config_info->key_nai_length); csr_fils_info->fils_rrk = qdf_mem_malloc(fils_config_info->r_rk_length); if (!csr_fils_info->fils_rrk) { pe_err("failed to alloc memory"); qdf_mem_free(csr_fils_info->keyname_nai_data); return; } if (fils_config_info->r_rk_length <= FILS_MAX_RRK_LENGTH) qdf_mem_copy(csr_fils_info->fils_rrk, fils_config_info->r_rk, fils_config_info->r_rk_length); qdf_mem_copy(csr_fils_info->fils_pmkid, fils_config_info->pmkid, PMKID_LEN); csr_fils_info->rsn_ie_len = sme_join_req->rsnIE.length; qdf_mem_copy(csr_fils_info->rsn_ie, sme_join_req->rsnIE.rsnIEdata, sme_join_req->rsnIE.length); csr_fils_info->fils_pmk_len = fils_config_info->pmk_len; if (fils_config_info->pmk_len) { csr_fils_info->fils_pmk = qdf_mem_malloc(fils_config_info->pmk_len); if (!csr_fils_info->fils_pmk) { qdf_mem_free(csr_fils_info->keyname_nai_data); qdf_mem_free(csr_fils_info->fils_rrk); pe_err("failed to alloc memory"); return; } qdf_mem_copy(csr_fils_info->fils_pmk, fils_config_info->pmk, fils_config_info->pmk_len); } pe_debug("fils=%d nai-len=%d rrk_len=%d akm=%d auth=%d pmk_len=%d", fils_config_info->is_fils_connection, fils_config_info->key_nai_length, fils_config_info->r_rk_length, fils_config_info->akm_type, fils_config_info->auth_type, fils_config_info->pmk_len); } #define EXTENDED_IE_HEADER_LEN 3 /** * lim_create_fils_auth_data()- This API creates the fils auth data * which needs to be sent in auth req. * @mac_ctx: mac context * @auth_frame: pointer to auth frame * @session: PE session * * Return: length of fils data */ uint32_t lim_create_fils_auth_data(tpAniSirGlobal mac_ctx, tpSirMacAuthFrameBody auth_frame, tpPESession session) { uint32_t frame_len = 0; uint32_t wrapped_data_len; if (!session->fils_info) return 0; /* These memory may already been allocated if auth retry */ if (session->fils_info->fils_rik) { qdf_mem_free(session->fils_info->fils_rik); session->fils_info->fils_rik = NULL; } if (session->fils_info->fils_erp_reauth_pkt) { qdf_mem_free(session->fils_info->fils_erp_reauth_pkt); session->fils_info->fils_erp_reauth_pkt = NULL; } if (auth_frame->authAlgoNumber == eSIR_FILS_SK_WITHOUT_PFS) { frame_len += session->fils_info->rsn_ie_len; /* FILS nounce */ frame_len += SIR_FILS_NONCE_LENGTH + EXTENDED_IE_HEADER_LEN; /* FILS Session */ frame_len += SIR_FILS_SESSION_LENGTH + EXTENDED_IE_HEADER_LEN; /* Calculate data/length for FILS Wrapped Data */ wrapped_data_len = lim_create_fils_wrapper_data(session->fils_info); if (wrapped_data_len < 0) { pe_err("failed to create warpped data"); return 0; } frame_len += wrapped_data_len + EXTENDED_IE_HEADER_LEN; } return frame_len; } void populate_fils_connect_params(tpAniSirGlobal mac_ctx, tpPESession session, tpSirSmeJoinRsp sme_join_rsp) { struct fils_join_rsp_params *fils_join_rsp; struct pe_fils_session *fils_info = (session->fils_info); if (!fils_info) return; if (!lim_is_fils_connection(session)) return; if (!fils_info->fils_pmk_len || !fils_info->tk_len || !fils_info->gtk_len || !fils_info->fils_pmk || !fils_info->kek_len) { pe_err("Invalid FILS info pmk len %d kek len %d tk len %d gtk len %d", fils_info->fils_pmk_len, fils_info->kek_len, fils_info->tk_len, fils_info->gtk_len); return; } sme_join_rsp->fils_join_rsp = qdf_mem_malloc(sizeof(*fils_join_rsp)); if (!sme_join_rsp->fils_join_rsp) { pe_err("fils_join_rsp malloc fails!"); pe_delete_fils_info(session); return; } fils_join_rsp = sme_join_rsp->fils_join_rsp; fils_join_rsp->fils_pmk = qdf_mem_malloc(fils_info->fils_pmk_len); if (!fils_join_rsp->fils_pmk) { pe_err("fils_pmk malloc fails!"); qdf_mem_free(fils_join_rsp); pe_delete_fils_info(session); return; } fils_join_rsp->fils_pmk_len = fils_info->fils_pmk_len; qdf_mem_copy(fils_join_rsp->fils_pmk, fils_info->fils_pmk, fils_info->fils_pmk_len); qdf_mem_copy(fils_join_rsp->fils_pmkid, fils_info->fils_pmkid, IEEE80211_PMKID_LEN); fils_join_rsp->kek_len = fils_info->kek_len; qdf_mem_copy(fils_join_rsp->kek, fils_info->kek, fils_info->kek_len); fils_join_rsp->tk_len = fils_info->tk_len; qdf_mem_copy(fils_join_rsp->tk, fils_info->tk, fils_info->tk_len); fils_join_rsp->gtk_len = fils_info->gtk_len; qdf_mem_copy(fils_join_rsp->gtk, fils_info->gtk, fils_info->gtk_len); cds_copy_hlp_info(&fils_info->dst_mac, &fils_info->src_mac, fils_info->hlp_data_len, fils_info->hlp_data, &fils_join_rsp->dst_mac, &fils_join_rsp->src_mac, &fils_join_rsp->hlp_data_len, fils_join_rsp->hlp_data); pe_debug("FILS connect params copied lim"); } /** * lim_parse_kde_elements() - Parse Key Delivery Elements * @mac_ctx: mac context * @fils_info: FILS info * @kde_list: KDE list buffer * @kde_list_len: Length of @kde_list * * This API is used to parse the Key Delivery Elements from buffer * and populate them in PE FILS session struct i.e @fils_info * * Key Delivery Element[KDE] format * +----------+--------+-----------+------------+----------+ * | ID(0xDD) | length | KDE OUI | data type | IE data | * |----------|--------|-----------|------------|----------| * | 1 byte | 1 byte | 3 bytes | 1 byte | variable | * +----------+--------+-----------+------------+----------+ * * there can be multiple KDE present inside KDE list. * the IE data could be GTK, IGTK etc based on the data type * * Return: QDF_STATUS_SUCCESS if we parse GTK successfully, * QDF_STATUS_E_FAILURE otherwise */ static QDF_STATUS lim_parse_kde_elements(tpAniSirGlobal mac_ctx, struct pe_fils_session *fils_info, uint8_t *kde_list, uint8_t kde_list_len) { uint8_t rem_len = kde_list_len; uint8_t *temp_ie = kde_list; uint8_t elem_id, data_type, data_len, *ie_data = NULL, *current_ie; uint16_t elem_len; if (!kde_list_len || !kde_list) { pe_err("kde_list NULL or kde_list_len %d", kde_list_len); return QDF_STATUS_E_FAILURE; } while (rem_len >= 2) { current_ie = temp_ie; elem_id = *temp_ie++; elem_len = *temp_ie++; rem_len -= 2; if (rem_len < elem_len || elem_len > kde_list_len) { pe_err("Invalid elem_len %d rem_len %d list_len %d", elem_len, rem_len, kde_list_len); return QDF_STATUS_E_FAILURE; } if (elem_len < KDE_IE_DATA_OFFSET) { pe_err("Not enough len to parse elem_len %d", elem_len); return QDF_STATUS_E_FAILURE; } if (lim_check_if_vendor_oui_match(mac_ctx, KDE_OUI_TYPE, KDE_OUI_TYPE_SIZE, current_ie, elem_len)) { data_type = *(temp_ie + KDE_DATA_TYPE_OFFSET); ie_data = (temp_ie + KDE_IE_DATA_OFFSET); data_len = (elem_len - KDE_IE_DATA_OFFSET); switch (data_type) { case DATA_TYPE_GTK: if (data_len < GTK_OFFSET) { pe_err("Invalid KDE data_len %d", data_len); return QDF_STATUS_E_FAILURE; } qdf_mem_copy(fils_info->gtk, (ie_data + GTK_OFFSET), (data_len - GTK_OFFSET)); fils_info->gtk_len = (data_len - GTK_OFFSET); break; case DATA_TYPE_IGTK: if (data_len < IGTK_OFFSET) { pe_err("Invalid KDE data_len %d", data_len); return QDF_STATUS_E_FAILURE; } fils_info->igtk_len = (data_len - IGTK_OFFSET); qdf_mem_copy(fils_info->igtk, (ie_data + IGTK_OFFSET), (data_len - IGTK_OFFSET)); qdf_mem_copy(fils_info->ipn, (ie_data + IPN_OFFSET), IPN_LEN); break; default: pe_err("Unknown KDE data type %x", data_type); break; } } temp_ie += elem_len; rem_len -= elem_len; ie_data = NULL; } /* Expecting GTK in KDE */ if (!fils_info->gtk_len) { pe_err("GTK not found in KDE"); return QDF_STATUS_E_FAILURE; } return QDF_STATUS_SUCCESS; } bool lim_verify_fils_params_assoc_rsp(tpAniSirGlobal mac_ctx, tpPESession session_entry, tpSirAssocRsp assoc_rsp, tLimMlmAssocCnf *assoc_cnf) { struct pe_fils_session *fils_info = (session_entry->fils_info); tDot11fIEfils_session fils_session = assoc_rsp->fils_session; tDot11fIEfils_key_confirmation fils_key_auth = assoc_rsp->fils_key_auth; tDot11fIEfils_kde fils_kde = assoc_rsp->fils_kde; QDF_STATUS status; if (!lim_is_fils_connection(session_entry)) return true; if (!fils_info) { pe_err("FILS Info not present"); goto verify_fils_params_fails; } if (!assoc_rsp->fils_session.present) { pe_err("FILS IE not present"); goto verify_fils_params_fails; } /* Compare FILS session */ if (qdf_mem_cmp(fils_info->fils_session, fils_session.session, DOT11F_IE_FILS_SESSION_MAX_LEN)) { pe_err("FILS session mismatch"); goto verify_fils_params_fails; } /* Compare FILS key auth */ if ((fils_key_auth.num_key_auth != fils_info->key_auth_len) || qdf_mem_cmp(fils_info->ap_key_auth_data, fils_key_auth.key_auth, fils_info->ap_key_auth_len)) { lim_fils_data_dump("session keyauth", fils_info->ap_key_auth_data, fils_info->ap_key_auth_len); lim_fils_data_dump("Pkt keyauth", fils_key_auth.key_auth, fils_key_auth.num_key_auth); goto verify_fils_params_fails; } /* Verify the Key Delivery Element presence */ if (!fils_kde.num_kde_list) { pe_err("FILS KDE list absent"); goto verify_fils_params_fails; } /* Derive KDE elements */ status = lim_parse_kde_elements(mac_ctx, fils_info, fils_kde.kde_list, fils_kde.num_kde_list); if (!QDF_IS_STATUS_SUCCESS(status)) { pe_err("KDE parsing fails"); goto verify_fils_params_fails; } if (assoc_rsp->hlp_data_len) { fils_info->hlp_data = qdf_mem_malloc(assoc_rsp->hlp_data_len); if (!fils_info->hlp_data) { pe_err("FILS session HLP data malloc fails"); return true; } /* Save the HLP container IE data if present*/ cds_copy_hlp_info(&assoc_rsp->dst_mac, &assoc_rsp->src_mac, assoc_rsp->hlp_data_len, assoc_rsp->hlp_data, &fils_info->dst_mac, &fils_info->src_mac, &fils_info->hlp_data_len, fils_info->hlp_data); } return true; verify_fils_params_fails: assoc_cnf->resultCode = eSIR_SME_ASSOC_REFUSED; assoc_cnf->protStatusCode = eSIR_MAC_UNSPEC_FAILURE_STATUS; return false; } /** * find_ie_data_after_fils_session_ie() - Find IE pointer after FILS Session IE * @mac_ctx: MAC context * @buf: IE buffer * @buf_len: Length of @buf * @ie: Pointer to update the found IE pointer after FILS session IE * @ie_len: length of the IE data after FILS session IE * * This API is used to find the IE data ptr and length after FILS session IE * * Return: QDF_STATUS_SUCCESS if found, else QDF_STATUS_E_FAILURE */ static QDF_STATUS find_ie_data_after_fils_session_ie(tpAniSirGlobal mac_ctx, uint8_t *buf, uint32_t buf_len, uint8_t **ie, uint32_t *ie_len) { uint32_t left = buf_len; uint8_t *ptr = buf; uint8_t elem_id, elem_len; if (NULL == buf || 0 == buf_len) return QDF_STATUS_E_FAILURE; while (left >= 2) { elem_id = ptr[0]; elem_len = ptr[1]; left -= 2; if (elem_len > left) return QDF_STATUS_E_FAILURE; if (elem_id == SIR_MAC_REQUEST_EID_MAX && ptr[2] == SIR_FILS_SESSION_EXT_EID) { (*ie) = ((&ptr[1]) + ptr[1] + 1); (*ie_len) = (left - elem_len); return QDF_STATUS_SUCCESS; } left -= elem_len; ptr += (elem_len + 2); } return QDF_STATUS_E_FAILURE; } /** * fils_aead_encrypt() - API to do FILS AEAD encryption * * @kek: Pointer to KEK * @kek_len: KEK length * @own_mac: Pointer to own MAC address * @bssid: Bssid * @snonce: Supplicant Nonce * @anonce: Authenticator Nonce * @data: Pointer to data after MAC header * @data_len: length of @data * @plain_text: Pointer to data after FILS Session IE * @plain_text_len: length of @plain_text * @out: Pointer to the encrypted data * * length of AEAD encryption @out is @plain_text_len + AES_BLOCK_SIZE[16 bytes] * * Return: zero on success, error otherwise */ static int fils_aead_encrypt(const u8 *kek, unsigned int kek_len, const u8 *own_mac, const u8 *bssid, const u8 *snonce, const u8 *anonce, const u8 *data, size_t data_len, u8 *plain_text, size_t plain_text_len, u8 *out) { u8 v[AES_BLOCK_SIZE]; const u8 *aad[6]; size_t aad_len[6]; u8 *buf; int ret; /* SIV Encrypt/Decrypt takes input key of length 256, 384 or 512 bits */ if (kek_len != 32 && kek_len != 48 && kek_len != 64) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, FL("Invalid key length: %u"), kek_len); return -EINVAL; } if (own_mac == NULL || bssid == NULL || snonce == NULL || anonce == NULL || data_len == 0 || plain_text_len == 0 || out == NULL) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, FL("Error missing params mac:%pK bssid:%pK snonce:%pK anonce:%pK data_len:%zu plain_text_len:%zu out:%pK"), own_mac, bssid, snonce, anonce, data_len, plain_text_len, out); return -EINVAL; } if (plain_text == out) { buf = qdf_mem_malloc(plain_text_len); if (buf == NULL) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, FL("Failed to allocate memory")); return -ENOMEM; } qdf_mem_copy(buf, plain_text, plain_text_len); } else { buf = plain_text; } aad[0] = own_mac; aad_len[0] = QDF_MAC_ADDR_SIZE; aad[1] = bssid; aad_len[1] = QDF_MAC_ADDR_SIZE; aad[2] = snonce; aad_len[2] = SIR_FILS_NONCE_LENGTH; aad[3] = anonce; aad_len[3] = SIR_FILS_NONCE_LENGTH; aad[4] = data; aad_len[4] = data_len; /* Plain text, P, is Sn in AES-SIV */ aad[5] = buf; aad_len[5] = plain_text_len; /* AES-SIV S2V */ /* K1 = leftmost(K, len(K)/2) */ ret = qdf_aes_s2v(kek, kek_len/2, aad, aad_len, 6, v); if (ret) goto error; /* out = SIV || C (Synthetic Initialization Vector || Ciphered text) */ qdf_mem_copy(out, v, AES_BLOCK_SIZE); /* AES-SIV CTR */ /* K2 = rightmost(K, len(K)/2) */ /* Clear 31st and 63rd bits in counter synthetic iv */ v[12] &= 0x7F; v[8] &= 0x7F; ret = qdf_aes_ctr(kek + kek_len/2, kek_len/2, v, buf, plain_text_len, out + AES_BLOCK_SIZE, true); error: if (plain_text == out) qdf_mem_free(buf); return ret; } QDF_STATUS aead_encrypt_assoc_req(tpAniSirGlobal mac_ctx, tpPESession pe_session, uint8_t *frm, uint32_t *frm_len) { uint8_t *plain_text = NULL, *data; uint32_t plain_text_len = 0, data_len; QDF_STATUS status; struct pe_fils_session *fils_info = (pe_session->fils_info); if (!fils_info) return QDF_STATUS_E_FAILURE; /* * data is the packet data after MAC header till * FILS session IE(inclusive) */ data = frm + sizeof(tSirMacMgmtHdr); /* * plain_text is the packet data after FILS session IE * which needs to be encrypted. Get plain_text ptr and * plain_text_len values using find_ptr_aft_fils_session_ie() */ status = find_ie_data_after_fils_session_ie(mac_ctx, data + FIXED_PARAM_OFFSET_ASSOC_REQ, (*frm_len - FIXED_PARAM_OFFSET_ASSOC_REQ), &plain_text, &plain_text_len); if (QDF_IS_STATUS_ERROR(status)) { pe_err("Could not find FILS session IE"); return QDF_STATUS_E_FAILURE; } data_len = ((*frm_len) - plain_text_len); lim_fils_data_dump("Plain text: ", plain_text, plain_text_len); /* Overwrite the AEAD encrypted output @ plain_text */ if (fils_aead_encrypt(fils_info->kek, fils_info->kek_len, pe_session->selfMacAddr, pe_session->bssId, fils_info->fils_nonce, fils_info->auth_info.fils_nonce, data, data_len, plain_text, plain_text_len, plain_text)) { pe_err("AEAD Encryption fails!"); return QDF_STATUS_E_FAILURE; } /* * AEAD encrypted output(cipher_text) will have length equals to * plain_text_len + AES_BLOCK_SIZE(AEAD encryption header info). * Add this to frm_len */ (*frm_len) += (AES_BLOCK_SIZE); return QDF_STATUS_SUCCESS; } /** * fils_aead_decrypt() - API to do AEAD decryption * * @kek: Pointer to KEK * @kek_len: KEK length * @own_mac: Pointer to own MAC address * @bssid: Bssid * @snonce: Supplicant Nonce * @anonce: Authenticator Nonce * @data: Pointer to data after MAC header * @data_len: length of @data * @plain_text: Pointer to data after FILS Session IE * @plain_text_len: length of @plain_text * @out: Pointer to the encrypted data * * Return: zero on success, error otherwise */ static int fils_aead_decrypt(const u8 *kek, unsigned int kek_len, const u8 *own_mac, const u8 *bssid, const u8 *snonce, const u8 *anonce, const u8 *data, size_t data_len, u8 *ciphered_text, size_t ciphered_text_len, u8 *plain_text) { const u8 *aad[6]; size_t aad_len[6]; u8 *buf; size_t buf_len; u8 v[AES_BLOCK_SIZE]; u8 siv[AES_BLOCK_SIZE]; int ret; /* SIV Encrypt/Decrypt takes input key of length 256, 384 or 512 bits */ if (kek_len != 32 && kek_len != 48 && kek_len != 64) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, FL("Invalid key length: %u"), kek_len); return -EINVAL; } if (own_mac == NULL || bssid == NULL || snonce == NULL || anonce == NULL || data_len == 0 || ciphered_text_len == 0 || plain_text == NULL) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, FL("Error missing params mac:%pK bssid:%pK snonce:%pK anonce:%pK data_len:%zu ciphered_text_len:%zu plain_text:%pK"), own_mac, bssid, snonce, anonce, data_len, ciphered_text_len, plain_text); return -EINVAL; } qdf_mem_copy(v, ciphered_text, AES_BLOCK_SIZE); qdf_mem_copy(siv, ciphered_text, AES_BLOCK_SIZE); v[12] &= 0x7F; v[8] &= 0x7F; buf_len = ciphered_text_len - AES_BLOCK_SIZE; if (ciphered_text == plain_text) { /* in place decryption */ buf = qdf_mem_malloc(buf_len); if (buf == NULL) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, FL("Failed to allocate memory")); return -ENOMEM; } qdf_mem_copy(buf, ciphered_text + AES_BLOCK_SIZE, buf_len); } else { buf = ciphered_text + AES_BLOCK_SIZE; } /* AES-SIV CTR */ /* K2 = rightmost(K, len(K)/2) */ ret = qdf_aes_ctr(kek + kek_len/2, kek_len/2, v, buf, buf_len, plain_text, false); if (ret) goto error; aad[0] = bssid; aad_len[0] = QDF_MAC_ADDR_SIZE; aad[1] = own_mac; aad_len[1] = QDF_MAC_ADDR_SIZE; aad[2] = anonce; aad_len[2] = SIR_FILS_NONCE_LENGTH; aad[3] = snonce; aad_len[3] = SIR_FILS_NONCE_LENGTH; aad[4] = data; aad_len[4] = data_len; aad[5] = plain_text; aad_len[5] = buf_len; /* AES-SIV S2V */ /* K1 = leftmost(K, len(K)/2) */ ret = qdf_aes_s2v(kek, kek_len/2, aad, aad_len, 6, v); if (ret) goto error; /* compare the iv generated against the one sent by AP */ if (memcmp(v, siv, AES_BLOCK_SIZE) != 0) { QDF_TRACE(QDF_MODULE_ID_QDF, QDF_TRACE_LEVEL_ERROR, FL("siv not same as frame siv")); ret = -EINVAL; } error: if (ciphered_text == plain_text) qdf_mem_free(buf); return ret; } QDF_STATUS aead_decrypt_assoc_rsp(tpAniSirGlobal mac_ctx, tpPESession session, tDot11fAssocResponse *ar, uint8_t *p_frame, uint32_t *n_frame) { QDF_STATUS status; uint32_t data_len, fils_ies_len; uint8_t *fils_ies; struct pe_fils_session *fils_info = (session->fils_info); status = find_ie_data_after_fils_session_ie(mac_ctx, p_frame + FIXED_PARAM_OFFSET_ASSOC_RSP, ((*n_frame) - FIXED_PARAM_OFFSET_ASSOC_RSP), &fils_ies, &fils_ies_len); if (!QDF_IS_STATUS_SUCCESS(status)) { pe_err("FILS session IE not present"); return status; } data_len = (*n_frame) - fils_ies_len; if (fils_aead_decrypt(fils_info->kek, fils_info->kek_len, session->selfMacAddr, session->bssId, fils_info->fils_nonce, fils_info->auth_info.fils_nonce, p_frame, data_len, fils_ies, fils_ies_len, fils_ies)){ pe_err("AEAD decryption fails"); return QDF_STATUS_E_FAILURE; } /* Dump the output of AEAD decrypt */ lim_fils_data_dump("Plain text: ", fils_ies, fils_ies_len - AES_BLOCK_SIZE); (*n_frame) -= AES_BLOCK_SIZE; return status; } void lim_update_fils_rik(tpPESession pe_session, tSirRoamOffloadScanReq *req_buffer) { struct pe_fils_session *pe_fils_info = pe_session->fils_info; struct roam_fils_params *roam_fils_params = &req_buffer->roam_fils_params; if (!roam_fils_params) return; /* * If it is first connection, LIM session entries will not be * set with FILS. However in RSO, CSR filled the RRK, realm * info and is_fils_connection to true in req_buffer, RIK * can be created with RRK and send all the FILS info to fw */ if ((!lim_is_fils_connection(pe_session) || !pe_fils_info) && (req_buffer->is_fils_connection)) { if (roam_fils_params->rrk_length > FILS_MAX_RRK_LENGTH) { pe_err("FILS rrk len(%d) max (%d)", roam_fils_params->rrk_length, FILS_MAX_RRK_LENGTH); return; } lim_create_fils_rik(roam_fils_params->rrk, roam_fils_params->rrk_length, roam_fils_params->rik, &roam_fils_params->rik_length); pe_debug("Fils created rik len %d", roam_fils_params->rik_length); return; } if ((pe_fils_info->fils_rik_len > FILS_MAX_RIK_LENGTH) || !pe_fils_info->fils_rik) { pe_err("Fils rik len(%d) max %d", pe_fils_info->fils_rik_len, FILS_MAX_RIK_LENGTH); return; } roam_fils_params->rik_length = pe_fils_info->fils_rik_len; qdf_mem_copy(roam_fils_params->rik, pe_fils_info->fils_rik, roam_fils_params->rik_length); pe_debug("fils rik len %d", roam_fils_params->rik_length); } #endif