diff --git a/scd/Makefile.am b/scd/Makefile.am index 4e4cd655d..dca4f3bcb 100644 --- a/scd/Makefile.am +++ b/scd/Makefile.am @@ -25,7 +25,7 @@ LDFLAGS = @LDFLAGS@ scdaemon_SOURCES = \ scdaemon.c scdaemon.h \ - command.c + command.c card.c scdaemon_LDADD = ../jnlib/libjnlib.a ../assuan/libassuan.a \ diff --git a/scd/card.c b/scd/card.c new file mode 100644 index 000000000..3702ae348 --- /dev/null +++ b/scd/card.c @@ -0,0 +1,221 @@ +/* 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 +#include +#include +#include +#include +#include + +#include + +#include "scdaemon.h" + + + +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 */ + +}; + +/* Map the SC error codes to the GNUPG ones */ +static int +map_sc_err (int rc) +{ + switch (rc) + { + case 0: rc = 0; break; + case SC_ERROR_CMD_TOO_SHORT: rc = GNUPG_Card_Error; break; + case SC_ERROR_CMD_TOO_LONG: rc = GNUPG_Card_Error; break; + case SC_ERROR_NOT_SUPPORTED: rc = GNUPG_Not_Supported; break; + case SC_ERROR_TRANSMIT_FAILED: rc = GNUPG_Card_Error; break; + case SC_ERROR_FILE_NOT_FOUND: rc = GNUPG_Card_Error; break; + case SC_ERROR_INVALID_ARGUMENTS: rc = GNUPG_Card_Error; break; + case SC_ERROR_PKCS15_APP_NOT_FOUND: rc = GNUPG_No_PKCS15_App; break; + case SC_ERROR_REQUIRED_PARAMETER_NOT_FOUND: rc = GNUPG_Card_Error; break; + case SC_ERROR_OUT_OF_MEMORY: rc = GNUPG_Out_Of_Core; break; + case SC_ERROR_NO_READERS_FOUND: rc = GNUPG_Card_Error; break; + case SC_ERROR_OBJECT_NOT_VALID: rc = GNUPG_Card_Error; break; + case SC_ERROR_ILLEGAL_RESPONSE: rc = GNUPG_Card_Error; break; + case SC_ERROR_PIN_CODE_INCORRECT: rc = GNUPG_Card_Error; break; + case SC_ERROR_SECURITY_STATUS_NOT_SATISFIED: rc = GNUPG_Card_Error; break; + case SC_ERROR_CONNECTING_TO_RES_MGR: rc = GNUPG_Card_Error; break; + case SC_ERROR_INVALID_ASN1_OBJECT: rc = GNUPG_Card_Error; break; + case SC_ERROR_BUFFER_TOO_SMALL: rc = GNUPG_Card_Error; break; + case SC_ERROR_CARD_NOT_PRESENT: rc = GNUPG_Card_Not_Present; break; + case SC_ERROR_RESOURCE_MANAGER: rc = GNUPG_Card_Error; break; + case SC_ERROR_CARD_REMOVED: rc = GNUPG_Card_Removed; break; + case SC_ERROR_INVALID_PIN_LENGTH: rc = GNUPG_Card_Error; break; + case SC_ERROR_UNKNOWN_SMARTCARD: rc = GNUPG_Card_Error; break; + case SC_ERROR_UNKNOWN_REPLY: rc = GNUPG_Card_Error; break; + case SC_ERROR_OBJECT_NOT_FOUND: rc = GNUPG_Card_Error; break; + case SC_ERROR_CARD_RESET: rc = GNUPG_Card_Reset; break; + case SC_ERROR_ASN1_OBJECT_NOT_FOUND: rc = GNUPG_Card_Error; break; + case SC_ERROR_ASN1_END_OF_CONTENTS: rc = GNUPG_Card_Error; break; + case SC_ERROR_TOO_MANY_OBJECTS: rc = GNUPG_Card_Error; break; + case SC_ERROR_INVALID_CARD: rc = GNUPG_Invalid_Card; break; + case SC_ERROR_WRONG_LENGTH: rc = GNUPG_Card_Error; break; + case SC_ERROR_RECORD_NOT_FOUND: rc = GNUPG_Card_Error; break; + case SC_ERROR_INTERNAL: rc = GNUPG_Card_Error; break; + default: rc = GNUPG_Card_Error; break; + } + return rc; +} + + +/* Create a new context for the card and figures out some basic + information of the card. Detects whether a PKCS_15 application is + stored. + + Common errors: GNUPG_Card_Not_Present */ +int +card_open (CARD *rcard) +{ + CARD card; + int rc; + + card = xtrycalloc (1, sizeof *card); + if (!card) + return GNUPG_Out_Of_Core; + card->reader = 0; + + rc = sc_establish_context (&card->ctx); + 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 = GNUPG_Card_Error; + } + card->ctx->error_file = log_get_stream (); + card->ctx->debug_file = log_get_stream (); + if (sc_detect_card (card->ctx, card->reader) != 1) + { + rc = GNUPG_Card_Not_Present; + goto leave; + } + + rc = sc_connect_card (card->ctx, card->reader, &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; + } + + rc = sc_pkcs15_bind (card->scard, &card->p15card); + if (rc == SC_ERROR_PKCS15_APP_NOT_FOUND) + rc = 0; /* okay */ + else if (rc) + { + log_error ("binding of existing PKCS-15 failed 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; +} + + +/* Close a card and release all resources */ +void +card_close (CARD card) +{ + if (card) + { + if (card->p15card) + { + sc_pkcs15_unbind (card->p15card); + card->p15card = NULL; + } + if (card->scard) + { + sc_unlock (card->scard); + sc_disconnect_card (card->scard); + card->scard = NULL; + } + if (card->ctx) + { + sc_destroy_context (card->ctx); + card->ctx = NULL; + } + xfree (card); + } +} + +/* 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 fucntion returns an error. */ +int +card_get_serial_and_stamp (CARD card, char **serial, time_t *stamp) +{ + char *s; + + if (!card || !serial || !stamp) + return GNUPG_Invalid_Value; + + *serial = NULL; + *stamp = 0; /* not available */ + if (!card->p15card) + { /* fixme: construct a serial number */ + /* We should lookup the iso 7812-1 and 8583-3 - argh ISO practice is + suppressing innovation - IETF rules! */ + return GNUPG_No_PKCS15_App; + } + s = card->p15card->serial_number; + if (!s || !hexdigitp (s) ) + return GNUPG_Invalid_Card; /* the serial number is mandatory */ + *serial = xstrdup (s); + if (!*serial) + return GNUPG_Out_Of_Core; + return 0; +} diff --git a/scd/command.c b/scd/command.c index b2631bb25..10b900628 100644 --- a/scd/command.c +++ b/scd/command.c @@ -18,9 +18,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ -/* FIXME: we should not use the default assuan buffering but setup - some buffering in secure mempory to protect session keys etc. */ - #include #include #include @@ -40,32 +37,103 @@ struct server_local_s { }; +/* 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); */ - + CTRL ctrl = assuan_get_pointer (ctx); + if (ctrl->card_ctx) + { + card_close (ctrl->card_ctx); + ctrl->card_ctx = NULL; + } } static int option_handler (ASSUAN_CONTEXT ctx, const char *key, const char *value) { -/* CTRL ctrl = assuan_get_pointer (ctx); */ - -/* if (!strcmp (key, "foo")) */ -/* { */ -/* } */ -/* else */ -/* return ASSUAN_Invalid_Option; */ - return 0; } +/* LEARN [--force] + + Learn all useful information of the currently inserted card. When + used without the force options, the command might to an INQUIRE + like this: + + INQUIRE KNOWNCARDP + + 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. + +*/ +static int +cmd_learn (ASSUAN_CONTEXT ctx, char *line) +{ + CTRL ctrl = assuan_get_pointer (ctx); + int rc = 0; + + /* if this is the first command issued for a new card, open the card and + and create a context */ + if (!ctrl->card_ctx) + { + rc = card_open (&ctrl->card_ctx); + if (rc) + return map_to_assuan_status (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 */ + if (!has_option (line, "--force")) + { + 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) + { + if (rc != ASSUAN_Canceled) + log_error ("inquire KNOWNCARDP failed: %s\n", + assuan_strerror (rc)); + return rc; + } + /* not canceled, so we have to proceeed */ + } + + return map_to_assuan_status (rc); +} @@ -81,6 +149,7 @@ register_commands (ASSUAN_CONTEXT ctx) int cmd_id; int (*handler)(ASSUAN_CONTEXT, char *line); } table[] = { + { "LEARN", 0, cmd_learn }, { "", ASSUAN_CMD_INPUT, NULL }, { "", ASSUAN_CMD_OUTPUT, NULL }, { NULL } @@ -168,7 +237,7 @@ scd_command_handler (int listen_fd) continue; } } - + reset_notify (ctx); /* used for cleanup */ assuan_deinit_server (ctx); } diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 76d00b1ce..7d43dc129 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -21,6 +21,7 @@ #ifndef SCDAEMON_H #define SCDAEMON_H +#include #include #include "../common/util.h" #include "../common/errors.h" @@ -55,13 +56,15 @@ struct { #define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE) struct server_local_s; +struct card_ctx_s; struct server_control_s { struct server_local_s *server_local; + struct card_ctx_s *card_ctx; }; typedef struct server_control_s *CTRL; - +typedef struct card_ctx_s *CARD; /*-- scdaemon.c --*/ void scd_exit (int rc); @@ -70,5 +73,11 @@ void scd_init_default_ctrl (CTRL ctrl); /*-- command.c --*/ void scd_command_handler (int); +/*-- 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); + + #endif /*SCDAEMON_H*/