/* * Copyright (C) 2006 Martin Willi * Hochschule fuer Technik Rapperswil * * 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. See . * * 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. * * $Id: eap_aka.c 4628 2008-11-11 15:19:13Z martin $ */ /* The EAP-AKA method uses it's own simple parser for processing EAP-AKA * payloads, as the IKEv2 parser is not suitable for that job. There are * two simple methods for parsing payloads, read_header() and read_attribute(). * Every EAP-AKA payload consists of a header and a list of attributes. Those * functions mentioned read the data and return the type of the found * attribute/EAP-AKA-type. For generating a EAP-AKA message, we have a * build_aka_payload(), which builds the whole message from a variable * argument list containing its attributes. * The processing of messages is split up in various functions: * - peer_process() - General processing multiplexer for the peer * - peer_process_challenge() - Specific AKA-Challenge processor * - peer_process_notification() - Processing of AKA-Notification * - server_process() - General processing multiplexer for the server * - peer_process_challenge() - Processing of a received Challenge response * - peer_process_synchronize() - Process a sequence number synchronization * - server_initiate() - Initiation method for the server, calls * - server_initiate_challenge() - Initiation of AKA-Challenge */ #include #include #include #include #include #include #include "eap_aka.h" #include #include #include /* Use test vectors specified in S.S0055 #define TEST_VECTORS */ #define RAND_LENGTH 16 #define RES_LENGTH 16 #define SQN_LENGTH 6 #define K_LENGTH 16 #define MAC_LENGTH 8 #define CK_LENGTH 16 #define IK_LENGTH 16 #define AK_LENGTH 6 #define AMF_LENGTH 2 #define FMK_LENGTH 4 #define AUTN_LENGTH (SQN_LENGTH + AMF_LENGTH + MAC_LENGTH) #define AUTS_LENGTH (SQN_LENGTH + MAC_LENGTH) #define PAYLOAD_LENGTH 64 #define MK_LENGTH 20 #define MSK_LENGTH 64 #define EMSK_LENGTH 64 #define KAUTH_LENGTH 16 #define KENCR_LENGTH 16 #define AT_MAC_LENGTH 16 #define F1 0x42 #define F1STAR 0x43 #define F2 0x44 #define F3 0x45 #define F4 0x46 #define F5 0x47 #define F5STAR 0x48 typedef enum aka_subtype_t aka_subtype_t; typedef enum aka_attribute_t aka_attribute_t; /** * Subtypes of AKA messages */ enum aka_subtype_t { AKA_CHALLENGE = 1, AKA_AUTHENTICATION_REJECT = 2, AKA_SYNCHRONIZATION_FAILURE = 4, AKA_IDENTITY = 5, AKA_NOTIFICATION = 12, AKA_REAUTHENTICATION = 13, AKA_CLIENT_ERROR = 14, }; /** * Attribute types in AKA messages */ enum aka_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(aka_subtype_names, AKA_CHALLENGE, AKA_IDENTITY, "AKA_CHALLENGE", "AKA_AUTHENTICATION_REJECT", "AKA_3", "AKA_SYNCHRONIZATION_FAILURE", "AKA_IDENTITY"); ENUM_NEXT(aka_subtype_names, AKA_NOTIFICATION, AKA_CLIENT_ERROR, AKA_IDENTITY, "AKA_NOTIFICATION", "AKA_REAUTHENTICATION", "AKA_CLIENT_ERROR"); ENUM_END(aka_subtype_names, AKA_CLIENT_ERROR); ENUM_BEGIN(aka_attribute_names, AT_END, AT_CLIENT_ERROR_CODE, "AT_END", "AT_0", "AT_RAND", "AT_AUTN", "AT_RES", "AT_AUTS", "AT_5", "AT_PADDING", "AT_NONCE_MT", "AT_8", "AT_9", "AT_PERMANENT_ID_REQ", "AT_MAC", "AT_NOTIFICATION", "AT_ANY_ID_REQ", "AT_IDENTITY", "AT_VERSION_LIST", "AT_SELECTED_VERSION", "AT_FULLAUTH_ID_REQ", "AT_18", "AT_COUNTER", "AT_COUNTER_TOO_SMALL", "AT_NONCE_S", "AT_CLIENT_ERROR_CODE"); ENUM_NEXT(aka_attribute_names, AT_IV, AT_RESULT_IND, AT_CLIENT_ERROR_CODE, "AT_IV", "AT_ENCR_DATA", "AT_131", "AT_NEXT_PSEUDONYM", "AT_NEXT_REAUTH_ID", "AT_CHECKCODE", "AT_RESULT_IND"); ENUM_END(aka_attribute_names, AT_RESULT_IND); typedef struct private_eap_aka_t private_eap_aka_t; /** * Private data of an eap_aka_t object. */ struct private_eap_aka_t { /** * Public authenticator_t interface. */ eap_aka_t public; /** * ID of the server */ identification_t *server; /** * ID of the peer */ identification_t *peer; /** * SHA11 hasher */ hasher_t *sha1; /** * MAC function used in EAP-AKA */ signer_t *signer; /** * pseudo random function used in EAP-aka */ prf_t *prf; /** * Special keyed SHA1 hasher used in EAP-AKA, implemented as PRF */ prf_t *keyed_prf; /** * Key for EAP MAC */ chunk_t k_auth; /** * Key for EAP encryption */ chunk_t k_encr; /** * MSK */ chunk_t msk; /** * Extendend MSK */ chunk_t emsk; /** * Expected result from client XRES */ chunk_t xres; /** * Shared secret K from ipsec.conf (padded) */ chunk_t k; /** * random value RAND generated by server */ chunk_t rand; }; /** Family key, as proposed in S.S0055 */ static u_int8_t fmk_buf[] = {0x41, 0x48, 0x41, 0x47}; static chunk_t fmk = chunk_from_buf(fmk_buf); /** Authentication management field */ static u_int8_t amf_buf[] = {0x00, 0x01}; static chunk_t amf = chunk_from_buf(amf_buf); /** AT_CLIENT_ERROR_CODE AKA attribute */ static u_int8_t client_error_code_buf[] = {0, 0}; static chunk_t client_error_code = chunk_from_buf(client_error_code_buf); /** previously used sqn by peer, next one must be greater */ static u_int8_t peer_sqn_buf[6]; static chunk_t peer_sqn = chunk_from_buf(peer_sqn_buf); /** set SQN to the current time */ static void update_sqn(u_int8_t *sqn, time_t offset) { timeval_t time; gettimeofday(&time, NULL); /* set sqb_sqn to an integer containing seconds followed by most * significant useconds */ time.tv_sec = htonl(time.tv_sec + offset); /* usec's are never larger than 0x000f423f, so we shift the 12 first bits */ time.tv_usec <<= 12; time.tv_usec = htonl(time.tv_usec); memcpy(sqn, &time.tv_sec, 4); memcpy(sqn + 4, &time.tv_usec, 2); } /** initialize peers SQN to the current system time at startup */ static void __attribute__ ((constructor))init_sqn(void) { update_sqn(peer_sqn_buf, 0); } /** * Binary represnation of the polynom T^160 + T^5 + T^3 + T^2 + 1 */ static u_int8_t g[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d }; /** * Predefined random bits from the RAND Corporation book */ static u_int8_t a[] = { 0x9d, 0xe9, 0xc9, 0xc8, 0xef, 0xd5, 0x78, 0x11, 0x48, 0x23, 0x14, 0x01, 0x90, 0x1f, 0x2d, 0x49, 0x3f, 0x4c, 0x63, 0x65 }; /** * Predefined random bits from the RAND Corporation book */ static u_int8_t b[] = { 0x75, 0xef, 0xd1, 0x5c, 0x4b, 0x8f, 0x8f, 0x51, 0x4e, 0xf3, 0xbc, 0xc3, 0x79, 0x4a, 0x76, 0x5e, 0x7e, 0xec, 0x45, 0xe0 }; /** * Multiplicate two mpz_t with bits interpreted as polynoms. */ static void mpz_mul_poly(mpz_t r, mpz_t a, mpz_t b) { mpz_t bm, rm; int current = 0, shifted = 0, shift; mpz_init_set(bm, b); mpz_init_set_ui(rm, 0); /* scan through a, for each found bit: */ while ((current = mpz_scan1(a, current)) != ULONG_MAX) { /* XOR shifted b into r */ shift = current - shifted; mpz_mul_2exp(bm, bm, shift); shifted += shift; mpz_xor(rm, rm, bm); current++; } mpz_swap(r, rm); mpz_clear(rm); mpz_clear(bm); } /** * Calculate the sum of a + b interpreted as polynoms. */ static void mpz_add_poly(mpz_t res, mpz_t a, mpz_t b) { /* addition of polynominals is just the XOR */ mpz_xor(res, a, b); } /** * Calculate the remainder of a/b interpreted as polynoms. */ static void mpz_mod_poly(mpz_t r, mpz_t a, mpz_t b) { /* Example: * a = 10001010 * b = 00000101 */ int a_bit, b_bit, diff; mpz_t bm, am; mpz_init_set(am, a); mpz_init(bm); a_bit = mpz_sizeinbase(a, 2); b_bit = mpz_sizeinbase(b, 2); /* don't do anything if b > a */ if (a_bit >= b_bit) { /* shift b left to align up most signaficant "1" to a: * a = 10001010 * b = 10100000 */ mpz_mul_2exp(bm, b, a_bit - b_bit); do { /* XOR b into a, this kills the most significant "1": * a = 00101010 */ mpz_xor(am, am, bm); /* find the next most significant "1" in a, and align up b: * a = 00101010 * b = 00101000 */ diff = a_bit - mpz_sizeinbase(am, 2); mpz_div_2exp(bm, bm, diff); a_bit -= diff; } while (b_bit <= mpz_sizeinbase(bm, 2)); /* While b is not shifted to its original value */ } /* after another iteration: * a = 00000010 * which is the polynomial modulo */ mpz_swap(r, am); mpz_clear(am); mpz_clear(bm); } /** * Step 4 of the various fx() functions: * Polynomial whiten calculations */ static void step4(private_eap_aka_t *this, u_int8_t x[]) { mpz_t xm, am, bm, gm; mpz_init(xm); mpz_init(am); mpz_init(bm); mpz_init(gm); mpz_import(xm, HASH_SIZE_SHA1, 1, 1, 1, 0, x); mpz_import(am, sizeof(a), 1, 1, 1, 0, a); mpz_import(bm, sizeof(b), 1, 1, 1, 0, b); mpz_import(gm, sizeof(g), 1, 1, 1, 0, g); mpz_mul_poly(xm, am, xm); mpz_add_poly(xm, bm, xm); mpz_mod_poly(xm, xm, gm); mpz_export(x, NULL, 1, HASH_SIZE_SHA1, 1, 0, xm); mpz_clear(xm); mpz_clear(am); mpz_clear(bm); mpz_clear(gm); } /** * Step 3 of the various fx() functions: * XOR the key into the SHA1 IV */ static void step3(private_eap_aka_t *this, chunk_t k, chunk_t payload, u_int8_t h[]) { u_int8_t buf[64]; if (payload.len < sizeof(buf)) { /* pad c with zeros */ memset(buf, 0, sizeof(buf)); memcpy(buf, payload.ptr, payload.len); payload.ptr = buf; payload.len = sizeof(buf); } else { /* not more than 512 bits can be G()-ed */ payload.len = sizeof(buf); } /* use the keyed hasher to build the hash */ this->keyed_prf->set_key(this->keyed_prf, k); this->keyed_prf->get_bytes(this->keyed_prf, payload, h); } /** * Calculation function for f2(), f3(), f4() */ static void fx(private_eap_aka_t *this, u_int8_t f, chunk_t k, chunk_t rand, u_int8_t out[]) { chunk_t payload = chunk_alloca(PAYLOAD_LENGTH); u_int8_t h[HASH_SIZE_SHA1]; u_int8_t i; for (i = 0; i < 2; i++) { memset(payload.ptr, 0x5c, payload.len); payload.ptr[11] ^= f; memxor(payload.ptr + 12, fmk.ptr, fmk.len); memxor(payload.ptr + 24, rand.ptr, rand.len); payload.ptr[3] ^= i; payload.ptr[19] ^= i; payload.ptr[35] ^= i; payload.ptr[51] ^= i; step3(this, k, payload, h); step4(this, h); memcpy(out + i * 8, h, 8); } } /** * Calculation function of f1() and f1star() */ static void f1x(private_eap_aka_t *this, u_int8_t f, chunk_t k, chunk_t rand, chunk_t sqn, chunk_t amf, u_int8_t mac[]) { /* generate MAC = f1(FMK, SQN, RAND, AMF) * K is loaded into hashers IV; FMK, RAND, SQN, AMF are XORed in a 512-bit * payload which gets hashed */ chunk_t payload = chunk_alloca(PAYLOAD_LENGTH); u_int8_t h[HASH_SIZE_SHA1]; memset(payload.ptr, 0x5c, PAYLOAD_LENGTH); payload.ptr[11] ^= f; memxor(payload.ptr + 12, fmk.ptr, fmk.len); memxor(payload.ptr + 16, rand.ptr, rand.len); memxor(payload.ptr + 34, sqn.ptr, sqn.len); memxor(payload.ptr + 42, amf.ptr, amf.len); step3(this, k, payload, h); step4(this, h); memcpy(mac, h, MAC_LENGTH); } /** * Calculation function of f5() and f5star() */ static void f5x(private_eap_aka_t *this, u_int8_t f, chunk_t k, chunk_t rand, u_int8_t ak[]) { chunk_t payload = chunk_alloca(PAYLOAD_LENGTH); u_int8_t h[HASH_SIZE_SHA1]; memset(payload.ptr, 0x5c, payload.len); payload.ptr[11] ^= f; memxor(payload.ptr + 12, fmk.ptr, fmk.len); memxor(payload.ptr + 16, rand.ptr, rand.len); step3(this, k, payload, h); step4(this, h); memcpy(ak, h, AK_LENGTH); } /** * Calculate the MAC from a RAND, SQN, AMF value using K */ static void f1(private_eap_aka_t *this, chunk_t k, chunk_t rand, chunk_t sqn, chunk_t amf, u_int8_t mac[]) { f1x(this, F1, k, rand, sqn, amf, mac); DBG3(DBG_IKE, "MAC %b", mac, MAC_LENGTH); } /** * Calculate the MACS from a RAND, SQN, AMF value using K */ static void f1star(private_eap_aka_t *this, chunk_t k, chunk_t rand, chunk_t sqn, chunk_t amf, u_int8_t macs[]) { f1x(this, F1STAR, k, rand, sqn, amf, macs); DBG3(DBG_IKE, "MACS %b", macs, MAC_LENGTH); } /** * Calculate RES from RAND using K */ static void f2(private_eap_aka_t *this, chunk_t k, chunk_t rand, u_int8_t res[]) { fx(this, F2, k, rand, res); DBG3(DBG_IKE, "RES %b", res, RES_LENGTH); } /** * Calculate CK from RAND using K */ static void f3(private_eap_aka_t *this, chunk_t k, chunk_t rand, u_int8_t ck[]) { fx(this, F3, k, rand, ck); DBG3(DBG_IKE, "CK %b", ck, CK_LENGTH); } /** * Calculate IK from RAND using K */ static void f4(private_eap_aka_t *this, chunk_t k, chunk_t rand, u_int8_t ik[]) { fx(this, F4, k, rand, ik); DBG3(DBG_IKE, "IK %b", ik, IK_LENGTH); } /** * Calculate AK from a RAND using K */ static void f5(private_eap_aka_t *this, chunk_t k, chunk_t rand, u_int8_t ak[]) { f5x(this, F5, k, rand, ak); DBG3(DBG_IKE, "AK %b", ak, AK_LENGTH); } /** * Calculate AKS from a RAND using K */ static void f5star(private_eap_aka_t *this, chunk_t k, chunk_t rand, u_int8_t aks[]) { f5x(this, F5STAR, k, rand, aks); DBG3(DBG_IKE, "AKS %b", aks, AK_LENGTH); } /** * derive the keys needed for EAP_AKA */ static bool derive_keys(private_eap_aka_t *this, identification_t *id) { chunk_t ck, ik, mk, identity, tmp; ck = chunk_alloca(CK_LENGTH); ik = chunk_alloca(IK_LENGTH); mk = chunk_alloca(MK_LENGTH); identity = id->get_encoding(id); /* MK = SHA1( Identity | IK | CK ) */ f3(this, this->k, this->rand, ck.ptr); f4(this, this->k, this->rand, ik.ptr); DBG3(DBG_IKE, "Identity %B", &identity); tmp = chunk_cata("ccc", identity, ik, ck); DBG3(DBG_IKE, "Identity|IK|CK %B", &tmp); this->sha1->get_hash(this->sha1, tmp, mk.ptr); /* K_encr | K_auth | MSK | EMSK = prf(0) | prf(0) * FIPS PRF has 320 bit block size, we need 160 byte for keys * => run prf four times */ this->prf->set_key(this->prf, mk); tmp = chunk_alloca(this->prf->get_block_size(this->prf) * 4); this->prf->get_bytes(this->prf, chunk_empty, tmp.ptr); this->prf->get_bytes(this->prf, chunk_empty, tmp.ptr + tmp.len / 4 * 1); this->prf->get_bytes(this->prf, chunk_empty, tmp.ptr + tmp.len / 4 * 2); this->prf->get_bytes(this->prf, chunk_empty, tmp.ptr + tmp.len / 4 * 3); chunk_free(&this->k_encr); chunk_free(&this->k_auth); chunk_free(&this->msk); chunk_free(&this->emsk); chunk_split(tmp, "aaaa", 16, &this->k_encr, 16, &this->k_auth, 64, &this->msk, 64, &this->emsk); DBG3(DBG_IKE, "MK %B", &mk); DBG3(DBG_IKE, "PRF res %B", &tmp); DBG3(DBG_IKE, "K_encr %B", &this->k_encr); DBG3(DBG_IKE, "K_auth %B", &this->k_auth); DBG3(DBG_IKE, "MSK %B", &this->msk); DBG3(DBG_IKE, "EMSK %B", &this->emsk); return TRUE; } /* * Get a shared key from ipsec.secrets. * We use the standard keys as used in preshared key authentication. As * these keys have an undefined length, we: * - strip them if they are longer * - fill them up with '\0' if they are shorter */ static status_t load_key(identification_t *me, identification_t *other, chunk_t *k) { shared_key_t *shared; chunk_t key; shared = charon->credentials->get_shared(charon->credentials, SHARED_EAP, me, other); if (shared == NULL) { return NOT_FOUND; } key = shared->get_key(shared); chunk_free(k); *k = chunk_alloc(K_LENGTH); memset(k->ptr, '\0', k->len); memcpy(k->ptr, key.ptr, min(key.len, k->len)); shared->destroy(shared); return SUCCESS; } /** * skip EAP_AKA header in message and returns its AKA subtype */ static aka_subtype_t read_header(chunk_t *message) { aka_subtype_t type; if (message->len < 8) { *message = chunk_empty; return 0; } type = *(message->ptr + 5); *message = chunk_skip(*message, 8); return type; } /** * read the next attribute from the chunk data */ static aka_attribute_t read_attribute(chunk_t *data, chunk_t *attr_data) { aka_attribute_t attribute; size_t length; DBG3(DBG_IKE, "reading attribute from %B", data); if (data->len < 2) { return AT_END; } /* read attribute and length */ attribute = *data->ptr++; length = *data->ptr++ * 4 - 2; data->len -= 2; DBG3(DBG_IKE, "found attribute %N with length %d", aka_attribute_names, attribute, length); if (length > data->len) { return AT_END; } /* apply attribute value to attr_data */ attr_data->len = length; attr_data->ptr = data->ptr; /* update data to point to next attribute */ *data = chunk_skip(*data, length); return attribute; } /** * Build an AKA payload from different attributes. * The variable argument takes an aka_attribute_t * followed by its data in a chunk. */ static eap_payload_t *build_aka_payload(private_eap_aka_t *this, eap_code_t code, u_int8_t identifier, aka_subtype_t type, ...) { chunk_t message = chunk_alloca(512); /* is enought for all current messages */ chunk_t pos = message; eap_payload_t *payload; va_list args; aka_attribute_t attr; u_int8_t *mac_pos = NULL; /* write EAP header, skip length bytes */ *pos.ptr++ = code; *pos.ptr++ = identifier; pos.ptr += 2; pos.len -= 4; /* write AKA header with type and subtype, null reserved bytes */ *pos.ptr++ = EAP_AKA; *pos.ptr++ = type; *pos.ptr++ = 0; *pos.ptr++ = 0; pos.len -= 4; va_start(args, type); while ((attr = va_arg(args, aka_attribute_t)) != AT_END) { chunk_t data = va_arg(args, chunk_t); DBG3(DBG_IKE, "building %N %B", aka_attribute_names, attr, &data); /* write attribute header */ *pos.ptr++ = attr; pos.len--; switch (attr) { case AT_RES: { /* attribute length in 4byte words */ *pos.ptr = data.len/4 + 1; pos = chunk_skip(pos, 1); /* RES length in bits */ *(u_int16_t*)pos.ptr = htons(data.len * 8); pos = chunk_skip(pos, sizeof(u_int16_t)); memcpy(pos.ptr, data.ptr, data.len); pos = chunk_skip(pos, data.len); break; } case AT_AUTN: case AT_RAND: { *pos.ptr++ = data.len/4 + 1; pos.len--; *pos.ptr++ = 0; pos.len--; *pos.ptr++ = 0; pos.len--; memcpy(pos.ptr, data.ptr, data.len); pos = chunk_skip(pos, data.len); break; } case AT_MAC: { *pos.ptr++ = 5; pos.len--; *pos.ptr++ = 0; pos.len--; *pos.ptr++ = 0; pos.len--; mac_pos = pos.ptr; /* MAC is calculated over message including zeroed AT_MAC attribute */ memset(mac_pos, 0, AT_MAC_LENGTH); pos.ptr += AT_MAC_LENGTH; pos.len -= AT_MAC_LENGTH; break; } default: { /* length is data length in 4-bytes + 1 for header */ *pos.ptr = data.len/4 + 1; pos = chunk_skip(pos, 1); memcpy(pos.ptr, data.ptr, data.len); pos = chunk_skip(pos, data.len); } } } va_end(args); /* calculate message length, write into header */ message.len = pos.ptr - message.ptr; *(u_int16_t*)(message.ptr + 2) = htons(message.len); /* create MAC if AT_MAC attribte was included */ if (mac_pos) { this->signer->set_key(this->signer, this->k_auth); DBG3(DBG_IKE, "AT_MAC signature of %B", &message); DBG3(DBG_IKE, "using key %B", &this->k_auth); this->signer->get_signature(this->signer, message, mac_pos); DBG3(DBG_IKE, "is %b", mac_pos, AT_MAC_LENGTH); } /* payload constructor takes data with some bytes skipped */ payload = eap_payload_create_data(message); DBG3(DBG_IKE, "created EAP message %B", &message); return payload; } /** * generate a new non-zero identifier */ static u_char get_identifier() { u_char id; do { id = random(); } while (!id); return id; } /** * Initiate a AKA-Challenge using SQN */ static status_t server_initiate_challenge(private_eap_aka_t *this, chunk_t sqn, eap_payload_t **out) { rng_t *rng; chunk_t mac, ak, autn; mac = chunk_alloca(MAC_LENGTH); ak = chunk_alloca(AK_LENGTH); chunk_free(&this->rand); chunk_free(&this->xres); /* generate RAND: * we use a registered RNG, not f0() proposed in S.S0055 */ rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); if (!rng) { DBG1(DBG_IKE, "generating RAND for EAP-AKA authentication failed"); return FAILED; } rng->allocate_bytes(rng, RAND_LENGTH, &this->rand); rng->destroy(rng); # ifdef TEST_VECTORS /* Test vector for RAND */ u_int8_t test_rand[] = { 0x4b,0x05,0x2b,0x20,0xe2,0xa0,0x6c,0x8f, 0xf7,0x00,0xda,0x51,0x2b,0x4e,0x11,0x1e, }; memcpy(this->rand.ptr, test_rand, this->rand.len); # endif /* TEST_VECTORS */ /* Get the shared key K: */ if (load_key(this->server, this->peer, &this->k) != SUCCESS) { DBG1(DBG_IKE, "no shared key found for IDs '%D' - '%D' to authenticate " "with EAP-AKA", this->server, this->peer); return FAILED; } # ifdef TEST_VECTORS /* Test vector for K */ u_int8_t test_k[] = { 0xad,0x1b,0x5a,0x15,0x9b,0xe8,0x6b,0x2c, 0xa6,0x6c,0x7a,0xe4,0x0b,0xba,0x9b,0x9d, }; memcpy(this->k.ptr, test_k, this->k.len); # endif /* TEST_VECTORS */ /* generate MAC */ f1(this, this->k, this->rand, sqn, amf, mac.ptr); /* generate AK */ f5(this, this->k, this->rand, ak.ptr); /* precalculate XRES as expected from client */ this->xres = chunk_alloc(RES_LENGTH); f2(this, this->k, this->rand, this->xres.ptr); /* calculate AUTN = (SQN xor AK) || AMF || MAC */ autn = chunk_cata("ccc", sqn, amf, mac); memxor(autn.ptr, ak.ptr, ak.len); DBG3(DBG_IKE, "AUTN %B", &autn); /* derive K_encr, K_auth, MSK, EMSK */ derive_keys(this, this->peer); /* build payload */ *out = build_aka_payload(this, EAP_REQUEST, get_identifier(), AKA_CHALLENGE, AT_RAND, this->rand, AT_AUTN, autn, AT_MAC, chunk_empty, AT_END); return NEED_MORE; } /** * Implementation of eap_method_t.initiate for an EAP_AKA server */ static status_t server_initiate(private_eap_aka_t *this, eap_payload_t **out) { chunk_t sqn = chunk_alloca(SQN_LENGTH); /* we use an offset of 3 minutes to tolerate clock inaccuracy * without the need to synchronize sequence numbers */ update_sqn(sqn.ptr, 180); # ifdef TEST_VECTORS /* Test vector for SQN */ u_int8_t test_sqn[] = {0x00,0x00,0x00,0x00,0x00,0x01}; memcpy(sqn.ptr, test_sqn, sqn.len); # endif /* TEST_VECTORS */ return server_initiate_challenge(this, sqn, out); } static status_t server_process_synchronize(private_eap_aka_t *this, eap_payload_t *in, eap_payload_t **out) { chunk_t attr, auts = chunk_empty, pos, message, macs, xmacs, sqn, aks, amf; u_int i; message = in->get_data(in); pos = message; read_header(&pos); /* iterate over attributes */ while (TRUE) { aka_attribute_t attribute = read_attribute(&pos, &attr); switch (attribute) { case AT_END: break; case AT_AUTS: auts = attr; continue; default: if (attribute >= 0 && attribute <= 127) { DBG1(DBG_IKE, "found non skippable attribute %N", aka_attribute_names, attribute); return FAILED; } DBG1(DBG_IKE, "ignoring skippable attribute %N", aka_attribute_names, attribute); continue; } break; } if (auts.len != AUTS_LENGTH) { DBG1(DBG_IKE, "synchronization request didn't contain useable AUTS"); return FAILED; } chunk_split(auts, "mm", SQN_LENGTH, &sqn, MAC_LENGTH, &macs); aks = chunk_alloca(AK_LENGTH); f5star(this, this->k, this->rand, aks.ptr); /* decrypt serial number by XORing AKS */ memxor(sqn.ptr, aks.ptr, aks.len); /* verify MACS */ xmacs = chunk_alloca(MAC_LENGTH); amf = chunk_alloca(AMF_LENGTH); /* an AMF of zero is used for MACS calculation */ memset(amf.ptr, 0, amf.len); f1star(this, this->k, this->rand, sqn, amf, xmacs.ptr); if (!chunk_equals(macs, xmacs)) { DBG1(DBG_IKE, "received MACS does not match XMACS"); DBG3(DBG_IKE, "MACS %B XMACS %B", &macs, &xmacs); return FAILED; } /* retry the challenge with the received SQN + 1*/ for (i = SQN_LENGTH - 1; i >= 0; i--) { if (++sqn.ptr[i] != 0) { break; } } return server_initiate_challenge(this, sqn, out); } /** * process an AKA_Challenge response */ static status_t server_process_challenge(private_eap_aka_t *this, eap_payload_t *in) { chunk_t attr, res = chunk_empty, at_mac = chunk_empty, pos, message; message = in->get_data(in); pos = message; read_header(&pos); /* iterate over attributes */ while (TRUE) { aka_attribute_t attribute = read_attribute(&pos, &attr); switch (attribute) { case AT_END: break; case AT_RES: res = attr; if (attr.len == 2 + RES_LENGTH && *(u_int16_t*)attr.ptr == htons(RES_LENGTH * 8)) { res = chunk_skip(attr, 2); } continue; case AT_MAC: attr = chunk_skip(attr, 2); at_mac = chunk_clonea(attr); /* zero MAC in message for MAC verification */ memset(attr.ptr, 0, attr.len); continue; default: if (attribute >= 0 && attribute <= 127) { DBG1(DBG_IKE, "found non skippable attribute %N", aka_attribute_names, attribute); return FAILED; } DBG1(DBG_IKE, "ignoring skippable attribute %N", aka_attribute_names, attribute); continue; } break; } /* verify EAP message MAC AT_MAC */ { this->signer->set_key(this->signer, this->k_auth); DBG3(DBG_IKE, "verifying AT_MAC signature of %B", &message); DBG3(DBG_IKE, "using key %B", &this->k_auth); if (!this->signer->verify_signature(this->signer, message, at_mac)) { DBG1(DBG_IKE, "MAC in AT_MAC attribute verification failed"); return FAILED; } } /* compare received RES against stored precalculated XRES */ if (!chunk_equals(res, this->xres)) { DBG1(DBG_IKE, "received RES does not match XRES"); DBG3(DBG_IKE, "RES %Bb XRES %B", &res, &this->xres); return FAILED; } return SUCCESS; } /** * Implementation of eap_method_t.process for EAP_AKA servers */ static status_t server_process(private_eap_aka_t *this, eap_payload_t *in, eap_payload_t **out) { chunk_t message; aka_subtype_t type; message = in->get_data(in); type = read_header(&message); DBG3(DBG_IKE, "received EAP message %B", &message); switch (type) { case AKA_CHALLENGE: { return server_process_challenge(this, in); } case AKA_AUTHENTICATION_REJECT: case AKA_CLIENT_ERROR: { DBG1(DBG_IKE, "received %N, authentication failed", aka_subtype_names, type); return FAILED; } case AKA_SYNCHRONIZATION_FAILURE: { DBG1(DBG_IKE, "received %N, retrying with received SQN", aka_subtype_names, type); return server_process_synchronize(this, in, out); } default: DBG1(DBG_IKE, "received unknown AKA subtype %N, authentication failed", aka_subtype_names, type); return FAILED; } } /** * Process an incoming AKA-Challenge client side */ static status_t peer_process_challenge(private_eap_aka_t *this, eap_payload_t *in, eap_payload_t **out) { chunk_t attr = chunk_empty; chunk_t autn = chunk_empty, at_mac = chunk_empty; chunk_t ak, sqn, sqn_ak, mac, xmac, res, amf, message, pos; u_int8_t identifier; ak = chunk_alloca(AK_LENGTH); xmac = chunk_alloca(MAC_LENGTH); res = chunk_alloca(RES_LENGTH); chunk_free(&this->rand); message = in->get_data(in); pos = message; read_header(&pos); identifier = in->get_identifier(in); DBG3(DBG_IKE, "reading attributes from %B", &pos); /* iterate over attributes */ while (TRUE) { aka_attribute_t attribute = read_attribute(&pos, &attr); switch (attribute) { case AT_END: break; case AT_RAND: this->rand = chunk_clone(chunk_skip(attr, 2)); continue; case AT_AUTN: autn = chunk_skip(attr, 2); continue; case AT_MAC: attr = chunk_skip(attr, 2); at_mac = chunk_clonea(attr); /* set MAC in message to zero for own MAC verification */ memset(attr.ptr, 0, attr.len); continue; default: if (attribute >= 0 && attribute <= 127) { /* non skippable attribute, abort */ *out = build_aka_payload(this, EAP_RESPONSE, identifier, AKA_CLIENT_ERROR, AT_CLIENT_ERROR_CODE, client_error_code, AT_END); DBG1(DBG_IKE, "found non skippable attribute %N, sending %N %d", aka_attribute_names, attribute, aka_attribute_names, AT_CLIENT_ERROR_CODE, 0); return NEED_MORE; } DBG1(DBG_IKE, "ignoring skippable attribute %N", aka_attribute_names, attribute); continue; } break; } if (this->rand.len != RAND_LENGTH || autn.len != AUTN_LENGTH) { /* required attributes wrong/not found, abort */ *out = build_aka_payload(this, EAP_RESPONSE, identifier, AKA_CLIENT_ERROR, AT_CLIENT_ERROR_CODE, client_error_code, AT_END); DBG1(DBG_IKE, "could not find valid RAND/AUTN attribute, sending %N %d", aka_attribute_names, AT_CLIENT_ERROR_CODE, 0); return NEED_MORE; } DBG3(DBG_IKE, "using autn %B", &autn); /* split up AUTN = SQN xor AK | AMF | MAC */ chunk_split(autn, "mmm", SQN_LENGTH, &sqn_ak, AMF_LENGTH, &amf, MAC_LENGTH, &mac); /* Get the shared key K: */ chunk_free(&this->k); if (load_key(this->peer, this->server, &this->k) != SUCCESS) { *out = build_aka_payload(this, EAP_RESPONSE, identifier, AKA_AUTHENTICATION_REJECT, AT_END); DBG3(DBG_IKE, "no shared key found for IDs '%D' - '%D' to authenticate " "with EAP-AKA, sending %N", this->peer, this->server, aka_subtype_names, AKA_AUTHENTICATION_REJECT); return NEED_MORE; } DBG3(DBG_IKE, "using K %B", &this->k); # ifdef TEST_VECTORS /* Test vector for K */ u_int8_t test_k[] = { 0xad,0x1b,0x5a,0x15,0x9b,0xe8,0x6b,0x2c, 0xa6,0x6c,0x7a,0xe4,0x0b,0xba,0x9b,0x9d, }; memcpy(this->k.ptr, test_k, this->k.len); # endif /* TEST_VECTORS */ /* calculate anonymity key AK */ f5(this, this->k, this->rand, ak.ptr); DBG3(DBG_IKE, "using rand %B", &this->rand); DBG3(DBG_IKE, "using ak %B", &ak); /* XOR AK into SQN to decrypt it */ sqn = chunk_clonea(sqn_ak); DBG3(DBG_IKE, "using ak xor sqn %B", &sqn_ak); memxor(sqn.ptr, ak.ptr, sqn.len); DBG3(DBG_IKE, "using sqn %B", &sqn); /* calculate expected MAC and compare against received one */ f1(this, this->k, this->rand, sqn, amf, xmac.ptr); if (!chunk_equals(mac, xmac)) { *out = build_aka_payload(this, EAP_RESPONSE, identifier, AKA_AUTHENTICATION_REJECT, AT_END); DBG1(DBG_IKE, "received MAC does not match XMAC, sending %N", aka_subtype_names, AKA_AUTHENTICATION_REJECT); DBG3(DBG_IKE, "MAC %B\nXMAC %B", &mac, &xmac); return NEED_MORE; } #if SEQ_CHECK if (memcmp(peer_sqn.ptr, sqn.ptr, sqn.len) >= 0) { /* sequence number invalid. send AUTS */ chunk_t auts, macs, aks, amf; macs = chunk_alloca(MAC_LENGTH); aks = chunk_alloca(AK_LENGTH); amf = chunk_alloca(AMF_LENGTH); /* AMF is set to zero in AKA_SYNCHRONIZATION_FAILURE */ memset(amf.ptr, 0, amf.len); /* AKS = f5*(RAND) */ f5star(this, this->k, this->rand, aks.ptr); /* MACS = f1*(RAND) */ f1star(this, this->k, this->rand, peer_sqn, amf, macs.ptr); /* AUTS = SQN xor AKS | MACS */ memxor(aks.ptr, peer_sqn.ptr, aks.len); auts = chunk_cata("cc", aks, macs); *out = build_aka_payload(this, EAP_RESPONSE, identifier, AKA_SYNCHRONIZATION_FAILURE, AT_AUTS, auts, AT_END); DBG1(DBG_IKE, "received SQN invalid, sending %N", aka_subtype_names, AKA_SYNCHRONIZATION_FAILURE); DBG3(DBG_IKE, "received SQN %B\ncurrent SQN %B", &sqn, &peer_sqn); return NEED_MORE; } #endif /* SEQ_CHECK */ /* derive K_encr, K_auth, MSK, EMSK */ derive_keys(this, this->peer); /* verify EAP message MAC AT_MAC */ DBG3(DBG_IKE, "verifying AT_MAC signature of %B", &message); DBG3(DBG_IKE, "using key %B", &this->k_auth); this->signer->set_key(this->signer, this->k_auth); if (!this->signer->verify_signature(this->signer, message, at_mac)) { *out = build_aka_payload(this, EAP_RESPONSE, identifier, AKA_CLIENT_ERROR, AT_CLIENT_ERROR_CODE, client_error_code, AT_END); DBG1(DBG_IKE, "MAC in AT_MAC attribute verification " "failed, sending %N %d", aka_attribute_names, AT_CLIENT_ERROR_CODE, 0); return NEED_MORE; } /* update stored SQN to the received one */ memcpy(peer_sqn.ptr, sqn.ptr, sqn.len); /* calculate RES */ f2(this, this->k, this->rand, res.ptr); /* build response */ *out = build_aka_payload(this, EAP_RESPONSE, identifier, AKA_CHALLENGE, AT_RES, res, AT_MAC, chunk_empty, AT_END); return NEED_MORE; } /** * Process an incoming AKA-Notification as client */ static status_t peer_process_notification(private_eap_aka_t *this, eap_payload_t *in, eap_payload_t **out) { chunk_t message, pos, attr; u_int8_t identifier; message = in->get_data(in); pos = message; read_header(&pos); identifier = in->get_identifier(in); DBG3(DBG_IKE, "reading attributes from %B", &pos); /* iterate over attributes */ while (TRUE) { aka_attribute_t attribute = read_attribute(&pos, &attr); switch (attribute) { case AT_END: break; case AT_NOTIFICATION: { u_int16_t code; if (attr.len != 2) { DBG1(DBG_IKE, "received invalid AKA notification, ignored"); continue; } code = ntohs(*(u_int16_t*)attr.ptr); switch (code) { case 0: DBG1(DBG_IKE, "received AKA notification 'general " "failure after authentication' (%d)", code); return FAILED; case 16384: DBG1(DBG_IKE, "received AKA notification 'general " "failure' (%d)", code); return FAILED; case 32768: DBG1(DBG_IKE, "received AKA notification 'successfully " "authenticated' (%d)", code); continue; case 1026: DBG1(DBG_IKE, "received AKA notification 'access " "temporarily denied' (%d)", code); return FAILED; case 1031: DBG1(DBG_IKE, "received AKA notification 'not " "subscribed to service' (%d)", code); return FAILED; default: DBG1(DBG_IKE, "received AKA notification code %d, " "ignored", code); continue; } } default: if (attribute >= 0 && attribute <= 127) { DBG1(DBG_IKE, "ignoring non-skippable attribute %N in %N", aka_attribute_names, attribute, aka_subtype_names, AKA_NOTIFICATION); } else { DBG1(DBG_IKE, "ignoring skippable attribute %N", aka_attribute_names, attribute); } continue; } break; } return NEED_MORE; } /** * Implementation of eap_method_t.process for an EAP_AKA peer */ static status_t peer_process(private_eap_aka_t *this, eap_payload_t *in, eap_payload_t **out) { aka_subtype_t type; chunk_t message; u_int8_t identifier; message = in->get_data(in); type = read_header(&message); identifier = in->get_identifier(in); DBG3(DBG_IKE, "received EAP message %B", &message); switch (type) { case AKA_CHALLENGE: { return peer_process_challenge(this, in, out); } case AKA_NOTIFICATION: { return peer_process_notification(this, in, out); } default: { *out = build_aka_payload(this, EAP_RESPONSE, identifier, AKA_CLIENT_ERROR, AT_CLIENT_ERROR_CODE, client_error_code, AT_END); DBG1(DBG_IKE, "received unsupported %N request, sending %N %d", aka_subtype_names, type, aka_attribute_names, AT_CLIENT_ERROR_CODE, 0); return NEED_MORE; } } } /** * Implementation of eap_method_t.initiate for an EAP AKA peer */ static status_t peer_initiate(private_eap_aka_t *this, eap_payload_t **out) { /* peer never initiates */ return FAILED; } /** * Implementation of eap_method_t.get_type. */ static eap_type_t get_type(private_eap_aka_t *this, u_int32_t *vendor) { *vendor = 0; return EAP_AKA; } /** * Implementation of eap_method_t.get_msk. */ static status_t get_msk(private_eap_aka_t *this, chunk_t *msk) { if (this->msk.ptr) { *msk = this->msk; return SUCCESS; } return FAILED; } /** * Implementation of eap_method_t.is_mutual. */ static bool is_mutual(private_eap_aka_t *this) { return TRUE; } /** * Implementation of eap_method_t.destroy. */ static void destroy(private_eap_aka_t *this) { this->server->destroy(this->server); this->peer->destroy(this->peer); DESTROY_IF(this->sha1); DESTROY_IF(this->signer); DESTROY_IF(this->prf); DESTROY_IF(this->keyed_prf); chunk_free(&this->k_encr); chunk_free(&this->k_auth); chunk_free(&this->msk); chunk_free(&this->emsk); chunk_free(&this->xres); chunk_free(&this->k); chunk_free(&this->rand); free(this); } /** * generic constructor used by client & server */ static private_eap_aka_t *eap_aka_create_generic(identification_t *server, identification_t *peer) { private_eap_aka_t *this = malloc_thing(private_eap_aka_t); this->public.eap_method_interface.initiate = NULL; this->public.eap_method_interface.process = NULL; 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->server = server->clone(server); this->peer = peer->clone(peer); this->k_encr = chunk_empty; this->k_auth = chunk_empty; this->msk = chunk_empty; this->emsk = chunk_empty; this->xres = chunk_empty; this->k = chunk_empty; this->rand = chunk_empty; this->sha1 = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); this->signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_SHA1_128); this->prf = lib->crypto->create_prf(lib->crypto, PRF_FIPS_SHA1_160); this->keyed_prf = lib->crypto->create_prf(lib->crypto, PRF_KEYED_SHA1); if (!this->sha1 || !this->signer || !this->prf || !this->keyed_prf) { DBG1(DBG_IKE, "unable to initiate EAP-AKA, FIPS-PRF/SHA1 not supported"); DESTROY_IF(this->sha1); DESTROY_IF(this->signer); DESTROY_IF(this->prf); DESTROY_IF(this->keyed_prf); destroy(this); return NULL; } return this; } /* * Described in header. */ eap_aka_t *eap_aka_create_server(identification_t *server, identification_t *peer) { private_eap_aka_t *this = eap_aka_create_generic(server, peer); if (this) { 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; } return (eap_aka_t*)this; } /* * Described in header. */ eap_aka_t *eap_aka_create_peer(identification_t *server, identification_t *peer) { private_eap_aka_t *this = eap_aka_create_generic(server, peer); if (this) { 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; } return (eap_aka_t*)this; }