/* * piv-tool.c: Tool for accessing smart cards with libopensc * * Copyright (C) 2001 Juha Yrjölä * Copyright (C) 2005,2010 Douglas E. Engert * * 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 "config.h" #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include /* Module only built if OPENSSL is enabled */ #include #include #if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined(OPENSSL_NO_EC) #include #endif #include #include #include #include #include #include #include #include "libopensc/opensc.h" #include "libopensc/cardctl.h" #include "libopensc/asn1.h" #include "util.h" static const char *app_name = "piv-tool"; static int opt_wait = 0; static char ** opt_apdus; static char * opt_reader; static int opt_apdu_count = 0; static int verbose = 0; enum { OPT_SERIAL = 0x100, OPT_KEY_SLOTS_DISCOVERY, OPT_CONTAINERS_DISCOVERY, }; static const struct option options[] = { { "serial", 0, NULL, OPT_SERIAL }, { "name", 0, NULL, 'n' }, { "admin", 1, NULL, 'A' }, { "genkey", 1, NULL, 'G' }, { "object", 1, NULL, 'O' }, { "cert", 1, NULL, 'C' }, { "compresscert", 1, NULL, 'Z' }, { "out", 1, NULL, 'o' }, { "in", 1, NULL, 'i' }, { "key-slots-discovery",0, NULL, OPT_KEY_SLOTS_DISCOVERY }, { "containers-discovery",0, NULL, OPT_CONTAINERS_DISCOVERY}, { "send-apdu", 1, NULL, 's' }, { "reader", 1, NULL, 'r' }, { "card-driver", 1, NULL, 'c' }, { "wait", 0, NULL, 'w' }, { "verbose", 0, NULL, 'v' }, { NULL, 0, NULL, 0 } }; static const char *option_help[] = { "Prints the card serial number", "Identify the card and print its name", "authenticate using default 3des key", "Generate key : 9A:06 on card, and output pubkey", "Load an object containerID as defined in 800-73 without leading 0x", "Load a cert where is 9A,9B,9C or 9D", "Load a cert that has been gziped ", "Output file for cert or key", "Inout file for cert", "Key slots discovery (need admin authentication)", "Containers discovery (need admin authentication)", "Sends an APDU in format AA:BB:CC:DD:EE:FF...", "Uses reader number [0]", "Forces the use of driver [auto-detect]", "Wait for a card to be inserted", "Verbose operation. Use several times to enable debug output.", }; static sc_context_t *ctx = NULL; static sc_card_t *card = NULL; static BIO * bp = NULL; static EVP_PKEY * evpkey = NULL; static char *algorithm_identifiers[] = { "3DES – ECB ", "2DES – ECB ", "2DES – CBC ", "3DES – ECB ", "3DES – CBC ", NULL, "RSA 1024 bits", "RSA 2048 bits", "AES-128 – ECB", "AES-128 – CBC", "AES-192 – ECB", "AES-192 – CBC", "AES-256 – ECB", "AES-256 – CBC", "ECC P-224 ", NULL, NULL, "ECC P-256 ", NULL, NULL, "ECC P-384 ", NULL, }; static int load_object(const char * object_id, const char * object_file) { FILE *fp; sc_path_t path; size_t derlen; u8 *der = NULL; u8 *body; size_t bodylen; int r; struct stat stat_buf; if((fp=fopen(object_file, "r"))==NULL){ printf("Cannot open object file, %s %s\n", (object_file)?object_file:"", strerror(errno)); return -1; } stat(object_file, &stat_buf); derlen = stat_buf.st_size; der = malloc(derlen); if (der == NULL) { printf("file %s is too big, %lu\n", object_file, (unsigned long)derlen); return-1 ; } if (1 != fread(der, derlen, 1, fp)) { printf("unable to read file %s\n",object_file); return -1; } /* check if tag and length are valid */ body = (u8 *)sc_asn1_find_tag(card->ctx, der, derlen, 0x53, &bodylen); if (body == NULL || derlen != body - der + bodylen) { fprintf(stderr, "object tag or length not valid\n"); return -1; } sc_format_path(object_id, &path); r = sc_select_file(card, &path, NULL); if (r < 0) { fprintf(stderr, "select file failed\n"); return -1; } /* leave 8 bits for flags, and pass in total length */ r = sc_write_binary(card, 0, der, derlen, derlen<<8); return r; } static int load_cert(const char * cert_id, const char * cert_file, int compress) { X509 * cert = NULL; FILE *fp; u8 buf[1]; size_t buflen = 1; sc_path_t path; u8 *der = NULL; u8 *p; size_t derlen; int r; if((fp=fopen(cert_file, "r"))==NULL){ printf("Cannot open cert file, %s %s\n", cert_file?cert_file:"", strerror(errno)); return -1; } if (compress) { /* file is gziped already */ struct stat stat_buf; stat(cert_file, &stat_buf); derlen = stat_buf.st_size; der = malloc(derlen); if (der == NULL) { printf("file %s is too big, %lu\n", cert_file, (unsigned long)derlen); return-1 ; } if (1 != fread(der, derlen, 1, fp)) { printf("unable to read file %s\n",cert_file); return -1; } } else { cert = PEM_read_X509(fp, &cert, NULL, NULL); if(cert == NULL){ printf("file %s does not conatin PEM-encoded certificate\n", cert_file); return -1 ; } derlen = i2d_X509(cert, NULL); der = malloc(derlen); p = der; i2d_X509(cert, &p); } fclose(fp); sc_hex_to_bin(cert_id, buf,&buflen); switch (buf[0]) { case 0x9a: sc_format_path("0101",&path); break; case 0x9b: sc_format_path("0500",&path); break; case 0x9c: sc_format_path("0100",&path); break; case 0x9d: sc_format_path("0102",&path); break; default: fprintf(stderr,"cert must be 9A, 9B, 9C or 9D\n"); return 2; } r = sc_select_file(card, &path, NULL); if (r < 0) { fprintf(stderr, "select file failed\n"); return -1; } /* we pass length and 8 bits of flag to card-piv.c write_binary */ /* pass in its a cert and if needs compress */ r = sc_write_binary(card, 0, der, derlen, (derlen<<8) | (compress<<4) | 1); return r; } static int admin_mode(const char* admin_info) { int r; u8 opts[3]; size_t buflen = 2; if (strlen(admin_info) == 7 && (admin_info[0] == 'A' || admin_info[0] == 'M') && admin_info[1] == ':' && (sc_hex_to_bin(admin_info+2, opts+1, &buflen) == 0) && buflen == 2) { opts[0] = admin_info[0]; } else { fprintf(stderr, " admin_mode params ::\n"); return -1; } r = sc_card_ctl(card, SC_CARDCTL_PIV_AUTHENTICATE, &opts); if (r) fprintf(stderr, " admin_mode failed %d\n", r); return r; } /* generate a new key pair, and save public key in newkey */ static int gen_key(const char * key_info) { int r; u8 buf[2]; size_t buflen = 2; sc_cardctl_piv_genkey_info_t keydata = {0, 0, 0, 0, NULL, 0, NULL, 0, NULL, 0}; unsigned long expl; u8 expc[4]; #if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined(OPENSSL_NO_EC) int nid = -1; #endif sc_hex_to_bin(key_info, buf, &buflen); if (buflen != 2) { fprintf(stderr, ": invalid, example: 9A:06\n"); return 2; } switch (buf[0]) { case 0x9a: case 0x9b: case 0x9c: case 0x9d: keydata.key_num = buf[0]; break; default: fprintf(stderr, ": must be 9A, 9B, 9C or 9D\n"); return 2; } switch (buf[1]) { case 0x05: keydata.key_bits = 3072; break; case 0x06: keydata.key_bits = 1024; break; case 0x07: keydata.key_bits = 2048; break; #if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined(OPENSSL_NO_EC) case 0x11: keydata.key_bits = 0; nid = NID_X9_62_prime256v1; /* We only support one curve per algid */ break; case 0x14: keydata.key_bits = 0; nid = NID_secp384r1; break; #endif default: fprintf(stderr, ": algid=RSA - 05, 06, 07 for 3072, 1024, 2048;EC - 11, 14 for 256, 384\n"); return 2; } keydata.key_algid = buf[1]; r = sc_card_ctl(card, SC_CARDCTL_PIV_GENERATE_KEY, &keydata); if (r) { fprintf(stderr, "gen_key failed %d\n", r); return r; } evpkey = EVP_PKEY_new(); if (keydata.key_bits > 0) { /* RSA key */ RSA * newkey = NULL; newkey = RSA_new(); if (newkey == NULL) { fprintf(stderr, "gen_key RSA_new failed %d\n",r); return -1; } newkey->n = BN_bin2bn(keydata.pubkey, keydata.pubkey_len, newkey->n); expl = keydata.exponent; expc[3] = (u8) expl & 0xff; expc[2] = (u8) (expl >>8) & 0xff; expc[1] = (u8) (expl >>16) & 0xff; expc[0] = (u8) (expl >>24) & 0xff; newkey->e = BN_bin2bn(expc, 4, newkey->e); if (verbose) RSA_print_fp(stdout, newkey,0); EVP_PKEY_assign_RSA(evpkey, newkey); } else { /* EC key */ #if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined(OPENSSL_NO_EC) int i; BIGNUM *x; BIGNUM *y; EC_KEY * eckey = NULL; EC_GROUP * ecgroup = NULL; EC_POINT * ecpoint = NULL; ecgroup = EC_GROUP_new_by_curve_name(nid); EC_GROUP_set_asn1_flag(ecgroup, OPENSSL_EC_NAMED_CURVE); ecpoint = EC_POINT_new(ecgroup); /* PIV returns 04||x||y and x and y are the same size */ i = (keydata.ecpoint_len - 1)/2; x = BN_bin2bn(keydata.ecpoint + 1, i, NULL); y = BN_bin2bn(keydata.ecpoint + 1 + i, i, NULL) ; r = EC_POINT_set_affine_coordinates_GFp(ecgroup, ecpoint, x, y, NULL); eckey = EC_KEY_new(); r = EC_KEY_set_group(eckey, ecgroup); r = EC_KEY_set_public_key(eckey, ecpoint); if (verbose) EC_KEY_print_fp(stdout, eckey, 0); EVP_PKEY_assign_EC_KEY(evpkey, eckey); #else fprintf(stderr, "This build of OpenSSL does not support EC keys\n"); r = 1; #endif /* OPENSSL_NO_EC */ } if (bp) r = i2d_PUBKEY_bio(bp, evpkey); if (evpkey) EVP_PKEY_free(evpkey); return r; } static int key_slots_discovery(void) { sc_apdu_t apdu; u8 sbuf[4] = {0x5C, 0x02, 0x3F, 0xF7}; u8 rbuf[SC_MAX_APDU_BUFFER_SIZE*3], *data = NULL; unsigned int cla_out, tag_out; size_t r, i, data_len; sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0xCB, 0x3F, 0xFF); apdu.lc = sizeof(sbuf); apdu.le = 256; apdu.data = sbuf; apdu.datalen = sizeof(sbuf); apdu.resp = rbuf; apdu.resplen = sizeof(rbuf); r = sc_transmit_apdu(card, &apdu); if (r) { fprintf(stderr, "APDU transmit failed: %s\n", sc_strerror(r)); return 1; } data = rbuf; if (*data != 0x53) { fprintf(stderr, "Invalid 'GET DATA' response\n"); return 1; } if (*(data + 1) & 0x80) { for (i=0, data_len=0; i < (*(data + 1) & 0x7F); i++) data_len = data_len * 0x100 + *(data + 2 + i); data += 2 + i; } else { data_len = *(data + 1); data += 2; } if (data_len % 12) { fprintf(stderr, "Invalid key discovery data length\n"); return 1; } for (i=0;i 20) { fprintf(stderr, "Invalid algorithm identifier\n"); return 1; } printf("%02X(%s): %02X(%s)", *(slot + 0), *(slot + 7) ? "loaded" : "not loaded", *(slot + 1), algorithm_identifiers[*(slot + 1)]); if (*(slot + 2) == 0) printf (", "); else if (*(slot + 2) == 0x35) printf (", PIV-ADMIN "); else if (*(slot + 2) == 0x29) printf (", MutualAuth"); else { fprintf(stderr, "Invalid role\n"); return 1; } printf(", ACLs %02X:%02X %02X:%02X", *(slot + 8), *(slot + 9), *(slot + 10), *(slot + 11)); printf("\n"); } return SC_SUCCESS; } static int containers_discovery(void) { sc_apdu_t apdu; u8 sbuf[4] = {0x5C, 0x02, 0x3F, 0xF6}; u8 rbuf[SC_MAX_APDU_BUFFER_SIZE*3], *data = NULL; unsigned int cla_out, tag_out; size_t r, i, data_len; sc_format_apdu(card, &apdu, SC_APDU_CASE_4_SHORT, 0xCB, 0x3F, 0xFF); apdu.lc = sizeof(sbuf); apdu.le = 256; apdu.data = sbuf; apdu.datalen = sizeof(sbuf); apdu.resp = rbuf; apdu.resplen = sizeof(rbuf); r = sc_transmit_apdu(card, &apdu); if (r) { fprintf(stderr, "APDU transmit failed: %s\n", sc_strerror(r)); return 1; } data = rbuf; if (*data != 0x53) { fprintf(stderr, "Invalid 'GET DATA' response\n"); return 1; } if (*(data + 1) & 0x80) { for (i=0, data_len=0; i < (*(data + 1) & 0x7F); i++) data_len = data_len * 0x100 + *(data + 2 + i); data += 2 + i; } else { data_len = *(data + 1); data += 2; } if (data_len % 9) { fprintf(stderr, "Invalid containers discovery data length\n"); return 1; } for (i=0;i SC_MAX_APDU_BUFFER_SIZE+2) { fprintf(stderr, "APDU too long, (must be at most %d bytes).\n", SC_MAX_APDU_BUFFER_SIZE+2); return 2; } if (len0 < 4) { fprintf(stderr, "APDU too short (must be at least 4 bytes).\n"); return 2; } len = len0; p = buf; memset(&apdu, 0, sizeof(apdu)); apdu.cla = *p++; apdu.ins = *p++; apdu.p1 = *p++; apdu.p2 = *p++; apdu.resp = rbuf; apdu.resplen = sizeof(rbuf); len -= 4; if (len > 1) { apdu.lc = *p++; len--; memcpy(sbuf, p, apdu.lc); apdu.data = sbuf; apdu.datalen = apdu.lc; if (len < apdu.lc) { fprintf(stderr, "APDU too short (need %lu bytes).\n", (unsigned long) apdu.lc-len); return 2; } len -= apdu.lc; if (len) { apdu.le = *p++; if (apdu.le == 0) apdu.le = 256; len--; apdu.cse = SC_APDU_CASE_4_SHORT; } else apdu.cse = SC_APDU_CASE_3_SHORT; if (len) { fprintf(stderr, "APDU too long (%lu bytes extra).\n", (unsigned long)len); return 2; } } else if (len == 1) { apdu.le = *p++; if (apdu.le == 0) apdu.le = 256; len--; apdu.cse = SC_APDU_CASE_2_SHORT; } else apdu.cse = SC_APDU_CASE_1; printf("Sending: "); for (r = 0; r < len0; r++) printf("%02X ", buf[r]); printf("\n"); r = sc_transmit_apdu(card, &apdu); if (r) { fprintf(stderr, "APDU transmit failed: %s\n", sc_strerror(r)); return 1; } printf("Received (SW1=0x%02X, SW2=0x%02X)%s\n", apdu.sw1, apdu.sw2, apdu.resplen ? ":" : ""); if (apdu.resplen) util_hex_dump_asc(stdout, apdu.resp, apdu.resplen, -1); } return 0; } static void print_serial(sc_card_t *in_card) { int r; sc_serial_number_t serial; r = sc_card_ctl(in_card, SC_CARDCTL_GET_SERIALNR, &serial); if (r < 0) fprintf(stderr, "sc_card_ctl(*, SC_CARDCTL_GET_SERIALNR, *) failed %d\n", r); else util_hex_dump_asc(stdout, serial.value, serial.len, -1); } int main(int argc, char * const argv[]) { int err = 0, r, c, long_optind = 0; int do_send_apdu = 0; int do_admin_mode = 0; int do_gen_key = 0; int do_load_cert = 0; int do_load_object = 0; int compress_cert = 0; int do_print_serial = 0; int do_print_name = 0; int do_key_slots_discovery = 0; int do_containers_discovery = 0; int action_count = 0; const char *opt_driver = NULL; const char *out_file = NULL; const char *in_file = NULL; const char *cert_id = NULL; const char *object_id = NULL; const char *key_info = NULL; const char *admin_info = NULL; sc_context_param_t ctx_param; setbuf(stderr, NULL); setbuf(stdout, NULL); while (1) { c = getopt_long(argc, argv, "nA:G:O:Z:C:i:o:fvs:c:w", options, &long_optind); if (c == -1) break; if (c == '?') util_print_usage_and_die(app_name, options, option_help); switch (c) { case OPT_SERIAL: do_print_serial = 1; action_count++; break; case OPT_KEY_SLOTS_DISCOVERY: do_key_slots_discovery = 1; action_count++; break; case OPT_CONTAINERS_DISCOVERY: do_containers_discovery = 1; action_count++; break; case 's': opt_apdus = (char **) realloc(opt_apdus, (opt_apdu_count + 1) * sizeof(char *)); opt_apdus[opt_apdu_count] = optarg; do_send_apdu++; if (opt_apdu_count == 0) action_count++; opt_apdu_count++; break; case 'n': do_print_name = 1; action_count++; break; case 'A': do_admin_mode = 1; admin_info = optarg; action_count++; break; case 'G': do_gen_key = 1; key_info = optarg; action_count++; break; case 'O': do_load_object = 1; object_id = optarg; action_count++; break; case 'Z': compress_cert = 1; case 'C': do_load_cert = 1; cert_id = optarg; action_count++; break; case 'i': in_file = optarg; break; case 'o': out_file = optarg; break; case 'r': opt_reader = optarg; break; case 'v': verbose++; break; case 'c': opt_driver = optarg; break; case 'w': opt_wait = 1; break; } } if (action_count == 0) util_print_usage_and_die(app_name, options, option_help); CRYPTO_malloc_init(); ERR_load_crypto_strings(); OpenSSL_add_all_algorithms(); if (out_file) { bp = BIO_new(BIO_s_file()); BIO_write_filename(bp, (char *)out_file); } else { bp = BIO_new(BIO_s_file()); BIO_set_fp(bp,stdout,BIO_NOCLOSE); } memset(&ctx_param, 0, sizeof(sc_context_param_t)); ctx_param.app_name = app_name; r = sc_context_create(&ctx, &ctx_param); if (r != SC_SUCCESS) { fprintf(stderr, "Failed to establish context: %s\n", sc_strerror(r)); return 1; } /* Only change if not in opensc.conf */ if (verbose > 1 && ctx->debug == 0) { ctx->debug = verbose; ctx->debug_file = stderr; } if (action_count <= 0) goto end; if (opt_driver != NULL) { err = sc_set_card_driver(ctx, opt_driver); if (err) { fprintf(stderr, "Driver '%s' not found!\n", opt_driver); err = 1; goto end; } } err = util_connect_card(ctx, &card, opt_reader, opt_wait, verbose); if (err) goto end; if (do_admin_mode) { if ((err = admin_mode(admin_info))) goto end; action_count--; } if (do_send_apdu) { /* can use pin before load cert for a beta card */ if ((err = send_apdu())) goto end; action_count--; } if (do_gen_key) { if ((err = gen_key(key_info))) goto end; action_count--; } if (do_load_object) { if ((err = load_object(object_id, in_file))) goto end; action_count--; } if (do_load_cert) { if ((err = load_cert(cert_id, in_file, compress_cert))) goto end; action_count--; } if (do_print_serial) { if (verbose) printf("Card serial number:"); print_serial(card); action_count--; } if (do_print_name) { if (verbose) printf("Card name: "); printf("%s\n", card->name); action_count--; } if (do_key_slots_discovery) { if (!do_admin_mode) { fprintf(stderr, "Key slots discovery needs admin authentication\n"); err = 1; goto end; } if (verbose) printf("Key slots discovery: "); if ((err = key_slots_discovery())) goto end; } if (do_containers_discovery) { if (!do_admin_mode) { fprintf(stderr, "Containers discovery needs admin authentication\n"); err = 1; goto end; } if (verbose) printf("Containers discovery: "); if ((err = containers_discovery())) goto end; } end: if (bp) BIO_free(bp); if (card) { sc_unlock(card); sc_disconnect_card(card); } if (ctx) sc_release_context(ctx); ERR_print_errors_fp(stderr); return err; }