2003-10-31 12:27:14 +00:00
|
|
|
/*
|
|
|
|
* PKCS15 emulation layer for OpenPGP card.
|
2003-10-31 16:01:35 +00:00
|
|
|
* To see how this works, run p15dump on your OpenPGP card.
|
2003-10-31 12:27:14 +00:00
|
|
|
*
|
|
|
|
* Copyright (C) 2003, Olaf Kirch <okir@suse.de>
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
|
2010-03-04 08:14:36 +00:00
|
|
|
#include "config.h"
|
|
|
|
|
2003-10-31 12:27:14 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <assert.h>
|
2010-03-04 08:14:36 +00:00
|
|
|
|
|
|
|
#include "common/compat_strlcpy.h"
|
|
|
|
#include "internal.h"
|
|
|
|
#include "pkcs15.h"
|
|
|
|
#include "log.h"
|
2003-10-31 12:27:14 +00:00
|
|
|
|
2012-05-16 21:14:42 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
typedef USHORT ushort;
|
|
|
|
#endif
|
|
|
|
|
2004-12-15 17:34:15 +00:00
|
|
|
int sc_pkcs15emu_openpgp_init_ex(sc_pkcs15_card_t *, sc_pkcs15emu_opt_t *);
|
|
|
|
|
2011-08-10 13:45:40 +00:00
|
|
|
|
2012-05-06 15:59:34 +00:00
|
|
|
#define PGP_USER_PIN_FLAGS (SC_PKCS15_PIN_FLAG_CASE_SENSITIVE \
|
|
|
|
| SC_PKCS15_PIN_FLAG_INITIALIZED \
|
|
|
|
| SC_PKCS15_PIN_FLAG_LOCAL)
|
|
|
|
#define PGP_ADMIN_PIN_FLAGS (PGP_USER_PIN_FLAGS \
|
|
|
|
| SC_PKCS15_PIN_FLAG_UNBLOCK_DISABLED \
|
|
|
|
| SC_PKCS15_PIN_FLAG_SO_PIN)
|
|
|
|
|
|
|
|
typedef struct _pgp_pin_cfg {
|
|
|
|
const char *label;
|
|
|
|
int reference;
|
|
|
|
unsigned int flags;
|
|
|
|
int min_length;
|
|
|
|
int do_index;
|
|
|
|
} pgp_pin_cfg_t;
|
|
|
|
|
|
|
|
/* OpenPGP cards v1:
|
|
|
|
* "Signature PIN2 & "Encryption PIN" are two different PINs - not sync'ed by hardware
|
|
|
|
*/
|
|
|
|
static const pgp_pin_cfg_t pin_cfg_v1[3] = {
|
|
|
|
{ "Signature PIN", 0x81, PGP_USER_PIN_FLAGS, 6, 0 }, // used for PSO:CDS
|
|
|
|
{ "Encryption PIN", 0x82, PGP_USER_PIN_FLAGS, 6, 1 }, // used for PSO:DEC, INT-AUT, {GET,PUT} DATA
|
|
|
|
{ "Admin PIN", 0x83, PGP_ADMIN_PIN_FLAGS, 8, 2 }
|
|
|
|
};
|
|
|
|
/* OpenPGP cards v2:
|
|
|
|
* "User PIN (sig)" & "User PIN" are the same PIN, but c$use different references depending on action
|
|
|
|
*/
|
|
|
|
static const pgp_pin_cfg_t pin_cfg_v2[3] = {
|
|
|
|
{ "User PIN (sig)", 0x81, PGP_USER_PIN_FLAGS, 6, 0 }, // used for PSO:CDS
|
|
|
|
{ "User PIN", 0x82, PGP_USER_PIN_FLAGS, 6, 0 }, // used for PSO:DEC, INT-AUT, {GET,PUT} DATA
|
|
|
|
{ "Admin PIN", 0x83, PGP_ADMIN_PIN_FLAGS, 8, 2 }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2012-05-06 16:35:47 +00:00
|
|
|
#define PGP_SIG_PRKEY_USAGE (SC_PKCS15_PRKEY_USAGE_SIGN \
|
|
|
|
| SC_PKCS15_PRKEY_USAGE_SIGNRECOVER \
|
|
|
|
| SC_PKCS15_PRKEY_USAGE_NONREPUDIATION)
|
|
|
|
#define PGP_ENC_PRKEY_USAGE (SC_PKCS15_PRKEY_USAGE_DECRYPT \
|
|
|
|
| SC_PKCS15_PRKEY_USAGE_UNWRAP)
|
|
|
|
#define PGP_AUTH_PRKEY_USAGE (SC_PKCS15_PRKEY_USAGE_NONREPUDIATION)
|
|
|
|
|
|
|
|
#define PGP_SIG_PUBKEY_USAGE (SC_PKCS15_PRKEY_USAGE_VERIFY \
|
|
|
|
| SC_PKCS15_PRKEY_USAGE_VERIFYRECOVER)
|
|
|
|
#define PGP_ENC_PUBKEY_USAGE (SC_PKCS15_PRKEY_USAGE_ENCRYPT \
|
|
|
|
| SC_PKCS15_PRKEY_USAGE_WRAP)
|
|
|
|
#define PGP_AUTH_PUBKEY_USAGE (SC_PKCS15_PRKEY_USAGE_VERIFY)
|
|
|
|
|
|
|
|
typedef struct _pgp_key_cfg {
|
|
|
|
const char *label;
|
|
|
|
const char *pubkey_path;
|
|
|
|
int prkey_pin;
|
|
|
|
int prkey_usage;
|
|
|
|
int pubkey_usage;
|
|
|
|
} pgp_key_cfg_t;
|
|
|
|
|
|
|
|
static const pgp_key_cfg_t key_cfg[3] = {
|
|
|
|
{ "Signature key", "B601", 1, PGP_SIG_PRKEY_USAGE, PGP_SIG_PUBKEY_USAGE },
|
|
|
|
{ "Encryption key", "B801", 2, PGP_ENC_PRKEY_USAGE, PGP_ENC_PUBKEY_USAGE },
|
|
|
|
{ "Authentication key", "A401", 2, PGP_AUTH_PRKEY_USAGE, PGP_AUTH_PUBKEY_USAGE }
|
|
|
|
};
|
|
|
|
|
2003-10-31 16:01:35 +00:00
|
|
|
|
2012-05-06 19:29:06 +00:00
|
|
|
typedef struct _pgp_manuf_map {
|
|
|
|
ushort id;
|
|
|
|
const char *name;
|
|
|
|
} pgp_manuf_map_t;
|
|
|
|
|
|
|
|
static const pgp_manuf_map_t manuf_map[] = {
|
|
|
|
{ 0x0001, "PPC Card Systems" },
|
|
|
|
{ 0x0002, "Prism" },
|
|
|
|
{ 0x0003, "OpenFortress" },
|
|
|
|
{ 0x0004, "Wewid AB" },
|
|
|
|
{ 0x0005, "ZeitControl" },
|
|
|
|
{ 0x002A, "Magrathea" },
|
|
|
|
{ 0xF517, "FSIJ" },
|
|
|
|
{ 0x0000, "test card" },
|
|
|
|
{ 0xffff, "test card" },
|
|
|
|
{ 0, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2003-10-31 12:27:14 +00:00
|
|
|
static void
|
|
|
|
set_string(char **strp, const char *value)
|
|
|
|
{
|
|
|
|
if (*strp)
|
2005-03-18 20:36:52 +00:00
|
|
|
free(*strp);
|
2003-10-31 12:27:14 +00:00
|
|
|
*strp = value? strdup(value) : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This function pretty much follows what find_tlv in the GNUpg
|
|
|
|
* code does.
|
|
|
|
*/
|
|
|
|
static int
|
2003-10-31 16:01:35 +00:00
|
|
|
read_file(sc_card_t *card, const char *path_name, void *buf, size_t len)
|
2003-10-31 12:27:14 +00:00
|
|
|
{
|
2003-10-31 16:01:35 +00:00
|
|
|
sc_path_t path;
|
|
|
|
sc_file_t *file;
|
2003-10-31 12:27:14 +00:00
|
|
|
int r;
|
|
|
|
|
2003-10-31 16:01:35 +00:00
|
|
|
sc_format_path(path_name, &path);
|
|
|
|
if ((r = sc_select_file(card, &path, &file)) < 0)
|
|
|
|
return r;
|
2003-10-31 12:27:14 +00:00
|
|
|
|
2003-10-31 16:01:35 +00:00
|
|
|
if (file->size < len)
|
|
|
|
len = file->size;
|
2003-12-18 21:37:34 +00:00
|
|
|
return sc_read_binary(card, 0, (u8 *) buf, len, 0);
|
2003-10-31 12:27:14 +00:00
|
|
|
}
|
|
|
|
|
2004-12-15 17:34:15 +00:00
|
|
|
static int
|
2003-10-31 12:27:14 +00:00
|
|
|
sc_pkcs15emu_openpgp_init(sc_pkcs15_card_t *p15card)
|
|
|
|
{
|
|
|
|
sc_card_t *card = p15card->card;
|
|
|
|
sc_context_t *ctx = card->ctx;
|
|
|
|
char string[256];
|
2003-10-31 16:01:35 +00:00
|
|
|
u8 buffer[256];
|
2003-10-31 12:27:14 +00:00
|
|
|
int r, i;
|
2012-05-06 15:59:34 +00:00
|
|
|
const pgp_pin_cfg_t *pin_cfg = (card->type == SC_CARD_TYPE_OPENPGP_V2) ? pin_cfg_v2 : pin_cfg_v1;
|
2003-10-31 12:27:14 +00:00
|
|
|
|
2011-08-10 13:45:40 +00:00
|
|
|
set_string(&p15card->tokeninfo->label, "OpenPGP card");
|
2010-10-05 15:44:58 +00:00
|
|
|
set_string(&p15card->tokeninfo->manufacturer_id, "OpenPGP project");
|
2003-10-31 12:27:14 +00:00
|
|
|
|
2012-05-06 19:29:06 +00:00
|
|
|
/* card->serialnr = 2 byte manufacturer_id + 4 byte serial_number */
|
|
|
|
if (card->serialnr.len > 0) {
|
|
|
|
ushort manuf_id = bebytes2ushort(card->serialnr.value);
|
|
|
|
int j;
|
|
|
|
|
|
|
|
sc_bin_to_hex(card->serialnr.value, card->serialnr.len, string, sizeof(string)-1, 0);
|
|
|
|
set_string(&p15card->tokeninfo->serial_number, string);
|
|
|
|
|
|
|
|
for (j = 0; manuf_map[j].name != NULL; j++) {
|
|
|
|
if (manuf_id == manuf_map[j].id) {
|
|
|
|
set_string(&p15card->tokeninfo->manufacturer_id, manuf_map[j].name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2003-10-31 12:27:14 +00:00
|
|
|
|
2012-05-06 19:08:09 +00:00
|
|
|
p15card->tokeninfo->version = (card->type == SC_CARD_TYPE_OPENPGP_V2) ? 2 : 1;
|
2010-10-05 15:44:58 +00:00
|
|
|
p15card->tokeninfo->flags = SC_PKCS15_TOKEN_PRN_GENERATION | SC_PKCS15_TOKEN_EID_COMPLIANT;
|
2003-10-31 12:27:14 +00:00
|
|
|
|
|
|
|
/* Extract preferred language */
|
2012-05-06 19:07:01 +00:00
|
|
|
r = read_file(card, "0065:5f2d", string, sizeof(string)-1);
|
2003-10-31 16:01:35 +00:00
|
|
|
if (r < 0)
|
2003-10-31 12:27:14 +00:00
|
|
|
goto failed;
|
2003-10-31 16:01:35 +00:00
|
|
|
string[r] = '\0';
|
2010-10-05 15:44:58 +00:00
|
|
|
set_string(&p15card->tokeninfo->preferred_language, string);
|
2003-10-31 12:27:14 +00:00
|
|
|
|
|
|
|
/* Get Application Related Data (006E) */
|
|
|
|
if ((r = sc_get_data(card, 0x006E, buffer, sizeof(buffer))) < 0)
|
|
|
|
goto failed;
|
|
|
|
|
2012-05-06 15:59:34 +00:00
|
|
|
/* Get CHV status bytes from DO 006E/0073/00C4:
|
2011-07-14 14:21:34 +00:00
|
|
|
* 00: 1 == user consent for signature PIN
|
2012-05-06 15:59:34 +00:00
|
|
|
* (i.e. PIN still valid for next PSO:CDS command)
|
2003-10-31 12:27:14 +00:00
|
|
|
* 01-03: max length of pins 1-3
|
|
|
|
* 04-07: tries left for pins 1-3
|
|
|
|
*/
|
2012-05-06 19:07:01 +00:00
|
|
|
if ((r = read_file(card, "006E:0073:00C4", buffer, sizeof(buffer))) < 0)
|
2003-10-31 12:27:14 +00:00
|
|
|
goto failed;
|
|
|
|
if (r != 7) {
|
2010-03-15 12:17:13 +00:00
|
|
|
sc_debug(ctx, SC_LOG_DEBUG_NORMAL,
|
2011-07-14 14:21:34 +00:00
|
|
|
"CHV status bytes have unexpected length (expected 7, got %d)\n", r);
|
2003-10-31 12:27:14 +00:00
|
|
|
return SC_ERROR_OBJECT_NOT_VALID;
|
|
|
|
}
|
|
|
|
|
2011-08-10 13:45:40 +00:00
|
|
|
/* Add PIN codes */
|
2003-10-31 12:27:14 +00:00
|
|
|
for (i = 0; i < 3; i++) {
|
2012-05-06 16:13:28 +00:00
|
|
|
sc_pkcs15_auth_info_t pin_info;
|
|
|
|
sc_pkcs15_object_t pin_obj;
|
2005-02-06 21:34:59 +00:00
|
|
|
|
|
|
|
memset(&pin_info, 0, sizeof(pin_info));
|
|
|
|
memset(&pin_obj, 0, sizeof(pin_obj));
|
2003-10-31 12:27:14 +00:00
|
|
|
|
2011-06-05 15:46:25 +00:00
|
|
|
pin_info.auth_type = SC_PKCS15_PIN_AUTH_TYPE_PIN;
|
2012-05-06 15:59:34 +00:00
|
|
|
pin_info.auth_id.len = 1;
|
2005-02-06 21:34:59 +00:00
|
|
|
pin_info.auth_id.value[0] = i + 1;
|
2012-05-06 15:59:34 +00:00
|
|
|
pin_info.attrs.pin.reference = pin_cfg[i].reference;
|
|
|
|
pin_info.attrs.pin.flags = pin_cfg[i].flags;
|
2011-06-05 15:46:25 +00:00
|
|
|
pin_info.attrs.pin.type = SC_PKCS15_PIN_TYPE_ASCII_NUMERIC;
|
2012-05-06 15:59:34 +00:00
|
|
|
pin_info.attrs.pin.min_length = pin_cfg[i].min_length;
|
|
|
|
pin_info.attrs.pin.stored_length = buffer[1 + pin_cfg[i].do_index];
|
|
|
|
pin_info.attrs.pin.max_length = buffer[1 + pin_cfg[i].do_index];
|
2011-06-05 15:46:25 +00:00
|
|
|
pin_info.attrs.pin.pad_char = '\0';
|
2012-05-06 15:59:34 +00:00
|
|
|
pin_info.tries_left = buffer[4 + pin_cfg[i].do_index];
|
|
|
|
|
2005-02-06 21:34:59 +00:00
|
|
|
sc_format_path("3F00", &pin_info.path);
|
2012-05-06 15:59:34 +00:00
|
|
|
|
|
|
|
strlcpy(pin_obj.label, pin_cfg[i].label, sizeof(pin_obj.label));
|
2005-02-06 21:34:59 +00:00
|
|
|
pin_obj.flags = SC_PKCS15_CO_FLAG_MODIFIABLE | SC_PKCS15_CO_FLAG_PRIVATE;
|
|
|
|
|
|
|
|
r = sc_pkcs15emu_add_pin_obj(p15card, &pin_obj, &pin_info);
|
|
|
|
if (r < 0)
|
|
|
|
return SC_ERROR_INTERNAL;
|
2003-10-31 16:01:35 +00:00
|
|
|
}
|
|
|
|
|
2012-05-06 16:13:28 +00:00
|
|
|
/* XXX: check if "halfkeys" can be stored with gpg2. If not, add keypairs in one loop */
|
2003-10-31 16:01:35 +00:00
|
|
|
for (i = 0; i < 3; i++) {
|
2012-05-06 16:13:28 +00:00
|
|
|
sc_pkcs15_prkey_info_t prkey_info;
|
|
|
|
sc_pkcs15_object_t prkey_obj;
|
2012-05-06 19:07:01 +00:00
|
|
|
char path_template[] = "006E:0073:00C0";
|
2012-05-06 16:35:47 +00:00
|
|
|
|
2005-02-06 21:34:59 +00:00
|
|
|
memset(&prkey_info, 0, sizeof(prkey_info));
|
|
|
|
memset(&prkey_obj, 0, sizeof(prkey_obj));
|
|
|
|
|
2012-05-06 19:07:01 +00:00
|
|
|
path_template[13] = '1' + i; /* The needed tags are C1 C2 and C3 */
|
2011-07-14 14:21:34 +00:00
|
|
|
if ((r = read_file(card, path_template, buffer, sizeof(buffer))) < 0)
|
|
|
|
goto failed;
|
|
|
|
if (r != 6) {
|
|
|
|
sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "Key info bytes have unexpected length(expected 6, got %d)\n", r);
|
|
|
|
return SC_ERROR_INTERNAL;
|
|
|
|
}
|
|
|
|
|
2012-05-06 17:52:58 +00:00
|
|
|
/* only add valid keys, i.e. those with a legal algorithm identifier */
|
|
|
|
if (buffer[0] != 0) {
|
|
|
|
prkey_info.id.len = 1;
|
|
|
|
prkey_info.id.value[0] = i + 1;
|
|
|
|
prkey_info.usage = key_cfg[i].prkey_usage;
|
|
|
|
prkey_info.native = 1;
|
|
|
|
prkey_info.key_reference = i;
|
|
|
|
prkey_info.modulus_length = bebytes2ushort(buffer + 1);
|
|
|
|
|
|
|
|
strlcpy(prkey_obj.label, key_cfg[i].label, sizeof(prkey_obj.label));
|
|
|
|
prkey_obj.flags = SC_PKCS15_CO_FLAG_PRIVATE | SC_PKCS15_CO_FLAG_MODIFIABLE;
|
|
|
|
prkey_obj.auth_id.len = 1;
|
|
|
|
prkey_obj.auth_id.value[0] = key_cfg[i].prkey_pin;
|
|
|
|
|
|
|
|
r = sc_pkcs15emu_add_rsa_prkey(p15card, &prkey_obj, &prkey_info);
|
|
|
|
if (r < 0)
|
|
|
|
return SC_ERROR_INTERNAL;
|
|
|
|
}
|
2003-10-31 16:01:35 +00:00
|
|
|
}
|
2011-08-10 13:45:40 +00:00
|
|
|
/* Add public keys */
|
2003-10-31 16:01:35 +00:00
|
|
|
for (i = 0; i < 3; i++) {
|
2012-05-06 16:13:28 +00:00
|
|
|
sc_pkcs15_pubkey_info_t pubkey_info;
|
|
|
|
sc_pkcs15_object_t pubkey_obj;
|
2012-05-06 19:07:01 +00:00
|
|
|
char path_template[] = "006E:0073:00C0";
|
2005-02-06 21:34:59 +00:00
|
|
|
|
|
|
|
memset(&pubkey_info, 0, sizeof(pubkey_info));
|
|
|
|
memset(&pubkey_obj, 0, sizeof(pubkey_obj));
|
|
|
|
|
2012-05-06 19:07:01 +00:00
|
|
|
path_template[13] = '1' + i; /* The needed tags are C1 C2 and C3 */
|
2011-07-14 14:21:34 +00:00
|
|
|
if ((r = read_file(card, path_template, buffer, sizeof(buffer))) < 0)
|
|
|
|
goto failed;
|
|
|
|
if (r != 6) {
|
|
|
|
sc_debug(ctx, SC_LOG_DEBUG_NORMAL, "Key info bytes have unexpected length(expected 6, got %d)\n", r);
|
|
|
|
return SC_ERROR_INTERNAL;
|
|
|
|
}
|
|
|
|
|
2012-05-06 17:52:58 +00:00
|
|
|
/* only add valid keys, i.e. those with a legal algorithm identifier */
|
|
|
|
if (buffer[0] != 0) {
|
|
|
|
pubkey_info.id.len = 1;
|
|
|
|
pubkey_info.id.value[0] = i + 1;
|
|
|
|
pubkey_info.modulus_length = bebytes2ushort(buffer + 1);
|
|
|
|
pubkey_info.usage = key_cfg[i].pubkey_usage;
|
|
|
|
sc_format_path(key_cfg[i].pubkey_path, &pubkey_info.path);
|
2005-02-06 21:34:59 +00:00
|
|
|
|
2012-05-06 17:52:58 +00:00
|
|
|
strlcpy(pubkey_obj.label, key_cfg[i].label, sizeof(pubkey_obj.label));
|
|
|
|
pubkey_obj.flags = SC_PKCS15_CO_FLAG_MODIFIABLE;
|
2005-02-06 21:34:59 +00:00
|
|
|
|
2012-05-06 17:52:58 +00:00
|
|
|
r = sc_pkcs15emu_add_rsa_pubkey(p15card, &pubkey_obj, &pubkey_info);
|
|
|
|
if (r < 0)
|
|
|
|
return SC_ERROR_INTERNAL;
|
|
|
|
}
|
2003-10-31 12:27:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2010-03-15 12:17:13 +00:00
|
|
|
failed: sc_debug(card->ctx, SC_LOG_DEBUG_NORMAL, "Failed to initialize OpenPGP emulation: %s\n",
|
2003-10-31 12:27:14 +00:00
|
|
|
sc_strerror(r));
|
|
|
|
return r;
|
|
|
|
}
|
2004-10-08 21:29:55 +00:00
|
|
|
|
|
|
|
static int openpgp_detect_card(sc_pkcs15_card_t *p15card)
|
|
|
|
{
|
2011-01-09 10:17:16 +00:00
|
|
|
if (p15card->card->type == SC_CARD_TYPE_OPENPGP_V1 || p15card->card->type == SC_CARD_TYPE_OPENPGP_V2)
|
|
|
|
return SC_SUCCESS;
|
|
|
|
else
|
|
|
|
return SC_ERROR_WRONG_CARD;
|
2004-10-08 21:29:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int sc_pkcs15emu_openpgp_init_ex(sc_pkcs15_card_t *p15card,
|
|
|
|
sc_pkcs15emu_opt_t *opts)
|
|
|
|
{
|
|
|
|
if (opts && opts->flags & SC_PKCS15EMU_FLAGS_NO_CHECK)
|
|
|
|
return sc_pkcs15emu_openpgp_init(p15card);
|
|
|
|
else {
|
|
|
|
int r = openpgp_detect_card(p15card);
|
|
|
|
if (r)
|
|
|
|
return SC_ERROR_WRONG_CARD;
|
|
|
|
return sc_pkcs15emu_openpgp_init(p15card);
|
|
|
|
}
|
|
|
|
}
|