From 40acedcc218c7770243a94eb5bc9d43078dd2209 Mon Sep 17 00:00:00 2001 From: Frank Morgner Date: Wed, 11 Nov 2015 00:28:16 +0100 Subject: [PATCH] Added support for PIN commands via escape commands As defined in BSI TR-03119 to issue SCardTransmit (with Uses Pseudo-APDU) instead of SCardControl (with FEATURE_VERIFY_PIN_DIRECT). It allows using a very basic PC/SC reader driver without special support for PIN verification or modification (such as the default CCID driver on Windows). Also gets IFD vendor information via escape commands. PC/SC's Get Uid command is now only triggered if enable_escape = true; was set by the user to allow disabling wrapped commands on broken readers (see https://github.com/OpenSC/OpenSC/issues/810) --- etc/opensc.conf.in | 6 + src/libopensc/Makefile.am | 4 +- src/libopensc/Makefile.mak | 2 +- src/libopensc/card.c | 4 + src/libopensc/ccid-types.h | 281 +++++++++ src/libopensc/libopensc.exports | 6 + src/libopensc/opensc.h | 1 + src/libopensc/reader-ctapi.c | 2 + src/libopensc/reader-openct.c | 2 + src/libopensc/reader-pcsc.c | 48 +- src/libopensc/reader-tr03119.c | 1012 +++++++++++++++++++++++++++++++ src/libopensc/reader-tr03119.h | 57 ++ src/pkcs11/slot.c | 33 +- 13 files changed, 1427 insertions(+), 31 deletions(-) create mode 100644 src/libopensc/ccid-types.h create mode 100644 src/libopensc/reader-tr03119.c create mode 100644 src/libopensc/reader-tr03119.h diff --git a/etc/opensc.conf.in b/etc/opensc.conf.in index bf74b636..5419b44e 100644 --- a/etc/opensc.conf.in +++ b/etc/opensc.conf.in @@ -103,6 +103,12 @@ app default { # Default: true # enable_pinpad = false; # + # Detect reader capabilities with escape commands (wrapped APDUs with + # CLA=0xFF as defined by PC/SC pt. 3 and BSI TR-03119, e.g. for getting + # the UID, escaped PIN commands and the reader's firmware version) + # Default: false + # enable_escape = true; + # # Use specific pcsc provider. # Default: @DEFAULT_PCSC_PROVIDER@ # provider_library = @DEFAULT_PCSC_PROVIDER@ diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index d836636a..9cd44daf 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -12,7 +12,7 @@ noinst_HEADERS = cards.h ctbcs.h internal.h esteid.h muscle.h muscle-filesystem. errors.h types.h compression.h itacns.h iso7816.h \ authentic.h iasecc.h iasecc-sdo.h sm.h card-sc-hsm.h \ pace.h cwa14890.h cwa-dnie.h card-gids.h aux-data.h \ - jpki.h sc-ossl-compat.h card-npa.h + jpki.h sc-ossl-compat.h card-npa.h ccid-types.h reader-tr03119.h AM_CPPFLAGS = -DOPENSC_CONF_PATH=\"$(sysconfdir)/opensc.conf\" \ -I$(top_srcdir)/src @@ -30,7 +30,7 @@ libopensc_la_SOURCES = \ \ muscle.c muscle-filesystem.c \ \ - ctbcs.c reader-ctapi.c reader-pcsc.c reader-openct.c \ + ctbcs.c reader-ctapi.c reader-pcsc.c reader-openct.c reader-tr03119.c \ \ card-setcos.c card-miocos.c card-flex.c card-gpk.c \ card-cardos.c card-tcos.c card-default.c \ diff --git a/src/libopensc/Makefile.mak b/src/libopensc/Makefile.mak index 1a3eb2a1..a2431d37 100644 --- a/src/libopensc/Makefile.mak +++ b/src/libopensc/Makefile.mak @@ -12,7 +12,7 @@ OBJECTS = \ \ muscle.obj muscle-filesystem.obj \ \ - ctbcs.obj reader-ctapi.obj reader-pcsc.obj reader-openct.obj \ + ctbcs.obj reader-ctapi.obj reader-pcsc.obj reader-openct.obj reader-tr03119.obj \ \ card-setcos.obj card-miocos.obj card-flex.obj card-gpk.obj \ card-cardos.obj card-tcos.obj card-default.obj \ diff --git a/src/libopensc/card.c b/src/libopensc/card.c index dd96e179..73f8ede6 100644 --- a/src/libopensc/card.c +++ b/src/libopensc/card.c @@ -29,6 +29,7 @@ #endif #include +#include "reader-tr03119.h" #include "internal.h" #include "asn1.h" #include "common/compat_strlcpy.h" @@ -204,6 +205,9 @@ int sc_connect_card(sc_reader_t *reader, sc_card_t **card_out) card->reader = reader; card->ctx = ctx; + if (reader->flags & SC_READER_ENABLE_ESCAPE) + sc_detect_escape_cmds(reader); + memcpy(&card->atr, &reader->atr, sizeof(card->atr)); memcpy(&card->uid, &reader->uid, sizeof(card->uid)); diff --git a/src/libopensc/ccid-types.h b/src/libopensc/ccid-types.h new file mode 100644 index 00000000..59d3cf3f --- /dev/null +++ b/src/libopensc/ccid-types.h @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2009-2015 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 + */ +/** + * @file + */ +#ifndef _CCID_TYPES_H +#define _CCID_TYPES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _MSC_VER +#define PACKED +#pragma pack(push,1) +#elif defined(__GNUC__) +#define PACKED __attribute__ ((__packed__)) +#endif + +#define USB_REQ_CCID 0xA1 + +#define CCID_CONTROL_ABORT 0x01 +#define CCID_CONTROL_GET_CLOCK_FREQUENCIES 0x02 +#define CCID_CONTROL_GET_DATA_RATES 0x03 + +#define CCID_OPERATION_VERIFY 0x00; +#define CCID_OPERATION_MODIFY 0x01; +#define CCID_ENTRY_VALIDATE 0x02 + +#define CCID_BERROR_CMD_ABORTED 0xff /** Host aborted the current activity */ +#define CCID_BERROR_ICC_MUTE 0xfe /** CCID timed out while talking to the ICC */ +#define CCID_BERROR_XFR_PARITY_ERROR 0xfd /** Parity error while talking to the ICC */ +#define CCID_BERROR_XFR_OVERRUN 0xfc /** Overrun error while talking to the ICC */ +#define CCID_BERROR_HW_ERROR 0xfb /** An all inclusive hardware error occurred */ +#define CCID_BERROR_BAD_ATR_TS 0xf +#define CCID_BERROR_BAD_ATR_TCK 0xf +#define CCID_BERROR_ICC_PROTOCOL_NOT_SUPPORTED 0xf6 +#define CCID_BERROR_ICC_CLASS_NOT_SUPPORTED 0xf5 +#define CCID_BERROR_PROCEDURE_BYTE_CONFLICT 0xf4 +#define CCID_BERROR_DEACTIVATED_PROTOCOL 0xf3 +#define CCID_BERROR_BUSY_WITH_AUTO_SEQUENCE 0xf2 /** Automatic Sequence Ongoing */ +#define CCID_BERROR_PIN_TIMEOUT 0xf0 +#define CCID_BERROR_PIN_CANCELLED 0xef +#define CCID_BERROR_CMD_SLOT_BUSY 0xe0 /** A second command was sent to a slot which was already processing a command. */ +#define CCID_BERROR_CMD_NOT_SUPPORTED 0x00 +#define CCID_BERROR_OK 0x00 + +#define CCID_BSTATUS_OK_ACTIVE 0x00 /** No error. An ICC is present and active */ +#define CCID_BSTATUS_OK_INACTIVE 0x01 /** No error. ICC is present and inactive */ +#define CCID_BSTATUS_OK_NOICC 0x02 /** No error. No ICC is present */ +#define CCID_BSTATUS_ERROR_ACTIVE 0x40 /** Failed. An ICC is present and active */ +#define CCID_BSTATUS_ERROR_INACTIVE 0x41 /** Failed. ICC is present and inactive */ +#define CCID_BSTATUS_ERROR_NOICC 0x42 /** Failed. No ICC is present */ + +#define CCID_WLEVEL_DIRECT __constant_cpu_to_le16(0) /** APDU begins and ends with this command */ +#define CCID_WLEVEL_CHAIN_NEXT_XFRBLOCK __constant_cpu_to_le16(1) /** APDU begins with this command, and continue in the next PC_to_RDR_XfrBlock */ +#define CCID_WLEVEL_CHAIN_END __constant_cpu_to_le16(2) /** abData field continues a command APDU and ends the APDU command */ +#define CCID_WLEVEL_CHAIN_CONTINUE __constant_cpu_to_le16(3) /** abData field continues a command APDU and another block is to follow */ +#define CCID_WLEVEL_RESPONSE_IN_DATABLOCK __constant_cpu_to_le16(0x10) /** empty abData field, continuation of response APDU is expected in the next RDR_to_PC_DataBlock */ + +#define CCID_PIN_ENCODING_BIN 0x00 +#define CCID_PIN_ENCODING_BCD 0x01 +#define CCID_PIN_ENCODING_ASCII 0x02 +#define CCID_PIN_UNITS_BYTES 0x80 +#define CCID_PIN_JUSTIFY_RIGHT 0x04 +#define CCID_PIN_CONFIRM_NEW 0x01 +#define CCID_PIN_INSERT_OLD 0x02 +#define CCID_PIN_NO_MSG 0x00 +#define CCID_PIN_MSG1 0x01 +#define CCID_PIN_MSG2 0x02 +#define CCID_PIN_MSG_REF 0x03 +#define CCID_PIN_MSG_DEFAULT 0xff + +#define CCID_SLOTS_UNCHANGED 0x00 +#define CCID_SLOT1_CARD_PRESENT 0x01 +#define CCID_SLOT1_CHANGED 0x02 +#define CCID_SLOT2_CARD_PRESENT 0x04 +#define CCID_SLOT2_CHANGED 0x08 +#define CCID_SLOT3_CARD_PRESENT 0x10 +#define CCID_SLOT3_CHANGED 0x20 +#define CCID_SLOT4_CARD_PRESENT 0x40 +#define CCID_SLOT4_CHANGED 0x80 + +#define CCID_EXT_APDU_MAX (4 + 3 + 0xffff + 3) +#define CCID_SHORT_APDU_MAX (4 + 1 + 0xff + 1) + +struct ccid_class_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdCCID; + uint8_t bMaxSlotIndex; + uint8_t bVoltageSupport; + uint32_t dwProtocols; + uint32_t dwDefaultClock; + uint32_t dwMaximumClock; + uint8_t bNumClockSupport; + uint32_t dwDataRate; + uint32_t dwMaxDataRate; + uint8_t bNumDataRatesSupported; + uint32_t dwMaxIFSD; + uint32_t dwSynchProtocols; + uint32_t dwMechanical; + uint32_t dwFeatures; + uint32_t dwMaxCCIDMessageLength; + uint8_t bClassGetResponse; + uint8_t bclassEnvelope; + uint16_t wLcdLayout; + uint8_t bPINSupport; + uint8_t bMaxCCIDBusySlots; +} PACKED; + +typedef struct { + uint8_t bmFindexDindex; + uint8_t bmTCCKST0; + uint8_t bGuardTimeT0; + uint8_t bWaitingIntegerT0; + uint8_t bClockStop; +} PACKED abProtocolDataStructure_T0_t; +typedef struct { + uint8_t bmFindexDindex; + uint8_t bmTCCKST1; + uint8_t bGuardTimeT1; + uint8_t bWaitingIntegersT1; + uint8_t bClockStop; + uint8_t bIFSC; + uint8_t bNadValue; +} PACKED abProtocolDataStructure_T1_t; + +typedef struct { + uint8_t bTimeOut; + uint8_t bmFormatString; + uint8_t bmPINBlockString; + uint8_t bmPINLengthFormat; + uint16_t wPINMaxExtraDigit; + uint8_t bEntryValidationCondition; + uint8_t bNumberMessage; + uint16_t wLangId; + uint8_t bMsgIndex; + uint8_t bTeoPrologue1; + uint16_t bTeoPrologue2; +} PACKED abPINDataStucture_Verification_t; +typedef struct { + uint8_t bTimeOut; + uint8_t bmFormatString; + uint8_t bmPINBlockString; + uint8_t bmPINLengthFormat; + uint8_t bInsertionOffsetOld; + uint8_t bInsertionOffsetNew; + uint16_t wPINMaxExtraDigit; + uint8_t bConfirmPIN; + uint8_t bEntryValidationCondition; + uint8_t bNumberMessage; + uint16_t wLangId; + uint8_t bMsgIndex1; +} PACKED abPINDataStucture_Modification_t; + +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t bBWI; + uint16_t wLevelParameter; +} PACKED PC_to_RDR_XfrBlock_t; +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t abRFU1; + uint16_t abRFU2; +} PACKED PC_to_RDR_IccPowerOff_t; +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t abRFU1; + uint16_t abRFU2; +} PACKED PC_to_RDR_GetSlotStatus_t; +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t abRFU1; + uint16_t abRFU2; +} PACKED PC_to_RDR_GetParameters_t; +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t abRFU1; + uint16_t abRFU2; +} PACKED PC_to_RDR_ResetParameters_t; +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t bProtocolNum; + uint16_t abRFU; +} PACKED PC_to_RDR_SetParameters_t; +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t bBWI; + uint16_t wLevelParameter; +} PACKED PC_to_RDR_Secure_t; +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t bPowerSelect; + uint16_t abRFU; +} PACKED PC_to_RDR_IccPowerOn_t; + +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t bStatus; + uint8_t bError; + uint8_t bClockStatus; +} PACKED RDR_to_PC_SlotStatus_t; +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t bStatus; + uint8_t bError; + uint8_t bChainParameter; +} PACKED RDR_to_PC_DataBlock_t; +typedef struct { + uint8_t bMessageType; + uint32_t dwLength; + uint8_t bSlot; + uint8_t bSeq; + uint8_t bStatus; + uint8_t bError; + uint8_t bProtocolNum; +} PACKED RDR_to_PC_Parameters_t; +typedef struct { + uint8_t bMessageType; + uint8_t bmSlotICCState; /* we support 1 slots, so we need 2*1 bits = 1 byte */ +} PACKED RDR_to_PC_NotifySlotChange_t; + +#ifdef _MSC_VER +#undef PACKED +#pragma pack(pop) +#elif defined(__GNUC__) +#undef PACKED +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/libopensc/libopensc.exports b/src/libopensc/libopensc.exports index a7027b19..18f80374 100644 --- a/src/libopensc/libopensc.exports +++ b/src/libopensc/libopensc.exports @@ -353,3 +353,9 @@ perform_chip_authentication npa_default_flags npa_reset_retry_counter npa_pace_get_tries_left +escape_pace_input_to_buf +escape_buf_to_pace_input +escape_pace_output_to_buf +escape_buf_to_pace_output +escape_pace_capabilities_to_buf +escape_buf_to_pace_capabilities diff --git a/src/libopensc/opensc.h b/src/libopensc/opensc.h index fcd28439..01cbbc36 100644 --- a/src/libopensc/opensc.h +++ b/src/libopensc/opensc.h @@ -287,6 +287,7 @@ struct sc_reader_driver { #define SC_READER_CARD_EXCLUSIVE 0x00000008 #define SC_READER_HAS_WAITING_AREA 0x00000010 #define SC_READER_REMOVED 0x00000020 +#define SC_READER_ENABLE_ESCAPE 0x00000040 /* reader capabilities */ #define SC_READER_CAP_DISPLAY 0x00000001 diff --git a/src/libopensc/reader-ctapi.c b/src/libopensc/reader-ctapi.c index 32f9104c..0fc01db6 100644 --- a/src/libopensc/reader-ctapi.c +++ b/src/libopensc/reader-ctapi.c @@ -397,6 +397,8 @@ static int ctapi_load_module(sc_context_t *ctx, if (conf_block) { reader->max_send_size = scconf_get_int(conf_block, "max_send_size", reader->max_send_size); reader->max_recv_size = scconf_get_int(conf_block, "max_recv_size", reader->max_recv_size); + if (scconf_get_bool(conf_block, "enable_escape", 0)) + reader->flags |= SC_READER_ENABLE_ESCAPE; } r = _sc_add_reader(ctx, reader); diff --git a/src/libopensc/reader-openct.c b/src/libopensc/reader-openct.c index d66d391b..9e3bef6c 100644 --- a/src/libopensc/reader-openct.c +++ b/src/libopensc/reader-openct.c @@ -138,6 +138,8 @@ openct_add_reader(sc_context_t *ctx, unsigned int num, ct_info_t *info) if (conf_block) { reader->max_send_size = scconf_get_int(conf_block, "max_send_size", reader->max_send_size); reader->max_recv_size = scconf_get_int(conf_block, "max_recv_size", reader->max_recv_size); + if (scconf_get_bool(conf_block, "enable_escape", 0)) + reader->flags |= SC_READER_ENABLE_ESCAPE; } if ((rc = _sc_add_reader(ctx, reader)) < 0) { diff --git a/src/libopensc/reader-pcsc.c b/src/libopensc/reader-pcsc.c index 93bfbbed..bc33cfcc 100644 --- a/src/libopensc/reader-pcsc.c +++ b/src/libopensc/reader-pcsc.c @@ -472,29 +472,31 @@ static int pcsc_reconnect(sc_reader_t * reader, DWORD action) static void initialize_uid(sc_reader_t *reader) { - sc_apdu_t apdu; - /* though we only expect 10 bytes max, we want to set the Le to 0x00 to not - * get 0x6282 as SW in case of a UID variant shorter than 10 bytes */ - u8 rbuf[256]; + if (reader->flags & SC_READER_ENABLE_ESCAPE) { + sc_apdu_t apdu; + /* though we only expect 10 bytes max, we want to set the Le to 0x00 to not + * get 0x6282 as SW in case of a UID variant shorter than 10 bytes */ + u8 rbuf[256]; - memset(&apdu, 0, sizeof(apdu)); - apdu.cse = SC_APDU_CASE_2_SHORT; - apdu.cla = 0xFF; - apdu.ins = 0xCA; - apdu.p1 = 0x00; - apdu.p2 = 0x00; - apdu.le = 0x00; - apdu.resp = rbuf; - apdu.resplen = sizeof rbuf; + memset(&apdu, 0, sizeof(apdu)); + apdu.cse = SC_APDU_CASE_2_SHORT; + apdu.cla = 0xFF; + apdu.ins = 0xCA; + apdu.p1 = 0x00; + apdu.p2 = 0x00; + apdu.le = 0x00; + apdu.resp = rbuf; + apdu.resplen = sizeof rbuf; - if (SC_SUCCESS == pcsc_transmit(reader, &apdu) - && apdu.sw1 == 0x90 && apdu.sw2 == 0x00) { - reader->uid.len = apdu.resplen; - memcpy(reader->uid.value, apdu.resp, reader->uid.len); - sc_debug_hex(reader->ctx, SC_LOG_DEBUG_NORMAL, "UID", - reader->uid.value, reader->uid.len); - } else { - sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, "unable to get UID"); + if (SC_SUCCESS == pcsc_transmit(reader, &apdu) + && apdu.sw1 == 0x90 && apdu.sw2 == 0x00) { + reader->uid.len = apdu.resplen; + memcpy(reader->uid.value, apdu.resp, reader->uid.len); + sc_debug_hex(reader->ctx, SC_LOG_DEBUG_NORMAL, "UID", + reader->uid.value, reader->uid.len); + } else { + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, "unable to get UID"); + } } } @@ -1306,6 +1308,8 @@ static int pcsc_detect_readers(sc_context_t *ctx) if (conf_block) { reader->max_send_size = scconf_get_int(conf_block, "max_send_size", reader->max_send_size); reader->max_recv_size = scconf_get_int(conf_block, "max_recv_size", reader->max_recv_size); + if (scconf_get_bool(conf_block, "enable_escape", 0)) + reader->flags |= SC_READER_ENABLE_ESCAPE; } sc_log(ctx, "reader's max-send-size: %i, max-recv-size: %i", reader->max_send_size, reader->max_recv_size); @@ -2393,6 +2397,8 @@ int cardmod_use_reader(sc_context_t *ctx, void * pcsc_context_handle, void * pcs if (conf_block) { reader->max_send_size = scconf_get_int(conf_block, "max_send_size", reader->max_send_size); reader->max_recv_size = scconf_get_int(conf_block, "max_recv_size", reader->max_recv_size); + if (scconf_get_bool(conf_block, "enable_escape", 0)) + reader->flags |= SC_READER_ENABLE_ESCAPE; } /* attempt to detect protocol in use T0/T1/RAW */ diff --git a/src/libopensc/reader-tr03119.c b/src/libopensc/reader-tr03119.c new file mode 100644 index 00000000..2aada6ec --- /dev/null +++ b/src/libopensc/reader-tr03119.c @@ -0,0 +1,1012 @@ +/* + * reader-escape.c: implementation related to escape commands with pseudo APDUs + * + * Copyright (C) 2013-2015 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 "reader-tr03119.h" +#include "ccid-types.h" +#include "internal.h" +#include "libopensc/asn1.h" +#include "libopensc/log.h" +#include "libopensc/opensc.h" +#include "libopensc/pace.h" +#include +#include + +#if _WIN32 +/* FIXME might not always work */ +#define htole16(x) (x) +#define htole32(x) (x) +#elif __APPLE__ +#include +#define htole16(x) OSSwapHostToLittleInt16(x) +#define htole32(x) OSSwapHostToLittleInt32(x) +#else +#ifndef _BSD_SOURCE +#define _BSD_SOURCE /* See feature_test_macros(7) */ +#endif +#include +#endif + +static const u8 escape_cla = 0xff; +static const u8 escape_ins = 0x9a; + +static const u8 escape_p1_PIN = 0x04; +static const u8 escape_p2_GetReaderPACECapabilities = 0x01; +static const u8 escape_p2_EstablishPACEChannel = 0x02; +/*static const u8 escape_p2_DestroyPACEChannel = 0x03;*/ +static const u8 escape_p2_PC_to_RDR_Secure = 0x10; + +static const u8 escape_p1_IFD = 0x01; +static const u8 escape_p2_vendor = 0x01; +/*static const u8 escape_p2_product = 0x03;*/ +static const u8 escape_p2_version_firmware = 0x06; +/*static const u8 escape_p2_version_driver = 0x07;*/ + +struct sc_asn1_entry g_boolean[] = { + { "boolean", + SC_ASN1_BOOLEAN, SC_ASN1_TAG_BOOLEAN, 0, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; +struct sc_asn1_entry g_int_as_octet_string[] = { + { "int as octet string", + SC_ASN1_OCTET_STRING, SC_ASN1_TAG_INTEGER, 0, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; +struct sc_asn1_entry g_octet_string[] = { + { "octet string", + SC_ASN1_OCTET_STRING, SC_ASN1_TAG_OCTET_STRING, SC_ASN1_ALLOC, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; +struct sc_asn1_entry g_numeric_string_as_octet_string[] = { + { "utf8string", + SC_ASN1_OCTET_STRING, SC_ASN1_TAG_NUMERICSTRING, SC_ASN1_ALLOC, NULL, NULL }, + { NULL , 0 , 0 , 0 , NULL , NULL } +}; + +static const struct sc_asn1_entry g_EstablishPACEChannelInput_data[] = { + { "passwordID", + /* use an OCTET STRING to avoid a conversion to int */ + SC_ASN1_STRUCT, SC_ASN1_CTX|0x01|SC_ASN1_CONS, 0, NULL, NULL }, + { "transmittedPassword", + SC_ASN1_STRUCT, SC_ASN1_CTX|0x02|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "cHAT", + SC_ASN1_STRUCT, SC_ASN1_CTX|0x03|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "certificateDescription", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x04|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "hashOID", + /* use an OCTET STRING to avoid a conversion to struct sc_object_id */ + SC_ASN1_STRUCT, SC_ASN1_CTX|0x05|SC_ASN1_CONS, 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_STRUCT, SC_ASN1_CTX|0x01|SC_ASN1_CONS, 0, NULL, NULL }, + { "statusMSESetAT", + SC_ASN1_STRUCT, SC_ASN1_CTX|0x02|SC_ASN1_CONS, 0, NULL, NULL }, + { "efCardAccess", + SC_ASN1_OCTET_STRING, SC_ASN1_CTX|0x03|SC_ASN1_CONS, SC_ASN1_ALLOC, NULL, NULL }, + { "idPICC", + SC_ASN1_STRUCT, SC_ASN1_CTX|0x04|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "curCAR", + SC_ASN1_STRUCT, SC_ASN1_CTX|0x05|SC_ASN1_CONS, SC_ASN1_OPTIONAL|SC_ASN1_ALLOC, NULL, NULL }, + { "prevCAR", + SC_ASN1_STRUCT, SC_ASN1_CTX|0x06|SC_ASN1_CONS, 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 escape_pace_input_to_buf(sc_context_t *ctx, + const struct establish_pace_channel_input *input, + unsigned char **asn1, size_t *asn1_len) +{ + size_t pin_id_len = sizeof input->pin_id; + struct sc_asn1_entry EstablishPACEChannelInput_data[ + sizeof g_EstablishPACEChannelInput_data/ + sizeof *g_EstablishPACEChannelInput_data]; + struct sc_asn1_entry EstablishPACEChannel[ + sizeof g_EstablishPACEChannel/ + sizeof *g_EstablishPACEChannel]; + struct sc_asn1_entry passwordID[ + sizeof g_int_as_octet_string/ + sizeof *g_int_as_octet_string]; + struct sc_asn1_entry transmittedPassword[ + sizeof g_numeric_string_as_octet_string/ + sizeof *g_numeric_string_as_octet_string]; + struct sc_asn1_entry cHAT[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + + sc_copy_asn1_entry(g_EstablishPACEChannel, + EstablishPACEChannel); + sc_format_asn1_entry(EstablishPACEChannel, + EstablishPACEChannelInput_data, 0, 1); + + sc_copy_asn1_entry(g_EstablishPACEChannelInput_data, + EstablishPACEChannelInput_data); + + sc_format_asn1_entry(EstablishPACEChannelInput_data+0, + passwordID, 0, 1); + sc_copy_asn1_entry(g_int_as_octet_string, + passwordID); + sc_format_asn1_entry(passwordID, + (unsigned char *) &input->pin_id, &pin_id_len, 1); + + if (input->pin) { + sc_format_asn1_entry(EstablishPACEChannelInput_data+1, + transmittedPassword, + 0, 1); + sc_copy_asn1_entry(g_numeric_string_as_octet_string, + transmittedPassword); + sc_format_asn1_entry(transmittedPassword, + (unsigned char *) input->pin, + (size_t *) &input->pin_length, 1); + } + + if (input->chat) { + sc_format_asn1_entry(EstablishPACEChannelInput_data+2, + cHAT, + 0, 1); + sc_copy_asn1_entry(g_octet_string, + cHAT); + sc_format_asn1_entry(cHAT, + (unsigned char *) input->chat, + (size_t *) &input->chat_length, 1); + } + + if (input->certificate_description) { + sc_format_asn1_entry(EstablishPACEChannelInput_data+3, + (unsigned char *) input->certificate_description, + (size_t *) &input->certificate_description_length, 1); + } + + return sc_asn1_encode(ctx, EstablishPACEChannel, asn1, asn1_len); +} + +int escape_buf_to_pace_input(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + struct establish_pace_channel_input *input) +{ + size_t pin_id_len = sizeof input->pin_id; + struct sc_asn1_entry EstablishPACEChannelInput_data[ + sizeof g_EstablishPACEChannelInput_data/ + sizeof *g_EstablishPACEChannelInput_data]; + struct sc_asn1_entry EstablishPACEChannel[ + sizeof g_EstablishPACEChannel/ + sizeof *g_EstablishPACEChannel]; + struct sc_asn1_entry passwordID[ + sizeof g_int_as_octet_string/ + sizeof *g_int_as_octet_string]; + struct sc_asn1_entry transmittedPassword[ + sizeof g_numeric_string_as_octet_string/ + sizeof *g_numeric_string_as_octet_string]; + struct sc_asn1_entry cHAT[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + /* FIXME handle hashOID */ + + sc_copy_asn1_entry(g_EstablishPACEChannel, + EstablishPACEChannel); + sc_format_asn1_entry(EstablishPACEChannel, + EstablishPACEChannelInput_data, 0, 0); + + sc_copy_asn1_entry(g_EstablishPACEChannelInput_data, + EstablishPACEChannelInput_data); + + sc_format_asn1_entry(EstablishPACEChannelInput_data+0, + passwordID, 0, 0); + sc_copy_asn1_entry(g_int_as_octet_string, + passwordID); + sc_format_asn1_entry(passwordID, + &input->pin_id, &pin_id_len, 0); + + if (input->pin) { + sc_format_asn1_entry(EstablishPACEChannelInput_data+1, + transmittedPassword, 0, 0); + sc_copy_asn1_entry(g_numeric_string_as_octet_string, + transmittedPassword); + sc_format_asn1_entry(transmittedPassword, + (unsigned char *) &input->pin, &input->pin_length, 0); + } + + if (input->chat) { + sc_format_asn1_entry(EstablishPACEChannelInput_data+2, + cHAT, 0, 0); + sc_copy_asn1_entry(g_octet_string, + cHAT); + sc_format_asn1_entry(cHAT, + (unsigned char *) &input->chat, &input->chat_length, 0); + } + + if (input->certificate_description) { + 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 escape_pace_output_to_buf(sc_context_t *ctx, + const struct establish_pace_channel_output *output, + unsigned char **asn1, size_t *asn1_len) +{ + uint16_t status_mse_set_at = ((output->mse_set_at_sw1 & 0xff) << 8) | output->mse_set_at_sw2; + size_t result_len = sizeof output->result, + status_mse_set_at_len = sizeof status_mse_set_at; + struct sc_asn1_entry EstablishPACEChannelOutput_data[ + sizeof g_EstablishPACEChannelOutput_data/ + sizeof *g_EstablishPACEChannelOutput_data]; + struct sc_asn1_entry EstablishPACEChannel[ + sizeof g_EstablishPACEChannel/ + sizeof *g_EstablishPACEChannel]; + struct sc_asn1_entry errorCode[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + struct sc_asn1_entry statusMSESetAT[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + struct sc_asn1_entry idPICC[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + struct sc_asn1_entry curCAR[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + struct sc_asn1_entry prevCAR[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + + sc_copy_asn1_entry(g_EstablishPACEChannel, + EstablishPACEChannel); + sc_format_asn1_entry(EstablishPACEChannel, + EstablishPACEChannelOutput_data, 0, 1); + + sc_copy_asn1_entry(g_EstablishPACEChannelOutput_data, + EstablishPACEChannelOutput_data); + + sc_format_asn1_entry(EstablishPACEChannelOutput_data+0, + errorCode, 0, 1); + sc_copy_asn1_entry(g_octet_string, + errorCode); + sc_format_asn1_entry(errorCode, + (unsigned char *) &output->result, &result_len, 1); + + sc_format_asn1_entry(EstablishPACEChannelOutput_data+1, + statusMSESetAT, 0, 1); + sc_copy_asn1_entry(g_octet_string, + statusMSESetAT); + sc_format_asn1_entry(statusMSESetAT, + &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, + idPICC, 0, 1); + sc_copy_asn1_entry(g_octet_string, + idPICC); + sc_format_asn1_entry(idPICC, + output->id_icc, (size_t *) &output->id_icc_length, 1); + } + + if (output->recent_car) { + sc_format_asn1_entry(EstablishPACEChannelOutput_data+4, + curCAR, 0, 1); + sc_copy_asn1_entry(g_octet_string, + curCAR); + sc_format_asn1_entry(curCAR, + output->recent_car, (size_t *) &output->recent_car_length, 1); + } + + if (output->previous_car) { + sc_format_asn1_entry(EstablishPACEChannelOutput_data+5, + prevCAR, 0, 1); + sc_copy_asn1_entry(g_octet_string, + prevCAR); + sc_format_asn1_entry(prevCAR, + output->previous_car, (size_t *) &output->previous_car_length, 1); + } + + return sc_asn1_encode(ctx, EstablishPACEChannel, asn1, asn1_len); +} + +int escape_buf_to_pace_output(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + struct establish_pace_channel_output *output) +{ + uint16_t status_mse_set_at; + size_t result_len = sizeof output->result, + status_mse_set_at_len = sizeof status_mse_set_at; + struct sc_asn1_entry EstablishPACEChannelOutput_data[ + sizeof g_EstablishPACEChannelOutput_data/ + sizeof *g_EstablishPACEChannelOutput_data]; + struct sc_asn1_entry EstablishPACEChannel[ + sizeof g_EstablishPACEChannel/ + sizeof *g_EstablishPACEChannel]; + struct sc_asn1_entry errorCode[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + struct sc_asn1_entry statusMSESetAT[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + struct sc_asn1_entry idPICC[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + struct sc_asn1_entry curCAR[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + struct sc_asn1_entry prevCAR[ + sizeof g_octet_string/ + sizeof *g_octet_string]; + + sc_copy_asn1_entry(g_EstablishPACEChannel, + EstablishPACEChannel); + sc_format_asn1_entry(EstablishPACEChannel, + EstablishPACEChannelOutput_data, 0, 0); + + sc_copy_asn1_entry(g_EstablishPACEChannelOutput_data, + EstablishPACEChannelOutput_data); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+0, + errorCode, 0, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+1, + statusMSESetAT, 0, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+2, + &output->ef_cardaccess, &output->ef_cardaccess_length, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+3, + idPICC, 0, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+4, + curCAR, 0, 0); + sc_format_asn1_entry(EstablishPACEChannelOutput_data+5, + prevCAR, 0, 0); + + sc_copy_asn1_entry(g_octet_string, + errorCode); + sc_format_asn1_entry(errorCode, + &output->result, &result_len, 0); + /* we already allocated memory for the result */ + errorCode->flags = 0; + + sc_copy_asn1_entry(g_octet_string, + statusMSESetAT); + sc_format_asn1_entry(statusMSESetAT, + &status_mse_set_at, &status_mse_set_at_len, 0); + /* we already allocated memory for the result */ + statusMSESetAT->flags = 0; + + sc_copy_asn1_entry(g_octet_string, + idPICC); + sc_format_asn1_entry(idPICC, + &output->id_icc, &output->id_icc_length, 0); + + sc_copy_asn1_entry(g_octet_string, + curCAR); + sc_format_asn1_entry(curCAR, + &output->recent_car, &output->recent_car_length, 0); + + sc_copy_asn1_entry(g_octet_string, + prevCAR); + sc_format_asn1_entry(prevCAR, + &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; +} + +#define CCID_PIN_TIMEOUT 30 +#define CCID_DISPLAY_DEFAULT 0xff +static int escape_pin_cmd_to_buf(sc_context_t *ctx, + const struct sc_pin_cmd_data *data, + unsigned char **pc_to_rdr_secure, size_t *pc_to_rdr_secure_len) +{ + PC_to_RDR_Secure_t *secure; + abPINDataStucture_Modification_t *modify; + abPINDataStucture_Verification_t *verify; + uint16_t wLangId = 0, + bTeoPrologue2 = 0, + wPINMaxExtraDigit; + uint8_t bTimeOut = CCID_PIN_TIMEOUT, + bNumberMessage = CCID_DISPLAY_DEFAULT, + bTeoPrologue1 = 0, + bMsgIndex = 0, + bMessageType = 0x69, + bSlot = 0, + bSeq = 0, + bBWI = 0xff, + wLevelParameter = 0, + bEntryValidationCondition = CCID_ENTRY_VALIDATE, + bmFormatString, bmPINLengthFormat, bmPINBlockString; + const struct sc_pin_cmd_pin *pin_ref; + int r; + unsigned char *pinapdu = NULL; + size_t pinapdu_len = 0; + + if (!data || !pc_to_rdr_secure || !pc_to_rdr_secure_len) { + r = SC_ERROR_INVALID_ARGUMENTS; + goto err; + } + + pin_ref = data->flags & SC_PIN_CMD_IMPLICIT_CHANGE ? + &data->pin2 : &data->pin1; + + wPINMaxExtraDigit = htole16( + (0xff & pin_ref->min_length) << 8) + | (pin_ref->max_length & 0xff); + + bmFormatString = CCID_PIN_UNITS_BYTES + | ((pin_ref->offset & 0xf) << 3); + switch (pin_ref->encoding) { + case SC_PIN_ENCODING_ASCII: + bmFormatString |= CCID_PIN_ENCODING_ASCII; + break; + case SC_PIN_ENCODING_BCD: + bmFormatString |= CCID_PIN_ENCODING_BCD; + break; + default: + r = SC_ERROR_INVALID_ARGUMENTS; + goto err; + } + + /* GLP PINs expect the effective PIN length from bit 4 */ + bmPINLengthFormat = pin_ref->encoding == SC_PIN_ENCODING_GLP ? + 0x04 : 0x00; + + if (pin_ref->encoding == SC_PIN_ENCODING_GLP) { + /* GLP PIN length is encoded in 4 bits and block size is always 8 bytes */ + bmPINBlockString = 0x40 | 0x08; + } else if (pin_ref->encoding == SC_PIN_ENCODING_ASCII && data->flags & SC_PIN_CMD_NEED_PADDING) { + bmPINBlockString = pin_ref->pad_length; + } else { + bmPINBlockString = 0x00; + } + + r = sc_apdu_get_octets(ctx, data->apdu, &pinapdu, &pinapdu_len, + SC_PROTO_T1); + if (r < 0) + goto err; + + switch (data->cmd) { + case SC_PIN_CMD_VERIFY: + *pc_to_rdr_secure_len = sizeof *secure + 1 + + sizeof *verify + pinapdu_len; + break; + + case SC_PIN_CMD_CHANGE: + *pc_to_rdr_secure_len = sizeof *secure + 1 + + sizeof *modify + 3 + pinapdu_len; + break; + + default: + r = SC_ERROR_INVALID_ARGUMENTS; + goto err; + } + + *pc_to_rdr_secure = malloc(*pc_to_rdr_secure_len); + if (!*pc_to_rdr_secure) { + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + secure = (PC_to_RDR_Secure_t *) *pc_to_rdr_secure; + secure->bMessageType = bMessageType; + secure->dwLength = htole32((*pc_to_rdr_secure_len) - sizeof *secure); + secure->bSlot = bSlot; + secure->bSeq = bSeq; + secure->bBWI = bBWI; + secure->wLevelParameter = wLevelParameter; + + switch (data->cmd) { + case SC_PIN_CMD_VERIFY: + /* bPINOperation */ + *((*pc_to_rdr_secure) + sizeof *secure) = CCID_OPERATION_VERIFY; + verify = (abPINDataStucture_Verification_t *) + ((*pc_to_rdr_secure) + sizeof *secure + 1); + verify->bTimeOut = bTimeOut; + verify->bmFormatString = bmFormatString; + verify->bmPINBlockString = bmPINBlockString; + verify->bmPINLengthFormat = bmPINLengthFormat; + verify->wPINMaxExtraDigit = wPINMaxExtraDigit; + verify->bEntryValidationCondition = bEntryValidationCondition; + verify->bNumberMessage = bNumberMessage; + verify->wLangId = wLangId; + verify->bMsgIndex = bMsgIndex; + verify->bTeoPrologue1 = bTeoPrologue1; + verify->bTeoPrologue2 = bTeoPrologue2; + + memcpy((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *verify, + pinapdu, pinapdu_len); + break; + + case SC_PIN_CMD_CHANGE: + /* bPINOperation */ + *((*pc_to_rdr_secure) + sizeof *secure) = CCID_OPERATION_MODIFY; + modify = (abPINDataStucture_Modification_t *) + ((*pc_to_rdr_secure) + sizeof *secure + 1); + modify->bTimeOut = bTimeOut; + modify->bmFormatString = bmFormatString; + modify->bmPINBlockString = bmPINBlockString; + modify->bmPINLengthFormat = bmPINLengthFormat; + if (!(data->flags & SC_PIN_CMD_IMPLICIT_CHANGE) + && data->pin1.offset) { + modify->bInsertionOffsetOld = data->pin1.offset - 5; + } else { + modify->bInsertionOffsetOld = 0; + } + modify->bInsertionOffsetNew = data->pin2.offset ? data->pin2.offset - 5 : 0; + modify->wPINMaxExtraDigit = wPINMaxExtraDigit; + modify->bConfirmPIN = CCID_PIN_CONFIRM_NEW + | (data->flags & SC_PIN_CMD_IMPLICIT_CHANGE ? 0 : CCID_PIN_INSERT_OLD); + modify->bEntryValidationCondition = bEntryValidationCondition; + modify->bNumberMessage = bNumberMessage; + modify->wLangId = wLangId; + modify->bMsgIndex1 = bMsgIndex; + *((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *modify + 0) = + bTeoPrologue1; + *((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *modify + 1) = + bTeoPrologue1; + *((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *modify + 2) = + bTeoPrologue1; + + memcpy((*pc_to_rdr_secure) + sizeof *secure + 1 + sizeof *modify + 3, + pinapdu, pinapdu_len); + break; + + default: + r = SC_ERROR_INVALID_ARGUMENTS; + goto err; + } + + r = SC_SUCCESS; + +err: + free(pinapdu); + if (r < 0 && pc_to_rdr_secure && *pc_to_rdr_secure) { + free(*pc_to_rdr_secure); + *pc_to_rdr_secure = NULL; + } + + return r; +} + +#define CCID_BSTATUS_OK_ACTIVE 0x00 /** No error. An ICC is present and active */ +static int escape_buf_to_verify_result(sc_context_t *ctx, + const unsigned char *rdr_to_pc_datablock, + size_t rdr_to_pc_datablock_len, + sc_apdu_t *apdu) +{ + RDR_to_PC_DataBlock_t *datablock = + (RDR_to_PC_DataBlock_t *) rdr_to_pc_datablock; + + if (!rdr_to_pc_datablock + || rdr_to_pc_datablock_len < sizeof *datablock + || datablock->bMessageType != 0x80) + return SC_ERROR_UNKNOWN_DATA_RECEIVED; + + if (datablock->bStatus != CCID_BSTATUS_OK_ACTIVE) + return SC_ERROR_TRANSMIT_FAILED; + + return sc_apdu_set_resp(ctx, apdu, + rdr_to_pc_datablock + sizeof *datablock, + htole32(datablock->dwLength)); +} + +static int escape_perform_verify(struct sc_reader *reader, + struct sc_pin_cmd_data *data) +{ + u8 rbuf[0xff]; + sc_apdu_t apdu; + int r; + + memset(&apdu, 0, sizeof(apdu)); + apdu.cse = SC_APDU_CASE_4_SHORT; + apdu.cla = escape_cla; + apdu.ins = escape_ins; + apdu.p1 = escape_p1_PIN; + apdu.p2 = escape_p2_PC_to_RDR_Secure; + 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 = escape_pin_cmd_to_buf(reader->ctx, data, + (unsigned char **) &apdu.data, &apdu.datalen); + if (r < 0) { + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Error encoding PC_to_RDR_Secure"); + goto err; + } + apdu.lc = apdu.datalen; + + r = SC_SUCCESS; + + r = reader->ops->transmit(reader, &apdu); + if (r < 0) { + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Error performing PC_to_RDR_Secure"); + goto err; + } + + if (apdu.sw1 != 0x90 && apdu.sw2 != 0x00) { + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Error decoding PC_to_RDR_Secure"); + r = SC_ERROR_NOT_SUPPORTED; + goto err; + } + + r = escape_buf_to_verify_result(reader->ctx, apdu.resp, apdu.resplen, + data->apdu); + +err: + free((unsigned char *) apdu.data); + + return r; +} + +static int escape_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 = escape_cla; + apdu.ins = escape_ins; + apdu.p1 = escape_p1_PIN; + apdu.p2 = escape_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 = escape_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 = escape_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_STRUCT, SC_ASN1_CTX|0x01|SC_ASN1_CONS, SC_ASN1_ALLOC, NULL, NULL }, + { "capabilityEID", + SC_ASN1_STRUCT, SC_ASN1_CTX|0x02|SC_ASN1_CONS, SC_ASN1_ALLOC, NULL, NULL }, + { "capabilityESign", + SC_ASN1_STRUCT, SC_ASN1_CTX|0x03|SC_ASN1_CONS, SC_ASN1_ALLOC, NULL, NULL }, + { "capabilityDestroy", + SC_ASN1_STRUCT, SC_ASN1_CTX|0x04|SC_ASN1_CONS, 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 escape_buf_to_pace_capabilities(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + unsigned long *sc_reader_t_capabilities) +{ + int pace = 0, eid = 0, esign = 0, destroy = 0; + struct sc_asn1_entry PACECapabilities_data[ + sizeof g_PACECapabilities_data/ + sizeof *g_PACECapabilities_data]; + struct sc_asn1_entry PACECapabilities[ + sizeof g_PACECapabilities/ + sizeof *g_PACECapabilities]; + struct sc_asn1_entry capabilityPACE[ + sizeof g_boolean/ + sizeof *g_boolean]; + struct sc_asn1_entry capabilityEID[ + sizeof g_boolean/ + sizeof *g_boolean]; + struct sc_asn1_entry capabilityESign[ + sizeof g_boolean/ + sizeof *g_boolean]; + struct sc_asn1_entry capabilityDestroy[ + sizeof g_boolean/ + sizeof *g_boolean]; + + sc_copy_asn1_entry(g_PACECapabilities, + PACECapabilities); + sc_format_asn1_entry(PACECapabilities, + PACECapabilities_data, 0, 1); + + sc_copy_asn1_entry(g_PACECapabilities_data, + PACECapabilities_data); + sc_format_asn1_entry(PACECapabilities_data+0, + &capabilityPACE, NULL, 1); + sc_format_asn1_entry(PACECapabilities_data+1, + &capabilityEID, NULL, 1); + sc_format_asn1_entry(PACECapabilities_data+2, + &capabilityESign, NULL, 1); + sc_format_asn1_entry(PACECapabilities_data+3, + &capabilityDestroy, NULL, 1); + + sc_copy_asn1_entry(g_boolean, + capabilityPACE); + sc_format_asn1_entry(capabilityPACE+0, + &pace, NULL, 0); + + sc_copy_asn1_entry(g_boolean, + capabilityEID); + sc_format_asn1_entry(capabilityEID+0, + &eid, NULL, 0); + + sc_copy_asn1_entry(g_boolean, + capabilityESign); + sc_format_asn1_entry(capabilityESign+0, + &esign, NULL, 0); + + sc_copy_asn1_entry(g_boolean, + capabilityDestroy); + sc_format_asn1_entry(capabilityDestroy+0, + &destroy, 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 escape + * 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 (pace) + *sc_reader_t_capabilities |= SC_READER_CAP_PACE_GENERIC; + if (eid) + *sc_reader_t_capabilities |= SC_READER_CAP_PACE_EID; + if (esign) + *sc_reader_t_capabilities |= SC_READER_CAP_PACE_ESIGN; + if (destroy) + *sc_reader_t_capabilities |= SC_READER_CAP_PACE_DESTROY_CHANNEL; + + return SC_SUCCESS; +} + +int escape_pace_capabilities_to_buf(sc_context_t *ctx, + const unsigned long sc_reader_t_capabilities, + unsigned char **asn1, size_t *asn1_len) +{ + int yes = 1, no = 0; + struct sc_asn1_entry PACECapabilities_data[ + sizeof g_PACECapabilities_data/ + sizeof *g_PACECapabilities_data]; + struct sc_asn1_entry PACECapabilities[ + sizeof g_PACECapabilities/ + sizeof *g_PACECapabilities]; + struct sc_asn1_entry capabilityPACE[ + sizeof g_boolean/ + sizeof *g_boolean]; + struct sc_asn1_entry capabilityEID[ + sizeof g_boolean/ + sizeof *g_boolean]; + struct sc_asn1_entry capabilityESign[ + sizeof g_boolean/ + sizeof *g_boolean]; + struct sc_asn1_entry capabilityDestroy[ + sizeof g_boolean/ + sizeof *g_boolean]; + + sc_copy_asn1_entry(g_EstablishPACEChannel, + PACECapabilities); + sc_format_asn1_entry(PACECapabilities, + PACECapabilities_data, 0, 1); + + sc_copy_asn1_entry(g_PACECapabilities_data, + PACECapabilities_data); + sc_format_asn1_entry(PACECapabilities_data+0, + &capabilityPACE, NULL, 1); + sc_format_asn1_entry(PACECapabilities_data+1, + &capabilityEID, NULL, 1); + sc_format_asn1_entry(PACECapabilities_data+2, + &capabilityESign, NULL, 1); + sc_format_asn1_entry(PACECapabilities_data+3, + &capabilityDestroy, NULL, 1); + + sc_copy_asn1_entry(g_boolean, + capabilityPACE); + sc_format_asn1_entry(capabilityPACE, + sc_reader_t_capabilities & SC_READER_CAP_PACE_GENERIC + ? &yes : &no, NULL, 1); + + sc_copy_asn1_entry(g_boolean, + capabilityEID); + sc_format_asn1_entry(capabilityEID, + sc_reader_t_capabilities & SC_READER_CAP_PACE_EID + ? &yes : &no, NULL, 1); + + sc_copy_asn1_entry(g_boolean, + capabilityESign); + sc_format_asn1_entry(capabilityESign, + sc_reader_t_capabilities & SC_READER_CAP_PACE_ESIGN + ? &yes : &no, NULL, 1); + + sc_copy_asn1_entry(g_boolean, + capabilityDestroy); + sc_format_asn1_entry(capabilityDestroy, + sc_reader_t_capabilities & SC_READER_CAP_PACE_DESTROY_CHANNEL + ? &yes : &no, NULL, 1); + + return sc_asn1_encode(ctx, PACECapabilities, asn1, asn1_len); +} + +void sc_detect_escape_cmds(sc_reader_t *reader) +{ + int error = 0; + u8 rbuf[0xff+1]; + sc_apdu_t apdu; + unsigned long capabilities; + + if (reader && reader->ops && reader->ops->transmit) { + memset(&apdu, 0, sizeof(apdu)); + apdu.cse = SC_APDU_CASE_2_SHORT; + apdu.cla = escape_cla; + apdu.ins = escape_ins; + apdu.p1 = escape_p1_PIN; + apdu.p2 = escape_p2_GetReaderPACECapabilities; + apdu.resp = rbuf; + apdu.resplen = sizeof rbuf; + apdu.le = sizeof rbuf; + + if (reader->ops->transmit(reader, &apdu) == SC_SUCCESS + && apdu.sw1 == 0x90 && apdu.sw2 == 0x00 + && escape_buf_to_pace_capabilities(reader->ctx, + apdu.resp, apdu.resplen, &capabilities) == SC_SUCCESS) { + if (capabilities & SC_READER_CAP_PIN_PAD + && !(reader->capabilities & SC_READER_CAP_PIN_PAD)) { + ((struct sc_reader_operations *) reader->ops)->perform_verify = + escape_perform_verify; + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Added escape 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 = + escape_perform_pace; + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "Added escape command wrappers for PACE to '%s'", reader->name); + } + + reader->capabilities |= capabilities; + } else { + error++; + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "%s does not support escape commands", reader->name); + } + + apdu.p1 = escape_p1_IFD; + apdu.p2 = escape_p2_vendor; + apdu.resplen = sizeof rbuf; + if (reader->ops->transmit(reader, &apdu) == SC_SUCCESS + && apdu.sw1 == 0x90 && apdu.sw2 == 0x00) { + if (!reader->vendor) { + /* add NUL termination, just in case... */ + rbuf[apdu.resplen] = '\0'; + reader->vendor = strdup((const char *) rbuf); + } + } else { + error++; + } + + apdu.p1 = escape_p1_IFD; + apdu.p2 = escape_p2_version_firmware; + apdu.resplen = sizeof rbuf; + if (reader->ops->transmit(reader, &apdu) == SC_SUCCESS + && apdu.sw1 == 0x90 && apdu.sw2 == 0x00) { + if (!reader->version_major && !reader->version_minor) { + unsigned int major = 0, minor = 0; + /* add NUL termination, just in case... */ + rbuf[apdu.resplen] = '\0'; + sscanf((const char *) rbuf, "%u.%u", &major, &minor); + reader->version_major = major>0xff ? 0xff : major; + reader->version_minor = minor>0xff ? 0xff : minor; + } + } else { + error++; + } + } + + if (error) { + sc_debug(reader->ctx, SC_LOG_DEBUG_NORMAL, + "%d escape command%s failed, need to reset the card", + error, error == 1 ? "" : "s"); + if (reader && reader->ops && reader->ops->transmit) { + memset(&apdu, 0, sizeof(apdu)); + apdu.cse = SC_APDU_CASE_3_SHORT; + apdu.cla = 0x00; + apdu.ins = 0xA4; + apdu.p1 = 8; + apdu.p2 = 0x0C; + apdu.data = rbuf; + rbuf[0] = 0x3F; + rbuf[1] = 0x00; + apdu.datalen = 2; + apdu.lc = 2; + apdu.resp = NULL; + apdu.resplen = 0; + apdu.le = 0; + reader->ops->transmit(reader, &apdu); + } + } +} diff --git a/src/libopensc/reader-tr03119.h b/src/libopensc/reader-tr03119.h new file mode 100644 index 00000000..089074fb --- /dev/null +++ b/src/libopensc/reader-tr03119.h @@ -0,0 +1,57 @@ +/* + * reader-tr03119.h: interface related to escape commands with pseudo APDUs + * + * Copyright (C) 2013-2015 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 _READER_TR03119_H +#define _READER_TR03119_H + +#include "libopensc/opensc.h" +#include "libopensc/pace.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void sc_detect_escape_cmds(sc_reader_t *reader); + +int escape_pace_input_to_buf(sc_context_t *ctx, + const struct establish_pace_channel_input *input, + unsigned char **asn1, size_t *asn1_len); +int escape_buf_to_pace_input(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + struct establish_pace_channel_input *input); +int escape_pace_output_to_buf(sc_context_t *ctx, + const struct establish_pace_channel_output *output, + unsigned char **asn1, size_t *asn1_len); +int escape_buf_to_pace_output(sc_context_t *ctx, + const unsigned char *asn1, size_t asn1_len, + struct establish_pace_channel_output *output); +int escape_pace_capabilities_to_buf(sc_context_t *ctx, + const unsigned long sc_reader_t_capabilities, + unsigned char **asn1, size_t *asn1_len); +int escape_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 + diff --git a/src/pkcs11/slot.c b/src/pkcs11/slot.c index ba5e4f4d..24891ef3 100644 --- a/src/pkcs11/slot.c +++ b/src/pkcs11/slot.c @@ -50,13 +50,20 @@ static struct sc_pkcs11_slot * reader_get_slot(sc_reader_t *reader) return NULL; } -static void init_slot_info(CK_SLOT_INFO_PTR pInfo) +static void init_slot_info(CK_SLOT_INFO_PTR pInfo, sc_reader_t *reader) { - strcpy_bp(pInfo->slotDescription, "Virtual hotplug slot", 64); - strcpy_bp(pInfo->manufacturerID, OPENSC_VS_FF_COMPANY_NAME, 32); + if (reader) { + strcpy_bp(pInfo->slotDescription, reader->name, 64); + strcpy_bp(pInfo->manufacturerID, reader->vendor, 32); + pInfo->hardwareVersion.major = reader->version_major; + pInfo->hardwareVersion.minor = reader->version_minor; + } else { + strcpy_bp(pInfo->slotDescription, "Virtual hotplug slot", 64); + strcpy_bp(pInfo->manufacturerID, OPENSC_VS_FF_COMPANY_NAME, 32); + pInfo->hardwareVersion.major = OPENSC_VERSION_MAJOR; + pInfo->hardwareVersion.minor = OPENSC_VERSION_MINOR; + } pInfo->flags = CKF_REMOVABLE_DEVICE | CKF_HW_SLOT; - pInfo->hardwareVersion.major = OPENSC_VERSION_MAJOR; - pInfo->hardwareVersion.minor = OPENSC_VERSION_MINOR; pInfo->firmwareVersion.major = 0; pInfo->firmwareVersion.minor = 0; } @@ -105,7 +112,7 @@ CK_RV create_slot(sc_reader_t *reader) slot->login_user = -1; slot->id = (CK_SLOT_ID) list_locate(&virtual_slots, slot); - init_slot_info(&slot->slot_info); + init_slot_info(&slot->slot_info, reader); sc_log(context, "Initializing slot with id 0x%lx", slot->id); if (reader != NULL) { @@ -127,7 +134,7 @@ void empty_slot(struct sc_pkcs11_slot *slot) * already been reset by `slot_token_removed()`, lists have been * emptied. We replace the reader with a virtual hotplug slot. */ slot->reader = NULL; - init_slot_info(&slot->slot_info); + init_slot_info(&slot->slot_info, NULL); } else { list_destroy(&slot->objects); list_destroy(&slot->logins); @@ -273,6 +280,18 @@ again: return sc_to_cryptoki_error(rc, NULL); } + /* escape commands are only guaranteed to be working with a card + * inserted. That's why by now, after sc_connect_card() the reader's + * metadata may have changed. We re-initialize the metadata for every + * slot of this reader here. */ + if (reader->flags & SC_READER_ENABLE_ESCAPE) { + for (i = 0; ireader == reader) + init_slot_info(&slot->slot_info, reader); + } + } + sc_log(context, "%s: Connected SC card %p", reader->name, p11card->card); }