465 lines
10 KiB
C
465 lines
10 KiB
C
/*
|
|
* Copyright (C) 2011 Martin Willi
|
|
* Copyright (C) 2011 revosec AG
|
|
*
|
|
* This program 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. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program 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.
|
|
*/
|
|
|
|
#include "certexpire_export.h"
|
|
|
|
#include "certexpire_cron.h"
|
|
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
#include <errno.h>
|
|
|
|
#include <utils/debug.h>
|
|
#include <daemon.h>
|
|
#include <collections/hashtable.h>
|
|
#include <threading/mutex.h>
|
|
#include <credentials/certificates/x509.h>
|
|
|
|
typedef struct private_certexpire_export_t private_certexpire_export_t;
|
|
|
|
/**
|
|
* Private data of an certexpire_export_t object.
|
|
*/
|
|
struct private_certexpire_export_t {
|
|
|
|
/**
|
|
* Public certexpire_export_t interface.
|
|
*/
|
|
certexpire_export_t public;
|
|
|
|
/**
|
|
* hashtable caching local trustchains, mapping entry_t => entry_t
|
|
*/
|
|
hashtable_t *local;
|
|
|
|
/**
|
|
* hashtable caching remote trustchains, mapping entry_t => entry_t
|
|
*/
|
|
hashtable_t *remote;
|
|
|
|
/**
|
|
* Mutex to lock hashtables
|
|
*/
|
|
mutex_t *mutex;
|
|
|
|
/**
|
|
* Cronjob for export
|
|
*/
|
|
certexpire_cron_t *cron;
|
|
|
|
/**
|
|
* strftime() format to generate local CSV file
|
|
*/
|
|
char *local_path;
|
|
|
|
/**
|
|
* strftime() format to generate remote CSV file
|
|
*/
|
|
char *remote_path;
|
|
|
|
/**
|
|
* stftime() format of the exported expiration date
|
|
*/
|
|
char *format;
|
|
|
|
/**
|
|
* CSV field separator
|
|
*/
|
|
char *separator;
|
|
|
|
/**
|
|
* TRUE to use fixed field count, CA at end
|
|
*/
|
|
bool fixed_fields;
|
|
|
|
/**
|
|
* String to use in empty fields, if using fixed_fields
|
|
*/
|
|
char *empty_string;
|
|
|
|
/**
|
|
* Force export of all trustchains we have a private key for
|
|
*/
|
|
bool force;
|
|
};
|
|
|
|
/**
|
|
* Maximum number of expiration dates we store (for subject + IM CAs + CA)
|
|
*/
|
|
#define MAX_TRUSTCHAIN_LENGTH 7
|
|
|
|
/**
|
|
* Hashtable entry
|
|
*/
|
|
typedef struct {
|
|
/** certificate subject as subjectAltName or CN of a DN */
|
|
char id[128];
|
|
/** list of expiration dates, 0 if no certificate */
|
|
time_t expire[MAX_TRUSTCHAIN_LENGTH];
|
|
} entry_t;
|
|
|
|
/**
|
|
* Hashtable hash function
|
|
*/
|
|
static u_int hash(entry_t *key)
|
|
{
|
|
return chunk_hash(chunk_create(key->id, strlen(key->id)));
|
|
}
|
|
|
|
/**
|
|
* Hashtable equals function
|
|
*/
|
|
static bool equals(entry_t *a, entry_t *b)
|
|
{
|
|
return streq(a->id, b->id);
|
|
}
|
|
|
|
/**
|
|
* Export a single trustchain to a path
|
|
*/
|
|
static void export_csv(private_certexpire_export_t *this, char *path,
|
|
hashtable_t *chains)
|
|
{
|
|
enumerator_t *enumerator;
|
|
char buf[PATH_MAX];
|
|
entry_t *entry;
|
|
FILE *file;
|
|
struct tm tm;
|
|
time_t t;
|
|
int i;
|
|
|
|
t = time(NULL);
|
|
localtime_r(&t, &tm);
|
|
|
|
strftime(buf, sizeof(buf), path, &tm);
|
|
file = fopen(buf, "a");
|
|
if (file)
|
|
{
|
|
DBG1(DBG_CFG, "exporting expiration dates of %d trustchain%s to '%s'",
|
|
chains->get_count(chains),
|
|
chains->get_count(chains) == 1 ? "" : "s", buf);
|
|
this->mutex->lock(this->mutex);
|
|
enumerator = chains->create_enumerator(chains);
|
|
while (enumerator->enumerate(enumerator, NULL, &entry))
|
|
{
|
|
fprintf(file, "%s%s", entry->id, this->separator);
|
|
for (i = 0; i < MAX_TRUSTCHAIN_LENGTH; i++)
|
|
{
|
|
if (entry->expire[i])
|
|
{
|
|
localtime_r(&entry->expire[i], &tm);
|
|
strftime(buf, sizeof(buf), this->format, &tm);
|
|
fprintf(file, "%s", buf);
|
|
}
|
|
if (i == MAX_TRUSTCHAIN_LENGTH - 1)
|
|
{
|
|
fprintf(file, "\n");
|
|
}
|
|
else if (entry->expire[i])
|
|
{
|
|
fprintf(file, "%s", this->separator);
|
|
}
|
|
else if (this->fixed_fields)
|
|
{
|
|
fprintf(file, "%s%s", this->empty_string, this->separator);
|
|
}
|
|
}
|
|
chains->remove_at(chains, enumerator);
|
|
free(entry);
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
this->mutex->unlock(this->mutex);
|
|
fclose(file);
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_CFG, "opening CSV file '%s' failed: %s", buf, strerror(errno));
|
|
}
|
|
}
|
|
|
|
METHOD(certexpire_export_t, add, void,
|
|
private_certexpire_export_t *this, linked_list_t *trustchain, bool local)
|
|
{
|
|
enumerator_t *enumerator;
|
|
certificate_t *cert;
|
|
int count;
|
|
|
|
/* don't store expiration dates if no path configured */
|
|
if (local)
|
|
{
|
|
if (!this->local_path)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!this->remote_path)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
count = min(trustchain->get_count(trustchain), MAX_TRUSTCHAIN_LENGTH) - 1;
|
|
|
|
enumerator = trustchain->create_enumerator(trustchain);
|
|
/* get subject cert */
|
|
if (enumerator->enumerate(enumerator, &cert))
|
|
{
|
|
identification_t *id;
|
|
entry_t *entry;
|
|
int i;
|
|
|
|
INIT(entry);
|
|
|
|
/* prefer FQDN subjectAltName... */
|
|
if (cert->get_type(cert) == CERT_X509)
|
|
{
|
|
x509_t *x509 = (x509_t*)cert;
|
|
enumerator_t *sans;
|
|
|
|
sans = x509->create_subjectAltName_enumerator(x509);
|
|
while (sans->enumerate(sans, &id))
|
|
{
|
|
if (id->get_type(id) == ID_FQDN)
|
|
{
|
|
snprintf(entry->id, sizeof(entry->id), "%Y", id);
|
|
break;
|
|
}
|
|
}
|
|
sans->destroy(sans);
|
|
}
|
|
/* fallback to CN of DN */
|
|
if (!entry->id[0])
|
|
{
|
|
enumerator_t *parts;
|
|
id_part_t part;
|
|
chunk_t data;
|
|
|
|
id = cert->get_subject(cert);
|
|
parts = id->create_part_enumerator(id);
|
|
while (parts->enumerate(parts, &part, &data))
|
|
{
|
|
if (part == ID_PART_RDN_CN)
|
|
{
|
|
snprintf(entry->id, sizeof(entry->id), "%.*s",
|
|
(int)data.len, data.ptr);
|
|
break;
|
|
}
|
|
}
|
|
parts->destroy(parts);
|
|
}
|
|
/* no usable identity? skip */
|
|
if (!entry->id[0])
|
|
{
|
|
enumerator->destroy(enumerator);
|
|
free(entry);
|
|
return;
|
|
}
|
|
|
|
/* get intermediate CA expiration dates */
|
|
cert->get_validity(cert, NULL, NULL, &entry->expire[0]);
|
|
for (i = 1; i < count && enumerator->enumerate(enumerator, &cert); i++)
|
|
{
|
|
cert->get_validity(cert, NULL, NULL, &entry->expire[i]);
|
|
}
|
|
/* get CA expiration date, as last array entry */
|
|
if (enumerator->enumerate(enumerator, &cert))
|
|
{
|
|
cert->get_validity(cert, NULL, NULL,
|
|
&entry->expire[MAX_TRUSTCHAIN_LENGTH - 1]);
|
|
}
|
|
this->mutex->lock(this->mutex);
|
|
if (local)
|
|
{
|
|
entry = this->local->put(this->local, entry, entry);
|
|
}
|
|
else
|
|
{
|
|
entry = this->remote->put(this->remote, entry, entry);
|
|
}
|
|
this->mutex->unlock(this->mutex);
|
|
if (entry)
|
|
{
|
|
free(entry);
|
|
}
|
|
if (!this->cron)
|
|
{ /* export directly if no cron job defined */
|
|
if (local)
|
|
{
|
|
export_csv(this, this->local_path, this->local);
|
|
}
|
|
else
|
|
{
|
|
export_csv(this, this->remote_path, this->remote);
|
|
}
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
}
|
|
|
|
/**
|
|
* Add trustchains we have a private key for to the list
|
|
*/
|
|
static void add_local_certs(private_certexpire_export_t *this)
|
|
{
|
|
enumerator_t *enumerator;
|
|
certificate_t *cert;
|
|
|
|
enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr,
|
|
CERT_X509, KEY_ANY, NULL, FALSE);
|
|
while (enumerator->enumerate(enumerator, &cert))
|
|
{
|
|
linked_list_t *trustchain;
|
|
private_key_t *private;
|
|
public_key_t *public;
|
|
identification_t *keyid;
|
|
chunk_t chunk;
|
|
x509_t *x509 = (x509_t*)cert;
|
|
|
|
trustchain = linked_list_create();
|
|
|
|
public = cert->get_public_key(cert);
|
|
if (public)
|
|
{
|
|
if (public->get_fingerprint(public, KEYID_PUBKEY_INFO_SHA1, &chunk))
|
|
{
|
|
keyid = identification_create_from_encoding(ID_KEY_ID, chunk);
|
|
private = lib->credmgr->get_private(lib->credmgr,
|
|
public->get_type(public), keyid, NULL);
|
|
keyid->destroy(keyid);
|
|
if (private)
|
|
{
|
|
trustchain->insert_last(trustchain, cert->get_ref(cert));
|
|
|
|
while (!(x509->get_flags(x509) & X509_SELF_SIGNED))
|
|
{
|
|
cert = lib->credmgr->get_cert(lib->credmgr, CERT_X509,
|
|
KEY_ANY, cert->get_issuer(cert), FALSE);
|
|
if (!cert)
|
|
{
|
|
break;
|
|
}
|
|
x509 = (x509_t*)cert;
|
|
trustchain->insert_last(trustchain, cert);
|
|
}
|
|
private->destroy(private);
|
|
}
|
|
}
|
|
public->destroy(public);
|
|
}
|
|
add(this, trustchain, TRUE);
|
|
trustchain->destroy_offset(trustchain, offsetof(certificate_t, destroy));
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
}
|
|
|
|
/**
|
|
* Export cached trustchain expiration dates to CSV files
|
|
*/
|
|
static void cron_export(private_certexpire_export_t *this)
|
|
{
|
|
if (this->local_path)
|
|
{
|
|
if (this->force)
|
|
{
|
|
add_local_certs(this);
|
|
}
|
|
export_csv(this, this->local_path, this->local);
|
|
}
|
|
if (this->remote_path)
|
|
{
|
|
export_csv(this, this->remote_path, this->remote);
|
|
}
|
|
}
|
|
|
|
METHOD(certexpire_export_t, destroy, void,
|
|
private_certexpire_export_t *this)
|
|
{
|
|
entry_t *key, *value;
|
|
enumerator_t *enumerator;
|
|
|
|
enumerator = this->local->create_enumerator(this->local);
|
|
while (enumerator->enumerate(enumerator, &key, &value))
|
|
{
|
|
free(value);
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
enumerator = this->remote->create_enumerator(this->remote);
|
|
while (enumerator->enumerate(enumerator, &key, &value))
|
|
{
|
|
free(value);
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
|
|
this->local->destroy(this->local);
|
|
this->remote->destroy(this->remote);
|
|
DESTROY_IF(this->cron);
|
|
this->mutex->destroy(this->mutex);
|
|
free(this);
|
|
}
|
|
|
|
/**
|
|
* See header
|
|
*/
|
|
certexpire_export_t *certexpire_export_create()
|
|
{
|
|
private_certexpire_export_t *this;
|
|
char *cron;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.add = _add,
|
|
.destroy = _destroy,
|
|
},
|
|
.local = hashtable_create((hashtable_hash_t)hash,
|
|
(hashtable_equals_t)equals, 4),
|
|
.remote = hashtable_create((hashtable_hash_t)hash,
|
|
(hashtable_equals_t)equals, 32),
|
|
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
|
|
.local_path = lib->settings->get_str(lib->settings,
|
|
"%s.plugins.certexpire.csv.local",
|
|
NULL, lib->ns),
|
|
.remote_path = lib->settings->get_str(lib->settings,
|
|
"%s.plugins.certexpire.csv.remote",
|
|
NULL, lib->ns),
|
|
.separator = lib->settings->get_str(lib->settings,
|
|
"%s.plugins.certexpire.csv.separator",
|
|
",", lib->ns),
|
|
.format = lib->settings->get_str(lib->settings,
|
|
"%s.plugins.certexpire.csv.format",
|
|
"%d:%m:%Y", lib->ns),
|
|
.fixed_fields = lib->settings->get_bool(lib->settings,
|
|
"%s.plugins.certexpire.csv.fixed_fields",
|
|
TRUE, lib->ns),
|
|
.empty_string = lib->settings->get_str(lib->settings,
|
|
"%s.plugins.certexpire.csv.empty_string",
|
|
"", lib->ns),
|
|
.force = lib->settings->get_bool(lib->settings,
|
|
"%s.plugins.certexpire.csv.force",
|
|
TRUE, lib->ns),
|
|
);
|
|
|
|
cron = lib->settings->get_str(lib->settings,
|
|
"%s.plugins.certexpire.csv.cron",
|
|
NULL, lib->ns);
|
|
if (cron)
|
|
{
|
|
this->cron = certexpire_cron_create(cron,
|
|
(certexpire_cron_job_t)cron_export, this);
|
|
}
|
|
return &this->public;
|
|
}
|