#include #include #include #include #include #include #include /* Utility program for implementing the SIM-side procedures of 3GPP Authentication and Key Agreement * as specified by 3GPP TS 33.102 Section 6.3.3 * * (C) 2021 by Harald Welte * Milenage library code used from libosmocore, which inherited it from wpa_supplicant */ /* FIXME: libosmogsm implements those, but doesn't declare them */ int milenage_f1(const uint8_t *opc, const uint8_t *k, const uint8_t *_rand, const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_a, uint8_t *mac_s); int milenage_f2345(const uint8_t *opc, const uint8_t *k, const uint8_t *_rand, uint8_t *res, uint8_t *ck, uint8_t *ik, uint8_t *ak, uint8_t *akstar); int milenage_opc_gen(uint8_t *opc, const uint8_t *k, const uint8_t *op); static int milenage_check(const uint8_t *opc, const uint8_t *k, const uint8_t *sqn, const uint8_t *_rand, const uint8_t *autn, uint8_t *ck, uint8_t *ik, uint8_t *res, size_t *res_len, uint8_t *auts) { int i; uint8_t xmac[8], ak[6], rx_sqn_bin[6]; unsigned long long rx_sqn; const uint8_t *amf; printf("=== Static SIM parameters:\n"); printf("Milenage SIM K: %s\n", osmo_hexdump_nospc(k, 16)); printf("Milenage SIM OPc: %s\n", osmo_hexdump_nospc(opc, 16)); printf("Milenage SIM SQN: %s\n", osmo_hexdump_nospc(sqn, 6)); printf("\n"); printf("=== Authentication Tuple as received from Network:\n"); printf("Milenage Input RAND: %s\n", osmo_hexdump_nospc(_rand, 16)); printf("Milenage Input AUTN: %s\n", osmo_hexdump_nospc(autn, 16)); printf("\tAUTN(+)AK: %s\n", osmo_hexdump_nospc(autn, 6)); printf("\tAMF: %s\n", osmo_hexdump_nospc(autn+6, 2)); printf("\tMAC: %s\n", osmo_hexdump_nospc(autn+8, 8)); printf("\n"); if (milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL)) return -1; *res_len = 8; printf("Milenage f2-Computed RES: %s\n", osmo_hexdump_nospc(res, *res_len)); printf("Milenage f3-Computed CK: %s\n", osmo_hexdump_nospc(ck, 16)); printf("Milenage f4-Computed IK: %s\n", osmo_hexdump_nospc(ik, 16)); printf("Milenage f5-Computed AK: %s\n", osmo_hexdump_nospc(ak, 6)); /* AUTN = (SQN ^ AK) || AMF || MAC */ for (i = 0; i < 6; i++) rx_sqn_bin[i] = autn[i] ^ ak[i]; rx_sqn = osmo_load64be_ext(rx_sqn_bin, 6); printf("Milenage Computed SQN: %s (%llu)\n", osmo_hexdump_nospc(rx_sqn_bin, 6), rx_sqn); if (memcmp(rx_sqn_bin, sqn, 6) <= 0) { printf("Milenage: RX-SQN differs from SIM SQN: Re-Sync!\n"); uint8_t auts_amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak)) return -1; printf("Milenage Computed AK*: %s", osmo_hexdump_nospc(ak, 6)); for (i = 0; i < 6; i++) auts[i] = sqn[i] ^ ak[i]; if (milenage_f1(opc, k, _rand, sqn, auts_amf, NULL, auts + 6)) return -1; printf("Milenage AUTS: %s\n", osmo_hexdump_nospc(auts, 14)); return -2; } amf = autn + 6; if (milenage_f1(opc, k, _rand, rx_sqn_bin, amf, xmac, NULL)) return -1; printf("Milenage f1-Computed XMAC: %s\n", osmo_hexdump_nospc(xmac, 8)); if (memcmp(xmac, autn + 8, 8) != 0) { fprintf(stderr, "Milenage: MAC mismatch!\n"); return -1; } return 0; } static void help() { printf( "Static SIM card parameters:\n" "-k --key\tSpecify Ki / K\n" "-o --opc\tSpecify OPC\n" "-O --op\tSpecify OP\n" "-f --amf\tSpecify AMF\n" "-s --sqn\tSpecify SQN\n" "\n" "Authentication Tuple by network:\n" //"-i --ind\tSpecify IND slot for new SQN after AUTS\n" //"-l --ind-len\tSpecify IND bit length (default=5)\n" "-r --rand\tSpecify RAND random value\n" "-A --autn\tSpecify AUTN authentication nonce\n" ); } static uint8_t g_k[16]; static uint8_t g_opc[16]; static uint8_t g_rand[16]; static uint8_t g_autn[16]; static uint8_t g_amf[16]; static unsigned long long g_sqn; static int handle_options(int argc, char **argv) { int rc, option_index; bool rand_is_set = false; bool autn_is_set = false; bool sqn_is_set = false; bool k_is_set = false; bool opc_is_set = false; bool amf_is_set = false; bool opc_is_op = false; int64_t val64; while (1) { int c; static struct option long_options[] = { { "key", 1, 0, 'k' }, { "opc", 1, 0, 'o' }, { "op", 1, 0, 'O' }, { "amf", 1, 0, 'f' }, { "sqn", 1, 0, 's' }, { "rand", 1, 0, 'r' }, { "autn", 1, 0, 'A' }, { "help", 0, 0, 'h' }, { 0, 0, 0, 0 } }; rc = 0; c = getopt_long(argc, argv, "k:o:O:f:s:r:A:h", long_options, &option_index); if (c == -1) break; switch (c) { case 'k': rc = osmo_hexparse(optarg, g_k, sizeof(g_k)); k_is_set = true; break; case 'o': rc = osmo_hexparse(optarg, g_opc, sizeof(g_opc)); opc_is_op = false; opc_is_set = true; break; case 'O': rc = osmo_hexparse(optarg, g_opc, sizeof(g_opc)); opc_is_op = true; opc_is_set = true; break; case 'A': rc = osmo_hexparse(optarg, g_autn, sizeof(g_autn)); autn_is_set = true; break; case 'f': rc = osmo_hexparse(optarg, g_amf, sizeof(g_amf)); amf_is_set = true; break; case 's': rc = osmo_str_to_int64(&val64, optarg, 10, 0, INT64_MAX); g_sqn = (unsigned long long)val64; sqn_is_set = true; break; case 'r': rc = osmo_hexparse(optarg, g_rand, sizeof(g_rand)); rand_is_set = true; break; case 'h': help(); exit(0); default: help(); exit(1); } if (rc < 0) { help(); fprintf(stderr, "\nError parsing argument of option `%c'\n", c); exit(2); } } if (!k_is_set || !opc_is_set || !autn_is_set || !rand_is_set) { fprintf(stderr, "Error: K, OP[c], AUTN and RAND are mandatory arguments\n"); fprintf(stderr, "\n"); help(); exit(2); } if (!sqn_is_set) printf("Warning: You may want to specify SQN\n"); if (!amf_is_set) printf("Warning: You may want to specify AMF\n"); if (opc_is_op) { uint8_t op[16]; memcpy(op, g_opc, 16); rc = milenage_opc_gen(g_opc, g_k, op); OSMO_ASSERT(rc == 0); } return 0; } int main(int argc, char **argv) { printf("osmo-aka-check (C) 2021 by Harald Welte\n"); printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n"); handle_options(argc, argv); printf("\n"); uint8_t ck[16]; uint8_t ik[16]; uint8_t res[16]; size_t res_len; uint8_t auts[14]; uint8_t sqn_bin[6]; int rc; osmo_store64be_ext(g_sqn, sqn_bin, 6); rc = milenage_check(g_opc, g_k, sqn_bin, g_rand, g_autn, ck, ik, res, &res_len, auts); if (rc < 0) { fprintf(stderr, "Authentication FAILED!\n"); exit(1); } else { printf("Authentication SUCCEEDED\n"); exit(0); } }