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