diff options
author | Werner Koch <wk@gnupg.org> | 2002-02-28 12:07:59 +0100 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2002-02-28 12:07:59 +0100 |
commit | 56341c289cabffb7f468f7a3ee706626a9106a96 (patch) | |
tree | 41fdd684c4cfdd7a164f22ae3fc56ba061d19a6c /scd | |
parent | * assuan-client.c (assuan_transact): Add 2 more arguments to (diff) | |
download | gnupg2-56341c289cabffb7f468f7a3ee706626a9106a96.tar.xz gnupg2-56341c289cabffb7f468f7a3ee706626a9106a96.zip |
Changes needed to support smartcards. Well, only _support_. There is
no real code yet.
Diffstat (limited to 'scd')
-rw-r--r-- | scd/Makefile.am | 6 | ||||
-rw-r--r-- | scd/card.c | 208 | ||||
-rw-r--r-- | scd/command.c | 156 | ||||
-rw-r--r-- | scd/scdaemon.c | 2 | ||||
-rw-r--r-- | scd/scdaemon.h | 5 |
5 files changed, 347 insertions, 30 deletions
diff --git a/scd/Makefile.am b/scd/Makefile.am index dca4f3bcb..5379d519a 100644 --- a/scd/Makefile.am +++ b/scd/Makefile.am @@ -20,7 +20,8 @@ bin_PROGRAMS = scdaemon -AM_CPPFLAGS = -I$(top_srcdir)/common $(LIBOPENSC_CFLAGS) $(LIBGCRYPT_CFLAGS) +AM_CPPFLAGS = -I$(top_srcdir)/common $(LIBOPENSC_CFLAGS) $(LIBGCRYPT_CFLAGS) \ + $(LIBKSBA_CFLAGS) LDFLAGS = @LDFLAGS@ scdaemon_SOURCES = \ @@ -29,7 +30,8 @@ scdaemon_SOURCES = \ scdaemon_LDADD = ../jnlib/libjnlib.a ../assuan/libassuan.a \ - ../common/libcommon.a $(LIBOPENSC_LIBS) $(LIBGCRYPT_LIBS) + ../common/libcommon.a \ + $(LIBOPENSC_LIBS) $(LIBGCRYPT_LIBS) $(LIBKSBA_LIBS) diff --git a/scd/card.c b/scd/card.c index 3702ae348..dbfbe5333 100644 --- a/scd/card.c +++ b/scd/card.c @@ -26,6 +26,7 @@ #include <time.h> #include <opensc-pkcs15.h> +#include <ksba.h> #include "scdaemon.h" @@ -114,13 +115,13 @@ card_open (CARD *rcard) } card->ctx->error_file = log_get_stream (); card->ctx->debug_file = log_get_stream (); - if (sc_detect_card (card->ctx, card->reader) != 1) + if (sc_detect_card_presence (card->ctx->reader[card->reader], 0) != 1) { rc = GNUPG_Card_Not_Present; goto leave; } - rc = sc_connect_card (card->ctx, card->reader, &card->scard); + 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", @@ -175,7 +176,7 @@ card_close (CARD card) if (card->scard) { sc_unlock (card->scard); - sc_disconnect_card (card->scard); + sc_disconnect_card (card->scard, 0); card->scard = NULL; } if (card->ctx) @@ -219,3 +220,204 @@ card_get_serial_and_stamp (CARD card, char **serial, time_t *stamp) return GNUPG_Out_Of_Core; return 0; } + + + +/* Get the keygrip from CERT, return 0 on success */ +static int +get_keygrip (KsbaCert cert, unsigned char *array) +{ + GCRY_SEXP 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; +} + + + +/* 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, NKEYID will then receive + the length of it. The function returns -1 when all keys have been + enumerated. Note that the error GNUPG_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, + unsigned char **keyid, size_t *nkeyid) +{ + int rc; + KsbaError krc; + struct sc_pkcs15_prkey_info *pinfo; + struct sc_pkcs15_cert_info *certinfo; + struct sc_pkcs15_cert *certder; + KsbaCert cert; + + if (keyid) + *keyid = NULL; + if (nkeyid) + *nkeyid = 0; + + if (!card || !keygrip || !card->p15card) + return GNUPG_Invalid_Value; + if (idx < 0) + return GNUPG_Invalid_Index; + + rc = sc_pkcs15_enum_private_keys (card->p15card); + if (rc < 0) + { + log_error ("sc_pkcs15_enum_private_keys failed: %s\n", sc_strerror (rc)); + return GNUPG_Card_Error; + } + if ( idx >= card->p15card->prkey_count) + return -1; + pinfo = card->p15card->prkey_info + idx; + + /* 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, &certinfo); + if (rc) + { + log_info ("certificate for private key %d not found: %s\n", + idx, sc_strerror (rc)); + /* but we should return the ID anyway */ + if (keyid) + { + *keyid = xtrymalloc (pinfo->id.len); + if (!*keyid) + return GNUPG_Out_Of_Core; + memcpy (*keyid, pinfo->id.value, pinfo->id.len); + } + if (nkeyid) + *nkeyid = pinfo->id.len; + return GNUPG_Missing_Certificate; + } + 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 GNUPG_Card_Error; + } + + cert = ksba_cert_new (); + if (!cert) + { + sc_pkcs15_free_certificate (certder); + return GNUPG_Out_Of_Core; + } + 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 GNUPG_Card_Error; + } + if (get_keygrip (cert, keygrip)) + { + log_error ("failed to calculate the keygrip of private key %d\n", idx); + ksba_cert_release (cert); + return GNUPG_Card_Error; + } + ksba_cert_release (cert); + + /* return the iD */ + if (keyid) + { + *keyid = xtrymalloc (pinfo->id.len); + if (!*keyid) + return GNUPG_Out_Of_Core; + memcpy (*keyid, pinfo->id.value, pinfo->id.len); + } + if (nkeyid) + *nkeyid = pinfo->id.len; + + return 0; +} + + + +/* 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) +{ + struct sc_pkcs15_id certid; + struct sc_pkcs15_cert_info *certinfo; + struct sc_pkcs15_cert *certder; + const char *s; + int rc, n; + + if (!card || !certidstr || !card->p15card || !cert || !ncert) + return GNUPG_Invalid_Value; + + /* For now we only support the standard DF */ + if (strncmp (certidstr, "3F005015.", 9) ) + return GNUPG_Invalid_Id; + for (s=certidstr+9, n=0; hexdigitp (s); s++, n++) + ; + if (*s || (n&1)) + return GNUPG_Invalid_Id; /* invalid or odd number of digits */ + n /= 2; + if (!n || n > SC_PKCS15_MAX_ID_SIZE) + return GNUPG_Invalid_Id; /* empty or too large */ + for (s=certidstr+9, n=0; *s; s += 2, n++) + certid.value[n] = xtoi_2 (s); + certid.len = n; + + rc = sc_pkcs15_find_cert_by_id (card->p15card, &certid, &certinfo); + if (rc) + { + log_info ("certificate '%s' not found: %s\n", + certidstr, sc_strerror (rc)); + return -1; + } + 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 GNUPG_Card_Error; + } + + *cert = xtrymalloc (certder->data_len); + if (!*cert) + { + sc_pkcs15_free_certificate (certder); + return GNUPG_Out_Of_Core; + } + memcpy (*cert, certder->data, certder->data_len); + *ncert = certder->data_len; + sc_pkcs15_free_certificate (certder); + return 0; +} + + + + + diff --git a/scd/command.c b/scd/command.c index 10b900628..b4eaa8a47 100644 --- a/scd/command.c +++ b/scd/command.c @@ -75,14 +75,20 @@ option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) /* LEARN [--force] Learn all useful information of the currently inserted card. When - used without the force options, the command might to an INQUIRE + 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. + error message. The response of this command is a list of status + lines formatted as this: + + 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. */ static int @@ -90,6 +96,7 @@ cmd_learn (ASSUAN_CONTEXT ctx, char *line) { CTRL ctrl = assuan_get_pointer (ctx); int rc = 0; + int idx; /* if this is the first command issued for a new card, open the card and and create a context */ @@ -104,40 +111,138 @@ cmd_learn (ASSUAN_CONTEXT ctx, char *line) 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 */ - if (!has_option (line, "--force")) + { + char *serial_and_stamp; + char *serial; + time_t stamp; + + 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); + } + + for (idx=0; !rc; idx++) { - char *serial; - time_t stamp; - char *command; - - rc = card_get_serial_and_stamp (ctrl->card_ctx, &serial, &stamp); - if (rc) - return map_to_assuan_status (rc); - - rc = asprintf (&command, "KNOWNCARDP %s %lu", - serial, (unsigned long)stamp); - xfree (serial); - if (rc < 0) - return ASSUAN_Out_Of_Core; - rc = 0; - rc = assuan_inquire (ctx, command, NULL, NULL, 0); - free (command); /* (must use standard free here) */ - if (rc) + unsigned char keygrip[20]; + unsigned char *keyid; + size_t nkeyid; + int no_cert = 0; + + rc = card_enum_keypairs (ctrl->card_ctx, idx, + keygrip, &keyid, &nkeyid); + if (rc == GNUPG_Missing_Certificate && 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) { - if (rc != ASSUAN_Canceled) - log_error ("inquire KNOWNCARDP failed: %s\n", - assuan_strerror (rc)); - return rc; + char *buf, *p; + + buf = p = xtrymalloc (40+1+9+2*nkeyid+1); + if (!buf) + rc = GNUPG_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++ = ' '; + /* fixme: we need to get the pkcs-15 DF from the card function */ + p = stpcpy (p, "3F005015."); + for (i=0; i < nkeyid; i++, p += 2) + sprintf (p, "%02X", keyid[i]); + *p = 0; + assuan_write_status (ctx, "KEYPAIRINFO", buf); + xfree (buf); + } } - /* not canceled, so we have to proceeed */ + xfree (keyid); } + if (rc == -1) + rc = 0; + 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 (!ctrl->card_ctx) + { + rc = card_open (&ctrl->card_ctx); + if (rc) + return map_to_assuan_status (rc); + } + + rc = card_read_cert (ctrl->card_ctx, line, &cert, &ncert); + if (rc) + { + log_error ("card_read_cert failed: %s\n", gnupg_strerror (rc)); + } + if (!rc) + { + rc = assuan_send_data (ctx, cert, ncert); + xfree (cert); + if (rc) + return rc; + } + + return map_to_assuan_status (rc); +} /* Tell the assuan library about our commands */ @@ -150,6 +255,7 @@ register_commands (ASSUAN_CONTEXT ctx) int (*handler)(ASSUAN_CONTEXT, char *line); } table[] = { { "LEARN", 0, cmd_learn }, + { "READCERT", 0, cmd_readcert }, { "", ASSUAN_CMD_INPUT, NULL }, { "", ASSUAN_CMD_OUTPUT, NULL }, { NULL } diff --git a/scd/scdaemon.c b/scd/scdaemon.c index d217b0020..92bccec9e 100644 --- a/scd/scdaemon.c +++ b/scd/scdaemon.c @@ -34,6 +34,7 @@ #include <unistd.h> #include <signal.h> +#include <ksba.h> #include <gcrypt.h> #define JNLIB_NEED_LOG_LOGV @@ -242,6 +243,7 @@ main (int argc, char **argv ) "1.1.5", 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); diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 7d43dc129..e77012416 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -77,6 +77,11 @@ void scd_command_handler (int); 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, + unsigned char **keyid, size_t *nkeyid); +int card_read_cert (CARD card, const char *certidstr, + unsigned char **cert, size_t *ncert); |