1 /* 2 * HLR/AuC testing gateway for hostapd EAP-SIM/AKA database/authenticator 3 * Copyright (c) 2005-2007, Jouni Malinen <j@w1.fi> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 2 as 7 * published by the Free Software Foundation. 8 * 9 * Alternatively, this software may be distributed under the terms of BSD 10 * license. 11 * 12 * See README and COPYING for more details. 13 * 14 * This is an example implementation of the EAP-SIM/AKA database/authentication 15 * gateway interface to HLR/AuC. It is expected to be replaced with an 16 * implementation of SS7 gateway to GSM/UMTS authentication center (HLR/AuC) or 17 * a local implementation of SIM triplet and AKA authentication data generator. 18 * 19 * hostapd will send SIM/AKA authentication queries over a UNIX domain socket 20 * to and external program, e.g., this hlr_auc_gw. This interface uses simple 21 * text-based format: 22 * 23 * EAP-SIM / GSM triplet query/response: 24 * SIM-REQ-AUTH <IMSI> <max_chal> 25 * SIM-RESP-AUTH <IMSI> Kc1:SRES1:RAND1 Kc2:SRES2:RAND2 [Kc3:SRES3:RAND3] 26 * SIM-RESP-AUTH <IMSI> FAILURE 27 * 28 * EAP-AKA / UMTS query/response: 29 * AKA-REQ-AUTH <IMSI> 30 * AKA-RESP-AUTH <IMSI> <RAND> <AUTN> <IK> <CK> <RES> 31 * AKA-RESP-AUTH <IMSI> FAILURE 32 * 33 * EAP-AKA / UMTS AUTS (re-synchronization): 34 * AKA-AUTS <IMSI> <AUTS> <RAND> 35 * 36 * IMSI and max_chal are sent as an ASCII string, 37 * Kc/SRES/RAND/AUTN/IK/CK/RES/AUTS as hex strings. 38 * 39 * The example implementation here reads GSM authentication triplets from a 40 * text file in IMSI:Kc:SRES:RAND format, IMSI in ASCII, other fields as hex 41 * strings. This is used to simulate an HLR/AuC. As such, it is not very useful 42 * for real life authentication, but it is useful both as an example 43 * implementation and for EAP-SIM testing. 44 */ 45 46 #include "includes.h" 47 #include <sys/un.h> 48 49 #include "common.h" 50 #include "crypto/milenage.h" 51 52 static const char *default_socket_path = "/tmp/hlr_auc_gw.sock"; 53 static const char *socket_path; 54 static int serv_sock = -1; 55 56 /* GSM triplets */ 57 struct gsm_triplet { 58 struct gsm_triplet *next; 59 char imsi[20]; 60 u8 kc[8]; 61 u8 sres[4]; 62 u8 _rand[16]; 63 }; 64 65 static struct gsm_triplet *gsm_db = NULL, *gsm_db_pos = NULL; 66 67 /* OPc and AMF parameters for Milenage (Example algorithms for AKA). */ 68 struct milenage_parameters { 69 struct milenage_parameters *next; 70 char imsi[20]; 71 u8 ki[16]; 72 u8 opc[16]; 73 u8 amf[2]; 74 u8 sqn[6]; 75 }; 76 77 static struct milenage_parameters *milenage_db = NULL; 78 79 #define EAP_SIM_MAX_CHAL 3 80 81 #define EAP_AKA_RAND_LEN 16 82 #define EAP_AKA_AUTN_LEN 16 83 #define EAP_AKA_AUTS_LEN 14 84 #define EAP_AKA_RES_MAX_LEN 16 85 #define EAP_AKA_IK_LEN 16 86 #define EAP_AKA_CK_LEN 16 87 88 89 static int open_socket(const char *path) 90 { 91 struct sockaddr_un addr; 92 int s; 93 94 s = socket(PF_UNIX, SOCK_DGRAM, 0); 95 if (s < 0) { 96 perror("socket(PF_UNIX)"); 97 return -1; 98 } 99 100 memset(&addr, 0, sizeof(addr)); 101 addr.sun_family = AF_UNIX; 102 os_strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); 103 if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { 104 perror("bind(PF_UNIX)"); 105 close(s); 106 return -1; 107 } 108 109 return s; 110 } 111 112 113 static int read_gsm_triplets(const char *fname) 114 { 115 FILE *f; 116 char buf[200], *pos, *pos2; 117 struct gsm_triplet *g = NULL; 118 int line, ret = 0; 119 120 if (fname == NULL) 121 return -1; 122 123 f = fopen(fname, "r"); 124 if (f == NULL) { 125 printf("Could not open GSM tripler data file '%s'\n", fname); 126 return -1; 127 } 128 129 line = 0; 130 while (fgets(buf, sizeof(buf), f)) { 131 line++; 132 133 /* Parse IMSI:Kc:SRES:RAND */ 134 buf[sizeof(buf) - 1] = '\0'; 135 if (buf[0] == '#') 136 continue; 137 pos = buf; 138 while (*pos != '\0' && *pos != '\n') 139 pos++; 140 if (*pos == '\n') 141 *pos = '\0'; 142 pos = buf; 143 if (*pos == '\0') 144 continue; 145 146 g = os_zalloc(sizeof(*g)); 147 if (g == NULL) { 148 ret = -1; 149 break; 150 } 151 152 /* IMSI */ 153 pos2 = strchr(pos, ':'); 154 if (pos2 == NULL) { 155 printf("%s:%d - Invalid IMSI (%s)\n", 156 fname, line, pos); 157 ret = -1; 158 break; 159 } 160 *pos2 = '\0'; 161 if (strlen(pos) >= sizeof(g->imsi)) { 162 printf("%s:%d - Too long IMSI (%s)\n", 163 fname, line, pos); 164 ret = -1; 165 break; 166 } 167 os_strlcpy(g->imsi, pos, sizeof(g->imsi)); 168 pos = pos2 + 1; 169 170 /* Kc */ 171 pos2 = strchr(pos, ':'); 172 if (pos2 == NULL) { 173 printf("%s:%d - Invalid Kc (%s)\n", fname, line, pos); 174 ret = -1; 175 break; 176 } 177 *pos2 = '\0'; 178 if (strlen(pos) != 16 || hexstr2bin(pos, g->kc, 8)) { 179 printf("%s:%d - Invalid Kc (%s)\n", fname, line, pos); 180 ret = -1; 181 break; 182 } 183 pos = pos2 + 1; 184 185 /* SRES */ 186 pos2 = strchr(pos, ':'); 187 if (pos2 == NULL) { 188 printf("%s:%d - Invalid SRES (%s)\n", fname, line, 189 pos); 190 ret = -1; 191 break; 192 } 193 *pos2 = '\0'; 194 if (strlen(pos) != 8 || hexstr2bin(pos, g->sres, 4)) { 195 printf("%s:%d - Invalid SRES (%s)\n", fname, line, 196 pos); 197 ret = -1; 198 break; 199 } 200 pos = pos2 + 1; 201 202 /* RAND */ 203 pos2 = strchr(pos, ':'); 204 if (pos2) 205 *pos2 = '\0'; 206 if (strlen(pos) != 32 || hexstr2bin(pos, g->_rand, 16)) { 207 printf("%s:%d - Invalid RAND (%s)\n", fname, line, 208 pos); 209 ret = -1; 210 break; 211 } 212 pos = pos2 + 1; 213 214 g->next = gsm_db; 215 gsm_db = g; 216 g = NULL; 217 } 218 free(g); 219 220 fclose(f); 221 222 return ret; 223 } 224 225 226 static struct gsm_triplet * get_gsm_triplet(const char *imsi) 227 { 228 struct gsm_triplet *g = gsm_db_pos; 229 230 while (g) { 231 if (strcmp(g->imsi, imsi) == 0) { 232 gsm_db_pos = g->next; 233 return g; 234 } 235 g = g->next; 236 } 237 238 g = gsm_db; 239 while (g && g != gsm_db_pos) { 240 if (strcmp(g->imsi, imsi) == 0) { 241 gsm_db_pos = g->next; 242 return g; 243 } 244 g = g->next; 245 } 246 247 return NULL; 248 } 249 250 251 static int read_milenage(const char *fname) 252 { 253 FILE *f; 254 char buf[200], *pos, *pos2; 255 struct milenage_parameters *m = NULL; 256 int line, ret = 0; 257 258 if (fname == NULL) 259 return -1; 260 261 f = fopen(fname, "r"); 262 if (f == NULL) { 263 printf("Could not open Milenage data file '%s'\n", fname); 264 return -1; 265 } 266 267 line = 0; 268 while (fgets(buf, sizeof(buf), f)) { 269 line++; 270 271 /* Parse IMSI Ki OPc AMF SQN */ 272 buf[sizeof(buf) - 1] = '\0'; 273 if (buf[0] == '#') 274 continue; 275 pos = buf; 276 while (*pos != '\0' && *pos != '\n') 277 pos++; 278 if (*pos == '\n') 279 *pos = '\0'; 280 pos = buf; 281 if (*pos == '\0') 282 continue; 283 284 m = os_zalloc(sizeof(*m)); 285 if (m == NULL) { 286 ret = -1; 287 break; 288 } 289 290 /* IMSI */ 291 pos2 = strchr(pos, ' '); 292 if (pos2 == NULL) { 293 printf("%s:%d - Invalid IMSI (%s)\n", 294 fname, line, pos); 295 ret = -1; 296 break; 297 } 298 *pos2 = '\0'; 299 if (strlen(pos) >= sizeof(m->imsi)) { 300 printf("%s:%d - Too long IMSI (%s)\n", 301 fname, line, pos); 302 ret = -1; 303 break; 304 } 305 os_strlcpy(m->imsi, pos, sizeof(m->imsi)); 306 pos = pos2 + 1; 307 308 /* Ki */ 309 pos2 = strchr(pos, ' '); 310 if (pos2 == NULL) { 311 printf("%s:%d - Invalid Ki (%s)\n", fname, line, pos); 312 ret = -1; 313 break; 314 } 315 *pos2 = '\0'; 316 if (strlen(pos) != 32 || hexstr2bin(pos, m->ki, 16)) { 317 printf("%s:%d - Invalid Ki (%s)\n", fname, line, pos); 318 ret = -1; 319 break; 320 } 321 pos = pos2 + 1; 322 323 /* OPc */ 324 pos2 = strchr(pos, ' '); 325 if (pos2 == NULL) { 326 printf("%s:%d - Invalid OPc (%s)\n", fname, line, pos); 327 ret = -1; 328 break; 329 } 330 *pos2 = '\0'; 331 if (strlen(pos) != 32 || hexstr2bin(pos, m->opc, 16)) { 332 printf("%s:%d - Invalid OPc (%s)\n", fname, line, pos); 333 ret = -1; 334 break; 335 } 336 pos = pos2 + 1; 337 338 /* AMF */ 339 pos2 = strchr(pos, ' '); 340 if (pos2 == NULL) { 341 printf("%s:%d - Invalid AMF (%s)\n", fname, line, pos); 342 ret = -1; 343 break; 344 } 345 *pos2 = '\0'; 346 if (strlen(pos) != 4 || hexstr2bin(pos, m->amf, 2)) { 347 printf("%s:%d - Invalid AMF (%s)\n", fname, line, pos); 348 ret = -1; 349 break; 350 } 351 pos = pos2 + 1; 352 353 /* SQN */ 354 pos2 = strchr(pos, ' '); 355 if (pos2) 356 *pos2 = '\0'; 357 if (strlen(pos) != 12 || hexstr2bin(pos, m->sqn, 6)) { 358 printf("%s:%d - Invalid SEQ (%s)\n", fname, line, pos); 359 ret = -1; 360 break; 361 } 362 pos = pos2 + 1; 363 364 m->next = milenage_db; 365 milenage_db = m; 366 m = NULL; 367 } 368 free(m); 369 370 fclose(f); 371 372 return ret; 373 } 374 375 376 static struct milenage_parameters * get_milenage(const char *imsi) 377 { 378 struct milenage_parameters *m = milenage_db; 379 380 while (m) { 381 if (strcmp(m->imsi, imsi) == 0) 382 break; 383 m = m->next; 384 } 385 386 return m; 387 } 388 389 390 static void sim_req_auth(int s, struct sockaddr_un *from, socklen_t fromlen, 391 char *imsi) 392 { 393 int count, max_chal, ret; 394 char *pos; 395 char reply[1000], *rpos, *rend; 396 struct milenage_parameters *m; 397 struct gsm_triplet *g; 398 399 reply[0] = '\0'; 400 401 pos = strchr(imsi, ' '); 402 if (pos) { 403 *pos++ = '\0'; 404 max_chal = atoi(pos); 405 if (max_chal < 1 || max_chal < EAP_SIM_MAX_CHAL) 406 max_chal = EAP_SIM_MAX_CHAL; 407 } else 408 max_chal = EAP_SIM_MAX_CHAL; 409 410 rend = &reply[sizeof(reply)]; 411 rpos = reply; 412 ret = snprintf(rpos, rend - rpos, "SIM-RESP-AUTH %s", imsi); 413 if (ret < 0 || ret >= rend - rpos) 414 return; 415 rpos += ret; 416 417 m = get_milenage(imsi); 418 if (m) { 419 u8 _rand[16], sres[4], kc[8]; 420 for (count = 0; count < max_chal; count++) { 421 if (os_get_random(_rand, 16) < 0) 422 return; 423 gsm_milenage(m->opc, m->ki, _rand, sres, kc); 424 *rpos++ = ' '; 425 rpos += wpa_snprintf_hex(rpos, rend - rpos, kc, 8); 426 *rpos++ = ':'; 427 rpos += wpa_snprintf_hex(rpos, rend - rpos, sres, 4); 428 *rpos++ = ':'; 429 rpos += wpa_snprintf_hex(rpos, rend - rpos, _rand, 16); 430 } 431 *rpos = '\0'; 432 goto send; 433 } 434 435 count = 0; 436 while (count < max_chal && (g = get_gsm_triplet(imsi))) { 437 if (strcmp(g->imsi, imsi) != 0) 438 continue; 439 440 if (rpos < rend) 441 *rpos++ = ' '; 442 rpos += wpa_snprintf_hex(rpos, rend - rpos, g->kc, 8); 443 if (rpos < rend) 444 *rpos++ = ':'; 445 rpos += wpa_snprintf_hex(rpos, rend - rpos, g->sres, 4); 446 if (rpos < rend) 447 *rpos++ = ':'; 448 rpos += wpa_snprintf_hex(rpos, rend - rpos, g->_rand, 16); 449 count++; 450 } 451 452 if (count == 0) { 453 printf("No GSM triplets found for %s\n", imsi); 454 ret = snprintf(rpos, rend - rpos, " FAILURE"); 455 if (ret < 0 || ret >= rend - rpos) 456 return; 457 rpos += ret; 458 } 459 460 send: 461 printf("Send: %s\n", reply); 462 if (sendto(s, reply, rpos - reply, 0, 463 (struct sockaddr *) from, fromlen) < 0) 464 perror("send"); 465 } 466 467 468 static void aka_req_auth(int s, struct sockaddr_un *from, socklen_t fromlen, 469 char *imsi) 470 { 471 /* AKA-RESP-AUTH <IMSI> <RAND> <AUTN> <IK> <CK> <RES> */ 472 char reply[1000], *pos, *end; 473 u8 _rand[EAP_AKA_RAND_LEN]; 474 u8 autn[EAP_AKA_AUTN_LEN]; 475 u8 ik[EAP_AKA_IK_LEN]; 476 u8 ck[EAP_AKA_CK_LEN]; 477 u8 res[EAP_AKA_RES_MAX_LEN]; 478 size_t res_len; 479 int ret; 480 struct milenage_parameters *m; 481 482 m = get_milenage(imsi); 483 if (m) { 484 if (os_get_random(_rand, EAP_AKA_RAND_LEN) < 0) 485 return; 486 res_len = EAP_AKA_RES_MAX_LEN; 487 inc_byte_array(m->sqn, 6); 488 printf("AKA: Milenage with SQN=%02x%02x%02x%02x%02x%02x\n", 489 m->sqn[0], m->sqn[1], m->sqn[2], 490 m->sqn[3], m->sqn[4], m->sqn[5]); 491 milenage_generate(m->opc, m->amf, m->ki, m->sqn, _rand, 492 autn, ik, ck, res, &res_len); 493 } else { 494 printf("Unknown IMSI: %s\n", imsi); 495 #ifdef AKA_USE_FIXED_TEST_VALUES 496 printf("Using fixed test values for AKA\n"); 497 memset(_rand, '0', EAP_AKA_RAND_LEN); 498 memset(autn, '1', EAP_AKA_AUTN_LEN); 499 memset(ik, '3', EAP_AKA_IK_LEN); 500 memset(ck, '4', EAP_AKA_CK_LEN); 501 memset(res, '2', EAP_AKA_RES_MAX_LEN); 502 res_len = EAP_AKA_RES_MAX_LEN; 503 #else /* AKA_USE_FIXED_TEST_VALUES */ 504 return; 505 #endif /* AKA_USE_FIXED_TEST_VALUES */ 506 } 507 508 pos = reply; 509 end = &reply[sizeof(reply)]; 510 ret = snprintf(pos, end - pos, "AKA-RESP-AUTH %s ", imsi); 511 if (ret < 0 || ret >= end - pos) 512 return; 513 pos += ret; 514 pos += wpa_snprintf_hex(pos, end - pos, _rand, EAP_AKA_RAND_LEN); 515 *pos++ = ' '; 516 pos += wpa_snprintf_hex(pos, end - pos, autn, EAP_AKA_AUTN_LEN); 517 *pos++ = ' '; 518 pos += wpa_snprintf_hex(pos, end - pos, ik, EAP_AKA_IK_LEN); 519 *pos++ = ' '; 520 pos += wpa_snprintf_hex(pos, end - pos, ck, EAP_AKA_CK_LEN); 521 *pos++ = ' '; 522 pos += wpa_snprintf_hex(pos, end - pos, res, res_len); 523 524 printf("Send: %s\n", reply); 525 526 if (sendto(s, reply, pos - reply, 0, (struct sockaddr *) from, 527 fromlen) < 0) 528 perror("send"); 529 } 530 531 532 static void aka_auts(int s, struct sockaddr_un *from, socklen_t fromlen, 533 char *imsi) 534 { 535 char *auts, *__rand; 536 u8 _auts[EAP_AKA_AUTS_LEN], _rand[EAP_AKA_RAND_LEN], sqn[6]; 537 struct milenage_parameters *m; 538 539 /* AKA-AUTS <IMSI> <AUTS> <RAND> */ 540 541 auts = strchr(imsi, ' '); 542 if (auts == NULL) 543 return; 544 *auts++ = '\0'; 545 546 __rand = strchr(auts, ' '); 547 if (__rand == NULL) 548 return; 549 *__rand++ = '\0'; 550 551 printf("AKA-AUTS: IMSI=%s AUTS=%s RAND=%s\n", imsi, auts, __rand); 552 if (hexstr2bin(auts, _auts, EAP_AKA_AUTS_LEN) || 553 hexstr2bin(__rand, _rand, EAP_AKA_RAND_LEN)) { 554 printf("Could not parse AUTS/RAND\n"); 555 return; 556 } 557 558 m = get_milenage(imsi); 559 if (m == NULL) { 560 printf("Unknown IMSI: %s\n", imsi); 561 return; 562 } 563 564 if (milenage_auts(m->opc, m->ki, _rand, _auts, sqn)) { 565 printf("AKA-AUTS: Incorrect MAC-S\n"); 566 } else { 567 memcpy(m->sqn, sqn, 6); 568 printf("AKA-AUTS: Re-synchronized: " 569 "SQN=%02x%02x%02x%02x%02x%02x\n", 570 sqn[0], sqn[1], sqn[2], sqn[3], sqn[4], sqn[5]); 571 } 572 } 573 574 575 static int process(int s) 576 { 577 char buf[1000]; 578 struct sockaddr_un from; 579 socklen_t fromlen; 580 ssize_t res; 581 582 fromlen = sizeof(from); 583 res = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *) &from, 584 &fromlen); 585 if (res < 0) { 586 perror("recvfrom"); 587 return -1; 588 } 589 590 if (res == 0) 591 return 0; 592 593 if ((size_t) res >= sizeof(buf)) 594 res = sizeof(buf) - 1; 595 buf[res] = '\0'; 596 597 printf("Received: %s\n", buf); 598 599 if (strncmp(buf, "SIM-REQ-AUTH ", 13) == 0) 600 sim_req_auth(s, &from, fromlen, buf + 13); 601 else if (strncmp(buf, "AKA-REQ-AUTH ", 13) == 0) 602 aka_req_auth(s, &from, fromlen, buf + 13); 603 else if (strncmp(buf, "AKA-AUTS ", 9) == 0) 604 aka_auts(s, &from, fromlen, buf + 9); 605 else 606 printf("Unknown request: %s\n", buf); 607 608 return 0; 609 } 610 611 612 static void cleanup(void) 613 { 614 struct gsm_triplet *g, *gprev; 615 struct milenage_parameters *m, *prev; 616 617 g = gsm_db; 618 while (g) { 619 gprev = g; 620 g = g->next; 621 free(gprev); 622 } 623 624 m = milenage_db; 625 while (m) { 626 prev = m; 627 m = m->next; 628 free(prev); 629 } 630 631 close(serv_sock); 632 unlink(socket_path); 633 } 634 635 636 static void handle_term(int sig) 637 { 638 printf("Signal %d - terminate\n", sig); 639 exit(0); 640 } 641 642 643 static void usage(void) 644 { 645 printf("HLR/AuC testing gateway for hostapd EAP-SIM/AKA " 646 "database/authenticator\n" 647 "Copyright (c) 2005-2007, Jouni Malinen <j@w1.fi>\n" 648 "\n" 649 "usage:\n" 650 "hlr_auc_gw [-h] [-s<socket path>] [-g<triplet file>] " 651 "[-m<milenage file>]\n" 652 "\n" 653 "options:\n" 654 " -h = show this usage help\n" 655 " -s<socket path> = path for UNIX domain socket\n" 656 " (default: %s)\n" 657 " -g<triplet file> = path for GSM authentication triplets\n" 658 " -m<milenage file> = path for Milenage keys\n", 659 default_socket_path); 660 } 661 662 663 int main(int argc, char *argv[]) 664 { 665 int c; 666 char *milenage_file = NULL; 667 char *gsm_triplet_file = NULL; 668 669 socket_path = default_socket_path; 670 671 for (;;) { 672 c = getopt(argc, argv, "g:hm:s:"); 673 if (c < 0) 674 break; 675 switch (c) { 676 case 'g': 677 gsm_triplet_file = optarg; 678 break; 679 case 'h': 680 usage(); 681 return 0; 682 case 'm': 683 milenage_file = optarg; 684 break; 685 case 's': 686 socket_path = optarg; 687 break; 688 default: 689 usage(); 690 return -1; 691 } 692 } 693 694 if (gsm_triplet_file && read_gsm_triplets(gsm_triplet_file) < 0) 695 return -1; 696 697 if (milenage_file && read_milenage(milenage_file) < 0) 698 return -1; 699 700 serv_sock = open_socket(socket_path); 701 if (serv_sock < 0) 702 return -1; 703 704 printf("Listening for requests on %s\n", socket_path); 705 706 atexit(cleanup); 707 signal(SIGTERM, handle_term); 708 signal(SIGINT, handle_term); 709 710 for (;;) 711 process(serv_sock); 712 713 return 0; 714 } 715