diff --git a/agent/agent.h b/agent/agent.h index 84e5e782b..e08507cb2 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -322,6 +322,7 @@ typedef enum CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */ CACHE_MODE_SSH, /* SSH related cache. */ CACHE_MODE_NONCE, /* This is a non-predictable nonce. */ + CACHE_MODE_PIN, /* PINs stored/retrieved by scdaemon. */ CACHE_MODE_DATA /* Arbitrary data. */ } cache_mode_t; @@ -479,7 +480,7 @@ int agent_clear_passphrase (ctrl_t ctrl, void initialize_module_cache (void); void deinitialize_module_cache (void); void agent_cache_housekeeping (void); -void agent_flush_cache (void); +void agent_flush_cache (int pincache_only); int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, const char *data, int ttl); char *agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode); diff --git a/agent/cache.c b/agent/cache.c index 4a3e5a547..8a6c43a30 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -204,7 +204,9 @@ housekeeping (void) /* First expire the actual data */ for (r=thecache; r; r = r->next) { - if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current) + if (r->cache_mode == CACHE_MODE_PIN) + ; /* Don't let it expire - scdaemon explictly flushes them. */ + else if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current) { if (DBG_CACHE) log_debug (" expired '%s'.%d (%ds after last access)\n", @@ -226,6 +228,7 @@ housekeeping (void) switch (r->cache_mode) { case CACHE_MODE_DATA: + case CACHE_MODE_PIN: continue; /* No MAX TTL here. */ case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; default: maxttl = opt.max_cache_ttl; break; @@ -288,13 +291,13 @@ agent_cache_housekeeping (void) void -agent_flush_cache (void) +agent_flush_cache (int pincache_only) { ITEM r; int res; if (DBG_CACHE) - log_debug ("agent_flush_cache\n"); + log_debug ("agent_flush_cache%s\n", pincache_only?" (pincache only)":""); res = npth_mutex_lock (&cache_lock); if (res) @@ -302,6 +305,8 @@ agent_flush_cache (void) for (r=thecache; r; r = r->next) { + if (pincache_only && r->cache_mode != CACHE_MODE_PIN) + continue; if (r->pw) { if (DBG_CACHE) @@ -361,6 +366,7 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, { case CACHE_MODE_SSH: ttl = opt.def_cache_ttl_ssh; break; case CACHE_MODE_DATA: ttl = DEF_CACHE_TTL_DATA; break; + case CACHE_MODE_PIN: ttl = -1; break; default: ttl = opt.def_cache_ttl; break; } } @@ -369,11 +375,24 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, for (r=thecache; r; r = r->next) { - if (((cache_mode != CACHE_MODE_USER - && cache_mode != CACHE_MODE_NONCE) - || cache_mode_equal (r->cache_mode, cache_mode)) - && r->restricted == restricted - && !strcmp (r->key, key)) + if (cache_mode == CACHE_MODE_PIN && data) + { + /* PIN mode is special because it is only used by scdaemon. */ + if (!strcmp (r->key, key)) + break; + } + else if (cache_mode == CACHE_MODE_PIN) + { + /* FIXME: Parse the structure of the key and delete several + * cached PINS. */ + if (!strcmp (r->key, key)) + break; + } + else if (((cache_mode != CACHE_MODE_USER + && cache_mode != CACHE_MODE_NONCE) + || cache_mode_equal (r->cache_mode, cache_mode)) + && r->restricted == restricted + && !strcmp (r->key, key)) break; } if (r) /* Replace. */ @@ -437,6 +456,7 @@ agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) int res; int last_stored = 0; int restricted = ctrl? ctrl->restricted : -1; + int yes; if (cache_mode == CACHE_MODE_IGNORE) return NULL; @@ -461,12 +481,19 @@ agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) for (r=thecache; r; r = r->next) { - if (r->pw - && ((cache_mode != CACHE_MODE_USER - && cache_mode != CACHE_MODE_NONCE) - || cache_mode_equal (r->cache_mode, cache_mode)) - && r->restricted == restricted - && !strcmp (r->key, key)) + if (cache_mode == CACHE_MODE_PIN) + yes = (r->pw && !strcmp (r->key, key)); + else if (r->pw + && ((cache_mode != CACHE_MODE_USER + && cache_mode != CACHE_MODE_NONCE) + || cache_mode_equal (r->cache_mode, cache_mode)) + && r->restricted == restricted + && !strcmp (r->key, key)) + yes = 1; + else + yes = 0; + + if (yes) { /* Note: To avoid races KEY may not be accessed anymore * below. Note also that we don't update the accessed time diff --git a/agent/call-scd.c b/agent/call-scd.c index a96f5b783..0bd40173e 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -246,6 +246,8 @@ wait_child_thread (void *arg) } #endif + agent_flush_cache (1); /* Flush the PIN cache. */ + err = npth_mutex_lock (&start_scd_lock); if (err) { @@ -389,6 +391,8 @@ start_scd (ctrl_t ctrl) if (opt.verbose) log_info ("no running SCdaemon - starting it\n"); + agent_flush_cache (1); /* Make sure the PIN cache is flushed. */ + if (fflush (NULL)) { #ifndef HAVE_W32_SYSTEM @@ -625,11 +629,137 @@ agent_reset_scd (ctrl_t ctrl) } + +/* This handler is a helper for pincache_put_cb but may also be called + * directly for that status code with ARGS being the arguments after + * the status keyword (and with white space removed). */ +static gpg_error_t +handle_pincache_put (const char *args) +{ + gpg_error_t err; + const char *s, *key, *hexwrappedpin; + char *keybuf = NULL; + unsigned char *wrappedpin = NULL; + size_t keylen, hexwrappedpinlen, wrappedpinlen; + char *value = NULL; + size_t valuelen; + gcry_cipher_hd_t cipherhd = NULL; + + key = s = args; + while (*s && !spacep (s)) + s++; + keylen = s - key; + if (keylen < 3) + { + /* At least we need 2 slashes and slot number. */ + log_error ("%s: ignoring invalid key\n", __func__); + err = 0; + goto leave; + } + + + keybuf = xtrymalloc (keylen+1); + if (!keybuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (keybuf, key, keylen); + keybuf[keylen] = 0; + key = keybuf; + + while (spacep (s)) + s++; + hexwrappedpin = s; + while (*s && !spacep (s)) + s++; + hexwrappedpinlen = s - hexwrappedpin; + if (!hexwrappedpinlen) + { + /* Flush the cache. The cache module knows aboput the structure + * of the key to flush only parts. */ + log_debug ("%s: flushing cache '%s'\n", __func__, key); + agent_put_cache (NULL, key, CACHE_MODE_PIN, NULL, -1); + err = 0; + goto leave; + } + + if (hexwrappedpinlen < 2*24) + { + log_error ("%s: ignoring request with too short cryptogram\n", __func__); + err = 0; + goto leave; + } + wrappedpinlen = hexwrappedpinlen / 2; + wrappedpin = xtrymalloc (wrappedpinlen); + if (!wrappedpin) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (hex2bin (hexwrappedpin, wrappedpin, wrappedpinlen) == -1) + { + log_error ("%s: invalid hex length\n", __func__); + err = gpg_error (GPG_ERR_INV_LENGTH); + goto leave; + } + + valuelen = wrappedpinlen - 8; + value = xtrymalloc_secure (valuelen+1); + if (!value) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_AESWRAP, 0); + if (!err) + err = gcry_cipher_setkey (cipherhd, "1234567890123456", 16); + if (!err) + err = gcry_cipher_decrypt (cipherhd, value, valuelen, + wrappedpin, wrappedpinlen); + if (err) + { + log_error ("%s: error decrypting the cryptogram: %s\n", + __func__, gpg_strerror (err)); + goto leave; + } + + log_debug ("%s: caching '%s'->'%s'\n", __func__, key, value); + agent_put_cache (NULL, key, CACHE_MODE_PIN, value, -1); + + leave: + xfree (keybuf); + xfree (value); + xfree (wrappedpin); + gcry_cipher_close (cipherhd); + return err; +} + + +/* This status callback is to intercept the PINCACHE_PUT status messages. */ +static gpg_error_t +pincache_put_cb (void *opaque, const char *line) +{ + const char *s; + + (void)opaque; + + s = has_leading_keyword (line, "PINCACHE_PUT"); + if (s) + return handle_pincache_put (s); + else + return 0; +} + + static gpg_error_t learn_status_cb (void *opaque, const char *line) { struct learn_parm_s *parm = opaque; + gpg_error_t err = 0; const char *keyword = line; int keywordlen; @@ -645,12 +775,14 @@ learn_status_cb (void *opaque, const char *line) { parm->kpinfo_cb (parm->kpinfo_cb_arg, line); } + else if (keywordlen == 12 && !memcmp (keyword, "PINCACHE_PUT", keywordlen)) + err = handle_pincache_put (line); else if (keywordlen && *line) { parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line); } - return 0; + return err; } /* Perform the LEARN command and return a list of all private keys @@ -692,6 +824,7 @@ agent_card_learn (ctrl_t ctrl, static gpg_error_t get_serialno_cb (void *opaque, const char *line) { + gpg_error_t err = 0; char **serialno = opaque; const char *keyword = line; const char *s; @@ -716,8 +849,10 @@ get_serialno_cb (void *opaque, const char *line) memcpy (*serialno, line, n); (*serialno)[n] = 0; } + else if (keywordlen == 12 && !memcmp (keyword, "PINCACHE_PUT", keywordlen)) + err = handle_pincache_put (line); - return 0; + return err; } /* Return the serial number of the card or an appropriate error. The @@ -787,6 +922,12 @@ inq_needpin (void *opaque, const char *line) rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, "", NULL, 0); } + else if ((s = has_leading_keyword (line, "PINCACHE_GET"))) + { + /* rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, */ + /* "", NULL, 0); */ + rc = 0; + } else if (parm->passthru) { unsigned char *value; @@ -876,7 +1017,7 @@ agent_card_pksign (ctrl_t ctrl, bin2hex (indata, indatalen, stpcpy (line, "SETDATA ")); rc = assuan_transact (ctrl->scd_local->ctx, line, - NULL, NULL, NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL, pincache_put_cb, NULL); if (rc) return unlock_scd (ctrl, rc); @@ -897,7 +1038,7 @@ agent_card_pksign (ctrl_t ctrl, rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, inq_needpin, &inqparm, - NULL, NULL); + pincache_put_cb, NULL); if (rc) { @@ -918,6 +1059,7 @@ agent_card_pksign (ctrl_t ctrl, static gpg_error_t padding_info_cb (void *opaque, const char *line) { + gpg_error_t err = 0; int *r_padding = opaque; const char *s; @@ -925,8 +1067,10 @@ padding_info_cb (void *opaque, const char *line) { *r_padding = atoi (s); } + else if ((s=has_leading_keyword (line, "PINCACHE_PUT"))) + err = handle_pincache_put (line); - return 0; + return err; } @@ -1023,7 +1167,7 @@ agent_card_readcert (ctrl_t ctrl, rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, NULL, NULL, - NULL, NULL); + pincache_put_cb, NULL); if (rc) { xfree (get_membuf (&data, &len)); @@ -1058,7 +1202,7 @@ agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, NULL, NULL, - NULL, NULL); + pincache_put_cb, NULL); if (rc) { xfree (get_membuf (&data, &len)); @@ -1122,7 +1266,8 @@ agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, parms.keydatalen = keydatalen; err = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, - inq_writekey_parms, &parms, NULL, NULL); + inq_writekey_parms, &parms, + pincache_put_cb, NULL); return unlock_scd (ctrl, err); } @@ -1140,6 +1285,7 @@ struct card_getattr_parm_s { static gpg_error_t card_getattr_cb (void *opaque, const char *line) { + gpg_error_t err = 0; struct card_getattr_parm_s *parm = opaque; const char *keyword = line; int keywordlen; @@ -1159,8 +1305,10 @@ card_getattr_cb (void *opaque, const char *line) if (!parm->data) parm->error = errno; } + else if (keywordlen == 12 && !memcmp (keyword, "PINCACHE_PUT", keywordlen)) + err = handle_pincache_put (line); - return 0; + return err; } @@ -1221,6 +1369,7 @@ struct card_cardlist_parm_s { static gpg_error_t card_cardlist_cb (void *opaque, const char *line) { + gpg_error_t err = 0; struct card_cardlist_parm_s *parm = opaque; const char *keyword = line; int keywordlen; @@ -1243,8 +1392,10 @@ card_cardlist_cb (void *opaque, const char *line) else add_to_strlist (&parm->list, line); } + else if (keywordlen == 12 && !memcmp (keyword, "PINCACHE_PUT", keywordlen)) + err = handle_pincache_put (line); - return 0; + return err; } /* Call the scdaemon to retrieve list of available cards. On success @@ -1290,6 +1441,7 @@ struct card_keyinfo_parm_s { static gpg_error_t card_keyinfo_cb (void *opaque, const char *line) { + gpg_error_t err = 0; struct card_keyinfo_parm_s *parm = opaque; const char *keyword = line; int keywordlen; @@ -1378,8 +1530,10 @@ card_keyinfo_cb (void *opaque, const char *line) *l_p = keyinfo; } + else if (keywordlen == 12 && !memcmp (keyword, "PINCACHE_PUT", keywordlen)) + err = handle_pincache_put (line); - return 0; + return err; } @@ -1435,6 +1589,7 @@ agent_card_keyinfo (ctrl_t ctrl, const char *keygrip, static gpg_error_t pass_status_thru (void *opaque, const char *line) { + gpg_error_t err = 0; assuan_context_t ctx = opaque; char keyword[200]; int i; @@ -1459,9 +1614,13 @@ pass_status_thru (void *opaque, const char *line) while (spacep (line)) line++; - assuan_write_status (ctx, keyword, line); + /* We do not want to pass PINCACHE_PUT through. */ + if (!strcmp (keyword, "PINCACHE_PUT")) + err = handle_pincache_put (line); + else + assuan_write_status (ctx, keyword, line); } - return 0; + return err; } static gpg_error_t @@ -1523,4 +1682,5 @@ agent_card_killscd (void) return; assuan_transact (primary_scd_ctx, "KILLSCD", NULL, NULL, NULL, NULL, NULL, NULL); + agent_flush_cache (1); /* Flush the PIN cache. */ } diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 57d5a459c..6a7213c67 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -2434,7 +2434,7 @@ agent_sighup_action (void) log_info ("SIGHUP received - " "re-reading configuration and flushing cache\n"); - agent_flush_cache (); + agent_flush_cache (0); reread_configuration (); agent_reload_trustlist (); /* We flush the module name cache so that after installing a