/* * * 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 #endif #include #include #include #include #include "src/missing.h" #include "src/iwd.h" #include "src/module.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 hierarchy, and all ultimately are derived from the * EMSK. All keys follow the rule: * * "The length of the 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; if (!hkdf_expand(L_CHECKSUM_SHA256, erp->r_rk, erp->cache->emsk_len, info, ptr - info, erp->rmsk, erp->cache->emsk_len)) goto eap_failed; 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)