implemented xauth as a pluto plugin

This commit is contained in:
Andreas Steffen
2010-05-18 13:51:27 +02:00
parent ea409980b9
commit 26ec52a405
22 changed files with 1071 additions and 476 deletions
+212 -289
View File
@@ -53,25 +53,26 @@
#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 {
identification_t *id;
id_list_t *next;
typedef enum secret_kind_t secret_kind_t;
enum secret_kind_t {
SECRET_PSK,
SECRET_PUBKEY,
SECRET_XAUTH,
SECRET_PIN
};
typedef struct secret secret_t;
typedef struct secret_t secret_t;
struct secret {
id_list_t *ids;
enum PrivateKeyKind kind;
struct secret_t {
linked_list_t *ids;
secret_kind_t kind;
union {
chunk_t preshared_secret;
xauth_t xauth_secret;
private_key_t *private_key;
smartcard_t *smartcard;
} u;
@@ -92,12 +93,11 @@ static void free_public_key(pubkey_t *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.
/**
* Find the secret associated with the combination of me and the peer.
*/
static const secret_t* get_secret(const connection_t *c,
enum PrivateKeyKind kind, bool asym)
const secret_t* match_secret(identification_t *my_id, identification_t *his_id,
secret_kind_t kind)
{
enum { /* bits */
match_default = 0x01,
@@ -106,29 +106,135 @@ static const secret_t* get_secret(const connection_t *c,
};
unsigned int best_match = 0;
secret_t *best = NULL;
secret_t *s;
identification_t *my_id, *his_id;
secret_t *s, *best = NULL;
/* is there a certificate assigned to this connection? */
if (kind == PPK_PUBKEY && c->spd.this.cert)
for (s = secrets; s != NULL; s = s->next)
{
certificate_t *certificate = c->spd.this.cert->cert;
unsigned int match = 0;
public_key_t *pub_key = certificate->get_public_key(certificate);
for (s = secrets; s != NULL; s = s->next)
if (s->kind != kind)
{
if (s->kind == kind &&
s->u.private_key->belongs_to(s->u.private_key, pub_key))
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))
{
best = s;
break; /* we have found the private key - no sense in searching further */
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;
}
}
pub_key->destroy(pub_key);
return best;
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;
@@ -137,7 +243,7 @@ static const secret_t* get_secret(const connection_t *c,
/* roadwarrior: replace him with 0.0.0.0 */
his_id = identification_create_from_string("%any");
}
else if (kind == PPK_PSK && (c->policy & (POLICY_PSK | POLICY_XAUTH_PSK)) &&
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))))
@@ -150,96 +256,8 @@ static const secret_t* get_secret(const connection_t *c,
his_id = c->spd.that.id->clone(c->spd.that.id);
}
for (s = secrets; s != NULL; s = s->next)
{
if (s->kind == kind)
{
unsigned int match = 0;
best = match_secret(my_id, his_id, kind);
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 (my_id->equals(my_id, i->id))
{
match |= match_me;
}
if (his_id->equals(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_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;
}
}
}
}
his_id->destroy(his_id);
return best;
}
@@ -250,7 +268,7 @@ static const secret_t* get_secret(const connection_t *c,
*/
const chunk_t* get_preshared_secret(const connection_t *c)
{
const secret_t *s = get_secret(c, PPK_PSK, FALSE);
const secret_t *s = get_secret(c, SECRET_PSK);
DBG(DBG_PRIVATE,
if (s == NULL)
@@ -272,7 +290,7 @@ bool has_private_key(cert_t *cert)
for (s = secrets; s != NULL; s = s->next)
{
if (s->kind == PPK_PUBKEY &&
if (s->kind == SECRET_PUBKEY &&
s->u.private_key->belongs_to(s->u.private_key, pub_key))
{
has_key = TRUE;
@@ -295,7 +313,7 @@ private_key_t* get_x509_private_key(const cert_t *cert)
for (s = secrets; s != NULL; s = s->next)
{
if (s->kind == PPK_PUBKEY &&
if (s->kind == SECRET_PUBKEY &&
s->u.private_key->belongs_to(s->u.private_key, public_key))
{
private_key = s->u.private_key;
@@ -311,9 +329,33 @@ private_key_t* get_x509_private_key(const cert_t *cert)
*/
private_key_t* get_private_key(const connection_t *c)
{
const secret_t *s = get_secret(c, PPK_PUBKEY, TRUE);
const secret_t *s, *best = NULL;
return s == NULL? NULL : s->u.private_key;
/* 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
@@ -555,120 +597,6 @@ static err_t process_keyfile(private_key_t **key, key_type_t type, int whackfd)
return *key ? NULL : "Private key file -- could not be loaded";
}
/**
* 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
*/
@@ -677,7 +605,7 @@ static err_t process_pin(secret_t *s, int whackfd)
smartcard_t *sc;
const char *pin_status = "no pin";
s->kind = PPK_PIN;
s->kind = SECRET_PIN;
/* looking for the smartcard keyword */
if (!shift() || strncmp(tok, SCX_TOKEN, strlen(SCX_TOKEN)) != 0)
@@ -748,57 +676,69 @@ static err_t process_pin(secret_t *s, int whackfd)
return NULL;
}
static void log_psk(secret_t *s)
static void log_psk(char *label, secret_t *s)
{
int n = 0;
char buf[BUF_LEN];
id_list_t *id_list = s->ids;
enumerator_t *enumerator;
identification_t *id;
if (id_list == NULL)
if (s->ids->get_count(s->ids) == 0)
{
n = snprintf(buf, BUF_LEN, "%%any");
}
else
{
do
enumerator = s->ids->create_enumerator(s->ids);
while(enumerator->enumerate(enumerator, &id))
{
n += snprintf(buf + n, BUF_LEN - n, "%Y ", id_list->id);
n += snprintf(buf + n, BUF_LEN - n, "%Y ", id);
if (n >= BUF_LEN)
{
n = BUF_LEN - 1;
break;
}
id_list = id_list->next;
}
while (id_list);
enumerator->destroy(enumerator);
}
plog(" loaded shared key for %.*s", n, buf);
plog(" loaded %s for %.*s", label, n, buf);
}
static void process_secret(secret_t *s, int whackfd)
{
err_t ugh = NULL;
s->kind = PPK_PSK; /* default */
s->kind = SECRET_PSK; /* default */
if (*tok == '"' || *tok == '\'')
{
log_psk("PSK", s);
/* old PSK format: just a string */
log_psk(s);
ugh = process_psk_secret(&s->u.preshared_secret);
}
else if (tokeqword("psk"))
{
log_psk("PSK", s);
/* 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("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 = PPK_PUBKEY;
s->kind = SECRET_PUBKEY;
if (!shift())
{
ugh = "bad RSA key syntax";
@@ -814,7 +754,7 @@ static void process_secret(secret_t *s, int whackfd)
}
else if (tokeqword("ecdsa"))
{
s->kind = PPK_PUBKEY;
s->kind = SECRET_PUBKEY;
if (!shift())
{
ugh = "bad ECDSA key syntax";
@@ -824,10 +764,6 @@ static void process_secret(secret_t *s, int whackfd)
ugh = process_keyfile(&s->u.private_key, KEY_ECDSA, whackfd);
}
}
else if (tokeqword("xauth"))
{
ugh = process_xauth(s);
}
else if (tokeqword("pin"))
{
ugh = process_pin(s, whackfd);
@@ -919,8 +855,8 @@ static void process_secret_records(int whackfd)
secret_t *s = malloc_thing(secret_t);
zero(s);
s->ids = NULL;
s->kind = PPK_PSK; /* default */
s->ids = linked_list_create();
s->kind = SECRET_PSK; /* default */
s->u.preshared_secret = chunk_empty;
s->next = NULL;
@@ -941,14 +877,10 @@ static void process_secret_records(int whackfd)
}
else
{
/* an id
* See RFC2407 IPsec Domain of Interpretation 4.6.2
*/
id_list_t *i = malloc_thing(id_list_t);
identification_t *id;
i->id = identification_create_from_string(tok);
i->next = s->ids;
s->ids = i;
id = identification_create_from_string(tok);
s->ids->insert_last(s->ids, id);
if (!shift())
{
@@ -1035,32 +967,23 @@ void free_preshared_secrets(void)
for (s = secrets; s != NULL; s = ns)
{
id_list_t *i, *ni;
ns = s->next;
for (i = s->ids; i != NULL; i = ni)
{
ni = i->next;
i->id->destroy(i->id);
free(i);
}
s->ids->destroy_offset(s->ids, offsetof(identification_t, destroy));
switch (s->kind)
{
case PPK_PSK:
free(s->u.preshared_secret.ptr);
break;
case PPK_PUBKEY:
DESTROY_IF(s->u.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);
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);
}