/* * 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 * Copyright 2003,2006 The FreeRADIUS server project * */ RCSID("$Id$") #include #include #include "../../eap.h" #include "eap_types.h" #include "eap_sim.h" #include "comp128.h" #include 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 */ };