diff options
Diffstat (limited to '')
-rw-r--r-- | scd/card.c | 564 |
1 files changed, 564 insertions, 0 deletions
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; +} + |