implemented boxing commands according to TR-03119
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
285
ccid/src/ccid.c
285
ccid/src/ccid.c
@@ -33,12 +33,41 @@
|
||||
|
||||
#include <npa/scutil.h>
|
||||
#ifdef WITH_PACE
|
||||
#include <npa/npa.h>
|
||||
#include <npa/boxing.h>
|
||||
#include <npa/iso-sm.h>
|
||||
#include <npa/npa.h>
|
||||
#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) (a<b?a:b)
|
||||
static int
|
||||
perform_pseudo_apdu(sc_reader_t *reader, sc_apdu_t *apdu)
|
||||
{
|
||||
if (!reader || !apdu || apdu->cla != 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);
|
||||
|
||||
Reference in New Issue
Block a user