3
0
mirror of https://git.kernel.org/pub/scm/network/wireless/iwd.git synced 2024-10-05 19:08:52 +02:00
iwd/src/eap-sim.c
James Prestwood 417367e272 eap-sim: Fix EAP-SIM version list length checks
The AT_VERSION_LIST attribute length was not being properly
checked. The actual length check did not include possible padding
bytes, so align_len() was added to ensure it was padded properly.
The comment about the padding being included in the Master Key
generation was not correct (padding is NOT included), and was removed.
2017-08-22 12:40:22 -05:00

642 lines
16 KiB
C

/*
*
* 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 <config.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <errno.h>
#include <ell/ell.h>
#include "crypto.h"
#include "simutil.h"
#include "util.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:
/* 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_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)