ike-auth: Add basic PPK support
Some of the work will have to be done in the authenticators.
This commit is contained in:
parent
94f9f421bc
commit
a9e60c96dc
|
@ -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>
|
||||
|
||||
|
@ -59,6 +60,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
|
||||
*/
|
||||
|
@ -278,6 +289,29 @@ static bool do_another_auth(private_ike_auth_t *this)
|
|||
return do_another;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -404,6 +438,151 @@ 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_OPAQUE:
|
||||
/* we currently don't support this */
|
||||
default:
|
||||
return FALSE;
|
||||
case PPK_ID_FIXED:
|
||||
data = chunk_skip(data, 1);
|
||||
break;
|
||||
}
|
||||
*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)
|
||||
{
|
||||
|
@ -433,6 +612,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)
|
||||
|
@ -508,6 +693,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:
|
||||
|
@ -522,6 +715,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))
|
||||
{
|
||||
|
@ -655,6 +854,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:
|
||||
|
@ -700,6 +912,36 @@ 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);
|
||||
}
|
||||
clear_ppk(this);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
METHOD(task_t, build_r, status_t,
|
||||
private_ike_auth_t *this, message_t *message)
|
||||
{
|
||||
|
@ -806,6 +1048,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:
|
||||
|
@ -820,6 +1066,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))
|
||||
{
|
||||
|
@ -955,7 +1211,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)
|
||||
{
|
||||
|
@ -1011,6 +1267,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)
|
||||
|
@ -1072,6 +1331,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:
|
||||
|
@ -1107,6 +1371,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:
|
||||
|
@ -1122,11 +1394,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))
|
||||
|
@ -1175,6 +1465,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,
|
||||
|
@ -1186,6 +1480,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);
|
||||
|
@ -1212,6 +1507,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);
|
||||
|
|
Loading…
Reference in New Issue