| /* |
| * |
| * 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 |
| * |
| */ |
| #include <errno.h> |
| |
| #include <ell/ell.h> |
| #include <ell/plugin.h> |
| |
| #include "src/simauth.h" |
| |
| struct hardcoded_sim { |
| char *identity; |
| uint8_t sim_supported; |
| uint8_t kc[NUM_RANDS_MAX][EAP_SIM_KC_LEN]; |
| uint8_t sres[NUM_RANDS_MAX][EAP_SIM_SRES_LEN]; |
| uint8_t aka_supported; |
| uint8_t ki[EAP_AKA_KI_LEN]; |
| uint8_t opc[EAP_AKA_OPC_LEN]; |
| uint8_t amf[EAP_AKA_AMF_LEN]; |
| uint8_t sqn[EAP_AKA_SQN_LEN]; |
| struct iwd_sim_auth *auth; |
| }; |
| |
| static struct hardcoded_sim *sim; |
| |
| /* |
| * Helper to XOR an array |
| * to - result of XOR array |
| * a - array 1 |
| * b - array 2 |
| * len - size of aray |
| */ |
| #define XOR(to, a, b, len) \ |
| for (i = 0; i < len; i++) { \ |
| to[i] = a[i] ^ b[i]; \ |
| } |
| |
| static int get_milenage(const uint8_t *opc, const uint8_t *k, |
| const uint8_t *rand, const uint8_t *sqn, const uint8_t *amf, |
| const uint8_t *autn_in, uint8_t *autn, uint8_t *ck, uint8_t *ik, |
| uint8_t *res, uint8_t *auts) |
| { |
| /* algorithm variables: TEMP, IN1, OUT1, OUT2, OUT5 (OUT3/4 == IK/CK) */ |
| uint8_t temp[16]; |
| uint8_t in1[16]; |
| uint8_t out1[16], out2[16], out5[16]; |
| /* other variables */ |
| struct l_cipher *aes; |
| int i; |
| uint8_t tmp1[16]; |
| uint8_t tmp2[16]; |
| uint8_t sqn_autn[6]; |
| |
| aes = l_cipher_new(L_CIPHER_AES, k, 16); |
| |
| /* temp = TEMP = E[RAND ^ OPc]k */ |
| XOR(tmp1, rand, opc, 16); |
| l_cipher_encrypt(aes, tmp1, temp, 16); |
| |
| /* IN1[0-47] = SQN[0-47] */ |
| memcpy(in1, sqn, 6); |
| /* IN1[48-63] = AMF[0-15] */ |
| memcpy(in1 + 6, amf, 2); |
| /* IN1[64-111] = SQN[0-47] */ |
| memcpy(in1 + 8, sqn, 6); |
| /* IN1[112-127] = AMF[0-15] */ |
| memcpy(in1 + 14, amf, 2); |
| |
| /* |
| * f1 and f1* output OUT1 |
| */ |
| /* |
| * tmp1 = rot(IN1 ^ OPc)r1 |
| * r1 = 64 bits = 8 bytes |
| */ |
| for (i = 0; i < 16; i++) |
| tmp1[(i + 8) % 16] = in1[i] ^ opc[i]; |
| |
| /* tmp2 = TEMP ^ tmp1 */ |
| XOR(tmp2, temp, tmp1, 16); |
| /* tmp2 = E[tmp2]k */ |
| l_cipher_encrypt(aes, tmp2, tmp1, 16); |
| /* out1 = OUT1 = tmp1 ^ opc */ |
| XOR(out1, tmp1, opc, 16); |
| |
| /* |
| * f2 outputs OUT2 (RES | AK) |
| * |
| * r2 = 0 == no rotation |
| */ |
| /* tmp1 = rot(TEMP ^ OPc)r2 */ |
| XOR(tmp1, temp, opc, 16); |
| /* tmp1 ^ c2. c2 at bit 127 == 1 */ |
| tmp1[15] ^= 1; |
| l_cipher_encrypt(aes, tmp1, out2, 16); |
| |
| /* get RES from OUT2 */ |
| XOR(out2, out2, opc, 16); |
| memcpy(res, out2 + 8, 8); |
| |
| /* check input autn (AUTN ^ AK = SQN)*/ |
| XOR(sqn_autn, autn_in, out2, 6); |
| |
| /* if SQN was not correct, generate AUTS */ |
| if (memcmp(sqn_autn, sqn, 6)) { |
| /* |
| * f5* outputs AK' (OUT5) |
| */ |
| for (i = 0; i < 16; i++) |
| tmp1[(i + 4) % 16] = temp[i] ^ opc[i]; |
| |
| /* tmp1 ^ c5. c5 at bit 124 == 1 */ |
| tmp1[15] ^= 1 << 3; |
| l_cipher_encrypt(aes, tmp1, out5, 16); |
| /* out5 ^ opc */ |
| XOR(out5, out5, opc, 16); |
| |
| XOR(auts, sqn, out5, 6); |
| |
| /* run f1 with zero'd AMF to finish AUTS */ |
| in1[6] = 0x00; |
| in1[7] = 0x00; |
| in1[14] = 0x00; |
| in1[15] = 0x00; |
| |
| for (i = 0; i < 16; i++) |
| tmp1[(i + 8) % 16] = in1[i] ^ opc[i]; |
| |
| /* tmp2 = TEMP ^ tmp1 */ |
| XOR(tmp2, temp, tmp1, 16); |
| /* tmp2 = E[tmp2]k */ |
| l_cipher_encrypt(aes, tmp2, tmp1, 16); |
| /* out1 = OUT1 = tmp1 ^ opc */ |
| XOR(out1, tmp1, opc, 16); |
| |
| memcpy(auts + 6, in1 + 8, 8); |
| |
| return -1; |
| } |
| |
| /* AUTN = (SQN ^ AK) | AMF | MAC_A */ |
| XOR(autn, sqn, out2, 6); |
| memcpy(autn + 6, amf, 2); |
| memcpy(autn + 8, out1, 8); |
| |
| if (memcmp(autn, autn_in, 16)) |
| return -2; |
| |
| /* |
| * f3 outputs CK (OUT3) |
| * |
| * tmp1 = rot(TEMP ^ OPc)r3 |
| * |
| * r3 = 32 bits = 4 bytes |
| */ |
| for (i = 0; i < 16; i++) |
| tmp1[(i + 12) % 16] = temp[i] ^ opc[i]; |
| |
| /* tmp1 ^ c3. c3 at bit 126 == 1 */ |
| tmp1[15] ^= 1 << 1; |
| l_cipher_encrypt(aes, tmp1, ck, 16); |
| /* ck ^ opc */ |
| XOR(ck, ck, opc, 16); |
| |
| /* |
| * f4 outputs IK (OUT4) |
| * |
| * tmp1 = rot(TEMP ^ OPc)r4 |
| * |
| * r4 = 64 bits = 8 bytes |
| */ |
| for (i = 0; i < 16; i++) |
| tmp1[(i + 8) % 16] = temp[i] ^ opc[i]; |
| |
| /* tmp1 ^ c4. c4 at bit 125 == 1 */ |
| tmp1[15] ^= 1 << 2; |
| l_cipher_encrypt(aes, tmp1, ik, 16); |
| /* ik ^ opc */ |
| XOR(ik, ik, opc, 16); |
| |
| l_cipher_free(aes); |
| |
| return 0; |
| } |
| |
| static int check_milenage(struct iwd_sim_auth *auth, const uint8_t *rand, |
| const uint8_t *autn, sim_auth_check_milenage_cb_t cb, |
| void *data) |
| { |
| uint8_t res[8]; |
| uint8_t ck[16]; |
| uint8_t ik[16]; |
| uint8_t _autn[16]; |
| uint8_t auts[14]; |
| int ret; |
| |
| if (!sim->aka_supported) |
| return -ENOTSUP; |
| |
| ret = get_milenage(sim->opc, sim->ki, rand, sim->sqn, sim->amf, |
| autn, _autn, ck, ik, res, auts); |
| |
| /* ret == 0, success; ret == -1, sync failure; ret == -2, failure */ |
| if (ret == 0) |
| cb(res, ck, ik, NULL, data); |
| else if (ret == -1) |
| cb(NULL, NULL, NULL, auts, data); |
| else |
| cb(NULL, NULL, NULL, NULL, data); |
| |
| return 0; |
| |
| } |
| |
| static int run_gsm(struct iwd_sim_auth *auth, const uint8_t *rands, |
| int num_rands, sim_auth_run_gsm_cb_t cb, void *data) |
| { |
| if (!sim->sim_supported) |
| return -ENOTSUP; |
| |
| cb((const uint8_t *)sim->sres, (const uint8_t *)sim->kc, data); |
| |
| return 0; |
| } |
| |
| static struct iwd_sim_auth_driver hardcoded_sim_driver = { |
| .name = "Hardcoded SIM driver", |
| .check_milenage = check_milenage, |
| .run_gsm = run_gsm |
| }; |
| |
| static int sim_hardcoded_init(void) |
| { |
| void *kc; |
| void *sres; |
| void *ki; |
| void *opc; |
| void *amf; |
| void *sqn; |
| const char *str; |
| size_t len; |
| struct l_settings *key_settings; |
| const char *config_path = getenv("IWD_SIM_KEYS"); |
| |
| if (!config_path) { |
| l_debug("IWD_SIM_KEYS not set in env"); |
| return -ENOENT; |
| } |
| |
| key_settings = l_settings_new(); |
| |
| if (!l_settings_load_from_file(key_settings, config_path)) { |
| l_error("No %s file found", config_path); |
| l_settings_free(key_settings); |
| return -ENOENT; |
| } |
| |
| sim = l_new(struct hardcoded_sim, 1); |
| |
| if (l_settings_has_group(key_settings, "SIM")) { |
| str = l_settings_get_value(key_settings, "SIM", "Kc"); |
| if (!str) { |
| l_debug("Kc value must be present for SIM"); |
| goto try_aka; |
| } |
| |
| kc = l_util_from_hexstring(str, &len); |
| memcpy(sim->kc, kc, len); |
| l_free(kc); |
| |
| str = l_settings_get_value(key_settings, "SIM", "SRES"); |
| if (!str) { |
| l_debug("SRES value must be present for SIM"); |
| goto try_aka; |
| } |
| |
| sres = l_util_from_hexstring(str, &len); |
| memcpy(sim->sres, sres, NUM_RANDS_MAX * EAP_SIM_SRES_LEN); |
| l_free(sres); |
| |
| str = l_settings_get_value(key_settings, "SIM", "Identity"); |
| if (!str) { |
| l_debug("Identity setting must be present for SIM"); |
| goto try_aka; |
| } |
| |
| sim->identity = l_strdup(str); |
| |
| sim->sim_supported = 1; |
| } |
| |
| try_aka: |
| if (l_settings_has_group(key_settings, "AKA")) { |
| str = l_settings_get_value(key_settings, "AKA", "KI"); |
| if (!str) { |
| l_debug("KI value must be present for AKA"); |
| goto end; |
| } |
| |
| ki = l_util_from_hexstring(str, &len); |
| memcpy(sim->ki, ki, EAP_AKA_KI_LEN); |
| l_free(ki); |
| |
| str = l_settings_get_value(key_settings, "AKA", "OPC"); |
| if (!str) { |
| l_debug("OPC value must be preset for AKA"); |
| goto end; |
| } |
| |
| opc = l_util_from_hexstring(str, &len); |
| memcpy(sim->opc, opc, EAP_AKA_OPC_LEN); |
| l_free(opc); |
| |
| str = l_settings_get_value(key_settings, "AKA", "AMF"); |
| if (!str) { |
| l_debug("AMF value must be present for AKA"); |
| goto end; |
| } |
| |
| amf = l_util_from_hexstring(str, &len); |
| memcpy(sim->amf, amf, EAP_AKA_AMF_LEN); |
| l_free(amf); |
| |
| str = l_settings_get_value(key_settings, "AKA", "SQN"); |
| if (!str) { |
| l_debug("SQN value must be present for AKA"); |
| goto end; |
| } |
| |
| sqn = l_util_from_hexstring(str, &len); |
| memcpy(sim->sqn, sqn, EAP_AKA_SQN_LEN); |
| l_free(sqn); |
| |
| str = l_settings_get_value(key_settings, "AKA", "Identity"); |
| if (!str) { |
| l_debug("Identity setting must be present for AKA"); |
| goto end; |
| } |
| |
| sim->identity = l_strdup(str); |
| |
| sim->aka_supported = 1; |
| } |
| end: |
| l_settings_free(key_settings); |
| |
| if (!sim->sim_supported && !sim->aka_supported) { |
| l_debug("error parsing config file, values missing"); |
| |
| return -EINVAL; |
| } |
| |
| sim->auth = iwd_sim_auth_create(&hardcoded_sim_driver); |
| |
| iwd_sim_auth_set_nai(sim->auth, sim->identity); |
| iwd_sim_auth_set_capabilities(sim->auth, sim->sim_supported, |
| sim->aka_supported); |
| |
| iwd_sim_auth_register(sim->auth); |
| |
| return 0; |
| } |
| |
| static void sim_hardcoded_exit(void) |
| { |
| iwd_sim_auth_remove(sim->auth); |
| |
| if (sim) |
| l_free(sim->identity); |
| |
| l_free(sim); |
| } |
| |
| L_PLUGIN_DEFINE(__iwd_builtin_sim_hardcoded, sim_hardcoded, |
| "Hardcoded SIM driver", "1.0", L_PLUGIN_PRIORITY_DEFAULT, |
| sim_hardcoded_init, sim_hardcoded_exit) |