From 283717b22faf3fd1de0b0cf8b03ceb730fa0d502 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Mon, 21 Aug 2017 14:09:03 -0700 Subject: [PATCH] sim: EAP-SIM protocol implementation --- Makefile.am | 3 +- src/eap-sim.c | 644 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/eap.h | 1 + src/simutil.c | 382 ++++++++++++++++++++++++++++++ src/simutil.h | 208 ++++++++++++++++ 5 files changed, 1237 insertions(+), 1 deletion(-) create mode 100644 src/eap-sim.c create mode 100644 src/simutil.c create mode 100644 src/simutil.h diff --git a/Makefile.am b/Makefile.am index ed1e098b..65e349ef 100644 --- a/Makefile.am +++ b/Makefile.am @@ -86,7 +86,8 @@ src_iwd_SOURCES = src/main.c linux/nl80211.h \ src/rfkill.h src/rfkill.c \ src/watchlist.h src/watchlist.c \ src/ftutil.h src/ftutil.c \ - src/iwd.h + src/iwd.h src/eap-sim.c \ + src/simutil.h src/simutil.c src_iwd_LDADD = ell/libell-internal.la -ldl dist_sysconf_DATA = src/iwd.conf diff --git a/src/eap-sim.c b/src/eap-sim.c new file mode 100644 index 00000000..4f6b412e --- /dev/null +++ b/src/eap-sim.c @@ -0,0 +1,644 @@ +/* + * + * Wireless daemon for Linux + * + * Copyright (C) 2017 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 "crypto.h" +#include "simutil.h" +#include "src/dbus.h" + +/* + * EAP-SIM authentication protocol. + * + * Open Items: + * - Fast Re-authentication. In order to implement this, the higher level + * EAP code would need to know/retrieve a fast re-authentication identity + * that it would send in the EAP-Start packet. This ID is provided by + * the server during the challenge in full authentication. EAP-SIM does + * save this ID, but there is no mechanism to provide it to the upper + * level EAP system. Once this is done the server will recognize the + * ID and send a SIM/Re-authentication request. + * + * - Version validation. Perhaps a real SIM card will provide a version + * of EAP-SIM that it supports? Currently we accept any version the + * server provides. + * + * - Real SIM authentication. Right now Kc/SRES/Identity values are loaded + * from a settings file. If a real SIM is used they would need to be + * obtained there. This would require providing the SIM with a RAND, to + * have it run its GSM algorithm. Kc/SRES can then be derived from that. + */ + +/* RFC 4187, Section 11 */ +#define EAP_SIM_ST_START 0x0a +#define EAP_SIM_ST_CHALLENGE 0x0b +#define EAP_SIM_ST_NOTIFICATION 0x0c +#define EAP_SIM_ST_CLIENT_ERROR 0x0e + +/* EAP-SIM value lengths */ +#define EAP_SIM_NONCE_LEN 16 +#define EAP_SIM_KC_LEN 8 +#define EAP_SIM_SRES_LEN 4 + +/* + * Internal client state, tracked to ensure that we are receiving the right + * messages at the right time. + */ +enum eap_sim_state { + EAP_SIM_STATE_UNCONNECTED = 0, + EAP_SIM_STATE_START, + EAP_SIM_STATE_CHALLENGE, + EAP_SIM_STATE_SUCCESS, + EAP_SIM_STATE_ERROR +}; + +struct eap_sim_handle { + enum eap_sim_state state; + /* Identity from SIM */ + char *identity; + + /* EAP-SIM supported version list */ + uint16_t *vlist; + uint16_t vlist_len; + + /* Negotiated EAP-SIM version */ + uint16_t selected_version; + + /* RAND's from AT_RAND attribute */ + uint8_t rands[3][EAP_SIM_RAND_LEN]; + + /* Kc values from SIM */ + uint8_t kc[3][EAP_SIM_KC_LEN]; + + /* Random generated nonce */ + uint8_t nonce[EAP_SIM_NONCE_LEN]; + + /* Derived master key */ + uint8_t mk[EAP_SIM_MK_LEN]; + + /* Derived K_encr key from PRNG */ + uint8_t k_encr[EAP_SIM_K_ENCR_LEN]; + + /* Derived K_aut key from PRNG */ + uint8_t k_aut[EAP_SIM_K_AUT_LEN]; + + /* Derived MSK from PRNG */ + uint8_t msk[EAP_SIM_MSK_LEN]; + + /* Derived EMSK from PRNG */ + uint8_t emsk[EAP_SIM_EMSK_LEN]; + + /* SRES values from SIM */ + uint8_t sres[3][EAP_SIM_SRES_LEN]; + + /* Flag set if AT_ANY_ID_REQ was present */ + bool any_id_req : 1; + + /* Flag to indicate protected status indications */ + bool protected : 1; +}; + +static int eap_sim_probe(struct eap_state *eap, const char *name) +{ + struct eap_sim_handle *sim; + + if (strcasecmp(name, "SIM")) + return -ENOTSUP; + + sim = l_new(struct eap_sim_handle, 1); + + eap_set_data(eap, sim); + + return 0; +} + +static void eap_sim_remove(struct eap_state *eap) +{ + struct eap_sim_handle *sim = eap_get_data(eap); + + l_free(sim->identity); + l_free(sim->vlist); + /* Kc values are crucial to security, zero them just in case */ + memset(sim->kc, 0, sizeof(sim->kc)); + l_free(sim); + + eap_set_data(eap, NULL); +} + +/* + * Derive the master key (MK): + * SHA1(identity | kc | nonce | version list | selected version) + */ +static bool derive_master_key(const char *identity, const void *kc, + const void *nonce, const void *vlist, uint16_t vlist_len, + uint16_t selected_version, uint8_t *mk) +{ + int ret; + struct iovec iov[5]; + struct l_checksum *checksum = l_checksum_new(L_CHECKSUM_SHA1); + + if (!checksum) { + l_error("could not create SHA1 checksum"); + return false; + } + + iov[0].iov_base = (void *)identity; + iov[0].iov_len = strlen(identity); + iov[1].iov_base = (void *)kc; + iov[1].iov_len = EAP_SIM_KC_LEN * 3; + iov[2].iov_base = (void *)nonce; + iov[2].iov_len = EAP_SIM_NONCE_LEN; + iov[3].iov_base = (void *)vlist; + iov[3].iov_len = vlist_len; + iov[4].iov_base = &selected_version; + iov[4].iov_len = 2; + + if (!l_checksum_updatev(checksum, iov, 5)) + goto mk_error; + + ret = l_checksum_get_digest(checksum, mk, EAP_SIM_MK_LEN); + l_checksum_free(checksum); + + return (ret == EAP_SIM_MK_LEN); + +mk_error: + l_checksum_free(checksum); + l_error("error deriving master key"); + return false; +} + +/* + * Handles EAP-SIM Start subtype + */ +static void handle_start(struct eap_state *eap, const uint8_t *pkt, + size_t len) +{ + struct eap_sim_handle *sim = eap_get_data(eap); + struct eap_sim_tlv_iter iter; + uint16_t resp_len; + uint8_t *response; + uint8_t *pos; + + if (len < 3) { + l_error("packet is too small"); + goto start_error; + } + + if (sim->state != EAP_SIM_STATE_UNCONNECTED) { + l_error("invalid packet for EAP-SIM state"); + goto start_error; + } + + eap_sim_tlv_iter_init(&iter, pkt + 3, len - 3); + + while (eap_sim_tlv_iter_next(&iter)) { + const uint8_t *contents = eap_sim_tlv_iter_get_data(&iter); + uint16_t length = eap_sim_tlv_iter_get_length(&iter); + + switch (eap_sim_tlv_iter_get_type(&iter)) { + case EAP_SIM_AT_VERSION_LIST: + if (length < 2) { + l_error("AT_VERSION_LIST was malformed"); + goto start_error; + } + + sim->vlist_len = l_get_be16(contents); + + if (length < 2 + sim->vlist_len) { + l_error("AT_VERSION_LIST was malformed"); + goto start_error; + } + + /* + * The version list is stored as-is (including + * padding). This does mean that there is potential + * for padding bytes at the end, but this is expected + * when generating the Master Key. + */ + sim->vlist = l_memdup(contents + 2, sim->vlist_len); + + sim->selected_version = sim->vlist[0]; + + break; + + case EAP_SIM_AT_ANY_ID_REQ: + sim->any_id_req = true; + + break; + + case EAP_SIM_AT_PERMANENT_ID_REQ: + case EAP_SIM_AT_FULLAUTH_ID_REQ: + /* + * TODO: Server requesting permanent ID/pseudonym + */ + break; + + default: + l_error("attribute %u was found in Start", + eap_sim_tlv_iter_get_type(&iter)); + goto start_error; + } + } + + sim->state = EAP_SIM_STATE_START; + + /* header + AT_NONCE + AT_SELECTED_VERSION */ + resp_len = (8) + (20) + (4); + if (sim->any_id_req) { + /* + AT_IDENTITY */ + resp_len += EAP_SIM_ROUND(strlen(sim->identity) + 4); + } + + l_getrandom(sim->nonce, EAP_SIM_NONCE_LEN); + + response = alloca(resp_len); + pos = response; + + pos += eap_sim_build_header(eap, EAP_TYPE_SIM, EAP_SIM_ST_START, pos, + resp_len); + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_NONCE, EAP_SIM_PAD_ZERO, + sim->nonce, EAP_SIM_NONCE_LEN); + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_SELECTED_VERSION, + EAP_SIM_PAD_NONE, (uint8_t *)&sim->selected_version, + 2); + + if (sim->any_id_req) + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_IDENTITY, + EAP_SIM_PAD_LENGTH, (uint8_t *)sim->identity, + strlen(sim->identity)); + + eap_send_response(eap, EAP_TYPE_SIM, response, resp_len); + + return; + +start_error: + eap_sim_client_error(eap, EAP_TYPE_SIM, EAP_SIM_ERROR_PROCESS); +} + +/* + * Handles EAP-SIM Challenge subtype + */ +static void handle_challenge(struct eap_state *eap, const uint8_t *pkt, + size_t len) +{ + struct eap_sim_handle *sim = eap_get_data(eap); + struct eap_sim_tlv_iter iter; + enum eap_sim_error code = EAP_SIM_ERROR_PROCESS; + /* header + AT_MAC */ + uint16_t resp_len = 8 + 20; + /* + * The response buf adds SRES*3 for MAC derivation + the response + * indicator, which is not always present. + * (resp_len gets incremented only if AT_RESPONSE_IND is present) + */ + uint8_t response[resp_len + 4 + (EAP_SIM_SRES_LEN * 3)]; + uint8_t *pos = response; + uint8_t prng_buf[160]; + uint8_t *mac_pos; + + if (sim->state != EAP_SIM_STATE_START) { + l_error("invalid packet for EAP-SIM state"); + goto chal_error; + } + + if (len < 3) { + l_error("packet is too small"); + goto chal_error; + } + + eap_sim_tlv_iter_init(&iter, pkt + 3, len - 3); + + while (eap_sim_tlv_iter_next(&iter)) { + const uint8_t *contents = eap_sim_tlv_iter_get_data(&iter); + uint16_t length = eap_sim_tlv_iter_get_length(&iter); + + switch (eap_sim_tlv_iter_get_type(&iter)) { + case EAP_SIM_AT_RAND: + if ((length - 2) / 16 != 3) { + l_error("insufficient RAND's %u", + (length - 2) / 16); + code = EAP_SIM_ERROR_CHALLENGE; + goto chal_error; + } + /* + * TODO: check that RAND's are fresh. Existing RAND's + * should only exist if we are re-authenticating to the + * server, which is currently not implemented. + */ + memcpy(sim->rands, contents + 2, length - 2); + break; + + case EAP_SIM_AT_RESULT_IND: + sim->protected = true; + resp_len += 4; + break; + + case EAP_SIM_AT_IV: + case EAP_SIM_AT_ENCR_DATA: + case EAP_SIM_AT_MAC: + /* need a case for these so the default wont get hit */ + break; + + default: + l_error("attribute type %u not allowed in Challenge", + eap_sim_tlv_iter_get_type(&iter)); + goto chal_error; + } + } + + if (!derive_master_key(sim->identity, sim->kc, sim->nonce, sim->vlist, + sim->vlist_len, sim->selected_version, sim->mk)) { + l_error("error deriving master key"); + goto chal_fatal; + } + + eap_sim_fips_prf(sim->mk, 20, prng_buf, 160); + + if (!eap_sim_get_encryption_keys(prng_buf, sim->k_encr, sim->k_aut, + sim->msk, sim->emsk)) { + l_error("could not derive encryption keys"); + goto chal_fatal; + } + + if (!eap_sim_verify_mac(eap, EAP_TYPE_SIM, pkt, len, sim->k_aut, + sim->nonce, EAP_SIM_NONCE_LEN)) { + l_error("server MAC was invalid"); + goto chal_error; + } + + sim->state = EAP_SIM_STATE_CHALLENGE; + + /* + * TODO: When/If fast re-authentication is supported, the AT_ENCR_DATA + * attribute would be decrypted here. Currently there is no need + * or reason to do this without support for fast + * re-authentication. + */ + + /* build response packet */ + pos += eap_sim_build_header(eap, EAP_TYPE_SIM, EAP_SIM_ST_CHALLENGE, + pos, resp_len); + + if (sim->protected) + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_RESULT_IND, + EAP_SIM_PAD_NONE, NULL, 2); + + /* save MAC position to know where to write it to */ + mac_pos = pos; + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_MAC, EAP_SIM_PAD_NONE, + NULL, EAP_SIM_MAC_LEN); + + /* append SRES for MAC derivation */ + memcpy(pos, sim->sres, EAP_SIM_SRES_LEN * 3); + pos += EAP_SIM_SRES_LEN * 3; + + if (!eap_sim_derive_mac(response, pos - response, sim->k_aut, + mac_pos + 4)) { + l_error("could not derive MAC"); + goto chal_fatal; + } + + eap_send_response(eap, EAP_TYPE_SIM, response, resp_len); + + if (!sim->protected) { + /* + * Result indication not required, we must accept success. + */ + eap_method_success(eap); + eap_set_key_material(eap, sim->msk, 32, NULL, 0, NULL, 0); + + sim->state = EAP_SIM_STATE_SUCCESS; + } + + return; + + /* + * fatal, unrecoverable error + */ +chal_fatal: + eap_method_error(eap); + sim->state = EAP_SIM_STATE_ERROR; + return; + +chal_error: + eap_sim_client_error(eap, EAP_TYPE_SIM, code); +} + +/* + * Handles EAP-SIM Notification subtype + */ +static void handle_notification(struct eap_state *eap, const uint8_t *pkt, + size_t len) +{ + struct eap_sim_handle *sim = eap_get_data(eap); + struct eap_sim_tlv_iter iter; + int32_t value = -1; + + if (len < 3) { + l_error("packet is too small"); + goto notif_error; + } + + eap_sim_tlv_iter_init(&iter, pkt + 3, len - 3); + + while (eap_sim_tlv_iter_next(&iter)) { + const uint8_t *contents = eap_sim_tlv_iter_get_data(&iter); + uint16_t length = eap_sim_tlv_iter_get_length(&iter); + + switch (eap_sim_tlv_iter_get_type(&iter)) { + case EAP_SIM_AT_NOTIFICATION: + if (length < 2) { + l_error("malformed AT_NOTIFICATION"); + goto notif_error; + } + + value = l_get_be16(contents); + + break; + + case EAP_SIM_AT_IV: + case EAP_SIM_AT_ENCR_DATA: + case EAP_SIM_AT_PADDING: + case EAP_SIM_AT_MAC: + /* RFC 4186, Section 10.1 */ + break; + + default: + l_error("attribute type %u not allowed in Notification", + eap_sim_tlv_iter_get_type(&iter)); + goto notif_error; + } + } + + if (value == EAP_SIM_SUCCESS && sim->protected && + sim->state == EAP_SIM_STATE_CHALLENGE) { + /* header + MAC + MAC header */ + uint8_t response[8 + EAP_SIM_MAC_LEN + 4]; + uint8_t *pos = response; + + /* + * Server sent successful result indication + */ + eap_method_success(eap); + eap_set_key_material(eap, sim->msk, 32, NULL, 0, NULL, 0); + + /* + * Build response packet + */ + pos += eap_sim_build_header(eap, EAP_TYPE_SIM, + EAP_SIM_ST_NOTIFICATION, pos, 20); + pos += eap_sim_add_attribute(pos, EAP_SIM_AT_MAC, + EAP_SIM_PAD_NONE, NULL, EAP_SIM_MAC_LEN); + + if (!eap_sim_derive_mac(response, pos - response, sim->k_aut, + response + 12)) { + l_error("could not derive MAC"); + eap_method_error(eap); + sim->state = EAP_SIM_STATE_ERROR; + return; + } + + eap_send_response(eap, EAP_TYPE_SIM, response, pos - response); + + sim->state = EAP_SIM_STATE_SUCCESS; + return; + } else if (value == EAP_SIM_SUCCESS) { + /* + * Unexpected success notification, what should + * be done here? + */ + l_error("Unexpected success notification"); + } else { + /* + * All other values are error conditions. + * Nothing unique can be done for any error so + * print the code and signal EAP failure. + */ + l_error("Error authenticating: code=%u", value); + } + +notif_error: + eap_sim_client_error(eap, EAP_TYPE_SIM, EAP_SIM_ERROR_PROCESS); +} + +static void eap_sim_handle_request(struct eap_state *eap, + const uint8_t *pkt, size_t len) +{ + if (len < 1) { + l_error("packet is too small"); + goto req_error; + } + + switch (pkt[0]) { + case EAP_SIM_ST_START: + handle_start(eap, pkt, len); + break; + case EAP_SIM_ST_CHALLENGE: + handle_challenge(eap, pkt, len); + break; + case EAP_SIM_ST_NOTIFICATION: + handle_notification(eap, pkt, len); + break; + default: + l_error("unknown EAP-SIM subtype: %u", pkt[0]); + goto req_error; + } + + return; + +req_error: + eap_sim_client_error(eap, EAP_TYPE_SIM, EAP_SIM_ERROR_PROCESS); +} + +static bool eap_sim_load_settings(struct eap_state *eap, + struct l_settings *settings, + const char *prefix) +{ + struct eap_sim_handle *sim = eap_get_data(eap); + char setting[64]; + const char *kcs; + const char *imsi; + const char *sres; + size_t len; + + /* + * TODO: These values will be loaded from a SIM card. Kc and SRES + * values should be kept secret and crucial to the security of EAP-SIM. + * It may be better to load them on the fly (from the SIM) as needed + * rather than storing them in the eap_sim_state structure. + */ + snprintf(setting, sizeof(setting), "%sSIM-Kc", prefix); + kcs = l_settings_get_value(settings, "Security", setting); + if (kcs) { + uint8_t *val = l_util_from_hexstring(kcs, &len); + + memcpy(sim->kc, val, len); + l_free(val); + } + + snprintf(setting, sizeof(setting), "%sSIM-IMSI", prefix); + imsi = l_settings_get_value(settings, "Security", setting); + if (imsi) + sim->identity = l_strdup(imsi); + + snprintf(setting, sizeof(setting), "%sSIM-SRES", prefix); + sres = l_settings_get_value(settings, "Security", setting); + if (sres) { + uint8_t *val = l_util_from_hexstring(sres, &len); + + memcpy(sim->sres, val, len); + l_free(val); + } + + return true; +} + +static struct eap_method eap_sim = { + .request_type = EAP_TYPE_SIM, + .exports_msk = true, + .name = "SIM", + .probe = eap_sim_probe, + .remove = eap_sim_remove, + .handle_request = eap_sim_handle_request, + .load_settings = eap_sim_load_settings, +}; + +static int eap_sim_init(void) +{ + l_debug(""); + return eap_register_method(&eap_sim); +} + +static void eap_sim_exit(void) +{ + l_debug(""); + eap_unregister_method(&eap_sim); +} + +EAP_METHOD_BUILTIN(eap_sim, eap_sim_init, eap_sim_exit) diff --git a/src/eap.h b/src/eap.h index 7be075d5..f321e964 100644 --- a/src/eap.h +++ b/src/eap.h @@ -71,6 +71,7 @@ enum eap_type { __EAP_TYPE_MIN_METHOD = 4, EAP_TYPE_MD5_CHALLENGE = 4, EAP_TYPE_TLS_EAP = 13, + EAP_TYPE_SIM = 18, EAP_TYPE_TTLS = 21, EAP_TYPE_MSCHAPV2 = 26, EAP_TYPE_EXPANDED = 254, diff --git a/src/simutil.c b/src/simutil.c new file mode 100644 index 00000000..180a1132 --- /dev/null +++ b/src/simutil.c @@ -0,0 +1,382 @@ +/* + * + * Wireless daemon for Linux + * + * Copyright (C) 2017 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 + * + */ + +#include +#include +#include +#include + +#include "crypto.h" +#include "simutil.h" + +/* + * RFC 3174 functions + */ +/* + * Section 3a - Circular left shift function S + */ +#define S(n, x) (((x) << (n)) | ((x) >> (32 - (n)))) + +/* + * Section 5 - Functions and Constants Used + * + * K(t) - sequence of constant words K(0) - K(79) + * (represented as a function, index t is constant for every 20 indexes) + */ +static uint32_t K(int t) +{ + if (t >= 0 && t <= 19) + return 0x5a827999; + else if (t >= 20 && t <= 39) + return 0x6ed9eba1; + else if (t >= 40 && t <= 59) + return 0x8f1bbcdc; + else if (t >= 60 && t <= 79) + return 0xca62c1d6; + + return 0; +} + +/* + * f(t, B, C, D) - sequence of logical functions f(0) - f(79) + * Every 20 indexes the value of t computes a different bit manipulation of + * B, C and D + */ +static uint32_t f(int t, uint32_t B, uint32_t C, uint32_t D) +{ + if (t >= 0 && t <= 19) + return (B & C) | ((~B) & D); + else if (t >= 20 && t <= 39) + return B ^ C ^ D; + else if (t >= 40 && t <= 59) + return (B & C) | (B & D) | (C & D); + else if (t >= 60 && t <= 79) + return B ^ C ^ D; + + return 0; +} + +/* + * RFC 3174 Section 6.1 Method 1 + * + * Core SHA1 block digest function. Computes the SHA1 digest of a single block. + * Named G as it appears in FIPS 182 PRNG. + * + * The Linux kernel does not expose this specific block digest function to the + * user. The SHA1 function exposed in the kernel automatically does the length + * encoded padding to the block which is different than what EAP-SIM requires. + * EAP-SIM requires and extra bits in the block to be zero. This function was + * implemented for this reason. + */ +static void G(uint32_t *out, uint8_t *block) +{ + int t; + uint32_t H[5]; + uint32_t W[80]; + uint32_t A, B, C, D, E; + uint32_t TEMP; + + H[0] = out[0]; + H[1] = out[1]; + H[2] = out[2]; + H[3] = out[3]; + H[4] = out[4]; + + /* + * a. Divide M (block) into 16 words, W(0) ... W(15) where W(0) is the + * left-most word + */ + for (t = 0; t < 16; t++) { + /* copy each word */ + W[t] = L_BE32_TO_CPU(((uint32_t *)block)[t]); + } + /* + * b. for t = 16 to 79 do + */ + for (t = 16; t <= 79; t++) { + /* W(t) = S^1(W(t-3) XOR W(t-8) XOR W(t-14) XOR W(t-16)) */ + W[t] = S(1, (W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16])); + } + /* c. Let A = H0, B = H1, C = H2, D = H3, E = H4 */ + A = H[0]; + B = H[1]; + C = H[2]; + D = H[3]; + E = H[4]; + + /* d. For t = 0 to 79 do */ + for (t = 0; t <= 79; t++) { + /* TEMP = S^5(A) + f(t;B,C,D) + E + W(t) + K(t); */ + TEMP = (S(5, A)) + (f(t, B, C, D) + E + W[t] + K(t)); + /* E = D; D = C; C = S^30(B); B = A; A = TEMP; */ + E = D; D = C; C = S(30, B); B = A; A = TEMP; + } + + /* + * e. Let H[0-4] == A, B, C, D, E + */ + H[0] += A; + H[1] += B; + H[2] += C; + H[3] += D; + H[4] += E; + + memcpy(out, H, sizeof(H)); +} + +void eap_sim_fips_prf(const void *seed, size_t slen, uint8_t *out, size_t olen) +{ + uint8_t xkey[64]; + uint32_t w_i[5]; + uint32_t t[] = { 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, + 0xC3D2E1F0 }; + uint8_t *pos = out; + uint32_t c; + int j, i; + + /* Copy seed and zero pad remainder */ + memcpy(xkey, seed, slen); + memset(xkey + slen, 0, sizeof(xkey) - slen); + + for (j = 0; j < (int)olen / 40; j++) { + for (i = 0; i < 2; i++) { + int k; + + memcpy(w_i, t, sizeof(t)); + /* w_i = G(t, XVAL) */ + G(w_i, xkey); + for (k = 0; k < 5; k++) + w_i[k] = L_CPU_TO_BE32(w_i[k]); + + memcpy(pos, w_i, 20); + /* XKEY = (1 + XKEY + w_i) mod 2^b*/ + c = 1; + for (k = 19; k >= 0; k--) { + uint32_t sum = xkey[k] + pos[k] + c; + + xkey[k] = sum & 0xff; + c = sum >> 8; + } + pos += 20; + } + } +} + +bool eap_sim_get_encryption_keys(const uint8_t *buf, uint8_t *k_encr, + uint8_t *k_aut, uint8_t *msk, uint8_t *emsk) +{ + const uint8_t *pos = buf; + + if (!buf || !msk || !emsk) { + l_error("key pointers are invalid"); + return false; + } + + if (k_encr) + memcpy(k_encr, pos, EAP_SIM_K_ENCR_LEN); + + pos += EAP_SIM_K_ENCR_LEN; + if (k_aut) + memcpy(k_aut, pos, EAP_SIM_K_AUT_LEN); + + pos += EAP_SIM_K_AUT_LEN; + memcpy(msk, pos, EAP_SIM_MSK_LEN); + pos += EAP_SIM_MSK_LEN; + memcpy(emsk, pos, EAP_SIM_EMSK_LEN); + + return true; +} + +bool eap_sim_derive_mac(const uint8_t *buf, size_t len, const uint8_t *key, + uint8_t *mac) +{ + return hmac_sha1(key, EAP_SIM_K_AUT_LEN, buf, len, mac, + EAP_SIM_MAC_LEN); +} + +size_t eap_sim_build_header(struct eap_state *eap, enum eap_type method, + uint8_t type, uint8_t *buf, uint16_t len) +{ + buf[0] = 0x02; + eap_save_last_id(eap, &buf[1]); + l_put_be16(len, buf + 2); + buf[4] = method; + buf[5] = type; + buf[6] = 0x00; + buf[7] = 0x00; + return 8; +} + +void eap_sim_client_error(struct eap_state *eap, enum eap_type type, + uint16_t code) +{ + uint8_t buf[12]; + + eap_sim_build_header(eap, type, 0x0e, buf, 12); + buf[8] = EAP_SIM_AT_CLIENT_ERROR_CODE; + buf[9] = 1; + l_put_be16(code, buf + 10); + + eap_send_response(eap, type, buf, 12); +} + +size_t eap_sim_add_attribute(uint8_t *buf, enum eap_sim_at attr, + uint8_t ptype, uint8_t *data, uint16_t dlen) +{ + int i; + uint8_t pos = 0; + uint8_t pad = 0; + + buf[pos++] = attr; + + if (ptype == EAP_SIM_PAD_NONE) + /* no padding indicates data directly follows ID/size */ + buf[pos++] = EAP_SIM_ROUND(dlen + 2) / 4; + else + /* any padding indicates 2 extra bytes before data */ + buf[pos++] = EAP_SIM_ROUND(dlen + 4) / 4; + + if (ptype == EAP_SIM_PAD_LENGTH) { + /* Encode length in next two bytes */ + l_put_be16(dlen, buf + pos); + pos += 2; + } else if (ptype == EAP_SIM_PAD_ZERO) { + buf[pos++] = 0x00; + buf[pos++] = 0x00; + } else if (ptype == EAP_SIM_PAD_LENGTH_BITS) { + l_put_be16(dlen * 8, buf + pos); + pos += 2; + } /* else no padding */ + + if (data) + memcpy(buf + pos, data, dlen); + else + memset(buf + pos, 0, dlen); + + pad = (buf[1] * 4) - (dlen + pos); + pos += dlen; + /* If header + data is not in multiple of 4 bytes then pad */ + for (i = 0; i < pad; i++) + buf[pos + i] = 0x00; + + pos += pad; + return pos; +} + +bool eap_sim_verify_mac(struct eap_state *eap, enum eap_type type, + const uint8_t *buf, uint16_t len, uint8_t *k_aut, + uint8_t *extra, size_t elen) +{ + struct l_checksum *hmac; + struct eap_sim_tlv_iter iter; + const uint8_t *mac_p = NULL; + uint8_t zero_mac[EAP_SIM_MAC_LEN] = { 0 }; + uint8_t hdr[5]; + struct iovec iov[4]; + + eap_sim_tlv_iter_init(&iter, buf + 3, len - 3); + + while (eap_sim_tlv_iter_next(&iter)) { + if (eap_sim_tlv_iter_get_type(&iter) == EAP_SIM_AT_MAC) { + mac_p = eap_sim_tlv_iter_get_data(&iter) + 2; + break; + } + } + + if (!mac_p) { + l_error("packet did not contain AT_MAC attribute"); + return false; + } + + /* re-build EAP packet header */ + hdr[0] = 0x01; + eap_save_last_id(eap, &hdr[1]); + l_put_be16(len + 5, hdr + 2); + hdr[4] = type; + + iov[0].iov_base = (void *)hdr; + iov[0].iov_len = 5; + iov[1].iov_base = (void *)buf; + iov[1].iov_len = len - EAP_SIM_MAC_LEN; + iov[2].iov_base = zero_mac; + iov[2].iov_len = EAP_SIM_MAC_LEN; + iov[3].iov_base = extra; + iov[3].iov_len = elen; + + hmac = l_checksum_new_hmac(L_CHECKSUM_SHA1, k_aut, EAP_SIM_K_AUT_LEN); + l_checksum_updatev(hmac, iov, 4); + /* reuse zero mac array for new mac */ + l_checksum_get_digest(hmac, zero_mac, EAP_SIM_MAC_LEN); + l_checksum_free(hmac); + + if (memcmp(zero_mac, mac_p, EAP_SIM_MAC_LEN)) { + l_error("MAC does not match"); + return false; + } + + return true; +} + +bool eap_sim_tlv_iter_init(struct eap_sim_tlv_iter *iter, const uint8_t *data, + uint32_t len) +{ + iter->data = NULL; + iter->pos = data; + iter->len = 0; + iter->end = data + len; + return true; +} + +bool eap_sim_tlv_iter_next(struct eap_sim_tlv_iter *iter) +{ + /* check room for tag/len */ + if (iter->end - iter->pos < 2) + return false; + + iter->tag = iter->pos[0]; + iter->len = (iter->pos[1] * 4) - 2; + iter->pos += 2; + + /* check room for value */ + if (iter->end - iter->pos < iter->len) + return false; + + iter->data = iter->pos; + iter->pos += iter->len; + + return true; +} + +uint8_t eap_sim_tlv_iter_get_type(struct eap_sim_tlv_iter *iter) +{ + return iter->tag; +} + +uint16_t eap_sim_tlv_iter_get_length(struct eap_sim_tlv_iter *iter) +{ + return iter->len; +} + +const void *eap_sim_tlv_iter_get_data(struct eap_sim_tlv_iter *iter) +{ + return iter->data; +} diff --git a/src/simutil.h b/src/simutil.h new file mode 100644 index 00000000..020c0cf1 --- /dev/null +++ b/src/simutil.h @@ -0,0 +1,208 @@ +/* + * + * Wireless daemon for Linux + * + * Copyright (C) 2017 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 + * + */ + +#include "eap.h" + +/* + * EAP-SIM/EAP-AKA shared values + */ +#define EAP_SIM_MK_LEN 20 +#define EAP_SIM_K_ENCR_LEN 16 +#define EAP_SIM_K_AUT_LEN 16 +#define EAP_SIM_MSK_LEN 64 +#define EAP_SIM_EMSK_LEN 64 +#define EAP_SIM_IV_LEN 16 +#define EAP_SIM_MAC_LEN 16 +#define EAP_SIM_RAND_LEN 16 + +/* + * Possible pad types for EAP-SIM/EAP-AKA attributes + * + * NONE - No padding, data passed in immediately follows type/size + * ZERO - Zero pad where "real" length would be + * LENGTH - A "real" data length in bytes immediately follows type/size + * LENGTH_BITS - A "real" data length in bits follows type/size + */ +#define EAP_SIM_PAD_NONE 0 +#define EAP_SIM_PAD_ZERO 1 +#define EAP_SIM_PAD_LENGTH 2 +#define EAP_SIM_PAD_LENGTH_BITS 3 + +/* + * Round up value to nearest word + */ +#define EAP_SIM_ROUND(x) ((x + 3) & ~0x3) + +struct eap_sim_tlv_iter { + const uint8_t *pos; + const uint8_t *end; + uint8_t tag; + uint16_t len; + const uint8_t *data; +}; + +/* + * RFC 4187, section 11 + */ +enum eap_sim_at { + EAP_SIM_AT_RAND = 0x01, + EAP_SIM_AT_AUTN = 0x02, + EAP_SIM_AT_RES = 0x03, + EAP_SIM_AT_AUTS = 0x04, + EAP_SIM_AT_PADDING = 0x06, + EAP_SIM_AT_NONCE = 0x07, + EAP_SIM_AT_PERMANENT_ID_REQ = 0x0a, + EAP_SIM_AT_MAC = 0x0b, + EAP_SIM_AT_NOTIFICATION = 0x0c, + EAP_SIM_AT_ANY_ID_REQ = 0x0d, + EAP_SIM_AT_IDENTITY = 0x0e, + EAP_SIM_AT_VERSION_LIST = 0x0f, + EAP_SIM_AT_SELECTED_VERSION = 0x10, + EAP_SIM_AT_FULLAUTH_ID_REQ = 0x11, + EAP_SIM_AT_COUNTER = 0x13, + EAP_SIM_AT_NONCE_S = 0x15, + EAP_SIM_AT_CLIENT_ERROR_CODE = 0x16, + EAP_SIM_AT_KDF_INPUT = 0x17, + EAP_SIM_AT_IV = 0x81, + EAP_SIM_AT_ENCR_DATA = 0x82, + EAP_SIM_AT_NEXT_PSEUDONYM = 0x84, + EAP_SIM_AT_NEXT_REAUTH_ID = 0x85, + EAP_SIM_AT_CHECKCODE = 0x86, + EAP_SIM_AT_RESULT_IND = 0x87, + EAP_SIM_AT_BIDDING = 0x88 +}; + +/* + * Possible client error's + */ +enum eap_sim_error { + EAP_SIM_ERROR_PROCESS = 0, + EAP_SIM_ERROR_VERSION_VERSION, + EAP_SIM_ERROR_CHALLENGE, + EAP_SIM_ERROR_RANDS +}; + +/* + * Notification error codes (and success). + */ +enum eap_sim_fail { + EAP_SIM_FAIL_AFTER_AUTH = 0, + EAP_SIM_FAIL_DENIED_ACCESS = 1026, + EAP_SIM_FAIL_NOT_SUBSCRIBED = 1031, + EAP_SIM_FAIL_BEFORE_AUTH = 16384, + EAP_SIM_SUCCESS = 32768 +}; + +/* + * RFC 4186 Appendix B. FIPS 186 Pseudo-random number generator + * + * seed - PRF seed, e.g. the Master Key (MK) + * slen - seed length + * out - PRF output buffer + * olen - length of out + */ +void eap_sim_fips_prf(const void *seed, size_t slen, uint8_t *out, size_t olen); + +/* + * Separate PRNG data into encryption keys. k_encr and k_aut may be NULL in the + * case of fast re-authentication. + * + * buf - output data from the PRNG, 160 bytes + * k_encr - first 16 bytes of buf + * k_aut - next 16 bytes of buf + * msk - next 64 bytes of buf + * emsk - next 64 bytes of buf + */ +bool eap_sim_get_encryption_keys(const uint8_t *buf, uint8_t *k_encr, + uint8_t *k_aut, uint8_t *msk, uint8_t *emsk); + +/* + * Derive a packets MAC. This can be used to compute the packets MAC in place, + * by setting mac to the proper zero'ed location in buf. + * + * buf - the SIM packet, including MAC portion zero'ed, plus extra (e.g. SRES) + * len - the total length of buf + * key - encryption key to use (e.g. K_encr) + * mac - buffer for the 16 byte MAC + */ +bool eap_sim_derive_mac(const uint8_t *buf, size_t len, const uint8_t *key, + uint8_t *mac); + +/* + * Helper to build the EAP packet header + * + * eap - eap_state, used to get the identifier + * method - EAP method (SIM or AKA) + * type - EAP-SIM subtype + * buf - EAP packet + * len - length of packet + */ +size_t eap_sim_build_header(struct eap_state *eap, enum eap_type method, + uint8_t type, uint8_t *buf, uint16_t len); + +/* + * Signal that the client has detected an error + * + * eap - eap_state + * type - type of EAP method (SIM or AKA) + * code - error code to send + */ +void eap_sim_client_error(struct eap_state *eap, enum eap_type type, + uint16_t code); + +/* + * Add an EAP-SIM attribute to a buffer. + * + * buf - pointer to start of EAP-SIM attribute + * attr - type of attribute + * ptype - Padding type AT_PAD_ZERO, AT_PAD_NONE or AT_PAD_LENGTH + * data - EAP-SIM attribute data, if NULL zeros will be written + * dlen - length of data pointer in bytes + * + * Returns the number of bytes written to buf. + */ +size_t eap_sim_add_attribute(uint8_t *buf, enum eap_sim_at attr, + uint8_t ptype, uint8_t *data, uint16_t dlen); + +/* + * Verify a packets MAC + * + * eap - eap_state pointer, used to rebuild the EAP header + * buf - should point to the start of the EAP-SIM packet + * len - length of EAP packet + * extra - Any extra block of data needed to compute the MAC + * elen - Length of 'extra' + */ +bool eap_sim_verify_mac(struct eap_state *eap, enum eap_type type, + const uint8_t *buf, uint16_t len, uint8_t *k_aut, + uint8_t *extra, size_t elen); + +bool eap_sim_tlv_iter_init(struct eap_sim_tlv_iter *iter, const uint8_t *data, + uint32_t len); + +bool eap_sim_tlv_iter_next(struct eap_sim_tlv_iter *iter); + +uint8_t eap_sim_tlv_iter_get_type(struct eap_sim_tlv_iter *iter); + +uint16_t eap_sim_tlv_iter_get_length(struct eap_sim_tlv_iter *iter); + +const void *eap_sim_tlv_iter_get_data(struct eap_sim_tlv_iter *iter);