/* * * Wireless daemon for Linux * * Copyright (C) 2017-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 #include "src/missing.h" #include "src/eap.h" #include "src/eap-private.h" #include "src/crypto.h" #include "src/simutil.h" #include "src/simauth.h" #include "src/util.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 /* * 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; /* 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]; /* Save RANDS from AT_RAND attribute for session ID derivation */ uint8_t rands[EAP_SIM_RAND_LEN * 3]; /* Flag set if AT_ANY_ID_REQ was present */ bool any_id_req : 1; /* Flag to indicate protected status indications */ bool protected : 1; uint8_t *chal_pkt; uint32_t pkt_len; struct iwd_sim_auth *auth; unsigned int auth_watch; }; static void eap_sim_clear_secrets(struct eap_sim_handle *sim) { explicit_bzero(sim->mk, sizeof(sim->mk)); explicit_bzero(sim->k_encr, sizeof(sim->k_encr)); explicit_bzero(sim->k_aut, sizeof(sim->k_aut)); explicit_bzero(sim->msk, sizeof(sim->msk)); explicit_bzero(sim->emsk, sizeof(sim->emsk)); } static void eap_sim_free(struct eap_state *eap) { struct eap_sim_handle *sim = eap_get_data(eap); if (sim->auth) sim_auth_unregistered_watch_remove(sim->auth, sim->auth_watch); eap_sim_clear_secrets(sim); l_free(sim->identity); l_free(sim->vlist); 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: /* Actual len (2) + version 1 (2) + padding (2) */ if (length < 6) { l_error("AT_VERSION_LIST was malformed"); goto start_error; } sim->vlist_len = l_get_be16(contents); /* check that attribute was properly padded */ if (length < 2 + align_len(sim->vlist_len, 4)) { l_error("AT_VERSION_LIST was malformed"); goto start_error; } 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_method_respond(eap, response, resp_len); return; start_error: eap_sim_client_error(eap, EAP_TYPE_SIM, EAP_SIM_ERROR_PROCESS); } static void eap_sim_finish(struct eap_state *eap) { struct eap_sim_handle *sim = eap_get_data(eap); uint8_t session_id[1 + sizeof(sim->rands) + EAP_SIM_NONCE_LEN]; session_id[0] = EAP_TYPE_SIM; memcpy(session_id + 1, sim->rands, sizeof(sim->rands)); memcpy(session_id + 1 + sizeof(sim->rands), sim->nonce, EAP_SIM_NONCE_LEN); eap_method_success(eap); eap_set_key_material(eap, sim->msk, 32, sim->emsk, 32, NULL, 0, session_id, sizeof(session_id)); } static void gsm_callback(const uint8_t *sres, const uint8_t *kc, void *user_data) { struct eap_state *eap = user_data; struct eap_sim_handle *sim = eap_get_data(eap); uint16_t resp_len = 8 + 20; uint8_t response[resp_len + 4 + (EAP_SIM_SRES_LEN * 3)]; uint8_t *pos = response; uint8_t prng_buf[160]; uint8_t *mac_pos; bool r; if (!sres || !kc) goto chal_error; if (sim->protected) resp_len += 4; if (!derive_master_key(sim->identity, 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); r = eap_sim_get_encryption_keys(prng_buf, sim->k_encr, sim->k_aut, sim->msk, sim->emsk); explicit_bzero(prng_buf, sizeof(prng_buf)); if (!r) { l_error("could not derive encryption keys"); goto chal_fatal; } if (!eap_sim_verify_mac(eap, EAP_TYPE_SIM, sim->chal_pkt, 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, sres, EAP_SIM_SRES_LEN * 3); pos += EAP_SIM_SRES_LEN * 3; if (!eap_sim_derive_mac(EAP_TYPE_SIM, response, pos - response, sim->k_aut, mac_pos + 4)) { l_error("could not derive MAC"); goto chal_fatal; } l_free(sim->chal_pkt); sim->chal_pkt = NULL; eap_method_respond(eap, response, resp_len); if (!sim->protected) { /* * Result indication not required, we must accept success. */ eap_sim_finish(eap); 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: l_free(sim->chal_pkt); sim->chal_pkt = NULL; 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; 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, EAP_SIM_RAND_LEN * 3); break; case EAP_SIM_AT_RESULT_IND: sim->protected = true; 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 won't get hit */ break; default: l_error("attribute type %u not allowed in Challenge", eap_sim_tlv_iter_get_type(&iter)); goto chal_error; } } sim->chal_pkt = l_memdup(pkt, len); sim->pkt_len = len; if (sim_auth_run_gsm(sim->auth, sim->rands, 3, gsm_callback, eap) < 0) { l_free(sim->chal_pkt); sim->chal_pkt = NULL; goto chal_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_sim_finish(eap); /* * 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(EAP_TYPE_SIM, 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_method_respond(eap, 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 const char *eap_sim_get_identity(struct eap_state *eap) { struct eap_sim_handle *sim = eap_get_data(eap); return sim->identity; } static void auth_destroyed(void *data) { struct eap_state *eap = data; struct eap_sim_handle *sim = eap_get_data(eap); /* * If AKA was already successful we can return. Also if the state * has been set to ERROR, then eap_method_error has already been called, * so we can return. */ if (sim->state == EAP_SIM_STATE_SUCCESS || sim->state == EAP_SIM_STATE_ERROR) return; l_error("auth provider destroyed before SIM could finish"); sim->state = EAP_SIM_STATE_ERROR; eap_method_error(eap); } static int eap_sim_check_settings(struct l_settings *settings, struct l_queue *secrets, const char *prefix, struct l_queue **out_missing) { struct iwd_sim_auth *auth; auth = iwd_sim_auth_find(true, false); if (!auth) { l_debug("No SIM driver available for EAP-SIM"); return -EUNATCH; } if (!iwd_sim_auth_get_nai(auth)) { l_error("SIM driver didn't provide NAI"); return -ENOENT; } return 0; } static bool eap_sim_reset_state(struct eap_state *eap) { struct eap_sim_handle *sim = eap_get_data(eap); sim->state = EAP_SIM_STATE_UNCONNECTED; l_free(sim->vlist); sim->vlist = NULL; l_free(sim->chal_pkt); sim->chal_pkt = NULL; memset(sim->nonce, 0, sizeof(sim->nonce)); eap_sim_clear_secrets(sim); return true; } static bool eap_sim_load_settings(struct eap_state *eap, struct l_settings *settings, const char *prefix) { struct eap_sim_handle *sim; /* * No specific settings for EAP-SIM, the auth provider will have all * required data. */ sim = l_new(struct eap_sim_handle, 1); eap_set_data(eap, sim); sim->auth = iwd_sim_auth_find(true, false); if (!sim->auth) return false; sim->auth_watch = sim_auth_unregistered_watch_add(sim->auth, auth_destroyed, eap); /* * RFC 4186 Section 4.2.1.6 * EAP-SIM identity prefix is '1' */ sim->identity = l_strdup_printf("%c%s", '1', iwd_sim_auth_get_nai(sim->auth)); return true; } static struct eap_method eap_sim = { .request_type = EAP_TYPE_SIM, .exports_msk = true, .name = "SIM", .free = eap_sim_free, .handle_request = eap_sim_handle_request, .check_settings = eap_sim_check_settings, .load_settings = eap_sim_load_settings, .get_identity = eap_sim_get_identity, .reset_state = eap_sim_reset_state }; 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)