summaryrefslogtreecommitdiff
path: root/src/pluto/keys.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/pluto/keys.c')
-rw-r--r--src/pluto/keys.c1514
1 files changed, 1514 insertions, 0 deletions
diff --git a/src/pluto/keys.c b/src/pluto/keys.c
new file mode 100644
index 000000000..eed81230f
--- /dev/null
+++ b/src/pluto/keys.c
@@ -0,0 +1,1514 @@
+/* mechanisms for preshared keys (public, private, and preshared secrets)
+ * Copyright (C) 1998-2001 D. Hugh Redelmeier.
+ *
+ * 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.
+ *
+ * RCSID $Id: keys.c,v 1.24 2006/01/27 08:59:40 as Exp $
+ */
+
+#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 <ipsec_policy.h>
+
+#include "constants.h"
+#include "defs.h"
+#include "mp_defs.h"
+#include "id.h"
+#include "x509.h"
+#include "pgp.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"
+#include "xauth.h"
+
+const char *shared_secrets_file = SHARED_SECRETS_FILE;
+
+typedef struct id_list id_list_t;
+
+struct id_list {
+ struct id id;
+ id_list_t *next;
+};
+
+typedef struct secret secret_t;
+
+struct secret {
+ id_list_t *ids;
+ enum PrivateKeyKind kind;
+ union {
+ chunk_t preshared_secret;
+ RSA_private_key_t RSA_private_key;
+ xauth_t xauth_secret;
+ smartcard_t *smartcard;
+ } u;
+ secret_t *next;
+};
+
+static pubkey_t*
+allocate_RSA_public_key(const cert_t cert)
+{
+ pubkey_t *pk = alloc_thing(pubkey_t, "pubkey");
+ chunk_t e, n;
+
+ switch (cert.type)
+ {
+ case CERT_PGP:
+ e = cert.u.pgp->publicExponent;
+ n = cert.u.pgp->modulus;
+ break;
+ case CERT_X509_SIGNATURE:
+ e = cert.u.x509->publicExponent;
+ n = cert.u.x509->modulus;
+ break;
+ default:
+ plog("RSA public key allocation error");
+ }
+
+ init_RSA_public_key(&pk->u.rsa, e, n);
+ DBG(DBG_RAW,
+ RSA_show_public_key(&pk->u.rsa)
+ )
+
+ pk->alg = PUBKEY_ALG_RSA;
+ pk->id = empty_id;
+ pk->issuer = empty_chunk;
+ pk->serial = empty_chunk;
+
+ return pk;
+}
+
+/*
+ * free a public key struct
+ */
+static void
+free_public_key(pubkey_t *pk)
+{
+ free_id_content(&pk->id);
+ freeanychunk(pk->issuer);
+ freeanychunk(pk->serial);
+
+ /* algorithm-specific freeing */
+ switch (pk->alg)
+ {
+ case PUBKEY_ALG_RSA:
+ free_RSA_public_content(&pk->u.rsa);
+ break;
+ default:
+ bad_case(pk->alg);
+ }
+ pfree(pk);
+}
+
+secret_t *secrets = NULL;
+
+/* find the struct secret associated with the combination of
+ * me and the peer. We match the Id (if none, the IP address).
+ * Failure is indicated by a NULL.
+ */
+static const secret_t *
+get_secret(const struct connection *c, enum PrivateKeyKind kind, bool asym)
+{
+ enum { /* bits */
+ match_default = 01,
+ match_him = 02,
+ match_me = 04
+ };
+
+ unsigned int best_match = 0;
+ secret_t *best = NULL;
+ secret_t *s;
+ const struct id *my_id = &c->spd.this.id
+ , *his_id = &c->spd.that.id;
+ struct id rw_id;
+
+ /* is there a certificate assigned to this connection? */
+ if (kind == PPK_RSA && c->spd.this.cert.type != CERT_NONE)
+ {
+ pubkey_t *my_public_key = allocate_RSA_public_key(c->spd.this.cert);
+
+ for (s = secrets; s != NULL; s = s->next)
+ {
+ if (s->kind == kind &&
+ same_RSA_public_key(&s->u.RSA_private_key.pub, &my_public_key->u.rsa))
+ {
+ best = s;
+ break; /* we have found the private key - no sense in searching further */
+ }
+ }
+ free_public_key(my_public_key);
+ return best;
+ }
+
+ if (his_id_was_instantiated(c))
+ {
+ /* roadwarrior: replace him with 0.0.0.0 */
+ rw_id.kind = c->spd.that.id.kind;
+ rw_id.name = empty_chunk;
+ happy(anyaddr(addrtypeof(&c->spd.that.host_addr), &rw_id.ip_addr));
+ his_id = &rw_id;
+ }
+ else if (kind == PPK_PSK
+ && (c->policy & (POLICY_PSK | POLICY_XAUTH_PSK))
+ && ((c->kind == CK_TEMPLATE && c->spd.that.id.kind == ID_NONE) ||
+ (c->kind == CK_INSTANCE && id_is_ipaddr(&c->spd.that.id))))
+ {
+ /* roadwarrior: replace him with 0.0.0.0 */
+ rw_id.kind = ID_IPV4_ADDR;
+ happy(anyaddr(addrtypeof(&c->spd.that.host_addr), &rw_id.ip_addr));
+ his_id = &rw_id;
+ }
+
+ for (s = secrets; s != NULL; s = s->next)
+ {
+ if (s->kind == kind)
+ {
+ unsigned int match = 0;
+
+ if (s->ids == NULL)
+ {
+ /* a default (signified by lack of ids):
+ * accept if no more specific match found
+ */
+ match = match_default;
+ }
+ else
+ {
+ /* check if both ends match ids */
+ id_list_t *i;
+
+ for (i = s->ids; i != NULL; i = i->next)
+ {
+ if (same_id(my_id, &i->id))
+ match |= match_me;
+
+ if (same_id(his_id, &i->id))
+ match |= match_him;
+ }
+
+ /* 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->next == NULL)
+ 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 (!asym)
+ 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 PPK_PSK:
+ same = s->u.preshared_secret.len == best->u.preshared_secret.len
+ && memcmp(s->u.preshared_secret.ptr, best->u.preshared_secret.ptr, s->u.preshared_secret.len) == 0;
+ break;
+ case PPK_RSA:
+ /* Dirty trick: since we have code to compare
+ * RSA public keys, but not private keys, we
+ * make the assumption that equal public keys
+ * mean equal private keys. This ought to work.
+ */
+ same = same_RSA_public_key(&s->u.RSA_private_key.pub
+ , &best->u.RSA_private_key.pub);
+ 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;
+}
+
+/* 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 struct connection *c)
+{
+ const secret_t *s = get_secret(c, PPK_PSK, FALSE);
+
+ 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 an RSA private key matching an RSA public
+ * key contained in an X.509 or OpenPGP certificate
+ */
+bool
+has_private_key(cert_t cert)
+{
+ secret_t *s;
+ bool has_key = FALSE;
+ pubkey_t *pubkey = allocate_RSA_public_key(cert);
+
+ for (s = secrets; s != NULL; s = s->next)
+ {
+ if (s->kind == PPK_RSA &&
+ same_RSA_public_key(&s->u.RSA_private_key.pub, &pubkey->u.rsa))
+ {
+ has_key = TRUE;
+ break;
+ }
+ }
+ free_public_key(pubkey);
+ return has_key;
+}
+
+/*
+ * get the matching RSA private key belonging to a given X.509 certificate
+ */
+const RSA_private_key_t*
+get_x509_private_key(const x509cert_t *cert)
+{
+ secret_t *s;
+ const RSA_private_key_t *pri = NULL;
+ const cert_t c = {CERT_X509_SIGNATURE, {cert}};
+
+ pubkey_t *pubkey = allocate_RSA_public_key(c);
+
+ for (s = secrets; s != NULL; s = s->next)
+ {
+ if (s->kind == PPK_RSA &&
+ same_RSA_public_key(&s->u.RSA_private_key.pub, &pubkey->u.rsa))
+ {
+ pri = &s->u.RSA_private_key;
+ break;
+ }
+ }
+ free_public_key(pubkey);
+ return pri;
+}
+
+/* find the appropriate RSA private key (see get_secret).
+ * Failure is indicated by a NULL pointer.
+ */
+const RSA_private_key_t *
+get_RSA_private_key(const struct connection *c)
+{
+ const secret_t *s = get_secret(c, PPK_RSA, TRUE);
+
+ return s == NULL? NULL : &s->u.RSA_private_key;
+}
+
+/* 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 == '\'')
+ {
+ clonetochunk(*psk, tok+1, flp->cur - tok - 2, "PSK");
+ (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
+ {
+ clonetochunk(*psk, buf, sz, "PSK");
+ (void) shift();
+ }
+ }
+ return ugh;
+}
+
+/* Parse fields of RSA private key.
+ * A braced list of keyword and value pairs.
+ * At the moment, each field is required, in order.
+ * The fields come from BIND 8.2's representation
+ */
+static err_t
+process_rsa_secret(RSA_private_key_t *rsak)
+{
+ char buf[RSA_MAX_ENCODING_BYTES]; /* limit on size of binary representation of key */
+ const struct fld *p;
+
+ /* save bytes of Modulus and PublicExponent for keyid calculation */
+ unsigned char ebytes[sizeof(buf)];
+ unsigned char *eb_next = ebytes;
+ chunk_t pub_bytes[2];
+ chunk_t *pb_next = &pub_bytes[0];
+
+ for (p = RSA_private_field; p < &RSA_private_field[RSA_PRIVATE_FIELD_ELEMENTS]; p++)
+ {
+ size_t sz;
+ err_t ugh;
+
+ if (!shift())
+ {
+ return "premature end of RSA key";
+ }
+ else if (!tokeqword(p->name))
+ {
+ return builddiag("%s keyword not found where expected in RSA key"
+ , p->name);
+ }
+ else if (!(shift()
+ && (!tokeq(":") || shift()))) /* ignore optional ":" */
+ {
+ return "premature end of RSA key";
+ }
+ else if (NULL != (ugh = ttodatav(tok, flp->cur - tok
+ , 0, buf, sizeof(buf), &sz, diag_space, sizeof(diag_space)
+ , TTODATAV_SPACECOUNTS)))
+ {
+ /* in RSA key, ttodata didn't like */
+ return builddiag("RSA data malformed (%s): %s", ugh, tok);
+ }
+ else
+ {
+ MP_INT *n = (MP_INT *) ((char *)rsak + p->offset);
+
+ n_to_mpz(n, buf, sz);
+ if (pb_next < &pub_bytes[elemsof(pub_bytes)])
+ {
+ if (eb_next - ebytes + sz > sizeof(ebytes))
+ return "public key takes too many bytes";
+
+ setchunk(*pb_next, eb_next, sz);
+ memcpy(eb_next, buf, sz);
+ eb_next += sz;
+ pb_next++;
+ }
+#if 0 /* debugging info that compromises security */
+ {
+ size_t sz = mpz_sizeinbase(n, 16);
+ char buf[RSA_MAX_OCTETS * 2 + 2]; /* ought to be big enough */
+
+ passert(sz <= sizeof(buf));
+ mpz_get_str(buf, 16, n);
+
+ loglog(RC_LOG_SERIOUS, "%s: %s", p->name, buf);
+ }
+#endif
+ }
+ }
+
+ /* 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("}"))
+ {
+ return "malformed end of RSA private key -- indented '}' required";
+ }
+ else if (shift())
+ {
+ return "malformed end of RSA private key -- unexpected token after '}'";
+ }
+ else
+ {
+ unsigned bits = mpz_sizeinbase(&rsak->pub.n, 2);
+
+ rsak->pub.k = (bits + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
+ rsak->pub.keyid[0] = '\0'; /* in case of splitkeytoid failure */
+ splitkeytoid(pub_bytes[1].ptr, pub_bytes[1].len
+ , pub_bytes[0].ptr, pub_bytes[0].len
+ , rsak->pub.keyid, sizeof(rsak->pub.keyid));
+ return RSA_private_key_sanity(rsak);
+ }
+}
+
+/* process rsa key file protected with optional passphrase which can either be
+ * read from ipsec.secrets or prompted for by using whack
+ */
+static err_t
+process_rsa_keyfile(RSA_private_key_t *rsak, 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 "RSA 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 "RSA private key file -- passphrase exceeds 64 characters";
+
+ memcpy(pass.secret, passphrase, len);
+ }
+ if (shift())
+ return "RSA private key file -- unexpected token after passphrase";
+ }
+ return load_rsa_private_key(filename, &pass, rsak);
+}
+
+/*
+ * process xauth secret read from ipsec.secrets
+ */
+static err_t
+process_xauth(secret_t *s)
+{
+ chunk_t user_name;
+
+ s->kind = PPK_XAUTH;
+
+ if (!shift())
+ return "missing xauth user name";
+ if (*tok == '"' || *tok == '\'') /* quoted user name */
+ {
+ user_name.ptr = tok + 1;
+ user_name.len = flp->cur - tok - 2;
+ }
+ else
+ {
+ user_name.ptr = tok;
+ user_name.len = flp->cur - tok;
+ }
+ plog(" loaded xauth credentials of user '%.*s'"
+ , user_name.len
+ , user_name.ptr);
+ clonetochunk(s->u.xauth_secret.user_name
+ , user_name.ptr, user_name.len, "xauth user name");
+
+ if (!shift())
+ return "missing xauth user password";
+ return process_psk_secret(&s->u.xauth_secret.user_password);
+}
+
+/* get XAUTH secret from chained secrets lists
+ * only one entry is currently supported
+ */
+static bool
+xauth_get_secret(xauth_t *xauth_secret)
+{
+ secret_t *s;
+ bool found = FALSE;
+
+ for (s = secrets; s != NULL; s = s->next)
+ {
+ if (s->kind == PPK_XAUTH)
+ {
+ if (found)
+ {
+ plog("found multiple xauth secrets - first selected");
+ }
+ else
+ {
+ found = TRUE;
+ *xauth_secret = s->u.xauth_secret;
+ }
+ }
+ }
+ return found;
+}
+
+/*
+ * find a matching secret
+ */
+static bool
+xauth_verify_secret(const xauth_t *xauth_secret)
+{
+ bool found = FALSE;
+ secret_t *s;
+
+ for (s = secrets; s != NULL; s = s->next)
+ {
+ if (s->kind == PPK_XAUTH)
+ {
+ if (!same_chunk(xauth_secret->user_name, s->u.xauth_secret.user_name))
+ continue;
+ found = TRUE;
+ if (same_chunk(xauth_secret->user_password, s->u.xauth_secret.user_password))
+ return TRUE;
+ }
+ }
+ plog("xauth user '%.*s' %s"
+ , xauth_secret->user_name.len, xauth_secret->user_name.ptr
+ , found? "sent wrong password":"not found");
+ return FALSE;
+}
+
+/*
+ * the global xauth_module struct is defined here
+ */
+xauth_module_t xauth_module;
+
+/*
+ * assign the default xauth functions to any null function pointers
+ */
+void
+xauth_defaults(void)
+{
+ if (xauth_module.get_secret == NULL)
+ {
+ DBG(DBG_CONTROL,
+ DBG_log("xauth module: using default get_secret() function")
+ )
+ xauth_module.get_secret = xauth_get_secret;
+ }
+ if (xauth_module.verify_secret == NULL)
+ {
+ DBG(DBG_CONTROL,
+ DBG_log("xauth module: using default verify_secret() function")
+ )
+ xauth_module.verify_secret = xauth_verify_secret;
+ }
+};
+
+/*
+ * 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 = PPK_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"))
+ {
+ shift();
+ /* pin will be entered via pin pad during verification */
+ clonetochunk(sc->pin, "", 0, "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
+process_secret(secret_t *s, int whackfd)
+{
+ err_t ugh = NULL;
+
+ s->kind = PPK_PSK; /* default */
+ if (*tok == '"' || *tok == '\'')
+ {
+ /* old PSK format: just a string */
+ ugh = process_psk_secret(&s->u.preshared_secret);
+ }
+ else if (tokeqword("psk"))
+ {
+ /* 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("rsa"))
+ {
+ /* RSA key: the fun begins.
+ * A braced list of keyword and value pairs.
+ */
+ s->kind = PPK_RSA;
+ if (!shift())
+ {
+ ugh = "bad RSA key syntax";
+ }
+ else if (tokeq("{"))
+ {
+ ugh = process_rsa_secret(&s->u.RSA_private_key);
+ }
+ else
+ {
+ ugh = process_rsa_keyfile(&s->u.RSA_private_key, whackfd);
+ }
+ }
+ else if (tokeqword("xauth"))
+ {
+ ugh = process_xauth(s);
+ }
+ 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);
+ pfree(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 = alloc_thing(secret_t, "secret");
+
+ s->ids = NULL;
+ s->kind = PPK_PSK; /* default */
+ setchunk(s->u.preshared_secret, NULL, 0);
+ 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
+ {
+ /* an id
+ * See RFC2407 IPsec Domain of Interpretation 4.6.2
+ */
+ struct id id;
+ err_t ugh;
+
+ if (tokeq("%any"))
+ {
+ id = empty_id;
+ id.kind = ID_IPV4_ADDR;
+ ugh = anyaddr(AF_INET, &id.ip_addr);
+ }
+ else if (tokeq("%any6"))
+ {
+ id = empty_id;
+ id.kind = ID_IPV6_ADDR;
+ ugh = anyaddr(AF_INET6, &id.ip_addr);
+ }
+ else
+ {
+ ugh = atoid(tok, &id, FALSE);
+ }
+
+ if (ugh != NULL)
+ {
+ loglog(RC_LOG_SERIOUS
+ , "ERROR \"%s\" line %d: index \"%s\" %s"
+ , flp->filename, flp->lino, tok, ugh);
+ }
+ else
+ {
+ id_list_t *i = alloc_thing(id_list_t
+ , "id_list");
+
+ i->id = id;
+ unshare_id_content(&i->id);
+ i->next = s->ids;
+ s->ids = i;
+ /* DBG_log("id type %d: %s %.*s", i->kind, ip_str(&i->ip_addr), (int)i->name.len, i->name.ptr); */
+ }
+ 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)
+ {
+ id_list_t *i, *ni;
+
+ ns = s->next; /* grab before freeing s */
+ for (i = s->ids; i != NULL; i = ni)
+ {
+ ni = i->next; /* grab before freeing i */
+ free_id_content(&i->id);
+ pfree(i);
+ }
+ switch (s->kind)
+ {
+ case PPK_PSK:
+ pfree(s->u.preshared_secret.ptr);
+ break;
+ case PPK_RSA:
+ free_RSA_private_content(&s->u.RSA_private_key);
+ break;
+ case PPK_XAUTH:
+ pfree(s->u.xauth_secret.user_name.ptr);
+ pfree(s->u.xauth_secret.user_password.ptr);
+ break;
+ case PPK_PIN:
+ scx_release(s->u.smartcard);
+ break;
+ default:
+ bad_case(s->kind);
+ }
+ pfree(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(const RSA_public_key_t *k)
+{
+ pubkey_t *p = alloc_thing(pubkey_t, "pubkey");
+
+ p->id = empty_id; /* don't know, doesn't matter */
+ p->issuer = empty_chunk;
+ p->serial = empty_chunk;
+ p->alg = PUBKEY_ALG_RSA;
+
+ memcpy(p->u.rsa.keyid, k->keyid, sizeof(p->u.rsa.keyid));
+ p->u.rsa.k = k->k;
+ mpz_init_set(&p->u.rsa.e, &k->e);
+ mpz_init_set(&p->u.rsa.n, &k->n);
+
+ /* note that we return a 1 reference count upon creation:
+ * invariant: recount > 0.
+ */
+ p->refcnt = 1;
+ time(&p->installed_time);
+ 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);
+ pfree(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 = alloc_thing(pubkey_list_t, "from TXT");
+
+ 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 */
+}
+
+/* decode of RSA pubkey chunk
+ * - format specified in RFC 2537 RSA/MD5 Keys and SIGs in the DNS
+ * - exponent length in bytes (1 or 3 octets)
+ * + 1 byte if in [1, 255]
+ * + otherwise 0x00 followed by 2 bytes of length
+ * - exponent
+ * - modulus
+ */
+err_t
+unpack_RSA_public_key(RSA_public_key_t *rsa, const chunk_t *pubkey)
+{
+ chunk_t exp;
+ chunk_t mod;
+
+ if (pubkey->len < 3)
+ return "RSA public key blob way to short"; /* not even room for length! */
+
+ if (pubkey->ptr[0] != 0x00)
+ {
+ setchunk(exp, pubkey->ptr + 1, pubkey->ptr[0]);
+ }
+ else
+ {
+ setchunk(exp, pubkey->ptr + 3
+ , (pubkey->ptr[1] << BITS_PER_BYTE) + pubkey->ptr[2]);
+ }
+
+ if (pubkey->len - (exp.ptr - pubkey->ptr) < exp.len + RSA_MIN_OCTETS_RFC)
+ return "RSA public key blob too short";
+
+ mod.ptr = exp.ptr + exp.len;
+ mod.len = &pubkey->ptr[pubkey->len] - mod.ptr;
+
+ if (mod.len < RSA_MIN_OCTETS)
+ return RSA_MIN_OCTETS_UGH;
+
+ if (mod.len > RSA_MAX_OCTETS)
+ return RSA_MAX_OCTETS_UGH;
+
+ init_RSA_public_key(rsa, exp, mod);
+ rsa->k = mpz_sizeinbase(&rsa->n, 2); /* size in bits, for a start */
+ rsa->k = (rsa->k + BITS_PER_BYTE - 1) / BITS_PER_BYTE; /* now octets */
+ DBG(DBG_RAW,
+ RSA_show_public_key(rsa)
+ )
+
+ if (rsa->k != mod.len)
+ {
+ mpz_clear(&rsa->e);
+ mpz_clear(&rsa->n);
+ return "RSA modulus shorter than specified";
+ }
+
+ return NULL;
+}
+
+static void
+install_public_key(pubkey_t *pk, pubkey_list_t **head)
+{
+ pubkey_list_t *p = alloc_thing(pubkey_list_t, "pubkey entry");
+
+ unshare_id_content(&pk->id);
+
+ /* copy issuer dn */
+ if (pk->issuer.ptr != NULL)
+ pk->issuer.ptr = clone_bytes(pk->issuer.ptr, pk->issuer.len, "issuer dn");
+
+ /* copy serial number */
+ if (pk->serial.ptr != NULL)
+ pk->serial.ptr = clone_bytes(pk->serial.ptr, pk->serial.len, "serialNumber");
+
+ /* store the time the public key was installed */
+ time(&pk->installed_time);
+
+ /* install new key at front */
+ p->key = reference_key(pk);
+ p->next = *head;
+ *head = p;
+}
+
+
+void
+delete_public_keys(const struct id *id, enum pubkey_alg alg
+, chunk_t issuer, chunk_t serial)
+{
+ pubkey_list_t **pp, *p;
+ pubkey_t *pk;
+
+ for (pp = &pubkeys; (p = *pp) != NULL; )
+ {
+ pk = p->key;
+
+ if (same_id(id, &pk->id) && pk->alg == alg
+ && (issuer.ptr == NULL || pk->issuer.ptr == NULL
+ || same_dn(issuer, pk->issuer))
+ && same_serial(serial, pk->serial))
+ *pp = free_public_keyentry(p);
+ else
+ pp = &p->next;
+ }
+}
+
+pubkey_t *
+reference_key(pubkey_t *pk)
+{
+ pk->refcnt++;
+ return pk;
+}
+
+void
+unreference_key(pubkey_t **pkp)
+{
+ pubkey_t *pk = *pkp;
+ char b[BUF_LEN];
+
+ if (pk == NULL)
+ return;
+
+ /* print stuff */
+ DBG(DBG_CONTROLMORE,
+ idtoa(&pk->id, b, sizeof(b));
+ DBG_log("unreference key: %p %s cnt %d--", pk, b, pk->refcnt)
+ )
+
+ /* cancel out the pointer */
+ *pkp = NULL;
+
+ passert(pk->refcnt != 0);
+ pk->refcnt--;
+ if (pk->refcnt == 0)
+ free_public_key(pk);
+}
+
+err_t
+add_public_key(const struct id *id
+, enum dns_auth_level dns_auth_level
+, enum pubkey_alg alg
+, const chunk_t *key
+, pubkey_list_t **head)
+{
+ pubkey_t *pk = alloc_thing(pubkey_t, "pubkey");
+
+ /* first: algorithm-specific decoding of key chunk */
+ switch (alg)
+ {
+ case PUBKEY_ALG_RSA:
+ {
+ err_t ugh = unpack_RSA_public_key(&pk->u.rsa, key);
+
+ if (ugh != NULL)
+ {
+ pfree(pk);
+ return ugh;
+ }
+ }
+ break;
+ default:
+ bad_case(alg);
+ }
+
+ pk->id = *id;
+ pk->dns_auth_level = dns_auth_level;
+ pk->alg = alg;
+ pk->until_time = UNDEFINED_TIME;
+ pk->issuer = empty_chunk;
+ pk->serial = empty_chunk;
+
+ install_public_key(pk, head);
+ return NULL;
+}
+
+/* extract id and public key from x.509 certificate and
+ * insert it into a pubkeyrec
+ */
+void
+add_x509_public_key(x509cert_t *cert , time_t until
+ , enum dns_auth_level dns_auth_level)
+{
+ generalName_t *gn;
+ pubkey_t *pk;
+ cert_t c = { CERT_X509_SIGNATURE, {cert} };
+
+ /* we support RSA only */
+ if (cert->subjectPublicKeyAlgorithm != PUBKEY_ALG_RSA)
+ return;
+
+ /* ID type: ID_DER_ASN1_DN (X.509 subject field) */
+ pk = allocate_RSA_public_key(c);
+ pk->id.kind = ID_DER_ASN1_DN;
+ pk->id.name = cert->subject;
+ pk->dns_auth_level = dns_auth_level;
+ pk->until_time = until;
+ pk->issuer = cert->issuer;
+ pk->serial = cert->serialNumber;
+ delete_public_keys(&pk->id, pk->alg, pk->issuer, pk->serial);
+ install_public_key(pk, &pubkeys);
+
+ gn = cert->subjectAltName;
+
+ while (gn != NULL) /* insert all subjectAltNames */
+ {
+ struct id id = empty_id;
+
+ gntoid(&id, gn);
+ if (id.kind != ID_NONE)
+ {
+ pk = allocate_RSA_public_key(c);
+ pk->id = id;
+ pk->dns_auth_level = dns_auth_level;
+ pk->until_time = until;
+ pk->issuer = cert->issuer;
+ pk->serial = cert->serialNumber;
+ delete_public_keys(&pk->id, pk->alg, pk->issuer, pk->serial);
+ install_public_key(pk, &pubkeys);
+ }
+ gn = gn->next;
+ }
+}
+
+/* extract id and public key from OpenPGP certificate and
+ * insert it into a pubkeyrec
+ */
+void
+add_pgp_public_key(pgpcert_t *cert , time_t until
+ , enum dns_auth_level dns_auth_level)
+{
+ pubkey_t *pk;
+ cert_t c;
+
+ c.type = CERT_PGP;
+ c.u.pgp = cert;
+
+ /* we support RSA only */
+ if (cert->pubkeyAlg != PUBKEY_ALG_RSA)
+ {
+ plog(" RSA public keys supported only");
+ return;
+ }
+
+ pk = allocate_RSA_public_key(c);
+ pk->id.kind = ID_KEY_ID;
+ pk->id.name.ptr = cert->fingerprint;
+ pk->id.name.len = PGP_FINGERPRINT_SIZE;
+ pk->dns_auth_level = dns_auth_level;
+ pk->until_time = until;
+ delete_public_keys(&pk->id, pk->alg, empty_chunk, empty_chunk);
+ 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 x509cert_t *cert)
+{
+ const cert_t c = {CERT_X509_SIGNATURE, {cert}};
+ pubkey_list_t *p, **pp;
+ pubkey_t *revoked_pk;
+
+ revoked_pk = allocate_RSA_public_key(c);
+ p = pubkeys;
+ pp = &pubkeys;
+
+ while(p != NULL)
+ {
+ if (same_RSA_public_key(&p->key->u.rsa, &revoked_pk->u.rsa))
+ {
+ /* remove p from list and free memory */
+ *pp = free_public_keyentry(p);
+ loglog(RC_LOG_SERIOUS,
+ "invalid RSA public key deleted");
+ }
+ else
+ {
+ pp = &p->next;
+ }
+ p =*pp;
+ }
+ free_public_key(revoked_pk);
+}
+
+/*
+ * 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:");
+ whack_log(RC_COMMENT, " ");
+ }
+
+ while (p != NULL)
+ {
+ pubkey_t *key = p->key;
+
+ if (key->alg == PUBKEY_ALG_RSA)
+ {
+ char buf[BUF_LEN];
+ char expires_buf[TIMETOA_BUF];
+
+ idtoa(&key->id, buf, BUF_LEN);
+ strcpy(expires_buf, timetoa(&key->until_time, utc));
+ whack_log(RC_COMMENT, "%s, %4d RSA Key %s, until %s %s",
+
+ timetoa(&key->installed_time, utc), 8*key->u.rsa.k, key->u.rsa.keyid,
+ expires_buf,
+ check_expiry(key->until_time, PUBKEY_WARNING_INTERVAL, TRUE));
+ whack_log(RC_COMMENT," %s '%s'",
+ enum_show(&ident_names, key->id.kind), buf);
+ if (key->issuer.len > 0)
+ {
+ dntoa(buf, BUF_LEN, key->issuer);
+ whack_log(RC_COMMENT," issuer: '%s'", buf);
+ }
+ if (key->serial.len > 0)
+ {
+ datatot(key->serial.ptr, key->serial.len, ':'
+ , buf, BUF_LEN);
+ whack_log(RC_COMMENT," serial: %s", buf);
+ }
+ }
+ p = p->next;
+ }
+}