From dcee2db36ba49a689625f8c4381000bb6e82ea76 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 30 Sep 2024 18:22:25 +0200 Subject: [PATCH] gpgsm: Use a cache to speed up parent certificate lookup. * sm/gpgsm.h (COMPAT_NO_CHAIN_CACHE): New. (struct cert_cache_item_s, cert_cache_item_t): New. (struct server_control_s): Add parent_cert_cache. * sm/gpgsm.c (compatibility_flags): Add "no-chain-cache". (parent_cache_stats): New. (gpgsm_exit): Print the stats with --debug=memstat. (gpgsm_deinit_default_ctrl): Release the cache. * sm/certchain.c (gpgsm_walk_cert_chain): Cache the certificates. (do_validate_chain): Ditto. -- This gives another boost of 30% (from 6.5 to 4.0 seconds in the test environment with ~1000 certs). do_validate_chain actually brings us the speedup becuase the gpgsm_walk_cert_chain is not used during a key listing. For the latter we actually cache all certificates because that was easier. GnuPG-bug-id: 7308 Adjusted for 2.2: - Add gpgsm_deinit_default_ctrl - Remove ctrl arg from keydb_new --- sm/certchain.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++---- sm/gpgsm.c | 27 ++++++++++++++++ sm/gpgsm.h | 18 ++++++++++- sm/server.c | 1 + 4 files changed, 125 insertions(+), 8 deletions(-) diff --git a/sm/certchain.c b/sm/certchain.c index 8c25bdec1..334af8d2d 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -1051,15 +1051,10 @@ gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next) gpg_error_t err = 0; char *issuer = NULL; char *subject = NULL; - KEYDB_HANDLE kh = keydb_new (); + KEYDB_HANDLE kh = NULL; + cert_cache_item_t ci; *r_next = NULL; - if (!kh) - { - log_error (_("failed to allocate keyDB handle\n")); - err = gpg_error (GPG_ERR_GENERAL); - goto leave; - } issuer = ksba_cert_get_issuer (start, 0); subject = ksba_cert_get_subject (start, 0); @@ -1082,6 +1077,30 @@ gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next) goto leave; } + if (!(opt.compat_flags & COMPAT_NO_CHAIN_CACHE)) + { + unsigned char fpr[20]; + + gpgsm_get_fingerprint (start, GCRY_MD_SHA1, fpr, NULL); + for (ci = ctrl->parent_cert_cache; ci; ci = ci->next) + { + if (!memcmp (fpr, ci->fpr, 20) && ci->result) + { + /* Found in the cache. */ + ksba_cert_ref ((*r_next = ci->result)); + goto leave; + } + } + } + + kh = keydb_new (); + if (!kh) + { + log_error (_("failed to allocate keyDB handle\n")); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + err = find_up (ctrl, kh, start, issuer, 0); if (err) { @@ -1100,6 +1119,22 @@ gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next) log_error ("keydb_get_cert() failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + /* Cache it. */ + if (!(opt.compat_flags & COMPAT_NO_CHAIN_CACHE)) + { + ci = xtrycalloc (1, sizeof *ci); + if (!ci) + { + err = gpg_error_from_syserror (); + goto leave; + } + gpgsm_get_fingerprint (start, GCRY_MD_SHA1, ci->fpr, NULL); + ksba_cert_ref ((ci->result = *r_next)); + ci->next = ctrl->parent_cert_cache; + ctrl->parent_cert_cache = ci; } leave: @@ -1816,6 +1851,24 @@ do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg, } /* Find the next cert up the tree. */ + if (!(opt.compat_flags & COMPAT_NO_CHAIN_CACHE)) + { + cert_cache_item_t ci; + unsigned char fpr[20]; + + gpgsm_get_fingerprint (subject_cert, GCRY_MD_SHA1, fpr, NULL); + for (ci = ctrl->parent_cert_cache; ci; ci = ci->next) + { + if (!memcmp (fpr, ci->fpr, 20) && ci->result) + { + /* Found in the cache. */ + ksba_cert_release (issuer_cert); + ksba_cert_ref ((issuer_cert = ci->result)); + goto found_in_cache; + } + } + } + keydb_search_reset (kh); rc = find_up (ctrl, kh, subject_cert, issuer, 0); if (rc) @@ -1846,6 +1899,26 @@ do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg, goto leave; } + /* Cache it. The chain->next is here so that the leaf + * certificates are not cached. */ + if (!(opt.compat_flags & COMPAT_NO_CHAIN_CACHE) && chain->next) + { + cert_cache_item_t ci; + + ci = xtrycalloc (1, sizeof *ci); + if (!ci) + { + rc = gpg_error_from_syserror (); + goto leave; + } + gpgsm_get_fingerprint (subject_cert, GCRY_MD_SHA1, ci->fpr, NULL); + ksba_cert_ref ((ci->result = issuer_cert)); + ci->next = ctrl->parent_cert_cache; + ctrl->parent_cert_cache = ci; + } + + found_in_cache: + try_another_cert: if (DBG_X509) { diff --git a/sm/gpgsm.c b/sm/gpgsm.c index 25fdfe57b..91a63bd13 100644 --- a/sm/gpgsm.c +++ b/sm/gpgsm.c @@ -469,6 +469,7 @@ static struct debug_flags_s debug_flags [] = static struct compatibility_flags_s compatibility_flags [] = { { COMPAT_ALLOW_KA_TO_ENCR, "allow-ka-to-encr" }, + { COMPAT_NO_CHAIN_CACHE, "no-chain-cache" }, { 0, NULL } }; @@ -499,6 +500,9 @@ static int default_include_certs = DEFAULT_INCLUDE_CERTS; /* Whether the chain mode shall be used for validation. */ static int default_validation_model; +/* Counter used to convey data from deinit_ctrl to gpgsm_exit. */ +static unsigned int parent_cache_stats; + /* The default cipher algo. */ #define DEFAULT_CIPHER_ALGO "AES256" @@ -2111,6 +2115,7 @@ main ( int argc, char **argv) } /* cleanup */ + gpgsm_deinit_default_ctrl (&ctrl); free_strlist (opt.keyserver); opt.keyserver = NULL; gpgsm_release_certlist (recplist); @@ -2135,6 +2140,7 @@ gpgsm_exit (int rc) gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE); if (opt.debug & DBG_MEMSTAT_VALUE) { + log_info ("cert_chain_cache: cached=%u\n", parent_cache_stats); gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); } @@ -2156,6 +2162,27 @@ gpgsm_init_default_ctrl (struct server_control_s *ctrl) } +/* This function is called to deinitialize a control object. The + * control object is is not released, though. */ +void +gpgsm_deinit_default_ctrl (ctrl_t ctrl) +{ + unsigned int n; + + n = 0; + while (ctrl->parent_cert_cache) + { + cert_cache_item_t next = ctrl->parent_cert_cache->next; + ksba_cert_release (ctrl->parent_cert_cache->result); + xfree (ctrl->parent_cert_cache); + ctrl->parent_cert_cache = next; + n++; + } + if (n > parent_cache_stats) + parent_cache_stats = n; +} + + int gpgsm_parse_validation_model (const char *model) { diff --git a/sm/gpgsm.h b/sm/gpgsm.h index 3946b5679..b05918f67 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -183,11 +183,23 @@ struct * policies: 1.3.6.1.4.1.7924.1.1:N: */ #define COMPAT_ALLOW_KA_TO_ENCR 1 - +/* Not actually a compatibiliy flag but useful to limit the + * required memory for a validated key listing. */ +#define COMPAT_NO_CHAIN_CACHE 2 /* Forward declaration for an object defined in server.c */ struct server_local_s; +/* On object used to keep a track of already known certificates. */ +struct cert_cache_item_s +{ + struct cert_cache_item_s *next; + unsigned char fpr[20]; /* The certificate's fingerprint. */ + ksba_cert_t result; /* The resulting certificate (ie. the issuer). */ +}; +typedef struct cert_cache_item_s *cert_cache_item_t; + + /* Session control object. This object is passed down to most functions. Note that the default values for it are set by gpgsm_init_default_ctrl(). */ @@ -236,6 +248,9 @@ struct server_control_s /* The current time. Used as a helper in certchain.c. */ ksba_isotime_t current_time; + + /* The cache used to find the parent cert. */ + cert_cache_item_t parent_cert_cache; }; @@ -271,6 +286,7 @@ extern int gpgsm_errors_seen; void gpgsm_exit (int rc); void gpgsm_init_default_ctrl (struct server_control_s *ctrl); +void gpgsm_deinit_default_ctrl (ctrl_t ctrl); int gpgsm_parse_validation_model (const char *model); /*-- server.c --*/ diff --git a/sm/server.c b/sm/server.c index 31abe3727..5afb7f150 100644 --- a/sm/server.c +++ b/sm/server.c @@ -1422,6 +1422,7 @@ gpgsm_server (certlist_t default_recplist) audit_release (ctrl.audit); ctrl.audit = NULL; + gpgsm_deinit_default_ctrl (&ctrl); assuan_release (ctx); }