summaryrefslogtreecommitdiffstats
path: root/dirmngr/crlcache.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
commiteee068778cb28ecf3c14e1bf843a95547d72c42d (patch)
tree0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /dirmngr/crlcache.c
parentInitial commit. (diff)
downloadgnupg2-upstream.tar.xz
gnupg2-upstream.zip
Adding upstream version 2.2.40.upstream/2.2.40upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dirmngr/crlcache.c')
-rw-r--r--dirmngr/crlcache.c2741
1 files changed, 2741 insertions, 0 deletions
diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c
new file mode 100644
index 0000000..2380369
--- /dev/null
+++ b/dirmngr/crlcache.c
@@ -0,0 +1,2741 @@
+/* crlcache.c - LDAP access
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2003, 2004, 2005, 2008 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*
+
+ 1. To keep track of the CRLs actually cached and to store the meta
+ information of the CRLs a simple record oriented text file is
+ used. Fields in the file are colon (':') separated and values
+ containing colons or linefeeds are percent escaped (e.g. a colon
+ itself is represented as "%3A").
+
+ The first field is a record type identifier, so that the file is
+ useful to keep track of other meta data too.
+
+ The name of the file is "DIR.txt".
+
+
+ 1.1. Comment record
+
+ Field 1: Constant beginning with "#".
+
+ Other fields are not defined and such a record is simply
+ skipped during processing.
+
+ 1.2. Version record
+
+ Field 1: Constant "v"
+ Field 2: Version number of this file. Must be 1.
+
+ This record must be the first non-comment record and
+ there shall only exist one record of this type.
+
+ 1.3. CRL cache record
+
+ Field 1: Constant "c", "u" or "i".
+ A "c" or "u" indicate a valid cache entry, however
+ "u" requires that a user root certificate check needs
+ to be done.
+ An "i" indicates an invalid cache entry which should
+ not be used but still exists so that it can be
+ updated at NEXT_UPDATE.
+ Field 2: Hexadecimal encoded SHA-1 hash of the issuer DN using
+ uppercase letters.
+ Field 3: Issuer DN in RFC-2253 notation.
+ Field 4: URL used to retrieve the corresponding CRL.
+ Field 5: 15 character ISO timestamp with THIS_UPDATE.
+ Field 6: 15 character ISO timestamp with NEXT_UPDATE.
+ Field 7: Hexadecimal encoded MD-5 hash of the DB file to detect
+ accidental modified (i.e. deleted and created) cache files.
+ Field 8: optional CRL number as a hex string.
+ Field 9: AuthorityKeyID.issuer, each Name separated by 0x01
+ Field 10: AuthorityKeyID.serial
+ Field 11: Hex fingerprint of trust anchor if field 1 is 'u'.
+
+ 2. Layout of the standard CRL Cache DB file:
+
+ We use records of variable length with this structure
+
+ n bytes Serialnumber (binary) used as key
+ thus there is no need to store the length explicitly with DB2.
+ 1 byte Reason for revocation
+ (currently the KSBA reason flags are used)
+ 15 bytes ISO date of revocation (e.g. 19980815T142000)
+ Note that there is no terminating 0 stored.
+
+ The filename used is the hexadecimal (using uppercase letters)
+ SHA-1 hash value of the issuer DN prefixed with a "crl-" and
+ suffixed with a ".db". Thus the length of the filename is 47.
+
+
+*/
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifndef HAVE_W32_SYSTEM
+#include <sys/utsname.h>
+#endif
+
+#include "dirmngr.h"
+#include "validate.h"
+#include "certcache.h"
+#include "crlcache.h"
+#include "crlfetch.h"
+#include "misc.h"
+#include "cdb.h"
+
+/* Change this whenever the format changes */
+#define DBDIR_D "crls.d"
+#define DBDIRFILE "DIR.txt"
+#define DBDIRVERSION 1
+
+/* The number of DB files we may have open at one time. We need to
+ limit this because there is no guarantee that the number of issuers
+ has a upper limit. We are currently using mmap, so it is a good
+ idea anyway to limit the number of opened cache files. */
+#define MAX_OPEN_DB_FILES 5
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+static const char oidstr_crlNumber[] = "2.5.29.20";
+/* static const char oidstr_issuingDistributionPoint[] = "2.5.29.28"; */
+static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35";
+
+
+/* Definition of one cached item. */
+struct crl_cache_entry_s
+{
+ struct crl_cache_entry_s *next;
+ int deleted; /* True if marked for deletion. */
+ int mark; /* Internally used by update_dir. */
+ unsigned int lineno;/* A 0 indicates a new entry. */
+ char *release_ptr; /* The actual allocated memory. */
+ char *url; /* Points into RELEASE_PTR. */
+ char *issuer; /* Ditto. */
+ char *issuer_hash; /* Ditto. */
+ char *dbfile_hash; /* MD5 sum of the cache file, points into RELEASE_PTR.*/
+ int invalid; /* Can't use this CRL. */
+ int user_trust_req; /* User supplied root certificate required. */
+ char *check_trust_anchor; /* Malloced fingerprint. */
+ ksba_isotime_t this_update;
+ ksba_isotime_t next_update;
+ ksba_isotime_t last_refresh; /* Use for the force_crl_refresh feature. */
+ char *crl_number;
+ char *authority_issuer;
+ char *authority_serialno;
+
+ struct cdb *cdb; /* The cache file handle or NULL if not open. */
+
+ unsigned int cdb_use_count; /* Current use count. */
+ unsigned int cdb_lru_count; /* Used for LRU purposes. */
+ int dbfile_checked; /* Set to true if the dbfile_hash value has
+ been checked one. */
+};
+
+
+/* Definition of the entire cache object. */
+struct crl_cache_s
+{
+ crl_cache_entry_t entries;
+};
+
+typedef struct crl_cache_s *crl_cache_t;
+
+
+/* Prototypes. */
+static crl_cache_entry_t find_entry (crl_cache_entry_t first,
+ const char *issuer_hash);
+
+
+
+/* The currently loaded cache object. This is usually initialized
+ right at startup. */
+static crl_cache_t current_cache;
+
+
+
+
+
+/* Return the current cache object or bail out if it is has not yet
+ been initialized. */
+static crl_cache_t
+get_current_cache (void)
+{
+ if (!current_cache)
+ log_fatal ("CRL cache has not yet been initialized\n");
+ return current_cache;
+}
+
+
+/*
+ Create ae directory if it does not yet exists. Returns on
+ success, or -1 on error.
+ */
+static int
+create_directory_if_needed (const char *name)
+{
+ gnupg_dir_t dir;
+ char *fname;
+
+ fname = make_filename (opt.homedir_cache, name, NULL);
+ dir = gnupg_opendir (fname);
+ if (!dir)
+ {
+ log_info (_("creating directory '%s'\n"), fname);
+ if (gnupg_mkdir (fname, "-rwx"))
+ {
+ int save_errno = errno;
+ log_error (_("error creating directory '%s': %s\n"),
+ fname, strerror (errno));
+ xfree (fname);
+ gpg_err_set_errno (save_errno);
+ return -1;
+ }
+ }
+ else
+ gnupg_closedir (dir);
+ xfree (fname);
+ return 0;
+}
+
+/* Remove all files from the cache directory. If FORCE is not true,
+ some sanity checks on the filenames are done. Return 0 if
+ everything went fine. */
+static int
+cleanup_cache_dir (int force)
+{
+ char *dname = make_filename (opt.homedir_cache, DBDIR_D, NULL);
+ gnupg_dir_t dir;
+ gnupg_dirent_t de;
+ int problem = 0;
+
+ if (!force)
+ { /* Very minor sanity checks. */
+ if (!strcmp (dname, "~/") || !strcmp (dname, "/" ))
+ {
+ log_error (_("ignoring database dir '%s'\n"), dname);
+ xfree (dname);
+ return -1;
+ }
+ }
+
+ dir = gnupg_opendir (dname);
+ if (!dir)
+ {
+ log_error (_("error reading directory '%s': %s\n"),
+ dname, strerror (errno));
+ xfree (dname);
+ return -1;
+ }
+
+ while ((de = gnupg_readdir (dir)))
+ {
+ if (strcmp (de->d_name, "." ) && strcmp (de->d_name, ".."))
+ {
+ char *cdbname = make_filename (dname, de->d_name, NULL);
+ int okay;
+ struct stat sbuf;
+
+ if (force)
+ okay = 1;
+ else
+ okay = (!gnupg_stat (cdbname, &sbuf) && S_ISREG (sbuf.st_mode));
+
+ if (okay)
+ {
+ log_info (_("removing cache file '%s'\n"), cdbname);
+ if (gnupg_remove (cdbname))
+ {
+ log_error ("failed to remove '%s': %s\n",
+ cdbname, strerror (errno));
+ problem = -1;
+ }
+ }
+ else
+ log_info (_("not removing file '%s'\n"), cdbname);
+ xfree (cdbname);
+ }
+ }
+ xfree (dname);
+ gnupg_closedir (dir);
+ return problem;
+}
+
+
+/* Read the next line from the file FP and return the line in an
+ malloced buffer. Return NULL on error or EOF. There is no
+ limitation os the line length. The trailing linefeed has been
+ removed, the function will read the last line of a file, even if
+ that is not terminated by a LF. */
+static char *
+next_line_from_file (estream_t fp, gpg_error_t *r_err)
+{
+ char buf[300];
+ char *largebuf = NULL;
+ size_t buflen;
+ size_t len = 0;
+ unsigned char *p;
+ int c;
+ char *tmpbuf;
+
+ *r_err = 0;
+ p = buf;
+ buflen = sizeof buf - 1;
+ while ((c=es_getc (fp)) != EOF && c != '\n')
+ {
+ if (len >= buflen)
+ {
+ if (!largebuf)
+ {
+ buflen += 1024;
+ largebuf = xtrymalloc ( buflen + 1 );
+ if (!largebuf)
+ {
+ *r_err = gpg_error_from_syserror ();
+ return NULL;
+ }
+ memcpy (largebuf, buf, len);
+ }
+ else
+ {
+ buflen += 1024;
+ tmpbuf = xtryrealloc (largebuf, buflen + 1);
+ if (!tmpbuf)
+ {
+ *r_err = gpg_error_from_syserror ();
+ xfree (largebuf);
+ return NULL;
+ }
+ largebuf = tmpbuf;
+ }
+ p = largebuf;
+ }
+ p[len++] = c;
+ }
+ if (c == EOF && !len)
+ return NULL;
+ p[len] = 0;
+
+ if (largebuf)
+ tmpbuf = xtryrealloc (largebuf, len+1);
+ else
+ tmpbuf = xtrystrdup (buf);
+ if (!tmpbuf)
+ {
+ *r_err = gpg_error_from_syserror ();
+ xfree (largebuf);
+ }
+ return tmpbuf;
+}
+
+
+/* Release one cache entry. */
+static void
+release_one_cache_entry (crl_cache_entry_t entry)
+{
+ if (entry)
+ {
+ if (entry->cdb)
+ {
+ int fd = cdb_fileno (entry->cdb);
+ cdb_free (entry->cdb);
+ xfree (entry->cdb);
+ if (close (fd))
+ log_error (_("error closing cache file: %s\n"), strerror(errno));
+ }
+ xfree (entry->release_ptr);
+ xfree (entry->check_trust_anchor);
+ xfree (entry);
+ }
+}
+
+
+/* Release the CACHE object. */
+static void
+release_cache (crl_cache_t cache)
+{
+ crl_cache_entry_t entry, entry2;
+
+ if (!cache)
+ return;
+
+ for (entry = cache->entries; entry; entry = entry2)
+ {
+ entry2 = entry->next;
+ release_one_cache_entry (entry);
+ }
+ cache->entries = NULL;
+ xfree (cache);
+}
+
+
+/* Open the dir file FNAME or create a new one if it does not yet
+ exist. */
+static estream_t
+open_dir_file (const char *fname)
+{
+ estream_t fp;
+
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ log_error (_("failed to open cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+
+ /* Make sure that the directory exists, try to create if otherwise. */
+ if (create_directory_if_needed (NULL)
+ || create_directory_if_needed (DBDIR_D))
+ return NULL;
+ fp = es_fopen (fname, "w");
+ if (!fp)
+ {
+ log_error (_("error creating new cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ return NULL;
+ }
+ es_fprintf (fp, "v:%d:\n", DBDIRVERSION);
+ if (es_ferror (fp))
+ {
+ log_error (_("error writing new cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ es_fclose (fp);
+ return NULL;
+ }
+ if (es_fclose (fp))
+ {
+ log_error (_("error closing new cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ return NULL;
+ }
+
+ log_info (_("new cache dir file '%s' created\n"), fname);
+
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ log_error (_("failed to re-open cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ return NULL;
+ }
+ }
+
+ return fp;
+}
+
+/* Helper for open_dir. */
+static gpg_error_t
+check_dir_version (estream_t *fpadr, const char *fname,
+ unsigned int *lineno,
+ int cleanup_on_mismatch)
+{
+ char *line;
+ gpg_error_t lineerr = 0;
+ estream_t fp = *fpadr;
+ int created = 0;
+
+ retry:
+ while ((line = next_line_from_file (fp, &lineerr)))
+ {
+ ++*lineno;
+ if (*line == 'v' && line[1] == ':')
+ break;
+ else if (*line != '#')
+ {
+ log_error (_("first record of '%s' is not the version\n"), fname);
+ xfree (line);
+ return gpg_error (GPG_ERR_CONFIGURATION);
+ }
+ xfree (line);
+ }
+ if (lineerr)
+ return lineerr;
+
+ /* The !line catches the case of an empty DIR file. We handle this
+ the same as a non-matching version. */
+ if (!line || strtol (line+2, NULL, 10) != DBDIRVERSION)
+ {
+ if (!created && cleanup_on_mismatch)
+ {
+ log_error (_("old version of cache directory - cleaning up\n"));
+ es_fclose (fp);
+ *fpadr = NULL;
+ if (!cleanup_cache_dir (1))
+ {
+ *lineno = 0;
+ fp = *fpadr = open_dir_file (fname);
+ if (!fp)
+ {
+ xfree (line);
+ return gpg_error (GPG_ERR_CONFIGURATION);
+ }
+ created = 1;
+ goto retry;
+ }
+ }
+ log_error (_("old version of cache directory - giving up\n"));
+ xfree (line);
+ return gpg_error (GPG_ERR_CONFIGURATION);
+ }
+ xfree (line);
+ return 0;
+}
+
+
+/* Open the dir file and read in all available information. Store
+ that in a newly allocated cache object and return that if
+ everything worked out fine. Create the cache directory and the dir
+ if it does not yet exist. Remove all files in that directory if
+ the version does not match. */
+static gpg_error_t
+open_dir (crl_cache_t *r_cache)
+{
+ crl_cache_t cache;
+ char *fname;
+ char *line = NULL;
+ gpg_error_t lineerr = 0;
+ estream_t fp;
+ crl_cache_entry_t entry, *entrytail;
+ unsigned int lineno;
+ gpg_error_t err = 0;
+ int anyerr = 0;
+
+ cache = xtrycalloc (1, sizeof *cache);
+ if (!cache)
+ return gpg_error_from_syserror ();
+
+ fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL);
+
+ lineno = 0;
+ fp = open_dir_file (fname);
+ if (!fp)
+ {
+ err = gpg_error (GPG_ERR_CONFIGURATION);
+ goto leave;
+ }
+
+ err = check_dir_version (&fp, fname, &lineno, 1);
+ if (err)
+ goto leave;
+
+
+ /* Read in all supported entries from the dir file. */
+ cache->entries = NULL;
+ entrytail = &cache->entries;
+ xfree (line);
+ while ((line = next_line_from_file (fp, &lineerr)))
+ {
+ int fieldno;
+ char *p, *endp;
+
+ lineno++;
+ if ( *line == 'c' || *line == 'u' || *line == 'i' )
+ {
+ entry = xtrycalloc (1, sizeof *entry);
+ if (!entry)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ entry->lineno = lineno;
+ entry->release_ptr = line;
+ if (*line == 'i')
+ {
+ entry->invalid = atoi (line+1);
+ if (entry->invalid < 1)
+ entry->invalid = 1;
+ }
+ else if (*line == 'u')
+ entry->user_trust_req = 1;
+
+ for (fieldno=1, p = line; p; p = endp, fieldno++)
+ {
+ endp = strchr (p, ':');
+ if (endp)
+ *endp++ = '\0';
+
+ switch (fieldno)
+ {
+ case 1: /* record type */ break;
+ case 2: entry->issuer_hash = p; break;
+ case 3: entry->issuer = unpercent_string (p); break;
+ case 4: entry->url = unpercent_string (p); break;
+ case 5:
+ strncpy (entry->this_update, p, 15);
+ entry->this_update[15] = 0;
+ break;
+ case 6:
+ strncpy (entry->next_update, p, 15);
+ entry->next_update[15] = 0;
+ break;
+ case 7: entry->dbfile_hash = p; break;
+ case 8: if (*p) entry->crl_number = p; break;
+ case 9:
+ if (*p)
+ entry->authority_issuer = unpercent_string (p);
+ break;
+ case 10:
+ if (*p)
+ entry->authority_serialno = unpercent_string (p);
+ break;
+ case 11:
+ if (*p)
+ entry->check_trust_anchor = xtrystrdup (p);
+ break;
+ default:
+ if (*p)
+ log_info (_("extra field detected in crl record of "
+ "'%s' line %u\n"), fname, lineno);
+ break;
+ }
+ }
+
+ if (!entry->issuer_hash)
+ {
+ log_info (_("invalid line detected in '%s' line %u\n"),
+ fname, lineno);
+ xfree (entry);
+ entry = NULL;
+ }
+ else if (find_entry (cache->entries, entry->issuer_hash))
+ {
+ /* Fixme: The duplicate checking used is not very
+ effective for large numbers of issuers. */
+ log_info (_("duplicate entry detected in '%s' line %u\n"),
+ fname, lineno);
+ xfree (entry);
+ entry = NULL;
+ }
+ else
+ {
+ line = NULL;
+ *entrytail = entry;
+ entrytail = &entry->next;
+ }
+ }
+ else if (*line == '#')
+ ;
+ else
+ log_info (_("unsupported record type in '%s' line %u skipped\n"),
+ fname, lineno);
+
+ if (line)
+ xfree (line);
+ }
+ if (lineerr)
+ {
+ err = lineerr;
+ log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
+ goto leave;
+ }
+ if (es_ferror (fp))
+ {
+ log_error (_("error reading '%s': %s\n"), fname, strerror (errno));
+ err = gpg_error (GPG_ERR_CONFIGURATION);
+ goto leave;
+ }
+
+ /* Now do some basic checks on the data. */
+ for (entry = cache->entries; entry; entry = entry->next)
+ {
+ assert (entry->lineno);
+ if (strlen (entry->issuer_hash) != 40)
+ {
+ anyerr++;
+ log_error (_("invalid issuer hash in '%s' line %u\n"),
+ fname, entry->lineno);
+ }
+ else if ( !*entry->issuer )
+ {
+ anyerr++;
+ log_error (_("no issuer DN in '%s' line %u\n"),
+ fname, entry->lineno);
+ }
+ else if ( check_isotime (entry->this_update)
+ || check_isotime (entry->next_update))
+ {
+ anyerr++;
+ log_error (_("invalid timestamp in '%s' line %u\n"),
+ fname, entry->lineno);
+ }
+
+ /* Checks not leading to an immediate fail. */
+ if (strlen (entry->dbfile_hash) != 32)
+ log_info (_("WARNING: invalid cache file hash in '%s' line %u\n"),
+ fname, entry->lineno);
+ }
+
+ if (anyerr)
+ {
+ log_error (_("detected errors in cache dir file\n"));
+ log_info (_("please check the reason and manually delete that file\n"));
+ err = gpg_error (GPG_ERR_CONFIGURATION);
+ }
+
+
+ leave:
+ es_fclose (fp);
+ xfree (line);
+ xfree (fname);
+ if (err)
+ {
+ release_cache (cache);
+ cache = NULL;
+ }
+ *r_cache = cache;
+ return err;
+}
+
+static void
+write_percented_string (const char *s, estream_t fp)
+{
+ for (; *s; s++)
+ if (*s == ':')
+ es_fputs ("%3A", fp);
+ else if (*s == '\n')
+ es_fputs ("%0A", fp);
+ else if (*s == '\r')
+ es_fputs ("%0D", fp);
+ else
+ es_putc (*s, fp);
+}
+
+
+static void
+write_dir_line_crl (estream_t fp, crl_cache_entry_t e)
+{
+ if (e->invalid)
+ es_fprintf (fp, "i%d", e->invalid);
+ else if (e->user_trust_req)
+ es_putc ('u', fp);
+ else
+ es_putc ('c', fp);
+ es_putc (':', fp);
+ es_fputs (e->issuer_hash, fp);
+ es_putc (':', fp);
+ write_percented_string (e->issuer, fp);
+ es_putc (':', fp);
+ write_percented_string (e->url, fp);
+ es_putc (':', fp);
+ es_fwrite (e->this_update, 15, 1, fp);
+ es_putc (':', fp);
+ es_fwrite (e->next_update, 15, 1, fp);
+ es_putc (':', fp);
+ es_fputs (e->dbfile_hash, fp);
+ es_putc (':', fp);
+ if (e->crl_number)
+ es_fputs (e->crl_number, fp);
+ es_putc (':', fp);
+ if (e->authority_issuer)
+ write_percented_string (e->authority_issuer, fp);
+ es_putc (':', fp);
+ if (e->authority_serialno)
+ es_fputs (e->authority_serialno, fp);
+ es_putc (':', fp);
+ if (e->check_trust_anchor && e->user_trust_req)
+ es_fputs (e->check_trust_anchor, fp);
+ es_putc ('\n', fp);
+}
+
+
+/* Update the current dir file using the cache. */
+static gpg_error_t
+update_dir (crl_cache_t cache)
+{
+ char *fname = NULL;
+ char *tmpfname = NULL;
+ char *line = NULL;
+ gpg_error_t lineerr = 0;
+ estream_t fp;
+ estream_t fpout = NULL;
+ crl_cache_entry_t e;
+ unsigned int lineno;
+ gpg_error_t err = 0;
+
+ fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL);
+
+ /* Fixme: Take an update file lock here. */
+
+ for (e= cache->entries; e; e = e->next)
+ e->mark = 1;
+
+ lineno = 0;
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("failed to open cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ goto leave;
+ }
+ err = check_dir_version (&fp, fname, &lineno, 0);
+ if (err)
+ goto leave;
+ es_rewind (fp);
+ lineno = 0;
+
+ /* Create a temporary DIR file. */
+ {
+ char *tmpbuf, *p;
+ const char *nodename;
+#ifndef HAVE_W32_SYSTEM
+ struct utsname utsbuf;
+#endif
+
+#ifdef HAVE_W32_SYSTEM
+ nodename = "unknown";
+#else
+ if (uname (&utsbuf))
+ nodename = "unknown";
+ else
+ nodename = utsbuf.nodename;
+#endif
+
+ gpgrt_asprintf (&tmpbuf, "DIR-tmp-%s-%u-%p.txt.tmp",
+ nodename, (unsigned int)getpid (), &tmpbuf);
+ if (!tmpbuf)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("failed to create temporary cache dir file '%s': %s\n"),
+ tmpfname, strerror (errno));
+ goto leave;
+ }
+ for (p=tmpbuf; *p; p++)
+ if (*p == '/')
+ *p = '.';
+ tmpfname = make_filename (opt.homedir_cache, DBDIR_D, tmpbuf, NULL);
+ xfree (tmpbuf);
+ }
+ fpout = es_fopen (tmpfname, "w");
+ if (!fpout)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("failed to create temporary cache dir file '%s': %s\n"),
+ tmpfname, strerror (errno));
+ goto leave;
+ }
+
+ while ((line = next_line_from_file (fp, &lineerr)))
+ {
+ lineno++;
+ if (*line == 'c' || *line == 'u' || *line == 'i')
+ {
+ /* Extract the issuer hash field. */
+ char *fieldp, *endp;
+
+ fieldp = strchr (line, ':');
+ endp = fieldp? strchr (++fieldp, ':') : NULL;
+ if (endp)
+ {
+ /* There should be no percent within the issuer hash
+ field, thus we can compare it pretty easily. */
+ *endp = 0;
+ e = find_entry ( cache->entries, fieldp);
+ *endp = ':'; /* Restore original line. */
+ if (e && e->deleted)
+ {
+ /* Marked for deletion, so don't write it. */
+ e->mark = 0;
+ }
+ else if (e)
+ {
+ /* Yep, this is valid entry we know about; write it out */
+ write_dir_line_crl (fpout, e);
+ e->mark = 0;
+ }
+ else
+ { /* We ignore entries we don't have in our cache
+ because they may have been added in the meantime
+ by other instances of dirmngr. */
+ es_fprintf (fpout, "# Next line added by "
+ "another process; our pid is %lu\n",
+ (unsigned long)getpid ());
+ es_fputs (line, fpout);
+ es_putc ('\n', fpout);
+ }
+ }
+ else
+ {
+ es_fputs ("# Invalid line detected: ", fpout);
+ es_fputs (line, fpout);
+ es_putc ('\n', fpout);
+ }
+ }
+ else
+ {
+ /* Write out all non CRL lines as they are. */
+ es_fputs (line, fpout);
+ es_putc ('\n', fpout);
+ }
+
+ xfree (line);
+ }
+ if (!es_ferror (fp) && !es_ferror (fpout) && !lineerr)
+ {
+ /* Write out the remaining entries. */
+ for (e= cache->entries; e; e = e->next)
+ if (e->mark)
+ {
+ if (!e->deleted)
+ write_dir_line_crl (fpout, e);
+ e->mark = 0;
+ }
+ }
+ if (lineerr)
+ {
+ err = lineerr;
+ log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
+ goto leave;
+ }
+ if (es_ferror (fp))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error reading '%s': %s\n"), fname, strerror (errno));
+ }
+ if (es_ferror (fpout))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error writing '%s': %s\n"), tmpfname, strerror (errno));
+ }
+ if (err)
+ goto leave;
+
+ /* Rename the files. */
+ es_fclose (fp);
+ fp = NULL;
+ if (es_fclose (fpout))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error closing '%s': %s\n"), tmpfname, strerror (errno));
+ goto leave;
+ }
+ fpout = NULL;
+
+#ifdef HAVE_W32_SYSTEM
+ /* No atomic mv on W32 systems. */
+ gnupg_remove (fname);
+#endif
+ if (rename (tmpfname, fname))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error renaming '%s' to '%s': %s\n"),
+ tmpfname, fname, strerror (errno));
+ goto leave;
+ }
+
+ leave:
+ /* Fixme: Relinquish update lock. */
+ xfree (line);
+ es_fclose (fp);
+ xfree (fname);
+ if (fpout)
+ {
+ es_fclose (fpout);
+ if (err && tmpfname)
+ gnupg_remove (tmpfname);
+ }
+ xfree (tmpfname);
+ return err;
+}
+
+
+
+
+/* Create the filename for the cache file from the 40 byte ISSUER_HASH
+ string. Caller must release the return string. */
+static char *
+make_db_file_name (const char *issuer_hash)
+{
+ char bname[50];
+
+ assert (strlen (issuer_hash) == 40);
+ memcpy (bname, "crl-", 4);
+ memcpy (bname + 4, issuer_hash, 40);
+ strcpy (bname + 44, ".db");
+ return make_filename (opt.homedir_cache, DBDIR_D, bname, NULL);
+}
+
+
+/* Hash the file FNAME and return the MD5 digest in MD5BUFFER. The
+ caller must allocate MD%buffer wityh at least 16 bytes. Returns 0
+ on success. */
+static int
+hash_dbfile (const char *fname, unsigned char *md5buffer)
+{
+ estream_t fp;
+ char *buffer;
+ size_t n;
+ gcry_md_hd_t md5;
+ gpg_error_t err;
+
+ buffer = xtrymalloc (65536);
+ fp = buffer? es_fopen (fname, "rb") : NULL;
+ if (!fp)
+ {
+ log_error (_("can't hash '%s': %s\n"), fname, strerror (errno));
+ xfree (buffer);
+ return -1;
+ }
+
+ err = gcry_md_open (&md5, GCRY_MD_MD5, 0);
+ if (err)
+ {
+ log_error (_("error setting up MD5 hash context: %s\n"),
+ gpg_strerror (err));
+ xfree (buffer);
+ es_fclose (fp);
+ return -1;
+ }
+
+ /* We better hash some information about the cache file layout in. */
+ sprintf (buffer, "%.100s/%.100s:%d", DBDIR_D, DBDIRFILE, DBDIRVERSION);
+ gcry_md_write (md5, buffer, strlen (buffer));
+
+ for (;;)
+ {
+ n = es_fread (buffer, 1, 65536, fp);
+ if (n < 65536 && es_ferror (fp))
+ {
+ log_error (_("error hashing '%s': %s\n"), fname, strerror (errno));
+ xfree (buffer);
+ es_fclose (fp);
+ gcry_md_close (md5);
+ return -1;
+ }
+ if (!n)
+ break;
+ gcry_md_write (md5, buffer, n);
+ }
+ es_fclose (fp);
+ xfree (buffer);
+ gcry_md_final (md5);
+
+ memcpy (md5buffer, gcry_md_read (md5, GCRY_MD_MD5), 16);
+ gcry_md_close (md5);
+ return 0;
+}
+
+/* Compare the file FNAME against the dexified MD5 hash MD5HASH and
+ return 0 if they match. */
+static int
+check_dbfile (const char *fname, const char *md5hexvalue)
+{
+ unsigned char buffer1[16], buffer2[16];
+
+ if (strlen (md5hexvalue) != 32)
+ {
+ log_error (_("invalid formatted checksum for '%s'\n"), fname);
+ return -1;
+ }
+ unhexify (buffer1, md5hexvalue);
+
+ if (hash_dbfile (fname, buffer2))
+ return -1;
+
+ return memcmp (buffer1, buffer2, 16);
+}
+
+
+/* Open the cache file for ENTRY. This function implements a caching
+ strategy and might close unused cache files. It is required to use
+ unlock_db_file after using the file. */
+static struct cdb *
+lock_db_file (crl_cache_t cache, crl_cache_entry_t entry)
+{
+ char *fname;
+ int fd;
+ int open_count;
+ crl_cache_entry_t e;
+
+ if (entry->cdb)
+ {
+ entry->cdb_use_count++;
+ return entry->cdb;
+ }
+
+ for (open_count = 0, e = cache->entries; e; e = e->next)
+ {
+ if (e->cdb)
+ open_count++;
+/* log_debug ("CACHE: cdb=%p use_count=%u lru_count=%u\n", */
+/* e->cdb,e->cdb_use_count,e->cdb_lru_count); */
+ }
+
+ /* If there are too many file open, find the least recent used DB
+ file and close it. Note that for Pth thread safeness we need to
+ use a loop here. */
+ while (open_count >= MAX_OPEN_DB_FILES )
+ {
+ crl_cache_entry_t last_e = NULL;
+ unsigned int last_lru = (unsigned int)(-1);
+
+ for (e = cache->entries; e; e = e->next)
+ if (e->cdb && !e->cdb_use_count && e->cdb_lru_count < last_lru)
+ {
+ last_lru = e->cdb_lru_count;
+ last_e = e;
+ }
+ if (!last_e)
+ {
+ log_error (_("too many open cache files; can't open anymore\n"));
+ return NULL;
+ }
+
+/* log_debug ("CACHE: closing file at cdb=%p\n", last_e->cdb); */
+
+ fd = cdb_fileno (last_e->cdb);
+ cdb_free (last_e->cdb);
+ xfree (last_e->cdb);
+ last_e->cdb = NULL;
+ if (close (fd))
+ log_error (_("error closing cache file: %s\n"), strerror(errno));
+ open_count--;
+ }
+
+
+ fname = make_db_file_name (entry->issuer_hash);
+ if (opt.verbose)
+ log_info (_("opening cache file '%s'\n"), fname );
+
+ if (!entry->dbfile_checked)
+ {
+ if (!check_dbfile (fname, entry->dbfile_hash))
+ entry->dbfile_checked = 1;
+ /* Note, in case of an error we don't print an error here but
+ let require the caller to do that check. */
+ }
+
+ entry->cdb = xtrycalloc (1, sizeof *entry->cdb);
+ if (!entry->cdb)
+ {
+ xfree (fname);
+ return NULL;
+ }
+ fd = gnupg_open (fname, O_RDONLY | O_BINARY, 0);
+ if (fd == -1)
+ {
+ log_error (_("error opening cache file '%s': %s\n"),
+ fname, strerror (errno));
+ xfree (entry->cdb);
+ entry->cdb = NULL;
+ xfree (fname);
+ return NULL;
+ }
+ if (cdb_init (entry->cdb, fd))
+ {
+ log_error (_("error initializing cache file '%s' for reading: %s\n"),
+ fname, strerror (errno));
+ xfree (entry->cdb);
+ entry->cdb = NULL;
+ close (fd);
+ xfree (fname);
+ return NULL;
+ }
+ xfree (fname);
+
+ entry->cdb_use_count = 1;
+ entry->cdb_lru_count = 0;
+
+ return entry->cdb;
+}
+
+/* Unlock a cache file, so that it can be reused. */
+static void
+unlock_db_file (crl_cache_t cache, crl_cache_entry_t entry)
+{
+ if (!entry->cdb)
+ log_error (_("calling unlock_db_file on a closed file\n"));
+ else if (!entry->cdb_use_count)
+ log_error (_("calling unlock_db_file on an unlocked file\n"));
+ else
+ {
+ entry->cdb_use_count--;
+ entry->cdb_lru_count++;
+ }
+
+ /* If the entry was marked for deletion in the meantime do it now.
+ We do this for the sake of Pth thread safeness. */
+ if (!entry->cdb_use_count && entry->deleted)
+ {
+ crl_cache_entry_t eprev, enext;
+
+ enext = entry->next;
+ for (eprev = cache->entries;
+ eprev && eprev->next != entry; eprev = eprev->next)
+ ;
+ assert (eprev);
+ if (eprev == cache->entries)
+ cache->entries = enext;
+ else
+ eprev->next = enext;
+ /* FIXME: Do we leak ENTRY? */
+ }
+}
+
+
+/* Find ISSUER_HASH in our cache FIRST. This may be used to enumerate
+ the linked list we use to keep the CRLs of an issuer. */
+static crl_cache_entry_t
+find_entry (crl_cache_entry_t first, const char *issuer_hash)
+{
+ while (first && (first->deleted || strcmp (issuer_hash, first->issuer_hash)))
+ first = first->next;
+ return first;
+}
+
+
+/* Create a new CRL cache. This function is usually called only once.
+ never fail. */
+void
+crl_cache_init(void)
+{
+ crl_cache_t cache = NULL;
+ gpg_error_t err;
+
+ if (current_cache)
+ {
+ log_error ("crl cache has already been initialized - not doing twice\n");
+ return;
+ }
+
+ err = open_dir (&cache);
+ if (err)
+ log_fatal (_("failed to create a new cache object: %s\n"),
+ gpg_strerror (err));
+ current_cache = cache;
+}
+
+
+/* Remove the cache information and all its resources. Note that we
+ still keep the cache on disk. */
+void
+crl_cache_deinit (void)
+{
+ if (current_cache)
+ {
+ release_cache (current_cache);
+ current_cache = NULL;
+ }
+}
+
+
+/* Delete the cache from disk and memory. Return 0 on success.*/
+int
+crl_cache_flush (void)
+{
+ int rc;
+
+ crl_cache_deinit ();
+ rc = cleanup_cache_dir (0)? -1 : 0;
+ crl_cache_init ();
+
+ return rc;
+}
+
+
+/* Check whether the certificate identified by ISSUER_HASH and
+ SN/SNLEN is valid; i.e. not listed in our cache. With
+ FORCE_REFRESH set to true, a new CRL will be retrieved even if the
+ cache has not yet expired. We use a 30 minutes threshold here so
+ that invoking this function several times won't load the CRL over
+ and over. */
+static crl_cache_result_t
+cache_isvalid (ctrl_t ctrl, const char *issuer_hash,
+ const unsigned char *sn, size_t snlen,
+ int force_refresh)
+{
+ crl_cache_t cache = get_current_cache ();
+ crl_cache_result_t retval;
+ struct cdb *cdb;
+ int rc;
+ crl_cache_entry_t entry;
+ gnupg_isotime_t current_time;
+ size_t n;
+
+ (void)ctrl;
+
+ entry = find_entry (cache->entries, issuer_hash);
+ if (!entry)
+ {
+ log_info (_("no CRL available for issuer id %s\n"), issuer_hash );
+ return CRL_CACHE_DONTKNOW;
+ }
+
+ gnupg_get_isotime (current_time);
+ if (strcmp (entry->next_update, current_time) < 0 )
+ {
+ log_info (_("cached CRL for issuer id %s too old; update required\n"),
+ issuer_hash);
+ return CRL_CACHE_DONTKNOW;
+ }
+ if (force_refresh)
+ {
+ gnupg_isotime_t tmptime;
+
+ if (*entry->last_refresh)
+ {
+ gnupg_copy_time (tmptime, entry->last_refresh);
+ add_seconds_to_isotime (tmptime, 30 * 60);
+ if (strcmp (tmptime, current_time) < 0 )
+ {
+ log_info (_("force-crl-refresh active and %d minutes passed for"
+ " issuer id %s; update required\n"),
+ 30, issuer_hash);
+ return CRL_CACHE_DONTKNOW;
+ }
+ }
+ else
+ {
+ log_info (_("force-crl-refresh active for"
+ " issuer id %s; update required\n"),
+ issuer_hash);
+ return CRL_CACHE_DONTKNOW;
+ }
+ }
+
+ if (entry->invalid)
+ {
+ log_info (_("available CRL for issuer ID %s can't be used\n"),
+ issuer_hash);
+ return CRL_CACHE_CANTUSE;
+ }
+
+ cdb = lock_db_file (cache, entry);
+ if (!cdb)
+ return CRL_CACHE_DONTKNOW; /* Hmmm, not the best error code. */
+
+ if (!entry->dbfile_checked)
+ {
+ log_error (_("cached CRL for issuer id %s tampered; we need to update\n")
+ , issuer_hash);
+ unlock_db_file (cache, entry);
+ return CRL_CACHE_DONTKNOW;
+ }
+
+ rc = cdb_find (cdb, sn, snlen);
+ if (rc == 1)
+ {
+ n = cdb_datalen (cdb);
+ if (n != 16)
+ {
+ log_error (_("WARNING: invalid cache record length for S/N "));
+ log_printf ("0x");
+ log_printhex (sn, snlen, "");
+ }
+ else if (opt.verbose)
+ {
+ unsigned char record[16];
+ char *tmp = hexify_data (sn, snlen, 1);
+
+ if (cdb_read (cdb, record, n, cdb_datapos (cdb)))
+ log_error (_("problem reading cache record for S/N %s: %s\n"),
+ tmp, strerror (errno));
+ else
+ log_info (_("S/N %s is not valid; reason=%02X date=%.15s\n"),
+ tmp, *record, record+1);
+ xfree (tmp);
+ }
+ retval = CRL_CACHE_INVALID;
+ }
+ else if (!rc)
+ {
+ if (opt.verbose)
+ {
+ char *serialno = hexify_data (sn, snlen, 1);
+ log_info (_("S/N %s is valid, it is not listed in the CRL\n"),
+ serialno );
+ xfree (serialno);
+ }
+ retval = CRL_CACHE_VALID;
+ }
+ else
+ {
+ log_error (_("error getting data from cache file: %s\n"),
+ strerror (errno));
+ retval = CRL_CACHE_DONTKNOW;
+ }
+
+
+ if (entry->user_trust_req
+ && (retval == CRL_CACHE_VALID || retval == CRL_CACHE_INVALID))
+ {
+ if (!entry->check_trust_anchor)
+ {
+ log_error ("inconsistent data on user trust check\n");
+ retval = CRL_CACHE_CANTUSE;
+ }
+ else if (get_istrusted_from_client (ctrl, entry->check_trust_anchor))
+ {
+ if (opt.verbose)
+ log_info ("no system trust and client does not trust either\n");
+ retval = CRL_CACHE_CANTUSE;
+ }
+ else
+ {
+ /* Okay, the CRL is considered valid by the client and thus
+ we can return the result as is. */
+ }
+ }
+
+ unlock_db_file (cache, entry);
+
+ return retval;
+}
+
+
+/* Check whether the certificate identified by ISSUER_HASH and
+ SERIALNO is valid; i.e. not listed in our cache. With
+ FORCE_REFRESH set to true, a new CRL will be retrieved even if the
+ cache has not yet expired. We use a 30 minutes threshold here so
+ that invoking this function several times won't load the CRL over
+ and over. */
+crl_cache_result_t
+crl_cache_isvalid (ctrl_t ctrl, const char *issuer_hash, const char *serialno,
+ int force_refresh)
+{
+ crl_cache_result_t result;
+ unsigned char snbuf_buffer[50];
+ unsigned char *snbuf;
+ size_t n;
+
+ n = strlen (serialno)/2+1;
+ if (n < sizeof snbuf_buffer - 1)
+ snbuf = snbuf_buffer;
+ else
+ {
+ snbuf = xtrymalloc (n);
+ if (!snbuf)
+ return CRL_CACHE_DONTKNOW;
+ }
+
+ n = unhexify (snbuf, serialno);
+
+ result = cache_isvalid (ctrl, issuer_hash, snbuf, n, force_refresh);
+
+ if (snbuf != snbuf_buffer)
+ xfree (snbuf);
+
+ return result;
+}
+
+
+/* Check whether the certificate CERT is valid; i.e. not listed in our
+ cache. With FORCE_REFRESH set to true, a new CRL will be retrieved
+ even if the cache has not yet expired. We use a 30 minutes
+ threshold here so that invoking this function several times won't
+ load the CRL over and over. */
+gpg_error_t
+crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert,
+ int force_refresh)
+{
+ gpg_error_t err;
+ crl_cache_result_t result;
+ unsigned char issuerhash[20];
+ char issuerhash_hex[41];
+ ksba_sexp_t serial;
+ unsigned char *sn;
+ size_t snlen;
+ char *endp, *tmp;
+ int i;
+
+ /* Compute the hash value of the issuer name. */
+ tmp = ksba_cert_get_issuer (cert, 0);
+ if (!tmp)
+ {
+ log_error ("oops: issuer missing in certificate\n");
+ return gpg_error (GPG_ERR_INV_CERT_OBJ);
+ }
+ gcry_md_hash_buffer (GCRY_MD_SHA1, issuerhash, tmp, strlen (tmp));
+ xfree (tmp);
+ for (i=0,tmp=issuerhash_hex; i < 20; i++, tmp += 2)
+ sprintf (tmp, "%02X", issuerhash[i]);
+
+ /* Get the serial number. */
+ serial = ksba_cert_get_serial (cert);
+ if (!serial)
+ {
+ log_error ("oops: S/N missing in certificate\n");
+ return gpg_error (GPG_ERR_INV_CERT_OBJ);
+ }
+ sn = serial;
+ if (*sn != '(')
+ {
+ log_error ("oops: invalid S/N\n");
+ xfree (serial);
+ return gpg_error (GPG_ERR_INV_CERT_OBJ);
+ }
+ sn++;
+ snlen = strtoul (sn, &endp, 10);
+ sn = endp;
+ if (*sn != ':')
+ {
+ log_error ("oops: invalid S/N\n");
+ xfree (serial);
+ return gpg_error (GPG_ERR_INV_CERT_OBJ);
+ }
+ sn++;
+
+ /* Check the cache. */
+ result = cache_isvalid (ctrl, issuerhash_hex, sn, snlen, force_refresh);
+ switch (result)
+ {
+ case CRL_CACHE_VALID:
+ err = 0;
+ break;
+ case CRL_CACHE_INVALID:
+ err = gpg_error (GPG_ERR_CERT_REVOKED);
+ break;
+ case CRL_CACHE_DONTKNOW:
+ err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+ break;
+ case CRL_CACHE_CANTUSE:
+ err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+ break;
+ default:
+ log_fatal ("cache_isvalid returned invalid status code %d\n", result);
+ }
+
+ xfree (serial);
+ return err;
+}
+
+
+/* Return the hash algorithm's algo id from its name given in the
+ * non-null termnated string in (buffer,buflen). Returns 0 on failure
+ * or if the algo is not known. */
+static int
+hash_algo_from_buffer (const void *buffer, size_t buflen)
+{
+ char *string;
+ int algo;
+
+ string = xtrymalloc (buflen + 1);
+ if (!string)
+ {
+ log_error (_("out of core\n"));
+ return 0;
+ }
+ memcpy (string, buffer, buflen);
+ string[buflen] = 0;
+ algo = gcry_md_map_name (string);
+ if (!algo)
+ log_error ("unknown digest algorithm '%s' used in certificate\n", string);
+ xfree (string);
+ return algo;
+}
+
+
+/* Return an unsigned integer from the non-null termnated string
+ * (buffer,buflen). Returns 0 on failure. */
+static unsigned int
+uint_from_buffer (const void *buffer, size_t buflen)
+{
+ char *string;
+ unsigned int val;
+
+ string = xtrymalloc (buflen + 1);
+ if (!string)
+ {
+ log_error (_("out of core\n"));
+ return 0;
+ }
+ memcpy (string, buffer, buflen);
+ string[buflen] = 0;
+ val = strtoul (string, NULL, 10);
+ xfree (string);
+ return val;
+}
+
+
+/* Prepare a hash context for the signature verification. Input is
+ the CRL and the output is the hash context MD as well as the uses
+ algorithm identifier ALGO. */
+static gpg_error_t
+start_sig_check (ksba_crl_t crl, gcry_md_hd_t *md, int *algo, int *use_pss)
+{
+ gpg_error_t err;
+ const char *algoid;
+
+ *use_pss = 0;
+ algoid = ksba_crl_get_digest_algo (crl);
+ if (algoid && !strcmp (algoid, "1.2.840.113549.1.1.10"))
+ {
+ /* Parse rsaPSS parameter. */
+ gcry_buffer_t ioarray[1] = { {0} };
+ ksba_sexp_t pssparam;
+ size_t n;
+ gcry_sexp_t psssexp;
+
+ pssparam = ksba_crl_get_sig_val (crl);
+ n = gcry_sexp_canon_len (pssparam, 0, NULL, NULL);
+ if (!n)
+ {
+ ksba_free (pssparam);
+ log_error (_("got an invalid S-expression from libksba\n"));
+ return gpg_error (GPG_ERR_INV_SEXP);
+ }
+ err = gcry_sexp_sscan (&psssexp, NULL, pssparam, n);
+ ksba_free (pssparam);
+ if (err)
+ {
+ log_error (_("converting S-expression failed: %s\n"),
+ gcry_strerror (err));
+ return err;
+ }
+
+ err = gcry_sexp_extract_param (psssexp, "sig-val",
+ "&'hash-algo'", ioarray, NULL);
+ gcry_sexp_release (psssexp);
+ if (err)
+ {
+ log_error ("extracting params from PSS failed: %s\n",
+ gpg_strerror (err));
+ return err;
+ }
+ *algo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len);
+ xfree (ioarray[0].data);
+ *use_pss = 1;
+ }
+ else
+ *algo = gcry_md_map_name (algoid);
+ if (!*algo)
+ {
+ log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?");
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+
+ err = gcry_md_open (md, *algo, 0);
+ if (err)
+ {
+ log_error (_("gcry_md_open for algorithm %d failed: %s\n"),
+ *algo, gcry_strerror (err));
+ return err;
+ }
+ if (DBG_HASHING)
+ gcry_md_debug (*md, "hash.cert");
+
+ ksba_crl_set_hash_function (crl, HASH_FNC, *md);
+ return 0;
+}
+
+
+/* Finish a hash context and verify the signature. This function
+ should return 0 on a good signature, GPG_ERR_BAD_SIGNATURE if the
+ signature does not verify or any other error code. CRL is the CRL
+ object we are working on, MD the hash context and ISSUER_CERT the
+ certificate of the CRL issuer. This function takes ownership of MD. */
+static gpg_error_t
+finish_sig_check (ksba_crl_t crl, gcry_md_hd_t md, int algo,
+ ksba_cert_t issuer_cert, int use_pss)
+{
+ gpg_error_t err;
+ ksba_sexp_t sigval = NULL, pubkey = NULL;
+ size_t n;
+ gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
+ unsigned int saltlen = 0; /* (used only with use_pss) */
+
+ /* This also stops debugging on the MD. */
+ gcry_md_final (md);
+
+ /* Get and convert the signature value. */
+ sigval = ksba_crl_get_sig_val (crl);
+ n = gcry_sexp_canon_len (sigval, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error (_("got an invalid S-expression from libksba\n"));
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto leave;
+ }
+ err = gcry_sexp_sscan (&s_sig, NULL, sigval, n);
+ if (err)
+ {
+ log_error (_("converting S-expression failed: %s\n"),
+ gcry_strerror (err));
+ goto leave;
+ }
+
+ if (use_pss)
+ {
+ /* Parse rsaPSS parameter which we should find in S_SIG. */
+ gcry_buffer_t ioarray[2] = { {0}, {0} };
+ ksba_sexp_t pssparam;
+ gcry_sexp_t psssexp;
+ int hashalgo;
+
+ pssparam = ksba_crl_get_sig_val (crl);
+ n = gcry_sexp_canon_len (pssparam, 0, NULL, NULL);
+ if (!n)
+ {
+ ksba_free (pssparam);
+ log_error (_("got an invalid S-expression from libksba\n"));
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto leave;
+ }
+ err = gcry_sexp_sscan (&psssexp, NULL, pssparam, n);
+ ksba_free (pssparam);
+ if (err)
+ {
+ log_error (_("converting S-expression failed: %s\n"),
+ gcry_strerror (err));
+ goto leave;
+ }
+
+ err = gcry_sexp_extract_param (psssexp, "sig-val",
+ "&'hash-algo''salt-length'",
+ ioarray+0, ioarray+1, NULL);
+ gcry_sexp_release (psssexp);
+ if (err)
+ {
+ log_error ("extracting params from PSS failed: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+ hashalgo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len);
+ saltlen = uint_from_buffer (ioarray[1].data, ioarray[1].len);
+ xfree (ioarray[0].data);
+ xfree (ioarray[1].data);
+ if (hashalgo != algo)
+ {
+ log_error ("hash algo mismatch: %d announced but %d used\n",
+ algo, hashalgo);
+ return gpg_error (GPG_ERR_INV_CRL);
+ }
+ /* Add some restrictions; see ../sm/certcheck.c for details. */
+ switch (algo)
+ {
+ case GCRY_MD_SHA1:
+ case GCRY_MD_SHA256:
+ case GCRY_MD_SHA384:
+ case GCRY_MD_SHA512:
+ case GCRY_MD_SHA3_256:
+ case GCRY_MD_SHA3_384:
+ case GCRY_MD_SHA3_512:
+ break;
+ default:
+ log_error ("PSS hash algorithm '%s' rejected\n",
+ gcry_md_algo_name (algo));
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+
+ if (gcry_md_get_algo_dlen (algo) != saltlen)
+ {
+ log_error ("PSS hash algorithm '%s' rejected due to salt length %u\n",
+ gcry_md_algo_name (algo), saltlen);
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+ }
+
+
+ /* Get and convert the public key for the issuer certificate. */
+ if (DBG_X509)
+ dump_cert ("crl_issuer_cert", issuer_cert);
+ pubkey = ksba_cert_get_public_key (issuer_cert);
+ n = gcry_sexp_canon_len (pubkey, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error (_("got an invalid S-expression from libksba\n"));
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto leave;
+ }
+ err = gcry_sexp_sscan (&s_pkey, NULL, pubkey, n);
+ if (err)
+ {
+ log_error (_("converting S-expression failed: %s\n"),
+ gcry_strerror (err));
+ goto leave;
+ }
+
+ /* Create an S-expression with the actual hash value. */
+ if (use_pss)
+ {
+ err = gcry_sexp_build (&s_hash, NULL,
+ "(data (flags pss)"
+ "(hash %s %b)"
+ "(salt-length %u))",
+ hash_algo_to_string (algo),
+ (int)gcry_md_get_algo_dlen (algo),
+ gcry_md_read (md, algo),
+ saltlen);
+ }
+ else
+ {
+ err = gcry_sexp_build (&s_hash, NULL,
+ "(data(flags pkcs1)(hash %s %b))",
+ hash_algo_to_string (algo),
+ (int)gcry_md_get_algo_dlen (algo),
+ gcry_md_read (md, algo));
+ }
+ if (err)
+ {
+ log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err));
+ goto leave;
+ }
+
+ /* Pass this on to the signature verification. */
+ err = gcry_pk_verify (s_sig, s_hash, s_pkey);
+ if (DBG_X509)
+ log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err));
+
+ leave:
+ xfree (sigval);
+ xfree (pubkey);
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_hash);
+ gcry_sexp_release (s_pkey);
+ gcry_md_close (md);
+
+ return err;
+}
+
+
+/* Call this to match a start_sig_check that can not be completed
+ normally. Takes ownership of MD if MD is not NULL. */
+static void
+abort_sig_check (ksba_crl_t crl, gcry_md_hd_t md)
+{
+ (void)crl;
+ if (md)
+ gcry_md_close (md);
+}
+
+
+/* Workhorse of the CRL loading machinery. The CRL is read using the
+ CRL object and stored in the data base file DB with the name FNAME
+ (only used for printing error messages). That DB should be a
+ temporary one and not the actual one. If the function fails the
+ caller should delete this temporary database file. CTRL is
+ required to retrieve certificates using the general dirmngr
+ callback service. R_CRLISSUER returns an allocated string with the
+ crl-issuer DN, THIS_UPDATE and NEXT_UPDATE are filled with the
+ corresponding data from the CRL. Note that these values might get
+ set even if the CRL processing fails at a later step; thus the
+ caller should free *R_ISSUER even if the function returns with an
+ error. R_TRUST_ANCHOR is set on exit to NULL or a string with the
+ hexified fingerprint of the root certificate, if checking this
+ certificate for trustiness is required.
+*/
+static int
+crl_parse_insert (ctrl_t ctrl, ksba_crl_t crl,
+ struct cdb_make *cdb, const char *fname,
+ char **r_crlissuer,
+ ksba_isotime_t thisupdate, ksba_isotime_t nextupdate,
+ char **r_trust_anchor)
+{
+ gpg_error_t err;
+ ksba_stop_reason_t stopreason;
+ ksba_cert_t crlissuer_cert = NULL;
+ gcry_md_hd_t md = NULL;
+ int algo = 0;
+ int use_pss = 0;
+ size_t n;
+
+ (void)fname;
+
+ *r_crlissuer = NULL;
+ *thisupdate = *nextupdate = 0;
+ *r_trust_anchor = NULL;
+
+ /* Start of the KSBA parser loop. */
+ do
+ {
+ err = ksba_crl_parse (crl, &stopreason);
+ if (err)
+ {
+ log_error (_("ksba_crl_parse failed: %s\n"), gpg_strerror (err) );
+ goto failure;
+ }
+
+ switch (stopreason)
+ {
+ case KSBA_SR_BEGIN_ITEMS:
+ {
+ err = start_sig_check (crl, &md, &algo, &use_pss);
+ if (err)
+ goto failure;
+
+ err = ksba_crl_get_update_times (crl, thisupdate, nextupdate);
+ if (err)
+ {
+ log_error (_("error getting update times of CRL: %s\n"),
+ gpg_strerror (err));
+ err = gpg_error (GPG_ERR_INV_CRL);
+ goto failure;
+ }
+
+ if (opt.verbose || !*nextupdate)
+ log_info (_("update times of this CRL: this=%s next=%s\n"),
+ thisupdate, nextupdate);
+ if (!*nextupdate)
+ {
+ log_info (_("nextUpdate not given; "
+ "assuming a validity period of one day\n"));
+ gnupg_copy_time (nextupdate, thisupdate);
+ add_seconds_to_isotime (nextupdate, 86400);
+ }
+ }
+ break;
+
+ case KSBA_SR_GOT_ITEM:
+ {
+ ksba_sexp_t serial;
+ const unsigned char *p;
+ ksba_isotime_t rdate;
+ ksba_crl_reason_t reason;
+ int rc;
+ unsigned char record[1+15];
+
+ err = ksba_crl_get_item (crl, &serial, rdate, &reason);
+ if (err)
+ {
+ log_error (_("error getting CRL item: %s\n"),
+ gpg_strerror (err));
+ err = gpg_error (GPG_ERR_INV_CRL);
+ ksba_free (serial);
+ goto failure;
+ }
+ p = serial_to_buffer (serial, &n);
+ if (!p)
+ BUG ();
+ record[0] = (reason & 0xff);
+ memcpy (record+1, rdate, 15);
+ rc = cdb_make_add (cdb, p, n, record, 1+15);
+ if (rc)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error inserting item into "
+ "temporary cache file: %s\n"),
+ strerror (errno));
+ goto failure;
+ }
+
+ ksba_free (serial);
+ }
+ break;
+
+ case KSBA_SR_END_ITEMS:
+ break;
+
+ case KSBA_SR_READY:
+ {
+ char *crlissuer;
+ ksba_name_t authid;
+ ksba_sexp_t authidsn;
+ ksba_sexp_t keyid;
+
+ /* We need to look for the issuer only after having read
+ all items. The issuer itselfs comes before the items
+ but the optional authorityKeyIdentifier comes after the
+ items. */
+ err = ksba_crl_get_issuer (crl, &crlissuer);
+ if( err )
+ {
+ log_error (_("no CRL issuer found in CRL: %s\n"),
+ gpg_strerror (err) );
+ err = gpg_error (GPG_ERR_INV_CRL);
+ goto failure;
+ }
+ /* Note: This should be released by ksba_free, not xfree.
+ May need a memory reallocation dance. */
+ *r_crlissuer = crlissuer; /* (Do it here so we don't need
+ to free it later) */
+
+ if (!ksba_crl_get_auth_key_id (crl, &keyid, &authid, &authidsn))
+ {
+ const char *s;
+
+ if (opt.verbose)
+ log_info (_("locating CRL issuer certificate by "
+ "authorityKeyIdentifier\n"));
+
+ s = ksba_name_enum (authid, 0);
+ if (s && *authidsn)
+ crlissuer_cert = find_cert_bysn (ctrl, s, authidsn);
+ if (!crlissuer_cert && keyid)
+ crlissuer_cert = find_cert_bysubject (ctrl,
+ crlissuer, keyid);
+
+ if (!crlissuer_cert)
+ {
+ log_info ("CRL issuer certificate ");
+ if (keyid)
+ {
+ log_printf ("{");
+ dump_serial (keyid);
+ log_printf ("} ");
+ }
+ if (authidsn)
+ {
+ log_printf ("(#");
+ dump_serial (authidsn);
+ log_printf ("/");
+ dump_string (s);
+ log_printf (") ");
+ }
+ log_printf ("not found\n");
+ }
+ ksba_name_release (authid);
+ xfree (authidsn);
+ xfree (keyid);
+ }
+ else
+ crlissuer_cert = find_cert_bysubject (ctrl, crlissuer, NULL);
+ err = 0;
+ if (!crlissuer_cert)
+ {
+ err = gpg_error (GPG_ERR_MISSING_CERT);
+ goto failure;
+ }
+
+ err = finish_sig_check (crl, md, algo, crlissuer_cert, use_pss);
+ md = NULL; /* Closed. */
+ if (err)
+ {
+ log_error (_("CRL signature verification failed: %s\n"),
+ gpg_strerror (err));
+ goto failure;
+ }
+
+ err = validate_cert_chain (ctrl, crlissuer_cert, NULL,
+ (VALIDATE_FLAG_TRUST_CONFIG
+ | VALIDATE_FLAG_CRL
+ | VALIDATE_FLAG_RECURSIVE),
+ r_trust_anchor);
+ if (err)
+ {
+ log_error (_("error checking validity of CRL "
+ "issuer certificate: %s\n"),
+ gpg_strerror (err));
+ goto failure;
+ }
+
+ }
+ break;
+
+ default:
+ log_debug ("crl_parse_insert: unknown stop reason\n");
+ err = gpg_error (GPG_ERR_BUG);
+ goto failure;
+ }
+ }
+ while (stopreason != KSBA_SR_READY);
+ assert (!err);
+
+
+ failure:
+ abort_sig_check (crl, md);
+ ksba_cert_release (crlissuer_cert);
+ return err;
+}
+
+
+
+/* Return the crlNumber extension as an allocated hex string or NULL
+ if there is none. */
+static char *
+get_crl_number (ksba_crl_t crl)
+{
+ gpg_error_t err;
+ ksba_sexp_t number;
+ char *string;
+
+ err = ksba_crl_get_crl_number (crl, &number);
+ if (err)
+ return NULL;
+ string = serial_hex (number);
+ ksba_free (number);
+ return string;
+}
+
+
+/* Return the authorityKeyIdentifier or NULL if it is not available.
+ The issuer name may consists of several parts - they are delimted by
+ 0x01. */
+static char *
+get_auth_key_id (ksba_crl_t crl, char **serialno)
+{
+ gpg_error_t err;
+ ksba_name_t name;
+ ksba_sexp_t sn;
+ int idx;
+ const char *s;
+ char *string;
+ size_t length;
+
+ *serialno = NULL;
+ err = ksba_crl_get_auth_key_id (crl, NULL, &name, &sn);
+ if (err)
+ return NULL;
+ *serialno = serial_hex (sn);
+ ksba_free (sn);
+
+ if (!name)
+ return xstrdup ("");
+
+ length = 0;
+ for (idx=0; (s = ksba_name_enum (name, idx)); idx++)
+ {
+ char *p = ksba_name_get_uri (name, idx);
+ length += strlen (p?p:s) + 1;
+ xfree (p);
+ }
+ string = xtrymalloc (length+1);
+ if (string)
+ {
+ *string = 0;
+ for (idx=0; (s = ksba_name_enum (name, idx)); idx++)
+ {
+ char *p = ksba_name_get_uri (name, idx);
+ if (*string)
+ strcat (string, "\x01");
+ strcat (string, p?p:s);
+ xfree (p);
+ }
+ }
+ ksba_name_release (name);
+ return string;
+}
+
+
+
+/* Insert the CRL retrieved using URL into the cache specified by
+ CACHE. The CRL itself will be read from the stream FP and is
+ expected in binary format.
+
+ Called by:
+ crl_cache_load
+ cmd_loadcrl
+ --load-crl
+ crl_cache_reload_crl
+ cmd_isvalid
+ cmd_checkcrl
+ cmd_loadcrl
+ --fetch-crl
+
+ */
+gpg_error_t
+crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader)
+{
+ crl_cache_t cache = get_current_cache ();
+ gpg_error_t err, err2;
+ ksba_crl_t crl;
+ char *fname = NULL;
+ char *newfname = NULL;
+ struct cdb_make cdb;
+ int fd_cdb = -1;
+ char *issuer = NULL;
+ char *issuer_hash = NULL;
+ ksba_isotime_t thisupdate, nextupdate;
+ crl_cache_entry_t entry = NULL;
+ crl_cache_entry_t e;
+ gnupg_isotime_t current_time;
+ char *checksum = NULL;
+ int invalidate_crl = 0;
+ int idx;
+ const char *oid;
+ int critical;
+ char *trust_anchor = NULL;
+
+ /* FIXME: We should acquire a mutex for the URL, so that we don't
+ simultaneously enter the same CRL twice. However this needs to be
+ interweaved with the checking function.*/
+
+ err2 = 0;
+
+ err = ksba_crl_new (&crl);
+ if (err)
+ {
+ log_error (_("ksba_crl_new failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ err = ksba_crl_set_reader (crl, reader);
+ if ( err )
+ {
+ log_error (_("ksba_crl_set_reader failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Create a temporary cache file to load the CRL into. */
+ {
+ char *tmpfname, *p;
+ const char *nodename;
+#ifndef HAVE_W32_SYSTEM
+ struct utsname utsbuf;
+#endif
+
+#ifdef HAVE_W32_SYSTEM
+ nodename = "unknown";
+#else
+ if (uname (&utsbuf))
+ nodename = "unknown";
+ else
+ nodename = utsbuf.nodename;
+#endif
+
+ gpgrt_asprintf (&tmpfname, "crl-tmp-%s-%u-%p.db.tmp",
+ nodename, (unsigned int)getpid (), &tmpfname);
+ if (!tmpfname)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ for (p=tmpfname; *p; p++)
+ if (*p == '/')
+ *p = '.';
+ fname = make_filename (opt.homedir_cache, DBDIR_D, tmpfname, NULL);
+ xfree (tmpfname);
+ if (!gnupg_remove (fname))
+ log_info (_("removed stale temporary cache file '%s'\n"), fname);
+ else if (errno != ENOENT)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("problem removing stale temporary cache file '%s': %s\n"),
+ fname, gpg_strerror (err));
+ goto leave;
+ }
+ }
+
+ fd_cdb = gnupg_open (fname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
+ if (fd_cdb == -1)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error creating temporary cache file '%s': %s\n"),
+ fname, strerror (errno));
+ goto leave;
+ }
+ cdb_make_start(&cdb, fd_cdb);
+
+ err = crl_parse_insert (ctrl, crl, &cdb, fname,
+ &issuer, thisupdate, nextupdate, &trust_anchor);
+ if (err)
+ {
+ log_error (_("crl_parse_insert failed: %s\n"), gpg_strerror (err));
+ /* Error in cleanup ignored. */
+ cdb_make_finish (&cdb);
+ goto leave;
+ }
+
+ /* Finish the database. */
+ if (cdb_make_finish (&cdb))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error finishing temporary cache file '%s': %s\n"),
+ fname, strerror (errno));
+ goto leave;
+ }
+ if (close (fd_cdb))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error closing temporary cache file '%s': %s\n"),
+ fname, strerror (errno));
+ goto leave;
+ }
+ fd_cdb = -1;
+
+
+ /* Create a checksum. */
+ {
+ unsigned char md5buf[16];
+
+ if (hash_dbfile (fname, md5buf))
+ {
+ err = gpg_error (GPG_ERR_CHECKSUM);
+ goto leave;
+ }
+ checksum = hexify_data (md5buf, 16, 0);
+ }
+
+
+ /* Check whether that new CRL is still not expired. */
+ gnupg_get_isotime (current_time);
+ if (strcmp (nextupdate, current_time) < 0 )
+ {
+ if (opt.force)
+ log_info (_("WARNING: new CRL still too old; it expired on %s "
+ "- loading anyway\n"), nextupdate);
+ else
+ {
+ log_error (_("new CRL still too old; it expired on %s\n"),
+ nextupdate);
+ if (!err2)
+ err2 = gpg_error (GPG_ERR_CRL_TOO_OLD);
+ invalidate_crl |= 1;
+ }
+ }
+
+ /* Check for unknown critical extensions. */
+ for (idx=0; !(err=ksba_crl_get_extension (crl, idx, &oid, &critical,
+ NULL, NULL)); idx++)
+ {
+ if (!critical
+ || !strcmp (oid, oidstr_authorityKeyIdentifier)
+ || !strcmp (oid, oidstr_crlNumber) )
+ continue;
+ log_error (_("unknown critical CRL extension %s\n"), oid);
+ if (!err2)
+ err2 = gpg_error (GPG_ERR_INV_CRL);
+ invalidate_crl |= 2;
+ }
+ if (gpg_err_code (err) == GPG_ERR_EOF
+ || gpg_err_code (err) == GPG_ERR_NO_DATA )
+ err = 0;
+ if (err)
+ {
+ log_error (_("error reading CRL extensions: %s\n"), gpg_strerror (err));
+ err = gpg_error (GPG_ERR_INV_CRL);
+ }
+
+
+ /* Create an hex encoded SHA-1 hash of the issuer DN to be
+ used as the key for the cache. */
+ issuer_hash = hashify_data (issuer, strlen (issuer));
+
+ /* Create an ENTRY. */
+ entry = xtrycalloc (1, sizeof *entry);
+ if (!entry)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ entry->release_ptr = xtrymalloc (strlen (issuer_hash) + 1
+ + strlen (issuer) + 1
+ + strlen (url) + 1
+ + strlen (checksum) + 1);
+ if (!entry->release_ptr)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (entry);
+ entry = NULL;
+ goto leave;
+ }
+ entry->issuer_hash = entry->release_ptr;
+ entry->issuer = stpcpy (entry->issuer_hash, issuer_hash) + 1;
+ entry->url = stpcpy (entry->issuer, issuer) + 1;
+ entry->dbfile_hash = stpcpy (entry->url, url) + 1;
+ strcpy (entry->dbfile_hash, checksum);
+ gnupg_copy_time (entry->this_update, thisupdate);
+ gnupg_copy_time (entry->next_update, nextupdate);
+ gnupg_copy_time (entry->last_refresh, current_time);
+ entry->crl_number = get_crl_number (crl);
+ entry->authority_issuer = get_auth_key_id (crl, &entry->authority_serialno);
+ entry->invalid = invalidate_crl;
+ entry->user_trust_req = !!trust_anchor;
+ entry->check_trust_anchor = trust_anchor;
+ trust_anchor = NULL;
+
+ /* Check whether we already have an entry for this issuer and mark
+ it as deleted. We better use a loop, just in case duplicates got
+ somehow into the list. */
+ for (e = cache->entries; (e=find_entry (e, entry->issuer_hash)); e = e->next)
+ e->deleted = 1;
+
+ /* Rename the temporary DB to the real name. */
+ newfname = make_db_file_name (entry->issuer_hash);
+ if (opt.verbose)
+ log_info (_("creating cache file '%s'\n"), newfname);
+
+ /* Just in case close unused matching files. Actually we need this
+ only under Windows but saving file descriptors is never bad. */
+ {
+ int any;
+ do
+ {
+ any = 0;
+ for (e = cache->entries; e; e = e->next)
+ if (!e->cdb_use_count && e->cdb
+ && !strcmp (e->issuer_hash, entry->issuer_hash))
+ {
+ int fd = cdb_fileno (e->cdb);
+ cdb_free (e->cdb);
+ xfree (e->cdb);
+ e->cdb = NULL;
+ if (close (fd))
+ log_error (_("error closing cache file: %s\n"),
+ strerror(errno));
+ any = 1;
+ break;
+ }
+ }
+ while (any);
+ }
+#ifdef HAVE_W32_SYSTEM
+ gnupg_remove (newfname);
+#endif
+ if (rename (fname, newfname))
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("problem renaming '%s' to '%s': %s\n"),
+ fname, newfname, gpg_strerror (err));
+ goto leave;
+ }
+ xfree (fname); fname = NULL; /*(let the cleanup code not try to remove it)*/
+
+ /* Link the new entry in. */
+ entry->next = cache->entries;
+ cache->entries = entry;
+ entry = NULL;
+
+ err = update_dir (cache);
+ if (err)
+ {
+ log_error (_("updating the DIR file failed - "
+ "cache entry will get lost with the next program start\n"));
+ err = 0; /* Keep on running. */
+ }
+
+
+ leave:
+ release_one_cache_entry (entry);
+ if (fd_cdb != -1)
+ close (fd_cdb);
+ if (fname)
+ {
+ gnupg_remove (fname);
+ xfree (fname);
+ }
+ xfree (newfname);
+ ksba_crl_release (crl);
+ xfree (issuer);
+ xfree (issuer_hash);
+ xfree (checksum);
+ xfree (trust_anchor);
+ return err ? err : err2;
+}
+
+
+/* Print one cached entry E in a human readable format to stream
+ FP. Return 0 on success. */
+static gpg_error_t
+list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, estream_t fp)
+{
+ struct cdb_find cdbfp;
+ struct cdb *cdb;
+ int rc;
+ int warn = 0;
+ const unsigned char *s;
+
+ es_fputs ("--------------------------------------------------------\n", fp );
+ es_fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url );
+ es_fprintf (fp, " Issuer:\t%s\n", e->issuer );
+ es_fprintf (fp, " Issuer Hash:\t%s\n", e->issuer_hash );
+ es_fprintf (fp, " This Update:\t%s\n", e->this_update );
+ es_fprintf (fp, " Next Update:\t%s\n", e->next_update );
+ es_fprintf (fp, " CRL Number :\t%s\n", e->crl_number? e->crl_number: "none");
+ es_fprintf (fp, " AuthKeyId :\t%s\n",
+ e->authority_serialno? e->authority_serialno:"none");
+ if (e->authority_serialno && e->authority_issuer)
+ {
+ es_fputs (" \t", fp);
+ for (s=e->authority_issuer; *s; s++)
+ if (*s == '\x01')
+ es_fputs ("\n \t", fp);
+ else
+ es_putc (*s, fp);
+ es_putc ('\n', fp);
+ }
+ es_fprintf (fp, " Trust Check:\t%s\n",
+ !e->user_trust_req? "[system]" :
+ e->check_trust_anchor? e->check_trust_anchor:"[missing]");
+
+ if ((e->invalid & 1))
+ es_fprintf (fp, _(" ERROR: The CRL will not be used "
+ "because it was still too old after an update!\n"));
+ if ((e->invalid & 2))
+ es_fprintf (fp, _(" ERROR: The CRL will not be used "
+ "due to an unknown critical extension!\n"));
+ if ((e->invalid & ~3))
+ es_fprintf (fp, _(" ERROR: The CRL will not be used\n"));
+
+ cdb = lock_db_file (cache, e);
+ if (!cdb)
+ return gpg_error (GPG_ERR_GENERAL);
+
+ if (!e->dbfile_checked)
+ es_fprintf (fp, _(" ERROR: This cached CRL may have been tampered with!\n"));
+
+ es_putc ('\n', fp);
+
+ rc = cdb_findinit (&cdbfp, cdb, NULL, 0);
+ while (!rc && (rc=cdb_findnext (&cdbfp)) > 0 )
+ {
+ unsigned char keyrecord[256];
+ unsigned char record[16];
+ int reason;
+ int any = 0;
+ cdbi_t n;
+ cdbi_t i;
+
+ rc = 0;
+ n = cdb_datalen (cdb);
+ if (n != 16)
+ {
+ log_error (_(" WARNING: invalid cache record length\n"));
+ warn = 1;
+ continue;
+ }
+
+ if (cdb_read (cdb, record, n, cdb_datapos (cdb)))
+ {
+ log_error (_("problem reading cache record: %s\n"),
+ strerror (errno));
+ warn = 1;
+ continue;
+ }
+
+ n = cdb_keylen (cdb);
+ if (n > sizeof keyrecord)
+ n = sizeof keyrecord;
+ if (cdb_read (cdb, keyrecord, n, cdb_keypos (cdb)))
+ {
+ log_error (_("problem reading cache key: %s\n"), strerror (errno));
+ warn = 1;
+ continue;
+ }
+
+ reason = *record;
+ es_fputs (" ", fp);
+ for (i = 0; i < n; i++)
+ es_fprintf (fp, "%02X", keyrecord[i]);
+ es_fputs (":\t reasons( ", fp);
+
+ if (reason & KSBA_CRLREASON_UNSPECIFIED)
+ es_fputs( "unspecified ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_KEY_COMPROMISE )
+ es_fputs( "key_compromise ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_CA_COMPROMISE )
+ es_fputs( "ca_compromise ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_AFFILIATION_CHANGED )
+ es_fputs( "affiliation_changed ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_SUPERSEDED )
+ es_fputs( "superseded", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION )
+ es_fputs( "cessation_of_operation", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_CERTIFICATE_HOLD )
+ es_fputs( "certificate_hold", fp ), any = 1;
+ if (reason && !any)
+ es_fputs( "other", fp );
+
+ es_fprintf (fp, ") rdate: %.15s\n", record+1);
+ }
+ if (rc)
+ log_error (_("error reading cache entry from db: %s\n"), strerror (rc));
+
+ unlock_db_file (cache, e);
+ es_fprintf (fp, _("End CRL dump\n") );
+ es_putc ('\n', fp);
+
+ return (rc||warn)? gpg_error (GPG_ERR_GENERAL) : 0;
+}
+
+
+/* Print the contents of the CRL CACHE in a human readable format to
+ stream FP. */
+gpg_error_t
+crl_cache_list (estream_t fp)
+{
+ crl_cache_t cache = get_current_cache ();
+ crl_cache_entry_t entry;
+ gpg_error_t err = 0;
+
+ for (entry = cache->entries;
+ entry && !entry->deleted && !err;
+ entry = entry->next )
+ err = list_one_crl_entry (cache, entry, fp);
+
+ return err;
+}
+
+
+/* Load the CRL containing the file named FILENAME into our CRL cache. */
+gpg_error_t
+crl_cache_load (ctrl_t ctrl, const char *filename)
+{
+ gpg_error_t err;
+ estream_t fp;
+ ksba_reader_t reader;
+
+ fp = es_fopen (filename, "rb");
+ if (!fp)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
+ return err;
+ }
+
+ err = create_estream_ksba_reader (&reader, fp);
+ if (!err)
+ {
+ err = crl_cache_insert (ctrl, filename, reader);
+ ksba_reader_release (reader);
+ }
+ es_fclose (fp);
+ return err;
+}
+
+
+/* Locate the corresponding CRL for the certificate CERT, read and
+ verify the CRL and store it in the cache. */
+gpg_error_t
+crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert)
+{
+ gpg_error_t err;
+ ksba_reader_t reader = NULL;
+ char *issuer = NULL;
+ ksba_name_t distpoint = NULL;
+ ksba_name_t issuername = NULL;
+ char *distpoint_uri = NULL;
+ int any_dist_point = 0;
+ int seq;
+ gpg_error_t last_err = 0;
+
+ /* Loop over all distribution points, get the CRLs and put them into
+ the cache. */
+ if (opt.verbose)
+ log_info ("checking distribution points\n");
+ seq = 0;
+ while (xfree (distpoint), xfree (issuername),
+ !(err = ksba_cert_get_crl_dist_point (cert, seq++,
+ &distpoint,
+ &issuername, NULL )))
+ {
+ int name_seq;
+
+ if (!distpoint && !issuername)
+ {
+ if (opt.verbose)
+ log_info ("no issuer name and no distribution point\n");
+ break; /* Not allowed; i.e. an invalid certificate. We give
+ up here and hope that the default method returns a
+ suitable CRL. */
+ }
+
+ /* Get the URIs. We do this in a loop to iterate over all names
+ in the crlDP. */
+ for (name_seq=0; ksba_name_enum (distpoint, name_seq); name_seq++)
+ {
+ xfree (distpoint_uri);
+ distpoint_uri = ksba_name_get_uri (distpoint, name_seq);
+ if (!distpoint_uri)
+ continue;
+
+ if (!strncmp (distpoint_uri, "ldap:", 5)
+ || !strncmp (distpoint_uri, "ldaps:", 6))
+ {
+ if (opt.ignore_ldap_dp)
+ continue;
+ }
+ else if (!strncmp (distpoint_uri, "http:", 5)
+ || !strncmp (distpoint_uri, "https:", 6))
+ {
+ if (opt.ignore_http_dp)
+ continue;
+ }
+ else
+ continue; /* Skip unknown schemes. */
+
+ any_dist_point = 1;
+
+ if (opt.verbose)
+ log_info ("fetching CRL from '%s'\n", distpoint_uri);
+ crl_close_reader (reader);
+ err = crl_fetch (ctrl, distpoint_uri, &reader);
+ if (err)
+ {
+ log_error (_("crl_fetch via DP failed: %s\n"),
+ gpg_strerror (err));
+ last_err = err;
+ continue; /* with the next name. */
+ }
+
+ if (opt.verbose)
+ log_info ("inserting CRL (reader %p)\n", reader);
+ err = crl_cache_insert (ctrl, distpoint_uri, reader);
+ if (err)
+ {
+ log_error (_("crl_cache_insert via DP failed: %s\n"),
+ gpg_strerror (err));
+ last_err = err;
+ continue; /* with the next name. */
+ }
+ goto leave; /* Ready - we got the CRL. */
+ }
+ }
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ err = 0;
+ if (!err && last_err)
+ {
+ err = last_err;
+ goto leave;
+ }
+
+ /* If we did not found any distpoint, try something reasonable. */
+ if (!any_dist_point )
+ {
+ if (opt.verbose)
+ log_info ("no distribution point - trying issuer name\n");
+
+ issuer = ksba_cert_get_issuer (cert, 0);
+ if (!issuer)
+ {
+ log_error ("oops: issuer missing in certificate\n");
+ err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("fetching CRL from default location\n");
+ crl_close_reader (reader);
+ err = crl_fetch_default (ctrl, issuer, &reader);
+ if (err)
+ {
+ log_error ("crl_fetch via issuer failed: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("inserting CRL (reader %p)\n", reader);
+ err = crl_cache_insert (ctrl, "default location(s)", reader);
+ if (err)
+ {
+ log_error (_("crl_cache_insert via issuer failed: %s\n"),
+ gpg_strerror (err));
+ goto leave;
+ }
+ }
+
+ leave:
+ crl_close_reader (reader);
+ xfree (distpoint_uri);
+ ksba_name_release (distpoint);
+ ksba_name_release (issuername);
+ ksba_free (issuer);
+ return err;
+}