3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-11-23 07:29:28 +01:00
iwd/src/erp.c
2019-05-19 13:07:13 -05:00

542 lines
11 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 <stdint.h>
#include <stdio.h>
#include <errno.h>
#include <ell/ell.h>
#include "src/missing.h"
#include "src/iwd.h"
#include "src/eap-private.h"
#include "src/erp.h"
#include "src/crypto.h"
#include "src/util.h"
#define ERP_DEFAULT_KEY_LIFETIME_US 86400000000
struct erp_cache_entry {
char *id;
void *emsk;
size_t emsk_len;
void *session_id;
size_t session_len;
char *ssid;
uint64_t expire_time;
uint32_t ref;
bool invalid : 1;
};
struct erp_state {
erp_tx_packet_func_t tx_packet;
void *user_data;
struct erp_cache_entry *cache;
uint8_t rmsk[64];
uint8_t r_rk[64];
uint8_t r_ik[64];
char keyname_nai[254];
uint16_t seq;
};
enum eap_erp_type {
ERP_TYPE_REAUTH_START = 1,
ERP_TYPE_REAUTH = 2,
};
enum eap_erp_tlv {
ERP_TLV_KEYNAME_NAI = 1,
ERP_TV_RRK_LIFETIME = 2,
ERP_TV_RMSK_LIFETIME = 3,
ERP_TLV_DOMAIN_NAME = 4,
ERP_TLV_CRYPTOSUITES = 5,
ERP_TLV_AUTH_INDICATION = 6,
ERP_TLV_CALLED_STATION_ID = 128,
ERP_TLV_CALLING_STATION_ID = 129,
ERP_TLV_NAS_IDENTIFIER = 130,
ERP_TLV_NAS_IP_ADDRESS = 131,
ERP_TLV_NAS_IPV6_ADDRESS = 132,
};
enum eap_erp_cryptosuite {
ERP_CRYPTOSUITE_SHA256_64 = 1,
ERP_CRYPTOSUITE_SHA256_128 = 2,
ERP_CRYPTOSUITE_SHA256_256 = 3,
};
struct erp_tlv_iter {
unsigned int max;
unsigned int pos;
const unsigned char *tlv;
unsigned int tag;
unsigned int len;
const unsigned char *data;
};
static struct l_queue *key_cache;
static void erp_tlv_iter_init(struct erp_tlv_iter *iter,
const unsigned char *tlv, unsigned int len)
{
iter->tlv = tlv;
iter->max = len;
iter->pos = 0;
}
static bool erp_tlv_iter_next(struct erp_tlv_iter *iter)
{
const unsigned char *tlv = iter->tlv + iter->pos;
const unsigned char *end = iter->tlv + iter->max;
unsigned int tag;
unsigned int len;
if (iter->pos + 2 >= iter->max)
return false;
tag = *tlv++;
/*
* These two tags are not actually TLVs (they are just type-value). Both
* are 32-bit integers.
*/
if (tag != ERP_TV_RMSK_LIFETIME && tag != ERP_TV_RRK_LIFETIME)
len = *tlv++;
else
len = 4;
if (tlv + len > end)
return false;
iter->tag = tag;
iter->len = len;
iter->data = tlv;
iter->pos = tlv + len - iter->tlv;
return true;
}
static void erp_cache_entry_destroy(void *data)
{
struct erp_cache_entry *entry = data;
if (entry->ref)
l_error("ERP entry still has a reference on cleanup!");
l_free(entry->id);
l_free(entry->emsk);
l_free(entry->session_id);
l_free(entry->ssid);
l_free(entry);
}
void erp_cache_add(const char *id, const void *session_id,
size_t session_len, const void *emsk, size_t emsk_len,
const char *ssid)
{
struct erp_cache_entry *entry;
if (!unlikely(id || session_id || emsk))
return;
entry = l_new(struct erp_cache_entry, 1);
entry->id = l_strdup(id);
entry->emsk = l_memdup(emsk, emsk_len);
entry->emsk_len = emsk_len;
entry->session_id = l_memdup(session_id, session_len);
entry->session_len = session_len;
entry->ssid = l_strdup(ssid);
entry->expire_time = l_time_offset(l_time_now(),
ERP_DEFAULT_KEY_LIFETIME_US);
l_queue_push_head(key_cache, entry);
}
static struct erp_cache_entry *find_keycache(const char *id, const char *ssid)
{
const struct l_queue_entry *entry;
if (!id && !ssid)
return NULL;
for (entry = l_queue_get_entries(key_cache); entry;
entry = entry->next) {
struct erp_cache_entry *cache = entry->data;
if (cache->invalid)
continue;
if (l_time_after(l_time_now(), cache->expire_time)) {
if (!cache->ref) {
l_queue_remove(key_cache, cache);
erp_cache_entry_destroy(cache);
} else
cache->invalid = true;
continue;
}
if (id && !strcmp(cache->id, id))
return cache;
if (ssid && !strcmp(cache->ssid, ssid))
return cache;
}
return NULL;
}
void erp_cache_remove(const char *id)
{
struct erp_cache_entry *entry = find_keycache(id, NULL);
if (!entry)
return;
if (entry->ref) {
entry->invalid = true;
return;
}
l_queue_remove(key_cache, entry);
erp_cache_entry_destroy(entry);
}
struct erp_cache_entry *erp_cache_get(const char *ssid)
{
struct erp_cache_entry *cache = find_keycache(NULL, ssid);
if (!cache)
return NULL;
cache->ref++;
return cache;
}
void erp_cache_put(struct erp_cache_entry *cache)
{
cache->ref--;
if (cache->ref)
return;
if (!cache->invalid)
return;
/*
* Cache entry marked as invalid, either it expired or something
* attempted to remove it. Either way, it can now be removed.
*/
l_queue_remove(key_cache, cache);
erp_cache_entry_destroy(cache);
}
const char *erp_cache_entry_get_identity(struct erp_cache_entry *cache)
{
return cache->id;
}
#define ERP_RRK_LABEL "EAP Re-authentication Root Key@ietf.org"
#define ERP_RIK_LABEL "Re-authentication Integrity Key@ietf.org"
#define ERP_RMSK_LABEL "Re-authentication Master Session Key@ietf.org"
/*
* RFC 5295 - Section 3.2. EMSK and USRK Name Derivation
*/
static bool erp_derive_emsk_name(const uint8_t *session_id, size_t session_len,
char buf[static 17])
{
uint8_t hex[8];
char info[7] = { 'E', 'M', 'S', 'K', '\0', 0x0, 0x8};
char *ascii;
if (!hkdf_expand(L_CHECKSUM_SHA256, session_id, session_len, info,
sizeof(info), hex, 8))
return false;
ascii = l_util_hexstring(hex, 8);
strcpy(buf, ascii);
l_free(ascii);
return true;
}
/*
* RFC 6696 - Section 4.1 and 4.3 - rRK and rIK derivation
*
* All reauth keys form a hiearchy, and all ultimately are derived from the
* EMSK. All keys follow the rule:
*
* "The length of the <key> MUST be equal to the length of the parent key used
* to derive it."
*
* Therefore all keys derived are equal to the EMSK length.
*/
static bool erp_derive_reauth_keys(const uint8_t *emsk, size_t emsk_len,
void *r_rk, void *r_ik)
{
char info[256];
char *ptr;;
ptr = info + l_strlcpy(info, ERP_RRK_LABEL, sizeof(info)) + 1;
l_put_be16(emsk_len, ptr);
ptr += 2;
if (!hkdf_expand(L_CHECKSUM_SHA256, emsk, emsk_len, (const char *)info,
ptr - info, r_rk, emsk_len))
return false;
ptr = info + l_strlcpy(info, ERP_RIK_LABEL, sizeof(info)) + 1;
*ptr++ = ERP_CRYPTOSUITE_SHA256_128;
l_put_be16(emsk_len, ptr);
ptr += 2;
if (!hkdf_expand(L_CHECKSUM_SHA256, r_rk, emsk_len, (const char *) info,
ptr - info, r_ik, emsk_len))
return false;
return true;
}
struct erp_state *erp_new(struct erp_cache_entry *cache,
erp_tx_packet_func_t tx_packet,
void *user_data)
{
struct erp_state *erp;
if (!cache)
return NULL;
erp = l_new(struct erp_state, 1);
erp->tx_packet = tx_packet;
erp->user_data = user_data;
erp->cache = cache;
return erp;
}
void erp_free(struct erp_state *erp)
{
erp_cache_put(erp->cache);
explicit_bzero(erp->rmsk, sizeof(erp->rmsk));
explicit_bzero(erp->r_ik, sizeof(erp->r_ik));
explicit_bzero(erp->r_rk, sizeof(erp->r_rk));
l_free(erp);
}
bool erp_start(struct erp_state *erp)
{
uint8_t buf[512];
uint8_t *ptr = buf;
char emsk_name[17];
size_t nai_len;
if (!erp_derive_emsk_name(erp->cache->session_id,
erp->cache->session_len, emsk_name))
return false;
if (!erp_derive_reauth_keys(erp->cache->emsk, erp->cache->emsk_len,
erp->r_rk, erp->r_ik))
return false;
nai_len = sprintf(erp->keyname_nai, "%s@%s", emsk_name,
util_get_domain(erp->cache->id));
*ptr++ = EAP_CODE_INITIATE;
*ptr++ = 0;
/* Header (8) + TL (2) + NAI (nai_len) + CS (1) + auth tag (16) */
l_put_be16(27 + nai_len, ptr);
ptr += 2;
*ptr++ = ERP_TYPE_REAUTH;
*ptr++ = 0;
l_put_be16(erp->seq, ptr);
ptr += 2;
/* keyName-NAI TLV */
*ptr++ = ERP_TLV_KEYNAME_NAI;
*ptr++ = nai_len;
memcpy(ptr, erp->keyname_nai, nai_len);
ptr += nai_len;
*ptr++ = ERP_CRYPTOSUITE_SHA256_128;
hmac_sha256(erp->r_ik, erp->cache->emsk_len, buf, ptr - buf, ptr, 16);
ptr += 16;
erp->tx_packet(buf, ptr - buf, erp->user_data);
return true;
}
int erp_rx_packet(struct erp_state *erp, const uint8_t *pkt, size_t len)
{
struct erp_tlv_iter iter;
enum eap_erp_cryptosuite cs;
uint8_t hash[16];
char info[256];
char *ptr = info;
const uint8_t *nai = NULL;
uint8_t type;
uint16_t seq;
bool r;
/*
* Not including the TLVs we have:
* header (8) + cryptosuite (1) + auth tag (16) = 25 bytes
*/
if (len < 25)
goto eap_failed;
/*
* We can skip code/id/len, since that was already parsed. We just need
* the whole packet so we can verify the Auth tag.
*/
type = pkt[4];
if (type != ERP_TYPE_REAUTH)
goto eap_failed;
r = util_is_bit_set(pkt[5], 0);
if (r)
goto eap_failed;
/*
* TODO: Parse B and L bits. L bit indicates rRK lifetime, but our ERP
* cache does not yet support this.
*/
seq = l_get_be16(pkt + 6);
if (seq != erp->seq)
goto eap_failed;
/*
* The Cryptosuite byte comes after the TLVs. Because of this we cannot
* parse the TLVs yet since we don't actually know where they end. There
* is really no good way to do this, but (at least for now) we can just
* require the 128 bit cryptosuite. If we limit to only this suite we
* can work backwards from the end (17 bytes) to get the cryptosuite. If
* it is not the 128 bit suite we just fail. If it is, we now know where
* the TLVs end;
*/
cs = *(pkt + len - 17);
if (cs != ERP_CRYPTOSUITE_SHA256_128)
goto eap_failed;
hmac_sha256(erp->r_ik, erp->cache->emsk_len, pkt, len - 16, hash, 16);
if (memcmp(hash, pkt + len - 16, 16) != 0) {
l_debug("Authentication Tag did not verify");
goto eap_failed;
}
erp_tlv_iter_init(&iter, pkt + 8, len - 8 - 17);
while (erp_tlv_iter_next(&iter)) {
switch (iter.tag) {
case ERP_TLV_KEYNAME_NAI:
if (nai)
goto eap_failed;
nai = iter.data;
break;
default:
break;
}
}
/*
* RFC 6696 Section 5.3.3
*
* Exactly one instance of the keyName-NAI attribute SHALL be present
* in an EAP-Finish/Re-auth message
*/
if (!nai) {
l_error("AP did not include keyName-NAI in EAP-Finish");
goto eap_failed;
}
if (memcmp(nai, erp->keyname_nai, strlen(erp->keyname_nai))) {
l_error("keyName-NAI did not match");
goto eap_failed;
}
/*
* RFC 6696 Section 4.6 - rMSK Derivation
*/
strcpy(ptr, ERP_RMSK_LABEL);
ptr += strlen(ERP_RMSK_LABEL);
*ptr++ = '\0';
l_put_be16(erp->seq, ptr);
ptr += 2;
l_put_be16(64, ptr);
ptr += 2;
hkdf_expand(L_CHECKSUM_SHA256, erp->r_rk, erp->cache->emsk_len,
info, ptr - info, erp->rmsk, erp->cache->emsk_len);
return 0;
eap_failed:
return -EINVAL;
}
const void *erp_get_rmsk(struct erp_state *erp, size_t *rmsk_len)
{
if (rmsk_len)
*rmsk_len = erp->cache->emsk_len;
return erp->rmsk;
}
static int erp_init(void)
{
key_cache = l_queue_new();
return 0;
}
static void erp_exit(void)
{
l_queue_destroy(key_cache, erp_cache_entry_destroy);
}
IWD_MODULE(erp, erp_init, erp_exit)