opensc/src/libopensc/dir.c

408 lines
11 KiB
C
Raw Normal View History

/*
* dir.c: Stuff for handling EF(DIR)
*
* Copyright (C) 2001, 2002 Juha Yrjölä <juha.yrjola@iki.fi>
*
* 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
*/
2015-04-22 21:55:33 +00:00
#if HAVE_CONFIG_H
#include "config.h"
2015-04-22 21:55:33 +00:00
#endif
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include "internal.h"
#include "asn1.h"
struct app_entry {
const u8 *aid;
size_t aid_len;
const char *desc;
};
static const struct app_entry apps[] = {
{ (const u8 *) "\xA0\x00\x00\x00\x63PKCS-15", 12, "PKCS #15" },
{ (const u8 *) "\xA0\x00\x00\x01\x77PKCS-15", 12, "Belgian eID" },
{ (const u8 *) "\x44\x46\x20\x69\x73\x73\x75\x65\x72", 9, "Portugal eID" },
{ (const u8 *) "\xE8\x28\xBD\x08\x0F\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E", 15, "ESIGN"}
};
static const struct sc_asn1_entry c_asn1_dirrecord[] = {
{ "aid", SC_ASN1_OCTET_STRING, SC_ASN1_APP | 15, 0, NULL, NULL },
{ "label", SC_ASN1_UTF8STRING, SC_ASN1_APP | 16, SC_ASN1_OPTIONAL, NULL, NULL },
{ "path", SC_ASN1_OCTET_STRING, SC_ASN1_APP | 17, SC_ASN1_OPTIONAL, NULL, NULL },
{ "ddo", SC_ASN1_OCTET_STRING, SC_ASN1_APP | 19 | SC_ASN1_CONS, SC_ASN1_OPTIONAL, NULL, NULL },
{ NULL, 0, 0, 0, NULL, NULL }
};
static const struct sc_asn1_entry c_asn1_dir[] = {
{ "dirRecord", SC_ASN1_STRUCT, SC_ASN1_APP | 1 | SC_ASN1_CONS, 0, NULL, NULL },
{ NULL, 0, 0, 0, NULL, NULL }
};
static int
parse_dir_record(sc_card_t *card, u8 ** buf, size_t *buflen, int rec_nr)
{
struct sc_context *ctx = card->ctx;
struct sc_asn1_entry asn1_dirrecord[5], asn1_dir[2];
scconf_block *conf_block = NULL;
sc_app_info_t *app = NULL;
struct sc_aid aid;
u8 label[128], path[128], ddo[128];
size_t label_len = sizeof(label), path_len = sizeof(path), ddo_len = sizeof(ddo);
int r;
LOG_FUNC_CALLED(ctx);
aid.len = sizeof(aid.value);
sc_copy_asn1_entry(c_asn1_dirrecord, asn1_dirrecord);
sc_copy_asn1_entry(c_asn1_dir, asn1_dir);
sc_format_asn1_entry(asn1_dir + 0, asn1_dirrecord, NULL, 0);
sc_format_asn1_entry(asn1_dirrecord + 0, aid.value, &aid.len, 0);
sc_format_asn1_entry(asn1_dirrecord + 1, label, &label_len, 0);
sc_format_asn1_entry(asn1_dirrecord + 2, path, &path_len, 0);
sc_format_asn1_entry(asn1_dirrecord + 3, ddo, &ddo_len, 0);
r = sc_asn1_decode(ctx, asn1_dir, *buf, *buflen, (const u8 **) buf, buflen);
if (r == SC_ERROR_ASN1_END_OF_CONTENTS)
LOG_FUNC_RETURN(ctx, r);
LOG_TEST_RET(ctx, r, "EF(DIR) parsing failed");
conf_block = sc_get_conf_block(ctx, "framework", "pkcs15", 1);
if (conf_block) {
scconf_block **blocks = NULL;
char aid_str[SC_MAX_AID_STRING_SIZE];
int ignore_app = 0;
sc_bin_to_hex(aid.value, aid.len, aid_str, sizeof(aid_str), 0);
blocks = scconf_find_blocks(card->ctx->conf, conf_block, "application", aid_str);
if (blocks) {
ignore_app = (blocks[0] && scconf_get_str(blocks[0], "disable", 0));
free(blocks);
}
if (ignore_app) {
sc_log(ctx, "Application '%s' ignored", aid_str);
LOG_FUNC_RETURN(ctx, SC_SUCCESS);
}
}
app = calloc(1, sizeof(struct sc_app_info));
if (app == NULL)
LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY);
memcpy(&app->aid, &aid, sizeof(struct sc_aid));
if (asn1_dirrecord[1].flags & SC_ASN1_PRESENT)
app->label = strdup((char *) label);
else
app->label = NULL;
if (asn1_dirrecord[2].flags & SC_ASN1_PRESENT && path_len > 0) {
/* application path present: ignore AID */
if (path_len > SC_MAX_PATH_SIZE) {
free(app);
LOG_TEST_RET(ctx, SC_ERROR_INVALID_ASN1_OBJECT, "Application path is too long.");
}
memcpy(app->path.value, path, path_len);
app->path.len = path_len;
app->path.type = SC_PATH_TYPE_PATH;
}
else {
/* application path not present: use AID as application path */
memcpy(app->path.value, aid.value, aid.len);
app->path.len = aid.len;
app->path.type = SC_PATH_TYPE_DF_NAME;
}
if (asn1_dirrecord[3].flags & SC_ASN1_PRESENT) {
app->ddo.value = malloc(ddo_len);
if (app->ddo.value == NULL) {
free(app);
LOG_TEST_RET(ctx, SC_ERROR_OUT_OF_MEMORY, "Cannot allocate DDO value");
}
memcpy(app->ddo.value, ddo, ddo_len);
app->ddo.len = ddo_len;
} else {
app->ddo.value = NULL;
app->ddo.len = 0;
}
app->rec_nr = rec_nr;
card->app[card->app_count] = app;
card->app_count++;
LOG_FUNC_RETURN(ctx, SC_SUCCESS);
}
int sc_enum_apps(sc_card_t *card)
{
struct sc_context *ctx = card->ctx;
sc_path_t path;
int ef_structure;
size_t file_size, jj;
int r, ii, idx;
LOG_FUNC_CALLED(ctx);
if (card->app_count < 0)
card->app_count = 0;
sc_format_path("3F002F00", &path);
sc_file_free(card->ef_dir);
card->ef_dir = NULL;
r = sc_select_file(card, &path, &card->ef_dir);
LOG_TEST_RET(ctx, r, "Cannot select EF.DIR file");
if (card->ef_dir->type != SC_FILE_TYPE_WORKING_EF) {
sc_file_free(card->ef_dir);
card->ef_dir = NULL;
LOG_TEST_RET(ctx, SC_ERROR_INVALID_CARD, "EF(DIR) is not a working EF.");
}
ef_structure = card->ef_dir->ef_structure;
if (ef_structure == SC_FILE_EF_TRANSPARENT) {
u8 *buf = NULL, *p;
size_t bufsize;
file_size = card->ef_dir->size;
if (file_size == 0)
LOG_FUNC_RETURN(ctx, 0);
Do not cast the return value of malloc(3) and calloc(3) From http://en.wikipedia.org/wiki/Malloc#Casting_and_type_safety " Casting and type safety malloc returns a void pointer (void *), which indicates that it is a pointer to a region of unknown data type. One may "cast" (see type conversion) this pointer to a specific type, as in int *ptr = (int*)malloc(10 * sizeof (int)); When using C, this is considered bad practice; it is redundant under the C standard. Moreover, putting in a cast may mask failure to include the header stdlib.h, in which the prototype for malloc is found. In the absence of a prototype for malloc, the C compiler will assume that malloc returns an int, and will issue a warning in a context such as the above, provided the error is not masked by a cast. On certain architectures and data models (such as LP64 on 64 bit systems, where long and pointers are 64 bit and int is 32 bit), this error can actually result in undefined behavior, as the implicitly declared malloc returns a 32 bit value whereas the actually defined function returns a 64 bit value. Depending on calling conventions and memory layout, this may result in stack smashing. The returned pointer need not be explicitly cast to a more specific pointer type, since ANSI C defines an implicit conversion between the void pointer type and other pointers to objects. An explicit cast of malloc's return value is sometimes performed because malloc originally returned a char *, but this cast is unnecessary in standard C code.[4][5] Omitting the cast, however, creates an incompatibility with C++, which does require it. The lack of a specific pointer type returned from malloc is type-unsafe behaviour: malloc allocates based on byte count but not on type. This distinguishes it from the C++ new operator that returns a pointer whose type relies on the operand. (see C Type Safety). " See also http://www.opensc-project.org/pipermail/opensc-devel/2010-August/014586.html git-svn-id: https://www.opensc-project.org/svnp/opensc/trunk@4636 c6295689-39f2-0310-b995-f0e70906c6a9
2010-08-18 15:08:51 +00:00
buf = malloc(file_size);
if (buf == NULL)
LOG_FUNC_RETURN(ctx, SC_ERROR_OUT_OF_MEMORY);
p = buf;
r = sc_read_binary(card, 0, buf, file_size, 0);
if (r < 0) {
free(buf);
LOG_TEST_RET(ctx, r, "sc_read_binary() failed");
}
bufsize = r;
while (bufsize > 0) {
if (card->app_count == SC_MAX_CARD_APPS) {
sc_log(ctx, "Too many applications on card");
break;
}
r = parse_dir_record(card, &p, &bufsize, -1);
if (r)
break;
}
if (buf)
free(buf);
}
else { /* record structure */
unsigned char buf[256], *p;
unsigned int rec_nr;
size_t rec_size;
/* Arbitrary set '16' as maximal number of records to check out:
* to avoid endless loop because of some incomplete cards/drivers */
for (rec_nr = 1; rec_nr < 16; rec_nr++) {
r = sc_read_record(card, rec_nr, buf, sizeof(buf), SC_RECORD_BY_REC_NR);
if (r == SC_ERROR_RECORD_NOT_FOUND)
break;
LOG_TEST_RET(ctx, r, "read_record() failed");
if (card->app_count == SC_MAX_CARD_APPS) {
sc_log(ctx, "Too many applications on card");
break;
}
rec_size = r;
p = buf;
parse_dir_record(card, &p, &rec_size, (int)rec_nr);
}
}
/* Move known PKCS#15 applications to the head of the list */
for (ii=0, idx=0; ii<card->app_count; ii++) {
for (jj=0; jj < sizeof(apps)/sizeof(apps[0]); jj++) {
if (apps[jj].aid_len != card->app[ii]->aid.len)
continue;
if (memcmp(apps[jj].aid, card->app[ii]->aid.value, apps[jj].aid_len))
continue;
break;
}
if (ii != idx && jj < sizeof(apps)/sizeof(apps[0])) {
struct sc_app_info *tmp = card->app[idx];
card->app[idx] = card->app[ii];
card->app[ii] = tmp;
idx++;
}
}
LOG_FUNC_RETURN(ctx, SC_SUCCESS);
}
void sc_free_apps(sc_card_t *card)
{
int i;
for (i = 0; i < card->app_count; i++) {
free(card->app[i]->label);
free(card->app[i]->ddo.value);
free(card->app[i]);
}
card->app_count = -1;
}
static int encode_dir_record(sc_context_t *ctx, const sc_app_info_t *app,
u8 **buf, size_t *buflen)
{
struct sc_asn1_entry asn1_dirrecord[5], asn1_dir[2];
sc_app_info_t tapp = *app;
int r;
size_t label_len;
sc_copy_asn1_entry(c_asn1_dirrecord, asn1_dirrecord);
sc_copy_asn1_entry(c_asn1_dir, asn1_dir);
sc_format_asn1_entry(asn1_dir + 0, asn1_dirrecord, NULL, 1);
sc_format_asn1_entry(asn1_dirrecord + 0, (void *) tapp.aid.value, (void *) &tapp.aid.len, 1);
if (tapp.label != NULL) {
label_len = strlen(tapp.label);
sc_format_asn1_entry(asn1_dirrecord + 1, tapp.label, &label_len, 1);
}
if (tapp.path.len)
sc_format_asn1_entry(asn1_dirrecord + 2, (void *) tapp.path.value,
(void *) &tapp.path.len, 1);
if (tapp.ddo.value != NULL && tapp.ddo.len)
sc_format_asn1_entry(asn1_dirrecord + 3, (void *) tapp.ddo.value,
(void *) &tapp.ddo.len, 1);
r = sc_asn1_encode(ctx, asn1_dir, buf, buflen);
LOG_TEST_RET(ctx, r, "Encode DIR record error");
return SC_SUCCESS;
}
static int update_transparent(sc_card_t *card, sc_file_t *file)
{
u8 *rec, *buf = NULL, *tmp;
size_t rec_size, buf_size = 0;
int i, r;
for (i = 0; i < card->app_count; i++) {
r = encode_dir_record(card->ctx, card->app[i], &rec, &rec_size);
if (r) {
if (buf)
free(buf);
return r;
}
tmp = (u8 *) realloc(buf, buf_size + rec_size);
if (!tmp) {
if (rec)
free(rec);
if (buf)
free(buf);
return SC_ERROR_OUT_OF_MEMORY;
}
buf = tmp;
memcpy(buf + buf_size, rec, rec_size);
buf_size += rec_size;
free(rec);
rec=NULL;
}
if (file->size > buf_size) {
tmp = (u8 *) realloc(buf, file->size);
if (!tmp) {
free(buf);
return SC_ERROR_OUT_OF_MEMORY;
}
buf = tmp;
memset(buf + buf_size, 0, file->size - buf_size);
buf_size = file->size;
}
r = sc_update_binary(card, 0, buf, buf_size, 0);
free(buf);
LOG_TEST_RET(card->ctx, r, "Unable to update EF(DIR)");
return SC_SUCCESS;
}
static int update_single_record(sc_card_t *card, sc_app_info_t *app)
{
u8 *rec;
size_t rec_size;
int r;
r = encode_dir_record(card->ctx, app, &rec, &rec_size);
if (r)
return r;
if (app->rec_nr > 0)
r = sc_update_record(card, (unsigned int)app->rec_nr, rec, rec_size, SC_RECORD_BY_REC_NR);
else if (app->rec_nr == 0) {
/* create new record entry */
r = sc_append_record(card, rec, rec_size, 0);
if (r == SC_ERROR_NOT_SUPPORTED) {
/* if the card doesn't support APPEND RECORD we try a
* UPDATE RECORD on the next unused record (and hope
* that there is a record with this index).
*/
int rec_nr = 0, i;
for(i = 0; i < card->app_count; i++)
if (card->app[i]->rec_nr > rec_nr)
rec_nr = card->app[i]->rec_nr;
rec_nr++;
r = sc_update_record(card, (unsigned int)rec_nr, rec, rec_size, SC_RECORD_BY_REC_NR);
}
} else {
sc_log(card->ctx, "invalid record number\n");
r = SC_ERROR_INTERNAL;
}
free(rec);
LOG_TEST_RET(card->ctx, r, "Unable to update EF(DIR) record");
return 0;
}
static int update_records(sc_card_t *card)
{
int i, r;
for (i = 0; i < card->app_count; i++) {
r = update_single_record(card, card->app[i]);
if (r)
return r;
}
return 0;
}
int sc_update_dir(sc_card_t *card, sc_app_info_t *app)
{
sc_path_t path;
sc_file_t *file;
int r;
sc_format_path("3F002F00", &path);
r = sc_select_file(card, &path, &file);
LOG_TEST_RET(card->ctx, r, "unable to select EF(DIR)");
if (file->ef_structure == SC_FILE_EF_TRANSPARENT)
r = update_transparent(card, file);
else if (app == NULL)
r = update_records(card);
else
r = update_single_record(card, app);
sc_file_free(file);
return r;
}