From d89385c3e14517f72766d6daee3b00be45097a00 Mon Sep 17 00:00:00 2001 From: Frank Morgner Date: Sun, 18 Jan 2015 00:55:29 +0100 Subject: [PATCH] added support for QES with German ID card libnpa: requires an ID card that is initialized for QES. Today, this means you need to register at sign-me. virtualsmartcard: We don't actually create a real signature, because PyCrypto can't handle ECDSA, let alone brainpoolP256r1. We only return a 64 byte buffer. --- npa/doc/README.txt | 23 +- npa/doc/README.txt.in | 21 +- npa/opensc.conf.in | 31 +- npa/src/Makefile.am | 7 +- npa/src/card-npa.c | 398 ++++++++++++++++-- npa/src/card-npa.h | 2 - npa/src/npa-tool.c | 46 +- npa/src/npa.c | 24 +- npa/src/npa/npa.h | 5 +- npa/src/npa/scutil.h | 2 + npa/src/pkcs15-npa.c | 261 ------------ npa/src/scutil.c | 38 ++ .../npa-example-data/ZZSTCVCA00001.cvcert | Bin 0 -> 356 bytes .../npa-example-data/ZZSTCVCA00001.pem | 8 + .../npa-example-data/ZZSTCVCA00001.pkcs8 | Bin 0 -> 113 bytes .../npa-example-data/ZZSTDVCA00001.cvcert | Bin 0 -> 195 bytes .../npa-example-data/ZZSTDVCA00001.pkcs8 | Bin 0 -> 97 bytes .../npa-example-data/ZZSTTERM00001.cvcert | Bin 0 -> 195 bytes .../npa-example-data/ZZSTTERM00001.pkcs8 | Bin 0 -> 97 bytes virtualsmartcard/src/vpicc/vicc.in | 20 +- .../vpicc/virtualsmartcard/CardGenerator.py | 26 +- .../virtualsmartcard/VirtualSmartcard.py | 13 +- .../src/vpicc/virtualsmartcard/cards/nPA.py | 54 +-- 23 files changed, 555 insertions(+), 424 deletions(-) delete mode 100644 npa/src/pkcs15-npa.c create mode 100644 virtualsmartcard/npa-example-data/ZZSTCVCA00001.cvcert create mode 100644 virtualsmartcard/npa-example-data/ZZSTCVCA00001.pem create mode 100644 virtualsmartcard/npa-example-data/ZZSTCVCA00001.pkcs8 create mode 100644 virtualsmartcard/npa-example-data/ZZSTDVCA00001.cvcert create mode 100644 virtualsmartcard/npa-example-data/ZZSTDVCA00001.pkcs8 create mode 100644 virtualsmartcard/npa-example-data/ZZSTTERM00001.cvcert create mode 100644 virtualsmartcard/npa-example-data/ZZSTTERM00001.pkcs8 diff --git a/npa/doc/README.txt b/npa/doc/README.txt index fb06fe8..877217f 100644 --- a/npa/doc/README.txt +++ b/npa/doc/README.txt @@ -196,7 +196,7 @@ components of OpenSC. Load it by setting :envvar:`OPENSC_CONF`:: export OPENSC_CONF=$VSMARTCARD/npa/opensc.conf On Windows you need to use :command:`set` instead of :command:`export`. In -:file:`npa-0.6_win32` do the following:: +:file:`npa-0.7_win32` do the following:: cd bin set OPENSC_CONF=..\etc\opensc.conf @@ -211,15 +211,6 @@ with :command:`opensc-explorer`:: OpenSC [3F00]> verify CHV3 313233343536 Code correct. -Since the German ID card does not contain any PKCS#15 data we emulate the data -structures with an other external library. Currently, the PKCS#15 emulator does -not support private key and certificate objects for qualified electronic -signature but only the PIN objects. For example, you can change the eID-PIN -using the :command:`pkcs15-tool`:: - - pkcs15-tool --change-pin --auth-id 03 \ - --pin=123456 --new-pin=abcdef # yes, an ASCII eID-PIN is allowed - When the eID-PIN was verified incorrectly three times, it is blocked and must be unblocked with the PUK. But unlike traditional cards the German ID card does suspend the eID-PIN after the second try of verification. To unlock the last @@ -227,7 +218,16 @@ retry, the CAN is required. Since the suspended state is not captured by OpenSC we handle it transparently within the driver. If the eID-PIN shall be verified and it is suspended, the card driver will verify the CAN first. If no CAN is given in :file:`opensc.conf`, the driver will request it on the standard -input. +input. You can use |npa-tool| to unblock or resume the eID-PIN. + +The German ID card is capable of creating a qualified electronic signature. +Therefor, the card must be initialized by a trust center with the user's +consent. Today this means, that the user need to register for `sign-me`_. The +process also initializes the QES-PIN, which unlocks the signature key. Below, +you can see two examples how to create a signature:: + + pkcs15-crypt --sign --sha-256 --input $SHA256_FILE --pin $QES_PIN + pkcs11-tool --module opensc-pkcs11.so --sign --pin $QES_PIN --input-file $SHA256_FILE .. include:: questions.txt @@ -242,3 +242,4 @@ Notes and References .. _OpenPACE: https://frankmorgner.github.io/openpace/ .. _OpenSC: https://github.com/OpenSC/OpenSC .. _OpenSSL: http://www.openssl.org +.. _sign-me: https://www.bundesdruckerei.de/en/798-sign-me diff --git a/npa/doc/README.txt.in b/npa/doc/README.txt.in index 530f151..2dfe25e 100644 --- a/npa/doc/README.txt.in +++ b/npa/doc/README.txt.in @@ -211,15 +211,6 @@ with :command:`opensc-explorer`:: OpenSC [3F00]> verify CHV3 313233343536 Code correct. -Since the German ID card does not contain any PKCS#15 data we emulate the data -structures with an other external library. Currently, the PKCS#15 emulator does -not support private key and certificate objects for qualified electronic -signature but only the PIN objects. For example, you can change the eID-PIN -using the :command:`pkcs15-tool`:: - - pkcs15-tool --change-pin --auth-id 03 \ - --pin=123456 --new-pin=abcdef # yes, an ASCII eID-PIN is allowed - When the eID-PIN was verified incorrectly three times, it is blocked and must be unblocked with the PUK. But unlike traditional cards the German ID card does suspend the eID-PIN after the second try of verification. To unlock the last @@ -227,7 +218,16 @@ retry, the CAN is required. Since the suspended state is not captured by OpenSC we handle it transparently within the driver. If the eID-PIN shall be verified and it is suspended, the card driver will verify the CAN first. If no CAN is given in :file:`opensc.conf`, the driver will request it on the standard -input. +input. You can use |npa-tool| to unblock or resume the eID-PIN. + +The German ID card is capable of creating a qualified electronic signature. +Therefor, the card must be initialized by a trust center with the user's +consent. Today this means, that the user need to register for `sign-me`_. The +process also initializes the QES-PIN, which unlocks the signature key. Below, +you can see two examples how to create a signature:: + + pkcs15-crypt --sign --sha-256 --input $SHA256_FILE --pin $QES_PIN + pkcs11-tool --module opensc-pkcs11.so --sign --pin $QES_PIN --input-file $SHA256_FILE .. include:: questions.txt @@ -242,3 +242,4 @@ Notes and References .. _OpenPACE: https://frankmorgner.github.io/openpace/ .. _OpenSC: https://github.com/OpenSC/OpenSC .. _OpenSSL: http://www.openssl.org +.. _sign-me: https://www.bundesdruckerei.de/en/798-sign-me diff --git a/npa/opensc.conf.in b/npa/opensc.conf.in index dd1d8ab..c099d13 100644 --- a/npa/opensc.conf.in +++ b/npa/opensc.conf.in @@ -19,23 +19,22 @@ app default { card_driver npa { # The location of the driver library module = @libdir@/libcardnpa.@DYN_LIB_EXT@; - } + # German ID card requires the CAN to be verified before QES PIN. This, + # however, is not part of the PKCS#15 profile of the card. So for + # verifying the PIN we actually need both. The CAN may be given here. + # If the CAN is not given here, it will be prompted on the command line + # or on the reader. + can = 222222; - # PKCS #15 - framework pkcs15 { - # additional settings per driver - # - # For pkcs15 emulators loaded from an external shared - # library/DLL, you need to specify the path name of the module - # and customize the card_atr example above correctly. - # - emulate npa { - # The location of the driver library - module = @libdir@/libpkcs15npa.@DYN_LIB_EXT@; - - function = sc_pkcs15emu_npa_init_ex; - can = 222222; - } + # QES is only possible with a Comfort Reader (CAT-K), which holds a + # cryptographic key to authenticate itself as signature terminal (ST). + # We usually will use the reader's capability to sign the data. + # However, during developement you may specify soft certificates and + # keys for a ST below. + # The following example EAC PKI can be found in npa-example-data + st_dv_certificate = ZZSTDVCA00001.cvcert; + st_certificate = ZZSTTERM00001.cvcert; + st_key = ZZSTTERM00001.pkcs8; } } diff --git a/npa/src/Makefile.am b/npa/src/Makefile.am index 9770f13..e498f7b 100644 --- a/npa/src/Makefile.am +++ b/npa/src/Makefile.am @@ -27,11 +27,6 @@ libcardnpa_la_LIBADD = $(OPENSC_LIBS) libnpa.la libcardnpa_la_CFLAGS = $(OPENSSL_CFLAGS) $(OPENPACE_CFLAGS) $(OPENSC_CFLAGS) libcardnpa_la_LDFLAGS = -no-undefined -libpkcs15npa_la_SOURCES = pkcs15-npa.c -libpkcs15npa_la_LIBADD = $(OPENSC_LIBS) libcardnpa.la libnpa.la -libpkcs15npa_la_CFLAGS = $(OPENSSL_CFLAGS) $(OPENPACE_CFLAGS) $(OPENSC_CFLAGS) -libpkcs15npa_la_LDFLAGS = -no-undefined - npa_tool_SOURCES = npa-tool.c $(BUILT_SOURCES) npa_tool_LDADD = libnpa.la $(OPENSC_LIBS) $(OPENPACE_LIBS) $(OPENSSL_LIBS) npa_tool_CFLAGS = $(OPENSSL_CFLAGS) $(OPENPACE_CFLAGS) $(OPENSC_CFLAGS) @@ -61,7 +56,7 @@ $(abs_builddir)/npa-tool.1: npa-tool.ggo bin_PROGRAMS = npa-tool noinst_PROGRAMS = example -lib_LTLIBRARIES = libnpa.la libcardnpa.la libpkcs15npa.la +lib_LTLIBRARIES = libnpa.la libcardnpa.la noinst_HEADERS = \ sslutil.h \ diff --git a/npa/src/card-npa.c b/npa/src/card-npa.c index ccdb316..77aaa56 100644 --- a/npa/src/card-npa.c +++ b/npa/src/card-npa.c @@ -24,12 +24,54 @@ #include "card-npa.h" #include #include +#include #include +struct npa_drv_data { + const char *can; + unsigned char *st_dv_certificate; + size_t st_dv_certificate_len; + unsigned char *st_certificate; + size_t st_certificate_len; + unsigned char *st_key; + size_t st_key_len; + unsigned char *ef_cardaccess; + size_t ef_cardaccess_length; + unsigned char *ef_cardsecurity; + size_t ef_cardsecurity_length; +}; + +static struct npa_drv_data *npa_drv_data_create(void) +{ + struct npa_drv_data *drv_data = calloc(1, sizeof *drv_data); + return drv_data; +} + +static void npa_drv_data_free(struct npa_drv_data *drv_data) +{ + if (drv_data) { + free(drv_data->ef_cardaccess); + free(drv_data->ef_cardsecurity); + free(drv_data->st_certificate); + free(drv_data->st_dv_certificate); + free(drv_data->st_key); + free(drv_data); + } +} + static struct sc_atr_table npa_atrs[] = { - {"3B:8A:80:01:80:31:F8:73:F7:41:E0:82:90:00:75", NULL, "German ID card (neuer Personalausweis, nPA)", SC_CARD_TYPE_NPA, 0, NULL}, - {"3B:84:80:01:00:00:90:00:95", NULL, "German ID card (Test neuer Personalausweis)", SC_CARD_TYPE_NPA_TEST, 0, NULL}, - {"3B:88:80:01:00:E1:F3:5E:13:77:83:00:00", "FF:FF:FF:FF:00:FF:FF:FF:FF:FF:FF:FF:00", "German ID card (Test Online-Ausweisfunktion)", SC_CARD_TYPE_NPA_ONLINE, 0, NULL}, + {"3B:8A:80:01:80:31:F8:73:F7:41:E0:82:90:00:75", + "FF:FF:FF:FF:FF:FF:00:FF:00:00:FF:FF:FF:FF:00", + "German ID card (neuer Personalausweis, nPA)", SC_CARD_TYPE_NPA, 0, NULL}, + {"3B:88:80:01:00:00:00:00:00:00:00:00:09", NULL, + "German ID card (neuer Personalausweis, nPA)", SC_CARD_TYPE_NPA, 0, NULL}, + {"3B:87:80:01:80:31:B8:73:84:01:E0:19", NULL, + "German ID card (neuer Personalausweis, nPA)", SC_CARD_TYPE_NPA, 0, NULL}, + {"3B:84:80:01:00:00:90:00:95", NULL, + "German ID card (Test neuer Personalausweis)", SC_CARD_TYPE_NPA_TEST, 0, NULL}, + {"3B:88:80:01:00:E1:F3:5E:13:77:83:00:00", + "FF:FF:FF:FF:00:FF:FF:FF:FF:FF:FF:FF:00", + "German ID card (Test Online-Ausweisfunktion)", SC_CARD_TYPE_NPA_ONLINE, 0, NULL}, {NULL, NULL, NULL, 0, 0, NULL} }; @@ -41,6 +83,64 @@ static struct sc_card_driver npa_drv = { NULL, 0, NULL }; +static int npa_load_options(sc_context_t *ctx, struct npa_drv_data *drv_data) +{ + int r; + size_t i, j; + scconf_block **found_blocks, *block; + const char *file; + + if (!ctx || !drv_data) { + r = SC_ERROR_INTERNAL; + goto err; + } + + for (i = 0; ctx->conf_blocks && ctx->conf_blocks[i] != NULL; i++) { + found_blocks = scconf_find_blocks(ctx->conf, ctx->conf_blocks[i], + "card_driver", "npa"); + if (!found_blocks) + continue; + + for (j = 0, block = found_blocks[j]; block; j++, block = found_blocks[j]) { + if (!drv_data->can) + drv_data->can = scconf_get_str(block, "can", NULL); + + if (!drv_data->st_dv_certificate + || !drv_data->st_dv_certificate_len) { + file = scconf_get_str(block, "st_dv_certificate", NULL); + if (!fread_to_eof(file, + (unsigned char **) &drv_data->st_dv_certificate, + &drv_data->st_dv_certificate_len)) + sc_log(ctx, "Waring: Could not read %s.\n", file); + } + + if (!drv_data->st_certificate + || !drv_data->st_certificate_len) { + file = scconf_get_str(block, "st_certificate", NULL); + if (!fread_to_eof(file, + (unsigned char **) &drv_data->st_certificate, + &drv_data->st_certificate_len)) + sc_log(ctx, "Waring: Could not read %s.\n", file); + } + + if (!drv_data->st_key + || !drv_data->st_key_len) { + file = scconf_get_str(block, "st_key", NULL); + if (!fread_to_eof(file, + (unsigned char **) &drv_data->st_key, + &drv_data->st_key_len)) + sc_log(ctx, "Waring: Could not read %s.\n", file); + } + } + + free(found_blocks); + } + r = SC_SUCCESS; + +err: + return r; +} + static int npa_match_card(sc_card_t * card) { if (_sc_match_atr(card, npa_atrs, &card->type) < 0) @@ -48,32 +148,267 @@ static int npa_match_card(sc_card_t * card) return 1; } -static int npa_init(sc_card_t * card) +static void npa_get_cached_pace_params(sc_card_t *card, + struct establish_pace_channel_input *pace_input, + struct establish_pace_channel_output *pace_output) { - if (card) { -#if 0 - /* we wait for https://github.com/OpenSC/OpenSC/pull/260 to be - * integrated before switching extended length on, here */ - card->max_recv_size = 0xFFFF+1; - card->max_send_size = 0xFFFF; -#endif - card->caps |= SC_CARD_CAP_APDU_EXT | SC_CARD_CAP_RNG; - memset(&card->sm_ctx, 0, sizeof card->sm_ctx); -#ifdef DISABLE_GLOBAL_BOXING_INITIALIZATION - sc_detect_boxing_cmds(card->reader); -#endif + struct npa_drv_data *drv_data; + + if (card->drv_data) { + drv_data = card->drv_data; + + if (pace_output) { + pace_output->ef_cardaccess = drv_data->ef_cardaccess; + pace_output->ef_cardaccess_length = drv_data->ef_cardaccess_length; + } + + if (pace_input && pace_input->pin_id == PACE_PIN_ID_CAN) { + pace_input->pin = (const unsigned char *) drv_data->can; + pace_input->pin_length = drv_data->can ? strlen(drv_data->can) : 0; + } + } +} + +static void npa_get_cached_ta_params(sc_card_t *card, + const unsigned char *certs[2], size_t certs_lens[2], + const unsigned char **st_key, size_t *st_key_len) +{ + struct npa_drv_data *drv_data; + size_t i; + + if (card->drv_data) { + drv_data = card->drv_data; + + if (certs && certs_lens) { + i = 0; + if (drv_data->st_dv_certificate) { + certs[i] = drv_data->st_dv_certificate; + certs_lens[i] = drv_data->st_dv_certificate_len; + i++; + } + if (drv_data->st_certificate) { + certs[i] = drv_data->st_certificate; + certs_lens[i] = drv_data->st_certificate_len; + } + } + if (st_key && st_key_len) { + *st_key = drv_data->st_key; + *st_key_len = drv_data->st_key_len; + } + } +} + +static void npa_get_cached_ca_params(sc_card_t *card, + unsigned char **ef_cardsecurity, size_t *ef_cardsecurity_length) +{ + struct npa_drv_data *drv_data; + + if (card->drv_data) { + drv_data = card->drv_data; + + if (ef_cardsecurity && ef_cardsecurity_length) { + *ef_cardsecurity = drv_data->ef_cardsecurity; + *ef_cardsecurity_length = drv_data->ef_cardsecurity_length; + } + } +} + +static void npa_cache_or_free(sc_card_t *card, + unsigned char **ef_cardaccess, size_t *ef_cardaccess_length, + unsigned char **ef_cardsecurity, size_t *ef_cardsecurity_length) +{ + struct npa_drv_data *drv_data; + + if (card->drv_data) { + drv_data = card->drv_data; + + if (ef_cardaccess && ef_cardaccess_length + && *ef_cardaccess && *ef_cardaccess_length) { + drv_data->ef_cardaccess = *ef_cardaccess; + drv_data->ef_cardaccess_length = *ef_cardaccess_length; + *ef_cardaccess = NULL; + *ef_cardaccess_length = 0; + } + if (ef_cardsecurity && ef_cardsecurity_length + && *ef_cardsecurity && *ef_cardsecurity_length) { + drv_data->ef_cardsecurity = *ef_cardsecurity; + drv_data->ef_cardsecurity_length = *ef_cardsecurity_length; + *ef_cardsecurity = NULL; + *ef_cardsecurity_length = 0; + } + } else { + if (ef_cardaccess && ef_cardaccess_length) { + free(*ef_cardaccess); + *ef_cardaccess = NULL; + *ef_cardaccess_length = 0; + } + if (ef_cardsecurity && ef_cardsecurity_length) { + free(*ef_cardsecurity); + *ef_cardsecurity = NULL; + *ef_cardsecurity_length = 0; + } + } +} + +static int npa_unlock_esign(sc_card_t *card) +{ + int r = SC_ERROR_INTERNAL; + struct establish_pace_channel_input pace_input; + struct establish_pace_channel_output pace_output; + const unsigned char *certs[] = { NULL, NULL }; + size_t certs_lens[] = { 0, 0}; + const unsigned char *st_key = NULL; + size_t st_key_len = 0; + unsigned char *ef_cardsecurity = NULL; + size_t ef_cardsecurity_len = 0; + memset(&pace_input, 0, sizeof pace_input); + memset(&pace_output, 0, sizeof pace_output); + + if (!card) { + r = SC_ERROR_INVALID_CARD; + goto err; } - return SC_SUCCESS; + sc_log(card->ctx, "Will verify CAN first for unlocking eSign application.\n"); + pace_input.chat = esign_chat; + pace_input.chat_length = sizeof esign_chat; + pace_input.pin_id = PACE_PIN_ID_CAN; + npa_get_cached_pace_params(card, &pace_input, &pace_output); + npa_get_cached_ta_params(card, certs, certs_lens, &st_key, &st_key_len); + npa_get_cached_ca_params(card, &ef_cardsecurity, &ef_cardsecurity_len); + + if (!(card->reader && (card->reader->capabilities & SC_READER_CAP_PACE_ESIGN)) + && (!st_key || !st_key_len)) { + sc_log(card->ctx, "QES requires a comfort reader (CAT-K) or a ST certificate.\n"); + r = SC_ERROR_NOT_SUPPORTED; + } + + /* FIXME set flags with opensc.conf */ + npa_default_flags |= NPA_FLAG_DISABLE_CHECK_ALL; + npa_default_flags |= NPA_FLAG_DISABLE_CHECK_TA; + npa_default_flags |= NPA_FLAG_DISABLE_CHECK_CA; + + /* FIXME show an alert to the user if can == NULL */ + r = perform_pace(card, pace_input, &pace_output, EAC_TR_VERSION_2_02); + if (SC_SUCCESS != r) { + sc_log(card->ctx, "Error verifying CAN.\n"); + goto err; + } + + if (card->reader->capabilities & SC_READER_CAP_PACE_ESIGN) { + sc_log(card->ctx, "Proved Access rights to eSign application with comfort reader (CAT-K).\n"); + } else { + r = perform_terminal_authentication(card, certs, certs_lens, st_key, + st_key_len, NULL, 0); + if (r != SC_SUCCESS) { + sc_log(card->ctx, "Error authenticating as signature terminal.\n"); + goto err; + } + r = perform_chip_authentication(card, &ef_cardsecurity, &ef_cardsecurity_len); + if ( SC_SUCCESS != r) { + sc_log(card->ctx, "Error verifying the chips authenticy.\n"); + } + + sc_log(card->ctx, "Proved Access rights to eSign application with configured key as ST.\n"); + } + +err: + npa_cache_or_free(card, &pace_output.ef_cardaccess, + &pace_output.ef_cardaccess_length, + &ef_cardsecurity, &ef_cardsecurity_len); + free(pace_output.recent_car); + free(pace_output.previous_car); + free(pace_output.id_icc); + free(pace_output.id_pcd); + + return r; +} + +static int npa_init(sc_card_t * card) +{ + int flags = SC_ALGORITHM_ECDSA_RAW; + int ext_flags = 0; + int r; + + if (!card) { + r = SC_ERROR_INVALID_CARD; + goto err; + } + + card->caps |= SC_CARD_CAP_APDU_EXT | SC_CARD_CAP_RNG; + card->max_recv_size = 0xFFFF+1; + card->max_send_size = 0xFFFF; + memset(&card->sm_ctx, 0, sizeof card->sm_ctx); + + r = _sc_card_add_ec_alg(card, 192, flags, ext_flags); + if (r != SC_SUCCESS) + goto err; + r = _sc_card_add_ec_alg(card, 224, flags, ext_flags); + if (r != SC_SUCCESS) + goto err; + r = _sc_card_add_ec_alg(card, 256, flags, ext_flags); + if (r != SC_SUCCESS) + goto err; + /* nPA does not encode the proprietary fieldSize in PrivateECKeyAttributes, + * which leaves it at 0 for OpenSC, so we need to add 0x00 as supported + * field_length */ + r = _sc_card_add_ec_alg(card, 0, flags, ext_flags); + if (r != SC_SUCCESS) + goto err; + +#ifdef DISABLE_GLOBAL_BOXING_INITIALIZATION + sc_detect_boxing_cmds(card->reader); +#endif + + EAC_init(); + card->drv_data = npa_drv_data_create(); + r = npa_load_options(card->ctx, card->drv_data); + if (r != SC_SUCCESS) + goto err; + + /* unlock the eSign application for reading the certificates + * by the PKCS#15 layer (i.e. sc_pkcs15_bind_internal) */ + r = npa_unlock_esign(card); + +err: + return r; } static int npa_finish(sc_card_t * card) { sm_stop(card); + npa_drv_data_free(card->drv_data); + card->drv_data = NULL; + EAC_cleanup(); return SC_SUCCESS; } +static int npa_set_security_env(struct sc_card *card, + const struct sc_security_env *env, int se_num) +{ + int r; + struct sc_card_driver *iso_drv; + struct sc_security_env fixed_env; + + iso_drv = sc_get_iso7816_driver(); + + if (!env || !iso_drv || !iso_drv->ops || !iso_drv->ops->set_security_env) { + r = SC_ERROR_INTERNAL; + } else { + memcpy(&fixed_env, env, sizeof fixed_env); + if (env->operation == SC_SEC_OPERATION_SIGN) { + /* The pkcs#15 layer assumes that the field_size of the private key + * object is correctly initialized and wants to include it as + * algorithm reference. We disable it here */ + fixed_env.flags &= ~SC_SEC_ENV_ALG_REF_PRESENT; + } + r = iso_drv->ops->set_security_env(card, &fixed_env, se_num); + } + + return r; +} + static int npa_pin_cmd_get_info(struct sc_card *card, struct sc_pin_cmd_data *data, int *tries_left) { @@ -134,6 +469,7 @@ static int npa_pace_verify(struct sc_card *card, pace_input.pin = pin->data; pace_input.pin_length = pin->len; } + npa_get_cached_pace_params(card, &pace_input, &pace_output); r = perform_pace(card, pace_input, &pace_output, EAC_TR_VERSION_2_02); @@ -199,7 +535,8 @@ static int npa_pace_verify(struct sc_card *card, } } - free(pace_output.ef_cardaccess); + npa_cache_or_free(card, &pace_output.ef_cardaccess, + &pace_output.ef_cardaccess_length, NULL, NULL); free(pace_output.recent_car); free(pace_output.previous_car); free(pace_output.id_icc); @@ -281,27 +618,17 @@ static int npa_pin_cmd(struct sc_card *card, goto err; break; - case NPA_PIN_ID_ESIGN_PIN: - if (card->reader->capabilities & SC_READER_CAP_PACE_ESIGN) { - sc_log(card->ctx, "Found a comfort reader (CAT-K).\n"); - sc_log(card->ctx, "Will verify CAN first.\n"); - r = npa_pace_verify(card, PACE_PIN_ID_CAN, NULL, - esign_chat, sizeof esign_chat, tries_left); - if (r != SC_SUCCESS) - goto err; - } else { - sc_log(card->ctx, "No comfort reader (CAT-K) found.\n"); - sc_log(card->ctx, "I hope you have already performed EAC as ST...\n"); - } + default: + /* assuming QES PIN */ + + /* We assume that the eSign application has already been + * unlocked, see npa_init(). + * + * Now, verify the QES PIN. */ r = npa_standard_pin_cmd(card, data, tries_left); if (r != SC_SUCCESS) goto err; break; - - default: - r = SC_ERROR_OBJECT_NOT_FOUND; - goto err; - break; } if (data->cmd == SC_PIN_CMD_CHANGE) { @@ -337,6 +664,7 @@ struct sc_card_driver *npa_get_driver(void) npa_ops.match_card = npa_match_card; npa_ops.init = npa_init; npa_ops.finish = npa_finish; + npa_ops.set_security_env = npa_set_security_env; npa_ops.pin_cmd = npa_pin_cmd; npa_ops.logout = npa_logout; diff --git a/npa/src/card-npa.h b/npa/src/card-npa.h index 6a34aee..abf8389 100644 --- a/npa/src/card-npa.h +++ b/npa/src/card-npa.h @@ -32,8 +32,6 @@ enum { SC_CARD_TYPE_NPA_ONLINE, }; -#define NPA_PIN_ID_ESIGN_PIN 0x83 - const unsigned char esign_chat[] = { 0x7F, 0x4C, 0x0E, 0x06, 0x09, 0x04, 0x00, 0x7F, 0x00, 0x07, 0x03, 0x01, 0x02, 0x03, diff --git a/npa/src/npa-tool.c b/npa/src/npa-tool.c index 7446308..f98ac22 100644 --- a/npa/src/npa-tool.c +++ b/npa/src/npa-tool.c @@ -53,44 +53,6 @@ static ssize_t getline(char **lineptr, size_t *n, FILE *stream) } #endif -int fread_to_eof(const char *file, unsigned char **buf, size_t *buflen) -{ - FILE *input = NULL; - int r = 0; - unsigned char *p; - - if (!buflen || !buf) - goto err; - -#define MAX_READ_LEN 0xfff - p = realloc(*buf, MAX_READ_LEN); - if (!p) - goto err; - *buf = p; - - input = fopen(file, "rb"); - if (!input) { - fprintf(stderr, "Could not open %s.\n", file); - goto err; - } - - *buflen = 0; - while (feof(input) == 0 && *buflen < MAX_READ_LEN) { - *buflen += fread(*buf+*buflen, 1, MAX_READ_LEN-*buflen, input); - if (ferror(input)) { - fprintf(stderr, "Could not read %s.\n", file); - goto err; - } - } - - r = 1; -err: - if (input) - fclose(input); - - return r; -} - static void read_dg(sc_card_t *card, unsigned char sfid, const char *dg_str, unsigned char **dg, size_t *dg_len) { @@ -299,6 +261,8 @@ main (int argc, char **argv) unsigned char *dg = NULL; size_t dg_len = 0; ASN1_AUXILIARY_DATA *templates = NULL; + unsigned char *ef_cardsecurity = NULL; + size_t ef_cardsecurity_len = 0; struct gengetopt_args_info cmdline; @@ -695,7 +659,7 @@ nopace: goto err; printf("Performed Terminal Authentication.\n"); - r = perform_chip_authentication(card); + r = perform_chip_authentication(card, &ef_cardsecurity, &ef_cardsecurity_len); if (r < 0) goto err; printf("Performed Chip Authentication.\n"); @@ -803,6 +767,10 @@ err: free(pace_output.previous_car); free(pace_output.id_icc); free(pace_output.id_pcd); + if (ef_cardsecurity) { + OPENSSL_cleanse(ef_cardsecurity, ef_cardsecurity_len); + free(ef_cardsecurity); + } if (input) fclose(input); if (certs) { diff --git a/npa/src/npa.c b/npa/src/npa.c index 08d6b0f..1c0ac6c 100644 --- a/npa/src/npa.c +++ b/npa/src/npa.c @@ -1842,15 +1842,15 @@ static int get_ef_card_security(sc_card_t *card, return read_binary_rec(card, SFID_EF_CARDSECURITY, ef_security, length_ef_security); } -int perform_chip_authentication(sc_card_t *card) +int perform_chip_authentication(sc_card_t *card, + unsigned char **ef_cardsecurity, size_t *ef_cardsecurity_len) { int r; BUF_MEM *picc_pubkey = NULL, *nonce = NULL, *token = NULL, *eph_pub_key = NULL; - unsigned char *ef_cardsecurity = NULL; - size_t ef_cardsecurity_len; - if (!card || !card->sm_ctx.info.cmd_data) { + if (!card || !card->sm_ctx.info.cmd_data + || !ef_cardsecurity || !ef_cardsecurity_len) { r = SC_ERROR_INVALID_ARGUMENTS; goto err; } @@ -1863,12 +1863,14 @@ int perform_chip_authentication(sc_card_t *card) /* Passive Authentication */ - r = get_ef_card_security(card, &ef_cardsecurity, &ef_cardsecurity_len); - if (r < 0) { - sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not get EF.CardSecurity."); - goto err; + if (!*ef_cardsecurity && !*ef_cardsecurity_len) { + r = get_ef_card_security(card, ef_cardsecurity, ef_cardsecurity_len); + if (r < 0 || !ef_cardsecurity || !ef_cardsecurity_len) { + sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not get EF.CardSecurity."); + goto err; + } } - picc_pubkey = CA_get_pubkey(eacsmctx->ctx, ef_cardsecurity, ef_cardsecurity_len); + picc_pubkey = CA_get_pubkey(eacsmctx->ctx, *ef_cardsecurity, *ef_cardsecurity_len); if (!picc_pubkey) { sc_debug(card->ctx, SC_LOG_DEBUG_VERBOSE, "Could not verify EF.CardSecurity."); ssl_error(card->ctx); @@ -1924,10 +1926,6 @@ int perform_chip_authentication(sc_card_t *card) } err: - if (ef_cardsecurity) { - OPENSSL_cleanse(ef_cardsecurity, ef_cardsecurity_len); - free(ef_cardsecurity); - } BUF_MEM_clear_free(picc_pubkey); BUF_MEM_clear_free(nonce); BUF_MEM_clear_free(token); diff --git a/npa/src/npa/npa.h b/npa/src/npa/npa.h index cdac603..aa8510e 100644 --- a/npa/src/npa/npa.h +++ b/npa/src/npa/npa.h @@ -180,10 +180,13 @@ int perform_terminal_authentication(sc_card_t *card, * Switches the SM context of \c card to the new established keys. * * @param[in] card + * @param[in,out] ef_cardsecurity + * @param[in,out] ef_cardsecurity_len * * @return \c SC_SUCCESS or error code if an error occurred */ -int perform_chip_authentication(sc_card_t *card); +int perform_chip_authentication(sc_card_t *card, + unsigned char **ef_cardsecurity, size_t *ef_cardsecurity_len); /** * @brief Sends a reset retry counter APDU diff --git a/npa/src/npa/scutil.h b/npa/src/npa/scutil.h index 31e0098..1b723ea 100644 --- a/npa/src/npa/scutil.h +++ b/npa/src/npa/scutil.h @@ -147,5 +147,7 @@ int sc_apdu_set_resp(sc_context_t *ctx, sc_apdu_t *apdu, const u8 *buf, * be null terminated. */ int _sc_match_atr(struct sc_card *card, struct sc_atr_table *table, int *type_out); +int fread_to_eof(const char *file, unsigned char **buf, size_t *buflen); + #endif /* @} */ diff --git a/npa/src/pkcs15-npa.c b/npa/src/pkcs15-npa.c deleted file mode 100644 index b3a8623..0000000 --- a/npa/src/pkcs15-npa.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * pkcs15-npa.c: PKCS#15 emulation for German ID card - * - * Copyright (C) 2014 Frank Morgner - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "libopensc/log.h" -#include "libopensc/opensc.h" -#include "libopensc/pace.h" -#include "libopensc/pkcs15.h" -#include "card-npa.h" -#include -#include -#include - - -static int npa_detect_card(sc_pkcs15_card_t *p15card) -{ - int r = SC_ERROR_WRONG_CARD; - - if (p15card && p15card->card - && (p15card->card->type == SC_CARD_TYPE_NPA - || p15card->card->type == SC_CARD_TYPE_NPA_TEST - || p15card->card->type == SC_CARD_TYPE_NPA_ONLINE)) { - r = SC_SUCCESS; - } - - return r; -} - -static int npa_add_pin(sc_pkcs15_card_t *p15card, - const char *label, int max_tries, - unsigned int flags, size_t min_length, - size_t max_length, unsigned char reference, - unsigned char auth_id, const struct sc_path *path, - const char *pin, size_t pin_length) -{ - struct sc_pkcs15_auth_info pin_info; - struct sc_pkcs15_object pin_obj; - memset(&pin_info, 0, sizeof pin_info); - memset(&pin_obj, 0, sizeof(pin_obj)); - - strncpy(pin_obj.label, label, sizeof pin_obj.label); - pin_obj.label[(sizeof pin_obj.label) - 1]= '\0'; - if (auth_id) { - pin_obj.auth_id.len = sizeof auth_id; - memcpy(&pin_obj.auth_id.value, &auth_id, sizeof auth_id); - } - - pin_info.auth_id.len = sizeof reference; - memcpy(&pin_info.auth_id.value, &reference, sizeof reference); - pin_info.auth_type = SC_PKCS15_PIN_AUTH_TYPE_PIN; - pin_info.auth_method = SC_AC_CHV; - pin_info.tries_left = -1; - pin_info.max_tries = max_tries; - if (path) - memcpy(&pin_info.path, path, sizeof *path); - - pin_info.attrs.pin.flags = flags; - pin_info.attrs.pin.type = SC_PKCS15_PIN_TYPE_ASCII_NUMERIC; - pin_info.attrs.pin.min_length = min_length; - pin_info.attrs.pin.max_length = max_length; - pin_info.attrs.pin.reference = reference; - - if (pin && pin_length) { - sc_pkcs15_pincache_add(p15card, &pin_obj, pin, pin_length); - } - - return sc_pkcs15emu_add_pin_obj(p15card, &pin_obj, &pin_info); -} - -static int npa_add_pins(sc_pkcs15_card_t *p15card, const char *can) -{ - int r; - const sc_path_t *mf = sc_get_mf_path(); - sc_path_t df_esign; - - r = sc_path_set(&df_esign, SC_PATH_TYPE_PATH, - df_esign_path, sizeof df_esign_path, 0, 0); - if (r != SC_SUCCESS) - goto err; - - r = npa_add_pin(p15card, npa_secret_name(PACE_PIN_ID_MRZ), -1, - SC_PKCS15_PIN_FLAG_CASE_SENSITIVE - | SC_PKCS15_PIN_FLAG_INITIALIZED - | SC_PKCS15_PIN_FLAG_SO_PIN - | SC_PKCS15_PIN_FLAG_UNBLOCK_DISABLED - | SC_PKCS15_PIN_FLAG_CHANGE_DISABLED, - 90, 90, PACE_PIN_ID_MRZ, 0, mf, NULL, 0); - if (r != SC_SUCCESS) - goto err; - - r = npa_add_pin(p15card, npa_secret_name(PACE_PIN_ID_CAN), -1, - SC_PKCS15_PIN_FLAG_CASE_SENSITIVE - | SC_PKCS15_PIN_FLAG_INITIALIZED - | SC_PKCS15_PIN_FLAG_SO_PIN - | SC_PKCS15_PIN_FLAG_UNBLOCK_DISABLED, - 6, 6, PACE_PIN_ID_CAN, 0, mf, can, can ? strlen(can) : 0); - if (r != SC_SUCCESS) - goto err; - /* TODO */ - /*r = sc_pkcs15_pincache_add(p15card, pin_obj, pinbuf, *pinsize);*/ - - r = npa_add_pin(p15card, npa_secret_name(PACE_PIN_ID_PIN), 3, - SC_PKCS15_PIN_FLAG_CASE_SENSITIVE - | SC_PKCS15_PIN_FLAG_INITIALIZED, - 5, 6, PACE_PIN_ID_PIN, PACE_PIN_ID_PUK, mf, NULL, 0); - if (r != SC_SUCCESS) - goto err; - - r = npa_add_pin(p15card, npa_secret_name(PACE_PIN_ID_PUK), -1, - SC_PKCS15_PIN_FLAG_CASE_SENSITIVE - | SC_PKCS15_PIN_FLAG_INITIALIZED - | SC_PKCS15_PIN_FLAG_UNBLOCKING_PIN - | SC_PKCS15_PIN_FLAG_UNBLOCK_DISABLED - | SC_PKCS15_PIN_FLAG_UNBLOCKING_PIN - | SC_PKCS15_PIN_FLAG_CHANGE_DISABLED, - 10, 10, PACE_PIN_ID_PUK, 0, mf, NULL, 0); - if (r != SC_SUCCESS) - goto err; - - r = npa_add_pin(p15card, "eSign PIN", 3, - SC_PKCS15_PIN_FLAG_CASE_SENSITIVE - | SC_PKCS15_PIN_FLAG_LOCAL - | SC_PKCS15_PIN_FLAG_INTEGRITY_PROTECTED - | SC_PKCS15_PIN_FLAG_INITIALIZED, - 6, 6, NPA_PIN_ID_ESIGN_PIN, PACE_PIN_ID_PUK, &df_esign, NULL, 0); - if (r != SC_SUCCESS) - goto err; - -err: - return r; -} - -static const char npa_manufacturer[] = "Bundesdruckerei GmbH"; - -static int npa_add_cardlabels(sc_pkcs15_card_t *p15card) -{ - if (!p15card || !p15card->tokeninfo) - return SC_ERROR_INTERNAL; - - /* manufacturer ID */ - free(p15card->tokeninfo->manufacturer_id); - p15card->tokeninfo->manufacturer_id = strdup(npa_manufacturer); - if (!p15card->tokeninfo->manufacturer_id) - return SC_ERROR_NOT_ENOUGH_MEMORY; - - /* card label */ - free(p15card->tokeninfo->label); - if (p15card->card && p15card->card->name) { - p15card->tokeninfo->label = strdup(p15card->card->name); - if (!p15card->tokeninfo->label) - return SC_ERROR_NOT_ENOUGH_MEMORY; - } else { - p15card->tokeninfo->label = NULL; - } - - return SC_SUCCESS; -} - -static int npa_get_cert(sc_pkcs15_card_t *p15card, const char *can) -{ - struct establish_pace_channel_input pace_input; - struct establish_pace_channel_output pace_output; - int r; - - if (!p15card || !p15card->card || !p15card->card->reader) { - r = SC_ERROR_INTERNAL; - goto err; - } - - if (!(p15card->card->reader->capabilities & SC_READER_CAP_PACE_ESIGN)) { - sc_log(p15card->card->ctx, "No comfort reader (CAT-K) found\n"); - r = SC_ERROR_NOT_SUPPORTED; - goto err; - } - - memset(&pace_input, 0, sizeof pace_input); - memset(&pace_output, 0, sizeof pace_output); - pace_input.chat = esign_chat; - pace_input.chat_length = sizeof esign_chat; - pace_input.pin_id = PACE_PIN_ID_CAN; - if (can) { - pace_input.pin = (const unsigned char *) can; - pace_input.pin_length = strlen(can); - } - - r = perform_pace(p15card->card, pace_input, &pace_output, EAC_TR_VERSION_2_02); - if (r != SC_SUCCESS) - goto err; - - /* TODO read certificate */ - r = SC_ERROR_OBJECT_NOT_FOUND; - -err: - return r; -} - -int sc_pkcs15emu_npa_init_ex(sc_pkcs15_card_t *p15card, - sc_pkcs15emu_opt_t *opts) -{ - int r; - const char *can = NULL; - - if (!p15card || !p15card->card) { - r = SC_ERROR_INTERNAL; - goto err; - } - - if (opts && (opts->flags & SC_PKCS15EMU_FLAGS_NO_CHECK)) { - /* don't do a card check */ - } else { - r = npa_detect_card(p15card); - if (r != SC_SUCCESS) - goto err; - } - - r = npa_add_cardlabels(p15card); - if (r != SC_SUCCESS) - goto err; - - if (opts) { - can = scconf_get_str(opts->blk, "can", NULL); - } - - r = npa_add_pins(p15card, can); - if (r != SC_SUCCESS) - goto err; - - r = npa_get_cert(p15card, can); - if (r != SC_SUCCESS) { - sc_log(p15card->card->ctx, "No certificate found, will continue anyway\n"); - r = SC_SUCCESS; - } - -err: - return r; -} - -const char *sc_driver_version(void) -{ - /** TODO fix the version check in opensc/src/libopensc/pkcs15-syn.c:271 - * here we choose 0.9.3 simply to pass the bogus check, see - * https://github.com/OpenSC/OpenSC/pull/258 */ - static const char version[] = "0.9.3"; - return version; -} diff --git a/npa/src/scutil.c b/npa/src/scutil.c index e48368e..9a38949 100644 --- a/npa/src/scutil.c +++ b/npa/src/scutil.c @@ -297,3 +297,41 @@ int write_binary_rec(sc_card_t *card, unsigned char sfid, err: return r; } + +int fread_to_eof(const char *file, unsigned char **buf, size_t *buflen) +{ + FILE *input = NULL; + int r = 0; + unsigned char *p; + + if (!buflen || !buf) + goto err; + +#define MAX_READ_LEN 0xfff + p = realloc(*buf, MAX_READ_LEN); + if (!p) + goto err; + *buf = p; + + input = fopen(file, "rb"); + if (!input) { + fprintf(stderr, "Could not open %s.\n", file); + goto err; + } + + *buflen = 0; + while (feof(input) == 0 && *buflen < MAX_READ_LEN) { + *buflen += fread(*buf+*buflen, 1, MAX_READ_LEN-*buflen, input); + if (ferror(input)) { + fprintf(stderr, "Could not read %s.\n", file); + goto err; + } + } + + r = 1; +err: + if (input) + fclose(input); + + return r; +} diff --git a/virtualsmartcard/npa-example-data/ZZSTCVCA00001.cvcert b/virtualsmartcard/npa-example-data/ZZSTCVCA00001.cvcert new file mode 100644 index 0000000000000000000000000000000000000000..e36ebee8bb0193fa32e165a6a2a44bb114a0ed34 GIT binary patch literal 356 zcmb=M3J!4&b9OWU0>gUG#=C4>EDZGw>`Xww+$ixM75sy7 zn{dheX_iP)WR-k6r{OvO@|UZNR17@-?YQ^mpxe%kEru)-y({=O7) zFaNpzHvJb6VwcpO`v1hOiev%7x7jLZuNMC-Unp1UA=rvy&CGj0%tW$JZrpN7!?4Ye zWzDU^mrIrYNBjuV-YEE=i+kSFYL0UKrWxLIISmi1yWCqfDb`uL)pLW>X&-Yok3*~5 z85!dh&^+wJ$HocvFEb+(b1>uocvUt=Rt82!=6Dr0Mm8|R+~Bk`SJ1h67c2P6Dm8>M oa?ac^bIq~&a!PQ`FJH!ge^%snPqQvRD5tmMwfg<~H`CeG0FcnC*#H0l literal 0 HcmV?d00001 diff --git a/virtualsmartcard/npa-example-data/ZZSTCVCA00001.pem b/virtualsmartcard/npa-example-data/ZZSTCVCA00001.pem new file mode 100644 index 0000000..f739b16 --- /dev/null +++ b/virtualsmartcard/npa-example-data/ZZSTCVCA00001.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BggqhkjOPQMBAQ== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MF8CAQEEGLxabXaKHuZ+jf1t1sK4rVH1ovP+Bm7Es6AKBggqhkjOPQMBAaE0AzIA +BKzacemlIv9Y+FIrsRH/Cgue5XsIdy+CmEudCTHDJ0TeqpJdQyuFSbBCy0w3BkjC +qw== +-----END EC PRIVATE KEY----- diff --git a/virtualsmartcard/npa-example-data/ZZSTCVCA00001.pkcs8 b/virtualsmartcard/npa-example-data/ZZSTCVCA00001.pkcs8 new file mode 100644 index 0000000000000000000000000000000000000000..08db769fa2eb8dce32e198603542830299be076b GIT binary patch literal 113 zcmXrWXJTY95N2c7YV$Z}%f!gW0cJ2WGO~mk1Tz6Ci9J!dWnFU5>U#g?UOTj7ZQ$2M zpZ~Gt9of9lgxQFJWzDU^mrIrYNBjuV-YEE=i+kSFYL0UKrWxLIISmi1yWCqfDb`uL P)pLW>X&-Yok3*{gkoPO& literal 0 HcmV?d00001 diff --git a/virtualsmartcard/npa-example-data/ZZSTDVCA00001.cvcert b/virtualsmartcard/npa-example-data/ZZSTDVCA00001.cvcert new file mode 100644 index 0000000000000000000000000000000000000000..136044f9d054953a278bb42c9fcbbaeef77aa0fe GIT binary patch literal 195 zcmb#B>$&=U7zN~JL(Nv>o(NHD}YUOft%>V z$HoaZk(rT+Ihe6NUX_iJm4T6wIbH?CU;;AC4MJc4Nb8y2J?XpMs>Cf8S{b@W*Tg() gIW|{q_klb+bFJ%Vy+hLXd-_DLI?id-*8avG00S39a{vGU literal 0 HcmV?d00001 diff --git a/virtualsmartcard/npa-example-data/ZZSTDVCA00001.pkcs8 b/virtualsmartcard/npa-example-data/ZZSTDVCA00001.pkcs8 new file mode 100644 index 0000000000000000000000000000000000000000..c0378ad9ff692adbc116089648e26b038a058108 GIT binary patch literal 97 zcmV-n0G|IaUjhLE1Q>;`qg`!?*+bpiV5|@l5=_nka;>Gj z12O;vzPZnkURb2g*F!5S)u`wH5f6&y%11Z=kB=12w^{t~%~=ofk1b4tqqu)Dg?_Mn DN6snM literal 0 HcmV?d00001 diff --git a/virtualsmartcard/npa-example-data/ZZSTTERM00001.cvcert b/virtualsmartcard/npa-example-data/ZZSTTERM00001.cvcert new file mode 100644 index 0000000000000000000000000000000000000000..7b48c4126364780d0e84821ab941d92a054ad83a GIT binary patch literal 195 zcmbfBx-BQK|WVZl1>>CfLqlos0c2F`vEuK+eN#5Kql zVxkWp8zj z12O;vh0)eO#NsP3+X!3JS?3(8?;w*w