strongswan/src/libcharon/plugins/vici/vici_authority.c

774 lines
16 KiB
C

/*
* Copyright (C) 2016-2019 Tobias Brunner
* Copyright (C) 2015 Andreas Steffen
* HSR Hochschule fuer Technik Rapperswil
*
* 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.
*/
#define _GNU_SOURCE
#include "vici_authority.h"
#include "vici_builder.h"
#include <threading/rwlock.h>
#include <collections/linked_list.h>
#include <credentials/certificates/x509.h>
#include <utils/debug.h>
#include <stdio.h>
typedef struct private_vici_authority_t private_vici_authority_t;
/**
* Private data of an vici_authority_t object.
*/
struct private_vici_authority_t {
/**
* Public vici_authority_t interface.
*/
vici_authority_t public;
/**
* Dispatcher
*/
vici_dispatcher_t *dispatcher;
/**
* credential backend managed by VICI used for our ca certificates
*/
vici_cred_t *cred;
/**
* List of certification authorities
*/
linked_list_t *authorities;
/**
* rwlock to lock access to certification authorities
*/
rwlock_t *lock;
};
typedef struct authority_t authority_t;
/**
* loaded certification authorities
*/
struct authority_t {
/**
* Name of the certification authority
*/
char *name;
/**
* Reference to CA certificate
*/
certificate_t *cert;
/**
* CRL URIs
*/
linked_list_t *crl_uris;
/**
* OCSP URIs
*/
linked_list_t *ocsp_uris;
/**
* Base URI used for certificates from this CA
*/
char *cert_uri_base;
};
/**
* create a new certification authority
*/
static authority_t *authority_create(char *name)
{
authority_t *authority;
INIT(authority,
.name = strdup(name),
.crl_uris = linked_list_create(),
.ocsp_uris = linked_list_create(),
);
return authority;
}
/**
* destroy a certification authority
*/
static void authority_destroy(authority_t *this)
{
this->crl_uris->destroy_function(this->crl_uris, free);
this->ocsp_uris->destroy_function(this->ocsp_uris, free);
DESTROY_IF(this->cert);
free(this->cert_uri_base);
free(this->name);
free(this);
}
/**
* Create a (error) reply message
*/
static vici_message_t* create_reply(char *fmt, ...)
{
vici_builder_t *builder;
va_list args;
builder = vici_builder_create();
builder->add_kv(builder, "success", fmt ? "no" : "yes");
if (fmt)
{
va_start(args, fmt);
builder->vadd_kv(builder, "errmsg", fmt, args);
va_end(args);
}
return builder->finalize(builder);
}
/**
* A rule to parse a key/value or list item
*/
typedef struct {
/** name of the key/value or list */
char *name;
/** function to parse value */
bool (*parse)(void *out, chunk_t value);
/** result, passed to parse() */
void *out;
} parse_rule_t;
/**
* Parse key/values using a rule-set
*/
static bool parse_rules(parse_rule_t *rules, int count, char *name,
chunk_t value, vici_message_t **reply)
{
int i;
for (i = 0; i < count; i++)
{
if (streq(name, rules[i].name))
{
if (rules[i].parse(rules[i].out, value))
{
return TRUE;
}
*reply = create_reply("invalid value for: %s, authority discarded",
name);
return FALSE;
}
}
*reply = create_reply("unknown option: %s, authority discarded", name);
return FALSE;
}
/**
* Parse callback data, passed to each callback
*/
typedef struct {
private_vici_authority_t *this;
vici_message_t *reply;
} request_data_t;
/**
* Data associated with an authority load
*/
typedef struct {
request_data_t *request;
authority_t *authority;
char *handle;
uint32_t slot;
char *module;
char *file;
} load_data_t;
/**
* Clean up data associated with an authority load
*/
static void free_load_data(load_data_t *data)
{
if (data->authority)
{
authority_destroy(data->authority);
}
free(data->handle);
free(data->module);
free(data->file);
free(data);
}
/**
* Parse a string
*/
CALLBACK(parse_string, bool,
char **str, chunk_t v)
{
if (!chunk_printable(v, NULL, ' '))
{
return FALSE;
}
*str = strndup(v.ptr, v.len);
return TRUE;
}
/**
* Parse a uint32_t
*/
CALLBACK(parse_uint32, bool,
uint32_t *out, chunk_t v)
{
char buf[16], *end;
u_long l;
if (!vici_stringify(v, buf, sizeof(buf)))
{
return FALSE;
}
l = strtoul(buf, &end, 0);
if (*end == 0)
{
*out = l;
return TRUE;
}
return FALSE;
}
/**
* Parse list of URIs
*/
CALLBACK(parse_uris, bool,
linked_list_t *out, chunk_t v)
{
char *uri;
if (!chunk_printable(v, NULL, ' '))
{
return FALSE;
}
uri = strndup(v.ptr, v.len);
out->insert_last(out, uri);
return TRUE;
}
/**
* Parse a CA certificate
*/
CALLBACK(parse_cacert, bool,
certificate_t **cacert, chunk_t v)
{
certificate_t *cert;
x509_t *x509;
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
BUILD_BLOB_PEM, v, BUILD_END);
if (!cert)
{
return create_reply("parsing %N certificate failed",
certificate_type_names, CERT_X509);
}
x509 = (x509_t*)cert;
if ((x509->get_flags(x509) & X509_CA) != X509_CA)
{
cert->destroy(cert);
return create_reply("certificate without CA flag, rejected");
}
*cacert = cert;
return TRUE;
}
CALLBACK(authority_kv, bool,
load_data_t *data, vici_message_t *message, char *name, chunk_t value)
{
parse_rule_t rules[] = {
{ "cacert", parse_cacert, &data->authority->cert },
{ "file", parse_string, &data->file },
{ "handle", parse_string, &data->handle },
{ "slot", parse_uint32, &data->slot },
{ "module", parse_string, &data->module },
{ "cert_uri_base", parse_string, &data->authority->cert_uri_base },
};
return parse_rules(rules, countof(rules), name, value,
&data->request->reply);
}
CALLBACK(authority_li, bool,
load_data_t *data, vici_message_t *message, char *name, chunk_t value)
{
parse_rule_t rules[] = {
{ "crl_uris", parse_uris, data->authority->crl_uris },
{ "ocsp_uris", parse_uris, data->authority->ocsp_uris },
};
return parse_rules(rules, countof(rules), name, value,
&data->request->reply);
}
static void log_authority_data(authority_t *authority)
{
enumerator_t *enumerator;
identification_t *subject;
bool first = TRUE;
char *uri;
subject = authority->cert->get_subject(authority->cert);
DBG2(DBG_CFG, " cacert = %Y", subject);
enumerator = authority->crl_uris->create_enumerator(authority->crl_uris);
while (enumerator->enumerate(enumerator, &uri))
{
if (first)
{
DBG2(DBG_CFG, " crl_uris = %s", uri);
first = FALSE;
}
else
{
DBG2(DBG_CFG, " %s", uri);
}
}
enumerator->destroy(enumerator);
first = TRUE;
enumerator = authority->ocsp_uris->create_enumerator(authority->ocsp_uris);
while (enumerator->enumerate(enumerator, &uri))
{
if (first)
{
DBG2(DBG_CFG, " ocsp_uris = %s", uri);
first = FALSE;
}
else
{
DBG2(DBG_CFG, " %s", uri);
}
}
enumerator->destroy(enumerator);
if (authority->cert_uri_base)
{
DBG2(DBG_CFG, " cert_uri_base = %s", authority->cert_uri_base);
}
}
CALLBACK(authority_sn, bool,
request_data_t *request, vici_message_t *message,
vici_parse_context_t *ctx, char *name)
{
enumerator_t *enumerator;
linked_list_t *authorities;
authority_t *authority;
vici_cred_t *cred;
load_data_t *data;
chunk_t handle;
INIT(data,
.request = request,
.authority = authority_create(name),
.slot = -1,
);
DBG2(DBG_CFG, " authority %s:", name);
if (!message->parse(message, ctx, NULL, authority_kv, authority_li, data))
{
free_load_data(data);
return FALSE;
}
if (!data->authority->cert)
{
if (data->file)
{
data->authority->cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, data->file, BUILD_END);
}
else if (data->handle)
{
handle = chunk_from_hex(chunk_from_str(data->handle), NULL);
if (data->slot != -1)
{
data->authority->cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_PKCS11_KEYID, handle,
BUILD_PKCS11_SLOT, data->slot,
data->module ? BUILD_PKCS11_MODULE : BUILD_END,
data->module, BUILD_END);
}
else
{
data->authority->cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_PKCS11_KEYID, handle,
data->module ? BUILD_PKCS11_MODULE : BUILD_END,
data->module, BUILD_END);
}
chunk_free(&handle);
}
}
if (!data->authority->cert)
{
request->reply = create_reply("CA certificate missing: %s", name);
free_load_data(data);
return FALSE;
}
log_authority_data(data->authority);
request->this->lock->write_lock(request->this->lock);
authorities = request->this->authorities;
enumerator = authorities->create_enumerator(authorities);
while (enumerator->enumerate(enumerator, &authority))
{
if (streq(authority->name, name))
{
/* remove the old authority definition */
authorities->remove_at(authorities, enumerator);
authority_destroy(authority);
break;
}
}
enumerator->destroy(enumerator);
authorities->insert_last(authorities, data->authority);
cred = request->this->cred;
data->authority->cert = cred->add_cert(cred, data->authority->cert);
data->authority = NULL;
request->this->lock->unlock(request->this->lock);
free_load_data(data);
return TRUE;
}
CALLBACK(load_authority, vici_message_t*,
private_vici_authority_t *this, char *name, u_int id, vici_message_t *message)
{
request_data_t request = {
.this = this,
};
if (!message->parse(message, NULL, authority_sn, NULL, NULL, &request))
{
if (request.reply)
{
return request.reply;
}
return create_reply("parsing request failed");
}
return create_reply(NULL);
}
CALLBACK(unload_authority, vici_message_t*,
private_vici_authority_t *this, char *name, u_int id, vici_message_t *message)
{
enumerator_t *enumerator;
authority_t *authority;
char *authority_name;
bool found = FALSE;
authority_name = message->get_str(message, NULL, "name");
if (!authority_name)
{
return create_reply("unload: missing authority name");
}
this->lock->write_lock(this->lock);
enumerator = this->authorities->create_enumerator(this->authorities);
while (enumerator->enumerate(enumerator, &authority))
{
if (streq(authority->name, authority_name))
{
this->authorities->remove_at(this->authorities, enumerator);
authority_destroy(authority);
found = TRUE;
break;
}
}
enumerator->destroy(enumerator);
this->lock->unlock(this->lock);
if (!found)
{
return create_reply("unload: authority '%s' not found", authority_name);
}
return create_reply(NULL);
}
CALLBACK(get_authorities, vici_message_t*,
private_vici_authority_t *this, char *name, u_int id,
vici_message_t *message)
{
vici_builder_t *builder;
enumerator_t *enumerator;
authority_t *authority;
builder = vici_builder_create();
builder->begin_list(builder, "authorities");
this->lock->read_lock(this->lock);
enumerator = this->authorities->create_enumerator(this->authorities);
while (enumerator->enumerate(enumerator, &authority))
{
builder->add_li(builder, "%s", authority->name);
}
enumerator->destroy(enumerator);
this->lock->unlock(this->lock);
builder->end_list(builder);
return builder->finalize(builder);
}
CALLBACK(list_authorities, vici_message_t*,
private_vici_authority_t *this, char *name, u_int id, vici_message_t *request)
{
enumerator_t *enumerator, *e;
authority_t *authority;
vici_builder_t *b;
char *str, *uri;
str = request->get_str(request, NULL, "name");
this->lock->read_lock(this->lock);
enumerator = this->authorities->create_enumerator(this->authorities);
while (enumerator->enumerate(enumerator, &authority))
{
if (str && !streq(str, authority->name))
{
continue;
}
b = vici_builder_create();
/* open authority section */
b->begin_section(b, authority->name);
/* subject DN of cacert */
b->add_kv(b, "cacert", "%Y",
authority->cert->get_subject(authority->cert));
/* list of crl_uris */
b->begin_list(b, "crl_uris");
e = authority->crl_uris->create_enumerator(authority->crl_uris);
while (e->enumerate(e, &uri))
{
b->add_li(b, "%s", uri);
}
e->destroy(e);
b->end_list(b);
/* list of ocsp_uris */
b->begin_list(b, "ocsp_uris");
e = authority->ocsp_uris->create_enumerator(authority->ocsp_uris);
while (e->enumerate(e, &uri))
{
b->add_li(b, "%s", uri);
}
e->destroy(e);
b->end_list(b);
/* cert_uri_base */
if (authority->cert_uri_base)
{
b->add_kv(b, "cert_uri_base", "%s", authority->cert_uri_base);
}
/* close authority and raise event */
b->end_section(b);
this->dispatcher->raise_event(this->dispatcher, "list-authority", id,
b->finalize(b));
}
enumerator->destroy(enumerator);
this->lock->unlock(this->lock);
b = vici_builder_create();
return b->finalize(b);
}
static void manage_command(private_vici_authority_t *this,
char *name, vici_command_cb_t cb, bool reg)
{
this->dispatcher->manage_command(this->dispatcher, name,
reg ? cb : NULL, this);
}
/**
* (Un-)register dispatcher functions
*/
static void manage_commands(private_vici_authority_t *this, bool reg)
{
this->dispatcher->manage_event(this->dispatcher, "list-authority", reg);
manage_command(this, "load-authority", load_authority, reg);
manage_command(this, "unload-authority", unload_authority, reg);
manage_command(this, "get-authorities", get_authorities, reg);
manage_command(this, "list-authorities", list_authorities, reg);
}
/**
* data to pass to create_inner_cdp
*/
typedef struct {
private_vici_authority_t *this;
certificate_type_t type;
identification_t *id;
} cdp_data_t;
/**
* destroy cdp enumerator data and unlock list
*/
static void cdp_data_destroy(cdp_data_t *data)
{
data->this->lock->unlock(data->this->lock);
free(data);
}
/**
* inner enumerator constructor for CDP URIs
*/
static enumerator_t *create_inner_cdp(authority_t *authority, cdp_data_t *data)
{
public_key_t *public;
enumerator_t *enumerator = NULL;
linked_list_t *list;
if (data->type == CERT_X509_OCSP_RESPONSE)
{
list = authority->ocsp_uris;
}
else
{
list = authority->crl_uris;
}
public = authority->cert->get_public_key(authority->cert);
if (public)
{
if (!data->id)
{
enumerator = list->create_enumerator(list);
}
else
{
if (public->has_fingerprint(public, data->id->get_encoding(data->id)))
{
enumerator = list->create_enumerator(list);
}
}
public->destroy(public);
}
return enumerator;
}
/**
* inner enumerator constructor for "Hash and URL"
*/
static enumerator_t *create_inner_cdp_hashandurl(authority_t *authority,
cdp_data_t *data)
{
enumerator_t *enumerator = NULL;
if (!data->id || !authority->cert_uri_base)
{
return NULL;
}
if (authority->cert->has_subject(authority->cert, data->id) != ID_MATCH_NONE)
{
enumerator = enumerator_create_single(strdup(authority->cert_uri_base),
free);
}
return enumerator;
}
METHOD(credential_set_t, create_cdp_enumerator, enumerator_t*,
private_vici_authority_t *this, certificate_type_t type,
identification_t *id)
{
cdp_data_t *data;
switch (type)
{ /* we serve CRLs, OCSP responders and URLs for "Hash and URL" */
case CERT_X509:
case CERT_X509_CRL:
case CERT_X509_OCSP_RESPONSE:
case CERT_ANY:
break;
default:
return NULL;
}
data = malloc_thing(cdp_data_t);
data->this = this;
data->type = type;
data->id = id;
this->lock->read_lock(this->lock);
return enumerator_create_nested(
this->authorities->create_enumerator(this->authorities),
(type == CERT_X509) ? (void*)create_inner_cdp_hashandurl :
(void*)create_inner_cdp, data, (void*)cdp_data_destroy);
}
METHOD(vici_authority_t, destroy, void,
private_vici_authority_t *this)
{
manage_commands(this, FALSE);
this->authorities->destroy_function(this->authorities,
(void*)authority_destroy);
this->lock->destroy(this->lock);
free(this);
}
/**
* See header
*/
vici_authority_t *vici_authority_create(vici_dispatcher_t *dispatcher,
vici_cred_t *cred)
{
private_vici_authority_t *this;
INIT(this,
.public = {
.set = {
.create_private_enumerator = (void*)return_null,
.create_cert_enumerator = (void*)return_null,
.create_shared_enumerator = (void*)return_null,
.create_cdp_enumerator = _create_cdp_enumerator,
.cache_cert = (void*)nop,
},
.destroy = _destroy,
},
.dispatcher = dispatcher,
.cred = cred,
.authorities = linked_list_create(),
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
);
manage_commands(this, TRUE);
return &this->public;
}