blob: acead5064f630c92fcf2ebc3a67f8df397ad043a [file] [log] [blame]
/*
*
* Wireless daemon for Linux
*
* Copyright (C) 2019 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ell/ell.h>
#include "src/ie.h"
#include "src/fils.h"
#include "src/handshake.h"
#include "src/mpdu.h"
#include "src/crypto.h"
#include "src/util.h"
#include "src/missing.h"
#include "src/erp.h"
#include "src/auth-proto.h"
#define FILS_NONCE_LEN 16
#define FILS_SESSION_LEN 8
struct fils_sm {
struct auth_proto ap;
struct erp_state *erp;
struct handshake_state *hs;
void *user_data;
fils_tx_authenticate_func_t auth;
fils_tx_associate_func_t assoc;
uint8_t nonce[FILS_NONCE_LEN];
uint8_t anonce[FILS_NONCE_LEN];
uint8_t session[FILS_SESSION_LEN];
uint8_t ick[48];
size_t ick_len;
uint8_t kek_and_tk[64 + 16];
size_t kek_len;
uint8_t pmk[48];
size_t pmk_len;
uint8_t pmkid[16];
uint8_t fils_ft[48];
size_t fils_ft_len;
};
static void fils_derive_pmkid(struct fils_sm *fils, const uint8_t *erp_data,
size_t len)
{
struct l_checksum *sha;
enum l_checksum_type type;
if (fils->hs->akm_suite & (IE_RSN_AKM_SUITE_FILS_SHA256 |
IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256))
type = L_CHECKSUM_SHA256;
else
type = L_CHECKSUM_SHA384;
sha = l_checksum_new(type);
l_checksum_update(sha, erp_data, len);
l_checksum_get_digest(sha, fils->pmkid, sizeof(fils->pmkid));
l_checksum_free(sha);
}
static void fils_erp_tx_func(const uint8_t *eap_data, size_t len,
void *user_data)
{
struct fils_sm *fils = user_data;
struct ie_tlv_builder builder;
uint8_t data[256];
uint8_t *ptr = data;
unsigned int tlv_len;
struct ie_rsn_info rsn_info;
uint8_t *rsne;
l_getrandom(fils->nonce, 16);
l_getrandom(fils->session, 8);
fils_derive_pmkid(fils, eap_data, len);
/* transaction */
l_put_le16(1, ptr);
ptr += 2;
/* status success */
l_put_le16(0, ptr);
ptr += 2;
ie_tlv_builder_init(&builder, ptr, sizeof(data) - 4);
ie_parse_rsne_from_data(fils->hs->supplicant_ie,
fils->hs->supplicant_ie[1] + 2,
&rsn_info);
rsne = alloca(256);
ie_build_rsne(&rsn_info, rsne);
ie_tlv_builder_next(&builder, IE_TYPE_RSN);
ie_tlv_builder_set_data(&builder, rsne + 2, rsne[1]);
ie_tlv_builder_next(&builder, IE_TYPE_FILS_NONCE);
ie_tlv_builder_set_data(&builder, fils->nonce, sizeof(fils->nonce));
ie_tlv_builder_next(&builder, IE_TYPE_FILS_SESSION);
ie_tlv_builder_set_data(&builder, fils->session, sizeof(fils->session));
ie_tlv_builder_next(&builder, IE_TYPE_FILS_WRAPPED_DATA);
ie_tlv_builder_set_data(&builder, eap_data, len);
if (fils->hs->mde) {
ie_tlv_builder_next(&builder, IE_TYPE_MOBILITY_DOMAIN);
ie_tlv_builder_set_data(&builder, fils->hs->mde + 2,
fils->hs->mde[1]);
}
ie_tlv_builder_finalize(&builder, &tlv_len);
fils->auth(data, ptr - data + tlv_len, fils->user_data);
}
static int fils_derive_key_data(struct fils_sm *fils)
{
const void *rmsk;
size_t rmsk_len;
struct ie_tlv_builder builder;
uint8_t key[FILS_NONCE_LEN * 2];
uint8_t key_data[64 + 48 + 16 + 48]; /* largest ICK, KEK, TK, FILS-FT */
uint8_t key_auth[48];
uint8_t data[44];
uint8_t *ptr = data;
size_t hash_len;
struct iovec iov[4];
size_t iov_elems = 0;
size_t fils_ft_len = 0;
bool sha384;
unsigned int ie_len;
uint8_t *rsne = NULL;
rmsk = erp_get_rmsk(fils->erp, &rmsk_len);
if (fils->hs->akm_suite == IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256)
fils_ft_len = 32;
else if (fils->hs->akm_suite == IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384)
fils_ft_len = 48;
/*
* IEEE 802.11ai - Section 12.12.2.5.3
*/
if (fils->hs->akm_suite & (IE_RSN_AKM_SUITE_FILS_SHA256 |
IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA256)) {
sha384 = false;
hash_len = 32;
} else {
sha384 = true;
hash_len = 48;
}
fils->kek_len = handshake_state_get_kek_len(fils->hs);
/* key is SNonce || ANonce */
memcpy(key, fils->nonce, sizeof(fils->nonce));
memcpy(key + FILS_NONCE_LEN, fils->anonce, sizeof(fils->anonce));
if (sha384)
hmac_sha384(key, sizeof(key), rmsk, rmsk_len,
fils->pmk, hash_len);
else
hmac_sha256(key, sizeof(key), rmsk, rmsk_len,
fils->pmk, hash_len);
fils->pmk_len = hash_len;
/*
* IEEE 802.11ai - 12.12.2.5.3 PTKSA key derivation with FILS
* authentication
*
* FILS-Key-Data = PRF-X(PMK, “FILS PTK Derivation”, SPA || AA ||
* SNonce || ANonce)
*/
memcpy(ptr, fils->hs->spa, 6);
ptr += 6;
memcpy(ptr, fils->hs->aa, 6);
ptr += 6;
memcpy(ptr, fils->nonce, sizeof(fils->nonce));
ptr += sizeof(fils->nonce);
memcpy(ptr, fils->anonce, sizeof(fils->anonce));
ptr += sizeof(fils->anonce);
if (sha384)
kdf_sha384(fils->pmk, hash_len, "FILS PTK Derivation",
strlen("FILS PTK Derivation"), data,
sizeof(data), key_data,
hash_len + fils->kek_len + 16 + fils_ft_len);
else
kdf_sha256(fils->pmk, hash_len, "FILS PTK Derivation",
strlen("FILS PTK Derivation"), data,
sizeof(data), key_data,
hash_len + fils->kek_len + 16 + fils_ft_len);
ptr = data;
/*
* IEEE 802.11ai - 12.12.2.6.2 (Re)Association Request for FILS key
* confirmation
*
* Key-Auth = HMAC-Hash(ICK, SNonce || ANonce || STA-MAC || AP-BSSID)
*/
memcpy(ptr, fils->nonce, sizeof(fils->nonce));
ptr += sizeof(fils->nonce);
memcpy(ptr, fils->anonce, sizeof(fils->anonce));
ptr += sizeof(fils->anonce);
memcpy(ptr, fils->hs->spa, 6);
ptr += 6;
memcpy(ptr, fils->hs->aa, 6);
ptr += 6;
memcpy(fils->ick, key_data, hash_len);
fils->ick_len = hash_len;
if (fils_ft_len) {
memcpy(fils->fils_ft, key_data + hash_len + fils->kek_len + 16,
fils_ft_len);
fils->fils_ft_len = fils_ft_len;
}
handshake_state_set_fils_ft(fils->hs, fils->fils_ft, fils->fils_ft_len);
if (sha384)
hmac_sha384(fils->ick, hash_len, data, ptr - data,
key_auth, hash_len);
else
hmac_sha256(fils->ick, hash_len, data, ptr - data,
key_auth, hash_len);
ie_tlv_builder_init(&builder, NULL, 0);
ie_tlv_builder_next(&builder, IE_TYPE_FILS_KEY_CONFIRMATION);
ie_tlv_builder_set_data(&builder, key_auth, hash_len);
ie_tlv_builder_next(&builder, IE_TYPE_FILS_SESSION);
ie_tlv_builder_set_data(&builder, fils->session, sizeof(fils->session));
iov[iov_elems].iov_base = ie_tlv_builder_finalize(&builder, &ie_len);
iov[iov_elems].iov_len = ie_len;
iov_elems++;
iov[iov_elems].iov_base = fils->hs->supplicant_ie;
iov[iov_elems].iov_len = fils->hs->supplicant_ie[1] + 2;
iov_elems++;
if (fils->hs->mde) {
struct ie_rsn_info rsn_info;
/*
* IEEE 8021.11ai Section 13.2.4:
*
* If a key hierarchy already exists for this STA belonging to
* the same mobility domain (i.e., having the same MDID), the
* R0KH shall delete the existing PMK-R0 security association
* and PMK-R1 security associations.
*
* All this means is we need to re-derive the new FT keys. This
* will rederive the PTK too, but it will be overwritten with
* the FILS PTK after associate
*/
handshake_state_derive_ptk(fils->hs);
iov[iov_elems].iov_base = fils->hs->mde;
iov[iov_elems].iov_len = fils->hs->mde[1] + 2;
iov_elems++;
if (ie_parse_rsne_from_data(fils->hs->supplicant_ie,
fils->hs->supplicant_ie[1] + 2,
&rsn_info) < 0)
return -EBADMSG;
rsn_info.num_pmkids = 1;
rsn_info.pmkids = fils->hs->pmk_r1_name;
rsne = alloca(256);
ie_build_rsne(&rsn_info, rsne);
iov[iov_elems].iov_base = rsne;
iov[iov_elems].iov_len = rsne[1] + 2;
iov_elems += 1;
}
memcpy(data, fils->nonce, sizeof(fils->nonce));
memcpy(data + sizeof(fils->nonce), fils->anonce, sizeof(fils->anonce));
memcpy(fils->kek_and_tk, key_data + hash_len, fils->kek_len + 16);
fils->assoc(iov, iov_elems, fils->kek_and_tk, fils->kek_len, data,
FILS_NONCE_LEN * 2, fils->user_data);
return 0;
}
static bool fils_start(struct auth_proto *driver)
{
struct fils_sm *fils = l_container_of(driver, struct fils_sm, ap);
return erp_start(fils->erp);
}
static void fils_free(struct auth_proto *driver)
{
struct fils_sm *fils = l_container_of(driver, struct fils_sm, ap);
erp_free(fils->erp);
explicit_bzero(fils->ick, sizeof(fils->ick));
explicit_bzero(fils->kek_and_tk, sizeof(fils->kek_and_tk));
explicit_bzero(fils->pmk, fils->pmk_len);
explicit_bzero(fils->pmkid, sizeof(fils->pmkid));
l_free(fils);
}
static int fils_rx_authenticate(struct auth_proto *driver, const uint8_t *frame,
size_t len)
{
struct fils_sm *fils = l_container_of(driver, struct fils_sm, ap);
const struct mmpdu_header *hdr = mpdu_validate(frame, len);
const struct mmpdu_authentication *auth;
uint16_t alg;
struct ie_tlv_iter iter;
const uint8_t *anonce = NULL;
const uint8_t *session = NULL;
const uint8_t *wrapped = NULL;
size_t wrapped_len = 0;
const uint8_t *rsne = NULL;
const uint8_t *mde = NULL;
const uint8_t *fte = NULL;
if (!hdr) {
l_debug("Auth frame header did not validate");
return -EBADMSG;
}
auth = mmpdu_body(hdr);
if (!auth) {
l_debug("Auth frame body did not validate");
return -EBADMSG;
}
if (auth->status != 0) {
l_debug("invalid status %u", auth->status);
return L_LE16_TO_CPU(auth->status);
}
alg = L_LE16_TO_CPU(auth->algorithm);
if (alg != MMPDU_AUTH_ALGO_FILS_SK &&
alg != MMPDU_AUTH_ALGO_FILS_SK_PFS) {
l_debug("invalid auth algorithm %u", auth->algorithm);
return MMPDU_STATUS_CODE_UNSUP_AUTH_ALG;
}
ie_tlv_iter_init(&iter, auth->ies, (const uint8_t *) hdr + len -
auth->ies);
while (ie_tlv_iter_next(&iter)) {
switch (iter.tag) {
case IE_TYPE_FILS_NONCE:
if (iter.len != FILS_NONCE_LEN)
goto invalid_ies;
anonce = iter.data;
break;
case IE_TYPE_FILS_SESSION:
if (iter.len != FILS_SESSION_LEN)
goto invalid_ies;
session = iter.data;
break;
case IE_TYPE_FILS_WRAPPED_DATA:
wrapped = iter.data;
wrapped_len = iter.len;
break;
case IE_TYPE_RSN:
if (rsne)
goto invalid_ies;
rsne = ie_tlv_iter_get_data(&iter) - 2;
break;
case IE_TYPE_MOBILITY_DOMAIN:
if (mde)
goto invalid_ies;
mde = ie_tlv_iter_get_data(&iter) - 2;
break;
case IE_TYPE_FAST_BSS_TRANSITION:
if (fte)
goto invalid_ies;
fte = ie_tlv_iter_get_data(&iter) - 2;
break;
default:
continue;
}
}
if (!anonce || !session || !wrapped) {
l_debug("Auth did not include required IEs");
goto invalid_ies;
}
if (mde)
handshake_state_set_mde(fils->hs, mde);
if (fte) {
struct handshake_state *hs = fils->hs;
uint32_t kck_len = handshake_state_get_kck_len(hs);
struct ie_ft_info ft_info;
if (ie_parse_fast_bss_transition_from_data(fte, fte[1] + 2,
kck_len, &ft_info) < 0)
goto invalid_ies;
handshake_state_set_fte(fils->hs, fte);
handshake_state_set_kh_ids(fils->hs, ft_info.r0khid,
ft_info.r0khid_len,
ft_info.r1khid);
}
memcpy(fils->anonce, anonce, FILS_NONCE_LEN);
if (erp_rx_packet(fils->erp, wrapped, wrapped_len) < 0)
goto invalid_ies;
return fils_derive_key_data(fils);
invalid_ies:
return MMPDU_STATUS_CODE_INVALID_ELEMENT;
}
static int fils_rx_associate(struct auth_proto *driver, const uint8_t *frame,
size_t len)
{
struct fils_sm *fils = l_container_of(driver, struct fils_sm, ap);
const struct mmpdu_header *hdr = mpdu_validate(frame, len);
const struct mmpdu_association_response *assoc;
struct ie_tlv_iter iter;
uint8_t key_rsc[8];
const uint8_t *gtk = NULL;
size_t gtk_len;
uint8_t gtk_key_index;
const uint8_t *igtk = NULL;
size_t igtk_len;
uint8_t igtk_key_index;
const uint8_t *ap_key_auth = NULL;
uint8_t expected_key_auth[48];
bool sha384 = (fils->hs->akm_suite & (IE_RSN_AKM_SUITE_FILS_SHA384 |
IE_RSN_AKM_SUITE_FT_OVER_FILS_SHA384));
uint8_t data[44];
uint8_t *ptr = data;
if (!hdr) {
l_debug("Assoc frame header did not validate");
return -EBADMSG;
}
assoc = mmpdu_body(hdr);
if (!assoc) {
l_debug("Assoc frame body did not validate");
return -EBADMSG;
}
if (assoc->status_code != 0)
return L_CPU_TO_LE16(assoc->status_code);
ie_tlv_iter_init(&iter, assoc->ies, (const uint8_t *) hdr + len -
assoc->ies);
while (ie_tlv_iter_next(&iter)) {
switch (iter.tag) {
case IE_TYPE_KEY_DELIVERY:
if (iter.len < 8)
goto invalid_ies;
memcpy(key_rsc, iter.data, 8);
gtk = handshake_util_find_gtk_kde(iter.data + 8,
iter.len - 8,
&gtk_len);
if (!gtk)
goto invalid_ies;
gtk_key_index = util_bit_field(gtk[0], 0, 2);
gtk += 2;
gtk_len -= 2;
if (!fils->hs->mfp)
break;
igtk = handshake_util_find_igtk_kde(iter.data + 8,
iter.len - 8,
&igtk_len);
if (!igtk)
goto invalid_ies;
igtk_key_index = l_get_le16(igtk);
igtk += 2;
igtk_len -= 2;
break;
case IE_TYPE_FILS_KEY_CONFIRMATION:
if (sha384 && iter.len != 48)
goto invalid_ies;
if (!sha384 && iter.len != 32)
goto invalid_ies;
ap_key_auth = iter.data;
break;
}
}
if (!ap_key_auth) {
l_debug("Associate did not include KeyAuth IE");
goto invalid_ies;
}
ptr = data;
memcpy(ptr, fils->anonce, sizeof(fils->anonce));
ptr += sizeof(fils->anonce);
memcpy(ptr, fils->nonce, sizeof(fils->nonce));
ptr += sizeof(fils->nonce);
memcpy(ptr, fils->hs->aa, 6);
ptr += 6;
memcpy(ptr, fils->hs->spa, 6);
ptr += 6;
if (sha384)
hmac_sha384(fils->ick, fils->ick_len, data, ptr - data,
expected_key_auth, fils->ick_len);
else
hmac_sha256(fils->ick, fils->ick_len, data, ptr - data,
expected_key_auth, fils->ick_len);
if (memcmp(ap_key_auth, expected_key_auth, fils->ick_len)) {
l_error("AP KeyAuth did not verify");
return -EBADMSG;
}
handshake_state_set_pmk(fils->hs, fils->pmk, fils->pmk_len);
handshake_state_set_pmkid(fils->hs, fils->pmkid);
if (gtk)
handshake_state_install_gtk(fils->hs, gtk_key_index, gtk,
gtk_len, key_rsc, 6);
if (igtk)
handshake_state_install_igtk(fils->hs, igtk_key_index,
igtk + 6, igtk_len - 6, igtk);
handshake_state_set_ptk(fils->hs, fils->kek_and_tk, fils->kek_len + 16);
handshake_state_install_ptk(fils->hs);
return 0;
invalid_ies:
return MMPDU_STATUS_CODE_INVALID_ELEMENT;
}
struct auth_proto *fils_sm_new(struct handshake_state *hs,
fils_tx_authenticate_func_t auth,
fils_tx_associate_func_t assoc,
void *user_data)
{
struct fils_sm *fils;
fils = l_new(struct fils_sm, 1);
fils->auth = auth;
fils->assoc = assoc;
fils->user_data = user_data;
fils->hs = hs;
fils->ap.start = fils_start;
fils->ap.free = fils_free;
fils->ap.rx_authenticate = fils_rx_authenticate;
fils->ap.rx_associate = fils_rx_associate;
fils->erp = erp_new(hs->erp_cache, fils_erp_tx_func, fils);
return &fils->ap;
}