diff --git a/ccid/doc/README.txt b/ccid/doc/README.txt index 6749be3..eb9a72b 100644 --- a/ccid/doc/README.txt +++ b/ccid/doc/README.txt @@ -127,32 +127,3 @@ similar. Install |libnpa| and it should be recognized automatically by the :file:`configure` script. -***** -Usage -***** - -The USB CCID Emulator has various command line options to customize the appearance -on the USB host. In order to run the USB CCID Emulator GadgetFS must be loaded -and mounted. The USB CCID Emulator is compatible with the unix driver libccid_ -and the `Windows USB CCID driver`_. To initialize |PACE| using the PC/SC API -you need to patch libccid (see :file:`patches`). On Windows, the USB CCID Emulator -currently has no support for |PACE|. - -.. program-output:: ccid-emulator --help - - -.. include:: questions.txt - - -******************** -Notes and References -******************** - -.. target-notes:: - -.. _`GadgetFS`: http://www.linux-usb.org/gadget/ -.. _`OpenSC`: http://www.opensc-project.org/opensc -.. _`libccid`: http://pcsclite.alioth.debian.org/ccid.html -.. _`Windows USB CCID driver`: http://msdn.microsoft.com/en-us/windows/hardware/gg487509 -.. _`OpenMoko Wiki`: http://wiki.openmoko.org/wiki/Building_Gadget_USB_Module>`_ -.. [#f1] Note that the heavily outdated Windows USB CCID driver does not support secure PIN entry or PIN modification. USB CCID Emulator comes with a patch for libccid to support |PACE|, because it is not yet standardised in USB CCID. However, the traditional commands can be used without restriction. diff --git a/ccid/doc/README.txt.in b/ccid/doc/README.txt.in index 0d414e9..f94084a 100644 --- a/ccid/doc/README.txt.in +++ b/ccid/doc/README.txt.in @@ -134,9 +134,16 @@ Usage The @PACKAGE_NAME@ has various command line options to customize the appearance on the USB host. In order to run the @PACKAGE_NAME@ GadgetFS must be loaded and mounted. The @PACKAGE_NAME@ is compatible with the unix driver libccid_ -and the `Windows USB CCID driver`_. To initialize |PACE| using the PC/SC API -you need to patch libccid (see :file:`patches`). On Windows, the @PACKAGE_NAME@ -currently has no support for |PACE|. +and the `Windows USB CCID driver`_. PIN commands are supported if implemented +by the driver. + +.. versionadd:: 0.7 + + @PACKAGE_NAME@ now supports the boxing commands defined in `BSI TR-03119 + 1.3`_. PIN verification/modification and |PACE| can be started by the + application transmitting (SCardTransmit) specially crafted APDUs. The + alternative initialization of |PACE| using SCardControl requires + patching the driver (available for libccid, see :file:`patches`). .. program-output:: ccid-emulator --help @@ -154,5 +161,6 @@ Notes and References .. _`OpenSC`: http://www.opensc-project.org/opensc .. _`libccid`: http://pcsclite.alioth.debian.org/ccid.html .. _`Windows USB CCID driver`: http://msdn.microsoft.com/en-us/windows/hardware/gg487509 -.. _`OpenMoko Wiki`: http://wiki.openmoko.org/wiki/Building_Gadget_USB_Module>`_ +.. _`OpenMoko Wiki`: http://wiki.openmoko.org/wiki/Building_Gadget_USB_Module +.. _`BSI TR-03119`: https://www.bsi.bund.de/DE/Publikationen/TechnischeRichtlinien/tr03119/index_htm.html .. [#f1] Note that the heavily outdated Windows USB CCID driver does not support secure PIN entry or PIN modification. @PACKAGE_NAME@ comes with a patch for libccid to support |PACE|, because it is not yet standardised in USB CCID. However, the traditional commands can be used without restriction. diff --git a/ccid/src/ccid.c b/ccid/src/ccid.c index ba402cc..c5b3ca3 100644 --- a/ccid/src/ccid.c +++ b/ccid/src/ccid.c @@ -33,12 +33,41 @@ #include #ifdef WITH_PACE -#include +#include #include +#include #else int sm_stop(struct sc_card *card) { return SC_SUCCESS; } #endif +static int +perform_PC_to_RDR_GetSlotStatus(const __u8 *in, size_t inlen, __u8 **out, size_t *outlen); +static int +perform_PC_to_RDR_IccPowerOn(const __u8 *in, size_t inlen, __u8 **out, size_t *outlen); +static int +perform_PC_to_RDR_IccPowerOff(const __u8 *in, size_t inlen, __u8 **out, size_t *outlen); +static int +perform_pseudo_apdu_EstablishPACEChannel(sc_apdu_t *apdu); +static int +perform_pseudo_apdu_GetReaderPACECapabilities(sc_apdu_t *apdu); +static int +perform_pseudo_apdu(sc_reader_t *reader, sc_apdu_t *apdu); +static int +perform_PC_to_RDR_XfrBlock(const u8 *in, size_t inlen, __u8** out, size_t *outlen); +static int +perform_PC_to_RDR_GetParamters(const __u8 *in, size_t inlen, __u8** out, size_t *outlen); +static int +perform_PC_to_RDR_Secure_EstablishPACEChannel(sc_card_t *card, + const __u8 *abData, size_t abDatalen, + __u8 **abDataOut, size_t *abDataOutLen); +static int +perform_PC_to_RDR_Secure_GetReadersPACECapabilities(__u8 **abDataOut, + size_t *abDataOutLen); +static int +perform_PC_to_RDR_Secure(const __u8 *in, size_t inlen, __u8** out, size_t *outlen); +static int +perform_unknown(const __u8 *in, size_t inlen, __u8 **out, size_t *outlen); + static sc_context_t *ctx = NULL; static sc_card_t *card = NULL; static sc_reader_t *reader = NULL; @@ -153,21 +182,24 @@ static int get_rapdu(sc_apdu_t *apdu, __u8 **buf, size_t *resplen) { int sc_result; - if (!apdu || !buf || !resplen) { + if (!apdu || !buf || !resplen || !card) { sc_result = SC_ERROR_INVALID_ARGUMENTS; goto err; } apdu->resplen = apdu->le; - /* Get two more bytes to later use as return buffer including sw1 and sw2 */ - apdu->resp = realloc(*buf, apdu->resplen + sizeof(__u8) + sizeof(__u8)); + apdu->resp = realloc(*buf, apdu->resplen); if (!apdu->resp) { sc_result = SC_ERROR_OUT_OF_MEMORY; goto err; } *buf = apdu->resp; - sc_result = sc_transmit_apdu(card, apdu); + if (apdu->cla == 0XFF) { + sc_result = perform_pseudo_apdu(card->reader, apdu); + } else { + sc_result = sc_transmit_apdu(card, apdu); + } if (sc_result < 0) { goto err; } @@ -178,10 +210,15 @@ static int get_rapdu(sc_apdu_t *apdu, __u8 **buf, size_t *resplen) goto err; } - apdu->resp[apdu->resplen] = apdu->sw1; - apdu->resp[apdu->resplen + sizeof(__u8)] = apdu->sw2; + /* Get two more bytes to use as return buffer including sw1 and sw2 */ + *buf = realloc(apdu->resp, apdu->resplen + sizeof(__u8) + sizeof(__u8)); + if (!*buf) { + sc_result = SC_ERROR_OUT_OF_MEMORY; + goto err; + } - *buf = apdu->resp; + (*buf)[apdu->resplen] = apdu->sw1; + (*buf)[apdu->resplen + sizeof(__u8)] = apdu->sw2; *resplen = apdu->resplen + sizeof(__u8) + sizeof(__u8); sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "R-APDU, %d byte%s:\tsw1=%02x sw2=%02x", @@ -387,6 +424,9 @@ perform_PC_to_RDR_IccPowerOn(const __u8 *in, size_t inlen, __u8 **out, size_t *o } if (sc_result >= 0) { +#ifndef DISABLE_GLOBAL_BOXING_INITIALIZATION + sc_initialize_boxing_cmds(ctx); +#endif return get_RDR_to_PC_SlotStatus(request->bSeq, sc_result, out, outlen, card->atr.value, card->atr.len); } else { @@ -422,75 +462,163 @@ perform_PC_to_RDR_IccPowerOff(const __u8 *in, size_t inlen, __u8 **out, size_t * out, outlen, NULL, 0); } -static int -perform_pseudo_apdu(const __u8 *in, size_t inlen, __u8 **out, size_t *outlen) -{ - __u8 *p; - sc_apdu_t apdu; +struct sw { + unsigned char sw1; + unsigned char sw2; +}; +static const struct sw iso_sw_ok = { 0x90, 0x00}; +static const struct sw iso_sw_incorrect_p1_p2 = { 0x6A, 0x86}; +static const struct sw iso_sw_ref_data_not_found = {0x6A, 0x88}; +static const struct sw iso_sw_inconsistent_data = {0x6A, 0x87}; +static const struct sw iso_sw_func_not_supported = {0x6A, 0x81}; +static const struct sw iso_sw_ins_not_supported = {0x6D, 0x00}; - if (!in || !out || !outlen) +static int +perform_pseudo_apdu_EstablishPACEChannel(sc_apdu_t *apdu) +{ + struct establish_pace_channel_input pace_input; + struct establish_pace_channel_output pace_output; + int r; + + memset(&pace_input, 0, sizeof pace_input); + memset(&pace_output, 0, sizeof pace_output); + + r = boxing_buf_to_pace_input(reader->ctx, apdu->data, apdu->datalen, + &pace_input); + if (r < 0) + goto err; + + r = perform_pace(card, pace_input, &pace_output, + EAC_TR_VERSION_2_02); + if (r < 0) + goto err; + + r = boxing_pace_output_to_buf(reader->ctx, &pace_output, &apdu->resp, + &apdu->resplen); + +err: + free((unsigned char *) pace_input.chat); + free((unsigned char *) pace_input.certificate_description); + free((unsigned char *) pace_input.pin); + free(pace_output.ef_cardaccess); + free(pace_output.recent_car); + free(pace_output.previous_car); + free(pace_output.id_icc); + free(pace_output.id_pcd); + + return r; +} + +static int +perform_pseudo_apdu_GetReaderPACECapabilities(sc_apdu_t *apdu) +{ + unsigned long sc_reader_t_capabilities = SC_READER_CAP_PACE_GENERIC + | SC_READER_CAP_PACE_EID | SC_READER_CAP_PACE_ESIGN; + + + return boxing_pace_capabilities_to_buf(reader->ctx, + sc_reader_t_capabilities, &apdu->resp, &apdu->resplen); +} + +#define min(a,b) (acla != 0xff) return SC_ERROR_INVALID_ARGUMENTS; - if (*in != 0xFF) - sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "malformed PC_to_RDR_XfrBlock, will continue anyway"); - - LOG_TEST_RET(ctx, sc_bytes2apdu(ctx, in, inlen, &apdu), "Could not parse Pseudo-APDU"); - - switch (apdu.ins) { + switch (apdu->ins) { case 0x9A: - /* GetReaderInfo */ - if (in[2] != 0x01 || inlen != 5 || in[4] != 0x00) { -parse_error: - p = realloc(*out, 2); - if (!p) - SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_OUT_OF_MEMORY); - *out = p; - memcpy(*out, "\x6A\x86", 2); - *outlen = 2; - } - /* TODO Merge this with STRINGID_MFGR, STRINGID_PRODUCT in usb.c */ - /* Copied from olsc/AusweisApp/Data/siqTerminalsInfo.cfg */ - const char *info; - const char *Herstellername = "REINER SCT"; - const char *Produktname = "cyberJack RFID komfort"; - const char *Firmwareversion = "1.0"; - const char *Treiberversion = "3.99.5"; - switch (in[3]) { - case 0x01: - info = Herstellername; - break; - case 0x03: - info = Produktname; - break; - case 0x06: - info = Firmwareversion; - break; - case 0x07: - info = Treiberversion; - break; - default: - goto parse_error; - } - size_t infolen = strlen(info); - p = realloc(*out, infolen+2); - if (!p) - SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_OUT_OF_MEMORY); - *out = p; - memcpy(*out, info, infolen); - *out[infolen] = 0x90; - *out[infolen+1] = 0x00; - *outlen = infolen+2; - break; + + switch (apdu->p1) { + case 0x01: + /* GetReaderInfo */ + if (apdu->datalen != 0) { + apdu->resplen = 0; + apdu->sw1 = iso_sw_incorrect_p1_p2.sw1; + apdu->sw2 = iso_sw_incorrect_p1_p2.sw2; + goto err; + } + /* TODO Merge this with STRINGID_MFGR, STRINGID_PRODUCT in usb.c */ + /* Copied from olsc/AusweisApp/Data/siqTerminalsInfo.cfg */ + char *Herstellername = "REINER SCT"; + char *Produktname = "cyberJack RFID komfort"; + char *Firmwareversion = "1.0"; + char *Treiberversion = "3.99.5"; + switch (apdu->p2) { + case 0x01: + apdu->resplen = min(apdu->resplen, strlen(Herstellername)); + memcpy(apdu->resp, Herstellername, apdu->resplen); + break; + case 0x03: + apdu->resplen = min(apdu->resplen, strlen(Produktname)); + memcpy(apdu->resp, Produktname, apdu->resplen); + break; + case 0x06: + apdu->resplen = min(apdu->resplen, strlen(Firmwareversion)); + memcpy(apdu->resp, Firmwareversion, apdu->resplen); + break; + case 0x07: + apdu->resplen = min(apdu->resplen, strlen(Treiberversion)); + memcpy(apdu->resp, Treiberversion, apdu->resplen); + break; + default: + apdu->resplen = 0; + apdu->sw1 = iso_sw_ref_data_not_found.sw1; + apdu->sw2 = iso_sw_ref_data_not_found.sw2; + goto err; + } + break; + + case 0x04: + switch (apdu->p2) { + case 0x04: + /* VerifyPIN/ModifyPIN */ + LOG_TEST_RET(ctx, + perform_PC_to_RDR_Secure(apdu->data, apdu->datalen, &apdu->resp, &apdu->resplen), + "Could not perform PC_to_RDR_Secure"); + apdu->sw1 = iso_sw_ok.sw1; + apdu->sw2 = iso_sw_ok.sw2; + break; + case 0x01: + /* GetReaderPACECapabilities */ + LOG_TEST_RET(ctx, + perform_pseudo_apdu_GetReaderPACECapabilities(apdu), + "Could not get reader's PACE Capabilities"); + apdu->sw1 = iso_sw_ok.sw1; + apdu->sw2 = iso_sw_ok.sw2; + break; + case 0x02: + /* EstablishPACEChannel */ + LOG_TEST_RET(ctx, + perform_pseudo_apdu_EstablishPACEChannel(apdu), + "Could not perform PACE"); + apdu->sw1 = iso_sw_ok.sw1; + apdu->sw2 = iso_sw_ok.sw2; + break; + case 0x03: + /* DestroyPACEChannel */ + default: + apdu->sw1 = iso_sw_func_not_supported.sw1; + apdu->sw2 = iso_sw_func_not_supported.sw2; + goto err; + } + break; + + default: + apdu->sw1 = iso_sw_func_not_supported.sw1; + apdu->sw2 = iso_sw_func_not_supported.sw2; + goto err; + } + break; default: - p = realloc(*out, 2); - if (!p) - SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_OUT_OF_MEMORY); - *out = p; - memcpy(*out, "\x6A\x81", 2); - *outlen = 2; + apdu->sw1 = iso_sw_ins_not_supported.sw1; + apdu->sw2 = iso_sw_ins_not_supported.sw2; + goto err; } +err: return SC_SUCCESS; } @@ -521,18 +649,13 @@ perform_PC_to_RDR_XfrBlock(const u8 *in, size_t inlen, __u8** out, size_t *outle SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_VERBOSE, SC_ERROR_INVALID_DATA); } - if (apdulen >= 1 && *abDataIn == 0xff) { - sc_result = perform_pseudo_apdu(abDataIn, apdulen, &abDataOut, - &abDataOutLen); - } else { - sc_result = sc_bytes2apdu(ctx, abDataIn, apdulen, &apdu); - if (sc_result >= 0) - sc_result = get_rapdu(&apdu, &abDataOut, - &abDataOutLen); - else - bin_log(ctx, SC_LOG_DEBUG_VERBOSE, "Invalid APDU", abDataIn, - __le32_to_cpu(request->dwLength)); - } + sc_result = sc_bytes2apdu(ctx, abDataIn, apdulen, &apdu); + if (sc_result >= 0) + sc_result = get_rapdu(&apdu, &abDataOut, + &abDataOutLen); + else + bin_log(ctx, SC_LOG_DEBUG_VERBOSE, "Invalid APDU", abDataIn, + __le32_to_cpu(request->dwLength)); sc_result = get_RDR_to_PC_DataBlock(request->bSeq, sc_result, out, outlen, abDataOut, abDataOutLen); diff --git a/npa/src/Makefile.am b/npa/src/Makefile.am index 29cd27e..4679bc2 100644 --- a/npa/src/Makefile.am +++ b/npa/src/Makefile.am @@ -16,7 +16,7 @@ MAINTAINERCLEANFILES = $(BUILT_SOURCES) npa-tool.ggo $(dist_man1_MANS) dist_man1_MANS = npa-tool.1 -libnpa_la_SOURCES = iso-sm.c scutil.c npa.c +libnpa_la_SOURCES = iso-sm.c scutil.c npa.c boxing.c libnpa_la_LIBADD = $(OPENSSL_LIBS) $(OPENPACE_LIBS) $(OPENSC_LIBS) libnpa_la_CFLAGS = $(OPENSSL_CFLAGS) $(OPENPACE_CFLAGS) $(OPENSC_CFLAGS) libnpa_la_LDFLAGS = -no-undefined @@ -58,4 +58,5 @@ noinst_HEADERS = \ nobase_include_HEADERS = \ npa/iso-sm.h \ npa/npa.h \ + npa/boxing.h \ npa/scutil.h diff --git a/npa/src/boxing.c b/npa/src/boxing.c new file mode 100644 index 0000000..03f0971 --- /dev/null +++ b/npa/src/boxing.c @@ -0,0 +1,509 @@ +/* + * boxing.c: implementation related to boxing commands with pseudo APDUs + * + * Copyright (C) 2013 Frank Morgner + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include + +static const u8 boxing_cla = 0xff; +static const u8 boxing_ins = 0x9a; +static const u8 boxing_p1 = 0x04; +static const u8 boxing_p2_GetReaderPACECapabilities = 0x01; +static const u8 boxing_p2_EstablishPACEChannel = 0x02; +static const u8 boxing_p2_DestroyPACEChannel = 0x03; +static const u8 boxing_p2_PC_to_RDR_Secure = 0x10; + +static const struct sc_asn1_entry g_EstablishPACEChannelInput_data[] = { + { "passwordID", + /* use an OCTET STRING to avoid a conversion to int */ + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x01, 0, NULL, NULL }, + { "transmittedPassword", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x02, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "cHAT", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x03, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "certificateDescription", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x04, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "hashOID", + /* use an OCTET STRING to avoid a conversion to struct sc_object_id */ + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x05, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; +static const struct sc_asn1_entry g_EstablishPACEChannelOutput_data[] = { + { "errorCode", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x01, 0, NULL, NULL }, + { "statusMSESetAT", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x02, 0, NULL, NULL }, + { "efCardAccess", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x03, SC_ASN1_ALLOC, NULL, NULL }, + { "idPICC", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x04, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "curCAR", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x05, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "prevCAR", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x06, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; +static const struct sc_asn1_entry g_EstablishPACEChannel[] = { + { "EstablishPACEChannel", + SC_ASN1_STRUCT, SC_ASN1_TAG_SEQUENCE|SC_ASN1_CONS, 0, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; + +int boxing_pace_input_to_buf(sc_context_t *ctx, + const struct establish_pace_channel_input *input, + unsigned char **asn1, size_t *asn1_len) +{ + const size_t count_data = + sizeof g_EstablishPACEChannelInput_data/ + sizeof *g_EstablishPACEChannelInput_data; + const size_t count_sequence = + sizeof g_EstablishPACEChannel/ + sizeof *g_EstablishPACEChannel; + size_t pin_id_len = sizeof input->pin_id, i; + struct sc_asn1_entry EstablishPACEChannelInput_data[count_data]; + struct sc_asn1_entry EstablishPACEChannel[count_sequence]; + + for (i = 0; i < count_sequence; i++) + sc_copy_asn1_entry(g_EstablishPACEChannel+i, + EstablishPACEChannel+i); + sc_format_asn1_entry(EstablishPACEChannel, + EstablishPACEChannelInput_data, 0, 1); + + for (i = 0; i < count_data; i++) + sc_copy_asn1_entry(g_EstablishPACEChannelInput_data+i, + EstablishPACEChannelInput_data+i); + sc_format_asn1_entry(EstablishPACEChannelInput_data, + (unsigned char *) &input->pin_id, &pin_id_len, 1); + if (input->pin) + sc_format_asn1_entry(EstablishPACEChannelInput_data, + (unsigned char *) input->pin, + (size_t *) &input->pin_length, 1); + if (input->chat) + sc_format_asn1_entry(EstablishPACEChannelInput_data, + (unsigned char *) input->chat, + (size_t *) &input->chat_length, 1); + if (input->certificate_description) + sc_format_asn1_entry(EstablishPACEChannelInput_data, + (unsigned char *) input->certificate_description, + (size_t *) &input->certificate_description_length, 1); + + return sc_asn1_encode(ctx, EstablishPACEChannel, asn1, asn1_len); +} + +int boxing_buf_to_pace_input(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + struct establish_pace_channel_input *input) +{ + const size_t count_data = + sizeof g_EstablishPACEChannelInput_data/ + sizeof *g_EstablishPACEChannelInput_data; + const size_t count_sequence = + sizeof g_EstablishPACEChannel/ + sizeof *g_EstablishPACEChannel; + size_t pin_id_len = sizeof input->pin_id, i; + struct sc_asn1_entry EstablishPACEChannelInput_data[count_data]; + struct sc_asn1_entry EstablishPACEChannel[count_sequence]; + + for (i = 0; i < count_sequence; i++) + sc_copy_asn1_entry(g_EstablishPACEChannel+i, + EstablishPACEChannel+i); + sc_format_asn1_entry(EstablishPACEChannel, + EstablishPACEChannelInput_data, 0, 0); + + for (i = 0; i < count_data; i++) + sc_copy_asn1_entry(g_EstablishPACEChannelInput_data+i, + EstablishPACEChannelInput_data+i); + sc_format_asn1_entry(EstablishPACEChannelInput_data+0, + &input->pin_id, &pin_id_len, 0); + sc_format_asn1_entry(EstablishPACEChannelInput_data+1, + (unsigned char *) input->pin, &input->pin_length, 0); + sc_format_asn1_entry(EstablishPACEChannelInput_data+2, + (unsigned char *) input->chat, &input->chat_length, 0); + sc_format_asn1_entry(EstablishPACEChannelInput_data+3, + (unsigned char *) input->certificate_description, + &input->certificate_description_length, 0); + + LOG_TEST_RET(ctx, + sc_asn1_decode(ctx, EstablishPACEChannel, asn1, asn1_len, NULL, NULL), + "Error decoding EstablishPACEChannel"); + + if (pin_id_len != sizeof input->pin_id) + return SC_ERROR_UNKNOWN_DATA_RECEIVED; + + return SC_SUCCESS; +} + +int boxing_pace_output_to_buf(sc_context_t *ctx, + const struct establish_pace_channel_output *output, + unsigned char **asn1, size_t *asn1_len) +{ + const size_t count_data = + sizeof g_EstablishPACEChannelOutput_data/ + sizeof *g_EstablishPACEChannelOutput_data; + const size_t count_sequence = + sizeof g_EstablishPACEChannel/ + sizeof *g_EstablishPACEChannel; + uint16_t status_mse_set_at = ((output->mse_set_at_sw1 & 0xff) << 8) | output->mse_set_at_sw2; + size_t i, result_len = sizeof output->result, + status_mse_set_at_len = sizeof status_mse_set_at; + struct sc_asn1_entry EstablishPACEChannelOutput_data[count_data]; + struct sc_asn1_entry EstablishPACEChannel[count_sequence]; + + for (i = 0; i < count_sequence; i++) + sc_copy_asn1_entry(g_EstablishPACEChannel+i, + EstablishPACEChannel+i); + sc_format_asn1_entry(EstablishPACEChannel, + EstablishPACEChannelOutput_data, 0, 1); + + for (i = 0; i < count_data; i++) + sc_copy_asn1_entry(g_EstablishPACEChannelOutput_data+i, + EstablishPACEChannelOutput_data+i); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+0, + (unsigned char *) &output->result, &result_len, 1); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+1, + &status_mse_set_at, &status_mse_set_at_len, 1); + if (output->ef_cardaccess) + sc_format_asn1_entry(EstablishPACEChannelOutput_data+2, + output->ef_cardaccess, (size_t *) &output->ef_cardaccess_length, 1); + if (output->id_icc) + sc_format_asn1_entry(EstablishPACEChannelOutput_data+3, + output->id_icc, (size_t *) &output->id_icc_length, 1); + if (output->recent_car) + sc_format_asn1_entry(EstablishPACEChannelOutput_data+4, + output->recent_car, (size_t *) &output->recent_car_length, 1); + if (output->previous_car) + sc_format_asn1_entry(EstablishPACEChannelOutput_data+5, + output->previous_car, (size_t *) &output->previous_car_length, 1); + + return sc_asn1_encode(ctx, EstablishPACEChannel, asn1, asn1_len); +} + +int boxing_buf_to_pace_output(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + struct establish_pace_channel_output *output) +{ + const size_t count_data = + sizeof g_EstablishPACEChannelOutput_data/ + sizeof *g_EstablishPACEChannelOutput_data; + const size_t count_sequence = + sizeof g_EstablishPACEChannel/ + sizeof *g_EstablishPACEChannel; + uint16_t status_mse_set_at; + size_t i, result_len = sizeof output->result, + status_mse_set_at_len = sizeof status_mse_set_at; + struct sc_asn1_entry EstablishPACEChannelOutput_data[count_data]; + struct sc_asn1_entry EstablishPACEChannel[count_sequence]; + + for (i = 0; i < count_sequence; i++) + sc_copy_asn1_entry(g_EstablishPACEChannel+i, + EstablishPACEChannel+i); + sc_format_asn1_entry(EstablishPACEChannel, + EstablishPACEChannelOutput_data, 0, 0); + + for (i = 0; i < count_data; i++) + sc_copy_asn1_entry(g_EstablishPACEChannelOutput_data+i, + EstablishPACEChannelOutput_data+i); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+0, + &output->result, &result_len, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+1, + &status_mse_set_at, &status_mse_set_at_len, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+2, + output->ef_cardaccess, &output->ef_cardaccess_length, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+3, + output->id_icc, &output->id_icc_length, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+4, + output->recent_car, &output->recent_car_length, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+5, + output->previous_car, &output->previous_car_length, 0); + + LOG_TEST_RET(ctx, + sc_asn1_decode(ctx, EstablishPACEChannel, + asn1, asn1_len, NULL, NULL), + "Error decoding EstablishPACEChannel"); + + if (status_mse_set_at_len != sizeof status_mse_set_at + || result_len != sizeof output->result) + return SC_ERROR_UNKNOWN_DATA_RECEIVED; + + output->mse_set_at_sw1 = (status_mse_set_at >> 8) & 0xff; + output->mse_set_at_sw2 = status_mse_set_at & 0xff; + + return SC_SUCCESS; +} + +static int boxing_perform_verify(struct sc_reader *reader, struct sc_pin_cmd_data *data) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +static int boxing_perform_pace(struct sc_reader *reader, + void *establish_pace_channel_input, + void *establish_pace_channel_output) +{ + u8 rbuf[0xffff]; + sc_apdu_t apdu; + int r; + struct establish_pace_channel_input *input = + establish_pace_channel_input; + struct establish_pace_channel_output *output = + establish_pace_channel_output; + + memset(&apdu, 0, sizeof(apdu)); + apdu.cse = SC_APDU_CASE_4_EXT; + apdu.cla = boxing_cla; + apdu.ins = boxing_ins; + apdu.p1 = boxing_p1; + apdu.p2 = boxing_p2_EstablishPACEChannel; + apdu.resp = rbuf; + apdu.resplen = sizeof rbuf; + apdu.le = sizeof rbuf; + + if (!reader || !reader->ops || !reader->ops->transmit) { + r = SC_ERROR_NOT_SUPPORTED; + goto err; + } + + r = boxing_pace_input_to_buf(reader->ctx, input, + (unsigned char **) &apdu.data, &apdu.datalen); + if (r < 0) { + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Error encoding EstablishPACEChannel"); + goto err; + } + apdu.lc = apdu.datalen; + + r = reader->ops->transmit(reader, &apdu); + if (r < 0) { + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Error performing EstablishPACEChannel"); + goto err; + } + + if (apdu.sw1 != 0x90 && apdu.sw2 != 0x00) { + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Error decoding EstablishPACEChannel"); + r = SC_ERROR_NOT_SUPPORTED; + goto err; + } + + r = boxing_buf_to_pace_output(reader->ctx, apdu.resp, apdu.resplen, + output); + +err: + free((unsigned char *) apdu.data); + + return r; +} + +struct sc_asn1_entry g_PACECapabilities_data[] = { + { "capabilityPACE", + SC_ASN1_BOOLEAN, SC_ASN1_CTX|0x01, SC_ASN1_ALLOC, NULL, NULL }, + { "capabilityEID", + SC_ASN1_BOOLEAN, SC_ASN1_CTX|0x02, SC_ASN1_ALLOC, NULL, NULL }, + { "capabilityESign", + SC_ASN1_BOOLEAN, SC_ASN1_CTX|0x03, SC_ASN1_ALLOC, NULL, NULL }, + { "capabilityDestroy", + SC_ASN1_BOOLEAN, SC_ASN1_CTX|0x04, SC_ASN1_ALLOC, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; +struct sc_asn1_entry g_PACECapabilities[] = { + { "PACECapabilities", + SC_ASN1_STRUCT, SC_ASN1_TAG_SEQUENCE|SC_ASN1_CONS, 0, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; + +int boxing_buf_to_pace_capabilities(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + unsigned long *sc_reader_t_capabilities) +{ + const size_t count_data = + sizeof g_PACECapabilities_data/ + sizeof *g_PACECapabilities_data; + const size_t count_sequence = + sizeof g_PACECapabilities/ + sizeof *g_PACECapabilities; + int capabilityPACE = 0, capabilityEID = 0, capabilityESign = 0, + capabilityDestroy = 0; + size_t i; + struct sc_asn1_entry PACECapabilities_data[count_data]; + struct sc_asn1_entry PACECapabilities[count_sequence]; + + for (i = 0; i < count_sequence; i++) + sc_copy_asn1_entry(g_PACECapabilities+i, + PACECapabilities+i); + sc_format_asn1_entry(PACECapabilities, + PACECapabilities_data, 0, 1); + + for (i = 0; i < count_data; i++) + sc_copy_asn1_entry(g_PACECapabilities_data+i, + PACECapabilities_data+i); + sc_format_asn1_entry(PACECapabilities_data+0, + &capabilityPACE, NULL, 0); + sc_format_asn1_entry(PACECapabilities_data+1, + &capabilityEID, NULL, 0); + sc_format_asn1_entry(PACECapabilities_data+2, + &capabilityESign, NULL, 0); + sc_format_asn1_entry(PACECapabilities_data+3, + &capabilityDestroy, NULL, 0); + + LOG_TEST_RET(ctx, + sc_asn1_decode(ctx, PACECapabilities, + asn1, asn1_len, NULL, NULL), + "Error decoding PACECapabilities"); + + /* We got a valid PACE Capabilities reply. There is currently no mechanism + * to determine support PIN verification/modification with a boxing + * command. Since the reader implements this mechanism it is reasonable to + * assume that PIN verification/modification is available. */ + *sc_reader_t_capabilities = SC_READER_CAP_PIN_PAD; + + if (capabilityPACE) + *sc_reader_t_capabilities |= SC_READER_CAP_PACE_GENERIC; + if (capabilityEID) + *sc_reader_t_capabilities |= SC_READER_CAP_PACE_EID; + if (capabilityESign) + *sc_reader_t_capabilities |= SC_READER_CAP_PACE_ESIGN; + if (capabilityDestroy) + *sc_reader_t_capabilities |= SC_READER_CAP_PACE_DESTROY_CHANNEL; + + return SC_SUCCESS; +} + +int boxing_pace_capabilities_to_buf(sc_context_t *ctx, + const unsigned long sc_reader_t_capabilities, + unsigned char **asn1, size_t *asn1_len) +{ + const size_t count_data = + sizeof g_PACECapabilities_data/ + sizeof *g_PACECapabilities_data; + const size_t count_sequence = + sizeof g_PACECapabilities/ + sizeof *g_PACECapabilities; + int yes = 1, no = 0; + size_t i; + struct sc_asn1_entry PACECapabilities_data[count_data]; + struct sc_asn1_entry PACECapabilities[count_sequence]; + + for (i = 0; i < count_sequence; i++) + sc_copy_asn1_entry(g_EstablishPACEChannel+i, + PACECapabilities+i); + sc_format_asn1_entry(PACECapabilities, + PACECapabilities_data, 0, 1); + + for (i = 0; i < count_data; i++) + sc_copy_asn1_entry(g_PACECapabilities_data+i, + PACECapabilities_data+i); + if (sc_reader_t_capabilities & SC_READER_CAP_PACE_GENERIC) + sc_format_asn1_entry(PACECapabilities_data+0, + &yes, NULL, 1); + else + sc_format_asn1_entry(PACECapabilities_data+0, + &no, NULL, 1); + if (sc_reader_t_capabilities & SC_READER_CAP_PACE_EID) + sc_format_asn1_entry(PACECapabilities_data+1, + &yes, NULL, 1); + else + sc_format_asn1_entry(PACECapabilities_data+1, + &no, NULL, 1); + if (sc_reader_t_capabilities & SC_READER_CAP_PACE_ESIGN) + sc_format_asn1_entry(PACECapabilities_data+2, + &yes, NULL, 1); + else + sc_format_asn1_entry(PACECapabilities_data+2, + &no, NULL, 1); + if (sc_reader_t_capabilities & SC_READER_CAP_PACE_DESTROY_CHANNEL) + sc_format_asn1_entry(PACECapabilities_data+3, + &yes, NULL, 1); + else + sc_format_asn1_entry(PACECapabilities_data+3, + &no, NULL, 1); + + return sc_asn1_encode(ctx, PACECapabilities, asn1, asn1_len); +} + +#ifdef DISABLE_GLOBAL_BOXING_INITIALIZATION +static +#endif +void sc_detect_boxing_cmds(sc_reader_t *reader) +{ + u8 rbuf[0xff]; + sc_apdu_t apdu; + unsigned long capabilities; + + memset(&apdu, 0, sizeof(apdu)); + apdu.cse = SC_APDU_CASE_1; + apdu.cla = boxing_cla; + apdu.ins = boxing_ins; + apdu.p1 = boxing_p1; + apdu.p2 = boxing_p2_GetReaderPACECapabilities; + apdu.resp = rbuf; + apdu.resplen = sizeof rbuf; + apdu.le = sizeof rbuf; + + if (!reader || !reader->ops || !reader->ops->transmit + || reader->ops->transmit(reader, &apdu) != SC_SUCCESS + || apdu.sw1 != 0x90 + || apdu.sw2 != 0x00 + || boxing_buf_to_pace_capabilities(reader->ctx, + apdu.resp, apdu.resplen, &capabilities) != SC_SUCCESS) { + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "%s does not support boxing commands", reader->name); + } else { + if (capabilities & SC_READER_CAP_PIN_PAD + && !(reader->capabilities & SC_READER_CAP_PIN_PAD)) { + ((struct sc_reader_operations *) reader->ops)->perform_verify = + boxing_perform_verify; + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Added boxing command wrappers for PIN verification/modification to '%s'", reader->name); + } + + if (capabilities & SC_READER_CAP_PACE_GENERIC + && !(reader->capabilities & SC_READER_CAP_PACE_GENERIC)) { + ((struct sc_reader_operations *) reader->ops)->perform_pace = + boxing_perform_pace; + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Added boxing command wrappers for PACE to '%s'", reader->name); + } + + reader->capabilities |= capabilities; + } +} + +#ifdef DISABLE_GLOBAL_BOXING_INITIALIZATION +void sc_initialize_boxing_cmds(sc_context_t *ctx) +{ + sc_reader_t *reader; + + if (!ctx) + return; + + if (!list_iterator_start(&ctx->readers)) + return; + + reader = list_iterator_next(&ctx->readers); + while (reader) { + reader = list_iterator_next(&ctx->readers); + sc_detect_boxing_cmds(reader); + } +} +#endif diff --git a/npa/src/npa-tool.c b/npa/src/npa-tool.c index 34dc521..3ba5939 100644 --- a/npa/src/npa-tool.c +++ b/npa/src/npa-tool.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -376,6 +377,10 @@ main (int argc, char **argv) exit(1); } +#ifndef DISABLE_GLOBAL_BOXING_INITIALIZATION + sc_initialize_boxing_cmds(ctx); +#endif + if (cmdline.break_flag) { /* The biggest buffer sprintf could write with "%llu" */ char secretbuf[strlen("18446744073709551615")+1]; diff --git a/npa/src/npa.c b/npa/src/npa.c index 08ecfa8..77fecaf 100644 --- a/npa/src/npa.c +++ b/npa/src/npa.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -1272,6 +1273,10 @@ static int establish_pace_channel(sc_card_t *card, } } +#ifdef DISABLE_GLOBAL_BOXING_INITIALIZATION + sc_detect_boxing_cmds(card->reader); +#endif + if (card->reader->capabilities & SC_READER_CAP_PACE_GENERIC && card->reader->ops->perform_pace) { r = card->reader->ops->perform_pace(card->reader, &pace_input, pace_output); diff --git a/npa/src/npa/boxing.h b/npa/src/npa/boxing.h new file mode 100644 index 0000000..5070223 --- /dev/null +++ b/npa/src/npa/boxing.h @@ -0,0 +1,65 @@ +/* + * boxing.c: interface related to boxing commands with pseudo APDUs + * + * Copyright (C) 2013 Frank Morgner + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _BOXING_CMDS_H +#define _BOXING_CMDS_H + +#include "libopensc/opensc.h" +#include "libopensc/pace.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define DISABLE_GLOBAL_BOXING_INITIALIZATION +#ifndef DISABLE_GLOBAL_BOXING_INITIALIZATION + /** initialize boxing for all available readers */ + /* currently disabled, because the OpenSC PC/SC reader implementation + * allows a transmit only when a card is connected. */ +void sc_initialize_boxing_cmds(sc_context_t *ctx); +#else +void sc_detect_boxing_cmds(sc_reader_t *reader); +#endif + +int boxing_pace_input_to_buf(sc_context_t *ctx, + const struct establish_pace_channel_input *input, + unsigned char **asn1, size_t *asn1_len); +int boxing_buf_to_pace_input(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + struct establish_pace_channel_input *input); +int boxing_pace_output_to_buf(sc_context_t *ctx, + const struct establish_pace_channel_output *output, + unsigned char **asn1, size_t *asn1_len); +int boxing_buf_to_pace_output(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + struct establish_pace_channel_output *output); +int boxing_pace_capabilities_to_buf(sc_context_t *ctx, + const unsigned long sc_reader_t_capabilities, + unsigned char **asn1, size_t *asn1_len); +int boxing_buf_to_pace_capabilities(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + unsigned long *sc_reader_t_capabilities); + +#ifdef __cplusplus +} +#endif + +#endif +