diff options
author | Repo Admin <[email protected]> | 2003-08-05 17:11:04 +0000 |
---|---|---|
committer | Repo Admin <[email protected]> | 2003-08-05 17:11:04 +0000 |
commit | 9ca4830a5b8392bc7bb01211407c876fd40b49d4 (patch) | |
tree | 5b5eb60b48cea2f86d01b8d9a458a2fa4fc0a2b2 /scd | |
parent | minor changes to make make distcheck happy (diff) | |
download | gnupg-9ca4830a5b8392bc7bb01211407c876fd40b49d4.tar.gz gnupg-9ca4830a5b8392bc7bb01211407c876fd40b49d4.zip |
This commit was manufactured by cvs2svn to create branch
'GNUPG-1-9-BRANCH'.
Diffstat (limited to 'scd')
-rw-r--r-- | scd/ChangeLog | 242 | ||||
-rw-r--r-- | scd/Makefile.am | 72 | ||||
-rw-r--r-- | scd/apdu.c | 558 | ||||
-rw-r--r-- | scd/apdu.h | 73 | ||||
-rw-r--r-- | scd/app-common.h | 128 | ||||
-rw-r--r-- | scd/app-openpgp.c | 1482 | ||||
-rw-r--r-- | scd/app.c | 278 | ||||
-rw-r--r-- | scd/card-common.h | 73 | ||||
-rw-r--r-- | scd/card-p15.c | 502 | ||||
-rw-r--r-- | scd/card.c | 564 | ||||
-rw-r--r-- | scd/command.c | 1034 | ||||
-rw-r--r-- | scd/iso7816.c | 371 | ||||
-rw-r--r-- | scd/iso7816.h | 56 | ||||
-rw-r--r-- | scd/sc-copykeys.c | 731 | ||||
-rw-r--r-- | scd/sc-investigate.c | 209 | ||||
-rw-r--r-- | scd/scdaemon.c | 638 | ||||
-rw-r--r-- | scd/scdaemon.h | 127 |
17 files changed, 7138 insertions, 0 deletions
diff --git a/scd/ChangeLog b/scd/ChangeLog new file mode 100644 index 000000000..ad4b0518c --- /dev/null +++ b/scd/ChangeLog @@ -0,0 +1,242 @@ +2003-07-31 Werner Koch <[email protected]> + + * Makefile.am (scdaemon_LDADD): Added INTLLIBS. + +2003-07-28 Werner Koch <[email protected]> + + * app-openpgp.c (do_setattr): Change implementation. Allow all + useful DOs. + +2003-07-27 Werner Koch <[email protected]> + + Adjusted for gcry_mpi_print and gcry_mpi_scan API change. + +2003-07-24 Werner Koch <[email protected]> + + * app-openpgp.c (do_learn_status): Print more status information. + (app_select_openpgp): Store the card version. + (store_fpr): Add argument card_version and fix DOs for old cards. + (app_openpgp_storekey): Likewise. + +2003-07-23 Werner Koch <[email protected]> + + * command.c (cmd_pkauth): New. + (cmd_setdata): Check whether data was given at all to avoid + passing 0 to malloc. + + * app.c (app_auth): New. + * app-openpgp.c (do_auth): New. + +2003-07-22 Werner Koch <[email protected]> + + * command.c (cmd_passwd): New. + * app.c (app_change_pin): New. + * app-openpgp.c (do_change_pin): New. + * iso7816.c (iso7816_reset_retry_counter): Implemented. + + * sc-investigate.c (main): New option --gen-random. + * iso7816.c (iso7816_get_challenge): Don't create APDUs with a + length larger than 255. + +2003-07-17 Werner Koch <[email protected]> + + * command.c (cmd_random): New command RANDOM. + + * iso7816.c (map_sw): New. Use it in this file to return + meaningful error messages. Changed all public fucntions to return + a gpg_error_t. + (iso7816_change_reference_data): New. + * apdu.c (apdu_open_reader): Use faked status words for soem + system errors. + +2003-07-16 Werner Koch <[email protected]> + + * apdu.c (apdu_send_simple): Use apdu_send_le so that we can + specify not to send Le as it should be. + +2003-07-15 Werner Koch <[email protected]> + + * Makefile.am: Add sc-copykeys program. + * sc-copykeys.c: New. + * app-openpgp.c (app_openpgp_storekey): New. + (app_openpgp_cardinfo): New. + (count_bits): New. + (store_fpr): And use it here to get the actual length in bit. + +2003-07-03 Werner Koch <[email protected]> + + * app-openpgp.c (do_setattr): Add setting of the URL. + (app_select_openpgp): Dump card data only in very verbose mode. + (do_decipher): New. + +2003-07-02 Werner Koch <[email protected]> + + * app-openpgp.c (get_sig_counter): New. + (do_sign): Print the signature counter and enable the PIN callback. + (do_genkey): Implement the PIN callback. + +2003-07-01 Werner Koch <[email protected]> + + * app-openpgp.c (store_fpr): Fixed fingerprint calculation. + +2003-06-26 Werner Koch <[email protected]> + + * app-openpgp.c (find_tlv): Fixed length header parsing. + + * app.c (app_genkey): New. + * command.c (cmd_genkey): New. + +2003-06-25 Werner Koch <[email protected]> + + * command.c (percent_plus_unescape): New. + (cmd_setattr): New. + +2003-06-24 Werner Koch <[email protected]> + + * command.c (send_status_info): New. + + * app-openpgp.c (app_select_openpgp): Replace SLOT arg by APP arg + and setup the function pointers in APP on success. Changed callers. + * app.c: New. + * app-common.h: New. + * scdaemon.h (APP): New type to handle applications. + (server_control_s): Add an APP context field. + + * command.c (cmd_serialno): Handle applications. + (cmd_pksign): Ditto. + (cmd_pkdecrypt): Ditto. + (reset_notify): Ditto. + (cmd_learn): For now return error for application contexts. + (cmd_readcert): Ditto. + (cmd_readkey): Ditto. + +2003-06-04 Werner Koch <[email protected]> + + * card.c (map_sc_err): Renamed gpg_make_err to gpg_err_make. + + Renamed error codes from INVALID to INV and removed _ERROR suffixes. + +2003-06-03 Werner Koch <[email protected]> + + Changed all error codes in all files to the new libgpg-error scheme. + + * scdaemon.h: Include gpg-error.h and errno.h + * card.c (map_sc_err): Use unknown for the error source. + * Makefile.am: Link with libgpg-error + +2003-05-14 Werner Koch <[email protected]> + + * atr.c, atr.h: New. + * sc-investigate.c: Dump the ATR in a human readable format. + +2003-05-08 Werner Koch <[email protected]> + + * scdaemon.h (DBG_CARD_IO_VALUE): New. + + * sc-investigate.c: New. + * scdaemon.c (main): Removed --print-atr option. + + * iso7816.c, iso7816.h, app-openpgp.c: New. + +2003-04-29 Werner Koch <[email protected]> + + * scdaemon.c: New options --print-atr and --reader-port + * apdu.c, apdu.h: New + + * card.c, card-p15.c, card-dinsig.c: Allow build without OpenSC. + + * Makefile.am (LDFLAGS): Removed. + + * command.c (register_commands): Adjusted for new Assuan semantics. + +2002-08-21 Werner Koch <[email protected]> + + * scdaemon.c (main): New option --daemon so that the program is + not accidently started in the background. + +2002-08-16 Werner Koch <[email protected]> + + * scdaemon.c: Include i18n.h. + + * card-common.h (struct p15_private_s): Forward declaration. Add + it to card_ctx_s. + * card.c (card_close): Make sure private data is released. + (card_enum_certs): New. + * card-p15.c (p15_release_private_data): New. + (init_private_data): New to work around an OpenSC weirdness. + (p15_enum_keypairs): Do an OpenSC get_objects only once. + (p15_enum_certs): New. + (card_p15_bind): Bind new function. + * command.c (cmd_learn): Return information about the certificates. + +2002-08-09 Werner Koch <[email protected]> + + * card.c (card_get_serial_and_stamp): Use the tokeinfo serial + number as a fallback. Add a special prefix for serial numbers. + +2002-07-30 Werner Koch <[email protected]> + + Changes to cope with OpenSC 0.7.0: + + * card.c: Removed the check for the packed opensc version. + Changed include file names of opensc. + (map_sc_err): Adjusted error codes for new opensc version. + * card-p15.c: Changed include filename of opensc. + * card-dinsig.c: Ditto. + + * card-p15.c (p15_decipher): Add flags argument to OpenSC call. + +2002-07-24 Werner Koch <[email protected]> + + * card.c (find_simple_tlv, find_iccsn): New. + (card_get_serial_and_stamp): Improved serial number parser. + +2002-06-27 Werner Koch <[email protected]> + + * scdaemon.c (main): Use GNUPG_DEFAULT_HOMEDIR constant. + +2002-06-15 Werner Koch <[email protected]> + + * card-dinsig.c: Documented some stuff from the DIN norm. + +2002-04-15 Werner Koch <[email protected]> + + * command.c (cmd_pksign, cmd_pkdecrypt): Use a copy of the key ID. + +2002-04-12 Werner Koch <[email protected]> + + * scdaemon.c: New option --debug-sc N. + * card.c (card_open): set it here. + + * card-p15.c (p15_prepare_key): Factored out common code from ... + (p15_sign, p15_decipher): here and made the decryption work the + regular way. + +2002-04-10 Werner Koch <[email protected]> + + * card.c (card_open): Return immediately when no reader is available. + +2002-03-27 Werner Koch <[email protected]> + + * card.c (card_open, card_close): Adjusted for changes in OpenSC. + +2002-03-10 Werner Koch <[email protected]> + + * card-p15.c, card-dinsig.c, card-common.h: New. + * card.c: Factored most code out to the new modules, so that we + can better support different types of card applications. + +2002-01-26 Werner Koch <[email protected]> + + * scdaemon.c scdaemon.h, command.c: New. Based on the code from + the gpg-agent. + + Copyright 2002 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/scd/Makefile.am b/scd/Makefile.am new file mode 100644 index 000000000..0771beb60 --- /dev/null +++ b/scd/Makefile.am @@ -0,0 +1,72 @@ +# Copyright (C) 2002, 2003 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# GnuPG 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +## Process this file with automake to produce Makefile.in + +localedir = $(datadir)/locale +INCLUDES = -I../intl -DLOCALEDIR=\"$(localedir)\" + +bin_PROGRAMS = scdaemon sc-investigate sc-copykeys + +AM_CPPFLAGS = -I$(top_srcdir)/common $(OPENSC_CFLAGS) $(LIBGCRYPT_CFLAGS) \ + $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) + +scdaemon_SOURCES = \ + scdaemon.c scdaemon.h \ + command.c card.c \ + card-common.h \ + card-p15.c card-dinsig.c \ + apdu.c apdu.h \ + iso7816.c iso7816.h \ + app.c app-common.h \ + app-openpgp.c + +scdaemon_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a \ + $(OPENSC_LIBS) $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) \ + -lgpg-error @INTLLIBS@ -ldl + +sc_investigate_SOURCES = \ + sc-investigate.c scdaemon.h \ + apdu.c apdu.h \ + iso7816.c iso7816.h \ + app.c app-common.h \ + app-openpgp.c \ + atr.c atr.h + +sc_investigate_LDADD = \ + ../jnlib/libjnlib.a ../common/libcommon.a \ + $(LIBGCRYPT_LIBS) @INTLLIBS@ -lgpg-error -ldl + + +sc_copykeys_SOURCES = \ + sc-copykeys.c scdaemon.h \ + apdu.c apdu.h \ + iso7816.c iso7816.h \ + app.c app-common.h \ + app-openpgp.c \ + atr.c atr.h + +sc_copykeys_LDADD = \ + ../jnlib/libjnlib.a ../common/libcommon.a \ + ../common/libsimple-pwquery.a \ + $(LIBGCRYPT_LIBS) -lgpg-error @INTLLIBS@ -ldl + + + + + diff --git a/scd/apdu.c b/scd/apdu.c new file mode 100644 index 000000000..6fec584b9 --- /dev/null +++ b/scd/apdu.c @@ -0,0 +1,558 @@ +/* apdu.c - ISO 7816 APDU functions and low level I/O + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dlfcn.h> +#include <assert.h> + +#include "scdaemon.h" +#include "apdu.h" + +#define HAVE_CTAPI 1 + +#define MAX_READER 4 /* Number of readers we support concurrently. */ +#define CARD_CONNECT_TIMEOUT 1 /* Number of seconds to wait for + insertion of the card (1 = don't wait). */ + + + +/* A global table to keep track of active readers. */ +static struct { + int used; /* True if slot is used. */ + unsigned short port; /* port number0 = unused, 1 - dev/tty */ + int status; + unsigned char atr[33]; + size_t atrlen; +} reader_table[MAX_READER]; + + +/* ct API function pointer. */ +static char (*CT_init) (unsigned short ctn, unsigned short Pn); +static char (*CT_data) (unsigned short ctn, unsigned char *dad, + unsigned char *sad, unsigned short lc, + unsigned char *cmd, unsigned short *lr, + unsigned char *rsp); +static char (*CT_close) (unsigned short ctn); + + + + + +/* + Helper + */ + + +/* Find an unused reader slot for PORT and put it into the reader + table. Return -1 on error or the index into the reader table. */ +static int +new_reader_slot (int port) +{ + int i, reader = -1; + + if (port < 0 || port > 0xffff) + { + log_error ("new_reader_slot: invalid port %d requested\n", port); + return -1; + } + + for (i=0; i < MAX_READER; i++) + { + if (reader_table[i].used && reader_table[i].port == port) + { + log_error ("new_reader_slot: requested port %d already in use\n", + reader); + return -1; + } + else if (!reader_table[i].used && reader == -1) + reader = i; + } + if (reader == -1) + { + log_error ("new_reader_slot: out of slots\n"); + return -1; + } + reader_table[reader].used = 1; + reader_table[reader].port = port; + return reader; +} + + +static void +dump_reader_status (int reader) +{ + log_info ("reader %d: %s\n", reader, + reader_table[reader].status == 1? "Processor ICC present" : + reader_table[reader].status == 0? "Memory ICC present" : + "ICC not present" ); + + if (reader_table[reader].status != -1) + { + log_info ("reader %d: ATR=", reader); + log_printhex ("", reader_table[reader].atr, + reader_table[reader].atrlen); + } +} + + + +#ifdef HAVE_CTAPI +/* + ct API Interface + */ + +static const char * +ct_error_string (int err) +{ + switch (err) + { + case 0: return "okay"; + case -1: return "invalid data"; + case -8: return "ct error"; + case -10: return "transmission error"; + case -11: return "memory allocation error"; + case -128: return "HTSI error"; + default: return "unknown CT-API error"; + } +} + +/* Wait for the card in READER and activate it. Return -1 on error or + 0 on success. */ +static int +ct_activate_card (int reader) +{ + int rc, count; + + for (count = 0; count < CARD_CONNECT_TIMEOUT; count++) + { + unsigned char dad[1], sad[1], cmd[11], buf[256]; + unsigned short buflen; + + if (count) + sleep (1); /* FIXME: we should use a more reliable timer. */ + + /* Check whether card has been inserted. */ + dad[0] = 1; /* Destination address: CT. */ + sad[0] = 2; /* Source address: Host. */ + + cmd[0] = 0x20; /* Class byte. */ + cmd[1] = 0x13; /* Request status. */ + cmd[2] = 0x00; /* From kernel. */ + cmd[3] = 0x80; /* Return card's DO. */ + cmd[4] = 0x00; + + buflen = DIM(buf); + + rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf); + if (rc || buflen < 2 || buf[buflen-2] != 0x90) + { + log_error ("ct_activate_card: can't get status of reader %d: %s\n", + reader, ct_error_string (rc)); + return -1; + } + + if (buf[0] == 0x05) + { /* Connected, now activate the card. */ + dad[0] = 1; /* Destination address: CT. */ + sad[0] = 2; /* Source address: Host. */ + + cmd[0] = 0x20; /* Class byte. */ + cmd[1] = 0x12; /* Request ICC. */ + cmd[2] = 0x01; /* From first interface. */ + cmd[3] = 0x01; /* Return card's ATR. */ + cmd[4] = 0x00; + + buflen = DIM(buf); + + rc = CT_data (reader, dad, sad, 5, cmd, &buflen, buf); + if (rc || buflen < 2 || buf[buflen-2] != 0x90) + { + log_error ("ct_activate_card(%d): activation failed: %s\n", + reader, ct_error_string (rc)); + return -1; + } + + /* Store the type and the ATR. */ + if (buflen - 2 > DIM (reader_table[0].atr)) + { + log_error ("ct_activate_card(%d): ATR too long\n", reader); + return -1; + } + + reader_table[reader].status = buf[buflen - 1]; + memcpy (reader_table[reader].atr, buf, buflen - 2); + reader_table[reader].atrlen = buflen - 2; + return 0; + } + + } + + log_info ("ct_activate_card(%d): timeout waiting for card\n", reader); + return -1; +} + + +/* Open a reader and return an internal handle for it. PORT is a + non-negative value with the port number of the reader. USB readers + do have port numbers starting at 32769. */ +static int +open_ct_reader (int port) +{ + int rc, reader; + + reader = new_reader_slot (port); + if (reader == -1) + return reader; + + rc = CT_init (reader, (unsigned short)port); + if (rc) + { + log_error ("apdu_open_ct_reader failed on port %d: %s\n", + port, ct_error_string (rc)); + reader_table[reader].used = 0; + return -1; + } + + rc = ct_activate_card (reader); + if (rc) + { + reader_table[reader].used = 0; + return -1; + } + + dump_reader_status (reader); + return reader; +} + + +/* Actually send the APDU of length APDULEN to SLOT and return a + maximum of *BUFLEN data in BUFFER, the actual retruned size will be + set to BUFLEN. Returns: CT API error code. */ +static int +ct_send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen) +{ + int rc; + unsigned char dad[1], sad[1]; + unsigned short ctbuflen; + + dad[0] = 0; /* Destination address: Card. */ + sad[0] = 2; /* Source address: Host. */ + ctbuflen = *buflen; + if (DBG_CARD_IO) + log_printhex (" CT_data:", apdu, apdulen); + rc = CT_data (slot, dad, sad, apdulen, apdu, &ctbuflen, buffer); + *buflen = ctbuflen; + + /* FIXME: map the errorcodes to GNUPG ones, so that they can be + shared between CTAPI and PCSC. */ + return rc; +} + + +#endif /*HAVE_CTAPI*/ + + +#ifdef HAVE_PCSC +/* + PC/SC Interface + */ + + +#endif /*HAVE_PCSC*/ + + +/* + Driver Access + */ + +/* Open the reader and return an internal slot number or -1 on + error. */ +int +apdu_open_reader (int port) +{ + static int ct_api_loaded; + + if (!ct_api_loaded) + { + void *handle; + + handle = dlopen ("libtowitoko.so", RTLD_LAZY); + if (!handle) + { + log_error ("apdu_open_reader: failed to open driver: %s", + dlerror ()); + return -1; + } + CT_init = dlsym (handle, "CT_init"); + CT_data = dlsym (handle, "CT_data"); + CT_close = dlsym (handle, "CT_close"); + if (!CT_init || !CT_data || !CT_close) + { + log_error ("apdu_open_reader: invalid driver\n"); + dlclose (handle); + return -1; + } + ct_api_loaded = 1; + } + return open_ct_reader (port); +} + + +unsigned char * +apdu_get_atr (int slot, size_t *atrlen) +{ + char *buf; + + if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used ) + return NULL; + + buf = xtrymalloc (reader_table[slot].atrlen); + if (!buf) + return NULL; + memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen); + *atrlen = reader_table[slot].atrlen; + return buf; +} + + +static const char * +error_string (int slot, int rc) +{ +#ifdef HAVE_CTAPI + return ct_error_string (rc); +#elif defined(HAVE_PCSC) + return "?"; +#else + return "?"; +#endif +} + + +/* Dispatcher for the actual send_apdu fucntion. */ +static int +send_apdu (int slot, unsigned char *apdu, size_t apdulen, + unsigned char *buffer, size_t *buflen) +{ +#ifdef HAVE_CTAPI + return ct_send_apdu (slot, apdu, apdulen, buffer, buflen); +#elif defined(HAVE_PCSC) + return SW_HOST_NO_DRIVER; +#else + return SW_HOST_NO_DRIVER; +#endif +} + +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1 + for LC won't sent this field and the data field; in this case DATA + must also be passed as NULL. The return value is the status word + or -1 for an invalid SLOT or other non card related error. If + RETBUF is not NULL, it will receive an allocated buffer with the + returned data. The length of that data will be put into + *RETBUFLEN. The caller is reponsible for releasing the buffer even + in case of errors. */ +int +apdu_send_le(int slot, int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen) +{ + unsigned char result[256+10]; /* 10 extra in case of bugs in the driver. */ + size_t resultlen = 256; + unsigned char apdu[5+256+1]; + size_t apdulen; + int rc, sw; + + if (DBG_CARD_IO) + log_debug ("send apdu: c=%02X i=%02X p0=%02X p1=%02X lc=%d le=%d\n", + class, ins, p0, p1, lc, le); + + if (lc != -1 && (lc > 255 || lc < 0)) + return SW_WRONG_LENGTH; + if (le != -1 && (le > 256 || le < 1)) + return SW_WRONG_LENGTH; + if ((!data && lc != -1) || (data && lc == -1)) + return SW_HOST_INV_VALUE; + + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = ins; + apdu[apdulen++] = p0; + apdu[apdulen++] = p1; + if (lc != -1) + { + apdu[apdulen++] = lc; + memcpy (apdu+apdulen, data, lc); + apdulen += lc; + } + if (le != -1) + apdu[apdulen++] = le; /* Truncation is okay becuase 0 means 256. */ + assert (sizeof (apdu) >= apdulen); + /* As safeguard don't pass any garbage from the stack to the driver. */ + memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); + rc = send_apdu (slot, apdu, apdulen, result, &resultlen); + if (rc || resultlen < 2) + { + log_error ("apdu_send_simple(%d) failed: %s\n", + slot, error_string (slot, rc)); + return SW_HOST_INCOMPLETE_CARD_RESPONSE; + } + sw = (result[resultlen-2] << 8) | result[resultlen-1]; + /* store away the returned data but strip the statusword. */ + resultlen -= 2; + if (DBG_CARD_IO) + { + log_debug (" response: sw=%04X datalen=%d\n", sw, resultlen); + if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA)) + log_printhex (" dump: ", result, resultlen); + } + + if (sw == SW_SUCCESS) + { + if (retbuf) + { + *retbuf = xtrymalloc (resultlen? resultlen : 1); + if (!*retbuf) + return SW_HOST_OUT_OF_CORE; + *retbuflen = resultlen; + memcpy (*retbuf, result, resultlen); + } + } + else if ((sw & 0xff00) == SW_MORE_DATA) + { + unsigned char *p = NULL, *tmp; + size_t bufsize = 4096; + + /* It is likely that we need to return much more data, so we + start off with a large buffer. */ + if (retbuf) + { + *retbuf = p = xtrymalloc (bufsize); + if (!*retbuf) + return SW_HOST_OUT_OF_CORE; + assert (resultlen < bufsize); + memcpy (p, result, resultlen); + p += resultlen; + } + + do + { + int len = (sw & 0x00ff); + + log_debug ("apdu_send_simple(%d): %d more bytes available\n", + slot, len); + apdulen = 0; + apdu[apdulen++] = class; + apdu[apdulen++] = 0xC0; + apdu[apdulen++] = 0; + apdu[apdulen++] = 0; + apdu[apdulen++] = 64; /* that is 256 bytes for Le */ + memset (apdu+apdulen, 0, sizeof (apdu) - apdulen); + rc = send_apdu (slot, apdu, apdulen, result, &resultlen); + if (rc || resultlen < 2) + { + log_error ("apdu_send_simple(%d) for get response failed: %s\n", + slot, error_string (slot, rc)); + return SW_HOST_INCOMPLETE_CARD_RESPONSE; + } + sw = (result[resultlen-2] << 8) | result[resultlen-1]; + resultlen -= 2; + if (DBG_CARD_IO) + { + log_debug (" more: sw=%04X datalen=%d\n", sw, resultlen); + if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA)) + log_printhex (" dump: ", result, resultlen); + } + + if ((sw & 0xff00) == SW_MORE_DATA || sw == SW_SUCCESS) + { + if (retbuf) + { + if (p - *retbuf + resultlen > bufsize) + { + bufsize += resultlen > 4096? resultlen: 4096; + tmp = xtryrealloc (*retbuf, bufsize); + if (!tmp) + return SW_HOST_OUT_OF_CORE; + p = tmp + (p - *retbuf); + *retbuf = tmp; + } + memcpy (p, result, resultlen); + p += resultlen; + } + } + else + log_info ("apdu_send_simple(%d) " + "got unexpected status %04X from get response\n", + slot, sw); + } + while ((sw & 0xff00) == SW_MORE_DATA); + + if (retbuf) + { + *retbuflen = p - *retbuf; + tmp = xtryrealloc (*retbuf, *retbuflen); + if (tmp) + *retbuf = tmp; + } + } + if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS) + log_printhex (" dump: ", *retbuf, *retbuflen); + + return sw; +} + +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for + LC won't sent this field and the data field; in this case DATA must + also be passed as NULL. The return value is the status word or -1 + for an invalid SLOT or other non card related error. If RETBUF is + not NULL, it will receive an allocated buffer with the returned + data. The length of that data will be put into *RETBUFLEN. The + caller is reponsible for releasing the buffer even in case of + errors. */ +int +apdu_send (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, unsigned char **retbuf, size_t *retbuflen) +{ + return apdu_send_le (slot, class, ins, p0, p1, lc, data, 256, + retbuf, retbuflen); +} + +/* Send an APDU to the card in SLOT. The APDU is created from all + given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for + LC won't sent this field and the data field; in this case DATA must + also be passed as NULL. The return value is the status word or -1 + for an invalid SLOT or other non card related error. No data will be + returned. */ +int +apdu_send_simple (int slot, int class, int ins, int p0, int p1, + int lc, const char *data) +{ + return apdu_send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL); +} + + + + diff --git a/scd/apdu.h b/scd/apdu.h new file mode 100644 index 000000000..44166a3fe --- /dev/null +++ b/scd/apdu.h @@ -0,0 +1,73 @@ +/* apdu.h - ISO 7816 APDU functions and low level I/O + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef APDU_H +#define APDU_H + +/* ISO 7816 values for the statusword are defined here because they + should not be visible to the users of the actual ISO command + API. */ +enum { + SW_MORE_DATA = 0x6100, /* Note: that the low byte must be + masked of.*/ + SW_EEPROM_FAILURE = 0x6581, + SW_WRONG_LENGTH = 0x6700, + SW_CHV_WRONG = 0x6982, + SW_CHV_BLOCKED = 0x6983, + SW_USE_CONDITIONS = 0x6985, + SW_NOT_SUPPORTED = 0x6a81, + SW_BAD_PARAMETER = 0x6a80, /* (in the data field) */ + SW_REF_NOT_FOUND = 0x6a88, + SW_BAD_P0_P1 = 0x6b00, + SW_INS_NOT_SUP = 0x6d00, + SW_CLA_NOT_SUP = 0x6e00, + SW_SUCCESS = 0x9000, + + /* The follwoing statuswords are no real ones but used to map host + OS errors into status words. A status word is 16 bit so that + those values can't be issued by a card. */ + SW_HOST_OUT_OF_CORE = 0x10001, /* No way yet to differentiate + between errnos on a failed malloc. */ + SW_HOST_INV_VALUE = 0x10002, + SW_HOST_INCOMPLETE_CARD_RESPONSE = 0x10003, +}; + + + +/* Note , that apdu_open_reader returns no status word but -1 on error. */ +int apdu_open_reader (int port); +unsigned char *apdu_get_atr (int slot, size_t *atrlen); + + +/* The apdu send functions do return status words. */ +int apdu_send_simple (int slot, int class, int ins, int p0, int p1, + int lc, const char *data); +int apdu_send (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, + unsigned char **retbuf, size_t *retbuflen); +int apdu_send_le (int slot, int class, int ins, int p0, int p1, + int lc, const char *data, int le, + unsigned char **retbuf, size_t *retbuflen); + + +#endif /*APDU_H*/ + + + diff --git a/scd/app-common.h b/scd/app-common.h new file mode 100644 index 000000000..282f82715 --- /dev/null +++ b/scd/app-common.h @@ -0,0 +1,128 @@ +/* app-common.h - Common declarations for all card applications + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef GNUPG_SCD_APP_COMMON_H +#define GNUPG_SCD_APP_COMMON_H + +struct app_ctx_s { + int initialized; /* The application has been initialied and the + function pointers may be used. Note that for + unsupported operations the particular + function pointer is set to NULL */ + int slot; /* Used reader. */ + unsigned char *serialno; /* Serialnumber in raw form, allocated. */ + size_t serialnolen; /* Length in octets of serialnumber. */ + unsigned int card_version; + int did_chv1; + int did_chv2; + int did_chv3; + struct { + int (*learn_status) (APP app, CTRL ctrl); + int (*setattr) (APP app, const char *name, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen); + int (*sign) (APP app, + const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); + int (*auth) (APP app, const char *keyidstr, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); + int (*decipher) (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); + int (*genkey) (APP app, CTRL ctrl, + const char *keynostr, unsigned int flags, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); + int (*change_pin) (APP app, CTRL ctrl, + const char *chvnostr, int reset_mode, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); + } fnc; + + +}; + +/*-- app.c --*/ +APP select_application (void); +int app_get_serial_and_stamp (APP app, char **serial, time_t *stamp); +int app_write_learn_status (APP app, CTRL ctrl); +int app_setattr (APP app, const char *name, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen); +int app_sign (APP app, const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); +int app_auth (APP app, const char *keyidstr, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); +int app_decipher (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); +int app_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); +int app_get_challenge (APP app, size_t nbytes, unsigned char *buffer); +int app_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); + + +/*-- app-openpgp.c --*/ +int app_select_openpgp (APP app, unsigned char **sn, size_t *snlen); + +int app_openpgp_cardinfo (APP app, + char **serialno, + char **disp_name, + char **pubkey_url, + unsigned char **fpr1, + unsigned char **fpr2, + unsigned char **fpr3); +int app_openpgp_storekey (APP app, int keyno, + unsigned char *template, size_t template_len, + time_t created_at, + const unsigned char *m, size_t mlen, + const unsigned char *e, size_t elen, + int (*pincb)(void*, const char *, char **), + void *pincb_arg); +int app_openpgp_readkey (APP app, int keyno, + unsigned char **m, size_t *mlen, + unsigned char **e, size_t *elen); + + +#endif /*GNUPG_SCD_APP_COMMON_H*/ + + + diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c new file mode 100644 index 000000000..09a19699d --- /dev/null +++ b/scd/app-openpgp.c @@ -0,0 +1,1482 @@ +/* app-openpgp.c - The OpenPGP card application. + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "scdaemon.h" +#include "app-common.h" +#include "iso7816.h" + + + +static struct { + int tag; + int constructed; + int get_from; /* Constructed DO with this DO or 0 for direct access. */ + int binary; + char *desc; +} data_objects[] = { + { 0x005E, 0, 0, 1, "Login Data" }, + { 0x5F50, 0, 0, 0, "URL" }, + { 0x0065, 1, 0, 1, "Cardholder Related Data"}, + { 0x005B, 0, 0x65, 0, "Name" }, + { 0x5F2D, 0, 0x65, 0, "Language preferences" }, + { 0x5F35, 0, 0x65, 0, "Sex" }, + { 0x006E, 1, 0, 1, "Application Related Data" }, + { 0x004F, 0, 0x6E, 1, "AID" }, + { 0x0073, 1, 0, 1, "Discretionary Data Objects" }, + { 0x0047, 0, 0x6E, 1, "Card Capabilities" }, + { 0x00C0, 0, 0x6E, 1, "Extended Card Capabilities" }, + { 0x00C1, 0, 0x6E, 1, "Algorithm Attributes Signature" }, + { 0x00C2, 0, 0x6E, 1, "Algorithm Attributes Decryption" }, + { 0x00C3, 0, 0x6E, 1, "Algorithm Attributes Authentication" }, + { 0x00C4, 0, 0x6E, 1, "CHV Status Bytes" }, + { 0x00C5, 0, 0x6E, 1, "Fingerprints" }, + { 0x00C6, 0, 0x6E, 1, "CA Fingerprints" }, + { 0x007A, 1, 0, 1, "Security Support Template" }, + { 0x0093, 0, 0x7A, 1, "Digital Signature Counter" }, + { 0 } +}; + + +static unsigned long get_sig_counter (APP app); + + +/* Locate a TLV encoded data object in BUFFER of LENGTH and + return a pointer to value as well as its length in NBYTES. Return + NULL if it was not found. Note, that the function does not check + whether the value fits into the provided buffer. + + FIXME: Move this to an extra file, it is mostly duplicated from card.c. +*/ +static const unsigned char * +find_tlv (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes, int nestlevel) +{ + const unsigned char *s = buffer; + size_t n = length; + size_t len; + int this_tag; + int composite; + + for (;;) + { + buffer = s; + if (n < 2) + return NULL; /* buffer definitely too short for tag and length. */ + if (!*s || *s == 0xff) + { /* Skip optional filler between TLV objects. */ + s++; + n--; + continue; + } + composite = !!(*s & 0x20); + if ((*s & 0x1f) == 0x1f) + { /* more tag bytes to follow */ + s++; + n--; + if (n < 2) + return NULL; /* buffer definitely too short for tag and length. */ + if ((*s & 0x1f) == 0x1f) + return NULL; /* We support only up to 2 bytes. */ + this_tag = (s[-1] << 8) | (s[0] & 0x7f); + } + else + this_tag = s[0]; + len = s[1]; + s += 2; n -= 2; + if (len < 0x80) + ; + else if (len == 0x81) + { /* One byte length follows. */ + if (!n) + return NULL; /* we expected 1 more bytes with the length. */ + len = s[0]; + s++; n--; + } + else if (len == 0x82) + { /* Two byte length follows. */ + if (n < 2) + return NULL; /* we expected 2 more bytes with the length. */ + len = (s[0] << 8) | s[1]; + s += 2; n -= 2; + } + else + return NULL; /* APDU limit is 65535, thus it does not make + sense to assume longer length fields. */ + + if (composite && nestlevel < 100) + { /* Dive into this composite DO after checking for too deep + nesting. */ + const unsigned char *tmp_s; + size_t tmp_len; + + tmp_s = find_tlv (s, len, tag, &tmp_len, nestlevel+1); + if (tmp_s) + { + *nbytes = tmp_len; + return tmp_s; + } + } + + if (this_tag == tag) + { + *nbytes = len; + return s; + } + if (len > n) + return NULL; /* buffer too short to skip to the next tag. */ + s += len; n -= len; + } +} + + +/* Get the DO identified by TAG from the card in SLOT and return a + buffer with its content in RESULT and NBYTES. The return value is + NULL if not found or a pointer which must be used to release the + buffer holding value. */ +static void * +get_one_do (int slot, int tag, unsigned char **result, size_t *nbytes) +{ + int rc, i; + unsigned char *buffer; + size_t buflen; + unsigned char *value; + size_t valuelen; + + *result = NULL; + *nbytes = 0; + for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) + ; + + value = NULL; + rc = -1; + if (data_objects[i].tag && data_objects[i].get_from) + { + rc = iso7816_get_data (slot, data_objects[i].get_from, + &buffer, &buflen); + if (!rc) + { + const unsigned char *s; + + s = find_tlv (buffer, buflen, tag, &valuelen, 0); + if (!s) + value = NULL; /* not found */ + else if (valuelen > buflen - (s - buffer)) + { + log_error ("warning: constructed DO too short\n"); + value = NULL; + xfree (buffer); buffer = NULL; + } + else + value = buffer + (s - buffer); + } + } + + if (!value) /* Not in a constructed DO, try simple. */ + { + rc = iso7816_get_data (slot, tag, &buffer, &buflen); + if (!rc) + { + value = buffer; + valuelen = buflen; + } + } + + if (!rc) + { + *nbytes = valuelen; + *result = value; + return buffer; + } + return NULL; +} + +#if 0 /* not used */ +static void +dump_one_do (int slot, int tag) +{ + int rc, i; + unsigned char *buffer; + size_t buflen; + const char *desc; + int binary; + const unsigned char *value; + size_t valuelen; + + for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) + ; + desc = data_objects[i].tag? data_objects[i].desc : "?"; + binary = data_objects[i].tag? data_objects[i].binary : 1; + + value = NULL; + rc = -1; + if (data_objects[i].tag && data_objects[i].get_from) + { + rc = iso7816_get_data (slot, data_objects[i].get_from, + &buffer, &buflen); + if (!rc) + { + value = find_tlv (buffer, buflen, tag, &valuelen, 0); + if (!value) + ; /* not found */ + else if (valuelen > buflen - (value - buffer)) + { + log_error ("warning: constructed DO too short\n"); + value = NULL; + xfree (buffer); buffer = NULL; + } + } + } + + if (!value) /* Not in a constructed DO, try simple. */ + { + rc = iso7816_get_data (slot, tag, &buffer, &buflen); + if (!rc) + { + value = buffer; + valuelen = buflen; + } + } + if (rc == 0x6a88) + log_info ("DO `%s' not available\n", desc); + else if (rc) + log_info ("DO `%s' not available (rc=%04X)\n", desc, rc); + else + { + if (binary) + { + log_info ("DO `%s': ", desc); + log_printhex ("", value, valuelen); + } + else + log_info ("DO `%s': `%.*s'\n", + desc, (int)valuelen, value); /* FIXME: sanitize */ + xfree (buffer); + } +} +#endif /*not used*/ + + +static void +dump_all_do (int slot) +{ + int rc, i, j; + unsigned char *buffer; + size_t buflen; + + for (i=0; data_objects[i].tag; i++) + { + if (data_objects[i].get_from) + continue; + + rc = iso7816_get_data (slot, data_objects[i].tag, &buffer, &buflen); + if (rc == 0x6a88) + ; + else if (rc) + log_info ("DO `%s' not available (rc=%04X)\n", + data_objects[i].desc, rc); + else + { + if (data_objects[i].binary) + { + log_info ("DO `%s': ", data_objects[i].desc); + log_printhex ("", buffer, buflen); + } + else + log_info ("DO `%s': `%.*s'\n", + data_objects[i].desc, + (int)buflen, buffer); /* FIXME: sanitize */ + } + + if (data_objects[i].constructed) + { + for (j=0; data_objects[j].tag; j++) + { + const unsigned char *value; + size_t valuelen; + + if (j==i || data_objects[i].tag != data_objects[j].get_from) + continue; + value = find_tlv (buffer, buflen, + data_objects[j].tag, &valuelen, 0); + if (!value) + ; /* not found */ + else if (valuelen > buflen - (value - buffer)) + log_error ("warning: constructed DO too short\n"); + else + { + if (data_objects[j].binary) + { + log_info ("DO `%s': ", data_objects[j].desc); + log_printhex ("", value, valuelen); + } + else + log_info ("DO `%s': `%.*s'\n", + data_objects[j].desc, + (int)valuelen, value); /* FIXME: sanitize */ + } + } + } + xfree (buffer); buffer = NULL; + } +} + + +/* Count the number of bits, assuming the A represents an unsigned big + integer of length LEN bytes. */ +static unsigned int +count_bits (const unsigned char *a, size_t len) +{ + unsigned int n = len * 8; + int i; + + for (; len && !*a; len--, a++, n -=8) + ; + if (len) + { + for (i=7; i && !(*a & (1<<i)); i--) + n--; + } + return n; +} + +/* Note, that FPR must be at least 20 bytes. */ +static int +store_fpr (int slot, int keynumber, u32 timestamp, + const unsigned char *m, size_t mlen, + const unsigned char *e, size_t elen, + unsigned char *fpr, unsigned int card_version) +{ + unsigned int n, nbits; + unsigned char *buffer, *p; + int rc; + + for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */ + ; + for (; elen && !*e; elen--, e++) /* strip leading zeroes */ + ; + + n = 6 + 2 + mlen + 2 + elen; + p = buffer = xtrymalloc (3 + n); + if (!buffer) + return out_of_core (); + + *p++ = 0x99; /* ctb */ + *p++ = n >> 8; /* 2 byte length header */ + *p++ = n; + *p++ = 4; /* key packet version */ + *p++ = timestamp >> 24; + *p++ = timestamp >> 16; + *p++ = timestamp >> 8; + *p++ = timestamp; + *p++ = 1; /* RSA */ + nbits = count_bits (m, mlen); + *p++ = nbits >> 8; + *p++ = nbits; + memcpy (p, m, mlen); p += mlen; + nbits = count_bits (e, elen); + *p++ = nbits >> 8; + *p++ = nbits; + memcpy (p, e, elen); p += elen; + + log_printhex ("fprbuf:", buffer, n+3); + gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3); + + xfree (buffer); + + rc = iso7816_put_data (slot, (card_version > 0x0007? 0xC7 : 0xC6) + + keynumber, fpr, 20); + if (rc) + log_error ("failed to store the fingerprint: rc=%04X\n", rc); + + return rc; +} + + +static void +send_fpr_if_not_null (CTRL ctrl, const char *keyword, + int number, const unsigned char *fpr) +{ + int i; + char buf[41]; + char numbuf[25]; + + for (i=0; i < 20 && !fpr[i]; i++) + ; + if (i==20) + return; /* All zero. */ + for (i=0; i< 20; i++) + sprintf (buf+2*i, "%02X", fpr[i]); + if (number == -1) + *numbuf = 0; /* Don't print the key number */ + else + sprintf (numbuf, "%d", number); + send_status_info (ctrl, keyword, + numbuf, (size_t)strlen(numbuf), + buf, (size_t)strlen (buf), NULL, 0); +} + +static void +send_key_data (CTRL ctrl, const char *name, + const unsigned char *a, size_t alen) +{ + char *p, *buf = xmalloc (alen*2+1); + + for (p=buf; alen; a++, alen--, p += 2) + sprintf (p, "%02X", *a); + + send_status_info (ctrl, "KEY-DATA", + name, (size_t)strlen(name), + buf, (size_t)strlen (buf), + NULL, 0); + xfree (buf); +} + + + +static int +do_learn_status (APP app, CTRL ctrl) +{ + void *relptr; + unsigned char *value; + size_t valuelen; + int i; + + relptr = get_one_do (app->slot, 0x005B, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "DISP-NAME", value, valuelen, NULL, 0); + xfree (relptr); + } + relptr = get_one_do (app->slot, 0x5F2D, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "DISP-LANG", value, valuelen, NULL, 0); + xfree (relptr); + } + relptr = get_one_do (app->slot, 0x5F35, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "DISP-SEX", value, valuelen, NULL, 0); + xfree (relptr); + } + relptr = get_one_do (app->slot, 0x5F50, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "PUBKEY-URL", value, valuelen, NULL, 0); + xfree (relptr); + } + relptr = get_one_do (app->slot, 0x005E, &value, &valuelen); + if (relptr) + { + send_status_info (ctrl, "LOGIN-DATA", value, valuelen, NULL, 0); + xfree (relptr); + } + + relptr = get_one_do (app->slot, 0x00C5, &value, &valuelen); + if (relptr && valuelen >= 60) + { + for (i=0; i < 3; i++) + send_fpr_if_not_null (ctrl, "KEY-FPR", i+1, value+i*20); + } + xfree (relptr); + relptr = get_one_do (app->slot, 0x00C6, &value, &valuelen); + if (relptr && valuelen >= 60) + { + for (i=0; i < 3; i++) + send_fpr_if_not_null (ctrl, "CA-FPR", i+1, value+i*20); + } + xfree (relptr); + relptr = get_one_do (app->slot, 0x00C4, &value, &valuelen); + if (relptr) + { + char numbuf[7*23]; + + for (i=0,*numbuf=0; i < valuelen && i < 7; i++) + sprintf (numbuf+strlen (numbuf), " %d", value[i]); + send_status_info (ctrl, "CHV-STATUS", numbuf, strlen (numbuf), NULL, 0); + xfree (relptr); + } + + { + unsigned long ul = get_sig_counter (app); + char numbuf[23]; + + sprintf (numbuf, "%lu", ul); + send_status_info (ctrl, "SIG-COUNTER", numbuf, strlen (numbuf), NULL, 0); + } + return 0; +} + + +/* Handle the SETATTR operation. All arguments are already basically + checked. */ +static int +do_setattr (APP app, const char *name, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t rc; + int idx; + static struct { + const char *name; + int tag; + } table[] = { + { "DISP-NAME", 0x005B }, + { "LOGIN-DATA", 0x005E }, + { "DISP-LANG", 0x5F2D }, + { "DISP-SEX", 0x5F35 }, + { "PUBKEY-URL", 0x5F50 }, + { "CHV-STATUS-1", 0x00C4 }, + { "CA-FPR-1", 0x00CA }, + { "CA-FPR-2", 0x00CB }, + { "CA-FPR-3", 0x00CC }, + { NULL, 0 } + }; + + + for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++) + ; + if (!table[idx].name) + return gpg_error (GPG_ERR_INV_NAME); + + if (!app->did_chv3) + { + char *pinvalue; + + rc = pincb (pincb_arg, "Admin PIN (CHV3)", + &pinvalue); +/* pinvalue = xstrdup ("12345678"); */ +/* rc = 0; */ + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV3 failed\n"); + rc = gpg_error (GPG_ERR_GENERAL); + return rc; + } + app->did_chv3 = 1; + } + + rc = iso7816_put_data (app->slot, table[idx].tag, value, valuelen); + if (rc) + log_error ("failed to set `%s': %s\n", table[idx].name, gpg_strerror (rc)); + /* FIXME: If this fails we should *once* try again after + doing a verify command, so that in case of a problem with + tracking the verify operation we have a fallback. */ + + return rc; +} + +/* Handle the PASSWD command. */ +static int +do_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc = 0; + int chvno = atoi (chvnostr); + char *pinvalue; + + if (reset_mode && chvno == 3) + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + else if (reset_mode || chvno == 3) + { + rc = pincb (pincb_arg, "Admin PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV3 failed: rc=%04X\n", rc); + goto leave; + } + } + else if (chvno == 1) + { + rc = pincb (pincb_arg, "Signature PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV1 failed: rc=%04X\n", rc); + goto leave; + } + } + else if (chvno == 2) + { + rc = pincb (pincb_arg, "Decryption PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + goto leave; + } + rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV2 failed: rc=%04X\n", rc); + goto leave; + } + } + else + { + rc = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + + rc = pincb (pincb_arg, chvno == 1? "New Signature PIN" : + chvno == 2? "New Decryption PIN" : + chvno == 3? "New Admin PIN" : "?", &pinvalue); + if (rc) + { + log_error ("error getting new PIN: %s\n", gpg_strerror (rc)); + goto leave; + } + + if (reset_mode) + rc = iso7816_reset_retry_counter (app->slot, 0x80 + chvno, + pinvalue, strlen (pinvalue)); + else + rc = iso7816_change_reference_data (app->slot, 0x80 + chvno, + NULL, 0, + pinvalue, strlen (pinvalue)); + xfree (pinvalue); + + + leave: + return rc; +} + + + +/* Handle the GENKEY command. */ +static int +do_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + int i; + char numbuf[30]; + unsigned char fprbuf[20]; + const unsigned char *fpr; + const unsigned char *keydata, *m, *e; + unsigned char *buffer; + size_t buflen, keydatalen, n, mlen, elen; + time_t created_at; + int keyno = atoi (keynostr); + int force = (flags & 1); + time_t start_at; + + if (keyno < 1 || keyno > 3) + return gpg_error (GPG_ERR_INV_ID); + keyno--; + + rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen); + if (rc) + { + log_error ("error reading application data\n"); + return gpg_error (GPG_ERR_GENERAL); + } + fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0); + if (!fpr || n != 60) + { + rc = gpg_error (GPG_ERR_GENERAL); + log_error ("error reading fingerprint DO\n"); + goto leave; + } + fpr += 20*keyno; + for (i=0; i < 20 && !fpr[i]; i++) + ; + if (i!=20 && !force) + { + rc = gpg_error (GPG_ERR_EEXIST); + log_error ("key already exists\n"); + goto leave; + } + else if (i!=20) + log_info ("existing key will be replaced\n"); + else + log_info ("generating new key\n"); + + { + char *pinvalue; + rc = pincb (pincb_arg, "Admin PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + } + if (rc) + { + log_error ("verify CHV3 failed: rc=%04X\n", rc); + goto leave; + } + + xfree (buffer); buffer = NULL; +#if 1 + log_info ("please wait while key is being generated ...\n"); + start_at = time (NULL); + rc = iso7816_generate_keypair +#else +#warning key generation temporary replaced by reading an existing key. + rc = iso7816_read_public_key +#endif + (app->slot, + keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4", + 2, + &buffer, &buflen); + if (rc) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("generating key failed\n"); + goto leave; + } + log_info ("key generation completed (%d seconds)\n", + (int)(time (NULL) - start_at)); + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0); + if (!keydata) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("response does not contain the public key data\n"); + goto leave; + } + + m = find_tlv (keydata, keydatalen, 0x0081, &mlen, 0); + if (!m) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("response does not contain the RSA modulus\n"); + goto leave; + } +/* log_printhex ("RSA n:", m, mlen); */ + send_key_data (ctrl, "n", m, mlen); + + e = find_tlv (keydata, keydatalen, 0x0082, &elen, 0); + if (!e) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("response does not contain the RSA public exponent\n"); + goto leave; + } +/* log_printhex ("RSA e:", e, elen); */ + send_key_data (ctrl, "e", e, elen); + + created_at = gnupg_get_time (); + sprintf (numbuf, "%lu", (unsigned long)created_at); + send_status_info (ctrl, "KEY-CREATED-AT", + numbuf, (size_t)strlen(numbuf), NULL, 0); + + rc = store_fpr (app->slot, keyno, (u32)created_at, + m, mlen, e, elen, fprbuf, app->card_version); + if (rc) + goto leave; + send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf); + + + leave: + xfree (buffer); + return rc; +} + + +static unsigned long +get_sig_counter (APP app) +{ + void *relptr; + unsigned char *value; + size_t valuelen; + unsigned long ul; + + relptr = get_one_do (app->slot, 0x0093, &value, &valuelen); + if (!relptr) + return 0; + if (valuelen == 3 ) + ul = (value[0] << 16) | (value[1] << 8) | value[2]; + else + { + log_error ("invalid structure of OpenPGP card (DO 0x93)\n"); + ul = 0; + } + xfree (relptr); + return ul; +} + +static int +compare_fingerprint (APP app, int keyno, unsigned char *sha1fpr) +{ + const unsigned char *fpr; + unsigned char *buffer; + size_t buflen, n; + int rc, i; + + assert (keyno >= 1 && keyno <= 3); + + rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen); + if (rc) + { + log_error ("error reading application data\n"); + return gpg_error (GPG_ERR_GENERAL); + } + fpr = find_tlv (buffer, buflen, 0x00C5, &n, 0); + if (!fpr || n != 60) + { + xfree (buffer); + log_error ("error reading fingerprint DO\n"); + return gpg_error (GPG_ERR_GENERAL); + } + fpr += (keyno-1)*20; + for (i=0; i < 20; i++) + if (sha1fpr[i] != fpr[i]) + { + xfree (buffer); + return gpg_error (GPG_ERR_WRONG_SECKEY); + } + xfree (buffer); + return 0; +} + + + +/* Compute a digital signature on INDATA which is expected to be the + raw message digest. For this application the KEYIDSTR consists of + the serialnumber and the fingerprint delimited by a slash. + + Note that this fucntion may return the error code + GPG_ERR_WRONG_CARD to indicate that the card currently present does + not match the one required for the requested action (e.g. the + serial number does not match). */ +static int +do_sign (APP app, const char *keyidstr, int hashalgo, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, + 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; + static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, + 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; + int rc; + unsigned char data[35]; + unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + const char *s; + int n; + const char *fpr = NULL; + unsigned long sigcount; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (indatalen != 20) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + + /* If a fingerprint has been specified check it against the one on + the card. This is allows for a meaningful error message in case + the key on the card has been replaced but the shadow information + known to gpg was not updated. If there is no fingerprint, gpg + will detect a bogus signature anyway due to the + verify-after-signing feature. */ + if (fpr) + { + for (s=fpr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 40) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* okay */ + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=fpr, n=0; n < 20; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + rc = compare_fingerprint (app, 1, tmp_sn); + if (rc) + return rc; + } + + if (hashalgo == GCRY_MD_SHA1) + memcpy (data, sha1_prefix, 15); + else if (hashalgo == GCRY_MD_RMD160) + memcpy (data, rmd160_prefix, 15); + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + memcpy (data+15, indata, indatalen); + + sigcount = get_sig_counter (app); + log_info ("signatures created so far: %lu\n", sigcount); + + /* FIXME: Check whether we are really required to enter the PIN for + each signature. There is a DO for this. */ + if (!app->did_chv1 || 1) + { + char *pinvalue; + + { + char *prompt; + if (asprintf (&prompt, "Signature PIN [sigs done: %lu]", sigcount) < 0) + return gpg_error_from_errno (errno); + rc = pincb (pincb_arg, prompt, &pinvalue); + free (prompt); + } +/* pinvalue = xstrdup ("123456"); */ +/* rc = 0; */ + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + rc = iso7816_verify (app->slot, 0x81, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV1 failed\n"); + rc = gpg_error (GPG_ERR_GENERAL); + return rc; + } + app->did_chv1 = 1; + } + + rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen); + return rc; +} + +/* Compute a digital signature using the INTERNAL AUTHENTICATE command + on INDATA which is expected to be the raw message digest. For this + application the KEYIDSTR consists of the serialnumber and the + fingerprint delimited by a slash. + + Note that this fucntion may return the error code + GPG_ERR_WRONG_CARD to indicate that the card currently present does + not match the one required for the requested action (e.g. the + serial number does not match). */ +static int +do_auth (APP app, const char *keyidstr, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + const char *s; + int n; + const char *fpr = NULL; + + if (!keyidstr || !*keyidstr) + return gpg_error (GPG_ERR_INV_VALUE); + if (indatalen > 50) /* For a 1024 bit key. */ + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + + /* If a fingerprint has been specified check it against the one on + the card. This is allows for a meaningful error message in case + the key on the card has been replaced but the shadow information + known to gpg was not updated. If there is no fingerprint, gpg + will detect a bogus signature anyway due to the + verify-after-signing feature. */ + if (fpr) + { + for (s=fpr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 40) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* okay */ + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=fpr, n=0; n < 20; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + rc = compare_fingerprint (app, 3, tmp_sn); + if (rc) + return rc; + } + + if (!app->did_chv2) + { + char *pinvalue; + + rc = pincb (pincb_arg, "Authentication/Decryption PIN", &pinvalue); + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV2 failed\n"); + rc = gpg_error (GPG_ERR_GENERAL); + return rc; + } + app->did_chv2 = 1; + } + + rc = iso7816_internal_authenticate (app->slot, indata, indatalen, + outdata, outdatalen); + return rc; +} + + +static int +do_decipher (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + unsigned char tmp_sn[20]; /* actually 16 but we use it also for the fpr. */ + const char *s; + int n; + const char *fpr = NULL; + + if (!keyidstr || !*keyidstr || !indatalen) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Check whether an OpenPGP card of any version has been requested. */ + if (strlen (keyidstr) < 32 || strncmp (keyidstr, "D27600012401", 12)) + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 32) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* no fingerprint given: we allow this for now. */ + else if (*s == '/') + fpr = s + 1; + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=keyidstr, n=0; n < 16; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + + if (app->serialnolen != 16) + return gpg_error (GPG_ERR_INV_CARD); + if (memcmp (app->serialno, tmp_sn, 16)) + return gpg_error (GPG_ERR_WRONG_CARD); + + /* If a fingerprint has been specified check it against the one on + the card. This is allows for a meaningful error message in case + the key on the card has been replaced but the shadow information + known to gpg was not updated. If there is no fingerprint, the + decryption will won't produce the right plaintext anyway. */ + if (fpr) + { + for (s=fpr, n=0; hexdigitp (s); s++, n++) + ; + if (n != 40) + return gpg_error (GPG_ERR_INV_ID); + else if (!*s) + ; /* okay */ + else + return gpg_error (GPG_ERR_INV_ID); + + for (s=fpr, n=0; n < 20; s += 2, n++) + tmp_sn[n] = xtoi_2 (s); + rc = compare_fingerprint (app, 2, tmp_sn); + if (rc) + return rc; + } + + if (!app->did_chv2) + { + char *pinvalue; + + rc = pincb (pincb_arg, "Decryption PIN", &pinvalue); +/* pinvalue = xstrdup ("123456"); */ +/* rc = 0; */ + if (rc) + { + log_info ("PIN callback returned error: %s\n", gpg_strerror (rc)); + return rc; + } + + rc = iso7816_verify (app->slot, 0x82, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_error ("verify CHV2 failed\n"); + rc = gpg_error (GPG_ERR_GENERAL); + return rc; + } + app->did_chv2 = 1; + } + + rc = iso7816_decipher (app->slot, indata, indatalen, outdata, outdatalen); + return rc; +} + + + + +/* Select the OpenPGP application on the card in SLOT. This function + must be used before any other OpenPGP application functions. */ +int +app_select_openpgp (APP app, unsigned char **sn, size_t *snlen) +{ + static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; + int slot = app->slot; + int rc; + unsigned char *buffer; + size_t buflen; + + rc = iso7816_select_application (slot, aid, sizeof aid); + if (!rc) + { + /* fixme: get the full AID and check that the version is okay + with us. */ + rc = iso7816_get_data (slot, 0x004F, &buffer, &buflen); + if (rc) + goto leave; + if (opt.verbose) + { + log_info ("got AID: "); + log_printhex ("", buffer, buflen); + } + + if (sn) + { + *sn = buffer; + *snlen = buflen; + app->card_version = buffer[6] << 8; + app->card_version |= buffer[7]; + } + else + xfree (buffer); + + if (opt.verbose > 1) + dump_all_do (slot); + + app->fnc.learn_status = do_learn_status; + app->fnc.setattr = do_setattr; + app->fnc.genkey = do_genkey; + app->fnc.sign = do_sign; + app->fnc.auth = do_auth; + app->fnc.decipher = do_decipher; + app->fnc.change_pin = do_change_pin; + } + +leave: + return rc; +} + + + +/* This function is a hack to retrieve essential information about the + card to be displayed by simple tools. It mostly resembles what the + LEARN command returns. All parameters return allocated strings or + buffers or NULL if the data object is not available. All returned + values are sanitized. */ +int +app_openpgp_cardinfo (APP app, + char **serialno, + char **disp_name, + char **pubkey_url, + unsigned char **fpr1, + unsigned char **fpr2, + unsigned char **fpr3) +{ + int rc; + void *relptr; + unsigned char *value; + size_t valuelen; + + if (serialno) + { + time_t dummy; + + *serialno = NULL; + rc = app_get_serial_and_stamp (app, serialno, &dummy); + if (rc) + { + log_error ("error getting serial number: %s\n", gpg_strerror (rc)); + return rc; + } + } + + if (disp_name) + { + *disp_name = NULL; + relptr = get_one_do (app->slot, 0x005B, &value, &valuelen); + if (relptr) + { + *disp_name = make_printable_string (value, valuelen, 0); + xfree (relptr); + } + } + + if (pubkey_url) + { + *pubkey_url = NULL; + relptr = get_one_do (app->slot, 0x5F50, &value, &valuelen); + if (relptr) + { + *pubkey_url = make_printable_string (value, valuelen, 0); + xfree (relptr); + } + } + + if (fpr1) + *fpr1 = NULL; + if (fpr2) + *fpr2 = NULL; + if (fpr3) + *fpr3 = NULL; + relptr = get_one_do (app->slot, 0x00C5, &value, &valuelen); + if (relptr && valuelen >= 60) + { + if (fpr1) + { + *fpr1 = xmalloc (20); + memcpy (*fpr1, value + 0, 20); + } + if (fpr2) + { + *fpr2 = xmalloc (20); + memcpy (*fpr2, value + 20, 20); + } + if (fpr3) + { + *fpr3 = xmalloc (20); + memcpy (*fpr3, value + 40, 20); + } + } + xfree (relptr); + + return 0; +} + + + +/* This function is currently only used by the sc-copykeys program to + store a key on the smartcard. APP ist the application handle, + KEYNO is the number of the key and PINCB, PINCB_ARG are used to ask + for the SO PIN. TEMPLATE and TEMPLATE_LEN describe a buffer with + the key template to store. CREATED_AT is the timestamp used to + create the fingerprint. M, MLEN is the RSA modulus and E, ELEN the + RSA public exponent. This function silently overwrites an existing + key.*/ +int +app_openpgp_storekey (APP app, int keyno, + unsigned char *template, size_t template_len, + time_t created_at, + const unsigned char *m, size_t mlen, + const unsigned char *e, size_t elen, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + unsigned char fprbuf[20]; + + if (keyno < 1 || keyno > 3) + return gpg_error (GPG_ERR_INV_ID); + keyno--; + + { + char *pinvalue; + rc = pincb (pincb_arg, "Admin PIN", &pinvalue); + if (rc) + { + log_error ("error getting PIN: %s\n", gpg_strerror (rc)); + return rc; + } + rc = iso7816_verify (app->slot, 0x83, pinvalue, strlen (pinvalue)); + xfree (pinvalue); + } + if (rc) + { + log_error ("verify CHV3 failed: rc=%04X\n", rc); + goto leave; + } + + rc = iso7816_put_data (app->slot, + (app->card_version > 0x0007? 0xE0 : 0xE9) + keyno, + template, template_len); + if (rc) + { + log_error ("failed to store the key: rc=%04X\n", rc); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + +/* log_printhex ("RSA n:", m, mlen); */ +/* log_printhex ("RSA e:", e, elen); */ + + rc = store_fpr (app->slot, keyno, (u32)created_at, + m, mlen, e, elen, fprbuf, app->card_version); + + leave: + return rc; +} + + +/* Utility function for external tools: Read the public RSA key at + KEYNO and return modulus and exponent in (M,MLEN) and (E,ELEN). */ +int +app_openpgp_readkey (APP app, int keyno, unsigned char **m, size_t *mlen, + unsigned char **e, size_t *elen) +{ + int rc; + const unsigned char *keydata, *a; + unsigned char *buffer; + size_t buflen, keydatalen, alen; + + *m = NULL; + *e = NULL; + + if (keyno < 1 || keyno > 3) + return gpg_error (GPG_ERR_INV_ID); + keyno--; + + rc = iso7816_read_public_key(app->slot, + keyno == 0? "\xB6" : + keyno == 1? "\xB8" : "\xA4", + 2, + &buffer, &buflen); + if (rc) + { + rc = gpg_error (GPG_ERR_CARD); + log_error ("reading key failed\n"); + goto leave; + } + + keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen, 0); + if (!keydata) + { + log_error ("response does not contain the public key data\n"); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + + a = find_tlv (keydata, keydatalen, 0x0081, &alen, 0); + if (!a) + { + log_error ("response does not contain the RSA modulus\n"); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + *mlen = alen; + *m = xmalloc (alen); + memcpy (*m, a, alen); + + a = find_tlv (keydata, keydatalen, 0x0082, &alen, 0); + if (!e) + { + log_error ("response does not contain the RSA public exponent\n"); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + *elen = alen; + *e = xmalloc (alen); + memcpy (*e, a, alen); + + leave: + xfree (buffer); + if (rc) + { + xfree (*m); *m = NULL; + xfree (*e); *e = NULL; + } + return rc; +} diff --git a/scd/app.c b/scd/app.c new file mode 100644 index 000000000..7a85df336 --- /dev/null +++ b/scd/app.c @@ -0,0 +1,278 @@ +/* app.c - Application selection. + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dlfcn.h> + +#include "scdaemon.h" +#include "app-common.h" +#include "apdu.h" +#include "iso7816.h" + +/* The select the best fitting application and return a context. + Returns NULL if no application was found or no card is present. */ +APP +select_application (void) +{ + int reader_port = 32768; /* First USB reader. */ + int slot; + int rc; + APP app; + + slot = apdu_open_reader (reader_port); + if (slot == -1) + { + log_error ("card reader not available\n"); + return NULL; + } + + app = xtrycalloc (1, sizeof *app); + if (!app) + { + rc = out_of_core (); + log_info ("error allocating context: %s\n", gpg_strerror (rc)); + /*apdu_close_reader (slot);*/ + return NULL; + } + + app->slot = slot; + rc = app_select_openpgp (app, &app->serialno, &app->serialnolen); + if (rc) + { +/* apdu_close_reader (slot); */ + log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc)); + xfree (app); + return NULL; + } + + app->initialized = 1; + return app; +} + + + +/* Retrieve the serial number and the time of the last update of the + card. The serial number is returned as a malloced string (hex + encoded) in SERIAL and the time of update is returned in STAMP. If + no update time is available the returned value is 0. Caller must + free SERIAL unless the function returns an error. */ +int +app_get_serial_and_stamp (APP app, char **serial, time_t *stamp) +{ + unsigned char *buf, *p; + int i; + + if (!app || !serial || !stamp) + return gpg_error (GPG_ERR_INV_VALUE); + + *serial = NULL; + *stamp = 0; /* not available */ + + buf = xtrymalloc (app->serialnolen * 2 + 1); + if (!buf) + return gpg_error_from_errno (errno); + for (p=buf, i=0; i < app->serialnolen; p +=2, i++) + sprintf (p, "%02X", app->serialno[i]); + *p = 0; + *serial = buf; + return 0; +} + + +/* Write out the application specifig status lines for the LEARN + command. */ +int +app_write_learn_status (APP app, CTRL ctrl) +{ + if (!app) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.learn_status) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + return app->fnc.learn_status (app, ctrl); +} + + +/* Perform a SETATTR operation. */ +int +app_setattr (APP app, const char *name, + int (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen) +{ + if (!app || !name || !*name || !value) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.setattr) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + return app->fnc.setattr (app, name, pincb, pincb_arg, value, valuelen); +} + +/* Create the signature and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +int +app_sign (APP app, const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.sign) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.sign (app, keyidstr, hashalgo, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("operation sign result: %s\n", gpg_strerror (rc)); + return rc; +} + +/* Create the signature using the INTERNAL AUTHENTICATE command and + return the allocated result in OUTDATA. If a PIN is required the + PINCB will be used to ask for the PIN; it should return the PIN in + an allocated buffer and put it into PIN. */ +int +app_auth (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.auth) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.auth (app, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("operation auth result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Decrypt the data in INDATA and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +int +app_decipher (APP app, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.decipher) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.decipher (app, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("operation decipher result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Perform a SETATTR operation. */ +int +app_genkey (APP app, CTRL ctrl, const char *keynostr, unsigned int flags, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + + if (!app || !keynostr || !*keynostr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.genkey) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.genkey (app, ctrl, keynostr, flags, pincb, pincb_arg); + if (opt.verbose) + log_info ("operation genkey result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Perform a GET CHALLENGE operation. This fucntion is special as it + directly accesses the card without any application specific + wrapper. */ +int +app_get_challenge (APP app, size_t nbytes, unsigned char *buffer) +{ + if (!app || !nbytes || !buffer) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + return iso7816_get_challenge (app->slot, nbytes, buffer); +} + + + +/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ +int +app_change_pin (APP app, CTRL ctrl, const char *chvnostr, int reset_mode, + int (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + int rc; + + if (!app || !chvnostr || !*chvnostr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.change_pin) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = app->fnc.change_pin (app, ctrl, chvnostr, reset_mode, pincb, pincb_arg); + if (opt.verbose) + log_info ("operation change_pin result: %s\n", gpg_strerror (rc)); + return rc; +} + + + + + + diff --git a/scd/card-common.h b/scd/card-common.h new file mode 100644 index 000000000..31f0dfe8f --- /dev/null +++ b/scd/card-common.h @@ -0,0 +1,73 @@ +/* card-common.h - Common declarations for all card types + * Copyright (C) 2001, 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef CARD_COMMON_H +#define CARD_COMMON_H + +/* Declaration of private data structure used by card-p15.c */ +struct p15private_s; + + +struct card_ctx_s { + int reader; /* used reader */ + struct sc_context *ctx; + struct sc_card *scard; + struct sc_pkcs15_card *p15card; /* only if there is a pkcs15 application */ + struct p15private_s *p15priv; /* private data used by card-p15.c */ + + struct { + int initialized; /* the card has been initialied and the function + pointers may be used. However for + unsupported operations the particular + function pointer is set to NULL */ + + int (*enum_keypairs) (CARD card, int idx, + unsigned char *keygrip, char **keyid); + int (*enum_certs) (CARD card, int idx, char **certid, int *certtype); + int (*read_cert) (CARD card, const char *certidstr, + unsigned char **cert, size_t *ncert); + int (*sign) (CARD card, + const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); + int (*decipher) (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); + } fnc; + +}; + +/*-- card.c --*/ +gpg_error_t map_sc_err (int rc); +int card_help_get_keygrip (KsbaCert cert, unsigned char *array); + +/*-- card-15.c --*/ +void p15_release_private_data (CARD card); + +/* constructors */ +void card_p15_bind (CARD card); +void card_dinsig_bind (CARD card); + + +#endif /*CARD_COMMON_H*/ diff --git a/scd/card-p15.c b/scd/card-p15.c new file mode 100644 index 000000000..3cf4ba519 --- /dev/null +++ b/scd/card-p15.c @@ -0,0 +1,502 @@ +/* card-p15.c - PKCS-15 based card access + * Copyright (C) 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifdef HAVE_OPENSC +#include <opensc/pkcs15.h> +#include <ksba.h> + +#include "scdaemon.h" +#include "card-common.h" + + +struct p15private_s { + int n_prkey_rsa_objs; + struct sc_pkcs15_object *prkey_rsa_objs[32]; + int n_cert_objs; + struct sc_pkcs15_object *cert_objs[32]; +}; + + +/* Allocate private data. */ +static int +init_private_data (CARD card) +{ + struct p15private_s *priv; + int rc; + + if (card->p15priv) + return 0; /* already done. */ + + priv = xtrycalloc (1, sizeof *priv); + if (!priv) + return out_of_core (); + + /* OpenSC (0.7.0) is a bit strange in that the get_objects functions + tries to be a bit too clever and implicitly does an enumeration + which eventually leads to the fact that every call to this + fucntion returns one more macthing object. The old code in + p15_enum_keypairs assume that it would alwyas return the same + numer of objects and used this to figure out what the last object + enumerated is. We now do an enum_objects just once and keep it + in the private data. */ + rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_PRKEY_RSA, + priv->prkey_rsa_objs, + DIM (priv->prkey_rsa_objs)); + if (rc < 0) + { + log_error ("private keys enumeration failed: %s\n", sc_strerror (rc)); + xfree (priv); + return gpg_error (GPG_ERR_CARD); + } + priv->n_prkey_rsa_objs = rc; + + /* Read all certificate objects. */ + rc = sc_pkcs15_get_objects (card->p15card, SC_PKCS15_TYPE_CERT_X509, + priv->cert_objs, + DIM (priv->cert_objs)); + if (rc < 0) + { + log_error ("private keys enumeration failed: %s\n", sc_strerror (rc)); + xfree (priv); + return gpg_error (GPG_ERR_CARD); + } + priv->n_cert_objs = rc; + + card->p15priv = priv; + return 0; +} + + +/* Release private data used in this module. */ +void +p15_release_private_data (CARD card) +{ + if (!card->p15priv) + return; + xfree (card->p15priv); + card->p15priv = NULL; +} + + + +/* See card.c for interface description */ +static int +p15_enum_keypairs (CARD card, int idx, + unsigned char *keygrip, char **keyid) +{ + int rc; + KsbaError krc; + struct p15private_s *priv; + struct sc_pkcs15_object *tmpobj; + int nobjs; + struct sc_pkcs15_prkey_info *pinfo; + struct sc_pkcs15_cert_info *certinfo; + struct sc_pkcs15_cert *certder; + KsbaCert cert; + + rc = init_private_data (card); + if (rc) + return rc; + priv = card->p15priv; + nobjs = priv->n_prkey_rsa_objs; + rc = 0; + if (idx >= nobjs) + return -1; + pinfo = priv->prkey_rsa_objs[idx]->data; + + /* now we need to read the certificate so that we can calculate the + keygrip */ + rc = sc_pkcs15_find_cert_by_id (card->p15card, &pinfo->id, &tmpobj); + if (rc) + { + log_info ("certificate for private key %d not found: %s\n", + idx, sc_strerror (rc)); + /* note, that we return the ID anyway */ + rc = gpg_error (GPG_ERR_MISSING_CERTIFICATE); + goto return_keyid; + } + certinfo = tmpobj->data; + rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder); + if (rc) + { + log_info ("failed to read certificate for private key %d: %s\n", + idx, sc_strerror (rc)); + return gpg_error (GPG_ERR_CARD); + } + + cert = ksba_cert_new (); + if (!cert) + { + gpg_error_t tmperr = out_of_core (); + sc_pkcs15_free_certificate (certder); + return tmperr; + } + krc = ksba_cert_init_from_mem (cert, certder->data, certder->data_len); + sc_pkcs15_free_certificate (certder); + if (krc) + { + log_error ("failed to parse the certificate for private key %d: %s\n", + idx, ksba_strerror (krc)); + ksba_cert_release (cert); + return gpg_error (GPG_ERR_CARD); + } + if (card_help_get_keygrip (cert, keygrip)) + { + log_error ("failed to calculate the keygrip of private key %d\n", idx); + ksba_cert_release (cert); + return gpg_error (GPG_ERR_CARD); + } + ksba_cert_release (cert); + + rc = 0; + return_keyid: + if (keyid) + { + char *p; + int i; + + *keyid = p = xtrymalloc (9+pinfo->id.len*2+1); + if (!*keyid) + return out_of_core (); + p = stpcpy (p, "P15-5015."); + for (i=0; i < pinfo->id.len; i++, p += 2) + sprintf (p, "%02X", pinfo->id.value[i]); + *p = 0; + } + + return rc; +} + +/* See card.c for interface description */ +static int +p15_enum_certs (CARD card, int idx, char **certid, int *type) +{ + int rc; + struct p15private_s *priv; + struct sc_pkcs15_object *obj; + struct sc_pkcs15_cert_info *cinfo; + int nobjs; + + rc = init_private_data (card); + if (rc) + return rc; + priv = card->p15priv; + nobjs = priv->n_cert_objs; + rc = 0; + if (idx >= nobjs) + return -1; + obj = priv->cert_objs[idx]; + cinfo = obj->data; + + if (certid) + { + char *p; + int i; + + *certid = p = xtrymalloc (9+cinfo->id.len*2+1); + if (!*certid) + return out_of_core (); + p = stpcpy (p, "P15-5015."); + for (i=0; i < cinfo->id.len; i++, p += 2) + sprintf (p, "%02X", cinfo->id.value[i]); + *p = 0; + } + if (type) + { + if (!obj->df) + *type = 0; /* unknown */ + else if (obj->df->type == SC_PKCS15_CDF) + *type = 100; + else if (obj->df->type == SC_PKCS15_CDF_TRUSTED) + *type = 101; + else if (obj->df->type == SC_PKCS15_CDF_USEFUL) + *type = 102; + else + *type = 0; /* error -> unknown */ + } + + return rc; +} + + + +static int +idstr_to_id (const char *idstr, struct sc_pkcs15_id *id) +{ + const char *s; + int n; + + /* For now we only support the standard DF */ + if (strncmp (idstr, "P15-5015.", 9) ) + return gpg_error (GPG_ERR_INV_ID); + for (s=idstr+9, n=0; hexdigitp (s); s++, n++) + ; + if (*s || (n&1)) + return gpg_error (GPG_ERR_INV_ID); /*invalid or odd number of digits*/ + n /= 2; + if (!n || n > SC_PKCS15_MAX_ID_SIZE) + return gpg_error (GPG_ERR_INV_ID); /* empty or too large */ + for (s=idstr+9, n=0; *s; s += 2, n++) + id->value[n] = xtoi_2 (s); + id->len = n; + return 0; +} + + +/* See card.c for interface description */ +static int +p15_read_cert (CARD card, const char *certidstr, + unsigned char **cert, size_t *ncert) +{ + struct sc_pkcs15_object *tmpobj; + struct sc_pkcs15_id certid; + struct sc_pkcs15_cert_info *certinfo; + struct sc_pkcs15_cert *certder; + int rc; + + if (!card || !certidstr || !cert || !ncert) + return gpg_error (GPG_ERR_INV_VALUE); + if (!card->p15card) + return gpg_error (GPG_ERR_NO_PKCS15_APP); + + rc = idstr_to_id (certidstr, &certid); + if (rc) + return rc; + + rc = sc_pkcs15_find_cert_by_id (card->p15card, &certid, &tmpobj); + if (rc) + { + log_info ("certificate '%s' not found: %s\n", + certidstr, sc_strerror (rc)); + return -1; + } + certinfo = tmpobj->data; + rc = sc_pkcs15_read_certificate (card->p15card, certinfo, &certder); + if (rc) + { + log_info ("failed to read certificate '%s': %s\n", + certidstr, sc_strerror (rc)); + return gpg_error (GPG_ERR_CARD); + } + + *cert = xtrymalloc (certder->data_len); + if (!*cert) + { + gpg_error_t tmperr = out_of_core (); + sc_pkcs15_free_certificate (certder); + return tmperr; + } + memcpy (*cert, certder->data, certder->data_len); + *ncert = certder->data_len; + sc_pkcs15_free_certificate (certder); + return 0; +} + + + + + +static int +p15_prepare_key (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, struct sc_pkcs15_object **r_keyobj) +{ + struct sc_pkcs15_id keyid; + struct sc_pkcs15_pin_info *pin; + struct sc_pkcs15_object *keyobj, *pinobj; + char *pinvalue; + int rc; + + rc = idstr_to_id (keyidstr, &keyid); + if (rc) + return rc; + + rc = sc_pkcs15_find_prkey_by_id (card->p15card, &keyid, &keyobj); + if (rc < 0) + { + log_error ("private key not found: %s\n", sc_strerror(rc)); + return gpg_error (GPG_ERR_NO_SECRET_KEY); + } + + rc = sc_pkcs15_find_pin_by_auth_id (card->p15card, + &keyobj->auth_id, &pinobj); + if (rc) + { + log_error ("failed to find PIN by auth ID: %s\n", sc_strerror (rc)); + return gpg_error (GPG_ERR_BAD_PIN_METHOD); + } + pin = pinobj->data; + + /* Fixme: pack this into a verification loop */ + /* Fixme: we might want to pass pin->min_length and + pin->stored_length */ + rc = pincb (pincb_arg, pinobj->label, &pinvalue); + if (rc) + { + log_info ("PIN callback returned error: %s\n", gnupg_strerror (rc)); + return rc; + } + + rc = sc_pkcs15_verify_pin (card->p15card, pin, + pinvalue, strlen (pinvalue)); + xfree (pinvalue); + if (rc) + { + log_info ("PIN verification failed: %s\n", sc_strerror (rc)); + return gpg_error (GPG_ERR_BAD_PIN); + } + + /* fixme: check wheter we need to release KEYOBJ in case of an error */ + *r_keyobj = keyobj; + return 0; +} + + +/* See card.c for interface description */ +static int +p15_sign (CARD card, const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + unsigned int cryptflags; + struct sc_pkcs15_object *keyobj; + int rc; + unsigned char *outbuf = NULL; + size_t outbuflen; + + if (hashalgo != GCRY_MD_SHA1) + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj); + if (rc) + return rc; + + cryptflags = SC_ALGORITHM_RSA_PAD_PKCS1; + + outbuflen = 1024; + outbuf = xtrymalloc (outbuflen); + if (!outbuf) + return out_of_core (); + + rc = sc_pkcs15_compute_signature (card->p15card, keyobj, + cryptflags, + indata, indatalen, + outbuf, outbuflen ); + if (rc < 0) + { + log_error ("failed to create signature: %s\n", sc_strerror (rc)); + rc = gpg_error (GPG_ERR_CARD); + } + else + { + *outdatalen = rc; + *outdata = outbuf; + outbuf = NULL; + rc = 0; + } + + xfree (outbuf); + return rc; +} + + +/* See card.c for description */ +static int +p15_decipher (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + struct sc_pkcs15_object *keyobj; + int rc; + unsigned char *outbuf = NULL; + size_t outbuflen; + + rc = p15_prepare_key (card, keyidstr, pincb, pincb_arg, &keyobj); + if (rc) + return rc; + + if (card && card->scard && card->scard->driver + && !strcasecmp (card->scard->driver->short_name, "tcos")) + { + /* very ugly hack to force the use of a local key. We need this + until we have fixed the initialization code for TCOS cards */ + struct sc_pkcs15_prkey_info *prkey = keyobj->data; + if ( !(prkey->key_reference & 0x80)) + { + prkey->key_reference |= 0x80; + log_debug ("using TCOS hack to force the use of local keys\n"); + } + if (*keyidstr && keyidstr[strlen(keyidstr)-1] == '6') + { + prkey->key_reference |= 1; + log_debug ("warning: using even more TCOS hacks\n"); + } + } + + outbuflen = indatalen < 256? 256 : indatalen; + outbuf = xtrymalloc (outbuflen); + if (!outbuf) + return out_of_core (); + + rc = sc_pkcs15_decipher (card->p15card, keyobj, + 0, + indata, indatalen, + outbuf, outbuflen); + if (rc < 0) + { + log_error ("failed to decipher the data: %s\n", sc_strerror (rc)); + rc = gpg_error (GPG_ERR_CARD); + } + else + { + *outdatalen = rc; + *outdata = outbuf; + outbuf = NULL; + rc = 0; + } + + xfree (outbuf); + return rc; +} + + + +/* Bind our operations to the card */ +void +card_p15_bind (CARD card) +{ + card->fnc.enum_keypairs = p15_enum_keypairs; + card->fnc.enum_certs = p15_enum_certs; + card->fnc.read_cert = p15_read_cert; + card->fnc.sign = p15_sign; + card->fnc.decipher = p15_decipher; +} +#endif /*HAVE_OPENSC*/ diff --git a/scd/card.c b/scd/card.c new file mode 100644 index 000000000..02b7bfdbf --- /dev/null +++ b/scd/card.c @@ -0,0 +1,564 @@ +/* card.c - SCdaemon card functions + * Copyright (C) 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifdef HAVE_OPENSC +#include <opensc/pkcs15.h> +#endif +#include <ksba.h> + +#include "scdaemon.h" +#include "card-common.h" + +/* Map the SC error codes to the GNUPG ones */ +gpg_error_t +map_sc_err (int rc) +{ + gpg_err_code_t e; + + switch (rc) + { + case 0: e = 0; break; +#ifdef HAVE_OPENSC + case SC_ERROR_NOT_SUPPORTED: e = GPG_ERR_NOT_SUPPORTED; break; + case SC_ERROR_PKCS15_APP_NOT_FOUND: e = GPG_ERR_NO_PKCS15_APP; break; + case SC_ERROR_OUT_OF_MEMORY: e = GPG_ERR_ENOMEM; break; + case SC_ERROR_CARD_NOT_PRESENT: e = GPG_ERR_CARD_NOT_PRESENT; break; + case SC_ERROR_CARD_REMOVED: e = GPG_ERR_CARD_REMOVED; break; + case SC_ERROR_INVALID_CARD: e = GPG_ERR_INV_CARD; break; +#endif + default: e = GPG_ERR_CARD; break; + } + return gpg_err_make (GPG_ERR_SOURCE_UNKNOWN, e); +} + +/* Get the keygrip from CERT, return 0 on success */ +int +card_help_get_keygrip (KsbaCert cert, unsigned char *array) +{ + gcry_sexp_t s_pkey; + int rc; + KsbaSexp p; + size_t n; + + p = ksba_cert_get_public_key (cert); + if (!p) + return -1; /* oops */ + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + return -1; /* libksba did not return a proper S-expression */ + rc = gcry_sexp_sscan ( &s_pkey, NULL, p, n); + xfree (p); + if (rc) + return -1; /* can't parse that S-expression */ + array = gcry_pk_get_keygrip (s_pkey, array); + gcry_sexp_release (s_pkey); + if (!array) + return -1; /* failed to calculate the keygrip */ + return 0; +} + + + + + + + +/* Create a new context for the card and figures out some basic + information of the card. Detects whgether a PKCS_15 application is + stored. + + Common errors: GPG_ERR_CARD_NOT_PRESENT */ +int +card_open (CARD *rcard) +{ +#ifdef HAVE_OPENSC + CARD card; + int rc; + + card = xtrycalloc (1, sizeof *card); + if (!card) + return out_of_core (); + card->reader = 0; + + rc = sc_establish_context (&card->ctx, "scdaemon"); + if (rc) + { + log_error ("failed to establish SC context: %s\n", sc_strerror (rc)); + rc = map_sc_err (rc); + goto leave; + } + if (card->reader >= card->ctx->reader_count) + { + log_error ("no card reader available\n"); + rc = gpg_error (GPG_ERR_CARD); + goto leave; + } + card->ctx->error_file = log_get_stream (); + card->ctx->debug = opt.debug_sc; + card->ctx->debug_file = log_get_stream (); + + if (sc_detect_card_presence (card->ctx->reader[card->reader], 0) != 1) + { + rc = gpg_error (GPG_ERR_CARD_NOT_PRESENT); + goto leave; + } + + rc = sc_connect_card (card->ctx->reader[card->reader], 0, &card->scard); + if (rc) + { + log_error ("failed to connect card in reader %d: %s\n", + card->reader, sc_strerror (rc)); + rc = map_sc_err (rc); + goto leave; + } + if (opt.verbose) + log_info ("connected to card in reader %d using driver `%s'\n", + card->reader, card->scard->driver->name); + + rc = sc_lock (card->scard); + if (rc) + { + log_error ("can't lock card in reader %d: %s\n", + card->reader, sc_strerror (rc)); + rc = map_sc_err (rc); + goto leave; + } + + + leave: + if (rc) + card_close (card); + else + *rcard = card; + + return rc; +#else + return gpg_error (GPG_ERR_NOT_SUPPORTED); +#endif +} + + +/* Close a card and release all resources */ +void +card_close (CARD card) +{ + if (card) + { +#ifdef HAVE_OPENSC + if (card->p15card) + { + sc_pkcs15_unbind (card->p15card); + card->p15card = NULL; + } + if (card->p15priv) + p15_release_private_data (card); + if (card->scard) + { + sc_unlock (card->scard); + sc_disconnect_card (card->scard, 0); + card->scard = NULL; + } + if (card->ctx) + { + sc_release_context (card->ctx); + card->ctx = NULL; + } +#endif + xfree (card); + } +} + +/* Locate a simple TLV encoded data object in BUFFER of LENGTH and + return a pointer to value as well as its length in NBYTES. Return + NULL if it was not found. Note, that the function does not check + whether the value fits into the provided buffer. */ +#ifdef HAVE_OPENSC +static const char * +find_simple_tlv (const unsigned char *buffer, size_t length, + int tag, size_t *nbytes) +{ + const char *s = buffer; + size_t n = length; + size_t len; + + for (;;) + { + buffer = s; + if (n < 2) + return NULL; /* buffer too short for tag and length. */ + len = s[1]; + s += 2; n -= 2; + if (len == 255) + { + if (n < 2) + return NULL; /* we expected 2 more bytes with the length. */ + len = (s[0] << 8) | s[1]; + s += 2; n -= 2; + } + if (*buffer == tag) + { + *nbytes = len; + return s; + } + if (len > n) + return NULL; /* buffer too short to skip to the next tag. */ + s += len; n -= len; + } +} +#endif /*HAVE_OPENSC*/ + +/* Find the ICC Serial Number within the provided BUFFER of LENGTH + (which should contain the GDO file) and return it as a hex encoded + string and allocated string in SERIAL. Return an error code when + the ICCSN was not found. */ +#ifdef HAVE_OPENSC +static int +find_iccsn (const unsigned char *buffer, size_t length, char **serial) +{ + size_t n; + const unsigned char *s; + char *p; + + s = find_simple_tlv (buffer, length, 0x5A, &n); + if (!s) + return gpg_error (GPG_ERR_CARD); + length -= s - buffer; + if (n > length) + { + /* Oops, it does not fit into the buffer. This is an invalid + encoding (or the buffer is too short. However, I have some + test cards with such an invalid encoding and therefore I use + this ugly workaround to return something I can further + experiment with. */ + if (n == 0x0D && length+1 == n) + { + log_debug ("enabling BMI testcard workaround\n"); + n--; + } + else + return gpg_error (GPG_ERR_CARD); /* Bad encoding; does + not fit into buffer. */ + } + if (!n) + return gpg_error (GPG_ERR_CARD); /* Well, that is too short. */ + + *serial = p = xtrymalloc (2*n+1); + if (!*serial) + return out_of_core (); + for (; n; n--, p += 2, s++) + sprintf (p, "%02X", *s); + *p = 0; + return 0; +} +#endif /*HAVE_OPENSC*/ + +/* Retrieve the serial number and the time of the last update of the + card. The serial number is returned as a malloced string (hex + encoded) in SERIAL and the time of update is returned in STAMP. + If no update time is available the returned value is 0. The serial + is mandatory for a PKCS_15 application and an error will be + returned if this value is not availbale. For non-PKCS-15 cards a + serial number is constructed by other means. Caller must free + SERIAL unless the function returns an error. */ +int +card_get_serial_and_stamp (CARD card, char **serial, time_t *stamp) +{ +#ifdef HAVE_OPENSC + int rc; + struct sc_path path; + struct sc_file *file; + unsigned char buf[256]; + int buflen; +#endif + + if (!card || !serial || !stamp) + return gpg_error (GPG_ERR_INV_VALUE); + + *serial = NULL; + *stamp = 0; /* not available */ + +#ifdef HAVE_OPENSC + if (!card->fnc.initialized) + { + card->fnc.initialized = 1; + /* The first use of this card tries to figure out the type of the card + and sets up the function pointers. */ + rc = sc_pkcs15_bind (card->scard, &card->p15card); + if (rc) + { + if (rc != SC_ERROR_PKCS15_APP_NOT_FOUND) + log_error ("binding of existing PKCS-15 failed in reader %d: %s\n", + card->reader, sc_strerror (rc)); + card->p15card = NULL; + rc = 0; + } + if (card->p15card) + card_p15_bind (card); + else + card_dinsig_bind (card); + card->fnc.initialized = 1; + } + + + /* We should lookup the iso 7812-1 and 8583-3 - argh ISO + practice is suppressing innovation - IETF rules! So we + always get the serialnumber from the 2F02 GDO file. */ + /* FIXME: in case we can't parse the 2F02 EF and we have a P15 card, + we should get the serial number from the respective P15 file */ + sc_format_path ("3F002F02", &path); + rc = sc_select_file (card->scard, &path, &file); + if (rc) + { + log_error ("sc_select_file failed: %s\n", sc_strerror (rc)); + return gpg_error (GPG_ERR_CARD); + } + if (file->type != SC_FILE_TYPE_WORKING_EF + || file->ef_structure != SC_FILE_EF_TRANSPARENT) + { + log_error ("wrong type or structure of GDO file\n"); + sc_file_free (file); + return gpg_error (GPG_ERR_CARD); + } + + if (!file->size || file->size >= DIM(buf) ) + { /* FIXME: Use a real parser */ + log_error ("unsupported size of GDO file (%d)\n", file->size); + sc_file_free (file); + return gpg_error (GPG_ERR_CARD); + } + buflen = file->size; + + rc = sc_read_binary (card->scard, 0, buf, buflen, 0); + sc_file_free (file); + if (rc < 0) + { + log_error ("error reading GDO file: %s\n", sc_strerror (rc)); + return gpg_error (GPG_ERR_CARD); + } + if (rc != buflen) + { + log_error ("short read on GDO file\n"); + return gpg_error (GPG_ERR_CARD); + } + + rc = find_iccsn (buf, buflen, serial); + if (gpg_err_code (rc) == GPG_ERR_CARD) + log_error ("invalid structure of GDO file\n"); + if (!rc && card->p15card && !strcmp (*serial, "D27600000000000000000000")) + { /* This is a German card with a silly serial number. Try to get + the serial number from the EF(TokenInfo). We indicate such a + serial number by the using the prefix: "FF0100". */ + const char *efser = card->p15card->serial_number; + char *p; + + if (!efser) + efser = ""; + + xfree (*serial); + *serial = NULL; + p = xtrymalloc (strlen (efser) + 7); + if (!p) + rc = out_of_core (); + else + { + strcpy (p, "FF0100"); + strcpy (p+6, efser); + *serial = p; + } + } + else if (!rc && **serial == 'F' && (*serial)[1] == 'F') + { /* The serial number starts with our special prefix. This + requires that we put our default prefix "FF0000" in front. */ + char *p = xtrymalloc (strlen (*serial) + 7); + if (!p) + { + xfree (*serial); + *serial = NULL; + rc = out_of_core (); + } + else + { + strcpy (p, "FF0000"); + strcpy (p+6, *serial); + xfree (*serial); + *serial = p; + } + } + return rc; +#else + return gpg_error (GPG_ERR_NOT_SUPPORTED); +#endif +} + + +/* Enumerate all keypairs on the card and return the Keygrip as well + as the internal identification of the key. KEYGRIP must be a + caller provided buffer with a size of 20 bytes which will receive + the KEYGRIP of the keypair. If KEYID is not NULL, it returns the + ID field of the key in allocated memory; this is a string without + spaces. The function returns -1 when all keys have been + enumerated. Note that the error GPG_ERR_MISSING_CERTIFICATE may be + returned if there is just the private key but no public key (ie.e a + certificate) available. Applications might want to continue + enumerating after this error.*/ +int +card_enum_keypairs (CARD card, int idx, + unsigned char *keygrip, + char **keyid) +{ + int rc; + + if (keyid) + *keyid = NULL; + + if (!card || !keygrip) + return gpg_error (GPG_ERR_INV_VALUE); + if (idx < 0) + return gpg_error (GPG_ERR_INV_INDEX); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.enum_keypairs) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.enum_keypairs (card, idx, keygrip, keyid); + if (opt.verbose) + log_info ("card operation enum_keypairs result: %s\n", + gpg_strerror (rc)); + return rc; +} + + +/* Enumerate all trusted certificates available on the card, return + their ID in CERT and the type in CERTTYPE. Types of certificates + are: + 0 := Unknown + 100 := Regular X.509 cert + 101 := Trusted X.509 cert + 102 := Useful X.509 cert + */ +int +card_enum_certs (CARD card, int idx, char **certid, int *certtype) +{ + int rc; + + if (certid) + *certid = NULL; + + if (!card) + return gpg_error (GPG_ERR_INV_VALUE); + if (idx < 0) + return gpg_error (GPG_ERR_INV_INDEX); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.enum_certs) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.enum_certs (card, idx, certid, certtype); + if (opt.verbose) + log_info ("card operation enum_certs result: %s\n", + gpg_strerror (rc)); + return rc; +} + + + +/* Read the certificate identified by CERTIDSTR which is the + hexadecimal encoded ID of the certificate, prefixed with the string + "3F005015.". The certificate is return in DER encoded form in CERT + and NCERT. */ +int +card_read_cert (CARD card, const char *certidstr, + unsigned char **cert, size_t *ncert) +{ + int rc; + + if (!card || !certidstr || !cert || !ncert) + return gpg_error (GPG_ERR_INV_VALUE); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.read_cert) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.read_cert (card, certidstr, cert, ncert); + if (opt.verbose) + log_info ("card operation read_cert result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Create the signature and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +int +card_sign (CARD card, const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.sign) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.sign (card, keyidstr, hashalgo, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("card operation sign result: %s\n", gpg_strerror (rc)); + return rc; +} + + +/* Create the signature and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +int +card_decipher (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + int rc; + + if (!card || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!card->fnc.initialized) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!card->fnc.decipher) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + rc = card->fnc.decipher (card, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + if (opt.verbose) + log_info ("card operation decipher result: %s\n", gpg_strerror (rc)); + return rc; +} + diff --git a/scd/command.c b/scd/command.c new file mode 100644 index 000000000..c53af84f9 --- /dev/null +++ b/scd/command.c @@ -0,0 +1,1034 @@ +/* command.c - SCdaemon command handler + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <ksba.h> + +#include <assuan.h> + +#include "scdaemon.h" +#include "app-common.h" + +/* maximum length aloowed as a PIN; used for INQUIRE NEEDPIN */ +#define MAXLEN_PIN 100 + +#define set_error(e,t) assuan_set_error (ctx, ASSUAN_ ## e, (t)) + +/* Data used to associate an Assuan context with local server data */ +struct server_local_s { + ASSUAN_CONTEXT assuan_ctx; +}; + + +/* Check whether the option NAME appears in LINE */ +static int +has_option (const char *line, const char *name) +{ + const char *s; + int n = strlen (name); + + s = strstr (line, name); + return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n))); +} + + + + +/* Note, that this reset_notify is also used for cleanup purposes. */ +static void +reset_notify (ASSUAN_CONTEXT ctx) +{ + CTRL ctrl = assuan_get_pointer (ctx); + + if (ctrl->card_ctx) + { + card_close (ctrl->card_ctx); + ctrl->card_ctx = NULL; + xfree (ctrl->in_data.value); + ctrl->in_data.value = NULL; + } + if (ctrl->app_ctx) + { + /* FIXME: close the application. */ + xfree (ctrl->app_ctx); + ctrl->app_ctx = NULL; + } +} + + +static int +option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) +{ + return 0; +} + + +/* If the card has not yet been opened, do it. Note that this + function returns an Assuan error, so don't map the error a second + time */ +static AssuanError +open_card (CTRL ctrl) +{ + if (ctrl->app_ctx) + return 0; /* Already initialized for one specific application. */ + if (ctrl->card_ctx) + return 0; /* Already initialized using a card context. */ + + ctrl->app_ctx = select_application (); + if (!ctrl->app_ctx) + { /* No application found - fall back to old mode. */ + int rc = card_open (&ctrl->card_ctx); + if (rc) + return map_to_assuan_status (rc); + } + return 0; +} + + +/* Do the percent and plus/space unescaping in place and return tghe + length of the valid buffer. */ +static size_t +percent_plus_unescape (unsigned char *string) +{ + unsigned char *p = string; + size_t n = 0; + + while (*string) + { + if (*string == '%' && string[1] && string[2]) + { + string++; + *p++ = xtoi_2 (string); + n++; + string+= 2; + } + else if (*string == '+') + { + *p++ = ' '; + n++; + string++; + } + else + { + *p++ = *string++; + n++; + } + } + + return n; +} + + + +/* SERIALNO + + Return the serial number of the card using a status reponse. This + functon should be used to check for the presence of a card. + + This function is special in that it can be used to reset the card. + Most other functions will return an error when a card change has + been detected and the use of this function is therefore required. + + Background: We want to keep the client clear of handling card + changes between operations; i.e. the client can assume that all + operations are done on the same card unless he calls this function. + */ +static int +cmd_serialno (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc = 0; + char *serial_and_stamp; + char *serial; + time_t stamp; + + if ((rc = open_card (ctrl))) + return rc; + + if (ctrl->app_ctx) + rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp); + else + rc = card_get_serial_and_stamp (ctrl->card_ctx, &serial, &stamp); + if (rc) + return map_to_assuan_status (rc); + rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp); + xfree (serial); + if (rc < 0) + return ASSUAN_Out_Of_Core; + rc = 0; + assuan_write_status (ctx, "SERIALNO", serial_and_stamp); + free (serial_and_stamp); + return 0; +} + + + + +/* LEARN [--force] + + Learn all useful information of the currently inserted card. When + used without the force options, the command might do an INQUIRE + like this: + + INQUIRE KNOWNCARDP <hexstring_with_serialNumber> <timestamp> + + The client should just send an "END" if the processing should go on + or a "CANCEL" to force the function to terminate with a Cancel + error message. The response of this command is a list of status + lines formatted as this: + + S APPTYPE <apptype> + + This returns the type of the application, currently the strings: + + P15 = PKCS-15 structure used + DINSIG = DIN SIG + OPENPGP = OpenPGP card + + are implemented. These strings are aliases for the AID + + S KEYPAIRINFO <hexstring_with_keygrip> <hexstring_with_id> + + If there is no certificate yet stored on the card a single "X" is + returned as the keygrip. In addition to the keypair info, information + about all certificates stored on the card is also returned: + + S CERTINFO <certtype> <hexstring_with_id> + + Where CERTTYPE is a number indicating the type of certificate: + 0 := Unknown + 100 := Regular X.509 cert + 101 := Trusted X.509 cert + 102 := Useful X.509 cert + + For certain cards, more information will be returned: + + S KEY-FPR <no> <hexstring> + + For OpenPGP cards this returns the stored fingerprints of the + keys. This can be used check whether a key is available on the + card. NO may be 1, 2 or 3. + + S CA-FPR <no> <hexstring> + + Similar to above, these are the fingerprints of keys assumed to be + ultimately trusted. + + S DISP-NAME <name_of_card_holder> + + The name of the card holder as stored on the card; percent + aescaping takes place, spaces are encoded as '+' + + S PUBKEY-URL <url> + + The URL to be used for locating the entire public key. + +*/ +static int +cmd_learn (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc = 0; + int idx; + + if ((rc = open_card (ctrl))) + return rc; + + /* Unless the force option is used we try a shortcut by identifying + the card using a serial number and inquiring the client with + that. The client may choose to cancel the operation if he already + knows about this card */ + { + char *serial_and_stamp; + char *serial; + time_t stamp; + + if (ctrl->app_ctx) + rc = app_get_serial_and_stamp (ctrl->app_ctx, &serial, &stamp); + else + rc = card_get_serial_and_stamp (ctrl->card_ctx, &serial, &stamp); + if (rc) + return map_to_assuan_status (rc); + rc = asprintf (&serial_and_stamp, "%s %lu", serial, (unsigned long)stamp); + xfree (serial); + if (rc < 0) + return ASSUAN_Out_Of_Core; + rc = 0; + assuan_write_status (ctx, "SERIALNO", serial_and_stamp); + + if (!has_option (line, "--force")) + { + char *command; + + rc = asprintf (&command, "KNOWNCARDP %s", serial_and_stamp); + if (rc < 0) + { + free (serial_and_stamp); + return ASSUAN_Out_Of_Core; + } + rc = 0; + rc = assuan_inquire (ctx, command, NULL, NULL, 0); + free (command); /* (must use standard free here) */ + if (rc) + { + if (rc != ASSUAN_Canceled) + log_error ("inquire KNOWNCARDP failed: %s\n", + assuan_strerror (rc)); + free (serial_and_stamp); + return rc; + } + /* not canceled, so we have to proceeed */ + } + free (serial_and_stamp); + } + + /* Return information about the certificates. */ + if (ctrl->app_ctx) + rc = -1; /* This information is not yet available for applications. */ + for (idx=0; !rc; idx++) + { + char *certid; + int certtype; + + rc = card_enum_certs (ctrl->card_ctx, idx, &certid, &certtype); + if (!rc) + { + char *buf; + + buf = xtrymalloc (40 + 1 + strlen (certid) + 1); + if (!buf) + rc = out_of_core (); + else + { + sprintf (buf, "%d %s", certtype, certid); + assuan_write_status (ctx, "CERTINFO", buf); + xfree (buf); + } + } + xfree (certid); + } + if (rc == -1) + rc = 0; + + + /* Return information about the keys. */ + if (ctrl->app_ctx) + rc = -1; /* This information is not yet available for applications. */ + for (idx=0; !rc; idx++) + { + unsigned char keygrip[20]; + char *keyid; + int no_cert = 0; + + rc = card_enum_keypairs (ctrl->card_ctx, idx, keygrip, &keyid); + if (gpg_err_code (rc) == GPG_ERR_MISSING_CERT && keyid) + { + /* this does happen with an incomplete personalized + card; i.e. during the time we have stored the key on the + card but not stored the certificate; probably becuase it + has not yet been received back from the CA. Note that we + must release KEYID in this case. */ + rc = 0; + no_cert = 1; + } + if (!rc) + { + char *buf, *p; + + buf = p = xtrymalloc (40 + 1 + strlen (keyid) + 1); + if (!buf) + rc = out_of_core (); + else + { + int i; + + if (no_cert) + *p++ = 'X'; + else + { + for (i=0; i < 20; i++, p += 2) + sprintf (p, "%02X", keygrip[i]); + } + *p++ = ' '; + strcpy (p, keyid); + assuan_write_status (ctx, "KEYPAIRINFO", buf); + xfree (buf); + } + } + xfree (keyid); + } + if (rc == -1) + rc = 0; + + if (!rc && ctrl->app_ctx) + rc = app_write_learn_status (ctrl->app_ctx, ctrl); + + + return map_to_assuan_status (rc); +} + + + +/* READCERT <hexified_certid> + + */ +static int +cmd_readcert (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *cert; + size_t ncert; + + if ((rc = open_card (ctrl))) + return rc; + + if (ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert); + if (rc) + { + log_error ("card_read_cert failed: %s\n", gpg_strerror (rc)); + } + if (!rc) + { + rc = assuan_send_data (ctx, cert, ncert); + xfree (cert); + if (rc) + return rc; + } + + return map_to_assuan_status (rc); +} + + +/* READKEY <hexified_certid> + + Return the public key for the given cert or key ID as an standard + S-Expression. */ +static int +cmd_readkey (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *cert = NULL; + size_t ncert, n; + KsbaCert kc = NULL; + KsbaSexp p; + + if ((rc = open_card (ctrl))) + return rc; + + if (ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert); + if (rc) + { + log_error ("card_read_cert failed: %s\n", gpg_strerror (rc)); + goto leave; + } + + kc = ksba_cert_new (); + if (!kc) + { + rc = out_of_core (); + xfree (cert); + goto leave; + } + rc = ksba_cert_init_from_mem (kc, cert, ncert); + if (rc) + { + log_error ("failed to parse the certificate: %s\n", ksba_strerror (rc)); + rc = map_ksba_err (rc); + goto leave; + } + + p = ksba_cert_get_public_key (kc); + if (!p) + { + rc = gpg_error (GPG_ERR_NO_PUBKEY); + goto leave; + } + + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + rc = assuan_send_data (ctx, p, n); + rc = map_assuan_err (rc); + xfree (p); + + + leave: + ksba_cert_release (kc); + xfree (cert); + return map_to_assuan_status (rc); +} + + + + +/* SETDATA <hexstring> + + The client should use this command to tell us the data he want to + sign. */ +static int +cmd_setdata (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int n; + char *p; + unsigned char *buf; + + /* parse the hexstring */ + for (p=line,n=0; hexdigitp (p); p++, n++) + ; + if (*p) + return set_error (Parameter_Error, "invalid hexstring"); + if (!n) + return set_error (Parameter_Error, "no data given"); + if ((n&1)) + return set_error (Parameter_Error, "odd number of digits"); + n /= 2; + buf = xtrymalloc (n); + if (!buf) + return ASSUAN_Out_Of_Core; + + ctrl->in_data.value = buf; + ctrl->in_data.valuelen = n; + for (p=line, n=0; n < ctrl->in_data.valuelen; p += 2, n++) + buf[n] = xtoi_2 (p); + return 0; +} + + + +static int +pin_cb (void *opaque, const char *info, char **retstr) +{ + ASSUAN_CONTEXT ctx = opaque; + char *command; + int rc; + char *value; + size_t valuelen; + + *retstr = NULL; + log_debug ("asking for PIN '%s'\n", info); + + rc = asprintf (&command, "NEEDPIN %s", info); + if (rc < 0) + return out_of_core (); + + /* FIXME: Write an inquire function which returns the result in + secure memory */ + rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); + free (command); + if (rc) + return map_assuan_err (rc); + + if (!valuelen || value[valuelen-1]) + { + /* We require that the returned value is an UTF-8 string */ + xfree (value); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + *retstr = value; + return 0; +} + + +/* PKSIGN <hexified_id> + + */ +static int +cmd_pksign (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *outdata; + size_t outdatalen; + char *keyidstr; + + if ((rc = open_card (ctrl))) + return rc; + + /* We have to use a copy of the key ID because the function may use + the pin_cb which in turn uses the assuan line buffer and thus + overwriting the original line with the keyid */ + keyidstr = strdup (line); + if (!keyidstr) + return ASSUAN_Out_Of_Core; + + if (ctrl->app_ctx) + rc = app_sign (ctrl->app_ctx, + keyidstr, GCRY_MD_SHA1, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + else + rc = card_sign (ctrl->card_ctx, + keyidstr, GCRY_MD_SHA1, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + free (keyidstr); + if (rc) + { + log_error ("card_sign failed: %s\n", gpg_strerror (rc)); + } + else + { + rc = assuan_send_data (ctx, outdata, outdatalen); + xfree (outdata); + if (rc) + return rc; /* that is already an assuan error code */ + } + + return map_to_assuan_status (rc); +} + +/* PKAUTH <hexified_id> + + */ +static int +cmd_pkauth (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *outdata; + size_t outdatalen; + char *keyidstr; + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + /* We have to use a copy of the key ID because the function may use + the pin_cb which in turn uses the assuan line buffer and thus + overwriting the original line with the keyid */ + keyidstr = strdup (line); + if (!keyidstr) + return ASSUAN_Out_Of_Core; + + rc = app_auth (ctrl->app_ctx, + keyidstr, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + free (keyidstr); + if (rc) + { + log_error ("app_auth_sign failed: %s\n", gpg_strerror (rc)); + } + else + { + rc = assuan_send_data (ctx, outdata, outdatalen); + xfree (outdata); + if (rc) + return rc; /* that is already an assuan error code */ + } + + return map_to_assuan_status (rc); +} + +/* PKDECRYPT <hexified_id> + + */ +static int +cmd_pkdecrypt (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *outdata; + size_t outdatalen; + char *keyidstr; + + if ((rc = open_card (ctrl))) + return rc; + + keyidstr = strdup (line); + if (!keyidstr) + return ASSUAN_Out_Of_Core; + if (ctrl->app_ctx) + rc = app_decipher (ctrl->app_ctx, + keyidstr, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + else + rc = card_decipher (ctrl->card_ctx, + keyidstr, + pin_cb, ctx, + ctrl->in_data.value, ctrl->in_data.valuelen, + &outdata, &outdatalen); + free (keyidstr); + if (rc) + { + log_error ("card_create_signature failed: %s\n", gpg_strerror (rc)); + } + else + { + rc = assuan_send_data (ctx, outdata, outdatalen); + xfree (outdata); + if (rc) + return rc; /* that is already an assuan error code */ + } + + return map_to_assuan_status (rc); +} + + +/* SETATTR <name> <value> + + This command is used to store data on a a smartcard. The allowed + names and values are depend on the currently selected smartcard + application. NAME and VALUE must be percent and '+' escaped. + + However, the curent implementation assumes that Name is not escaped; + this works as long as noone uses arbitrary escaping. + + A PIN will be requested for most NAMEs. See the corresponding + setattr function of the actually used application (app-*.c) for + details. */ +static int +cmd_setattr (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + char *keyword; + int keywordlen; + size_t nbytes; + + if ((rc = open_card (ctrl))) + return rc; + + keyword = line; + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + if (*line) + *line++ = 0; + while (spacep (line)) + line++; + nbytes = percent_plus_unescape (line); + + rc = app_setattr (ctrl->app_ctx, keyword, pin_cb, ctx, line, nbytes); + + return map_to_assuan_status (rc); +} + +/* GENKEY [--force] <no> + + Generate a key on-card identified by NO, which is application + specific. Return values are application specific. For OpenPGP + cards 2 status lines are returned: + + S KEY-FPR <hexstring> + S KEY-CREATED-AT <seconds_since_epoch> + S KEY-DATA [p|n] <hexdata> + + + --force is required to overwriet an already existing key. The + KEY-CREATED-AT is required for further processing because it is + part of the hashed key material for the fingerprint. + + The public part of the key can also later be retrieved using the + READKEY command. + + */ +static int +cmd_genkey (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + char *keyno; + int force = has_option (line, "--force"); + + /* Skip over options. */ + while ( *line == '-' && line[1] == '-' ) + { + while (!spacep (line)) + line++; + while (spacep (line)) + line++; + } + if (!*line) + return set_error (Parameter_Error, "no key number given"); + keyno = line; + while (!spacep (line)) + line++; + *line = 0; + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + rc = app_genkey (ctrl->app_ctx, ctrl, keyno, force? 1:0, pin_cb, ctx); + + return map_to_assuan_status (rc); +} + + +/* RANDOM <nbytes> + + Get NBYTES of random from the card and send them back as data. +*/ +static int +cmd_random (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + size_t nbytes; + unsigned char *buffer; + + if (!*line) + return set_error (Parameter_Error, "number of requested bytes missing"); + nbytes = strtoul (line, NULL, 0); + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + buffer = xtrymalloc (nbytes); + if (!buffer) + return ASSUAN_Out_Of_Core; + + rc = app_get_challenge (ctrl->app_ctx, nbytes, buffer); + if (!rc) + { + rc = assuan_send_data (ctx, buffer, nbytes); + xfree (buffer); + return rc; /* that is already an assuan error code */ + } + xfree (buffer); + + return map_to_assuan_status (rc); +} + + +/* PASSWD [--reset] <chvno> + + Change the PIN or reset thye retry counter of the card holder + verfication vector CHVNO. */ +static int +cmd_passwd (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc; + char *chvnostr; + int reset_mode = has_option (line, "--reset"); + + /* Skip over options. */ + while (*line == '-' && line[1] == '-') + { + while (!spacep (line)) + line++; + while (spacep (line)) + line++; + } + if (!*line) + return set_error (Parameter_Error, "no CHV number given"); + chvnostr = line; + while (!spacep (line)) + line++; + *line = 0; + + if ((rc = open_card (ctrl))) + return rc; + + if (!ctrl->app_ctx) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, reset_mode, pin_cb, ctx +); + if (rc) + log_error ("command passwd failed: %s\n", gpg_strerror (rc)); + return map_to_assuan_status (rc); +} + + + + +/* Tell the assuan library about our commands */ +static int +register_commands (ASSUAN_CONTEXT ctx) +{ + static struct { + const char *name; + int (*handler)(ASSUAN_CONTEXT, char *line); + } table[] = { + { "SERIALNO", cmd_serialno }, + { "LEARN", cmd_learn }, + { "READCERT", cmd_readcert }, + { "READKEY", cmd_readkey }, + { "SETDATA", cmd_setdata }, + { "PKSIGN", cmd_pksign }, + { "PKAUTH", cmd_pkauth }, + { "PKDECRYPT", cmd_pkdecrypt }, + { "INPUT", NULL }, + { "OUTPUT", NULL }, + { "SETATTR", cmd_setattr }, + { "GENKEY", cmd_genkey }, + { "RANDOM", cmd_random }, + { "PASSWD", cmd_passwd }, + { NULL } + }; + int i, rc; + + for (i=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler); + if (rc) + return rc; + } + assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready"); + + assuan_register_reset_notify (ctx, reset_notify); + assuan_register_option_handler (ctx, option_handler); + return 0; +} + + +/* Startup the server. If LISTEN_FD is given as -1, this is simple + piper server, otherwise it is a regular server */ +void +scd_command_handler (int listen_fd) +{ + int rc; + ASSUAN_CONTEXT ctx; + struct server_control_s ctrl; + + memset (&ctrl, 0, sizeof ctrl); + scd_init_default_ctrl (&ctrl); + + if (listen_fd == -1) + { + int filedes[2]; + + filedes[0] = 0; + filedes[1] = 1; + rc = assuan_init_pipe_server (&ctx, filedes); + } + else + { + rc = assuan_init_socket_server (&ctx, listen_fd); + } + if (rc) + { + log_error ("failed to initialize the server: %s\n", + assuan_strerror(rc)); + scd_exit (2); + } + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to register commands with Assuan: %s\n", + assuan_strerror(rc)); + scd_exit (2); + } + assuan_set_pointer (ctx, &ctrl); + ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local); + ctrl.server_local->assuan_ctx = ctx; + + if (DBG_ASSUAN) + assuan_set_log_stream (ctx, log_get_stream ()); + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", assuan_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", assuan_strerror (rc)); + continue; + } + } + reset_notify (ctx); /* used for cleanup */ + + assuan_deinit_server (ctx); +} + + +/* Send a line with status information via assuan and escape all given + buffers. The variable elements are pairs of (char *, size_t), + terminated with a (NULL, 0). */ +void +send_status_info (CTRL ctrl, const char *keyword, ...) +{ + va_list arg_ptr; + const unsigned char *value; + size_t valuelen; + char buf[950], *p; + size_t n; + ASSUAN_CONTEXT ctx = ctrl->server_local->assuan_ctx; + + va_start (arg_ptr, keyword); + + p = buf; + n = 0; + while ( (value = va_arg (arg_ptr, const unsigned char *)) ) + { + valuelen = va_arg (arg_ptr, size_t); + if (!valuelen) + continue; /* empty buffer */ + if (n) + { + *p++ = ' '; + n++; + } + for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++) + { + if (*value < ' ' || *value == '+') + { + sprintf (p, "%%%02X", *value); + p += 3; + } + else if (*value == ' ') + *p++ = '+'; + else + *p++ = *value; + } + } + *p = 0; + assuan_write_status (ctx, keyword, buf); + + va_end (arg_ptr); +} + diff --git a/scd/iso7816.c b/scd/iso7816.c new file mode 100644 index 000000000..8903d8a5c --- /dev/null +++ b/scd/iso7816.c @@ -0,0 +1,371 @@ +/* iso7816.c - ISO 7816 commands + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dlfcn.h> + +#include "scdaemon.h" +#include "iso7816.h" +#include "apdu.h" + +#define CMD_SELECT_FILE 0xA4 +#define CMD_VERIFY 0x20 +#define CMD_CHANGE_REFERENCE_DATA 0x24 +#define CMD_RESET_RETRY_COUNTER 0x2C +#define CMD_GET_DATA 0xCA +#define CMD_PUT_DATA 0xDA +#define CMD_PSO 0x2A +#define CMD_INTERNAL_AUTHENTICATE 0x88 +#define CMD_GENERATE_KEYPAIR 0x47 +#define CMD_GET_CHALLENGE 0x84 + +static gpg_error_t +map_sw (int sw) +{ + gpg_err_code_t ec; + + switch (sw) + { + case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break; + case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break; + case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break; + case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break; + case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break; + case SW_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break; + case SW_BAD_PARAMETER: ec = GPG_ERR_INV_VALUE; break; + case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break; + case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break; + case SW_INS_NOT_SUP: ec = GPG_ERR_CARD; break; + case SW_CLA_NOT_SUP: ec = GPG_ERR_CARD; break; + case SW_SUCCESS: ec = 0; break; + + case SW_HOST_OUT_OF_CORE: ec = GPG_ERR_ENOMEM; break; + case SW_HOST_INV_VALUE: ec = GPG_ERR_INV_VALUE; break; + case SW_HOST_INCOMPLETE_CARD_RESPONSE: ec = GPG_ERR_CARD; break; + default: + if ((sw & 0x010000)) + ec = GPG_ERR_GENERAL; /* Should not happen. */ + else if ((sw & 0xff00) == SW_MORE_DATA) + ec = 0; /* This should actually never been seen here. */ + else + ec = GPG_ERR_CARD; + } + return gpg_error (ec); +} + +/* This function is specialized version of the SELECT FILE command. + SLOT is the card and reader as created for example by + apdu_open_reader (), AID is a buffer of size AIDLEN holding the + requested application ID. The function can't be used to enumerate + AIDs and won't return the AID on success. The return value is 0 + for okay or GNUPG error code. Note that ISO error codes are + internally mapped. */ +gpg_error_t +iso7816_select_application (int slot, const char *aid, size_t aidlen) +{ + int sw; + + sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4, 0, aidlen, aid); + return map_sw (sw); +} + + +/* Perform a VERIFY command on SLOT using the card holder verification + vector CHVNO with a CHV of lenght CHVLEN. Returns 0 on success. */ +gpg_error_t +iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen) +{ + int sw; + + sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv); + return map_sw (sw); +} + +/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder + verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN + 0), a "change reference data" is done, otherwise an "exchange + reference data". The new reference data is expected in NEWCHV of + length NEWCHVLEN. */ +gpg_error_t +iso7816_change_reference_data (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen) +{ + int sw; + char *buf; + + if ((!oldchv && oldchvlen) + || (oldchv && !oldchvlen) + || !newchv || !newchvlen ) + return gpg_error (GPG_ERR_INV_VALUE); + + buf = xtrymalloc (oldchvlen + newchvlen); + if (!buf) + return out_of_core (); + if (oldchvlen) + memcpy (buf, oldchv, oldchvlen); + memcpy (buf+oldchvlen, newchv, newchvlen); + + sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA, + oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf); + xfree (buf); + return map_sw (sw); + +} + +gpg_error_t +iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen) +{ + int sw; + + if (!newchv || !newchvlen ) + return gpg_error (GPG_ERR_INV_VALUE); + + sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER, + 2, chvno, newchvlen, newchv); + return map_sw (sw); +} + + +/* Perform a GET DATA command requesting TAG and storing the result in + a newly allocated buffer at the address passed by RESULT. Return + the length of this data at the address of RESULTLEN. */ +gpg_error_t +iso7816_get_data (int slot, int tag, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_GET_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +/* Perform a PUT DATA command on card in SLOT. Write DATA of length + DATALEN to TAG. */ +gpg_error_t +iso7816_put_data (int slot, int tag, + const unsigned char *data, size_t datalen) +{ + int sw; + + sw = apdu_send_simple (slot, 0x00, CMD_PUT_DATA, + ((tag >> 8) & 0xff), (tag & 0xff), + datalen, data); + return map_sw (sw); +} + + +/* Perform the security operation COMPUTE DIGITAL SIGANTURE. On + success 0 is returned and the data is availavle in a newly + allocated buffer stored at RESULT with its length stored at + RESULTLEN. */ +gpg_error_t +iso7816_compute_ds (int slot, const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, data, + result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +/* Perform the security operation DECIPHER. On + success 0 is returned and the plaintext is available in a newly + allocated buffer stored at RESULT with its length stored at + RESULTLEN. */ +gpg_error_t +iso7816_decipher (int slot, const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + unsigned char *buf; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + /* We need to prepend the padding indicator. */ + buf = xtrymalloc (datalen + 1); + if (!buf) + return out_of_core (); + *buf = 0; /* Padding indicator. */ + memcpy (buf+1, data, datalen); + sw = apdu_send (slot, 0x00, CMD_PSO, 0x80, 0x86, datalen+1, buf, + result, resultlen); + xfree (buf); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +gpg_error_t +iso7816_internal_authenticate (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0, + datalen, data, result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +static gpg_error_t +generate_keypair (int slot, int readonly, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + int sw; + + if (!data || !datalen || !result || !resultlen) + return gpg_error (GPG_ERR_INV_VALUE); + *result = NULL; + *resultlen = 0; + + sw = apdu_send (slot, 0x00, CMD_GENERATE_KEYPAIR, readonly? 0x81:0x80, 0, + datalen, data, result, resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (*result); + *result = NULL; + *resultlen = 0; + return map_sw (sw); + } + + return 0; +} + + +gpg_error_t +iso7816_generate_keypair (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + return generate_keypair (slot, 0, data, datalen, result, resultlen); +} + + +gpg_error_t +iso7816_read_public_key (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen) +{ + return generate_keypair (slot, 1, data, datalen, result, resultlen); +} + + + +gpg_error_t +iso7816_get_challenge (int slot, int length, unsigned char *buffer) +{ + int sw; + unsigned char *result; + size_t resultlen, n; + + if (!buffer || length < 1) + return gpg_error (GPG_ERR_INV_VALUE); + + do + { + result = NULL; + n = length > 254? 254 : length; + sw = apdu_send_le (slot, 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, + n, + &result, &resultlen); + if (sw != SW_SUCCESS) + { + /* Make sure that pending buffers are released. */ + xfree (result); + return map_sw (sw); + } + if (resultlen > n) + resultlen = n; + memcpy (buffer, result, resultlen); + buffer += resultlen; + length -= resultlen; + xfree (result); + } + while (length > 0); + + return 0; +} diff --git a/scd/iso7816.h b/scd/iso7816.h new file mode 100644 index 000000000..d7e77a101 --- /dev/null +++ b/scd/iso7816.h @@ -0,0 +1,56 @@ +/* iso7816.h - ISO 7816 commands + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef ISO7816_H +#define ISO7816_H + +gpg_error_t iso7816_select_application (int slot, + const char *aid, size_t aidlen); +gpg_error_t iso7816_verify (int slot, + int chvno, const char *chv, size_t chvlen); +gpg_error_t iso7816_change_reference_data (int slot, int chvno, + const char *oldchv, size_t oldchvlen, + const char *newchv, size_t newchvlen); +gpg_error_t iso7816_reset_retry_counter (int slot, int chvno, + const char *newchv, size_t newchvlen); +gpg_error_t iso7816_get_data (int slot, int tag, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_put_data (int slot, int tag, + const unsigned char *data, size_t datalen); +gpg_error_t iso7816_compute_ds (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_decipher (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_internal_authenticate (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_generate_keypair (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_read_public_key (int slot, + const unsigned char *data, size_t datalen, + unsigned char **result, size_t *resultlen); +gpg_error_t iso7816_get_challenge (int slot, + int length, unsigned char *buffer); + + +#endif /*ISO7816_H*/ diff --git a/scd/sc-copykeys.c b/scd/sc-copykeys.c new file mode 100644 index 000000000..9caf39a8a --- /dev/null +++ b/scd/sc-copykeys.c @@ -0,0 +1,731 @@ +/* sc-copykeys.c - A tool to store keys on a smartcard. + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#define JNLIB_NEED_LOG_LOGV +#include "scdaemon.h" +#include <gcrypt.h> + +#include "../common/ttyio.h" +#include "../common/simple-pwquery.h" +#include "apdu.h" /* for open_reader */ +#include "atr.h" +#include "app-common.h" + +#define _(a) (a) + + +enum cmd_and_opt_values +{ oVerbose = 'v', + oReaderPort = 500, + oDebug, + oDebugAll, + +aTest }; + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, "@Options:\n " }, + + { oVerbose, "verbose", 0, "verbose" }, + { oReaderPort, "reader-port", 1, "|N|connect to reader at port N"}, + { oDebug, "debug" ,4|16, "set debugging flags"}, + { oDebugAll, "debug-all" ,0, "enable full debugging"}, + {0} +}; + + +static void copykeys (APP app, const char *fname); + + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 11: p = "sc-copykeys (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: sc-copykeys [options] (-h for help)\n"); + break; + case 41: p = _("Syntax: sc-copykeys [options] " + "file-with-key\n" + "Copy keys to a smartcards\n"); + break; + + default: p = NULL; + } + return p; +} + +/* Used by gcry for logging */ +static void +my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) +{ + /* translate the log levels */ + switch (level) + { + case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break; + case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break; + case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break; + case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break; + case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break; + case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break; + case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break; + default: level = JNLIB_LOG_ERROR; break; + } + log_logv (level, fmt, arg_ptr); +} + + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int slot, rc; + int reader_port = 32768; /* First USB reader. */ + struct app_ctx_s appbuf; + + memset (&appbuf, 0, sizeof appbuf); + + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + log_set_prefix ("sc-copykeys", 1); + + /* check that the libraries are suitable. Do it here because + the option parsing may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + { + log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + } + + gcry_set_log_handler (my_gcry_logger, NULL); + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); /* FIXME - we want to use it */ + /* FIXME? gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);*/ + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + while (arg_parse (&pargs, opts) ) + { + switch (pargs.r_opt) + { + case oVerbose: opt.verbose++; break; + case oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + default : pargs.err = 2; break; + } + } + if (log_get_errorcount(0)) + exit(2); + + if (argc != 1) + usage (1); + + slot = apdu_open_reader (reader_port); + if (slot == -1) + exit (1); + + /* FIXME: Use select_application. */ + appbuf.slot = slot; + rc = app_select_openpgp (&appbuf, &appbuf.serialno, &appbuf.serialnolen); + if (rc) + { + log_error ("selecting openpgp failed: %s\n", gpg_strerror (rc)); + exit (1); + } + appbuf.initialized = 1; + log_info ("openpgp application selected\n"); + + copykeys (&appbuf, *argv); + + + return 0; +} + + + +void +send_status_info (CTRL ctrl, const char *keyword, ...) +{ + /* DUMMY */ +} + + + +static char * +read_file (const char *fname, size_t *r_length) +{ + FILE *fp; + struct stat st; + char *buf; + size_t buflen; + + fp = fname? fopen (fname, "rb") : stdin; + if (!fp) + { + log_error ("can't open `%s': %s\n", + fname? fname: "[stdin]", strerror (errno)); + return NULL; + } + + if (fstat (fileno(fp), &st)) + { + log_error ("can't stat `%s': %s\n", + fname? fname: "[stdin]", strerror (errno)); + if (fname) + fclose (fp); + return NULL; + } + + buflen = st.st_size; + buf = xmalloc (buflen+1); + if (fread (buf, buflen, 1, fp) != 1) + { + log_error ("error reading `%s': %s\n", + fname? fname: "[stdin]", strerror (errno)); + if (fname) + fclose (fp); + xfree (buf); + return NULL; + } + if (fname) + fclose (fp); + + *r_length = buflen; + return buf; +} + + +static gcry_sexp_t +read_key (const char *fname) +{ + char *buf; + size_t buflen; + gcry_sexp_t private; + int rc; + + buf = read_file (fname, &buflen); + if (!buf) + return NULL; + + rc = gcry_sexp_new (&private, buf, buflen, 1); + if (rc) + { + log_error ("gcry_sexp_new failed: %s\n", gpg_strerror (rc)); + return NULL; + } + xfree (buf); + + return private; +} + + + +static gcry_mpi_t * +sexp_to_kparms (gcry_sexp_t sexp, unsigned long *created) +{ + gcry_sexp_t list, l2; + const char *name; + const char *s; + size_t n; + int i, idx; + const char *elems; + gcry_mpi_t *array; + + *created = 0; + list = gcry_sexp_find_token (sexp, "private-key", 0 ); + if(!list) + return NULL; + + /* quick hack to get the creation time. */ + l2 = gcry_sexp_find_token (list, "created", 0); + if (l2 && (name = gcry_sexp_nth_data (l2, 1, &n))) + { + char *tmp = xmalloc (n+1); + memcpy (tmp, name, n); + tmp[n] = 0; + *created = strtoul (tmp, NULL, 10); + xfree (tmp); + } + gcry_sexp_release (l2); + l2 = gcry_sexp_cadr (list); + gcry_sexp_release (list); + list = l2; + name = gcry_sexp_nth_data (list, 0, &n); + if(!name || n != 3 || memcmp (name, "rsa", 3)) + { + gcry_sexp_release (list); + return NULL; + } + + /* Parameter names used with RSA. */ + elems = "nedpqu"; + array = xcalloc (strlen(elems) + 1, sizeof *array); + for (idx=0, s=elems; *s; s++, idx++ ) + { + l2 = gcry_sexp_find_token (list, s, 1); + if (!l2) + { + for (i=0; i<idx; i++) + gcry_mpi_release (array[i]); + xfree (array); + gcry_sexp_release (list); + return NULL; /* required parameter not found */ + } + array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l2); + if (!array[idx]) + { + for (i=0; i<idx; i++) + gcry_mpi_release (array[i]); + xfree (array); + gcry_sexp_release (list); + return NULL; /* required parameter is invalid */ + } + } + + gcry_sexp_release (list); + return array; +} + + +/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */ +static int +fpr_is_zero (const char *fpr) +{ + int i; + + for (i=0; i < 20 && !fpr[i]; i++) + ; + return (i == 20); +} + + +static void +show_sha1_fpr (const unsigned char *fpr) +{ + int i; + + if (fpr) + { + for (i=0; i < 20 ; i+=2, fpr += 2 ) + { + if (i == 10 ) + tty_printf (" "); + tty_printf (" %02X%02X", *fpr, fpr[1]); + } + } + else + tty_printf (" [none]"); + tty_printf ("\n"); +} + +/* Query the card, show a list of already stored keys and ask the user + where to store the key. Returns the key number or 0 for cancel + operation. */ +static int +query_card (APP app) +{ + int keyno = 0; + char *serialno, *disp_name, *pubkey_url; + unsigned char *fpr1, *fpr2, *fpr3; + + + if (app_openpgp_cardinfo (app, + &serialno, + &disp_name, + &pubkey_url, + &fpr1, &fpr2, &fpr3)) + return 0; + + + for (;;) + { + char *answer; + + tty_printf ("\n"); + + tty_printf ("Serial number ....: %s\n", + serialno? serialno : "[none]"); + tty_printf ("Name of cardholder: %s\n", + disp_name && *disp_name? disp_name : "[not set]"); + tty_printf ("URL of public key : %s\n", + pubkey_url && *pubkey_url? pubkey_url : "[not set]"); + tty_printf ("Signature key ....:"); + show_sha1_fpr (fpr1); + tty_printf ("Encryption key....:"); + show_sha1_fpr (fpr2); + tty_printf ("Authentication key:"); + show_sha1_fpr (fpr3); + + tty_printf ("\n" + "1 - store as signature key and reset usage counter\n" + "2 - store as encryption key\n" + "3 - store as authentication key\n" + "Q - quit\n" + "\n"); + + answer = tty_get("Your selection? "); + tty_kill_prompt(); + if (strlen (answer) != 1) + ; + else if ( *answer == '1' ) + { + if ( (fpr1 && !fpr_is_zero (fpr1)) ) + { + tty_printf ("\n"); + log_error ("WARNING: signature key does already exists!\n"); + tty_printf ("\n"); + if ( tty_get_answer_is_yes ("Replace existing key? ") ) + { + keyno = 1; + break; + } + } + else + { + keyno = 1; + break; + } + } + else if ( *answer == '2' ) + { + if ( (fpr2 && !fpr_is_zero (fpr2)) ) + { + tty_printf ("\n"); + log_error ("WARNING: encryption key does already exists!\n"); + tty_printf ("\n"); + if ( tty_get_answer_is_yes ("Replace existing key? ") ) + { + keyno = 2; + break; + } + } + else + { + keyno = 2; + break; + } + } + else if ( *answer == '3' ) + { + if ( (fpr3 && !fpr_is_zero (fpr3)) ) + { + tty_printf ("\n"); + log_error ("WARNING: authentication key does already exists!\n"); + tty_printf ("\n"); + if ( tty_get_answer_is_yes ("Replace existing key? ") ) + { + keyno = 3; + break; + } + } + else + { + keyno = 3; + break; + } + } + else if ( *answer == 'q' || *answer == 'Q') + { + keyno = 0; + break; + } + } + + xfree (serialno); + xfree (disp_name); + xfree (pubkey_url); + xfree (fpr1); + xfree (fpr2); + xfree (fpr3); + + return keyno; +} + + +/* Callback function to ask for a PIN. */ +static int +pincb (void *arg, const char *prompt, char **pinvalue) +{ + char *pin = xstrdup ("12345678"); + +/* pin = simple_pwquery (NULL, NULL, prompt, */ +/* "We need the admin's PIN to store the key on the card", */ +/* NULL); */ +/* if (!pin) */ +/* return gpg_error (GPG_ERR_CANCELED); */ + + + + *pinvalue = pin; + return 0; +} + + +/* This function expects a file (or NULL for stdin) with the secret + and public key parameters. This file should consist of an + S-expression as used by gpg-agent. Only the unprotected format is + supported. Example: + + (private-key + (rsa + (n #00e0ce9..[some bytes not shown]..51#) + (e #010001#) + (d #046129F..[some bytes not shown]..81#) + (p #00e861b..[some bytes not shown]..f1#) + (q #00f7a7c..[some bytes not shown]..61#) + (u #304559a..[some bytes not shown]..9b#)) + (uri http://foo.bar x-foo:whatever_you_want)) + +*/ +static void +copykeys (APP app, const char *fname) +{ + int rc; + gcry_sexp_t private; + gcry_mpi_t *mpis, rsa_n, rsa_e, rsa_p, rsa_q; + unsigned int nbits; + size_t n; + unsigned char *template, *tp; + unsigned char m[128], e[4]; + size_t mlen, elen; + unsigned long creation_date; + time_t created_at; + int keyno; + + if (!strcmp (fname, "-")) + fname = NULL; + + private = read_key (fname); + if (!private) + exit (1); + + mpis = sexp_to_kparms (private, &creation_date); + if (!creation_date) + { + log_info ("no creation date found - assuming current date\n"); + created_at = time (NULL); + } + else + created_at = creation_date; + gcry_sexp_release (private); + if (!mpis) + { + log_error ("invalid structure of key file or not RSA\n"); + exit (1); + } + /* MPIS is now an array with the key parameters as defined by OpenPGP. */ + rsa_n = mpis[0]; + rsa_e = mpis[1]; + gcry_mpi_release (mpis[2]); + rsa_p = mpis[3]; + rsa_q = mpis[4]; + gcry_mpi_release (mpis[5]); + xfree (mpis); + + nbits = gcry_mpi_get_nbits (rsa_e); + if (nbits < 2 || nbits > 32) + { + log_error ("public exponent too large (more than 32 bits)\n"); + goto failure; + } + nbits = gcry_mpi_get_nbits (rsa_p); + if (nbits != 512) + { + log_error ("length of first RSA prime is not 512\n"); + goto failure; + } + nbits = gcry_mpi_get_nbits (rsa_q); + if (nbits != 512) + { + log_error ("length of second RSA prime is not 512\n"); + goto failure; + } + + nbits = gcry_mpi_get_nbits (rsa_n); + if (nbits != 1024) + { + log_error ("length of RSA modulus is not 1024\n"); + goto failure; + } + + keyno = query_card (app); + if (!keyno) + goto failure; + + /* Build the private key template as described in section 4.3.3.6 of + the specs. + 0xC0 <length> public exponent + 0xC1 <length> prime p + 0xC2 <length> prime q */ + template = tp = xmalloc (1+2 + 1+1+4 + 1+1+64 + 1+1+64); + *tp++ = 0xC0; + *tp++ = 4; + rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 4, &n, rsa_e); + if (rc) + { + log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); + goto failure; + } + assert (n <= 4); + memcpy (e, tp, n); + elen = n; + if (n != 4) + { + memmove (tp+4-n, tp, 4-n); + memset (tp, 0, 4-n); + } + tp += 4; + + *tp++ = 0xC1; + *tp++ = 64; + rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 64, &n, rsa_p); + if (rc) + { + log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); + goto failure; + } + assert (n == 64); + tp += 64; + + *tp++ = 0xC2; + *tp++ = 64; + rc = gcry_mpi_print (GCRYMPI_FMT_USG, tp, 64, &n, rsa_q); + if (rc) + { + log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); + goto failure; + } + assert (n == 64); + tp += 64; + assert (tp - template == 138); + + /* (we need the modulus to calculate the fingerprint) */ + rc = gcry_mpi_print (GCRYMPI_FMT_USG, m, 128, &n, rsa_n); + if (rc) + { + log_error ("mpi_print failed: %s\n", gpg_strerror (rc)); + goto failure; + } + assert (n == 128); + mlen = 128; + + + rc = app_openpgp_storekey (app, keyno, + template, tp - template, + created_at, + m, mlen, + e, elen, + pincb, NULL); + + if (rc) + { + log_error ("error storing key: %s\n", gpg_strerror (rc)); + goto failure; + } + log_info ("key successfully stored\n"); + { + unsigned char *mm, *ee; + size_t mmlen, eelen; + int i; + + rc = app_openpgp_readkey (app, keyno, &mm, &mmlen, &ee, &eelen); + if (rc) + { + log_error ("error reading key back: %s\n", gpg_strerror (rc)); + goto failure; + } + + /* Strip leading zeroes. */ + for (i=0; i < mmlen && !mm[i]; i++) + ; + mmlen -= i; + memmove (mm, mm+i, mmlen); + for (i=0; i < eelen && !ee[i]; i++) + ; + eelen -= i; + memmove (ee, ee+i, eelen); + + if (eelen != elen || mmlen != mlen) + { + log_error ("key parameter length mismatch (n=%u/%u, e=%u/%u)\n", + (unsigned int)mlen, (unsigned int)mmlen, + (unsigned int)elen, (unsigned int)eelen); + xfree (mm); + xfree (ee); + goto failure; + } + + if (memcmp (m, mm, mlen)) + { + log_error ("key parameter n mismatch\n"); + log_printhex ("original n: ", m, mlen); + log_printhex (" copied n: ", mm, mlen); + xfree (mm); + xfree (ee); + goto failure; + } + if (memcmp (e, ee, elen)) + { + log_error ("key parameter e mismatch\n"); + log_printhex ("original e: ", e, elen); + log_printhex (" copied e: ", ee, elen); + xfree (mm); + xfree (ee); + goto failure; + } + xfree (mm); + xfree (ee); + } + + + gcry_mpi_release (rsa_e); + gcry_mpi_release (rsa_p); + gcry_mpi_release (rsa_q); + gcry_mpi_release (rsa_n); + return; + + failure: + gcry_mpi_release (rsa_e); + gcry_mpi_release (rsa_p); + gcry_mpi_release (rsa_q); + gcry_mpi_release (rsa_n); + exit (1); +} + + diff --git a/scd/sc-investigate.c b/scd/sc-investigate.c new file mode 100644 index 000000000..e8f0eb83c --- /dev/null +++ b/scd/sc-investigate.c @@ -0,0 +1,209 @@ +/* sc-investigate.c - A tool to look around on smartcards. + * Copyright (C) 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#define JNLIB_NEED_LOG_LOGV +#include "scdaemon.h" +#include <gcrypt.h> + +#include "apdu.h" /* for open_reader */ +#include "atr.h" +#include "app-common.h" + +#define _(a) (a) + + +enum cmd_and_opt_values +{ oVerbose = 'v', + oReaderPort = 500, + oDebug, + oDebugAll, + + oGenRandom, + +aTest }; + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, "@Options:\n " }, + + { oVerbose, "verbose", 0, "verbose" }, + { oReaderPort, "reader-port", 1, "|N|connect to reader at port N"}, + { oDebug, "debug" ,4|16, "set debugging flags"}, + { oDebugAll, "debug-all" ,0, "enable full debugging"}, + { oGenRandom, "gen-random", 4, "|N|generate N bytes of random"}, + {0} +}; + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 11: p = "sc-investigate (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: sc-investigate [options] (-h for help)\n"); + break; + case 41: p = _("Syntax: sc-investigate [options] [args]]\n" + "Have a look at smartcards\n"); + break; + + default: p = NULL; + } + return p; +} + +/* Used by gcry for logging */ +static void +my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) +{ + /* translate the log levels */ + switch (level) + { + case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break; + case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break; + case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break; + case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break; + case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break; + case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break; + case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break; + default: level = JNLIB_LOG_ERROR; break; + } + log_logv (level, fmt, arg_ptr); +} + + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int slot, rc; + int reader_port = 32768; /* First USB reader. */ + struct app_ctx_s appbuf; + unsigned long gen_random = 0; + + memset (&appbuf, 0, sizeof appbuf); + + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + log_set_prefix ("sc-investigate", 1); + + /* check that the libraries are suitable. Do it here because + the option parsing may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + { + log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + } + + gcry_set_log_handler (my_gcry_logger, NULL); + /* FIXME? gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);*/ + + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + while (arg_parse (&pargs, opts) ) + { + switch (pargs.r_opt) + { + case oVerbose: opt.verbose++; break; + case oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + case oGenRandom: gen_random = pargs.r.ret_ulong; break; + default : pargs.err = 2; break; + } + } + if (log_get_errorcount(0)) + exit(2); + + if (opt.verbose < 2) + opt.verbose = 2; /* hack to let select_openpgp print some info. */ + + if (argc) + usage (1); + + slot = apdu_open_reader (reader_port); + if (slot == -1) + exit (1); + + if (!gen_random) + { + rc = atr_dump (slot, stdout); + if (rc) + log_error ("can't dump ATR: %s\n", gpg_strerror (rc)); + } + + appbuf.slot = slot; + rc = app_select_openpgp (&appbuf, NULL, NULL); + if (rc) + log_error ("selecting openpgp failed: %s\n", gpg_strerror (rc)); + else + { + appbuf.initialized = 1; + log_info ("openpgp application selected\n"); + + if (gen_random) + { + size_t nbytes; + unsigned char *buffer; + + buffer = xmalloc (4096); + do + { + nbytes = gen_random > 4096? 4096 : gen_random; + rc = app_get_challenge (&appbuf, nbytes, buffer); + if (rc) + log_error ("app_get_challenge failed: %s\n",gpg_strerror (rc)); + else + { + if (fwrite (buffer, nbytes, 1, stdout) != 1) + log_error ("writing to stdout failed: %s\n", + strerror (errno)); + gen_random -= nbytes; + } + } + while (gen_random && !log_get_errorcount (0)); + xfree (buffer); + } + } + + return log_get_errorcount (0)? 2:0; +} + + + +void +send_status_info (CTRL ctrl, const char *keyword, ...) +{ + /* DUMMY */ +} diff --git a/scd/scdaemon.c b/scd/scdaemon.c new file mode 100644 index 000000000..8e0ef37c9 --- /dev/null +++ b/scd/scdaemon.c @@ -0,0 +1,638 @@ +/* scdaemon.c - The GnuPG Smartcard Daemon + * Copyright (C) 2001, 2002 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <time.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <signal.h> + +#define JNLIB_NEED_LOG_LOGV +#include "scdaemon.h" +#include <ksba.h> +#include <gcrypt.h> + +#include <assuan.h> /* malloc hooks */ + +#include "i18n.h" +#include "sysutils.h" + + + +enum cmd_and_opt_values +{ aNull = 0, + oCsh = 'c', + oQuiet = 'q', + oSh = 's', + oVerbose = 'v', + + oNoVerbose = 500, + oOptions, + oDebug, + oDebugAll, + oDebugWait, + oDebugSC, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oNoGrab, + oLogFile, + oServer, + oDaemon, + oBatch, + oReaderPort, + +aTest }; + + + +static ARGPARSE_OPTS opts[] = { + + { 301, NULL, 0, N_("@Options:\n ") }, + + { oServer, "server", 0, N_("run in server mode (foreground)") }, + { oDaemon, "daemon", 0, N_("run in daemon mode (background)") }, + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oSh, "sh", 0, N_("sh-style command output") }, + { oCsh, "csh", 0, N_("csh-style command output") }, + { oOptions, "options" , 2, N_("read options from file")}, + { oDebug, "debug" ,4|16, N_("set debugging flags")}, + { oDebugAll, "debug-all" ,0, N_("enable full debugging")}, + { oDebugWait,"debug-wait",1, "@"}, + { oDebugSC, "debug-sc", 1, N_("|N|set OpenSC debug level to N")}, + { oNoDetach, "no-detach" ,0, N_("do not detach from the console")}, + { oLogFile, "log-file" ,2, N_("use a log file for the server")}, + { oReaderPort, "reader-port", 1, N_("|N|connect to reader at port N")}, + + {0} +}; + + +static volatile int caught_fatal_sig = 0; + +/* It is possible that we are currently running under setuid permissions */ +static int maybe_setuid = 1; + +/* Name of the communication socket */ +static char socket_name[128]; + +static const char * +my_strusage (int level) +{ + const char *p; + switch (level) + { + case 11: p = "scdaemon (GnuPG)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n"); + break; + case 1: + case 40: p = _("Usage: scdaemon [options] (-h for help)"); + break; + case 41: p = _("Syntax: scdaemon [options] [command [args]]\n" + "Smartcard daemon for GnuPG\n"); + break; + + default: p = NULL; + } + return p; +} + + + +static void +i18n_init (void) +{ +#ifdef USE_SIMPLE_GETTEXT + set_gettext_file( PACKAGE ); +#else +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); +#endif +#endif +} + + + +/* Used by gcry for logging */ +static void +my_gcry_logger (void *dummy, int level, const char *fmt, va_list arg_ptr) +{ + /* translate the log levels */ + switch (level) + { + case GCRY_LOG_CONT: level = JNLIB_LOG_CONT; break; + case GCRY_LOG_INFO: level = JNLIB_LOG_INFO; break; + case GCRY_LOG_WARN: level = JNLIB_LOG_WARN; break; + case GCRY_LOG_ERROR:level = JNLIB_LOG_ERROR; break; + case GCRY_LOG_FATAL:level = JNLIB_LOG_FATAL; break; + case GCRY_LOG_BUG: level = JNLIB_LOG_BUG; break; + case GCRY_LOG_DEBUG:level = JNLIB_LOG_DEBUG; break; + default: level = JNLIB_LOG_ERROR; break; + } + log_logv (level, fmt, arg_ptr); +} + + +static void +cleanup (void) +{ + if (*socket_name) + { + char *p; + + remove (socket_name); + p = strrchr (socket_name, '/'); + if (p) + { + *p = 0; + rmdir (socket_name); + *p = '/'; + } + *socket_name = 0; + } +} + + +static RETSIGTYPE +cleanup_sh (int sig) +{ + if (caught_fatal_sig) + raise (sig); + caught_fatal_sig = 1; + + /* gcry_control( GCRYCTL_TERM_SECMEM );*/ + cleanup (); + +#ifndef HAVE_DOSISH_SYSTEM + { /* reset action to default action and raise signal again */ + struct sigaction nact; + nact.sa_handler = SIG_DFL; + sigemptyset( &nact.sa_mask ); + nact.sa_flags = 0; + sigaction( sig, &nact, NULL); + } +#endif + raise( sig ); +} + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + int may_coredump; + char **orig_argv; + FILE *configfp = NULL; + char *configname = NULL; + const char *shell; + unsigned configlineno; + int parse_debug = 0; + int default_config =1; + int greeting = 0; + int nogreeting = 0; + int pipe_server = 0; + int is_daemon = 0; + int nodetach = 0; + int csh_style = 0; + char *logfile = NULL; + int debug_wait = 0; + int reader_port = 32768; /* First USB reader. */ + + set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to INIT_SECMEM() + somewhere after the option parsing */ + log_set_prefix ("scdaemon", 1|4); + i18n_init (); + + /* check that the libraries are suitable. Do it here because + the option parsing may need services of the library */ + if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) ) + { + log_fatal( _("libgcrypt is too old (need %s, have %s)\n"), + NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) ); + } + + ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + assuan_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free); + gcry_set_log_handler (my_gcry_logger, NULL); + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + may_coredump = disable_core_dumps (); + + shell = getenv ("SHELL"); + if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) + csh_style = 1; + + /* FIXME: Using this homedir option does only make sense when not + running as a system service. We might want to check for this by + looking at the uid or ebtter use an explict option for this */ + opt.homedir = getenv("GNUPGHOME"); + if (!opt.homedir || !*opt.homedir) + opt.homedir = GNUPG_DEFAULT_HOMEDIR; + + /* check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ + while (arg_parse( &pargs, opts)) + { + if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) + parse_debug++; + else if (pargs.r_opt == oOptions) + { /* yes there is one, so we do not try the default one, but + read the option file when it is encountered at the + commandline */ + default_config = 0; + } + else if (pargs.r_opt == oNoOptions) + default_config = 0; /* --no-options */ + else if (pargs.r_opt == oHomedir) + opt.homedir = pargs.r.ret_str; + } + + /* initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + Now we are working under our real uid + */ + + + if (default_config) + configname = make_filename (opt.homedir, "scdaemon.conf", NULL ); + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + next_pass: + if (configname) + { + configlineno = 0; + configfp = fopen (configname, "r"); + if (!configfp) + { + if (default_config) + { + if( parse_debug ) + log_info (_("NOTE: no default option file `%s'\n"), + configname ); + } + else + { + log_error (_("option file `%s': %s\n"), + configname, strerror(errno) ); + exit(2); + } + xfree (configname); + configname = NULL; + } + if (parse_debug && configname ) + log_info (_("reading options from `%s'\n"), configname ); + default_config = 0; + } + + while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) + { + switch (pargs.r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oBatch: opt.batch=1; break; + + case oDebug: opt.debug |= pargs.r.ret_ulong; break; + case oDebugAll: opt.debug = ~0; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oDebugSC: opt.debug_sc = pargs.r.ret_int; break; + + case oOptions: + /* config files may not be nested (silently ignore them) */ + if (!configfp) + { + xfree(configname); + configname = xstrdup(pargs.r.ret_str); + goto next_pass; + } + break; + case oNoGreeting: nogreeting = 1; break; + case oNoVerbose: opt.verbose = 0; break; + case oNoOptions: break; /* no-options */ + case oHomedir: opt.homedir = pargs.r.ret_str; break; + case oNoDetach: nodetach = 1; break; + case oLogFile: logfile = pargs.r.ret_str; break; + case oCsh: csh_style = 1; break; + case oSh: csh_style = 0; break; + case oServer: pipe_server = 1; break; + case oDaemon: is_daemon = 1; break; + + case oReaderPort: reader_port = pargs.r.ret_int; break; + + default : pargs.err = configfp? 1:2; break; + } + } + if (configfp) + { + fclose( configfp ); + configfp = NULL; + xfree(configname); + configname = NULL; + goto next_pass; + } + xfree (configname); + configname = NULL; + if (log_get_errorcount(0)) + exit(2); + if (nogreeting ) + greeting = 0; + + if (greeting) + { + fprintf (stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + fprintf (stderr, "%s\n", strusage(15) ); + } +#ifdef IS_DEVELOPMENT_VERSION + log_info ("NOTE: this is a development version!\n"); +#endif + + + if (atexit (cleanup)) + { + log_error ("atexit failed\n"); + cleanup (); + exit (1); + } + + + if (debug_wait && pipe_server) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + sleep (debug_wait); + log_debug ("... okay\n"); + } + + /* now start with logging to a file if this is desired */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, 1|2|4); + } + + + if (pipe_server) + { /* this is the simple pipe based server */ + scd_command_handler (-1); + } + else if (!is_daemon) + { + log_info (_("please use the option `--daemon'" + " to run the program in the background\n")); + } + else + { /* regular server mode */ + int fd; + pid_t pid; + int i; + int len; + struct sockaddr_un serv_addr; + char *p; + + /* fixme: if there is already a running gpg-agent we should + share the same directory - and vice versa */ + *socket_name = 0; + snprintf (socket_name, DIM(socket_name)-1, + "/tmp/gpg-XXXXXX/S.scdaemon"); + socket_name[DIM(socket_name)-1] = 0; + p = strrchr (socket_name, '/'); + if (!p) + BUG (); + *p = 0;; + if (!mkdtemp(socket_name)) + { + log_error ("can't create directory `%s': %s\n", + socket_name, strerror(errno) ); + exit (1); + } + *p = '/'; + + if (strchr (socket_name, ':') ) + { + log_error ("colons are not allowed in the socket name\n"); + exit (1); + } + if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) + { + log_error ("name of socket to long\n"); + exit (1); + } + + + fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + log_error ("can't create socket: %s\n", strerror(errno) ); + exit (1); + } + + memset (&serv_addr, 0, sizeof serv_addr); + serv_addr.sun_family = AF_UNIX; + strcpy (serv_addr.sun_path, socket_name); + len = (offsetof (struct sockaddr_un, sun_path) + + strlen(serv_addr.sun_path) + 1); + + if (bind (fd, (struct sockaddr*)&serv_addr, len) == -1) + { + log_error ("error binding socket to `%s': %s\n", + serv_addr.sun_path, strerror (errno) ); + close (fd); + exit (1); + } + + if (listen (fd, 5 ) == -1) + { + log_error ("listen() failed: %s\n", strerror (errno)); + close (fd); + exit (1); + } + + if (opt.verbose) + log_info ("listening on socket `%s'\n", socket_name ); + + + fflush (NULL); + pid = fork (); + if (pid == (pid_t)-1) + { + log_fatal ("fork failed: %s\n", strerror (errno) ); + exit (1); + } + else if (pid) + { /* we are the parent */ + char *infostr; + + close (fd); + + /* create the info string: <name>:<pid>:<protocol_version> */ + if (asprintf (&infostr, "SCDAEMON_INFO=%s:%lu:1", + socket_name, (ulong)pid ) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + *socket_name = 0; /* don't let cleanup() remove the socket - + the child should do this from now on */ + if (argc) + { /* run the program given on the commandline */ + if (putenv (infostr)) + { + log_error ("failed to set environment: %s\n", + strerror (errno) ); + kill (pid, SIGTERM ); + exit (1); + } + execvp (argv[0], argv); + log_error ("failed to run the command: %s\n", strerror (errno)); + kill (pid, SIGTERM); + exit (1); + } + else + { + /* print the environment string, so that the caller can use + shell's eval to set it */ + if (csh_style) + { + *strchr (infostr, '=') = ' '; + printf ( "setenv %s\n", infostr); + } + else + { + printf ( "%s; export SCDAEMON_INFO;\n", infostr); + } + free (infostr); + exit (0); + } + /* NOTREACHED */ + } /* end parent */ + + /* this is the child */ + + /* detach from tty and put process into a new session */ + if (!nodetach ) + { /* close stdin, stdout and stderr unless it is the log stream */ + for (i=0; i <= 2; i++) + { + if ( log_get_fd () != i) + close (i); + } + if (setsid() == -1) + { + log_error ("setsid() failed: %s\n", strerror(errno) ); + cleanup (); + exit (1); + } + } + + /* setup signals */ + { + struct sigaction oact, nact; + + nact.sa_handler = cleanup_sh; + sigemptyset (&nact.sa_mask); + nact.sa_flags = 0; + + sigaction (SIGHUP, NULL, &oact); + if (oact.sa_handler != SIG_IGN) + sigaction (SIGHUP, &nact, NULL); + sigaction( SIGTERM, NULL, &oact ); + if (oact.sa_handler != SIG_IGN) + sigaction (SIGTERM, &nact, NULL); + nact.sa_handler = SIG_IGN; + sigaction (SIGPIPE, &nact, NULL); + sigaction (SIGINT, &nact, NULL); + } + + if (chdir("/")) + { + log_error ("chdir to / failed: %s\n", strerror (errno)); + exit (1); + } + + scd_command_handler (fd); + + close (fd); + } + + return 0; +} + +void +scd_exit (int rc) +{ + #if 0 +#warning no update_random_seed_file + update_random_seed_file(); + #endif +#if 0 + /* at this time a bit annoying */ + if (opt.debug & DBG_MEMSTAT_VALUE) + { + gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); + gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); + } + if (opt.debug) + gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); +#endif + gcry_control (GCRYCTL_TERM_SECMEM ); + rc = rc? rc : log_get_errorcount(0)? 2 : 0; + exit (rc); +} + + +void +scd_init_default_ctrl (CTRL ctrl) +{ + +} + diff --git a/scd/scdaemon.h b/scd/scdaemon.h new file mode 100644 index 000000000..b21e19f8c --- /dev/null +++ b/scd/scdaemon.h @@ -0,0 +1,127 @@ +/* scdaemon.h - Global definitions for the SCdaemon + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef SCDAEMON_H +#define SCDAEMON_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_SCD +#include <gpg-error.h> +#include <errno.h> + +#include <time.h> +#include <gcrypt.h> +#include "../common/util.h" +#include "../common/errors.h" + +/* Convenience funcion to be used instead of returning the old + GNUPG_Out_Of_Core. */ +static __inline__ gpg_error_t +out_of_core (void) +{ + return gpg_error (gpg_err_code_from_errno (errno)); +} + + +#define MAX_DIGEST_LEN 24 + +/* A large struct name "opt" to keep global flags */ +struct { + unsigned int debug; /* debug flags (DBG_foo_VALUE) */ + int debug_sc; /* OpenSC debug level */ + int verbose; /* verbosity level */ + int quiet; /* be as quiet as possible */ + int dry_run; /* don't change any persistent data */ + int batch; /* batch mode */ + const char *homedir; /* configuration directory name */ +} opt; + + +#define DBG_COMMAND_VALUE 1 /* debug commands i/o */ +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the caching */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_ASSUAN_VALUE 1024 +#define DBG_CARD_IO_VALUE 2048 + +#define DBG_COMMAND (opt.debug & DBG_COMMAND_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE) +#define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE) + +struct server_local_s; +struct card_ctx_s; +struct app_ctx_s; + +struct server_control_s { + struct server_local_s *server_local; + struct card_ctx_s *card_ctx; + struct app_ctx_s *app_ctx; + struct { + unsigned char *value; + int valuelen; + } in_data; /* helper to store the value we are going to sign */ + +}; + +typedef struct server_control_s *CTRL; +typedef struct card_ctx_s *CARD; +typedef struct app_ctx_s *APP; + +/*-- scdaemon.c --*/ +void scd_exit (int rc); +void scd_init_default_ctrl (CTRL ctrl); + +/*-- command.c --*/ +void scd_command_handler (int); +void send_status_info (CTRL ctrl, const char *keyword, ...); + +/*-- card.c --*/ +int card_open (CARD *rcard); +void card_close (CARD card); +int card_get_serial_and_stamp (CARD card, char **serial, time_t *stamp); +int card_enum_keypairs (CARD card, int idx, + unsigned char *keygrip, + char **keyid); +int card_enum_certs (CARD card, int idx, char **certid, int *certtype); +int card_read_cert (CARD card, const char *certidstr, + unsigned char **cert, size_t *ncert); +int card_sign (CARD card, + const char *keyidstr, int hashalgo, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ); +int card_decipher (CARD card, const char *keyidstr, + int (pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen); + + +#endif /*SCDAEMON_H*/ |