diff --git a/agent/ChangeLog b/agent/ChangeLog index 775a44489..118559c64 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,5 +1,13 @@ 2005-02-24 Werner Koch + * call-scd.c (unescape_status_string): New. Actual a copy of + ../g10/call-agent.c + (card_getattr_cb, agent_card_getattr): New. + + * command-ssh.c (card_key_available): New. + (ssh_handler_request_identities): First see whether a card key is + available. + * gpg-agent.c (handle_connections): Need to check for events if select returns with -1. diff --git a/agent/agent.h b/agent/agent.h index 0661cc4ad..39e479e48 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -259,6 +259,7 @@ int agent_card_pkdecrypt (ctrl_t ctrl, int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen); int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf); +gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result); int agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context); diff --git a/agent/call-scd.c b/agent/call-scd.c index bffdbcbad..f7d32f7cf 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -1,5 +1,5 @@ /* call-scd.c - fork of the scdaemon to do SC operations - * Copyright (C) 2001, 2002 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -66,7 +66,7 @@ static pth_mutex_t scd_lock; static int active_connection_fd = -1; static int active_connection = 0; -/* callback parameter for learn card */ +/* Callback parameter for learn card */ struct learn_parm_s { void (*kpinfo_cb)(void*, const char *); void *kpinfo_cb_arg; @@ -266,6 +266,41 @@ agent_reset_scd (ctrl_t ctrl) } + +/* Return a new malloced string by unescaping the string S. Escaping + is percent escaping and '+'/space mapping. A binary Nul will + silently be replaced by a 0xFF. Function returns NULL to indicate + an out of memory status. */ +static char * +unescape_status_string (const unsigned char *s) +{ + char *buffer, *d; + + buffer = d = xtrymalloc (strlen (s)+1); + if (!buffer) + return NULL; + while (*s) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d = xtoi_2 (s); + if (!*d) + *d = '\xff'; + d++; + s += 2; + } + else if (*s == '+') + { + *d++ = ' '; + s++; + } + else + *d++ = *s++; + } + *d = 0; + return buffer; +} @@ -375,14 +410,6 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno) if (rc) return rc; - /* Hmm, do we really need this reset - scddaemon should do this or - we can do this if we for some reason figure out that the - operation might have failed due to a missing RESET. Hmmm, I feel - this is really SCdaemon's duty */ -/* rc = assuan_transact (scd_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); */ -/* if (rc) */ -/* return unlock_scd (map_assuan_err (rc)); */ - rc = assuan_transact (scd_ctx, "SERIALNO", NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); @@ -395,6 +422,8 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno) return unlock_scd (0); } + + static AssuanError membuf_data_cb (void *opaque, const void *buffer, size_t length) @@ -644,6 +673,90 @@ agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) } + +/* Type used with the card_getattr_cb. */ +struct card_getattr_parm_s { + const char *keyword; /* Keyword to look for. */ + size_t keywordlen; /* strlen of KEYWORD. */ + char *data; /* Malloced and unescaped data. */ + int error; /* ERRNO value or 0 on success. */ +}; + +/* Callback function for agent_card_getattr. */ +static assuan_error_t +card_getattr_cb (void *opaque, const char *line) +{ + struct card_getattr_parm_s *parm = opaque; + const char *keyword = line; + int keywordlen; + + if (parm->data) + return 0; /* We want only the first occurrence. */ + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == parm->keywordlen + && !memcmp (keyword, parm->keyword, keywordlen)) + { + parm->data = unescape_status_string (line); + if (!parm->data) + parm->error = errno; + } + + return 0; +} + + +/* Call the agent to retrieve a single line data object. On success + the object is malloced and stored at RESULT; it is guaranteed that + NULL is never stored in this case. On error an error code is + returned and NULL stored at RESULT. */ +gpg_error_t +agent_card_getattr (ctrl_t ctrl, const char *name, char **result) +{ + int err; + struct card_getattr_parm_s parm; + char line[ASSUAN_LINELENGTH]; + + *result = NULL; + + if (!*name) + return gpg_error (GPG_ERR_INV_VALUE); + + memset (&parm, 0, sizeof parm); + parm.keyword = name; + parm.keywordlen = strlen (name); + + /* We assume that NAME does not need escaping. */ + if (8 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + stpcpy (stpcpy (line, "GETATTR "), name); + + err = start_scd (ctrl); + if (err) + return err; + + err = map_assuan_err (assuan_transact (scd_ctx, line, + NULL, NULL, NULL, NULL, + card_getattr_cb, &parm)); + if (!err && parm.error) + err = gpg_error_from_errno (parm.error); + + if (!err && !parm.data) + err = gpg_error (GPG_ERR_NO_DATA); + + if (!err) + *result = parm.data; + else + xfree (parm.data); + + return unlock_scd (err); +} + + static AssuanError diff --git a/agent/command-ssh.c b/agent/command-ssh.c index 8ea042e19..2c0d25ef6 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -1201,7 +1201,7 @@ sexp_key_extract (gcry_sexp_t sexp, return err; } -/* Extract the car from SEXP, and create a newly created C-string it, +/* Extract the car from SEXP, and create a newly created C-string which is to be stored in IDENTIFIER. */ static gpg_error_t sexp_extract_identifier (gcry_sexp_t sexp, const char **identifier) @@ -1404,6 +1404,7 @@ ssh_convert_key_to_blob (unsigned char **blob, size_t *blob_size, } +/* Write the public key KEY_PUBLIC to STREAM in SSH key format. */ static gpg_error_t ssh_send_key_public (estream_t stream, gcry_sexp_t key_public) { @@ -1516,7 +1517,78 @@ key_secret_to_public (gcry_sexp_t *key_public, return err; } - + +/* Chec whether a smartcard is available and whether it has a usable + key. Store a copy of that key at R_PK and return 0. If no key is + available store NULL at R_PK and return an error code. */ +static gpg_error_t +card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk) +{ + gpg_error_t err; + char *appname; + unsigned char *sbuf; + size_t sbuflen; + gcry_sexp_t pk; + + *r_pk = NULL; + + /* First see whether a card is available and whether the application + is supported. */ + err = agent_card_getattr (ctrl, "APPTYPE", &appname); + if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED ) + { + /* Ask for the serial number to reset the card. */ + err = agent_card_serialno (ctrl, &appname); + if (err) + { + if (opt.verbose) + log_info (_("can't get serial number of card: %s\n"), + gpg_strerror (err)); + return err; + } + log_info (_("detected card with S/N: %s\n"), appname); + xfree (appname); + err = agent_card_getattr (ctrl, "APPTYPE", &appname); + } + if (err) + { + log_error (_("error getting application type of card: %s\n"), + gpg_strerror (err)); + return err; + } + if (strcmp (appname, "OPENPGP")) + { + log_info (_("card application `%s' is not supported\n"), appname); + xfree (appname); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + xfree (appname); + appname = NULL; + + /* Read the public key. */ + err = agent_card_readkey (ctrl, "OPENPGP.3", &sbuf); + if (err) + { + if (opt.verbose) + log_info (_("no suitable card key found: %s\n"), gpg_strerror (err)); + return err; + } + + sbuflen = gcry_sexp_canon_len (sbuf, 0, NULL, NULL); + err = gcry_sexp_sscan (&pk, NULL, sbuf, sbuflen); + xfree (sbuf); + if (err) + { + log_error ("failed to build S-Exp from received card key: %s\n", + gpg_strerror (err)); + return err; + } + + *r_pk = pk; + return 0; +} + + /* Request handler. @@ -1589,91 +1661,95 @@ ssh_handler_request_identities (ctrl_t ctrl, goto out; } - /* Iterate over key files. */ - /* FIXME: make sure that buffer gets deallocated properly. */ + + /* First check whether a key is currently available in the card + reader - this should be allowed even without being listed in + sshcontrol.txt. */ + + if (!card_key_available (ctrl, &key_public)) + { + err = ssh_send_key_public (key_blobs, key_public); + gcry_sexp_release (key_public); + key_public = NULL; + if (err) + goto out; + + key_counter++; + } + + + /* Then look at all the registered an allowed keys. */ + /* Fixme: We should better iterate over the control file and check whether the key file is there. This is better in resepct to performance if tehre are a lot of key sin our key storage. */ - + /* FIXME: make sure that buffer gets deallocated properly. */ err = open_control_file (&ctrl_fp, 0); if (err) goto out; -#warning Really need to fix this fixme. - /* - FIXME: First check whether a key is currently available in the card reader - this should be allowed even without being listed in sshcontrol.txt. - */ - - while (1) + while ( (dir_entry = readdir (dir)) ) { - dir_entry = readdir (dir); - if (dir_entry) - { - if ((strlen (dir_entry->d_name) == 44) - && (! strncmp (dir_entry->d_name + 40, ".key", 4))) - { - char hexgrip[41]; - int disabled; + if ((strlen (dir_entry->d_name) == 44) + && (! strncmp (dir_entry->d_name + 40, ".key", 4))) + { + char hexgrip[41]; + int disabled; - /* We do only want to return keys listed in our control - file. */ - strncpy (hexgrip, dir_entry->d_name, 40); - hexgrip[40] = 0; - if ( strlen (hexgrip) != 40 ) - continue; - if (search_control_file (ctrl_fp, hexgrip, &disabled) - || disabled) - continue; + /* We do only want to return keys listed in our control + file. */ + strncpy (hexgrip, dir_entry->d_name, 40); + hexgrip[40] = 0; + if ( strlen (hexgrip) != 40 ) + continue; + if (search_control_file (ctrl_fp, hexgrip, &disabled) + || disabled) + continue; - strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40); + strncpy (key_path + key_directory_n + 1, dir_entry->d_name, 40); - /* Read file content. */ - err = file_to_buffer (key_path, &buffer, &buffer_n); - if (err) - break; + /* Read file content. */ + err = file_to_buffer (key_path, &buffer, &buffer_n); + if (err) + goto out; - err = gcry_sexp_sscan (&key_secret, NULL, buffer, buffer_n); - if (err) - break; + err = gcry_sexp_sscan (&key_secret, NULL, buffer, buffer_n); + if (err) + goto out; - xfree (buffer); - buffer = NULL; + xfree (buffer); + buffer = NULL; - err = sexp_extract_identifier (key_secret, &key_type); - if (err) - break; + err = sexp_extract_identifier (key_secret, &key_type); + if (err) + goto out; - err = ssh_key_type_lookup (NULL, key_type, &spec); - if (err) - break; + err = ssh_key_type_lookup (NULL, key_type, &spec); + if (err) + goto out; - xfree ((void *) key_type); - key_type = NULL; + xfree ((void *) key_type); + key_type = NULL; - err = key_secret_to_public (&key_public, spec, key_secret); - if (err) - break; + err = key_secret_to_public (&key_public, spec, key_secret); + if (err) + goto out; - gcry_sexp_release (key_secret); - key_secret = NULL; + gcry_sexp_release (key_secret); + key_secret = NULL; - err = ssh_send_key_public (key_blobs, key_public); - if (err) - break; + err = ssh_send_key_public (key_blobs, key_public); + if (err) + goto out; - gcry_sexp_release (key_public); - key_public = NULL; + gcry_sexp_release (key_public); + key_public = NULL; - key_counter++; - } - } - else - break; + key_counter++; + } } - if (err) - goto out; ret = es_fseek (key_blobs, 0, SEEK_SET); if (ret) diff --git a/scd/ChangeLog b/scd/ChangeLog index c78bd011f..dc394b677 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,5 +1,8 @@ 2005-02-24 Werner Koch + * app.c (app_getattr): Return APPTYPE or SERIALNO type even if the + application does dot support the getattr call. + * app-openpgp.c (get_one_do): Never try to get a non cacheable object from the cache. (get_one_do): Add new arg to return an error code. Changed all @@ -13,6 +16,7 @@ been removed without a reset. (do_reset, cmd_serialno): Clear that error flag. (TEST_CARD_REMOVAL): New. Use it with all command handlers. + (scd_update_reader_status_file): Set the error flag on all changes. * scdaemon.c (ticker_thread): Termintate if a shutdown is pending. diff --git a/scd/app.c b/scd/app.c index 857f9e10b..384ee2143 100644 --- a/scd/app.c +++ b/scd/app.c @@ -305,6 +305,35 @@ app_getattr (APP app, CTRL ctrl, const char *name) return gpg_error (GPG_ERR_INV_VALUE); if (!app->initialized) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + + if (app->apptype && name && !strcmp (name, "APPTYPE")) + { + send_status_info (ctrl, "APPTYPE", + app->apptype, strlen (app->apptype), NULL, 0); + return 0; + } + if (name && !strcmp (name, "SERIALNO")) + { + char *serial_and_stamp; + char *serial; + time_t stamp; + int rc; + + rc = app_get_serial_and_stamp (app, &serial, &stamp); + if (rc) + return rc; + rc = asprintf (&serial_and_stamp, "%s %lu", + serial, (unsigned long)stamp); + rc = (rc < 0)? gpg_error_from_errno (errno) : 0; + xfree (serial); + if (rc) + return rc; + send_status_info (ctrl, "SERIALNO", + serial_and_stamp, strlen (serial_and_stamp), NULL, 0); + free (serial_and_stamp); + return 0; + } + if (!app->fnc.getattr) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); return app->fnc.getattr (app, ctrl, name); diff --git a/scd/command.c b/scd/command.c index a4fb968cf..63e3e28e1 100644 --- a/scd/command.c +++ b/scd/command.c @@ -239,7 +239,7 @@ percent_plus_unescape (unsigned char *string) operations are done on the same card unless he calls this function. */ static int -cmd_serialno (ASSUAN_CONTEXT ctx, char *line) +cmd_serialno (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; @@ -248,7 +248,8 @@ cmd_serialno (ASSUAN_CONTEXT ctx, char *line) time_t stamp; /* Clear the remove flag so that the open_card is able to reread it. */ - ctrl->server_local->card_removed = 0; + if (ctrl->server_local->card_removed) + do_reset (ctrl, 0); if ((rc = open_card (ctrl, *line? line:NULL))) return rc; @@ -1092,7 +1093,6 @@ cmd_checkpin (ASSUAN_CONTEXT ctx, char *line) - /* Tell the assuan library about our commands */ static int @@ -1299,10 +1299,6 @@ scd_update_reader_status_file (void) char templ[50]; FILE *fp; - last[slot].any = 1; - last[slot].status = status; - last[slot].changed = changed; - log_info ("updating status of slot %d to 0x%04X\n", slot, status); sprintf (templ, "reader_%d.status", slot); @@ -1318,7 +1314,19 @@ scd_update_reader_status_file (void) } xfree (fname); - /* Send a signal to the primary client, if any. */ + /* Set the card removed flag. We will set this on any + card change because a reset or SERIALNO request must be + done in any case. */ + if (primary_connection && primary_connection->server_local + && last[slot].any ) + primary_connection->server_local->card_removed = 1; + + last[slot].any = 1; + last[slot].status = status; + last[slot].changed = changed; + + + /* Send a signal to the primary client, if any. */ if (primary_connection && primary_connection->server_local && primary_connection->server_local->assuan_ctx) {