1557 lines
36 KiB
C
1557 lines
36 KiB
C
/* 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$
|
|
*/
|
|
|
|
#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 = malloc_thing(pubkey_t);
|
|
chunk_t e = chunk_empty, n = chunk_empty;
|
|
|
|
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");
|
|
}
|
|
|
|
zero(pk);
|
|
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 = chunk_empty;
|
|
pk->serial = chunk_empty;
|
|
|
|
return pk;
|
|
}
|
|
|
|
/*
|
|
* free a public key struct
|
|
*/
|
|
static void
|
|
free_public_key(pubkey_t *pk)
|
|
{
|
|
free_id_content(&pk->id);
|
|
free(pk->issuer.ptr);
|
|
free(pk->serial.ptr);
|
|
|
|
/* algorithm-specific freeing */
|
|
switch (pk->alg)
|
|
{
|
|
case PUBKEY_ALG_RSA:
|
|
free_RSA_public_content(&pk->u.rsa);
|
|
break;
|
|
default:
|
|
bad_case(pk->alg);
|
|
}
|
|
free(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 = chunk_empty;
|
|
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
|
|
&& memeq(s->u.preshared_secret.ptr, best->u.preshared_secret.ptr, s->u.preshared_secret.len);
|
|
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, {(x509cert_t*)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 == '\'')
|
|
{
|
|
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;
|
|
}
|
|
|
|
/* 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[countof(pub_bytes)])
|
|
{
|
|
if (eb_next - ebytes + sz > sizeof(ebytes))
|
|
return "public key takes too many bytes";
|
|
|
|
*pb_next = chunk_create(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);
|
|
s->u.xauth_secret.user_name = chunk_clone(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_peer_t *peer, 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 (!chunk_equals(xauth_secret->user_name, s->u.xauth_secret.user_name))
|
|
continue;
|
|
found = TRUE;
|
|
if (chunk_equals(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"))
|
|
{
|
|
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(secret_t *s)
|
|
{
|
|
int n = 0;
|
|
char buf[BUF_LEN];
|
|
id_list_t *id_list = s->ids;
|
|
|
|
if (id_list == NULL)
|
|
{
|
|
n = snprintf(buf, BUF_LEN, "%%any");
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
n += idtoa(&id_list->id, buf + n, BUF_LEN - n);
|
|
if (n >= BUF_LEN)
|
|
{
|
|
n = BUF_LEN - 1;
|
|
break;
|
|
}
|
|
else if (n < BUF_LEN - 1)
|
|
{
|
|
n += snprintf(buf + n, BUF_LEN - n, " ");
|
|
}
|
|
id_list = id_list->next;
|
|
}
|
|
while (id_list);
|
|
}
|
|
plog(" loaded shared key for %.*s", n, buf);
|
|
}
|
|
|
|
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 */
|
|
log_psk(s);
|
|
ugh = process_psk_secret(&s->u.preshared_secret);
|
|
}
|
|
else if (tokeqword("psk"))
|
|
{
|
|
/* preshared key: quoted string or ttodata format */
|
|
log_psk(s);
|
|
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);
|
|
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 = NULL;
|
|
s->kind = PPK_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
|
|
{
|
|
/* 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 = malloc_thing(id_list_t);
|
|
|
|
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);
|
|
free(i);
|
|
}
|
|
switch (s->kind)
|
|
{
|
|
case PPK_PSK:
|
|
free(s->u.preshared_secret.ptr);
|
|
break;
|
|
case PPK_RSA:
|
|
free_RSA_private_content(&s->u.RSA_private_key);
|
|
break;
|
|
case PPK_XAUTH:
|
|
free(s->u.xauth_secret.user_name.ptr);
|
|
free(s->u.xauth_secret.user_password.ptr);
|
|
break;
|
|
case PPK_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(const RSA_public_key_t *k)
|
|
{
|
|
pubkey_t *p = malloc_thing(pubkey_t);
|
|
|
|
zero(p);
|
|
p->id = empty_id; /* don't know, doesn't matter */
|
|
p->issuer = chunk_empty;
|
|
p->serial = chunk_empty;
|
|
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);
|
|
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 */
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
exp = chunk_create(pubkey->ptr + 1, pubkey->ptr[0]);
|
|
}
|
|
else
|
|
{
|
|
exp = chunk_create(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 = malloc_thing(pubkey_list_t);
|
|
|
|
unshare_id_content(&pk->id);
|
|
|
|
/* copy issuer dn */
|
|
pk->issuer = chunk_clone(pk->issuer);
|
|
|
|
/* copy serial number */
|
|
pk->serial = chunk_clone(pk->serial);
|
|
|
|
/* 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;
|
|
|
|
pk = malloc_thing(pubkey_t); zero(pk);
|
|
|
|
/* 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)
|
|
{
|
|
free(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 = chunk_empty;
|
|
pk->serial = chunk_empty;
|
|
|
|
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, chunk_empty, chunk_empty);
|
|
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, {(x509cert_t*)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;
|
|
}
|
|
}
|