From 070211eb990f5ea41271eba432b6a6b485cef7c7 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 17 Feb 2017 16:39:48 +0100 Subject: [PATCH] dirmngr: Add options --tls and --systrust to the VALIDATE cmd. * dirmngr/certcache.h (certlist_s, certlist_t): New. * dirmngr/certcache.c (read_certlist_from_stream): New. (release_certlist): New. * dirmngr/server.c (MAX_CERTLIST_LENGTH): New. (cmd_validate): Add options --tls and --systrust. Implement them using a kludge for now. * dirmngr/validate.c (validate_cert_chain): Support systrust checking. Add kludge to disable the CRL checking for tls mode. -- This can now be used to test a list of certificates as returned by TLS. Put the certs PEM encoded into a a file certlist.pem with the target certificate being the first. Then run gpg-connect-agent --dirmngr \ '/definqfile CERTLIST wiki-gnupg-chain.pem' \ 'validate --systrust --tls' /bye CRLS check has been disabled becuase we can't yet pass the systrust flag to the CRL checking code. Signed-off-by: Werner Koch --- dirmngr/certcache.c | 90 ++++++++++++++++++++++++++++++++++++++ dirmngr/certcache.h | 14 +++++- dirmngr/dirmngr.h | 4 +- dirmngr/server.c | 104 +++++++++++++++++++++++++++++++++++--------- dirmngr/validate.c | 12 +++-- 5 files changed, 198 insertions(+), 26 deletions(-) diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c index cd026c2d5..ff86f61b7 100644 --- a/dirmngr/certcache.c +++ b/dirmngr/certcache.c @@ -225,6 +225,7 @@ cert_compute_fpr (ksba_cert_t cert, unsigned char *digest) } + /* Cleanup one slot. This releases all resourses but keeps the actual slot in the cache marked for reuse. */ static void @@ -1669,3 +1670,92 @@ find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert) return err; } + + + +/* Read a list of certificates in PEM format from stream FP and store + * them on success at R_CERTLIST. On error NULL is stored at R_CERT + * list and an error code returned. Note that even on success an + * empty list of certificates can be returned (i.e. NULL stored at + * R_CERTLIST) iff the input stream has no certificates. */ +gpg_error_t +read_certlist_from_stream (certlist_t *r_certlist, estream_t fp) +{ + gpg_error_t err; + gnupg_ksba_io_t ioctx = NULL; + ksba_reader_t reader; + ksba_cert_t cert = NULL; + certlist_t certlist = NULL; + certlist_t cl, *cltail; + + *r_certlist = NULL; + + err = gnupg_ksba_create_reader (&ioctx, + (GNUPG_KSBA_IO_PEM | GNUPG_KSBA_IO_MULTIPEM), + fp, &reader); + if (err) + goto leave; + + /* Loop to read all certificates from the stream. */ + cltail = &certlist; + do + { + ksba_cert_release (cert); + cert = NULL; + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_read_der (cert, reader); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + goto leave; + } + + /* Append the certificate to the list. We also store the + * fingerprint and check whether we have a cached certificate; + * in that case the cached certificate is put into the list to + * take advantage of a validation result which might be stored + * in the cached certificate. */ + cl = xtrycalloc (1, sizeof *cl); + if (!cl) + { + err = gpg_error_from_syserror (); + goto leave; + } + cert_compute_fpr (cert, cl->fpr); + cl->cert = get_cert_byfpr (cl->fpr); + if (!cl->cert) + { + cl->cert = cert; + cert = NULL; + } + *cltail = cl; + cltail = &cl->next; + ksba_reader_clear (reader, NULL, NULL); + } + while (!gnupg_ksba_reader_eof_seen (ioctx)); + + leave: + ksba_cert_release (cert); + gnupg_ksba_destroy_reader (ioctx); + if (err) + release_certlist (certlist); + else + *r_certlist = certlist; + + return err; +} + + +/* Release the certificate list CL. */ +void +release_certlist (certlist_t cl) +{ + while (cl) + { + certlist_t next = cl->next; + ksba_cert_release (cl->cert); + cl = next; + } +} diff --git a/dirmngr/certcache.h b/dirmngr/certcache.h index ac93ee699..1f8670673 100644 --- a/dirmngr/certcache.h +++ b/dirmngr/certcache.h @@ -46,7 +46,6 @@ gpg_error_t cache_cert_silent (ksba_cert_t cert, void *fpr_buffer); * provided certificates are considered trusted. */ gpg_error_t is_trusted_cert (ksba_cert_t cert, int with_systrust); - /* Return a certificate object for the given fingerprint. FPR is expected to be a 20 byte binary SHA-1 fingerprint. If no matching certificate is available in the cache NULL is returned. The caller @@ -100,5 +99,18 @@ gpg_error_t find_issuing_cert (ctrl_t ctrl, +/* A simple list of certificates. */ +struct certlist_s +{ + struct certlist_s *next; + ksba_cert_t cert; + unsigned char fpr[20]; /* of the certificate. */ +}; +typedef struct certlist_s *certlist_t; + +gpg_error_t read_certlist_from_stream (certlist_t *r_certlist, estream_t fp); +void release_certlist (certlist_t cl); + + #endif /*CERTCACHE_H*/ diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index 3724c007e..19d2303ca 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -155,7 +155,8 @@ struct #define DBG_NETWORK (opt.debug & DBG_NETWORK_VALUE) #define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) -/* A simple list of certificate references. */ +/* A simple list of certificate references. FIXME: Better use + certlist_t also for references (Store NULL at .cert) */ struct cert_ref_s { struct cert_ref_s *next; @@ -163,6 +164,7 @@ struct cert_ref_s }; typedef struct cert_ref_s *cert_ref_t; + /* Forward references; access only through server.c. */ struct server_local_s; diff --git a/dirmngr/server.c b/dirmngr/server.c index bc373f5b0..05ef439a1 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -60,6 +60,10 @@ Dirmngr was a system service and not a user service. */ #define MAX_CERT_LENGTH (16*1024) +/* The limit for the CERTLIST inquiry. We allow for up to 20 + * certificates but also take PEM encoding into account. */ +#define MAX_CERTLIST_LENGTH ((MAX_CERT_LENGTH * 20 * 4)/3) + /* The same goes for OpenPGP keyblocks, but here we need to allow for much longer blocks; a 200k keyblock is not too unusual for keys with a lot of signatures (e.g. 0x5b0358a2). 9C31503C6D866396 even @@ -1729,7 +1733,7 @@ cmd_cachecert (assuan_context_t ctx, char *line) static const char hlp_validate[] = - "VALIDATE\n" + "VALIDATE [--systrust] [--tls]\n" "\n" "Validate a certificate using the certificate validation function\n" "used internally by dirmngr. This command is only useful for\n" @@ -1739,20 +1743,38 @@ static const char hlp_validate[] = " INQUIRE TARGETCERT\n" "\n" "and the caller is expected to return the certificate for the\n" - "request as a binary blob."; + "request as a binary blob. The option --tls modifies this by asking\n" + "for list of certificates with\n" + "\n" + " INQUIRE CERTLIST\n" + "\n" + "Here the first certificate is the target certificate, the remaining\n" + "certificates are suggested intermediary certificates. All certifciates\n" + "need to be PEM encoded.\n" + "\n" + "The option --systrust changes the behaviour to include the system\n" + "provided root certificates as trust anchors."; static gpg_error_t cmd_validate (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; ksba_cert_t cert = NULL; + certlist_t certlist = NULL; unsigned char *value = NULL; size_t valuelen; + int systrust_mode, tls_mode; - (void)line; + systrust_mode = has_option (line, "--systrust"); + tls_mode = has_option (line, "--tls"); + line = skip_options (line); - err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", - &value, &valuelen, MAX_CERT_LENGTH); + if (tls_mode) + err = assuan_inquire (ctrl->server_local->assuan_ctx, "CERTLIST", + &value, &valuelen, MAX_CERTLIST_LENGTH); + else + err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", + &value, &valuelen, MAX_CERT_LENGTH); if (err) { log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); @@ -1761,6 +1783,27 @@ cmd_validate (assuan_context_t ctx, char *line) if (!valuelen) /* No data returned; return a comprehensible error. */ err = gpg_error (GPG_ERR_MISSING_CERT); + else if (tls_mode) + { + estream_t fp; + + fp = es_fopenmem_init (0, "rb", value, valuelen); + if (!fp) + err = gpg_error_from_syserror (); + else + { + err = read_certlist_from_stream (&certlist, fp); + es_fclose (fp); + if (!err && !certlist) + err = gpg_error (GPG_ERR_MISSING_CERT); + if (!err) + { + /* Extraxt the first certificate from the list. */ + cert = certlist->cert; + ksba_cert_ref (cert); + } + } + } else { err = ksba_cert_new (&cert); @@ -1771,26 +1814,47 @@ cmd_validate (assuan_context_t ctx, char *line) if(err) goto leave; - /* If we have this certificate already in our cache, use the cached - * version for validation because this will take care of any cached - * results. */ - { - unsigned char fpr[20]; - ksba_cert_t tmpcert; + if (!tls_mode) + { + /* If we have this certificate already in our cache, use the + * cached version for validation because this will take care of + * any cached results. We don't need to do this in tls mode + * because this has already been done for certificate in a + * certlist_t. */ + unsigned char fpr[20]; + ksba_cert_t tmpcert; - cert_compute_fpr (cert, fpr); - tmpcert = get_cert_byfpr (fpr); - if (tmpcert) - { - ksba_cert_release (cert); - cert = tmpcert; - } - } + cert_compute_fpr (cert, fpr); + tmpcert = get_cert_byfpr (fpr); + if (tmpcert) + { + ksba_cert_release (cert); + cert = tmpcert; + } + } - err = validate_cert_chain (ctrl, cert, NULL, VALIDATE_MODE_CERT, NULL); + /* Quick hack to make verification work by inserting the supplied + * certs into the cache. */ + if (tls_mode && certlist) + { + certlist_t cl; + + for (cl = certlist->next; cl; cl = cl->next) + cache_cert (cl->cert); + } + + + err = validate_cert_chain + (ctrl, cert, NULL, + tls_mode && systrust_mode ? VALIDATE_MODE_TLS_SYSTRUST : + tls_mode ? VALIDATE_MODE_TLS : + /**/ systrust_mode ? VALIDATE_MODE_CERT_SYSTRUST : + /**/ VALIDATE_MODE_CERT, + NULL); leave: ksba_cert_release (cert); + release_certlist (certlist); return leave_cmd (ctx, err); } diff --git a/dirmngr/validate.c b/dirmngr/validate.c index 5081ae0f7..8fb2df2c3 100644 --- a/dirmngr/validate.c +++ b/dirmngr/validate.c @@ -233,8 +233,8 @@ check_revocations (ctrl_t ctrl, chain_item_t chain) int any_crl_too_old = 0; chain_item_t ci; - assert (ctrl->check_revocations_nest_level >= 0); - assert (chain); + log_assert (ctrl->check_revocations_nest_level >= 0); + log_assert (chain); if (ctrl->check_revocations_nest_level > 10) { @@ -551,7 +551,9 @@ validate_cert_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, if (err) goto leave; /* No. */ - err = is_trusted_cert (subject_cert, 0); + err = is_trusted_cert (subject_cert, + (mode == VALIDATE_MODE_CERT_SYSTRUST + || mode == VALIDATE_MODE_TLS_SYSTRUST)); if (!err) ; /* Yes we trust this cert. */ else if (gpg_err_code (err) == GPG_ERR_NOT_TRUSTED) @@ -772,7 +774,9 @@ validate_cert_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, * our validity results to avoid double work. Far worse a * catch-22 may happen for an improper setup hierarchy and we * need a way to break up such a deadlock. */ - err = check_revocations (ctrl, chain); + if (mode != VALIDATE_MODE_TLS_SYSTRUST) + err = check_revocations (ctrl, chain); +#warning fix the above } if (!err && opt.verbose)