Files
strongswan-ext/src/libcharon/plugins/stroke/stroke_cred.c
T
Tobias Brunner a9f3016ef3 stroke: Don't ignore %any as owner of shared secrets
If users want to associate secrets with any identity, let 'em. This is
also possible with vici and might help if e.g. the remote identity is
actually %any as that would match a PSK with local IP and %any better
than one with local and different remote IP.

Fixes #2497.
2017-12-22 10:33:27 +01:00

1514 lines
36 KiB
C

/*
* Copyright (C) 2008-2017 Tobias Brunner
* Copyright (C) 2008 Martin Willi
* 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.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_GLOB_H
#include <glob.h>
#endif
#include "stroke_cred.h"
#include <credentials/certificates/x509.h>
#include <credentials/certificates/crl.h>
#include <credentials/certificates/ac.h>
#include <credentials/containers/pkcs12.h>
#include <credentials/sets/mem_cred.h>
#include <credentials/sets/callback_cred.h>
#include <collections/linked_list.h>
#include <utils/lexparser.h>
#include <threading/rwlock.h>
#include <daemon.h>
/* configuration directories and files */
#define CONFIG_DIR IPSEC_CONFDIR
#define IPSEC_D_DIR CONFIG_DIR "/ipsec.d"
#define PRIVATE_KEY_DIR IPSEC_D_DIR "/private"
#define CERTIFICATE_DIR IPSEC_D_DIR "/certs"
#define CA_CERTIFICATE_DIR IPSEC_D_DIR "/cacerts"
#define AA_CERTIFICATE_DIR IPSEC_D_DIR "/aacerts"
#define ATTR_CERTIFICATE_DIR IPSEC_D_DIR "/acerts"
#define OCSP_CERTIFICATE_DIR IPSEC_D_DIR "/ocspcerts"
#define CRL_DIR IPSEC_D_DIR "/crls"
#define SECRETS_FILE CONFIG_DIR "/ipsec.secrets"
#define MAX_SECRETS_RECURSION 10
typedef struct private_stroke_cred_t private_stroke_cred_t;
/**
* private data of stroke_cred
*/
struct private_stroke_cred_t {
/**
* public functions
*/
stroke_cred_t public;
/**
* secrets file with credential information
*/
char *secrets_file;
/**
* credentials: end entity certs, attribute certs, CRLs, etc.
*/
mem_cred_t *creds;
/**
* Attribute Authority certificates
*/
mem_cred_t *aacerts;
/**
* ignore missing CA basic constraint (i.e. treat all certificates in
* ipsec.conf ca sections and ipsec.d/cacerts as CA certificates)
*/
bool force_ca_cert;
/**
* cache CRLs to disk?
*/
bool cachecrl;
/**
* CA certificate store
*/
stroke_ca_t *ca;
};
/** Length of smartcard specifier parts (module, keyid) */
#define SC_PART_LEN 128
/**
* Kind of smartcard specifier token
*/
typedef enum {
SC_FORMAT_SLOT_MODULE_KEYID,
SC_FORMAT_SLOT_KEYID,
SC_FORMAT_KEYID,
SC_FORMAT_INVALID,
} smartcard_format_t;
/**
* Parse a smartcard specifier token
*/
static smartcard_format_t parse_smartcard(char *smartcard, u_int *slot,
char *module, char *keyid)
{
/* The token has one of the following three formats:
* - %smartcard<slot>@<module>:<keyid>
* - %smartcard<slot>:<keyid>
* - %smartcard:<keyid>
*/
char buf[2 * SC_PART_LEN], *pos;
if (sscanf(smartcard, "%%smartcard%u@%255s", slot, buf) == 2)
{
pos = strchr(buf, ':');
if (!pos)
{
return SC_FORMAT_INVALID;
}
*pos++ = '\0';
snprintf(module, SC_PART_LEN, "%s", buf);
snprintf(keyid, SC_PART_LEN, "%s", pos);
return SC_FORMAT_SLOT_MODULE_KEYID;
}
if (sscanf(smartcard, "%%smartcard%u:%127s", slot, keyid) == 2)
{
return SC_FORMAT_SLOT_KEYID;
}
if (sscanf(smartcard, "%%smartcard:%127s", keyid) == 1)
{
return SC_FORMAT_KEYID;
}
return SC_FORMAT_INVALID;
}
/**
* Load a credential from a smartcard
*/
static certificate_t *load_from_smartcard(smartcard_format_t format,
u_int slot, char *module, char *keyid,
credential_type_t type, int subtype)
{
chunk_t chunk;
void *cred;
chunk = chunk_from_hex(chunk_create(keyid, strlen(keyid)), NULL);
switch (format)
{
case SC_FORMAT_SLOT_MODULE_KEYID:
cred = lib->creds->create(lib->creds, type, subtype,
BUILD_PKCS11_SLOT, slot,
BUILD_PKCS11_MODULE, module,
BUILD_PKCS11_KEYID, chunk, BUILD_END);
break;
case SC_FORMAT_SLOT_KEYID:
cred = lib->creds->create(lib->creds, type, subtype,
BUILD_PKCS11_SLOT, slot,
BUILD_PKCS11_KEYID, chunk, BUILD_END);
break;
case SC_FORMAT_KEYID:
cred = lib->creds->create(lib->creds, type, subtype,
BUILD_PKCS11_KEYID, chunk, BUILD_END);
break;
default:
cred = NULL;
break;
}
free(chunk.ptr);
return cred;
}
METHOD(stroke_cred_t, load_peer, certificate_t*,
private_stroke_cred_t *this, char *filename)
{
certificate_t *cert = NULL;
char path[PATH_MAX];
if (strpfx(filename, "%smartcard"))
{
smartcard_format_t format;
char module[SC_PART_LEN], keyid[SC_PART_LEN];
u_int slot;
format = parse_smartcard(filename, &slot, module, keyid);
if (format != SC_FORMAT_INVALID)
{
cert = (certificate_t*)load_from_smartcard(format,
slot, module, keyid, CRED_CERTIFICATE, CERT_X509);
}
}
else
{
if (*filename == '/')
{
snprintf(path, sizeof(path), "%s", filename);
}
else
{
snprintf(path, sizeof(path), "%s/%s", CERTIFICATE_DIR, filename);
}
cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_ANY,
BUILD_FROM_FILE, path,
BUILD_END);
}
if (cert)
{
cert = this->creds->add_cert_ref(this->creds, TRUE, cert);
DBG1(DBG_CFG, " loaded certificate \"%Y\" from '%s'",
cert->get_subject(cert), filename);
return cert;
}
DBG1(DBG_CFG, " loading certificate from '%s' failed", filename);
return NULL;
}
METHOD(stroke_cred_t, load_pubkey, certificate_t*,
private_stroke_cred_t *this, char *filename, identification_t *identity)
{
certificate_t *cert;
public_key_t *key;
char path[PATH_MAX];
builder_part_t build_part;
key_type_t type = KEY_ANY;
if (streq(filename, "%dns"))
{
return NULL;
}
if (strncaseeq(filename, "dns:", 4))
{ /* RFC 3110 format */
build_part = BUILD_BLOB_DNSKEY;
/* not a complete RR, only RSA supported */
type = KEY_RSA;
filename += 4;
}
else if (strncaseeq(filename, "ssh:", 4))
{ /* SSH key */
build_part = BUILD_BLOB_SSHKEY;
filename += 4;
}
else
{ /* try PKCS#1 by default */
build_part = BUILD_BLOB_ASN1_DER;
}
if (strncaseeq(filename, "0x", 2) || strncaseeq(filename, "0s", 2))
{
chunk_t printable_key, raw_key;
printable_key = chunk_create(filename + 2, strlen(filename) - 2);
raw_key = strncaseeq(filename, "0x", 2) ?
chunk_from_hex(printable_key, NULL) :
chunk_from_base64(printable_key, NULL);
key = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, type,
build_part, raw_key, BUILD_END);
chunk_free(&raw_key);
if (key)
{
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE,
CERT_TRUSTED_PUBKEY,
BUILD_PUBLIC_KEY, key,
BUILD_SUBJECT, identity,
BUILD_END);
type = key->get_type(key);
key->destroy(key);
if (cert)
{
cert = this->creds->add_cert_ref(this->creds, TRUE, cert);
DBG1(DBG_CFG, " loaded %N public key for \"%Y\"",
key_type_names, type, identity);
return cert;
}
}
DBG1(DBG_CFG, " loading public key for \"%Y\" failed", identity);
}
else
{
if (*filename == '/')
{
snprintf(path, sizeof(path), "%s", filename);
}
else
{
snprintf(path, sizeof(path), "%s/%s", CERTIFICATE_DIR, filename);
}
cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_TRUSTED_PUBKEY,
BUILD_FROM_FILE, path,
BUILD_SUBJECT, identity,
BUILD_END);
if (cert)
{
cert = this->creds->add_cert_ref(this->creds, TRUE, cert);
key = cert->get_public_key(cert);
type = key->get_type(key);
key->destroy(key);
DBG1(DBG_CFG, " loaded %N public key for \"%Y\" from '%s'",
key_type_names, type, identity, filename);
return cert;
}
DBG1(DBG_CFG, " loading public key for \"%Y\" from '%s' failed",
identity, filename);
}
return NULL;
}
/**
* Load a CA certificate, optionally force it to be one
*/
static certificate_t *load_ca_cert(char *filename, bool force_ca_cert)
{
certificate_t *cert = NULL;
char path[PATH_MAX];
if (strpfx(filename, "%smartcard"))
{
smartcard_format_t format;
char module[SC_PART_LEN], keyid[SC_PART_LEN];
u_int slot;
format = parse_smartcard(filename, &slot, module, keyid);
if (format != SC_FORMAT_INVALID)
{
cert = (certificate_t*)load_from_smartcard(format,
slot, module, keyid, CRED_CERTIFICATE, CERT_X509);
}
}
else
{
if (*filename == '/')
{
snprintf(path, sizeof(path), "%s", filename);
}
else
{
snprintf(path, sizeof(path), "%s/%s", CA_CERTIFICATE_DIR, filename);
}
if (force_ca_cert)
{ /* we treat this certificate as a CA certificate even if it has no
* CA basic constraint */
cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, path, BUILD_X509_FLAG, X509_CA,
BUILD_END);
}
else
{
cert = lib->creds->create(lib->creds,
CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, path,
BUILD_END);
}
}
if (cert)
{
x509_t *x509 = (x509_t*)cert;
if (!(x509->get_flags(x509) & X509_CA))
{
DBG1(DBG_CFG, " ca certificate \"%Y\" lacks ca basic constraint, "
"discarded", cert->get_subject(cert));
cert->destroy(cert);
return NULL;
}
DBG1(DBG_CFG, " loaded ca certificate \"%Y\" from '%s'",
cert->get_subject(cert), filename);
return cert;
}
return NULL;
}
/**
* Used by stroke_ca.c
*/
certificate_t *stroke_load_ca_cert(char *filename)
{
bool force_ca_cert;
force_ca_cert = lib->settings->get_bool(lib->settings,
"%s.plugins.stroke.ignore_missing_ca_basic_constraint",
FALSE, lib->ns);
return load_ca_cert(filename, force_ca_cert);
}
/**
* Load a CA certificate from disk
*/
static void load_x509_ca(private_stroke_cred_t *this, char *file,
mem_cred_t *creds)
{
certificate_t *cert;
cert = load_ca_cert(file, this->force_ca_cert);
if (cert)
{
cert = this->ca->get_cert_ref(this->ca, cert);
creds->add_cert(creds, TRUE, cert);
}
else
{
DBG1(DBG_CFG, " loading ca certificate from '%s' failed", file);
}
}
/**
* Load AA certificate with flags from disk
*/
static void load_x509_aa(private_stroke_cred_t *this,char *file,
mem_cred_t *creds)
{
certificate_t *cert;
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, file,
BUILD_X509_FLAG, X509_AA, BUILD_END);
if (cert)
{
DBG1(DBG_CFG, " loaded AA certificate \"%Y\" from '%s'",
cert->get_subject(cert), file);
creds->add_cert(creds, TRUE, cert);
}
else
{
DBG1(DBG_CFG, " loading AA certificate from '%s' failed", file);
}
}
/**
* Load a certificate with flags from disk
*/
static void load_x509(private_stroke_cred_t *this, char *file, x509_flag_t flag,
mem_cred_t *creds)
{
certificate_t *cert;
/* for all other flags, we add them to the certificate. */
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509,
BUILD_FROM_FILE, file,
BUILD_X509_FLAG, flag, BUILD_END);
if (cert)
{
DBG1(DBG_CFG, " loaded certificate \"%Y\" from '%s'",
cert->get_subject(cert), file);
creds->add_cert(creds, TRUE, cert);
}
else
{
DBG1(DBG_CFG, " loading certificate from '%s' failed", file);
}
}
/**
* Load a CRL from a file
*/
static void load_x509_crl(private_stroke_cred_t *this, char *file,
mem_cred_t *creds)
{
certificate_t *cert;
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
BUILD_FROM_FILE, file, BUILD_END);
if (cert)
{
DBG1(DBG_CFG, " loaded crl from '%s'", file);
creds->add_crl(creds, (crl_t*)cert);
}
else
{
DBG1(DBG_CFG, " loading crl from '%s' failed", file);
}
}
/**
* Load an attribute certificate from a file
*/
static void load_x509_ac(private_stroke_cred_t *this, char *file,
mem_cred_t *creds)
{
certificate_t *cert;
cert = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_AC,
BUILD_FROM_FILE, file, BUILD_END);
if (cert)
{
DBG1(DBG_CFG, " loaded attribute certificate from '%s'", file);
creds->add_cert(creds, FALSE, cert);
}
else
{
DBG1(DBG_CFG, " loading attribute certificate from '%s' failed", file);
}
}
/**
* load trusted certificates from a directory
*/
static void load_certdir(private_stroke_cred_t *this, char *path,
certificate_type_t type, x509_flag_t flag,
mem_cred_t *creds)
{
enumerator_t *enumerator;
struct stat st;
char *file;
enumerator = enumerator_create_directory(path);
if (enumerator)
{
while (enumerator->enumerate(enumerator, NULL, &file, &st))
{
if (!S_ISREG(st.st_mode))
{
/* skip special file */
continue;
}
switch (type)
{
case CERT_X509:
if (flag & X509_CA)
{
load_x509_ca(this, file, creds);
}
else if (flag & X509_AA)
{
load_x509_aa(this, file, creds);
}
else
{
load_x509(this, file, flag, creds);
}
break;
case CERT_X509_CRL:
load_x509_crl(this, file, creds);
break;
case CERT_X509_AC:
load_x509_ac(this, file, creds);
break;
default:
break;
}
}
enumerator->destroy(enumerator);
}
else
{
DBG1(DBG_CFG, " reading directory failed");
}
}
METHOD(credential_set_t, cache_cert, void,
private_stroke_cred_t *this, certificate_t *cert)
{
if (cert->get_type(cert) == CERT_X509_CRL && this->cachecrl)
{
/* CRLs get written to /etc/ipsec.d/crls/<authkeyId>.crl */
crl_t *crl = (crl_t*)cert;
cert->get_ref(cert);
if (this->creds->add_crl(this->creds, crl))
{
char buf[BUF_LEN];
chunk_t chunk, hex;
bool is_delta_crl;
is_delta_crl = crl->is_delta_crl(crl, NULL);
chunk = crl->get_authKeyIdentifier(crl);
hex = chunk_to_hex(chunk, NULL, FALSE);
snprintf(buf, sizeof(buf), "%s/%s%s.crl", CRL_DIR, hex.ptr,
is_delta_crl ? "_delta" : "");
free(hex.ptr);
if (cert->get_encoding(cert, CERT_ASN1_DER, &chunk))
{
if (chunk_write(chunk, buf, 022, TRUE))
{
DBG1(DBG_CFG, " written crl file '%s' (%d bytes)",
buf, chunk.len);
}
else
{
DBG1(DBG_CFG, " writing crl file '%s' failed: %s",
buf, strerror(errno));
}
free(chunk.ptr);
}
}
}
}
METHOD(stroke_cred_t, cachecrl, void,
private_stroke_cred_t *this, bool enabled)
{
DBG1(DBG_CFG, "crl caching to %s %s",
CRL_DIR, enabled ? "enabled" : "disabled");
this->cachecrl = enabled;
}
/**
* Convert a string of characters into a binary secret
* A string between single or double quotes is treated as ASCII characters
* A string prepended by 0x is treated as HEX and prepended by 0s as Base64
*/
static err_t extract_secret(chunk_t *secret, chunk_t *line)
{
chunk_t raw_secret;
char delimiter = ' ';
bool quotes = FALSE;
if (!eat_whitespace(line))
{
return "missing secret";
}
if (*line->ptr == '\'' || *line->ptr == '"')
{
quotes = TRUE;
delimiter = *line->ptr;
line->ptr++; line->len--;
}
if (!extract_token(&raw_secret, delimiter, line))
{
if (delimiter == ' ')
{
raw_secret = *line;
}
else
{
return "missing second delimiter";
}
}
if (quotes)
{
/* treat as an ASCII string */
*secret = chunk_clone(raw_secret);
return NULL;
}
/* treat 0x as hex, 0s as base64 */
if (raw_secret.len > 2)
{
if (strncasecmp("0x", raw_secret.ptr, 2) == 0)
{
*secret = chunk_from_hex(chunk_skip(raw_secret, 2), NULL);
return NULL;
}
if (strncasecmp("0s", raw_secret.ptr, 2) == 0)
{
*secret = chunk_from_base64(chunk_skip(raw_secret, 2), NULL);
return NULL;
}
}
*secret = chunk_clone(raw_secret);
return NULL;
}
/**
* Data for passphrase callback
*/
typedef struct {
/** cached passphrases */
mem_cred_t *cache;
/** socket we use for prompting */
FILE *prompt;
/** type of secret to unlock */
int type;
/** private key file */
char *path;
/** number of tries */
int try;
} passphrase_cb_data_t;
/**
* Callback function to receive passphrases
*/
static shared_key_t* passphrase_cb(passphrase_cb_data_t *data,
shared_key_type_t type, identification_t *me,
identification_t *other, id_match_t *match_me,
id_match_t *match_other)
{
static const int max_tries = 3;
shared_key_t *shared;
chunk_t secret;
char buf[256];
if (type != SHARED_ANY && type != SHARED_PRIVATE_KEY_PASS)
{
return NULL;
}
data->try++;
if (data->try > max_tries + 1)
{ /* another builder might call this after we gave up, fail silently */
return NULL;
}
if (data->try > max_tries)
{
fprintf(data->prompt, "Passphrase invalid, giving up.\n");
return NULL;
}
if (data->try > 1)
{
fprintf(data->prompt, "Passphrase invalid!\n");
}
fprintf(data->prompt, "%s '%s' is encrypted.\n",
data->type == CRED_PRIVATE_KEY ? "Private key" : "PKCS#12 file",
data->path);
fprintf(data->prompt, "Passphrase:\n");
if (fgets(buf, sizeof(buf), data->prompt))
{
secret = chunk_create(buf, strlen(buf));
if (secret.len > 1)
{ /* trim appended \n */
secret.len--;
if (match_me)
{
*match_me = ID_MATCH_PERFECT;
}
if (match_other)
{
*match_other = ID_MATCH_NONE;
}
shared = shared_key_create(SHARED_PRIVATE_KEY_PASS,
chunk_clone(secret));
data->cache->add_shared(data->cache, shared->get_ref(shared), NULL);
return shared;
}
}
return NULL;
}
/**
* Data for PIN callback
*/
typedef struct {
/** socket we use for prompting */
FILE *prompt;
/** card label */
char *card;
/** card keyid */
chunk_t keyid;
/** number of tries */
int try;
/** provided PIN */
shared_key_t *shared;
} pin_cb_data_t;
/**
* Callback function to receive PINs
*/
static shared_key_t* pin_cb(pin_cb_data_t *data, shared_key_type_t type,
identification_t *me, identification_t *other,
id_match_t *match_me, id_match_t *match_other)
{
chunk_t secret;
char buf[256];
if (type != SHARED_ANY && type != SHARED_PIN)
{
return NULL;
}
if (!me || !chunk_equals(me->get_encoding(me), data->keyid))
{
return NULL;
}
data->try++;
if (data->try > 1)
{
fprintf(data->prompt, "PIN invalid, aborting.\n");
return NULL;
}
fprintf(data->prompt, "Login to '%s' required\n", data->card);
fprintf(data->prompt, "PIN:\n");
if (fgets(buf, sizeof(buf), data->prompt))
{
secret = chunk_create(buf, strlen(buf));
if (secret.len > 1)
{ /* trim appended \n */
secret.len--;
if (match_me)
{
*match_me = ID_MATCH_PERFECT;
}
if (match_other)
{
*match_other = ID_MATCH_NONE;
}
DESTROY_IF(data->shared);
data->shared = shared_key_create(SHARED_PIN, chunk_clone(secret));
return data->shared->get_ref(data->shared);
}
}
return NULL;
}
/**
* Load a smartcard with a PIN
*/
static bool load_pin(mem_cred_t *secrets, chunk_t line, int line_nr,
FILE *prompt)
{
chunk_t sc = chunk_empty, secret = chunk_empty;
char smartcard[BUF_LEN], keyid[SC_PART_LEN], module[SC_PART_LEN];
private_key_t *key = NULL;
u_int slot;
chunk_t chunk;
shared_key_t *shared = NULL;
identification_t *id;
mem_cred_t *mem = NULL;
callback_cred_t *cb = NULL;
pin_cb_data_t pin_data;
smartcard_format_t format;
err_t ugh = extract_value(&sc, &line);
if (ugh != NULL)
{
DBG1(DBG_CFG, "line %d: %s", line_nr, ugh);
return FALSE;
}
if (sc.len == 0)
{
DBG1(DBG_CFG, "line %d: expected %%smartcard specifier", line_nr);
return FALSE;
}
snprintf(smartcard, sizeof(smartcard), "%.*s", (int)sc.len, sc.ptr);
smartcard[sizeof(smartcard) - 1] = '\0';
format = parse_smartcard(smartcard, &slot, module, keyid);
if (format == SC_FORMAT_INVALID)
{
DBG1(DBG_CFG, "line %d: the given %%smartcard specifier is not"
" supported or invalid", line_nr);
return FALSE;
}
if (!eat_whitespace(&line))
{
DBG1(DBG_CFG, "line %d: expected PIN", line_nr);
return FALSE;
}
ugh = extract_secret(&secret, &line);
if (ugh != NULL)
{
DBG1(DBG_CFG, "line %d: malformed PIN: %s", line_nr, ugh);
return FALSE;
}
chunk = chunk_from_hex(chunk_create(keyid, strlen(keyid)), NULL);
if (secret.len == 7 && strpfx(secret.ptr, "%prompt"))
{
free(secret.ptr);
if (!prompt)
{ /* no IO channel to prompt, skip */
chunk_clear(&chunk);
return TRUE;
}
/* use callback credential set to prompt for the pin */
pin_data = (pin_cb_data_t){
.prompt = prompt,
.card = smartcard,
.keyid = chunk,
};
cb = callback_cred_create_shared((void*)pin_cb, &pin_data);
lib->credmgr->add_local_set(lib->credmgr, &cb->set, FALSE);
}
else
{
/* provide our pin in a temporary credential set */
shared = shared_key_create(SHARED_PIN, secret);
id = identification_create_from_encoding(ID_KEY_ID, chunk);
mem = mem_cred_create();
mem->add_shared(mem, shared->get_ref(shared), id, NULL);
lib->credmgr->add_local_set(lib->credmgr, &mem->set, FALSE);
}
/* unlock: smartcard needs the pin and potentially calls public set */
key = (private_key_t*)load_from_smartcard(format, slot, module, keyid,
CRED_PRIVATE_KEY, KEY_ANY);
if (key)
{
DBG1(DBG_CFG, " loaded private key from %.*s", (int)sc.len, sc.ptr);
secrets->add_key(secrets, key);
}
if (mem)
{
if (!key)
{
shared->destroy(shared);
shared = NULL;
}
lib->credmgr->remove_local_set(lib->credmgr, &mem->set);
mem->destroy(mem);
}
if (cb)
{
if (key)
{
shared = pin_data.shared;
}
else
{
DESTROY_IF(pin_data.shared);
}
lib->credmgr->remove_local_set(lib->credmgr, &cb->set);
cb->destroy(cb);
}
if (shared)
{
id = identification_create_from_encoding(ID_KEY_ID, chunk);
secrets->add_shared(secrets, shared, id, NULL);
}
chunk_clear(&chunk);
return TRUE;
}
/**
* Load a private key or PKCS#12 container from a file
*/
static bool load_from_file(chunk_t line, int line_nr, FILE *prompt,
char *path, int type, int subtype,
void **result)
{
chunk_t filename;
chunk_t secret = chunk_empty;
err_t ugh = extract_value(&filename, &line);
if (ugh != NULL)
{
DBG1(DBG_CFG, "line %d: %s", line_nr, ugh);
return FALSE;
}
if (filename.len == 0)
{
DBG1(DBG_CFG, "line %d: empty filename", line_nr);
return FALSE;
}
if (*filename.ptr == '/')
{
/* absolute path name */
snprintf(path, PATH_MAX, "%.*s", (int)filename.len, filename.ptr);
}
else
{
/* relative path name */
snprintf(path, PATH_MAX, "%s/%.*s", PRIVATE_KEY_DIR,
(int)filename.len, filename.ptr);
}
/* check for optional passphrase */
if (eat_whitespace(&line))
{
ugh = extract_secret(&secret, &line);
if (ugh != NULL)
{
DBG1(DBG_CFG, "line %d: malformed passphrase: %s", line_nr, ugh);
return FALSE;
}
}
if (secret.len == 7 && strpfx(secret.ptr, "%prompt"))
{
callback_cred_t *cb;
passphrase_cb_data_t pp_data = {
.prompt = prompt,
.type = type,
.path = path,
.try = 0,
};
free(secret.ptr);
if (!prompt)
{
*result = NULL;
return TRUE;
}
/* add cache first so if valid passphrases are needed multiple times
* the callback is not called anymore */
pp_data.cache = mem_cred_create();
lib->credmgr->add_local_set(lib->credmgr, &pp_data.cache->set, FALSE);
/* use callback credential set to prompt for the passphrase */
cb = callback_cred_create_shared((void*)passphrase_cb, &pp_data);
lib->credmgr->add_local_set(lib->credmgr, &cb->set, FALSE);
*result = lib->creds->create(lib->creds, type, subtype,
BUILD_FROM_FILE, path, BUILD_END);
lib->credmgr->remove_local_set(lib->credmgr, &cb->set);
cb->destroy(cb);
lib->credmgr->remove_local_set(lib->credmgr, &pp_data.cache->set);
pp_data.cache->destroy(pp_data.cache);
}
else
{
mem_cred_t *mem = NULL;
shared_key_t *shared;
/* provide our pin in a temporary credential set */
shared = shared_key_create(SHARED_PRIVATE_KEY_PASS, secret);
mem = mem_cred_create();
mem->add_shared(mem, shared, NULL);
if (eat_whitespace(&line))
{ /* if there is a second passphrase add that too, could be needed for
* PKCS#12 files using different passwords for MAC and encryption */
ugh = extract_secret(&secret, &line);
if (ugh != NULL)
{
DBG1(DBG_CFG, "line %d: malformed passphrase: %s", line_nr, ugh);
mem->destroy(mem);
return FALSE;
}
shared = shared_key_create(SHARED_PRIVATE_KEY_PASS, secret);
mem->add_shared(mem, shared, NULL);
}
lib->credmgr->add_local_set(lib->credmgr, &mem->set, FALSE);
*result = lib->creds->create(lib->creds, type, subtype,
BUILD_FROM_FILE, path, BUILD_END);
lib->credmgr->remove_local_set(lib->credmgr, &mem->set);
mem->destroy(mem);
}
return TRUE;
}
/**
* Load a private key
*/
static bool load_private(mem_cred_t *secrets, chunk_t line, int line_nr,
FILE *prompt, key_type_t key_type)
{
char path[PATH_MAX];
private_key_t *key;
if (!load_from_file(line, line_nr, prompt, path, CRED_PRIVATE_KEY,
key_type, (void**)&key))
{
return FALSE;
}
if (key)
{
DBG1(DBG_CFG, " loaded %N private key from '%s'",
key_type_names, key->get_type(key), path);
secrets->add_key(secrets, key);
}
else
{
DBG1(DBG_CFG, " loading private key from '%s' failed", path);
}
return TRUE;
}
/**
* Load a PKCS#12 container
*/
static bool load_pkcs12(private_stroke_cred_t *this, mem_cred_t *secrets,
chunk_t line, int line_nr, FILE *prompt)
{
enumerator_t *enumerator;
char path[PATH_MAX];
certificate_t *cert;
private_key_t *key;
pkcs12_t *pkcs12;
if (!load_from_file(line, line_nr, prompt, path, CRED_CONTAINER,
CONTAINER_PKCS12, (void**)&pkcs12))
{
return FALSE;
}
if (!pkcs12)
{
DBG1(DBG_CFG, " loading credentials from '%s' failed", path);
return TRUE;
}
enumerator = pkcs12->create_cert_enumerator(pkcs12);
while (enumerator->enumerate(enumerator, &cert))
{
x509_t *x509 = (x509_t*)cert;
if (x509->get_flags(x509) & X509_CA)
{
DBG1(DBG_CFG, " loaded ca certificate \"%Y\" from '%s'",
cert->get_subject(cert), path);
}
else
{
DBG1(DBG_CFG, " loaded certificate \"%Y\" from '%s'",
cert->get_subject(cert), path);
}
this->creds->add_cert(this->creds, TRUE, cert->get_ref(cert));
}
enumerator->destroy(enumerator);
enumerator = pkcs12->create_key_enumerator(pkcs12);
while (enumerator->enumerate(enumerator, &key))
{
DBG1(DBG_CFG, " loaded %N private key from '%s'",
key_type_names, key->get_type(key), path);
secrets->add_key(secrets, key->get_ref(key));
}
enumerator->destroy(enumerator);
pkcs12->container.destroy(&pkcs12->container);
return TRUE;
}
/**
* Load a shared key
*/
static bool load_shared(mem_cred_t *secrets, chunk_t line, int line_nr,
shared_key_type_t type, chunk_t ids)
{
shared_key_t *shared_key;
linked_list_t *owners;
chunk_t secret = chunk_empty;
err_t ugh = extract_secret(&secret, &line);
if (ugh != NULL)
{
DBG1(DBG_CFG, "line %d: malformed secret: %s", line_nr, ugh);
return FALSE;
}
shared_key = shared_key_create(type, secret);
DBG1(DBG_CFG, " loaded %N secret for %s", shared_key_type_names, type,
ids.len > 0 ? (char*)ids.ptr : "%any");
DBG4(DBG_CFG, " secret: %#B", &secret);
owners = linked_list_create();
while (ids.len > 0)
{
chunk_t id;
ugh = extract_value(&id, &ids);
if (ugh != NULL)
{
DBG1(DBG_CFG, "line %d: %s", line_nr, ugh);
shared_key->destroy(shared_key);
owners->destroy_offset(owners, offsetof(identification_t, destroy));
return FALSE;
}
if (id.len == 0)
{
continue;
}
/* NULL terminate the ID string */
*(id.ptr + id.len) = '\0';
owners->insert_last(owners, identification_create_from_string(id.ptr));
}
if (!owners->get_count(owners))
{
owners->insert_last(owners,
identification_create_from_encoding(ID_ANY, chunk_empty));
}
secrets->add_shared_list(secrets, shared_key, owners);
return TRUE;
}
/**
* reload ipsec.secrets
*/
static void load_secrets(private_stroke_cred_t *this, mem_cred_t *secrets,
char *file, int level, FILE *prompt)
{
int line_nr = 0;
chunk_t *src, line;
DBG1(DBG_CFG, "loading secrets from '%s'", file);
src = chunk_map(file, FALSE);
if (!src)
{
DBG1(DBG_CFG, "opening secrets file '%s' failed: %s", file,
strerror(errno));
return;
}
if (!secrets)
{
secrets = mem_cred_create();
}
while (fetchline(src, &line))
{
chunk_t ids, token;
key_type_t key_type;
shared_key_type_t type;
line_nr++;
if (!eat_whitespace(&line))
{
continue;
}
if (line.len > strlen("include ") && strpfx(line.ptr, "include "))
{
char **expanded, *dir, pattern[PATH_MAX];
u_char *pos;
if (level > MAX_SECRETS_RECURSION)
{
DBG1(DBG_CFG, "maximum level of %d includes reached, ignored",
MAX_SECRETS_RECURSION);
continue;
}
/* terminate filename by space */
line = chunk_skip(line, strlen("include "));
pos = memchr(line.ptr, ' ', line.len);
if (pos)
{
line.len = pos - line.ptr;
}
if (line.len && line.ptr[0] == '/')
{
if (line.len + 1 > sizeof(pattern))
{
DBG1(DBG_CFG, "include pattern too long, ignored");
continue;
}
snprintf(pattern, sizeof(pattern), "%.*s",
(int)line.len, line.ptr);
}
else
{ /* use directory of current file if relative */
dir = path_dirname(file);
if (line.len + 1 + strlen(dir) + 1 > sizeof(pattern))
{
DBG1(DBG_CFG, "include pattern too long, ignored");
free(dir);
continue;
}
snprintf(pattern, sizeof(pattern), "%s/%.*s",
dir, (int)line.len, line.ptr);
free(dir);
}
#ifdef HAVE_GLOB_H
{
glob_t buf;
if (glob(pattern, GLOB_ERR, NULL, &buf) != 0)
{
DBG1(DBG_CFG, "expanding file expression '%s' failed",
pattern);
}
else
{
for (expanded = buf.gl_pathv; *expanded != NULL; expanded++)
{
load_secrets(this, secrets, *expanded, level + 1,
prompt);
}
}
globfree(&buf);
}
#else /* HAVE_GLOB_H */
/* if glob(3) is not available, try to load pattern directly */
load_secrets(this, secrets, pattern, level + 1, prompt);
#endif /* HAVE_GLOB_H */
continue;
}
if (line.len > 2 && strpfx(line.ptr, ": "))
{
/* no ids, skip the ':' */
ids = chunk_empty;
line.ptr++;
line.len--;
}
else if (extract_token_str(&ids, " : ", &line))
{
/* NULL terminate the extracted id string */
*(ids.ptr + ids.len) = '\0';
}
else
{
DBG1(DBG_CFG, "line %d: missing ' : ' separator", line_nr);
break;
}
if (!eat_whitespace(&line) || !extract_token(&token, ' ', &line))
{
DBG1(DBG_CFG, "line %d: missing token", line_nr);
break;
}
if (match("RSA", &token) || match("ECDSA", &token) ||
match("BLISS", &token) || match("PKCS8", &token))
{
if (match("RSA", &token))
{
key_type = KEY_RSA;
}
else if (match("ECDSA", &token))
{
key_type = KEY_ECDSA;
}
else if (match("BLISS", &token))
{
key_type = KEY_BLISS;
}
else
{
key_type = KEY_ANY;
}
if (!load_private(secrets, line, line_nr, prompt, key_type))
{
break;
}
}
else if (match("P12", &token))
{
if (!load_pkcs12(this, secrets, line, line_nr, prompt))
{
break;
}
}
else if (match("PIN", &token))
{
if (!load_pin(secrets, line, line_nr, prompt))
{
break;
}
}
else if ((match("PSK", &token) && (type = SHARED_IKE)) ||
(match("EAP", &token) && (type = SHARED_EAP)) ||
(match("NTLM", &token) && (type = SHARED_NT_HASH)) ||
(match("XAUTH", &token) && (type = SHARED_EAP)))
{
if (!load_shared(secrets, line, line_nr, type, ids))
{
break;
}
}
else
{
DBG1(DBG_CFG, "line %d: token must be either RSA, ECDSA, BLISS, "
"PKCS8 P12, PIN, PSK, EAP, XAUTH or NTLM", line_nr);
break;
}
}
chunk_unmap(src);
if (level == 0)
{ /* replace secrets in active credential set */
this->creds->replace_secrets(this->creds, secrets, FALSE);
secrets->destroy(secrets);
}
}
/**
* load all certificates from ipsec.d
*/
static void load_certs(private_stroke_cred_t *this)
{
mem_cred_t *creds;
DBG1(DBG_CFG, "loading ca certificates from '%s'",
CA_CERTIFICATE_DIR);
creds = mem_cred_create();
load_certdir(this, CA_CERTIFICATE_DIR, CERT_X509, X509_CA, creds);
this->ca->replace_certs(this->ca, creds);
creds->destroy(creds);
DBG1(DBG_CFG, "loading aa certificates from '%s'",
AA_CERTIFICATE_DIR);
load_certdir(this, AA_CERTIFICATE_DIR, CERT_X509, X509_AA, this->aacerts);
DBG1(DBG_CFG, "loading ocsp signer certificates from '%s'",
OCSP_CERTIFICATE_DIR);
load_certdir(this, OCSP_CERTIFICATE_DIR, CERT_X509, X509_OCSP_SIGNER,
this->creds);
DBG1(DBG_CFG, "loading attribute certificates from '%s'",
ATTR_CERTIFICATE_DIR);
load_certdir(this, ATTR_CERTIFICATE_DIR, CERT_X509_AC, 0, this->creds);
DBG1(DBG_CFG, "loading crls from '%s'",
CRL_DIR);
load_certdir(this, CRL_DIR, CERT_X509_CRL, 0, this->creds);
}
METHOD(stroke_cred_t, reread, void,
private_stroke_cred_t *this, stroke_msg_t *msg, FILE *prompt)
{
mem_cred_t *creds;
if (msg->reread.flags & REREAD_SECRETS)
{
DBG1(DBG_CFG, "rereading secrets");
load_secrets(this, NULL, this->secrets_file, 0, prompt);
}
if (msg->reread.flags & REREAD_CACERTS)
{
/* first reload certificates in ca sections, so we can refer to them */
this->ca->reload_certs(this->ca);
DBG1(DBG_CFG, "rereading ca certificates from '%s'",
CA_CERTIFICATE_DIR);
creds = mem_cred_create();
load_certdir(this, CA_CERTIFICATE_DIR, CERT_X509, X509_CA, creds);
this->ca->replace_certs(this->ca, creds);
creds->destroy(creds);
}
if (msg->reread.flags & REREAD_AACERTS)
{
DBG1(DBG_CFG, "rereading aa certificates from '%s'",
AA_CERTIFICATE_DIR);
creds = mem_cred_create();
load_certdir(this, AA_CERTIFICATE_DIR, CERT_X509, X509_AA, creds);
this->aacerts->replace_certs(this->aacerts, creds, FALSE);
creds->destroy(creds);
lib->credmgr->flush_cache(lib->credmgr, CERT_X509);
}
if (msg->reread.flags & REREAD_OCSPCERTS)
{
DBG1(DBG_CFG, "rereading ocsp signer certificates from '%s'",
OCSP_CERTIFICATE_DIR);
load_certdir(this, OCSP_CERTIFICATE_DIR, CERT_X509,
X509_OCSP_SIGNER, this->creds);
}
if (msg->reread.flags & REREAD_ACERTS)
{
DBG1(DBG_CFG, "rereading attribute certificates from '%s'",
ATTR_CERTIFICATE_DIR);
load_certdir(this, ATTR_CERTIFICATE_DIR, CERT_X509_AC, 0, this->creds);
}
if (msg->reread.flags & REREAD_CRLS)
{
DBG1(DBG_CFG, "rereading crls from '%s'",
CRL_DIR);
load_certdir(this, CRL_DIR, CERT_X509_CRL, 0, this->creds);
}
}
METHOD(stroke_cred_t, add_shared, void,
private_stroke_cred_t *this, shared_key_t *shared, linked_list_t *owners)
{
this->creds->add_shared_list(this->creds, shared, owners);
}
METHOD(stroke_cred_t, destroy, void,
private_stroke_cred_t *this)
{
lib->credmgr->remove_set(lib->credmgr, &this->aacerts->set);
lib->credmgr->remove_set(lib->credmgr, &this->creds->set);
this->aacerts->destroy(this->aacerts);
this->creds->destroy(this->creds);
free(this);
}
/*
* see header file
*/
stroke_cred_t *stroke_cred_create(stroke_ca_t *ca)
{
private_stroke_cred_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 = (void*)return_null,
.cache_cert = (void*)_cache_cert,
},
.reread = _reread,
.load_peer = _load_peer,
.load_pubkey = _load_pubkey,
.add_shared = _add_shared,
.cachecrl = _cachecrl,
.destroy = _destroy,
},
.secrets_file = lib->settings->get_str(lib->settings,
"%s.plugins.stroke.secrets_file", SECRETS_FILE,
lib->ns),
.creds = mem_cred_create(),
.aacerts = mem_cred_create(),
.ca = ca,
);
if (lib->settings->get_bool(lib->settings, "%s.cache_crls", FALSE, lib->ns))
{
cachecrl(this, TRUE);
}
lib->credmgr->add_set(lib->credmgr, &this->creds->set);
lib->credmgr->add_set(lib->credmgr, &this->aacerts->set);
this->force_ca_cert = lib->settings->get_bool(lib->settings,
"%s.plugins.stroke.ignore_missing_ca_basic_constraint",
FALSE, lib->ns);
load_certs(this);
load_secrets(this, NULL, this->secrets_file, 0, NULL);
return &this->public;
}