diff options
Diffstat (limited to 'src/charon/sa/authenticators/eap/eap_sim.c')
-rw-r--r-- | src/charon/sa/authenticators/eap/eap_sim.c | 482 |
1 files changed, 400 insertions, 82 deletions
diff --git a/src/charon/sa/authenticators/eap/eap_sim.c b/src/charon/sa/authenticators/eap/eap_sim.c index c9eb5ce8f..90898fb46 100644 --- a/src/charon/sa/authenticators/eap/eap_sim.c +++ b/src/charon/sa/authenticators/eap/eap_sim.c @@ -29,6 +29,21 @@ #define MAX_TRIES 3 +/* number of triplets for one authentication */ +#define TRIPLET_COUNT 3 + +typedef enum sim_subtype_t sim_subtype_t; + +/** + * Subtypes of SIM messages + */ +enum sim_subtype_t { + SIM_START = 10, + SIM_CHALLENGE = 11, + SIM_NOTIFICATION = 12, + SIM_CLIENT_ERROR = 14, +}; + ENUM(sim_subtype_names, SIM_START, SIM_CLIENT_ERROR, "SIM_START", "SIM_CHALLENGE", @@ -37,6 +52,40 @@ ENUM(sim_subtype_names, SIM_START, SIM_CLIENT_ERROR, "SIM_CLIENT_ERROR", ); +typedef enum sim_attribute_t sim_attribute_t; + +/** + * Attributes in SIM messages + */ +enum sim_attribute_t { + /** defines the end of attribute list */ + AT_END = -1, + AT_RAND = 1, + AT_AUTN = 2, + AT_RES = 3, + AT_AUTS = 4, + AT_PADDING = 6, + AT_NONCE_MT = 7, + AT_PERMANENT_ID_REQ = 10, + AT_MAC = 11, + AT_NOTIFICATION = 12, + AT_ANY_ID_REQ = 13, + AT_IDENTITY = 14, + AT_VERSION_LIST = 15, + AT_SELECTED_VERSION = 16, + AT_FULLAUTH_ID_REQ = 17, + AT_COUNTER = 19, + AT_COUNTER_TOO_SMALL = 20, + AT_NONCE_S = 21, + AT_CLIENT_ERROR_CODE = 22, + AT_IV = 129, + AT_ENCR_DATA = 130, + AT_NEXT_PSEUDONYM = 132, + AT_NEXT_REAUTH_ID = 133, + AT_CHECKCODE = 134, + AT_RESULT_IND = 135, +}; + ENUM_BEGIN(sim_attribute_names, AT_END, AT_CLIENT_ERROR_CODE, "AT_END", "AT_0", @@ -96,6 +145,11 @@ struct private_eap_sim_t { sim_algo_t alg; /** + * libraries get_triplet() function returning a triplet + */ + sim_get_triplet_t get_triplet; + + /** * handle of the loaded library */ void *handle; @@ -106,6 +160,16 @@ struct private_eap_sim_t { int tries; /** + * unique EAP identifier + */ + u_int8_t identifier; + + /** + * EAP message type this role sends + */ + u_int8_t type; + + /** * version this implementation uses */ chunk_t version; @@ -121,6 +185,11 @@ struct private_eap_sim_t { chunk_t nonce; /** + * concatenated SRES values + */ + chunk_t sreses; + + /** * k_encr key derived from MK */ chunk_t k_encr; @@ -147,6 +216,10 @@ struct private_eap_sim_t { #define MAC_LEN 16 /** length of the AT_RAND value */ #define RAND_LEN 16 +/** length of Kc */ +#define KC_LEN 8 +/** length of SRES */ +#define SRES_LEN 4 /** length of the k_encr key */ #define KENCR_LEN 16 /** length of the k_auth key */ @@ -156,6 +229,7 @@ struct private_eap_sim_t { /** length of the EMSK */ #define EMSK_LEN 64 +static char version[] = {0x00,0x01}; /* client error codes used in AT_CLIENT_ERROR_CODE */ char client_error_general_buf[] = {0x00, 0x01}; char client_error_unsupported_buf[] = {0x00, 0x02}; @@ -229,7 +303,7 @@ static eap_payload_t *build_payload(private_eap_sim_t *this, u_int8_t identifier chunk_t mac_data = chunk_empty; /* write EAP header, skip length bytes */ - *pos.ptr++ = EAP_RESPONSE; + *pos.ptr++ = this->type; *pos.ptr++ = identifier; pos.ptr += 2; pos.len -= 4; @@ -263,6 +337,7 @@ static eap_payload_t *build_payload(private_eap_sim_t *this, u_int8_t identifier break; } case AT_IDENTITY: + case AT_VERSION_LIST: { u_int16_t act_len = data.len; /* align up to four byte */ @@ -346,8 +421,8 @@ static eap_payload_t *build_payload(private_eap_sim_t *this, u_int8_t identifier /** * process an EAP-SIM/Request/Start message */ -static status_t process_start(private_eap_sim_t *this, eap_payload_t *in, - eap_payload_t **out) +static status_t peer_process_start(private_eap_sim_t *this, eap_payload_t *in, + eap_payload_t **out) { chunk_t message, data; sim_attribute_t attribute, include_id = AT_END; @@ -440,18 +515,56 @@ static status_t process_start(private_eap_sim_t *this, eap_payload_t *in, } /** + * derive EAP keys from kc + */ +static void derive_keys(private_eap_sim_t *this, chunk_t kcs) +{ + chunk_t tmp, mk; + hasher_t *hasher; + prf_t *prf; + int i; + + /* build MK = SHA1(Identity|n*Kc|NONCE_MT|Version List|Selected Version) */ + tmp = chunk_cata("ccccc", this->peer->get_encoding(this->peer), kcs, + this->nonce, this->version_list, this->version); + hasher = hasher_create(HASH_SHA1); + mk = chunk_alloca(hasher->get_hash_size(hasher)); + hasher->get_hash(hasher, tmp, mk.ptr); + hasher->destroy(hasher); + DBG3(DBG_IKE, "MK = SHA1(%B\n) = %B", &tmp, &mk); + + /* K_encr | K_auth | MSK | EMSK = prf() | prf() | prf() | prf() + * FIPS PRF has 320 bit block size, we need 160 byte for keys + * => run prf four times */ + prf = prf_create(PRF_FIPS_SHA1_160); + prf->set_key(prf, mk); + tmp = chunk_alloca(prf->get_block_size(prf) * 4); + for (i = 0; i < 4; i++) + { + prf->get_bytes(prf, chunk_empty, tmp.ptr + tmp.len / 4 * i); + } + prf->destroy(prf); + chunk_free(&this->k_encr); + chunk_free(&this->k_auth); + chunk_free(&this->msk); + chunk_free(&this->emsk); + chunk_split(tmp, "aaaa", KENCR_LEN, &this->k_encr, KAUTH_LEN, &this->k_auth, + MSK_LEN, &this->msk, EMSK_LEN, &this->emsk); + DBG3(DBG_IKE, "K_encr %B\nK_auth %B\nMSK %B\nEMSK %B", + &this->k_encr, &this->k_auth, &this->msk, &this->emsk); +} + +/** * process an EAP-SIM/Request/Challenge message */ -static status_t process_challenge(private_eap_sim_t *this, eap_payload_t *in, - eap_payload_t **out) +static status_t peer_process_challenge(private_eap_sim_t *this, + eap_payload_t *in, eap_payload_t **out) { - chunk_t message, data, tmp, kcs, kc, sreses, sres, mk; + chunk_t message, data, tmp, kcs, kc, sreses, sres; sim_attribute_t attribute; - u_int8_t identifier, i; + u_int8_t identifier; chunk_t mac = chunk_empty, rands = chunk_empty; signer_t *signer; - hasher_t *hasher; - prf_t *prf; if (this->tries-- <= 0) { @@ -541,7 +654,7 @@ static status_t process_challenge(private_eap_sim_t *this, eap_payload_t *in, if (this->alg(rands.ptr, RAND_LEN, sres.ptr, &sres_len, kc.ptr, &kc_len)) { - DBG1(DBG_IKE, "unable to get triplets from SIM"); + DBG1(DBG_IKE, "unable to get EAP-SIM triplet"); *out = build_payload(this, identifier, SIM_CLIENT_ERROR, AT_CLIENT_ERROR_CODE, client_error_general, AT_END); @@ -554,34 +667,7 @@ static status_t process_challenge(private_eap_sim_t *this, eap_payload_t *in, rands = chunk_skip(rands, RAND_LEN); } - /* build MK = SHA1(Identity|n*Kc|NONCE_MT|Version List|Selected Version) */ - tmp = chunk_cata("ccccc", this->peer->get_encoding(this->peer), kcs, - this->nonce, this->version_list, this->version); - hasher = hasher_create(HASH_SHA1); - mk = chunk_alloca(hasher->get_hash_size(hasher)); - hasher->get_hash(hasher, tmp, mk.ptr); - hasher->destroy(hasher); - DBG3(DBG_IKE, "MK = SHA1(%B\n) = %B", &tmp, &mk); - - /* K_encr | K_auth | MSK | EMSK = prf() | prf() | prf() | prf() - * FIPS PRF has 320 bit block size, we need 160 byte for keys - * => run prf four times */ - prf = prf_create(PRF_FIPS_SHA1_160); - prf->set_key(prf, mk); - tmp = chunk_alloca(prf->get_block_size(prf) * 4); - for (i = 0; i < 4; i++) - { - prf->get_bytes(prf, chunk_empty, tmp.ptr + tmp.len / 4 * i); - } - prf->destroy(prf); - chunk_free(&this->k_encr); - chunk_free(&this->k_auth); - chunk_free(&this->msk); - chunk_free(&this->emsk); - chunk_split(tmp, "aaaa", KENCR_LEN, &this->k_encr, KAUTH_LEN, &this->k_auth, - MSK_LEN, &this->msk, EMSK_LEN, &this->emsk); - DBG3(DBG_IKE, "K_encr %B\nK_auth %B\nMSK %B\nEMSK %B", - &this->k_encr, &this->k_auth, &this->msk, &this->emsk); + derive_keys(this, kcs); /* verify AT_MAC attribute, signature is over "EAP packet | NONCE_MT" */ signer = signer_create(AUTH_HMAC_SHA1_128); @@ -606,10 +692,144 @@ static status_t process_challenge(private_eap_sim_t *this, eap_payload_t *in, } /** + * process an EAP-SIM/Response/Challenge message + */ +static status_t server_process_challenge(private_eap_sim_t *this, + eap_payload_t *in, eap_payload_t **out) +{ + chunk_t message, data; + sim_attribute_t attribute; + chunk_t mac = chunk_empty, tmp; + signer_t *signer; + + message = in->get_data(in); + read_header(&message); + + while ((attribute = read_attribute(&message, &data)) != AT_END) + { + switch (attribute) + { + case AT_MAC: + /* MAC has two reserved bytes */ + if (data.len == MAC_LEN + 2) + { /* clone and zero MAC for verification */ + mac = chunk_clonea(chunk_skip(data, 2)); + memset(data.ptr, 0, data.len); + } + break; + default: + DBG1(DBG_IKE, "ignoring EAP_SIM attribute %N", + sim_attribute_names, attribute); + break; + } + } + if (!mac.ptr) + { + DBG1(DBG_IKE, "no valid AT_MAC attribute received"); + return FAILED; + } + /* verify AT_MAC attribute, signature is over "EAP packet | n*SRES" */ + signer = signer_create(AUTH_HMAC_SHA1_128); + signer->set_key(signer, this->k_auth); + tmp = chunk_cata("cc", in->get_data(in), this->sreses); + if (!signer->verify_signature(signer, tmp, mac)) + { + DBG1(DBG_IKE, "AT_MAC verification failed"); + signer->destroy(signer); + return FAILED; + } + signer->destroy(signer); + return SUCCESS; +} + +/** + * process an EAP-SIM/Response/Start message + */ +static status_t server_process_start(private_eap_sim_t *this, + eap_payload_t *in, eap_payload_t **out) +{ + chunk_t message, data; + sim_attribute_t attribute; + bool supported = FALSE; + chunk_t rands, rand, kcs, kc, sreses, sres; + char id[64]; + int len, i, rand_len, kc_len, sres_len; + + message = in->get_data(in); + read_header(&message); + + while ((attribute = read_attribute(&message, &data)) != AT_END) + { + switch (attribute) + { + case AT_NONCE_MT: + if (data.len == NONCE_LEN + 2) + { + this->nonce = chunk_clone(chunk_skip(data, 2)); + } + break; + case AT_SELECTED_VERSION: + if (chunk_equals(data, this->version)) + { + supported = TRUE; + } + break; + default: + DBG1(DBG_IKE, "ignoring EAP_SIM attribute %N", + sim_attribute_names, attribute); + break; + } + } + if (!supported || !this->nonce.ptr) + { + DBG1(DBG_IKE, "received incomplete EAP-SIM/Response/Start"); + return FAILED; + } + len = snprintf(id, sizeof(id), "%D", this->peer); + if (len > sizeof(id) || len < 0) + { + return FAILED; + } + + /* read triplets from provider */ + rand = rands = chunk_alloca(RAND_LEN * TRIPLET_COUNT); + kc = kcs = chunk_alloca(KC_LEN * TRIPLET_COUNT); + sres = sreses = chunk_alloca(SRES_LEN * TRIPLET_COUNT); + rands.len = 0; + kcs.len = 0; + sreses.len = 0; + for (i = 0; i < TRIPLET_COUNT; i++) + { + rand_len = RAND_LEN; + kc_len = KC_LEN; + sres_len = SRES_LEN; + if (this->get_triplet(id, rand.ptr, &rand_len, sres.ptr, &sres_len, + kc.ptr, &kc_len)) + { + DBG1(DBG_IKE, "getting EAP-SIM triplet %d failed", i); + return FAILED; + } + rands.len += rand_len; + kcs.len += kc_len; + sreses.len += sres_len; + rand = chunk_skip(rand, rand_len); + kc = chunk_skip(kc, kc_len); + sres = chunk_skip(sres, sres_len); + } + derive_keys(this, kcs); + + /* build MAC over "EAP packet | NONCE_MT" */ + *out = build_payload(this, this->identifier++, SIM_CHALLENGE, AT_RAND, + rands, AT_MAC, this->nonce, AT_END); + this->sreses = chunk_clone(sreses); + return NEED_MORE; +} + +/** * process an EAP-SIM/Request/Notification message */ -static status_t process_notification(private_eap_sim_t *this, eap_payload_t *in, - eap_payload_t **out) +static status_t peer_process_notification(private_eap_sim_t *this, + eap_payload_t *in, eap_payload_t **out) { chunk_t message, data; sim_attribute_t attribute; @@ -656,12 +876,44 @@ static status_t process_notification(private_eap_sim_t *this, eap_payload_t *in, return NEED_MORE; } +/** + * Process a client error + */ +static status_t server_process_client_error(private_eap_sim_t *this, + eap_payload_t *in, eap_payload_t **out) +{ + chunk_t message, data; + sim_attribute_t attribute; + + message = in->get_data(in); + read_header(&message); + + while ((attribute = read_attribute(&message, &data)) != AT_END) + { + if (attribute == AT_CLIENT_ERROR_CODE) + { + u_int16_t code = 0; + if (data.len == 2) + { + code = ntohs(*(u_int16_t*)data.ptr); + } + DBG1(DBG_IKE, "received %N error %d", + sim_attribute_names, attribute, code); + } + else + { + DBG1(DBG_IKE, "ignoring EAP_SIM attribute %N", + sim_attribute_names, attribute); + } + } + return FAILED; +} /** * Implementation of eap_method_t.process for the peer */ -static status_t process(private_eap_sim_t *this, - eap_payload_t *in, eap_payload_t **out) +static status_t peer_process(private_eap_sim_t *this, + eap_payload_t *in, eap_payload_t **out) { sim_subtype_t type; chunk_t message; @@ -672,11 +924,11 @@ static status_t process(private_eap_sim_t *this, switch (type) { case SIM_START: - return process_start(this, in, out); + return peer_process_start(this, in, out); case SIM_CHALLENGE: - return process_challenge(this, in, out); + return peer_process_challenge(this, in, out); case SIM_NOTIFICATION: - return process_notification(this, in, out); + return peer_process_notification(this, in, out); default: DBG1(DBG_IKE, "unable to process EAP_SIM subtype %N", sim_subtype_names, type); @@ -687,15 +939,55 @@ static status_t process(private_eap_sim_t *this, } /** + * Implementation of eap_method_t.process for the server + */ +static status_t server_process(private_eap_sim_t *this, + eap_payload_t *in, eap_payload_t **out) +{ + sim_subtype_t type; + chunk_t message; + + message = in->get_data(in); + type = read_header(&message); + + switch (type) + { + case SIM_START: + return server_process_start(this, in, out); + case SIM_CHALLENGE: + return server_process_challenge(this, in, out); + case SIM_CLIENT_ERROR: + return server_process_client_error(this, in, out); + default: + DBG1(DBG_IKE, "unable to process EAP_SIM subtype %N", + sim_subtype_names, type); + return FAILED; + } +} + +/** * Implementation of eap_method_t.initiate for the peer */ -static status_t initiate(private_eap_sim_t *this, eap_payload_t **out) +static status_t peer_initiate(private_eap_sim_t *this, eap_payload_t **out) { /* peer never initiates */ return FAILED; } /** + * Implementation of eap_method_t.initiate for the server + */ +static status_t server_initiate(private_eap_sim_t *this, eap_payload_t **out) +{ + /* version_list to derive MK, no padding */ + this->version_list = chunk_clone(this->version); + /* build_payloads adds padding itself */ + *out = build_payload(this, this->identifier++, SIM_START, + AT_VERSION_LIST, this->version, AT_END); + return NEED_MORE; +} + +/** * Implementation of eap_method_t.get_type. */ static eap_type_t get_type(private_eap_sim_t *this, u_int32_t *vendor) @@ -732,6 +1024,7 @@ static void destroy(private_eap_sim_t *this) { dlclose(this->handle); chunk_free(&this->nonce); + chunk_free(&this->sreses); chunk_free(&this->version_list); chunk_free(&this->k_auth); chunk_free(&this->k_encr); @@ -748,60 +1041,85 @@ eap_sim_t *eap_create(eap_role_t role, { private_eap_sim_t *this; randomizer_t *randomizer; - static char version[] = {0x00,0x01}; - - if (role != EAP_PEER) - { - return NULL; - } - this = malloc_thing(private_eap_sim_t); + void *symbol; + char *name; + + this = malloc_thing(private_eap_sim_t); + this->alg = NULL; + this->get_triplet = NULL; + this->nonce = chunk_empty; + this->sreses = chunk_empty; + this->peer = peer; + this->tries = MAX_TRIES; + this->version.ptr = version; + this->version.len = sizeof(version); + this->version_list = chunk_empty; + this->k_auth = chunk_empty; + this->k_encr = chunk_empty; + this->msk = chunk_empty; + this->emsk = chunk_empty; + this->identifier = random(); this->handle = dlopen(SIM_READER_LIB, RTLD_LAZY); if (this->handle == NULL) { - DBG1(DBG_IKE, "unable to open SIM reader '%s'", SIM_READER_LIB); + DBG1(DBG_IKE, "unable to open SIM reader '%s'", SIM_READER_LIB); free(this); return NULL; } - this->alg = dlsym(this->handle, SIM_READER_ALG); - if (this->alg == NULL) + switch (role) { - DBG1(DBG_IKE, "unable to open SIM reader function '%s' in '%s'", - SIM_READER_ALG, SIM_READER_LIB); + case EAP_PEER: + name = SIM_READER_ALG; + break; + case EAP_SERVER: + name = SIM_READER_GET_TRIPLET; + break; + default: + free(this); + return NULL; + } + symbol = dlsym(this->handle, name); + if (symbol == NULL) + { + DBG1(DBG_IKE, "unable to open SIM function '%s' in '%s'", + name, SIM_READER_LIB); dlclose(this->handle); free(this); return NULL; } - - randomizer = randomizer_create(); - if (randomizer->allocate_pseudo_random_bytes(randomizer, NONCE_LEN, - &this->nonce)) + switch (role) { - DBG1(DBG_IKE, "unable to generate NONCE for EAP_SIM"); - randomizer->destroy(randomizer); - free(this); - return NULL; + case EAP_SERVER: + this->public.eap_method_interface.initiate = (status_t(*)(eap_method_t*,eap_payload_t**))server_initiate; + this->public.eap_method_interface.process = (status_t(*)(eap_method_t*,eap_payload_t*,eap_payload_t**))server_process; + this->get_triplet = symbol; + this->type = EAP_REQUEST; + break; + case EAP_PEER: + this->public.eap_method_interface.initiate = (status_t(*)(eap_method_t*,eap_payload_t**))peer_initiate; + this->public.eap_method_interface.process = (status_t(*)(eap_method_t*,eap_payload_t*,eap_payload_t**))peer_process; + this->alg = symbol; + this->type = EAP_RESPONSE; + randomizer = randomizer_create(); + if (randomizer->allocate_pseudo_random_bytes(randomizer, NONCE_LEN, + &this->nonce)) + { + DBG1(DBG_IKE, "unable to generate NONCE for EAP_SIM"); + randomizer->destroy(randomizer); + free(this); + return NULL; + } + randomizer->destroy(randomizer); + break; + default: + free(this); + return NULL; } - randomizer->destroy(randomizer); - - /* public functions */ - this->public.eap_method_interface.initiate = (status_t(*)(eap_method_t*,eap_payload_t**))initiate; - this->public.eap_method_interface.process = (status_t(*)(eap_method_t*,eap_payload_t*,eap_payload_t**))process; this->public.eap_method_interface.get_type = (eap_type_t(*)(eap_method_t*,u_int32_t*))get_type; this->public.eap_method_interface.is_mutual = (bool(*)(eap_method_t*))is_mutual; this->public.eap_method_interface.get_msk = (status_t(*)(eap_method_t*,chunk_t*))get_msk; this->public.eap_method_interface.destroy = (void(*)(eap_method_t*))destroy; - /* private data */ - this->peer = peer; - this->tries = MAX_TRIES; - this->version.ptr = version; - this->version.len = sizeof(version); - this->version_list = chunk_empty; - this->k_auth = chunk_empty; - this->k_encr = chunk_empty; - this->msk = chunk_empty; - this->emsk = chunk_empty; - return &this->public; } |