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
52static const char *default_socket_path = "/tmp/hlr_auc_gw.sock";
53static const char *socket_path;
54static int serv_sock = -1;
55
56/* GSM triplets */
57struct 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
65static struct gsm_triplet *gsm_db = NULL, *gsm_db_pos = NULL;
66
67/* OPc and AMF parameters for Milenage (Example algorithms for AKA). */
68struct 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
77static 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
89static 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
113static 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
226static 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
251static 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
376static 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
390static 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
460send:
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
468static 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
532static 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
575static 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
612static 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
636static void handle_term(int sig)
637{
638	printf("Signal %d - terminate\n", sig);
639	exit(0);
640}
641
642
643static 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
663int 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