diff options
Diffstat (limited to 'src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c')
-rw-r--r-- | src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c b/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c new file mode 100644 index 0000000..38fe997 --- /dev/null +++ b/src/modules/rlm_eap/types/rlm_eap_sim/rlm_eap_sim.c @@ -0,0 +1,697 @@ +/* + * rlm_eap_sim.c Handles that are called from eap for SIM + * + * The development of the EAP/SIM support was funded by Internet Foundation + * Austria (http://www.nic.at/ipa). + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2003 Michael Richardson <mcr@sandelman.ottawa.on.ca> + * Copyright 2003,2006 The FreeRADIUS server project + * + */ + +RCSID("$Id$") + +#include <stdio.h> +#include <stdlib.h> + +#include "../../eap.h" +#include "eap_types.h" +#include "eap_sim.h" +#include "comp128.h" + +#include <freeradius-devel/rad_assert.h> + +typedef struct eap_sim_server_state { + enum eapsim_serverstates state; + struct eapsim_keys keys; + int sim_id; +} eap_sim_state_t; + +static int eap_sim_sendstart(eap_handler_t *handler) +{ + VALUE_PAIR **vps, *newvp; + uint16_t words[3]; + eap_sim_state_t *ess; + RADIUS_PACKET *packet; + uint8_t *p; + + rad_assert(handler->request != NULL); + rad_assert(handler->request->reply); + + ess = (eap_sim_state_t *)handler->opaque; + + /* these are the outgoing attributes */ + packet = handler->request->reply; + vps = &packet->vps; + rad_assert(vps != NULL); + + + /* + * Add appropriate TLVs for the EAP things we wish to send. + */ + + /* the version list. We support only version 1. */ + words[0] = htons(sizeof(words[1])); + words[1] = htons(EAP_SIM_VERSION); + words[2] = 0; + + newvp = fr_pair_afrom_num(packet, PW_EAP_SIM_VERSION_LIST, 0); + fr_pair_value_memcpy(newvp, (uint8_t const *) words, sizeof(words)); + + fr_pair_add(vps, newvp); + + /* set the EAP_ID - new value */ + newvp = fr_pair_afrom_num(packet, PW_EAP_ID, 0); + newvp->vp_integer = ess->sim_id++; + fr_pair_replace(vps, newvp); + + /* record it in the ess */ + ess->keys.versionlistlen = 2; + memcpy(ess->keys.versionlist, words + 1, ess->keys.versionlistlen); + + /* the ANY_ID attribute. We do not support re-auth or pseudonym */ + newvp = fr_pair_afrom_num(packet, PW_EAP_SIM_FULLAUTH_ID_REQ, 0); + newvp->vp_length = 2; + newvp->vp_octets = p = talloc_array(newvp, uint8_t, 2); + + p[0] = 0; + p[0] = 1; + fr_pair_add(vps, newvp); + + /* the SUBTYPE, set to start. */ + newvp = fr_pair_afrom_num(packet, PW_EAP_SIM_SUBTYPE, 0); + newvp->vp_integer = EAPSIM_START; + fr_pair_replace(vps, newvp); + + return 1; +} + +static int eap_sim_get_challenge(eap_handler_t *handler, VALUE_PAIR *vps, int idx, eap_sim_state_t *ess) +{ + REQUEST *request = handler->request; + VALUE_PAIR *vp, *ki, *algo_version; + + rad_assert(idx >= 0 && idx < 3); + + /* + * Generate a new RAND value, and derive Kc and SRES from Ki + */ + ki = fr_pair_find_by_num(vps, PW_EAP_SIM_KI, 0, TAG_ANY); + if (ki) { + int i; + + /* + * Check to see if have a Ki for the IMSI, this allows us to generate the rest + * of the triplets. + */ + algo_version = fr_pair_find_by_num(vps, PW_EAP_SIM_ALGO_VERSION, 0, TAG_ANY); + if (!algo_version) { + REDEBUG("Found Ki, but missing EAP-Sim-Algo-Version"); + return 0; + } + + for (i = 0; i < EAPSIM_RAND_SIZE; i++) { + ess->keys.rand[idx][i] = fr_rand(); + } + + switch (algo_version->vp_integer) { + case 1: + comp128v1(ess->keys.sres[idx], ess->keys.Kc[idx], ki->vp_octets, ess->keys.rand[idx]); + break; + + case 2: + comp128v23(ess->keys.sres[idx], ess->keys.Kc[idx], ki->vp_octets, ess->keys.rand[idx], + true); + break; + + case 3: + comp128v23(ess->keys.sres[idx], ess->keys.Kc[idx], ki->vp_octets, ess->keys.rand[idx], + false); + break; + + case 4: + REDEBUG("Comp128-4 algorithm is not supported as details have not yet been published. " + "If you have details of this algorithm please contact the FreeRADIUS " + "maintainers"); + return 0; + + default: + REDEBUG("Unknown/unsupported algorithm Comp128-%i", algo_version->vp_integer); + } + + if (RDEBUG_ENABLED2) { + char buffer[33]; /* 32 hexits (16 bytes) + 1 */ + char *p; + + RDEBUG2("Generated following triplets for round %i:", idx); + + RINDENT(); + p = buffer; + for (i = 0; i < EAPSIM_RAND_SIZE; i++) { + p += sprintf(p, "%02x", ess->keys.rand[idx][i]); + } + RDEBUG2("RAND : 0x%s", buffer); + + p = buffer; + for (i = 0; i < EAPSIM_SRES_SIZE; i++) { + p += sprintf(p, "%02x", ess->keys.sres[idx][i]); + } + RDEBUG2("SRES : 0x%s", buffer); + + p = buffer; + for (i = 0; i < EAPSIM_KC_SIZE; i++) { + p += sprintf(p, "%02x", ess->keys.Kc[idx][i]); + } + RDEBUG2("Kc : 0x%s", buffer); + REXDENT(); + } + return 1; + } + + /* + * Use known RAND, SRES, and Kc values, these may of been pulled in from an AuC, + * or created by sending challenges to the SIM directly. + */ + vp = fr_pair_find_by_num(vps, PW_EAP_SIM_RAND1 + idx, 0, TAG_ANY); + /* Hack for backwards compatibility */ + if (!vp) { + vp = fr_pair_find_by_num(request->reply->vps, PW_EAP_SIM_RAND1 + idx, 0, TAG_ANY); + } + if (!vp) { + /* bad, we can't find stuff! */ + REDEBUG("EAP-SIM-RAND%i not found", idx + 1); + return 0; + } + if (vp->vp_length != EAPSIM_RAND_SIZE) { + REDEBUG("EAP-SIM-RAND%i is not " STRINGIFY(EAPSIM_RAND_SIZE) " bytes, got %zu bytes", + idx + 1, vp->vp_length); + return 0; + } + memcpy(ess->keys.rand[idx], vp->vp_strvalue, EAPSIM_RAND_SIZE); + + vp = fr_pair_find_by_num(vps, PW_EAP_SIM_SRES1 + idx, 0, TAG_ANY); + /* Hack for backwards compatibility */ + if (!vp) { + vp = fr_pair_find_by_num(request->reply->vps, PW_EAP_SIM_SRES1 + idx, 0, TAG_ANY); + } + if (!vp) { + /* bad, we can't find stuff! */ + REDEBUG("EAP-SIM-SRES%i not found", idx + 1); + return 0; + } + + if (vp->vp_length != EAPSIM_SRES_SIZE) { + REDEBUG("EAP-SIM-SRES%i is not " STRINGIFY(EAPSIM_SRES_SIZE) " bytes, got %zu bytes", + idx + 1, vp->vp_length); + return 0; + } + memcpy(ess->keys.sres[idx], vp->vp_strvalue, EAPSIM_SRES_SIZE); + + vp = fr_pair_find_by_num(vps, PW_EAP_SIM_KC1 + idx, 0, TAG_ANY); + /* Hack for backwards compatibility */ + if (!vp) { + vp = fr_pair_find_by_num(request->reply->vps, PW_EAP_SIM_KC1 + idx, 0, TAG_ANY); + } + + if (!vp) { + /* bad, we can't find stuff! */ + REDEBUG("EAP-SIM-Kc%i not found", idx + 1); + return 0; + } + if (vp->vp_length != EAPSIM_KC_SIZE) { + REDEBUG("EAP-SIM-Kc%i is not " STRINGIFY(EAPSIM_KC_SIZE) " bytes, got %zu bytes", + idx + 1, vp->vp_length); + return 0; + } + memcpy(ess->keys.Kc[idx], vp->vp_strvalue, EAPSIM_KC_SIZE); + + return 1; +} + +/** Send the challenge itself + * + * Challenges will come from one of three places eventually: + * + * 1 from attributes like PW_EAP_SIM_RANDx + * (these might be retrieved from a database) + * + * 2 from internally implemented SIM authenticators + * (a simple one based upon XOR will be provided) + * + * 3 from some kind of SS7 interface. + * + * For now, they only come from attributes. + * It might be that the best way to do 2/3 will be with a different + * module to generate/calculate things. + * + */ +static int eap_sim_sendchallenge(eap_handler_t *handler) +{ + REQUEST *request = handler->request; + eap_sim_state_t *ess; + VALUE_PAIR **invps, **outvps, *newvp; + RADIUS_PACKET *packet; + uint8_t *p; + + ess = (eap_sim_state_t *)handler->opaque; + rad_assert(handler->request != NULL); + rad_assert(handler->request->reply); + + /* + * Invps is the data from the client but this is for non-protocol data here. + * We should already have consumed any client originated data. + */ + invps = &handler->request->packet->vps; + + /* + * Outvps is the data to the client + */ + packet = handler->request->reply; + outvps = &packet->vps; + + if (RDEBUG_ENABLED2) { + RDEBUG2("EAP-SIM decoded packet"); + rdebug_pair_list(L_DBG_LVL_2, request, *invps, NULL); + } + + /* + * Okay, we got the challenges! Put them into an attribute. + */ + newvp = fr_pair_afrom_num(packet, PW_EAP_SIM_RAND, 0); + newvp->vp_length = 2 + (EAPSIM_RAND_SIZE * 3); + newvp->vp_octets = p = talloc_array(newvp, uint8_t, newvp->vp_length); + + memset(p, 0, 2); /* clear reserved bytes */ + p += 2; + memcpy(p, ess->keys.rand[0], EAPSIM_RAND_SIZE); + p += EAPSIM_RAND_SIZE; + memcpy(p, ess->keys.rand[1], EAPSIM_RAND_SIZE); + p += EAPSIM_RAND_SIZE; + memcpy(p, ess->keys.rand[2], EAPSIM_RAND_SIZE); + fr_pair_add(outvps, newvp); + + /* + * Set the EAP_ID - new value + */ + newvp = fr_pair_afrom_num(packet, PW_EAP_ID, 0); + newvp->vp_integer = ess->sim_id++; + fr_pair_replace(outvps, newvp); + + /* + * Make a copy of the identity + */ + ess->keys.identitylen = strlen(handler->identity); + memcpy(ess->keys.identity, handler->identity, ess->keys.identitylen); + + /* + * Use the SIM identity, if available + */ + newvp = fr_pair_find_by_num(*invps, PW_EAP_SIM_IDENTITY, 0, TAG_ANY); + if (newvp && newvp->vp_length > 2) { + uint16_t len; + + memcpy(&len, newvp->vp_octets, sizeof(uint16_t)); + len = ntohs(len); + if (len <= newvp->vp_length - 2 && len <= MAX_STRING_LEN) { + ess->keys.identitylen = len; + memcpy(ess->keys.identity, newvp->vp_octets + 2, ess->keys.identitylen); + } + } + + /* + * All set, calculate keys! + */ + eapsim_calculate_keys(&ess->keys); + +#ifdef EAP_SIM_DEBUG_PRF + eapsim_dump_mk(&ess->keys); +#endif + + /* + * Need to include an AT_MAC attribute so that it will get + * calculated. The NONCE_MT and the MAC are both 16 bytes, so + * We store the NONCE_MT in the MAC for the encoder, which + * will pull it out before it does the operation. + */ + newvp = fr_pair_afrom_num(packet, PW_EAP_SIM_MAC, 0); + fr_pair_value_memcpy(newvp, ess->keys.nonce_mt, 16); + fr_pair_replace(outvps, newvp); + + newvp = fr_pair_afrom_num(packet, PW_EAP_SIM_KEY, 0); + fr_pair_value_memcpy(newvp, ess->keys.K_aut, 16); + fr_pair_replace(outvps, newvp); + + /* the SUBTYPE, set to challenge. */ + newvp = fr_pair_afrom_num(packet, PW_EAP_SIM_SUBTYPE, 0); + newvp->vp_integer = EAPSIM_CHALLENGE; + fr_pair_replace(outvps, newvp); + + return 1; +} + +#ifndef EAPTLS_MPPE_KEY_LEN +#define EAPTLS_MPPE_KEY_LEN 32 +#endif + +/* + * this code sends the success message. + * + * the only work to be done is the add the appropriate SEND/RECV + * radius attributes derived from the MSK. + * + */ +static int eap_sim_sendsuccess(eap_handler_t *handler) +{ + unsigned char *p; + eap_sim_state_t *ess; + VALUE_PAIR *vp; + RADIUS_PACKET *packet; + + /* outvps is the data to the client. */ + packet = handler->request->reply; + ess = (eap_sim_state_t *)handler->opaque; + + /* set the EAP_ID - new value */ + vp = fr_pair_afrom_num(packet, PW_EAP_ID, 0); + vp->vp_integer = ess->sim_id++; + fr_pair_replace(&handler->request->reply->vps, vp); + + p = ess->keys.msk; + eap_add_reply(handler->request, "MS-MPPE-Recv-Key", p, EAPTLS_MPPE_KEY_LEN); + p += EAPTLS_MPPE_KEY_LEN; + eap_add_reply(handler->request, "MS-MPPE-Send-Key", p, EAPTLS_MPPE_KEY_LEN); + + return 1; +} + + +/** Run the server state machine + * + */ +static void eap_sim_state_enter(REQUEST *request, eap_handler_t *handler, + eap_sim_state_t *ess, + enum eapsim_serverstates newstate) +{ + switch (newstate) { + /* + * Send the EAP-SIM Start message, listing the versions that we support. + */ + case EAPSIM_SERVER_START: + eap_sim_sendstart(handler); + break; + /* + * Send the EAP-SIM Challenge message. + */ + case EAPSIM_SERVER_CHALLENGE: + eap_sim_sendchallenge(handler); + break; + + /* + * Send the EAP Success message + */ + case EAPSIM_SERVER_SUCCESS: + eap_sim_sendsuccess(handler); + handler->eap_ds->request->code = PW_EAP_SUCCESS; + break; + /* + * Nothing to do for this transition. + */ + default: + + break; + } + + ess->state = newstate; + + /* build the target packet */ + /* we will set the ID on requests, since we have to HMAC it */ + handler->eap_ds->set_request_id = 1; + + if (!map_eapsim_basictypes(handler->request->reply, + handler->eap_ds->request)) { + REDEBUG("Failed encoding EAP-SIM packet"); + } +} + +/* + * Initiate the EAP-SIM session by starting the state machine + * and initiating the state. + */ +static int mod_session_init(UNUSED void *instance, eap_handler_t *handler) +{ + REQUEST *request = handler->request; + eap_sim_state_t *ess; + time_t n; + + ess = talloc_zero(handler, eap_sim_state_t); + if (!ess) { + RDEBUG2("No space for EAP-SIM state"); + return 0; + } + + handler->opaque = ess; + handler->stage = PROCESS; + + /* + * Save the keying material, because it could change on a subsequent retrival. + */ + if (!eap_sim_get_challenge(handler, request->config, 0, ess) || + !eap_sim_get_challenge(handler, request->config, 1, ess) || + !eap_sim_get_challenge(handler, request->config, 2, ess)) { + return 0; /* already printed error */ + } + + /* + * This value doesn't have be strong, but it is good if it is different now and then. + */ + time(&n); + ess->sim_id = (n & 0xff); + + eap_sim_state_enter(request, handler, ess, EAPSIM_SERVER_START); + + return 1; +} + + +/** Process an EAP-Sim/Response/Start + * + * Verify that client chose a version, and provided a NONCE_MT, + * and if so, then change states to challenge, and send the new + * challenge, else, resend the Request/Start. + */ +static int process_eap_sim_start(eap_handler_t *handler, VALUE_PAIR *vps) +{ + REQUEST *request = handler->request; + VALUE_PAIR *nonce_vp, *selectedversion_vp; + eap_sim_state_t *ess; + uint16_t simversion; + ess = (eap_sim_state_t *)handler->opaque; + + nonce_vp = fr_pair_find_by_num(vps, PW_EAP_SIM_NONCE_MT, 0, TAG_ANY); + selectedversion_vp = fr_pair_find_by_num(vps, PW_EAP_SIM_SELECTED_VERSION, 0, TAG_ANY); + if (!nonce_vp || !selectedversion_vp) { + RDEBUG2("Client did not select a version and send a NONCE"); + eap_sim_state_enter(request, handler, ess, EAPSIM_SERVER_START); + return 1; + } + + /* + * Okay, good got stuff that we need. Check the version we found. + */ + if (selectedversion_vp->vp_length < 2) { + REDEBUG("EAP-SIM version field is too short"); + return 0; + } + memcpy(&simversion, selectedversion_vp->vp_strvalue, sizeof(simversion)); + simversion = ntohs(simversion); + if(simversion != EAP_SIM_VERSION) { + REDEBUG("EAP-SIM version %i is unknown", simversion); + return 0; + } + + /* + * Record it for later keying + */ + memcpy(ess->keys.versionselect, selectedversion_vp->vp_strvalue, sizeof(ess->keys.versionselect)); + + /* + * Double check the nonce size. + */ + if(nonce_vp->vp_length != 18) { + REDEBUG("EAP-SIM nonce_mt must be 16 bytes (+2 bytes padding), not %zu", nonce_vp->vp_length); + return 0; + } + memcpy(ess->keys.nonce_mt, nonce_vp->vp_strvalue + 2, 16); + + /* + * Everything looks good, change states + */ + eap_sim_state_enter(request, handler, ess, EAPSIM_SERVER_CHALLENGE); + + return 1; +} + + +/** Process an EAP-Sim/Response/Challenge + * + * Verify that MAC that we received matches what we would have + * calculated from the packet with the SRESx appended. + * + */ +static int process_eap_sim_challenge(eap_handler_t *handler, VALUE_PAIR *vps) +{ + REQUEST *request = handler->request; + eap_sim_state_t *ess = handler->opaque; + + uint8_t srescat[EAPSIM_SRES_SIZE * 3]; + uint8_t *p = srescat; + + uint8_t calcmac[EAPSIM_CALCMAC_SIZE]; + + memcpy(p, ess->keys.sres[0], EAPSIM_SRES_SIZE); + p += EAPSIM_SRES_SIZE; + memcpy(p, ess->keys.sres[1], EAPSIM_SRES_SIZE); + p += EAPSIM_SRES_SIZE; + memcpy(p, ess->keys.sres[2], EAPSIM_SRES_SIZE); + + /* + * Verify the MAC, now that we have all the keys + */ + if (eapsim_checkmac(handler, vps, ess->keys.K_aut, srescat, sizeof(srescat), calcmac)) { + RDEBUG2("MAC check succeed"); + } else { + int i, j; + char macline[20*3]; + char *m = macline; + + j=0; + for (i = 0; i < EAPSIM_CALCMAC_SIZE; i++) { + if(j==4) { + *m++ = '_'; + j=0; + } + j++; + + sprintf(m, "%02x", calcmac[i]); + m = m + strlen(m); + } + REDEBUG("Calculated MAC (%s) did not match", macline); + return 0; + } + + /* everything looks good, change states */ + eap_sim_state_enter(request, handler, ess, EAPSIM_SERVER_SUCCESS); + return 1; +} + + +/** Authenticate a previously sent challenge + * + */ +static int mod_process(UNUSED void *arg, eap_handler_t *handler) +{ + REQUEST *request = handler->request; + eap_sim_state_t *ess = handler->opaque; + + VALUE_PAIR *vp, *vps; + + enum eapsim_subtype subtype; + + int success; + + /* + * VPS is the data from the client + */ + vps = handler->request->packet->vps; + + success = unmap_eapsim_basictypes(handler->request->packet, + handler->eap_ds->response->type.data, + handler->eap_ds->response->type.length); + + if (!success) { + REDEBUG("Failed decoding EAP-SIM packet: %s", fr_strerror()); + return 0; + } + + /* + * See what kind of message we have gotten + */ + vp = fr_pair_find_by_num(vps, PW_EAP_SIM_SUBTYPE, 0, TAG_ANY); + if (!vp) { + REDEBUG2("No subtype attribute was created, message dropped"); + return 0; + } + subtype = vp->vp_integer; + + /* + * Client error supersedes anything else. + */ + if (subtype == EAPSIM_CLIENT_ERROR) { + return 0; + } + + switch (ess->state) { + case EAPSIM_SERVER_START: + switch (subtype) { + /* + * Pretty much anything else here is illegal, so we will retransmit the request. + */ + default: + + eap_sim_state_enter(request, handler, ess, EAPSIM_SERVER_START); + return 1; + /* + * A response to our EAP-Sim/Request/Start! + */ + case EAPSIM_START: + return process_eap_sim_start(handler, vps); + } + + case EAPSIM_SERVER_CHALLENGE: + switch (subtype) { + /* + * Pretty much anything else here is illegal, so we will retransmit the request. + */ + default: + eap_sim_state_enter(request, handler, ess, EAPSIM_SERVER_CHALLENGE); + return 1; + /* + * A response to our EAP-Sim/Request/Challenge! + */ + case EAPSIM_CHALLENGE: + return process_eap_sim_challenge(handler, vps); + } + + default: + rad_assert(0 == 1); + } + + return 0; +} + +/* + * The module name should be the only globally exported symbol. + * That is, everything else should be 'static'. + */ +extern rlm_eap_module_t rlm_eap_sim; +rlm_eap_module_t rlm_eap_sim = { + .name = "eap_sim", + .session_init = mod_session_init, /* Initialise a new EAP session */ + .process = mod_process, /* Process next round of EAP method */ +}; |