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 <wk@gnupg.org>
This commit is contained in:
Werner Koch 2017-02-17 16:39:48 +01:00
parent ed99af030d
commit 070211eb99
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
5 changed files with 198 additions and 26 deletions

View File

@ -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;
}
}

View File

@ -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*/

View File

@ -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;

View File

@ -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);
}

View File

@ -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)