From 63ce563d76f0c4003b248654f09dd9b2af1b1069 Mon Sep 17 00:00:00 2001 From: German Blanco Date: Thu, 13 Jun 2013 17:12:07 +0200 Subject: [PATCH] Adding support for DNIe. --- .gitignore | 3 +- configure.ac | 23 + doc/tools/dnie-tool.xml | 98 ++ src/libopensc/Makefile.am | 4 +- src/libopensc/Makefile.mak | 5 +- src/libopensc/card-dnie.c | 2311 ++++++++++++++++++++++++++++++++ src/libopensc/cardctl.h | 9 +- src/libopensc/cards.h | 8 + src/libopensc/ctx.c | 3 + src/libopensc/cwa-dnie.c | 885 ++++++++++++ src/libopensc/cwa-dnie.h | 60 + src/libopensc/cwa14890.c | 2136 +++++++++++++++++++++++++++++ src/libopensc/cwa14890.h | 415 ++++++ src/libopensc/pkcs15-dnie.c | 274 ++++ src/libopensc/pkcs15-syn.c | 8 + src/libopensc/user-interface.c | 317 +++++ src/libopensc/user-interface.h | 66 + src/tools/Makefile.am | 6 +- src/tools/Makefile.mak | 2 +- src/tools/dnie-tool.c | 244 ++++ win32/OpenSC.wxs.in | 4 + 21 files changed, 6874 insertions(+), 7 deletions(-) create mode 100755 doc/tools/dnie-tool.xml create mode 100644 src/libopensc/card-dnie.c create mode 100644 src/libopensc/cwa-dnie.c create mode 100644 src/libopensc/cwa-dnie.h create mode 100644 src/libopensc/cwa14890.c create mode 100644 src/libopensc/cwa14890.h create mode 100644 src/libopensc/pkcs15-dnie.c create mode 100644 src/libopensc/user-interface.c create mode 100644 src/libopensc/user-interface.h create mode 100644 src/tools/dnie-tool.c mode change 100755 => 100644 win32/OpenSC.wxs.in diff --git a/.gitignore b/.gitignore index 57909574..1f5b5bea 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ src/tools/cardos-info src/tools/cryptoflex-tool src/tools/netkey-tool src/tools/pkcs11-tool +src/tools/dnie-tool win32/OpenSC.iss win32/OpenSC.wxs @@ -97,4 +98,4 @@ src/tests/base64 src/tests/lottery src/tests/p15dump src/tests/pintest -src/tests/prngtest \ No newline at end of file +src/tests/prngtest diff --git a/configure.ac b/configure.ac index 2841f610..fa505064 100644 --- a/configure.ac +++ b/configure.ac @@ -168,6 +168,13 @@ AC_ARG_ENABLE( [enable_doc="no"] ) +AC_ARG_ENABLE( + [dnie-ui], + [AS_HELP_STRING([--enable-dnie-ui],[enable use of external user interface program to request DNIe pin@<:@disabled@:>@])], + , + [enable_dnie_ui="no"] +) + AC_ARG_WITH( [xsl-stylesheetsdir], [AS_HELP_STRING([--with-xsl-stylesheetsdir=PATH],[docbook xsl-stylesheets for svn build @<:@detect@:>@])], @@ -326,6 +333,18 @@ if test "${enable_sm}" = "yes"; then AC_DEFINE_UNQUOTED([DEFAULT_SM_MODULE], ["${DEFAULT_SM_MODULE}"], [Default SM module]) fi +if test "${enable_dnie_ui}" = "yes"; then + AC_DEFINE([ENABLE_DNIE_UI], [1], [Enable the use of external user interface program to request DNIe user pin]) + + case "${host}" in + *-apple-*) + if test "${enable_dnie_ui}" = "yes"; then + LDFLAGS="${LDFLAGS} -framework Carbon" + fi + ;; + esac +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 @@ -588,6 +607,7 @@ 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"]) +AM_CONDITIONAL([ENABLE_DNIE_UI], [test "${enable_dnie_ui}" = "yes"]) if test "${enable_pedantic}" = "yes"; then enable_strict="yes"; @@ -601,6 +621,8 @@ if test "$GCC" = "yes"; then CFLAGS="-fno-strict-aliasing ${CFLAGS}" fi +CFLAGS="${CFLAGS} -Werror=declaration-after-statement" + AC_CONFIG_FILES([ Makefile doc/Makefile @@ -652,6 +674,7 @@ CT-API support: ${enable_ctapi} minidriver support: ${enable_minidriver} SM support: ${enable_sm} SM default module: ${DEFAULT_SM_MODULE} +DNIe UI support: ${enable_dnie_ui} Debug file: ${DEBUG_FILE} PC/SC default provider: ${DEFAULT_PCSC_PROVIDER} diff --git a/doc/tools/dnie-tool.xml b/doc/tools/dnie-tool.xml new file mode 100755 index 00000000..ebc1dcc5 --- /dev/null +++ b/doc/tools/dnie-tool.xml @@ -0,0 +1,98 @@ + + + + dnie-tool + 1 + opensc + + + + dnie-tool + displays information about DNIe based security tokens + + + + Synopsis + + dnie-tool [OPTIONS] + + + + + Description + + The dnie-tool utility is used to display additional information about DNIe, the Spanish National eID card. + + + + + Options + + + + + Show the DNIe IDESP value. + + + + + Show DNIe personal information. + Reads and print DNIe number and User Name and SurName + + + + Displays every available information. + This command is equivalent to -d -i -s + + + + + Displays DNIe Serial Number + + + + + Show DNIe sw version. + Displays sofware version for in-card DNIe OS + + + pin, pin + Specify the user pin value to use. + The default is do not enter pin + + + number, number + Specify the reader number to use. + The default is reader 0. + + + number, driver + Specify the reader driver name to use. + Default is use driver from configuration file, or auto-detect if absent + + + + Causes dnie-tool to wait for the token to be inserted into reader. + + + + + Causes dnie-tool to be more verbose. + Specify this flag several times +to enable debug output in the opensc library. + + + + + + + See also + opensc(7) + + + Authors + dnie-tool was written by + Juan Antonio Martinez jonsito@terra.es. + + + diff --git a/src/libopensc/Makefile.am b/src/libopensc/Makefile.am index 69a977db..176713e5 100644 --- a/src/libopensc/Makefile.am +++ b/src/libopensc/Makefile.am @@ -11,7 +11,7 @@ noinst_HEADERS = cards.h ctbcs.h internal.h esteid.h muscle.h muscle-filesystem. cardctl.h asn1.h log.h \ 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 + pace.h cwa14890.h user-interface.h cwa-dnie.h AM_CPPFLAGS = -DOPENSC_CONF_PATH=\"$(sysconfdir)/opensc.conf\" \ -I$(top_srcdir)/src @@ -40,12 +40,14 @@ libopensc_la_SOURCES = \ card-rtecp.c card-westcos.c card-myeid.c card-ias.c \ card-javacard.c card-itacns.c card-authentic.c \ card-iasecc.c iasecc-sdo.c iasecc-sm.c card-sc-hsm.c \ + card-dnie.c cwa14890.c cwa-dnie.c user-interface.c \ \ pkcs15-openpgp.c pkcs15-infocamere.c pkcs15-starcert.c \ pkcs15-tcos.c pkcs15-esteid.c pkcs15-postecert.c pkcs15-gemsafeGPK.c \ pkcs15-actalis.c pkcs15-atrust-acos.c pkcs15-tccardos.c pkcs15-piv.c \ pkcs15-esinit.c pkcs15-westcos.c pkcs15-pteid.c pkcs15-oberthur.c \ pkcs15-itacns.c pkcs15-gemsafeV1.c pkcs15-sc-hsm.c \ + pkcs15-dnie.c \ compression.c p15card-helper.c sm.c \ libopensc.exports if WIN32 diff --git a/src/libopensc/Makefile.mak b/src/libopensc/Makefile.mak index d604c7d5..89bf9b65 100644 --- a/src/libopensc/Makefile.mak +++ b/src/libopensc/Makefile.mak @@ -22,14 +22,15 @@ OBJECTS = \ card-asepcos.obj card-akis.obj card-gemsafeV1.obj card-rutoken.obj \ card-rtecp.obj card-westcos.obj card-myeid.obj card-ias.obj \ card-javacard.obj card-itacns.obj card-authentic.obj \ - card-iasecc.obj iasecc-sdo.obj iasecc-sm.obj \ - card-sc-hsm.obj \ + card-iasecc.obj iasecc-sdo.obj iasecc-sm.obj cwa-dnie.obj cwa14890.obj \ + card-sc-hsm.obj card-dnie.obj user-interface.obj \ \ pkcs15-openpgp.obj pkcs15-infocamere.obj pkcs15-starcert.obj \ pkcs15-tcos.obj pkcs15-esteid.obj pkcs15-postecert.obj pkcs15-gemsafeGPK.obj \ pkcs15-actalis.obj pkcs15-atrust-acos.obj pkcs15-tccardos.obj pkcs15-piv.obj \ pkcs15-esinit.obj pkcs15-westcos.obj pkcs15-pteid.obj pkcs15-oberthur.obj \ pkcs15-itacns.obj pkcs15-gemsafeV1.obj pkcs15-sc-hsm.obj \ + pkcs15-dnie.obj \ compression.obj p15card-helper.obj sm.obj \ $(TOPDIR)\win32\versioninfo.res diff --git a/src/libopensc/card-dnie.c b/src/libopensc/card-dnie.c new file mode 100644 index 00000000..bd07105b --- /dev/null +++ b/src/libopensc/card-dnie.c @@ -0,0 +1,2311 @@ +/** + * card-dnie.c: Support for Spanish DNI electronico (DNIe card). + * + * Copyright (C) 2010 Juan Antonio Martinez + * + * This work is derived from many sources at OpenSC Project site, + * (see references) and the information made public for Spanish + * Direccion General de la Policia y de la Guardia Civil + * + * 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 + */ + +#define __CARD_DNIE_C__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef ENABLE_OPENSSL /* empty file without openssl */ + +#include +#include +#include +#include + +#include "opensc.h" +#include "cardctl.h" +#include "internal.h" +#include "compression.h" +#include "cwa14890.h" +#include "cwa-dnie.h" +#include "user-interface.h" + +#ifdef ENABLE_SM +static int dnie_sm_get_wrapped_apdu(sc_card_t *card, sc_apdu_t *apdu, sc_apdu_t **sm_apdu); +static int dnie_sm_free_and_unwrap_apdu(sc_card_t *card, sc_apdu_t *apdu, sc_apdu_t **sm_apdu); +#endif + +extern cwa_provider_t *dnie_get_cwa_provider(sc_card_t * card); +extern int dnie_read_file( + sc_card_t * card, + const sc_path_t * path, + sc_file_t ** file, + u8 ** buffer, size_t * length); + +#define DNIE_CHIP_NAME "DNIe: Spanish eID card" +#define DNIE_CHIP_SHORTNAME "dnie" +#define DNIE_MF_NAME "Master.File" + +/* default user consent program (if required) */ +#define USER_CONSENT_CMD "/usr/bin/pinentry" + +/** + * SW internal apdu response table. + * + * Override APDU response error codes from iso7816.c to allow + * handling of SM specific error + */ +static struct sc_card_error dnie_errors[] = { + {0x6688, SC_ERROR_SM, "Cryptographic checksum invalid"}, + {0x6987, SC_ERROR_SM, "Expected SM Data Object missing"}, + {0x6988, SC_ERROR_SM, "SM Data Object incorrect"}, + {0, 0, NULL} +}; + +/* + * DNIe ATR info from DGP web page + * +Tag Value Meaning +TS 0x3B Direct Convention +T0 0x7F Y1=0x07=0111; TA1,TB1 y TC1 present. + K=0x0F=1111; 15 historical bytes +TA1 0x38 FI (Factor de conversión de la tasa de reloj) = 744 + DI (Factor de ajuste de la tasa de bits) = 12 + Máximo 8 Mhz. +TB1 0x00 Vpp (voltaje de programación) no requerido. +TC1 0x00 No se requiere tiempo de espera adicional. +H1 0x00 No usado +H2 0x6A Datos de preexpedición. Diez bytes con identificación del expedidor. +H3 0x44 'D' +H4 0x4E 'N' +H5 0x49 'I' +H6 0x65 'e' +H7 Fabricante de la tecnología Match-on-Card incorporada. + 0x10 SAGEM + 0x20 SIEMENS +H8 0x02 Fabricante del CI: STMicroelectronics. +H9 0x4C +H10 0x34 Tipo de CI: 19WL34 +H11 0x01 MSB de la version del SO: 1 +H12 0x1v LSB de la version del SO: 1v +H13 Fase del ciclo de vida . + 0x00 prepersonalización. + 0x01 personalización. + 0x03 usuario. + 0x0F final. +H14 0xss +H15 0xss Bytes de estado + +H13-H15: 0x03 0x90 0x00 user phase: tarjeta operativa +H13-H15: 0x0F 0x65 0x81 final phase: tarjeta no operativa +*/ + +/** + * ATR Table list. + * OpenDNIe defines two ATR's for user and finalized card state + */ +static struct sc_atr_table dnie_atrs[] = { + /* TODO: get ATR for uninitalized DNIe */ + { /** card activated; normal operation state */ + "3B:7F:00:00:00:00:6A:44:4E:49:65:00:00:00:00:00:00:03:90:00", + "FF:FF:00:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:FF:FF:FF", + DNIE_CHIP_SHORTNAME, + SC_CARD_TYPE_DNIE_USER, + 0, + NULL}, + { /** card finalized, unusable */ + "3B:7F:00:00:00:00:6A:44:4E:49:65:00:00:00:00:00:00:0F:65:81", + "FF:FF:00:FF:FF:FF:FF:FF:FF:FF:FF:00:00:00:00:00:00:FF:FF:FF", + DNIE_CHIP_SHORTNAME, + SC_CARD_TYPE_DNIE_TERMINATED, + 0, + NULL}, + {NULL, NULL, NULL, 0, 0, NULL} +}; + +/** + * Messages used on user consent procedures + */ +const char *user_consent_title="Signature Requested"; + +#ifdef linux +const char *user_consent_message="Está a punto de realizar una firma electrónica con su clave de FIRMA del DNI electrónico. ¿Desea permitir esta operación?"; +#else +const char *user_consent_message="Esta a punto de realizar una firma digital\ncon su clave de FIRMA del DNI electronico.\nDesea permitir esta operacion?"; +#endif + +/** + * DNIe specific card driver operations + */ +static struct sc_card_operations dnie_ops; + +/** + * Local copy of iso7816 card driver operations + */ +static struct sc_card_operations *iso_ops = NULL; + +/** + * Module definition for OpenDNIe card driver + */ +static sc_card_driver_t dnie_driver = { + DNIE_CHIP_NAME, /**< Full name for DNIe card driver */ + DNIE_CHIP_SHORTNAME, /**< Short name for DNIe card driver */ + &dnie_ops, /**< pointer to dnie_ops (DNIe card driver operations) */ + dnie_atrs, /**< List of card ATR's handled by this driver */ + 0, /**< (natrs) number of atr's to check for this driver */ + NULL /**< (dll) Card driver module (on DNIe is null) */ +}; + +/************************** card-dnie.c internal functions ****************/ + +/** + * Parse configuration file for dnie parameters. + * + * DNIe card driver has two main paramaters: + * - The name of the user consent Application to be used in Linux. This application shoud be any of pinentry-xxx family + * - A flag to indicate if user consent is to be used in this driver. If false, the user won't be prompted for confirmation on signature operations + * + * @See ../../etc/opensc.conf for details + * @param card Pointer to card structure + * @param ui_context Pointer to ui_context structure to store data into + * @return SC_SUCCESS (should return no errors) + * + * TODO: Code should be revised in order to store user consent info + * in a card-independent way at configuration file + */ +#ifdef ENABLE_DNIE_UI +static int dnie_get_environment( + sc_card_t * card, + ui_context_t * ui_context) +{ + int i; + scconf_block **blocks, *blk; + sc_context_t *ctx; + /* set default values */ + ui_context->user_consent_app = USER_CONSENT_CMD; + ui_context->user_consent_enabled = 1; + /* look for sc block in opensc.conf */ + ctx = card->ctx; + for (i = 0; ctx->conf_blocks[i]; i++) { + blocks = + scconf_find_blocks(ctx->conf, ctx->conf_blocks[i], + "card_driver", "dnie"); + if (!blocks) + continue; + blk = blocks[0]; + free(blocks); + if (blk == NULL) + continue; + /* fill private data with configuration parameters */ + ui_context->user_consent_app = /* def user consent app is "pinentry" */ + (char *)scconf_get_str(blk, "user_consent_app", + USER_CONSENT_CMD); + ui_context->user_consent_enabled = /* user consent is enabled by default */ + scconf_get_bool(blk, "user_consent_enabled", 1); + } + return SC_SUCCESS; +} +#endif + +/************************** cardctl defined operations *******************/ + +/** + * Generate a public/private key pair. + * + * Manual says that generate_keys() is a reserved operation; that is: + * only can be done at DGP offices. But several authors talk about + * this operation is available also outside. So need to test :-) + * Notice that write operations are not supported, so we can't use + * created keys to generate and store new certificates into the card. + * TODO: copy code from card-jcop.c::jcop_generate_keys() + * @param card pointer to card info data + * @param data where to store function results + * @return SC_SUCCESS if ok, else error code + */ +static int dnie_generate_key(sc_card_t * card, void *data) +{ + int result = SC_ERROR_NOT_SUPPORTED; + if ((card == NULL) || (data == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + /* TODO: write dnie_generate_key() */ + LOG_FUNC_RETURN(card->ctx, result); +} + +/** + * Analyze a buffer looking for provided data pattern. + * + * Comodity function for dnie_get_info() that searches a byte array + * in provided buffer + * + * @param card pointer to card info data + * @param pat data pattern to find in buffer + * @param buf where to look for pattern + * @param len buffer length + * @return retrieved value or NULL if pattern not found + * @see dnie_get_info() + */ +static char *findPattern(u8 *pat, u8 *buf, size_t len) +{ + char *res = NULL; + u8 *from = buf; + int size = 0; + /* Locate pattern. Assume pattern length=6 */ + for ( from = buf; from < buf+len-6; from++) { + if (memcmp(from,pat,6) == 0 ) goto data_found; + } + /* arriving here means pattern not found */ + return NULL; + +data_found: + /* assume length is less than 128 bytes, so is coded in 1 byte */ + size = 0x000000ff & (int) *(from+6); + if ( size == 0 ) return NULL; /* empty data */ + res = calloc( size+1, sizeof(char) ); + if ( res == NULL) return NULL; /* calloc() error */ + memcpy(res,from+7,size); + return res; +} + +/** + * Retrieve name, surname, and DNIe number. + * + * This is done by mean of reading and parsing CDF file + * at address 3F0050156004 + * No need to enter pin nor use Secure Channel + * + * Notice that this is done by mean of a dirty trick: instead + * of parsing ASN1 data on EF(CDF), + * we look for desired OID patterns in binary array + * + * @param card pointer to card info data + * @param data where to store function results (number,name,surname,idesp,version) + * @return SC_SUCCESS if ok, else error code + */ +static int dnie_get_info(sc_card_t * card, char *data[]) +{ + sc_file_t *file = NULL; + sc_path_t *path = NULL; + u8 *buffer = NULL; + size_t bufferlen = 0; + char *msg = NULL; + u8 SerialNumber [] = { 0x06, 0x03, 0x55, 0x04, 0x05, 0x13 }; + u8 Name [] = { 0x06, 0x03, 0x55, 0x04, 0x04, 0x0C }; + u8 GivenName [] = { 0x06, 0x03, 0x55, 0x04, 0x2A, 0x0C }; + int res = SC_ERROR_NOT_SUPPORTED; + + if ((card == NULL) || (data == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + + /* phase 1: get DNIe number, Name and GivenName */ + + /* read EF(CDF) at 3F0050156004 */ + path = (sc_path_t *) calloc(1, sizeof(sc_path_t)); + if (!path) { + msg = "Cannot allocate path data for EF(CDF) read"; + res = SC_ERROR_OUT_OF_MEMORY; + goto get_info_end; + } + sc_format_path("3F0050156004", path); + res = dnie_read_file(card, path, &file, &buffer, &bufferlen); + if (res != SC_SUCCESS) { + msg = "Cannot read EF(CDF)"; + goto get_info_end; + } + /* locate OID 2.5.4.5 (SerialNumber) - DNIe number*/ + data[0]= findPattern(SerialNumber,buffer,bufferlen); + /* locate OID 2.5.4.4 (Name) - Apellidos */ + data[1]= findPattern(Name,buffer,bufferlen); + /* locate OID 2.5.4.42 (GivenName) - Nombre */ + data[2]= findPattern(GivenName,buffer,bufferlen); + if ( ! data[0] || !data[1] || !data[2] ) { + res = SC_ERROR_INVALID_DATA; + msg = "Cannot retrieve info from EF(CDF)"; + goto get_info_end; + } + + /* phase 2: get IDESP */ + sc_format_path("3F000006", path); + if (file) { + sc_file_free(file); + file = NULL; + } + if (buffer) { + free(buffer); + buffer=NULL; + bufferlen=0; + } + res = dnie_read_file(card, path, &file, &buffer, &bufferlen); + if (res != SC_SUCCESS) { + msg = "Cannot read IDESP EF"; + data[3]=NULL; + goto get_info_ph3; + } + data[3]=calloc(bufferlen+1,sizeof(char)); + if ( !data[3] ) { + msg = "Cannot allocate memory for IDESP data"; + res = SC_ERROR_OUT_OF_MEMORY; + goto get_info_end; + } + memcpy(data[3],buffer,bufferlen); + +get_info_ph3: + /* phase 3: get DNIe software version */ + sc_format_path("3F002F03", path); + if (file) { + sc_file_free(file); + file = NULL; + } + if (buffer) { + free(buffer); + buffer=NULL; + bufferlen=0; + } + /* + * Some old DNIe cards seems not to include SW version file, + * so let this code fail without notice + */ + res = dnie_read_file(card, path, &file, &buffer, &bufferlen); + if (res != SC_SUCCESS) { + msg = "Cannot read DNIe Version EF"; + data[4]=NULL; + res = SC_SUCCESS; /* let function return successfully */ + goto get_info_end; + } + data[4]=calloc(bufferlen+1,sizeof(char)); + if ( !data[4] ) { + msg = "Cannot allocate memory for DNIe Version data"; + res = SC_ERROR_OUT_OF_MEMORY; + goto get_info_end; + } + memcpy(data[4],buffer,bufferlen); + + /* arriving here means ok */ + res = SC_SUCCESS; + msg = NULL; + +get_info_end: + if (file) { + sc_file_free(file); + free(buffer); + file = NULL; + buffer = NULL; + bufferlen = 0; + } + if (msg) + sc_log(card->ctx,msg); + LOG_FUNC_RETURN(card->ctx, res); +} + +/** + * Retrieve serial number (7 bytes) from card. + * + * This is done by mean of an special APDU command described + * in the DNIe Reference Manual + * + * @param card pointer to card description + * @param serial where to store data retrieved + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_serialnr(sc_card_t * card, sc_serial_number_t * serial) +{ + int result; + sc_apdu_t apdu; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; + if ((card == NULL) || (card->ctx == NULL) || (serial == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + + LOG_FUNC_CALLED(card->ctx); + if (card->type != SC_CARD_TYPE_DNIE_USER) + return SC_ERROR_NOT_SUPPORTED; + /* if serial number is cached, use it */ + if (card->serialnr.len) { + memcpy(serial, &card->serialnr, sizeof(*serial)); + sc_log(card->ctx, "Serial Number (cached): '%s'", + sc_dump_hex(serial->value, serial->len)); + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); + } + /* not cached, retrieve it by mean of an APDU */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xb8, 0x00, 0x00); + apdu.cla = 0x90; /* propietary cmd */ + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); + /* official driver read 0x11 bytes, but only uses 7. Manual says just 7 */ + apdu.le = 0x07; + apdu.lc = 0; + apdu.datalen = 0; + /* send apdu */ + result = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(card->ctx, result, "APDU transmit failed"); + if (apdu.sw1 != 0x90 || apdu.sw2 != 0x00) + return SC_ERROR_INTERNAL; + /* cache serial number */ + memcpy(card->serialnr.value, apdu.resp, 7 * sizeof(u8)); + card->serialnr.len = 7 * sizeof(u8); + /* TODO: fill Issuer Identification Number data with proper (ATR?) info */ + /* + card->serialnr.iin.mii=; + card->serialnr.iin.country=; + card->serialnr.iin.issuer_id=; + */ + /* copy and return serial number */ + memcpy(serial, &card->serialnr, sizeof(*serial)); + sc_log(card->ctx, "Serial Number (apdu): '%s'", + sc_dump_hex(serial->value, serial->len)); + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +static void dnie_clear_cache(dnie_private_data_t * data) +{ + if (data == NULL) return; + if (data->cache != NULL) + free(data->cache); + data->cache = NULL; + data->cachelen = 0; +} + +static inline void init_flags(struct sc_card *card) +{ + unsigned long algoflags; + /* set up flags according documentation */ + card->name = DNIE_CHIP_SHORTNAME; + card->cla = 0x00; /* default APDU class (interindustry) */ + card->caps |= SC_CARD_CAP_RNG; /* we have a random number generator */ + card->max_send_size = (255 - 12); /* manual says 255, but we need 12 extra bytes when encoding */ + card->max_recv_size = 255; + + algoflags = SC_ALGORITHM_RSA_RAW; /* RSA support */ + algoflags |= SC_ALGORITHM_RSA_HASH_NONE; + _sc_card_add_rsa_alg(card, 1024, algoflags, 0); + _sc_card_add_rsa_alg(card, 2048, algoflags, 0); +} + +/**************************** sc_card_operations **********************/ + +/* Generic operations */ + +/** + * Check if provided card can be handled by OpenDNIe. + * + * Called in sc_connect_card(). Must return 1, if the current + * card can be handled with this driver, or 0 otherwise. ATR + * field of the sc_card struct is filled in before calling + * this function. + * do not declare static, as used by pkcs15-dnie module + * + * @param card Pointer to card structure + * @return on card matching 0 if not match; negative return means error + */ +int dnie_match_card(struct sc_card *card) +{ + int result = 0; + int matched = -1; + LOG_FUNC_CALLED(card->ctx); + matched = _sc_match_atr(card, dnie_atrs, &card->type); + result = (matched >= 0) ? 1 : 0; + LOG_FUNC_RETURN(card->ctx, result); +} + +/** + * OpenDNIe card structures initialization. + * + * Called when ATR of the inserted card matches an entry in ATR + * table. May return SC_ERROR_INVALID_CARD to indicate that + * the card cannot be handled with this driver. + * + * @param card Pointer to card structure + * @return SC_SUCCES if ok; else error code + */ +static int dnie_init(struct sc_card *card) +{ + int res = SC_SUCCESS; + sc_context_t *ctx = card->ctx; + cwa_provider_t *provider = NULL; + + LOG_FUNC_CALLED(ctx); + + /* if recognized as terminated DNIe card, return error */ + if (card->type == SC_CARD_TYPE_DNIE_TERMINATED) + LOG_TEST_RET(card->ctx, SC_ERROR_INVALID_CARD, "DNIe card is terminated."); + + /* create and initialize cwa-dnie provider*/ + provider = dnie_get_cwa_provider(card); + if (!provider) + LOG_TEST_RET(card->ctx, SC_ERROR_INTERNAL, "Error initializing cwa-dnie provider"); + +#ifdef ENABLE_SM + /** Secure messaging initialization section **/ + memset(&(card->sm_ctx), 0, sizeof(sm_context_t)); + /* setup dnie sm driver *im*properly */ + /* TODO: at the moment this is a wild guess, based on card-authentic.c */ + card->sm_ctx.ops.get_sm_apdu = NULL; /*dnie_sm_get_wrapped_apdu;*/ + card->sm_ctx.ops.free_sm_apdu = NULL; /*dnie_sm_free_and_unwrap_apdu;*/ +#endif + + init_flags(card); + +#ifdef ENABLE_SM + res=cwa_create_secure_channel(card,provider,CWA_SM_OFF); + LOG_TEST_RET(card->ctx, res, "Failure creating CWA secure channel."); +#endif + + /* initialize private data */ + card->drv_data = calloc(1, sizeof(dnie_private_data_t)); + if (card->drv_data == NULL) + LOG_TEST_RET(card->ctx, SC_ERROR_OUT_OF_MEMORY, "Could not allocate DNIe private data."); + +#ifdef ENABLE_DNIE_UI + /* read environment from configuration file */ + res = dnie_get_environment(card, &(GET_DNIE_UI_CTX(card))); + if (res != SC_SUCCESS) { + free(card->drv_data); + LOG_TEST_RET(card->ctx, res, "Failure reading DNIe environment."); + } +#endif + + GET_DNIE_PRIV_DATA(card)->cwa_provider = provider; + + LOG_FUNC_RETURN(card->ctx, res); +} + +/** + * De-initialization routine. + * + * Called when the card object is being freed. finish() has to + * deallocate all possible private data. + * + * @param card Pointer to card driver data structure + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_finish(struct sc_card *card) +{ + int result = SC_SUCCESS; + LOG_FUNC_CALLED(card->ctx); + dnie_clear_cache(GET_DNIE_PRIV_DATA(card)); +#ifdef ENABLE_SM + /* disable sm channel if established */ + result = cwa_create_secure_channel(card, GET_DNIE_PRIV_DATA(card)->cwa_provider, CWA_SM_OFF); +#endif + if (card->drv_data != NULL) + free(card->drv_data); + LOG_FUNC_RETURN(card->ctx, result); +} + +/* ISO 7816-4 functions */ + +/** + * Convert little-endian data into unsigned long. + * + * @param pt pointer to little-endian data + * @return equivalent long + */ +static unsigned long le2ulong(u8 * pt) +{ + unsigned long res = 0L; + if (pt==NULL) return res; + res = (0xff & *(pt + 0)) + + ((0xff & *(pt + 1)) << 8) + + ((0xff & *(pt + 2)) << 16) + ((0xff & *(pt + 3)) << 24); + return res; +} + +/** + * Uncompress data if in compressed format. + * + * @param card poiner to sc_card_t structure + * @param from buffer to get data from + * @param len pointer to buffer length + * @return uncompresed or original buffer; len points to new buffer length + * on error return null + */ +static u8 *dnie_uncompress(sc_card_t * card, u8 * from, size_t *len) +{ + int res = SC_SUCCESS; + u8 *upt = from; + size_t uncompressed = 0L; + size_t compressed = 0L; + +#ifdef ENABLE_ZLIB + if (!card || !card->ctx || !from || !len) + return NULL; + LOG_FUNC_CALLED(card->ctx); + + /* if data size not enought for compression header assume uncompressed */ + if (*len < 8) + goto compress_exit; + /* evaluate compressed an uncompressed sizes (little endian format) */ + uncompressed = le2ulong(from); + compressed = le2ulong(from + 4); + /* if compressed size doesn't match data length assume not compressed */ + if (compressed != (*len) - 8) + goto compress_exit; + /* if compressed size greater than uncompressed, assume uncompressed data */ + if (uncompressed < compressed) + goto compress_exit; + + sc_log(card->ctx, "Data seems to be compressed. calling uncompress"); + /* ok: data seems to be compressed */ + upt = calloc(uncompressed, sizeof(u8)); + if (!upt) { + sc_log(card->ctx, "alloc() for uncompressed buffer failed"); + return NULL; + } + res = sc_decompress(upt, /* try to uncompress by calling sc_xx routine */ + (size_t *) & uncompressed, + from + 8, (size_t) compressed, COMPRESSION_ZLIB); + /* TODO: check that returned uncompressed size matches expected */ + if (res != SC_SUCCESS) { + sc_log(card->ctx, "Uncompress() failed or data not compressed"); + goto compress_exit; /* assume not need uncompression */ + } + /* Done; update buffer len and return pt to uncompressed data */ + *len = uncompressed; + sc_log(card->ctx, "Compressed data:\n%s\n", + sc_dump_hex(from + 8, compressed)); + sc_log(card->ctx, "Uncompress() done. Before:'%lu' After: '%lu'", + compressed, uncompressed); + sc_log(card->ctx, "Uncompressed data:\n%s\n", + sc_dump_hex(upt, uncompressed)); + compress_exit: + +#endif + + sc_log(card->ctx, "uncompress: returning with%s de-compression ", + (upt == from) ? "out" : ""); + return upt; +} + +/** + * Fill file cache for read_binary() operation. + * + * Fill a temporary buffer by mean of consecutive calls to read_binary() + * until card sends eof + * + * DNIe card stores user certificates in compressed format. so we need + * some way to detect and uncompress on-the-fly compressed files, to + * let read_binary() work transparently. + * This is the main goal of this routine: create an in-memory buffer + * for read_binary operation, filling this buffer on first read_binary() + * call, and uncompress data if compression detected. Further + * read_binary() calls then make use of cached data, instead + * of accessing the card + * + * @param card Pointer to card structure + * @return SC_SUCCESS if OK; else error code + */ +static int dnie_fill_cache(sc_card_t * card) +{ + u8 tmp[SC_MAX_APDU_BUFFER_SIZE]; + sc_apdu_t apdu; + size_t count = 0; + size_t len = 0; + u8 *buffer = NULL; + u8 *pt = NULL; + sc_context_t *ctx = NULL; + + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + + LOG_FUNC_CALLED(ctx); + + /* mark cache empty */ + dnie_clear_cache(GET_DNIE_PRIV_DATA(card)); + + /* initialize apdu */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xB0, 0x00, 0x00); + + /* try to read_binary while data available but never long than 32767 */ + count = card->max_recv_size; + for (len = 0; len < 0x7fff;) { + int r = SC_SUCCESS; + /* fill apdu */ + apdu.p1 = 0xff & (len >> 8); + apdu.p2 = 0xff & len; + apdu.le = count; + apdu.resplen = count; + apdu.resp = tmp; + /* transmit apdu */ + r = dnie_transmit_apdu(card, &apdu); + if (r != SC_SUCCESS) { + if (buffer) + free(buffer); + sc_log(ctx, "read_binary() APDU transmit failed"); + LOG_FUNC_RETURN(ctx, r); + } + if (apdu.resplen == 0) { + /* on no data received, check if requested len is longer than + available data in card. If so, ask just for remaining data */ + r = sc_check_sw(card, apdu.sw1, apdu.sw2); + if (r == SC_ERROR_WRONG_LENGTH) { + count = 0xff & apdu.sw2; + if (count != 0) + continue; /* read again with correct size */ + goto read_done; /* no more data to read */ + } + if (r == SC_ERROR_INCORRECT_PARAMETERS) + goto read_done; + LOG_FUNC_RETURN(ctx, r); /* arriving here means response error */ + } + /* copy received data into buffer. realloc() if not enought space */ + count = apdu.resplen; + buffer = realloc(buffer, len + count); + if (!buffer) + LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); + memcpy(buffer + len, apdu.resp, count); + len += count; + if (count != card->max_recv_size) + goto read_done; + } + + read_done: + /* no more data to read: check if data is compressed */ + pt = dnie_uncompress(card, buffer, &len); + if (pt == NULL) { + sc_log(ctx, "Uncompress proccess failed"); + if (buffer) + free(buffer); + LOG_FUNC_RETURN(ctx, SC_ERROR_INTERNAL); + } + if (pt != buffer) + if (buffer) + free(buffer); + + /* ok: as final step, set correct cache data into dnie_priv structures */ + GET_DNIE_PRIV_DATA(card)->cache = pt; + GET_DNIE_PRIV_DATA(card)->cachelen = len; + sc_log(ctx, "fill_cache() done. length '%d' bytes", len); + LOG_FUNC_RETURN(ctx,len); +} + +/** + * OpenDNIe implementation of read_binary(). + * + * Reads a binary stream from card by mean of READ BINARY iso command + * Creates and handle a cache to allow data uncompression + * + * @param card pointer to sc_card_t structure + * @param idx offset from card file to ask data for + * @param buf where to store readed data. must be non null + * @param count number of bytes to read + * @param flags. not used + * @return number of bytes readed, 0 on EOF, error code on error + */ +static int dnie_read_binary(struct sc_card *card, + unsigned int idx, + u8 * buf, size_t count, unsigned long flags) +{ + int res = 0; + sc_context_t *ctx = NULL; + /* preliminary checks */ + if (!card || !card->ctx || !buf || (count <= 0)) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + + LOG_FUNC_CALLED(ctx); + if (idx == 0 || GET_DNIE_PRIV_DATA(card)->cache == NULL) { + /* on first block or no cache, try to fill */ + res = dnie_fill_cache(card); + if (res < 0) { + sc_log(ctx, + "Cannot fill cache. using iso_read_binary()"); + return iso_ops->read_binary(card, idx, buf, count, + flags); + } + } + if (idx >= GET_DNIE_PRIV_DATA(card)->cachelen) + return 0; /* at eof */ + res = MIN(count, GET_DNIE_PRIV_DATA(card)->cachelen - idx); /* eval how many bytes to read */ + memcpy(buf, GET_DNIE_PRIV_DATA(card)->cache + idx, res); /* copy data from buffer */ + sc_log(ctx, "dnie_read_binary() '%d' bytes", res); + LOG_FUNC_RETURN(ctx, res); +} + +/** + * Invalidate pathfile cache. + * + * Marks cache path invalid, so next select_file() will traverse + * the entire card filesystem + * + * @param card pointer to card structure + */ +static inline void dnie_invalidate_path(sc_card_t *card) { + memset(&card->cache, 0, sizeof(card->cache)); + card->cache.valid = 0; +} + +/** + * Tracks current path to avoid extra filesystem operation. + * + * Tracks selected DF's to let card know their current working directory + * + * TODO: use common opensc file cache structure and functions + * + * @param card card pointer structure + * @param file current DF to be cached + */ +static int dnie_cache_path(sc_card_t *card, struct sc_file *file) +{ + u8 path[] = {0x00,0x00}; + LOG_FUNC_CALLED(card->ctx); + path[0]=(u8) (0xff & (file->id >>8)); + path[1]=(u8) (0xff & (file->id >>0)); + if (path[0]==0x3F && path[1]==0x00) { + /* if absolute path, just copy data */ + dnie_invalidate_path(card); + card->cache.current_path.value[0]=path[0]; + card->cache.current_path.value[1]=path[1]; + card->cache.current_path.len=2; + } else { + /* if relative path add to current */ + size_t curlen=card->cache.current_path.len; + card->cache.current_path.value[curlen+0] =path[0]; + card->cache.current_path.value[curlen+1] =path[1]; + card->cache.current_path.len += 2; + } + card->cache.current_path.type=SC_PATH_TYPE_PATH; + card->cache.valid=1; + LOG_FUNC_RETURN(card->ctx,SC_SUCCESS); +} + +/** + * Check proposed path against current (cached) one. + * + * This code compares proposed path to stored one, evaluating required path + * ID to be selected if finally select_file() is required, + * + * @param card card pointer structure + * @param pathptr pointer to proposed path + * @param pathlen len of proposed path + * @param need_info set if process_fci is needed + * @return 1 on match; 0 on fail + */ +static int dnie_check_path(sc_card_t *card, u8 **pathptr, size_t *pathlen, + int need_info) +{ + u8 *cacheptr = card->cache.current_path.value; + size_t cachelen = card->cache.current_path.len; + size_t len = *pathlen; + u8 *ptr = *pathptr; + int hit=1; + if (card->cache.valid==0) hit = 0; /* no valid cache */ + if (cachelen < 2) hit = 0; /* no data cached */ + if (len < 2) hit = 0; /* no proposed path */ + if (len, if not NULL. + * + * SELECT file in DNIe is a bit tricky: + * - only handles some types: + * -- SC_PATH_TYPE_FILE_ID 2-byte long file ID + * -- SC_PATH_TYPE_DF_NAME named DF's + * -- SC_PATH_TYPE_PARENT jump to parent DF of current EF/DF - undocummented in DNIe manual + * -- other file types are marked as unssupported + * + * - Also MF must be addressed by their Name, not their ID + * So some magic is needed: + * - split SC_PATH_TYPE_PATH into several calls to each 2-byte data file ID + * - Translate initial file id 3F00 to be DF name 'Master.File' + * + * Also, Response always handle a proprietary FCI info, so + * need to handle it manually via dnie_process_fci() + * + * @param card Pointer to Card Structure + * @param in_path Path ID to be selected + * @param file_out where to store fci information + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_select_file(struct sc_card *card, + const struct sc_path *in_path, + struct sc_file **file_out) +{ + + u8 buf[SC_MAX_APDU_BUFFER_SIZE]; + u8 pathbuf[SC_MAX_PATH_SIZE]; + char pbuf[SC_MAX_PATH_STRING_SIZE]; + u8 *path = pathbuf; + size_t pathlen; + int cached=0; + + sc_file_t *file = NULL; + int res = SC_SUCCESS; + sc_apdu_t apdu; + sc_context_t *ctx = NULL; + + if (!card || !card->ctx || !in_path) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + + LOG_FUNC_CALLED(ctx); + + memcpy(path, in_path->value, in_path->len); + pathlen = in_path->len; + + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0xA4, 0, 0); + + switch (in_path->type) { + case SC_PATH_TYPE_FILE_ID: + /* pathlen must be of len=2 */ + /* + * gscriptor shows that DNIe also handles + * Select child DF (p1=1) and Select EF (p1=2), + * but we'll use P1=0 as general solution for all cases + * + * According iso7816-4 sect 7.1.1 pathlen==0 implies + * select MF, but this case is not supported by DNIe + */ + if (pathlen != 2) + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + sc_log(ctx, "select_file(ID): %s", sc_dump_hex(path, pathlen)); + apdu.p1 = 0; + break; + case SC_PATH_TYPE_DF_NAME: + sc_log(ctx, "select_file(NAME): %s", + sc_dump_hex(path, pathlen)); + apdu.p1 = 4; + break; + case SC_PATH_TYPE_PATH: + if ((pathlen & 1) != 0) /* not divisible by 2 */ + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + + /* evaluate current patch from cache */ + res = sc_path_print(pbuf, sizeof(pbuf), &card->cache.current_path); + if (res != SC_SUCCESS) pbuf[0] = '\0'; + sc_log(ctx, "select_file(PATH): requested:%s cached:%s", + sc_dump_hex(path, pathlen),pbuf); + + /* check pathfile cache + * cached returns true if: + * - path matches cache + * - path starts with cache + * remember that only DF's are cached + */ + cached = dnie_check_path(card, &path, &pathlen, file_out != NULL); + if (pathlen == 0) { + /* request to select_file on current df */ + sc_log(ctx,"Cache hit: already on cached DF"); + LOG_FUNC_RETURN(ctx,SC_SUCCESS); + } + + /* convert to SC_PATH_TYPE_FILE_ID */ + res = sc_lock(card); /* lock to ensure path traversal */ + LOG_TEST_RET(ctx, res, "sc_lock() failed"); + while (pathlen > 0) { + sc_path_t tmpp; + if ( memcmp(path, "\x3F\x00", 2) == 0) { + /* if MF, use their name as path */ + tmpp.type = SC_PATH_TYPE_DF_NAME; + strcpy((char *)tmpp.value, DNIE_MF_NAME); + tmpp.len = sizeof(DNIE_MF_NAME) - 1; + } else { + /* else use 2-byte file id */ + tmpp.type = SC_PATH_TYPE_FILE_ID; + tmpp.value[0] = path[0]; + tmpp.value[1] = path[1]; + tmpp.len = 2; + } + /* recursively call to select_file */ + res = card->ops->select_file(card, &tmpp, file_out); + if (res != SC_SUCCESS) { + sc_unlock(card); + sc_log(ctx,"select_file(PATH) failed"); + LOG_FUNC_RETURN(ctx,res); + } + pathlen -= 2; + path += 2; + } + sc_unlock(card); + LOG_FUNC_RETURN(ctx, SC_SUCCESS); + break; + case SC_PATH_TYPE_FROM_CURRENT: + LOG_FUNC_RETURN(ctx, SC_ERROR_NO_CARD_SUPPORT); + case SC_PATH_TYPE_PARENT: + /* Hey!! Manual doesn't says anything on this, but + * gscriptor shows that this type is supported + */ + sc_log(ctx, "select_file(PARENT)"); + /* according iso7816-4 sect 7.1.1 shouldn't have any parameters */ + if (pathlen != 0) + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + apdu.cse= SC_APDU_CASE_1; + apdu.p1 = 3; + break; + default: + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + break; + } + /* Arriving here means need to compose and send apdu */ + apdu.p2 = 0; /* first record, return FCI */ + apdu.lc = pathlen; + apdu.data = path; + apdu.datalen = pathlen; + + if (file_out != NULL) { + apdu.resp = buf; + apdu.resplen = sizeof(buf); + apdu.le = card->max_recv_size > 0 ? card->max_recv_size : 256; + } else { + apdu.cse = + (apdu.lc == 0) ? SC_APDU_CASE_1 : SC_APDU_CASE_3_SHORT; + } + res = dnie_transmit_apdu(card, &apdu); + if (res!=SC_SUCCESS) + dnie_invalidate_path(card); /* failed: invalidate cache */ + LOG_TEST_RET(ctx, res, "SelectFile() APDU transmit failed"); + if (file_out == NULL) { + if (apdu.sw1 == 0x61) + SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_VERBOSE, 0); + SC_FUNC_RETURN(ctx, SC_LOG_DEBUG_VERBOSE, + sc_check_sw(card, apdu.sw1, apdu.sw2)); + } + + /* analyze response. if FCI, try to parse */ + res = sc_check_sw(card, apdu.sw1, apdu.sw2); + LOG_TEST_RET(ctx, res, "SelectFile() check_sw failed"); + if (apdu.resplen < 2) + LOG_FUNC_RETURN(ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED); + if (apdu.resp[0] == 0x00) /* proprietary coding */ + LOG_FUNC_RETURN(ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED); + + /* finally process FCI response */ + file = sc_file_new(); + if (file == NULL) + LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); + if (!card->ops->process_fci) { /* hey! DNIe MUST have process_fci */ + if (file) + sc_file_free(file); + LOG_FUNC_RETURN(ctx, SC_ERROR_INTERNAL); + } + res = card->ops->process_fci(card, file, apdu.resp + 2, apdu.resp[1]); + *file_out = file; + /* if file is a DF, store it into DF cache */ + if (file->type==SC_FILE_TYPE_DF) dnie_cache_path(card,file); + /* as last step clear data cache and return */ + dnie_clear_cache(GET_DNIE_PRIV_DATA(card)); + LOG_FUNC_RETURN(ctx, res); +} + +/** + * OpenDNIe implementation of Get_Challenge() command. + * + * Get challenge: retrieve 8 random bytes for any further use + * (eg perform an external authenticate command) + * + * NOTE: + * Official driver redundantly sets SM before execute this command + * No reason to do it, as is needed to do SM handshake... + * Also: official driver reads in blocks of 20 bytes. + * Why? Manual and iso-7816-4 states that only 8 bytes + * are required... so we will obbey Manual + * + * @param card Pointer to card Structure + * @param rnd Where to store challenge + * @param len requested challenge length + * @return SC_SUCCESS if OK; else error code + */ +static int dnie_get_challenge(struct sc_card *card, u8 * rnd, size_t len) +{ + sc_apdu_t apdu; + u8 buf[10]; + int result = SC_SUCCESS; + if ((card == NULL) || (card->ctx == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + /* just a copy of iso7816::get_challenge() but call dnie_check_sw to + * look for extra error codes */ + if ( (rnd==NULL) || (len==0) ) { + /* no valid buffer provided */ + result = SC_ERROR_INVALID_ARGUMENTS; + goto dnie_get_challenge_error; + } + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0x84, 0x00, 0x00); + apdu.le = 8; + apdu.resp = buf; + apdu.resplen = 8; /* include SW's */ + + /* + * As DNIe cannot handle other data length than 0x08 and 0x14, + * perform consecutive reads of 8 bytes until retrieve requested length + */ + while (len > 0) { + size_t n = len > 8 ? 8 : len; + result = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(card->ctx, result, "APDU transmit failed"); + if (apdu.resplen != 8) { + result = sc_check_sw(card, apdu.sw1, apdu.sw2); + goto dnie_get_challenge_error; + } + memcpy(rnd, apdu.resp, n); + len -= n; + rnd += n; + } + result = SC_SUCCESS; + dnie_get_challenge_error: + LOG_FUNC_RETURN(card->ctx, result); +} + +/* + * ISO 7816-8 functions + */ + +/** + * OpenDNIe implementation of Logout() card_driver function. + * + * Resets all access rights that were gained. Disable SM + * + * @param card Pointer to Card Structure + * @return SC_SUCCESS if OK; else error code + */ +static int dnie_logout(struct sc_card *card) +{ + int result = SC_SUCCESS; + + if ((card == NULL) || (card->ctx == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); +#ifdef ENABLE_SM + /* disable and free any sm channel related data */ + result = + cwa_create_secure_channel(card, GET_DNIE_PRIV_DATA(card)->cwa_provider, CWA_SM_OFF); +#endif + /* TODO: _logout() see comments.txt on what to do here */ + LOG_FUNC_RETURN(card->ctx, result); +} + +/** + * Implementation of Set_Security_Environment card driver command. + * + * Initializes the security environment on card + * according to , and stores the environment as on the + * card. If se_num <= 0, the environment will not be stored. + * Notice that OpenDNIe SM handling requires a buffer longer than + * provided for this command; so special apdu is used in cwa code + * + * @param card Pointer to card driver Structure + * @param env Pointer to security environment data + * @param num: which Card Security environment to use (ignored in OpenDNIe) + * @return SC_SUCCESS if OK; else error code + * + * TODO: mix these code with SM set_security_env operations + * + */ +static int dnie_set_security_env(struct sc_card *card, + const struct sc_security_env *env, int se_num) +{ + sc_apdu_t apdu; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; /* buffer to compose apdu data */ + u8 *p = sbuf; + int result = SC_SUCCESS; + if ((card == NULL) || (card->ctx == NULL) || (env == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + if (se_num!=0) { + sc_log(card->ctx,"DNIe cannot handle several security envs"); + LOG_FUNC_RETURN(card->ctx,SC_ERROR_INVALID_ARGUMENTS); + } + + /* Secure Channel should be on here, if not means an error */ + /* + result = + cwa_create_secure_channel(card, dnie_priv.provider, CWA_SM_WARM); + LOG_TEST_RET(card->ctx, result, + "set_security_env(); Cannot establish SM"); + */ + + /* check for algorithms */ + if (env->flags & SC_SEC_ENV_ALG_REF_PRESENT) { + sc_log(card->ctx, "checking algorithms"); + switch (env->algorithm) { + case SC_ALGORITHM_RSA: + result = SC_SUCCESS; + break; + case SC_ALGORITHM_DSA: + case SC_ALGORITHM_EC: + case SC_ALGORITHM_GOSTR3410: + default: + result = SC_ERROR_NOT_SUPPORTED; + break; + } + LOG_TEST_RET(card->ctx, result, "Unsupported algorithm"); + if ((env->algorithm_flags & SC_ALGORITHM_RSA_HASH_SHA1) == 0) { + result = SC_ERROR_NOT_SUPPORTED; + /* TODO: + * Manual says that only RSA with SHA1 is supported, but found + * some docs where states that SHA256 is also handled + */ + } + LOG_TEST_RET(card->ctx, result, + "Only RSA with SHA1 is supported"); + /* ok: insert algorithm reference into buffer */ + *p++ = 0x80; /* algorithm reference tag */ + *p++ = 0x01; /* len */ + *p++ = env->algorithm_ref & 0xff; /* val */ + } + + /* check for key references */ + if (env->flags & SC_SEC_ENV_KEY_REF_PRESENT) { + sc_log(card->ctx, "checking key references"); + if (env->key_ref_len != 1) { + sc_log(card->ctx, "Null or invalid key ID reference"); + result = SC_ERROR_INVALID_ARGUMENTS; + } + sc_log(card->ctx, "Using key reference '%s'", + sc_dump_hex(env->key_ref, env->key_ref_len)); + /* ok: insert key reference into buffer */ + /* notice that DNIe uses same key reference for pubk and privk */ + + /* see cwa14890-2 sect B.1 about Control Reference Template Tags */ + *p++ = 0x84; /* TODO: make proper detection of 0x83 /0x84 tag usage */ + *p++ = 0x02; /* len */ + *p++ = 0x01; /* key ID prefix (MSB byte of keyFile ID) */ + memcpy(p, env->key_ref, env->key_ref_len); /* in DNIe key_ref_len=1 */ + p += env->key_ref_len; + /* store key reference into private data */ + GET_DNIE_PRIV_DATA(card)->rsa_key_ref = 0xff & env->key_ref[0]; + } + + /* create and format apdu */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, 0x00, 0x00); + + /* check and perform operation */ + switch (env->operation) { + case SC_SEC_OPERATION_DECIPHER: + /* TODO: Manual is unsure about if (de)cipher() is supported */ + apdu.p1 = 0xC1; + apdu.p2 = 0xB8; + break; + case SC_SEC_OPERATION_SIGN: + apdu.p1 = 0x41; /* SET; internal operation */ + apdu.p2 = 0xB6; /* Template for Digital Signature */ + break; + case SC_SEC_OPERATION_AUTHENTICATE: + /* TODO: _set_security_env() study diffs on internal/external auth */ + apdu.p1 = 0x41; /* SET; internal operation */ + apdu.p2 = 0xA4; /* Template for Authenticate */ + break; + default: + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + } + + /* complete apdu contents with buffer data */ + apdu.data = sbuf; + apdu.datalen = p - sbuf; + apdu.lc = p - sbuf; + apdu.resplen = 0; + + /* Notice that Manual states that DNIE only allows handle of + * current security environment, so se_num is ignored, and + * store sec env apdu (00 22 F2 se_num) command will not be issued */ + + /* send composed apdu and parse result */ + result = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(card->ctx, result, "Set Security Environment failed"); + result = sc_check_sw(card, apdu.sw1, apdu.sw2); + + LOG_FUNC_RETURN(card->ctx, result); +} + +/** + * OpenDNIe implementation of Decipher() card driver operation. + * + * Engages the deciphering operation. Card will use the + * security environment set in a call to set_security_env or + * restore_security_env. + * + * Notice that DNIe manual doesn't say anything about crypt/decrypt + * operations. So this code is based on ISO standards and still needs + * to be checked + * + * ADD: seems that DNIe supports a minimal cipher/decipher operation + * but restricted to 1024 data chunks . Need more info and tests + * + * @param card Pointer to Card Driver Structure + * @param crgram cryptogram to be (de)ciphered + * @param crgram_len cryptogram length + * @param out where to store result + * @param outlen length of result buffer + * @return SC_SUCCESS if OK; else error code + */ +static int dnie_decipher(struct sc_card *card, + const u8 * crgram, size_t crgram_len, + u8 * out, size_t outlen) +{ + struct sc_apdu apdu; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; + size_t len; + int result = SC_SUCCESS; + if ((card == NULL) || (card->ctx == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + if ((crgram == NULL) || (out == NULL) || (crgram_len > 255)) { + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + } + /* Secure Channel should be on. Elsewhere an error will be thrown */ + /* + result = + cwa_create_secure_channel(card, dnie_priv.provider, CWA_SM_WARM); + LOG_TEST_RET(card->ctx, result, "decipher(); Cannot establish SM"); + */ + + /* Official driver uses an undocumented proprietary APDU + * (90 74 40 keyID). This code uses standard 00 2A 80 8x one) + * as shown in card-atrust-acos.c and card-jcop.c + */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x2A, /* INS: 0x2A perform security operation */ + 0x80, /* P1: Response is plain value */ + 0x86 /* P2: 8x: Padding indicator byte followed by cryptogram */ + ); + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); + + sbuf[0] = 0; /* padding indicator byte, 0x00 = No further indication */ + memcpy(sbuf + 1, crgram, crgram_len); + apdu.data = sbuf; + apdu.lc = crgram_len + 1; + apdu.datalen = crgram_len + 1; + apdu.le = 256; + /* send apdu */ + result = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(card->ctx, result, "APDU transmit failed"); + /* check response */ + result = sc_check_sw(card, apdu.sw1, apdu.sw2); + LOG_TEST_RET(card->ctx, result, "decipher returned error"); + /* responde ok: fill result data and return */ + len = apdu.resplen > outlen ? outlen : apdu.resplen; + memcpy(out, apdu.resp, len); + LOG_FUNC_RETURN(card->ctx, result); +} + +/** + * OpenDNIe implementation of Compute_Signature() card driver operation. + * + * Generates a digital signature on the card. + * This function handles the process of hash + sign + * with previously selected keys (by mean of set_security environment + * + * AS iso7816 and DNIe Manual states there are 3 ways to perform + * this operation: + * + * - (plaintext) Hash on plaintext + sign + * - (partial hash) Send a externally evaluated pkcs1 hash + sign + * - (hash) directly sign a given sha1 hash + * + * So the code analyze incoming data, decide which method to be used + * and applies + * + * @param card pointer to sc_card_t structure + * @param data data to be hased/signed + * @param datalen length of provided data + * @param out buffer to store results into + * @param outlen available space in result buffer + * @return + * - Positive value: Size of data stored in out buffer when no error + * - Negative value: error code + */ +static int dnie_compute_signature(struct sc_card *card, + const u8 * data, size_t datalen, + u8 * out, size_t outlen) +{ + int result = SC_SUCCESS; + struct sc_apdu apdu; + u8 sbuf[SC_MAX_APDU_BUFFER_SIZE]; /* to compose digest+hash data */ + size_t sbuflen = 0; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; /* to receive sign response */ + + /* some preliminar checks */ + if ((card == NULL) || (card->ctx == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + /* OK: start working */ + LOG_FUNC_CALLED(card->ctx); + /* more checks */ + if ((data == NULL) || (out == NULL)) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + if (datalen > SC_MAX_APDU_BUFFER_SIZE) /* should be 256 */ + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + if (outlen<256) /* enought space to store 2048 bit response */ + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + +#ifdef ENABLE_DNIE_UI + /* (Requested by DGP): on signature operation, ask user consent */ + if (GET_DNIE_PRIV_DATA(card)->rsa_key_ref == 0x02) { /* TODO: revise key ID handling */ + result = sc_ask_user_consent(card,user_consent_title,user_consent_message); + LOG_TEST_RET(card->ctx, result, "User consent denied"); + } +#endif + + /* + Seems that OpenSC already provides pkcs#1 v1.5 DigestInfo structure + with pre-calculated hash. So no need to to any Hash calculation, + + So just extract 15+20 DigestInfo+Hash info from ASN.1 provided + data and feed them into sign() command + */ + sc_log(card->ctx, + "Compute signature len: '%d' bytes:\n%s\n============================================================", + datalen, sc_dump_hex(data, datalen)); + if (datalen != 256) { + sc_log(card->ctx, "Expected pkcs#1 v1.5 DigestInfo data"); + LOG_FUNC_RETURN(card->ctx, SC_ERROR_WRONG_LENGTH); + } + + /* try to strip pkcs1 padding */ + sbuflen = sizeof(sbuf); + memset(sbuf, 0, sbuflen); + result = sc_pkcs1_strip_01_padding(data, datalen, sbuf, &sbuflen); + if (result != SC_SUCCESS) { + sc_log(card->ctx, "Provided data is not pkcs#1 padded"); + /* TODO: study what to do on plain data */ + LOG_FUNC_RETURN(card->ctx, SC_ERROR_WRONG_PADDING); + } + + /*INS: 0x2A PERFORM SECURITY OPERATION + * P1: 0x9E Resp: Digital Signature + * P2: 0x9A Cmd: Input for Digital Signature */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x2A, 0x9E, 0x9A); + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); + apdu.le = 256; /* signature response size */ + apdu.data = sbuf; + apdu.lc = sbuflen; /* 15 SHA1 DigestInfo + 20 SHA1 computed Hash */ + apdu.datalen = sizeof(sbuf); + /* tell card to compute signature */ + result = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(card->ctx, result, "compute_signature() failed"); + /* check response */ + result = sc_check_sw(card, apdu.sw1, apdu.sw2); + LOG_TEST_RET(card->ctx, result, "compute_signature() response error"); + + /* ok: copy result from buffer */ + memcpy(out, apdu.resp, apdu.resplen); + /* and return response length */ + LOG_FUNC_RETURN(card->ctx, apdu.resplen); +} + +/* + * ISO 7816-9 functions + */ + +/** + * OpenDNIe implementation of List_Files() card driver operation. + * + * List available files in current DF + * This is a dirty and trick implementation: + * Just try every ID in current dir + * + * @param card Pointer to Card Driver structure + * @param buff buffer to store result into + * @param bufflen size of provided buffer + * @return SC_SUCCESS if OK; else error code + * + * TODO: check for presence of every file ids on a DF is not + * practical. Locate a better way to handle, or remove code + */ +static int dnie_list_files(sc_card_t * card, u8 * buf, size_t buflen) +{ + int res = SC_SUCCESS; + int id1 = 0; + int id2 = 0; + size_t count = 0; + u8 data[2]; + sc_apdu_t apdu; + sc_apdu_t back; + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + + LOG_FUNC_CALLED(card->ctx); + if (!buf || (buflen < 2)) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + + /* compose select_file(ID) command */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0xA4, 0x00, 0x00); + apdu.le = 0; + apdu.lc = 2; + apdu.data = data; + apdu.resp = NULL; + apdu.datalen = 2; + apdu.resplen = 0; + /* compose select_file(PARENT) command */ + sc_format_apdu(card, &back, SC_APDU_CASE_1, 0xA4, 0x03, 0x00); + back.le = 0; + back.lc = 0; + back.data = NULL; + back.resp = NULL; + back.datalen = 0; + back.resplen = 0; + /* iterate on every possible ids */ + for (id1 = 0; id1 < 256; id1++) { + for (id2 = 0; id2 < 256; id2++) { + if (count >= (buflen - 2)) { + sc_log(card->ctx, + "list_files: end of buffer. Listing stopped"); + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); + } + /* according iso several ids are not allowed, so check for it */ + if ((id1 == 0x3F) && (id2 == 0xFF)) + continue; /* generic parent "." DF */ + if ((id1 == 0x2F) && (id2 == 0x00)) + continue; /* RFU see iso 8.2.1.1 */ + if ((id1 == 0x2F) && (id2 == 0x01)) + continue; /* RFU */ + /* compose and transmit select_file() cmd */ + data[0] = (u8) (0xff & id1); + data[1] = (u8) (0xff & id2); + res = dnie_transmit_apdu(card, &apdu); + if (res != SC_SUCCESS) { + sc_log(card->ctx, "List file '%02X%02X' failed", + id1, id2); + /* if file not found, continue; else abort */ + if (res != SC_ERROR_FILE_NOT_FOUND) + LOG_FUNC_RETURN(card->ctx, res); + continue; + } + /* if file found, process fci to get file type */ + sc_log(card->ctx, "Found File ID '%02X%02X'", id1, id2); + /* store id into buffer */ + *(buf + count++) = data[0]; + *(buf + count++) = data[1]; + /* TODO: + * if found file is a DF go back to parent DF + * to continue search */ + } + } + /* arriving here means all done */ + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +/** + * Parse APDU results to generate proper error code. + * + * Traps standard check_sw function to take care on special error codes + * for OpenDNIe (mostly related to SM status and operations) + * + * @param card Pointer to Card driver Structure + * @param sw1 SW1 APDU response byte + * @param sw2 SW2 APDU response byte + * @return SC_SUCCESS if no error; else proper error code + */ +static int dnie_check_sw(struct sc_card *card, + unsigned int sw1, unsigned int sw2) +{ + int res = SC_SUCCESS; + int n = 0; + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + + /* check specific dnie errors */ + for (n = 0; dnie_errors[n].SWs != 0; n++) { + if (dnie_errors[n].SWs == ((sw1 << 8) | sw2)) { + sc_log(card->ctx, "%s", dnie_errors[n].errorstr); + return dnie_errors[n].errorno; + } + } + + /* arriving here means check for supported iso error codes */ + res = iso_ops->check_sw(card, sw1, sw2); + LOG_FUNC_RETURN(card->ctx, res); +} + +/** + * OpenDNIe implementation for Card_Ctl() card driver operation. + * + * This command provides access to non standard functions provided by + * this card driver, as defined in cardctl.h + * + * @param card Pointer to card driver structure + * @param request Operation requested + * @param data where to get data/store response + * @return SC_SUCCESS if ok; else error code + * @see cardctl.h + * + * TODO: wait for GET_CARD_INFO generic cardctl to be implemented + * in opensc and rewrite code according it + */ +static int dnie_card_ctl(struct sc_card *card, + unsigned long request, void *data) +{ + int result = SC_SUCCESS; + if ((card == NULL) || (card->ctx == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + if (data == NULL) { + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + } + switch (request) { + /* obtain lifecycle status by reading card->type */ + case SC_CARDCTL_LIFECYCLE_GET: + switch (card->type) { + case SC_CARD_TYPE_DNIE_ADMIN: + result = SC_CARDCTRL_LIFECYCLE_ADMIN; + break; + case SC_CARD_TYPE_DNIE_USER: + result = SC_CARDCTRL_LIFECYCLE_USER; + break; + case SC_CARD_TYPE_DNIE_BLANK: + case SC_CARD_TYPE_DNIE_TERMINATED: + result = SC_CARDCTRL_LIFECYCLE_OTHER; + break; + } + *(int *)data = result; + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); + /* call card to obtain serial number */ + case SC_CARDCTL_GET_SERIALNR: + result = dnie_get_serialnr(card, (sc_serial_number_t *) data); + LOG_FUNC_RETURN(card->ctx, result); + case SC_CARDCTL_DNIE_GENERATE_KEY: + /* some reports says that this card supports genkey */ + result = dnie_generate_key(card, data); + LOG_FUNC_RETURN(card->ctx, result); + case SC_CARDCTL_DNIE_GET_INFO: + /* retrieve name, surname and eid number */ + result = dnie_get_info(card, data); + LOG_FUNC_RETURN(card->ctx, result); + default: + /* default: unsupported function */ + LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED); + } +} + +/** + * Read first bytes of an EF to check for compression data. + * + * FCI info on compressed files provides the length of the compressed + * data. When fci returns filetype = 0x24, needs to check if the + * file is compressed, and set up properly correct file length, to let + * the read_binary() file cache work + * + * Extract real file length from compressed file is done by mean of + * reading 8 first bytes for uncompressed/compressed lenght. + * Lengths are provided as two 4-byte little endian numbers + * + * Implemented just like a direct read binary apdu bypassing dnie file cache + * + * @param card sc_card_t structure pointer + * @return <0: error code - ==0 not compressed - >0 file size + */ +static int dnie_read_header(struct sc_card *card) +{ + sc_apdu_t apdu; + int r; + u8 buf[SC_MAX_APDU_BUFFER_SIZE]; + unsigned long uncompressed = 0L; + unsigned long compressed = 0L; + sc_context_t *ctx = NULL; + + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + + /* initialize apdu */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_2_SHORT, 0xB0, 0x00, 0x00); + apdu.p1 = 0x00; + apdu.p2 = 0x00; + apdu.le = 8; /* read 8 bytes at begining of file */ + apdu.resplen = SC_MAX_APDU_BUFFER_SIZE; + apdu.resp = buf; + /* transmit apdu */ + r = dnie_transmit_apdu(card, &apdu); + if (r != SC_SUCCESS) { + sc_log(ctx, "read_header() APDU transmit failed"); + LOG_FUNC_RETURN(ctx, r); + } + /* check response */ + if (apdu.resplen != 8) + goto header_notcompressed; + uncompressed = le2ulong(apdu.resp); + compressed = le2ulong(apdu.resp + 4); + if (uncompressed < compressed) + goto header_notcompressed; + if (uncompressed > 32767) + goto header_notcompressed; + /* ok: assume data is correct */ + sc_log(ctx, "read_header: uncompressed file size is %lu", uncompressed); + return (int)(0x7FFF & uncompressed); + + header_notcompressed: + sc_log(ctx, "response doesn't match compressed file header"); + return 0; +} + +/** + * Access control list bytes for propietary DNIe FCI response for DF's. + * based in information from official DNIe Driver + * Parsing code based on itacns card driver + */ +static int df_acl[] = { /* to handle DF's */ + SC_AC_OP_CREATE, SC_AC_OP_DELETE, + SC_AC_OP_REHABILITATE, SC_AC_OP_INVALIDATE, + -1 /* !hey!, what about 5th byte of FCI info? */ +}; + +/** + * Access control list bytes for propietary DNIe FCI response for EF's. + * based in information from official DNIe Driver + * Parsing code based on itacns card driver + */ +static int ef_acl[] = { /* to handle EF's */ + SC_AC_OP_READ, SC_AC_OP_UPDATE, + SC_AC_OP_REHABILITATE, SC_AC_OP_INVALIDATE, + -1 /* !hey!, what about 5th byte of FCI info? */ +}; + +/** + * OpenDNIe implementation of Process_FCI() card driver command. + * + * Parse SelectFile's File Control information. + * - First, std iso_parse_fci is called to parse std fci tags + * - Then analyze propietary tag according DNIe Manual + * + * @param card OpenSC card structure pointer + * @param file currently selected EF or DF + * @param buf received FCI data + * @param buflen FCI length + * @return SC_SUCCESS if OK; else error code + */ +static int dnie_process_fci(struct sc_card *card, + struct sc_file *file, const u8 * buf, size_t buflen) +{ + int res = SC_SUCCESS; + int *op = df_acl; + int n = 0; + sc_context_t *ctx = NULL; + if ((card == NULL) || (card->ctx == NULL) || (file == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + /* first of all, let iso do the hard work */ + res = iso_ops->process_fci(card, file, buf, buflen); + LOG_TEST_RET(ctx, res, "iso7816_process_fci() failed"); + /* if tag 0x85 is received, then file->prop_attr_len should be filled + * by sc_file_set_prop_attr() code. So check and set data according manual + * Note errata at pg 35 of Manual about DF identifier (should be 0x38) */ + if (file->prop_attr_len == 0) { /* no proprietary tag (0x85) received */ + res = SC_SUCCESS; + goto dnie_process_fci_end; + } + /* at least 10 bytes should be received */ + if (file->prop_attr_len < 10) { + res = SC_ERROR_WRONG_LENGTH; + goto dnie_process_fci_end; + } + /* byte 0 denotes file type */ + switch (file->prop_attr[0]) { + case 0x01: /* EF for plain files */ + file->type = SC_FILE_TYPE_WORKING_EF; + file->ef_structure = SC_FILE_EF_TRANSPARENT; + break; + case 0x15: /* EF for keys: linear variable simple TLV */ + file->type = SC_FILE_TYPE_WORKING_EF; + /* pin file 3F000000 has also this EF type */ + if ( ( file->prop_attr[3] == 0x00 ) && (file->prop_attr[3] == 0x00 ) ) { + sc_log(ctx,"Processing pin EF"); + break; + } + /* FCI response for Keys EF returns 3 additional bytes */ + if (file->prop_attr_len < 13) { + sc_log(ctx, + "FCI response len for Keys EF should be 13 bytes"); + res = SC_ERROR_WRONG_LENGTH; + goto dnie_process_fci_end; + } + break; + case 0x24: /* EF for compressed certificates */ + file->type = SC_FILE_TYPE_WORKING_EF; + file->ef_structure = SC_FILE_EF_TRANSPARENT; + /* evaluate real length by reading first 8 bytes from file */ + res = dnie_read_header(card); + /* Hey!, we need pin to read certificates... */ + if (res == SC_ERROR_SECURITY_STATUS_NOT_SATISFIED) + goto dnie_process_fci_end; + if (res <= 0) { + sc_log(ctx, + "Cannot evaluate uncompressed size. use fci length"); + } else { + sc_log(ctx, "Storing uncompressed size '%d' into fci", + res); + file->prop_attr[3] = (u8) ((res >> 8) & 0xff); + file->prop_attr[4] = (u8) (res & 0xff); + } + break; + case 0x38: /* Errata: manual page 35 says wrong 0x34 */ + file->type = SC_FILE_TYPE_DF; + break; + default: + res = SC_ERROR_UNKNOWN_DATA_RECEIVED; + goto dnie_process_fci_end; + } + + /* bytes 1 and 2 stores file ID */ + file->id = ( ( 0xff & (int)file->prop_attr[1] ) << 8 ) | + ( 0xff & (int)file->prop_attr[2] ) ; + + /* bytes 3 and 4 states file length */ + file->size = ( ( 0xff & (int)file->prop_attr[3] ) << 8 ) | + ( 0xff & (int)file->prop_attr[4] ) ; + + /* bytes 5 to 9 states security attributes */ + /* NOTE: + * seems that these 5 bytes are handled according iso7816-9 sect 8. + * but sadly that each card uses their own bits :-( + * Moreover: Manual talks on 5 bytes, but official driver only uses 4 + * No info available (yet), so copy code from card-jcos.c / card-flex.c + * card drivers and pray... */ + op = (file->type == SC_FILE_TYPE_DF) ? df_acl : ef_acl; + for (n = 0; n < 5; n++) { + int key_ref = 0; + if (*(op + n) == -1) + continue; /* unused entry: skip */ + key_ref = file->prop_attr[5 + n] & 0x0F; + switch (0xF0 & file->prop_attr[5 + n]) { + case 0x00: + sc_file_add_acl_entry(file, *(op + n), SC_AC_NONE, + SC_AC_KEY_REF_NONE); + break; + case 0x10: + /* this tag is omitted in official code + case 0x20: + */ + case 0x30: + sc_file_add_acl_entry(file, *(op + n), SC_AC_CHV, + key_ref); + break; + case 0x40: + sc_file_add_acl_entry(file, *(op + n), SC_AC_TERM, + key_ref); + break; + case 0xF0: + sc_file_add_acl_entry(file, *(op + n), SC_AC_NEVER, + SC_AC_KEY_REF_NONE); + break; + default: + sc_file_add_acl_entry(file, *(op + n), SC_AC_UNKNOWN, + SC_AC_KEY_REF_NONE); + break; + } + } + /* NOTE: Following bytes are described at DNIe manual pg 36, but No + documentation about what to do with following data is provided... + logs suggest that they are neither generated nor handled. + + UPDATE: these additional bytes are received when FileDescriptor tag + is 0x15 (EF for keys) + */ + if (file->prop_attr[0] == 0x15) { + sc_log(card->ctx, + "Processing flags for Cryptographic key files"); + /* byte 10 (if present) shows Control Flags for security files */ + /* bytes 11 and 12 (if present) states Control bytes for + RSA crypto files */ + /* TODO: write when know what to do */ + } + res = SC_SUCCESS; /* arriving here means success */ + dnie_process_fci_end: + LOG_FUNC_RETURN(card->ctx, res); +} + +/* + * PIN related functions + * NOTE: + * DNIe manual says only about CHV1 PIN verify, but several sources talks + * about the ability to also handle CHV1 PIN change + * So prepare code to eventually support + * + * Anyway pin unlock is not available: no way to get PUK as these code is + * obtained by mean of user fingerprint, only available at police station + */ + +/** + * Change PIN. + * + * Not implemented yet, as current availability for DNIe user driver + * is unknown + * + * @param card Pointer to Card Driver data structrure + * @param data Pointer to Pin data structure + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_pin_change(struct sc_card *card, struct sc_pin_cmd_data * data) +{ + int res=SC_SUCCESS; + LOG_FUNC_CALLED(card->ctx); +#ifdef ENABLE_SM + /* Ensure that secure channel is established from reset */ + res = cwa_create_secure_channel(card, GET_DNIE_PRIV_DATA(card)->cwa_provider, CWA_SM_COLD); + LOG_TEST_RET(card->ctx, res, "Establish SM failed"); +#endif + LOG_FUNC_RETURN(card->ctx,SC_ERROR_NOT_SUPPORTED); +} + +/** + * Verify PIN. + * + * Initialize SM and send pin verify CHV1 command to DNIe + * + * @param card Pointer to Card Driver data structure + * @param data Pointer to Pin data structure + * @param tries_left; on fail stores the number of tries left before car lock + * @return SC_SUCCESS if ok, else error code; on pin incorrect also sets tries_left + */ +static int dnie_pin_verify(struct sc_card *card, + struct sc_pin_cmd_data *data, int *tries_left) +{ +#ifdef ENABLE_SM + int res=SC_SUCCESS; + sc_apdu_t apdu; + + u8 pinbuffer[SC_MAX_APDU_BUFFER_SIZE]; + int pinlen = 0; + int padding = 0; + + LOG_FUNC_CALLED(card->ctx); + /* ensure that secure channel is established from reset */ + res = cwa_create_secure_channel(card, GET_DNIE_PRIV_DATA(card)->cwa_provider, CWA_SM_COLD); + LOG_TEST_RET(card->ctx, res, "Establish SM failed"); + + data->apdu = &apdu; /* prepare apdu struct */ + /* compose pin data to be inserted in apdu */ + if (data->flags & SC_PIN_CMD_NEED_PADDING) + padding = 1; + data->pin1.offset = 0; + res = sc_build_pin(pinbuffer, sizeof(pinbuffer), &data->pin1, padding); + if (res < 0) + LOG_FUNC_RETURN(card->ctx, res); + pinlen = res; + + /* compose apdu */ + memset(&apdu, 0, sizeof(apdu)); /* clear buffer */ + apdu.cla = 0x00; + apdu.cse = SC_APDU_CASE_3_SHORT; + apdu.ins = (u8) 0x20; /* Verify cmd */ + apdu.p1 = (u8) 0x00; + apdu.p2 = (u8) 0x00; + apdu.lc = pinlen; + apdu.datalen = pinlen; + apdu.data = pinbuffer; + apdu.resplen = 0; + apdu.le = 0; + + /* and send to card throught virtual channel */ + res = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(card->ctx, res, "VERIFY APDU Transmit fail"); + + /* check response and if requested setup tries_left */ + if (tries_left != NULL) { /* returning tries_left count is requested */ + if ((apdu.sw1 == 0x63) && ((apdu.sw2 & 0xF0) == 0xC0)) { + *tries_left = apdu.sw2 & 0x0F; + LOG_FUNC_RETURN(card->ctx, SC_ERROR_PIN_CODE_INCORRECT); + } + } + res = dnie_check_sw(card, apdu.sw1, apdu.sw2); /* not a pinerr: parse result */ + + /* the end: a bit of Mister Proper and return */ + memset(&apdu, 0, sizeof(apdu)); /* clear buffer */ + data->apdu = NULL; + LOG_FUNC_RETURN(card->ctx, res); +#else + LOG_TEST_RET(card->ctx, SC_ERROR_NOT_SUPPORTED, "built without support of SM and External Authentication"); + return SC_ERROR_NOT_SUPPORTED; +#endif +} + +/* pin_cmd: verify/change/unblock command; optionally using the + * card's pin pad if supported. + */ + +/** + * OpenDNIe implementation for Pin_Cmd() card driver command. + * + * @param card Pointer to Card Driver data structure + * @param data Pointer to Pin data structure + * @param tries_left; if pin_verify() operation, on incorrect pin stores the number of tries left before car lock + * @return SC_SUCCESS if ok, else error code; on pin incorrect also sets tries_left + */ +static int dnie_pin_cmd(struct sc_card *card, + struct sc_pin_cmd_data *data, int *tries_left) +{ + int res = SC_SUCCESS; + int lc = SC_CARDCTRL_LIFECYCLE_USER; + + if ((card == NULL) || (card->ctx == NULL) || (data == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + + /* + * some flags and settings from documentation + * No (easy) way to handle pinpad throught SM, so disable it + */ + data->flags &= ~SC_PIN_CMD_NEED_PADDING; /* no pin padding */ + data->flags &= ~SC_PIN_CMD_USE_PINPAD; /* cannot handle pinpad */ + + /* ensure that card is in USER Lifecycle */ + res = dnie_card_ctl(card, SC_CARDCTL_LIFECYCLE_GET, &lc); + LOG_TEST_RET(card->ctx, res, "Cannot get card LC status"); + if (lc != SC_CARDCTRL_LIFECYCLE_USER) { + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_CARD); + } + + /* only allow changes on CHV pin ) */ + switch (data->pin_type) { + case SC_AC_CHV: /* Card Holder Verifier */ + break; + case SC_AC_TERM: /* Terminal auth */ + case SC_AC_PRO: /* SM auth */ + case SC_AC_AUT: /* Key auth */ + LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_SUPPORTED); + default: + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + } + /* This DNIe driver only supports VERIFY operation */ + switch (data->cmd) { + case SC_PIN_CMD_VERIFY: + res = dnie_pin_verify(card,data,tries_left); + break; + case SC_PIN_CMD_CHANGE: + res = dnie_pin_change(card,data); + break; + case SC_PIN_CMD_UNBLOCK: + case SC_PIN_CMD_GET_INFO: + res= SC_ERROR_NOT_SUPPORTED; + break; + default: + res= SC_ERROR_INVALID_ARGUMENTS; + break; + } + /* return result */ + LOG_FUNC_RETURN(card->ctx, res); +} + +#ifdef ENABLE_SM +static int dnie_sm_wrap_apdu(struct sc_card *card, struct sc_apdu *plain, struct sc_apdu *wrapped) +{ + int res = SC_SUCCESS; + sc_context_t *ctx = card->ctx; + cwa_provider_t *provider = NULL; + + LOG_FUNC_CALLED(ctx); + + if ((plain == NULL) || (wrapped == NULL)) + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + + provider = GET_DNIE_PRIV_DATA(card)->cwa_provider; + + wrapped->cse = plain->cse; + wrapped->cla = plain->cla; + wrapped->ins = plain->ins; + wrapped->p1 = plain->p1; + wrapped->p2 = plain->p2; + wrapped->lc = plain->lc; + wrapped->le = plain->le; + wrapped->control = plain->control; + wrapped->flags = plain->flags; + memcpy(wrapped->data, plain->data, plain->datalen); + + /* if SM is ON, ensure resp exists, and force getResponse() */ + if (provider->status.session.state == CWA_SM_ACTIVE) { + /* set up proper apdu type */ + if (wrapped->cse == SC_APDU_CASE_3_SHORT) + wrapped->cse = SC_APDU_CASE_4_SHORT; + } + sc_log(card->ctx, "Data to be enveloped & sent: (%d bytes)\n%s\n==================",wrapped->lc,sc_dump_hex(wrapped->data,wrapped->lc)); + + LOG_FUNC_RETURN(ctx, res); +} + +static int dnie_sm_get_wrapped_apdu(sc_card_t *card, sc_apdu_t *plain, sc_apdu_t **sm_apdu) +{ + struct sc_context *ctx = card->ctx; + struct sc_apdu *apdu = NULL; + int rv; + + LOG_FUNC_CALLED(ctx); + + if (!plain || !sm_apdu) + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + + *sm_apdu = NULL; + apdu = calloc(1, sizeof(struct sc_apdu)); + if (!apdu) + LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); + apdu->data = calloc (1, SC_MAX_EXT_APDU_BUFFER_SIZE); + if (!apdu->data) + LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); + apdu->datalen = SC_MAX_EXT_APDU_BUFFER_SIZE; + apdu->resp = calloc (1, SC_MAX_EXT_APDU_BUFFER_SIZE); + if (!apdu->resp) + LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); + apdu->resplen = SC_MAX_EXT_APDU_BUFFER_SIZE; + + rv = dnie_sm_wrap_apdu(card, plain, apdu); + if (rv) { + rv = dnie_sm_free_and_unwrap_apdu(card, NULL, &apdu); + LOG_FUNC_RETURN(ctx, rv); + } + + *sm_apdu = apdu; + LOG_FUNC_RETURN(ctx, rv); +} + +static int dnie_sm_unwrap_apdu(sc_card_t *card, sc_apdu_t *wrapped, sc_apdu_t *plain) +{ + int res = SC_SUCCESS; + struct sc_context *ctx = card->ctx; + cwa_provider_t *provider = NULL; + + LOG_FUNC_CALLED(ctx); + + provider = GET_DNIE_PRIV_DATA(card)->cwa_provider; + + /* parse response and handle SM related errors */ + res = sc_check_sw(card, wrapped->sw1, wrapped->sw2); + + if (res == SC_SUCCESS) { + /* memcopy result to original apdu */ + memcpy(plain->resp, wrapped->resp, wrapped->resplen); + plain->resplen = wrapped->resplen; + plain->sw1 = wrapped->sw1; + plain->sw2 = wrapped->sw2; + } else { + sc_log(ctx, "Detected SM error/collision (%d).", res); + } + + sc_log(card->ctx, "unwrapped APDU: resplen %i, SW %02X%02X", plain->resplen, plain->sw1, plain->sw2); + LOG_FUNC_RETURN(ctx, SC_SUCCESS); +} + +static int dnie_sm_free_and_unwrap_apdu(sc_card_t *card, sc_apdu_t *plain, sc_apdu_t **sm_apdu) +{ + struct sc_context *ctx = card->ctx; + int rv = SC_SUCCESS; + + LOG_FUNC_CALLED(ctx); + + if (sm_apdu == NULL) + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + + if ((*sm_apdu) == NULL) + LOG_FUNC_RETURN(ctx, SC_SUCCESS); + + if (plain) + rv = dnie_sm_unwrap_apdu(card, *sm_apdu, plain); + + if ((*sm_apdu)->data) + free((*sm_apdu)->data); + if ((*sm_apdu)->resp) + free((*sm_apdu)->resp); + free(*sm_apdu); + *sm_apdu = NULL; + + LOG_FUNC_RETURN(ctx, SC_SUCCESS); +} + +#endif + +/**********************************************************************/ + +/** + * Internal function to initialize card driver function pointers. + * + * This is done by getting a copy for iso7816 card operations, + * and replace every DNIe specific functions + * + * @return DNIe card driver data, or null on failure + */ +static sc_card_driver_t *get_dnie_driver(void) +{ + sc_card_driver_t *iso_drv = sc_get_iso7816_driver(); + + /* memcpy() from standard iso7816 declared operations */ + if (iso_ops == NULL) + iso_ops = iso_drv->ops; + dnie_ops = *iso_drv->ops; + + /* fill card specific function pointers */ + /* NULL means that function is not supported neither by DNIe nor iso7816.c */ + /* if pointer is omitted, default ISO7816 function will be used */ + + /* initialization */ + dnie_ops.match_card = dnie_match_card; + dnie_ops.init = dnie_init; + dnie_ops.finish = dnie_finish; + + /* iso7816-4 functions */ + dnie_ops.read_binary = dnie_read_binary; + dnie_ops.write_binary = NULL; + dnie_ops.update_binary = NULL; + dnie_ops.erase_binary = NULL; + dnie_ops.read_record = NULL; + dnie_ops.write_record = NULL; + dnie_ops.append_record = NULL; + dnie_ops.update_record = NULL; + dnie_ops.select_file = dnie_select_file; + dnie_ops.get_challenge = dnie_get_challenge; + + /* iso7816-8 functions */ + dnie_ops.verify = NULL; + dnie_ops.logout = dnie_logout; + /* dnie_ops.restore_security_env */ + dnie_ops.set_security_env = dnie_set_security_env; + dnie_ops.decipher = dnie_decipher; + dnie_ops.compute_signature = dnie_compute_signature; + dnie_ops.change_reference_data = NULL; + dnie_ops.reset_retry_counter = NULL; + + /* iso7816-9 functions */ + dnie_ops.create_file = NULL; + dnie_ops.delete_file = NULL; + dnie_ops.list_files = dnie_list_files; + dnie_ops.check_sw = dnie_check_sw; + dnie_ops.card_ctl = dnie_card_ctl; + dnie_ops.process_fci = dnie_process_fci; + /* dnie_ops.construct_fci */ + dnie_ops.pin_cmd = dnie_pin_cmd; + dnie_ops.get_data = NULL; + dnie_ops.put_data = NULL; + dnie_ops.delete_record = NULL; + + return &dnie_driver; +} + +/** + * Entry point for (static) OpenDNIe card driver. + * + * This is the only public function on this module + * + * @return properly initialized array pointer to card driver operations + */ +sc_card_driver_t *sc_get_dnie_driver(void) +{ + return get_dnie_driver(); +} + +#undef __CARD_DNIE_C__ + +#endif /* ENABLE_OPENSSL */ diff --git a/src/libopensc/cardctl.h b/src/libopensc/cardctl.h index 370cff42..1184f8b0 100644 --- a/src/libopensc/cardctl.h +++ b/src/libopensc/cardctl.h @@ -248,7 +248,14 @@ enum { SC_CARDCTL_SC_HSM_INITIALIZE, SC_CARDCTL_SC_HSM_IMPORT_DKEK_SHARE, SC_CARDCTL_SC_HSM_WRAP_KEY, - SC_CARDCTL_SC_HSM_UNWRAP_KEY + SC_CARDCTL_SC_HSM_UNWRAP_KEY, + + /* + * DNIe specific calls + */ + SC_CARDCTL_DNIE_BASE = _CTL_PREFIX('D', 'N', 'I'), + SC_CARDCTL_DNIE_GENERATE_KEY, + SC_CARDCTL_DNIE_GET_INFO }; enum { diff --git a/src/libopensc/cards.h b/src/libopensc/cards.h index 0fbf9ca8..6bb4f359 100644 --- a/src/libopensc/cards.h +++ b/src/libopensc/cards.h @@ -191,6 +191,13 @@ enum { /* SmartCard-HSM */ SC_CARD_TYPE_SC_HSM = 26000, + + /* Spanish DNIe card */ + SC_CARD_TYPE_DNIE_BASE = 27000, + SC_CARD_TYPE_DNIE_BLANK, /* ATR LC byte: 00 */ + SC_CARD_TYPE_DNIE_ADMIN, /* ATR LC byte: 01 */ + SC_CARD_TYPE_DNIE_USER, /* ATR LC byte: 03 */ + SC_CARD_TYPE_DNIE_TERMINATED /* ATR LC byte: 0F */ }; extern sc_card_driver_t *sc_get_default_driver(void); @@ -227,6 +234,7 @@ extern sc_card_driver_t *sc_get_itacns_driver(void); extern sc_card_driver_t *sc_get_authentic_driver(void); extern sc_card_driver_t *sc_get_iasecc_driver(void); extern sc_card_driver_t *sc_get_epass2003_driver(void); +extern sc_card_driver_t *sc_get_dnie_driver(void); #ifdef __cplusplus } diff --git a/src/libopensc/ctx.c b/src/libopensc/ctx.c index 1c94a9d8..51a0aa54 100644 --- a/src/libopensc/ctx.c +++ b/src/libopensc/ctx.c @@ -97,6 +97,9 @@ static const struct _sc_driver_entry internal_card_drivers[] = { { "westcos", (void *(*)(void)) sc_get_westcos_driver }, { "myeid", (void *(*)(void)) sc_get_myeid_driver }, { "sc-hsm", (void *(*)(void)) sc_get_sc_hsm_driver }, +#ifdef ENABLE_OPENSSL + { "dnie", (void *(*)(void)) sc_get_dnie_driver }, +#endif /* Here should be placed drivers that need some APDU transactions to * recognise its cards. */ diff --git a/src/libopensc/cwa-dnie.c b/src/libopensc/cwa-dnie.c new file mode 100644 index 00000000..62060db0 --- /dev/null +++ b/src/libopensc/cwa-dnie.c @@ -0,0 +1,885 @@ +/** + * cwa-dnie.c: DNIe data provider for CWA SM handling. + * + * Copyright (C) 2010 Juan Antonio Martinez + * + * This work is derived from many sources at OpenSC Project site, + * (see references) and the information made public by Spanish + * Direccion General de la Policia y de la Guardia Civil + * + * 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 + */ + +#define __SM_DNIE_C__ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef ENABLE_OPENSSL /* empty file without openssl */ + +#include +#include + +#include "opensc.h" +#include "cardctl.h" +#include "internal.h" +#include "cwa14890.h" + +#include "cwa-dnie.h" + +#include +#include + +/********************* Keys and certificates as published by DGP ********/ + +/** + * Modulo de la clave pública de la Root CA del DNIe electronico + */ +static u8 icc_root_ca_modulus[] = { + 0xEA, 0xDE, 0xDA, 0x45, 0x53, 0x32, 0x94, 0x50, 0x39, 0xDA, 0xA4, 0x04, + 0xC8, 0xEB, 0xC4, 0xD3, 0xB7, 0xF5, 0xDC, 0x86, 0x92, 0x83, 0xCD, 0xEA, + 0x2F, 0x10, 0x1E, 0x2A, 0xB5, 0x4F, 0xB0, 0xD0, 0xB0, 0x3D, 0x8F, 0x03, + 0x0D, 0xAF, 0x24, 0x58, 0x02, 0x82, 0x88, 0xF5, 0x4C, 0xE5, 0x52, 0xF8, + 0xFA, 0x57, 0xAB, 0x2F, 0xB1, 0x03, 0xB1, 0x12, 0x42, 0x7E, 0x11, 0x13, + 0x1D, 0x1D, 0x27, 0xE1, 0x0A, 0x5B, 0x50, 0x0E, 0xAA, 0xE5, 0xD9, 0x40, + 0x30, 0x1E, 0x30, 0xEB, 0x26, 0xC3, 0xE9, 0x06, 0x6B, 0x25, 0x71, 0x56, + 0xED, 0x63, 0x9D, 0x70, 0xCC, 0xC0, 0x90, 0xB8, 0x63, 0xAF, 0xBB, 0x3B, + 0xFE, 0xD8, 0xC1, 0x7B, 0xE7, 0x67, 0x30, 0x34, 0xB9, 0x82, 0x3E, 0x97, + 0x7E, 0xD6, 0x57, 0x25, 0x29, 0x27, 0xF9, 0x57, 0x5B, 0x9F, 0xFF, 0x66, + 0x91, 0xDB, 0x64, 0xF8, 0x0B, 0x5E, 0x92, 0xCD +}; + +/** + * Exponente de la clave publica de la Root CA del DNI electronico + */ +static u8 icc_root_ca_public_exponent[] = { + 0x01, 0x00, 0x01 +}; + +/** + * Terminal (IFD) key modulus for SM channel creation + */ +static u8 ifd_modulus[] = { + 0xdb, 0x2c, 0xb4, 0x1e, 0x11, 0x2b, 0xac, 0xfa, 0x2b, 0xd7, 0xc3, 0xd3, + 0xd7, 0x96, 0x7e, 0x84, 0xfb, 0x94, 0x34, 0xfc, 0x26, 0x1f, 0x9d, 0x09, + 0x0a, 0x89, 0x83, 0x94, 0x7d, 0xaf, 0x84, 0x88, 0xd3, 0xdf, 0x8f, 0xbd, + 0xcc, 0x1f, 0x92, 0x49, 0x35, 0x85, 0xe1, 0x34, 0xa1, 0xb4, 0x2d, 0xe5, + 0x19, 0xf4, 0x63, 0x24, 0x4d, 0x7e, 0xd3, 0x84, 0xe2, 0x6d, 0x51, 0x6c, + 0xc7, 0xa4, 0xff, 0x78, 0x95, 0xb1, 0x99, 0x21, 0x40, 0x04, 0x3a, 0xac, + 0xad, 0xfc, 0x12, 0xe8, 0x56, 0xb2, 0x02, 0x34, 0x6a, 0xf8, 0x22, 0x6b, + 0x1a, 0x88, 0x21, 0x37, 0xdc, 0x3c, 0x5a, 0x57, 0xf0, 0xd2, 0x81, 0x5c, + 0x1f, 0xcd, 0x4b, 0xb4, 0x6f, 0xa9, 0x15, 0x7f, 0xdf, 0xfd, 0x79, 0xec, + 0x3a, 0x10, 0xa8, 0x24, 0xcc, 0xc1, 0xeb, 0x3c, 0xe0, 0xb6, 0xb4, 0x39, + 0x6a, 0xe2, 0x36, 0x59, 0x00, 0x16, 0xba, 0x69 +}; + +/** + * Terminal (IFD) public exponent for SM channel creation + */ +static u8 ifd_public_exponent[] = { + 0x01, 0x00, 0x01 +}; + +/** + * Terminal (IFD) private exponent for SM channel establishment + */ +static u8 ifd_private_exponent[] = { + 0x18, 0xb4, 0x4a, 0x3d, 0x15, 0x5c, 0x61, 0xeb, 0xf4, 0xe3, 0x26, 0x1c, + 0x8b, 0xb1, 0x57, 0xe3, 0x6f, 0x63, 0xfe, 0x30, 0xe9, 0xaf, 0x28, 0x89, + 0x2b, 0x59, 0xe2, 0xad, 0xeb, 0x18, 0xcc, 0x8c, 0x8b, 0xad, 0x28, 0x4b, + 0x91, 0x65, 0x81, 0x9c, 0xa4, 0xde, 0xc9, 0x4a, 0xa0, 0x6b, 0x69, 0xbc, + 0xe8, 0x17, 0x06, 0xd1, 0xc1, 0xb6, 0x68, 0xeb, 0x12, 0x86, 0x95, 0xe5, + 0xf7, 0xfe, 0xde, 0x18, 0xa9, 0x08, 0xa3, 0x01, 0x1a, 0x64, 0x6a, 0x48, + 0x1d, 0x3e, 0xa7, 0x1d, 0x8a, 0x38, 0x7d, 0x47, 0x46, 0x09, 0xbd, 0x57, + 0xa8, 0x82, 0xb1, 0x82, 0xe0, 0x47, 0xde, 0x80, 0xe0, 0x4b, 0x42, 0x21, + 0x41, 0x6b, 0xd3, 0x9d, 0xfa, 0x1f, 0xac, 0x03, 0x00, 0x64, 0x19, 0x62, + 0xad, 0xb1, 0x09, 0xe2, 0x8c, 0xaf, 0x50, 0x06, 0x1b, 0x68, 0xc9, 0xca, + 0xbd, 0x9b, 0x00, 0x31, 0x3c, 0x0f, 0x46, 0xed +}; + +/** + * Intermediate CA certificate in CVC format (Card verifiable certificate) + */ +static u8 C_CV_CA_CS_AUT_cert[] = { + 0x7f, 0x21, 0x81, 0xce, 0x5f, 0x37, 0x81, 0x80, 0x3c, 0xba, 0xdc, 0x36, + 0x84, 0xbe, 0xf3, 0x20, 0x41, 0xad, 0x15, 0x50, 0x89, 0x25, 0x8d, 0xfd, + 0x20, 0xc6, 0x91, 0x15, 0xd7, 0x2f, 0x9c, 0x38, 0xaa, 0x99, 0xad, 0x6c, + 0x1a, 0xed, 0xfa, 0xb2, 0xbf, 0xac, 0x90, 0x92, 0xfc, 0x70, 0xcc, 0xc0, + 0x0c, 0xaf, 0x48, 0x2a, 0x4b, 0xe3, 0x1a, 0xfd, 0xbd, 0x3c, 0xbc, 0x8c, + 0x83, 0x82, 0xcf, 0x06, 0xbc, 0x07, 0x19, 0xba, 0xab, 0xb5, 0x6b, 0x6e, + 0xc8, 0x07, 0x60, 0xa4, 0xa9, 0x3f, 0xa2, 0xd7, 0xc3, 0x47, 0xf3, 0x44, + 0x27, 0xf9, 0xff, 0x5c, 0x8d, 0xe6, 0xd6, 0x5d, 0xac, 0x95, 0xf2, 0xf1, + 0x9d, 0xac, 0x00, 0x53, 0xdf, 0x11, 0xa5, 0x07, 0xfb, 0x62, 0x5e, 0xeb, + 0x8d, 0xa4, 0xc0, 0x29, 0x9e, 0x4a, 0x21, 0x12, 0xab, 0x70, 0x47, 0x58, + 0x8b, 0x8d, 0x6d, 0xa7, 0x59, 0x22, 0x14, 0xf2, 0xdb, 0xa1, 0x40, 0xc7, + 0xd1, 0x22, 0x57, 0x9b, 0x5f, 0x38, 0x3d, 0x22, 0x53, 0xc8, 0xb9, 0xcb, + 0x5b, 0xc3, 0x54, 0x3a, 0x55, 0x66, 0x0b, 0xda, 0x80, 0x94, 0x6a, 0xfb, + 0x05, 0x25, 0xe8, 0xe5, 0x58, 0x6b, 0x4e, 0x63, 0xe8, 0x92, 0x41, 0x49, + 0x78, 0x36, 0xd8, 0xd3, 0xab, 0x08, 0x8c, 0xd4, 0x4c, 0x21, 0x4d, 0x6a, + 0xc8, 0x56, 0xe2, 0xa0, 0x07, 0xf4, 0x4f, 0x83, 0x74, 0x33, 0x37, 0x37, + 0x1a, 0xdd, 0x8e, 0x03, 0x00, 0x01, 0x00, 0x01, 0x42, 0x08, 0x65, 0x73, + 0x52, 0x44, 0x49, 0x60, 0x00, 0x06 +}; + +/** + * Terminal (IFD) certificate in CVC format (PK.IFD.AUT) + */ +static u8 C_CV_IFDUser_AUT_cert[] = { + 0x7f, 0x21, 0x81, 0xcd, 0x5f, 0x37, 0x81, 0x80, 0x82, 0x5b, 0x69, 0xc6, + 0x45, 0x1e, 0x5f, 0x51, 0x70, 0x74, 0x38, 0x5f, 0x2f, 0x17, 0xd6, 0x4d, + 0xfe, 0x2e, 0x68, 0x56, 0x75, 0x67, 0x09, 0x4b, 0x57, 0xf3, 0xc5, 0x78, + 0xe8, 0x30, 0xe4, 0x25, 0x57, 0x2d, 0xe8, 0x28, 0xfa, 0xf4, 0xde, 0x1b, + 0x01, 0xc3, 0x94, 0xe3, 0x45, 0xc2, 0xfb, 0x06, 0x29, 0xa3, 0x93, 0x49, + 0x2f, 0x94, 0xf5, 0x70, 0xb0, 0x0b, 0x1d, 0x67, 0x77, 0x29, 0xf7, 0x55, + 0xd1, 0x07, 0x02, 0x2b, 0xb0, 0xa1, 0x16, 0xe1, 0xd7, 0xd7, 0x65, 0x9d, + 0xb5, 0xc4, 0xac, 0x0d, 0xde, 0xab, 0x07, 0xff, 0x04, 0x5f, 0x37, 0xb5, + 0xda, 0xf1, 0x73, 0x2b, 0x54, 0xea, 0xb2, 0x38, 0xa2, 0xce, 0x17, 0xc9, + 0x79, 0x41, 0x87, 0x75, 0x9c, 0xea, 0x9f, 0x92, 0xa1, 0x78, 0x05, 0xa2, + 0x7c, 0x10, 0x15, 0xec, 0x56, 0xcc, 0x7e, 0x47, 0x1a, 0x48, 0x8e, 0x6f, + 0x1b, 0x91, 0xf7, 0xaa, 0x5f, 0x38, 0x3c, 0xad, 0xfc, 0x12, 0xe8, 0x56, + 0xb2, 0x02, 0x34, 0x6a, 0xf8, 0x22, 0x6b, 0x1a, 0x88, 0x21, 0x37, 0xdc, + 0x3c, 0x5a, 0x57, 0xf0, 0xd2, 0x81, 0x5c, 0x1f, 0xcd, 0x4b, 0xb4, 0x6f, + 0xa9, 0x15, 0x7f, 0xdf, 0xfd, 0x79, 0xec, 0x3a, 0x10, 0xa8, 0x24, 0xcc, + 0xc1, 0xeb, 0x3c, 0xe0, 0xb6, 0xb4, 0x39, 0x6a, 0xe2, 0x36, 0x59, 0x00, + 0x16, 0xba, 0x69, 0x00, 0x01, 0x00, 0x01, 0x42, 0x08, 0x65, 0x73, 0x53, + 0x44, 0x49, 0x60, 0x00, 0x06 +}; + +/** + * Root CA card key reference + */ +static u8 root_ca_keyref[] = { 0x02, 0x0f }; + + +/** + * ICC card private key reference + */ +static u8 icc_priv_keyref[] = { 0x02, 0x1f }; + +/** + * Intermediate CA card key reference + */ +static u8 cvc_intca_keyref[] = + { 0x65, 0x73, 0x53, 0x44, 0x49, 0x60, 0x00, 0x06 }; + +/** + * In memory key reference for selecting IFD sent certificate + */ +static u8 cvc_ifd_keyref[] = + { 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + +/** + * Serial number for IFD Terminal application + */ +static u8 sn_ifd[] = { 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; + +/** + * Serial number for ICC card. + * This buffer is to be filled at runtime + */ +static u8 sn_icc[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +/************ internal functions **********************************/ + +/** + * Select a file from card, process fci and read data. + * + * This is done by mean of iso_select_file() and iso_read_binary() + * + * @param card pointer to sc_card data + * @param path pathfile + * @param file pointer to resulting file descriptor + * @param buffer pointer to buffer where to store file contents + * @param length length of buffer data + * @return SC_SUCCESS if ok; else error code + */ +int dnie_read_file(sc_card_t * card, + const sc_path_t * path, + sc_file_t ** file, u8 ** buffer, size_t * length) +{ + u8 *data; + char *msg = NULL; + int res = SC_SUCCESS; + size_t fsize = 0; /* file size */ + sc_context_t *ctx = NULL; + + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(card->ctx); + if (!buffer || !length || !path) /* check received arguments */ + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + /* select file by mean of iso7816 ops */ + res = card->ops->select_file(card, path, file); + if (res != SC_SUCCESS) { + msg = "select_file failed"; + goto dnie_read_file_err; + } + /* iso's select file calls if needed process_fci, so arriving here + * we have file structure filled. + */ + if ((*file)->type == SC_FILE_TYPE_DF) { + /* just a DF, no need to read_binary() */ + *buffer = NULL; + *length = 0; + res = SC_SUCCESS; + msg = "File is a DF: no need to read_binary()"; + goto dnie_read_file_end; + } + fsize = (*file)->size; + /* reserve enought space to read data from card */ + if (fsize <= 0) { + res = SC_ERROR_FILE_TOO_SMALL; + msg = "provided buffer size is too small"; + goto dnie_read_file_err; + } + data = calloc(fsize, sizeof(u8)); + if (data == NULL) { + res = SC_ERROR_OUT_OF_MEMORY; + msg = "cannot reserve requested buffer size"; + goto dnie_read_file_err; + } + /* call sc_read_binary() to retrieve data */ + sc_log(ctx, "read_binary(): expected '%d' bytes", fsize); + res = sc_read_binary(card, 0, data, fsize, 0L); + if (res < 0) { /* read_binary returns number of bytes readed */ + res = SC_ERROR_CARD_CMD_FAILED; + msg = "read_binary() failed"; + goto dnie_read_file_err; + } + *buffer = data; + *length = res; + /* arriving here means success */ + res = SC_SUCCESS; + goto dnie_read_file_end; + dnie_read_file_err: + if (*file) + sc_file_free(*file); + dnie_read_file_end: + if (msg) + sc_log(ctx, msg); + LOG_FUNC_RETURN(ctx, res); +} + +/** + * Read SM required certificates from card. + * + * This function uses received path to read a certificate file from + * card. + * No validation is done except that received data is effectively a certificate + * @param card Pointer to card driver structure + * @param certpat path to requested certificate + * @param cert where to store resultig data + * @return SC_SUCCESS if ok, else error code + */ +static int dnie_read_certificate(sc_card_t * card, char *certpath, X509 ** cert) +{ + sc_file_t *file = NULL; + sc_path_t *path = NULL; + u8 *buffer = NULL; + char *msg = NULL; + size_t bufferlen = 0; + int res = SC_SUCCESS; + + LOG_FUNC_CALLED(card->ctx); + path = (sc_path_t *) calloc(1, sizeof(sc_path_t)); + if (!path) { + msg = "Cannot allocate path data for cert read"; + res = SC_ERROR_OUT_OF_MEMORY; + goto read_cert_end; + } + sc_format_path(certpath, path); + res = dnie_read_file(card, path, &file, &buffer, &bufferlen); + if (res != SC_SUCCESS) { + msg = "Cannot get intermediate CA cert"; + goto read_cert_end; + } + *cert = d2i_X509(NULL, (const unsigned char **)&buffer, bufferlen); + if (*cert == NULL) { /* received data is not a certificate */ + res = SC_ERROR_OBJECT_NOT_VALID; + msg = "Readed data is not a certificate"; + goto read_cert_end; + } + res = SC_SUCCESS; + + read_cert_end: + if (file) { + sc_file_free(file); + file = NULL; + buffer = NULL; + bufferlen = 0; + } + if (msg) + sc_log(card->ctx, msg); + LOG_FUNC_RETURN(card->ctx, res); +} + +/************ implementation of cwa provider methods **************/ + +/** + * Retrieve Root CA public key. + * + * Just returns (as local SM authentication) static data + * @param card Pointer to card driver structure + * @param root_ca_key pointer to resulting returned key + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_root_ca_pubkey(sc_card_t * card, EVP_PKEY ** root_ca_key) +{ + int res=SC_SUCCESS; + RSA *root_ca_rsa=NULL; + LOG_FUNC_CALLED(card->ctx); + + /* compose root_ca_public key with data provided by Dnie Manual */ + *root_ca_key = EVP_PKEY_new(); + root_ca_rsa = RSA_new(); + if (!*root_ca_key || !root_ca_rsa) { + sc_log(card->ctx, "Cannot create data for root CA public key"); + return SC_ERROR_OUT_OF_MEMORY; + } + root_ca_rsa->n = BN_bin2bn(icc_root_ca_modulus, + sizeof(icc_root_ca_modulus), root_ca_rsa->n); + root_ca_rsa->e = BN_bin2bn(icc_root_ca_public_exponent, + sizeof(icc_root_ca_public_exponent), + root_ca_rsa->e); + res = EVP_PKEY_assign_RSA(*root_ca_key, root_ca_rsa); + if (!res) { + if (*root_ca_key) + EVP_PKEY_free(*root_ca_key); /*implies root_ca_rsa free() */ + sc_log(card->ctx, "Cannot compose root CA public key"); + return SC_ERROR_INTERNAL; + } + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +/** + * Retrieve IFD (application) CVC intermediate CA certificate and length. + * + * Returns a byte array with the intermediate CA certificate + * (in CardVerifiable Certificate format) to be sent to the + * card in External Authentication process + * As this is local provider, just points to provided static data, + * and allways return success + * + * @param card Pointer to card driver Certificate + * @param cert Where to store resulting byte array + * @param length len of returned byte array + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_cvc_ca_cert(sc_card_t * card, u8 ** cert, size_t * length) +{ + LOG_FUNC_CALLED(card->ctx); + *cert = C_CV_CA_CS_AUT_cert; + *length = sizeof(C_CV_CA_CS_AUT_cert); + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +/** + * Retrieve IFD (application) CVC certificate and length. + * + * Returns a byte array with the application's certificate + * (in CardVerifiable Certificate format) to be sent to the + * card in External Authentication process + * As this is local provider, just points to provided static data, + * and allways return success + * + * @param card Pointer to card driver Certificate + * @param cert Where to store resulting byte array + * @param length len of returned byte array + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_cvc_ifd_cert(sc_card_t * card, u8 ** cert, size_t * length) +{ + LOG_FUNC_CALLED(card->ctx); + *cert = C_CV_IFDUser_AUT_cert; + *length = sizeof(C_CV_IFDUser_AUT_cert); + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +/** + * Get IFD (Terminal) private key data. + * + * As this is a local (in memory) provider, just get data specified in + * DNIe's manual and compose an OpenSSL private key structure + * + * Notice that resulting data should be keept in memory as little as possible + * Erasing them once used + * + * @param card pointer to card driver structure + * @param ifd_privkey where to store IFD private key + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_ifd_privkey(sc_card_t * card, EVP_PKEY ** ifd_privkey) +{ + RSA *ifd_rsa=NULL; + int res=SC_SUCCESS; + + LOG_FUNC_CALLED(card->ctx); + + /* compose ifd_private key with data provided in Annex 3 of DNIe Manual */ + *ifd_privkey = EVP_PKEY_new(); + ifd_rsa = RSA_new(); + if (!*ifd_privkey || !ifd_rsa) { + sc_log(card->ctx, "Cannot create data for IFD private key"); + return SC_ERROR_OUT_OF_MEMORY; + } + ifd_rsa->n = BN_bin2bn(ifd_modulus, sizeof(ifd_modulus), ifd_rsa->n); + ifd_rsa->e = + BN_bin2bn(ifd_public_exponent, sizeof(ifd_public_exponent), + ifd_rsa->e); + ifd_rsa->d = + BN_bin2bn(ifd_private_exponent, sizeof(ifd_private_exponent), + ifd_rsa->d); + res = EVP_PKEY_assign_RSA(*ifd_privkey, ifd_rsa); + if (!res) { + if (*ifd_privkey) + EVP_PKEY_free(*ifd_privkey); /* implies ifd_rsa free() */ + sc_log(card->ctx, "Cannot compose IFD private key"); + return SC_ERROR_INTERNAL; + } + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +/** + * Get ICC intermediate CA Certificate from card. + * + * @param card Pointer to card driver structure + * @param cert where to store resulting certificate + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_icc_intermediate_ca_cert(sc_card_t * card, X509 ** cert) +{ + return dnie_read_certificate(card, "3F006020", cert); +} + +/** + * Get ICC (card) certificate. + * + * @param card Pointer to card driver structure + * @param cert where to store resulting certificate + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_icc_cert(sc_card_t * card, X509 ** cert) +{ + return dnie_read_certificate(card, "3F00601F", cert); +} + +/** + * Retrieve key reference for Root CA to validate CVC intermediate CA certs. + * + * This is required in the process of On card external authenticate + * @param card Pointer to card driver structure + * @param buf where to store resulting key reference + * @param len where to store buffer length + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_root_ca_pubkey_ref(sc_card_t * card, u8 ** buf, + size_t * len) +{ + *buf = root_ca_keyref; + *len = sizeof(root_ca_keyref); + return SC_SUCCESS; +} + +/** + * Retrieve public key reference for intermediate CA to validate IFD cert. + * + * This is required in the process of On card external authenticate + * As this driver is for local SM authentication SC_SUCCESS is allways returned + * + * @param card Pointer to card driver structure + * @param buf where to store resulting key reference + * @param len where to store buffer length + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_intermediate_ca_pubkey_ref(sc_card_t * card, u8 ** buf, + size_t * len) +{ + *buf = cvc_intca_keyref; + *len = sizeof(cvc_intca_keyref); + return SC_SUCCESS; +} + +/** + * Retrieve public key reference for IFD certificate. + * + * This tells the card with in memory key reference is to be used + * when CVC cert is sent for external auth procedure + * As this driver is for local SM authentication SC_SUCCESS is allways returned + * + * @param card pointer to card driver structure + * @param buf where to store data to be sent + * @param len where to store data length + * @return SC_SUCCESS if ok; else error code + */ +static int dnie_get_ifd_pubkey_ref(sc_card_t * card, u8 ** buf, size_t * len) +{ + *buf = cvc_ifd_keyref; + *len = sizeof(cvc_ifd_keyref); + return SC_SUCCESS; +} + +/** + * Retrieve key reference for ICC privkey. + * + * In local SM stablishment, just retrieve key reference from static + * data tables and just return success + * + * @param card pointer to card driver structure + * @param buf where to store data + * @param len where to store data length + * @return SC_SUCCESS if ok; else error + */ +static int dnie_get_icc_privkey_ref(sc_card_t * card, u8 ** buf, size_t * len) +{ + *buf = icc_priv_keyref; + *len = sizeof(icc_priv_keyref); + return SC_SUCCESS; +} + +/** + * Retrieve SN.IFD (8 bytes left padded with zeroes if required). + * + * In DNIe local SM procedure, just read it from static data and + * return SC_SUCCESS + * + * @param card pointer to card structure + * @param buf where to store result (8 bytes) + * @return SC_SUCCESS if ok; else error + */ +static int dnie_get_sn_ifd(sc_card_t * card, u8 ** buf) +{ + *buf = sn_ifd; + return SC_SUCCESS; +} + +/* Retrieve SN.ICC (8 bytes left padded with zeroes if needed). + * + * As DNIe reads serial number at startup, no need to read again + * Just retrieve it from cache and return success + * + * @param card pointer to card structure + * @param buf where to store result (8 bytes) + * @return SC_SUCCESS if ok; else error + */ +static int dnie_get_sn_icc(sc_card_t * card, u8 ** buf) +{ + int res=SC_SUCCESS; + sc_serial_number_t serial; + + res = sc_card_ctl(card, SC_CARDCTL_GET_SERIALNR, &serial); + LOG_TEST_RET(card->ctx, res, "Error in gettting serial number"); + /* copy into sn_icc buffer.Remember that dnie sn has 7 bytes length */ + memset(&sn_icc[0], 0, sizeof(sn_icc)); + memcpy(&sn_icc[1], serial.value, 7); + /* return data */ + *buf = &sn_icc[0]; + return SC_SUCCESS; +} + +/** + * CWA-14890 SM stablisment pre-operations. + * + * DNIe needs to get icc serial number at the begin of the sm creation + * (to avoid breaking key references) so get it an store into serialnr + * cache here. + * + * In this way if get_sn_icc is called(), we make sure that no APDU + * command is to be sent to card, just retrieve it from cache + * + * @param card pointer to card driver structure + * @param provider pointer to SM data provider for DNIe + * @return SC_SUCCESS if OK. else error code + */ +static int dnie_create_pre_ops(sc_card_t * card, cwa_provider_t * provider) +{ + sc_serial_number_t serial; + + /* make sure that this cwa provider is used with a working DNIe card */ + if (card->type != SC_CARD_TYPE_DNIE_USER) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_CARD); + + /* ensure that Card Serial Number is properly cached */ + return sc_card_ctl(card, SC_CARDCTL_GET_SERIALNR, &serial); +} + +/** + * Main entry point for DNIe CWA14890 SM data provider. + * + * Return a pointer to DNIe data provider with proper function pointers + * + * @param card pointer to card driver data structure + * @return cwa14890 DNIe data provider if success, null on error + */ +cwa_provider_t *dnie_get_cwa_provider(sc_card_t * card) +{ + + cwa_provider_t *res = cwa_get_default_provider(card); + if (!res) + return NULL; + + /* set up proper data */ + + /* pre and post operations */ + res->cwa_create_pre_ops = dnie_create_pre_ops; + res->cwa_create_post_ops = NULL; + + /* Get ICC intermediate CA path */ + res->cwa_get_icc_intermediate_ca_cert = + dnie_get_icc_intermediate_ca_cert; + /* Get ICC certificate path */ + res->cwa_get_icc_cert = dnie_get_icc_cert; + + /* Obtain RSA public key from RootCA */ + res->cwa_get_root_ca_pubkey = dnie_get_root_ca_pubkey; + /* Obtain RSA IFD private key */ + res->cwa_get_ifd_privkey = dnie_get_ifd_privkey; + + /* Retrieve CVC intermediate CA certificate and length */ + res->cwa_get_cvc_ca_cert = dnie_get_cvc_ca_cert; + /* Retrieve CVC IFD certificate and length */ + res->cwa_get_cvc_ifd_cert = dnie_get_cvc_ifd_cert; + + /* Get public key references for Root CA to validate intermediate CA cert */ + res->cwa_get_root_ca_pubkey_ref = dnie_get_root_ca_pubkey_ref; + + /* Get public key reference for IFD intermediate CA certificate */ + res->cwa_get_intermediate_ca_pubkey_ref = + dnie_get_intermediate_ca_pubkey_ref; + + /* Get public key reference for IFD CVC certificate */ + res->cwa_get_ifd_pubkey_ref = dnie_get_ifd_pubkey_ref; + + /* Get ICC private key reference */ + res->cwa_get_icc_privkey_ref = dnie_get_icc_privkey_ref; + + /* Get IFD Serial Number */ + res->cwa_get_sn_ifd = dnie_get_sn_ifd; + + /* Get ICC Serial Number */ + res->cwa_get_sn_icc = dnie_get_sn_icc; + + /************** operations related with APDU encoding ******************/ + + /* pre and post operations */ + res->cwa_encode_pre_ops = NULL; + res->cwa_encode_post_ops = NULL; + + /************** operations related APDU response decoding **************/ + + /* pre and post operations */ + res->cwa_decode_pre_ops = NULL; + res->cwa_decode_post_ops = NULL; + + return res; +} + +static int dnie_transmit_apdu_internal(sc_card_t * card, sc_apdu_t * apdu) +{ + u8 *buf = NULL; /* use for store partial le responses */ + int res = SC_SUCCESS; + cwa_provider_t *provider = NULL; + if ((card == NULL) || (card->ctx == NULL) || (apdu == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + provider = GET_DNIE_PRIV_DATA(card)->cwa_provider; + buf = calloc(2048, sizeof(u8)); + if (!buf) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); + + /* check if envelope is needed */ + if (apdu->lc <= card->max_send_size) { + int tmp; + /* no envelope needed */ + sc_log(card->ctx, "envelope tx is not required"); + + tmp = apdu->cse; /* save original apdu type */ + /* if SM is on, assure rx buffer exists and force get_response */ + if (provider->status.session.state == CWA_SM_ACTIVE) { + if (tmp == SC_APDU_CASE_3_SHORT) + apdu->cse = SC_APDU_CASE_4_SHORT; + if (apdu->resplen == 0) { /* no response buffer: create */ + apdu->resp = buf; + apdu->resplen = 2048; + apdu->le = card->max_recv_size; + } + } + /* call std sc_transmit_apdu */ + res = sc_transmit_apdu(card, apdu); + /* and restore original apdu type */ + apdu->cse = tmp; + } else { + + size_t e_txlen = 0; + size_t index = 0; + sc_apdu_t *e_apdu = NULL; + u8 *e_tx = NULL; + + /* envelope needed */ + sc_log(card->ctx, "envelope tx required: lc:%d", apdu->lc); + + e_apdu = calloc(1, sizeof(sc_apdu_t)); /* enveloped apdu */ + e_tx = calloc(7 + apdu->datalen, sizeof(u8)); /* enveloped data */ + if (!e_apdu || !e_tx) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_OUT_OF_MEMORY); + + /* copy apdu info into enveloped data */ + *(e_tx + 0) = apdu->cla; /* apdu header */ + *(e_tx + 1) = apdu->ins; + *(e_tx + 2) = apdu->p1; + *(e_tx + 3) = apdu->p2; + *(e_tx + 4) = 0x00; /* length in extended format */ + *(e_tx + 5) = 0xff & (apdu->lc >> 8); + *(e_tx + 6) = 0xff & apdu->lc; + memcpy(e_tx + 7, apdu->data, apdu->lc); + e_txlen = 7 + apdu->lc; + /* sc_log(card->ctx, "Data to be enveloped & sent: (%d bytes)\n%s\n===============================================================",e_txlen,sc_dump_hex(e_tx,e_txlen)); */ + /* split apdu in n chunks of max_send_size len */ + for (index = 0; index < e_txlen; index += card->max_send_size) { + size_t len = MIN(card->max_send_size, e_txlen - index); + sc_log(card->ctx, "envelope tx offset:%04X size:%02X", + index, len); + + /* compose envelope apdu command */ + sc_format_apdu(card, e_apdu, apdu->cse, 0xC2, 0x00, + 0x00); + e_apdu->cla = 0x90; /* propietary CLA */ + e_apdu->data = e_tx + index; + e_apdu->lc = len; + e_apdu->datalen = len; + e_apdu->le = apdu->le; + e_apdu->resp = apdu->resp; + e_apdu->resplen = apdu->resplen; + /* if SM is ON, ensure resp exists, and force getResponse() */ + if (provider->status.session.state == CWA_SM_ACTIVE) { + /* set up proper apdu type */ + if (e_apdu->cse == SC_APDU_CASE_3_SHORT) + e_apdu->cse = SC_APDU_CASE_4_SHORT; + /* if no response buffer: create */ + if (apdu->resplen == 0) { + e_apdu->resp = buf; + e_apdu->resplen = 2048; + e_apdu->le = card->max_recv_size; + } + } + /* send data chunk bypassing apdu wrapping */ + res = sc_transmit_apdu(card, e_apdu); + LOG_TEST_RET(card->ctx, res, + "Error in envelope() send apdu"); + } /* for */ + /* last apdu sent contains response to enveloped cmd */ + apdu->resp = e_apdu->resp; + apdu->resplen = e_apdu->resplen; + res = SC_SUCCESS; + } + LOG_FUNC_RETURN(card->ctx, res); +} + +/** + * APDU Wrapping routine. + * + * Called before sc_transmit_apdu() to allowing APDU wrapping + * If set to NULL no wrapping process will be done + * Usefull on Secure Messaging APDU encode/decode + * If returned value is greater than zero, do_single_transmit() + * will be called, else means either SC_SUCCESS or error code + * + * NOTE: + * DNIe doesn't handle apdu chaining; instead apdus with + * lc>max_send_size are sent by mean of envelope() apdu command + * So we use this method for + * - encode and decode SM if SM is on + * - use envelope instead of apdu chain if lc>max_send_size + * + * @param card Pointer to Card Structure + * @param apdu to be wrapped + * @return + * - positive: use OpenSC's sc_transmit_apdu() + * - negative: error + * - zero: success: no need to further transmission + */ +static int dnie_wrap_apdu(sc_card_t * card, sc_apdu_t * apdu) +{ + int res = SC_SUCCESS; + sc_apdu_t wrapped; + sc_context_t *ctx; + cwa_provider_t *provider = NULL; + int retries = 3; + + if ((card == NULL) || (card->ctx == NULL) || (apdu == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + ctx=card->ctx; + LOG_FUNC_CALLED(ctx); + provider = GET_DNIE_PRIV_DATA(card)->cwa_provider; + for (retries=3; retries>0; retries--) { + /* preserve original apdu to take care of retransmission */ + memcpy(&wrapped, apdu, sizeof(sc_apdu_t)); + /* SM is active, encode apdu */ + if (provider->status.session.state == CWA_SM_ACTIVE) { + wrapped.resp = NULL; + wrapped.resplen = 0; /* let get_response() assign space */ + res = cwa_encode_apdu(card, provider, apdu, &wrapped); + LOG_TEST_RET(ctx, res, + "Error in cwa_encode_apdu process"); + } + /* send apdu via envelope() cmd if needed */ + res = dnie_transmit_apdu_internal(card, &wrapped); + /* check for tx errors */ + LOG_TEST_RET(ctx, res, "Error in dnie_transmit_apdu process"); + + /* parse response and handle SM related errors */ + res=card->ops->check_sw(card,wrapped.sw1,wrapped.sw2); + if ( res == SC_ERROR_SM ) { + sc_log(ctx,"Detected SM error/collision. Try %d",retries); + switch(provider->status.session.state) { + /* No SM or creating: collision with other process + just retry as SM error reset ICC SM state */ + case CWA_SM_NONE: + case CWA_SM_INPROGRESS: + continue; + /* SM was active: force restart SM and retry */ + case CWA_SM_ACTIVE: + res=cwa_create_secure_channel(card, provider, CWA_SM_COLD); + LOG_TEST_RET(ctx,res,"Cannot re-enable SM"); + continue; + } + } + + /* if SM is active; decode apdu */ + if (provider->status.session.state == CWA_SM_ACTIVE) { + apdu->resp = NULL; + apdu->resplen = 0; /* let cwa_decode_response() eval & create size */ + res = cwa_decode_response(card, provider, &wrapped, apdu); + LOG_TEST_RET(ctx, res, "Error in cwa_decode_response process"); + } else { + /* memcopy result to original apdu */ + memcpy(apdu, &wrapped, sizeof(sc_apdu_t)); + } + LOG_FUNC_RETURN(ctx, res); + } + sc_log(ctx,"Too many retransmissions. Abort and return"); + LOG_FUNC_RETURN(ctx, SC_ERROR_INTERNAL); +} + +int dnie_transmit_apdu(sc_card_t * card, sc_apdu_t * apdu) +{ + int res = SC_SUCCESS; + res = dnie_wrap_apdu(card, apdu); + if (res <= 0) return res; + return sc_transmit_apdu(card, apdu); +} + +#endif /* HAVE_OPENSSL */ +/* _ end of cwa-dnie.c - */ diff --git a/src/libopensc/cwa-dnie.h b/src/libopensc/cwa-dnie.h new file mode 100644 index 00000000..31844704 --- /dev/null +++ b/src/libopensc/cwa-dnie.h @@ -0,0 +1,60 @@ +/** + * cwa-dnie.h: Defines dnie_transmit_apdu wrapper for sc_transmit_apdu + * + * This work is derived from many sources at OpenSC Project site, + * (see references), and the information made public for Spanish + * Direccion General de la Policia y de la Guardia Civil + * + * 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 __CWADNIE_H__ +#define __CWADNIE_H__ + +#ifdef ENABLE_OPENSSL + +#include "libopensc/opensc.h" +#include "cwa14890.h" +#ifdef ENABLE_DNIE_UI +#include "user-interface.h" +#endif + +/** + * OpenDNIe private data declaration + * + * Defines internal data used in OpenDNIe code + */ + typedef struct dnie_private_data_st { + /* sc_serial_number_t *serialnumber; < Cached copy of card serial number NOT USED AT THE MOMENT */ + int rsa_key_ref; /**< Key id reference being used in sec operation */ + u8 *cache; /**< Cache buffer for read_binary() operation */ + size_t cachelen; /**< length of cache buffer */ + cwa_provider_t *cwa_provider; +#ifdef ENABLE_DNIE_UI + struct ui_context ui_ctx; +#endif + } dnie_private_data_t; + +/** + * DNIe Card Driver private data + */ +#define GET_DNIE_PRIV_DATA(card) ((dnie_private_data_t *) ((card)->drv_data)) +#define GET_DNIE_UI_CTX(card) (((dnie_private_data_t *) ((card)->drv_data))->ui_ctx) + +int dnie_transmit_apdu(sc_card_t * card, sc_apdu_t * apdu); + +#endif + +#endif diff --git a/src/libopensc/cwa14890.c b/src/libopensc/cwa14890.c new file mode 100644 index 00000000..c7c23cae --- /dev/null +++ b/src/libopensc/cwa14890.c @@ -0,0 +1,2136 @@ +/** + * cwa14890.c: Implementation of Secure Messaging according CWA-14890-1 and CWA-14890-2 standards. + * + * Copyright (C) 2010 Juan Antonio Martinez + * + * This work is derived from many sources at OpenSC Project site, + * (see references) and the information made public by Spanish + * Direccion General de la Policia y de la Guardia Civil + * + * 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 + */ + +#define __CWA14890_C__ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef ENABLE_OPENSSL /* empty file without openssl */ + +#include +#include +#include + +#include "opensc.h" +#include "cardctl.h" +#include "internal.h" +#include +#include +#include +#include "cwa-dnie.h" + +#include "cwa14890.h" + +/*********************** utility functions ************************/ + +/** + * Tool for create a string dump of a provided buffer. + * + * When buffer length is longer than 16384 bytes, output is cut + * + * @param buff Buffer to be printed + * @param len Buffer len + * @return a char buffer with data dump in hex+ascii format + */ +static char *cwa_hexdump(const u8 * buf, size_t len) +{ + size_t j; + size_t count = 0; + static char res[16384]; + memset(res, 0, sizeof(res)); + len = MIN(len, sizeof(res)); + for (count = 0; count < len; count += 16) { + size_t nitems = MIN(16, len - count); + for (j = 0; j < nitems; j++) + sprintf(res, "%s%02X ", res, 0xff & *(buf + count + j)); + for (; j < 16; j++) + sprintf(res, "%s ", res); + for (j = 0; j < nitems; j++) { + char c = (char)*(buf + count + j); + sprintf(res, "%s%c", res, (isprint(c) ? c : '.')); + } + for (; j < 16; j++) + sprintf(res, "%s ", res); + sprintf(res, "%s\n", res); + } + return res; +} + +/** + * Dump an APDU before SM translation. + * + * This is mainly for debugging purposes. programmer should disable + * this function in a production environment, as APDU will be shown + * in text-plain on debug traces + * + * @param card Pointer to card driver data structure + * @param apdu APDU to be encoded, or APDU response after decoded + * @param flag 0: APDU is to be encoded: 1; APDU decoded response + */ +static void cwa_trace_apdu(sc_card_t * card, sc_apdu_t * apdu, int flag) +{ + char *buf = NULL; +/* set to 0 in production */ +#if 1 + if (!card || !card->ctx || !apdu) + return; + if (flag == 0) { /* apdu command */ + if (apdu->datalen > 0) { /* apdu data to show */ + buf = cwa_hexdump(apdu->data, apdu->datalen); + sc_log(card->ctx, + "\nAPDU before encode: ==================================================\nCLA: %02X INS: %02X P1: %02X P2: %02X Lc: %02X Le: %02X DATA: [%5u bytes]\n%s======================================================================\n", + apdu->cla, apdu->ins, apdu->p1, apdu->p2, + apdu->lc, apdu->le, apdu->datalen, buf); + } else { /* apdu data field is empty */ + sc_log(card->ctx, + "\nAPDU before encode: ==================================================\nCLA: %02X INS: %02X P1: %02X P2: %02X Lc: %02X Le: %02X (NO DATA)\n======================================================================\n", + apdu->cla, apdu->ins, apdu->p1, apdu->p2, + apdu->lc, apdu->le); + } + } else { /* apdu response */ + buf = cwa_hexdump(apdu->resp, apdu->resplen); + sc_log(card->ctx, + "\nAPDU response after decode: ==========================================\nSW1: %02X SW2: %02X RESP: [%5u bytes]\n%s======================================================================\n", + apdu->sw1, apdu->sw2, apdu->resplen, buf); + } +#endif + +} + +/** + * Increase send sequence counter SSC. + * + * @param card smart card info structure + * @param sm Secure Message session handling data structure + * @return SC_SUCCESS if ok; else error code + * + * TODO: to further study: what about using bignum arithmetics? + */ +static int cwa_increase_ssc(sc_card_t * card, cwa_sm_session_t * sm) +{ + int n; + /* preliminary checks */ + if (!card || !card->ctx ) + return SC_ERROR_INVALID_ARGUMENTS; + if (!sm ) + return SC_ERROR_SM_NOT_INITIALIZED; + LOG_FUNC_CALLED(card->ctx); + /* u8 arithmetic; exit loop if no carry */ + sc_log(card->ctx, "Curr SSC: '%s'", sc_dump_hex(sm->ssc, 8)); + for (n = 7; n >= 0; n--) { + sm->ssc[n]++; + if ((sm->ssc[n]) != 0x00) + break; + } + sc_log(card->ctx, "Next SSC: '%s'", sc_dump_hex(sm->ssc, 8)); + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); +} + +/** + * ISO 7816 padding. + * + * Adds an 0x80 at the end of buffer and as many zeroes to get len + * multiple of 8 + * Buffer must be long enougth to store additional bytes + * + * @param buffer where to compose data + * @param len pointer to buffer length + */ +static void cwa_iso7816_padding(u8 * buf, size_t * buflen) +{ + buf[*buflen] = 0x80; + (*buflen)++; + for (; *buflen & 0x07; (*buflen)++) + buf[*buflen] = 0x00; +} + +/** + * compose a BER-TLV data in provided buffer. + * + * Multybyte tag id are not supported + * Also multibyte id 0x84 is unhandled + * + * Notice that TLV is composed starting at offset lenght from + * the buffer. Consecutive calls to cwa_add_tlv, appends a new + * TLV at the end of the buffer + * + * @param card card info structure + * @param tag tag id + * @param len data length + * @param value data buffer + * @param out pointer to dest data + * @param outlen length of composed tlv data + * @return SC_SUCCESS if ok; else error + */ +static int cwa_compose_tlv(sc_card_t * card, + u8 tag, + size_t len, u8 * data, u8 ** out, size_t * outlen) +{ + u8 *pt; + size_t size; + sc_context_t *ctx; + /* preliminary checks */ + if (!card || !card->ctx || !out || !outlen) + return SC_ERROR_INVALID_ARGUMENTS; + /* comodity vars */ + ctx = card->ctx; + + LOG_FUNC_CALLED(ctx); + pt = *out; + size = *outlen; + + /* assume tag id is not multibyte */ + *(pt + size++) = tag; + /* evaluate tag length value according iso7816-4 sect 5.2.2 */ + if (len < 0x80) { + *(pt + size++) = len; + } else if (len < 0x00000100) { + *(pt + size++) = 0x81; + *(pt + size++) = 0xff & len; + } else if (len < 0x00010000) { + *(pt + size++) = 0x82; + *(pt + size++) = 0xff & (len >> 8); + *(pt + size++) = 0xff & len; + } else if (len < 0x01000000) { + *(pt + size++) = 0x83; + *(pt + size++) = 0xff & (len >> 16); + *(pt + size++) = 0xff & (len >> 8); + *(pt + size++) = 0xff & len; + } else { /* do not handle tag length 0x84 */ + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + } + /* copy remaining data to buffer */ + if (len != 0) + memcpy(pt + size, data, len); + size += len; + *outlen = size; + LOG_FUNC_RETURN(ctx, SC_SUCCESS); +} + +/** + * Parse and APDU Response and extract specific BER-TLV data. + * + * NOTICE that iso7816 sect 5.2.2 states that Tag length may be 1 to n bytes + * length. In this code we'll assume allways tag lenght = 1 byte + * + * @param card card info structure + * @param data Buffer to look for tlv into + * @param datalen Buffer len + * @param tlv array of TLV structure to store results into + * @return SC_SUCCESS if OK; else error code + */ +static int cwa_parse_tlv(sc_card_t * card, + u8 * data, size_t datalen, cwa_tlv_t tlv_array[] + ) +{ + size_t n = 0; + size_t next = 0; + sc_context_t *ctx = NULL; + u8 *buffer = NULL; + + /* preliminary checks */ + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + /* comodity vars */ + ctx = card->ctx; + + LOG_FUNC_CALLED(ctx); + if (!data || !tlv_array) + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + + /* create buffer and copy data into */ + buffer = calloc(datalen, sizeof(u8)); + if (!buffer) + LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); + memcpy(buffer, data, datalen); + for (n = 0; n < datalen; n += next) { + cwa_tlv_t *tlv = NULL; /* pointer to TLV structure to store info */ + size_t j = 2; /* TLV has at least two bytes */ + switch (*(buffer + n)) { + case CWA_SM_PLAIN_TAG: + tlv = &tlv_array[0]; + break; /* 0x81 Plain */ + case CWA_SM_CRYPTO_TAG: + tlv = &tlv_array[1]; + break; /* 0x87 Crypto */ + case CWA_SM_MAC_TAG: + tlv = &tlv_array[2]; + break; /* 0x8E MAC CC */ + case CWA_SM_STATUS_TAG: + tlv = &tlv_array[3]; + break; /* 0x99 Status */ + default: /* CWA_SM_LE_TAG (0x97) is not valid here */ + sc_log(ctx, "Invalid TLV Tag type: '0x%02X'", + *(buffer + n)); + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_DATA); + } + tlv->buf = buffer + n; + tlv->tag = 0xff & *(buffer + n); + tlv->len = 0; /* temporary */ + /* evaluate len and start of data */ + switch (0xff & *(buffer + n + 1)) { + case 0x84: + tlv->len = (0xff & *(buffer + n + j++)); + case 0x83: + tlv->len = + (tlv->len << 8) + (0xff & *(buffer + n + j++)); + case 0x82: + tlv->len = + (tlv->len << 8) + (0xff & *(buffer + n + j++)); + case 0x81: + tlv->len = + (tlv->len << 8) + (0xff & *(buffer + n + j++)); + break; + /* case 0x80 is not standard, but official code uses it */ + case 0x80: + tlv->len = + (tlv->len << 8) + (0xff & *(buffer + n + j++)); + break; + default: + if ((*(buffer + n + 1) & 0xff) < 0x80) { + tlv->len = 0xff & *(buffer + n + 1); + } else { + sc_log(ctx, "Invalid tag length indicator: %d", + *(buffer + n + 1)); + LOG_FUNC_RETURN(ctx, SC_ERROR_WRONG_LENGTH); + } + } + tlv->data = buffer + n + j; + tlv->buflen = j + tlv->len;; + sc_log(ctx, "Found Tag: '0x%02X': Length: '%d 'Value:\n%s", + tlv->tag, tlv->len, sc_dump_hex(tlv->data, tlv->len)); + /* set index to next Tag to jump to */ + next = tlv->buflen; + } + LOG_FUNC_RETURN(ctx, SC_SUCCESS); /* mark no error */ +} + +/*********************** authentication routines *******************/ + +/** + * Verify certificates provided by card. + * + * This routine uses Root CA public key data From Annex III of manual + * to verify intermediate CA icc certificate provided by card + * if verify sucess, then extract public keys from intermediate CA + * and verify icc certificate + * + * @param card pointer to sc_card_contex + * @param sub_ca_cert icc intermediate CA certificate readed from card + * @param icc_ca icc certificate from card + * @return SC_SUCCESS if verification is ok; else error code + */ +static int cwa_verify_icc_certificates(sc_card_t * card, + cwa_provider_t * provider, + X509 * sub_ca_cert, X509 * icc_cert) +{ + char *msg; + int res = SC_SUCCESS; + EVP_PKEY *root_ca_key = NULL; + EVP_PKEY *sub_ca_key = NULL; + sc_context_t *ctx = NULL; + + /* safety check */ + if (!card || !card->ctx || !provider) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + if (!sub_ca_cert || !icc_cert) /* check received arguments */ + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + + /* retrieve root ca pkey from provider */ + res = provider->cwa_get_root_ca_pubkey(card, &root_ca_key); + if (res != SC_SUCCESS) { + msg = "Cannot get root CA public key"; + res = SC_ERROR_INTERNAL; + goto verify_icc_certificates_end; + } + + /* verify sub_ca_cert against root_ca_key */ + res = X509_verify(sub_ca_cert, root_ca_key); + if (!res) { + msg = "Cannot verify icc Sub-CA certificate"; + res = SC_ERROR_SM_AUTHENTICATION_FAILED; + goto verify_icc_certificates_end; + } + + /* extract sub_ca_key from sub_ca_cert */ + sub_ca_key = X509_get_pubkey(sub_ca_cert); + + /* verify icc_cert against sub_ca_key */ + res = X509_verify(icc_cert, sub_ca_key); + if (!res) { + msg = "Cannot verify icc certificate"; + res = SC_ERROR_SM_AUTHENTICATION_FAILED; + goto verify_icc_certificates_end; + } + + /* arriving here means certificate verification success */ + res = SC_SUCCESS; + verify_icc_certificates_end: + if (root_ca_key) + EVP_PKEY_free(root_ca_key); + if (sub_ca_key) + EVP_PKEY_free(sub_ca_key); + if (res != SC_SUCCESS) + sc_log(ctx, msg); + LOG_FUNC_RETURN(ctx, res); +} + +/** + * Verify CVC certificates in SM establishment process. + * + * This is done by mean of 00 2A 00 AE + * (Perform Security Operation: Verify Certificate ) + * + * @param card pointer to card data + * @param cert Certificate in CVC format + * @param len length of CVC certificate + * @return SC_SUCCESS if ok; else error code + */ +static int cwa_verify_cvc_certificate(sc_card_t * card, + const u8 * cert, size_t len) +{ + sc_apdu_t apdu; + int result = SC_SUCCESS; + sc_context_t *ctx = NULL; + + /* safety check */ + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + if (!cert || (len <= 0)) /* check received arguments */ + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + + /* compose apdu for Perform Security Operation (Verify cert) cmd */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x2A, 0x00, 0xAE); + apdu.data = cert; + apdu.datalen = len; + apdu.lc = len; + apdu.le = 0; + apdu.resplen = 0; + apdu.resp = NULL; + + /* send composed apdu and parse result */ + result = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(ctx, result, "Verify CVC certificate failed"); + result = sc_check_sw(card, apdu.sw1, apdu.sw2); + LOG_FUNC_RETURN(ctx, result); +} + +/** + * Alternate implementation for set_security environment. + * + * Used to handle raw apdu data in set_security_env() on SM stblishment + * Standard set_securiy_env() method has sc_security_env->buffer limited + * to 8 bytes; so cannot send some of required SM commands. + * + * @param card pointer to card data + * @param p1 apdu P1 parameter + * @param p2 apdu P2 parameter + * @param buffer raw data to be inserted in apdu + * @param length size of buffer + * @return SC_SUCCESS if ok; else error code + */ +static int cwa_set_security_env(sc_card_t * card, + u8 p1, u8 p2, u8 * buffer, size_t length) +{ + sc_apdu_t apdu; + int result = SC_SUCCESS; + sc_context_t *ctx = NULL; + + /* safety check */ + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + if (!buffer || (length <= 0)) /* check received arguments */ + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + + /* compose apdu for Manage Security Environment cmd */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x22, p1, p2); + apdu.data = buffer; + apdu.datalen = length; + apdu.lc = length; + apdu.resp = NULL; + apdu.resplen = 0; + apdu.le = 0; + + /* send composed apdu and parse result */ + result = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(ctx, result, "SM Set Security Environment failed"); + result = sc_check_sw(card, apdu.sw1, apdu.sw2); + LOG_FUNC_RETURN(ctx, result); +} + +/** + * SM internal authenticate. + * + * Internal (Card) authentication (let the card verify sent ifd certs) + * + * @param card pointer to card data + * @param sm secure message data pointer + * @param data data to be sent in apdu + * @param datalen length of data to send + * @return SC_SUCCESS if OK: else error code + */ +static int cwa_internal_auth(sc_card_t * card, + cwa_sm_status_t * sm, u8 * data, size_t datalen) +{ + sc_apdu_t apdu; + u8 rbuf[SC_MAX_APDU_BUFFER_SIZE]; + int result = SC_SUCCESS; + sc_context_t *ctx = NULL; + + /* safety check */ + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + if (!data || (datalen <= 0)) /* check received arguments */ + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + + /* compose apdu for Internal Authenticate cmd */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0x88, 0x00, 0x00); + apdu.data = data; + apdu.datalen = datalen; + apdu.lc = datalen; + apdu.le = 0x80; /* expected 1024 bits response */ + apdu.resp = rbuf; + apdu.resplen = sizeof(rbuf); + + /* send composed apdu and parse result */ + result = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(ctx, result, "SM internal auth failed"); + + result = sc_check_sw(card, apdu.sw1, apdu.sw2); + LOG_TEST_RET(ctx, result, "SM internal auth invalid response"); + + if (apdu.resplen != sizeof(sm->sig)) /* invalid number of bytes received */ + LOG_FUNC_RETURN(ctx, SC_ERROR_UNKNOWN_DATA_RECEIVED); + memcpy(sm->sig, apdu.resp, apdu.resplen); /* copy result to buffer */ + LOG_FUNC_RETURN(ctx, SC_SUCCESS); +} + +/** + * Compose signature data for external auth according CWA-14890. + * + * This code prepares data to be sent to ICC for external + * authentication procedure + * + * Store resulting data into sm->sig + * + * @param card pointer to st_card_t card data information + * @param icc_pubkey public key of card + * @param ifd_privkey private RSA key of ifd + * @param sn_icc card serial number + * @param sm pointer to cwa_internal_t data + * @return SC_SUCCESS if ok; else errorcode + */ +static int cwa_prepare_external_auth(sc_card_t * card, + RSA * icc_pubkey, + RSA * ifd_privkey, + u8 * sn_icc, cwa_sm_status_t * sm) +{ + /* we have to compose following message: + data = E[PK.ICC.AUT](SIGMIN) + SIGMIN = min ( SIG, N.IFD-SIG ) + SIG= DS[SK.IFD.AUT] ( + 0x6A || - padding according iso 9796-2 + PRND2 || - (74 bytes) random data to make buffer 128 bytes length + Kifd || - (32 bytes)- ifd random generated key + sha1_hash( + PRND2 || + Kifd || + RND.ICC || - (8 bytes) response to get_challenge() cmd + SN.ICC - (8 bytes) serial number from get_serialnr() cmd + ) || + 0xBC - iso 9796-2 padding + ) - total: 128 bytes + + then, we should encrypt with our private key and then with icc pub key + returning resulting data + */ + char *msg; /* to store error messages */ + int res = SC_SUCCESS; + u8 *buf1; /* where to encrypt with icc pub key */ + u8 *buf2; /* where to encrypt with ifd pub key */ + u8 *buf3; /* where to compose message to be encrypted */ + int len1, len2, len3; + u8 *sha_buf; /* to compose message to be sha'd */ + u8 *sha_data; /* sha signature data */ + BIGNUM *bn = NULL; + BIGNUM *bnsub = NULL; + BIGNUM *bnres = NULL; + sc_context_t *ctx = NULL; + + /* safety check */ + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + /* check received arguments */ + if (!icc_pubkey || !ifd_privkey || !sn_icc || !sm) + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + buf1 = calloc(128, sizeof(u8)); + buf2 = calloc(128, sizeof(u8)); + buf3 = calloc(128, sizeof(u8)); + sha_buf = calloc(74 + 32 + 8 + 8, sizeof(u8)); + sha_data = calloc(SHA_DIGEST_LENGTH, sizeof(u8)); + /* alloc() resources */ + if (!buf1 || !buf2 || !buf3 || !sha_buf || !sha_data) { + msg = "prepare external auth: calloc error"; + res = SC_ERROR_OUT_OF_MEMORY; + goto prepare_external_auth_end; + } + + /* compose buffer data */ + buf3[0] = 0x6A; /* iso padding */ + RAND_bytes(buf3 + 1, 74); /* pRND */ + RAND_bytes(sm->kifd, 32); /* Kifd */ + memcpy(buf3 + 1 + 74, sm->kifd, 32); /* copy Kifd into buffer */ + /* prepare data to be hashed */ + memcpy(sha_buf, buf3 + 1, 74); /* copy pRND into sha_buf */ + memcpy(sha_buf + 74, buf3 + 1 + 74, 32); /* copy kifd into sha_buf */ + memcpy(sha_buf + 74 + 32, sm->rndicc, 8); /* copy 8 byte icc challenge */ + memcpy(sha_buf + 74 + 32 + 8, sn_icc, 8); /* copy serialnr, 8 bytes */ + SHA1(sha_buf, 74 + 32 + 8 + 8, sha_data); + /* copy hashed data into buffer */ + memcpy(buf3 + 1 + 74 + 32, sha_data, SHA_DIGEST_LENGTH); + buf3[127] = 0xBC; /* iso padding */ + + /* encrypt with ifd private key */ + len2 = + RSA_private_decrypt(128, buf3, buf2, ifd_privkey, RSA_NO_PADDING); + if (len2 < 0) { + msg = "Prepare external auth: ifd_privk encrypt failed"; + res = SC_ERROR_SM_ENCRYPT_FAILED; + goto prepare_external_auth_end; + } + + /* evaluate value of minsig and store into buf3 */ + bn = BN_bin2bn(buf2, len2, NULL); + bnsub = BN_new(); + if (!bn || !bnsub) { + msg = "Prepare external auth: BN creation failed"; + res = SC_ERROR_INTERNAL; + goto prepare_external_auth_end; + } + res = BN_sub(bnsub, ifd_privkey->n, bn); /* eval N.IFD-SIG */ + if (res == 0) { /* 1:success 0 fail */ + msg = "Prepare external auth: BN sigmin evaluation failed"; + res = SC_ERROR_INTERNAL; + goto prepare_external_auth_end; + } + bnres = (BN_cmp(bn, bnsub) < 0) ? bn : bnsub; /* choose min(SIG,N.IFD-SIG) */ + if (BN_num_bytes(bnres) > 128) { + msg = "Prepare external auth: BN sigmin result is too big"; + res = SC_ERROR_INTERNAL; + goto prepare_external_auth_end; + } + len3 = BN_bn2bin(bnres, buf3); /* convert result back into buf3 */ + if (len3 <= 0) { + msg = "Prepare external auth: BN to buffer conversion failed"; + res = SC_ERROR_INTERNAL; + goto prepare_external_auth_end; + } + + /* re-encrypt result with icc public key */ + len1 = RSA_public_encrypt(len3, buf3, buf1, icc_pubkey, RSA_NO_PADDING); + if (len1 <= 0) { + msg = "Prepare external auth: icc_pubk encrypt failed"; + res = SC_ERROR_SM_ENCRYPT_FAILED; + goto prepare_external_auth_end; + } + + /* process done: copy result into cwa_internal buffer and return success */ + memcpy(sm->sig, buf1, len1); + res = SC_SUCCESS; + + prepare_external_auth_end: + if (bn) + BN_free(bn); + if (bnsub) + BN_free(bnsub); + if (buf1) { + memset(buf1, 0, 128); + free(buf1); + } + if (buf2) { + memset(buf2, 0, 128); + free(buf2); + } + if (buf3) { + memset(buf3, 0, 128); + free(buf3); + } + if (sha_buf) { + memset(sha_buf, 0, 74 + 32 + 8 + 1 + 7); + free(sha_buf); + } + if (sha_data) { + memset(sha_data, 0, SHA_DIGEST_LENGTH); + free(sha_data); + } + + if (res != SC_SUCCESS) + sc_log(ctx, msg); + LOG_FUNC_RETURN(ctx, res); +} + +/** + * SM external authenticate. + * + * Perform external (IFD) authenticate procedure (8.4.1.2) + * + * @param data apdu signature content + * @param datalen signature length (128) + * @return SC_SUCCESS if OK: else error code + */ +static int cwa_external_auth(sc_card_t * card, cwa_sm_status_t * sm) +{ + sc_apdu_t apdu; + int result = SC_SUCCESS; + sc_context_t *ctx = NULL; + + /* safety check */ + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + + /* compose apdu for External Authenticate cmd */ + sc_format_apdu(card, &apdu, SC_APDU_CASE_3_SHORT, 0x82, 0x00, 0x00); + apdu.data = sm->sig; + apdu.datalen = sizeof(sm->sig); + apdu.lc = sizeof(sm->sig); + apdu.le = 0; + apdu.resp = NULL; + apdu.resplen = 0; + + /* send composed apdu and parse result */ + result = dnie_transmit_apdu(card, &apdu); + LOG_TEST_RET(ctx, result, "SM external auth failed"); + result = sc_check_sw(card, apdu.sw1, apdu.sw2); + LOG_TEST_RET(ctx, result, "SM external auth invalid response"); + LOG_FUNC_RETURN(ctx, SC_SUCCESS); +} + +/** + * SM creation of session keys. + * + * Compute Kenc,Kmac, and SSC and store it into sm data + * + * @param card pointer to sc_card_t data + * @param sm pointer to cwa_internal_t data + * @return SC_SUCCESS if ok; else error code + */ +static int cwa_compute_session_keys(sc_card_t * card, cwa_sm_status_t * sm) +{ + + char *msg = NULL; + int n = 0; + int res = SC_SUCCESS; + u8 *kseed; /* to compose kifd ^ kicc */ + u8 *data; /* to compose kenc and kmac to be hashed */ + u8 *sha_data; /* to store hash result */ + u8 kenc[4] = { 0x00, 0x00, 0x00, 0x01 }; + u8 kmac[4] = { 0x00, 0x00, 0x00, 0x02 }; + sc_context_t *ctx = NULL; + + /* safety check */ + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + /* Just a literal transcription of cwa14890-1 sections 8.7.2 to 8.9 */ + kseed = calloc(32, sizeof(u8)); + data = calloc(32 + 4, sizeof(u8)); + sha_data = calloc(SHA_DIGEST_LENGTH, sizeof(u8)); + if (!kseed || !data || !sha_data) { + msg = "Compute Session Keys: calloc() failed"; + res = SC_ERROR_OUT_OF_MEMORY; + goto compute_session_keys_end; + } + /* compose kseed (cwa-14890-1 sect 8.7.2) */ + for (n = 0; n < 32; n++) + *(kseed + n) = sm->kicc[n] ^ sm->kifd[n]; + + /* evaluate kenc (cwa-14890-1 sect 8.8) */ + memcpy(data, kseed, 32); + memcpy(data + 32, kenc, 4); + SHA1(data, 32 + 4, sha_data); + memcpy(sm->session.kenc, sha_data, 16); /* kenc=16 fsb sha((kifd^kicc)||00000001) */ + + /* evaluate kmac */ + memset(data, 0, 32 + 4); + memset(sha_data, 0, SHA_DIGEST_LENGTH); /* clear buffers */ + + memcpy(data, kseed, 32); + memcpy(data + 32, kmac, 4); + SHA1(data, 32 + 4, sha_data); + memcpy(sm->session.kmac, sha_data, 16); /* kmac=16 fsb sha((kifd^kicc)||00000002) */ + + /* evaluate send sequence counter (cwa-14890-1 sect 8.9 & 9.6 */ + memcpy(sm->session.ssc, sm->rndicc + 4, 4); /* 4 least significant bytes of rndicc */ + memcpy(sm->session.ssc + 4, sm->rndifd + 4, 4); /* 4 least significant bytes of rndifd */ + + /* arriving here means process ok */ + res = SC_SUCCESS; + + compute_session_keys_end: + if (kseed) { + memset(kseed, 0, 32); + free(kseed); + } + if (data) { + memset(data, 0, 32 + 4); + free(data); + } + if (sha_data) { + memset(sha_data, 0, SHA_DIGEST_LENGTH); + free(sha_data); + } + if (res != SC_SUCCESS) + sc_log(ctx, msg); + else { + sc_log(ctx, "Kenc: %s", sc_dump_hex(sm->session.kenc, 16)); + sc_log(ctx, "Kmac: %s", sc_dump_hex(sm->session.kmac, 16)); + sc_log(ctx, "SSC: %s", sc_dump_hex(sm->session.ssc, 8)); + } + LOG_FUNC_RETURN(ctx, res); +} + +/* + * Compare signature for internal auth procedure. + * + * @param data Received data to be checked + * @param dlen data length + * @param expected results + * @return SC_SUCCESS or error code + */ +static int cwa_compare_signature(u8 * data, size_t dlen, u8 * ifd_data) +{ + u8 *buf = calloc(74 + 32 + 32, sizeof(u8)); + u8 *sha = calloc(SHA_DIGEST_LENGTH, sizeof(u8)); + int res = SC_SUCCESS; + if (!buf || !sha) { + res = SC_ERROR_OUT_OF_MEMORY; + goto compare_signature_end; + } + res = SC_ERROR_INVALID_DATA; + if (dlen != 128) + goto compare_signature_end; /* check length */ + if (data[0] != 0x6a) + goto compare_signature_end; /* iso 9796-2 padding */ + if (data[127] != 0xBC) + goto compare_signature_end; /* iso 9796-2 padding */ + memcpy(buf, data + 1, 74 + 32); + memcpy(buf + 74 + 32, ifd_data, 16); + SHA1(buf, 74 + 32 + 16, sha); + if (memcmp(data + 127 - SHA_DIGEST_LENGTH, sha, SHA_DIGEST_LENGTH) == 0) + res = SC_SUCCESS; + compare_signature_end: + if (buf) + free(buf); + if (sha) + free(sha); + return res; +} + +/** + * check the result of internal_authenticate operation. + * + * Checks icc received data from internal auth procedure against + * expected results + * + * @param card Pointer to sc_card_t data + * @param icc_pubkey icc public key + * @param ifd_privkey ifd private key + * @param ifdbuf buffer containing ( RND.IFD || SN.IFD ) + * @param ifdlen buffer length; should be 16 + * @param sm secure messaging internal data + * @return SC_SUCCESS if ok; else error code + */ +static int cwa_verify_internal_auth(sc_card_t * card, + RSA * icc_pubkey, + RSA * ifd_privkey, + u8 * ifdbuf, + size_t ifdlen, cwa_sm_status_t * sm) +{ + int res = SC_SUCCESS; + char *msg = NULL; + u8 *buf1 = NULL; /* to decrypt with our private key */ + u8 *buf2 = NULL; /* to try SIGNUM==SIG */ + u8 *buf3 = NULL; /* to try SIGNUM==N.ICC-SIG */ + int len1 = 0; + int len2 = 0; + int len3 = 0; + BIGNUM *bn = NULL; + BIGNUM *sigbn = NULL; + sc_context_t *ctx = NULL; + + if (!card || !card->ctx) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + LOG_FUNC_CALLED(ctx); + if (!ifdbuf || (ifdlen != 16)) { + res = SC_ERROR_INVALID_ARGUMENTS; + msg = "Null buffers received as parameters"; + goto verify_internal_done; + } + if (!icc_pubkey || !ifd_privkey) { + res = SC_ERROR_SM_NO_SESSION_KEYS; + msg = "Either provided icc_pubk or ifd_privk are null"; + goto verify_internal_done; + } + buf1 = (u8 *) calloc(128, sizeof(u8)); /* 128: RSA key len in bytes */ + buf2 = (u8 *) calloc(128, sizeof(u8)); + buf3 = (u8 *) calloc(128, sizeof(u8)); + if (!buf1 || !buf2 || !buf3) { + msg = "Verify Signature: calloc() error"; + res = SC_ERROR_OUT_OF_MEMORY; + goto verify_internal_done; + } + + /* + We have received data with this format: + sigbuf = E[PK.IFD.AUT](SIGMIN) + SIGMIN = min ( SIG, N.ICC-SIG ) + SIG= DS[SK.ICC.AUT] ( + 0x6A || + PRND1 || + Kicc || + sha1_hash(PRND1 || Kicc || RND.IFD || SN.IFD) || + 0xBC + ) + So we should reverse the process and try to get valid results + */ + + /* decrypt data with our ifd priv key */ + len1 = + RSA_private_decrypt(sizeof(sm->sig), sm->sig, buf1, ifd_privkey, + RSA_NO_PADDING); + if (len1 <= 0) { + msg = "Verify Signature: decrypt with ifd privk failed"; + res = SC_ERROR_SM_ENCRYPT_FAILED; + goto verify_internal_done; + } + + /* OK: now we have SIGMIN in buf1 */ + /* check if SIGMIN data matches SIG or N.ICC-SIG */ + /* evaluate DS[SK.ICC.AUTH](SIG) trying to decrypt with icc pubk */ + len3 = RSA_public_encrypt(len1, buf1, buf3, icc_pubkey, RSA_NO_PADDING); + if (len3 <= 0) + goto verify_nicc_sig; /* evaluate N.ICC-SIG and retry */ + res = cwa_compare_signature(buf3, len3, ifdbuf); + if (res == SC_SUCCESS) + goto verify_internal_ok; + + verify_nicc_sig: + /* + * Arriving here means need to evaluate N.ICC-SIG + * So convert buffers to bignums to operate + */ + bn = BN_bin2bn(buf1, len1, NULL); /* create BN data */ + sigbn = BN_new(); + if (!bn || !sigbn) { + msg = "Verify Signature: cannot bignums creation error"; + res = SC_ERROR_OUT_OF_MEMORY; + goto verify_internal_done; + } + res = BN_sub(sigbn, icc_pubkey->n, bn); /* eval N.ICC-SIG */ + if (!res) { + msg = "Verify Signature: evaluation of N.ICC-SIG failed"; + res = SC_ERROR_INTERNAL; + goto verify_internal_done; + } + len2 = BN_bn2bin(sigbn, buf2); /* copy result to buffer */ + if (len2 <= 0) { + msg = "Verify Signature: cannot conver bignum to buffer"; + res = SC_ERROR_INTERNAL; + goto verify_internal_done; + } + /* ok: check again with new data */ + /* evaluate DS[SK.ICC.AUTH](I.ICC-SIG) trying to decrypt with icc pubk */ + len3 = RSA_public_encrypt(len2, buf2, buf3, icc_pubkey, RSA_NO_PADDING); + if (len3 <= 0) { + msg = "Verify Signature: cannot get valid SIG data"; + res = SC_ERROR_INVALID_DATA; + goto verify_internal_done; + } + res = cwa_compare_signature(buf3, len3, ifdbuf); + if (res != SC_SUCCESS) { + msg = "Verify Signature: cannot get valid SIG data"; + res = SC_ERROR_INVALID_DATA; + goto verify_internal_done; + } + /* arriving here means OK: complete data structures */ + verify_internal_ok: + memcpy(sm->kicc, buf3 + 1 + 74, 32); /* extract Kicc from buf3 */ + res = SC_SUCCESS; + verify_internal_done: + if (buf1) + free(buf1); + if (buf2) + free(buf2); + if (buf3) + free(buf3); + if (bn) + BN_free(bn); + if (sigbn) + BN_free(sigbn); + if (res != SC_SUCCESS) + sc_log(ctx, msg); + LOG_FUNC_RETURN(ctx, res); +} + +/** + * Create Secure Messaging channel. + * + * This is the main entry point for CWA14890 SM chanel creation. + * It closely follows cwa standard, with a minor modification: + * - ICC serial number is taken at the begining of SM creation + * - ICC and IFD certificate agreement process is reversed, to allow + * card to retain key references on further proccess (this behavior + * is also defined in standard) + * + * Based on Several documents: + * - "Understanding the DNIe" + * - "Manual de comandos del DNIe" + * - ISO7816-4 and CWA14890-{1,2} + * + * @param card card info structure + * @param provider cwa14890 info provider + * @param flag requested init method ( OFF, COLD, WARM ) + * @return SC_SUCCESS if OK; else error code + */ +int cwa_create_secure_channel(sc_card_t * card, + cwa_provider_t * provider, int flag) +{ + u8 *cert; + size_t certlen; + + int res = SC_SUCCESS; + char *msg = "Success"; + + u8 *sn_icc; + + /* data to get and parse certificates */ + X509 *icc_cert = NULL; + X509 *ca_cert = NULL; + EVP_PKEY *icc_pubkey = NULL; + EVP_PKEY *ifd_privkey = NULL; + sc_context_t *ctx = NULL; + cwa_sm_status_t *sm = NULL; + + /* several buffer and buffer pointers */ + u8 *buffer; + size_t bufferlen; + u8 *tlv = NULL; /* buffer to compose TLV messages */ + size_t tlvlen = 0; + u8 *rndbuf=NULL; + + /* preliminary checks */ + if (!card || !card->ctx ) + return SC_ERROR_INVALID_ARGUMENTS; + if (!provider) + return SC_ERROR_SM_NOT_INITIALIZED; + /* comodity vars */ + ctx = card->ctx; + sm = &(provider->status); + + LOG_FUNC_CALLED(ctx); + + /* check requested initialization method */ + switch (flag) { + case CWA_SM_OFF: /* disable SM */ + provider->status.session.state = CWA_SM_NONE; /* just mark channel inactive */ + sc_log(ctx, "Setting CWA SM status to none"); + LOG_FUNC_RETURN(ctx, SC_SUCCESS); + case CWA_SM_WARM: /* only initialize if not already done */ + if (provider->status.session.state != CWA_SM_NONE) { + sc_log(ctx, + "Warm CWA SM requested: already in SM state"); + LOG_FUNC_RETURN(ctx, SC_SUCCESS); + } + case CWA_SM_COLD: /* force sm initialization process */ + sc_log(ctx, "CWA SM initialization requested"); + break; + default: + sc_log(ctx, "Invalid provided SM initialization flag"); + LOG_FUNC_RETURN(ctx, SC_ERROR_INVALID_ARGUMENTS); + } + + /* OK: lets start process */ + + /* reset card (warm reset, do not unpower card) */ + sc_log(ctx, "Resseting card"); + sc_reset(card, 0); + + /* mark SM status as in progress */ + provider->status.session.state = CWA_SM_INPROGRESS; + + /* call provider pre-operation method */ + sc_log(ctx, "CreateSecureChannel pre-operations"); + if (provider->cwa_create_pre_ops) { + res = provider->cwa_create_pre_ops(card, provider); + if (res != SC_SUCCESS) { + msg = "Create SM: provider pre_ops() failed"; + sc_log(ctx, msg); + goto csc_end; + } + } + + /* retrieve icc serial number */ + sc_log(ctx, "Retrieve ICC serial number"); + if (provider->cwa_get_sn_icc) { + res = provider->cwa_get_sn_icc(card, &sn_icc); + if (res != SC_SUCCESS) { + msg = "Retrieve ICC failed"; + sc_log(ctx, msg); + goto csc_end; + } + } else { + msg = "Don't know how to obtain ICC serial number"; + sc_log(ctx, msg); + res = SC_ERROR_INTERNAL; + goto csc_end; + } + + /* + * Notice that this code inverts ICC and IFD certificate standard + * checking sequence. + */ + + /* Read Intermediate CA from card */ + if (!provider->cwa_get_icc_intermediate_ca_cert) { + sc_log(ctx, + "Step 8.4.1.6: Skip Retrieveing ICC intermediate CA"); + ca_cert = NULL; + } else { + sc_log(ctx, "Step 8.4.1.7: Retrieving ICC intermediate CA"); + res = + provider->cwa_get_icc_intermediate_ca_cert(card, &ca_cert); + if (res != SC_SUCCESS) { + msg = + "Cannot get ICC intermediate CA certificate from provider"; + goto csc_end; + } + } + + /* Read ICC certificate from card */ + sc_log(ctx, "Step 8.4.1.8: Retrieve ICC certificate"); + res = provider->cwa_get_icc_cert(card, &icc_cert); + if (res != SC_SUCCESS) { + msg = "Cannot get ICC certificate from provider"; + goto csc_end; + } + + /* Verify icc Card certificate chain */ + /* Notice that Some implementations doesn't verify cert chain + * but simply verifies that icc_cert is a valid certificate */ + if (ca_cert) { + sc_log(ctx, "Verifying ICC certificate chain"); + res = + cwa_verify_icc_certificates(card, provider, ca_cert, + icc_cert); + if (res != SC_SUCCESS) { + res = SC_ERROR_SM_AUTHENTICATION_FAILED; + msg = "Icc Certificates verification failed"; + goto csc_end; + } + } else { + sc_log(ctx, "Cannot verify Certificate chain. skip step"); + } + + /* Extract public key from ICC certificate */ + icc_pubkey = X509_get_pubkey(icc_cert); + + /* Select Root CA in card for ifd certificate verification */ + sc_log(ctx, + "Step 8.4.1.2: Select Root CA in card for IFD cert verification"); + res = provider->cwa_get_root_ca_pubkey_ref(card, &buffer, &bufferlen); + if (res != SC_SUCCESS) { + msg = "Cannot get Root CA key reference from provider"; + goto csc_end; + } + tlvlen = 0; + tlv = calloc(10 + bufferlen, sizeof(u8)); + if (!tlv) { + msg = "calloc error"; + res = SC_ERROR_OUT_OF_MEMORY; + goto csc_end; + } + res = cwa_compose_tlv(card, 0x83, bufferlen, buffer, &tlv, &tlvlen); + if (res != SC_SUCCESS) { + msg = "Cannot compose tlv for setting Root CA key reference"; + goto csc_end; + } + res = cwa_set_security_env(card, 0x81, 0xB6, tlv, tlvlen); + if (res != SC_SUCCESS) { + msg = "Select Root CA key ref failed"; + goto csc_end; + } + + /* Send IFD intermediate CA in CVC format C_CV_CA */ + sc_log(ctx, + "Step 8.4.1.3: Send CVC IFD intermediate CA Cert for ICC verification"); + res = provider->cwa_get_cvc_ca_cert(card, &cert, &certlen); + if (res != SC_SUCCESS) { + msg = "Get CVC CA cert from provider failed"; + goto csc_end; + } + res = cwa_verify_cvc_certificate(card, cert, certlen); + if (res != SC_SUCCESS) { + msg = "Verify CVC CA failed"; + goto csc_end; + } + + /* select public key reference for sent IFD intermediate CA certificate */ + sc_log(ctx, + "Step 8.4.1.4: Select Intermediate CA pubkey ref for ICC verification"); + res = + provider->cwa_get_intermediate_ca_pubkey_ref(card, &buffer, + &bufferlen); + if (res != SC_SUCCESS) { + msg = "Cannot get intermediate CA key reference from provider"; + goto csc_end; + } + tlvlen = 0; + free(tlv); + tlv = calloc(10 + bufferlen, sizeof(u8)); + if (!tlv) { + msg = "calloc error"; + res = SC_ERROR_OUT_OF_MEMORY; + goto csc_end; + } + res = cwa_compose_tlv(card, 0x83, bufferlen, buffer, &tlv, &tlvlen); + if (res != SC_SUCCESS) { + msg = + "Cannot compose tlv for setting intermeditate CA key reference"; + goto csc_end; + } + res = cwa_set_security_env(card, 0x81, 0xB6, tlv, tlvlen); + if (res != SC_SUCCESS) { + msg = "Select CVC CA pubk failed"; + goto csc_end; + } + + /* Send IFD certiticate in CVC format C_CV_IFD */ + sc_log(ctx, + "Step 8.4.1.5: Send CVC IFD Certificate for ICC verification"); + res = provider->cwa_get_cvc_ifd_cert(card, &cert, &certlen); + if (res != SC_SUCCESS) { + msg = "Get CVC IFD cert from provider failed"; + goto csc_end; + } + res = cwa_verify_cvc_certificate(card, cert, certlen); + if (res != SC_SUCCESS) { + msg = "Verify CVC IFD failed"; + goto csc_end; + } + + /* remember that this code changes IFD and ICC Cert verification steps */ + + /* select public key of ifd certificate and icc private key */ + sc_log(ctx, + "Step 8.4.1.9: Send IFD pubk and ICC privk key references for Internal Auth"); + res = provider->cwa_get_ifd_pubkey_ref(card, &buffer, &bufferlen); + if (res != SC_SUCCESS) { + msg = "Cannot get ifd public key reference from provider"; + goto csc_end; + } + tlvlen = 0; + free(tlv); + tlv = calloc(10 + bufferlen, sizeof(u8)); + if (!tlv) { + msg = "calloc error"; + res = SC_ERROR_OUT_OF_MEMORY; + goto csc_end; + } + res = cwa_compose_tlv(card, 0x83, bufferlen, buffer, &tlv, &tlvlen); + if (res != SC_SUCCESS) { + msg = "Cannot compose tlv for setting ifd pubkey reference"; + goto csc_end; + } + res = provider->cwa_get_icc_privkey_ref(card, &buffer, &bufferlen); + if (res != SC_SUCCESS) { + msg = "Cannot get icc private key reference from provider"; + goto csc_end; + } + /* add this tlv to old one; do not call calloc */ + res = cwa_compose_tlv(card, 0x84, bufferlen, buffer, &tlv, &tlvlen); + if (res != SC_SUCCESS) { + msg = "Cannot compose tlv for setting ifd pubkey reference"; + goto csc_end; + } + + res = cwa_set_security_env(card, 0xC1, 0xA4, tlv, tlvlen); + if (res != SC_SUCCESS) { + msg = "Select CVC IFD pubk failed"; + goto csc_end; + } + + /* Internal (Card) authentication (let the card verify sent ifd certs) + SN.IFD equals 8 lsb bytes of ifd.pubk ref according cwa14890 sec 8.4.1 */ + sc_log(ctx, "Step 8.4.1.10: Perform Internal authentication"); + res = provider->cwa_get_sn_ifd(card, &buffer); + if (res != SC_SUCCESS) { + msg = "Cannot get ifd serial number from provider"; + goto csc_end; + } + rndbuf = calloc(8 /*RND.IFD */ + 8 /*SN.IFD */ , sizeof(u8)); + if (!rndbuf) { + msg = "Cannot calloc for RND.IFD+SN.IFD"; + res = SC_ERROR_OUT_OF_MEMORY; + goto csc_end; + } + RAND_bytes(sm->rndifd, 8); /* generate 8 random bytes */ + memcpy(rndbuf, sm->rndifd, 8); /* insert RND.IFD into rndbuf */ + memcpy(rndbuf + 8, buffer, 8); /* insert SN.IFD into rndbuf */ + res = cwa_internal_auth(card, sm, rndbuf, 16); + if (res != SC_SUCCESS) { + msg = "Internal auth cmd failed"; + goto csc_end; + } + + /* retrieve ifd private key from provider */ + res = provider->cwa_get_ifd_privkey(card, &ifd_privkey); + if (res != SC_SUCCESS) { + msg = "Cannot retrieve IFD private key from provider"; + res = SC_ERROR_SM_NO_SESSION_KEYS; + goto csc_end; + } + + /* verify received signature */ + sc_log(ctx, "Verify Internal Auth command response"); + res = cwa_verify_internal_auth(card, icc_pubkey->pkey.rsa, /* evaluated icc public key */ + ifd_privkey->pkey.rsa, /* evaluated from DGP's Manual Annex 3 Data */ + rndbuf, /* RND.IFD || SN.IFD */ + 16, /* rndbuf length; should be 16 */ + sm /* sm data */ + ); + if (res != SC_SUCCESS) { + msg = "Internal Auth Verify failed"; + goto csc_end; + } + + /* get challenge: retrieve 8 random bytes from card */ + sc_log(ctx, "Step 8.4.1.11: Prepare External Auth: Get Challenge"); + res = card->ops->get_challenge(card, sm->rndicc, sizeof(sm->rndicc)); + if (res != SC_SUCCESS) { + msg = "Get Challenge failed"; + goto csc_end; + } + + /* compose signature data for external auth */ + res = cwa_prepare_external_auth(card, + icc_pubkey->pkey.rsa, + ifd_privkey->pkey.rsa, sn_icc, sm); + if (res != SC_SUCCESS) { + msg = "Prepare external auth failed"; + goto csc_end; + } + + /* External (IFD) authentication */ + sc_log(ctx, "Step 8.4.1.12: Perform External (IFD) Authentication"); + res = cwa_external_auth(card, sm); + if (res != SC_SUCCESS) { + msg = "External auth cmd failed"; + goto csc_end; + } + + /* Session key generation */ + sc_log(ctx, "Step 8.4.2: Compute Session Keys"); + res = cwa_compute_session_keys(card, sm); + if (res != SC_SUCCESS) { + msg = "Session Key generation failed"; + goto csc_end; + } + + /* call provider post-operation method */ + sc_log(ctx, "CreateSecureChannel post-operations"); + if (provider->cwa_create_post_ops) { + res = provider->cwa_create_post_ops(card, provider); + if (res != SC_SUCCESS) { + sc_log(ctx, "Create SM: provider post_ops() failed"); + goto csc_end; + } + } + + /* arriving here means ok: cleanup */ + res = SC_SUCCESS; + csc_end: + if (icc_pubkey) + EVP_PKEY_free(icc_pubkey); + if (ifd_privkey) + EVP_PKEY_free(ifd_privkey); + /* setup SM state according result */ + if (res != SC_SUCCESS) { + sc_log(ctx, msg); + provider->status.session.state = CWA_SM_NONE; + } else { + provider->status.session.state = CWA_SM_ACTIVE; + } + LOG_FUNC_RETURN(ctx, res); +} + +/******************* SM internal APDU encoding / decoding functions ******/ + +/** + * Encode an APDU. + * + * Calling this functions means that It's has been verified + * That source apdu needs encoding + * Based on section 9 of CWA-14890 and Sect 6 of iso7816-4 standards + * And DNIe's manual + * + * @param card card info structure + * @param sm Secure Messaging state information + * @param from APDU to be encoded + * @param to where to store encoded apdu + * @return SC_SUCCESS if ok; else error code + */ +int cwa_encode_apdu(sc_card_t * card, + cwa_provider_t * provider, sc_apdu_t * from, sc_apdu_t * to) +{ + u8 *apdubuf; /* to store resulting apdu */ + size_t apdulen; + u8 *ccbuf; /* where to store data to eval cryptographic checksum CC */ + size_t cclen = 0; + u8 macbuf[8]; /* to store and compute CC */ + DES_key_schedule k1; + DES_key_schedule k2; + char *msg = NULL; + + size_t i, j; /* for xor loops */ + int res = SC_SUCCESS; + sc_context_t *ctx = NULL; + cwa_sm_session_t *sm_session = NULL; + u8 *msgbuf = NULL; /* to encrypt apdu data */ + u8 *cryptbuf = NULL; + + /* reserve extra bytes for padding and tlv header */ + msgbuf = calloc(12 + from->lc, sizeof(u8)); /* to encrypt apdu data */ + cryptbuf = calloc(12 + from->lc, sizeof(u8)); + + /* mandatory check */ + if (!card || !card->ctx || !provider) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + sm_session = &(provider->status.session); + + LOG_FUNC_CALLED(ctx); + /* check remaining arguments */ + if (!from || !to || !sm_session) + LOG_FUNC_RETURN(ctx, SC_ERROR_SM_NOT_INITIALIZED); + if (sm_session->state != CWA_SM_ACTIVE) + LOG_FUNC_RETURN(ctx, SC_ERROR_SM_INVALID_LEVEL); + if (!msgbuf || !cryptbuf) + LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); + + /* check if APDU is already encoded */ + if ((from->cla & 0x0C) != 0) { + memcpy(to, from, sizeof(sc_apdu_t)); + return SC_SUCCESS; /* already encoded */ + } + if (from->ins == 0xC0) { + memcpy(to, from, sizeof(sc_apdu_t)); + return SC_SUCCESS; /* dont encode GET Response cmd */ + } +#if 0 + /* For testing results according DNIe manual */ + u8 kenc[16] = + { 0x59, 0x8f, 0x26, 0xe3, 0x6e, 0x11, 0xa8, 0xec, 0x14, 0xb8, 0x1e, + 0x19, 0xbd, 0xa2, 0x23, 0xca }; + u8 kmac[16] = + { 0x5d, 0xe2, 0x93, 0x9a, 0x1e, 0xa0, 0x3a, 0x93, 0x0b, 0x88, 0x20, + 0x6d, 0x8f, 0x73, 0xe8, 0xa7 }; + u8 ssc[8] = { 0xd3, 0x1a, 0xc8, 0xec, 0x7b, 0xa0, 0xfe, 0x74 }; + memcpy(sm->kenc, kenc, 16); + memcpy(sm->kmac, kmac, 16); + memcpy(sm->ssc, ssc, 16); + from->le = 0; + /* para debugging end */ +#endif + + /* call provider pre-operation method */ + if (provider->cwa_encode_pre_ops) { + res = provider->cwa_encode_pre_ops(card, provider, from, to); + if (res != SC_SUCCESS) { + msg = "Encode APDU: provider pre_ops() failed"; + goto encode_end; + } + } + + /* trace APDU before encoding process */ + cwa_trace_apdu(card, from, 0); + + /* reserve enougth space for apdulen+tlv bytes + * to-be-crypted buffer and result apdu buffer */ + apdubuf = + calloc(MAX(SC_MAX_APDU_BUFFER_SIZE, 20 + from->datalen), + sizeof(u8)); + ccbuf = + calloc(MAX(SC_MAX_APDU_BUFFER_SIZE, 20 + from->datalen), + sizeof(u8)); + if (!apdubuf || !ccbuf) + LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY); + + /* set up data on destination apdu */ + to->cse = SC_APDU_CASE_3_SHORT; + to->cla = from->cla | 0x0C; /* mark apdu as encoded */ + to->ins = from->ins; + to->p1 = from->p1; + to->p2 = from->p2; + to->le = from->le; + to->lc = 0; /* to be evaluated */ + /* fill buffer with header info */ + *(ccbuf + cclen++) = to->cla; + *(ccbuf + cclen++) = to->ins; + *(ccbuf + cclen++) = to->p1; + *(ccbuf + cclen++) = to->p2; + cwa_iso7816_padding(ccbuf, &cclen); /* pad header (4 bytes pad) */ + + /* if no data, skip data encryption step */ + if (from->lc != 0) { + size_t dlen = from->lc; + + /* prepare keys */ + DES_cblock iv = { 0, 0, 0, 0, 0, 0, 0, 0 }; + DES_set_key_unchecked((const_DES_cblock *) & (sm_session->kenc[0]), + &k1); + DES_set_key_unchecked((const_DES_cblock *) & (sm_session->kenc[8]), + &k2); + + /* pad message */ + memcpy(msgbuf, from->data, dlen); + cwa_iso7816_padding(msgbuf, &dlen); + + /* start kriptbuff with iso padding indicator */ + *cryptbuf = 0x01; + /* aply TDES + CBC with kenc and iv=(0,..,0) */ + DES_ede3_cbc_encrypt(msgbuf, cryptbuf + 1, dlen, &k1, &k2, &k1, + &iv, DES_ENCRYPT); + /* compose data TLV and add to result buffer */ + res = + cwa_compose_tlv(card, 0x87, dlen + 1, cryptbuf, &ccbuf, + &cclen); + if (res != SC_SUCCESS) { + msg = "Error in compose tag 8x87 TLV"; + goto encode_end; + } + } + + /* if le byte is declared, compose and add Le TLV */ + /* TODO: study why original driver checks for le>=256? */ + if (from->le > 0) { + u8 le = 0xff & from->le; + res = cwa_compose_tlv(card, 0x97, 1, &le, &ccbuf, &cclen); + if (res != SC_SUCCESS) { + msg = "Encode APDU compose_tlv(0x97) failed"; + goto encode_end; + } + } + /* copy current data to apdu buffer (skip header and header padding) */ + memcpy(apdubuf, ccbuf + 8, cclen - 8); + apdulen = cclen - 8; + /* pad again ccbuffer to compute CC */ + cwa_iso7816_padding(ccbuf, &cclen); + + /* sc_log(ctx,"data to compose mac: %s",sc_dump_hex(ccbuf,cclen)); */ + /* compute MAC Cryptographic Checksum using kmac and increased SSC */ + res = cwa_increase_ssc(card, sm_session); /* increase send sequence counter */ + if (res != SC_SUCCESS) { + msg = "Error in computing SSC"; + goto encode_end; + } + /* set up keys for mac computing */ + DES_set_key_unchecked((const_DES_cblock *) & (sm_session->kmac[0]),&k1); + DES_set_key_unchecked((const_DES_cblock *) & (sm_session->kmac[8]),&k2); + + memcpy(macbuf, sm_session->ssc, 8); /* start with computed SSC */ + for (i = 0; i < cclen; i += 8) { /* divide data in 8 byte blocks */ + /* compute DES */ + DES_ecb_encrypt((const_DES_cblock *) macbuf, + (DES_cblock *) macbuf, &k1, DES_ENCRYPT); + /* XOR with next data and repeat */ + for (j = 0; j < 8; j++) + macbuf[j] ^= ccbuf[i + j]; + } + /* and apply 3DES to result */ + DES_ecb2_encrypt((const_DES_cblock *) macbuf, (DES_cblock *) macbuf, + &k1, &k2, DES_ENCRYPT); + + /* compose and add computed MAC TLV to result buffer */ + res = cwa_compose_tlv(card, 0x8E, 4, macbuf, &apdubuf, &apdulen); + if (res != SC_SUCCESS) { + msg = "Encode APDU compose_tlv(0x87) failed"; + goto encode_end; + } + + /* rewrite resulting header */ + to->lc = apdulen; + to->data = apdubuf; + to->datalen = apdulen; + + /* call provider post-operation method */ + if (provider->cwa_encode_post_ops) { + res = provider->cwa_encode_post_ops(card, provider, from, to); + if (res != SC_SUCCESS) { + msg = "Encode APDU: provider post_ops() failed"; + goto encode_end; + } + } + /* that's all folks */ + res = SC_SUCCESS; + + encode_end: + if (msg) + sc_log(ctx, msg); + LOG_FUNC_RETURN(ctx, res); +} + +/** + * Decode an APDU response. + * + * Calling this functions means that It's has been verified + * That apdu response comes in TLV encoded format and needs decoding + * Based on section 9 of CWA-14890 and Sect 6 of iso7816-4 standards + * And DNIe's manual + * + * @param card card info structure + * @param sm Secure Messaging state information + * @param from APDU with response to be decoded + * @param to where to store decoded apdu + * @return SC_SUCCESS if ok; else error code + */ +int cwa_decode_response(sc_card_t * card, + cwa_provider_t * provider, + sc_apdu_t * from, sc_apdu_t * to) +{ + size_t i, j; + cwa_tlv_t tlv_array[4]; + cwa_tlv_t *p_tlv = &tlv_array[0]; /* to store plain data (Tag 0x81) */ + cwa_tlv_t *e_tlv = &tlv_array[1]; /* to store pad encoded data (Tag 0x87) */ + cwa_tlv_t *m_tlv = &tlv_array[2]; /* to store mac CC (Tag 0x8E) */ + cwa_tlv_t *s_tlv = &tlv_array[3]; /* to store sw1-sw2 status (Tag 0x99) */ + u8 *ccbuf = NULL; /* buffer for mac CC calculation */ + size_t cclen = 0; /* ccbuf len */ + u8 macbuf[8]; /* where to calculate mac */ + size_t resplen = 0; /* respbuf length */ + DES_key_schedule k1; + DES_key_schedule k2; + int res = SC_SUCCESS; + char *msg = NULL; /* to store error messages */ + sc_context_t *ctx = NULL; + cwa_sm_session_t *sm_session = NULL; + + /* mandatory check */ + if (!card || !card->ctx || !provider) + return SC_ERROR_INVALID_ARGUMENTS; + ctx = card->ctx; + sm_session = &(provider->status.session); + + LOG_FUNC_CALLED(ctx); + /* check remaining arguments */ + if ((from == NULL) || (to == NULL) || (sm_session == NULL)) + LOG_FUNC_RETURN(ctx, SC_ERROR_SM_NOT_INITIALIZED); + if (sm_session->state != CWA_SM_ACTIVE) + LOG_FUNC_RETURN(ctx, SC_ERROR_SM_INVALID_LEVEL); + + /* cwa14890 sect 9.3: check SW1 or SW2 for SM related errors */ + if (from->sw1 == 0x69) { + if ((from->sw2 == 0x88) || (from->sw2 == 0x87)) { + msg = "SM related errors in APDU response"; + res = SC_ERROR_SM_ENCRYPT_FAILED; /* tell driver to restart SM */ + goto response_decode_end; + } + } + /* if response is null/empty assume unencoded apdu */ + if (!from->resp || (from->resplen == 0)) { + sc_log(ctx, "Empty APDU response: assume not cwa encoded"); + memcpy(to, from, sizeof(sc_apdu_t)); + return SC_SUCCESS; + } + /* checks if apdu response needs decoding by checking tags in response */ + switch (*from->resp) { + case CWA_SM_PLAIN_TAG: + case CWA_SM_CRYPTO_TAG: + case CWA_SM_MAC_TAG: + case CWA_SM_LE_TAG: + case CWA_SM_STATUS_TAG: + break; /* cwa tags found: continue decoding */ + default: /* else apdu response seems not to be cwa encoded */ + sc_log(card->ctx, "APDU Response seems not to be cwa encoded"); + memcpy(to, from, sizeof(sc_apdu_t)); + return SC_SUCCESS; /* let process continue */ + } + + /* call provider pre-operation method */ + if (provider->cwa_decode_pre_ops) { + res = provider->cwa_decode_pre_ops(card, provider, from, to); + if (res != SC_SUCCESS) { + sc_log(ctx, "Decode APDU: provider pre_ops() failed"); + LOG_FUNC_RETURN(ctx, res); + } + } + + /* parse response to find TLV's data and check results */ + memset(tlv_array, 0, 4 * sizeof(cwa_tlv_t)); + + res = cwa_parse_tlv(card, from->resp, from->resplen, tlv_array); + if (res != SC_SUCCESS) { + msg = "Error in TLV parsing"; + goto response_decode_end; + } + + /* check consistency of received TLV's */ + if (p_tlv->buf && e_tlv->buf) { + msg = + "Plain and Encoded data are mutually exclusive in apdu response"; + res = SC_ERROR_INVALID_DATA; + goto response_decode_end; + } + if (!m_tlv->buf) { + msg = "No MAC TAG found in apdu response"; + res = SC_ERROR_INVALID_DATA; + goto response_decode_end; + } + if (m_tlv->len != 4) { + msg = "Invalid MAC TAG Length"; + res = SC_ERROR_INVALID_DATA; + goto response_decode_end; + } + + /* compose buffer to evaluate mac */ + + /* reserve enought space for data+status+padding */ + ccbuf = + calloc(e_tlv->buflen + s_tlv->buflen + p_tlv->buflen + 8, + sizeof(u8)); + if (!ccbuf) { + msg = "Cannot allocate space for mac checking"; + res = SC_ERROR_OUT_OF_MEMORY; + goto response_decode_end; + } + /* copy data into buffer */ + cclen = 0; + if (e_tlv->buf) { /* encoded data */ + memcpy(ccbuf, e_tlv->buf, e_tlv->buflen); + cclen = e_tlv->buflen; + } + if (p_tlv->buf) { /* plain data */ + memcpy(ccbuf, p_tlv->buf, p_tlv->buflen); + cclen += p_tlv->buflen; + } + if (s_tlv->buf) { /* response status */ + if (s_tlv->len != 2) { + msg = "Invalid SW TAG length"; + res = SC_ERROR_INVALID_DATA; + goto response_decode_end; + } + memcpy(ccbuf + cclen, s_tlv->buf, s_tlv->buflen); + cclen += s_tlv->buflen; + to->sw1 = s_tlv->data[0]; + to->sw2 = s_tlv->data[1]; + } else { /* if no response status tag, use sw1 and sw2 from apdu */ + to->sw1 = from->sw1; + to->sw2 = from->sw2; + } + /* add iso7816 padding */ + cwa_iso7816_padding(ccbuf, &cclen); + + /* evaluate mac by mean of kmac and increased SendSequence Counter SSC */ + + /* increase SSC */ + res = cwa_increase_ssc(card, sm_session); /* increase send sequence counter */ + if (res != SC_SUCCESS) { + msg = "Error in computing SSC"; + goto response_decode_end; + } + /* set up keys for mac computing */ + DES_set_key_unchecked((const_DES_cblock *) & (sm_session->kmac[0]), &k1); + DES_set_key_unchecked((const_DES_cblock *) & (sm_session->kmac[8]), &k2); + + memcpy(macbuf, sm_session->ssc, 8); /* start with computed SSC */ + for (i = 0; i < cclen; i += 8) { /* divide data in 8 byte blocks */ + /* compute DES */ + DES_ecb_encrypt((const_DES_cblock *) macbuf, + (DES_cblock *) macbuf, &k1, DES_ENCRYPT); + /* XOR with data and repeat */ + for (j = 0; j < 8; j++) + macbuf[j] ^= ccbuf[i + j]; + } + /* finally apply 3DES to result */ + DES_ecb2_encrypt((const_DES_cblock *) macbuf, (DES_cblock *) macbuf, + &k1, &k2, DES_ENCRYPT); + + /* check evaluated mac with provided by apdu response */ + + res = memcmp(m_tlv->data, macbuf, 4); /* check first 4 bytes */ + if (res != 0) { + msg = "Error in MAC CC checking: value doesn't match"; + res = SC_ERROR_SM_ENCRYPT_FAILED; + goto response_decode_end; + } + + /* allocate response buffer */ + resplen = 10 + MAX(p_tlv->len, e_tlv->len); /* estimate response buflen */ + if (to->resp) { /* if response apdu provides buffer, try to use it */ + if (to->resplen < resplen) { + msg = + "Provided buffer has not enought size to store response"; + res = SC_ERROR_OUT_OF_MEMORY; + goto response_decode_end; + } + } else { /* buffer not provided: create and assing to response apdu */ + to->resp = calloc(resplen, sizeof(u8)); + if (!to->resp) { + msg = "Cannot allocate buffer to store response"; + res = SC_ERROR_OUT_OF_MEMORY; + goto response_decode_end; + } + } + to->resplen = resplen; + + /* fill destination response apdu buffer with data */ + + /* if plain data, just copy TLV data into apdu response */ + if (p_tlv->buf) { /* plain data */ + memcpy(to->resp, p_tlv->data, p_tlv->len); + to->resplen = p_tlv->len; + } + + /* if encoded data, decode and store into apdu response */ + else if (e_tlv->buf) { /* encoded data */ + DES_cblock iv = { 0, 0, 0, 0, 0, 0, 0, 0 }; + /* check data len */ + if ((e_tlv->len < 9) || ((e_tlv->len - 1) % 8) != 0) { + msg = "Invalid length for Encoded data TLV"; + res = SC_ERROR_INVALID_DATA; + goto response_decode_end; + } + /* first byte is padding info; check value */ + if (e_tlv->data[0] != 0x01) { + msg = "Encoded TLV: Invalid padding info value"; + res = SC_ERROR_INVALID_DATA; + goto response_decode_end; + } + /* prepare keys to decode */ + DES_set_key_unchecked((const_DES_cblock *) & (sm_session->kenc[0]), + &k1); + DES_set_key_unchecked((const_DES_cblock *) & (sm_session->kenc[8]), + &k2); + /* decrypt into response buffer + * by using 3DES CBC by mean of kenc and iv={0,...0} */ + DES_ede3_cbc_encrypt(&e_tlv->data[1], to->resp, e_tlv->len - 1, + &k1, &k2, &k1, &iv, DES_DECRYPT); + to->resplen = e_tlv->len - 1; + /* remove iso padding from response length */ + for (; (to->resplen > 0) && *(to->resp + to->resplen - 1) == 0x00; to->resplen--) ; /* empty loop */ + + if (*(to->resp + to->resplen - 1) != 0x80) { /* check padding byte */ + msg = + "Decrypted TLV has no 0x80 iso padding indicator!"; + res = SC_ERROR_INVALID_DATA; + goto response_decode_end; + } + /* everything ok: remove ending 0x80 from response */ + to->resplen--; + } + + else + to->resplen = 0; /* neither plain, nor encoded data */ + + /* call provider post-operation method */ + if (provider->cwa_decode_post_ops) { + res = provider->cwa_decode_post_ops(card, provider, from, to); + if (res != SC_SUCCESS) { + sc_log(ctx, "Decode APDU: provider post_ops() failed"); + LOG_FUNC_RETURN(ctx, res); + } + } + + /* that's all folks */ + res = SC_SUCCESS; + + response_decode_end: + if (ccbuf) + free(ccbuf); + if (msg) { + sc_log(ctx, msg); + } else { + cwa_trace_apdu(card, to, 1); + } /* trace apdu response */ + LOG_FUNC_RETURN(ctx, res); +} + +/********************* default provider for cwa14890 ****************/ + +/* pre and post operations */ + +static int default_create_pre_ops(sc_card_t * card, cwa_provider_t * provider) +{ + return SC_SUCCESS; +} + +static int default_create_post_ops(sc_card_t * card, cwa_provider_t * provider) +{ + return SC_SUCCESS; +} + +static int default_get_root_ca_pubkey(sc_card_t * card, EVP_PKEY ** root_ca_key) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* retrieve CVC intermediate CA certificate and length */ +static int default_get_cvc_ca_cert(sc_card_t * card, u8 ** cert, + size_t * length) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* retrieve CVC IFD certificate and length */ +static int default_get_cvc_ifd_cert(sc_card_t * card, u8 ** cert, + size_t * length) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +static int default_get_ifd_privkey(sc_card_t * card, EVP_PKEY ** ifd_privkey) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* get ICC intermediate CA path */ +static int default_get_icc_intermediate_ca_cert(sc_card_t * card, X509 ** cert) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* get ICC certificate path */ +static int default_get_icc_cert(sc_card_t * card, X509 ** cert) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* Retrieve key reference for Root CA to validate CVC intermediate CA certs */ +static int default_get_root_ca_pubkey_ref(sc_card_t * card, u8 ** buf, + size_t * len) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* Retrieve key reference for intermediate CA to validate IFD certs */ +static int default_get_intermediate_ca_pubkey_ref(sc_card_t * card, u8 ** buf, + size_t * len) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* Retrieve key reference for IFD certificate */ +static int default_get_ifd_pubkey_ref(sc_card_t * card, u8 ** buf, size_t * len) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* Retrieve key reference for ICC privkey */ +static int default_get_icc_privkey_ref(sc_card_t * card, u8 ** buf, + size_t * len) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* Retrieve SN.IFD (8 bytes left padded with zeroes if needed) */ +static int default_get_sn_ifd(sc_card_t * card, u8 ** buf) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/* Retrieve SN.ICC (8 bytes left padded with zeroes if needed) */ +static int default_get_sn_icc(sc_card_t * card, u8 ** buf) +{ + return SC_ERROR_NOT_SUPPORTED; +} + +/************** operations related with APDU encoding ******************/ + +/* pre and post operations */ +static int default_encode_pre_ops(sc_card_t * card, cwa_provider_t * provider, + sc_apdu_t * from, sc_apdu_t * to) +{ + return SC_SUCCESS; +} + +static int default_encode_post_ops(sc_card_t * card, cwa_provider_t * provider, + sc_apdu_t * from, sc_apdu_t * to) +{ + return SC_SUCCESS; +} + +/************** operations related APDU response decoding **************/ + +/* pre and post operations */ +static int default_decode_pre_ops(sc_card_t * card, cwa_provider_t * provider, + sc_apdu_t * from, sc_apdu_t * to) +{ + return SC_SUCCESS; +} + +static int default_decode_post_ops(sc_card_t * card, cwa_provider_t * provider, + sc_apdu_t * from, sc_apdu_t * to) +{ + return SC_SUCCESS; +} + +static cwa_provider_t default_cwa_provider = { + + /************ data related with SM operations *************************/ + { + { /* KICC */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + { /* KIFD */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + { /* RND.ICC */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + { /* RND.IFD */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + { /* SigBuf */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + { /* sm session */ + CWA_SM_NONE, /* state */ + { /* Kenc */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + { /* Kmac */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + { /* SSC Send Sequence counter */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + } + }, + + /************ operations related with secure channel creation *********/ + + /* pre and post operations */ + default_create_pre_ops, + default_create_post_ops, + + /* Get ICC intermediate CA path */ + default_get_icc_intermediate_ca_cert, + /* Get ICC certificate path */ + default_get_icc_cert, + + /* Obtain RSA public key from RootCA */ + default_get_root_ca_pubkey, + /* Obtain RSA IFD private key */ + default_get_ifd_privkey, + + /* Retrieve CVC intermediate CA certificate and length */ + default_get_cvc_ca_cert, + /* Retrieve CVC IFD certificate and length */ + default_get_cvc_ifd_cert, + + /* Get public key references for Root CA to validate intermediate CA cert */ + default_get_root_ca_pubkey_ref, + + /* Get public key reference for IFD intermediate CA certificate */ + default_get_intermediate_ca_pubkey_ref, + + /* Get public key reference for IFD CVC certificate */ + default_get_ifd_pubkey_ref, + + /* Get ICC private key reference */ + default_get_icc_privkey_ref, + + /* Get IFD Serial Number */ + default_get_sn_ifd, + + /* Get ICC Serial Number */ + default_get_sn_icc, + + /************** operations related with APDU encoding ******************/ + + /* pre and post operations */ + default_encode_pre_ops, + default_encode_post_ops, + + /************** operations related APDU response decoding **************/ + + /* pre and post operations */ + default_decode_pre_ops, + default_decode_post_ops, +}; + +/** + * Get a copy of default cwa provider. + * + * @param card pointer to card info structure + * @return copy of default provider or null on error + */ +cwa_provider_t *cwa_get_default_provider(sc_card_t * card) +{ + cwa_provider_t *res = NULL; + if (!card || !card->ctx) + return NULL; + LOG_FUNC_CALLED(card->ctx); + res = calloc(1, sizeof(cwa_provider_t)); + if (!res) { + sc_log(card->ctx, "Cannot allocate space for cwa_provider"); + return NULL; + } + memcpy(res, &default_cwa_provider, sizeof(cwa_provider_t)); + return res; +} + +/* end of cwa14890.c */ +#undef __CWA14890_C__ + +#endif /* ENABLE_OPENSSL */ diff --git a/src/libopensc/cwa14890.h b/src/libopensc/cwa14890.h new file mode 100644 index 00000000..10a0e0ee --- /dev/null +++ b/src/libopensc/cwa14890.h @@ -0,0 +1,415 @@ +/** + * cwa14890.h: Defines, Typedefs and prototype functions for SM Messaging according CWA-14890 standard. + * + * Copyright (C) 2010 Juan Antonio Martinez + * + * This work is derived from many sources at OpenSC Project site, + * (see references), and the information made public for Spanish + * Direccion General de la Policia y de la Guardia Civil + * + * 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 __CWA14890_H__ +#define __CWA14890_H__ + +#ifdef ENABLE_OPENSSL + +/* Secure Messaging state indicator */ +#define CWA_SM_NONE 0x00 /** No SM channel defined */ +#define CWA_SM_INPROGRESS 0x01 /** SM channel is being created: don't use */ +#define CWA_SM_ACTIVE 0x02 /** SM channel is active */ + +/* Flags for setting SM status */ +#define CWA_SM_OFF 0x00 /** Disable SM channel */ +#define CWA_SM_COLD 0x01 /** force creation of a new SM channel */ +#define CWA_SM_WARM 0x02 /** Create new SM channel only if state is NONE */ + +/* TAGS for encoded APDU's */ +#define CWA_SM_PLAIN_TAG 0x81 /** Plain value (to be protected by CC) */ +#define CWA_SM_CRYPTO_TAG 0x87 /** Padding-content + cryptogram */ +#define CWA_SM_MAC_TAG 0x8E /** Cryptographic checksum (MAC) */ +#define CWA_SM_LE_TAG 0x97 /** Le (to be protected by CC ) */ +#define CWA_SM_STATUS_TAG 0x99 /** Processing status (SW1-SW2 mac protected ) */ + +/*************** data structures for CWA14890 SM handling **************/ + +#include "libopensc/types.h" + +#include +#include + +/** + * Structure used to compose BER-TLV encoded data + * according to iso7816-4 sect 5.2.2. + * + * Notice that current implementation does not handle properly + * multibyte tag id. Just asume that tag is 1-byte lenght + * Also, encodings for data lenght longer than 0x01000000 bytes + * are not supported (tag 0x84) + */ +typedef struct cwa_tlv_st { + u8 *buf; /** local copy of TLV byte array */ + size_t buflen; /** lengt of buffer */ + unsigned int tag; /** tag ID */ + size_t len; /** lenght of data field */ + u8 *data; /** pointer to start of data in buf buffer */ +} cwa_tlv_t; + +/** + * Structure used to handle keys and sequence counter once SM session + * is stablished + */ +typedef struct cwa_sm_session_st { + /* variables used once SM is started */ + int state; /** one of NONE, INPROGRESS, or ACTIVE */ + u8 kenc[16]; /** key used for data encoding */ + u8 kmac[16]; /** key for mac checksum calculation */ + u8 ssc[8]; /** send sequence counter */ +} cwa_sm_session_t; + +/** + * Estructure used to compose and store variables related to SM setting + * and encode/decode apdu messages. + */ +typedef struct cwa_sm_status_st { + /* variables used in SM establishment */ + u8 kicc[32]; + u8 kifd[32]; + u8 rndicc[8]; /** 8 bytes random number generated by card */ + u8 rndifd[8]; /** 8 bytes random number generated by application */ + u8 sig[128]; /** buffer to store & compute signatures (1024 bits) */ + cwa_sm_session_t session; /** current session data */ +} cwa_sm_status_t; + +/** + * Data and function pointers to provide information to create and handle + * Secure Channel. + */ +typedef struct cwa_provider_st { + /************ data related with SM operations *************************/ + + cwa_sm_status_t status; /** sm status for this provider */ + + /************ operations related with secure channel creation *********/ + + /* pre and post operations */ + + /** + * CWA-14890 SM stablisment pre-operations. + * + * This code is called before any operation required in + * standard cwa14890 SM stablisment process. It's usually + * used for adquiring/initialize data to be used in the + * process (i.e: retrieve card serial number), to make sure + * that no extra apdu is sent during the SM stablishment procedure + * + * @param card pointer to card driver structure + * @param provider pointer to SM data provider for DNIe + * @return SC_SUCCESS if OK. else error code + */ + int (*cwa_create_pre_ops) (sc_card_t * card, + struct cwa_provider_st * provider); + + /** + * CWA-14890 SM stablisment post-operations. + * + * This code is called after sucessfull SM chanel stablishment + * procedure, and before returning from create_sm_chanel() function + * May be use for store data, trace, logs and so + * + * @param card pointer to card driver structure + * @param provider pointer to SM data provider for DNIe + * @return SC_SUCCESS if OK. else error code + */ + int (*cwa_create_post_ops) (sc_card_t * card, + struct cwa_provider_st * provider); + + /** + * Get ICC (card) intermediate CA Certificate. + * + * @param card Pointer to card driver structure + * @param cert where to store resulting certificate + * @return SC_SUCCESS if ok; else error code + */ + int (*cwa_get_icc_intermediate_ca_cert) (sc_card_t * card, + X509 ** cert); + + /** + * Get ICC (card) certificate. + * + * @param card Pointer to card driver structure + * @param cert where to store resulting certificate + * @return SC_SUCCESS if ok; else error code + */ + int (*cwa_get_icc_cert) (sc_card_t * card, X509 ** cert); + + /** + * Obtain RSA public key from RootCA. + * + * @param root_ca_key pointer to resulting returned key + * @return SC_SUCCESS if ok; else error code + */ + int (*cwa_get_root_ca_pubkey) (sc_card_t * card, EVP_PKEY ** key); + + /** + * Get RSA IFD (Terminal) private key data. + * + * Notice that resulting data should be keept in memory as little + * as possible Erasing them once used + * + * @param card pointer to card driver structure + * @param ifd_privkey where to store IFD private key + * @return SC_SUCCESS if ok; else error code + */ + int (*cwa_get_ifd_privkey) (sc_card_t * card, EVP_PKEY ** key); + + /* TODO: + * CVC handling routines should be grouped in just retrieve CVC + * certificate. The key reference, as stated by CWA should be + * extracted from CVC... + * + * But to do this, an special OpenSSL with PACE extensions is + * needed. In the meantime, let's use binary buffers to get + * CVC and key references, until an CV_CERT hancling API + * become available in standard OpenSSL + * + *@see http://openpace.sourceforge.net + */ + + /** + * Retrieve IFD (application) CVC intermediate CA certificate and length. + * + * Returns a byte array with the intermediate CA certificate + * (in CardVerifiable Certificate format) to be sent to the + * card in External Authentication process + * + * @param card Pointer to card driver Certificate + * @param cert Where to store resulting byte array + * @param length len of returned byte array + * @return SC_SUCCESS if ok; else error code + */ + int (*cwa_get_cvc_ca_cert) (sc_card_t * card, u8 ** cert, + size_t * lenght); + + /** + * Retrieve IFD (application) CVC certificate and length. + * + * Returns a byte array with the application's certificate + * (in CardVerifiable Certificate format) to be sent to the + * card in External Authentication process + * + * @param card Pointer to card driver Certificate + * @param cert Where to store resulting byte array + * @param length len of returned byte array + * @return SC_SUCCESS if ok; else error code + */ + int (*cwa_get_cvc_ifd_cert) (sc_card_t * card, u8 ** cert, + size_t * lenght); + + /** + * Retrieve public key reference for Root CA to validate CVC intermediate CA certs. + * + * This is required in the process of On card external authenticate + * @param card Pointer to card driver structure + * @param buf where to store resulting key reference + * @param len where to store buffer length + * @return SC_SUCCESS if ok; else error code + */ + int (*cwa_get_root_ca_pubkey_ref) (sc_card_t * card, u8 ** buf, + size_t * len); + + /** + * Get public key reference for intermediate CA to validate IFD cert. + * + * This is required in the process of On card external authenticate + * + * @param card Pointer to card driver structure + * @param buf where to store resulting key reference + * @param len where to store buffer length + * @return SC_SUCCESS if ok; else error code + */ + int (*cwa_get_intermediate_ca_pubkey_ref) (sc_card_t * card, u8 ** buf, + size_t * len); + + /** + * Retrieve public key reference for IFD certificate. + * + * This tells the card with in memory key reference is to be used + * when CVC cert is sent for external auth procedure + * + * @param card pointer to card driver structure + * @param buf where to store data to be sent + * @param len where to store data length + * @return SC_SUCCESS if ok; else error code + */ + int (*cwa_get_ifd_pubkey_ref) (sc_card_t * card, u8 ** buf, + size_t * len); + + /** + * Retrieve key reference for ICC private key. + * + * @param card pointer to card driver structure + * @param buf where to store data + * @param len where to store data length + * @return SC_SUCCESS if ok; else error + */ + int (*cwa_get_icc_privkey_ref) (sc_card_t * card, u8 ** buf, + size_t * len); + + /** + * Retrieve SN.IFD - Terminal Serial Number. + * + * Result SN is 8 bytes long left padded with zeroes if required. + * + * @param card pointer to card structure + * @param buf where to store result (8 bytes) + * @return SC_SUCCESS if ok; else error + */ + int (*cwa_get_sn_ifd) (sc_card_t * card, u8 ** buf); + + /** + * Get SN.ICC - Card Serial Number. + * + * Result value is 8 bytes long left padded with zeroes if needed) + * + * @param card pointer to card structure + * @param buf where to store result (8 bytes) + * @return SC_SUCCESS if ok; else error + */ + int (*cwa_get_sn_icc) (sc_card_t * card, u8 ** buf); + + /************** operations related with APDU encoding ******************/ + + /** + * Operation to be done before any APDU encode procedure. + * + * @param card Pointer to card driver data structure + * @param provider pointer to cwa1890 SM provider + * @param from APDU to be encoded + * @param to resulting APDU to be sent to encode procedure + * @return SC_SUCCESS if OK, else error code + */ + int (*cwa_encode_pre_ops) (sc_card_t * card, + struct cwa_provider_st * provider, + sc_apdu_t * from, sc_apdu_t * to); + + /** + * Operation to be done after APDU encode process finished ok. + * + * @param card Pointer to card driver data structure + * @param provider pointer to cwa1890 SM provider + * @param from encoded APDU + * @param to resulting encoded APDU to be returned to libopensc + * @return SC_SUCCESS if OK, else error code + */ + int (*cwa_encode_post_ops) (sc_card_t * card, + struct cwa_provider_st * provider, + sc_apdu_t * from, sc_apdu_t * to); + + /************** operations related APDU response decoding **************/ + + /** + * Operation to be done before any APDU Response decode procedure. + * + * @param card Pointer to card driver data structure + * @param provider pointer to cwa1890 SM provider + * @param from APDU Response to be decoded + * @param to resulting APDU response to be sent to decode procedure + * @return SC_SUCCESS if OK, else error code + */ + int (*cwa_decode_pre_ops) (sc_card_t * card, + struct cwa_provider_st * provider, + sc_apdu_t * from, sc_apdu_t * to); + + /** + * Operation to be done after APDU Response decode process finished ok. + * + * @param card Pointer to card driver data structure + * @param provider pointer to cwa1890 SM provider + * @param from decoded APDU Response + * @param to resulting APDU Response to be returned to libopensc + * @return SC_SUCCESS if OK, else error code + */ + int (*cwa_decode_post_ops) (sc_card_t * card, + struct cwa_provider_st * provider, + sc_apdu_t * from, sc_apdu_t * to); +} cwa_provider_t; + +/************************** external function prototypes ******************/ + +/** + * Create Secure channel. + * + * Based on Several documents: + * - "Understanding the DNIe" + * - "Manual de comandos del DNIe" + * - ISO7816-4 and CWA14890-{1,2} + * + * @param card card info structure + * @param provider pointer to cwa provider + * @param flag Requested SM final state (OFF,COLD,WARM) + * @return SC_SUCCESS if OK; else error code + */ +extern int cwa_create_secure_channel(sc_card_t * card, + cwa_provider_t * provider, int flag); + +/** + * Decode an APDU response. + * + * Calling this functions means that It's has been verified + * That apdu response comes in TLV encoded format and needs decoding + * Based on section 9 of CWA-14890 and Sect 6 of iso7816-4 standards + * And DNIe's manual + * + * @param card card info structure + * @param provider cwa provider data to handle SM channel + * @param from apdu to be decoded + * @param to where to store decoded apdu + * @return SC_SUCCESS if ok; else error code + */ +extern int cwa_decode_response(sc_card_t * card, + cwa_provider_t * provider, + sc_apdu_t * from, sc_apdu_t * to); + +/** + * Encode an APDU. + * + * Calling this functions means that It's has been verified + * That source apdu needs encoding + * Based on section 9 of CWA-14890 and Sect 6 of iso7816-4 standards + * And DNIe's manual + * + * @param card card info structure + * @param provider cwa provider data to handle SM channel + * @param from apdu to be encoded + * @param to Where to store encoded apdu + * @return SC_SUCCESS if ok; else error code + */ +extern int cwa_encode_apdu(sc_card_t * card, + cwa_provider_t * provider, + sc_apdu_t * from, sc_apdu_t * to); + +/** + * Gets a default cwa_provider structure. + * + * @param card Pointer to card driver information + * @return default cwa_provider data, or null on error + */ +extern cwa_provider_t *cwa_get_default_provider(sc_card_t * card); + +#endif /* ENABLE_OPENSSL */ + +#endif diff --git a/src/libopensc/pkcs15-dnie.c b/src/libopensc/pkcs15-dnie.c new file mode 100644 index 00000000..f7f1b9e4 --- /dev/null +++ b/src/libopensc/pkcs15-dnie.c @@ -0,0 +1,274 @@ +/** + * PKCS15 emulation layer for DNIe card. + * + * Copyright (C) 2011, Andre Zepezauer + * Copyright (C) 2011, Juan Antonio Martinez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include "config.h" +#include "libopensc/log.h" +#include "libopensc/asn1.h" +#include "libopensc/pkcs15.h" + +/* Card driver related */ +extern int dnie_match_card(struct sc_card *card); + +/* Helper functions to get the pkcs15 stuff bound. */ + +static +int dump_ef(sc_card_t * card, const char *path, u8 * buf, size_t * buf_len) +{ + int rv; + sc_file_t *file = sc_file_new(); + sc_format_path(path, &file->path); + sc_select_file(card, &file->path, &file); + if (file->size > *buf_len) + return SC_ERROR_BUFFER_TOO_SMALL; + rv = sc_read_binary(card, 0, buf, file->size, 0); + if (rv < 0) + return rv; + *buf_len = rv; + + return SC_SUCCESS; +} + +static const struct sc_asn1_entry c_asn1_odf[] = { + {"privateKeys", SC_ASN1_STRUCT, SC_ASN1_CTX | 0 | SC_ASN1_CONS, 0, NULL, + NULL}, + {"publicKeys", SC_ASN1_STRUCT, SC_ASN1_CTX | 1 | SC_ASN1_CONS, 0, NULL, + NULL}, + {"trustedPublicKeys", SC_ASN1_STRUCT, SC_ASN1_CTX | 2 | SC_ASN1_CONS, 0, + NULL, NULL}, + {"secretKeys", SC_ASN1_STRUCT, SC_ASN1_CTX | 3 | SC_ASN1_CONS, 0, NULL, + NULL}, + {"certificates", SC_ASN1_STRUCT, SC_ASN1_CTX | 4 | SC_ASN1_CONS, 0, + NULL, NULL}, + {"trustedCertificates", SC_ASN1_STRUCT, SC_ASN1_CTX | 5 | SC_ASN1_CONS, + 0, NULL, NULL}, + {"usefulCertificates", SC_ASN1_STRUCT, SC_ASN1_CTX | 6 | SC_ASN1_CONS, + 0, NULL, NULL}, + {"dataObjects", SC_ASN1_STRUCT, SC_ASN1_CTX | 7 | SC_ASN1_CONS, 0, NULL, + NULL}, + {"authObjects", SC_ASN1_STRUCT, SC_ASN1_CTX | 8 | SC_ASN1_CONS, 0, NULL, + NULL}, + {NULL, 0, 0, 0, NULL, NULL} +}; + +static const unsigned int odf_indexes[] = { + SC_PKCS15_PRKDF, + SC_PKCS15_PUKDF, + SC_PKCS15_PUKDF_TRUSTED, + SC_PKCS15_SKDF, + SC_PKCS15_CDF, + SC_PKCS15_CDF_TRUSTED, + SC_PKCS15_CDF_USEFUL, + SC_PKCS15_DODF, + SC_PKCS15_AODF, +}; + +static +int parse_odf(const u8 * buf, size_t buflen, struct sc_pkcs15_card *p15card) +{ + const u8 *p = buf; + size_t left = buflen; + int r, i, type; + sc_path_t path; + struct sc_asn1_entry asn1_obj_or_path[] = { + {"path", SC_ASN1_PATH, SC_ASN1_CONS | SC_ASN1_SEQUENCE, 0, + &path, NULL}, + {NULL, 0, 0, 0, NULL, NULL} + }; + struct sc_asn1_entry asn1_odf[10]; + + sc_path_t *path_prefix = calloc(1, sizeof(sc_path_t)); + sc_format_path("3F005015", path_prefix); + + sc_copy_asn1_entry(c_asn1_odf, asn1_odf); + for (i = 0; asn1_odf[i].name != NULL; i++) + sc_format_asn1_entry(asn1_odf + i, asn1_obj_or_path, NULL, 0); + while (left > 0) { + r = sc_asn1_decode_choice(p15card->card->ctx, asn1_odf, p, left, + &p, &left); + if (r == SC_ERROR_ASN1_END_OF_CONTENTS) + break; + if (r < 0) + return r; + type = r; + r = sc_pkcs15_make_absolute_path(path_prefix, &path); + if (r < 0) + return r; + r = sc_pkcs15_add_df(p15card, odf_indexes[type], &path); + if (r) + return r; + } + return 0; +} + +static int sc_pkcs15emu_dnie_init(sc_pkcs15_card_t * p15card) +{ + u8 buf[1024]; + sc_pkcs15_df_t *df; + sc_pkcs15_object_t *p15_obj; + size_t len = sizeof(buf); + int rv; + + sc_context_t *ctx = p15card->card->ctx; + LOG_FUNC_CALLED(ctx); + + /* Check for correct card driver (i.e. iso7816) */ + if (strcmp(p15card->card->driver->short_name, "dnie") != 0) + return SC_ERROR_WRONG_CARD; + + /* Check for correct card atr */ + if (dnie_match_card(p15card->card) != 1) + return SC_ERROR_WRONG_CARD; + + /* Set root path of this application */ + p15card->file_app = sc_file_new(); + sc_format_path("3F00", &p15card->file_app->path); + + /* Load TokenInfo */ + rv = dump_ef(p15card->card, "3F0050155032", buf, &len); + if (rv != SC_SUCCESS) { + sc_log(ctx, "Reading of EF.TOKENINFO failed: %d", rv); + LOG_FUNC_RETURN(ctx, rv); + } + rv = sc_pkcs15_parse_tokeninfo(p15card->card->ctx, p15card->tokeninfo, + buf, len); + if (rv != SC_SUCCESS) { + sc_log(ctx, "Decoding of EF.TOKENINFO failed: %d", rv); + LOG_FUNC_RETURN(ctx, rv); + } + + /* Only accept the original stuff */ + if (strcmp(p15card->tokeninfo->manufacturer_id, "DGP-FNMT") != 0) + LOG_FUNC_RETURN(ctx, SC_ERROR_WRONG_CARD); + + /* Load ODF */ + rv = dump_ef(p15card->card, "3F0050155031", buf, &len); + if (rv != SC_SUCCESS) { + sc_log(ctx, "Reading of ODF failed: %d", rv); + LOG_FUNC_RETURN(ctx, rv); + } + rv = parse_odf(buf, len, p15card); + if (rv != SC_SUCCESS) { + sc_log(ctx, "Decoding of ODF failed: %d", rv); + LOG_FUNC_RETURN(ctx, rv); + } + + /* Decode EF.PrKDF, EF.PuKDF and EF.CDF */ + for (df = p15card->df_list; df != NULL; df = df->next) { + if (df->type == SC_PKCS15_PRKDF) { + rv = sc_pkcs15_parse_df(p15card, df); + if (rv != SC_SUCCESS) { + sc_log(ctx, + "Decoding of EF.PrKDF (%s) failed: %d", + sc_print_path(&df->path), rv); + } + } + if (df->type == SC_PKCS15_PUKDF) { + rv = sc_pkcs15_parse_df(p15card, df); + if (rv != SC_SUCCESS) { + sc_log(ctx, + "Decoding of EF.PuKDF (%s) failed: %d", + sc_print_path(&df->path), rv); + } + } + if (df->type == SC_PKCS15_CDF) { + rv = sc_pkcs15_parse_df(p15card, df); + if (rv != SC_SUCCESS) { + sc_log(ctx, + "Decoding of EF.CDF (%s) failed: %d", + sc_print_path(&df->path), rv); + } + } + if (df->type == SC_PKCS15_DODF) { + rv = sc_pkcs15_parse_df(p15card, df); + if (rv != SC_SUCCESS) { + sc_log(ctx, + "Decoding of EF.DODF (%s) failed: %d", + sc_print_path(&df->path), rv); + } + } + } + + /* Perform required fixes */ + p15_obj = p15card->obj_list; + while (p15_obj != NULL) { + /* Add missing 'auth_id' to private objects */ + if ((p15_obj->flags & SC_PKCS15_CO_FLAG_PRIVATE) + && (p15_obj->auth_id.len == 0)) { + p15_obj->auth_id.value[0] = 0x01; + p15_obj->auth_id.len = 1; + } + /* Remove found public keys as cannot be read_binary()'d */ + if ( p15_obj->df && (p15_obj->df->type == SC_PKCS15_PUKDF) ) { + sc_pkcs15_object_t *puk = p15_obj; + p15_obj = p15_obj->next; + sc_pkcs15_remove_object(p15card, puk); + sc_pkcs15_free_object(puk); + } else { + p15_obj = p15_obj->next; + } + } + + LOG_FUNC_RETURN(ctx, SC_SUCCESS); +} + +/********************************************/ +/* Public Functions When called as DLL Module*/ +/********************************************/ + +const char *sc_driver_version() +{ + return "0.12.3-svn"; /* defined in config.h of OpenSC */ +} + +int bind(sc_pkcs15_card_t * p15card, sc_pkcs15emu_opt_t * options) +{ + /* Check for correct card driver (i.e. iso7816) */ + if (strcmp(p15card->card->driver->short_name, "dnie") != 0) + return SC_ERROR_WRONG_CARD; + + /* Check for correct card */ + if (dnie_match_card(p15card->card) != 1) + return SC_ERROR_WRONG_CARD; + return sc_pkcs15emu_dnie_init(p15card); +} + +/****************************************/ +/* public functions for in-built module */ +/****************************************/ +int sc_pkcs15emu_dnie_init_ex(sc_pkcs15_card_t * p15card, + sc_pkcs15emu_opt_t * opts) +{ + int r=SC_SUCCESS; + sc_context_t *ctx = p15card->card->ctx; + LOG_FUNC_CALLED(ctx); + + /* if no check flag execute unconditionally */ + if (opts && opts->flags & SC_PKCS15EMU_FLAGS_NO_CHECK) + LOG_FUNC_RETURN(ctx, sc_pkcs15emu_dnie_init(p15card)); + /* check for proper card */ + r = dnie_match_card(p15card->card); + if (r == 0) + LOG_FUNC_RETURN(ctx, SC_ERROR_WRONG_CARD); + /* ok: initialize and return */ + LOG_FUNC_RETURN(ctx, sc_pkcs15emu_dnie_init(p15card)); +} diff --git a/src/libopensc/pkcs15-syn.c b/src/libopensc/pkcs15-syn.c index e2f60043..9d182ce1 100644 --- a/src/libopensc/pkcs15-syn.c +++ b/src/libopensc/pkcs15-syn.c @@ -67,6 +67,8 @@ extern int sc_pkcs15emu_itacns_init_ex(sc_pkcs15_card_t *, sc_pkcs15emu_opt_t *); extern int sc_pkcs15emu_sc_hsm_init_ex(sc_pkcs15_card_t *, sc_pkcs15emu_opt_t *); +extern int sc_pkcs15emu_dnie_init_ex(sc_pkcs15_card_t *, + sc_pkcs15emu_opt_t *); static struct { const char * name; @@ -90,6 +92,7 @@ static struct { { "pteid", sc_pkcs15emu_pteid_init_ex }, { "oberthur", sc_pkcs15emu_oberthur_init_ex }, { "sc-hsm", sc_pkcs15emu_sc_hsm_init_ex }, + { "dnie", sc_pkcs15emu_dnie_init_ex }, { NULL, NULL } }; @@ -113,6 +116,11 @@ int sc_pkcs15_is_emulation_only(sc_card_t *card) case SC_CARD_TYPE_OPENPGP_V1: case SC_CARD_TYPE_OPENPGP_V2: case SC_CARD_TYPE_SC_HSM: + case SC_CARD_TYPE_DNIE_BASE: + case SC_CARD_TYPE_DNIE_BLANK: + case SC_CARD_TYPE_DNIE_ADMIN: + case SC_CARD_TYPE_DNIE_USER: + case SC_CARD_TYPE_DNIE_TERMINATED: return 1; default: return 0; diff --git a/src/libopensc/user-interface.c b/src/libopensc/user-interface.c new file mode 100644 index 00000000..f88aee0f --- /dev/null +++ b/src/libopensc/user-interface.c @@ -0,0 +1,317 @@ +/** + * user-interface.c: Support for GUI functions + * + * This file contains code for several related user-interface + * functions: + * - Ask user confirmation + * - Let user enter pin + * + * Copyright (C) 2010 Juan Antonio Martinez + * + * 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 + */ + +#define __USER_INTERFACE_C__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#ifdef _WIN32 +#define UNICODE +#include +#endif +#ifdef __APPLE__ +#include +#endif +/* default titles */ +#define USER_CONSENT_TITLE "Confirm" +#define USER_PIN_TITLE "PIN Request" +#define USER_PIN_PROMPT "Enter PIN:" + +#include "libopensc/opensc.h" +#include "libopensc/log.h" +#include "libopensc/user-interface.h" +#include "libopensc/cwa-dnie.h" + +#ifdef ENABLE_DNIE_UI +/** + * Messages used on pinentry protocol + */ +char *user_consent_msgs[] = { "SETTITLE", "SETDESC", "CONFIRM", "BYE" }; +char *user_pin_msgs[] = { "SETTITLE", "SETPROMPT", "GETPIN", "BYE" }; + +static int ui_ask_user_pin( + sc_context_t *ctx, /* Card context */ + const char *title, /* Title of the window */ + const char *msg, /* Text to be shown to the user */ + char *pinbuf, /* Where to store the user entered data */ + size_t *pinlen) { /* buffer length; on return user data length */ + + /* TODO: write :-) */ + return SC_ERROR_NOT_SUPPORTED; +} + +/** + * Ask user for pin. + * + * Check the user pin configuration, + * Invoke proper gui app and check result + * + * @param card pointer to sc_card structure + * @param title Text to appear in the window header + * @param pin Structure to handle/store pin related data + * @return SC_SUCCESS if user accepts , else error code + */ +int sc_ask_user_pin(struct sc_card * card, const char *title, struct sc_pin_cmd_pin *pin) { + char *pinbuf=NULL; + size_t pinlen=0; + int res=SC_ERROR_INTERNAL; + char *msg=NULL; + + if ( (card==NULL) || (card->ctx==NULL) ) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + if (pin==NULL) LOG_FUNC_RETURN(card->ctx,SC_ERROR_INVALID_ARGUMENTS); + + /* use a temporary buffer to ask for pin */ + if (pin->max_length<=0) { + msg="Invalid pin max lenght"; + res=SC_ERROR_INVALID_ARGUMENTS; + goto ask_user_pin_end; + } + pinlen=pin->max_length; + + pinbuf= calloc(pin->max_length, sizeof(char)); + if (!pinbuf) { + msg="Cannot create pin buffer"; + res=SC_ERROR_OUT_OF_MEMORY; + goto ask_user_pin_end; + } + + res= ui_ask_user_pin( + card->ctx, + (title==NULL)?USER_PIN_TITLE:title, + (pin->prompt==NULL)?USER_PIN_PROMPT:pin->prompt, + pinbuf, + &pinlen); + if (res!=SC_SUCCESS) { + msg="Error in ui_ask_user_pin"; + goto ask_user_pin_end; + } + + /* TODO: parse received data and fill result structure */ + + /* arriving here means success */ + res=SC_SUCCESS; + +ask_user_pin_end: + if (msg!=NULL) sc_log(card->ctx,msg); + if (pinbuf!=NULL) { + memset(pinbuf,0,pinlen); + free(pinbuf); + } + LOG_FUNC_RETURN(card->ctx,res); +} + +/** + * Ask for user consent. + * + * Check for user consent configuration, + * Invoke proper gui app and check result + * + * @param card pointer to sc_card structure + * @param title Text to appear in the window header + * @param text Message to show to the user + * @return SC_SUCCESS on user consent OK , else error code + */ +int sc_ask_user_consent(struct sc_card * card, const char *title, const char *message) +{ +#ifdef __APPLE__ + CFOptionFlags result; /* result code from the message box */ + /* convert the strings from char* to CFStringRef */ + CFStringRef header_ref; /* to store title */ + CFStringRef message_ref; /* to store message */ +#endif +#ifdef linux + pid_t pid; + FILE *fin=NULL; + FILE *fout=NULL; /* to handle pipes as streams */ + struct stat st_file; /* to verify that executable exists */ + int srv_send[2]; /* to send data from server to client */ + int srv_recv[2]; /* to receive data from client to server */ + char outbuf[1024]; /* to compose and send messages */ + char buf[1024]; /* to store client responses */ + int n = 0; /* to iterate on to-be-sent messages */ +#endif + int res = SC_ERROR_INTERNAL; /* by default error :-( */ + char *msg = NULL; /* to makr errors */ + + if ((card == NULL) || (card->ctx == NULL)) + return SC_ERROR_INVALID_ARGUMENTS; + LOG_FUNC_CALLED(card->ctx); + + if ((title==NULL) || (message==NULL)) + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + + if (GET_DNIE_UI_CTX(card).user_consent_enabled == 0) { + sc_log(card->ctx, + "User Consent is disabled in configuration file"); + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); + } +#ifdef _WIN32 + /* in Windows, do not use pinentry, but MessageBox system call */ + res = MessageBox ( + NULL, + TEXT(message), + TEXT(title), + MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON2 | MB_APPLMODAL + ); + if ( res == IDOK ) + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); + LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_ALLOWED); +#elif __APPLE__ + /* Also in Mac OSX use native functions */ + + /* convert the strings from char* to CFStringRef */ + header_ref = CFStringCreateWithCString( NULL, title, strlen(title) ); + message_ref = CFStringCreateWithCString( NULL,message, strlen(message) ); + + /* Displlay user notification alert */ + CFUserNotificationDisplayAlert( + 0, /* no timeout */ + kCFUserNotificationNoteAlertLevel, /* Alert level */ + NULL, /* IconURL, use default, you can change */ + /* it depending message_type flags */ + NULL, /* SoundURL (not used) */ + NULL, /* localization of strings */ + header_ref, /* header. Cannot be null */ + message_ref, /* message text */ + CFSTR("Cancel"), /* default ( "OK" if null) button text */ + CFSTR("OK"), /* second button title */ + NULL, /* third button title, null--> no other button */ + &result /* response flags */ + ); + + /* Clean up the strings */ + CFRelease( header_ref ); + CFRelease( message_ref ); + /* Return 0 only if "OK" is selected */ + if( result == kCFUserNotificationAlternateResponse ) + LOG_FUNC_RETURN(card->ctx, SC_SUCCESS); + LOG_FUNC_RETURN(card->ctx, SC_ERROR_NOT_ALLOWED); +#elif linux + /* check that user_consent_app exists. TODO: check if executable */ + res = stat(GET_DNIE_UI_CTX(card).user_consent_app, &st_file); + if (res != 0) { + sc_log(card->ctx, "Invalid pinentry application: %s\n", + GET_DNIE_UI_CTX(card).user_consent_app); + LOG_FUNC_RETURN(card->ctx, SC_ERROR_INVALID_ARGUMENTS); + } + + /* just a simple bidirectional pipe+fork+exec implementation */ + /* In a pipe, xx[0] is for reading, xx[1] is for writing */ + if (pipe(srv_send) < 0) { + msg = "pipe(srv_send)"; + goto do_error; + } + if (pipe(srv_recv) < 0) { + msg = "pipe(srv_recv)"; + goto do_error; + } + pid = fork(); + switch (pid) { + case -1: /* error */ + msg = "fork()"; + goto do_error; + case 0: /* child */ + /* make our pipes, our new stdin & stderr, closing older ones */ + dup2(srv_send[0], STDIN_FILENO); /* map srv send for input */ + dup2(srv_recv[1], STDOUT_FILENO); /* map srv_recv for output */ + /* once dup2'd pipes are no longer needed on client; so close */ + close(srv_send[0]); + close(srv_send[1]); + close(srv_recv[0]); + close(srv_recv[1]); + /* call exec() with proper user_consent_app from configuration */ + /* if ok should never return */ + execlp(GET_DNIE_UI_CTX(card).user_consent_app, GET_DNIE_UI_CTX(card).user_consent_app, (char *)NULL); + res = SC_ERROR_INTERNAL; + msg = "execlp() error"; /* exec() failed */ + goto do_error; + default: /* parent */ + /* Close the pipe ends that the child uses to read from / write to + * so when we close the others, an EOF will be transmitted properly. + */ + close(srv_send[0]); + close(srv_recv[1]); + /* use iostreams to take care on newlines and text based data */ + fin = fdopen(srv_recv[0], "r"); + if (fin == NULL) { + msg = "fdopen(in)"; + goto do_error; + } + fout = fdopen(srv_send[1], "w"); + if (fout == NULL) { + msg = "fdopen(out)"; + goto do_error; + } + /* read and ignore first line */ + fflush(stdin); + for (n = 0; n<4; n++) { + char *pt; + memset(outbuf, 0, sizeof(outbuf)); + if (n==0) snprintf(outbuf,1023,"%s %s\n",user_consent_msgs[0],title); + else if (n==1) snprintf(outbuf,1023,"%s %s\n",user_consent_msgs[1],message); + else snprintf(outbuf,1023,"%s\n",user_consent_msgs[n]); + /* send message */ + fputs(outbuf, fout); + fflush(fout); + /* get response */ + memset(buf, 0, sizeof(buf)); + pt=fgets(buf, sizeof(buf) - 1, fin); + if (pt==NULL) { + res = SC_ERROR_INTERNAL; + msg = "fgets() Unexpected IOError/EOF"; + goto do_error; + } + if (strstr(buf, "OK") == NULL) { + res = SC_ERROR_NOT_ALLOWED; + msg = "fail/cancel"; + goto do_error; + } + } + } /* switch */ + /* arriving here means signature has been accepted by user */ + res = SC_SUCCESS; + msg = NULL; +do_error: + /* close out channel to force client receive EOF and also die */ + if (fout != NULL) fclose(fout); + if (fin != NULL) fclose(fin); +#else +#error "Don't know how to handle user consent in this (rare) Operating System" +#endif + if (msg != NULL) + sc_log(card->ctx, "%s", msg); + LOG_FUNC_RETURN(card->ctx, res); +} + +#endif diff --git a/src/libopensc/user-interface.h b/src/libopensc/user-interface.h new file mode 100644 index 00000000..14f635a5 --- /dev/null +++ b/src/libopensc/user-interface.h @@ -0,0 +1,66 @@ +/** + * user-interface.c: Support for GUI functions + * + * This file contains code for several related user-interface + * functions: + * - Ask user confirmation + * - Let user enter pin + * + * Copyright (C) 2010 Juan Antonio Martinez + * + * 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 __USER_INTERFACE_H__ +#define __USER_INTERFACE_H__ + +/** + * To handle user interface routines + */ +typedef struct ui_context { + int user_consent_enabled; + char *user_consent_app; +} ui_context_t; + +struct sc_card; +struct sc_pin_cmd_pin; + +/** + * Ask for user consent. + * + * Check for user consent configuration, + * invoke proper gui app and check result + * + * @param card pointer to sc_card structure + * @param title Text to appear in the window header + * @param text Message to show to the user + * @return SC_SUCCESS if user accepts , else error code + */ +int sc_ask_user_consent(struct sc_card * card, const char *title, const char *message); + +/** + * Ask user for pin. + * + * Check the user pin configuration, + * invoke proper gui app and check result + * + * @param card pointer to sc_card structure + * @param title Text to appear in the window header + * @param pin Structure to handle/store pin related data + * @return SC_SUCCESS if user accepts , else error code + */ +int sc_ask_user_pin(struct sc_card * card, const char *title, struct sc_pin_cmd_pin *pin); + +#endif diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index cc8ab280..c1de31ad 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -7,7 +7,8 @@ noinst_HEADERS = util.h bin_PROGRAMS = opensc-tool opensc-explorer pkcs15-tool pkcs15-crypt \ pkcs11-tool cardos-tool eidenv openpgp-tool iasecc-tool if ENABLE_OPENSSL -bin_PROGRAMS += cryptoflex-tool pkcs15-init netkey-tool piv-tool westcos-tool sc-hsm-tool +bin_PROGRAMS += cryptoflex-tool pkcs15-init netkey-tool piv-tool \ + westcos-tool sc-hsm-tool dnie-tool endif # compile with $(PTHREAD_CFLAGS) to allow debugging with gdb @@ -48,6 +49,8 @@ iasecc_tool_SOURCES = iasecc-tool.c util.c iasecc_tool_LDADD = $(OPTIONAL_OPENSSL_LIBS) sc_hsm_tool_SOURCES = sc-hsm-tool.c util.c sc_hsm_tool_LDADD = $(OPTIONAL_OPENSSL_LIBS) +dnie_tool_SOURCES = dnie-tool.c util.c +dnie_tool_LDADD = $(OPTIONAL_OPENSSL_LIBS) if WIN32 opensc_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc @@ -65,4 +68,5 @@ westcos_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc openpgp_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc iasecc_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc sc_hsm_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc +sc_hsm_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc endif diff --git a/src/tools/Makefile.mak b/src/tools/Makefile.mak index 2ac667e5..4fb1947c 100644 --- a/src/tools/Makefile.mak +++ b/src/tools/Makefile.mak @@ -3,7 +3,7 @@ TOPDIR = ..\.. !INCLUDE $(TOPDIR)\win32\Make.rules.mak TARGETS = opensc-tool.exe opensc-explorer.exe pkcs15-tool.exe pkcs15-crypt.exe \ - pkcs11-tool.exe cardos-tool.exe eidenv.exe sc-hsm-tool.exe openpgp-tool.exe \ + pkcs11-tool.exe cardos-tool.exe eidenv.exe sc-hsm-tool.exe openpgp-tool.exe dnie-tool.exe \ $(PROGRAMS_OPENSSL) $(TARGETS): $(TOPDIR)\win32\versioninfo.res util.obj diff --git a/src/tools/dnie-tool.c b/src/tools/dnie-tool.c new file mode 100644 index 00000000..b3db7f55 --- /dev/null +++ b/src/tools/dnie-tool.c @@ -0,0 +1,244 @@ +/* + * dnie-tool.c: DNIe tool + * + * Copyright (C) 2011 Juan Antonio Martinez + * + * Based on file rutoken-tool.c from Pavel Mironchik + * and Eugene Hermann + * + * 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 "config.h" + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include + +#include "libopensc/opensc.h" +#include "libopensc/errors.h" +#include "libopensc/cardctl.h" +#include "libopensc/pkcs15.h" +#include "util.h" + +/* win32 needs this in open(2) */ +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +static const char *app_name = "dnie-tool"; + +#define OP_NONE 0 /* no operation requested */ +#define OP_GET_DATA 1 /* retrieve DNIe number, apellidos, nombre */ +#define OP_GET_IDESP 2 /* retrieve IDESP */ +#define OP_GET_VERSION 4 /* retrieve DNIe version number */ +#define OP_GET_SERIALNR 8 /* Get SerialNumber */ + +static const struct option options[] = { + {"reader", 1, NULL, 'r'}, + {"driver", 1, NULL, 'c'}, + {"wait", 0, NULL, 'w'}, + {"pin", 1, NULL, 'p'}, + {"idesp", 0, NULL, 'i'}, + {"version", 0, NULL, 'V'}, + {"data", 0, NULL, 'd'}, + {"serial", 0, NULL, 's'}, + {"all", 0, NULL, 'a'}, + {"verbose", 0, NULL, 'v'}, + {NULL, 0, NULL, 0 } +}; + +static const char *option_help[] = { + "Uses reader number [0]", + "Uses reader driver [auto-detect]", + "Wait for a card to be inserted", + "Specify PIN", + "Retrieve IDESP", + "Gets DNIe software version", + "Show DNIe number, Name, and SurName", + "Show DNIe serial number", + "Display all the information available", + "Verbose operation. Use several times to enable debug output." +}; + +/* Get DNIe device extra information */ + +int main(int argc, char* argv[]) +{ + int opt_wait = 0; + const char *opt_pin = NULL; + const char *opt_reader = NULL; + const char *opt_driver = NULL; + int opt_operation = OP_NONE; + int verbose = 0; + + int err = 0; + sc_context_t *ctx = NULL; + sc_context_param_t ctx_param; + sc_card_t *card = NULL; + int c, long_optind, r, tries_left; + + char *data[] = { NULL, NULL, NULL, NULL, NULL }; + sc_serial_number_t serial; + + while (1) { + c = getopt_long(argc, argv, "r:c:wp:iVdsav", + options, &long_optind); + if (c == -1) + break; + switch (c) { + case '?': + util_print_usage_and_die(app_name, options, option_help, NULL); + case 'r': + opt_reader = optarg; + break; + case 'c': + opt_driver = optarg; + break; + case 'w': + opt_wait = 1; + break; + case 'p': + opt_pin = optarg; + break; + case 'i': + opt_operation |= OP_GET_IDESP; + break; + case 'V': + opt_operation |= OP_GET_VERSION; + break; + case 'd': + opt_operation |= OP_GET_DATA; + break; + case 's': + opt_operation |= OP_GET_SERIALNR; + break; + case 'a': + opt_operation = OP_GET_IDESP | OP_GET_VERSION | OP_GET_DATA | OP_GET_SERIALNR; + break; + case 'v': + verbose++; + break; + } + } + + memset(&ctx_param, 0, sizeof(ctx_param)); + ctx_param.app_name = app_name; + r = sc_context_create(&ctx, &ctx_param); + if (r) { + fprintf(stderr, "Error: Failed to establish context: %s\n", + sc_strerror(r)); + return -1; + } + + if (verbose > 1) { + ctx->debug = verbose; + sc_ctx_log_to_file(ctx,"stderr"); + } + + if (opt_driver != NULL) { + err = sc_set_card_driver(ctx, opt_driver); + if (err) { + fprintf(stderr, "Driver '%s' not found!\n", + opt_driver); + err = -1; + goto dnie_tool_end; + } + } + + if (util_connect_card(ctx, &card, opt_reader, opt_wait, verbose) ) { + fprintf(stderr, "Error: Cannot connect with card\n"); + err = -1; + goto dnie_tool_end; + } + + if ( strcmp(card->name,"dnie") ) { + fprintf(stderr, "Error: Card sems not to be a DNIe\n"); + err=-1; + goto dnie_tool_end; + } + + if ( opt_pin ) { + /* verify */ + r = sc_verify(card, SC_AC_CHV, 0, + (u8*)opt_pin, strlen(opt_pin), &tries_left); + if (r) { + fprintf(stderr, "Error: PIN verification failed: %s", + sc_strerror(r)); + if (r == SC_ERROR_PIN_CODE_INCORRECT) + fprintf(stderr, " (tries left %d)", tries_left); + putc('\n', stderr); + err=-1; + goto dnie_tool_end; + } + } + + if (opt_operation==0) { + fprintf(stderr,"Error: No operation specified"); + err = -1; + goto dnie_tool_end; + } + if (opt_operation & 0x0f) { + r = sc_card_ctl(card, SC_CARDCTL_DNIE_GET_INFO, data); + if ( r != SC_SUCCESS ) { + fprintf(stderr, "Error: Get info failed: %s\n", sc_strerror(r)); + err = -1; + goto dnie_tool_end; + } + } + if (opt_operation & OP_GET_DATA) { + printf("DNIe Number: %s\n",data[0]); + printf("SurName: %s\n",data[1]); + printf("Name: %s\n",data[2]); + } + if (opt_operation & OP_GET_IDESP) { + if (data[3]==NULL) + printf("IDESP: (No disponible)\n"); + else printf("IDESP: %s\n",data[3]); + } + if (opt_operation & OP_GET_VERSION) { + if (data[4]==NULL) + printf("DNIe Version: (No disponible)\n"); + else printf("DNIe Version: %s\n",data[4]); + } + if (opt_operation & OP_GET_SERIALNR) { + r = sc_card_ctl(card, SC_CARDCTL_GET_SERIALNR, &serial); + if ( r != SC_SUCCESS ) { + fprintf(stderr,"Error: Get serial failed: %s\n",sc_strerror(r)); + err = -1; + goto dnie_tool_end; + } + printf("Serial number: "); + util_hex_dump(stdout, serial.value, serial.len, NULL); + putchar('\n'); + } + +dnie_tool_end: + if (card) { + /* sc_lock and sc_connect_card in util_connect_card */ + sc_unlock(card); + sc_disconnect_card(card); + } + if (ctx) + sc_release_context(ctx); + return err; +} + diff --git a/win32/OpenSC.wxs.in b/win32/OpenSC.wxs.in old mode 100755 new mode 100644 index fb2f1e1c..632178d6 --- a/win32/OpenSC.wxs.in +++ b/win32/OpenSC.wxs.in @@ -106,6 +106,9 @@ + + + @@ -215,6 +218,7 @@ +