erp: ERP implementation and key cache move

ERP (EAP Reauthentication Protocol) allows a station to quickly
reauthenticate using keys from a previous EAP authentication.

This change both implements ERP as well as moves the key cache into
the ERP module.

ERP in its current form is here to only support FILS. ERP is likely not
widespread and there is no easy way to determine if an AP supports ERP
without trying it. Attempting ERP with a non-ERP enabled AP will actually
result in longer connection times since ERP must fail and then full EAP
is done afterwards. For this reason ERP was separated from EAP and a
separate ERP state machine must be created. As it stands now, ERP cannot
be used on its own, only with FILS.
This commit is contained in:
James Prestwood 2019-04-17 14:53:40 -07:00 committed by Denis Kenzior
parent ea228bc8ab
commit d938d362b2
4 changed files with 556 additions and 177 deletions

View File

@ -167,8 +167,7 @@ eap_sources = src/eap.c src/eap.h src/eap-private.h \
src/simauth.h src/simauth.c \
src/watchlist.h src/watchlist.c \
src/eap-tls-common.h src/eap-tls-common.c \
src/mschaputil.h src/mschaputil.c \
src/erpcache.h src/erpcache.c
src/mschaputil.h src/mschaputil.c
if DAEMON
libexec_PROGRAMS += src/iwd
@ -203,6 +202,7 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h src/iwd.h src/missing.h \
src/owe.h src/owe.c \
src/blacklist.h src/blacklist.c \
src/manager.c \
src/erp.h src/erp.c \
$(eap_sources) \
$(builtin_sources)
src_iwd_LDADD = $(ell_ldadd) -ldl

527
src/erp.c Normal file
View File

@ -0,0 +1,527 @@
/*
*
* 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 <ell/ell.h>
#include "eap-private.h"
#include "erp.h"
#include "crypto.h"
#include "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;
erp_complete_func_t complete;
void *user_data;
struct erp_cache_entry *cache;
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,
erp_complete_func_t complete, void *user_data)
{
struct erp_state *erp;
if (!cache)
return NULL;
erp = l_new(struct erp_state, 1);
erp->tx_packet = tx_packet;
erp->complete = complete;
erp->user_data = user_data;
erp->cache = cache;
return erp;
}
void erp_free(struct erp_state *erp)
{
erp_cache_put(erp->cache);
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;
}
void 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];
uint8_t rmsk[64];
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)
return;
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, rmsk, erp->cache->emsk_len);
erp->complete(ERP_RESULT_SUCCESS, rmsk, erp->cache->emsk_len,
erp->user_data);
return;
eap_failed:
erp->complete(ERP_RESULT_FAIL, NULL, 0, erp->user_data);
}
void erp_init(void)
{
key_cache = l_queue_new();
}
void erp_exit(void)
{
l_queue_destroy(key_cache, erp_cache_entry_destroy);
}

View File

@ -20,19 +20,37 @@
*
*/
void erp_add_key(const char *id, const void *session_id, size_t session_len,
struct erp_state;
struct erp_cache_entry;
enum erp_result {
ERP_RESULT_SUCCESS,
ERP_RESULT_FAIL,
};
typedef void (*erp_tx_packet_func_t)(const uint8_t *erp_data, size_t len,
void *user_data);
typedef void (*erp_complete_func_t)(enum erp_result result, const void *rmsk,
size_t rmsk_len, void *user_data);
struct erp_state *erp_new(struct erp_cache_entry *cache,
erp_tx_packet_func_t tx_packet,
erp_complete_func_t complete, void *user_data);
void erp_free(struct erp_state *erp);
bool erp_start(struct erp_state *erp);
void erp_rx_packet(struct erp_state *erp, const uint8_t *erp_data, size_t len);
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, const char *erp_domain);
const char *ssid);
void erp_remove_key(const char *id);
void erp_cache_remove(const char *id);
bool erp_find_key_by_identity(const char *id, void *session,
size_t *session_len, void *emsk, size_t *emsk_len,
const char **erp_domain);
struct erp_cache_entry *erp_cache_get(const char *ssid);
void erp_cache_put(struct erp_cache_entry *cache);
bool erp_has_key_for_ssid(const char *ssid);
bool erp_has_key_for_identity(const char *id);
const char *erp_cache_entry_get_identity(struct erp_cache_entry *cache);
void erp_init(void);
void erp_exit(void);

View File

@ -1,166 +0,0 @@
/*
*
* 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 <ell/ell.h>
#include "erpcache.h"
#define ERP_DEFAULT_KEY_LIFETIME 86400000000
struct erp_cache_entry {
char *id;
void *emsk;
size_t emsk_len;
void *session_id;
size_t session_len;
char *erp_domain;
char *ssid;
uint64_t expire_time;
};
static struct l_queue *key_cache;
static void destroy_entry(void *data)
{
struct erp_cache_entry *entry = data;
l_free(entry->id);
l_free(entry->emsk);
l_free(entry->session_id);
l_free(entry->ssid);
if (entry->erp_domain)
l_free(entry->erp_domain);
l_free(entry);
}
void erp_add_key(const char *id, const void *session_id,
size_t session_len, const void *emsk, size_t emsk_len,
const char *ssid, const char *erp_domain)
{
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);
if (erp_domain)
entry->erp_domain = l_strdup(erp_domain);
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;
for (entry = l_queue_get_entries(key_cache); entry;
entry = entry->next) {
struct erp_cache_entry *cache = entry->data;
if (l_time_after(l_time_now(), cache->expire_time)) {
l_queue_remove(key_cache, cache);
destroy_entry(cache);
continue;
}
if (id) {
if (strcmp(cache->id, id))
continue;
} else if (ssid) {
if (strcmp(cache->ssid, ssid))
continue;
} else
return NULL;
return cache;
}
return NULL;
}
void erp_remove_key(const char *id)
{
struct erp_cache_entry *entry = find_keycache(id, NULL);
if (!entry)
return;
l_queue_remove(key_cache, entry);
destroy_entry(entry);
}
bool erp_find_key_by_identity(const char *id, void *session,
size_t *session_len, void *emsk, size_t *emsk_len,
const char **erp_domain)
{
struct erp_cache_entry *cache = find_keycache(id, NULL);
if (!cache)
return false;
memcpy(emsk, cache->emsk, cache->emsk_len);
*emsk_len = cache->emsk_len;
memcpy(session, cache->session_id, cache->session_len);
*session_len = cache->session_len;
*erp_domain = cache->erp_domain;
return true;
}
bool erp_has_key_for_ssid(const char *ssid)
{
return find_keycache(NULL, ssid) != NULL;
}
bool erp_has_key_for_identity(const char *id)
{
return find_keycache(id, NULL) != NULL;
}
void erp_init(void)
{
key_cache = l_queue_new();
}
void erp_exit(void)
{
l_queue_destroy(key_cache, destroy_entry);
}