summaryrefslogtreecommitdiff
path: root/src/libcharon/sa/ikev2/tasks/ike_auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcharon/sa/ikev2/tasks/ike_auth.c')
-rw-r--r--src/libcharon/sa/ikev2/tasks/ike_auth.c352
1 files changed, 330 insertions, 22 deletions
diff --git a/src/libcharon/sa/ikev2/tasks/ike_auth.c b/src/libcharon/sa/ikev2/tasks/ike_auth.c
index 6b63197d5..b055ff064 100644
--- a/src/libcharon/sa/ikev2/tasks/ike_auth.c
+++ b/src/libcharon/sa/ikev2/tasks/ike_auth.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2015 Tobias Brunner
+ * Copyright (C) 2012-2018 Tobias Brunner
* Copyright (C) 2005-2009 Martin Willi
* Copyright (C) 2005 Jan Hutter
* HSR Hochschule fuer Technik Rapperswil
@@ -24,6 +24,7 @@
#include <encoding/payloads/auth_payload.h>
#include <encoding/payloads/eap_payload.h>
#include <encoding/payloads/nonce_payload.h>
+#include <sa/ikev2/keymat_v2.h>
#include <sa/ikev2/authenticators/eap_authenticator.h>
#include <processing/jobs/delete_ike_sa_job.h>
@@ -60,6 +61,16 @@ struct private_ike_auth_t {
chunk_t other_nonce;
/**
+ * PPK_ID sent or received
+ */
+ identification_t *ppk_id;
+
+ /**
+ * Optional PPK to use
+ */
+ chunk_t ppk;
+
+ /**
* IKE_SA_INIT message sent by us
*/
packet_t *my_packet;
@@ -144,7 +155,7 @@ static status_t collect_my_init_data(private_ike_auth_t *this,
/* get the nonce that was generated in ike_init */
nonce = (nonce_payload_t*)message->get_payload(message, PLV2_NONCE);
- if (nonce == NULL)
+ if (!nonce)
{
return FAILED;
}
@@ -170,7 +181,7 @@ static status_t collect_other_init_data(private_ike_auth_t *this,
/* get the nonce that was generated in ike_init */
nonce = (nonce_payload_t*)message->get_payload(message, PLV2_NONCE);
- if (nonce == NULL)
+ if (!nonce)
{
return FAILED;
}
@@ -279,19 +290,47 @@ static bool do_another_auth(private_ike_auth_t *this)
}
/**
+ * Check if this is the first authentication round
+ */
+static bool is_first_round(private_ike_auth_t *this, bool local)
+{
+ enumerator_t *done;
+ auth_cfg_t *cfg;
+
+ if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MULTIPLE_AUTH))
+ {
+ return TRUE;
+ }
+
+ done = this->ike_sa->create_auth_cfg_enumerator(this->ike_sa, local);
+ if (done->enumerate(done, &cfg))
+ {
+ done->destroy(done);
+ return FALSE;
+ }
+ done->destroy(done);
+ return TRUE;
+}
+
+/**
* Get peer configuration candidates from backends
*/
static bool load_cfg_candidates(private_ike_auth_t *this)
{
enumerator_t *enumerator;
peer_cfg_t *peer_cfg;
+ ike_cfg_t *ike_cfg;
host_t *me, *other;
identification_t *my_id, *other_id;
+ proposal_t *ike_proposal;
+ bool private;
me = this->ike_sa->get_my_host(this->ike_sa);
other = this->ike_sa->get_other_host(this->ike_sa);
my_id = this->ike_sa->get_my_id(this->ike_sa);
other_id = this->ike_sa->get_other_id(this->ike_sa);
+ ike_proposal = this->ike_sa->get_proposal(this->ike_sa);
+ private = this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN);
DBG1(DBG_CFG, "looking for peer configs matching %H[%Y]...%H[%Y]",
me, my_id, other, other_id);
@@ -299,11 +338,18 @@ static bool load_cfg_candidates(private_ike_auth_t *this)
me, other, my_id, other_id, IKEV2);
while (enumerator->enumerate(enumerator, &peer_cfg))
{
+ /* ignore all configs that have no matching IKE proposal */
+ ike_cfg = peer_cfg->get_ike_cfg(peer_cfg);
+ if (!ike_cfg->has_proposal(ike_cfg, ike_proposal, private))
+ {
+ DBG2(DBG_CFG, "ignore candidate '%s' without matching IKE proposal",
+ peer_cfg->get_name(peer_cfg));
+ continue;
+ }
peer_cfg->get_ref(peer_cfg);
- if (this->peer_cfg == NULL)
+ if (!this->peer_cfg)
{ /* best match */
this->peer_cfg = peer_cfg;
- this->ike_sa->set_peer_cfg(this->ike_sa, peer_cfg);
}
else
{
@@ -313,6 +359,7 @@ static bool load_cfg_candidates(private_ike_auth_t *this)
enumerator->destroy(enumerator);
if (this->peer_cfg)
{
+ this->ike_sa->set_peer_cfg(this->ike_sa, this->peer_cfg);
DBG1(DBG_CFG, "selected peer config '%s'",
this->peer_cfg->get_name(this->peer_cfg));
return TRUE;
@@ -369,7 +416,7 @@ static bool update_cfg_candidates(private_ike_auth_t *this, bool strict)
{
break;
}
- DBG1(DBG_CFG, "selected peer config '%s' inacceptable: %s",
+ DBG1(DBG_CFG, "selected peer config '%s' unacceptable: %s",
this->peer_cfg->get_name(this->peer_cfg), comply_error);
this->peer_cfg->destroy(this->peer_cfg);
}
@@ -391,6 +438,149 @@ static bool update_cfg_candidates(private_ike_auth_t *this, bool strict)
return this->peer_cfg != NULL;
}
+/**
+ * Currently defined PPK_ID types
+ */
+#define PPK_ID_OPAQUE 1
+#define PPK_ID_FIXED 2
+
+/**
+ * Parse the payload data of the given PPK_IDENTITY notify
+ */
+static bool parse_ppk_identity(notify_payload_t *notify, identification_t **id)
+{
+ chunk_t data;
+
+ data = notify->get_notification_data(notify);
+ if (data.len < 2)
+ {
+ return FALSE;
+ }
+ switch (data.ptr[0])
+ {
+ case PPK_ID_FIXED:
+ data = chunk_skip(data, 1);
+ break;
+ default:
+ return FALSE;
+ }
+ *id = identification_create_from_data(data);
+ return TRUE;
+}
+
+/**
+ * Add a PPK_IDENTITY with the given PPK_ID to the given message
+ */
+static void add_ppk_identity(identification_t *id, message_t *msg)
+{
+ chunk_t data;
+ uint8_t type = PPK_ID_FIXED;
+
+ /* we currently only support one type */
+ data = chunk_cata("cc", chunk_from_thing(type), id->get_encoding(id));
+ msg->add_notify(msg, FALSE, PPK_IDENTITY, data);
+}
+
+/**
+ * Use the given PPK_ID to find a PPK and store it and the ID in the task
+ */
+static bool get_ppk(private_ike_auth_t *this, identification_t *ppk_id)
+{
+ shared_key_t *key;
+
+ key = lib->credmgr->get_shared(lib->credmgr, SHARED_PPK, ppk_id, NULL);
+ if (!key)
+ {
+ if (this->peer_cfg->ppk_required(this->peer_cfg))
+ {
+ DBG1(DBG_CFG, "PPK required but no PPK found for '%Y'", ppk_id);
+ return FALSE;
+ }
+ DBG1(DBG_CFG, "no PPK for '%Y' found, ignored because PPK is not "
+ "required", ppk_id);
+ return TRUE;
+ }
+ this->ppk = chunk_clone(key->get_key(key));
+ this->ppk_id = ppk_id->clone(ppk_id);
+ key->destroy(key);
+ return TRUE;
+}
+
+/**
+ * Check if we have a PPK available and, if not, whether we require one as
+ * initiator
+ */
+static bool get_ppk_i(private_ike_auth_t *this)
+{
+ identification_t *ppk_id;
+
+ if (!this->ike_sa->supports_extension(this->ike_sa, EXT_PPK))
+ {
+ if (this->peer_cfg->ppk_required(this->peer_cfg))
+ {
+ DBG1(DBG_CFG, "PPK required but peer does not support PPK");
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ ppk_id = this->peer_cfg->get_ppk_id(this->peer_cfg);
+ if (!ppk_id)
+ {
+ if (this->peer_cfg->ppk_required(this->peer_cfg))
+ {
+ DBG1(DBG_CFG, "PPK required but no PPK_ID configured");
+ return FALSE;
+ }
+ return TRUE;
+ }
+ return get_ppk(this, ppk_id);
+}
+
+/**
+ * Check if we have a PPK available and if not whether we require one as
+ * responder
+ */
+static bool get_ppk_r(private_ike_auth_t *this, message_t *msg)
+{
+ notify_payload_t *notify;
+ identification_t *ppk_id, *ppk_id_cfg;
+ bool result;
+
+ if (!this->ike_sa->supports_extension(this->ike_sa, EXT_PPK))
+ {
+ if (this->peer_cfg->ppk_required(this->peer_cfg))
+ {
+ DBG1(DBG_CFG, "PPK required but peer does not support PPK");
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ notify = msg->get_notify(msg, PPK_IDENTITY);
+ if (!notify || !parse_ppk_identity(notify, &ppk_id))
+ {
+ if (this->peer_cfg->ppk_required(this->peer_cfg))
+ {
+ DBG1(DBG_CFG, "PPK required but no PPK_IDENTITY received");
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ ppk_id_cfg = this->peer_cfg->get_ppk_id(this->peer_cfg);
+ if (ppk_id_cfg && !ppk_id->matches(ppk_id, ppk_id_cfg))
+ {
+ DBG1(DBG_CFG, "received PPK_ID '%Y', but require '%Y'", ppk_id,
+ ppk_id_cfg);
+ ppk_id->destroy(ppk_id);
+ return FALSE;
+ }
+ result = get_ppk(this, ppk_id);
+ ppk_id->destroy(ppk_id);
+ return result;
+}
+
METHOD(task_t, build_i, status_t,
private_ike_auth_t *this, message_t *message)
{
@@ -401,7 +591,7 @@ METHOD(task_t, build_i, status_t,
return collect_my_init_data(this, message);
}
- if (this->peer_cfg == NULL)
+ if (!this->peer_cfg)
{
this->peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
this->peer_cfg->get_ref(this->peer_cfg);
@@ -420,6 +610,12 @@ METHOD(task_t, build_i, status_t,
/* indicate support for RFC 6311 Message ID synchronization */
message->add_notify(message, FALSE, IKEV2_MESSAGE_ID_SYNC_SUPPORTED,
chunk_empty);
+ /* only use a PPK in the first round */
+ if (!get_ppk_i(this))
+ {
+ charon->bus->alert(charon->bus, ALERT_LOCAL_AUTH_FAILED);
+ return FAILED;
+ }
}
if (!this->do_another_auth && !this->my_auth)
@@ -428,7 +624,7 @@ METHOD(task_t, build_i, status_t,
}
/* check if an authenticator is in progress */
- if (this->my_auth == NULL)
+ if (!this->my_auth)
{
identification_t *idi, *idr = NULL;
id_payload_t *id_payload;
@@ -495,6 +691,14 @@ METHOD(task_t, build_i, status_t,
return FAILED;
}
}
+ /* for authentication methods that return NEED_MORE, the PPK will be reset
+ * in process_i() for messages without PPK_ID notify, so we always set it
+ * during the first round (afterwards the PPK won't be available) */
+ if (this->ppk.ptr && this->my_auth->use_ppk)
+ {
+ this->my_auth->use_ppk(this->my_auth, this->ppk,
+ !this->peer_cfg->ppk_required(this->peer_cfg));
+ }
switch (this->my_auth->build(this->my_auth, message))
{
case SUCCESS:
@@ -509,6 +713,12 @@ METHOD(task_t, build_i, status_t,
return FAILED;
}
+ /* add a PPK_IDENTITY notify to the message that contains AUTH */
+ if (this->ppk_id && message->get_payload(message, PLV2_AUTH))
+ {
+ add_ppk_identity(this->ppk_id, message);
+ }
+
/* check for additional authentication rounds */
if (do_another_auth(this))
{
@@ -536,7 +746,7 @@ METHOD(task_t, process_r, status_t,
return collect_other_init_data(this, message);
}
- if (this->my_auth == NULL && this->do_another_auth)
+ if (!this->my_auth && this->do_another_auth)
{
/* handle (optional) IDr payload, apply proposed identity */
id_payload = (id_payload_t*)message->get_payload(message, PLV2_ID_RESPONDER);
@@ -573,7 +783,7 @@ METHOD(task_t, process_r, status_t,
}
}
- if (this->other_auth == NULL)
+ if (!this->other_auth)
{
/* handle IDi payload */
id_payload = (id_payload_t*)message->get_payload(message, PLV2_ID_INITIATOR);
@@ -588,7 +798,7 @@ METHOD(task_t, process_r, status_t,
cfg = this->ike_sa->get_auth_cfg(this->ike_sa, FALSE);
cfg->add(cfg, AUTH_RULE_IDENTITY, id->clone(id));
- if (this->peer_cfg == NULL)
+ if (!this->peer_cfg)
{
if (!load_cfg_candidates(this))
{
@@ -596,14 +806,14 @@ METHOD(task_t, process_r, status_t,
return NEED_MORE;
}
}
- if (message->get_payload(message, PLV2_AUTH) == NULL)
+ if (!message->get_payload(message, PLV2_AUTH))
{ /* before authenticating with EAP, we need a EAP config */
cand = get_auth_cfg(this, FALSE);
while (!cand || (
(uintptr_t)cand->get(cand, AUTH_RULE_EAP_TYPE) == EAP_NAK &&
(uintptr_t)cand->get(cand, AUTH_RULE_EAP_VENDOR) == 0))
{ /* peer requested EAP, but current config does not match */
- DBG1(DBG_IKE, "peer requested EAP, config inacceptable");
+ DBG1(DBG_IKE, "peer requested EAP, config unacceptable");
this->peer_cfg->destroy(this->peer_cfg);
this->peer_cfg = NULL;
if (!update_cfg_candidates(this, FALSE))
@@ -642,6 +852,19 @@ METHOD(task_t, process_r, status_t,
return NEED_MORE;
}
}
+ if (message->get_payload(message, PLV2_AUTH) &&
+ is_first_round(this, FALSE))
+ {
+ if (!get_ppk_r(this, message))
+ {
+ this->authentication_failed = TRUE;
+ return NEED_MORE;
+ }
+ else if (this->ppk.ptr && this->other_auth->use_ppk)
+ {
+ this->other_auth->use_ppk(this->other_auth, this->ppk, FALSE);
+ }
+ }
switch (this->other_auth->process(this->other_auth, message))
{
case SUCCESS:
@@ -675,7 +898,7 @@ METHOD(task_t, process_r, status_t,
return NEED_MORE;
}
- if (message->get_notify(message, ANOTHER_AUTH_FOLLOWS) == NULL)
+ if (!message->get_notify(message, ANOTHER_AUTH_FOLLOWS))
{
this->expect_another_auth = FALSE;
if (!update_cfg_candidates(this, TRUE))
@@ -687,6 +910,37 @@ METHOD(task_t, process_r, status_t,
return NEED_MORE;
}
+/**
+ * Clear the PPK and PPK_ID
+ */
+static void clear_ppk(private_ike_auth_t *this)
+{
+ DESTROY_IF(this->ppk_id);
+ this->ppk_id = NULL;
+ chunk_clear(&this->ppk);
+}
+
+/**
+ * Derive new keys and clear the PPK
+ */
+static bool apply_ppk(private_ike_auth_t *this)
+{
+ keymat_v2_t *keymat;
+
+ if (this->ppk.ptr)
+ {
+ keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa);
+ if (!keymat->derive_ike_keys_ppk(keymat, this->ppk))
+ {
+ return FALSE;
+ }
+ DBG1(DBG_CFG, "using PPK for PPK_ID '%Y'", this->ppk_id);
+ this->ike_sa->set_condition(this->ike_sa, COND_PPK, TRUE);
+ }
+ clear_ppk(this);
+ return TRUE;
+}
+
METHOD(task_t, build_r, status_t,
private_ike_auth_t *this, message_t *message)
{
@@ -703,12 +957,12 @@ METHOD(task_t, build_r, status_t,
return collect_my_init_data(this, message);
}
- if (this->authentication_failed || this->peer_cfg == NULL)
+ if (this->authentication_failed || !this->peer_cfg)
{
goto peer_auth_failed;
}
- if (this->my_auth == NULL && this->do_another_auth)
+ if (!this->my_auth && this->do_another_auth)
{
identification_t *id, *id_cfg;
id_payload_t *id_payload;
@@ -793,6 +1047,10 @@ METHOD(task_t, build_r, status_t,
}
if (this->my_auth)
{
+ if (this->ppk.ptr && this->my_auth->use_ppk)
+ {
+ this->my_auth->use_ppk(this->my_auth, this->ppk, FALSE);
+ }
switch (this->my_auth->build(this->my_auth, message))
{
case SUCCESS:
@@ -807,6 +1065,16 @@ METHOD(task_t, build_r, status_t,
}
}
+ /* add a PPK_IDENTITY notify and derive new keys and clear the PPK */
+ if (this->ppk.ptr)
+ {
+ message->add_notify(message, FALSE, PPK_IDENTITY, chunk_empty);
+ if (!apply_ppk(this))
+ {
+ goto local_auth_failed;
+ }
+ }
+
/* check for additional authentication rounds */
if (do_another_auth(this))
{
@@ -942,7 +1210,7 @@ METHOD(task_t, process_i, status_t,
enumerator_t *enumerator;
payload_t *payload;
auth_cfg_t *cfg;
- bool mutual_eap = FALSE;
+ bool mutual_eap = FALSE, ppk_id_received = FALSE;
if (message->get_exchange_type(message) == IKE_SA_INIT)
{
@@ -998,6 +1266,9 @@ METHOD(task_t, process_i, status_t,
this->ike_sa->enable_extension(this->ike_sa,
EXT_IKE_MESSAGE_ID_SYNC);
break;
+ case PPK_IDENTITY:
+ ppk_id_received = TRUE;
+ break;
default:
{
if (type <= 16383)
@@ -1019,7 +1290,7 @@ METHOD(task_t, process_i, status_t,
if (this->expect_another_auth)
{
- if (this->other_auth == NULL)
+ if (!this->other_auth)
{
id_payload_t *id_payload;
identification_t *id;
@@ -1059,6 +1330,11 @@ METHOD(task_t, process_i, status_t,
}
if (this->other_auth)
{
+ if (ppk_id_received && is_first_round(this, FALSE) &&
+ this->other_auth->use_ppk)
+ {
+ this->other_auth->use_ppk(this->other_auth, this->ppk, FALSE);
+ }
switch (this->other_auth->process(this->other_auth, message))
{
case SUCCESS:
@@ -1094,6 +1370,14 @@ METHOD(task_t, process_i, status_t,
if (this->my_auth)
{
+ /* while we already set the PPK in build_i(), we MUST not use it if
+ * the peer did not reply with a PPK_ID notify */
+ if (this->ppk.ptr && this->my_auth->use_ppk)
+ {
+ this->my_auth->use_ppk(this->my_auth,
+ ppk_id_received ? this->ppk : chunk_empty,
+ FALSE);
+ }
switch (this->my_auth->process(this->my_auth, message))
{
case SUCCESS:
@@ -1109,11 +1393,29 @@ METHOD(task_t, process_i, status_t,
case NEED_MORE:
break;
default:
- charon->bus->alert(charon->bus, ALERT_LOCAL_AUTH_FAILED);
- send_auth_failed_informational(this, message);
- return FAILED;
+ goto local_auth_failed;
+ }
+ }
+
+ /* change keys and clear PPK after we are done with our authentication, so
+ * we only explicitly use it for the first round, afterwards we just use the
+ * changed SK_p keys implicitly */
+ if (!this->my_auth && this->ppk_id)
+ {
+ if (ppk_id_received)
+ {
+ if (!apply_ppk(this))
+ {
+ goto local_auth_failed;
+ }
+ }
+ else
+ {
+ DBG1(DBG_CFG, "peer didn't use PPK for PPK_ID '%Y'", this->ppk_id);
}
+ clear_ppk(this);
}
+
if (mutual_eap)
{
if (!this->my_auth || !this->my_auth->is_mutual(this->my_auth))
@@ -1124,7 +1426,7 @@ METHOD(task_t, process_i, status_t,
DBG1(DBG_IKE, "allow mutual EAP-only authentication");
}
- if (message->get_notify(message, ANOTHER_AUTH_FOLLOWS) == NULL)
+ if (!message->get_notify(message, ANOTHER_AUTH_FOLLOWS))
{
this->expect_another_auth = FALSE;
}
@@ -1162,6 +1464,10 @@ peer_auth_failed:
charon->bus->alert(charon->bus, ALERT_PEER_AUTH_FAILED);
send_auth_failed_informational(this, message);
return FAILED;
+local_auth_failed:
+ charon->bus->alert(charon->bus, ALERT_LOCAL_AUTH_FAILED);
+ send_auth_failed_informational(this, message);
+ return FAILED;
}
METHOD(task_t, get_type, task_type_t,
@@ -1173,6 +1479,7 @@ METHOD(task_t, get_type, task_type_t,
METHOD(task_t, migrate, void,
private_ike_auth_t *this, ike_sa_t *ike_sa)
{
+ clear_ppk(this);
chunk_free(&this->my_nonce);
chunk_free(&this->other_nonce);
DESTROY_IF(this->my_packet);
@@ -1199,6 +1506,7 @@ METHOD(task_t, migrate, void,
METHOD(task_t, destroy, void,
private_ike_auth_t *this)
{
+ clear_ppk(this);
chunk_free(&this->my_nonce);
chunk_free(&this->other_nonce);
DESTROY_IF(this->my_packet);