diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index e7984989..0a5131ff 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -5,7 +5,7 @@ EXTRA_DIST = Makefile.mak noinst_HEADERS = util.h bin_PROGRAMS = opensc-tool opensc-explorer pkcs15-tool pkcs15-crypt \ - pkcs11-tool cardos-tool eidenv + pkcs11-tool cardos-tool eidenv openpgp-tool if ENABLE_OPENSSL bin_PROGRAMS += cryptoflex-tool pkcs15-init netkey-tool piv-tool westcos-tool endif @@ -42,6 +42,8 @@ netkey_tool_SOURCES = netkey-tool.c netkey_tool_LDADD = $(OPTIONAL_OPENSSL_LIBS) westcos_tool_SOURCES = westcos-tool.c util.c westcos_tool_LDADD = $(OPTIONAL_OPENSSL_LIBS) +openpgp_tool_SOURCES = openpgp-tool.c util.c +openpgp_tool_LDADD = $(OPTIONAL_OPENSSL_LIBS) if WIN32 opensc_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc @@ -56,4 +58,5 @@ cardos_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc eidenv_SOURCES += $(top_builddir)/win32/versioninfo.rc netkey_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc westcos_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc +openpgp_tool_SOURCES += $(top_builddir)/win32/versioninfo.rc endif diff --git a/src/tools/openpgp-tool.c b/src/tools/openpgp-tool.c new file mode 100644 index 00000000..6764a172 --- /dev/null +++ b/src/tools/openpgp-tool.c @@ -0,0 +1,372 @@ +/* + * openpgp-tool.c: OpenPGP card utility + * + * Copyright (C) 2012 Peter Marschall + * + * 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 +#ifndef _WIN32 +#include +#endif +#include +#include + +#include "common/compat_getopt.h" +#include "libopensc/opensc.h" +#include "libopensc/asn1.h" +#include "libopensc/cards.h" +#include "util.h" + +/* define structures */ +struct ef_name_map { + const char *name; + const char *env_name; + const char *ef; + char *(*prettify_value)(char *); +}; + +/* declare functions */ +static void show_version(void); +static void show_help(void); +static char *prettify_language(char *str); +static char *prettify_gender(char *str); +static void display_data(const struct ef_name_map *mapping, char *value); +static int decode_options(int argc, char **argv); +static void do_openpgp(sc_card_t *card); +static int read_transp(sc_card_t *card, const char *pathstring, unsigned char *buf, int buflen); +static void bintohex(char *buf, int len); + +/* define global variables */ +static char *opt_reader = NULL; +static int stats = 0; +static int opt_wait = 0; +static char *exec_program = NULL; +static int exit_status = EXIT_FAILURE; + +static const struct option options[] = { + { "reader", required_argument, NULL, 'r' }, + { "print", no_argument, NULL, 'p' }, + { "exec", required_argument, NULL, 'x' }, + { "stats", no_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + { "wait", no_argument, NULL, 'w' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + +/* Probably not used, but needed to build on Windows */ +static const char *app_name = "openpgp-tool"; + +static const struct ef_name_map openpgp_data[] = { + { "Account", "OPENGPG_ACCOUNT", "3F00:005E", NULL }, + { "URL", "OPENPGP_URL", "3F00:5F50", NULL }, + { "Name", "OPENPGP_NAME", "3F00:0065:005B", NULL }, + { "Language", "OPENPGP_LANG", "3F00:0065:5F2D", prettify_language }, + { "Gender", "OPENPGP_GENDER", "3F00:0065:5F35", prettify_gender }, + { "DO 0101", "OPENPGP_DO0101", "3F00:0101", NULL }, + { "DO 0102", "OPENPGP_DO0102", "3F00:0102", NULL }, +// { "DO 0103", "OPENPGP_DO0103", "3F00:0103", NULL }, +// { "DO 0104", "OPENPGP_DO0104", "3F00:0104", NULL }, + { NULL, NULL, NULL, NULL } +}; + + +static void show_version(void) +{ + fprintf(stderr, + "openpgp-tool - OpenPGP card utility version " PACKAGE_VERSION "\n" + "\n" + "Copyright (c) 2012 Peter Marschall \n" + "Licensed under LGPL v2\n"); +} + + +static void show_help(void) +{ + show_version(); + fprintf(stderr, + "-h --help - show this text and exit\n" + "-v --version - show version and exit\n" + "-r --reader - the reader to use\n" + "-w --wait - wait for a card to be inserted\n" + "-p --print - print the datafile\n" + "-t --stats - show usage counts of keys\n" + "-x --exec - execute a program with data in env vars.\n"); +} + + +/* prettify language */ +static char *prettify_language(char *str) +{ + if (str != NULL) { + switch (strlen(str)) { + case 8: memmove(str+7, str+6, 1+strlen(str+6)); + str[6] = ','; + /* fall through */ + case 6: memmove(str+5, str+4, 1+strlen(str+4)); + str[4] = ','; + /* fall through */ + case 4: memmove(str+3, str+2, 1+strlen(str+2)); + str[2] = ','; + /* fall through */ + case 2: return str; + } + } + return NULL; +} + + +/* convert the raw ISO-5218 SEX value to an english word */ +static char *prettify_gender(char *str) +{ + if (str != NULL) { + switch (*str) { + case '0': return "unknown"; + case '1': return "male"; + case '2': return "female"; + case '9': return "not applicable"; + } + } + return NULL; +} + + +static void display_data(const struct ef_name_map *mapping, char *value) +{ + if (mapping != NULL && value != NULL) { + if (mapping->prettify_value != NULL) + value = mapping->prettify_value(value); + + if (value != NULL) { + if (exec_program) { + char *envvar; + + envvar = malloc(strlen(mapping->env_name) + + strlen(value) + 2); + if (envvar != NULL) { + strcpy(envvar, mapping->env_name); + strcat(envvar, "="); + strcat(envvar, value); + putenv(envvar); + } + } else { + const char *label = mapping->name; + + printf("%s:%*s%s\n", label, 10-strlen(label), "", value); + } + } + } +} + + +static int decode_options(int argc, char **argv) +{ + int c; + + while ((c = getopt_long(argc, argv,"pwtr:x:hV", options, (int *) 0)) != EOF) { + switch (c) { + case 'r': + opt_reader = optarg; + break; + case 't': + stats = !stats; + break; + case 'x': + if (exec_program) + free(exec_program); + exec_program = strdup(optarg); + break; + case 'h': + show_help(); + exit(EXIT_SUCCESS); + break; + case 'p': + break; + case 'w': + opt_wait = 1; + break; + case 'V': + show_version(); + exit(EXIT_SUCCESS); + break; + default: + show_help(); + exit(EXIT_FAILURE); + } + } + + return(optind); +} + + +static void do_openpgp(sc_card_t *card) +{ + int i; + unsigned char buf[2048]; + + for (i = 0; openpgp_data[i].ef != NULL; i++) { + sc_path_t path; + sc_file_t *file; + size_t count; + size_t offset = 0; + int r; + + sc_format_path(openpgp_data[i].ef, &path); + r = sc_select_file(card, &path, &file); + + if (r) { + fprintf(stderr, "Failed to select EF %s: %s\n", + openpgp_data[i].ef, sc_strerror(r)); + goto out; + } + + count = file->size; + while (count > 0) { + int c = count > sizeof(buf) ? sizeof(buf) : count; + + r = sc_read_binary(card, offset, buf+offset, c, 0); + if (r < 0) { + fprintf(stderr, "%s: read failed - %s\n", + openpgp_data[i].ef, sc_strerror(r)); + goto out; + } + if (r != c) { + fprintf(stderr, "%s: expecting %d, got only %d bytes\n", + openpgp_data[i].ef, c, r); + goto out; + } + + offset += r; + count -= r; + } + + buf[file->size] = '\0'; + + if (file->size > 0) { + display_data(openpgp_data + i, buf); + } + } + + exit_status = EXIT_SUCCESS; + +out: + return; +} + + +/* Select and read a transparent EF */ +static int read_transp(sc_card_t *card, const char *pathstring, unsigned char *buf, int buflen) +{ + sc_path_t path; + int r; + + sc_format_path(pathstring, &path); + r = sc_select_file(card, &path, NULL); + if (r < 0) + fprintf(stderr, "\nFailed to select file %s: %s\n", pathstring, sc_strerror(r)); + else { + r = sc_read_binary(card, 0, buf, buflen, 0); + if (r < 0) + fprintf(stderr, "\nFailed to read %s: %s\n", pathstring, sc_strerror(r)); + } + + return r; +} + + +/* Hex-encode the buf, 2*len+1 bytes must be reserved. E.g. {'1','2'} -> {'3','1','3','2','\0'} */ +static void bintohex(char *buf, int len) +{ + static const char hextable[] = "0123456789ABCDEF"; + int i; + + for (i = len - 1; i >= 0; i--) { + unsigned char c = (unsigned char) buf[i]; + + buf[2 * i + 1] = hextable[c % 16]; + buf[2 * i] = hextable[c / 16]; + } +} + + +int main(int argc, char **argv) +{ + sc_context_t *ctx = NULL; + sc_context_param_t ctx_param; + sc_card_t *card = NULL; + int r; + int argind = 1; + + /* get options */ + argind = decode_options(argc, argv); + + /* connect to the card */ + memset(&ctx_param, 0, sizeof(ctx_param)); + ctx_param.ver = 0; + ctx_param.app_name = app_name; + + r = sc_context_create(&ctx, &ctx_param); + if (r) { + fprintf(stderr, "Failed to establish context: %s\n", + sc_strerror(r)); + return 1; + } + r = util_connect_card(ctx, &card, opt_reader, opt_wait, 0); + if (r) { + fprintf(stderr, "Failed to connect to card: %s\n", + sc_strerror(r)); + return 1; + } + + /* Check card type */ + if ((card->type != SC_CARD_TYPE_OPENPGP_V1) && + (card->type != SC_CARD_TYPE_OPENPGP_V2)) { + fprintf(stderr, "Not an OpenPGP card!\n"); + goto out; + } + + do_openpgp(card); + + if (argind > argc) + show_help(); + + if (strcmp(argv[argind], "load") == 0) { + ++argind; + } + else if (strcmp(argv[argind], "store") == 0) { + ++argind; + } + + if (exec_program) { + char *const largv[] = {exec_program, NULL}; + sc_unlock(card); + sc_disconnect_card(card); + sc_release_context(ctx); + execv(exec_program, largv); + /* we should not get here */ + perror("execv()"); + exit(1); + } + +out: + sc_unlock(card); + sc_disconnect_card(card); + sc_release_context(ctx); + exit(exit_status); +}