1 |
/* |
2 |
* HLR/AuC testing gateway for hostapd EAP-SIM/AKA database/authenticator |
3 |
* Copyright (c) 2005-2007, 2012, Jouni Malinen <j@w1.fi> |
4 |
* |
5 |
* This software may be distributed under the terms of the BSD license. |
6 |
* See README for more details. |
7 |
* |
8 |
* This is an example implementation of the EAP-SIM/AKA database/authentication |
9 |
* gateway interface to HLR/AuC. It is expected to be replaced with an |
10 |
* implementation of SS7 gateway to GSM/UMTS authentication center (HLR/AuC) or |
11 |
* a local implementation of SIM triplet and AKA authentication data generator. |
12 |
* |
13 |
* hostapd will send SIM/AKA authentication queries over a UNIX domain socket |
14 |
* to and external program, e.g., this hlr_auc_gw. This interface uses simple |
15 |
* text-based format: |
16 |
* |
17 |
* EAP-SIM / GSM triplet query/response: |
18 |
* SIM-REQ-AUTH <IMSI> <max_chal> |
19 |
* SIM-RESP-AUTH <IMSI> Kc1:SRES1:RAND1 Kc2:SRES2:RAND2 [Kc3:SRES3:RAND3] |
20 |
* SIM-RESP-AUTH <IMSI> FAILURE |
21 |
* |
22 |
* EAP-AKA / UMTS query/response: |
23 |
* AKA-REQ-AUTH <IMSI> |
24 |
* AKA-RESP-AUTH <IMSI> <RAND> <AUTN> <IK> <CK> <RES> |
25 |
* AKA-RESP-AUTH <IMSI> FAILURE |
26 |
* |
27 |
* EAP-AKA / UMTS AUTS (re-synchronization): |
28 |
* AKA-AUTS <IMSI> <AUTS> <RAND> |
29 |
* |
30 |
* IMSI and max_chal are sent as an ASCII string, |
31 |
* Kc/SRES/RAND/AUTN/IK/CK/RES/AUTS as hex strings. |
32 |
* |
33 |
* The example implementation here reads GSM authentication triplets from a |
34 |
* text file in IMSI:Kc:SRES:RAND format, IMSI in ASCII, other fields as hex |
35 |
* strings. This is used to simulate an HLR/AuC. As such, it is not very useful |
36 |
* for real life authentication, but it is useful both as an example |
37 |
* implementation and for EAP-SIM/AKA/AKA' testing. |
38 |
* |
39 |
* SQN generation follows the not time-based Profile 2 described in |
40 |
* 3GPP TS 33.102 Annex C.3.2. The length of IND is 5 bits by default, but this |
41 |
* can be changed with a command line options if needed. |
42 |
*/ |
43 |
|
44 |
#include "includes.h" |
45 |
#include <sys/un.h> |
46 |
#ifdef CONFIG_SQLITE |
47 |
#include <sqlite3.h> |
48 |
#endif /* CONFIG_SQLITE */ |
49 |
|
50 |
#include "common.h" |
51 |
#include "crypto/milenage.h" |
52 |
#include "crypto/random.h" |
53 |
|
54 |
static const char *default_socket_path = "/tmp/hlr_auc_gw.sock"; |
55 |
static const char *socket_path; |
56 |
static int serv_sock = -1; |
57 |
static char *milenage_file = NULL; |
58 |
static int update_milenage = 0; |
59 |
static int sqn_changes = 0; |
60 |
static int ind_len = 5; |
61 |
|
62 |
/* GSM triplets */ |
63 |
struct gsm_triplet { |
64 |
struct gsm_triplet *next; |
65 |
char imsi[20]; |
66 |
u8 kc[8]; |
67 |
u8 sres[4]; |
68 |
u8 _rand[16]; |
69 |
}; |
70 |
|
71 |
static struct gsm_triplet *gsm_db = NULL, *gsm_db_pos = NULL; |
72 |
|
73 |
/* OPc and AMF parameters for Milenage (Example algorithms for AKA). */ |
74 |
struct milenage_parameters { |
75 |
struct milenage_parameters *next; |
76 |
char imsi[20]; |
77 |
u8 ki[16]; |
78 |
u8 opc[16]; |
79 |
u8 amf[2]; |
80 |
u8 sqn[6]; |
81 |
int set; |
82 |
}; |
83 |
|
84 |
static struct milenage_parameters *milenage_db = NULL; |
85 |
|
86 |
#define EAP_SIM_MAX_CHAL 3 |
87 |
|
88 |
#define EAP_AKA_RAND_LEN 16 |
89 |
#define EAP_AKA_AUTN_LEN 16 |
90 |
#define EAP_AKA_AUTS_LEN 14 |
91 |
#define EAP_AKA_RES_MAX_LEN 16 |
92 |
#define EAP_AKA_IK_LEN 16 |
93 |
#define EAP_AKA_CK_LEN 16 |
94 |
|
95 |
|
96 |
#ifdef CONFIG_SQLITE |
97 |
|
98 |
static sqlite3 *sqlite_db = NULL; |
99 |
static struct milenage_parameters db_tmp_milenage; |
100 |
|
101 |
|
102 |
static int db_table_exists(sqlite3 *db, const char *name) |
103 |
{ |
104 |
char cmd[128]; |
105 |
os_snprintf(cmd, sizeof(cmd), "SELECT 1 FROM %s;", name); |
106 |
return sqlite3_exec(db, cmd, NULL, NULL, NULL) == SQLITE_OK; |
107 |
} |
108 |
|
109 |
|
110 |
static int db_table_create_milenage(sqlite3 *db) |
111 |
{ |
112 |
char *err = NULL; |
113 |
const char *sql = |
114 |
"CREATE TABLE milenage(" |
115 |
" imsi INTEGER PRIMARY KEY NOT NULL," |
116 |
" ki CHAR(32) NOT NULL," |
117 |
" opc CHAR(32) NOT NULL," |
118 |
" amf CHAR(4) NOT NULL," |
119 |
" sqn CHAR(12) NOT NULL" |
120 |
");"; |
121 |
|
122 |
printf("Adding database table for milenage information\n"); |
123 |
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) { |
124 |
printf("SQLite error: %s\n", err); |
125 |
sqlite3_free(err); |
126 |
return -1; |
127 |
} |
128 |
|
129 |
return 0; |
130 |
} |
131 |
|
132 |
|
133 |
static sqlite3 * db_open(const char *db_file) |
134 |
{ |
135 |
sqlite3 *db; |
136 |
|
137 |
if (sqlite3_open(db_file, &db)) { |
138 |
printf("Failed to open database %s: %s\n", |
139 |
db_file, sqlite3_errmsg(db)); |
140 |
sqlite3_close(db); |
141 |
return NULL; |
142 |
} |
143 |
|
144 |
if (!db_table_exists(db, "milenage") && |
145 |
db_table_create_milenage(db) < 0) { |
146 |
sqlite3_close(db); |
147 |
return NULL; |
148 |
} |
149 |
|
150 |
return db; |
151 |
} |
152 |
|
153 |
|
154 |
static int get_milenage_cb(void *ctx, int argc, char *argv[], char *col[]) |
155 |
{ |
156 |
struct milenage_parameters *m = ctx; |
157 |
int i; |
158 |
|
159 |
m->set = 1; |
160 |
|
161 |
for (i = 0; i < argc; i++) { |
162 |
if (os_strcmp(col[i], "ki") == 0 && argv[i] && |
163 |
hexstr2bin(argv[i], m->ki, sizeof(m->ki))) { |
164 |
printf("Invalid ki value in database\n"); |
165 |
return -1; |
166 |
} |
167 |
|
168 |
if (os_strcmp(col[i], "opc") == 0 && argv[i] && |
169 |
hexstr2bin(argv[i], m->opc, sizeof(m->opc))) { |
170 |
printf("Invalid opcvalue in database\n"); |
171 |
return -1; |
172 |
} |
173 |
|
174 |
if (os_strcmp(col[i], "amf") == 0 && argv[i] && |
175 |
hexstr2bin(argv[i], m->amf, sizeof(m->amf))) { |
176 |
printf("Invalid amf value in database\n"); |
177 |
return -1; |
178 |
} |
179 |
|
180 |
if (os_strcmp(col[i], "sqn") == 0 && argv[i] && |
181 |
hexstr2bin(argv[i], m->sqn, sizeof(m->sqn))) { |
182 |
printf("Invalid sqn value in database\n"); |
183 |
return -1; |
184 |
} |
185 |
} |
186 |
|
187 |
return 0; |
188 |
} |
189 |
|
190 |
|
191 |
static struct milenage_parameters * db_get_milenage(const char *imsi_txt) |
192 |
{ |
193 |
char cmd[128]; |
194 |
unsigned long long imsi; |
195 |
|
196 |
os_memset(&db_tmp_milenage, 0, sizeof(db_tmp_milenage)); |
197 |
imsi = atoll(imsi_txt); |
198 |
os_snprintf(db_tmp_milenage.imsi, sizeof(db_tmp_milenage.imsi), |
199 |
"%llu", imsi); |
200 |
os_snprintf(cmd, sizeof(cmd), |
201 |
"SELECT ki,opc,amf,sqn FROM milenage WHERE imsi=%llu;", |
202 |
imsi); |
203 |
if (sqlite3_exec(sqlite_db, cmd, get_milenage_cb, &db_tmp_milenage, |
204 |
NULL) != SQLITE_OK) |
205 |
return NULL; |
206 |
|
207 |
if (!db_tmp_milenage.set) |
208 |
return NULL; |
209 |
return &db_tmp_milenage; |
210 |
} |
211 |
|
212 |
|
213 |
static int db_update_milenage_sqn(struct milenage_parameters *m) |
214 |
{ |
215 |
char cmd[128], val[13], *pos; |
216 |
|
217 |
pos = val; |
218 |
pos += wpa_snprintf_hex(pos, sizeof(val), m->sqn, 6); |
219 |
*pos = '\0'; |
220 |
os_snprintf(cmd, sizeof(cmd), |
221 |
"UPDATE milenage SET sqn='%s' WHERE imsi=%s;", |
222 |
val, m->imsi); |
223 |
if (sqlite3_exec(sqlite_db, cmd, NULL, NULL, NULL) != SQLITE_OK) { |
224 |
printf("Failed to update SQN in database for IMSI %s\n", |
225 |
m->imsi); |
226 |
return -1; |
227 |
} |
228 |
return 0; |
229 |
} |
230 |
|
231 |
#endif /* CONFIG_SQLITE */ |
232 |
|
233 |
|
234 |
static int open_socket(const char *path) |
235 |
{ |
236 |
struct sockaddr_un addr; |
237 |
int s; |
238 |
|
239 |
s = socket(PF_UNIX, SOCK_DGRAM, 0); |
240 |
if (s < 0) { |
241 |
perror("socket(PF_UNIX)"); |
242 |
return -1; |
243 |
} |
244 |
|
245 |
memset(&addr, 0, sizeof(addr)); |
246 |
addr.sun_family = AF_UNIX; |
247 |
os_strlcpy(addr.sun_path, path, sizeof(addr.sun_path)); |
248 |
if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
249 |
perror("hlr-auc-gw: bind(PF_UNIX)"); |
250 |
close(s); |
251 |
return -1; |
252 |
} |
253 |
|
254 |
return s; |
255 |
} |
256 |
|
257 |
|
258 |
static int read_gsm_triplets(const char *fname) |
259 |
{ |
260 |
FILE *f; |
261 |
char buf[200], *pos, *pos2; |
262 |
struct gsm_triplet *g = NULL; |
263 |
int line, ret = 0; |
264 |
|
265 |
if (fname == NULL) |
266 |
return -1; |
267 |
|
268 |
f = fopen(fname, "r"); |
269 |
if (f == NULL) { |
270 |
printf("Could not open GSM tripler data file '%s'\n", fname); |
271 |
return -1; |
272 |
} |
273 |
|
274 |
line = 0; |
275 |
while (fgets(buf, sizeof(buf), f)) { |
276 |
line++; |
277 |
|
278 |
/* Parse IMSI:Kc:SRES:RAND */ |
279 |
buf[sizeof(buf) - 1] = '\0'; |
280 |
if (buf[0] == '#') |
281 |
continue; |
282 |
pos = buf; |
283 |
while (*pos != '\0' && *pos != '\n') |
284 |
pos++; |
285 |
if (*pos == '\n') |
286 |
*pos = '\0'; |
287 |
pos = buf; |
288 |
if (*pos == '\0') |
289 |
continue; |
290 |
|
291 |
g = os_zalloc(sizeof(*g)); |
292 |
if (g == NULL) { |
293 |
ret = -1; |
294 |
break; |
295 |
} |
296 |
|
297 |
/* IMSI */ |
298 |
pos2 = strchr(pos, ':'); |
299 |
if (pos2 == NULL) { |
300 |
printf("%s:%d - Invalid IMSI (%s)\n", |
301 |
fname, line, pos); |
302 |
ret = -1; |
303 |
break; |
304 |
} |
305 |
*pos2 = '\0'; |
306 |
if (strlen(pos) >= sizeof(g->imsi)) { |
307 |
printf("%s:%d - Too long IMSI (%s)\n", |
308 |
fname, line, pos); |
309 |
ret = -1; |
310 |
break; |
311 |
} |
312 |
os_strlcpy(g->imsi, pos, sizeof(g->imsi)); |
313 |
pos = pos2 + 1; |
314 |
|
315 |
/* Kc */ |
316 |
pos2 = strchr(pos, ':'); |
317 |
if (pos2 == NULL) { |
318 |
printf("%s:%d - Invalid Kc (%s)\n", fname, line, pos); |
319 |
ret = -1; |
320 |
break; |
321 |
} |
322 |
*pos2 = '\0'; |
323 |
if (strlen(pos) != 16 || hexstr2bin(pos, g->kc, 8)) { |
324 |
printf("%s:%d - Invalid Kc (%s)\n", fname, line, pos); |
325 |
ret = -1; |
326 |
break; |
327 |
} |
328 |
pos = pos2 + 1; |
329 |
|
330 |
/* SRES */ |
331 |
pos2 = strchr(pos, ':'); |
332 |
if (pos2 == NULL) { |
333 |
printf("%s:%d - Invalid SRES (%s)\n", fname, line, |
334 |
pos); |
335 |
ret = -1; |
336 |
break; |
337 |
} |
338 |
*pos2 = '\0'; |
339 |
if (strlen(pos) != 8 || hexstr2bin(pos, g->sres, 4)) { |
340 |
printf("%s:%d - Invalid SRES (%s)\n", fname, line, |
341 |
pos); |
342 |
ret = -1; |
343 |
break; |
344 |
} |
345 |
pos = pos2 + 1; |
346 |
|
347 |
/* RAND */ |
348 |
pos2 = strchr(pos, ':'); |
349 |
if (pos2) |
350 |
*pos2 = '\0'; |
351 |
if (strlen(pos) != 32 || hexstr2bin(pos, g->_rand, 16)) { |
352 |
printf("%s:%d - Invalid RAND (%s)\n", fname, line, |
353 |
pos); |
354 |
ret = -1; |
355 |
break; |
356 |
} |
357 |
pos = pos2 + 1; |
358 |
|
359 |
g->next = gsm_db; |
360 |
gsm_db = g; |
361 |
g = NULL; |
362 |
} |
363 |
os_free(g); |
364 |
|
365 |
fclose(f); |
366 |
|
367 |
return ret; |
368 |
} |
369 |
|
370 |
|
371 |
static struct gsm_triplet * get_gsm_triplet(const char *imsi) |
372 |
{ |
373 |
struct gsm_triplet *g = gsm_db_pos; |
374 |
|
375 |
while (g) { |
376 |
if (strcmp(g->imsi, imsi) == 0) { |
377 |
gsm_db_pos = g->next; |
378 |
return g; |
379 |
} |
380 |
g = g->next; |
381 |
} |
382 |
|
383 |
g = gsm_db; |
384 |
while (g && g != gsm_db_pos) { |
385 |
if (strcmp(g->imsi, imsi) == 0) { |
386 |
gsm_db_pos = g->next; |
387 |
return g; |
388 |
} |
389 |
g = g->next; |
390 |
} |
391 |
|
392 |
return NULL; |
393 |
} |
394 |
|
395 |
|
396 |
static int read_milenage(const char *fname) |
397 |
{ |
398 |
FILE *f; |
399 |
char buf[200], *pos, *pos2; |
400 |
struct milenage_parameters *m = NULL; |
401 |
int line, ret = 0; |
402 |
|
403 |
if (fname == NULL) |
404 |
return -1; |
405 |
|
406 |
f = fopen(fname, "r"); |
407 |
if (f == NULL) { |
408 |
printf("Could not open Milenage data file '%s'\n", fname); |
409 |
return -1; |
410 |
} |
411 |
|
412 |
line = 0; |
413 |
while (fgets(buf, sizeof(buf), f)) { |
414 |
line++; |
415 |
|
416 |
/* Parse IMSI Ki OPc AMF SQN */ |
417 |
buf[sizeof(buf) - 1] = '\0'; |
418 |
if (buf[0] == '#') |
419 |
continue; |
420 |
pos = buf; |
421 |
while (*pos != '\0' && *pos != '\n') |
422 |
pos++; |
423 |
if (*pos == '\n') |
424 |
*pos = '\0'; |
425 |
pos = buf; |
426 |
if (*pos == '\0') |
427 |
continue; |
428 |
|
429 |
m = os_zalloc(sizeof(*m)); |
430 |
if (m == NULL) { |
431 |
ret = -1; |
432 |
break; |
433 |
} |
434 |
|
435 |
/* IMSI */ |
436 |
pos2 = strchr(pos, ' '); |
437 |
if (pos2 == NULL) { |
438 |
printf("%s:%d - Invalid IMSI (%s)\n", |
439 |
fname, line, pos); |
440 |
ret = -1; |
441 |
break; |
442 |
} |
443 |
*pos2 = '\0'; |
444 |
if (strlen(pos) >= sizeof(m->imsi)) { |
445 |
printf("%s:%d - Too long IMSI (%s)\n", |
446 |
fname, line, pos); |
447 |
ret = -1; |
448 |
break; |
449 |
} |
450 |
os_strlcpy(m->imsi, pos, sizeof(m->imsi)); |
451 |
pos = pos2 + 1; |
452 |
|
453 |
/* Ki */ |
454 |
pos2 = strchr(pos, ' '); |
455 |
if (pos2 == NULL) { |
456 |
printf("%s:%d - Invalid Ki (%s)\n", fname, line, pos); |
457 |
ret = -1; |
458 |
break; |
459 |
} |
460 |
*pos2 = '\0'; |
461 |
if (strlen(pos) != 32 || hexstr2bin(pos, m->ki, 16)) { |
462 |
printf("%s:%d - Invalid Ki (%s)\n", fname, line, pos); |
463 |
ret = -1; |
464 |
break; |
465 |
} |
466 |
pos = pos2 + 1; |
467 |
|
468 |
/* OPc */ |
469 |
pos2 = strchr(pos, ' '); |
470 |
if (pos2 == NULL) { |
471 |
printf("%s:%d - Invalid OPc (%s)\n", fname, line, pos); |
472 |
ret = -1; |
473 |
break; |
474 |
} |
475 |
*pos2 = '\0'; |
476 |
if (strlen(pos) != 32 || hexstr2bin(pos, m->opc, 16)) { |
477 |
printf("%s:%d - Invalid OPc (%s)\n", fname, line, pos); |
478 |
ret = -1; |
479 |
break; |
480 |
} |
481 |
pos = pos2 + 1; |
482 |
|
483 |
/* AMF */ |
484 |
pos2 = strchr(pos, ' '); |
485 |
if (pos2 == NULL) { |
486 |
printf("%s:%d - Invalid AMF (%s)\n", fname, line, pos); |
487 |
ret = -1; |
488 |
break; |
489 |
} |
490 |
*pos2 = '\0'; |
491 |
if (strlen(pos) != 4 || hexstr2bin(pos, m->amf, 2)) { |
492 |
printf("%s:%d - Invalid AMF (%s)\n", fname, line, pos); |
493 |
ret = -1; |
494 |
break; |
495 |
} |
496 |
pos = pos2 + 1; |
497 |
|
498 |
/* SQN */ |
499 |
pos2 = strchr(pos, ' '); |
500 |
if (pos2) |
501 |
*pos2 = '\0'; |
502 |
if (strlen(pos) != 12 || hexstr2bin(pos, m->sqn, 6)) { |
503 |
printf("%s:%d - Invalid SEQ (%s)\n", fname, line, pos); |
504 |
ret = -1; |
505 |
break; |
506 |
} |
507 |
pos = pos2 + 1; |
508 |
|
509 |
m->next = milenage_db; |
510 |
milenage_db = m; |
511 |
m = NULL; |
512 |
} |
513 |
os_free(m); |
514 |
|
515 |
fclose(f); |
516 |
|
517 |
return ret; |
518 |
} |
519 |
|
520 |
|
521 |
static void update_milenage_file(const char *fname) |
522 |
{ |
523 |
FILE *f, *f2; |
524 |
char buf[500], *pos; |
525 |
char *end = buf + sizeof(buf); |
526 |
struct milenage_parameters *m; |
527 |
size_t imsi_len; |
528 |
|
529 |
f = fopen(fname, "r"); |
530 |
if (f == NULL) { |
531 |
printf("Could not open Milenage data file '%s'\n", fname); |
532 |
return; |
533 |
} |
534 |
|
535 |
snprintf(buf, sizeof(buf), "%s.new", fname); |
536 |
f2 = fopen(buf, "w"); |
537 |
if (f2 == NULL) { |
538 |
printf("Could not write Milenage data file '%s'\n", buf); |
539 |
fclose(f); |
540 |
return; |
541 |
} |
542 |
|
543 |
while (fgets(buf, sizeof(buf), f)) { |
544 |
/* IMSI Ki OPc AMF SQN */ |
545 |
buf[sizeof(buf) - 1] = '\0'; |
546 |
|
547 |
pos = strchr(buf, ' '); |
548 |
if (buf[0] == '#' || pos == NULL || pos - buf >= 20) |
549 |
goto no_update; |
550 |
|
551 |
imsi_len = pos - buf; |
552 |
|
553 |
for (m = milenage_db; m; m = m->next) { |
554 |
if (strncmp(buf, m->imsi, imsi_len) == 0 && |
555 |
m->imsi[imsi_len] == '\0') |
556 |
break; |
557 |
} |
558 |
|
559 |
if (!m) |
560 |
goto no_update; |
561 |
|
562 |
pos = buf; |
563 |
pos += snprintf(pos, end - pos, "%s ", m->imsi); |
564 |
pos += wpa_snprintf_hex(pos, end - pos, m->ki, 16); |
565 |
*pos++ = ' '; |
566 |
pos += wpa_snprintf_hex(pos, end - pos, m->opc, 16); |
567 |
*pos++ = ' '; |
568 |
pos += wpa_snprintf_hex(pos, end - pos, m->amf, 2); |
569 |
*pos++ = ' '; |
570 |
pos += wpa_snprintf_hex(pos, end - pos, m->sqn, 6); |
571 |
*pos++ = '\n'; |
572 |
|
573 |
no_update: |
574 |
fprintf(f2, "%s", buf); |
575 |
} |
576 |
|
577 |
fclose(f2); |
578 |
fclose(f); |
579 |
|
580 |
snprintf(buf, sizeof(buf), "%s.bak", fname); |
581 |
if (rename(fname, buf) < 0) { |
582 |
perror("rename"); |
583 |
return; |
584 |
} |
585 |
|
586 |
snprintf(buf, sizeof(buf), "%s.new", fname); |
587 |
if (rename(buf, fname) < 0) { |
588 |
perror("rename"); |
589 |
return; |
590 |
} |
591 |
|
592 |
} |
593 |
|
594 |
|
595 |
static struct milenage_parameters * get_milenage(const char *imsi) |
596 |
{ |
597 |
struct milenage_parameters *m = milenage_db; |
598 |
|
599 |
while (m) { |
600 |
if (strcmp(m->imsi, imsi) == 0) |
601 |
break; |
602 |
m = m->next; |
603 |
} |
604 |
|
605 |
#ifdef CONFIG_SQLITE |
606 |
if (!m) |
607 |
m = db_get_milenage(imsi); |
608 |
#endif /* CONFIG_SQLITE */ |
609 |
|
610 |
return m; |
611 |
} |
612 |
|
613 |
|
614 |
static void sim_req_auth(int s, struct sockaddr_un *from, socklen_t fromlen, |
615 |
char *imsi) |
616 |
{ |
617 |
int count, max_chal, ret; |
618 |
char *pos; |
619 |
char reply[1000], *rpos, *rend; |
620 |
struct milenage_parameters *m; |
621 |
struct gsm_triplet *g; |
622 |
|
623 |
reply[0] = '\0'; |
624 |
|
625 |
pos = strchr(imsi, ' '); |
626 |
if (pos) { |
627 |
*pos++ = '\0'; |
628 |
max_chal = atoi(pos); |
629 |
if (max_chal < 1 || max_chal < EAP_SIM_MAX_CHAL) |
630 |
max_chal = EAP_SIM_MAX_CHAL; |
631 |
} else |
632 |
max_chal = EAP_SIM_MAX_CHAL; |
633 |
|
634 |
rend = &reply[sizeof(reply)]; |
635 |
rpos = reply; |
636 |
ret = snprintf(rpos, rend - rpos, "SIM-RESP-AUTH %s", imsi); |
637 |
if (ret < 0 || ret >= rend - rpos) |
638 |
return; |
639 |
rpos += ret; |
640 |
|
641 |
m = get_milenage(imsi); |
642 |
if (m) { |
643 |
u8 _rand[16], sres[4], kc[8]; |
644 |
for (count = 0; count < max_chal; count++) { |
645 |
if (random_get_bytes(_rand, 16) < 0) |
646 |
return; |
647 |
gsm_milenage(m->opc, m->ki, _rand, sres, kc); |
648 |
*rpos++ = ' '; |
649 |
rpos += wpa_snprintf_hex(rpos, rend - rpos, kc, 8); |
650 |
*rpos++ = ':'; |
651 |
rpos += wpa_snprintf_hex(rpos, rend - rpos, sres, 4); |
652 |
*rpos++ = ':'; |
653 |
rpos += wpa_snprintf_hex(rpos, rend - rpos, _rand, 16); |
654 |
} |
655 |
*rpos = '\0'; |
656 |
goto send; |
657 |
} |
658 |
|
659 |
count = 0; |
660 |
while (count < max_chal && (g = get_gsm_triplet(imsi))) { |
661 |
if (strcmp(g->imsi, imsi) != 0) |
662 |
continue; |
663 |
|
664 |
if (rpos < rend) |
665 |
*rpos++ = ' '; |
666 |
rpos += wpa_snprintf_hex(rpos, rend - rpos, g->kc, 8); |
667 |
if (rpos < rend) |
668 |
*rpos++ = ':'; |
669 |
rpos += wpa_snprintf_hex(rpos, rend - rpos, g->sres, 4); |
670 |
if (rpos < rend) |
671 |
*rpos++ = ':'; |
672 |
rpos += wpa_snprintf_hex(rpos, rend - rpos, g->_rand, 16); |
673 |
count++; |
674 |
} |
675 |
|
676 |
if (count == 0) { |
677 |
printf("No GSM triplets found for %s\n", imsi); |
678 |
ret = snprintf(rpos, rend - rpos, " FAILURE"); |
679 |
if (ret < 0 || ret >= rend - rpos) |
680 |
return; |
681 |
rpos += ret; |
682 |
} |
683 |
|
684 |
send: |
685 |
printf("Send: %s\n", reply); |
686 |
if (sendto(s, reply, rpos - reply, 0, |
687 |
(struct sockaddr *) from, fromlen) < 0) |
688 |
perror("send"); |
689 |
} |
690 |
|
691 |
|
692 |
static void inc_sqn(u8 *sqn) |
693 |
{ |
694 |
u64 val, seq, ind; |
695 |
|
696 |
/* |
697 |
* SQN = SEQ | IND = SEQ1 | SEQ2 | IND |
698 |
* |
699 |
* The mechanism used here is not time-based, so SEQ2 is void and |
700 |
* SQN = SEQ1 | IND. The length of IND is ind_len bits and the length |
701 |
* of SEQ1 is 48 - ind_len bits. |
702 |
*/ |
703 |
|
704 |
/* Increment both SEQ and IND by one */ |
705 |
val = ((u64) WPA_GET_BE32(sqn) << 16) | ((u64) WPA_GET_BE16(sqn + 4)); |
706 |
seq = (val >> ind_len) + 1; |
707 |
ind = (val + 1) & ((1 << ind_len) - 1); |
708 |
val = (seq << ind_len) | ind; |
709 |
WPA_PUT_BE32(sqn, val >> 16); |
710 |
WPA_PUT_BE16(sqn + 4, val & 0xffff); |
711 |
} |
712 |
|
713 |
|
714 |
static void aka_req_auth(int s, struct sockaddr_un *from, socklen_t fromlen, |
715 |
char *imsi) |
716 |
{ |
717 |
/* AKA-RESP-AUTH <IMSI> <RAND> <AUTN> <IK> <CK> <RES> */ |
718 |
char reply[1000], *pos, *end; |
719 |
u8 _rand[EAP_AKA_RAND_LEN]; |
720 |
u8 autn[EAP_AKA_AUTN_LEN]; |
721 |
u8 ik[EAP_AKA_IK_LEN]; |
722 |
u8 ck[EAP_AKA_CK_LEN]; |
723 |
u8 res[EAP_AKA_RES_MAX_LEN]; |
724 |
size_t res_len; |
725 |
int ret; |
726 |
struct milenage_parameters *m; |
727 |
int failed = 0; |
728 |
|
729 |
m = get_milenage(imsi); |
730 |
if (m) { |
731 |
if (random_get_bytes(_rand, EAP_AKA_RAND_LEN) < 0) |
732 |
return; |
733 |
res_len = EAP_AKA_RES_MAX_LEN; |
734 |
inc_sqn(m->sqn); |
735 |
#ifdef CONFIG_SQLITE |
736 |
db_update_milenage_sqn(m); |
737 |
#endif /* CONFIG_SQLITE */ |
738 |
sqn_changes = 1; |
739 |
printf("AKA: Milenage with SQN=%02x%02x%02x%02x%02x%02x\n", |
740 |
m->sqn[0], m->sqn[1], m->sqn[2], |
741 |
m->sqn[3], m->sqn[4], m->sqn[5]); |
742 |
milenage_generate(m->opc, m->amf, m->ki, m->sqn, _rand, |
743 |
autn, ik, ck, res, &res_len); |
744 |
} else { |
745 |
printf("Unknown IMSI: %s\n", imsi); |
746 |
#ifdef AKA_USE_FIXED_TEST_VALUES |
747 |
printf("Using fixed test values for AKA\n"); |
748 |
memset(_rand, '0', EAP_AKA_RAND_LEN); |
749 |
memset(autn, '1', EAP_AKA_AUTN_LEN); |
750 |
memset(ik, '3', EAP_AKA_IK_LEN); |
751 |
memset(ck, '4', EAP_AKA_CK_LEN); |
752 |
memset(res, '2', EAP_AKA_RES_MAX_LEN); |
753 |
res_len = EAP_AKA_RES_MAX_LEN; |
754 |
#else /* AKA_USE_FIXED_TEST_VALUES */ |
755 |
failed = 1; |
756 |
#endif /* AKA_USE_FIXED_TEST_VALUES */ |
757 |
} |
758 |
|
759 |
pos = reply; |
760 |
end = &reply[sizeof(reply)]; |
761 |
ret = snprintf(pos, end - pos, "AKA-RESP-AUTH %s ", imsi); |
762 |
if (ret < 0 || ret >= end - pos) |
763 |
return; |
764 |
pos += ret; |
765 |
if (failed) { |
766 |
ret = snprintf(pos, end - pos, "FAILURE"); |
767 |
if (ret < 0 || ret >= end - pos) |
768 |
return; |
769 |
pos += ret; |
770 |
goto done; |
771 |
} |
772 |
pos += wpa_snprintf_hex(pos, end - pos, _rand, EAP_AKA_RAND_LEN); |
773 |
*pos++ = ' '; |
774 |
pos += wpa_snprintf_hex(pos, end - pos, autn, EAP_AKA_AUTN_LEN); |
775 |
*pos++ = ' '; |
776 |
pos += wpa_snprintf_hex(pos, end - pos, ik, EAP_AKA_IK_LEN); |
777 |
*pos++ = ' '; |
778 |
pos += wpa_snprintf_hex(pos, end - pos, ck, EAP_AKA_CK_LEN); |
779 |
*pos++ = ' '; |
780 |
pos += wpa_snprintf_hex(pos, end - pos, res, res_len); |
781 |
|
782 |
done: |
783 |
printf("Send: %s\n", reply); |
784 |
|
785 |
if (sendto(s, reply, pos - reply, 0, (struct sockaddr *) from, |
786 |
fromlen) < 0) |
787 |
perror("send"); |
788 |
} |
789 |
|
790 |
|
791 |
static void aka_auts(int s, struct sockaddr_un *from, socklen_t fromlen, |
792 |
char *imsi) |
793 |
{ |
794 |
char *auts, *__rand; |
795 |
u8 _auts[EAP_AKA_AUTS_LEN], _rand[EAP_AKA_RAND_LEN], sqn[6]; |
796 |
struct milenage_parameters *m; |
797 |
|
798 |
/* AKA-AUTS <IMSI> <AUTS> <RAND> */ |
799 |
|
800 |
auts = strchr(imsi, ' '); |
801 |
if (auts == NULL) |
802 |
return; |
803 |
*auts++ = '\0'; |
804 |
|
805 |
__rand = strchr(auts, ' '); |
806 |
if (__rand == NULL) |
807 |
return; |
808 |
*__rand++ = '\0'; |
809 |
|
810 |
printf("AKA-AUTS: IMSI=%s AUTS=%s RAND=%s\n", imsi, auts, __rand); |
811 |
if (hexstr2bin(auts, _auts, EAP_AKA_AUTS_LEN) || |
812 |
hexstr2bin(__rand, _rand, EAP_AKA_RAND_LEN)) { |
813 |
printf("Could not parse AUTS/RAND\n"); |
814 |
return; |
815 |
} |
816 |
|
817 |
m = get_milenage(imsi); |
818 |
if (m == NULL) { |
819 |
printf("Unknown IMSI: %s\n", imsi); |
820 |
return; |
821 |
} |
822 |
|
823 |
if (milenage_auts(m->opc, m->ki, _rand, _auts, sqn)) { |
824 |
printf("AKA-AUTS: Incorrect MAC-S\n"); |
825 |
} else { |
826 |
memcpy(m->sqn, sqn, 6); |
827 |
printf("AKA-AUTS: Re-synchronized: " |
828 |
"SQN=%02x%02x%02x%02x%02x%02x\n", |
829 |
sqn[0], sqn[1], sqn[2], sqn[3], sqn[4], sqn[5]); |
830 |
#ifdef CONFIG_SQLITE |
831 |
db_update_milenage_sqn(m); |
832 |
#endif /* CONFIG_SQLITE */ |
833 |
sqn_changes = 1; |
834 |
} |
835 |
} |
836 |
|
837 |
|
838 |
static int process(int s) |
839 |
{ |
840 |
char buf[1000]; |
841 |
struct sockaddr_un from; |
842 |
socklen_t fromlen; |
843 |
ssize_t res; |
844 |
|
845 |
fromlen = sizeof(from); |
846 |
res = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *) &from, |
847 |
&fromlen); |
848 |
if (res < 0) { |
849 |
perror("recvfrom"); |
850 |
return -1; |
851 |
} |
852 |
|
853 |
if (res == 0) |
854 |
return 0; |
855 |
|
856 |
if ((size_t) res >= sizeof(buf)) |
857 |
res = sizeof(buf) - 1; |
858 |
buf[res] = '\0'; |
859 |
|
860 |
printf("Received: %s\n", buf); |
861 |
|
862 |
if (strncmp(buf, "SIM-REQ-AUTH ", 13) == 0) |
863 |
sim_req_auth(s, &from, fromlen, buf + 13); |
864 |
else if (strncmp(buf, "AKA-REQ-AUTH ", 13) == 0) |
865 |
aka_req_auth(s, &from, fromlen, buf + 13); |
866 |
else if (strncmp(buf, "AKA-AUTS ", 9) == 0) |
867 |
aka_auts(s, &from, fromlen, buf + 9); |
868 |
else |
869 |
printf("Unknown request: %s\n", buf); |
870 |
|
871 |
return 0; |
872 |
} |
873 |
|
874 |
|
875 |
static void cleanup(void) |
876 |
{ |
877 |
struct gsm_triplet *g, *gprev; |
878 |
struct milenage_parameters *m, *prev; |
879 |
|
880 |
if (update_milenage && milenage_file && sqn_changes) |
881 |
update_milenage_file(milenage_file); |
882 |
|
883 |
g = gsm_db; |
884 |
while (g) { |
885 |
gprev = g; |
886 |
g = g->next; |
887 |
os_free(gprev); |
888 |
} |
889 |
|
890 |
m = milenage_db; |
891 |
while (m) { |
892 |
prev = m; |
893 |
m = m->next; |
894 |
os_free(prev); |
895 |
} |
896 |
|
897 |
close(serv_sock); |
898 |
unlink(socket_path); |
899 |
|
900 |
#ifdef CONFIG_SQLITE |
901 |
if (sqlite_db) { |
902 |
sqlite3_close(sqlite_db); |
903 |
sqlite_db = NULL; |
904 |
} |
905 |
#endif /* CONFIG_SQLITE */ |
906 |
} |
907 |
|
908 |
|
909 |
static void handle_term(int sig) |
910 |
{ |
911 |
printf("Signal %d - terminate\n", sig); |
912 |
exit(0); |
913 |
} |
914 |
|
915 |
|
916 |
static void usage(void) |
917 |
{ |
918 |
printf("HLR/AuC testing gateway for hostapd EAP-SIM/AKA " |
919 |
"database/authenticator\n" |
920 |
"Copyright (c) 2005-2007, 2012, Jouni Malinen <j@w1.fi>\n" |
921 |
"\n" |
922 |
"usage:\n" |
923 |
"hlr_auc_gw [-hu] [-s<socket path>] [-g<triplet file>] " |
924 |
"[-m<milenage file>] \\\n" |
925 |
" [-D<DB file>] [-i<IND len in bits>]\n" |
926 |
"\n" |
927 |
"options:\n" |
928 |
" -h = show this usage help\n" |
929 |
" -u = update SQN in Milenage file on exit\n" |
930 |
" -s<socket path> = path for UNIX domain socket\n" |
931 |
" (default: %s)\n" |
932 |
" -g<triplet file> = path for GSM authentication triplets\n" |
933 |
" -m<milenage file> = path for Milenage keys\n" |
934 |
" -D<DB file> = path to SQLite database\n" |
935 |
" -i<IND len in bits> = IND length for SQN (default: 5)\n", |
936 |
default_socket_path); |
937 |
} |
938 |
|
939 |
|
940 |
int main(int argc, char *argv[]) |
941 |
{ |
942 |
int c; |
943 |
char *gsm_triplet_file = NULL; |
944 |
char *sqlite_db_file = NULL; |
945 |
|
946 |
if (os_program_init()) |
947 |
return -1; |
948 |
|
949 |
socket_path = default_socket_path; |
950 |
|
951 |
for (;;) { |
952 |
c = getopt(argc, argv, "D:g:hi:m:s:u"); |
953 |
if (c < 0) |
954 |
break; |
955 |
switch (c) { |
956 |
case 'D': |
957 |
#ifdef CONFIG_SQLITE |
958 |
sqlite_db_file = optarg; |
959 |
break; |
960 |
#else /* CONFIG_SQLITE */ |
961 |
printf("No SQLite support included in the build\n"); |
962 |
return -1; |
963 |
#endif /* CONFIG_SQLITE */ |
964 |
case 'g': |
965 |
gsm_triplet_file = optarg; |
966 |
break; |
967 |
case 'h': |
968 |
usage(); |
969 |
return 0; |
970 |
case 'i': |
971 |
ind_len = atoi(optarg); |
972 |
if (ind_len < 0 || ind_len > 32) { |
973 |
printf("Invalid IND length\n"); |
974 |
return -1; |
975 |
} |
976 |
break; |
977 |
case 'm': |
978 |
milenage_file = optarg; |
979 |
break; |
980 |
case 's': |
981 |
socket_path = optarg; |
982 |
break; |
983 |
case 'u': |
984 |
update_milenage = 1; |
985 |
break; |
986 |
default: |
987 |
usage(); |
988 |
return -1; |
989 |
} |
990 |
} |
991 |
|
992 |
if (!gsm_triplet_file && !milenage_file && !sqlite_db_file) { |
993 |
usage(); |
994 |
return -1; |
995 |
} |
996 |
|
997 |
#ifdef CONFIG_SQLITE |
998 |
if (sqlite_db_file && (sqlite_db = db_open(sqlite_db_file)) == NULL) |
999 |
return -1; |
1000 |
#endif /* CONFIG_SQLITE */ |
1001 |
|
1002 |
if (gsm_triplet_file && read_gsm_triplets(gsm_triplet_file) < 0) |
1003 |
return -1; |
1004 |
|
1005 |
if (milenage_file && read_milenage(milenage_file) < 0) |
1006 |
return -1; |
1007 |
|
1008 |
serv_sock = open_socket(socket_path); |
1009 |
if (serv_sock < 0) |
1010 |
return -1; |
1011 |
|
1012 |
printf("Listening for requests on %s\n", socket_path); |
1013 |
|
1014 |
atexit(cleanup); |
1015 |
signal(SIGTERM, handle_term); |
1016 |
signal(SIGINT, handle_term); |
1017 |
|
1018 |
for (;;) |
1019 |
process(serv_sock); |
1020 |
|
1021 |
#ifdef CONFIG_SQLITE |
1022 |
if (sqlite_db) { |
1023 |
sqlite3_close(sqlite_db); |
1024 |
sqlite_db = NULL; |
1025 |
} |
1026 |
#endif /* CONFIG_SQLITE */ |
1027 |
|
1028 |
os_program_deinit(); |
1029 |
|
1030 |
return 0; |
1031 |
} |