mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-10-06 11:28:38 +02:00
d2e7d47c84
The TLV builder APIs were not very intuative, and in some (or all) cases required access to the builder structure directly, either to set the TLV buffer or to get the buffer at the end. This change adds a new API, ie_tlv_builder_set_data, which both sets the length for the current TLV and copies the TLV data in one go. This will avoid the need for memcpy(ie_tlv_builder_get_data(...),...) ie_tlv_builder_finalize was also changed to return a pointer to the start of the build buffer. This will eliminate the need to access builder.tlv after building the TLVs. ie_tlv_builder_init was changed to take an optional buffer to hold the TLV data. Passing NULL/0 will build the TLV in the internal buffer. Passing in a pointer and length will build into the passed in buffer.
489 lines
12 KiB
C
489 lines
12 KiB
C
/*
|
|
*
|
|
* 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"
|
|
|
|
#define FILS_NONCE_LEN 16
|
|
#define FILS_SESSION_LEN 8
|
|
|
|
struct fils_sm {
|
|
struct erp_state *erp;
|
|
struct handshake_state *hs;
|
|
void *user_data;
|
|
|
|
fils_tx_authenticate_func_t auth;
|
|
fils_tx_associate_func_t assoc;
|
|
fils_complete_func_t complete;
|
|
|
|
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;
|
|
|
|
bool in_auth : 1;
|
|
};
|
|
|
|
static void fils_failed(struct fils_sm *fils, uint16_t status, bool ap_reject)
|
|
{
|
|
fils->complete(status, fils->in_auth, ap_reject, fils->user_data);
|
|
}
|
|
|
|
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;
|
|
|
|
l_getrandom(fils->nonce, 16);
|
|
l_getrandom(fils->session, 8);
|
|
|
|
/* 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_tlv_builder_next(&builder, IE_TYPE_FILS_NONCE);
|
|
ie_tlv_builder_set_length(&builder, sizeof(fils->nonce));
|
|
memcpy(ie_tlv_builder_get_data(&builder), fils->nonce,
|
|
sizeof(fils->nonce));
|
|
|
|
ie_tlv_builder_next(&builder, IE_TYPE_FILS_SESSION);
|
|
ie_tlv_builder_set_length(&builder, sizeof(fils->session));
|
|
memcpy(ie_tlv_builder_get_data(&builder), fils->session,
|
|
sizeof(fils->session));
|
|
|
|
ie_tlv_builder_next(&builder, IE_TYPE_FILS_WRAPPED_DATA);
|
|
ie_tlv_builder_set_length(&builder, len);
|
|
memcpy(ie_tlv_builder_get_data(&builder), eap_data, len);
|
|
|
|
ie_tlv_builder_finalize(&builder, &tlv_len);
|
|
|
|
fils->auth(data, ptr - data + tlv_len, fils->user_data);
|
|
}
|
|
|
|
static void fils_erp_complete(enum erp_result result, const void *rmsk,
|
|
size_t rmsk_len, void *user_data)
|
|
{
|
|
struct fils_sm *fils = user_data;
|
|
struct ie_tlv_builder builder;
|
|
uint8_t key[FILS_NONCE_LEN * 2];
|
|
uint8_t key_data[64 + 48 + 16]; /* largest ICK, KEK, TK */
|
|
uint8_t key_auth[48];
|
|
uint8_t data[44];
|
|
uint8_t *ptr = data;
|
|
size_t hash_len;
|
|
struct iovec iov[2];
|
|
bool sha384;
|
|
unsigned int ie_len;
|
|
|
|
if (result != ERP_RESULT_SUCCESS) {
|
|
fils_failed(fils, MMPDU_STATUS_CODE_UNSPECIFIED, false);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* IEEE 802.11ai - Section 12.12.2.5.3
|
|
*/
|
|
if (fils->hs->akm_suite == IE_RSN_AKM_SUITE_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);
|
|
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);
|
|
|
|
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 (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_length(&builder, hash_len);
|
|
memcpy(ie_tlv_builder_get_data(&builder), key_auth, hash_len);
|
|
|
|
ie_tlv_builder_next(&builder, IE_TYPE_FILS_SESSION);
|
|
ie_tlv_builder_set_length(&builder, sizeof(fils->session));
|
|
memcpy(ie_tlv_builder_get_data(&builder), fils->session,
|
|
sizeof(fils->session));
|
|
|
|
ie_tlv_builder_finalize(&builder, &ie_len);
|
|
|
|
iov[0].iov_base = builder.tlv;
|
|
iov[0].iov_len = ie_len;
|
|
iov[1].iov_base = fils->hs->supplicant_ie;
|
|
iov[1].iov_len = fils->hs->supplicant_ie[1] + 2;
|
|
|
|
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, 2, fils->kek_and_tk, fils->kek_len, data,
|
|
FILS_NONCE_LEN * 2, fils->user_data);
|
|
|
|
fils->in_auth = false;
|
|
}
|
|
|
|
struct fils_sm *fils_sm_new(struct handshake_state *hs,
|
|
fils_tx_authenticate_func_t auth,
|
|
fils_tx_associate_func_t assoc,
|
|
fils_complete_func_t complete, void *user_data)
|
|
{
|
|
struct fils_sm *fils;
|
|
|
|
fils = l_new(struct fils_sm, 1);
|
|
|
|
fils->auth = auth;
|
|
fils->assoc = assoc;
|
|
fils->complete = complete;
|
|
fils->user_data = user_data;
|
|
fils->hs = hs;
|
|
fils->in_auth = true;
|
|
|
|
fils->erp = erp_new(hs->erp_cache, fils_erp_tx_func,
|
|
fils_erp_complete, fils);
|
|
|
|
return fils;
|
|
}
|
|
|
|
void fils_sm_free(struct fils_sm *fils)
|
|
{
|
|
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);
|
|
|
|
l_free(fils);
|
|
}
|
|
|
|
void fils_start(struct fils_sm *fils)
|
|
{
|
|
if (!erp_start(fils->erp))
|
|
fils->complete(MMPDU_STATUS_CODE_UNSPECIFIED, fils->in_auth,
|
|
false, fils->user_data);
|
|
}
|
|
|
|
void fils_rx_authenticate(struct fils_sm *fils, const uint8_t *frame,
|
|
size_t len)
|
|
{
|
|
const struct mmpdu_header *hdr = mpdu_validate(frame, len);
|
|
const struct mmpdu_authentication *auth;
|
|
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;
|
|
|
|
if (!hdr) {
|
|
l_debug("Auth frame header did not validate");
|
|
goto auth_failed;
|
|
}
|
|
|
|
auth = mmpdu_body(hdr);
|
|
|
|
if (!auth) {
|
|
l_debug("Auth frame body did not validate");
|
|
goto auth_failed;
|
|
}
|
|
|
|
if (auth->status != 0) {
|
|
l_debug("invalid status %u", auth->status);
|
|
fils_failed(fils, auth->status, true);
|
|
return;
|
|
}
|
|
|
|
if (auth->algorithm != MMPDU_AUTH_ALGO_FILS_SK &&
|
|
auth->algorithm != MMPDU_AUTH_ALGO_FILS_SK_PFS) {
|
|
l_debug("invalid auth algorithm %u", auth->algorithm);
|
|
fils_failed(fils, MMPDU_STATUS_CODE_UNSUP_AUTH_ALG, false);
|
|
return;
|
|
}
|
|
|
|
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 auth_failed;
|
|
|
|
anonce = iter.data;
|
|
break;
|
|
case IE_TYPE_FILS_SESSION:
|
|
if (iter.len != FILS_SESSION_LEN)
|
|
goto auth_failed;
|
|
|
|
session = iter.data;
|
|
break;
|
|
case IE_TYPE_FILS_WRAPPED_DATA:
|
|
wrapped = iter.data;
|
|
wrapped_len = iter.len;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!anonce || !session || !wrapped) {
|
|
l_debug("Auth did not include required IEs");
|
|
fils_failed(fils, MMPDU_STATUS_CODE_INVALID_ELEMENT, false);
|
|
return;
|
|
}
|
|
|
|
memcpy(fils->anonce, anonce, FILS_NONCE_LEN);
|
|
|
|
erp_rx_packet(fils->erp, wrapped, wrapped_len);
|
|
|
|
/* EAP should now call the key materials callback, giving us the rMSK */
|
|
return;
|
|
|
|
auth_failed:
|
|
fils_failed(fils, MMPDU_REASON_CODE_UNSPECIFIED, false);
|
|
}
|
|
|
|
void fils_rx_associate(struct fils_sm *fils, const uint8_t *frame, size_t len)
|
|
{
|
|
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);
|
|
uint8_t data[44];
|
|
uint8_t *ptr = data;
|
|
|
|
if (!hdr) {
|
|
l_debug("Assoc frame header did not validate");
|
|
goto assoc_failed;
|
|
}
|
|
|
|
assoc = mmpdu_body(hdr);
|
|
|
|
if (!assoc) {
|
|
l_debug("Assoc frame body did not validate");
|
|
goto assoc_failed;;
|
|
}
|
|
|
|
if (assoc->status_code != 0) {
|
|
fils_failed(fils, assoc->status_code, true);
|
|
return;
|
|
}
|
|
|
|
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,
|
|
>k_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;
|
|
}
|
|
}
|
|
|
|
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");
|
|
goto assoc_failed;
|
|
}
|
|
|
|
handshake_state_set_pmk(fils->hs, fils->pmk, fils->pmk_len);
|
|
|
|
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);
|
|
|
|
fils->complete(0, fils->in_auth, false, fils->user_data);
|
|
|
|
return;
|
|
|
|
assoc_failed:
|
|
fils_failed(fils, MMPDU_STATUS_CODE_UNSPECIFIED, false);
|
|
return;
|
|
|
|
invalid_ies:
|
|
fils_failed(fils, MMPDU_STATUS_CODE_INVALID_ELEMENT, false);
|
|
}
|