pkcs15-init etc. support for pkcs15 data objects

patch supplied by Victor Tarasov <vtarasov@idealx.com>


git-svn-id: https://www.opensc-project.org/svnp/opensc/trunk@1877 c6295689-39f2-0310-b995-f0e70906c6a9
This commit is contained in:
nils 2004-07-26 18:47:23 +00:00
parent 1e13a2e011
commit bfd5b49436
7 changed files with 431 additions and 8 deletions

View File

@ -106,9 +106,20 @@ struct pkcs15_pubkey_object {
#define is_pubkey(obj) (__p15_type(obj) == SC_PKCS15_TYPE_PUBKEY_RSA)
#define is_cert(obj) (__p15_type(obj) == SC_PKCS15_TYPE_CERT_X509)
struct pkcs15_data_object {
struct pkcs15_any_object base;
struct sc_pkcs15_data_info *info;
struct sc_pkcs15_data *value;
};
#define data_flags base.base.flags
#define data_p15obj base.p15_object
#define is_data(obj) (__p15_type(obj) == SC_PKCS15_TYPE_DATA_OBJECT)
extern struct sc_pkcs11_object_ops pkcs15_cert_ops;
extern struct sc_pkcs11_object_ops pkcs15_prkey_ops;
extern struct sc_pkcs11_object_ops pkcs15_pubkey_ops;
extern struct sc_pkcs11_object_ops pkcs15_dobj_ops;
static int __pkcs15_release_object(struct pkcs15_any_object *);
@ -321,6 +332,28 @@ __pkcs15_create_prkey_object(struct pkcs15_fw_data *fw_data,
return 0;
}
static int
__pkcs15_create_data_object(struct pkcs15_fw_data *fw_data,
struct sc_pkcs15_object *object, struct pkcs15_any_object **data_object)
{
struct pkcs15_data_object *dobj = NULL;
int rv;
rv = __pkcs15_create_object(fw_data, (struct pkcs15_any_object **) &dobj,
object, &pkcs15_dobj_ops,
sizeof(struct pkcs15_data_object));
if (rv >= 0) {
dobj->info = (struct sc_pkcs15_data_info *) object->data;
dobj->value = NULL;
}
if (data_object != NULL)
*data_object = (struct pkcs15_any_object *) dobj;
return 0;
}
static int
pkcs15_create_pkcs11_objects(struct pkcs15_fw_data *fw_data,
int p15_type, const char *name,
@ -590,6 +623,13 @@ static CK_RV pkcs15_create_tokens(struct sc_pkcs11_card *p11card)
if (rv < 0)
return sc_to_cryptoki_error(rv, reader);
rv = pkcs15_create_pkcs11_objects(fw_data,
SC_PKCS15_TYPE_DATA_OBJECT,
"data object",
__pkcs15_create_data_object);
if (rv < 0)
return sc_to_cryptoki_error(rv, reader);
/* Match up related keys and certificates */
pkcs15_bind_related_objects(fw_data);
@ -610,14 +650,21 @@ static CK_RV pkcs15_create_tokens(struct sc_pkcs11_card *p11card)
for (j=0; j < fw_data->num_objects; j++) {
struct pkcs15_any_object *obj = fw_data->objects[j];
if (!is_privkey(obj)
|| !sc_pkcs15_compare_id(&pin_info->auth_id,
if (__p15_type(obj) == -1)
continue;
else if (!sc_pkcs15_compare_id(&pin_info->auth_id,
&obj->p15_object->auth_id))
continue;
if (is_privkey(obj)) {
sc_debug(context, "Adding private key %d to PIN %d\n", j, i);
pkcs15_add_object(slot, obj, NULL);
}
else if (is_data(obj)) {
sc_debug(context, "Adding data object %d to PIN %d\n", j, i);
pkcs15_add_object(slot, obj, NULL);
}
}
}
/* Add all public objects to a virtual slot without pin protection */
@ -1329,7 +1376,7 @@ CK_RV pkcs15_gen_keypair(struct sc_pkcs11_card *p11card, struct sc_pkcs11_slot *
rc = __pkcs15_create_prkey_object(fw_data, priv_key_obj, &priv_any_obj);
if (rc == 0)
__pkcs15_create_pubkey_object(fw_data, pub_key_obj, &pub_any_obj);
rc = __pkcs15_create_pubkey_object(fw_data, pub_key_obj, &pub_any_obj);
if (rc != 0) {
sc_debug(context, "__pkcs15_create_pr/pubkey_object returned %d\n", rc);
rv = sc_to_cryptoki_error(rc, p11card->reader);
@ -2008,6 +2055,148 @@ struct sc_pkcs11_object_ops pkcs15_pubkey_ops = {
NULL
};
/* PKCS#15 Data Object*/
void pkcs15_dobj_release(void *object)
{
__pkcs15_release_object((struct pkcs15_any_object *) object);
}
CK_RV pkcs15_dobj_set_attribute(struct sc_pkcs11_session *session,
void *object, CK_ATTRIBUTE_PTR attr)
{
struct pkcs15_data_object *dobj = (struct pkcs15_data_object*) object;
return pkcs15_set_attrib(session, dobj->base.p15_object, attr);
}
int pkcs15_dobj_get_value(struct sc_pkcs11_session *session,
struct pkcs15_data_object *dobj,
struct sc_pkcs15_data **out_data)
{
int rv;
struct pkcs15_fw_data *fw_data =
(struct pkcs15_fw_data *) session->slot->card->fw_data;
struct pkcs15_slot_data *data = slot_data(session->slot->fw_data);
struct sc_card *card = session->slot->card->card;
int reader = session->slot->card->reader;
if (!out_data)
return SC_ERROR_INVALID_ARGUMENTS;
rv = sc_lock(card);
if (rv < 0)
return sc_to_cryptoki_error(rv, reader);
if (slot_data_pin_info(data)) {
rv = revalidate_pin(data, session);
if (rv < 0)
goto done;
}
if ((rv = sc_pkcs15_read_data_object(fw_data->p15_card, dobj->info, out_data)) < 0)
goto done;
done:
sc_unlock(card);
if (rv < 0)
return sc_to_cryptoki_error(rv, reader);
return rv;
}
CK_RV pkcs15_dobj_get_attribute(struct sc_pkcs11_session *session,
void *object,
CK_ATTRIBUTE_PTR attr)
{
struct pkcs15_data_object *dobj = (struct pkcs15_data_object*) object;
size_t len;
switch (attr->type) {
case CKA_CLASS:
check_attribute_buffer(attr, sizeof(CK_OBJECT_CLASS));
*(CK_OBJECT_CLASS*)attr->pValue = CKO_DATA;
break;
case CKA_TOKEN:
check_attribute_buffer(attr, sizeof(CK_BBOOL));
*(CK_BBOOL*)attr->pValue = TRUE;
break;
case CKA_PRIVATE:
check_attribute_buffer(attr, sizeof(CK_BBOOL));
*(CK_BBOOL*)attr->pValue =
(dobj->base.p15_object->flags & 0x01) != 0;
break;
case CKA_MODIFIABLE:
check_attribute_buffer(attr, sizeof(CK_BBOOL));
*(CK_BBOOL*)attr->pValue =
(dobj->base.p15_object->flags & 0x02) != 0;
break;
case CKA_LABEL:
len = strlen(dobj->base.p15_object->label);
check_attribute_buffer(attr, len);
memcpy(attr->pValue, dobj->base.p15_object->label, len);
break;
case CKA_APPLICATION:
len = strlen(dobj->info->app_label);
check_attribute_buffer(attr, len);
memcpy(attr->pValue, dobj->info->app_label, len);
break;
#if 0
case CKA_ID:
check_attribute_buffer(attr, dobj->info->id.len);
memcpy(attr->pValue, dobj->info->id.value, dobj->info->id.len);
break;
#endif
case CKA_OBJECT_ID:
{
int len = sizeof(dobj->info->app_oid);
check_attribute_buffer(attr, len);
memcpy(attr->pValue, dobj->info->app_oid.value, len);
}
break;
case CKA_VALUE:
{
CK_RV rv;
struct sc_pkcs15_data *data = NULL;
rv = pkcs15_dobj_get_value(session, dobj, &data);
if (rv!=CKR_OK)
return rv;
else if (!data)
return CKR_ATTRIBUTE_VALUE_INVALID;
sc_debug(context, "data %p\n", data);
sc_debug(context, "data_len %i\n", data->data_len);
check_attribute_buffer(attr, data->data_len);
memcpy(attr->pValue, data->data, data->data_len);
}
break;
default:
return CKR_ATTRIBUTE_TYPE_INVALID;
}
return CKR_OK;
}
struct sc_pkcs11_object_ops pkcs15_dobj_ops = {
pkcs15_dobj_release,
pkcs15_dobj_set_attribute,
pkcs15_dobj_get_attribute,
sc_pkcs11_any_cmp_attribute,
NULL,
NULL,
NULL,
NULL,
NULL,
};
/*
* get_attribute helpers
*/
@ -2218,14 +2407,15 @@ register_mechanisms(struct sc_pkcs11_card *p11card)
num = card->algorithm_count;
alg_info = card->algorithms;
while (num--) {
if (alg_info->algorithm != SC_ALGORITHM_RSA)
continue;
if (alg_info->algorithm == SC_ALGORITHM_RSA) {
if (alg_info->key_length < mech_info.ulMinKeySize)
mech_info.ulMinKeySize = alg_info->key_length;
if (alg_info->key_length > mech_info.ulMaxKeySize)
mech_info.ulMaxKeySize = alg_info->key_length;
flags |= alg_info->flags;
}
alg_info++;
}

View File

@ -58,6 +58,10 @@ CK_RV C_Initialize(CK_VOID_PTR pReserved)
__card_detect_all(0);
rv = sc_pkcs11_init_lock((CK_C_INITIALIZE_ARGS_PTR) pReserved);
if (rv != CKR_OK) {
sc_release_context(context);
context = NULL;
}
out: if (context != NULL)
sc_debug(context, "C_Initialize: result = %d\n", rv);

View File

@ -70,7 +70,7 @@
#define DEFAULT_PRKEY_FLAGS 0x03
#define DEFAULT_PUBKEY_FLAGS 0x02
#define DEFAULT_CERT_FLAGS 0x02
#define DEFAULT_DATA_FLAGS 0x03
#define DEFAULT_DATA_FLAGS 0x02
/* Handle encoding of PKCS15 on the card */
typedef int (*pkcs15_encoder)(struct sc_context *,
@ -2496,13 +2496,16 @@ set_user_pin_from_authid(struct sc_pkcs15_card *p15card,
* Possible fix: store all file info from the profile on the card
*/
if (pin->path.len != 0) {
sc_file_t *df;
sc_file_t *df = NULL;
r = sc_profile_get_file_by_path(profile, &pin->path, &df);
if (r == SC_ERROR_FILE_NOT_FOUND
&& (r = sc_select_file(p15card->card, &pin->path, &df)) == 0) {
sc_profile_add_file(profile, "pin-dir (auto)", df);
}
if (df)
sc_file_free(df);
}
return sc_keycache_set_pin_name(&pin->path,

View File

@ -41,6 +41,7 @@ enum {
OPT_MODULE = 0x100,
OPT_SLOT,
OPT_SLOT_LABEL,
OPT_APPLICATION_ID,
};
const struct option options[] = {
@ -58,6 +59,8 @@ const struct option options[] = {
{ "change-pin", 0, 0, 'c' },
{ "keypairgen", 0, 0, 'k' },
{ "write-object", 1, 0, 'w' },
{ "read-object", 0, 0, 'r' },
{ "application-id", 1, 0, OPT_APPLICATION_ID },
{ "type", 1, 0, 'y' },
{ "id", 1, 0, 'd' },
{ "label", 1, 0, 'a' },
@ -88,6 +91,8 @@ const char *option_help[] = {
"Change your (user) PIN",
"Key pair generation",
"Write an object (key, cert) to the card",
"Get object's CKA_VALUE attribute (use with --type)",
"Specify the application id of the data object (use with --type data)",
"Specify the type of object (e.g. cert, privkey, pubkey)",
"Specify the id of the object",
"Specify the label of the object",
@ -119,6 +124,7 @@ static CK_BYTE opt_object_id[100], new_object_id[100];
static size_t opt_object_id_len = 0, new_object_id_len = 0;
static char * opt_object_label = NULL;
static char * opt_pin = NULL;
static char * opt_application_id = NULL;
static void *module = NULL;
static CK_FUNCTION_LIST_PTR p11 = NULL;
@ -145,12 +151,14 @@ static int change_pin(CK_SLOT_ID, CK_SESSION_HANDLE);
static void show_object(CK_SESSION_HANDLE, CK_OBJECT_HANDLE);
static void show_key(CK_SESSION_HANDLE, CK_OBJECT_HANDLE, int);
static void show_cert(CK_SESSION_HANDLE, CK_OBJECT_HANDLE);
static void show_dobj(CK_SESSION_HANDLE sess, CK_OBJECT_HANDLE obj);
static void sign_data(CK_SLOT_ID,
CK_SESSION_HANDLE, CK_OBJECT_HANDLE);
static void hash_data(CK_SLOT_ID, CK_SESSION_HANDLE);
static int gen_keypair(CK_SLOT_ID, CK_SESSION_HANDLE,
CK_OBJECT_HANDLE *, CK_OBJECT_HANDLE *);
static int write_object(CK_SLOT_ID slot, CK_SESSION_HANDLE session);
static int read_object(CK_SLOT_ID slot, CK_SESSION_HANDLE session);
static void set_id_attr(CK_SLOT_ID slot, CK_SESSION_HANDLE session);
static int find_object(CK_SESSION_HANDLE, CK_OBJECT_CLASS,
CK_OBJECT_HANDLE_PTR,
@ -173,6 +181,10 @@ static const char * CKR2Str(CK_ULONG res);
static int p11_test(CK_SLOT_ID slot, CK_SESSION_HANDLE session);
static int hex_to_bin(const char *in, CK_BYTE *out, size_t *outlen);
static void test_kpgen_certwrite(CK_SLOT_ID slot, CK_SESSION_HANDLE session);
static CK_RV find_object_with_attributes(
CK_SESSION_HANDLE session, CK_OBJECT_HANDLE *out,
CK_ATTRIBUTE *attrs, CK_ULONG attrsLen,
CK_ULONG obj_index);
/* win32 needs this in open(2) */
#ifndef O_BINARY
@ -193,6 +205,7 @@ main(int argc, char * const argv[])
int do_hash = 0;
int do_gen_keypair = 0;
int do_write_object = 0;
int do_read_object = 0;
int do_set_id = 0;
int do_test = 0;
int do_test_kpgen_certwrite = 0;
@ -203,7 +216,7 @@ main(int argc, char * const argv[])
CK_RV rv;
while (1) {
c = getopt_long(argc, argv, "ILMOa:d:e:hi:klm:o:p:scvty:w:z:",
c = getopt_long(argc, argv, "ILMOa:d:e:hi:klm:o:p:scvty:w:z:r",
options, &long_optind);
if (c == -1)
break;
@ -241,6 +254,11 @@ main(int argc, char * const argv[])
opt_file_to_write = optarg;
action_count++;
break;
case 'r':
need_session |= NEED_SESSION_RO;
do_read_object = 1;
action_count++;
break;
case 'e':
need_session |= NEED_SESSION_RW;
do_set_id = 1;
@ -259,6 +277,8 @@ main(int argc, char * const argv[])
opt_object_class = CKO_PRIVATE_KEY;
else if (strcmp(optarg, "pubkey") == 0)
opt_object_class = CKO_PUBLIC_KEY;
else if (strcmp(optarg, "data") == 0)
opt_object_class = CKO_DATA;
else {
printf("Unsupported object type \"%s\"\n", optarg);
print_usage_and_die();
@ -323,6 +343,9 @@ main(int argc, char * const argv[])
case OPT_MODULE:
opt_module = optarg;
break;
case OPT_APPLICATION_ID:
opt_application_id = optarg;
break;
default:
print_usage_and_die();
}
@ -448,6 +471,15 @@ main(int argc, char * const argv[])
write_object(opt_slot, session);
}
if (do_read_object) {
if (opt_object_class_str == NULL)
fatal("You should specify type of the object to read");
if (opt_object_id_len == 0 && opt_object_label == NULL &&
opt_application_id == NULL)
fatal("You should specify at least one of the "
"object ID, object label or application ID\n");
read_object(opt_slot, session);
}
if (do_set_id) {
if (opt_object_class_str == NULL)
fatal("You should specify the object type with the -y option\n");
@ -1041,6 +1073,43 @@ done: if (count == 0)
return count;
}
CK_RV find_object_with_attributes(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE *out,
CK_ATTRIBUTE *attrs, CK_ULONG attrsLen,
CK_ULONG obj_index)
{
CK_ULONG count, ii;
CK_OBJECT_HANDLE ret;
CK_RV rv;
if (!out || !attrs || !attrsLen)
return CKR_ARGUMENTS_BAD;
else
*out = CK_INVALID_HANDLE;
rv = p11->C_FindObjectsInit(session, attrs, attrsLen);
if (rv != CKR_OK)
return rv;
for (ii = 0; ii < obj_index; ii++) {
rv = p11->C_FindObjects(session, &ret, 1, &count);
if (rv != CKR_OK)
return rv;
else if (!count)
goto done;
}
rv = p11->C_FindObjects(session, &ret, 1, &count);
if (rv != CKR_OK)
return rv;
else if (count)
*out = ret;
done:
p11->C_FindObjectsFinal(session);
return CKR_OK;
}
CK_MECHANISM_TYPE
find_mechanism(CK_SLOT_ID slot, CK_FLAGS flags, int stop_if_not_found)
{
@ -1120,7 +1189,9 @@ ATTR_METHOD(KEY_TYPE, CK_KEY_TYPE);
ATTR_METHOD(CERTIFICATE_TYPE, CK_CERTIFICATE_TYPE);
ATTR_METHOD(MODULUS_BITS, CK_ULONG);
VARATTR_METHOD(LABEL, char);
VARATTR_METHOD(APPLICATION, char);
VARATTR_METHOD(ID, unsigned char);
VARATTR_METHOD(OBJECT_ID, unsigned char);
VARATTR_METHOD(MODULUS, unsigned char);
VARATTR_METHOD(VALUE, unsigned char);
@ -1139,6 +1210,9 @@ show_object(CK_SESSION_HANDLE sess, CK_OBJECT_HANDLE obj)
case CKO_CERTIFICATE:
show_cert(sess, obj);
break;
case CKO_DATA:
show_dobj(sess, obj);
break;
default:
printf("Object %u, type %u\n",
(unsigned int) obj,
@ -1250,6 +1324,59 @@ show_cert(CK_SESSION_HANDLE sess, CK_OBJECT_HANDLE obj)
}
}
void
show_dobj(CK_SESSION_HANDLE sess, CK_OBJECT_HANDLE obj)
{
int *app_oid;
char *label;
CK_ULONG size;
printf("Data object %u\n", (unsigned int) obj);
printf(" label: ");
if ((label = getLABEL(sess, obj, NULL)) != NULL) {
printf("'%s'\n", label);
free(label);
}
else {
printf("<empty>\n");
}
printf(" application: ");
if ((label = getAPPLICATION(sess, obj, NULL)) != NULL) {
printf("'%s'\n", label);
free(label);
}
else {
printf("<empty>\n");
}
printf(" app_id: ");
app_oid = (int *)getOBJECT_ID(sess, obj, &size);
if (app_oid != NULL && size) {
unsigned int n;
size /= sizeof(int);
printf("%i", app_oid[0]);
if (app_oid[0] >= 0)
for (n = 1; (n < size) && (app_oid[n] >= 0); n++)
printf(".%i", app_oid[n]);
printf("\n");
free(app_oid);
}
else {
printf("<empty>\n");
}
printf(" flags: ");
if (getMODIFIABLE(sess, obj))
printf(" modifiable");
if (getPRIVATE(sess, obj))
printf(" private");
printf ("\n");
}
void
get_token_info(CK_SLOT_ID slot, CK_TOKEN_INFO_PTR info)
{
@ -1294,6 +1421,73 @@ get_mechanisms(CK_SLOT_ID slot,
return ulCount;
}
/*
* Read object CKA_VALUE attribute's value.
*/
int
read_object(CK_SLOT_ID slot, CK_SESSION_HANDLE session)
{
CK_RV rv;
CK_ATTRIBUTE attrs[20];
CK_OBJECT_CLASS clazz = opt_object_class;
CK_OBJECT_HANDLE obj = CK_INVALID_HANDLE;
int nn_attrs = 0;
unsigned char *value = NULL;
CK_ULONG len;
FILE *out;
struct sc_object_id oid;
if (opt_object_class_str != NULL) {
FILL_ATTR(attrs[nn_attrs], CKA_CLASS,
&clazz, sizeof(clazz));
nn_attrs++;
}
if (opt_object_id_len != 0) {
FILL_ATTR(attrs[nn_attrs], CKA_ID,
opt_object_id, opt_object_id_len);
nn_attrs++;
}
if (opt_object_label != NULL) {
FILL_ATTR(attrs[nn_attrs], CKA_LABEL,
opt_object_label, strlen(opt_object_label));
nn_attrs++;
}
if (opt_application_id != NULL) {
parse_application_id(&oid, opt_application_id);
FILL_ATTR(attrs[nn_attrs], CKA_OBJECT_ID,
(unsigned char *)oid.value, sizeof(oid.value));
nn_attrs++;
}
rv = find_object_with_attributes(session, &obj, attrs, nn_attrs, 0);
if (rv != CKR_OK)
p11_fatal("find_object_with_attributes()", rv);
else if (obj==CK_INVALID_HANDLE)
fatal("object not found\n");
value = getVALUE(session, obj, &len);
if (value == NULL)
fatal("get CKA_VALUE failed\n");
if (opt_output) {
out = fopen(opt_output, "wb");
if (out==NULL)
fatal("cannot open '%s'\n", opt_output);
}
else
out = stdout;
if (fwrite(value, 1, len, out) != len)
fatal("cannot write to '%s'\n", opt_output);
if (opt_output)
fclose(out);
return 1;
}
static int
test_digest(CK_SLOT_ID slot)
{

View File

@ -114,6 +114,7 @@ enum {
OPT_SECRET,
OPT_PUBKEY_LABEL,
OPT_CERT_LABEL,
OPT_APPLICATION_ID,
OPT_PIN1 = 0x10000, /* don't touch these values */
OPT_PUK1 = 0x10001,
@ -146,6 +147,7 @@ const struct option options[] = {
{ "label", required_argument, 0, 'l' },
{ "public-key-label", required_argument, 0, OPT_PUBKEY_LABEL },
{ "cert-label", required_argument, 0, OPT_CERT_LABEL },
{ "application-id", required_argument, 0, OPT_APPLICATION_ID },
{ "output-file", required_argument, 0, 'o' },
{ "format", required_argument, 0, 'f' },
{ "passphrase", required_argument, 0, OPT_PASSPHRASE },
@ -195,6 +197,7 @@ const char * option_help[] = {
"Specify label of PIN/key",
"Specify public key label (use with --generate-key)",
"Specify user cert label (use with --store-private-key)",
"Specify application id of data object (use with --store-data-object)",
"Output public portion of generated key to file",
"Specify key file format (default PEM)",
"Specify passphrase for unlocking secret key",
@ -287,6 +290,7 @@ static char * opt_serial = 0;
static char * opt_passphrase = 0;
static char * opt_newkey = 0;
static char * opt_outkey = 0;
static char * opt_application_id = 0;
static unsigned int opt_x509_usage = 0;
static int ignore_cmdline_pins = 0;
static struct secret opt_secrets[MAX_SECRETS];
@ -872,6 +876,9 @@ do_store_data_object(struct sc_profile *profile)
if (opt_authid)
sc_pkcs15_format_id(opt_authid, &args.auth_id);
args.label = opt_label;
args.app_label = "pkcs15-init";
parse_application_id(&args.app_oid, opt_application_id);
r = do_read_data_object(opt_infile, &data, &datalen);
if (r >= 0) {
@ -1913,6 +1920,9 @@ handle_option(const struct option *opt)
case OPT_SOFT_KEYGEN:
opt_softkeygen = 1;
break;
case OPT_APPLICATION_ID:
opt_application_id = optarg;
break;
case 'T':
opt_use_defkeys = 1;
break;

View File

@ -270,3 +270,24 @@ warn(const char *fmt, ...)
fprintf(stderr, "\n");
va_end(ap);
}
void parse_application_id(struct sc_object_id *oid, char *oid_str)
{
int ii;
char *nb;
if (!oid)
return;
for (ii=0; ii<SC_MAX_OBJECT_ID_OCTETS; ii++)
oid->value[ii] = -1;
nb = strtok(oid_str, ".");
for (ii=0; nb && ii < SC_MAX_OBJECT_ID_OCTETS; ii++) {
oid->value[ii] = strtol(nb, NULL, 10);
nb = strtok(NULL, ".");
}
}

View File

@ -40,6 +40,7 @@ void fatal(const char *fmt, ...);
/* All singing all dancing card connect routine */
int connect_card(struct sc_context *, struct sc_card **,
int reader_id, int slot_id, int wait, int verbose);
void parse_application_id(struct sc_object_id *oid, char *oid_str);
#ifdef __cplusplus
}