strongswan/src/pluto/keys.c

1345 lines
31 KiB
C

/* mechanisms for preshared keys (public, private, and preshared secrets)
* Copyright (C) 1998-2001 D. Hugh Redelmeier.
* Copyright (C) 2009 Andreas Steffen - 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 <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <resolv.h>
#include <arpa/nameser.h> /* missing from <resolv.h> on old systems */
#include <sys/queue.h>
#include <glob.h>
#ifndef GLOB_ABORTED
# define GLOB_ABORTED GLOB_ABEND /* fix for old versions */
#endif
#include <freeswan.h>
#include <library.h>
#include <asn1/asn1.h>
#include <credentials/certificates/pgp_certificate.h>
#include "constants.h"
#include "defs.h"
#include "x509.h"
#include "certs.h"
#include "smartcard.h"
#include "connections.h"
#include "state.h"
#include "lex.h"
#include "keys.h"
#include "adns.h" /* needs <resolv.h> */
#include "dnskey.h" /* needs keys.h and adns.h */
#include "log.h"
#include "whack.h" /* for RC_LOG_SERIOUS */
#include "timer.h"
#include "fetch.h"
const char *shared_secrets_file = SHARED_SECRETS_FILE;
typedef enum secret_kind_t secret_kind_t;
enum secret_kind_t {
SECRET_PSK,
SECRET_PUBKEY,
SECRET_XAUTH,
SECRET_PIN
};
typedef struct secret_t secret_t;
struct secret_t {
linked_list_t *ids;
secret_kind_t kind;
union {
chunk_t preshared_secret;
private_key_t *private_key;
smartcard_t *smartcard;
} u;
secret_t *next;
};
/*
* free a public key struct
*/
static void free_public_key(pubkey_t *pk)
{
DESTROY_IF(pk->id);
DESTROY_IF(pk->public_key);
DESTROY_IF(pk->issuer);
free(pk->serial.ptr);
free(pk);
}
secret_t *secrets = NULL;
/**
* Find the secret associated with the combination of me and the peer.
*/
const secret_t* match_secret(identification_t *my_id, identification_t *his_id,
secret_kind_t kind)
{
enum { /* bits */
match_default = 0x01,
match_him = 0x02,
match_me = 0x04
};
unsigned int best_match = 0;
secret_t *s, *best = NULL;
for (s = secrets; s != NULL; s = s->next)
{
unsigned int match = 0;
if (s->kind != kind)
{
continue;
}
if (s->ids->get_count(s->ids) == 0)
{
/* a default (signified by lack of ids):
* accept if no more specific match found
*/
match = match_default;
}
else
{
/* check if both ends match ids */
enumerator_t *enumerator;
identification_t *id;
enumerator = s->ids->create_enumerator(s->ids);
while (enumerator->enumerate(enumerator, &id))
{
if (my_id->equals(my_id, id))
{
match |= match_me;
}
if (his_id->equals(his_id, id))
{
match |= match_him;
}
}
enumerator->destroy(enumerator);
/* If our end matched the only id in the list,
* default to matching any peer.
* A more specific match will trump this.
*/
if (match == match_me && s->ids->get_count(s->ids) == 1)
{
match |= match_default;
}
}
switch (match)
{
case match_me:
/* if this is an asymmetric (eg. public key) system,
* allow this-side-only match to count, even if
* there are other ids in the list.
*/
if (kind != SECRET_PUBKEY)
{
break;
}
/* FALLTHROUGH */
case match_default: /* default all */
case match_me | match_default: /* default peer */
case match_me | match_him: /* explicit */
if (match == best_match)
{
/* two good matches are equally good: do they agree? */
bool same = FALSE;
switch (kind)
{
case SECRET_PSK:
case SECRET_XAUTH:
same = chunk_equals(s->u.preshared_secret,
best->u.preshared_secret);
break;
case SECRET_PUBKEY:
same = s->u.private_key->equals(s->u.private_key,
best->u.private_key);
break;
default:
bad_case(kind);
}
if (!same)
{
loglog(RC_LOG_SERIOUS, "multiple ipsec.secrets entries with "
"distinct secrets match endpoints: first secret used");
best = s; /* list is backwards: take latest in list */
}
}
else if (match > best_match)
{
/* this is the best match so far */
best_match = match;
best = s;
}
}
}
return best;
}
/**
* Retrieves an XAUTH secret primarily based on the user ID and
* secondarily based on the server ID
*/
bool get_xauth_secret(identification_t *user, identification_t *server,
chunk_t *secret)
{
const secret_t *s;
s = match_secret(user, server, SECRET_XAUTH);
if (s)
{
*secret = chunk_clone(s->u.preshared_secret);
return TRUE;
}
else
{
*secret = chunk_empty;
return FALSE;
}
}
/**
* We match the ID (if none, the IP address). Failure is indicated by a NULL.
*/
static const secret_t* get_secret(const connection_t *c, secret_kind_t kind)
{
identification_t *my_id, *his_id;
const secret_t *best;
my_id = c->spd.this.id;
if (his_id_was_instantiated(c))
{
/* roadwarrior: replace him with 0.0.0.0 */
his_id = identification_create_from_string("%any");
}
else if (kind == SECRET_PSK && (c->policy & (POLICY_PSK | POLICY_XAUTH_PSK)) &&
((c->kind == CK_TEMPLATE &&
c->spd.that.id->get_type(c->spd.that.id) == ID_ANY) ||
(c->kind == CK_INSTANCE && id_is_ipaddr(c->spd.that.id))))
{
/* roadwarrior: replace him with 0.0.0.0 */
his_id = identification_create_from_string("%any");
}
else
{
his_id = c->spd.that.id->clone(c->spd.that.id);
}
best = match_secret(my_id, his_id, kind);
his_id->destroy(his_id);
return best;
}
/* find the appropriate preshared key (see get_secret).
* Failure is indicated by a NULL pointer.
* Note: the result is not to be freed by the caller.
*/
const chunk_t* get_preshared_secret(const connection_t *c)
{
const secret_t *s = get_secret(c, SECRET_PSK);
DBG(DBG_PRIVATE,
if (s == NULL)
DBG_log("no Preshared Key Found");
else
DBG_dump_chunk("Preshared Key", s->u.preshared_secret);
)
return s == NULL? NULL : &s->u.preshared_secret;
}
/* check the existence of a private key matching a public key contained
* in an X.509 or OpenPGP certificate
*/
bool has_private_key(cert_t *cert)
{
secret_t *s;
bool has_key = FALSE;
public_key_t *pub_key = cert->cert->get_public_key(cert->cert);
for (s = secrets; s != NULL; s = s->next)
{
if (s->kind == SECRET_PUBKEY &&
s->u.private_key->belongs_to(s->u.private_key, pub_key))
{
has_key = TRUE;
break;
}
}
pub_key->destroy(pub_key);
return has_key;
}
/*
* get the matching private key belonging to a given X.509 certificate
*/
private_key_t* get_x509_private_key(const cert_t *cert)
{
public_key_t *public_key = cert->cert->get_public_key(cert->cert);
private_key_t *private_key = NULL;
secret_t *s;
for (s = secrets; s != NULL; s = s->next)
{
if (s->kind == SECRET_PUBKEY &&
s->u.private_key->belongs_to(s->u.private_key, public_key))
{
private_key = s->u.private_key;
break;
}
}
public_key->destroy(public_key);
return private_key;
}
/* find the appropriate private key (see get_secret).
* Failure is indicated by a NULL pointer.
*/
private_key_t* get_private_key(const connection_t *c)
{
const secret_t *s, *best = NULL;
/* is a certificate assigned to this connection? */
if (c->spd.this.cert)
{
certificate_t *certificate;
public_key_t *pub_key;
certificate = c->spd.this.cert->cert;
pub_key = certificate->get_public_key(certificate);
for (s = secrets; s != NULL; s = s->next)
{
if (s->kind == SECRET_PUBKEY &&
s->u.private_key->belongs_to(s->u.private_key, pub_key))
{
best = s;
break; /* found the private key - no sense in searching further */
}
}
pub_key->destroy(pub_key);
}
else
{
best = get_secret(c, SECRET_PUBKEY);
}
return best ? best->u.private_key : NULL;
}
/* digest a secrets file
*
* The file is a sequence of records. A record is a maximal sequence of
* tokens such that the first, and only the first, is in the first column
* of a line.
*
* Tokens are generally separated by whitespace and are key words, ids,
* strings, or data suitable for ttodata(3). As a nod to convention,
* a trailing ":" on what would otherwise be a token is taken as a
* separate token. If preceded by whitespace, a "#" is taken as starting
* a comment: it and the rest of the line are ignored.
*
* One kind of record is an include directive. It starts with "include".
* The filename is the only other token in the record.
* If the filename does not start with /, it is taken to
* be relative to the directory containing the current file.
*
* The other kind of record describes a key. It starts with a
* sequence of ids and ends with key information. Each id
* is an IP address, a Fully Qualified Domain Name (which will immediately
* be resolved), or @FQDN which will be left as a name.
*
* The key part can be in several forms.
*
* The old form of the key is still supported: a simple
* quoted strings (with no escapes) is taken as a preshred key.
*
* The new form starts the key part with a ":".
*
* For Preshared Key, use the "PSK" keyword, and follow it by a string
* or a data token suitable for ttodata(3).
*
* For RSA Private Key, use the "RSA" keyword, followed by a
* brace-enclosed list of key field keywords and data values.
* The data values are large integers to be decoded by ttodata(3).
* The fields are a subset of those used by BIND 8.2 and have the
* same names.
*/
/* parse PSK from file */
static err_t process_psk_secret(chunk_t *psk)
{
err_t ugh = NULL;
if (*tok == '"' || *tok == '\'')
{
chunk_t secret = { tok + 1, flp->cur - tok -2 };
*psk = chunk_clone(secret);
(void) shift();
}
else
{
char buf[BUF_LEN]; /* limit on size of binary representation of key */
size_t sz;
ugh = ttodatav(tok, flp->cur - tok, 0, buf, sizeof(buf), &sz
, diag_space, sizeof(diag_space), TTODATAV_SPACECOUNTS);
if (ugh != NULL)
{
/* ttodata didn't like PSK data */
ugh = builddiag("PSK data malformed (%s): %s", ugh, tok);
}
else
{
chunk_t secret = { buf, sz };
*psk = chunk_clone(secret);
(void) shift();
}
}
return ugh;
}
typedef enum rsa_private_key_part_t rsa_private_key_part_t;
enum rsa_private_key_part_t {
RSA_PART_MODULUS = 0,
RSA_PART_PUBLIC_EXPONENT = 1,
RSA_PART_PRIVATE_EXPONENT = 2,
RSA_PART_PRIME1 = 3,
RSA_PART_PRIME2 = 4,
RSA_PART_EXPONENT1 = 5,
RSA_PART_EXPONENT2 = 6,
RSA_PART_COEFFICIENT = 7
};
const char *rsa_private_key_part_names[] = {
"Modulus",
"PublicExponent",
"PrivateExponent",
"Prime1",
"Prime2",
"Exponent1",
"Exponent2",
"Coefficient"
};
/**
* Parse fields of an RSA private key in BIND 8.2's representation
* consistiong of a braced list of keyword and value pairs in required order.
*/
static err_t process_rsa_secret(private_key_t **key)
{
chunk_t rsa_chunk[countof(rsa_private_key_part_names)];
u_char buf[RSA_MAX_ENCODING_BYTES]; /* limit on size of binary representation of key */
rsa_private_key_part_t part, p;
size_t sz;
err_t ugh;
for (part = RSA_PART_MODULUS; part <= RSA_PART_COEFFICIENT; part++)
{
const char *keyword = rsa_private_key_part_names[part];
if (!shift())
{
ugh = "premature end of RSA key";
goto end;
}
if (!tokeqword(keyword))
{
ugh = builddiag("%s keyword not found where expected in RSA key"
, keyword);
goto end;
}
if (!(shift() && (!tokeq(":") || shift()))) /* ignore optional ":" */
{
ugh = "premature end of RSA key";
goto end;
}
ugh = ttodatav(tok, flp->cur - tok, 0, buf, sizeof(buf), &sz,
diag_space, sizeof(diag_space), TTODATAV_SPACECOUNTS);
if (ugh)
{
ugh = builddiag("RSA data malformed (%s): %s", ugh, tok);
part++;
goto end;
}
rsa_chunk[part] = chunk_create(buf, sz);
rsa_chunk[part] = chunk_clone(rsa_chunk[part]);
}
/* We require an (indented) '}' and the end of the record.
* We break down the test so that the diagnostic will be more helpful.
* Some people don't seem to wish to indent the brace!
*/
if (!shift() || !tokeq("}"))
{
ugh = "malformed end of RSA private key -- indented '}' required";
goto end;
}
if (shift())
{
ugh = "malformed end of RSA private key -- unexpected token after '}'";
goto end;
}
*key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, KEY_RSA,
BUILD_RSA_MODULUS, rsa_chunk[RSA_PART_MODULUS],
BUILD_RSA_PUB_EXP, rsa_chunk[RSA_PART_PUBLIC_EXPONENT],
BUILD_RSA_PRIV_EXP, rsa_chunk[RSA_PART_PRIVATE_EXPONENT],
BUILD_RSA_PRIME1, rsa_chunk[RSA_PART_PRIME1],
BUILD_RSA_PRIME2, rsa_chunk[RSA_PART_PRIME2],
BUILD_RSA_EXP1, rsa_chunk[RSA_PART_EXPONENT1],
BUILD_RSA_EXP2, rsa_chunk[RSA_PART_EXPONENT2],
BUILD_RSA_COEFF, rsa_chunk[RSA_PART_COEFFICIENT],
BUILD_END);
if (*key == NULL)
{
ugh = "parsing of RSA private key failed";
}
end:
/* clean up and return */
for (p = RSA_PART_MODULUS ; p < part; p++)
{
chunk_clear(&rsa_chunk[p]);
}
return ugh;
}
/**
* process a key file protected with optional passphrase which can either be
* read from ipsec.secrets or prompted for by using whack
*/
static err_t process_keyfile(private_key_t **key, key_type_t type, int whackfd)
{
char filename[BUF_LEN];
prompt_pass_t pass;
memset(filename,'\0', BUF_LEN);
memset(pass.secret,'\0', sizeof(pass.secret));
pass.prompt = FALSE;
pass.fd = whackfd;
/* we expect the filename of a PKCS#1 private key file */
if (*tok == '"' || *tok == '\'') /* quoted filename */
memcpy(filename, tok+1, flp->cur - tok - 2);
else
memcpy(filename, tok, flp->cur - tok);
if (shift())
{
/* we expect an appended passphrase or passphrase prompt*/
if (tokeqword("%prompt"))
{
if (pass.fd == NULL_FD)
{
return "Private key file -- enter passphrase using 'ipsec secrets'";
}
pass.prompt = TRUE;
}
else
{
char *passphrase = tok;
size_t len = flp->cur - passphrase;
if (*tok == '"' || *tok == '\'') /* quoted passphrase */
{
passphrase++;
len -= 2;
}
if (len > PROMPT_PASS_LEN)
{
return "Private key file -- passphrase exceeds 64 characters";
}
memcpy(pass.secret, passphrase, len);
}
if (shift())
{
return "Private key file -- unexpected token after passphrase";
}
}
*key = load_private_key(filename, &pass, type);
return *key ? NULL : "Private key file -- could not be loaded";
}
/**
* Process pin read from ipsec.secrets or prompted for it using whack
*/
static err_t process_pin(secret_t *s, int whackfd)
{
smartcard_t *sc;
const char *pin_status = "no pin";
s->kind = SECRET_PIN;
/* looking for the smartcard keyword */
if (!shift() || strncmp(tok, SCX_TOKEN, strlen(SCX_TOKEN)) != 0)
return "PIN keyword must be followed by %smartcard<reader>:<id>";
sc = scx_add(scx_parse_number_slot_id(tok + strlen(SCX_TOKEN)));
s->u.smartcard = sc;
scx_share(sc);
if (sc->pin.ptr != NULL)
{
scx_release_context(sc);
scx_free_pin(&sc->pin);
}
sc->valid = FALSE;
if (!shift())
return "PIN statement must be terminated either by <pin code>, %pinpad or %prompt";
if (tokeqword("%prompt"))
{
shift();
/* if whackfd exists, whack will be used to prompt for a pin */
if (whackfd != NULL_FD)
pin_status = scx_get_pin(sc, whackfd) ? "valid pin" : "invalid pin";
else
pin_status = "pin entry via prompt";
}
else if (tokeqword("%pinpad"))
{
chunk_t empty_pin = { "", 0 };
shift();
/* pin will be entered via pin pad during verification */
sc->pin = chunk_clone(empty_pin);
sc->pinpad = TRUE;
sc->valid = TRUE;
pin_status = "pin entry via pad";
if (pkcs11_keep_state)
{
scx_verify_pin(sc);
}
}
else
{
/* we read the pin directly from ipsec.secrets */
err_t ugh = process_psk_secret(&sc->pin);
if (ugh != NULL)
return ugh;
/* verify the pin */
pin_status = scx_verify_pin(sc) ? "valid PIN" : "invalid PIN";
}
#ifdef SMARTCARD
{
char buf[BUF_LEN];
if (sc->any_slot)
snprintf(buf, BUF_LEN, "any slot");
else
snprintf(buf, BUF_LEN, "slot: %lu", sc->slot);
plog(" %s for #%d (%s, id: %s)"
, pin_status, sc->number, scx_print_slot(sc, ""), sc->id);
}
#else
plog(" warning: SMARTCARD support is deactivated in pluto/Makefile!");
#endif
return NULL;
}
static void log_psk(char *label, secret_t *s)
{
int n = 0;
char buf[BUF_LEN];
enumerator_t *enumerator;
identification_t *id;
if (s->ids->get_count(s->ids) == 0)
{
n = snprintf(buf, BUF_LEN, "%%any");
}
else
{
enumerator = s->ids->create_enumerator(s->ids);
while(enumerator->enumerate(enumerator, &id))
{
n += snprintf(buf + n, BUF_LEN - n, "%Y ", id);
if (n >= BUF_LEN)
{
n = BUF_LEN - 1;
break;
}
}
enumerator->destroy(enumerator);
}
plog(" loaded %s secret for %.*s", label, n, buf);
}
static void process_secret(secret_t *s, int whackfd)
{
err_t ugh = NULL;
s->kind = SECRET_PSK; /* default */
if (*tok == '"' || *tok == '\'')
{
log_psk("PSK", s);
/* old PSK format: just a string */
ugh = process_psk_secret(&s->u.preshared_secret);
}
else if (tokeqword("psk"))
{
log_psk("PSK", s);
/* preshared key: quoted string or ttodata format */
ugh = !shift()? "unexpected end of record in PSK"
: process_psk_secret(&s->u.preshared_secret);
}
else if (tokeqword("xauth"))
{
s->kind = SECRET_XAUTH;
log_psk("XAUTH", s);
/* xauth secret: quoted string or ttodata format */
ugh = !shift()? "unexpected end of record in XAUTH"
: process_psk_secret(&s->u.preshared_secret);
}
else if (tokeqword("rsa"))
{
/* RSA key: the fun begins.
* A braced list of keyword and value pairs.
*/
s->kind = SECRET_PUBKEY;
if (!shift())
{
ugh = "bad RSA key syntax";
}
else if (tokeq("{"))
{
ugh = process_rsa_secret(&s->u.private_key);
}
else
{
ugh = process_keyfile(&s->u.private_key, KEY_RSA, whackfd);
}
}
else if (tokeqword("ecdsa"))
{
s->kind = SECRET_PUBKEY;
if (!shift())
{
ugh = "bad ECDSA key syntax";
}
else
{
ugh = process_keyfile(&s->u.private_key, KEY_ECDSA, whackfd);
}
}
else if (tokeqword("pin"))
{
ugh = process_pin(s, whackfd);
}
else
{
ugh = builddiag("unrecognized key format: %s", tok);
}
if (ugh != NULL)
{
loglog(RC_LOG_SERIOUS, "\"%s\" line %d: %s"
, flp->filename, flp->lino, ugh);
free(s);
}
else if (flushline("expected record boundary in key"))
{
/* gauntlet has been run: install new secret */
lock_certs_and_keys("process_secret");
s->next = secrets;
secrets = s;
unlock_certs_and_keys("process_secrets");
}
}
static void process_secrets_file(const char *file_pat, int whackfd); /* forward declaration */
static void process_secret_records(int whackfd)
{
/* read records from ipsec.secrets and load them into our table */
for (;;)
{
(void)flushline(NULL); /* silently ditch leftovers, if any */
if (flp->bdry == B_file)
{
break;
}
flp->bdry = B_none; /* eat the Record Boundary */
(void)shift(); /* get real first token */
if (tokeqword("include"))
{
/* an include directive */
char fn[MAX_TOK_LEN]; /* space for filename (I hope) */
char *p = fn;
char *end_prefix = strrchr(flp->filename, '/');
if (!shift())
{
loglog(RC_LOG_SERIOUS, "\"%s\" line %d: unexpected end of include directive"
, flp->filename, flp->lino);
continue; /* abandon this record */
}
/* if path is relative and including file's pathname has
* a non-empty dirname, prefix this path with that dirname.
*/
if (tok[0] != '/' && end_prefix != NULL)
{
size_t pl = end_prefix - flp->filename + 1;
/* "clamp" length to prevent problems now;
* will be rediscovered and reported later.
*/
if (pl > sizeof(fn))
{
pl = sizeof(fn);
}
memcpy(fn, flp->filename, pl);
p += pl;
}
if (flp->cur - tok >= &fn[sizeof(fn)] - p)
{
loglog(RC_LOG_SERIOUS, "\"%s\" line %d: include pathname too long"
, flp->filename, flp->lino);
continue; /* abandon this record */
}
strcpy(p, tok);
(void) shift(); /* move to Record Boundary, we hope */
if (flushline("ignoring malformed INCLUDE -- expected Record Boundary after filename"))
{
process_secrets_file(fn, whackfd);
tok = NULL; /* correct, but probably redundant */
}
}
else
{
/* expecting a list of indices and then the key info */
secret_t *s = malloc_thing(secret_t);
zero(s);
s->ids = linked_list_create();
s->kind = SECRET_PSK; /* default */
s->u.preshared_secret = chunk_empty;
s->next = NULL;
for (;;)
{
if (tok[0] == '"' || tok[0] == '\'')
{
/* found key part */
process_secret(s, whackfd);
break;
}
else if (tokeq(":"))
{
/* found key part */
shift(); /* discard explicit separator */
process_secret(s, whackfd);
break;
}
else
{
identification_t *id;
id = identification_create_from_string(tok);
s->ids->insert_last(s->ids, id);
if (!shift())
{
/* unexpected Record Boundary or EOF */
loglog(RC_LOG_SERIOUS, "\"%s\" line %d: unexpected end of id list"
, flp->filename, flp->lino);
break;
}
}
}
}
}
}
static int globugh(const char *epath, int eerrno)
{
log_errno_routine(eerrno, "problem with secrets file \"%s\"", epath);
return 1; /* stop glob */
}
static void process_secrets_file(const char *file_pat, int whackfd)
{
struct file_lex_position pos;
char **fnp;
glob_t globbuf;
pos.depth = flp == NULL? 0 : flp->depth + 1;
if (pos.depth > 10)
{
loglog(RC_LOG_SERIOUS, "preshared secrets file \"%s\" nested too deeply", file_pat);
return;
}
/* do globbing */
{
int r = glob(file_pat, GLOB_ERR, globugh, &globbuf);
if (r != 0)
{
switch (r)
{
case GLOB_NOSPACE:
loglog(RC_LOG_SERIOUS, "out of space processing secrets filename \"%s\"", file_pat);
break;
case GLOB_ABORTED:
break; /* already logged */
case GLOB_NOMATCH:
loglog(RC_LOG_SERIOUS, "no secrets filename matched \"%s\"", file_pat);
break;
default:
loglog(RC_LOG_SERIOUS, "unknown glob error %d", r);
break;
}
globfree(&globbuf);
return;
}
}
/* for each file... */
for (fnp = globbuf.gl_pathv; *fnp != NULL; fnp++)
{
if (lexopen(&pos, *fnp, FALSE))
{
plog("loading secrets from \"%s\"", *fnp);
(void) flushline("file starts with indentation (continuation notation)");
process_secret_records(whackfd);
lexclose();
}
}
globfree(&globbuf);
}
void free_preshared_secrets(void)
{
lock_certs_and_keys("free_preshared_secrets");
if (secrets != NULL)
{
secret_t *s, *ns;
plog("forgetting secrets");
for (s = secrets; s != NULL; s = ns)
{
ns = s->next;
s->ids->destroy_offset(s->ids, offsetof(identification_t, destroy));
switch (s->kind)
{
case SECRET_PSK:
case SECRET_XAUTH:
free(s->u.preshared_secret.ptr);
break;
case SECRET_PUBKEY:
DESTROY_IF(s->u.private_key);
break;
case SECRET_PIN:
scx_release(s->u.smartcard);
break;
default:
bad_case(s->kind);
}
free(s);
}
secrets = NULL;
}
unlock_certs_and_keys("free_preshard_secrets");
}
void load_preshared_secrets(int whackfd)
{
free_preshared_secrets();
(void) process_secrets_file(shared_secrets_file, whackfd);
}
/* public key machinery
* Note: caller must set dns_auth_level.
*/
pubkey_t* public_key_from_rsa(public_key_t *key)
{
pubkey_t *p = malloc_thing(pubkey_t);
zero(p);
p->id = identification_create_from_string("%any"); /* don't know, doesn't matter */
p->issuer = NULL;
p->serial = chunk_empty;
p->public_key = key;
/* note that we return a 1 reference count upon creation:
* invariant: recount > 0.
*/
p->refcnt = 1;
return p;
}
/* Free a public key record.
* As a convenience, this returns a pointer to next.
*/
pubkey_list_t* free_public_keyentry(pubkey_list_t *p)
{
pubkey_list_t *nxt = p->next;
if (p->key != NULL)
{
unreference_key(&p->key);
}
free(p);
return nxt;
}
void free_public_keys(pubkey_list_t **keys)
{
while (*keys != NULL)
{
*keys = free_public_keyentry(*keys);
}
}
/* root of chained public key list */
pubkey_list_t *pubkeys = NULL; /* keys from ipsec.conf */
void free_remembered_public_keys(void)
{
free_public_keys(&pubkeys);
}
/**
* Transfer public keys from *keys list to front of pubkeys list
*/
void transfer_to_public_keys(struct gw_info *gateways_from_dns
#ifdef USE_KEYRR
, pubkey_list_t **keys
#endif /* USE_KEYRR */
)
{
{
struct gw_info *gwp;
for (gwp = gateways_from_dns; gwp != NULL; gwp = gwp->next)
{
pubkey_list_t *pl = malloc_thing(pubkey_list_t);
pl->key = gwp->key; /* note: this is a transfer */
gwp->key = NULL; /* really, it is! */
pl->next = pubkeys;
pubkeys = pl;
}
}
#ifdef USE_KEYRR
{
pubkey_list_t **pp = keys;
while (*pp != NULL)
{
pp = &(*pp)->next;
}
*pp = pubkeys;
pubkeys = *keys;
*keys = NULL;
}
#endif /* USE_KEYRR */
}
static void install_public_key(pubkey_t *pk, pubkey_list_t **head)
{
pubkey_list_t *p = malloc_thing(pubkey_list_t);
/* install new key at front */
p->key = reference_key(pk);
p->next = *head;
*head = p;
}
void delete_public_keys(identification_t *id, key_type_t type,
identification_t *issuer, chunk_t serial)
{
pubkey_list_t **pp, *p;
pubkey_t *pk;
key_type_t pk_type;
for (pp = &pubkeys; (p = *pp) != NULL; )
{
pk = p->key;
pk_type = pk->public_key->get_type(pk->public_key);
if (id->equals(id, pk->id) && pk_type == type
&& (issuer == NULL || pk->issuer == NULL
|| issuer->equals(issuer, pk->issuer))
&& (serial.ptr == NULL || chunk_equals(serial, pk->serial)))
{
*pp = free_public_keyentry(p);
}
else
{
pp = &p->next;
}
}
}
pubkey_t* reference_key(pubkey_t *pk)
{
DBG(DBG_CONTROLMORE,
DBG_log(" ref key: %p %p cnt %d '%Y'",
pk, pk->public_key, pk->refcnt, pk->id)
)
pk->refcnt++;
return pk;
}
void unreference_key(pubkey_t **pkp)
{
pubkey_t *pk = *pkp;
if (pk == NULL)
{
return;
}
DBG(DBG_CONTROLMORE,
DBG_log("unref key: %p %p cnt %d '%Y'",
pk, pk->public_key, pk->refcnt, pk->id)
)
/* cancel out the pointer */
*pkp = NULL;
passert(pk->refcnt != 0);
pk->refcnt--;
if (pk->refcnt == 0)
{
free_public_key(pk);
}
}
bool add_public_key(identification_t *id, enum dns_auth_level dns_auth_level,
enum pubkey_alg alg, chunk_t rfc3110_key,
pubkey_list_t **head)
{
public_key_t *key = NULL;
pubkey_t *pk;
/* first: algorithm-specific decoding of key chunk */
switch (alg)
{
case PUBKEY_ALG_RSA:
key = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, KEY_RSA,
BUILD_BLOB_DNSKEY, rfc3110_key,
BUILD_END);
if (key == NULL)
{
return FALSE;
}
break;
default:
bad_case(alg);
}
pk = malloc_thing(pubkey_t);
zero(pk);
pk->public_key = key;
pk->id = id->clone(id);
pk->dns_auth_level = dns_auth_level;
pk->until_time = UNDEFINED_TIME;
pk->issuer = NULL;
pk->serial = chunk_empty;
install_public_key(pk, head);
return TRUE;
}
/**
* Extract id and public key a certificate and insert it into a pubkeyrec
*/
void add_public_key_from_cert(cert_t *cert , time_t until,
enum dns_auth_level dns_auth_level)
{
certificate_t *certificate = cert->cert;
identification_t *subject = certificate->get_subject(certificate);
identification_t *issuer = NULL;
identification_t *id;
chunk_t serialNumber = chunk_empty;
pubkey_t *pk;
key_type_t pk_type;
/* ID type: ID_DER_ASN1_DN (X.509 subject field) */
pk = malloc_thing(pubkey_t);
zero(pk);
pk->public_key = certificate->get_public_key(certificate);
pk_type = pk->public_key->get_type(pk->public_key);
pk->id = subject->clone(subject);
pk->dns_auth_level = dns_auth_level;
pk->until_time = until;
if (certificate->get_type(certificate) == CERT_X509)
{
x509_t *x509 = (x509_t*)certificate;
issuer = certificate->get_issuer(certificate);
serialNumber = x509->get_serial(x509);
pk->issuer = issuer->clone(issuer);
pk->serial = chunk_clone(serialNumber);
}
delete_public_keys(pk->id, pk_type, pk->issuer, pk->serial);
install_public_key(pk, &pubkeys);
if (certificate->get_type(certificate) == CERT_X509)
{
x509_t *x509 = (x509_t*)certificate;
enumerator_t *enumerator;
/* insert all subjectAltNames from X.509 certificates */
enumerator = x509->create_subjectAltName_enumerator(x509);
while (enumerator->enumerate(enumerator, &id))
{
if (id->get_type(id) != ID_ANY)
{
pk = malloc_thing(pubkey_t);
zero(pk);
pk->id = id->clone(id);
pk->public_key = certificate->get_public_key(certificate);
pk->dns_auth_level = dns_auth_level;
pk->until_time = until;
pk->issuer = issuer->clone(issuer);
pk->serial = chunk_clone(serialNumber);
delete_public_keys(pk->id, pk_type, pk->issuer, pk->serial);
install_public_key(pk, &pubkeys);
}
}
enumerator->destroy(enumerator);
}
else
{
pgp_certificate_t *pgp_cert = (pgp_certificate_t*)certificate;
chunk_t fingerprint = pgp_cert->get_fingerprint(pgp_cert);
/* add v3 or v4 PGP fingerprint */
pk = malloc_thing(pubkey_t);
zero(pk);
pk->id = identification_create_from_encoding(ID_KEY_ID, fingerprint);
pk->public_key = certificate->get_public_key(certificate);
pk->dns_auth_level = dns_auth_level;
pk->until_time = until;
delete_public_keys(pk->id, pk_type, pk->issuer, pk->serial);
install_public_key(pk, &pubkeys);
}
}
/* when a X.509 certificate gets revoked, all instances of
* the corresponding public key must be removed
*/
void remove_x509_public_key(const cert_t *cert)
{
public_key_t *revoked_key = cert->cert->get_public_key(cert->cert);
pubkey_list_t *p, **pp;
p = pubkeys;
pp = &pubkeys;
while(p != NULL)
{
if (revoked_key->equals(revoked_key, p->key->public_key))
{
/* remove p from list and free memory */
*pp = free_public_keyentry(p);
loglog(RC_LOG_SERIOUS, "invalid public key deleted");
}
else
{
pp = &p->next;
}
p =*pp;
}
revoked_key->destroy(revoked_key);
}
/*
* list all public keys in the chained list
*/
void list_public_keys(bool utc)
{
pubkey_list_t *p = pubkeys;
if (p != NULL)
{
whack_log(RC_COMMENT, " ");
whack_log(RC_COMMENT, "List of Public Keys:");
}
while (p != NULL)
{
pubkey_t *key = p->key;
public_key_t *public = key->public_key;
chunk_t keyid;
whack_log(RC_COMMENT, " ");
whack_log(RC_COMMENT, " identity: '%Y'", key->id);
whack_log(RC_COMMENT, " pubkey: %N %4d bits, until %T %s",
key_type_names, public->get_type(public),
public->get_keysize(public) * BITS_PER_BYTE,
&key->until_time, utc,
check_expiry(key->until_time, PUBKEY_WARNING_INTERVAL, TRUE));
if (public->get_fingerprint(public, KEYID_PUBKEY_INFO_SHA1, &keyid))
{
whack_log(RC_COMMENT," keyid: %#B", &keyid);
}
if (key->issuer)
{
whack_log(RC_COMMENT," issuer: \"%Y\"", key->issuer);
}
if (key->serial.len)
{
whack_log(RC_COMMENT," serial: %#B", &key->serial);
}
p = p->next;
}
}