strongswan/src/libcharon/plugins/stroke/stroke_cred.c

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;
}