mirror of
git://git.gnupg.org/gnupg.git
synced 2024-11-09 21:28:51 +01:00
d83ba4897b
* dirmngr/dirmngr.h (opts): Remove fields 'system_service' and 'system_daemon'. * common/homedir.c (dirmngr_sys_socket_name): Remove. (dirmngr_user_socket_name): Rename to ... (dirmngr_socket_name): this. Change call callers. * common/asshelp.c (start_new_dirmngr): Remove the system socket feature. * tools/gpgconf.c (list_dirs): Do not print "dirmngr-sys-socket". * sm/server.c (gpgsm_server): Adjust for removed system socket feature. * dirmngr/server.c (cmd_getinfo): Ditto. (cmd_killdirmngr): Remove check for system daemon. (cmd_reloaddirmngr): Ditto. * dirmngr/dirmngr.c (USE_W32_SERVICE): Remove macro. (aService): Remove. (opts): Remove --service. (w32_service_control): Remove. (real_main, call_real_main) [W32]: Remove wrapper. (main): Remove Windows system service feature. Remove system dameon feature. Use only the "~/.gnupg/dirmngr_ldapservers.conf" file. * dirmngr/certcache.c (load_certs_from_dir): Remove warning in the system dameon case. * dirmngr/crlcache.c (DBDIR_D): Always use "~/.gnupg/crls.d". * dirmngr/ocsp.c (validate_responder_cert): Do not call validate_cert_chain which was used only in system daemon mode. * dirmngr/validate.c (validate_cert_chain): Always use the code. -- We are now starting dirmngr as needed as a user daemon. The deprecated system daemon mode does not anymore make sense. In case a system wide daemon is required, it is better to setup a dedicated account to run dirmngr and tweak socket permissions accordingly. Signed-off-by: Werner Koch <wk@gnupg.org>
2585 lines
71 KiB
C
2585 lines
71 KiB
C
/* 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 <http://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 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
|
||
#ifdef MKDIR_TAKES_ONE_ARG
|
||
#undef mkdir
|
||
#define mkdir(a,b) mkdir(a)
|
||
#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
|
||
|
||
|
||
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)
|
||
{
|
||
DIR *dir;
|
||
char *fname;
|
||
|
||
fname = make_filename (opt.homedir_cache, name, NULL);
|
||
dir = opendir (fname);
|
||
if (!dir)
|
||
{
|
||
log_info (_("creating directory '%s'\n"), fname);
|
||
if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR) )
|
||
{
|
||
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
|
||
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);
|
||
DIR *dir;
|
||
struct dirent *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 = opendir (dname);
|
||
if (!dir)
|
||
{
|
||
log_error (_("error reading directory '%s': %s\n"),
|
||
dname, strerror (errno));
|
||
xfree (dname);
|
||
return -1;
|
||
}
|
||
|
||
while ((de = 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 = (!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);
|
||
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 = open (fname, O_RDONLY);
|
||
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. Return 0 on success.*/
|
||
int
|
||
crl_cache_flush (void)
|
||
{
|
||
int rc;
|
||
|
||
rc = cleanup_cache_dir (0)? -1 : 0;
|
||
|
||
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;
|
||
}
|
||
|
||
|
||
/* 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)
|
||
{
|
||
gpg_error_t err;
|
||
const char *algoid;
|
||
|
||
algoid = ksba_crl_get_digest_algo (crl);
|
||
*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 closes MD. */
|
||
static gpg_error_t
|
||
finish_sig_check (ksba_crl_t crl, gcry_md_hd_t md, int algo,
|
||
ksba_cert_t issuer_cert)
|
||
{
|
||
gpg_error_t err;
|
||
ksba_sexp_t sigval = NULL, pubkey = NULL;
|
||
const char *s;
|
||
char algoname[50];
|
||
size_t n;
|
||
gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
|
||
unsigned int i;
|
||
|
||
/* 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;
|
||
}
|
||
|
||
/* 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. */
|
||
s = gcry_md_algo_name (algo);
|
||
for (i = 0; *s && i < sizeof(algoname) - 1; s++, i++)
|
||
algoname[i] = ascii_tolower (*s);
|
||
algoname[i] = 0;
|
||
err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
|
||
algoname,
|
||
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. */
|
||
static void
|
||
abort_sig_check (ksba_crl_t crl, gcry_md_hd_t md)
|
||
{
|
||
(void)crl;
|
||
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;
|
||
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);
|
||
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);
|
||
if (err)
|
||
{
|
||
log_error (_("CRL signature verification failed: %s\n"),
|
||
gpg_strerror (err));
|
||
goto failure;
|
||
}
|
||
md = NULL;
|
||
|
||
err = validate_cert_chain (ctrl, crlissuer_cert, NULL,
|
||
VALIDATE_MODE_CRL_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:
|
||
if (md)
|
||
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 = open (fname, O_WRONLY | O_CREAT | O_TRUNC, 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, "r");
|
||
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;
|
||
char *issuername_uri = NULL;
|
||
int any_dist_point = 0;
|
||
int seq;
|
||
|
||
/* 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 ( !(err = ksba_cert_get_crl_dist_point (cert, seq++,
|
||
&distpoint,
|
||
&issuername, NULL )))
|
||
{
|
||
int name_seq;
|
||
gpg_error_t last_err = 0;
|
||
|
||
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. */
|
||
}
|
||
|
||
xfree (issuername_uri); issuername_uri = NULL;
|
||
|
||
/* 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 = NULL;
|
||
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);
|
||
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. */
|
||
}
|
||
last_err = 0;
|
||
break; /* Ready. */
|
||
}
|
||
if (last_err)
|
||
{
|
||
err = last_err;
|
||
goto leave;
|
||
}
|
||
|
||
ksba_name_release (distpoint); distpoint = NULL;
|
||
|
||
/* We don't do anything with issuername_uri yet but we keep the
|
||
code for documentation. */
|
||
issuername_uri = ksba_name_get_uri (issuername, 0);
|
||
ksba_name_release (issuername); issuername = NULL;
|
||
|
||
/* Close the reader. */
|
||
crl_close_reader (reader);
|
||
reader = NULL;
|
||
}
|
||
if (gpg_err_code (err) == GPG_ERR_EOF)
|
||
err = 0;
|
||
|
||
/* 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");
|
||
|
||
crl_close_reader (reader);
|
||
reader = NULL;
|
||
|
||
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");
|
||
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);
|
||
xfree (issuername_uri);
|
||
ksba_name_release (distpoint);
|
||
ksba_name_release (issuername);
|
||
ksba_free (issuer);
|
||
return err;
|
||
}
|