From cfd5aaba7d4f87372f72d2b0849d4eb435671b9a Mon Sep 17 00:00:00 2001 From: Viktor Tarasov Date: Mon, 4 Jun 2012 09:26:56 +0200 Subject: [PATCH] SM: initial implementation of secure messaging framework --- configure.ac | 38 +++ etc/Makefile.am | 4 +- src/libopensc/Makefile.am | 2 +- src/libopensc/apdu.c | 686 +++++++++++++++++++++++--------------- src/libopensc/card.c | 274 ++++++++++++++- src/libopensc/opensc.h | 7 + src/libopensc/sm.h | 344 +++++++++++++++++++ src/libopensc/types.h | 2 + src/sm/Makefile.am | 17 + 9 files changed, 1095 insertions(+), 279 deletions(-) create mode 100644 src/libopensc/sm.h create mode 100644 src/sm/Makefile.am diff --git a/configure.ac b/configure.ac index 48bb768e..3381485e 100644 --- a/configure.ac +++ b/configure.ac @@ -74,6 +74,16 @@ case "${host}" in ;; esac +case "${host}" in + *-mingw*|*-winnt*|*-cygwin*) + DEBUG_FILE="%TEMP%\opensc-debug.log" + ;; + *) + DEBUG_FILE="/tmp/opensc-debug.log" + ;; +esac +AC_DEFINE_UNQUOTED([DEBUG_FILE], ["${DEBUG_FILE}"], [Debug file]) + AC_ARG_ENABLE( [strict], [AS_HELP_STRING([--enable-strict],[enable strict compile mode @<:@disabled@:>@])], @@ -137,6 +147,13 @@ AC_ARG_ENABLE( [enable_minidriver="no"] ) +AC_ARG_ENABLE( + [sm], + [AS_HELP_STRING([--enable-sm],[enable secure messaging support and modules @<:@disabled@:>@])], + , + [enable_sm="no"] +) + AC_ARG_ENABLE( [man], [AS_HELP_STRING([--disable-man],[disable installation of manuals @<:@enabled for none Windows@:>@])], @@ -298,6 +315,20 @@ if test "${enable_minidriver}" = "yes"; then AC_DEFINE([ENABLE_MINIDRIVER], [1], [Enable minidriver support]) fi +if test "${enable_sm}" = "yes"; then + AC_DEFINE([ENABLE_SM], [1], [Enable secure messaging support]) + + case "${host}" in + *-mingw*|*-winnt*|*-cygwin*) + DEFAULT_SM_MODULE="smm-local.dll" + ;; + *) + DEFAULT_SM_MODULE="libsmm-local.so.3" + ;; + esac + AC_DEFINE_UNQUOTED([DEFAULT_SM_MODULE], ["${DEFAULT_SM_MODULE}"], [Default SM module]) +fi + AC_ARG_VAR([ZLIB_CFLAGS], [C compiler flags for zlib]) AC_ARG_VAR([ZLIB_LIBS], [linker flags for zlib]) if test -z "${ZLIB_LIBS}"; then @@ -547,6 +578,8 @@ AC_SUBST([OPTIONAL_OPENCT_CFLAGS]) AC_SUBST([OPTIONAL_OPENCT_LIBS]) AC_SUBST([OPTIONAL_PCSC_CFLAGS]) AC_SUBST([LIBRARY_BITNESS]) +AC_SUBST([DEFAULT_SM_MODULE]) +AC_SUBST([DEBUG_FILE]) AM_CONDITIONAL([ENABLE_MAN], [test "${enable_man}" = "yes"]) AM_CONDITIONAL([ENABLE_ZLIB], [test "${enable_zlib}" = "yes"]) @@ -557,6 +590,7 @@ AM_CONDITIONAL([ENABLE_DOC], [test "${enable_doc}" = "yes"]) AM_CONDITIONAL([WIN32], [test "${WIN32}" = "yes"]) AM_CONDITIONAL([CYGWIN], [test "${CYGWIN}" = "yes"]) AM_CONDITIONAL([ENABLE_MINIDRIVER], [test "${enable_minidriver}" = "yes"]) +AM_CONDITIONAL([ENABLE_SM], [test "${enable_sm}" = "yes"]) if test "${enable_pedantic}" = "yes"; then enable_strict="yes"; @@ -585,6 +619,7 @@ AC_CONFIG_FILES([ src/tests/Makefile src/tests/regression/Makefile src/tools/Makefile + src/sm/Makefile src/minidriver/Makefile src/minidriver/opensc-minidriver.inf win32/Makefile @@ -618,6 +653,9 @@ PC/SC support: ${enable_pcsc} OpenCT support: ${enable_openct} CT-API support: ${enable_ctapi} minidriver support: ${enable_minidriver} +SM support: ${enable_sm} +SM default module: ${DEFAULT_SM_MODULE} +Debug file: ${DEBUG_FILE} PC/SC default provider: ${DEFAULT_PCSC_PROVIDER} diff --git a/etc/Makefile.am b/etc/Makefile.am index 478a7bb7..9c388acd 100644 --- a/etc/Makefile.am +++ b/etc/Makefile.am @@ -1,5 +1,5 @@ MAINTAINERCLEANFILES = $(srcdir)/Makefile.in -DISTCLEANFILES = opensc.conf +DISTCLEANFILES = opensc.conf SUFFIXES = .in @@ -16,6 +16,8 @@ opensc.conf: opensc.conf.in force sed \ -e 's|@pkgdatadir[@]|$(pkgdatadir)|g' \ -e 's|@DEFAULT_PCSC_PROVIDER[@]|$(DEFAULT_PCSC_PROVIDER)|g' \ + -e 's|@DEFAULT_SM_MODULE[@]|$(DEFAULT_SM_MODULE)|g' \ + -e 's|@DEBUG_FILE[@]|$(DEBUG_FILE)|g' \ < $< > $@ install-exec-hook: opensc.conf diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index 7bfc910e..db1de86e 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -10,7 +10,7 @@ noinst_HEADERS = cards.h ctbcs.h internal.h esteid.h muscle.h muscle-filesystem. opensc.h pkcs15.h \ cardctl.h asn1.h log.h \ errors.h types.h compression.h itacns.h iso7816.h \ - authentic.h iasecc.h iasecc-sdo.h + authentic.h iasecc.h iasecc-sdo.h sm.h AM_CPPFLAGS = -DOPENSC_CONF_PATH=\"$(sysconfdir)/opensc.conf\" AM_CFLAGS = $(OPTIONAL_OPENSSL_CFLAGS) $(OPTIONAL_OPENCT_CFLAGS) \ diff --git a/src/libopensc/apdu.c b/src/libopensc/apdu.c index 5621018d..1ea8e5d9 100644 --- a/src/libopensc/apdu.c +++ b/src/libopensc/apdu.c @@ -26,6 +26,7 @@ #include #include "internal.h" +#include "asn1.h" /*********************************************************************/ /* low level APDU handling functions */ @@ -73,7 +74,7 @@ static size_t sc_apdu_get_length(const sc_apdu_t *apdu, unsigned int proto) * @param ctx sc_context_t object (used for logging) * @param apdu APDU to be encoded as an octet string * @param proto protocol version to be used - * @param out output buffer of size outlen. + * @param out output buffer of size outlen. * @param outlen size of hte output buffer * @return SC_SUCCESS on success and an error code otherwise */ @@ -121,14 +122,13 @@ static int sc_apdu2bytes(sc_context_t *ctx, const sc_apdu_t *apdu, /* in case of T0 the command is transmitted in chunks * < 255 using the ENVELOPE command ... */ if (apdu->lc > 255) { - /* ... so if Lc is greater than 255 bytes + /* ... so if Lc is greater than 255 bytes * an error has occurred on a higher level */ - sc_debug(ctx, SC_LOG_DEBUG_NORMAL, - "invalid Lc length for CASE 3 " - "extended APDU (need ENVELOPE)"); + sc_log(ctx, "invalid Lc length for CASE 3 extended APDU (need ENVELOPE)"); return SC_ERROR_INVALID_ARGUMENTS; } - } else { + } + else { /* in case of T1 always use 3 bytes for length */ *p++ = (u8)0x00; *p++ = (u8)(apdu->lc >> 8); @@ -151,7 +151,8 @@ static int sc_apdu2bytes(sc_context_t *ctx, const sc_apdu_t *apdu, * transferred using ENVELOPE and GET RESPONSE */ *p++ = (u8)apdu->lc; memcpy(p, apdu->data, apdu->lc); - } else { + } + else { *p++ = (u8)0x00; *p++ = (u8)(apdu->lc >> 8); *p++ = (u8)apdu->lc; @@ -215,7 +216,7 @@ int sc_apdu_set_resp(sc_context_t *ctx, sc_apdu_t *apdu, const u8 *buf, { if (len < 2) { /* no SW1 SW2 ... something went terrible wrong */ - sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "invalid response: SW1 SW2 missing"); + sc_log(ctx, "invalid response: SW1 SW2 missing"); return SC_ERROR_INTERNAL; } /* set the SW1 and SW2 status bytes (the last two bytes of @@ -233,6 +234,96 @@ int sc_apdu_set_resp(sc_context_t *ctx, sc_apdu_t *apdu, const u8 *buf, return SC_SUCCESS; } +#ifdef ENABLE_SM +static const struct sc_asn1_entry c_asn1_sm_response[4] = { + { "encryptedData", SC_ASN1_OCTET_STRING, SC_ASN1_CTX | 7, SC_ASN1_OPTIONAL, NULL, NULL }, + { "statusWord", SC_ASN1_OCTET_STRING, SC_ASN1_CTX | 0x19, 0, NULL, NULL }, + { "mac", SC_ASN1_OCTET_STRING, SC_ASN1_CTX | 0x0E, 0, NULL, NULL }, + { NULL, 0, 0, 0, NULL, NULL } +}; +static int +sc_sm_parse_answer(struct sc_context *ctx, unsigned char *resp_data, size_t resp_len, + struct sm_card_response *out) +{ + struct sc_asn1_entry asn1_sm_response[4]; + unsigned char data[SC_MAX_APDU_BUFFER_SIZE]; + size_t data_len = sizeof(data); + unsigned char status[2] = {0, 0}; + size_t status_len = sizeof(status); + unsigned char mac[8]; + size_t mac_len = sizeof(mac); + int r; + + if (!resp_data || !resp_len || !out) + return SC_ERROR_INVALID_ARGUMENTS; + + sc_copy_asn1_entry(c_asn1_sm_response, asn1_sm_response); + + sc_format_asn1_entry(asn1_sm_response + 0, data, &data_len, 0); + sc_format_asn1_entry(asn1_sm_response + 1, status, &status_len, 0); + sc_format_asn1_entry(asn1_sm_response + 2, mac, &mac_len, 0); + + r = sc_asn1_decode(ctx, asn1_sm_response, resp_data, resp_len, NULL, NULL); + if (r) + return r; + + if (asn1_sm_response[1].flags & SC_ASN1_PRESENT) { + out->sw1 = status[0]; + out->sw2 = status[1]; + } + + if (asn1_sm_response[2].flags & SC_ASN1_PRESENT) { + memcpy(out->mac, mac, mac_len); + out->mac_len = mac_len; + } + /* TODO: to be continued ... */ + + return SC_SUCCESS; +} + +/** parse answer of SM protected APDU returned by APDU or by 'GET RESPONSE' + * @param card 'sc_card' smartcard object + * @param resp_data 'raw data returned by SM protected APDU + * @param resp_len 'length of raw data returned by SM protected APDU + * @param ref_rv 'status word returned by APDU or 'GET RESPONSE' (can be different from status word encoded into SM response date) + * @param apdu 'sc_apdu' object to update + * @return SC_SUCCESS on success and an error code otherwise + */ +static int +sc_sm_update_apdu_response(struct sc_card *card, unsigned char *resp_data, size_t resp_len, int ref_rv, + struct sc_apdu *apdu) +{ + struct sm_card_response sm_resp; + int r; + + if (!apdu) + return SC_ERROR_INVALID_ARGUMENTS; + else if (!resp_data || !resp_len) + return SC_SUCCESS; + + memset(&sm_resp, 0, sizeof(sm_resp)); + r = sc_sm_parse_answer(card->ctx, resp_data, resp_len, &sm_resp); + if (r) + return r; + else if (!sm_resp.sw1 && !sm_resp.sw2) + return SC_ERROR_INVALID_DATA; + else if (ref_rv != sc_check_sw(card, sm_resp.sw1, sm_resp.sw2)) + return SC_ERROR_INVALID_DATA; + + if (sm_resp.mac_len) { + if (sm_resp.mac_len > sizeof(apdu->mac)) + return SC_ERROR_INVALID_DATA; + memcpy(apdu->mac, sm_resp.mac, sm_resp.mac_len); + apdu->mac_len = sm_resp.mac_len; + } + + apdu->sw1 = sm_resp.sw1; + apdu->sw2 = sm_resp.sw2; + + return SC_SUCCESS; +} +#endif + /*********************************************************************/ /* higher level APDU transfer handling functions */ /*********************************************************************/ @@ -247,7 +338,7 @@ int sc_apdu_set_resp(sc_context_t *ctx, sc_apdu_t *apdu, const u8 *buf, * | +------------------------------------> | sc_check_apdu | * | +--------------------+ * | send single APDU +--------------------+ - * +---------------------------------------> | do_single_transmit | + * +---------------------------------------> | sc_transmit | * ^ +--------------------+ * | | * | re-transmit if wrong length | @@ -267,20 +358,22 @@ static int sc_check_apdu(sc_card_t *card, const sc_apdu_t *apdu) { if ((apdu->cse & ~SC_APDU_SHORT_MASK) == 0) { /* length check for short APDU */ - if (apdu->le > 256 || (apdu->lc > 255 && - (apdu->flags & SC_APDU_FLAGS_CHAINING) == 0)) + if (apdu->le > 256 || (apdu->lc > 255 && (apdu->flags & SC_APDU_FLAGS_CHAINING) == 0)) goto error; - } else if ((apdu->cse & SC_APDU_EXT) != 0) { + } + else if ((apdu->cse & SC_APDU_EXT) != 0) { /* check if the card supports extended APDUs */ if ((card->caps & SC_CARD_CAP_APDU_EXT) == 0) { - sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "card doesn't support extended APDUs"); + sc_log(card->ctx, "card doesn't support extended APDUs"); goto error; } /* length check for extended APDU */ if (apdu->le > 65536 || apdu->lc > 65535) goto error; - } else + } + else { goto error; + } switch (apdu->cse & SC_APDU_SHORT_MASK) { case SC_APDU_CASE_1: @@ -296,7 +389,7 @@ static int sc_check_apdu(sc_card_t *card, const sc_apdu_t *apdu) if (apdu->resplen == 0 || apdu->resp == NULL) goto error; /* return buffer to small */ - if ((apdu->le == 0 && apdu->resplen < SC_MAX_APDU_BUFFER_SIZE-2) + if ((apdu->le == 0 && apdu->resplen < SC_MAX_APDU_BUFFER_SIZE-2) || (apdu->resplen < apdu->le)) goto error; break; @@ -327,12 +420,12 @@ static int sc_check_apdu(sc_card_t *card, const sc_apdu_t *apdu) goto error; break; default: - sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "Invalid APDU case %d\n", apdu->cse); + sc_log(card->ctx, "Invalid APDU case %d", apdu->cse); return SC_ERROR_INVALID_ARGUMENTS; } return SC_SUCCESS; error: - sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "Invalid Case %d %s APDU:\n" + sc_log(card->ctx, "Invalid Case %d %s APDU:\n" "cse=%02x cla=%02x ins=%02x p1=%02x p2=%02x lc=%lu le=%lu\n" "resp=%p resplen=%lu data=%p datalen=%lu", apdu->cse & SC_APDU_SHORT_MASK, @@ -345,7 +438,7 @@ error: } /** Tries to determine the APDU type (short or extended) of the supplied - * APDU if one of the SC_APDU_CASE_? types is used. + * APDU if one of the SC_APDU_CASE_? types is used. * @param apdu APDU object */ static void sc_detect_apdu_cse(const sc_card_t *card, sc_apdu_t *apdu) @@ -365,156 +458,220 @@ static void sc_detect_apdu_cse(const sc_card_t *card, sc_apdu_t *apdu) } -/** Sends a single APDU to the card reader and calls - * GET RESPONSE to get the return data if necessary. +#ifdef ENABLE_SM +static int +sc_single_sm_transmit(struct sc_card *card, struct sc_apdu *apdu) +{ + struct sc_context *ctx = card->ctx; + struct sc_apdu *sm_apdu = NULL; + int rv; + + LOG_FUNC_CALLED(ctx); + sc_log(ctx, "SM_MODE:%X", card->sm_ctx.sm_mode); + if (!card->sm_ctx.ops.get_sm_apdu || !card->sm_ctx.ops.free_sm_apdu) + LOG_FUNC_RETURN(ctx, SC_ERROR_NOT_SUPPORTED); + + /* get SM encoded APDU */ + rv = card->sm_ctx.ops.get_sm_apdu(card, apdu, &sm_apdu); + LOG_TEST_RET(ctx, rv, "get SM APDU error"); + + if (!sm_apdu) + LOG_TEST_RET(ctx, SC_ERROR_INTERNAL, "cannot get SM APDU"); + + /* check if SM APDU is still valid */ + rv = sc_check_apdu(card, sm_apdu); + if (rv < 0) { + card->sm_ctx.ops.free_sm_apdu(card, apdu, &sm_apdu); + LOG_TEST_RET(ctx, rv, "cannot validate SM encoded APDU"); + } + + /* send APDU to the reader driver */ + rv = card->reader->ops->transmit(card->reader, sm_apdu); + LOG_TEST_RET(ctx, rv, "unable to transmit APDU"); + + /* decode SM answer and free temporary SM related data */ + rv = card->sm_ctx.ops.free_sm_apdu(card, apdu, &sm_apdu); + + LOG_FUNC_RETURN(ctx, rv); +} +#endif + + +static int +sc_single_transmit(struct sc_card *card, struct sc_apdu *apdu) +{ + struct sc_context *ctx = card->ctx; + int rv; + + LOG_FUNC_CALLED(ctx); + if (card->reader->ops->transmit == NULL) + LOG_TEST_RET(card->ctx, SC_ERROR_NOT_SUPPORTED, "cannot transmit APDU"); + +#ifdef ENABLE_SM + if (card->sm_ctx.sm_mode == SM_MODE_TRANSMIT) + return sc_single_sm_transmit(card, apdu); +#endif + + /* send APDU to the reader driver */ + rv = card->reader->ops->transmit(card->reader, apdu); + LOG_TEST_RET(ctx, rv, "unable to transmit APDU"); + + LOG_FUNC_RETURN(ctx, rv); +} + +static int +sc_set_le_and_transmit(struct sc_card *card, struct sc_apdu *apdu, size_t olen) +{ + struct sc_context *ctx = card->ctx; + size_t nlen = apdu->sw2 ? (size_t)apdu->sw2 : 256; + int rv; + + LOG_FUNC_CALLED(ctx); + /* we cannot re-transmit the APDU with the demanded Le value + * as the buffer is too small => error */ + if (olen < nlen) + LOG_TEST_RET(ctx, SC_ERROR_WRONG_LENGTH, "wrong length: required length exceeds resplen"); + + /* don't try again if it doesn't work this time */ + apdu->flags |= SC_APDU_FLAGS_NO_GET_RESP; + /* set the new expected length */ + apdu->resplen = olen; + apdu->le = nlen; + /* Belpic V1 applets have a problem: if the card sends a 6C XX (only XX bytes available), + * and we resend the command too soon (i.e. the reader is too fast), the card doesn't respond. + * So we build in a delay. */ + if (card->type == SC_CARD_TYPE_BELPIC_EID) + msleep(40); + + /* re-transmit the APDU with new Le length */ + rv = sc_single_transmit(card, apdu); + LOG_TEST_RET(ctx, rv, "cannot re-transmit APDU"); + + LOG_FUNC_RETURN(ctx, rv); +} + + +static int +sc_get_response(struct sc_card *card, struct sc_apdu *apdu, size_t olen) +{ + struct sc_context *ctx = card->ctx; + size_t le, minlen, buflen; + unsigned char *buf; + int rv; + + LOG_FUNC_CALLED(ctx); + if (apdu->le == 0) { + /* no data is requested => change return value to 0x9000 and ignore the remaining data */ + apdu->sw1 = 0x90; + apdu->sw2 = 0x00; + return SC_SUCCESS; + } + + /* this should _never_ happen */ + if (!card->ops->get_response) + LOG_TEST_RET(ctx, SC_ERROR_NOT_SUPPORTED, "no GET RESPONSE command"); + + /* call GET RESPONSE until we have read all data requested or until the card retuns 0x9000, + * whatever happens first. */ + + /* if there are already data in response append a new data to the end of the buffer */ + buf = apdu->resp + apdu->resplen; + + /* read as much data as fits in apdu->resp (i.e. min(apdu->resplen, amount of data available)). */ + buflen = olen - apdu->resplen; + + /* 0x6100 means at least 256 more bytes to read */ + le = apdu->sw2 != 0 ? (size_t)apdu->sw2 : 256; + /* we try to read at least as much as bytes as promised in the response bytes */ + minlen = le; + + do { + unsigned char resp[256]; + size_t resp_len = le; + + /* call GET RESPONSE to get more date from the card; + * note: GET RESPONSE returns the left amount of data (== SW2) */ + memset(resp, 0, sizeof(resp)); + rv = card->ops->get_response(card, &resp_len, resp); + if (rv < 0) { +#ifdef ENABLE_SM + if (resp_len) { + sc_log(ctx, "SM response data %s", sc_dump_hex(resp, resp_len)); + sc_sm_update_apdu_response(card, resp, resp_len, rv, apdu); + } +#endif + LOG_TEST_RET(ctx, rv, "GET RESPONSE error"); + } + + le = resp_len; + /* copy as much as will fit in requested buffer */ + if (buflen < le) + le = buflen; + + memcpy(buf, resp, le); + buf += le; + buflen -= le; + + /* we have all the data the caller requested even if the card has more data */ + if (buflen == 0) + break; + + minlen -= le; + if (rv != 0) + le = minlen = (size_t)rv; + else + /* if the card has returned 0x9000 but we still expect data ask for more + * until we have read enough bytes */ + le = minlen; + } while (rv != 0 || minlen != 0); + + /* we've read all data, let's return 0x9000 */ + apdu->resplen = buf - apdu->resp; + apdu->sw1 = 0x90; + apdu->sw2 = 0x00; + + LOG_FUNC_RETURN(ctx, SC_SUCCESS); +} + + +/** Sends a single APDU to the card reader and calls GET RESPONSE to get the return data if necessary. * @param card sc_card_t object for the smartcard * @param apdu APDU to be sent * @return SC_SUCCESS on success and an error value otherwise */ -static int do_single_transmit(sc_card_t *card, sc_apdu_t *apdu) +static int +sc_transmit(sc_card_t *card, sc_apdu_t *apdu) { - int r; + struct sc_context *ctx = card->ctx; size_t olen = apdu->resplen; - sc_context_t *ctx = card->ctx; + int r; - /* XXX: insert secure messaging here (?), i.e. something like - if (card->sm_ctx->use_sm != 0) { - r = card->ops->sm_transform(...); - if (r != SC_SUCCESS) - ... - r = sc_check_apdu(...); - if (r != SC_SUCCESS) - ... - } - */ + LOG_FUNC_CALLED(ctx); - /* send APDU to the reader driver */ - if (card->reader->ops->transmit == NULL) - return SC_ERROR_NOT_SUPPORTED; - r = card->reader->ops->transmit(card->reader, apdu); - if (r != 0) { - sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "unable to transmit APDU"); - return r; - } - /* ok, the APDU was successfully transmitted. Now we have two - * special cases: - * 1. the card returned 0x6Cxx: in this case we re-trasmit the APDU - * wit hLe set to SW2 (this is course only possible if the - * response buffer size is larger than the new Le = SW2) + r = sc_single_transmit(card, apdu); + LOG_TEST_RET(ctx, r, "transmit APDU failed"); + + /* ok, the APDU was successfully transmitted. Now we have two special cases: + * 1. the card returned 0x6Cxx: in this case APDU will be re-trasmitted with Le set to SW2 + * (possible only if response buffer size is larger than new Le = SW2) */ - if (apdu->sw1 == 0x6C && (apdu->flags & SC_APDU_FLAGS_NO_RETRY_WL) == 0) { - size_t nlen = apdu->sw2 != 0 ? (size_t)apdu->sw2 : 256; - if (olen >= nlen) { - /* don't try again if it doesn't work this time */ - apdu->flags |= SC_APDU_FLAGS_NO_GET_RESP; - /* set the new expected length */ - apdu->resplen = olen; - apdu->le = nlen; - /* Belpic V1 applets have a problem: if the card sends a 6C XX - * (only XX bytes available), and we resend the command too soon - * (i.e. the reader is too fast), the card doesn't respond. So - * we build in a delay. */ - if (card->type == SC_CARD_TYPE_BELPIC_EID) - msleep(40); - /* re-transmit the APDU with new Le length */ - r = card->reader->ops->transmit(card->reader, apdu); - if (r != SC_SUCCESS) { - sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "unable to transmit APDU"); - return r; - } - } else { - /* we cannot re-transmit the APDU with the demanded - * Le value as the buffer is too small => error */ - sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "wrong length: required length exceeds resplen"); - return SC_ERROR_WRONG_LENGTH; - } - } + if (apdu->sw1 == 0x6C && (apdu->flags & SC_APDU_FLAGS_NO_RETRY_WL) == 0) + r = sc_set_le_and_transmit(card, apdu, olen); + LOG_TEST_RET(ctx, r, "cannot re-transmit APDU "); /* 2. the card returned 0x61xx: more data can be read from the card * using the GET RESPONSE command (mostly used in the T0 protocol). * Unless the SC_APDU_FLAGS_NO_GET_RESP is set we try to read as * much data as possible using GET RESPONSE. */ - if (apdu->sw1 == 0x61 && (apdu->flags & SC_APDU_FLAGS_NO_GET_RESP) == 0) { - if (apdu->le == 0) { - /* no data is requested => change return value to - * 0x9000 and ignore the remaining data */ - /* FIXME: why not return 0x61xx ? It's not an - * error */ - apdu->sw1 = 0x90; - apdu->sw2 = 0x00; - - } else { - /* call GET RESPONSE until we have read all data - * requested or until the card retuns 0x9000, - * whatever happens first. - */ - size_t le, minlen, buflen; - u8 *buf; + if (apdu->sw1 == 0x61 && (apdu->flags & SC_APDU_FLAGS_NO_GET_RESP) == 0) + r = sc_get_response(card, apdu, olen); + LOG_TEST_RET(ctx, r, "cannot get all data with 'GET RESPONSE'"); - if (card->ops->get_response == NULL) { - /* this should _never_ happen */ - sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "no GET RESPONSE command\n"); - return SC_ERROR_NOT_SUPPORTED; - } - - /* if the command already returned some data - * append the new data to the end of the buffer - */ - buf = apdu->resp + apdu->resplen; - - /* read as much data as fits in apdu->resp (i.e. - * max(apdu->resplen, amount of data available)). - */ - buflen = olen - apdu->resplen; - - /* 0x6100 means at least 256 more bytes to read */ - le = apdu->sw2 != 0 ? (size_t)apdu->sw2 : 256; - /* we try to read at least as much as bytes as - * promised in the response bytes */ - minlen = le; - - do { - u8 tbuf[256]; - /* call GET RESPONSE to get more date from - * the card; note: GET RESPONSE returns the - * amount of data left (== SW2) */ - r = card->ops->get_response(card, &le, tbuf); - if (r < 0) - SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_VERBOSE, r); - - if (buflen < le) - /* copy as much as will fit in requested buffer */ - le = buflen; - - memcpy(buf, tbuf, le); - buf += le; - buflen -= le; - - /* we have all the data the caller requested - * even if the card has more data */ - if (buflen == 0) - break; - - minlen -= le; - if (r != 0) - le = minlen = (size_t)r; - else - /* if the card has returned 0x9000 but - * we still expect data ask for more - * until we have read enough bytes */ - le = minlen; - } while (r != 0 || minlen != 0); - /* we've read all data, let's return 0x9000 */ - apdu->resplen = buf - apdu->resp; - apdu->sw1 = 0x90; - apdu->sw2 = 0x00; - } - } - - return SC_SUCCESS; + LOG_FUNC_RETURN(ctx, SC_SUCCESS); } + int sc_transmit_apdu(sc_card_t *card, sc_apdu_t *apdu) { int r = SC_SUCCESS; @@ -522,7 +679,7 @@ int sc_transmit_apdu(sc_card_t *card, sc_apdu_t *apdu) if (card == NULL || apdu == NULL) return SC_ERROR_INVALID_ARGUMENTS; - SC_FUNC_CALLED(card->ctx, SC_LOG_DEBUG_VERBOSE); + LOG_FUNC_CALLED(card->ctx); /* determine the APDU type if necessary, i.e. to use * short or extended APDUs */ @@ -534,9 +691,9 @@ int sc_transmit_apdu(sc_card_t *card, sc_apdu_t *apdu) r = sc_lock(card); /* acquire card lock*/ if (r != SC_SUCCESS) { - sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "unable to acquire lock"); + sc_log(card->ctx, "unable to acquire lock"); return r; - } + } if ((apdu->flags & SC_APDU_FLAGS_CHAINING) != 0) { /* divide et impera: transmit APDU in chunks with Lc <= max_send_size @@ -576,15 +733,15 @@ int sc_transmit_apdu(sc_card_t *card, sc_apdu_t *apdu) r = sc_check_apdu(card, &tapdu); if (r != SC_SUCCESS) { - sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "inconsistent APDU while chaining"); + sc_log(card->ctx, "inconsistent APDU while chaining"); break; } - r = do_single_transmit(card, &tapdu); + r = sc_transmit(card, &tapdu); if (r != SC_SUCCESS) break; if (last != 0) { - /* in case of the last APDU set the SW1 + /* in case of the last APDU set the SW1 * and SW2 bytes in the original APDU */ apdu->sw1 = tapdu.sw1; apdu->sw2 = tapdu.sw2; @@ -598,123 +755,130 @@ int sc_transmit_apdu(sc_card_t *card, sc_apdu_t *apdu) len -= plen; buf += plen; } - } else + } else /* transmit single APDU */ - r = do_single_transmit(card, apdu); + r = sc_transmit(card, apdu); /* all done => release lock */ if (sc_unlock(card) != SC_SUCCESS) - sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "sc_unlock failed"); + sc_log(card->ctx, "sc_unlock failed"); return r; } -int sc_bytes2apdu(sc_context_t *ctx, const u8 *buf, size_t len, sc_apdu_t *apdu) + +int +sc_bytes2apdu(sc_context_t *ctx, const u8 *buf, size_t len, sc_apdu_t *apdu) { - const u8 *p; - size_t len0; + const unsigned char *p; + size_t len0; - if (!buf || !apdu) - return SC_ERROR_INVALID_ARGUMENTS; + if (!buf || !apdu) + return SC_ERROR_INVALID_ARGUMENTS; - len0 = len; - if (len < 4) { - sc_debug(ctx, SC_LOG_DEBUG_VERBOSE, "APDU too short (must be at least 4 bytes)"); - return SC_ERROR_INVALID_DATA; - } + len0 = len; + if (len < 4) { + sc_log(ctx, "APDU too short (must be at least 4 bytes)"); + return SC_ERROR_INVALID_DATA; + } - memset(apdu, 0, sizeof *apdu); - p = buf; - apdu->cla = *p++; - apdu->ins = *p++; - apdu->p1 = *p++; - apdu->p2 = *p++; - len -= 4; - if (!len) { - apdu->cse = SC_APDU_CASE_1; - } else { - if (*p == 0 && len >= 3) { - /* ...must be an extended APDU */ - p++; - if (len == 3) { - apdu->le = (*p++)<<8; - apdu->le += *p++; - if (apdu->le == 0) - apdu->le = 0xffff+1; - len -= 3; - apdu->cse = SC_APDU_CASE_2_EXT; - } else { - /* len > 3 */ - apdu->lc = (*p++)<<8; - apdu->lc += *p++; - len -= 3; - if (len < apdu->lc) { - sc_debug(ctx, SC_LOG_DEBUG_VERBOSE, "APDU too short (need %lu more bytes)\n", - (unsigned long) apdu->lc - len); - return SC_ERROR_INVALID_DATA; - } - apdu->data = p; - apdu->datalen = apdu->lc; - len -= apdu->lc; - p += apdu->lc; - if (!len) { - apdu->cse = SC_APDU_CASE_3_EXT; - } else { - /* at this point the apdu has a Lc, so Le is on 2 bytes */ - if (len < 2) { - sc_debug(ctx, SC_LOG_DEBUG_VERBOSE, "APDU too short (need 2 more bytes)\n"); - return SC_ERROR_INVALID_DATA; - } - apdu->le = (*p++)<<8; - apdu->le += *p++; - if (apdu->le == 0) - apdu->le = 0xffff+1; - len -= 2; - apdu->cse = SC_APDU_CASE_4_EXT; - } - } - } else { - /* ...must be a short APDU */ - if (len == 1) { - apdu->le = *p++; - if (apdu->le == 0) - apdu->le = 0xff+1; - len--; - apdu->cse = SC_APDU_CASE_2_SHORT; - } else { - apdu->lc = *p++; - len--; - if (len < apdu->lc) { - sc_debug(ctx, SC_LOG_DEBUG_VERBOSE, "APDU too short (need %lu more bytes)\n", - (unsigned long) apdu->lc - len); - return SC_ERROR_INVALID_DATA; - } - apdu->data = p; - apdu->datalen = apdu->lc; - len -= apdu->lc; - p += apdu->lc; - if (!len) { - apdu->cse = SC_APDU_CASE_3_SHORT; - } else { - apdu->le = *p++; - if (apdu->le == 0) - apdu->le = 0xff+1; - len--; - apdu->cse = SC_APDU_CASE_4_SHORT; + memset(apdu, 0, sizeof *apdu); + p = buf; + apdu->cla = *p++; + apdu->ins = *p++; + apdu->p1 = *p++; + apdu->p2 = *p++; + len -= 4; - } - } - } - if (len) { - sc_debug(ctx, SC_LOG_DEBUG_VERBOSE, "APDU too long (%lu bytes extra)\n", - (unsigned long) len); - return SC_ERROR_INVALID_DATA; - } - } + if (!len) { + apdu->cse = SC_APDU_CASE_1; + sc_log(ctx, "CASE_1 APDU: %lu bytes:\tins=%02x p1=%02x p2=%02x lc=%04x le=%04x", + (unsigned long) len0, apdu->ins, apdu->p1, apdu->p2, apdu->lc, apdu->le); + return SC_SUCCESS; + } - sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "Case %d %s APDU, %lu bytes:\tins=%02x p1=%02x p2=%02x lc=%04x le=%04x", - apdu->cse & SC_APDU_SHORT_MASK, - (apdu->cse & SC_APDU_EXT) != 0 ? "extended" : "short", - (unsigned long) len0, apdu->ins, apdu->p1, apdu->p2, apdu->lc, apdu->le); + if (*p == 0 && len >= 3) { + /* ...must be an extended APDU */ + p++; + if (len == 3) { + apdu->le = (*p++)<<8; + apdu->le += *p++; + if (apdu->le == 0) + apdu->le = 0xffff+1; + len -= 3; + apdu->cse = SC_APDU_CASE_2_EXT; + } + else { + /* len > 3 */ + apdu->lc = (*p++)<<8; + apdu->lc += *p++; + len -= 3; + if (len < apdu->lc) { + sc_log(ctx, "APDU too short (need %lu more bytes)", (unsigned long) apdu->lc - len); + return SC_ERROR_INVALID_DATA; + } + apdu->data = p; + apdu->datalen = apdu->lc; + len -= apdu->lc; + p += apdu->lc; + if (!len) { + apdu->cse = SC_APDU_CASE_3_EXT; + } + else { + /* at this point the apdu has a Lc, so Le is on 2 bytes */ + if (len < 2) { + sc_debug(ctx, SC_LOG_DEBUG_VERBOSE, "APDU too short (need 2 more bytes)\n"); + return SC_ERROR_INVALID_DATA; + } + apdu->le = (*p++)<<8; + apdu->le += *p++; + if (apdu->le == 0) + apdu->le = 0xffff+1; + len -= 2; + apdu->cse = SC_APDU_CASE_4_EXT; + } + } + } + else { + /* ...must be a short APDU */ + if (len == 1) { + apdu->le = *p++; + if (apdu->le == 0) + apdu->le = 0xff+1; + len--; + apdu->cse = SC_APDU_CASE_2_SHORT; + } + else { + apdu->lc = *p++; + len--; + if (len < apdu->lc) { + sc_log(ctx, "APDU too short (need %lu more bytes)", (unsigned long) apdu->lc - len); + return SC_ERROR_INVALID_DATA; + } + apdu->data = p; + apdu->datalen = apdu->lc; + len -= apdu->lc; + p += apdu->lc; + if (!len) { + apdu->cse = SC_APDU_CASE_3_SHORT; + } + else { + apdu->le = *p++; + if (apdu->le == 0) + apdu->le = 0xff+1; + len--; + apdu->cse = SC_APDU_CASE_4_SHORT; + } + } + } + if (len) { + sc_log(ctx, "APDU too long (%lu bytes extra)",(unsigned long) len); + return SC_ERROR_INVALID_DATA; + } - return SC_SUCCESS; + sc_log(ctx, "Case %d %s APDU, %lu bytes:\tins=%02x p1=%02x p2=%02x lc=%04x le=%04x", + apdu->cse & SC_APDU_SHORT_MASK, + (apdu->cse & SC_APDU_EXT) != 0 ? "extended" : "short", + (unsigned long) len0, apdu->ins, apdu->p1, apdu->p2, apdu->lc, apdu->le); + + return SC_SUCCESS; } diff --git a/src/libopensc/card.c b/src/libopensc/card.c index 4ec64945..6b111523 100644 --- a/src/libopensc/card.c +++ b/src/libopensc/card.c @@ -34,6 +34,12 @@ #define INVALIDATE_CARD_CACHE_IN_UNLOCK */ +#ifdef ENABLE_SM +static int sc_card_sm_load(sc_card_t *card, const char *path, const char *module); +static int sc_card_sm_unload(sc_card_t *card); +static int sc_card_sm_check(sc_card_t *card); +#endif + int sc_check_sw(sc_card_t *card, unsigned int sw1, unsigned int sw2) { if (card == NULL) @@ -93,6 +99,10 @@ static void sc_card_free(sc_card_t *card) free(card->ops); if (card->algorithms != NULL) free(card->algorithms); + if (card->cache.current_ef) + sc_file_free(card->cache.current_ef); + if (card->cache.current_df) + sc_file_free(card->cache.current_df); if (card->mutex != NULL) { int r = sc_mutex_destroy(card->ctx, card->mutex); if (r != SC_SUCCESS) @@ -133,7 +143,7 @@ int sc_connect_card(sc_reader_t *reader, sc_card_t **card_out) /* See if the ATR matches any ATR specified in the config file */ if ((driver = ctx->forced_driver) == NULL) { - sc_debug(ctx, SC_LOG_DEBUG_MATCH, "matching configured ATRs"); + sc_log(ctx, "matching configured ATRs"); for (i = 0; ctx->card_drivers[i] != NULL; i++) { driver = ctx->card_drivers[i]; @@ -142,12 +152,12 @@ int sc_connect_card(sc_reader_t *reader, sc_card_t **card_out) driver = NULL; continue; } - sc_debug(ctx, SC_LOG_DEBUG_MATCH, "trying driver: %s", driver->short_name); + sc_log(ctx, "trying driver '%s'", driver->short_name); idx = _sc_match_atr(card, driver->atr_map, NULL); if (idx >= 0) { struct sc_atr_table *src = &driver->atr_map[idx]; - sc_debug(ctx, SC_LOG_DEBUG_MATCH, "matched: %s", driver->name); + sc_log(ctx, "matched driver '%s'", driver->name); /* It's up to card driver to notice these correctly */ card->name = src->name; card->type = src->type; @@ -166,8 +176,7 @@ int sc_connect_card(sc_reader_t *reader, sc_card_t **card_out) if (card->ops->init != NULL) { r = card->ops->init(card); if (r) { - sc_debug(ctx, SC_LOG_DEBUG_MATCH, "driver '%s' init() failed: %s", - card->driver->name, sc_strerror(r)); + sc_log(ctx, "driver '%s' init() failed: %s", card->driver->name, sc_strerror(r)); goto err; } } @@ -177,20 +186,19 @@ int sc_connect_card(sc_reader_t *reader, sc_card_t **card_out) struct sc_card_driver *drv = ctx->card_drivers[i]; const struct sc_card_operations *ops = drv->ops; - sc_debug(ctx, SC_LOG_DEBUG_MATCH, "trying driver: %s", drv->short_name); + sc_log(ctx, "trying driver '%s'", drv->short_name); if (ops == NULL || ops->match_card == NULL) continue; /* Needed if match_card() needs to talk with the card (e.g. card-muscle) */ *card->ops = *ops; if (ops->match_card(card) != 1) continue; - sc_debug(ctx, SC_LOG_DEBUG_MATCH, "matched: %s", drv->name); + sc_log(ctx, "matched: %s", drv->name); memcpy(card->ops, ops, sizeof(struct sc_card_operations)); card->driver = drv; r = ops->init(card); if (r) { - sc_debug(ctx, SC_LOG_DEBUG_MATCH, "driver '%s' init() failed: %s", drv->name, - sc_strerror(r)); + sc_log(ctx, "driver '%s' init() failed: %s", drv->name, sc_strerror(r)); if (r == SC_ERROR_INVALID_CARD) { card->driver = NULL; continue; @@ -201,7 +209,7 @@ int sc_connect_card(sc_reader_t *reader, sc_card_t **card_out) } } if (card->driver == NULL) { - sc_debug(ctx, SC_LOG_DEBUG_MATCH, "unable to find driver for inserted card"); + sc_log(ctx, "unable to find driver for inserted card"); r = SC_ERROR_INVALID_CARD; goto err; } @@ -222,6 +230,16 @@ int sc_connect_card(sc_reader_t *reader, sc_card_t **card_out) sc_log(ctx, "card info name:'%s', type:%i, flags:0x%X, max_send/recv_size:%i/%i", card->name, card->type, card->flags, card->max_send_size, card->max_recv_size); + +#ifdef ENABLE_SM + /* Check, if secure messaging module present. */ + r = sc_card_sm_check(card); + if (r) { + sc_log(ctx, "cannot load secure messaging module"); + goto err; + } +#endif + LOG_FUNC_RETURN(ctx, SC_SUCCESS); err: if (connected) @@ -254,6 +272,11 @@ int sc_disconnect_card(sc_card_t *card) sc_log(ctx, "disconnect() failed: %s", sc_strerror(r)); } +#ifdef ENABLE_SM + /* release SM related resources */ + sc_card_sm_unload(card); +#endif + sc_card_free(card); LOG_FUNC_RETURN(ctx, SC_SUCCESS); } @@ -290,7 +313,7 @@ int sc_lock(sc_card_t *card) int r = 0, r2 = 0; LOG_FUNC_CALLED(card->ctx); - + if (card == NULL) return SC_ERROR_INVALID_ARGUMENTS; r = sc_mutex_lock(card->ctx, card->mutex); @@ -423,6 +446,13 @@ int sc_read_binary(sc_card_t *card, unsigned int idx, if (count == 0) return 0; +#ifdef ENABLE_SM + if (card->sm_ctx.ops.read_binary) { + r = card->sm_ctx.ops.read_binary(card, idx, buf, count); + if (r) + LOG_FUNC_RETURN(card->ctx, r); + } +#endif if (card->ops->read_binary == NULL) LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED); @@ -508,6 +538,15 @@ int sc_update_binary(sc_card_t *card, unsigned int idx, sc_log(card->ctx, "called; %d bytes at index %d", count, idx); if (count == 0) return 0; + +#ifdef ENABLE_SM + if (card->sm_ctx.ops.update_binary) { + r = card->sm_ctx.ops.update_binary(card, idx, buf, count); + if (r) + LOG_FUNC_RETURN(card->ctx, r); + } +#endif + if (card->ops->update_binary == NULL) LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED); @@ -786,7 +825,7 @@ sc_algorithm_info_t * sc_card_find_ec_alg(sc_card_t *card, { return sc_card_find_alg(card, SC_ALGORITHM_EC, key_length); } - + int _sc_card_add_rsa_alg(sc_card_t *card, unsigned int key_length, unsigned long flags, unsigned long exponent) { @@ -854,7 +893,7 @@ static int match_atr_table(sc_context_t *ctx, struct sc_atr_table *table, struct mbin_len = sizeof(mbin); sc_hex_to_bin(matr, mbin, &mbin_len); if (mbin_len != fix_bin_len) { - sc_log(ctx, "length of atr and atr mask do not match - ignored: %s - %s", tatr, matr); + sc_log(ctx, "length of atr and atr mask do not match - ignored: %s - %s", tatr, matr); continue; } for (s = 0; s < tbin_len; s++) { @@ -982,10 +1021,10 @@ scconf_block *sc_get_conf_block(sc_context_t *ctx, const char *name1, const char { int i; scconf_block *conf_block = NULL; - + for (i = 0; ctx->conf_blocks[i] != NULL; i++) { scconf_block **blocks; - + blocks = scconf_find_blocks(ctx->conf, ctx->conf_blocks[i], name1, name2); if (blocks != NULL) { conf_block = blocks[0]; @@ -1005,7 +1044,7 @@ void sc_print_cache(struct sc_card *card) { if (!card->cache.valid || (!card->cache.current_ef && !card->cache.current_df)) { sc_log(ctx, "card cache invalid"); - return; + return; } if (card->cache.current_ef) @@ -1018,3 +1057,206 @@ void sc_print_cache(struct sc_card *card) { sc_print_path(&card->cache.current_df->path)); } + +#ifdef ENABLE_SM +static int +sc_card_sm_unload(struct sc_card *card) +{ + if (card->sm_ctx.module.ops.module_cleanup) + card->sm_ctx.module.ops.module_cleanup(card->ctx); + + if (card->sm_ctx.module.handle) + sc_dlclose(card->sm_ctx.module.handle); + card->sm_ctx.module.handle = NULL; + return 0; +} + + +static int +sc_card_sm_load(struct sc_card *card, const char *module_path, const char *in_module) +{ + struct sc_context *ctx = NULL; + int rv = SC_ERROR_INTERNAL; + char *module = NULL; +#ifdef _WIN32 + char temp_path[PATH_MAX]; + int temp_len; + long rc; + HKEY hKey; + const char path_delim = '\\'; +#else + const char path_delim = '/'; +#endif + + assert(card != NULL); + ctx = card->ctx; + SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_NORMAL); + if (!in_module) + return sc_card_sm_unload(card); + +#ifdef _WIN32 + if (!module_path) { + rc = RegOpenKeyEx( HKEY_CURRENT_USER, "Software\\OpenSC Project\\OpenSC", 0, KEY_QUERY_VALUE, &hKey ); + if( rc == ERROR_SUCCESS ) { + temp_len = PATH_MAX; + rc = RegQueryValueEx( hKey, "SmDir", NULL, NULL, (LPBYTE) temp_path, &temp_len); + if( (rc == ERROR_SUCCESS) && (temp_len < PATH_MAX) ) + module_path = temp_path; + RegCloseKey( hKey ); + } + } + if (!module_path) { + rc = RegOpenKeyEx( HKEY_LOCAL_MACHINE, "Software\\OpenSC Project\\OpenSC", 0, KEY_QUERY_VALUE, &hKey ); + if( rc == ERROR_SUCCESS ) { + temp_len = PATH_MAX; + rc = RegQueryValueEx( hKey, "SmDir", NULL, NULL, (LPBYTE) temp_path, &temp_len); + if(rc == ERROR_SUCCESS && temp_len < PATH_MAX) + module_path = temp_path; + RegCloseKey( hKey ); + } + } +#endif + sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "SM module '%s' located in '%s'", in_module, module_path); + if (module_path) { + int sz = strlen(in_module) + strlen(module_path) + 3; + module = malloc(sz); + if (module) + snprintf(module, sz, "%s%c%s", module_path, path_delim, in_module); + } + else { + module = strdup(in_module); + } + + if (!module) + return SC_ERROR_MEMORY_FAILURE; + + sc_log(ctx, "try to load SM module '%s'", module); + do { + struct sm_module_operations *mod_ops = &card->sm_ctx.module.ops; + void *mod_handle; + + card->sm_ctx.module.handle = sc_dlopen(module); + if (!card->sm_ctx.module.handle) { + sc_log(ctx, "cannot open dynamic library '%s': %s", module, sc_dlerror()); + break; + } + mod_handle = card->sm_ctx.module.handle; + + mod_ops->initialize = sc_dlsym(mod_handle, "initialize"); + if (!mod_ops->initialize) { + sc_log(ctx, "SM handler 'initialize' not exported: %s", sc_dlerror()); + break; + } + + mod_ops->get_apdus = sc_dlsym(mod_handle, "get_apdus"); + if (!mod_ops->get_apdus) { + sc_log(ctx, "SM handler 'get_apdus' not exported: %s", sc_dlerror()); + break; + } + + mod_ops->finalize = sc_dlsym(mod_handle, "finalize"); + if (!mod_ops->finalize) + sc_log(ctx, "SM handler 'finalize' not exported -- ignored"); + + mod_ops->module_init = sc_dlsym(mod_handle, "module_init"); + if (!mod_ops->module_init) + sc_log(ctx, "SM handler 'module_init' not exported -- ignored"); + + mod_ops->module_cleanup = sc_dlsym(mod_handle, "module_cleanup"); + if (!mod_ops->module_cleanup) + sc_log(ctx, "SM handler 'module_cleanup' not exported -- ignored"); + + mod_ops->test = sc_dlsym(mod_handle, "test"); + if (mod_ops->test) + sc_log(ctx, "SM handler 'test' not exported -- ignored"); + + rv = 0; + break; + } while(0); + + if (rv) + sc_card_sm_unload(card); + + card->sm_ctx.sm_mode = SM_MODE_ACL; + if (module) + free(module); + + SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_VERBOSE, rv); +} + + +/* get SM related configuration settings and initialize SM session, SM module, ... */ +static int +sc_card_sm_check(struct sc_card *card) +{ + const char *sm = NULL, *module_name = NULL, *module_path = NULL, *module_data = NULL, *sm_mode = NULL; + struct sc_context *ctx = card->ctx; + scconf_block *atrblock = NULL, *sm_conf_block = NULL; + int rv, ii; + + SC_FUNC_CALLED(ctx, SC_LOG_DEBUG_NORMAL); + + /* get the name of card specific SM configuration section */ + atrblock = _sc_match_atr_block(ctx, card->driver, &card->atr); + if (atrblock == NULL) + LOG_FUNC_RETURN(ctx, SC_SUCCESS); + sm = scconf_get_str(atrblock, "secure_messaging", NULL); + if (!sm) + LOG_FUNC_RETURN(ctx, SC_SUCCESS); + + /* get SM configuration section by the name */ + sc_log(ctx, "secure_messaging configuration block '%s'", sm); + for (ii = 0; ctx->conf_blocks[ii]; ii++) { + scconf_block **blocks; + + blocks = scconf_find_blocks(ctx->conf, ctx->conf_blocks[ii], "secure_messaging", sm); + if (blocks) { + sm_conf_block = blocks[0]; + free(blocks); + } + if (sm_conf_block != NULL) + break; + } + + if (!sm_conf_block) + LOG_TEST_RET(ctx, SC_ERROR_INCONSISTENT_CONFIGURATION, "SM configuration block not preset"); + + /* check if an external SM module has to be used */ + module_path = scconf_get_str(sm_conf_block, "module_path", NULL); + module_name = scconf_get_str(sm_conf_block, "module_name", NULL); + sc_log(ctx, "SM module '%s' in '%s'", module_name, module_path); + if (!module_name) + LOG_TEST_RET(ctx, SC_ERROR_INCONSISTENT_CONFIGURATION, "Invalid SM configuration: module not defined"); + + rv = sc_card_sm_load(card, module_path, module_name); + LOG_TEST_RET(ctx, rv, "Failed to load SM module"); + + strncpy(card->sm_ctx.module.filename, module_name, sizeof(card->sm_ctx.module.filename)); + strncpy(card->sm_ctx.config_section, sm, sizeof(card->sm_ctx.config_section)); + + /* allocate resources for the external SM module */ + sc_log(ctx, "'module_init' handler %p", card->sm_ctx.module.ops.module_init); + if (card->sm_ctx.module.ops.module_init) { + module_data = scconf_get_str(sm_conf_block, "module_data", NULL); + sc_log(ctx, "module_data '%s'", module_data); + + rv = card->sm_ctx.module.ops.module_init(ctx, module_data); + SC_TEST_RET(ctx, SC_LOG_DEBUG_NORMAL, rv, "Cannot initialize SM module"); + } + + /* initialize SM session in the case of 'APDU TRANSMIT' SM mode */ + sm_mode = scconf_get_str(sm_conf_block, "mode", NULL); + sc_log(ctx, "SM mode '%s'; 'open' handler %p", sm_mode, card->sm_ctx.ops.open); + if (sm_mode && !strcasecmp("Transmit", sm_mode)) { + if (!card->sm_ctx.ops.open || !card->sm_ctx.ops.get_sm_apdu || !card->sm_ctx.ops.free_sm_apdu) + LOG_TEST_RET(ctx, SC_ERROR_NOT_SUPPORTED, "'Transmit' SM asked but not supported by card driver"); + + card->sm_ctx.sm_mode = SM_MODE_TRANSMIT; + rv = card->sm_ctx.ops.open(card); + LOG_TEST_RET(ctx, rv, "Cannot initialize SM"); + } + + sc_log(ctx, "SM mode:%X", card->sm_ctx.sm_mode); + SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_VERBOSE, rv); +} +#endif diff --git a/src/libopensc/opensc.h b/src/libopensc/opensc.h index 15b35acb..d45d1e52 100644 --- a/src/libopensc/opensc.h +++ b/src/libopensc/opensc.h @@ -40,6 +40,10 @@ extern "C" { #include "scconf/scconf.h" #include "libopensc/errors.h" #include "libopensc/types.h" +#ifdef ENABLE_SM +#include "libopensc/sm.h" +#endif + #define SC_SEC_OPERATION_DECIPHER 0x0001 #define SC_SEC_OPERATION_SIGN 0x0002 @@ -523,6 +527,9 @@ typedef struct sc_card { sc_serial_number_t serialnr; void *mutex; +#ifdef ENABLE_SM + struct sm_context sm_ctx; +#endif unsigned int magic; } sc_card_t; diff --git a/src/libopensc/sm.h b/src/libopensc/sm.h new file mode 100644 index 00000000..6edfc196 --- /dev/null +++ b/src/libopensc/sm.h @@ -0,0 +1,344 @@ +/* + * sm.h: Support of Secure Messaging + * + * Copyright (C) 2010 Viktor Tarasov + * OpenTrust + * + * 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 _SM_H +#define _SM_H + +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +#ifndef SHA_DIGEST_LENGTH +#define SHA_DIGEST_LENGTH 20 +#define SHA1_DIGEST_LENGTH 20 +#define SHA256_DIGEST_LENGTH 32 +#endif + +#define SM_TYPE_GP_SCP01 0x100 +#define SM_TYPE_CWA14890 0x400 + +#define SM_MODE_NONE 0x0 +#define SM_MODE_ACL 0x100 +#define SM_MODE_TRANSMIT 0x200 + +#define SM_CMD_INITIALIZE 0x10 +#define SM_CMD_MUTUAL_AUTHENTICATION 0x20 +#define SM_CMD_RSA 0x100 +#define SM_CMD_RSA_GENERATE 0x101 +#define SM_CMD_RSA_UPDATE 0x102 +#define SM_CMD_RSA_READ_PUBLIC 0x103 +#define SM_CMD_FILE 0x200 +#define SM_CMD_FILE_READ 0x201 +#define SM_CMD_FILE_UPDATE 0x202 +#define SM_CMD_FILE_CREATE 0x203 +#define SM_CMD_FILE_DELETE 0x204 +#define SM_CMD_PIN 0x300 +#define SM_CMD_PIN_VERIFY 0x301 +#define SM_CMD_PIN_RESET 0x302 +#define SM_CMD_PIN_SET_PIN 0x303 +#define SM_CMD_PSO 0x400 +#define SM_CMD_PSO_DST 0x401 +#define SM_CMD_APDU 0x500 +#define SM_CMD_APDU_TRANSMIT 0x501 +#define SM_CMD_APDU_RAW 0x502 +#define SM_CMD_APPLET 0x600 +#define SM_CMD_APPLET_DELETE 0x601 +#define SM_CMD_APPLET_LOAD 0x602 +#define SM_CMD_APPLET_INSTALL 0x603 +#define SM_CMD_EXTERNAL_AUTH 0x700 +#define SM_CMD_EXTERNAL_AUTH_INIT 0x701 +#define SM_CMD_EXTERNAL_AUTH_CHALLENGE 0x702 +#define SM_CMD_EXTERNAL_AUTH_DOIT 0x703 +#define SM_CMD_SDO_UPDATE 0x800 +#define SM_CMD_FINALIZE 0x900 + +#define SM_RESPONSE_CONTEXT_TAG 0xA1 +#define SM_RESPONSE_CONTEXT_DATA_TAG 0xA2 + +#define SM_MAX_DATA_SIZE 0xE0 + +#define SM_SMALL_CHALLENGE_LEN 8 + +#define SM_GP_SECURITY_NO 0x00 +#define SM_GP_SECURITY_MAC 0x01 +#define SM_GP_SECURITY_ENC 0x03 + +/* Global Platform (SCP01) data types */ +/* + * @struct sm_type_params_gp + * Global Platform SM channel parameters + */ +struct sm_type_params_gp { + unsigned level; + unsigned index; + unsigned version; + + struct sc_cplc cplc; +}; + +/* + * @struct sm_gp_keyset + * Global Platform keyset: + * - version, index; + * - keyset presented in three parts: 'ENC', 'MAC' and 'KEK'; + * - keyset presented in continuous manner - raw or 'to be diversified'. + */ +struct sm_gp_keyset { + int version; + int index; + unsigned char enc[16]; + unsigned char mac[16]; + unsigned char kek[16]; + + unsigned char kmc[48]; + unsigned kmc_len; +}; + +/* + * @struct sm_gp_session + * Global Platform SM session data + */ +struct sm_gp_session { + unsigned char *session_enc, *session_mac, *session_kek; + unsigned char mac_icv[8]; +}; + + +/* CWA, IAS/ECC data types */ + +/* + * @struct sm_type_params_cwa + */ +struct sm_type_params_cwa { + struct sc_crt crt_at; +}; + +/* + * @struct sm_cwa_keyset + * CWA keyset: + * - SDO reference; + * - 'ENC' and 'MAC' 3DES keys. + */ +struct sm_cwa_keyset { + unsigned sdo_reference; + unsigned char enc[16]; + unsigned char mac[16]; +}; + +/* + * @struct sm_cwa_token_data + * CWA token data: + * - serial; + * - 'small' random; + * - 'big' random. + */ +struct sm_cwa_token_data { + unsigned char sn[8]; + unsigned char rnd[8]; + unsigned char k[32]; +}; + +/* + * @struct sm_cwa_session + * CWA working SM session data: + * - ICC and IFD token data; + * - ENC and MAC session keys; + * - SSC (SM Sequence Counter); + * - 'mutual authentication' data. + */ +struct sm_cwa_session { + struct sm_cwa_token_data icc; + struct sm_cwa_token_data ifd; + + unsigned char session_enc[16]; + unsigned char session_mac[16]; + + unsigned char ssc[8]; + + unsigned char mdata[0x48]; + size_t mdata_len; +}; + +/* + * @struct sc_secure channel + * data type to open and maintain the Secure Messaging session. + */ +struct sm_secure_channel { + union { + struct sm_gp_keyset gp; + struct sm_cwa_keyset cwa; + } keyset; + + union { + struct sm_gp_session gp; + struct sm_cwa_session cwa; + } session; + + unsigned char host_challenge[SM_SMALL_CHALLENGE_LEN]; + unsigned char card_challenge[SM_SMALL_CHALLENGE_LEN]; +}; + + +/* + * @struct sc_info is the + * placehold for the secure messaging working data: + * - SM type; + * - SM session state; + * - command to execute by external SM module; + * - data related to the current card context. + */ +struct sm_info { + char config_section[64]; + unsigned card_type; + + unsigned cmd; + void *cmd_data; + + unsigned sm_type; + union { + struct sm_type_params_gp gp; + struct sm_type_params_cwa cwa; + } sm_params; + + struct sc_serial_number serialnr; + + unsigned security_condition; + + struct sc_path current_path_df; + struct sc_path current_path_ef; + struct sc_aid current_aid; + + unsigned char *rdata; + size_t rdata_len; + + struct sm_secure_channel schannel; +}; + +/* + * @struct sm_card_response + * data type to return card response. + */ +typedef struct sm_card_response { + int num; + + unsigned char data[SC_MAX_APDU_BUFFER_SIZE]; + size_t data_len; + + unsigned char mac[8]; + size_t mac_len; + + unsigned char sw1, sw2; + + struct sm_card_response *next; + struct sm_card_response *prev; +} sm_card_response_t; + +struct sc_context; +struct sc_card; + +/* + * @struct sm_card_operations + * card driver handlers related to secure messaging (in 'APDU TRANSMIT' mode) + * - 'open' - initialize SM session; + * - 'encode apdu' - SM encoding of the raw APDU; + * - 'decrypt response' - decode card answer; + * - 'close' - close SM session. + */ +struct sm_card_operations { + int (*open)(struct sc_card *card); + int (*get_sm_apdu)(struct sc_card *card, struct sc_apdu *apdu, struct sc_apdu **sm_apdu); + int (*free_sm_apdu)(struct sc_card *card, struct sc_apdu *apdu, struct sc_apdu **sm_apdu); + int (*close)(struct sc_card *card); + + int (*read_binary)(struct sc_card *card, unsigned int idx, + unsigned char * buf, size_t count); + int (*update_binary)(struct sc_card *card, unsigned int idx, + const unsigned char * buf, size_t count); +}; + +/* + * @struct sm_module_operations + * API to use external SM modules: + * - 'initiliaze' - get APDU(s) to initialize SM session; + * - 'get apdus' - get secured APDUs to execute particular command; + * - 'finalize' - get APDU(s) to finalize SM session; + * - 'module init' - initialize external module (allocate data, read configuration, ...); + * - 'module cleanup' - free resources allocated by external module. + */ +struct sm_module_operations { + int (*initialize)(struct sc_context *ctx, struct sm_info *info, + struct sc_remote_data *out); + int (*get_apdus)(struct sc_context *ctx, struct sm_info *sm_info, + unsigned char *init_data, size_t init_len, + struct sc_remote_data *out); + int (*finalize)(struct sc_context *ctx, struct sm_info *info, struct sc_remote_data *rdata, + unsigned char *out, size_t out_len); + int (*module_init)(struct sc_context *ctx, const char *data); + int (*module_cleanup)(struct sc_context *ctx); + + int (*test)(struct sc_context *ctx, struct sm_info *info, char *out); +}; + +typedef struct sm_module { + char filename[128]; + void *handle; + + struct sm_module_operations ops; +} sm_module_t; + +/* @struct sm_context + * SM context -- top level of the SM data type + * - SM mode ('ACL' or 'APDU TRANSMIT'), flags; + * - working SM data; + * - card operations related to SM in 'APDU TRANSMIT' mode; + * - external SM module; + * - 'lock'/'unlock' handlers to allow SM transfer in the locked card session. + */ +typedef struct sm_context { + char config_section[64]; + unsigned sm_mode, sm_flags; + + struct sm_info info; + + struct sm_card_operations ops; + + struct sm_module module; + + unsigned long (*app_lock)(void); + void (*app_unlock)(void); +} sm_context_t; + +int iasecc_sm_external_authentication(struct sc_card *, unsigned, int *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libopensc/types.h b/src/libopensc/types.h index fb0b7b5e..2676e80d 100644 --- a/src/libopensc/types.h +++ b/src/libopensc/types.h @@ -267,6 +267,8 @@ typedef struct sc_apdu { u8 control; /* Set if APDU should go to the reader */ unsigned int sw1, sw2; /* Status words returned in R-APDU */ + unsigned char mac[8]; + size_t mac_len; unsigned long flags; diff --git a/src/sm/Makefile.am b/src/sm/Makefile.am new file mode 100644 index 00000000..90fb80a5 --- /dev/null +++ b/src/sm/Makefile.am @@ -0,0 +1,17 @@ +# Process this file with automake to create Makefile.in + +MAINTAINERCLEANFILES = Makefile.in + +EXTRA_DIST = Makefile.mak + +AM_CFLAGS = $(OPTIONAL_OPENSSL_CFLAGS) $(OPTIONAL_READLINE_CFLAGS) +INCLUDES = -I$(top_srcdir)/src -I$(top_srcdir)/src/common -I$(top_builddir)/src/include +LIBS = $(top_builddir)/src/libopensc/libopensc.la $(top_builddir)/src/common/libcompat.la + +lib_LTLIBRARIES = libsmm-local.la + +libsmm_local_la_SOURCES = +libsmm_local_la_LIBADD = ../libopensc/libopensc.la +libsmm_local_la_LDFLAGS = -version-info @OPENSC_LT_CURRENT@:@OPENSC_LT_REVISION@:@OPENSC_LT_AGE@ + +# noinst_HEADERS = sm.h