diff --git a/configure.ac b/configure.ac index 4c6b9ff83d..182bb69970 100644 --- a/configure.ac +++ b/configure.ac @@ -58,6 +58,7 @@ AC_CANONICAL_HOST AC_PROG_CC # AC_PROG_CXX is needed to built the win32 custom action. Indeed dutil.h use [extern "C"] definition which fails on pure c compiler AC_PROG_CXX +AC_PROG_OBJC PKG_PROG_PKG_CONFIG AC_C_BIGENDIAN @@ -195,6 +196,13 @@ AC_ARG_ENABLE( [enable_pcsc="yes"] ) +AC_ARG_ENABLE( + [cryptotokenkit], + [AS_HELP_STRING([--disable-cryptotokenkit],[disable CryptoTokenKit support @<:@enabled@:>@])], + , + [enable_cryptotokenkit="no"] +) + AC_ARG_ENABLE( [ctapi], [AS_HELP_STRING([--enable-ctapi],[enable CT-API support @<:@disabled@:>@])], @@ -267,11 +275,11 @@ AC_ARG_WITH( dnl ./configure check reader_count="" -for rdriver in "${enable_pcsc}" "${enable_openct}" "${enable_ctapi}"; do +for rdriver in "${enable_pcsc}" "${enable_cryptotokenkit}" "${enable_openct}" "${enable_ctapi}"; do test "${rdriver}" = "yes" && reader_count="${reader_count}x" done if test "${reader_count}" != "x"; then - AC_MSG_ERROR([Only one of --enable-pcsc, --enable-openct, --enable-ctapi can be specified!]) + AC_MSG_ERROR([Only one of --enable-pcsc, --enable-cryptotokenkit, --enable-openct, --enable-ctapi can be specified!]) fi dnl Checks for programs. @@ -711,6 +719,22 @@ if test "${enable_pcsc}" = "yes"; then AC_DEFINE([ENABLE_PCSC], [1], [Define if PC/SC is to be enabled]) fi +if test "${enable_cryptotokenkit}" = "yes"; then + if test -z "${CRYPTOTOKENKIT_CFLAGS}"; then + case "${host}" in + *-apple-*) + CRYPTOTOKENKIT_CFLAGS="-framework CryptoTokenKit -framework Foundation" + LDFLAGS="${LDFLAGS} -framework CryptoTokenKit -framework Foundation" + ;; + *) + AC_MSG_ERROR([CryptoTokenKit only supported on Darwin]) + ;; + esac + fi + AC_DEFINE([ENABLE_CRYPTOTOKENKIT], [1], [Define if CryptoTokenKit is to be enabled]) +fi + + AC_SUBST(DYN_LIB_EXT) AC_SUBST(LIBDIR) AC_SUBST(LIB_PRE) @@ -803,6 +827,9 @@ if test "${enable_pcsc}" = "yes"; then OPENSC_FEATURES="${OPENSC_FEATURES} pcsc(${DEFAULT_PCSC_PROVIDER})" OPTIONAL_PCSC_CFLAGS="${PCSC_CFLAGS}" fi +if test "${enable_cryptotokenkit}" = "yes"; then + OPTIONAL_CRYPTOTOKENKIT_CFLAGS="${CRYPTOTOKENKIT_CFLAGS}" +fi if test "${enable_ctapi}" = "yes"; then OPENSC_FEATURES="${OPENSC_FEATURES} ctapi" fi @@ -878,7 +905,7 @@ AM_CONDITIONAL([ENABLE_THREAD_LOCKING], [test "${enable_thread_locking}" = "yes" AM_CONDITIONAL([ENABLE_ZLIB], [test "${enable_zlib}" = "yes"]) AM_CONDITIONAL([ENABLE_READLINE], [test "${enable_readline}" = "yes"]) AM_CONDITIONAL([ENABLE_OPENSSL], [test "${enable_openssl}" = "yes"]) -AM_CONDITIONAL([ENABLE_OPENCT], [test "${enable_openct}" = "yes"]) +AM_CONDITIONAL([ENABLE_CRYPTOTOKENKIT], [test "${enable_cryptotokenkit}" = "yes"]) AM_CONDITIONAL([ENABLE_DOC], [test "${enable_doc}" = "yes"]) AM_CONDITIONAL([WIN32], [test "${WIN32}" = "yes"]) AM_CONDITIONAL([CYGWIN], [test "${CYGWIN}" = "yes"]) @@ -970,6 +997,7 @@ zlib support: ${enable_zlib} readline support: ${enable_readline} OpenSSL support: ${enable_openssl} PC/SC support: ${enable_pcsc} +CryptoTokenKit support: ${enable_cryptotokenkit} OpenCT support: ${enable_openct} CT-API support: ${enable_ctapi} minidriver support: ${enable_minidriver} @@ -999,6 +1027,7 @@ OPENPACE_LIBS: ${OPENPACE_LIBS} OPENCT_CFLAGS: ${OPENCT_CFLAGS} OPENCT_LIBS: ${OPENCT_LIBS} PCSC_CFLAGS: ${PCSC_CFLAGS} +CRYPTOTOKENKIT_CFLAGS: ${CRYPTOTOKENKIT_CFLAGS} EOF diff --git a/etc/opensc.conf.in b/etc/opensc.conf.in index 237c1284ae..239c44c18f 100644 --- a/etc/opensc.conf.in +++ b/etc/opensc.conf.in @@ -124,7 +124,18 @@ app default { # Default: n/a # max_send_size = 255; # max_recv_size = 256; - }; + } + + # Options for CryptoTokenKit support + reader_driver cryptotokenkit { + # Limit command and response sizes. Some Readers don't propagate their + # transceive capabilities correctly. max_send_size and max_recv_size + # allow setting the limits manually, for example to enable extended + # length capabilities. + # Default: autodetect + # max_send_size = 65535; + # max_recv_size = 65536; + } # Whitelist of card drivers to load at start-up # diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index 1f6d07ae58..269cbf06f9 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -18,6 +18,7 @@ AM_CPPFLAGS = -DOPENSC_CONF_PATH=\"$(sysconfdir)/opensc.conf\" \ -I$(top_srcdir)/src AM_CFLAGS = $(OPENPACE_CFLAGS) $(OPTIONAL_OPENSSL_CFLAGS) $(OPTIONAL_OPENCT_CFLAGS) \ $(OPTIONAL_PCSC_CFLAGS) $(OPTIONAL_ZLIB_CFLAGS) +AM_OBJCFLAGS = $(AM_CFLAGS) libopensc_la_SOURCES_BASE = \ sc.c ctx.c log.c errors.c \ @@ -55,9 +56,20 @@ libopensc_la_SOURCES_BASE = \ pkcs15-dnie.c pkcs15-gids.c pkcs15-iasecc.c pkcs15-jpki.c \ compression.c p15card-helper.c sm.c \ aux-data.c + +if ENABLE_CRYPTOTOKENKIT +# most platforms don't support objective C the way we needed. +# Only include it if needed +libopensc_la_SOURCES_BASE += reader-cryptotokenkit.m +else +libopensc_la_LIBTOOLFLAGS = --tag CC +libopensc_static_la_LIBTOOLFLAGS = --tag CC +endif + libopensc_la_SOURCES = $(libopensc_la_SOURCES_BASE) \ libopensc.exports libopensc_static_la_SOURCES = $(libopensc_la_SOURCES_BASE) + if WIN32 libopensc_la_SOURCES += $(top_builddir)/win32/versioninfo.rc endif diff --git a/src/libopensc/ctx.c b/src/libopensc/ctx.c index 6b08bd0da1..81a9bd7566 100644 --- a/src/libopensc/ctx.c +++ b/src/libopensc/ctx.c @@ -797,6 +797,8 @@ int sc_context_create(sc_context_t **ctx_out, const sc_context_param_t *parm) if(strcmp(ctx->app_name, "cardmod") == 0) ctx->reader_driver = sc_get_cardmod_driver(); #endif +#elif defined(ENABLE_CRYPTOTOKENKIT) + ctx->reader_driver = sc_get_cryptotokenkit_driver(); #elif defined(ENABLE_CTAPI) ctx->reader_driver = sc_get_ctapi_driver(); #elif defined(ENABLE_OPENCT) diff --git a/src/libopensc/internal.h b/src/libopensc/internal.h index efc076a5b4..9ff27d817f 100644 --- a/src/libopensc/internal.h +++ b/src/libopensc/internal.h @@ -257,6 +257,7 @@ extern struct sc_reader_driver *sc_get_pcsc_driver(void); extern struct sc_reader_driver *sc_get_ctapi_driver(void); extern struct sc_reader_driver *sc_get_openct_driver(void); extern struct sc_reader_driver *sc_get_cardmod_driver(void); +extern struct sc_reader_driver *sc_get_cryptotokenkit_driver(void); #ifdef __cplusplus } diff --git a/src/libopensc/reader-cryptotokenkit.m b/src/libopensc/reader-cryptotokenkit.m new file mode 100644 index 0000000000..cead041c71 --- /dev/null +++ b/src/libopensc/reader-cryptotokenkit.m @@ -0,0 +1,424 @@ +/* + * reader-cryptotokenkit.m: Reader driver for CryptoTokenKit interface + * + * Copyright (C) 2017 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 + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef ENABLE_CRYPTOTOKENKIT /* empty file without cryptotokenkit */ + +#import +#include "internal.h" +#include "log.h" +#include "opensc.h" + +struct cryptotokenkit_private_data { + TKSmartCardSlot* tksmartcardslot; + TKSmartCard* tksmartcard; +}; + +static struct sc_reader_operations cryptotokenkit_ops; + +static struct sc_reader_driver cryptotokenkit_reader_driver = { + "CryptoTokenKit pseudo reader", + "cryptotokenkit", + &cryptotokenkit_ops, + NULL +}; + +static int cryptotokenkit_init(sc_context_t *ctx) +{ + return SC_SUCCESS; +} + +static int cryptotokenkit_release(sc_reader_t *reader) +{ + struct cryptotokenkit_private_data *priv = reader->drv_data; + + free(priv); + return SC_SUCCESS; +} + +static int cryptotokenkit_detect_card_presence(sc_reader_t *reader) +{ + struct cryptotokenkit_private_data *priv = reader->drv_data; + int r = SC_SUCCESS; + int old_flags = reader->flags; + + LOG_FUNC_CALLED(reader->ctx); + + reader->flags &= ~(SC_READER_CARD_INUSE); + + switch (priv->tksmartcardslot.state) { + case TKSmartCardSlotStateMuteCard: + // The card inserted in the slot does not answer. + r = SC_ERROR_CARD_UNRESPONSIVE; + // fall through */ + case TKSmartCardSlotStateProbing: + // The card was inserted into the slot and an initial probe is in progress. + reader->flags |= SC_READER_CARD_INUSE; + // fall through */ + case TKSmartCardSlotStateValidCard: + // Card properly answered to reset. + reader->flags |= SC_READER_CARD_PRESENT; + if ([priv->tksmartcardslot.ATR.bytes length] > SC_MAX_ATR_SIZE) + return SC_ERROR_INTERNAL; + reader->atr.len = [priv->tksmartcardslot.ATR.bytes length]; + memcpy(reader->atr.value, (unsigned char*) [priv->tksmartcardslot.ATR.bytes bytes], reader->atr.len); + break; + case TKSmartCardSlotStateMissing: + // Slot is no longer known to the system. + reader->flags &= ~(SC_READER_CARD_PRESENT); + reader->flags |= SC_READER_REMOVED; + r = SC_ERROR_READER_DETACHED; + break; + case TKSmartCardSlotStateEmpty: + /// The slot is empty, no card is inserted. + reader->flags &= ~SC_READER_CARD_PRESENT; + break; + default: + r = SC_ERROR_UNKNOWN; + break; + } + + if ((old_flags & SC_READER_CARD_PRESENT) == (reader->flags & SC_READER_CARD_PRESENT)) + reader->flags &= ~SC_READER_CARD_CHANGED; + else + reader->flags |= SC_READER_CARD_CHANGED; + + sc_log(reader->ctx, "card %s%s", + reader->flags & SC_READER_CARD_PRESENT ? "present" : "absent", + reader->flags & SC_READER_CARD_CHANGED ? ", changed": ""); + + if (r == SC_SUCCESS) + r = reader->flags; + + LOG_FUNC_RETURN(reader->ctx, r); +} + +static int ctk_proto_to_opensc(TKSmartCardProtocol proto) +{ + switch (proto) { + case TKSmartCardProtocolT0: + return SC_PROTO_T0; + case TKSmartCardProtocolT1: + /* fall through */ + case TKSmartCardProtocolT15: + return SC_PROTO_T1; + case TKSmartCardProtocolAny: + return SC_PROTO_ANY; + default: + return 0; + } +} + +static void ctk_set_proto(sc_reader_t *reader) +{ + struct cryptotokenkit_private_data *priv = reader->drv_data; + if (priv->tksmartcard) { + reader->active_protocol = ctk_proto_to_opensc(priv->tksmartcard.currentProtocol); + if (priv->tksmartcard.allowedProtocols & TKSmartCardProtocolAny) { + reader->supported_protocols = ctk_proto_to_opensc(TKSmartCardProtocolAny); + } else { + if (priv->tksmartcard.allowedProtocols & TKSmartCardProtocolT0) + reader->supported_protocols |= ctk_proto_to_opensc(TKSmartCardProtocolT0); + if (priv->tksmartcard.allowedProtocols & TKSmartCardProtocolT1) + reader->supported_protocols |= ctk_proto_to_opensc(TKSmartCardProtocolT1); + if (priv->tksmartcard.allowedProtocols & TKSmartCardProtocolT15) + reader->supported_protocols |= ctk_proto_to_opensc(TKSmartCardProtocolT1); + } + } +} + +static int cryptotokenkit_connect(sc_reader_t *reader) +{ + struct cryptotokenkit_private_data *priv = reader->drv_data; + + if (!priv->tksmartcard) { + priv->tksmartcard = [priv->tksmartcardslot makeSmartCard]; + } + + if (!priv->tksmartcard || ![priv->tksmartcard valid]) + return SC_ERROR_CARD_NOT_PRESENT; + + /* attempt to detect protocol in use T0/T1/RAW */ + ctk_set_proto(reader); + + return SC_SUCCESS; +} + +static int cryptotokenkit_disconnect(sc_reader_t * reader) +{ + struct cryptotokenkit_private_data *priv = reader->drv_data; + + priv->tksmartcard = NULL; + + reader->flags = 0; + return SC_SUCCESS; +} + +static int cryptotokenkit_lock(sc_reader_t *reader) +{ + __block int r = SC_ERROR_NOT_ALLOWED; + struct cryptotokenkit_private_data *priv = reader->drv_data; + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + + LOG_FUNC_CALLED(reader->ctx); + + if (reader->ctx->flags & SC_CTX_FLAG_TERMINATE) + goto err; + + [priv->tksmartcard beginSessionWithReply:^(BOOL success, NSError *error) { + if (success != TRUE) { + NSLog(@"Error locking card <%@>", error); + r = SC_ERROR_UNKNOWN; + } else { + r = SC_SUCCESS; + } + dispatch_semaphore_signal(sema); + }]; + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + +err: + LOG_FUNC_RETURN(reader->ctx, r); +} + +static int cryptotokenkit_unlock(sc_reader_t *reader) +{ + struct cryptotokenkit_private_data *priv = reader->drv_data; + + LOG_FUNC_CALLED(reader->ctx); + + if (reader->ctx->flags & SC_CTX_FLAG_TERMINATE) + return SC_ERROR_NOT_ALLOWED; + + [priv->tksmartcard endSession]; + + LOG_FUNC_RETURN(reader->ctx, SC_SUCCESS); +} + +static int cryptotokenkit_transmit(sc_reader_t *reader, sc_apdu_t *apdu) +{ + size_t ssize = 0; + __block u8 *rbuf = NULL; + __block int r; + __block size_t rsize; + u8 *sbuf = NULL; + struct cryptotokenkit_private_data *priv = reader->drv_data; + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + + LOG_FUNC_CALLED(reader->ctx); + + r = sc_apdu_get_octets(reader->ctx, apdu, &sbuf, &ssize, reader->active_protocol); + if (r != SC_SUCCESS) + goto err; + + if (reader->name) + sc_log(reader->ctx, "reader '%s'", reader->name); + sc_apdu_log(reader->ctx, SC_LOG_DEBUG_NORMAL, sbuf, ssize, 1); + + [priv->tksmartcard transmitRequest: + [NSData dataWithBytes: sbuf length: ssize] + reply:^(NSData *response, NSError *error) { + if (response) { + rsize = [response length]; + rbuf = malloc(rsize); + if (!rbuf) + r = SC_ERROR_OUT_OF_MEMORY; + else + memcpy(rbuf, (unsigned char*) [response bytes], rsize); + } + if (r == SC_SUCCESS) { + if (error) { + NSLog(@"Error transmitting to card <%@>", error); + r = SC_ERROR_TRANSMIT_FAILED; + } else { + r = SC_SUCCESS; + } + } + dispatch_semaphore_signal(sema); + }]; + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + if (r != SC_SUCCESS) + goto err; + + sc_apdu_log(reader->ctx, SC_LOG_DEBUG_NORMAL, rbuf, rsize, 0); + r = sc_apdu_set_resp(reader->ctx, apdu, rbuf, rsize); + +err: + if (sbuf != NULL) { + sc_mem_clear(sbuf, ssize); + free(sbuf); + } + if (rbuf != NULL) { + sc_mem_clear(rbuf, rsize); + free(rbuf); + } + + LOG_FUNC_RETURN(reader->ctx, r); +} + +int cryptotokenkit_use_reader(sc_context_t *ctx, void *pcsc_context_handle, void *pcsc_card_handle) +{ + int r; + struct cryptotokenkit_private_data *priv; + sc_reader_t *reader = NULL; + scconf_block *conf_block = NULL; + TKSmartCardSlot* tksmartcardslot = (__bridge TKSmartCardSlot *)(pcsc_context_handle); + TKSmartCard* tksmartcard = (__bridge TKSmartCard *)(pcsc_card_handle); + const char* utf8String; + + if (!pcsc_context_handle) { + if (!pcsc_card_handle) + return SC_ERROR_INVALID_ARGUMENTS; + tksmartcardslot = tksmartcard.slot; + } + + if ((reader = calloc(1, sizeof(sc_reader_t))) == NULL) { + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + if ((priv = calloc(1, sizeof(struct cryptotokenkit_private_data))) == NULL) { + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + [priv->tksmartcard autorelease]; + priv->tksmartcard = [tksmartcard retain]; + [priv->tksmartcardslot autorelease]; + priv->tksmartcardslot = [tksmartcardslot retain]; + + reader->drv_data = priv; + reader->ops = &cryptotokenkit_ops; + reader->driver = &cryptotokenkit_reader_driver; + utf8String = [tksmartcardslot.name UTF8String]; + if ((reader->name = strdup(utf8String)) == NULL) { + r = SC_ERROR_OUT_OF_MEMORY; + goto err; + } + + /* By testing we found that maxInputLength/maxOutputLength are + * most likely initialized badly. We still take this value as is + * and leave it up to the user to overwrite the reader's + * capabilities */ + reader->max_send_size = tksmartcardslot.maxInputLength; + reader->max_recv_size = tksmartcardslot.maxOutputLength; + + conf_block = sc_get_conf_block(ctx, "reader_driver", "cryptotokenkit", 1); + 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); + } + + /* attempt to detect protocol in use T0/T1/RAW */ + ctk_set_proto(reader); + + r = _sc_add_reader(ctx, reader); + +err: + if (r != SC_SUCCESS) { + free(priv); + if (reader != NULL) { + free(reader->name); + free(reader->vendor); + free(reader); + } + } + + return r; +} + +static int cryptotokenkit_detect_readers(sc_context_t *ctx) +{ + size_t i; + int r; + TKSmartCardSlotManager *mngr = [TKSmartCardSlotManager defaultManager]; + + LOG_FUNC_CALLED(ctx); + + if (!mngr) { + /* com.apple.security.smartcard entitlement is disabled */ + r = SC_ERROR_NOT_ALLOWED; + goto err; + } + + /* temporarily mark all readers as removed */ + for (i=0; i < sc_ctx_get_reader_count(ctx); i++) { + sc_reader_t *reader = sc_ctx_get_reader(ctx, i); + reader->flags |= SC_READER_REMOVED; + } + + sc_log(ctx, "Probing CryptoTokenKit readers"); + + for (NSString *slotName in [mngr slotNames]) { + sc_reader_t *old_reader; + int found = 0; + const char *reader_name = [slotName UTF8String]; + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + + for (i=0; i < sc_ctx_get_reader_count(ctx) && !found; i++) { + old_reader = sc_ctx_get_reader(ctx, i); + if (old_reader == NULL) { + r = SC_ERROR_INTERNAL; + goto err; + } + if (!strcmp(old_reader->name, reader_name)) { + found = 1; + } + } + + /* Reader already available, skip */ + if (found) { + old_reader->flags &= ~SC_READER_REMOVED; + continue; + } + + sc_log(ctx, "Found new CryptoTokenKit reader '%s'", reader_name); + [mngr getSlotWithName:slotName reply:^(TKSmartCardSlot *slot) { + cryptotokenkit_use_reader(ctx, slot, NULL); + dispatch_semaphore_signal(sema); + }]; + dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); + } + + r = SC_SUCCESS; + +err: + LOG_FUNC_RETURN(ctx, r); +} + +struct sc_reader_driver *sc_get_cryptotokenkit_driver(void) +{ + cryptotokenkit_ops.init = cryptotokenkit_init; + cryptotokenkit_ops.finish = NULL; + cryptotokenkit_ops.release = cryptotokenkit_release; + cryptotokenkit_ops.detect_card_presence = cryptotokenkit_detect_card_presence; + cryptotokenkit_ops.connect = cryptotokenkit_connect; + cryptotokenkit_ops.disconnect = cryptotokenkit_disconnect; + cryptotokenkit_ops.lock = cryptotokenkit_lock; + cryptotokenkit_ops.unlock = cryptotokenkit_unlock; + cryptotokenkit_ops.transmit = cryptotokenkit_transmit; + cryptotokenkit_ops.perform_verify = NULL; + cryptotokenkit_ops.perform_pace = NULL; + cryptotokenkit_ops.use_reader = cryptotokenkit_use_reader; + cryptotokenkit_ops.detect_readers = cryptotokenkit_detect_readers; + + return &cryptotokenkit_reader_driver; +} +#endif