1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-10-29 19:48:43 +01:00

gpgsm: Implement a cache for the KEYINFO queries.

* sm/gpgsm.h (struct keyinfo_cache_item_s): New.
(struct server_control_s): Add keyinfo_cache and keyinfo_cache_valid.
* sm/call-agent.c (keyinfo_cache_disabled): New flag.
(release_a_keyinfo_cache): New.
(gpgsm_flush_keyinfo_cache): New.
(struct keyinfo_status_parm_s): New.
(keyinfo_status_cb): Implement a fill mode.
(gpgsm_agent_keyinfo): Implement a cache.
* sm/server.c (reset_notify): Flush the cache.
* sm/gpgsm.c (gpgsm_deinit_default_ctrl): Ditto.
--

In almost all cases we have just a few private keys in the agent and
thus it is better to fetch them early.  This does not work in a
restricted connection but we take care and disable the cache in this
case.

This cache gives a a minor speed up.

GnuPG-bug-id: 7308
(cherry picked from commit 241971fac0)
This commit is contained in:
Werner Koch 2024-10-02 16:44:04 +02:00
parent 09d4b8f496
commit 9087c1d363
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
4 changed files with 154 additions and 20 deletions

View File

@ -98,6 +98,9 @@ static istrusted_cache_t istrusted_cache;
static int istrusted_cache_valid; static int istrusted_cache_valid;
static int istrusted_cache_disabled; static int istrusted_cache_disabled;
/* Flag indicating that we can't use the keyinfo cache at all. The
* actual cache is stored in CTRL. */
static int keyinfo_cache_disabled;
@ -121,6 +124,33 @@ flush_istrusted_cache (void)
} }
/* Release all items in *CACHEP and set CACHEP to NULL */
static void
release_a_keyinfo_cache (keyinfo_cache_item_t *cachep)
{
keyinfo_cache_item_t mycache;
/* First unlink the cache to be npth safe. */
mycache = *cachep;
*cachep = NULL;
while (mycache)
{
keyinfo_cache_item_t next = mycache->next;
xfree (mycache);
mycache = next;
}
}
/* Flush the keyinfo cache for the session CTRL. */
void
gpgsm_flush_keyinfo_cache (ctrl_t ctrl)
{
ctrl->keyinfo_cache_valid = 0;
release_a_keyinfo_cache (&ctrl->keyinfo_cache);
}
/* Print a warning if the server's version number is less than our /* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */ version number. Returns an error code on a connection problem. */
static gpg_error_t static gpg_error_t
@ -990,9 +1020,10 @@ gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr,
if (rc) if (rc)
{ {
if (gpg_err_code (rc) != GPG_ERR_FORBIDDEN) if (gpg_err_code (rc) != GPG_ERR_FORBIDDEN)
log_error ("filling istrusted cache failed: %s\n", log_info ("filling istrusted cache failed: %s\n",
gpg_strerror (rc)); gpg_strerror (rc));
istrusted_cache_disabled = 1; istrusted_cache_disabled = 1;
flush_istrusted_cache ();
rc = 0; /* Fallback to single requests. */ rc = 0; /* Fallback to single requests. */
} }
else else
@ -1330,42 +1361,79 @@ gpgsm_agent_send_nop (ctrl_t ctrl)
struct keyinfo_status_parm_s
{
char *serialno;
int fill_mode; /* True if we want to fill the cache. */
keyinfo_cache_item_t cache;
};
static gpg_error_t static gpg_error_t
keyinfo_status_cb (void *opaque, const char *line) keyinfo_status_cb (void *opaque, const char *line)
{ {
char **serialno = opaque; struct keyinfo_status_parm_s *parm = opaque;
const char *s, *s2; const char *s0, *s, *s2;
if ((s = has_leading_keyword (line, "KEYINFO")) && !*serialno) if ((s0 = has_leading_keyword (line, "KEYINFO"))
&& (!parm->serialno || parm->fill_mode))
{ {
s = strchr (s, ' '); s = strchr (s0, ' ');
xfree (parm->serialno);
parm->serialno = NULL;
if (s && s[1] == 'T' && s[2] == ' ' && s[3]) if (s && s[1] == 'T' && s[2] == ' ' && s[3])
{ {
s += 3; s += 3;
s2 = strchr (s, ' '); s2 = strchr (s, ' ');
if ( s2 > s ) if ( s2 > s )
{ {
*serialno = xtrymalloc ((s2 - s)+1); parm->serialno = xtrymalloc ((s2 - s)+1);
if (*serialno) if (parm->serialno)
{ {
memcpy (*serialno, s, s2 - s); memcpy (parm->serialno, s, s2 - s);
(*serialno)[s2 - s] = 0; parm->serialno[s2 - s] = 0;
} }
} }
} }
if (parm->fill_mode && *s0)
{
keyinfo_cache_item_t ci;
size_t n;
n = s? (s - s0) : strlen (s0);
ci = xtrymalloc (sizeof *ci + n);
if (!ci)
return gpg_error_from_syserror ();
memcpy (ci->hexgrip, s0, n);
ci->hexgrip[n] = 0;
ci->serialno = parm->serialno;
parm->serialno = NULL;
ci->next = parm->cache;
parm->cache = ci;
}
} }
return 0; return 0;
} }
/* Return the serial number for a secret key. If the returned serial /* Return the serial number for a secret key. If the returned serial
number is NULL, the key is not stored on a smartcard. Caller needs * number is NULL, the key is not stored on a smartcard. Caller needs
to free R_SERIALNO. */ * to free R_SERIALNO.
*
* Take care: The cache is currently only used in the key listing and
* it should not interfere with import or creation of new keys because
* we assume that is done by another process. However we assume that
* in server mode the key listing is not directly followed by an import
* and another key listing.
*/
gpg_error_t gpg_error_t
gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno) gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno)
{ {
gpg_error_t err; gpg_error_t err;
char line[ASSUAN_LINELENGTH]; char line[ASSUAN_LINELENGTH];
char *serialno = NULL; keyinfo_cache_item_t ci;
struct keyinfo_status_parm_s parm = { NULL };
*r_serialno = NULL; *r_serialno = NULL;
@ -1376,20 +1444,70 @@ gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno)
if (!hexkeygrip || strlen (hexkeygrip) != 40) if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE); return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip); /* First try to fill the cache. */
if (!keyinfo_cache_disabled && !ctrl->keyinfo_cache_valid)
{
parm.fill_mode = 1;
err = assuan_transact (agent_ctx, "KEYINFO --list",
NULL, NULL, NULL, NULL,
keyinfo_status_cb, &parm);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_FORBIDDEN)
log_error ("filling keyinfo cache failed: %s\n",
gpg_strerror (err));
keyinfo_cache_disabled = 1;
release_a_keyinfo_cache (&parm.cache);
err = 0; /* Fallback to single requests. */
}
else
{
ctrl->keyinfo_cache_valid = 1;
ctrl->keyinfo_cache = parm.cache;
parm.cache = NULL;
}
}
/* Then consult the cache or send a query */
if (ctrl->keyinfo_cache_valid)
{
for (ci = ctrl->keyinfo_cache; ci; ci = ci->next)
if (!strcmp (hexkeygrip, ci->hexgrip))
break;
if (ci)
{
xfree (parm.serialno);
parm.serialno = NULL;
err = 0;
if (ci->serialno)
{
parm.serialno = xtrystrdup (ci->serialno);
if (!parm.serialno)
err = gpg_error_from_syserror ();
}
}
else
err = gpg_error (GPG_ERR_NOT_FOUND);
}
else
{
snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip);
parm.fill_mode = 0;
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &serialno); keyinfo_status_cb, &parm);
if (!err && serialno) }
if (!err && parm.serialno)
{ {
/* Sanity check for bad characters. */ /* Sanity check for bad characters. */
if (strpbrk (serialno, ":\n\r")) if (strpbrk (parm.serialno, ":\n\r"))
err = GPG_ERR_INV_VALUE; err = gpg_error (GPG_ERR_INV_VALUE);
} }
if (err) if (err)
xfree (serialno); xfree (parm.serialno);
else else
*r_serialno = serialno; *r_serialno = parm.serialno;
return err; return err;
} }

View File

@ -2169,6 +2169,7 @@ gpgsm_deinit_default_ctrl (ctrl_t ctrl)
{ {
unsigned int n; unsigned int n;
gpgsm_flush_keyinfo_cache (ctrl);
n = 0; n = 0;
while (ctrl->parent_cert_cache) while (ctrl->parent_cert_cache)
{ {

View File

@ -199,6 +199,15 @@ struct cert_cache_item_s
}; };
typedef struct cert_cache_item_s *cert_cache_item_t; typedef struct cert_cache_item_s *cert_cache_item_t;
/* On object used to keep a KEYINFO data from the agent. */
struct keyinfo_cache_item_s
{
struct keyinfo_cache_item_s *next;
char *serialno; /* Malloced serialnumber of a card. */
char hexgrip[1]; /* The keygrip in hexformat. */
};
typedef struct keyinfo_cache_item_s *keyinfo_cache_item_t;
/* Session control object. This object is passed down to most /* Session control object. This object is passed down to most
functions. Note that the default values for it are set by functions. Note that the default values for it are set by
@ -251,6 +260,10 @@ struct server_control_s
/* The cache used to find the parent cert. */ /* The cache used to find the parent cert. */
cert_cache_item_t parent_cert_cache; cert_cache_item_t parent_cert_cache;
/* Cache of recently gathered KEYINFO data. */
keyinfo_cache_item_t keyinfo_cache;
int keyinfo_cache_valid;
}; };
@ -443,6 +456,7 @@ gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert);
gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert); gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert);
/*-- call-agent.c --*/ /*-- call-agent.c --*/
void gpgsm_flush_keyinfo_cache (ctrl_t ctrl);
int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc, int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char *digest, unsigned char *digest,
size_t digestlen, size_t digestlen,

View File

@ -330,6 +330,7 @@ reset_notify (assuan_context_t ctx, char *line)
(void) line; (void) line;
gpgsm_flush_keyinfo_cache (ctrl);
gpgsm_release_certlist (ctrl->server_local->recplist); gpgsm_release_certlist (ctrl->server_local->recplist);
gpgsm_release_certlist (ctrl->server_local->signerlist); gpgsm_release_certlist (ctrl->server_local->signerlist);
ctrl->server_local->recplist = NULL; ctrl->server_local->recplist = NULL;