2010-06-09 18:53:51 +02:00
|
|
|
|
/* 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/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
/*
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
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
|
2011-02-04 12:57:53 +01:00
|
|
|
|
itself is represented as "%3A").
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
The first field is a record type identifier, so that the file is
|
2011-02-04 12:57:53 +01:00
|
|
|
|
useful to keep track of other meta data too.
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
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.
|
2010-12-14 13:10:28 +01:00
|
|
|
|
An "i" indicates an invalid cache entry which should
|
2010-06-09 18:53:51 +02:00
|
|
|
|
not be used but still exists so that it can be
|
2011-02-04 12:57:53 +01:00
|
|
|
|
updated at NEXT_UPDATE.
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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.
|
2011-02-04 12:57:53 +01:00
|
|
|
|
1 byte Reason for revocation
|
2010-06-09 18:53:51 +02:00
|
|
|
|
(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.
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#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"
|
|
|
|
|
#include "estream-printf.h"
|
|
|
|
|
|
|
|
|
|
/* Change this whenever the format changes */
|
|
|
|
|
#define DBDIR_D (opt.system_daemon? "crls.d" : "dirmngr-cache.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. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
struct crl_cache_entry_s
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
struct crl_cache_s
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2010-12-14 20:17:58 +01:00
|
|
|
|
/* The currently loaded cache object. This is usually initialized
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
/*
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
gpg_err_set_errno (save_errno);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
return -1;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (force)
|
|
|
|
|
okay = 1;
|
|
|
|
|
else
|
|
|
|
|
okay = (!stat (cdbname, &sbuf) && S_ISREG (sbuf.st_mode));
|
|
|
|
|
|
|
|
|
|
if (okay)
|
|
|
|
|
{
|
|
|
|
|
log_info (_("removing cache file `%s'\n"), cdbname);
|
2010-08-12 13:43:46 +02:00
|
|
|
|
if (gnupg_remove (cdbname))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
log_error ("failed to remove `%s': %s\n",
|
|
|
|
|
cdbname, strerror (errno));
|
|
|
|
|
problem = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
log_info (_("not removing file `%s'\n"), cdbname);
|
|
|
|
|
xfree (cdbname);
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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 *
|
2010-07-16 15:19:45 +02:00
|
|
|
|
next_line_from_file (estream_t fp, gpg_error_t *r_err)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
while ((c=es_getc (fp)) != EOF && c != '\n')
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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);
|
2010-12-14 20:17:58 +01:00
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
cache->entries = NULL;
|
|
|
|
|
xfree (cache);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Open the dir file FNAME or create a new one if it does not yet
|
|
|
|
|
exist. */
|
2010-07-16 15:19:45 +02:00
|
|
|
|
static estream_t
|
2010-06-09 18:53:51 +02:00
|
|
|
|
open_dir_file (const char *fname)
|
|
|
|
|
{
|
2010-07-16 15:19:45 +02:00
|
|
|
|
estream_t fp;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2010-07-16 15:19:45 +02:00
|
|
|
|
fp = es_fopen (fname, "r");
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (create_directory_if_needed (NULL)
|
|
|
|
|
|| create_directory_if_needed (DBDIR_D))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
return NULL;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
fp = es_fopen (fname, "w");
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("error creating new cache dir file `%s': %s\n"),
|
|
|
|
|
fname, strerror (errno));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fp, "v:%d:\n", DBDIRVERSION);
|
|
|
|
|
if (es_ferror (fp))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
log_error (_("error writing new cache dir file `%s': %s\n"),
|
|
|
|
|
fname, strerror (errno));
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
return NULL;
|
|
|
|
|
}
|
2010-07-16 15:19:45 +02:00
|
|
|
|
if (es_fclose (fp))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
2010-07-16 15:19:45 +02:00
|
|
|
|
fp = es_fopen (fname, "r");
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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
|
2010-07-16 15:19:45 +02:00
|
|
|
|
check_dir_version (estream_t *fpadr, const char *fname,
|
2010-06-09 18:53:51 +02:00
|
|
|
|
unsigned int *lineno,
|
|
|
|
|
int cleanup_on_mismatch)
|
|
|
|
|
{
|
|
|
|
|
char *line;
|
|
|
|
|
gpg_error_t lineerr = 0;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
estream_t fp = *fpadr;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
if (strtol (line+2, NULL, 10) != DBDIRVERSION)
|
|
|
|
|
{
|
|
|
|
|
if (!created && cleanup_on_mismatch)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("old version of cache directory - cleaning up\n"));
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
*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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
char *fname;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
char *line = NULL;
|
|
|
|
|
gpg_error_t lineerr = 0;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
estream_t fp;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
crl_cache_entry_t entry, *entrytail;
|
|
|
|
|
unsigned int lineno;
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
int anyerr = 0;
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
cache = xtrycalloc (1, sizeof *cache);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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); break;
|
|
|
|
|
case 6: strncpy (entry->next_update, p, 15); break;
|
|
|
|
|
case 7: entry->dbfile_hash = p; break;
|
|
|
|
|
case 8: if (*p) entry->crl_number = p; break;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
case 9:
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (*p)
|
|
|
|
|
entry->authority_issuer = unpercent_string (p);
|
|
|
|
|
break;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
case 10:
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (*p)
|
|
|
|
|
entry->authority_serialno = unpercent_string (p);
|
|
|
|
|
break;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
case 11:
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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
|
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
|
line = NULL;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
*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;
|
|
|
|
|
}
|
2010-07-16 15:19:45 +02:00
|
|
|
|
if (es_ferror (fp))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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"),
|
2011-02-04 12:57:53 +01:00
|
|
|
|
fname, entry->lineno);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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:
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
xfree (line);
|
|
|
|
|
xfree (fname);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
release_cache (cache);
|
|
|
|
|
cache = NULL;
|
|
|
|
|
}
|
|
|
|
|
*r_cache = cache;
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2010-07-16 15:19:45 +02:00
|
|
|
|
write_percented_string (const char *s, estream_t fp)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
for (; *s; s++)
|
|
|
|
|
if (*s == ':')
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs ("%3A", fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
else if (*s == '\n')
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs ("%0A", fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
else if (*s == '\r')
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs ("%0D", fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
else
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc (*s, fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
2010-07-16 15:19:45 +02:00
|
|
|
|
write_dir_line_crl (estream_t fp, crl_cache_entry_t e)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
if (e->invalid)
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fp, "i%d", e->invalid);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
else if (e->user_trust_req)
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc ('u', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
else
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc ('c', fp);
|
|
|
|
|
es_putc (':', fp);
|
|
|
|
|
es_fputs (e->issuer_hash, fp);
|
|
|
|
|
es_putc (':', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
write_percented_string (e->issuer, fp);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc (':', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
write_percented_string (e->url, fp);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc (':', fp);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fwrite (e->this_update, 15, 1, fp);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc (':', fp);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fwrite (e->next_update, 15, 1, fp);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc (':', fp);
|
|
|
|
|
es_fputs (e->dbfile_hash, fp);
|
|
|
|
|
es_putc (':', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (e->crl_number)
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs (e->crl_number, fp);
|
|
|
|
|
es_putc (':', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (e->authority_issuer)
|
|
|
|
|
write_percented_string (e->authority_issuer, fp);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc (':', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (e->authority_serialno)
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs (e->authority_serialno, fp);
|
|
|
|
|
es_putc (':', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (e->check_trust_anchor && e->user_trust_req)
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs (e->check_trust_anchor, fp);
|
|
|
|
|
es_putc ('\n', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
estream_t fp;
|
|
|
|
|
estream_t fpout = NULL;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
fp = es_fopen (fname, "r");
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_rewind (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
lineno = 0;
|
|
|
|
|
|
|
|
|
|
/* Create a temporary DIR file. */
|
|
|
|
|
{
|
|
|
|
|
char *tmpbuf, *p;
|
|
|
|
|
const char *nodename;
|
|
|
|
|
#ifndef HAVE_W32_SYSTEM
|
|
|
|
|
struct utsname utsbuf;
|
|
|
|
|
#endif
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
#ifdef HAVE_W32_SYSTEM
|
|
|
|
|
nodename = "unknown";
|
|
|
|
|
#else
|
|
|
|
|
if (uname (&utsbuf))
|
|
|
|
|
nodename = "unknown";
|
|
|
|
|
else
|
|
|
|
|
nodename = utsbuf.nodename;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
estream_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);
|
|
|
|
|
}
|
2010-07-16 15:19:45 +02:00
|
|
|
|
fpout = es_fopen (tmpfname, "w");
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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 orginal line. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (e && e->deleted)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
/* Marked for deletion, so don't write it. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
e->mark = 0;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
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. */
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fpout, "# Next line added by "
|
2011-02-04 12:57:53 +01:00
|
|
|
|
"another process; our pid is %lu\n",
|
2010-07-16 15:19:45 +02:00
|
|
|
|
(unsigned long)getpid ());
|
|
|
|
|
es_fputs (line, fpout);
|
|
|
|
|
es_putc ('\n', fpout);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs ("# Invalid line detected: ", fpout);
|
|
|
|
|
es_fputs (line, fpout);
|
|
|
|
|
es_putc ('\n', fpout);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
else
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
/* Write out all non CRL lines as they are. */
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs (line, fpout);
|
|
|
|
|
es_putc ('\n', fpout);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xfree (line);
|
|
|
|
|
}
|
2010-07-23 18:16:14 +02:00
|
|
|
|
if (!es_ferror (fp) && !es_ferror (fpout) && !lineerr)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
2010-07-16 15:19:45 +02:00
|
|
|
|
if (es_ferror (fp))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_errno (errno);
|
|
|
|
|
log_error (_("error reading `%s': %s\n"), fname, strerror (errno));
|
|
|
|
|
}
|
2010-07-16 15:19:45 +02:00
|
|
|
|
if (es_ferror (fpout))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_errno (errno);
|
|
|
|
|
log_error (_("error writing `%s': %s\n"), tmpfname, strerror (errno));
|
|
|
|
|
}
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
/* Rename the files. */
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
fp = NULL;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
if (es_fclose (fpout))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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. */
|
2010-08-12 13:43:46 +02:00
|
|
|
|
gnupg_remove (fname);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
#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);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
xfree (fname);
|
|
|
|
|
if (fpout)
|
|
|
|
|
{
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fpout);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (err && tmpfname)
|
2010-07-16 15:19:45 +02:00
|
|
|
|
gnupg_remove (tmpfname);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
{
|
2010-07-16 15:19:45 +02:00
|
|
|
|
estream_t fp;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
char *buffer;
|
|
|
|
|
size_t n;
|
|
|
|
|
gcry_md_hd_t md5;
|
|
|
|
|
gpg_err_code_t err;
|
|
|
|
|
|
|
|
|
|
buffer = xtrymalloc (65536);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
fp = buffer? es_fopen (fname, "rb") : NULL;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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));
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
for (;;)
|
|
|
|
|
{
|
2010-07-16 15:19:45 +02:00
|
|
|
|
n = es_fread (buffer, 1, 65536, fp);
|
|
|
|
|
if (n < 65536 && es_ferror (fp))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
log_error (_("error hashing `%s': %s\n"), fname, strerror (errno));
|
|
|
|
|
xfree (buffer);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
gcry_md_close (md5);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (!n)
|
|
|
|
|
break;
|
|
|
|
|
gcry_md_write (md5, buffer, n);
|
|
|
|
|
}
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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
|
2011-02-04 12:57:53 +01:00
|
|
|
|
unlock_db_file after using the file. */
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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"));
|
2011-02-04 12:57:53 +01:00
|
|
|
|
else
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
2010-12-14 20:17:58 +01:00
|
|
|
|
/* FIXME: Do we leak ENTRY? */
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static crl_cache_entry_t
|
2010-06-09 18:53:51 +02:00
|
|
|
|
find_entry (crl_cache_entry_t first, const char *issuer_hash)
|
|
|
|
|
{
|
|
|
|
|
while (first && (first->deleted || strcmp (issuer_hash, first->issuer_hash)))
|
|
|
|
|
first = first->next;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
return first;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Create a new CRL cache. This fucntion 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);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (err)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
void
|
2010-06-09 18:53:51 +02:00
|
|
|
|
crl_cache_deinit (void)
|
|
|
|
|
{
|
|
|
|
|
if (current_cache)
|
|
|
|
|
{
|
|
|
|
|
release_cache (current_cache);
|
|
|
|
|
current_cache = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Delete the cache from disk. Return 0 on success.*/
|
2011-02-04 12:57:53 +01:00
|
|
|
|
int
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
static crl_cache_result_t
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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"
|
2011-02-04 12:57:53 +01:00
|
|
|
|
" issuer id %s; update required\n"),
|
2010-06-09 18:53:51 +02:00
|
|
|
|
30, issuer_hash);
|
|
|
|
|
return CRL_CACHE_DONTKNOW;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
log_info (_("force-crl-refresh active for"
|
2011-02-04 12:57:53 +01:00
|
|
|
|
" issuer id %s; update required\n"),
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (rc == 1)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
n = cdb_datalen (cdb);
|
|
|
|
|
if (n != 16)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("WARNING: invalid cache record length for S/N "));
|
|
|
|
|
log_printhex ("", sn, snlen);
|
|
|
|
|
}
|
|
|
|
|
else if (opt.verbose)
|
|
|
|
|
{
|
|
|
|
|
unsigned char record[16];
|
|
|
|
|
char *tmp = hexify_data (sn, snlen);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
log_info (_("S/N %s is valid, it is not listed in the CRL\n"),
|
|
|
|
|
serialno );
|
|
|
|
|
xfree (serialno);
|
|
|
|
|
}
|
|
|
|
|
retval = CRL_CACHE_VALID;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
else
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
crl_cache_result_t
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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]);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
/* 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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
case CRL_CACHE_DONTKNOW:
|
2010-06-09 18:53:51 +02:00
|
|
|
|
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
case CRL_CACHE_CANTUSE:
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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
|
2011-02-04 12:57:53 +01:00
|
|
|
|
start_sig_check (ksba_crl_t crl, gcry_md_hd_t *md, int *algo)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
const char *algoid;
|
|
|
|
|
|
|
|
|
|
algoid = ksba_crl_get_digest_algo (crl);
|
|
|
|
|
*algo = gcry_md_map_name (algoid);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (!*algo)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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");
|
|
|
|
|
|
2011-02-04 12:57:53 +01:00
|
|
|
|
ksba_crl_set_hash_function (crl, HASH_FNC, *md);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (!n)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (err)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
log_error (_("converting S-expression failed: %s\n"),
|
|
|
|
|
gcry_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
/* 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);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (!n)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (err)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
|
2010-06-09 18:53:51 +02:00
|
|
|
|
algoname,
|
|
|
|
|
gcry_md_get_algo_dlen (algo), gcry_md_read (md, algo));
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (err)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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.
|
2011-02-04 12:57:53 +01:00
|
|
|
|
*/
|
|
|
|
|
static int
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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)
|
2011-02-04 12:57:53 +01:00
|
|
|
|
{
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
(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:
|
|
|
|
|
{
|
|
|
|
|
if (start_sig_check (crl, &md, &algo ))
|
|
|
|
|
goto failure;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
err = ksba_crl_get_update_times (crl, thisupdate, nextupdate);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("error getting update times of CRL: %s\n"),
|
2011-02-04 12:57:53 +01:00
|
|
|
|
gpg_strerror (err));
|
2010-06-09 18:53:51 +02:00
|
|
|
|
err = gpg_error (GPG_ERR_INV_CRL);
|
|
|
|
|
goto failure;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (opt.verbose || !*nextupdate)
|
2011-02-04 12:57:53 +01:00
|
|
|
|
log_info (_("update times of this CRL: this=%s next=%s\n"),
|
|
|
|
|
thisupdate, nextupdate);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
memcpy (record+1, rdate, 15);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
case KSBA_SR_END_ITEMS:
|
|
|
|
|
break;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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"));
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
err = finish_sig_check (crl, md, algo, crlissuer_cert);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
|
log_error (_("CRL signature verification failed: %s\n"),
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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 "
|
2011-02-04 12:57:53 +01:00
|
|
|
|
"issuer certificate: %s\n"),
|
2010-06-09 18:53:51 +02:00
|
|
|
|
gpg_strerror (err));
|
|
|
|
|
goto failure;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
break;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
default:
|
|
|
|
|
log_debug ("crl_parse_insert: unknown stop reason\n");
|
|
|
|
|
err = gpg_error (GPG_ERR_BUG);
|
|
|
|
|
goto failure;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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 ("");
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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
|
2010-10-18 14:59:19 +02:00
|
|
|
|
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
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-10-18 14:59:19 +02:00
|
|
|
|
*/
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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.*/
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
err2 = 0;
|
|
|
|
|
|
|
|
|
|
err = ksba_crl_new (&crl);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error (_("ksba_crl_new failed: %s\n"), gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
estream_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);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
if (!gnupg_remove (fname))
|
2010-06-09 18:53:51 +02:00
|
|
|
|
log_info (_("removed stale temporary cache file `%s'\n"), fname);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
else if (errno != ENOENT)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_EOF
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|| 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;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
entry->release_ptr = xtrymalloc (strlen (issuer_hash) + 1
|
2010-06-09 18:53:51 +02:00
|
|
|
|
+ strlen (issuer) + 1
|
2011-02-04 12:57:53 +01:00
|
|
|
|
+ strlen (url) + 1
|
2010-06-09 18:53:51 +02:00
|
|
|
|
+ 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);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
gnupg_copy_time (entry->this_update, thisupdate);
|
|
|
|
|
gnupg_copy_time (entry->next_update, nextupdate);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
/* 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);
|
2010-12-14 20:17:58 +01:00
|
|
|
|
|
|
|
|
|
/* Just in case close unused matching files. Actually we need this
|
|
|
|
|
only under Windows but saving file descriptors is never bad. */
|
|
|
|
|
{
|
|
|
|
|
int any;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
do
|
2010-12-14 20:17:58 +01:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
#ifdef HAVE_W32_SYSTEM
|
2010-08-12 13:43:46 +02:00
|
|
|
|
gnupg_remove (newfname);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
#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)
|
|
|
|
|
{
|
2010-07-16 15:19:45 +02:00
|
|
|
|
gnupg_remove (fname);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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
|
2010-07-16 15:19:45 +02:00
|
|
|
|
list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, estream_t fp)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
struct cdb_find cdbfp;
|
|
|
|
|
struct cdb *cdb;
|
|
|
|
|
int rc;
|
|
|
|
|
int warn = 0;
|
|
|
|
|
const unsigned char *s;
|
|
|
|
|
|
2010-07-16 15:19:45 +02:00
|
|
|
|
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 );
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fprintf (fp, " This Update:\t%s\n", e->this_update );
|
|
|
|
|
es_fprintf (fp, " Next Update:\t%s\n", e->next_update );
|
2010-07-16 15:19:45 +02:00
|
|
|
|
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");
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (e->authority_serialno && e->authority_issuer)
|
|
|
|
|
{
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs (" \t", fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
for (s=e->authority_issuer; *s; s++)
|
|
|
|
|
if (*s == '\x01')
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs ("\n \t", fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
else
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc (*s, fp);
|
|
|
|
|
es_putc ('\n', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fprintf (fp, " Trust Check:\t%s\n",
|
2010-07-16 15:19:45 +02:00
|
|
|
|
!e->user_trust_req? "[system]" :
|
|
|
|
|
e->check_trust_anchor? e->check_trust_anchor:"[missing]");
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
if ((e->invalid & 1))
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fp, _(" ERROR: The CRL will not be used "
|
|
|
|
|
"because it was still too old after an update!\n"));
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if ((e->invalid & 2))
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fp, _(" ERROR: The CRL will not be used "
|
|
|
|
|
"due to an unknown critical extension!\n"));
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if ((e->invalid & ~3))
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fp, _(" ERROR: The CRL will not be used\n"));
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
cdb = lock_db_file (cache, e);
|
|
|
|
|
if (!cdb)
|
|
|
|
|
return gpg_error (GPG_ERR_GENERAL);
|
|
|
|
|
|
|
|
|
|
if (!e->dbfile_checked)
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fp, _(" ERROR: This cached CRL may has been tampered with!\n"));
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_putc ('\n', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs (" ", fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
for (i = 0; i < n; i++)
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fp, "%02X", keyrecord[i]);
|
|
|
|
|
es_fputs (":\t reasons( ", fp);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (reason & KSBA_CRLREASON_UNSPECIFIED)
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fputs( "unspecified ", fp ), any = 1;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (reason & KSBA_CRLREASON_KEY_COMPROMISE )
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fputs( "key_compromise ", fp ), any = 1;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (reason & KSBA_CRLREASON_CA_COMPROMISE )
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fputs( "ca_compromise ", fp ), any = 1;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (reason & KSBA_CRLREASON_AFFILIATION_CHANGED )
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fputs( "affiliation_changed ", fp ), any = 1;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (reason & KSBA_CRLREASON_SUPERSEDED )
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fputs( "superseeded", fp ), any = 1;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION )
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fputs( "cessation_of_operation", fp ), any = 1;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (reason & KSBA_CRLREASON_CERTIFICATE_HOLD )
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fputs( "certificate_hold", fp ), any = 1;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (reason && !any)
|
2011-02-04 12:57:53 +01:00
|
|
|
|
es_fputs( "other", fp );
|
|
|
|
|
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fp, ") rdate: %.15s\n", record+1);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
}
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (rc)
|
|
|
|
|
log_error (_("error reading cache entry from db: %s\n"), strerror (rc));
|
|
|
|
|
|
|
|
|
|
unlock_db_file (cache, e);
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fprintf (fp, _("End CRL dump\n") );
|
|
|
|
|
es_putc ('\n', fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
|
|
|
|
|
return (rc||warn)? gpg_error (GPG_ERR_GENERAL) : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Print the contents of the CRL CACHE in a human readable format to
|
|
|
|
|
stream FP. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
gpg_error_t
|
|
|
|
|
crl_cache_list (estream_t fp)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
entry = entry->next )
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2010-07-16 15:19:45 +02:00
|
|
|
|
estream_t fp;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ksba_reader_t reader;
|
|
|
|
|
|
2010-07-16 15:19:45 +02:00
|
|
|
|
fp = es_fopen (filename, "r");
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_errno (errno);
|
|
|
|
|
log_error (_("can't open `%s': %s\n"), filename, strerror (errno));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
2010-08-06 15:52:01 +02:00
|
|
|
|
err = create_estream_ksba_reader (&reader, fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (!err)
|
|
|
|
|
{
|
2010-08-06 15:52:01 +02:00
|
|
|
|
err = crl_cache_insert (ctrl, filename, reader);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ksba_reader_release (reader);
|
|
|
|
|
}
|
2010-07-16 15:19:45 +02:00
|
|
|
|
es_fclose (fp);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Locate the corresponding CRL for the certificate CERT, read and
|
|
|
|
|
verify the CRL and store it in the cache. */
|
|
|
|
|
gpg_error_t
|
2011-02-04 12:57:53 +01:00
|
|
|
|
crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
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 )))
|
|
|
|
|
{
|
2011-02-04 12:57:53 +01:00
|
|
|
|
int name_seq;
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
any_dist_point = 1;
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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. */
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (opt.verbose)
|
|
|
|
|
log_info ("inserting CRL (reader %p)\n", reader);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
err = crl_cache_insert (ctrl, distpoint_uri, reader);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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;
|
|
|
|
|
}
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ksba_name_release (distpoint); distpoint = NULL;
|
|
|
|
|
|
|
|
|
|
/* We don't do anything with issuername_uri yet but we keep the
|
|
|
|
|
code for documentation. */
|
2011-02-04 12:57:53 +01:00
|
|
|
|
issuername_uri = ksba_name_get_uri (issuername, 0);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ksba_name_release (issuername); issuername = 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");
|
2011-02-04 12:57:53 +01:00
|
|
|
|
|
2010-06-09 18:53:51 +02:00
|
|
|
|
if (reader)
|
|
|
|
|
{
|
|
|
|
|
crl_close_reader (reader);
|
|
|
|
|
reader = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
issuer = ksba_cert_get_issuer (cert, 0);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
if (!issuer)
|
2010-06-09 18:53:51 +02:00
|
|
|
|
{
|
|
|
|
|
log_error ("oops: issuer missing in certificate\n");
|
2011-02-04 12:57:53 +01:00
|
|
|
|
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
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:
|
|
|
|
|
if (reader)
|
|
|
|
|
crl_close_reader (reader);
|
|
|
|
|
xfree (distpoint_uri);
|
|
|
|
|
xfree (issuername_uri);
|
2011-02-04 12:57:53 +01:00
|
|
|
|
ksba_name_release (distpoint);
|
|
|
|
|
ksba_name_release (issuername);
|
2010-06-09 18:53:51 +02:00
|
|
|
|
ksba_free (issuer);
|
|
|
|
|
return err;
|
|
|
|
|
}
|