]>
Commit | Line | Data |
---|---|---|
6fc6879b JM |
1 | /* |
2 | * WPA Supplicant / PC/SC smartcard interface for USIM, GSM SIM | |
3 | * Copyright (c) 2004-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 file implements wrapper functions for accessing GSM SIM and 3GPP USIM | |
15 | * cards through PC/SC smartcard library. These functions are used to implement | |
16 | * authentication routines for EAP-SIM and EAP-AKA. | |
17 | */ | |
18 | ||
19 | #include "includes.h" | |
20 | #include <winscard.h> | |
21 | ||
22 | #include "common.h" | |
23 | #include "pcsc_funcs.h" | |
24 | ||
25 | ||
26 | /* See ETSI GSM 11.11 and ETSI TS 102 221 for details. | |
27 | * SIM commands: | |
28 | * Command APDU: CLA INS P1 P2 P3 Data | |
29 | * CLA (class of instruction): A0 for GSM, 00 for USIM | |
30 | * INS (instruction) | |
31 | * P1 P2 P3 (parameters, P3 = length of Data) | |
32 | * Response APDU: Data SW1 SW2 | |
33 | * SW1 SW2 (Status words) | |
34 | * Commands (INS P1 P2 P3): | |
35 | * SELECT: A4 00 00 02 <file_id, 2 bytes> | |
36 | * GET RESPONSE: C0 00 00 <len> | |
37 | * RUN GSM ALG: 88 00 00 00 <RAND len = 10> | |
38 | * RUN UMTS ALG: 88 00 81 <len=0x22> data: 0x10 | RAND | 0x10 | AUTN | |
39 | * P1 = ID of alg in card | |
40 | * P2 = ID of secret key | |
41 | * READ BINARY: B0 <offset high> <offset low> <len> | |
42 | * READ RECORD: B2 <record number> <mode> <len> | |
43 | * P2 (mode) = '02' (next record), '03' (previous record), | |
44 | * '04' (absolute mode) | |
45 | * VERIFY CHV: 20 00 <CHV number> 08 | |
46 | * CHANGE CHV: 24 00 <CHV number> 10 | |
47 | * DISABLE CHV: 26 00 01 08 | |
48 | * ENABLE CHV: 28 00 01 08 | |
49 | * UNBLOCK CHV: 2C 00 <00=CHV1, 02=CHV2> 10 | |
50 | * SLEEP: FA 00 00 00 | |
51 | */ | |
52 | ||
53 | /* GSM SIM commands */ | |
54 | #define SIM_CMD_SELECT 0xa0, 0xa4, 0x00, 0x00, 0x02 | |
55 | #define SIM_CMD_RUN_GSM_ALG 0xa0, 0x88, 0x00, 0x00, 0x10 | |
56 | #define SIM_CMD_GET_RESPONSE 0xa0, 0xc0, 0x00, 0x00 | |
57 | #define SIM_CMD_READ_BIN 0xa0, 0xb0, 0x00, 0x00 | |
58 | #define SIM_CMD_READ_RECORD 0xa0, 0xb2, 0x00, 0x00 | |
59 | #define SIM_CMD_VERIFY_CHV1 0xa0, 0x20, 0x00, 0x01, 0x08 | |
60 | ||
61 | /* USIM commands */ | |
62 | #define USIM_CLA 0x00 | |
63 | #define USIM_CMD_RUN_UMTS_ALG 0x00, 0x88, 0x00, 0x81, 0x22 | |
64 | #define USIM_CMD_GET_RESPONSE 0x00, 0xc0, 0x00, 0x00 | |
65 | ||
66 | #define SIM_RECORD_MODE_ABSOLUTE 0x04 | |
67 | ||
68 | #define USIM_FSP_TEMPL_TAG 0x62 | |
69 | ||
70 | #define USIM_TLV_FILE_DESC 0x82 | |
71 | #define USIM_TLV_FILE_ID 0x83 | |
72 | #define USIM_TLV_DF_NAME 0x84 | |
73 | #define USIM_TLV_PROPR_INFO 0xA5 | |
74 | #define USIM_TLV_LIFE_CYCLE_STATUS 0x8A | |
75 | #define USIM_TLV_FILE_SIZE 0x80 | |
76 | #define USIM_TLV_TOTAL_FILE_SIZE 0x81 | |
77 | #define USIM_TLV_PIN_STATUS_TEMPLATE 0xC6 | |
78 | #define USIM_TLV_SHORT_FILE_ID 0x88 | |
79 | ||
80 | #define USIM_PS_DO_TAG 0x90 | |
81 | ||
82 | #define AKA_RAND_LEN 16 | |
83 | #define AKA_AUTN_LEN 16 | |
84 | #define AKA_AUTS_LEN 14 | |
85 | #define RES_MAX_LEN 16 | |
86 | #define IK_LEN 16 | |
87 | #define CK_LEN 16 | |
88 | ||
89 | ||
90 | typedef enum { SCARD_GSM_SIM, SCARD_USIM } sim_types; | |
91 | ||
92 | struct scard_data { | |
93 | SCARDCONTEXT ctx; | |
94 | SCARDHANDLE card; | |
95 | DWORD protocol; | |
96 | sim_types sim_type; | |
97 | int pin1_required; | |
98 | }; | |
99 | ||
100 | #ifdef __MINGW32_VERSION | |
101 | /* MinGW does not yet support WinScard, so load the needed functions | |
102 | * dynamically from winscard.dll for now. */ | |
103 | ||
104 | static HINSTANCE dll = NULL; /* winscard.dll */ | |
105 | ||
106 | static const SCARD_IO_REQUEST *dll_g_rgSCardT0Pci, *dll_g_rgSCardT1Pci; | |
107 | #undef SCARD_PCI_T0 | |
108 | #define SCARD_PCI_T0 (dll_g_rgSCardT0Pci) | |
109 | #undef SCARD_PCI_T1 | |
110 | #define SCARD_PCI_T1 (dll_g_rgSCardT1Pci) | |
111 | ||
112 | ||
113 | static WINSCARDAPI LONG WINAPI | |
114 | (*dll_SCardEstablishContext)(IN DWORD dwScope, | |
115 | IN LPCVOID pvReserved1, | |
116 | IN LPCVOID pvReserved2, | |
117 | OUT LPSCARDCONTEXT phContext); | |
118 | #define SCardEstablishContext dll_SCardEstablishContext | |
119 | ||
120 | static long (*dll_SCardReleaseContext)(long hContext); | |
121 | #define SCardReleaseContext dll_SCardReleaseContext | |
122 | ||
123 | static WINSCARDAPI LONG WINAPI | |
124 | (*dll_SCardListReadersA)(IN SCARDCONTEXT hContext, | |
125 | IN LPCSTR mszGroups, | |
126 | OUT LPSTR mszReaders, | |
127 | IN OUT LPDWORD pcchReaders); | |
128 | #undef SCardListReaders | |
129 | #define SCardListReaders dll_SCardListReadersA | |
130 | ||
131 | static WINSCARDAPI LONG WINAPI | |
132 | (*dll_SCardConnectA)(IN SCARDCONTEXT hContext, | |
133 | IN LPCSTR szReader, | |
134 | IN DWORD dwShareMode, | |
135 | IN DWORD dwPreferredProtocols, | |
136 | OUT LPSCARDHANDLE phCard, | |
137 | OUT LPDWORD pdwActiveProtocol); | |
138 | #undef SCardConnect | |
139 | #define SCardConnect dll_SCardConnectA | |
140 | ||
141 | static WINSCARDAPI LONG WINAPI | |
142 | (*dll_SCardDisconnect)(IN SCARDHANDLE hCard, | |
143 | IN DWORD dwDisposition); | |
144 | #define SCardDisconnect dll_SCardDisconnect | |
145 | ||
146 | static WINSCARDAPI LONG WINAPI | |
147 | (*dll_SCardTransmit)(IN SCARDHANDLE hCard, | |
148 | IN LPCSCARD_IO_REQUEST pioSendPci, | |
149 | IN LPCBYTE pbSendBuffer, | |
150 | IN DWORD cbSendLength, | |
151 | IN OUT LPSCARD_IO_REQUEST pioRecvPci, | |
152 | OUT LPBYTE pbRecvBuffer, | |
153 | IN OUT LPDWORD pcbRecvLength); | |
154 | #define SCardTransmit dll_SCardTransmit | |
155 | ||
156 | static WINSCARDAPI LONG WINAPI | |
157 | (*dll_SCardBeginTransaction)(IN SCARDHANDLE hCard); | |
158 | #define SCardBeginTransaction dll_SCardBeginTransaction | |
159 | ||
160 | static WINSCARDAPI LONG WINAPI | |
161 | (*dll_SCardEndTransaction)(IN SCARDHANDLE hCard, IN DWORD dwDisposition); | |
162 | #define SCardEndTransaction dll_SCardEndTransaction | |
163 | ||
164 | ||
165 | static int mingw_load_symbols(void) | |
166 | { | |
167 | char *sym; | |
168 | ||
169 | if (dll) | |
170 | return 0; | |
171 | ||
172 | dll = LoadLibrary("winscard"); | |
173 | if (dll == NULL) { | |
174 | wpa_printf(MSG_DEBUG, "WinSCard: Could not load winscard.dll " | |
175 | "library"); | |
176 | return -1; | |
177 | } | |
178 | ||
179 | #define LOADSYM(s) \ | |
180 | sym = #s; \ | |
181 | dll_ ## s = (void *) GetProcAddress(dll, sym); \ | |
182 | if (dll_ ## s == NULL) \ | |
183 | goto fail; | |
184 | ||
185 | LOADSYM(SCardEstablishContext); | |
186 | LOADSYM(SCardReleaseContext); | |
187 | LOADSYM(SCardListReadersA); | |
188 | LOADSYM(SCardConnectA); | |
189 | LOADSYM(SCardDisconnect); | |
190 | LOADSYM(SCardTransmit); | |
191 | LOADSYM(SCardBeginTransaction); | |
192 | LOADSYM(SCardEndTransaction); | |
193 | LOADSYM(g_rgSCardT0Pci); | |
194 | LOADSYM(g_rgSCardT1Pci); | |
195 | ||
196 | #undef LOADSYM | |
197 | ||
198 | return 0; | |
199 | ||
200 | fail: | |
201 | wpa_printf(MSG_DEBUG, "WinSCard: Could not get address for %s from " | |
202 | "winscard.dll", sym); | |
203 | FreeLibrary(dll); | |
204 | dll = NULL; | |
205 | return -1; | |
206 | } | |
207 | ||
208 | ||
209 | static void mingw_unload_symbols(void) | |
210 | { | |
211 | if (dll == NULL) | |
212 | return; | |
213 | ||
214 | FreeLibrary(dll); | |
215 | dll = NULL; | |
216 | } | |
217 | ||
218 | #else /* __MINGW32_VERSION */ | |
219 | ||
220 | #define mingw_load_symbols() 0 | |
221 | #define mingw_unload_symbols() do { } while (0) | |
222 | ||
223 | #endif /* __MINGW32_VERSION */ | |
224 | ||
225 | ||
226 | static int _scard_select_file(struct scard_data *scard, unsigned short file_id, | |
227 | unsigned char *buf, size_t *buf_len, | |
228 | sim_types sim_type, unsigned char *aid, | |
229 | size_t aidlen); | |
230 | static int scard_select_file(struct scard_data *scard, unsigned short file_id, | |
231 | unsigned char *buf, size_t *buf_len); | |
232 | static int scard_verify_pin(struct scard_data *scard, const char *pin); | |
233 | static int scard_get_record_len(struct scard_data *scard, | |
234 | unsigned char recnum, unsigned char mode); | |
235 | static int scard_read_record(struct scard_data *scard, | |
236 | unsigned char *data, size_t len, | |
237 | unsigned char recnum, unsigned char mode); | |
238 | ||
239 | ||
240 | static int scard_parse_fsp_templ(unsigned char *buf, size_t buf_len, | |
241 | int *ps_do, int *file_len) | |
242 | { | |
243 | unsigned char *pos, *end; | |
244 | ||
245 | if (ps_do) | |
246 | *ps_do = -1; | |
247 | if (file_len) | |
248 | *file_len = -1; | |
249 | ||
250 | pos = buf; | |
251 | end = pos + buf_len; | |
252 | if (*pos != USIM_FSP_TEMPL_TAG) { | |
253 | wpa_printf(MSG_DEBUG, "SCARD: file header did not " | |
254 | "start with FSP template tag"); | |
255 | return -1; | |
256 | } | |
257 | pos++; | |
258 | if (pos >= end) | |
259 | return -1; | |
260 | if ((pos + pos[0]) < end) | |
261 | end = pos + 1 + pos[0]; | |
262 | pos++; | |
263 | wpa_hexdump(MSG_DEBUG, "SCARD: file header FSP template", | |
264 | pos, end - pos); | |
265 | ||
266 | while (pos + 1 < end) { | |
267 | wpa_printf(MSG_MSGDUMP, "SCARD: file header TLV " | |
268 | "0x%02x len=%d", pos[0], pos[1]); | |
269 | if (pos + 2 + pos[1] > end) | |
270 | break; | |
271 | ||
272 | if (pos[0] == USIM_TLV_FILE_SIZE && | |
273 | (pos[1] == 1 || pos[1] == 2) && file_len) { | |
274 | if (pos[1] == 1) | |
275 | *file_len = (int) pos[2]; | |
276 | else | |
277 | *file_len = ((int) pos[2] << 8) | | |
278 | (int) pos[3]; | |
279 | wpa_printf(MSG_DEBUG, "SCARD: file_size=%d", | |
280 | *file_len); | |
281 | } | |
282 | ||
283 | if (pos[0] == USIM_TLV_PIN_STATUS_TEMPLATE && | |
284 | pos[1] >= 2 && pos[2] == USIM_PS_DO_TAG && | |
285 | pos[3] >= 1 && ps_do) { | |
286 | wpa_printf(MSG_DEBUG, "SCARD: PS_DO=0x%02x", | |
287 | pos[4]); | |
288 | *ps_do = (int) pos[4]; | |
289 | } | |
290 | ||
291 | pos += 2 + pos[1]; | |
292 | ||
293 | if (pos == end) | |
294 | return 0; | |
295 | } | |
296 | return -1; | |
297 | } | |
298 | ||
299 | ||
300 | static int scard_pin_needed(struct scard_data *scard, | |
301 | unsigned char *hdr, size_t hlen) | |
302 | { | |
303 | if (scard->sim_type == SCARD_GSM_SIM) { | |
304 | if (hlen > SCARD_CHV1_OFFSET && | |
305 | !(hdr[SCARD_CHV1_OFFSET] & SCARD_CHV1_FLAG)) | |
306 | return 1; | |
307 | return 0; | |
308 | } | |
309 | ||
310 | if (scard->sim_type == SCARD_USIM) { | |
311 | int ps_do; | |
312 | if (scard_parse_fsp_templ(hdr, hlen, &ps_do, NULL)) | |
313 | return -1; | |
314 | /* TODO: there could be more than one PS_DO entry because of | |
315 | * multiple PINs in key reference.. */ | |
316 | if (ps_do > 0 && (ps_do & 0x80)) | |
317 | return 1; | |
318 | return 0; | |
319 | } | |
320 | ||
321 | return -1; | |
322 | } | |
323 | ||
324 | ||
325 | static int scard_get_aid(struct scard_data *scard, unsigned char *aid, | |
326 | size_t maxlen) | |
327 | { | |
328 | int rlen, rec; | |
329 | struct efdir { | |
330 | unsigned char appl_template_tag; /* 0x61 */ | |
331 | unsigned char appl_template_len; | |
332 | unsigned char appl_id_tag; /* 0x4f */ | |
333 | unsigned char aid_len; | |
334 | unsigned char rid[5]; | |
335 | unsigned char appl_code[2]; /* 0x1002 for 3G USIM */ | |
336 | } *efdir; | |
337 | unsigned char buf[100]; | |
338 | size_t blen; | |
339 | ||
340 | efdir = (struct efdir *) buf; | |
341 | blen = sizeof(buf); | |
342 | if (scard_select_file(scard, SCARD_FILE_EF_DIR, buf, &blen)) { | |
343 | wpa_printf(MSG_DEBUG, "SCARD: Failed to read EF_DIR"); | |
344 | return -1; | |
345 | } | |
346 | wpa_hexdump(MSG_DEBUG, "SCARD: EF_DIR select", buf, blen); | |
347 | ||
348 | for (rec = 1; rec < 10; rec++) { | |
349 | rlen = scard_get_record_len(scard, rec, | |
350 | SIM_RECORD_MODE_ABSOLUTE); | |
351 | if (rlen < 0) { | |
352 | wpa_printf(MSG_DEBUG, "SCARD: Failed to get EF_DIR " | |
353 | "record length"); | |
354 | return -1; | |
355 | } | |
356 | blen = sizeof(buf); | |
357 | if (rlen > (int) blen) { | |
358 | wpa_printf(MSG_DEBUG, "SCARD: Too long EF_DIR record"); | |
359 | return -1; | |
360 | } | |
361 | if (scard_read_record(scard, buf, rlen, rec, | |
362 | SIM_RECORD_MODE_ABSOLUTE) < 0) { | |
363 | wpa_printf(MSG_DEBUG, "SCARD: Failed to read " | |
364 | "EF_DIR record %d", rec); | |
365 | return -1; | |
366 | } | |
367 | wpa_hexdump(MSG_DEBUG, "SCARD: EF_DIR record", buf, rlen); | |
368 | ||
369 | if (efdir->appl_template_tag != 0x61) { | |
370 | wpa_printf(MSG_DEBUG, "SCARD: Unexpected application " | |
371 | "template tag 0x%x", | |
372 | efdir->appl_template_tag); | |
373 | continue; | |
374 | } | |
375 | ||
376 | if (efdir->appl_template_len > rlen - 2) { | |
377 | wpa_printf(MSG_DEBUG, "SCARD: Too long application " | |
378 | "template (len=%d rlen=%d)", | |
379 | efdir->appl_template_len, rlen); | |
380 | continue; | |
381 | } | |
382 | ||
383 | if (efdir->appl_id_tag != 0x4f) { | |
384 | wpa_printf(MSG_DEBUG, "SCARD: Unexpected application " | |
385 | "identifier tag 0x%x", efdir->appl_id_tag); | |
386 | continue; | |
387 | } | |
388 | ||
389 | if (efdir->aid_len < 1 || efdir->aid_len > 16) { | |
390 | wpa_printf(MSG_DEBUG, "SCARD: Invalid AID length %d", | |
391 | efdir->aid_len); | |
392 | continue; | |
393 | } | |
394 | ||
395 | wpa_hexdump(MSG_DEBUG, "SCARD: AID from EF_DIR record", | |
396 | efdir->rid, efdir->aid_len); | |
397 | ||
398 | if (efdir->appl_code[0] == 0x10 && | |
399 | efdir->appl_code[1] == 0x02) { | |
400 | wpa_printf(MSG_DEBUG, "SCARD: 3G USIM app found from " | |
401 | "EF_DIR record %d", rec); | |
402 | break; | |
403 | } | |
404 | } | |
405 | ||
406 | if (rec >= 10) { | |
407 | wpa_printf(MSG_DEBUG, "SCARD: 3G USIM app not found " | |
408 | "from EF_DIR records"); | |
409 | return -1; | |
410 | } | |
411 | ||
412 | if (efdir->aid_len > maxlen) { | |
413 | wpa_printf(MSG_DEBUG, "SCARD: Too long AID"); | |
414 | return -1; | |
415 | } | |
416 | ||
417 | os_memcpy(aid, efdir->rid, efdir->aid_len); | |
418 | ||
419 | return efdir->aid_len; | |
420 | } | |
421 | ||
422 | ||
423 | /** | |
424 | * scard_init - Initialize SIM/USIM connection using PC/SC | |
425 | * @sim_type: Allowed SIM types (SIM, USIM, or both) | |
426 | * Returns: Pointer to private data structure, or %NULL on failure | |
427 | * | |
428 | * This function is used to initialize SIM/USIM connection. PC/SC is used to | |
429 | * open connection to the SIM/USIM card and the card is verified to support the | |
430 | * selected sim_type. In addition, local flag is set if a PIN is needed to | |
431 | * access some of the card functions. Once the connection is not needed | |
432 | * anymore, scard_deinit() can be used to close it. | |
433 | */ | |
434 | struct scard_data * scard_init(scard_sim_type sim_type) | |
435 | { | |
436 | long ret; | |
437 | unsigned long len; | |
438 | struct scard_data *scard; | |
439 | #ifdef CONFIG_NATIVE_WINDOWS | |
440 | TCHAR *readers = NULL; | |
441 | #else /* CONFIG_NATIVE_WINDOWS */ | |
442 | char *readers = NULL; | |
443 | #endif /* CONFIG_NATIVE_WINDOWS */ | |
444 | unsigned char buf[100]; | |
445 | size_t blen; | |
446 | int transaction = 0; | |
447 | int pin_needed; | |
448 | ||
449 | wpa_printf(MSG_DEBUG, "SCARD: initializing smart card interface"); | |
450 | if (mingw_load_symbols()) | |
451 | return NULL; | |
452 | scard = os_zalloc(sizeof(*scard)); | |
453 | if (scard == NULL) | |
454 | return NULL; | |
455 | ||
456 | ret = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, | |
457 | &scard->ctx); | |
458 | if (ret != SCARD_S_SUCCESS) { | |
459 | wpa_printf(MSG_DEBUG, "SCARD: Could not establish smart card " | |
460 | "context (err=%ld)", ret); | |
461 | goto failed; | |
462 | } | |
463 | ||
464 | ret = SCardListReaders(scard->ctx, NULL, NULL, &len); | |
465 | if (ret != SCARD_S_SUCCESS) { | |
466 | wpa_printf(MSG_DEBUG, "SCARD: SCardListReaders failed " | |
467 | "(err=%ld)", ret); | |
468 | goto failed; | |
469 | } | |
470 | ||
471 | #ifdef UNICODE | |
472 | len *= 2; | |
473 | #endif /* UNICODE */ | |
474 | readers = os_malloc(len); | |
475 | if (readers == NULL) { | |
476 | wpa_printf(MSG_INFO, "SCARD: malloc failed\n"); | |
477 | goto failed; | |
478 | } | |
479 | ||
480 | ret = SCardListReaders(scard->ctx, NULL, readers, &len); | |
481 | if (ret != SCARD_S_SUCCESS) { | |
482 | wpa_printf(MSG_DEBUG, "SCARD: SCardListReaders failed(2) " | |
483 | "(err=%ld)", ret); | |
484 | goto failed; | |
485 | } | |
486 | if (len < 3) { | |
487 | wpa_printf(MSG_WARNING, "SCARD: No smart card readers " | |
488 | "available."); | |
489 | goto failed; | |
490 | } | |
491 | /* readers is a list of available reader. Last entry is terminated with | |
492 | * double NUL. | |
493 | * TODO: add support for selecting the reader; now just use the first | |
494 | * one.. */ | |
495 | #ifdef UNICODE | |
496 | wpa_printf(MSG_DEBUG, "SCARD: Selected reader='%S'", readers); | |
497 | #else /* UNICODE */ | |
498 | wpa_printf(MSG_DEBUG, "SCARD: Selected reader='%s'", readers); | |
499 | #endif /* UNICODE */ | |
500 | ||
501 | ret = SCardConnect(scard->ctx, readers, SCARD_SHARE_SHARED, | |
502 | SCARD_PROTOCOL_T0, &scard->card, &scard->protocol); | |
503 | if (ret != SCARD_S_SUCCESS) { | |
504 | if (ret == (long) SCARD_E_NO_SMARTCARD) | |
505 | wpa_printf(MSG_INFO, "No smart card inserted."); | |
506 | else | |
507 | wpa_printf(MSG_WARNING, "SCardConnect err=%lx", ret); | |
508 | goto failed; | |
509 | } | |
510 | ||
511 | os_free(readers); | |
512 | readers = NULL; | |
513 | ||
514 | wpa_printf(MSG_DEBUG, "SCARD: card=0x%x active_protocol=%lu (%s)", | |
515 | (unsigned int) scard->card, scard->protocol, | |
516 | scard->protocol == SCARD_PROTOCOL_T0 ? "T0" : "T1"); | |
517 | ||
518 | ret = SCardBeginTransaction(scard->card); | |
519 | if (ret != SCARD_S_SUCCESS) { | |
520 | wpa_printf(MSG_DEBUG, "SCARD: Could not begin transaction: " | |
521 | "0x%x", (unsigned int) ret); | |
522 | goto failed; | |
523 | } | |
524 | transaction = 1; | |
525 | ||
526 | blen = sizeof(buf); | |
527 | ||
528 | scard->sim_type = SCARD_GSM_SIM; | |
529 | if (sim_type == SCARD_USIM_ONLY || sim_type == SCARD_TRY_BOTH) { | |
530 | wpa_printf(MSG_DEBUG, "SCARD: verifying USIM support"); | |
531 | if (_scard_select_file(scard, SCARD_FILE_MF, buf, &blen, | |
532 | SCARD_USIM, NULL, 0)) { | |
533 | wpa_printf(MSG_DEBUG, "SCARD: USIM is not supported"); | |
534 | if (sim_type == SCARD_USIM_ONLY) | |
535 | goto failed; | |
536 | wpa_printf(MSG_DEBUG, "SCARD: Trying to use GSM SIM"); | |
537 | scard->sim_type = SCARD_GSM_SIM; | |
538 | } else { | |
539 | wpa_printf(MSG_DEBUG, "SCARD: USIM is supported"); | |
540 | scard->sim_type = SCARD_USIM; | |
541 | } | |
542 | } | |
543 | ||
544 | if (scard->sim_type == SCARD_GSM_SIM) { | |
545 | blen = sizeof(buf); | |
546 | if (scard_select_file(scard, SCARD_FILE_MF, buf, &blen)) { | |
547 | wpa_printf(MSG_DEBUG, "SCARD: Failed to read MF"); | |
548 | goto failed; | |
549 | } | |
550 | ||
551 | blen = sizeof(buf); | |
552 | if (scard_select_file(scard, SCARD_FILE_GSM_DF, buf, &blen)) { | |
553 | wpa_printf(MSG_DEBUG, "SCARD: Failed to read GSM DF"); | |
554 | goto failed; | |
555 | } | |
556 | } else { | |
557 | unsigned char aid[32]; | |
558 | int aid_len; | |
559 | ||
560 | aid_len = scard_get_aid(scard, aid, sizeof(aid)); | |
561 | if (aid_len < 0) { | |
562 | wpa_printf(MSG_DEBUG, "SCARD: Failed to find AID for " | |
563 | "3G USIM app - try to use standard 3G RID"); | |
564 | os_memcpy(aid, "\xa0\x00\x00\x00\x87", 5); | |
565 | aid_len = 5; | |
566 | } | |
567 | wpa_hexdump(MSG_DEBUG, "SCARD: 3G USIM AID", aid, aid_len); | |
568 | ||
569 | /* Select based on AID = 3G RID from EF_DIR. This is usually | |
570 | * starting with A0 00 00 00 87. */ | |
571 | blen = sizeof(buf); | |
572 | if (_scard_select_file(scard, 0, buf, &blen, scard->sim_type, | |
573 | aid, aid_len)) { | |
574 | wpa_printf(MSG_INFO, "SCARD: Failed to read 3G USIM " | |
575 | "app"); | |
576 | wpa_hexdump(MSG_INFO, "SCARD: 3G USIM AID", | |
577 | aid, aid_len); | |
578 | goto failed; | |
579 | } | |
580 | } | |
581 | ||
582 | /* Verify whether CHV1 (PIN1) is needed to access the card. */ | |
583 | pin_needed = scard_pin_needed(scard, buf, blen); | |
584 | if (pin_needed < 0) { | |
585 | wpa_printf(MSG_DEBUG, "SCARD: Failed to determine whether PIN " | |
586 | "is needed"); | |
587 | goto failed; | |
588 | } | |
589 | if (pin_needed) { | |
590 | scard->pin1_required = 1; | |
591 | wpa_printf(MSG_DEBUG, "PIN1 needed for SIM access"); | |
592 | } | |
593 | ||
594 | ret = SCardEndTransaction(scard->card, SCARD_LEAVE_CARD); | |
595 | if (ret != SCARD_S_SUCCESS) { | |
596 | wpa_printf(MSG_DEBUG, "SCARD: Could not end transaction: " | |
597 | "0x%x", (unsigned int) ret); | |
598 | } | |
599 | ||
600 | return scard; | |
601 | ||
602 | failed: | |
603 | if (transaction) | |
604 | SCardEndTransaction(scard->card, SCARD_LEAVE_CARD); | |
605 | os_free(readers); | |
606 | scard_deinit(scard); | |
607 | return NULL; | |
608 | } | |
609 | ||
610 | ||
611 | /** | |
612 | * scard_set_pin - Set PIN (CHV1/PIN1) code for accessing SIM/USIM commands | |
613 | * @scard: Pointer to private data from scard_init() | |
a17df5fb | 614 | * @pin: PIN code as an ASCII string (e.g., "1234") |
6fc6879b JM |
615 | * Returns: 0 on success, -1 on failure |
616 | */ | |
617 | int scard_set_pin(struct scard_data *scard, const char *pin) | |
618 | { | |
619 | if (scard == NULL) | |
620 | return -1; | |
621 | ||
622 | /* Verify whether CHV1 (PIN1) is needed to access the card. */ | |
623 | if (scard->pin1_required) { | |
624 | if (pin == NULL) { | |
625 | wpa_printf(MSG_DEBUG, "No PIN configured for SIM " | |
626 | "access"); | |
627 | return -1; | |
628 | } | |
629 | if (scard_verify_pin(scard, pin)) { | |
630 | wpa_printf(MSG_INFO, "PIN verification failed for " | |
631 | "SIM access"); | |
632 | return -1; | |
633 | } | |
634 | } | |
635 | ||
636 | return 0; | |
637 | } | |
638 | ||
639 | ||
640 | /** | |
641 | * scard_deinit - Deinitialize SIM/USIM connection | |
642 | * @scard: Pointer to private data from scard_init() | |
643 | * | |
644 | * This function closes the SIM/USIM connect opened with scard_init(). | |
645 | */ | |
646 | void scard_deinit(struct scard_data *scard) | |
647 | { | |
648 | long ret; | |
649 | ||
650 | if (scard == NULL) | |
651 | return; | |
652 | ||
653 | wpa_printf(MSG_DEBUG, "SCARD: deinitializing smart card interface"); | |
654 | if (scard->card) { | |
655 | ret = SCardDisconnect(scard->card, SCARD_UNPOWER_CARD); | |
656 | if (ret != SCARD_S_SUCCESS) { | |
657 | wpa_printf(MSG_DEBUG, "SCARD: Failed to disconnect " | |
658 | "smart card (err=%ld)", ret); | |
659 | } | |
660 | } | |
661 | ||
662 | if (scard->ctx) { | |
663 | ret = SCardReleaseContext(scard->ctx); | |
664 | if (ret != SCARD_S_SUCCESS) { | |
665 | wpa_printf(MSG_DEBUG, "Failed to release smart card " | |
666 | "context (err=%ld)", ret); | |
667 | } | |
668 | } | |
669 | os_free(scard); | |
670 | mingw_unload_symbols(); | |
671 | } | |
672 | ||
673 | ||
674 | static long scard_transmit(struct scard_data *scard, | |
675 | unsigned char *_send, size_t send_len, | |
676 | unsigned char *_recv, size_t *recv_len) | |
677 | { | |
678 | long ret; | |
679 | unsigned long rlen; | |
680 | ||
681 | wpa_hexdump_key(MSG_DEBUG, "SCARD: scard_transmit: send", | |
682 | _send, send_len); | |
683 | rlen = *recv_len; | |
684 | ret = SCardTransmit(scard->card, | |
685 | scard->protocol == SCARD_PROTOCOL_T1 ? | |
686 | SCARD_PCI_T1 : SCARD_PCI_T0, | |
687 | _send, (unsigned long) send_len, | |
688 | NULL, _recv, &rlen); | |
689 | *recv_len = rlen; | |
690 | if (ret == SCARD_S_SUCCESS) { | |
691 | wpa_hexdump(MSG_DEBUG, "SCARD: scard_transmit: recv", | |
692 | _recv, rlen); | |
693 | } else { | |
694 | wpa_printf(MSG_WARNING, "SCARD: SCardTransmit failed " | |
695 | "(err=0x%lx)", ret); | |
696 | } | |
697 | return ret; | |
698 | } | |
699 | ||
700 | ||
701 | static int _scard_select_file(struct scard_data *scard, unsigned short file_id, | |
702 | unsigned char *buf, size_t *buf_len, | |
703 | sim_types sim_type, unsigned char *aid, | |
704 | size_t aidlen) | |
705 | { | |
706 | long ret; | |
707 | unsigned char resp[3]; | |
708 | unsigned char cmd[50] = { SIM_CMD_SELECT }; | |
709 | int cmdlen; | |
710 | unsigned char get_resp[5] = { SIM_CMD_GET_RESPONSE }; | |
711 | size_t len, rlen; | |
712 | ||
713 | if (sim_type == SCARD_USIM) { | |
714 | cmd[0] = USIM_CLA; | |
715 | cmd[3] = 0x04; | |
716 | get_resp[0] = USIM_CLA; | |
717 | } | |
718 | ||
719 | wpa_printf(MSG_DEBUG, "SCARD: select file %04x", file_id); | |
720 | if (aid) { | |
721 | wpa_hexdump(MSG_DEBUG, "SCARD: select file by AID", | |
722 | aid, aidlen); | |
723 | if (5 + aidlen > sizeof(cmd)) | |
724 | return -1; | |
725 | cmd[2] = 0x04; /* Select by AID */ | |
726 | cmd[4] = aidlen; /* len */ | |
727 | os_memcpy(cmd + 5, aid, aidlen); | |
728 | cmdlen = 5 + aidlen; | |
729 | } else { | |
730 | cmd[5] = file_id >> 8; | |
731 | cmd[6] = file_id & 0xff; | |
732 | cmdlen = 7; | |
733 | } | |
734 | len = sizeof(resp); | |
735 | ret = scard_transmit(scard, cmd, cmdlen, resp, &len); | |
736 | if (ret != SCARD_S_SUCCESS) { | |
737 | wpa_printf(MSG_WARNING, "SCARD: SCardTransmit failed " | |
738 | "(err=0x%lx)", ret); | |
739 | return -1; | |
740 | } | |
741 | ||
742 | if (len != 2) { | |
743 | wpa_printf(MSG_WARNING, "SCARD: unexpected resp len " | |
744 | "%d (expected 2)", (int) len); | |
745 | return -1; | |
746 | } | |
747 | ||
748 | if (resp[0] == 0x98 && resp[1] == 0x04) { | |
749 | /* Security status not satisfied (PIN_WLAN) */ | |
750 | wpa_printf(MSG_WARNING, "SCARD: Security status not satisfied " | |
751 | "(PIN_WLAN)"); | |
752 | return -1; | |
753 | } | |
754 | ||
755 | if (resp[0] == 0x6e) { | |
756 | wpa_printf(MSG_DEBUG, "SCARD: used CLA not supported"); | |
757 | return -1; | |
758 | } | |
759 | ||
760 | if (resp[0] != 0x6c && resp[0] != 0x9f && resp[0] != 0x61) { | |
761 | wpa_printf(MSG_WARNING, "SCARD: unexpected response 0x%02x " | |
762 | "(expected 0x61, 0x6c, or 0x9f)", resp[0]); | |
763 | return -1; | |
764 | } | |
765 | /* Normal ending of command; resp[1] bytes available */ | |
766 | get_resp[4] = resp[1]; | |
767 | wpa_printf(MSG_DEBUG, "SCARD: trying to get response (%d bytes)", | |
768 | resp[1]); | |
769 | ||
770 | rlen = *buf_len; | |
771 | ret = scard_transmit(scard, get_resp, sizeof(get_resp), buf, &rlen); | |
772 | if (ret == SCARD_S_SUCCESS) { | |
773 | *buf_len = resp[1] < rlen ? resp[1] : rlen; | |
774 | return 0; | |
775 | } | |
776 | ||
777 | wpa_printf(MSG_WARNING, "SCARD: SCardTransmit err=0x%lx\n", ret); | |
778 | return -1; | |
779 | } | |
780 | ||
781 | ||
782 | static int scard_select_file(struct scard_data *scard, unsigned short file_id, | |
783 | unsigned char *buf, size_t *buf_len) | |
784 | { | |
785 | return _scard_select_file(scard, file_id, buf, buf_len, | |
786 | scard->sim_type, NULL, 0); | |
787 | } | |
788 | ||
789 | ||
790 | static int scard_get_record_len(struct scard_data *scard, unsigned char recnum, | |
791 | unsigned char mode) | |
792 | { | |
793 | unsigned char buf[255]; | |
794 | unsigned char cmd[5] = { SIM_CMD_READ_RECORD /* , len */ }; | |
795 | size_t blen; | |
796 | long ret; | |
797 | ||
798 | if (scard->sim_type == SCARD_USIM) | |
799 | cmd[0] = USIM_CLA; | |
800 | cmd[2] = recnum; | |
801 | cmd[3] = mode; | |
802 | cmd[4] = sizeof(buf); | |
803 | ||
804 | blen = sizeof(buf); | |
805 | ret = scard_transmit(scard, cmd, sizeof(cmd), buf, &blen); | |
806 | if (ret != SCARD_S_SUCCESS) { | |
807 | wpa_printf(MSG_DEBUG, "SCARD: failed to determine file " | |
808 | "length for record %d", recnum); | |
809 | return -1; | |
810 | } | |
811 | ||
812 | wpa_hexdump(MSG_DEBUG, "SCARD: file length determination response", | |
813 | buf, blen); | |
814 | ||
f2fe5b36 | 815 | if (blen < 2 || (buf[0] != 0x6c && buf[0] != 0x67)) { |
6fc6879b JM |
816 | wpa_printf(MSG_DEBUG, "SCARD: unexpected response to file " |
817 | "length determination"); | |
818 | return -1; | |
819 | } | |
820 | ||
821 | return buf[1]; | |
822 | } | |
823 | ||
824 | ||
825 | static int scard_read_record(struct scard_data *scard, | |
826 | unsigned char *data, size_t len, | |
827 | unsigned char recnum, unsigned char mode) | |
828 | { | |
829 | unsigned char cmd[5] = { SIM_CMD_READ_RECORD /* , len */ }; | |
830 | size_t blen = len + 3; | |
831 | unsigned char *buf; | |
832 | long ret; | |
833 | ||
834 | if (scard->sim_type == SCARD_USIM) | |
835 | cmd[0] = USIM_CLA; | |
836 | cmd[2] = recnum; | |
837 | cmd[3] = mode; | |
838 | cmd[4] = len; | |
839 | ||
840 | buf = os_malloc(blen); | |
841 | if (buf == NULL) | |
842 | return -1; | |
843 | ||
844 | ret = scard_transmit(scard, cmd, sizeof(cmd), buf, &blen); | |
845 | if (ret != SCARD_S_SUCCESS) { | |
846 | os_free(buf); | |
847 | return -2; | |
848 | } | |
849 | if (blen != len + 2) { | |
850 | wpa_printf(MSG_DEBUG, "SCARD: record read returned unexpected " | |
851 | "length %ld (expected %ld)", | |
852 | (long) blen, (long) len + 2); | |
853 | os_free(buf); | |
854 | return -3; | |
855 | } | |
856 | ||
857 | if (buf[len] != 0x90 || buf[len + 1] != 0x00) { | |
858 | wpa_printf(MSG_DEBUG, "SCARD: record read returned unexpected " | |
859 | "status %02x %02x (expected 90 00)", | |
860 | buf[len], buf[len + 1]); | |
861 | os_free(buf); | |
862 | return -4; | |
863 | } | |
864 | ||
865 | os_memcpy(data, buf, len); | |
866 | os_free(buf); | |
867 | ||
868 | return 0; | |
869 | } | |
870 | ||
871 | ||
872 | static int scard_read_file(struct scard_data *scard, | |
873 | unsigned char *data, size_t len) | |
874 | { | |
875 | unsigned char cmd[5] = { SIM_CMD_READ_BIN /* , len */ }; | |
876 | size_t blen = len + 3; | |
877 | unsigned char *buf; | |
878 | long ret; | |
879 | ||
880 | cmd[4] = len; | |
881 | ||
882 | buf = os_malloc(blen); | |
883 | if (buf == NULL) | |
884 | return -1; | |
885 | ||
886 | if (scard->sim_type == SCARD_USIM) | |
887 | cmd[0] = USIM_CLA; | |
888 | ret = scard_transmit(scard, cmd, sizeof(cmd), buf, &blen); | |
889 | if (ret != SCARD_S_SUCCESS) { | |
890 | os_free(buf); | |
891 | return -2; | |
892 | } | |
893 | if (blen != len + 2) { | |
894 | wpa_printf(MSG_DEBUG, "SCARD: file read returned unexpected " | |
895 | "length %ld (expected %ld)", | |
896 | (long) blen, (long) len + 2); | |
897 | os_free(buf); | |
898 | return -3; | |
899 | } | |
900 | ||
901 | if (buf[len] != 0x90 || buf[len + 1] != 0x00) { | |
902 | wpa_printf(MSG_DEBUG, "SCARD: file read returned unexpected " | |
903 | "status %02x %02x (expected 90 00)", | |
904 | buf[len], buf[len + 1]); | |
905 | os_free(buf); | |
906 | return -4; | |
907 | } | |
908 | ||
909 | os_memcpy(data, buf, len); | |
910 | os_free(buf); | |
911 | ||
912 | return 0; | |
913 | } | |
914 | ||
915 | ||
916 | static int scard_verify_pin(struct scard_data *scard, const char *pin) | |
917 | { | |
918 | long ret; | |
919 | unsigned char resp[3]; | |
920 | unsigned char cmd[5 + 8] = { SIM_CMD_VERIFY_CHV1 }; | |
921 | size_t len; | |
922 | ||
923 | wpa_printf(MSG_DEBUG, "SCARD: verifying PIN"); | |
924 | ||
925 | if (pin == NULL || os_strlen(pin) > 8) | |
926 | return -1; | |
927 | ||
928 | if (scard->sim_type == SCARD_USIM) | |
929 | cmd[0] = USIM_CLA; | |
930 | os_memcpy(cmd + 5, pin, os_strlen(pin)); | |
931 | os_memset(cmd + 5 + os_strlen(pin), 0xff, 8 - os_strlen(pin)); | |
932 | ||
933 | len = sizeof(resp); | |
934 | ret = scard_transmit(scard, cmd, sizeof(cmd), resp, &len); | |
935 | if (ret != SCARD_S_SUCCESS) | |
936 | return -2; | |
937 | ||
938 | if (len != 2 || resp[0] != 0x90 || resp[1] != 0x00) { | |
939 | wpa_printf(MSG_WARNING, "SCARD: PIN verification failed"); | |
940 | return -1; | |
941 | } | |
942 | ||
943 | wpa_printf(MSG_DEBUG, "SCARD: PIN verified successfully"); | |
944 | return 0; | |
945 | } | |
946 | ||
947 | ||
948 | /** | |
949 | * scard_get_imsi - Read IMSI from SIM/USIM card | |
950 | * @scard: Pointer to private data from scard_init() | |
951 | * @imsi: Buffer for IMSI | |
952 | * @len: Length of imsi buffer; set to IMSI length on success | |
953 | * Returns: 0 on success, -1 if IMSI file cannot be selected, -2 if IMSI file | |
954 | * selection returns invalid result code, -3 if parsing FSP template file fails | |
955 | * (USIM only), -4 if IMSI does not fit in the provided imsi buffer (len is set | |
956 | * to needed length), -5 if reading IMSI file fails. | |
957 | * | |
958 | * This function can be used to read IMSI from the SIM/USIM card. If the IMSI | |
959 | * file is PIN protected, scard_set_pin() must have been used to set the | |
960 | * correct PIN code before calling scard_get_imsi(). | |
961 | */ | |
962 | int scard_get_imsi(struct scard_data *scard, char *imsi, size_t *len) | |
963 | { | |
964 | unsigned char buf[100]; | |
965 | size_t blen, imsilen, i; | |
966 | char *pos; | |
967 | ||
968 | wpa_printf(MSG_DEBUG, "SCARD: reading IMSI from (GSM) EF-IMSI"); | |
969 | blen = sizeof(buf); | |
970 | if (scard_select_file(scard, SCARD_FILE_GSM_EF_IMSI, buf, &blen)) | |
971 | return -1; | |
972 | if (blen < 4) { | |
973 | wpa_printf(MSG_WARNING, "SCARD: too short (GSM) EF-IMSI " | |
974 | "header (len=%ld)", (long) blen); | |
975 | return -2; | |
976 | } | |
977 | ||
978 | if (scard->sim_type == SCARD_GSM_SIM) { | |
979 | blen = (buf[2] << 8) | buf[3]; | |
980 | } else { | |
981 | int file_size; | |
982 | if (scard_parse_fsp_templ(buf, blen, NULL, &file_size)) | |
983 | return -3; | |
984 | blen = file_size; | |
985 | } | |
986 | if (blen < 2 || blen > sizeof(buf)) { | |
987 | wpa_printf(MSG_DEBUG, "SCARD: invalid IMSI file length=%ld", | |
988 | (long) blen); | |
989 | return -3; | |
990 | } | |
991 | ||
992 | imsilen = (blen - 2) * 2 + 1; | |
993 | wpa_printf(MSG_DEBUG, "SCARD: IMSI file length=%ld imsilen=%ld", | |
994 | (long) blen, (long) imsilen); | |
995 | if (blen < 2 || imsilen > *len) { | |
996 | *len = imsilen; | |
997 | return -4; | |
998 | } | |
999 | ||
1000 | if (scard_read_file(scard, buf, blen)) | |
1001 | return -5; | |
1002 | ||
1003 | pos = imsi; | |
1004 | *pos++ = '0' + (buf[1] >> 4 & 0x0f); | |
1005 | for (i = 2; i < blen; i++) { | |
1006 | unsigned char digit; | |
1007 | ||
1008 | digit = buf[i] & 0x0f; | |
1009 | if (digit < 10) | |
1010 | *pos++ = '0' + digit; | |
1011 | else | |
1012 | imsilen--; | |
1013 | ||
1014 | digit = buf[i] >> 4 & 0x0f; | |
1015 | if (digit < 10) | |
1016 | *pos++ = '0' + digit; | |
1017 | else | |
1018 | imsilen--; | |
1019 | } | |
1020 | *len = imsilen; | |
1021 | ||
1022 | return 0; | |
1023 | } | |
1024 | ||
1025 | ||
8ab7a370 SB |
1026 | /** |
1027 | * scard_get_mnc_len - Read length of MNC in the IMSI from SIM/USIM card | |
1028 | * @scard: Pointer to private data from scard_init() | |
1029 | * Returns: length (>0) on success, -1 if administrative data file cannot be | |
1030 | * selected, -2 if administrative data file selection returns invalid result | |
1031 | * code, -3 if parsing FSP template file fails (USIM only), -4 if length of | |
1032 | * the file is unexpected, -5 if reading file fails, -6 if MNC length is not | |
1033 | * in range (i.e. 2 or 3), -7 if MNC length is not available. | |
1034 | * | |
1035 | */ | |
1036 | int scard_get_mnc_len(struct scard_data *scard) | |
1037 | { | |
1038 | unsigned char buf[100]; | |
1039 | size_t blen; | |
1040 | int file_size; | |
1041 | ||
1042 | wpa_printf(MSG_DEBUG, "SCARD: reading MNC len from (GSM) EF-AD"); | |
1043 | blen = sizeof(buf); | |
1044 | if (scard_select_file(scard, SCARD_FILE_GSM_EF_AD, buf, &blen)) | |
1045 | return -1; | |
1046 | if (blen < 4) { | |
1047 | wpa_printf(MSG_WARNING, "SCARD: too short (GSM) EF-AD " | |
1048 | "header (len=%ld)", (long) blen); | |
1049 | return -2; | |
1050 | } | |
1051 | ||
1052 | if (scard->sim_type == SCARD_GSM_SIM) { | |
1053 | file_size = (buf[2] << 8) | buf[3]; | |
1054 | } else { | |
1055 | if (scard_parse_fsp_templ(buf, blen, NULL, &file_size)) | |
1056 | return -3; | |
1057 | } | |
1058 | if (file_size == 3) { | |
1059 | wpa_printf(MSG_DEBUG, "SCARD: MNC length not available"); | |
1060 | return -7; | |
1061 | } | |
1e49cceb | 1062 | if (file_size < 4 || file_size > (int) sizeof(buf)) { |
8ab7a370 SB |
1063 | wpa_printf(MSG_DEBUG, "SCARD: invalid file length=%ld", |
1064 | (long) file_size); | |
1065 | return -4; | |
1066 | } | |
1067 | ||
1068 | if (scard_read_file(scard, buf, file_size)) | |
1069 | return -5; | |
1070 | buf[3] = buf[3] & 0x0f; /* upper nibble reserved for future use */ | |
1071 | if (buf[3] < 2 || buf[3] > 3) { | |
1072 | wpa_printf(MSG_DEBUG, "SCARD: invalid MNC length=%ld", | |
1073 | (long) buf[3]); | |
1074 | return -6; | |
1075 | } | |
1076 | wpa_printf(MSG_DEBUG, "SCARD: MNC length=%ld", (long) buf[3]); | |
1077 | return buf[3]; | |
1078 | } | |
1079 | ||
1080 | ||
6fc6879b JM |
1081 | /** |
1082 | * scard_gsm_auth - Run GSM authentication command on SIM card | |
1083 | * @scard: Pointer to private data from scard_init() | |
1084 | * @_rand: 16-byte RAND value from HLR/AuC | |
1085 | * @sres: 4-byte buffer for SRES | |
1086 | * @kc: 8-byte buffer for Kc | |
1087 | * Returns: 0 on success, -1 if SIM/USIM connection has not been initialized, | |
1088 | * -2 if authentication command execution fails, -3 if unknown response code | |
1089 | * for authentication command is received, -4 if reading of response fails, | |
1090 | * -5 if if response data is of unexpected length | |
1091 | * | |
1092 | * This function performs GSM authentication using SIM/USIM card and the | |
1093 | * provided RAND value from HLR/AuC. If authentication command can be completed | |
1094 | * successfully, SRES and Kc values will be written into sres and kc buffers. | |
1095 | */ | |
1096 | int scard_gsm_auth(struct scard_data *scard, const unsigned char *_rand, | |
1097 | unsigned char *sres, unsigned char *kc) | |
1098 | { | |
1099 | unsigned char cmd[5 + 1 + 16] = { SIM_CMD_RUN_GSM_ALG }; | |
1100 | int cmdlen; | |
1101 | unsigned char get_resp[5] = { SIM_CMD_GET_RESPONSE }; | |
1102 | unsigned char resp[3], buf[12 + 3 + 2]; | |
1103 | size_t len; | |
1104 | long ret; | |
1105 | ||
1106 | if (scard == NULL) | |
1107 | return -1; | |
1108 | ||
1109 | wpa_hexdump(MSG_DEBUG, "SCARD: GSM auth - RAND", _rand, 16); | |
1110 | if (scard->sim_type == SCARD_GSM_SIM) { | |
1111 | cmdlen = 5 + 16; | |
1112 | os_memcpy(cmd + 5, _rand, 16); | |
1113 | } else { | |
1114 | cmdlen = 5 + 1 + 16; | |
1115 | cmd[0] = USIM_CLA; | |
1116 | cmd[3] = 0x80; | |
1117 | cmd[4] = 17; | |
1118 | cmd[5] = 16; | |
1119 | os_memcpy(cmd + 6, _rand, 16); | |
1120 | } | |
1121 | len = sizeof(resp); | |
1122 | ret = scard_transmit(scard, cmd, cmdlen, resp, &len); | |
1123 | if (ret != SCARD_S_SUCCESS) | |
1124 | return -2; | |
1125 | ||
1126 | if ((scard->sim_type == SCARD_GSM_SIM && | |
1127 | (len != 2 || resp[0] != 0x9f || resp[1] != 0x0c)) || | |
1128 | (scard->sim_type == SCARD_USIM && | |
1129 | (len != 2 || resp[0] != 0x61 || resp[1] != 0x0e))) { | |
1130 | wpa_printf(MSG_WARNING, "SCARD: unexpected response for GSM " | |
1131 | "auth request (len=%ld resp=%02x %02x)", | |
1132 | (long) len, resp[0], resp[1]); | |
1133 | return -3; | |
1134 | } | |
1135 | get_resp[4] = resp[1]; | |
1136 | ||
1137 | len = sizeof(buf); | |
1138 | ret = scard_transmit(scard, get_resp, sizeof(get_resp), buf, &len); | |
1139 | if (ret != SCARD_S_SUCCESS) | |
1140 | return -4; | |
1141 | ||
1142 | if (scard->sim_type == SCARD_GSM_SIM) { | |
1143 | if (len != 4 + 8 + 2) { | |
1144 | wpa_printf(MSG_WARNING, "SCARD: unexpected data " | |
1145 | "length for GSM auth (len=%ld, expected 14)", | |
1146 | (long) len); | |
1147 | return -5; | |
1148 | } | |
1149 | os_memcpy(sres, buf, 4); | |
1150 | os_memcpy(kc, buf + 4, 8); | |
1151 | } else { | |
1152 | if (len != 1 + 4 + 1 + 8 + 2) { | |
1153 | wpa_printf(MSG_WARNING, "SCARD: unexpected data " | |
1154 | "length for USIM auth (len=%ld, " | |
1155 | "expected 16)", (long) len); | |
1156 | return -5; | |
1157 | } | |
1158 | if (buf[0] != 4 || buf[5] != 8) { | |
1159 | wpa_printf(MSG_WARNING, "SCARD: unexpected SREC/Kc " | |
1160 | "length (%d %d, expected 4 8)", | |
1161 | buf[0], buf[5]); | |
1162 | } | |
1163 | os_memcpy(sres, buf + 1, 4); | |
1164 | os_memcpy(kc, buf + 6, 8); | |
1165 | } | |
1166 | ||
1167 | wpa_hexdump(MSG_DEBUG, "SCARD: GSM auth - SRES", sres, 4); | |
1168 | wpa_hexdump(MSG_DEBUG, "SCARD: GSM auth - Kc", kc, 8); | |
1169 | ||
1170 | return 0; | |
1171 | } | |
1172 | ||
1173 | ||
1174 | /** | |
1175 | * scard_umts_auth - Run UMTS authentication command on USIM card | |
1176 | * @scard: Pointer to private data from scard_init() | |
1177 | * @_rand: 16-byte RAND value from HLR/AuC | |
1178 | * @autn: 16-byte AUTN value from HLR/AuC | |
1179 | * @res: 16-byte buffer for RES | |
1180 | * @res_len: Variable that will be set to RES length | |
1181 | * @ik: 16-byte buffer for IK | |
1182 | * @ck: 16-byte buffer for CK | |
1183 | * @auts: 14-byte buffer for AUTS | |
1184 | * Returns: 0 on success, -1 on failure, or -2 if USIM reports synchronization | |
1185 | * failure | |
1186 | * | |
1187 | * This function performs AKA authentication using USIM card and the provided | |
1188 | * RAND and AUTN values from HLR/AuC. If authentication command can be | |
1189 | * completed successfully, RES, IK, and CK values will be written into provided | |
1190 | * buffers and res_len is set to length of received RES value. If USIM reports | |
1191 | * synchronization failure, the received AUTS value will be written into auts | |
1192 | * buffer. In this case, RES, IK, and CK are not valid. | |
1193 | */ | |
1194 | int scard_umts_auth(struct scard_data *scard, const unsigned char *_rand, | |
1195 | const unsigned char *autn, | |
1196 | unsigned char *res, size_t *res_len, | |
1197 | unsigned char *ik, unsigned char *ck, unsigned char *auts) | |
1198 | { | |
1199 | unsigned char cmd[5 + 1 + AKA_RAND_LEN + 1 + AKA_AUTN_LEN] = | |
1200 | { USIM_CMD_RUN_UMTS_ALG }; | |
1201 | unsigned char get_resp[5] = { USIM_CMD_GET_RESPONSE }; | |
1202 | unsigned char resp[3], buf[64], *pos, *end; | |
1203 | size_t len; | |
1204 | long ret; | |
1205 | ||
1206 | if (scard == NULL) | |
1207 | return -1; | |
1208 | ||
1209 | if (scard->sim_type == SCARD_GSM_SIM) { | |
1210 | wpa_printf(MSG_ERROR, "SCARD: Non-USIM card - cannot do UMTS " | |
1211 | "auth"); | |
1212 | return -1; | |
1213 | } | |
1214 | ||
1215 | wpa_hexdump(MSG_DEBUG, "SCARD: UMTS auth - RAND", _rand, AKA_RAND_LEN); | |
1216 | wpa_hexdump(MSG_DEBUG, "SCARD: UMTS auth - AUTN", autn, AKA_AUTN_LEN); | |
1217 | cmd[5] = AKA_RAND_LEN; | |
1218 | os_memcpy(cmd + 6, _rand, AKA_RAND_LEN); | |
1219 | cmd[6 + AKA_RAND_LEN] = AKA_AUTN_LEN; | |
1220 | os_memcpy(cmd + 6 + AKA_RAND_LEN + 1, autn, AKA_AUTN_LEN); | |
1221 | ||
1222 | len = sizeof(resp); | |
1223 | ret = scard_transmit(scard, cmd, sizeof(cmd), resp, &len); | |
1224 | if (ret != SCARD_S_SUCCESS) | |
1225 | return -1; | |
1226 | ||
1227 | if (len <= sizeof(resp)) | |
1228 | wpa_hexdump(MSG_DEBUG, "SCARD: UMTS alg response", resp, len); | |
1229 | ||
1230 | if (len == 2 && resp[0] == 0x98 && resp[1] == 0x62) { | |
1231 | wpa_printf(MSG_WARNING, "SCARD: UMTS auth failed - " | |
1232 | "MAC != XMAC"); | |
1233 | return -1; | |
1234 | } else if (len != 2 || resp[0] != 0x61) { | |
1235 | wpa_printf(MSG_WARNING, "SCARD: unexpected response for UMTS " | |
1236 | "auth request (len=%ld resp=%02x %02x)", | |
1237 | (long) len, resp[0], resp[1]); | |
1238 | return -1; | |
1239 | } | |
1240 | get_resp[4] = resp[1]; | |
1241 | ||
1242 | len = sizeof(buf); | |
1243 | ret = scard_transmit(scard, get_resp, sizeof(get_resp), buf, &len); | |
1244 | if (ret != SCARD_S_SUCCESS || len > sizeof(buf)) | |
1245 | return -1; | |
1246 | ||
1247 | wpa_hexdump(MSG_DEBUG, "SCARD: UMTS get response result", buf, len); | |
1248 | if (len >= 2 + AKA_AUTS_LEN && buf[0] == 0xdc && | |
1249 | buf[1] == AKA_AUTS_LEN) { | |
1250 | wpa_printf(MSG_DEBUG, "SCARD: UMTS Synchronization-Failure"); | |
1251 | os_memcpy(auts, buf + 2, AKA_AUTS_LEN); | |
1252 | wpa_hexdump(MSG_DEBUG, "SCARD: AUTS", auts, AKA_AUTS_LEN); | |
1253 | return -2; | |
1254 | } else if (len >= 6 + IK_LEN + CK_LEN && buf[0] == 0xdb) { | |
1255 | pos = buf + 1; | |
1256 | end = buf + len; | |
1257 | ||
1258 | /* RES */ | |
1259 | if (pos[0] > RES_MAX_LEN || pos + pos[0] > end) { | |
1260 | wpa_printf(MSG_DEBUG, "SCARD: Invalid RES"); | |
1261 | return -1; | |
1262 | } | |
1263 | *res_len = *pos++; | |
1264 | os_memcpy(res, pos, *res_len); | |
1265 | pos += *res_len; | |
1266 | wpa_hexdump(MSG_DEBUG, "SCARD: RES", res, *res_len); | |
1267 | ||
1268 | /* CK */ | |
1269 | if (pos[0] != CK_LEN || pos + CK_LEN > end) { | |
1270 | wpa_printf(MSG_DEBUG, "SCARD: Invalid CK"); | |
1271 | return -1; | |
1272 | } | |
1273 | pos++; | |
1274 | os_memcpy(ck, pos, CK_LEN); | |
1275 | pos += CK_LEN; | |
1276 | wpa_hexdump(MSG_DEBUG, "SCARD: CK", ck, CK_LEN); | |
1277 | ||
1278 | /* IK */ | |
1279 | if (pos[0] != IK_LEN || pos + IK_LEN > end) { | |
1280 | wpa_printf(MSG_DEBUG, "SCARD: Invalid IK"); | |
1281 | return -1; | |
1282 | } | |
1283 | pos++; | |
1284 | os_memcpy(ik, pos, IK_LEN); | |
1285 | pos += IK_LEN; | |
1286 | wpa_hexdump(MSG_DEBUG, "SCARD: IK", ik, IK_LEN); | |
1287 | ||
1288 | return 0; | |
1289 | } | |
1290 | ||
1291 | wpa_printf(MSG_DEBUG, "SCARD: Unrecognized response"); | |
1292 | return -1; | |
1293 | } |