| 1 | |
|---|
| 2 | |
|---|
| 3 | |
|---|
| 4 | |
|---|
| 5 | |
|---|
| 6 | |
|---|
| 7 | |
|---|
| 8 | |
|---|
| 9 | |
|---|
| 10 | |
|---|
| 11 | |
|---|
| 12 | |
|---|
| 13 | |
|---|
| 14 | |
|---|
| 15 | |
|---|
| 16 | |
|---|
| 17 | |
|---|
| 18 | |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | #include "internal.h" |
|---|
| 22 | #include "asn1.h" |
|---|
| 23 | #include <assert.h> |
|---|
| 24 | #include <stdlib.h> |
|---|
| 25 | #include <string.h> |
|---|
| 26 | |
|---|
| 27 | struct app_entry { |
|---|
| 28 | const u8 *aid; |
|---|
| 29 | size_t aid_len; |
|---|
| 30 | const char *desc; |
|---|
| 31 | }; |
|---|
| 32 | |
|---|
| 33 | static const struct app_entry apps[] = { |
|---|
| 34 | { (const u8 *) "\xA0\x00\x00\x00\x63PKCS-15", 12, "PKCS #15" }, |
|---|
| 35 | { (const u8 *) "\xA0\x00\x00\x01\x77PKCS-15", 12, "Belgian eID" }, |
|---|
| 36 | }; |
|---|
| 37 | |
|---|
| 38 | static const struct app_entry * find_app_entry(const u8 * aid, size_t aid_len) |
|---|
| 39 | { |
|---|
| 40 | size_t i; |
|---|
| 41 | |
|---|
| 42 | for (i = 0; i < sizeof(apps)/sizeof(apps[0]); i++) { |
|---|
| 43 | if (apps[i].aid_len == aid_len && |
|---|
| 44 | memcmp(apps[i].aid, aid, aid_len) == 0) |
|---|
| 45 | return &apps[i]; |
|---|
| 46 | } |
|---|
| 47 | return NULL; |
|---|
| 48 | } |
|---|
| 49 | |
|---|
| 50 | const sc_app_info_t * sc_find_pkcs15_app(sc_card_t *card) |
|---|
| 51 | { |
|---|
| 52 | const sc_app_info_t *app = NULL; |
|---|
| 53 | unsigned int i; |
|---|
| 54 | |
|---|
| 55 | i = sizeof(apps)/sizeof(apps[0]); |
|---|
| 56 | while (!app && i--) |
|---|
| 57 | app = sc_find_app_by_aid(card, apps[i].aid, apps[i].aid_len); |
|---|
| 58 | |
|---|
| 59 | return app; |
|---|
| 60 | } |
|---|
| 61 | |
|---|
| 62 | static const struct sc_asn1_entry c_asn1_dirrecord[] = { |
|---|
| 63 | { "aid", SC_ASN1_OCTET_STRING, SC_ASN1_APP | 15, 0, NULL, NULL }, |
|---|
| 64 | { "label", SC_ASN1_UTF8STRING, SC_ASN1_APP | 16, SC_ASN1_OPTIONAL, NULL, NULL }, |
|---|
| 65 | { "path", SC_ASN1_OCTET_STRING, SC_ASN1_APP | 17, SC_ASN1_OPTIONAL, NULL, NULL }, |
|---|
| 66 | { "ddo", SC_ASN1_OCTET_STRING, SC_ASN1_APP | 19 | SC_ASN1_CONS, SC_ASN1_OPTIONAL, NULL, NULL }, |
|---|
| 67 | { NULL, 0, 0, 0, NULL, NULL } |
|---|
| 68 | }; |
|---|
| 69 | |
|---|
| 70 | static const struct sc_asn1_entry c_asn1_dir[] = { |
|---|
| 71 | { "dirRecord", SC_ASN1_STRUCT, SC_ASN1_APP | 1 | SC_ASN1_CONS, 0, NULL, NULL }, |
|---|
| 72 | { NULL, 0, 0, 0, NULL, NULL } |
|---|
| 73 | }; |
|---|
| 74 | |
|---|
| 75 | static int parse_dir_record(sc_card_t *card, u8 ** buf, size_t *buflen, |
|---|
| 76 | int rec_nr) |
|---|
| 77 | { |
|---|
| 78 | struct sc_asn1_entry asn1_dirrecord[5], asn1_dir[2]; |
|---|
| 79 | sc_app_info_t *app = NULL; |
|---|
| 80 | const struct app_entry *ae; |
|---|
| 81 | int r; |
|---|
| 82 | u8 aid[128], label[128], path[128]; |
|---|
| 83 | u8 ddo[128]; |
|---|
| 84 | size_t aid_len = sizeof(aid), label_len = sizeof(label), |
|---|
| 85 | path_len = sizeof(path), ddo_len = sizeof(ddo); |
|---|
| 86 | |
|---|
| 87 | sc_copy_asn1_entry(c_asn1_dirrecord, asn1_dirrecord); |
|---|
| 88 | sc_copy_asn1_entry(c_asn1_dir, asn1_dir); |
|---|
| 89 | sc_format_asn1_entry(asn1_dir + 0, asn1_dirrecord, NULL, 0); |
|---|
| 90 | sc_format_asn1_entry(asn1_dirrecord + 0, aid, &aid_len, 0); |
|---|
| 91 | sc_format_asn1_entry(asn1_dirrecord + 1, label, &label_len, 0); |
|---|
| 92 | sc_format_asn1_entry(asn1_dirrecord + 2, path, &path_len, 0); |
|---|
| 93 | sc_format_asn1_entry(asn1_dirrecord + 3, ddo, &ddo_len, 0); |
|---|
| 94 | |
|---|
| 95 | r = sc_asn1_decode(card->ctx, asn1_dir, *buf, *buflen, (const u8 **) buf, buflen); |
|---|
| 96 | if (r == SC_ERROR_ASN1_END_OF_CONTENTS) |
|---|
| 97 | return r; |
|---|
| 98 | if (r) { |
|---|
| 99 | sc_error(card->ctx, "EF(DIR) parsing failed: %s\n", |
|---|
| 100 | sc_strerror(r)); |
|---|
| 101 | return r; |
|---|
| 102 | } |
|---|
| 103 | if (aid_len > SC_MAX_AID_SIZE) { |
|---|
| 104 | sc_error(card->ctx, "AID is too long.\n"); |
|---|
| 105 | return SC_ERROR_INVALID_ASN1_OBJECT; |
|---|
| 106 | } |
|---|
| 107 | app = (sc_app_info_t *) malloc(sizeof(sc_app_info_t)); |
|---|
| 108 | if (app == NULL) |
|---|
| 109 | return SC_ERROR_OUT_OF_MEMORY; |
|---|
| 110 | |
|---|
| 111 | memcpy(app->aid, aid, aid_len); |
|---|
| 112 | app->aid_len = aid_len; |
|---|
| 113 | if (asn1_dirrecord[1].flags & SC_ASN1_PRESENT) |
|---|
| 114 | app->label = strdup((char *) label); |
|---|
| 115 | else |
|---|
| 116 | app->label = NULL; |
|---|
| 117 | if (asn1_dirrecord[2].flags & SC_ASN1_PRESENT) { |
|---|
| 118 | if (path_len > SC_MAX_PATH_SIZE) { |
|---|
| 119 | sc_error(card->ctx, "Application path is too long.\n"); |
|---|
| 120 | free(app); |
|---|
| 121 | return SC_ERROR_INVALID_ASN1_OBJECT; |
|---|
| 122 | } |
|---|
| 123 | memcpy(app->path.value, path, path_len); |
|---|
| 124 | app->path.len = path_len; |
|---|
| 125 | app->path.type = SC_PATH_TYPE_PATH; |
|---|
| 126 | } else if (aid_len < sizeof(app->path.value)) { |
|---|
| 127 | memcpy(app->path.value, aid, aid_len); |
|---|
| 128 | app->path.len = aid_len; |
|---|
| 129 | app->path.type = SC_PATH_TYPE_DF_NAME; |
|---|
| 130 | } else |
|---|
| 131 | app->path.len = 0; |
|---|
| 132 | if (asn1_dirrecord[3].flags & SC_ASN1_PRESENT) { |
|---|
| 133 | app->ddo = (u8 *) malloc(ddo_len); |
|---|
| 134 | if (app->ddo == NULL) { |
|---|
| 135 | free(app); |
|---|
| 136 | return SC_ERROR_OUT_OF_MEMORY; |
|---|
| 137 | } |
|---|
| 138 | memcpy(app->ddo, ddo, ddo_len); |
|---|
| 139 | app->ddo_len = ddo_len; |
|---|
| 140 | } else { |
|---|
| 141 | app->ddo = NULL; |
|---|
| 142 | app->ddo_len = 0; |
|---|
| 143 | } |
|---|
| 144 | ae = find_app_entry(aid, aid_len); |
|---|
| 145 | if (ae != NULL) |
|---|
| 146 | app->desc = ae->desc; |
|---|
| 147 | else |
|---|
| 148 | app->desc = NULL; |
|---|
| 149 | app->rec_nr = rec_nr; |
|---|
| 150 | card->app[card->app_count] = app; |
|---|
| 151 | card->app_count++; |
|---|
| 152 | |
|---|
| 153 | return 0; |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | int sc_enum_apps(sc_card_t *card) |
|---|
| 157 | { |
|---|
| 158 | sc_path_t path; |
|---|
| 159 | int ef_structure; |
|---|
| 160 | size_t file_size; |
|---|
| 161 | int r; |
|---|
| 162 | |
|---|
| 163 | if (card->app_count < 0) |
|---|
| 164 | card->app_count = 0; |
|---|
| 165 | sc_format_path("3F002F00", &path); |
|---|
| 166 | if (card->ef_dir != NULL) { |
|---|
| 167 | sc_file_free(card->ef_dir); |
|---|
| 168 | card->ef_dir = NULL; |
|---|
| 169 | } |
|---|
| 170 | sc_ctx_suppress_errors_on(card->ctx); |
|---|
| 171 | r = sc_select_file(card, &path, &card->ef_dir); |
|---|
| 172 | sc_ctx_suppress_errors_off(card->ctx); |
|---|
| 173 | if (r) |
|---|
| 174 | return r; |
|---|
| 175 | if (card->ef_dir->type != SC_FILE_TYPE_WORKING_EF) { |
|---|
| 176 | sc_debug(card->ctx, "EF(DIR) is not a working EF.\n"); |
|---|
| 177 | sc_file_free(card->ef_dir); |
|---|
| 178 | card->ef_dir = NULL; |
|---|
| 179 | return SC_ERROR_INVALID_CARD; |
|---|
| 180 | } |
|---|
| 181 | ef_structure = card->ef_dir->ef_structure; |
|---|
| 182 | file_size = card->ef_dir->size; |
|---|
| 183 | if (file_size == 0) |
|---|
| 184 | return 0; |
|---|
| 185 | if (ef_structure == SC_FILE_EF_TRANSPARENT) { |
|---|
| 186 | u8 *buf = NULL, *p; |
|---|
| 187 | size_t bufsize; |
|---|
| 188 | |
|---|
| 189 | buf = (u8 *) malloc(file_size); |
|---|
| 190 | if (buf == NULL) |
|---|
| 191 | return SC_ERROR_OUT_OF_MEMORY; |
|---|
| 192 | p = buf; |
|---|
| 193 | r = sc_read_binary(card, 0, buf, file_size, 0); |
|---|
| 194 | if (r < 0) { |
|---|
| 195 | free(buf); |
|---|
| 196 | SC_TEST_RET(card->ctx, r, "sc_read_binary() failed"); |
|---|
| 197 | } |
|---|
| 198 | bufsize = r; |
|---|
| 199 | while (bufsize > 0) { |
|---|
| 200 | if (card->app_count == SC_MAX_CARD_APPS) { |
|---|
| 201 | sc_error(card->ctx, "Too many applications on card"); |
|---|
| 202 | break; |
|---|
| 203 | } |
|---|
| 204 | r = parse_dir_record(card, &p, &bufsize, -1); |
|---|
| 205 | if (r) |
|---|
| 206 | break; |
|---|
| 207 | } |
|---|
| 208 | if (buf) |
|---|
| 209 | free(buf); |
|---|
| 210 | |
|---|
| 211 | } else { |
|---|
| 212 | u8 buf[256], *p; |
|---|
| 213 | unsigned int rec_nr; |
|---|
| 214 | size_t rec_size; |
|---|
| 215 | |
|---|
| 216 | for (rec_nr = 1; ; rec_nr++) { |
|---|
| 217 | sc_ctx_suppress_errors_on(card->ctx); |
|---|
| 218 | r = sc_read_record(card, rec_nr, buf, sizeof(buf), |
|---|
| 219 | SC_RECORD_BY_REC_NR); |
|---|
| 220 | sc_ctx_suppress_errors_off(card->ctx); |
|---|
| 221 | if (r == SC_ERROR_RECORD_NOT_FOUND) |
|---|
| 222 | break; |
|---|
| 223 | SC_TEST_RET(card->ctx, r, "read_record() failed"); |
|---|
| 224 | if (card->app_count == SC_MAX_CARD_APPS) { |
|---|
| 225 | sc_error(card->ctx, "Too many applications on card"); |
|---|
| 226 | break; |
|---|
| 227 | } |
|---|
| 228 | rec_size = r; |
|---|
| 229 | p = buf; |
|---|
| 230 | parse_dir_record(card, &p, &rec_size, (int)rec_nr); |
|---|
| 231 | } |
|---|
| 232 | } |
|---|
| 233 | return card->app_count; |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | void sc_free_apps(sc_card_t *card) |
|---|
| 237 | { |
|---|
| 238 | int i; |
|---|
| 239 | |
|---|
| 240 | for (i = 0; i < card->app_count; i++) { |
|---|
| 241 | if (card->app[i]->label) |
|---|
| 242 | free(card->app[i]->label); |
|---|
| 243 | if (card->app[i]->ddo) |
|---|
| 244 | free(card->app[i]->ddo); |
|---|
| 245 | free(card->app[i]); |
|---|
| 246 | } |
|---|
| 247 | card->app_count = -1; |
|---|
| 248 | } |
|---|
| 249 | |
|---|
| 250 | const sc_app_info_t * sc_find_app_by_aid(sc_card_t *card, |
|---|
| 251 | const u8 *aid, size_t aid_len) |
|---|
| 252 | { |
|---|
| 253 | int i; |
|---|
| 254 | |
|---|
| 255 | assert(card->app_count > 0); |
|---|
| 256 | for (i = 0; i < card->app_count; i++) { |
|---|
| 257 | if (card->app[i]->aid_len == aid_len && |
|---|
| 258 | memcmp(card->app[i]->aid, aid, aid_len) == 0) |
|---|
| 259 | return card->app[i]; |
|---|
| 260 | } |
|---|
| 261 | return NULL; |
|---|
| 262 | } |
|---|
| 263 | |
|---|
| 264 | static int encode_dir_record(sc_context_t *ctx, const sc_app_info_t *app, |
|---|
| 265 | u8 **buf, size_t *buflen) |
|---|
| 266 | { |
|---|
| 267 | struct sc_asn1_entry asn1_dirrecord[5], asn1_dir[2]; |
|---|
| 268 | sc_app_info_t tapp = *app; |
|---|
| 269 | int r; |
|---|
| 270 | size_t label_len; |
|---|
| 271 | |
|---|
| 272 | sc_copy_asn1_entry(c_asn1_dirrecord, asn1_dirrecord); |
|---|
| 273 | sc_copy_asn1_entry(c_asn1_dir, asn1_dir); |
|---|
| 274 | sc_format_asn1_entry(asn1_dir + 0, asn1_dirrecord, NULL, 1); |
|---|
| 275 | sc_format_asn1_entry(asn1_dirrecord + 0, (void *) tapp.aid, (void *) &tapp.aid_len, 1); |
|---|
| 276 | if (tapp.label != NULL) { |
|---|
| 277 | label_len = strlen(tapp.label); |
|---|
| 278 | sc_format_asn1_entry(asn1_dirrecord + 1, tapp.label, &label_len, 1); |
|---|
| 279 | } |
|---|
| 280 | if (tapp.path.len) |
|---|
| 281 | sc_format_asn1_entry(asn1_dirrecord + 2, (void *) tapp.path.value, |
|---|
| 282 | (void *) &tapp.path.len, 1); |
|---|
| 283 | if (tapp.ddo != NULL) |
|---|
| 284 | sc_format_asn1_entry(asn1_dirrecord + 3, (void *) tapp.ddo, |
|---|
| 285 | (void *) &tapp.ddo_len, 1); |
|---|
| 286 | r = sc_asn1_encode(ctx, asn1_dir, buf, buflen); |
|---|
| 287 | if (r) { |
|---|
| 288 | sc_error(ctx, "sc_asn1_encode() failed: %s\n", |
|---|
| 289 | sc_strerror(r)); |
|---|
| 290 | return r; |
|---|
| 291 | } |
|---|
| 292 | return 0; |
|---|
| 293 | } |
|---|
| 294 | |
|---|
| 295 | static int update_transparent(sc_card_t *card, sc_file_t *file) |
|---|
| 296 | { |
|---|
| 297 | u8 *rec, *buf = NULL, *tmp; |
|---|
| 298 | size_t rec_size, buf_size = 0; |
|---|
| 299 | int i, r; |
|---|
| 300 | |
|---|
| 301 | for (i = 0; i < card->app_count; i++) { |
|---|
| 302 | r = encode_dir_record(card->ctx, card->app[i], &rec, &rec_size); |
|---|
| 303 | if (r) { |
|---|
| 304 | if (rec) |
|---|
| 305 | free(rec); |
|---|
| 306 | if (buf) |
|---|
| 307 | free(buf); |
|---|
| 308 | return r; |
|---|
| 309 | } |
|---|
| 310 | tmp = (u8 *) realloc(buf, buf_size + rec_size); |
|---|
| 311 | if (!tmp) { |
|---|
| 312 | if (rec) |
|---|
| 313 | free(rec); |
|---|
| 314 | if (buf) |
|---|
| 315 | free(buf); |
|---|
| 316 | return SC_ERROR_OUT_OF_MEMORY; |
|---|
| 317 | } |
|---|
| 318 | buf = tmp; |
|---|
| 319 | memcpy(buf + buf_size, rec, rec_size); |
|---|
| 320 | buf_size += rec_size; |
|---|
| 321 | free(rec); |
|---|
| 322 | rec=NULL; |
|---|
| 323 | } |
|---|
| 324 | if (file->size > buf_size) { |
|---|
| 325 | tmp = (u8 *) realloc(buf, file->size); |
|---|
| 326 | if (!tmp) { |
|---|
| 327 | free(buf); |
|---|
| 328 | return SC_ERROR_OUT_OF_MEMORY; |
|---|
| 329 | } |
|---|
| 330 | buf = tmp; |
|---|
| 331 | memset(buf + buf_size, 0, file->size - buf_size); |
|---|
| 332 | buf_size = file->size; |
|---|
| 333 | } |
|---|
| 334 | r = sc_update_binary(card, 0, buf, buf_size, 0); |
|---|
| 335 | free(buf); |
|---|
| 336 | SC_TEST_RET(card->ctx, r, "Unable to update EF(DIR)"); |
|---|
| 337 | |
|---|
| 338 | return 0; |
|---|
| 339 | } |
|---|
| 340 | |
|---|
| 341 | static int update_single_record(sc_card_t *card, sc_file_t *file, |
|---|
| 342 | sc_app_info_t *app) |
|---|
| 343 | { |
|---|
| 344 | u8 *rec; |
|---|
| 345 | size_t rec_size; |
|---|
| 346 | int r; |
|---|
| 347 | |
|---|
| 348 | r = encode_dir_record(card->ctx, app, &rec, &rec_size); |
|---|
| 349 | if (r) |
|---|
| 350 | return r; |
|---|
| 351 | if (app->rec_nr > 0) |
|---|
| 352 | r = sc_update_record(card, (unsigned int)app->rec_nr, rec, rec_size, SC_RECORD_BY_REC_NR); |
|---|
| 353 | else if (app->rec_nr == 0) { |
|---|
| 354 | |
|---|
| 355 | sc_ctx_suppress_errors_on(card->ctx); |
|---|
| 356 | r = sc_append_record(card, rec, rec_size, 0); |
|---|
| 357 | sc_ctx_suppress_errors_off(card->ctx); |
|---|
| 358 | if (r == SC_ERROR_NOT_SUPPORTED) { |
|---|
| 359 | |
|---|
| 360 | |
|---|
| 361 | |
|---|
| 362 | |
|---|
| 363 | int rec_nr = 0, i; |
|---|
| 364 | for(i = 0; i < card->app_count; i++) |
|---|
| 365 | if (card->app[i]->rec_nr > rec_nr) |
|---|
| 366 | rec_nr = card->app[i]->rec_nr; |
|---|
| 367 | rec_nr++; |
|---|
| 368 | r = sc_update_record(card, (unsigned int)rec_nr, rec, rec_size, SC_RECORD_BY_REC_NR); |
|---|
| 369 | } |
|---|
| 370 | } else { |
|---|
| 371 | sc_error(card->ctx, "invalid record number\n"); |
|---|
| 372 | r = SC_ERROR_INTERNAL; |
|---|
| 373 | } |
|---|
| 374 | free(rec); |
|---|
| 375 | SC_TEST_RET(card->ctx, r, "Unable to update EF(DIR) record"); |
|---|
| 376 | return 0; |
|---|
| 377 | } |
|---|
| 378 | |
|---|
| 379 | static int update_records(sc_card_t *card, sc_file_t *file) |
|---|
| 380 | { |
|---|
| 381 | int i, r; |
|---|
| 382 | |
|---|
| 383 | for (i = 0; i < card->app_count; i++) { |
|---|
| 384 | r = update_single_record(card, file, card->app[i]); |
|---|
| 385 | if (r) |
|---|
| 386 | return r; |
|---|
| 387 | } |
|---|
| 388 | return 0; |
|---|
| 389 | } |
|---|
| 390 | |
|---|
| 391 | int sc_update_dir(sc_card_t *card, sc_app_info_t *app) |
|---|
| 392 | { |
|---|
| 393 | sc_path_t path; |
|---|
| 394 | sc_file_t *file; |
|---|
| 395 | int r; |
|---|
| 396 | |
|---|
| 397 | sc_format_path("3F002F00", &path); |
|---|
| 398 | |
|---|
| 399 | r = sc_select_file(card, &path, &file); |
|---|
| 400 | SC_TEST_RET(card->ctx, r, "unable to select EF(DIR)"); |
|---|
| 401 | if (file->ef_structure == SC_FILE_EF_TRANSPARENT) |
|---|
| 402 | r = update_transparent(card, file); |
|---|
| 403 | else if (app == NULL) |
|---|
| 404 | r = update_records(card, file); |
|---|
| 405 | else |
|---|
| 406 | r = update_single_record(card, file, app); |
|---|
| 407 | sc_file_free(file); |
|---|
| 408 | return r; |
|---|
| 409 | } |
|---|