From f68dcde6c5ca3b68662efe6a408076adc7916fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomcs=C3=A1nyi=2C=20Domonkos?= Date: Thu, 26 Aug 2021 19:45:59 +0200 Subject: [PATCH] Add support for EAP-AKA against USIM in PC/SC reader --- configure.ac | 14 +- src/libcharon/Makefile.am | 7 + .../plugins/eap_simaka_pcsc/Makefile.am | 23 + .../eap_simaka_pcsc/eap_simaka_pcsc_card.c | 828 ++++++++++++++++++ .../eap_simaka_pcsc/eap_simaka_pcsc_card.h | 44 + .../eap_simaka_pcsc/eap_simaka_pcsc_plugin.c | 98 +++ .../eap_simaka_pcsc/eap_simaka_pcsc_plugin.h | 35 + 7 files changed, 1048 insertions(+), 1 deletion(-) create mode 100644 src/libcharon/plugins/eap_simaka_pcsc/Makefile.am create mode 100644 src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_card.c create mode 100644 src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_card.h create mode 100644 src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_plugin.c create mode 100644 src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_plugin.h diff --git a/configure.ac b/configure.ac index a72faeb08..73e370445 100644 --- a/configure.ac +++ b/configure.ac @@ -3,6 +3,8 @@ # Copyright (C) 2006-2019 Andreas Steffen # Copyright (C) 2006-2014 Martin Willi # HSR Hochschule fuer Technik Rapperswil +# Copyright (C) 2017 Domonkos P. Tomcsanyi +# P3 Communications Gmbh. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the @@ -199,7 +201,8 @@ ARG_ENABL_SET([eap-sim-pcsc], [enable EAP-SIM backend based on a smartcard rea ARG_ENABL_SET([eap-aka], [enable EAP AKA authentication module.]) ARG_ENABL_SET([eap-aka-3gpp], [enable EAP AKA backend implementing 3GPP MILENAGE algorithms in software.]) ARG_ENABL_SET([eap-aka-3gpp2], [enable EAP AKA backend implementing 3GPP2 algorithms in software. Requires libgmp.]) -ARG_ENABL_SET([eap-simaka-sql], [enable EAP-SIM/AKA backend based on a triplet/quintuplet SQL database.]) +ARG_ENABL_SET([eap-simaka-pcsc], [enable EAP-SIM/AKA backend based on a smartcard reader. Requires libpcsclite.]) +ARG_ENABL_SET([eap-simaka-sql], [enable EAP-SIM/AKA backend based on a triplet/quintuplet SQL database.]) ARG_ENABL_SET([eap-simaka-pseudonym], [enable EAP-SIM/AKA pseudonym storage plugin.]) ARG_ENABL_SET([eap-simaka-reauth], [enable EAP-SIM/AKA reauthentication data storage plugin.]) ARG_ENABL_SET([eap-identity], [enable EAP module providing EAP-Identity helper.]) @@ -1203,6 +1206,12 @@ if test x$eap_sim_pcsc = xtrue; then AC_SUBST(pcsclite_LIBS) fi +if test x$eap_simaka_pcsc = xtrue; then + PKG_CHECK_MODULES(pcsclite, [libpcsclite]) + AC_SUBST(pcsclite_CFLAGS) + AC_SUBST(pcsclite_LIBS) +fi + if test x$nm = xtrue; then PKG_CHECK_MODULES(nm, [gthread-2.0 libnm]) AC_SUBST(nm_CFLAGS) @@ -1533,6 +1542,7 @@ ADD_PLUGIN([eap-sim-pcsc], [c charon]) ADD_PLUGIN([eap-aka], [c charon]) ADD_PLUGIN([eap-aka-3gpp], [c charon]) ADD_PLUGIN([eap-aka-3gpp2], [c charon]) +ADD_PLUGIN([eap-simaka-pcsc], [c charon]) ADD_PLUGIN([eap-simaka-sql], [c charon]) ADD_PLUGIN([eap-simaka-pseudonym], [c charon]) ADD_PLUGIN([eap-simaka-reauth], [c charon]) @@ -1704,6 +1714,7 @@ AM_CONDITIONAL(USE_RADATTR, test x$radattr = xtrue) AM_CONDITIONAL(USE_EAP_SIM, test x$eap_sim = xtrue) AM_CONDITIONAL(USE_EAP_SIM_FILE, test x$eap_sim_file = xtrue) AM_CONDITIONAL(USE_EAP_SIM_PCSC, test x$eap_sim_pcsc = xtrue) +AM_CONDITIONAL(USE_EAP_SIMAKA_PCSC, test x$eap_simaka_pcsc = xtrue) AM_CONDITIONAL(USE_EAP_SIMAKA_SQL, test x$eap_simaka_sql = xtrue) AM_CONDITIONAL(USE_EAP_SIMAKA_PSEUDONYM, test x$eap_simaka_pseudonym = xtrue) AM_CONDITIONAL(USE_EAP_SIMAKA_REAUTH, test x$eap_simaka_reauth = xtrue) @@ -1996,6 +2007,7 @@ AC_CONFIG_FILES([ src/libcharon/plugins/eap_sim/Makefile src/libcharon/plugins/eap_sim_file/Makefile src/libcharon/plugins/eap_sim_pcsc/Makefile + src/libcharon/plugins/eap_simaka_pcsc/Makefile src/libcharon/plugins/eap_simaka_sql/Makefile src/libcharon/plugins/eap_simaka_pseudonym/Makefile src/libcharon/plugins/eap_simaka_reauth/Makefile diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index 46346a44d..43c399a8b 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -369,6 +369,13 @@ if MONOLITHIC endif endif +if USE_EAP_SIMAKA_PCSC + SUBDIRS += plugins/eap_simaka_pcsc +if MONOLITHIC + libcharon_la_LIBADD += plugins/eap_simaka_pcsc/libstrongswan-eap-simaka-pcsc.la +endif +endif + if USE_EAP_SIMAKA_REAUTH SUBDIRS += plugins/eap_simaka_reauth if MONOLITHIC diff --git a/src/libcharon/plugins/eap_simaka_pcsc/Makefile.am b/src/libcharon/plugins/eap_simaka_pcsc/Makefile.am new file mode 100644 index 000000000..3bccd7495 --- /dev/null +++ b/src/libcharon/plugins/eap_simaka_pcsc/Makefile.am @@ -0,0 +1,23 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libcharon \ + -I$(top_srcdir)/src/libsimaka + +AM_CFLAGS = \ + ${pcsclite_CFLAGS} \ + $(PLUGIN_CFLAGS) + +libstrongswan_eap_simaka_pcsc_la_LIBADD = ${pcsclite_LIBS} + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-eap-simaka-pcsc.la +else +plugin_LTLIBRARIES = libstrongswan-eap-simaka-pcsc.la +libstrongswan_eap_simaka_pcsc_la_LIBADD += $(top_builddir)/src/libsimaka/libsimaka.la +endif + +libstrongswan_eap_simaka_pcsc_la_SOURCES = \ + eap_simaka_pcsc_plugin.h eap_simaka_pcsc_plugin.c \ + eap_simaka_pcsc_card.h eap_simaka_pcsc_card.c + +libstrongswan_eap_simaka_pcsc_la_LDFLAGS = -module -avoid-version diff --git a/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_card.c b/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_card.c new file mode 100644 index 000000000..1dc1fc996 --- /dev/null +++ b/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_card.c @@ -0,0 +1,828 @@ +/* + * Copyright (C) 2017 Domonkos P. Tomcsanyi + * P3 Communications Gmbh. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#define _GNU_SOURCE +#include "eap_simaka_pcsc_card.h" +#include +#include +#include +#include + +typedef struct private_eap_simaka_pcsc_card_t private_eap_simaka_pcsc_card_t; + +/** + * Private data of an eap_simaka_pcsc_card_t object. + */ +struct private_eap_simaka_pcsc_card_t { + + /** + * Public eap_simaka_pcsc_card_t interface. + */ + eap_simaka_pcsc_card_t public; +}; + +/** + * Maximum length for an IMSI. + */ +#define SIM_IMSI_MAX_LEN 15 + +/** + * Length of the status at the end of response APDUs. + */ +#define APDU_STATUS_LEN 2 + +/** + * First byte of status word indicating success. + */ +#define APDU_SW1_SUCCESS 0x90 + +/** + * First byte of status word indicating there is response data to be read. + */ +#define APDU_SW1_RESPONSE_DATA 0x9f + +/* + * Communication status + */ +#define ERROR_NONE 0 +#define ERROR_SCARD 1 +#define ERROR_CARD_ERROR 2 + +/** + * Decode IMSI EF (Elementary File) into an ASCII string + */ +static bool decode_imsi_ef(unsigned char *input, int input_len, char *output) +{ + /* Only digits 0-9 valid in IMSIs */ + static const char bcd_num_digits[] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '\0', '\0', '\0', '\0', '\0', '\0' + }; + int i; + + /* Check length byte matches how many bytes we have, and that input + * is correct length for an IMSI */ + if (input[0] != input_len-1 || input_len < 2 || input_len > 9) + { + return FALSE; + } + + /* Check type byte is IMSI (bottom 3 bits == 001) */ + if ((input[1] & 0x07) != 0x01) + { + return FALSE; + } + *output++ = bcd_num_digits[input[1] >> 4]; + + for (i = 2; i < input_len; i++) + { + *output++ = bcd_num_digits[input[i] & 0xf]; + *output++ = bcd_num_digits[input[i] >> 4]; + } + + *output++ = '\0'; + return TRUE; +} + +METHOD(simaka_card_t, get_triplet, bool, + private_eap_simaka_pcsc_card_t *this, identification_t *id, + char rand[SIM_RAND_LEN], char sres[SIM_SRES_LEN], char kc[SIM_KC_LEN]) +{ + status_t found = FALSE; + LONG rv; + SCARDCONTEXT hContext; + DWORD dwReaders; + LPSTR mszReaders; + char *cur_reader; + char full_nai[128]; + SCARDHANDLE hCard; + enum { DISCONNECTED, CONNECTED, TRANSACTION } hCard_status = DISCONNECTED; + + snprintf(full_nai, sizeof(full_nai), "%Y", id); + + DBG2(DBG_IKE, "looking for triplet: %Y rand %b", id, rand, SIM_RAND_LEN); + + rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardEstablishContext: %s", pcsc_stringify_error(rv)); + return FALSE; + } + + rv = SCardListReaders(hContext, NULL, NULL, &dwReaders); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardListReaders: %s", pcsc_stringify_error(rv)); + return FALSE; + } + mszReaders = malloc(sizeof(char)*dwReaders); + + rv = SCardListReaders(hContext, NULL, mszReaders, &dwReaders); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardListReaders: %s", pcsc_stringify_error(rv)); + return FALSE; + } + + /* mszReaders is a multi-string of readers, separated by '\0' and + * terminated by an additional '\0' */ + for (cur_reader = mszReaders; *cur_reader != '\0' && found == FALSE; + cur_reader += strlen(cur_reader) + 1) + { + DWORD dwActiveProtocol = -1; + const SCARD_IO_REQUEST *pioSendPci; + SCARD_IO_REQUEST pioRecvPci; + BYTE pbRecvBuffer[64]; + DWORD dwRecvLength; + char imsi[SIM_IMSI_MAX_LEN + 1]; + + /* See GSM 11.11 for SIM APDUs */ + static const BYTE pbSelectMF[] = { 0xa0, 0xa4, 0x00, 0x00, 0x02, 0x3f, 0x00 }; + static const BYTE pbSelectDFGSM[] = { 0xa0, 0xa4, 0x00, 0x00, 0x02, 0x7f, 0x20 }; + static const BYTE pbSelectIMSI[] = { 0xa0, 0xa4, 0x00, 0x00, 0x02, 0x6f, 0x07 }; + static const BYTE pbReadBinary[] = { 0xa0, 0xb0, 0x00, 0x00, 0x09 }; + BYTE pbRunGSMAlgorithm[5 + SIM_RAND_LEN] = { 0xa0, 0x88, 0x00, 0x00, 0x10 }; + static const BYTE pbGetResponse[] = { 0xa0, 0xc0, 0x00, 0x00, 0x0c }; + + /* If on 2nd or later reader, make sure we end the transaction + * and disconnect card in the previous reader */ + switch (hCard_status) + { + case TRANSACTION: + SCardEndTransaction(hCard, SCARD_LEAVE_CARD); + /* FALLTHRU */ + case CONNECTED: + SCardDisconnect(hCard, SCARD_LEAVE_CARD); + /* FALLTHRU */ + case DISCONNECTED: + hCard_status = DISCONNECTED; + } + + /* Copy RAND into APDU */ + memcpy(pbRunGSMAlgorithm + 5, rand, SIM_RAND_LEN); + + rv = SCardConnect(hContext, cur_reader, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardConnect: %s", pcsc_stringify_error(rv)); + continue; + } + hCard_status = CONNECTED; + + switch(dwActiveProtocol) + { + case SCARD_PROTOCOL_T0: + pioSendPci = SCARD_PCI_T0; + break; + case SCARD_PROTOCOL_T1: + pioSendPci = SCARD_PCI_T1; + break; + default: + DBG1(DBG_IKE, "Unknown SCARD_PROTOCOL"); + continue; + } + + /* Start transaction */ + rv = SCardBeginTransaction(hCard); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardBeginTransaction: %s", pcsc_stringify_error(rv)); + continue; + } + hCard_status = TRANSACTION; + + /* APDU: Select MF */ + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, pbSelectMF, sizeof(pbSelectMF), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_RESPONSE_DATA) + { + DBG1(DBG_IKE, "Select MF failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } + + /* APDU: Select DF GSM */ + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, pbSelectDFGSM, sizeof(pbSelectDFGSM), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_RESPONSE_DATA) + { + DBG1(DBG_IKE, "Select DF GSM failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } + + /* APDU: Select IMSI */ + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, pbSelectIMSI, sizeof(pbSelectIMSI), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_RESPONSE_DATA) + { + DBG1(DBG_IKE, "Select IMSI failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } + + /* APDU: Read Binary (of IMSI) */ + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, pbReadBinary, sizeof(pbReadBinary), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_SUCCESS) + { + DBG1(DBG_IKE, "Select IMSI failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } + + if (!decode_imsi_ef(pbRecvBuffer, dwRecvLength-APDU_STATUS_LEN, imsi)) + { + DBG1(DBG_IKE, "Couldn't decode IMSI EF: %b", + pbRecvBuffer, (u_int)dwRecvLength); + continue; + } + + /* The IMSI could be post/prefixed in the full NAI, so just make sure + * it's in there */ + if (!(strlen(full_nai) && strstr(full_nai, imsi))) + { + DBG1(DBG_IKE, "Not the SIM we're looking for, IMSI: %s", imsi); + continue; + } + + /* APDU: Run GSM Algorithm */ + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, + pbRunGSMAlgorithm, sizeof(pbRunGSMAlgorithm), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_RESPONSE_DATA) + { + DBG1(DBG_IKE, "Run GSM Algorithm failed: %b", + pbRecvBuffer, (u_int)dwRecvLength); + continue; + } + + /* APDU: Get Response (of Run GSM Algorithm) */ + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, pbGetResponse, sizeof(pbGetResponse), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_SUCCESS) + { + DBG1(DBG_IKE, "Get Response failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } + + /* Extract out Kc and SRES from response */ + if (dwRecvLength == SIM_SRES_LEN + SIM_KC_LEN + APDU_STATUS_LEN) + { + memcpy(sres, pbRecvBuffer, SIM_SRES_LEN); + memcpy(kc, pbRecvBuffer+4, SIM_KC_LEN); + /* This will also cause the loop to exit */ + found = TRUE; + } + else + { + DBG1(DBG_IKE, "Get Response incorrect length: %b", + pbRecvBuffer, (u_int)dwRecvLength); + continue; + } + + /* Transaction will be ended and card disconnected at the + * beginning of this loop or after this loop */ + } + + /* Make sure we end any previous transaction and disconnect card */ + switch (hCard_status) + { + case TRANSACTION: + SCardEndTransaction(hCard, SCARD_LEAVE_CARD); + /* FALLTHRU */ + case CONNECTED: + SCardDisconnect(hCard, SCARD_LEAVE_CARD); + /* FALLTHRU */ + case DISCONNECTED: + hCard_status = DISCONNECTED; + } + + rv = SCardReleaseContext(hContext); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardReleaseContext: %s", pcsc_stringify_error(rv)); + } + + free(mszReaders); + return found; +} + +METHOD(simaka_card_t, get_quintuplet, status_t, + private_eap_simaka_pcsc_card_t *this, identification_t *id, + char rand[AKA_RAND_LEN], char autn[AKA_AUTN_LEN], char ck[AKA_CK_LEN], + char ik[AKA_IK_LEN], char res[AKA_RES_MAX], int *res_len) +{ + status_t found = SUCCESS; + LONG rv; + SCARDCONTEXT hContext; + DWORD dwReaders; + LPSTR mszReaders; + char *cur_reader; + char full_nai[128]; + SCARDHANDLE hCard; + enum { DISCONNECTED, CONNECTED, TRANSACTION } hCard_status = DISCONNECTED; + + snprintf(full_nai, sizeof(full_nai), "%Y", id); + + DBG2(DBG_IKE, "looking for quintuplet: %Y rand %b", id, rand, SIM_RAND_LEN); + + rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardEstablishContext: %s", pcsc_stringify_error(rv)); + return FALSE; + } + + rv = SCardListReaders(hContext, NULL, NULL, &dwReaders); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardListReaders: %s", pcsc_stringify_error(rv)); + return FALSE; + } + mszReaders = malloc(sizeof(char)*dwReaders); + + rv = SCardListReaders(hContext, NULL, mszReaders, &dwReaders); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardListReaders: %s", pcsc_stringify_error(rv)); + return FALSE; + } + + /* mszReaders is a multi-string of readers, separated by '\0' and + * terminated by an additional '\0' */ + for (cur_reader = mszReaders; *cur_reader != '\0' && found == FALSE; + cur_reader += strlen(cur_reader) + 1) + { + DWORD dwActiveProtocol = -1; + const SCARD_IO_REQUEST *pioSendPci; + SCARD_IO_REQUEST pioRecvPci; + BYTE pbRecvBuffer[512]; + DWORD dwRecvLength; + char imsi[SIM_IMSI_MAX_LEN + 1]; + char aid_pattern[] = {0xA0, 0x00, 0x00, 0x00, 0x87, 0x10, 0x02}; //based on mitshell/card USIM.py:SELECT_ADF_USIM() + char *correct_aid = NULL; + int resLen = 0; + + /* USIM APDUs */ + BYTE abSelectEFDIR[] = {0x00, 0xA4, 0x08, 0x04, 0x02, 0x2F, 0x00}; + // CLA SELECT P1 P2 len + // 0x00 0xC0 0x00 0x00 0x00 + BYTE abGetResponse[] = {0x00, 0xC0, 0x00, 0x00, 0x1C}; + // CLA SELECT P1 (by fileID) P2(UICC) len DATA (EF_IMSI address) + // 0x00 0xA4 0x00 0x04 0x02 0x6F 0x07 + BYTE abSelectIMSI[] = {0x00, 0xA4, 0x00, 0x04, 0x02, 0x6F, 0x07}; + BYTE abReadRecord[] = {0x00, 0xB2, 0x01, 0x04, 0x00}; //Le byte (last one) set to 0x00 so complete record is read up to 256 bytes + // CLA SELECT P1 (AID) P2(UICC) len DATA (AID) + // 0x00 0xA4 0x04 0x04 '0xc', '0xa0', '0x0', '0x0', '0x0', '0x87', '0x10', '0x2', '0xff', '0x49', '0xff', '0x5', '0x89' + BYTE abSelectUICC[] = {0x00, 0xA4, 0x04, 0x04}; + BYTE abReadBinary[] = {0x00, 0xB0, 0x00, 0x00, 0x09}; + BYTE abAuthenticate[5 + 1 + SIM_RAND_LEN + 1 + SIM_RAND_LEN] = { 0x00, 0x88, 0x00, 0x81, 0x22, 0x10 }; //TOTAL_LEN + LEN(RAND) + RAND + LEN(AUTN) + AUTN + int i,j; + /* If on 2nd or later reader, make sure we end the transaction + * and disconnect card in the previous reader */ + switch (hCard_status) + { + case TRANSACTION: + SCardEndTransaction(hCard, SCARD_LEAVE_CARD); + /* FALLTHRU */ + case CONNECTED: + SCardDisconnect(hCard, SCARD_LEAVE_CARD); + /* FALLTHRU */ + case DISCONNECTED: + hCard_status = DISCONNECTED; + } + + /* Copy RAND into APDU */ + memcpy(abAuthenticate + 6, rand, SIM_RAND_LEN); + abAuthenticate[6 + SIM_RAND_LEN] = 0x10; //LEN of AUTN + memcpy(abAuthenticate + 6 + SIM_RAND_LEN + 1, autn, SIM_RAND_LEN); //Copy AUTN into APDU + + rv = SCardConnect(hContext, cur_reader, SCARD_SHARE_SHARED, + SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardConnect: %s", pcsc_stringify_error(rv)); + continue; + } + hCard_status = CONNECTED; + + switch(dwActiveProtocol) + { + case SCARD_PROTOCOL_T0: + pioSendPci = SCARD_PCI_T0; + break; + case SCARD_PROTOCOL_T1: + pioSendPci = SCARD_PCI_T1; + break; + default: + DBG1(DBG_IKE, "Unknown SCARD_PROTOCOL"); + continue; + } + + /* Start transaction */ + rv = SCardBeginTransaction(hCard); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardBeginTransaction: %s", pcsc_stringify_error(rv)); + continue; + } + hCard_status = TRANSACTION; + + /* APDU: Select EFDIR */ + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, abSelectEFDIR, sizeof(abSelectEFDIR), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + + + if (pbRecvBuffer[0] == 0x61 && dwRecvLength < 3) + { // Response bytes available, GET RESPONSE needs to be run + abGetResponse[4] = pbRecvBuffer[1]; //setting the expected length to the one sent by the card + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, abGetResponse, sizeof(abGetResponse), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + + } + else if ((pbRecvBuffer[0] == 0x6C && dwRecvLength < 3) || (pbRecvBuffer[0] == 0x67 && dwRecvLength < 3)) //WRONG length used, correcting it + { + abSelectEFDIR[4] = pbRecvBuffer[1]; //setting the expected length to the one sent by the card + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, abSelectEFDIR, sizeof(abSelectEFDIR), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + } + + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } +/* + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_RESPONSE_DATA) + { + DBG1(DBG_IKE, "Select EFDIR failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + }*/ + + for(j=0; j<5; j++) //Fingers crossed there is no SIM card with more than 5 applications in its EF_DIR + { + /* APDU: Read Record */ + dwRecvLength = sizeof(pbRecvBuffer); + abReadRecord[2] = j; + rv = SCardTransmit(hCard, pioSendPci, abReadRecord, sizeof(abReadRecord), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } +/* + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_RESPONSE_DATA) + { + DBG1(DBG_IKE, "Read Record failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } +*/ + DBG1(DBG_IKE, "READ RECORD #%d: %b", j, pbRecvBuffer, + (u_int)dwRecvLength); + if ((pbRecvBuffer[0] == 0x6C && dwRecvLength < 3) || (pbRecvBuffer[0] == 0x67 && dwRecvLength < 3)) //WRONG length used, correcting it + { + abReadRecord[4] = pbRecvBuffer[1]; //setting the expected length to the one sent by the card + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, abReadRecord, sizeof(abReadRecord), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + } + + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + + if ((pbRecvBuffer[0]== 0x61) && (pbRecvBuffer[2] == 0x4F) && dwRecvLength > 6) + { + correct_aid = memmem(pbRecvBuffer, dwRecvLength, aid_pattern, sizeof(aid_pattern)); + DBG1(DBG_IKE, "Detecting AIDs..."); + if(correct_aid) + break; + } else { + DBG1(DBG_IKE, "Failed to get AID, will not be able to proceed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } + } + + if(!correct_aid) + { + DBG1(DBG_IKE, "NOT finding USIM AID (see ETSI TS 101 220 Annex E) pattern, will not be able to proceed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + + } else { + unsigned char aid[pbRecvBuffer[3]]; //the transaction buffer contains the right AID with its length + for(i=0; i < sizeof(aid); i++) { + aid[i] = (*correct_aid); + correct_aid++; + } + unsigned char final_apdu[sizeof(aid)+4]; + for (i=0; i < sizeof(abSelectUICC); i++) { + final_apdu[i] = abSelectUICC[i]; + } + final_apdu[sizeof(abSelectUICC)] = sizeof(aid); //len byte + for (i=0; i < sizeof(aid); i++) { //adding AID to the APDU + final_apdu[i+5] = aid[i]; + } + DBG1(DBG_IKE, "Got AID: %b", aid, + sizeof(aid)); + + DBG1(DBG_IKE, "Selecting UICC..."); + /* APDU: Select UICC */ + dwRecvLength = sizeof(pbRecvBuffer); + DBG1(DBG_IKE, "Sending APDU: %b", final_apdu, + (u_int)pbRecvBuffer[3]+5); + + rv = SCardTransmit(hCard, pioSendPci, final_apdu, pbRecvBuffer[3]+5, + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + if (pbRecvBuffer[0] == 0x61 && dwRecvLength < 3) + { // Response bytes available, GET RESPONSE needs to be run + abGetResponse[4] = pbRecvBuffer[1]; //setting the expected length to the one sent by the card + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, abGetResponse, sizeof(abGetResponse), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + } + } + +/* if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_RESPONSE_DATA) + { + DBG1(DBG_IKE, "Select UICC failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } +*/ + + + DBG1(DBG_IKE, "Selecting IMSI..."); + /* APDU: Select IMSI */ + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, abSelectIMSI, sizeof(abSelectIMSI), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + if (pbRecvBuffer[0] == 0x61 && dwRecvLength < 3) + { // Response bytes available, GET RESPONSE needs to be run + abGetResponse[4] = pbRecvBuffer[1]; //setting the expected length to the one sent by the card + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, abGetResponse, sizeof(abGetResponse), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + } +/* + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_RESPONSE_DATA) + { + DBG1(DBG_IKE, "Select IMSI failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } + */ + /* APDU: Read Binary (of IMSI) */ + DBG1(DBG_IKE, "Reading binary of IMSI..."); + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, abReadBinary, sizeof(abReadBinary), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + DBG1(DBG_IKE, "Response: %b", pbRecvBuffer, + (u_int)dwRecvLength); + + if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_SUCCESS) + { + DBG1(DBG_IKE, "Read binary of IMSI failed: %b", pbRecvBuffer, + (u_int)dwRecvLength); + continue; + } + + if (!decode_imsi_ef(pbRecvBuffer, dwRecvLength-APDU_STATUS_LEN, imsi)) + { + DBG1(DBG_IKE, "Couldn't decode IMSI EF: %b", + pbRecvBuffer, (u_int)dwRecvLength); + continue; + } + DBG1(DBG_IKE, "Got IMSI: %s", imsi); + /* The IMSI could be post/prefixed in the full NAI, so just make sure + * it's in there */ + if (!(strlen(full_nai) && strstr(full_nai, imsi))) + { + DBG1(DBG_IKE, "Not the SIM we're looking for, IMSI: %s", imsi); + continue; + } + + /* APDU: Authenticate */ + DBG1(DBG_IKE, "Running AUTHENTICATE..."); + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, + abAuthenticate, sizeof(abAuthenticate), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardTransmit: %s", pcsc_stringify_error(rv)); + continue; + } + + if (pbRecvBuffer[0] == 0x61 && dwRecvLength < 3) + { // Response bytes available, GET RESPONSE needs to be run + abGetResponse[4] = pbRecvBuffer[1]; //setting the expected length to the one sent by the card + dwRecvLength = sizeof(pbRecvBuffer); + rv = SCardTransmit(hCard, pioSendPci, abGetResponse, sizeof(abGetResponse), + &pioRecvPci, pbRecvBuffer, &dwRecvLength); + + } + + DBG1(DBG_IKE, "Response: %b", + pbRecvBuffer, (u_int)dwRecvLength); + +/* if (dwRecvLength < APDU_STATUS_LEN || + pbRecvBuffer[dwRecvLength-APDU_STATUS_LEN] != APDU_SW1_RESPONSE_DATA) + { + DBG1(DBG_IKE, "Authenticate failed: %b", + pbRecvBuffer, (u_int)dwRecvLength); + continue; + } +*/ + /* Parsing data from the response into RES, CK, IK */ + if(pbRecvBuffer[0] == 0xDB) + { + DBG1(DBG_IKE, "Successful 3G authentication"); + if(pbRecvBuffer[1] == 0x08 || pbRecvBuffer[1] == 0x04) //RES + { + resLen = pbRecvBuffer[1]; + for(i=0; i< pbRecvBuffer[1]; i++) + { + res[i] = pbRecvBuffer[i+2]; + } + (*res_len) = resLen; + } else { + DBG1(DBG_IKE, "RES not 8 or 4 byte long, can't copy it.\n"); + continue; + } + if(pbRecvBuffer[resLen+2] == 0x10) //CK SUCCESS_BYTE 0xDB(len=1) + RES_LEN(len1) + resLen + { + for(i=0; i<16; i++) + { + ck[i] = pbRecvBuffer[i+resLen+3]; + } + } else { + DBG1(DBG_IKE, "CK not 16 byte long, can't copy it\n"); + continue; + } + if(pbRecvBuffer[resLen+3+16] == 0x10) //IK SUCCESS_BYTE(len1) + RES_LEN(len1) + res + CK_LEN(len1) + CK(len16) + { + for(i=0; i<16; i++) + { + ik[i] = pbRecvBuffer[i+ resLen+3+16+1]; + } + } else { + DBG1(DBG_IKE, "IK not 16 byte long, can't copy it\n"); + continue; + } + DBG1(DBG_IKE, "KEYs established. RES: %b", res, resLen); + DBG1(DBG_IKE, "KEYs established. CK: %b", ck, 16); + DBG1(DBG_IKE, "KEYs established. IK: %b", ik, 16); + found = SUCCESS; + + } + if(pbRecvBuffer[0] == 0xDC) + { + DBG1(DBG_IKE, "Sync error between SIM card and network, currently NOT supported.\n"); + return NOT_SUPPORTED; + } + + /* Transaction will be ended and card disconnected at the + * beginning of this loop or after this loop */ + } + + /* Make sure we end any previous transaction and disconnect card */ + switch (hCard_status) + { + case TRANSACTION: + SCardEndTransaction(hCard, SCARD_LEAVE_CARD); + /* FALLTHRU */ + case CONNECTED: + SCardDisconnect(hCard, SCARD_LEAVE_CARD); + /* FALLTHRU */ + case DISCONNECTED: + hCard_status = DISCONNECTED; + } + + rv = SCardReleaseContext(hContext); + if (rv != SCARD_S_SUCCESS) + { + DBG1(DBG_IKE, "SCardReleaseContext: %s", pcsc_stringify_error(rv)); + } + + free(mszReaders); + return found; +} + +METHOD(eap_simaka_pcsc_card_t, destroy, void, + private_eap_simaka_pcsc_card_t *this) +{ + free(this); +} + +/** + * See header + */ +eap_simaka_pcsc_card_t *eap_simaka_pcsc_card_create() +{ + private_eap_simaka_pcsc_card_t *this; + + INIT(this, + .public = { + .card = { + .get_triplet = _get_triplet, + .get_quintuplet = _get_quintuplet, + .resync = (void*)return_false, + .get_pseudonym = (void*)return_null, + .set_pseudonym = (void*)nop, + .get_reauth = (void*)return_null, + .set_reauth = (void*)nop, + }, + .destroy = _destroy, + }, + ); + return &this->public; +} + diff --git a/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_card.h b/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_card.h new file mode 100644 index 000000000..4da42640c --- /dev/null +++ b/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_card.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 Domonkos P. Tomcsanyi + * P3 Communications Gmbh. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef EAP_SIMAKA_PCSC_CARD_H_ +#define EAP_SIMAKA_PCSC_CARD_H_ + +#include + +typedef struct eap_simaka_pcsc_card_t eap_simaka_pcsc_card_t; + +/** + * SIM card implementing PC/SC backend. + */ +struct eap_simaka_pcsc_card_t { + + /** + * Implements simaka_card_t interface + */ + simaka_card_t card; + + /** + * Destroy a eap_simaka_pcsc_card_t. + */ + void (*destroy)(eap_simaka_pcsc_card_t *this); +}; + +/** + * Create a eap_simaka_pcsc_card instance. + */ +eap_simaka_pcsc_card_t *eap_simaka_pcsc_card_create(); + +#endif /** EAP_SIMAKA_PCSC_CARD_H_ @}*/ diff --git a/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_plugin.c b/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_plugin.c new file mode 100644 index 000000000..2ec6b971c --- /dev/null +++ b/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_plugin.c @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 Domonkos P. Tomcsanyi + * P3 Communications Gmbh. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "eap_simaka_pcsc_plugin.h" +#include "eap_simaka_pcsc_card.h" + +#include + +typedef struct private_eap_simaka_pcsc_t private_eap_simaka_pcsc_t; + +/** + * Private data of an eap_simaka_pcsc_t object. + */ +struct private_eap_simaka_pcsc_t { + + /** + * Public eap_simaka_pcsc_plugin_t interface. + */ + eap_simaka_pcsc_plugin_t public; + + /** + * SIM card + */ + eap_simaka_pcsc_card_t *card; + +}; + +METHOD(plugin_t, get_name, char*, + private_eap_simaka_pcsc_t *this) +{ + return "eap-simaka-pcsc"; +} + +/** + * Callback providing our card to register + */ +static simaka_card_t* get_card(private_eap_simaka_pcsc_t *this) +{ + if (!this->card) + { + this->card = eap_simaka_pcsc_card_create(); + } + return &this->card->card; +} + +METHOD(plugin_t, get_features, int, + private_eap_simaka_pcsc_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK(simaka_manager_register, get_card), + PLUGIN_PROVIDE(CUSTOM, "aka-card"), + PLUGIN_DEPENDS(CUSTOM, "aka-manager"), + PLUGIN_PROVIDE(CUSTOM, "sim-card"), + PLUGIN_DEPENDS(CUSTOM, "sim-manager"), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_eap_simaka_pcsc_t *this) +{ + DESTROY_IF(this->card); + free(this); +} + +/** + * See header + */ +plugin_t *eap_simaka_pcsc_plugin_create() +{ + private_eap_simaka_pcsc_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + ); + + return &this->public.plugin; +} + diff --git a/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_plugin.h b/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_plugin.h new file mode 100644 index 000000000..77d95fca2 --- /dev/null +++ b/src/libcharon/plugins/eap_simaka_pcsc/eap_simaka_pcsc_plugin.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 Domonkos P. Tomcsanyi + * P3 Communications Gmbh. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + + +#ifndef EAP_SIMAKA_PCSC_PLUGIN_H_ +#define EAP_SIMAKA_PCSC_PLUGIN_H_ + +#include + +typedef struct eap_simaka_pcsc_plugin_t eap_simaka_pcsc_plugin_t; + +/** + * Plugin to provide EAP-AKA PC/SC based USIM card backend + */ +struct eap_simaka_pcsc_plugin_t { + + /** + * implements plugin interface + */ + plugin_t plugin; +}; + +#endif /** EAP_SIMAKA_PCSC_PLUGIN_H_ @}*/