Merge branch 'multi-ke'

This adds support for multiple key exchanges (no KEMs yet as none are
standardized so far).  Work on this started over five years ago and went
through multiple iterations (first our own protocol, then standardized
extensions in different variations).

IKE_INTERMEDIATE exchanges, defined RFC 9242, are used to transport
multiple KE payloads between the IKE_SA_INIT and IKE_AUTH exchanges.
To rekey IKE and CHILD_SAs with multiple key exchanges, IKE_FOLLOWUP_KE
exchanges are used, as defined in RFC 9370.

In proposals, additional key exchange methods are configured via `keX_`
prefix, where X is a number between 1 and 7.  For example, `ke1_ecp256`
adds ECP_256 as additional KE method.  As with regular key exchanges,
peers have to agree on a method for each round unless no algorithms are
defined by both or `keX_none` is configured to make that round explicitly
optional.

Also changed is how rekey collisions are handled, which makes CHILD_SAs
properly trackable via child_rekey() hook.
This commit is contained in:
Tobias Brunner
2024-08-07 16:20:42 +02:00
69 changed files with 8437 additions and 1600 deletions
+8 -10
View File
@@ -107,29 +107,27 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
}
esa = *(esa_info_t *)(data->enc_key.ptr);
/* only handle the case where we have both distinct ESP spi's available */
if (esa.spi_r == id->spi)
/* only handle the case where we have both distinct ESP SPIs available,
* which is always the outbound SA */
if (esa.spi_l == id->spi)
{
chunk_free(&esa.nonce_i);
chunk_free(&esa.nonce_r);
return SUCCESS;
}
spi_loc = esa.spi_l;
spi_rem = id->spi;
local = id->src;
peer = id->dst;
if (data->initiator)
{
spi_loc = id->spi;
spi_rem = esa.spi_r;
local = id->dst;
peer = id->src;
nonce_loc = &esa.nonce_i;
nonce_rem = &esa.nonce_r;
}
else
{
spi_loc = esa.spi_r;
spi_rem = id->spi;
local = id->src;
peer = id->dst;
nonce_loc = &esa.nonce_r;
nonce_rem = &esa.nonce_i;
}
+36 -12
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2015 Tobias Brunner
* Copyright (C) 2015-2020 Tobias Brunner
* Copyright (C) 2012 Reto Buerki
* Copyright (C) 2012 Adrian-Ken Rueegsegger
*
@@ -95,13 +95,14 @@ METHOD(keymat_t, create_nonce_gen, nonce_gen_t*,
}
METHOD(keymat_v2_t, derive_ike_keys, bool,
private_tkm_keymat_t *this, proposal_t *proposal, key_exchange_t *ke,
private_tkm_keymat_t *this, proposal_t *proposal, array_t *kes,
chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id,
pseudo_random_function_t rekey_function, chunk_t rekey_skd)
{
uint64_t nc_id, spi_loc, spi_rem;
chunk_t *nonce;
tkm_diffie_hellman_t *tkm_dh;
key_exchange_t *ke;
dh_id_type dh_id;
nonce_type nonce_rem;
result_type res;
@@ -109,6 +110,12 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
icv_len_type icv_len;
iv_len_type iv_len;
if (array_count(kes) != 1)
{
DBG1(DBG_IKE, "the TKM currently only supports a single key exchange");
return FALSE;
}
/* Acquire nonce context id */
nonce = this->initiator ? &nonce_i : &nonce_r;
nc_id = tkm->chunk_map->get_id(tkm->chunk_map, nonce);
@@ -119,6 +126,7 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
}
/* Get DH context id */
array_get(kes, ARRAY_HEAD, &ke);
tkm_dh = (tkm_diffie_hellman_t *)ke;
dh_id = tkm_dh->get_id(tkm_dh);
@@ -198,21 +206,22 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
}
METHOD(keymat_v2_t, derive_child_keys, bool,
private_tkm_keymat_t *this, proposal_t *proposal, key_exchange_t *ke,
private_tkm_keymat_t *this, proposal_t *proposal, array_t *kes,
chunk_t nonce_i, chunk_t nonce_r, chunk_t *encr_i, chunk_t *integ_i,
chunk_t *encr_r, chunk_t *integ_r)
{
esa_info_t *esa_info_i, *esa_info_r;
dh_id_type dh_id = 0;
key_exchange_t *ke;
if (ke)
if (kes && array_get(kes, ARRAY_HEAD, &ke))
{
dh_id = ((tkm_diffie_hellman_t *)ke)->get_id((tkm_diffie_hellman_t *)ke);
}
INIT(esa_info_i,
.isa_id = this->isa_ctx_id,
.spi_r = proposal->get_spi(proposal),
.spi_l = proposal->get_spi(proposal),
.nonce_i = chunk_clone(nonce_i),
.nonce_r = chunk_clone(nonce_r),
.is_encr_r = FALSE,
@@ -221,15 +230,15 @@ METHOD(keymat_v2_t, derive_child_keys, bool,
INIT(esa_info_r,
.isa_id = this->isa_ctx_id,
.spi_r = proposal->get_spi(proposal),
.spi_l = proposal->get_spi(proposal),
.nonce_i = chunk_clone(nonce_i),
.nonce_r = chunk_clone(nonce_r),
.is_encr_r = TRUE,
.dh_id = dh_id,
);
DBG1(DBG_CHD, "passing on esa info (isa: %llu, spi_r: %x, dh_id: %llu)",
esa_info_i->isa_id, ntohl(esa_info_i->spi_r), esa_info_i->dh_id);
DBG1(DBG_CHD, "passing on esa info (isa: %llu, spi_l: %x, dh_id: %llu)",
esa_info_i->isa_id, ntohl(esa_info_i->spi_l), esa_info_i->dh_id);
/* store ESA info in encr_i/r, which is passed to add_sa */
*encr_i = chunk_create((u_char *)esa_info_i, sizeof(esa_info_t));
@@ -246,10 +255,18 @@ METHOD(keymat_t, get_aead, aead_t*,
return this->aead;
}
METHOD(keymat_v2_t, get_int_auth, bool,
private_tkm_keymat_t *this, bool verify, chunk_t data, chunk_t prev,
chunk_t *auth)
{
DBG1(DBG_IKE, "TKM doesn't support IntAuth calculation");
return FALSE;
}
METHOD(keymat_v2_t, get_auth_octets, bool,
private_tkm_keymat_t *this, bool verify, chunk_t ike_sa_init,
chunk_t nonce, chunk_t ppk, identification_t *id, char reserved[3],
chunk_t *octets, array_t *schemes)
chunk_t nonce, chunk_t int_auth, chunk_t ppk, identification_t *id,
char reserved[3], chunk_t *octets, array_t *schemes)
{
sign_info_t *sign;
@@ -279,6 +296,12 @@ METHOD(keymat_v2_t, get_skd, pseudo_random_function_t,
{
isa_info_t *isa_info;
if (!this->ae_ctx_id)
{
*skd = chunk_empty;
return PRF_UNDEFINED;
}
INIT(isa_info,
.parent_isa_id = this->isa_ctx_id,
.ae_id = this->ae_ctx_id,
@@ -291,8 +314,8 @@ METHOD(keymat_v2_t, get_skd, pseudo_random_function_t,
METHOD(keymat_v2_t, get_psk_sig, bool,
private_tkm_keymat_t *this, bool verify, chunk_t ike_sa_init, chunk_t nonce,
chunk_t secret, chunk_t ppk, identification_t *id, char reserved[3],
chunk_t *sig)
chunk_t int_auth, chunk_t secret, chunk_t ppk, identification_t *id,
char reserved[3], chunk_t *sig)
{
return FALSE;
}
@@ -388,6 +411,7 @@ tkm_keymat_t *tkm_keymat_create(bool initiator)
.derive_ike_keys_ppk = (void*)return_false,
.derive_child_keys = _derive_child_keys,
.get_skd = _get_skd,
.get_int_auth = _get_int_auth,
.get_auth_octets = _get_auth_octets,
.get_psk_sig = _get_psk_sig,
.add_hash_algorithm = _add_hash_algorithm,
+2 -2
View File
@@ -49,9 +49,9 @@ struct esa_info_t {
isa_id_type isa_id;
/**
* Responder SPI of child SA.
* Local SPI of child SA.
*/
esp_spi_type spi_r;
esp_spi_type spi_l;
/**
* Initiator nonce.
+10 -5
View File
@@ -55,9 +55,12 @@ START_TEST(test_derive_ike_keys)
ck_assert(dh->ke.get_public_key(&dh->ke, &pubvalue));
ck_assert(dh->ke.set_public_key(&dh->ke, pubvalue));
array_t *kes = NULL;
array_insert_create(&kes, ARRAY_TAIL, dh);
fail_unless(keymat->keymat_v2.derive_ike_keys(&keymat->keymat_v2, proposal,
&dh->ke, nonce, nonce, ike_sa_id, PRF_UNDEFINED, chunk_empty),
kes, nonce, nonce, ike_sa_id, PRF_UNDEFINED, chunk_empty),
"Key derivation failed");
array_destroy(kes);
chunk_free(&nonce);
aead_t * const aead = keymat->keymat_v2.keymat.get_aead(&keymat->keymat_v2.keymat, TRUE);
@@ -92,17 +95,19 @@ START_TEST(test_derive_child_keys)
chunk_t encr_i, encr_r, integ_i, integ_r;
chunk_t nonce = chunk_from_chars("test chunk");
array_t *kes = NULL;
array_insert_create(&kes, ARRAY_TAIL, dh);
fail_unless(keymat->keymat_v2.derive_child_keys(&keymat->keymat_v2, proposal,
&dh->ke,
nonce, nonce, &encr_i,
kes, nonce, nonce, &encr_i,
&integ_i, &encr_r, &integ_r),
"Child key derivation failed");
array_destroy(kes);
esa_info_t *info = (esa_info_t *)encr_i.ptr;
fail_if(!info, "encr_i does not contain esa information");
fail_if(info->isa_id != keymat->get_isa_id(keymat),
"Isa context id mismatch (encr_i)");
fail_if(info->spi_r != 42,
fail_if(info->spi_l != 42,
"SPI mismatch (encr_i)");
fail_unless(chunk_equals(info->nonce_i, nonce),
"nonce_i mismatch (encr_i)");
@@ -119,7 +124,7 @@ START_TEST(test_derive_child_keys)
fail_if(!info, "encr_r does not contain esa information");
fail_if(info->isa_id != keymat->get_isa_id(keymat),
"Isa context id mismatch (encr_r)");
fail_if(info->spi_r != 42,
fail_if(info->spi_l != 42,
"SPI mismatch (encr_r)");
fail_unless(chunk_equals(info->nonce_i, nonce),
"nonce_i mismatch (encr_r)");
+2 -2
View File
@@ -239,8 +239,8 @@ static bool build_auth(private_pretend_auth_t *this,
}
keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
if (!keymat->get_auth_octets(keymat, TRUE, this->ike_init, this->nonce,
chunk_empty, this->id, this->reserved,
&octets, NULL))
chunk_empty, chunk_empty, this->id,
this->reserved, &octets, NULL))
{
private->destroy(private);
return FALSE;
+2 -1
View File
@@ -138,7 +138,8 @@ static bool rebuild_auth(private_rebuild_auth_t *this, ike_sa_t *ike_sa,
}
keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
if (!keymat->get_auth_octets(keymat, FALSE, this->ike_init, this->nonce,
chunk_empty, id, reserved, &octets, NULL))
chunk_empty, chunk_empty, id, reserved,
&octets, NULL))
{
private->destroy(private);
id->destroy(id);
+4 -4
View File
@@ -574,7 +574,7 @@ METHOD(bus_t, message, void,
}
METHOD(bus_t, ike_keys, void,
private_bus_t *this, ike_sa_t *ike_sa, key_exchange_t *dh,
private_bus_t *this, ike_sa_t *ike_sa, array_t *kes,
chunk_t dh_other, chunk_t nonce_i, chunk_t nonce_r,
ike_sa_t *rekey, shared_key_t *shared, auth_method_t method)
{
@@ -591,7 +591,7 @@ METHOD(bus_t, ike_keys, void,
continue;
}
entry->calling++;
keep = entry->listener->ike_keys(entry->listener, ike_sa, dh, dh_other,
keep = entry->listener->ike_keys(entry->listener, ike_sa, kes, dh_other,
nonce_i, nonce_r, rekey, shared,
method);
entry->calling--;
@@ -639,7 +639,7 @@ METHOD(bus_t, ike_derived_keys, void,
METHOD(bus_t, child_keys, void,
private_bus_t *this, child_sa_t *child_sa, bool initiator,
key_exchange_t *dh, chunk_t nonce_i, chunk_t nonce_r)
array_t *kes, chunk_t nonce_i, chunk_t nonce_r)
{
enumerator_t *enumerator;
ike_sa_t *ike_sa;
@@ -658,7 +658,7 @@ METHOD(bus_t, child_keys, void,
}
entry->calling++;
keep = entry->listener->child_keys(entry->listener, ike_sa,
child_sa, initiator, dh, nonce_i, nonce_r);
child_sa, initiator, kes, nonce_i, nonce_r);
entry->calling--;
if (!keep)
{
+5 -4
View File
@@ -30,6 +30,7 @@ typedef struct bus_t bus_t;
#include <stdarg.h>
#include <utils/debug.h>
#include <collections/array.h>
#include <sa/ike_sa.h>
#include <sa/child_sa.h>
#include <processing/jobs/job.h>
@@ -348,7 +349,7 @@ struct bus_t {
* IKE_SA keymat hook.
*
* @param ike_sa IKE_SA this keymat belongs to
* @param dh diffie hellman shared secret
* @param kes array of key_exchange_t*
* @param dh_other others DH public value (IKEv1 only)
* @param nonce_i initiator's nonce
* @param nonce_r responder's nonce
@@ -356,7 +357,7 @@ struct bus_t {
* @param shared shared key used for key derivation (IKEv1-PSK only)
* @param method auth method for key derivation (IKEv1-non-PSK only)
*/
void (*ike_keys)(bus_t *this, ike_sa_t *ike_sa, key_exchange_t *dh,
void (*ike_keys)(bus_t *this, ike_sa_t *ike_sa, array_t *kes,
chunk_t dh_other, chunk_t nonce_i, chunk_t nonce_r,
ike_sa_t *rekey, shared_key_t *shared,
auth_method_t method);
@@ -381,12 +382,12 @@ struct bus_t {
*
* @param child_sa CHILD_SA this keymat is used for
* @param initiator initiator of the CREATE_CHILD_SA exchange
* @param dh diffie hellman shared secret
* @param kes array of key_exchange_t*, or NULL
* @param nonce_i initiator's nonce
* @param nonce_r responder's nonce
*/
void (*child_keys)(bus_t *this, child_sa_t *child_sa, bool initiator,
key_exchange_t *dh, chunk_t nonce_i, chunk_t nonce_r);
array_t *kes, chunk_t nonce_i, chunk_t nonce_r);
/**
* CHILD_SA derived keys hook.
+4 -4
View File
@@ -83,7 +83,7 @@ struct listener_t {
* Hook called with IKE_SA key material.
*
* @param ike_sa IKE_SA this keymat belongs to
* @param dh diffie hellman shared secret
* @param kes array of key_exchange_t*
* @param dh_other others DH public value (IKEv1 only)
* @param nonce_i initiator's nonce
* @param nonce_r responder's nonce
@@ -92,7 +92,7 @@ struct listener_t {
* @param method auth method for key derivation (IKEv1-non-PSK only)
* @return TRUE to stay registered, FALSE to unregister
*/
bool (*ike_keys)(listener_t *this, ike_sa_t *ike_sa, key_exchange_t *dh,
bool (*ike_keys)(listener_t *this, ike_sa_t *ike_sa, array_t *kes,
chunk_t dh_other, chunk_t nonce_i, chunk_t nonce_r,
ike_sa_t *rekey, shared_key_t *shared,
auth_method_t method);
@@ -119,13 +119,13 @@ struct listener_t {
* @param ike_sa IKE_SA the child sa belongs to
* @param child_sa CHILD_SA this keymat is used for
* @param initiator initiator of the CREATE_CHILD_SA exchange
* @param dh diffie hellman shared secret
* @param kes array of key_exchange_t*, or NULL
* @param nonce_i initiator's nonce
* @param nonce_r responder's nonce
* @return TRUE to stay registered, FALSE to unregister
*/
bool (*child_keys)(listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
bool initiator, key_exchange_t *dh,
bool initiator, array_t *kes,
chunk_t nonce_i, chunk_t nonce_r);
/**
+4 -1
View File
@@ -430,7 +430,10 @@ METHOD(generator_t, get_chunk, chunk_t,
{
chunk_t data;
*lenpos = (uint32_t*)(this->buffer + this->header_length_offset);
if (lenpos)
{
*lenpos = (uint32_t*)(this->buffer + this->header_length_offset);
}
data = chunk_create(this->buffer, get_length(this));
if (this->debug)
{
+177 -5
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2006-2018 Tobias Brunner
* Copyright (C) 2006-2020 Tobias Brunner
* Copyright (C) 2005-2010 Martin Willi
* Copyright (C) 2006 Daniel Roethlisberger
* Copyright (C) 2005 Jan Hutter
@@ -400,6 +400,86 @@ static payload_order_t create_child_sa_r_order[] = {
{PLV2_FRAGMENT, 0},
};
/**
* Message rule for IKE_INTERMEDIATE from initiator.
*/
static payload_rule_t ike_intermediate_i_rules[] = {
/* payload type min max encr suff */
{PLV2_FRAGMENT, 0, 1, TRUE, TRUE},
{PLV2_NOTIFY, 0, MAX_NOTIFY_PAYLOADS, TRUE, FALSE},
{PLV2_KEY_EXCHANGE, 0, 1, TRUE, FALSE},
};
/**
* payload order for IKE_INTERMEDIATE initiator
*/
static payload_order_t ike_intermediate_i_order[] = {
/* payload type notify type */
{PLV2_KEY_EXCHANGE, 0},
{PLV2_NOTIFY, 0},
{PLV2_FRAGMENT, 0},
};
/**
* Message rule for IKE_INTERMEDIATE from responder.
*/
static payload_rule_t ike_intermediate_r_rules[] = {
/* payload type min max encr suff */
{PLV2_FRAGMENT, 0, 1, TRUE, TRUE},
{PLV2_NOTIFY, 0, MAX_NOTIFY_PAYLOADS, TRUE, TRUE},
{PLV2_KEY_EXCHANGE, 0, 1, TRUE, FALSE},
};
/**
* payload order for IKE_INTERMEDIATE responder
*/
static payload_order_t ike_intermediate_r_order[] = {
/* payload type notify type */
{PLV2_KEY_EXCHANGE, 0},
{PLV2_NOTIFY, 0},
{PLV2_FRAGMENT, 0},
};
/**
* Message rule for IKE_FOLLOWUP_KE from initiator.
*/
static payload_rule_t ike_followup_ke_i_rules[] = {
/* payload type min max encr suff */
{PLV2_FRAGMENT, 0, 1, TRUE, TRUE},
{PLV2_NOTIFY, 0, MAX_NOTIFY_PAYLOADS, TRUE, FALSE},
{PLV2_KEY_EXCHANGE, 1, 1, TRUE, FALSE},
};
/**
* payload order for IKE_FOLLOWUP_KE initiator
*/
static payload_order_t ike_followup_ke_i_order[] = {
/* payload type notify type */
{PLV2_KEY_EXCHANGE, 0},
{PLV2_NOTIFY, 0},
{PLV2_FRAGMENT, 0},
};
/**
* Message rule for IKE_FOLLOWUP_KE from responder.
*/
static payload_rule_t ike_followup_ke_r_rules[] = {
/* payload type min max encr suff */
{PLV2_FRAGMENT, 0, 1, TRUE, TRUE},
{PLV2_NOTIFY, 0, MAX_NOTIFY_PAYLOADS, TRUE, TRUE},
{PLV2_KEY_EXCHANGE, 1, 1, TRUE, FALSE},
};
/**
* payload order for IKE_FOLLOWUP_KE responder
*/
static payload_order_t ike_followup_ke_r_order[] = {
/* payload type notify type */
{PLV2_KEY_EXCHANGE, 0},
{PLV2_NOTIFY, 0},
{PLV2_FRAGMENT, 0},
};
#ifdef ME
/**
* Message rule for ME_CONNECT from initiator.
@@ -767,6 +847,22 @@ static message_rule_t message_rules[] = {
countof(create_child_sa_r_rules), create_child_sa_r_rules,
countof(create_child_sa_r_order), create_child_sa_r_order,
},
{IKE_INTERMEDIATE, TRUE, TRUE,
countof(ike_intermediate_i_rules), ike_intermediate_i_rules,
countof(ike_intermediate_i_order), ike_intermediate_i_order,
},
{IKE_INTERMEDIATE, FALSE, TRUE,
countof(ike_intermediate_r_rules), ike_intermediate_r_rules,
countof(ike_intermediate_r_order), ike_intermediate_r_order,
},
{IKE_FOLLOWUP_KE, TRUE, TRUE,
countof(ike_followup_ke_i_rules), ike_followup_ke_i_rules,
countof(ike_followup_ke_i_order), ike_followup_ke_i_order,
},
{IKE_FOLLOWUP_KE, FALSE, TRUE,
countof(ike_followup_ke_r_rules), ike_followup_ke_r_rules,
countof(ike_followup_ke_r_order), ike_followup_ke_r_order,
},
#ifdef ME
{ME_CONNECT, TRUE, TRUE,
countof(me_connect_i_rules), me_connect_i_rules,
@@ -1888,6 +1984,82 @@ METHOD(message_t, generate, status_t,
return SUCCESS;
}
METHOD(message_t, get_plain, bool,
private_message_t *this, chunk_t *plain)
{
generator_t *generator, *enc_generator;
enumerator_t *enumerator;
ike_header_t *ike_header;
payload_t *payload;
encrypted_payload_t *encrypted;
chunk_t int_auth_a, enc_header, int_auth_p;
struct {
uint8_t next_payload;
uint8_t flags;
uint16_t length;
} __attribute__((packed)) header = {};
uint32_t *lenpos;
if (this->major_version == IKEV1_MAJOR_VERSION ||
this->exchange_type != IKE_INTERMEDIATE)
{
return FALSE;
}
/* we expect to be called after the message has either been parsed
* or already generated once, so we don't modify payload order */
generator = generator_create_no_dbg();
ike_header = create_header(this);
payload = (payload_t*)ike_header;
/* for parsed messages the payloads were already extracted from the
* encrypted payload, if there were any unprotected paylaods we wouldn't
* know. lets assume there aren't any (also for sent messages) */
payload->set_next_type(payload, PLV2_ENCRYPTED);
generator->generate_payload(generator, payload);
int_auth_a = generator->get_chunk(generator, &lenpos);
enc_generator = generator_create_no_dbg();
this->payloads->get_first(this->payloads, (void**)&payload);
if (payload && payload->get_type(payload) == PLV2_ENCRYPTED)
{
/* we have to generate only the contents of this payload,
* not the payload itself, the header is added manually */
encrypted = (encrypted_payload_t*)payload;
encrypted->generate_payloads(encrypted, enc_generator);
header.next_payload = payload->get_next_type(payload);
}
else
{
/* as mentioned above, assume all received payloads were contained in an
* encrypted payload */
enumerator = create_payload_enumerator(this);
while (enumerator->enumerate(enumerator, &payload))
{
enc_generator->generate_payload(enc_generator, payload);
}
enumerator->destroy(enumerator);
header.next_payload = this->first_payload;
}
int_auth_p = enc_generator->get_chunk(enc_generator, NULL);
/* flags are currently no copied, but the critical bit and the reserved
* bits MUST be zero for encrypted payloads, so that's what we assume */
enc_header = chunk_from_thing(header);
header.length = htons(enc_header.len + int_auth_p.len);
htoun32(lenpos, int_auth_a.len + enc_header.len + int_auth_p.len);
*plain = chunk_cat("ccc", int_auth_a, enc_header, int_auth_p);
enc_generator->destroy(enc_generator);
generator->destroy(generator);
ike_header->destroy(ike_header);
return TRUE;
}
/**
* Creates a (basic) clone of the given message
*/
@@ -1994,7 +2166,6 @@ METHOD(message_t, fragment, status_t,
host_t *src, *dst;
chunk_t data;
status_t status;
uint32_t *lenpos;
size_t len;
src = this->packet->get_source(this->packet);
@@ -2027,7 +2198,7 @@ METHOD(message_t, fragment, status_t,
DESTROY_IF(generator);
return status;
}
data = generator->get_chunk(generator, &lenpos);
data = generator->get_chunk(generator, NULL);
len = data.len + (encrypted ? encrypted->get_length(encrypted) : 0);
}
@@ -2083,10 +2254,10 @@ METHOD(message_t, fragment, status_t,
}
next = encrypted->payload_interface.get_next_type((payload_t*)encrypted);
encrypted->generate_payloads(encrypted, generator);
data = generator->get_chunk(generator, &lenpos);
data = generator->get_chunk(generator, NULL);
if (!is_encoded(this))
{
encrypted->destroy(encrypted);
this->payloads->insert_last(this->payloads, encrypted);
}
aead = keymat->get_aead(keymat, FALSE);
/* overhead for the encrypted fragment payload */
@@ -3036,6 +3207,7 @@ message_t *message_create_from_packet(packet_t *packet)
.get_fragments = _get_fragments,
.get_metadata = _get_metadata,
.set_metadata = _set_metadata,
.get_plain = _get_plain,
.destroy = _destroy,
},
.exchange_type = EXCHANGE_TYPE_UNDEFINED,
+15
View File
@@ -258,6 +258,21 @@ struct message_t {
*/
status_t (*generate) (message_t *this, keymat_t *keymat, packet_t **packet);
/**
* Generate the plaintext encoding of this message as needed to authenticate
* IKE_INTERMEDIATE exchanges.
*
* The data returned is the concatenation of the IKE header and plaintext
* payloads (if any) up until the end of the header of the Encrypted
* Payload followed by the plaintext data of the Encrypted Payload (if any).
* Lenght fields are adjusted to only contain that of returned data (e.g.
* IV or padding is ignored).
*
* @param[out] plain plaintext encoding (allocated)
* @return TRUE if generated successfully
*/
bool (*get_plain)(message_t *this, chunk_t *plain);
/**
* Check if the message has already been encoded using generate().
*
+12 -6
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2007 Tobias Brunner
* Copyright (C) 2007-2020 Tobias Brunner
* Copyright (C) 2005-2006 Martin Willi
* Copyright (C) 2005 Jan Hutter
*
@@ -129,15 +129,19 @@ ENUM_NEXT(exchange_type_names, QUICK_MODE, IKE_SESSION_RESUME, TRANSACTION,
"CREATE_CHILD_SA",
"INFORMATIONAL",
"IKE_SESSION_RESUME");
ENUM_NEXT(exchange_type_names, IKE_INTERMEDIATE, IKE_FOLLOWUP_KE,
IKE_SESSION_RESUME,
"IKE_INTERMEDIATE",
"IKE_FOLLOWUP_KE");
#ifdef ME
ENUM_NEXT(exchange_type_names, ME_CONNECT, ME_CONNECT, IKE_SESSION_RESUME,
ENUM_NEXT(exchange_type_names, ME_CONNECT, ME_CONNECT, IKE_FOLLOWUP_KE,
"ME_CONNECT");
ENUM_NEXT(exchange_type_names, EXCHANGE_TYPE_UNDEFINED,
EXCHANGE_TYPE_UNDEFINED, ME_CONNECT,
ENUM_NEXT(exchange_type_names, EXCHANGE_TYPE_UNDEFINED, EXCHANGE_TYPE_UNDEFINED,
ME_CONNECT,
"EXCHANGE_TYPE_UNDEFINED");
#else
ENUM_NEXT(exchange_type_names, EXCHANGE_TYPE_UNDEFINED,
EXCHANGE_TYPE_UNDEFINED, IKE_SESSION_RESUME,
ENUM_NEXT(exchange_type_names, EXCHANGE_TYPE_UNDEFINED, EXCHANGE_TYPE_UNDEFINED,
IKE_FOLLOWUP_KE,
"EXCHANGE_TYPE_UNDEFINED");
#endif /* ME */
ENUM_END(exchange_type_names, EXCHANGE_TYPE_UNDEFINED);
@@ -218,10 +222,12 @@ METHOD(payload_t, verify, status_t,
}
break;
case IKE_SA_INIT:
case IKE_INTERMEDIATE:
case IKE_AUTH:
case CREATE_CHILD_SA:
case INFORMATIONAL:
case IKE_SESSION_RESUME:
case IKE_FOLLOWUP_KE:
#ifdef ME
case ME_CONNECT:
#endif /* ME */
+7 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2007 Tobias Brunner
* Copyright (C) 2007-2020 Tobias Brunner
* Copyright (C) 2005-2011 Martin Willi
* Copyright (C) 2005 Jan Hutter
*
@@ -122,6 +122,12 @@ enum exchange_type_t{
*/
IKE_SESSION_RESUME = 38,
/* IKE_INTERMEDIATE (RFC 9242) */
IKE_INTERMEDIATE = 43,
/* IKE_FOLLOWUP_KE (RFC 9370) */
IKE_FOLLOWUP_KE = 44,
#ifdef ME
/**
* ME_CONNECT
@@ -61,7 +61,9 @@ ENUM_NEXT(notify_type_names, SINGLE_PAIR_REQUIRED, CHILD_SA_NOT_FOUND, AUTHENTIC
"USE_ASSIGNED_HoA",
"TEMPORARY_FAILURE",
"CHILD_SA_NOT_FOUND");
ENUM_NEXT(notify_type_names, ME_CONNECT_FAILED, ME_CONNECT_FAILED, CHILD_SA_NOT_FOUND,
ENUM_NEXT(notify_type_names, STATE_NOT_FOUND, STATE_NOT_FOUND, CHILD_SA_NOT_FOUND,
"STATE_NOT_FOUND");
ENUM_NEXT(notify_type_names, ME_CONNECT_FAILED, ME_CONNECT_FAILED, STATE_NOT_FOUND,
"ME_CONNECT_FAILED");
ENUM_NEXT(notify_type_names, MS_NOTIFY_STATUS, MS_NOTIFY_STATUS, ME_CONNECT_FAILED,
"MS_NOTIFY_STATUS");
@@ -114,11 +116,14 @@ ENUM_NEXT(notify_type_names, INITIAL_CONTACT, SIGNATURE_HASH_ALGORITHMS, MS_NOTI
"SENDER_REQUEST_ID",
"FRAGMENTATION_SUPPORTED",
"SIGNATURE_HASH_ALGORITHMS");
ENUM_NEXT(notify_type_names, USE_PPK, NO_PPK_AUTH, SIGNATURE_HASH_ALGORITHMS,
ENUM_NEXT(notify_type_names, USE_PPK, INTERMEDIATE_EXCHANGE_SUPPORTED, SIGNATURE_HASH_ALGORITHMS,
"USE_PPK",
"PPK_IDENTITY",
"NO_PPK_AUTH");
ENUM_NEXT(notify_type_names, INITIAL_CONTACT_IKEV1, INITIAL_CONTACT_IKEV1, NO_PPK_AUTH,
"NO_PPK_AUTH",
"INTERMEDIATE_EXCHANGE_SUPPORTED");
ENUM_NEXT(notify_type_names, ADDITIONAL_KEY_EXCHANGE, ADDITIONAL_KEY_EXCHANGE, INTERMEDIATE_EXCHANGE_SUPPORTED,
"ADDITIONAL_KEY_EXCHANGE");
ENUM_NEXT(notify_type_names, INITIAL_CONTACT_IKEV1, INITIAL_CONTACT_IKEV1, ADDITIONAL_KEY_EXCHANGE,
"INITIAL_CONTACT");
ENUM_NEXT(notify_type_names, DPD_R_U_THERE, DPD_R_U_THERE_ACK, INITIAL_CONTACT_IKEV1,
"DPD_R_U_THERE",
@@ -175,7 +180,9 @@ ENUM_NEXT(notify_type_short_names, SINGLE_PAIR_REQUIRED, CHILD_SA_NOT_FOUND, AUT
"ASSIGNED_HoA",
"TEMP_FAIL",
"NO_CHILD_SA");
ENUM_NEXT(notify_type_short_names, ME_CONNECT_FAILED, ME_CONNECT_FAILED, CHILD_SA_NOT_FOUND,
ENUM_NEXT(notify_type_short_names, STATE_NOT_FOUND, STATE_NOT_FOUND, CHILD_SA_NOT_FOUND,
"NO_STATE");
ENUM_NEXT(notify_type_short_names, ME_CONNECT_FAILED, ME_CONNECT_FAILED, STATE_NOT_FOUND,
"ME_CONN_FAIL");
ENUM_NEXT(notify_type_short_names, MS_NOTIFY_STATUS, MS_NOTIFY_STATUS, ME_CONNECT_FAILED,
"MS_STATUS");
@@ -228,11 +235,14 @@ ENUM_NEXT(notify_type_short_names, INITIAL_CONTACT, SIGNATURE_HASH_ALGORITHMS, M
"SENDER_REQ_ID",
"FRAG_SUP",
"HASH_ALG");
ENUM_NEXT(notify_type_short_names, USE_PPK, NO_PPK_AUTH, SIGNATURE_HASH_ALGORITHMS,
ENUM_NEXT(notify_type_short_names, USE_PPK, INTERMEDIATE_EXCHANGE_SUPPORTED, SIGNATURE_HASH_ALGORITHMS,
"USE_PPK",
"PPK_ID",
"NO_PPK");
ENUM_NEXT(notify_type_short_names, INITIAL_CONTACT_IKEV1, INITIAL_CONTACT_IKEV1, NO_PPK_AUTH,
"NO_PPK",
"IKE_INT_SUP");
ENUM_NEXT(notify_type_short_names, ADDITIONAL_KEY_EXCHANGE, ADDITIONAL_KEY_EXCHANGE, INTERMEDIATE_EXCHANGE_SUPPORTED,
"ADD_KE");
ENUM_NEXT(notify_type_short_names, INITIAL_CONTACT_IKEV1, INITIAL_CONTACT_IKEV1, ADDITIONAL_KEY_EXCHANGE,
"INITIAL_CONTACT");
ENUM_NEXT(notify_type_short_names, DPD_R_U_THERE, DPD_R_U_THERE_ACK, INITIAL_CONTACT_IKEV1,
"DPD",
@@ -80,6 +80,9 @@ enum notify_type_t {
TEMPORARY_FAILURE = 43,
CHILD_SA_NOT_FOUND = 44,
/* multiple key exchanges, RFC 9370 */
STATE_NOT_FOUND = 47,
/* IKE-ME, private use */
ME_CONNECT_FAILED = 8192,
@@ -154,19 +157,29 @@ enum notify_type_t {
FRAGMENTATION_SUPPORTED = 16430,
/* Signature Hash Algorithms, RFC 7427 */
SIGNATURE_HASH_ALGORITHMS = 16431,
/* Use Postquantum Preshared Key, RFC 8784 */
USE_PPK = 16435,
/* Postquantum Preshared Key Identity, RFC 8784 */
PPK_IDENTITY = 16436,
/* No Postquantum Preshared Key Auth, RFC 8784 */
NO_PPK_AUTH = 16437,
/* IKEv2 Intermediate Exchanges, RFC 9242 */
INTERMEDIATE_EXCHANGE_SUPPORTED = 16438,
/* multiple key exchanges, RFC 9370 */
ADDITIONAL_KEY_EXCHANGE = 16441,
/* IKEv1 initial contact */
INITIAL_CONTACT_IKEV1 = 24578,
/* IKEv1 DPD */
DPD_R_U_THERE = 36136,
DPD_R_U_THERE_ACK = 36137,
/* IKEv1 Cisco High Availability */
UNITY_LOAD_BALANCE = 40501,
/* BEET mode, not even a draft yet. private use */
USE_BEET_MODE = 40961,
/* IKE-ME, private use */
@@ -1443,22 +1443,21 @@ static void set_from_proposal_v1(private_proposal_substructure_t *this,
}
/**
* Add an IKEv2 proposal to the substructure
* Encode all transforms of the given type
*/
static void set_from_proposal_v2(private_proposal_substructure_t *this,
proposal_t *proposal)
static void encode_transforms_v2(private_proposal_substructure_t *this,
proposal_t *proposal, transform_type_t type)
{
transform_substructure_t *transform;
uint16_t alg, key_size;
enumerator_t *enumerator;
uint16_t alg, key_size;
/* encryption algorithm is only available in ESP */
enumerator = proposal->create_enumerator(proposal, ENCRYPTION_ALGORITHM);
enumerator = proposal->create_enumerator(proposal, type);
while (enumerator->enumerate(enumerator, &alg, &key_size))
{
transform = transform_substructure_create_type(PLV2_TRANSFORM_SUBSTRUCTURE,
ENCRYPTION_ALGORITHM, alg);
if (key_size)
transform = transform_substructure_create_type(
PLV2_TRANSFORM_SUBSTRUCTURE, type, alg);
if (type == ENCRYPTION_ALGORITHM && key_size)
{
transform->add_transform_attribute(transform,
transform_attribute_create_value(PLV2_TRANSFORM_ATTRIBUTE,
@@ -1467,46 +1466,26 @@ static void set_from_proposal_v2(private_proposal_substructure_t *this,
add_transform_substructure(this, transform);
}
enumerator->destroy(enumerator);
}
/* integrity algorithms */
enumerator = proposal->create_enumerator(proposal, INTEGRITY_ALGORITHM);
while (enumerator->enumerate(enumerator, &alg, &key_size))
{
transform = transform_substructure_create_type(PLV2_TRANSFORM_SUBSTRUCTURE,
INTEGRITY_ALGORITHM, alg);
add_transform_substructure(this, transform);
}
enumerator->destroy(enumerator);
/* prf algorithms */
enumerator = proposal->create_enumerator(proposal, PSEUDO_RANDOM_FUNCTION);
while (enumerator->enumerate(enumerator, &alg, &key_size))
{
transform = transform_substructure_create_type(PLV2_TRANSFORM_SUBSTRUCTURE,
PSEUDO_RANDOM_FUNCTION, alg);
add_transform_substructure(this, transform);
}
enumerator->destroy(enumerator);
/* dh groups */
enumerator = proposal->create_enumerator(proposal, KEY_EXCHANGE_METHOD);
while (enumerator->enumerate(enumerator, &alg, NULL))
{
transform = transform_substructure_create_type(PLV2_TRANSFORM_SUBSTRUCTURE,
KEY_EXCHANGE_METHOD, alg);
add_transform_substructure(this, transform);
}
enumerator->destroy(enumerator);
/* extended sequence numbers */
enumerator = proposal->create_enumerator(proposal, EXTENDED_SEQUENCE_NUMBERS);
while (enumerator->enumerate(enumerator, &alg, NULL))
{
transform = transform_substructure_create_type(PLV2_TRANSFORM_SUBSTRUCTURE,
EXTENDED_SEQUENCE_NUMBERS, alg);
add_transform_substructure(this, transform);
}
enumerator->destroy(enumerator);
/**
* Add an IKEv2 proposal to the substructure
*/
static void set_from_proposal_v2(private_proposal_substructure_t *this,
proposal_t *proposal)
{
encode_transforms_v2(this, proposal, ENCRYPTION_ALGORITHM);
encode_transforms_v2(this, proposal, INTEGRITY_ALGORITHM);
encode_transforms_v2(this, proposal, PSEUDO_RANDOM_FUNCTION);
encode_transforms_v2(this, proposal, KEY_EXCHANGE_METHOD);
encode_transforms_v2(this, proposal, ADDITIONAL_KEY_EXCHANGE_1);
encode_transforms_v2(this, proposal, ADDITIONAL_KEY_EXCHANGE_2);
encode_transforms_v2(this, proposal, ADDITIONAL_KEY_EXCHANGE_3);
encode_transforms_v2(this, proposal, ADDITIONAL_KEY_EXCHANGE_4);
encode_transforms_v2(this, proposal, ADDITIONAL_KEY_EXCHANGE_5);
encode_transforms_v2(this, proposal, ADDITIONAL_KEY_EXCHANGE_6);
encode_transforms_v2(this, proposal, ADDITIONAL_KEY_EXCHANGE_7);
encode_transforms_v2(this, proposal, EXTENDED_SEQUENCE_NUMBERS);
}
/**
+4 -3
View File
@@ -51,10 +51,10 @@ struct private_ha_child_t {
METHOD(listener_t, child_keys, bool,
private_ha_child_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
bool initiator, key_exchange_t *dh, chunk_t nonce_i, chunk_t nonce_r)
bool initiator, array_t *kes, chunk_t nonce_i, chunk_t nonce_r)
{
ha_message_t *m;
chunk_t secret;
chunk_t secret, add_secret = chunk_empty;
proposal_t *proposal;
uint16_t alg, len;
linked_list_t *local_ts, *remote_ts;
@@ -101,10 +101,11 @@ METHOD(listener_t, child_keys, bool,
}
m->add_attribute(m, HA_NONCE_I, nonce_i);
m->add_attribute(m, HA_NONCE_R, nonce_r);
if (dh && dh->get_shared_secret(dh, &secret))
if (kes && key_exchange_concat_secrets(kes, &secret, &add_secret))
{
m->add_attribute(m, HA_SECRET, secret);
chunk_clear(&secret);
chunk_clear(&add_secret);
}
local_ts = linked_list_create();
+8 -2
View File
@@ -234,9 +234,12 @@ static void process_ike_add(private_ha_dispatcher_t *this, ha_message_t *message
if (ike_sa->get_version(ike_sa) == IKEV2)
{
keymat_v2_t *keymat_v2 = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
array_t *kes = NULL;
ok = keymat_v2->derive_ike_keys(keymat_v2, proposal, dh, nonce_i,
array_insert_create(&kes, ARRAY_HEAD, dh);
ok = keymat_v2->derive_ike_keys(keymat_v2, proposal, kes, nonce_i,
nonce_r, ike_sa->get_id(ike_sa), old_prf, old_skd);
array_destroy(kes);
}
if (ike_sa->get_version(ike_sa) == IKEV1)
{
@@ -662,6 +665,7 @@ static void process_child_add(private_ha_dispatcher_t *this,
chunk_t encr_i, integ_i, encr_r, integ_r;
linked_list_t *local_ts, *remote_ts;
key_exchange_t *dh = NULL;
array_t *kes = NULL;
enumerator = message->create_attribute_enumerator(message);
while (enumerator->enumerate(enumerator, &attribute, &value))
@@ -767,12 +771,13 @@ static void process_child_add(private_ha_dispatcher_t *this,
if (secret.len)
{
dh = ha_diffie_hellman_create(secret, chunk_empty);
array_insert_create(&kes, ARRAY_HEAD, dh);
}
if (ike_sa->get_version(ike_sa) == IKEV2)
{
keymat_v2_t *keymat_v2 = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
ok = keymat_v2->derive_child_keys(keymat_v2, proposal, dh,
ok = keymat_v2->derive_child_keys(keymat_v2, proposal, kes,
nonce_i, nonce_r, &encr_i, &integ_i, &encr_r, &integ_r);
}
if (ike_sa->get_version(ike_sa) == IKEV1)
@@ -786,6 +791,7 @@ static void process_child_add(private_ha_dispatcher_t *this,
ok = keymat_v1->derive_child_keys(keymat_v1, proposal, dh, spi_i, spi_r,
nonce_i, nonce_r, &encr_i, &integ_i, &encr_r, &integ_r);
}
array_destroy(kes);
DESTROY_IF(dh);
if (!ok)
{
+9 -4
View File
@@ -82,12 +82,13 @@ static void copy_extensions(ha_message_t *m, ike_sa_t *ike_sa)
}
METHOD(listener_t, ike_keys, bool,
private_ha_ike_t *this, ike_sa_t *ike_sa, key_exchange_t *dh,
private_ha_ike_t *this, ike_sa_t *ike_sa, array_t *kes,
chunk_t dh_other, chunk_t nonce_i, chunk_t nonce_r, ike_sa_t *rekey,
shared_key_t *shared, auth_method_t method)
{
ha_message_t *m;
chunk_t secret;
key_exchange_t *ke;
chunk_t secret = chunk_empty, add_secret = chunk_empty;
proposal_t *proposal;
uint16_t alg, len;
@@ -95,8 +96,12 @@ METHOD(listener_t, ike_keys, bool,
{ /* do not sync SA between nodes */
return TRUE;
}
if (!dh->get_shared_secret(dh, &secret))
if (!key_exchange_concat_secrets(kes, &secret, &add_secret) ||
!array_get(kes, ARRAY_HEAD, &ke) ||
add_secret.len > 0)
{
chunk_clear(&secret);
chunk_clear(&add_secret);
return TRUE;
}
@@ -142,7 +147,7 @@ METHOD(listener_t, ike_keys, bool,
chunk_clear(&secret);
if (ike_sa->get_version(ike_sa) == IKEV1)
{
if (dh->get_public_key(dh, &secret))
if (ke->get_public_key(ke, &secret))
{
m->add_attribute(m, HA_LOCAL_DH, secret);
chunk_free(&secret);
+6 -1
View File
@@ -54,6 +54,11 @@
#include <stdio.h>
/**
* Maximum proposal length
*/
#define MAX_PROPOSAL_LEN 2048
/**
* Magic value for an undefined lifetime
*/
@@ -599,7 +604,7 @@ static void free_child_data(child_data_t *data)
*/
static bool parse_proposal(linked_list_t *list, protocol_id_t proto, chunk_t v)
{
char buf[BUF_LEN];
char buf[MAX_PROPOSAL_LEN];
proposal_t *proposal;
if (!vici_stringify(v, buf, sizeof(buf)))
+25 -2
View File
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2015-2020 Tobias Brunner
* Copyright (C) 2015-2018 Andreas Steffen
* Copyright (C) 2015-2019 Andreas Steffen
* Copyright (C) 2014 Martin Willi
*
* Copyright (C) secunet Security Networks AG
@@ -172,6 +172,27 @@ static void list_label(vici_builder_t *b, child_sa_t *child, child_cfg_t *cfg)
}
}
/**
* List additional key exchanges
*/
static void list_ake(vici_builder_t *b, proposal_t *proposal)
{
transform_type_t transform;
char ake_str[5];
uint16_t alg;
int ake;
for (ake = 1; ake <= 7; ake++)
{
transform = ADDITIONAL_KEY_EXCHANGE_1 + ake - 1;
if (proposal->get_algorithm(proposal, transform, &alg, NULL))
{
sprintf(ake_str, "ake%d", ake);
b->add_kv(b, ake_str, "%N", key_exchange_method_names, alg);
}
}
}
/**
* List IPsec-related details about a CHILD_SA
*/
@@ -235,6 +256,7 @@ static void list_child_ipsec(vici_builder_t *b, child_sa_t *child)
{
b->add_kv(b, "dh-group", "%N", key_exchange_method_names, alg);
}
list_ake(b, proposal);
if (proposal->get_algorithm(proposal, EXTENDED_SEQUENCE_NUMBERS,
&alg, NULL) && alg == EXT_SEQ_NUMBERS)
{
@@ -493,6 +515,7 @@ static void list_ike(private_vici_query_t *this, vici_builder_t *b,
{
b->add_kv(b, "dh-group", "%N", key_exchange_method_names, alg);
}
list_ake(b, proposal);
}
add_condition(b, ike_sa, "ppk", COND_PPK);
@@ -1382,7 +1405,7 @@ CALLBACK(get_algorithms, vici_message_t*,
enumerator->destroy(enumerator);
b->end_section(b);
b->begin_section(b, "dh");
b->begin_section(b, "ke");
enumerator = lib->crypto->create_ke_enumerator(lib->crypto);
while (enumerator->enumerate(enumerator, &group, &plugin_name))
{
+10
View File
@@ -168,6 +168,16 @@ struct authenticator_t {
*/
void (*use_ppk)(authenticator_t *this, chunk_t ppk, bool no_ppk_auth);
/**
* Optional method to set authentication data for IKE_INTERMEDIATE
* exchanges.
*
* Has to be called before the final call to process()/build().
*
* @param int_auth concatenated IntAuth_I|R data
*/
void (*set_int_auth)(authenticator_t *this, chunk_t int_auth);
/**
* Check if the authenticator is capable of mutual authentication.
*
+10 -9
View File
@@ -131,9 +131,10 @@ struct private_child_sa_t {
bool tfcv3;
/**
* The outbound SPI of the CHILD_SA that replaced this one during a rekeying
* The "other" CHILD_SA involved in a passive rekeying (either replacing
* this one, or being replaced by it)
*/
uint32_t rekey_spi;
child_sa_t *rekey_sa;
/**
* Protocol used to protect this SA, ESP|AH
@@ -1588,16 +1589,16 @@ METHOD(child_sa_t, remove_outbound, void,
this->outbound_state = CHILD_OUTBOUND_NONE;
}
METHOD(child_sa_t, set_rekey_spi, void,
private_child_sa_t *this, uint32_t spi)
METHOD(child_sa_t, set_rekey_sa, void,
private_child_sa_t *this, child_sa_t *sa)
{
this->rekey_spi = spi;
this->rekey_sa = sa;
}
METHOD(child_sa_t, get_rekey_spi, uint32_t,
METHOD(child_sa_t, get_rekey_sa, child_sa_t*,
private_child_sa_t *this)
{
return this->rekey_spi;
return this->rekey_sa;
}
CALLBACK(reinstall_vip, void,
@@ -2077,8 +2078,8 @@ child_sa_t *child_sa_create(host_t *me, host_t *other, child_cfg_t *config,
.register_outbound = _register_outbound,
.install_outbound = _install_outbound,
.remove_outbound = _remove_outbound,
.set_rekey_spi = _set_rekey_spi,
.get_rekey_spi = _get_rekey_spi,
.set_rekey_sa = _set_rekey_sa,
.get_rekey_sa = _get_rekey_sa,
.update = _update,
.set_policies = _set_policies,
.install_policies = _install_policies,
+10 -9
View File
@@ -504,23 +504,24 @@ struct child_sa_t {
status_t (*install_policies)(child_sa_t *this);
/**
* Set the outbound SPI of the CHILD_SA that replaced this CHILD_SA during
* a rekeying.
* Set the CHILD_SA that either replaced this one or the CHILD_SA that is
* being replaced by this one during a passive rekeying (i.e. it links the
* two SAs bidirectionally).
*
* @param spi outbound SPI of the CHILD_SA that replaced this CHILD_SA
* @param sa other CHILD_SA involved in a passive rekeying
*/
void (*set_rekey_spi)(child_sa_t *this, uint32_t spi);
void (*set_rekey_sa)(child_sa_t *this, child_sa_t *sa);
/**
* Get the outbound SPI of the CHILD_SA that replaced this CHILD_SA during
* a rekeying.
* Get the CHILD_SA that's linked to this in a passive rekeying (either
* replacing this one, or being replaced by it).
*
* @return outbound SPI of the CHILD_SA that replaced this CHILD_SA
* @return other CHILD_SA involved in a passive rekeying
*/
uint32_t (*get_rekey_spi)(child_sa_t *this);
child_sa_t *(*get_rekey_sa)(child_sa_t *this);
/**
* Update hosts and ecapsulation mode in the kernel SAs and policies.
* Update hosts and encapsulation mode in the kernel SAs and policies.
*
* @param me the new local host
* @param other the new remote host
+6 -1
View File
@@ -161,7 +161,7 @@ enum ike_extension_t {
EXT_IKE_MESSAGE_ID_SYNC = (1<<14),
/**
* Postquantum Preshared Keys, draft-ietf-ipsecme-qr-ikev2
* Postquantum Preshared Keys, RFC 8784
*/
EXT_PPK = (1<<15),
@@ -169,6 +169,11 @@ enum ike_extension_t {
* Responder accepts childless IKE_SAs, RFC 6023
*/
EXT_IKE_CHILDLESS = (1<<16),
/**
* IKEv2 Intermediate Exchange, RFC 9242
*/
EXT_IKE_INTERMEDIATE = (1<<17),
};
/**
+4 -1
View File
@@ -220,6 +220,7 @@ METHOD(phase1_t, derive_keys, bool,
private_phase1_t *this, peer_cfg_t *peer_cfg, auth_method_t method)
{
shared_key_t *shared_key = NULL;
array_t *kes = NULL;
switch (method)
{
@@ -245,9 +246,11 @@ METHOD(phase1_t, derive_keys, bool,
DBG1(DBG_IKE, "key derivation for %N failed", auth_method_names, method);
return FALSE;
}
charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, this->dh_value,
array_insert_create(&kes, ARRAY_HEAD, this->dh);
charon->bus->ike_keys(charon->bus, this->ike_sa, kes, this->dh_value,
this->nonce_i, this->nonce_r, NULL, shared_key,
method);
array_destroy(kes);
DESTROY_IF(shared_key);
return TRUE;
}
+7 -1
View File
@@ -270,6 +270,7 @@ static bool install(private_quick_mode_t *this)
chunk_t encr_i, encr_r, integ_i, integ_r;
linked_list_t *tsi, *tsr, *my_ts, *other_ts;
child_sa_t *old = NULL;
array_t *kes = NULL;
this->child_sa->set_proposal(this->child_sa, this->proposal);
this->child_sa->set_state(this->child_sa, CHILD_INSTALLING);
@@ -377,8 +378,13 @@ static bool install(private_quick_mode_t *this)
return FALSE;
}
if (this->dh)
{
array_insert_create(&kes, ARRAY_HEAD, this->dh);
}
charon->bus->child_keys(charon->bus, this->child_sa, this->initiator,
this->dh, this->nonce_i, this->nonce_r);
kes, this->nonce_i, this->nonce_r);
array_destroy(kes);
my_ts = linked_list_create_from_enumerator(
this->child_sa->create_ts_enumerator(this->child_sa, TRUE));
@@ -60,6 +60,11 @@ struct private_eap_authenticator_t {
*/
chunk_t sent_init;
/**
* IntAuth data to include in AUTH calculation
*/
chunk_t int_auth;
/**
* Reserved bytes of ID payload
*/
@@ -495,8 +500,9 @@ static bool verify_auth(private_eap_authenticator_t *this, message_t *message,
other_id = this->ike_sa->get_other_id(this->ike_sa);
keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa);
if (!keymat->get_psk_sig(keymat, TRUE, init, nonce, this->msk, this->ppk,
other_id, this->reserved, &auth_data))
if (!keymat->get_psk_sig(keymat, TRUE, init, nonce, this->int_auth,
this->msk, this->ppk, other_id, this->reserved,
&auth_data))
{
return FALSE;
}
@@ -541,8 +547,9 @@ static bool build_auth(private_eap_authenticator_t *this, message_t *message,
DBG1(DBG_IKE, "authentication of '%Y' (myself) with %N",
my_id, auth_class_names, AUTH_CLASS_EAP);
if (!keymat->get_psk_sig(keymat, FALSE, init, nonce, this->msk, this->ppk,
my_id, this->reserved, &auth_data))
if (!keymat->get_psk_sig(keymat, FALSE, init, nonce, this->int_auth,
this->msk, this->ppk, my_id, this->reserved,
&auth_data))
{
return FALSE;
}
@@ -554,8 +561,9 @@ static bool build_auth(private_eap_authenticator_t *this, message_t *message,
if (this->no_ppk_auth)
{
if (!keymat->get_psk_sig(keymat, FALSE, init, nonce, this->msk,
chunk_empty, my_id, this->reserved, &auth_data))
if (!keymat->get_psk_sig(keymat, FALSE, init, nonce, this->int_auth,
this->msk, chunk_empty, my_id, this->reserved,
&auth_data))
{
DBG1(DBG_IKE, "failed adding NO_PPK_AUTH notify");
return FALSE;
@@ -767,6 +775,12 @@ METHOD(authenticator_t, use_ppk, void,
this->no_ppk_auth = no_ppk_auth;
}
METHOD(authenticator_t, set_int_auth, void,
private_eap_authenticator_t *this, chunk_t int_auth)
{
this->int_auth = int_auth;
}
METHOD(authenticator_t, destroy, void,
private_eap_authenticator_t *this)
{
@@ -793,6 +807,7 @@ eap_authenticator_t *eap_authenticator_create_builder(ike_sa_t *ike_sa,
.build = _build_client,
.process = _process_client,
.use_ppk = _use_ppk,
.set_int_auth = _set_int_auth,
.is_mutual = _is_mutual,
.destroy = _destroy,
},
@@ -824,6 +839,7 @@ eap_authenticator_t *eap_authenticator_create_verifier(ike_sa_t *ike_sa,
.build = _build_server,
.process = _process_server,
.use_ppk = _use_ppk,
.set_int_auth = _set_int_auth,
.is_mutual = _is_mutual,
.destroy = _destroy,
},
@@ -49,6 +49,11 @@ struct private_psk_authenticator_t {
*/
chunk_t ike_sa_init;
/**
* IntAuth data to include in AUTH calculation
*/
chunk_t int_auth;
/**
* Reserved bytes of ID payload
*/
@@ -86,8 +91,8 @@ METHOD(authenticator_t, build, status_t,
return NOT_FOUND;
}
if (!keymat->get_psk_sig(keymat, FALSE, this->ike_sa_init, this->nonce,
key->get_key(key), this->ppk, my_id,
this->reserved, &auth_data))
this->int_auth, key->get_key(key), this->ppk,
my_id, this->reserved, &auth_data))
{
key->destroy(key);
return FAILED;
@@ -103,8 +108,8 @@ METHOD(authenticator_t, build, status_t,
if (this->no_ppk_auth)
{
if (!keymat->get_psk_sig(keymat, FALSE, this->ike_sa_init, this->nonce,
key->get_key(key), chunk_empty, my_id,
this->reserved, &auth_data))
this->int_auth, key->get_key(key), chunk_empty,
my_id, this->reserved, &auth_data))
{
DBG1(DBG_IKE, "failed adding NO_PPK_AUTH notify");
key->destroy(key);
@@ -160,8 +165,8 @@ METHOD(authenticator_t, process, status_t,
keys_found++;
if (!keymat->get_psk_sig(keymat, TRUE, this->ike_sa_init, this->nonce,
key->get_key(key), this->ppk, other_id,
this->reserved, &auth_data))
this->int_auth, key->get_key(key), this->ppk,
other_id, this->reserved, &auth_data))
{
continue;
}
@@ -199,6 +204,12 @@ METHOD(authenticator_t, use_ppk, void,
this->no_ppk_auth = no_ppk_auth;
}
METHOD(authenticator_t, set_int_auth, void,
private_psk_authenticator_t *this, chunk_t int_auth)
{
this->int_auth = int_auth;
}
METHOD(authenticator_t, destroy, void,
private_psk_authenticator_t *this)
{
@@ -220,6 +231,7 @@ psk_authenticator_t *psk_authenticator_create_builder(ike_sa_t *ike_sa,
.build = _build,
.process = (void*)return_failed,
.use_ppk = _use_ppk,
.set_int_auth = _set_int_auth,
.is_mutual = (void*)return_false,
.destroy = _destroy,
},
@@ -248,6 +260,7 @@ psk_authenticator_t *psk_authenticator_create_verifier(ike_sa_t *ike_sa,
.build = (void*)return_failed,
.process = _process,
.use_ppk = _use_ppk,
.set_int_auth = _set_int_auth,
.is_mutual = (void*)return_false,
.destroy = _destroy,
},
@@ -53,6 +53,11 @@ struct private_pubkey_authenticator_t {
*/
chunk_t ike_sa_init;
/**
* IntAuth data to include in AUTH calculation
*/
chunk_t int_auth;
/**
* Reserved bytes of ID payload
*/
@@ -325,7 +330,8 @@ static status_t sign_signature_auth(private_pubkey_authenticator_t *this,
}
if (keymat->get_auth_octets(keymat, FALSE, this->ike_sa_init, this->nonce,
this->ppk, id, this->reserved, &octets, schemes))
this->int_auth, this->ppk, id, this->reserved,
&octets, schemes))
{
enumerator = array_create_enumerator(schemes);
while (enumerator->enumerate(enumerator, &params))
@@ -347,8 +353,9 @@ static status_t sign_signature_auth(private_pubkey_authenticator_t *this,
chunk_free(&octets);
if (keymat->get_auth_octets(keymat, FALSE, this->ike_sa_init,
this->nonce, chunk_empty, id,
this->reserved, &octets, schemes) &&
this->nonce, this->int_auth,
chunk_empty, id, this->reserved,
&octets, schemes) &&
private->sign(private, params->scheme, params->params,
octets, &auth_data) &&
build_signature_auth_data(&auth_data, params))
@@ -412,7 +419,7 @@ static bool get_auth_octets_scheme(private_pubkey_authenticator_t *this,
keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa);
if (keymat->get_auth_octets(keymat, verify, this->ike_sa_init, this->nonce,
ppk, id, this->reserved, octets,
this->int_auth, ppk, id, this->reserved, octets,
schemes) &&
array_remove(schemes, 0, scheme))
{
@@ -696,6 +703,12 @@ METHOD(authenticator_t, use_ppk, void,
this->no_ppk_auth = no_ppk_auth;
}
METHOD(authenticator_t, set_int_auth, void,
private_pubkey_authenticator_t *this, chunk_t int_auth)
{
this->int_auth = int_auth;
}
METHOD(authenticator_t, destroy, void,
private_pubkey_authenticator_t *this)
{
@@ -717,6 +730,7 @@ pubkey_authenticator_t *pubkey_authenticator_create_builder(ike_sa_t *ike_sa,
.build = _build,
.process = (void*)return_failed,
.use_ppk = _use_ppk,
.set_int_auth = _set_int_auth,
.is_mutual = (void*)return_false,
.destroy = _destroy,
},
@@ -745,6 +759,7 @@ pubkey_authenticator_t *pubkey_authenticator_create_verifier(ike_sa_t *ike_sa,
.build = (void*)return_failed,
.process = _process,
.use_ppk = _use_ppk,
.set_int_auth = _set_int_auth,
.is_mutual = (void*)return_false,
.destroy = _destroy,
},
+59 -24
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2015 Tobias Brunner
* Copyright (C) 2015-2020 Tobias Brunner
* Copyright (C) 2008 Martin Willi
*
* Copyright (C) secunet Security Networks AG
@@ -237,13 +237,13 @@ static bool set_aead_keys(private_keymat_v2_t *this, uint16_t enc_alg,
}
METHOD(keymat_v2_t, derive_ike_keys, bool,
private_keymat_v2_t *this, proposal_t *proposal, key_exchange_t *dh,
private_keymat_v2_t *this, proposal_t *proposal, array_t *kes,
chunk_t nonce_i, chunk_t nonce_r, ike_sa_id_t *id,
pseudo_random_function_t rekey_function, chunk_t rekey_skd)
{
chunk_t skeyseed = chunk_empty, secret, full_nonce, fixed_nonce;
chunk_t prf_plus_seed, spi_i, spi_r, keymat = chunk_empty;
chunk_t sk_ei = chunk_empty, sk_er = chunk_empty;
chunk_t skeyseed = chunk_empty, secret, add_secret = chunk_empty;
chunk_t full_nonce, fixed_nonce, prf_plus_seed, spi_i, spi_r;
chunk_t keymat = chunk_empty, sk_ei = chunk_empty, sk_er = chunk_empty;
chunk_t sk_ai = chunk_empty, sk_ar = chunk_empty, sk_pi, sk_pr;
kdf_t *prf = NULL, *prf_plus = NULL;
uint16_t prf_alg, key_size, enc_alg, enc_size, int_alg;
@@ -261,6 +261,7 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
return FALSE;
}
this->prf_alg = prf_alg;
DESTROY_IF(this->prf);
this->prf = lib->crypto->create_prf(lib->crypto, this->prf_alg);
if (!this->prf)
{
@@ -279,6 +280,8 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
ENCRYPTION_ALGORITHM);
return FALSE;
}
DESTROY_IF(this->aead_in);
DESTROY_IF(this->aead_out);
if (!encryption_algorithm_is_aead(enc_alg))
{
if (!proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &int_alg,
@@ -299,13 +302,15 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
return FALSE;
}
if (!dh->get_shared_secret(dh, &secret))
if (!key_exchange_concat_secrets(kes, &secret, &add_secret))
{
return FALSE;
}
DBG4(DBG_IKE, "shared Diffie Hellman secret %B", &secret);
DBG4(DBG_IKE, "key exchange secret %B", &secret);
DBG4(DBG_IKE, "additional key exchange secret %B", &add_secret);
/* full nonce is used as seed for PRF+ ... */
full_nonce = chunk_cat("cc", nonce_i, nonce_r);
DBG4(DBG_IKE, "nonces %B", &full_nonce);
/* but the PRF may need a fixed key which only uses the first bytes of
* the nonces. */
switch (prf_alg)
@@ -339,6 +344,7 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
key_derivation_function_names, KDF_PRF,
pseudo_random_function_names, this->prf_alg);
chunk_clear(&secret);
chunk_clear(&add_secret);
chunk_free(&full_nonce);
chunk_free(&fixed_nonce);
return FALSE;
@@ -362,11 +368,12 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
key_derivation_function_names, KDF_PRF,
pseudo_random_function_names, rekey_function);
chunk_clear(&secret);
chunk_clear(&add_secret);
chunk_free(&full_nonce);
chunk_free(&fixed_nonce);
return FALSE;
}
secret = chunk_cat("sc", secret, full_nonce);
secret = chunk_cat("scc", secret, full_nonce, add_secret);
if (prf->set_param(prf, KDF_PARAM_KEY, secret) &&
prf->set_param(prf, KDF_PARAM_SALT, rekey_skd) &&
prf->allocate_bytes(prf, 0, &skeyseed))
@@ -377,6 +384,7 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
}
DBG4(DBG_IKE, "SKEYSEED %B", &skeyseed);
chunk_clear(&secret);
chunk_clear(&add_secret);
chunk_free(&fixed_nonce);
DESTROY_IF(prf);
@@ -411,6 +419,7 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
{
goto failure;
}
chunk_clear(&this->skd);
chunk_split(keymat, "ammmmaa", key_size, &this->skd, sk_ai.len, &sk_ai,
sk_ar.len, &sk_ar, sk_ei.len, &sk_ei, sk_er.len, &sk_er,
key_size, &sk_pi, key_size, &sk_pr);
@@ -432,6 +441,8 @@ METHOD(keymat_v2_t, derive_ike_keys, bool,
/* SK_pi/SK_pr used for authentication => stored for later */
DBG4(DBG_IKE, "Sk_pi secret %B", &sk_pi);
DBG4(DBG_IKE, "Sk_pr secret %B", &sk_pr);
chunk_clear(&this->skp_build);
chunk_clear(&this->skp_verify);
if (this->initiator)
{
this->skp_build = sk_pi;
@@ -523,12 +534,13 @@ METHOD(keymat_v2_t, derive_ike_keys_ppk, bool,
}
METHOD(keymat_v2_t, derive_child_keys, bool,
private_keymat_v2_t *this, proposal_t *proposal, key_exchange_t *dh,
private_keymat_v2_t *this, proposal_t *proposal, array_t *kes,
chunk_t nonce_i, chunk_t nonce_r, chunk_t *encr_i, chunk_t *integ_i,
chunk_t *encr_r, chunk_t *integ_r)
{
uint16_t enc_alg, int_alg, enc_size = 0, int_size = 0;
chunk_t seed, secret = chunk_empty, keymat = chunk_empty;
chunk_t seed, secret = chunk_empty, add_secret = chunk_empty;
chunk_t keymat = chunk_empty;
kdf_t *prf_plus;
if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM,
@@ -595,15 +607,16 @@ METHOD(keymat_v2_t, derive_child_keys, bool,
int_size /= 8;
}
if (dh)
if (kes)
{
if (!dh->get_shared_secret(dh, &secret))
if (!key_exchange_concat_secrets(kes, &secret, &add_secret))
{
return FALSE;
}
DBG4(DBG_CHD, "DH secret %B", &secret);
DBG4(DBG_CHD, "key exchange secret %B", &secret);
DBG4(DBG_CHD, "additional key exchange secret %B", &add_secret);
}
seed = chunk_cata("scc", secret, nonce_i, nonce_r);
seed = chunk_cata("sccs", secret, nonce_i, nonce_r, add_secret);
DBG4(DBG_CHD, "seed %B", &seed);
prf_plus = lib->crypto->create_kdf(lib->crypto, KDF_PRF_PLUS, this->prf_alg);
@@ -656,10 +669,31 @@ METHOD(keymat_t, get_aead, aead_t*,
return in ? this->aead_in : this->aead_out;
}
METHOD(keymat_v2_t, get_int_auth, bool,
private_keymat_v2_t *this, bool verify, chunk_t data, chunk_t prev,
chunk_t *auth)
{
chunk_t skp;
skp = verify ? this->skp_verify : this->skp_build;
DBG3(DBG_IKE, "IntAuth_N-1 %B", &prev);
DBG3(DBG_IKE, "IntAuth_A|P %B", &data);
DBG4(DBG_IKE, "SK_p %B", &skp);
if (!this->prf->set_key(this->prf, skp) ||
!this->prf->allocate_bytes(this->prf, prev, NULL) ||
!this->prf->allocate_bytes(this->prf, data, auth))
{
return FALSE;
}
DBG3(DBG_IKE, "IntAuth_N = prf(Sk_px, data) %B", auth);
return TRUE;
}
METHOD(keymat_v2_t, get_auth_octets, bool,
private_keymat_v2_t *this, bool verify, chunk_t ike_sa_init,
chunk_t nonce, chunk_t ppk, identification_t *id, char reserved[3],
chunk_t *octets, array_t *schemes)
chunk_t nonce, chunk_t int_auth, chunk_t ppk, identification_t *id,
char reserved[3], chunk_t *octets, array_t *schemes)
{
chunk_t chunk, idx;
chunk_t skp_ppk = chunk_empty;
@@ -690,8 +724,9 @@ METHOD(keymat_v2_t, get_auth_octets, bool,
return FALSE;
}
chunk_clear(&skp_ppk);
*octets = chunk_cat("ccm", ike_sa_init, nonce, chunk);
DBG3(DBG_IKE, "octets = message + nonce + prf(Sk_px, IDx') %B", octets);
*octets = chunk_cat("ccmc", ike_sa_init, nonce, chunk, int_auth);
DBG3(DBG_IKE, "octets = message + nonce + prf(Sk_px, IDx') + IntAuth %B",
octets);
return TRUE;
}
@@ -702,9 +737,9 @@ METHOD(keymat_v2_t, get_auth_octets, bool,
#define IKEV2_KEY_PAD_LENGTH 17
METHOD(keymat_v2_t, get_psk_sig, bool,
private_keymat_v2_t *this, bool verify, chunk_t ike_sa_init, chunk_t nonce,
chunk_t secret, chunk_t ppk, identification_t *id, char reserved[3],
chunk_t *sig)
private_keymat_v2_t *this, bool verify, chunk_t ike_sa_init,
chunk_t nonce, chunk_t int_auth, chunk_t secret, chunk_t ppk,
identification_t *id, char reserved[3], chunk_t *sig)
{
chunk_t skp_ppk = chunk_empty, key = chunk_empty, octets = chunk_empty;
chunk_t key_pad;
@@ -722,8 +757,8 @@ METHOD(keymat_v2_t, get_psk_sig, bool,
secret = skp_ppk;
}
}
if (!get_auth_octets(this, verify, ike_sa_init, nonce, ppk, id, reserved,
&octets, NULL))
if (!get_auth_octets(this, verify, ike_sa_init, nonce, int_auth, ppk, id,
reserved, &octets, NULL))
{
goto failure;
}
@@ -749,7 +784,6 @@ failure:
chunk_free(&octets);
chunk_free(&key);
return success;
}
METHOD(keymat_v2_t, hash_algorithm_supported, bool,
@@ -805,6 +839,7 @@ keymat_v2_t *keymat_v2_create(bool initiator)
.derive_ike_keys_ppk = _derive_ike_keys_ppk,
.derive_child_keys = _derive_child_keys,
.get_skd = _get_skd,
.get_int_auth = _get_int_auth,
.get_auth_octets = _get_auth_octets,
.get_psk_sig = _get_psk_sig,
.add_hash_algorithm = _add_hash_algorithm,
+34 -13
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2011-2015 Tobias Brunner
* Copyright (C) 2011-2020 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
@@ -44,7 +44,7 @@ struct keymat_v2_t {
* crypters and authentication functions.
*
* @param proposal selected algorithms
* @param dh diffie hellman key allocated by create_ke()
* @param kes array of key_exchange_t* created by create_ke()
* @param nonce_i initiators nonce value
* @param nonce_r responders nonce value
* @param id IKE_SA identifier
@@ -53,7 +53,7 @@ struct keymat_v2_t {
* @return TRUE on success
*/
bool (*derive_ike_keys)(keymat_v2_t *this, proposal_t *proposal,
key_exchange_t *dh, chunk_t nonce_i,
array_t *kes, chunk_t nonce_i,
chunk_t nonce_r, ike_sa_id_t *id,
pseudo_random_function_t rekey_function,
chunk_t rekey_skd);
@@ -77,7 +77,7 @@ struct keymat_v2_t {
* If no PFS is used for the CHILD_SA, dh can be NULL.
*
* @param proposal selected algorithms
* @param dh diffie hellman key allocated by create_ke(), or NULL
* @param kes array of key_exchange_t* created by create_ke(), or NULL
* @param nonce_i initiators nonce value
* @param nonce_r responders nonce value
* @param encr_i chunk to write initiators encryption key to
@@ -87,10 +87,11 @@ struct keymat_v2_t {
* @return TRUE on success
*/
bool (*derive_child_keys)(keymat_v2_t *this,
proposal_t *proposal, key_exchange_t *dh,
proposal_t *proposal, array_t *kes,
chunk_t nonce_i, chunk_t nonce_r,
chunk_t *encr_i, chunk_t *integ_i,
chunk_t *encr_r, chunk_t *integ_r);
/**
* Get SKd to pass to derive_ikey_keys() during rekeying.
*
@@ -99,6 +100,22 @@ struct keymat_v2_t {
*/
pseudo_random_function_t (*get_skd)(keymat_v2_t *this, chunk_t *skd);
/**
* Generate data for signed octets when using IKE_INTEMEDIATE exchanges.
*
* The supplied chunk must contain the IKE header until the end of the
* Encrypted Payload header followed by the plaintext contents of the
* latter.
*
* @param verify TRUE as recipient, FALSE as sender
* @param data IKE_INTERMEDIATE packet data
* @param prev previous IntAuth value
* @param[out] auth IntAuth data to be used later with get_auth_octets()
* @return TRUE if octets created successfully
*/
bool (*get_int_auth)(keymat_v2_t *this, bool verify, chunk_t data,
chunk_t prev, chunk_t *auth);
/**
* Generate octets to use for authentication procedure (RFC4306 2.15).
*
@@ -107,21 +124,23 @@ struct keymat_v2_t {
* the get_psk_sig() method instead.
*
* @param verify TRUE to create for verification, FALSE to sign
* @param ike_sa_init encoded ike_sa_init message
* @param ike_sa_init encoded IKE_SA_INIT message
* @param nonce nonce value
* @param int_auth concatenated data of IKE_INTERMEDIATE exchanges
* @param ppk optional postquantum preshared key
* @param id identity
* @param reserved reserved bytes of id_payload
* @param octests chunk receiving allocated auth octets
* @param octets chunk receiving allocated auth octets
* @param schemes array containing signature schemes
* (signature_params_t*) in case they need to be
* modified by the keymat implementation
* @return TRUE if octets created successfully
*/
bool (*get_auth_octets)(keymat_v2_t *this, bool verify, chunk_t ike_sa_init,
chunk_t nonce, chunk_t ppk, identification_t *id,
char reserved[3], chunk_t *octets,
array_t *schemes);
chunk_t nonce, chunk_t int_auth, chunk_t ppk,
identification_t *id, char reserved[3],
chunk_t *octets, array_t *schemes);
/**
* Build the shared secret signature used for PSK and EAP authentication.
*
@@ -130,8 +149,9 @@ struct keymat_v2_t {
* used as secret (used for EAP methods without MSK).
*
* @param verify TRUE to create for verification, FALSE to sign
* @param ike_sa_init encoded ike_sa_init message
* @param ike_sa_init encoded IKE_SA_INIT message
* @param nonce nonce value
* @param int_auth concatenated data of IKE_INTERMEDIATE exchanges
* @param secret optional secret to include into signature
* @param ppk optional postquantum preshared key
* @param id identity
@@ -140,8 +160,9 @@ struct keymat_v2_t {
* @return TRUE if signature created successfully
*/
bool (*get_psk_sig)(keymat_v2_t *this, bool verify, chunk_t ike_sa_init,
chunk_t nonce, chunk_t secret, chunk_t ppk,
identification_t *id, char reserved[3], chunk_t *sig);
chunk_t nonce, chunk_t int_auth, chunk_t secret,
chunk_t ppk, identification_t *id, char reserved[3],
chunk_t *sig);
/**
* Add a hash algorithm supported by the peer for signature authentication.
+64 -37
View File
@@ -244,6 +244,49 @@ METHOD(task_manager_t, flush, void,
flush_queue(this, TASK_QUEUE_ACTIVE);
}
/**
* Check if a given task has been queued already
*/
static bool has_queued(private_task_manager_t *this, task_queue_t queue,
task_type_t type)
{
enumerator_t *enumerator;
array_t *array;
task_t *task;
bool found = FALSE;
switch (queue)
{
case TASK_QUEUE_ACTIVE:
array = this->active_tasks;
break;
case TASK_QUEUE_PASSIVE:
array = this->passive_tasks;
break;
case TASK_QUEUE_QUEUED:
array = this->queued_tasks;
break;
default:
return FALSE;
}
enumerator = array_create_enumerator(array);
while (enumerator->enumerate(enumerator, &task))
{
if (queue == TASK_QUEUE_QUEUED)
{
task = ((queued_task_t*)task)->task;
}
if (task->get_type(task) == type)
{
found = TRUE;
break;
}
}
enumerator->destroy(enumerator);
return found;
}
/**
* Move a task of a specific type from the queue to the active list, if it is
* not delayed.
@@ -892,9 +935,10 @@ static bool handle_collisions(private_task_manager_t *this, task_t *task)
type = task->get_type(task);
/* do we have to check */
if (type == TASK_IKE_REKEY || type == TASK_CHILD_REKEY ||
type == TASK_CHILD_DELETE || type == TASK_IKE_DELETE)
/* collisions between a child-rekey and child-delete task are handled
* directly by the latter */
if (type == TASK_IKE_REKEY || type == TASK_IKE_DELETE ||
type == TASK_CHILD_REKEY)
{
/* find an exchange collision, and notify these tasks */
enumerator = array_create_enumerator(this->active_tasks);
@@ -911,7 +955,7 @@ static bool handle_collisions(private_task_manager_t *this, task_t *task)
}
continue;
case TASK_CHILD_REKEY:
if (type == TASK_CHILD_REKEY || type == TASK_CHILD_DELETE)
if (type == TASK_CHILD_REKEY)
{
child_rekey_t *rekey = (child_rekey_t*)active;
adopted = rekey->collide(rekey, task);
@@ -1676,6 +1720,11 @@ static inline bool reject_request(private_task_manager_t *this,
case IKE_SA_INIT:
reject = state != IKE_CREATED;
break;
case IKE_INTERMEDIATE:
/* only accept this if we have not yet completed the KEs */
reject = state != IKE_CONNECTING ||
!has_queued(this, TASK_QUEUE_PASSIVE, TASK_IKE_INIT);
break;
case IKE_AUTH:
reject = state != IKE_CONNECTING;
break;
@@ -2029,64 +2078,42 @@ METHOD(task_manager_t, queue_task, void,
queue_task_delayed(this, task, 0);
}
/**
* Check if a given task has been queued already
*/
static bool has_queued(private_task_manager_t *this, task_type_t type)
{
enumerator_t *enumerator;
bool found = FALSE;
queued_task_t *queued;
enumerator = array_create_enumerator(this->queued_tasks);
while (enumerator->enumerate(enumerator, &queued))
{
if (queued->task->get_type(queued->task) == type)
{
found = TRUE;
break;
}
}
enumerator->destroy(enumerator);
return found;
}
METHOD(task_manager_t, queue_ike, void,
private_task_manager_t *this)
{
if (!has_queued(this, TASK_IKE_VENDOR))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_VENDOR))
{
queue_task(this, (task_t*)ike_vendor_create(this->ike_sa, TRUE));
}
if (!has_queued(this, TASK_IKE_INIT))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_INIT))
{
queue_task(this, (task_t*)ike_init_create(this->ike_sa, TRUE, NULL));
}
if (!has_queued(this, TASK_IKE_NATD))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_NATD))
{
queue_task(this, (task_t*)ike_natd_create(this->ike_sa, TRUE));
}
if (!has_queued(this, TASK_IKE_CERT_PRE))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_CERT_PRE))
{
queue_task(this, (task_t*)ike_cert_pre_create(this->ike_sa, TRUE));
}
if (!has_queued(this, TASK_IKE_AUTH))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_AUTH))
{
queue_task(this, (task_t*)ike_auth_create(this->ike_sa, TRUE));
}
if (!has_queued(this, TASK_IKE_CERT_POST))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_CERT_POST))
{
queue_task(this, (task_t*)ike_cert_post_create(this->ike_sa, TRUE));
}
if (!has_queued(this, TASK_IKE_CONFIG))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_CONFIG))
{
queue_task(this, (task_t*)ike_config_create(this->ike_sa, TRUE));
}
if (!has_queued(this, TASK_IKE_AUTH_LIFETIME))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_AUTH_LIFETIME))
{
queue_task(this, (task_t*)ike_auth_lifetime_create(this->ike_sa, TRUE));
}
if (!has_queued(this, TASK_IKE_MOBIKE))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_MOBIKE))
{
peer_cfg_t *peer_cfg;
@@ -2096,12 +2123,12 @@ METHOD(task_manager_t, queue_ike, void,
queue_task(this, (task_t*)ike_mobike_create(this->ike_sa, TRUE));
}
}
if (!has_queued(this, TASK_IKE_ESTABLISH))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_ESTABLISH))
{
queue_task(this, (task_t*)ike_establish_create(this->ike_sa, TRUE));
}
#ifdef ME
if (!has_queued(this, TASK_IKE_ME))
if (!has_queued(this, TASK_QUEUE_QUEUED, TASK_IKE_ME))
{
queue_task(this, (task_t*)ike_me_create(this->ike_sa, TRUE));
}
File diff suppressed because it is too large Load Diff
+11 -4
View File
@@ -81,13 +81,13 @@ struct child_create_t {
void (*use_label)(child_create_t *this, sec_label_t *label);
/**
* Initially propose a specific DH group to override configuration.
* Initially propose a specific KE method to override configuration.
*
* This is used during rekeying to prefer the previously negotiated group.
* This is used during rekeying to prefer the previously negotiated method.
*
* @param dh_group DH group to use
* @param ke_method KE method to use
*/
void (*use_dh_group)(child_create_t *this, key_exchange_method_t dh_group);
void (*use_ke_method)(child_create_t *this, key_exchange_method_t ke_method);
/**
* Get the lower of the two nonces, used for rekey collisions.
@@ -103,6 +103,13 @@ struct child_create_t {
*/
child_sa_t* (*get_child) (child_create_t *this);
/**
* Get the SPI of the other peer's selected proposal, if available.
*
* @return other's SPI, 0 if unknown
*/
uint32_t (*get_other_spi)(child_create_t *this);
/**
* Enforce a specific CHILD_SA config as responder.
*
+470 -325
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2009-2016 Tobias Brunner
* Copyright (C) 2009-2022 Tobias Brunner
* Copyright (C) 2006-2007 Martin Willi
*
* Copyright (C) secunet Security Networks AG
@@ -76,10 +76,10 @@ struct private_child_delete_t {
typedef struct {
/** Deleted CHILD_SA */
child_sa_t *child_sa;
/** Whether the CHILD_SA was rekeyed */
bool rekeyed;
/** Whether to enforce any delete action policy */
bool check_delete_action;
/** The original state of the CHILD_SA */
child_sa_state_t orig_state;
/** How this CHILD_SA collides with an active rekeying */
child_rekey_collision_t collision;
} entry_t;
CALLBACK(match_child, bool,
@@ -133,18 +133,448 @@ static void build_payloads(private_child_delete_t *this, message_t *message)
default:
break;
}
entry->child_sa->set_state(entry->child_sa, CHILD_DELETING);
}
enumerator->destroy(enumerator);
}
/**
* Check if the given CHILD_SA is the redundant SA created in a rekey collision.
* Install the outbound SA of the CHILD_SA that replaced the given CHILD_SA
* in a rekeying.
*/
static bool is_redundant(private_child_delete_t *this, child_sa_t *child)
static void conclude_rekeying(private_child_delete_t *this, child_sa_t *old)
{
child_sa_t *child_sa;
child_sa = old->get_rekey_sa(old);
old->set_rekey_sa(old, NULL);
child_sa->set_rekey_sa(child_sa, NULL);
child_rekey_conclude_rekeying(old, child_sa);
}
/**
* Destroy and optionally reestablish the given CHILD_SA according to config.
*/
static status_t destroy_and_reestablish_internal(ike_sa_t *ike_sa,
child_sa_t *child_sa,
bool trigger_updown,
bool delete_action,
action_t forced_action)
{
child_init_args_t args = {};
child_cfg_t *child_cfg;
protocol_id_t protocol;
uint32_t spi;
action_t action;
status_t status = SUCCESS;
child_sa->set_state(child_sa, CHILD_DELETED);
if (trigger_updown)
{
charon->bus->child_updown(charon->bus, child_sa, FALSE);
}
protocol = child_sa->get_protocol(child_sa);
spi = child_sa->get_spi(child_sa, TRUE);
child_cfg = child_sa->get_config(child_sa);
child_cfg->get_ref(child_cfg);
args.reqid = child_sa->get_reqid_ref(child_sa);
args.label = child_sa->get_label(child_sa);
if (args.label)
{
args.label = args.label->clone(args.label);
}
action = forced_action ?: child_sa->get_close_action(child_sa);
DBG1(DBG_IKE, "CHILD_SA %s{%u} closed", child_sa->get_name(child_sa),
child_sa->get_unique_id(child_sa));
ike_sa->destroy_child_sa(ike_sa, protocol, spi);
if (delete_action)
{
if (action & ACTION_TRAP)
{
charon->traps->install(charon->traps,
ike_sa->get_peer_cfg(ike_sa),
child_cfg);
}
if (action & ACTION_START)
{
child_cfg->get_ref(child_cfg);
status = ike_sa->initiate(ike_sa, child_cfg, &args);
}
}
child_cfg->destroy(child_cfg);
if (args.reqid)
{
charon->kernel->release_reqid(charon->kernel, args.reqid);
}
DESTROY_IF(args.label);
return status;
}
/*
* Described in header
*/
status_t child_delete_destroy_and_reestablish(ike_sa_t *ike_sa,
child_sa_t *child_sa)
{
return destroy_and_reestablish_internal(ike_sa, child_sa, TRUE, TRUE, 0);
}
/*
* Described in header
*/
status_t child_delete_destroy_and_force_reestablish(ike_sa_t *ike_sa,
child_sa_t *child_sa)
{
return destroy_and_reestablish_internal(ike_sa, child_sa, TRUE, TRUE,
ACTION_START);
}
/*
* Described in header
*/
void child_delete_destroy_rekeyed(ike_sa_t *ike_sa, child_sa_t *child_sa)
{
time_t now, expire;
u_int delay;
/* make sure the SA is in the correct state and the outbound SA is not
* installed */
child_sa->remove_outbound(child_sa);
child_sa->set_state(child_sa, CHILD_DELETED);
now = time_monotonic(NULL);
delay = lib->settings->get_int(lib->settings, "%s.delete_rekeyed_delay",
DELETE_REKEYED_DELAY, lib->ns);
expire = child_sa->get_lifetime(child_sa, TRUE);
if (delay && (!expire || ((now + delay) < expire)))
{
DBG1(DBG_IKE, "delay closing of inbound CHILD_SA %s{%u} for %us",
child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa),
delay);
lib->scheduler->schedule_job(lib->scheduler,
(job_t*)delete_child_sa_job_create_id(
child_sa->get_unique_id(child_sa)), delay);
return;
}
else if (now < expire)
{
/* let it expire naturally */
DBG1(DBG_IKE, "let rekeyed inbound CHILD_SA %s{%u} expire naturally "
"in %us", child_sa->get_name(child_sa),
child_sa->get_unique_id(child_sa), expire-now);
return;
}
/* no delay and no lifetime, destroy it immediately. since we suppress
* actions, there is no need to check the return value */
destroy_and_reestablish_internal(ike_sa, child_sa, FALSE, FALSE, 0);
}
/**
* Check if the SA should be ignored and kept until a concurrent active rekeying
* is concluded (the rekey task is responsible for destroying the CHILD_SA).
*/
static bool keep_while_rekeying(entry_t *entry)
{
switch (entry->collision)
{
case CHILD_REKEY_COLLISION_NONE:
break;
case CHILD_REKEY_COLLISION_OLD:
/* if the peer deletes the SA we are trying to rekey and there
* hasn't been a collision, it might have sent the delete before our
* request arrived. but it could also be an incorrect delete sent
* after it processed our rekey request, which we'd have to ignore.
* the active rekey task will decide once it has the response */
if (entry->orig_state == CHILD_REKEYING)
{
return TRUE;
}
/* if there was a collision, the peer is expected to delete the old
* SA only if it won the collision, the SA is in state CHILD_REKEYED
* in this case. we don't completely ignore the SA and conclude the
* rekeying for it now to switch to the new outbound SA (the peer
* will remove the old inbound SA once it receives the DELETE
* response), but don't destroy the old SA yet even though we return
* FALSE here.
* the active rekey task will later decide if the delete was
* legitimate or an incorrect delete for the old SA */
break;
case CHILD_REKEY_COLLISION_PEER:
/* the peer deletes the SA it created itself before we received
* the rekey response, this is either the redundant SA, which
* would be fine, or the winning SA it already is deleting for
* some reason (presumably, after also sending a delete for the
* rekeyed SA). let the active rekey task decide once it receives
* the response and knows who won the collision */
return TRUE;
}
return FALSE;
}
/**
* Log an SA we are not yet closing completely.
*/
static void log_kept_sa(entry_t *entry)
{
DBG1(DBG_IKE, "keeping %s CHILD_SA %s{%u} until active rekeying is "
"concluded",
entry->collision == CHILD_REKEY_COLLISION_OLD ? "rekeyed"
: "peer's",
entry->child_sa->get_name(entry->child_sa),
entry->child_sa->get_unique_id(entry->child_sa));
}
/**
* Destroy the children listed in this->child_sas, reestablish by policy
*/
static status_t destroy_and_reestablish(private_child_delete_t *this)
{
enumerator_t *enumerator;
entry_t *entry;
child_sa_t *child_sa, *other;
status_t status = SUCCESS;
enumerator = this->child_sas->create_enumerator(this->child_sas);
while (enumerator->enumerate(enumerator, (void**)&entry))
{
child_sa = entry->child_sa;
other = child_sa->get_rekey_sa(child_sa);
/* check if we have to keep the SA during a collision with an active
* rekey task */
if (keep_while_rekeying(entry))
{
/* if the peer deleted its own SA, reset the link to the old SA,
* which might already be reset if the peer deleted the old SA
* first (the active rekey task will eventually destroy both) */
if (other && entry->collision == CHILD_REKEY_COLLISION_PEER)
{
child_sa->set_rekey_sa(child_sa, NULL);
other->set_rekey_sa(other, NULL);
/* reset the state of the old SA until the active rekey task is
* done, but only if it's not also getting deleted by the peer
* and is already in state DELETING. note that we won't end up
* here if the peer deleted the old SA first as the link between
* the two SAs would already be reset then. so this is only the
* case if the peer sends the deletes for both SAs in the same
* message and the payload for the old one comes after the one
* for its own SA */
if (other->get_state(other) == CHILD_REKEYED)
{
other->set_state(other, CHILD_REKEYING);
}
}
log_kept_sa(entry);
continue;
}
child_sa->set_state(child_sa, CHILD_DELETED);
if (entry->orig_state == CHILD_REKEYED)
{
/* conclude the rekeying as responder/loser. the initiator/winner
* already did this right after the rekeying was completed (or
* before a delete was initiated), but in some cases the outbound
* SA was not yet removed, make sure it is */
if (other)
{
conclude_rekeying(this, child_sa);
}
else
{
child_sa->remove_outbound(child_sa);
}
/* if this is a delete for the SA we are actively rekeying, let the
* rekey task handle the SA appropriately once the collision is
* resolved. otherwise, destroy the SA now, but usually delayed to
* process delayed packets */
if (entry->collision == CHILD_REKEY_COLLISION_OLD)
{
log_kept_sa(entry);
}
else
{
child_delete_destroy_rekeyed(this->ike_sa, child_sa);
}
}
else
{
/* regular CHILD_SA delete, with one special case after a lost
* collision. usually, the peer will delete the old SA and we
* conclude the rekeying above. however, if it deletes its winning
* SA first, we assume it wants to delete the CHILD_SA and we
* conclude the rekeying here to trigger the events correctly */
if (other && entry->orig_state == CHILD_INSTALLED)
{
conclude_rekeying(this, other);
}
status = destroy_and_reestablish_internal(this->ike_sa, child_sa,
TRUE, !this->initiator &&
entry->orig_state == CHILD_INSTALLED, 0);
if (status != SUCCESS)
{
break;
}
}
}
enumerator->destroy(enumerator);
return status;
}
/**
* Print a log message for every closed CHILD_SA
*/
static void log_children(private_child_delete_t *this)
{
linked_list_t *my_ts, *other_ts;
enumerator_t *enumerator;
entry_t *entry;
child_sa_t *child_sa;
uint64_t bytes_in, bytes_out;
enumerator = this->child_sas->create_enumerator(this->child_sas);
while (enumerator->enumerate(enumerator, (void**)&entry))
{
child_sa = entry->child_sa;
my_ts = linked_list_create_from_enumerator(
child_sa->create_ts_enumerator(child_sa, TRUE));
other_ts = linked_list_create_from_enumerator(
child_sa->create_ts_enumerator(child_sa, FALSE));
if (this->expired)
{
DBG0(DBG_IKE, "closing expired CHILD_SA %s{%u} "
"with SPIs %.8x_i %.8x_o and TS %#R === %#R",
child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa),
ntohl(child_sa->get_spi(child_sa, TRUE)),
ntohl(child_sa->get_spi(child_sa, FALSE)), my_ts, other_ts);
}
else
{
child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in, NULL);
child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out, NULL);
DBG0(DBG_IKE, "closing CHILD_SA %s{%u} with SPIs %.8x_i "
"(%llu bytes) %.8x_o (%llu bytes) and TS %#R === %#R",
child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa),
ntohl(child_sa->get_spi(child_sa, TRUE)), bytes_in,
ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out,
my_ts, other_ts);
}
my_ts->destroy(my_ts);
other_ts->destroy(other_ts);
}
enumerator->destroy(enumerator);
}
METHOD(task_t, build_i, status_t,
private_child_delete_t *this, message_t *message)
{
child_sa_t *child_sa, *other;
entry_t *entry;
child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol,
this->spi, TRUE);
if (!child_sa)
{
/* check if it is an outbound SA */
child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol,
this->spi, FALSE);
if (!child_sa)
{
/* child does not exist anymore, abort exchange */
message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
return SUCCESS;
}
/* we work only with the inbound SPI */
this->spi = child_sa->get_spi(child_sa, TRUE);
}
/* check if this SA is involved in a passive rekeying, either the old
* rekeyed one or the new one created by the peer */
other = child_sa->get_rekey_sa(child_sa);
if (other)
{
if (child_sa->get_state(child_sa) == CHILD_REKEYED)
{
/* the peer was expected to delete this rekeyed SA. we don't send a
* DELETE, in particular, if this is triggered by an expire, because
* that could cause a collision if the CREATE_CHILD_SA response is
* delayed (the peer might interpret that as a deletion of the SA by
* a user and might then ignore the CREATE_CHILD_SA response once it
* arrives - like old strongSwan versions did - although it
* shouldn't as we properly replied to that request so only a delete
* for the new CHILD_SA should result in a deletion) */
child_sa->set_state(child_sa, CHILD_DELETED);
conclude_rekeying(this, child_sa);
}
else
{
/* the rekeying for the new SA we are about to delete on the user's
* behalf has not yet been completed, that is, we are waiting for
* the delete for the old SA and have not yet fully installed this
* new one. we do that now so events are triggered properly when
* we delete it */
DBG2(DBG_IKE, "complete rekeying for %s{%u} before deleting "
"replacement CHILD_SA %s{%u}",
other->get_name(other), other->get_unique_id(other),
child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa));
conclude_rekeying(this, other);
}
}
if (child_sa->get_state(child_sa) == CHILD_DELETED)
{
/* DELETEs for this CHILD_SA were already exchanged, but it was not yet
* destroyed to allow delayed packets to get processed, or we suppress
* the DELETE explicitly (see above) */
destroy_and_reestablish_internal(this->ike_sa, child_sa, FALSE, FALSE, 0);
message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
return SUCCESS;
}
INIT(entry,
.child_sa = child_sa,
.orig_state = child_sa->get_state(child_sa),
);
child_sa->set_state(child_sa, CHILD_DELETING);
this->child_sas->insert_last(this->child_sas, entry);
log_children(this);
build_payloads(this, message);
if (this->expired)
{
child_cfg_t *child_cfg;
DBG1(DBG_IKE, "scheduling CHILD_SA recreate after hard expire");
child_cfg = child_sa->get_config(child_sa);
this->ike_sa->queue_task(this->ike_sa, (task_t*)
child_create_create(this->ike_sa, child_cfg->get_ref(child_cfg),
FALSE, NULL, NULL));
}
return NEED_MORE;
}
/**
* Check if the given CHILD_SA is the SA created by the peer in a rekey
* collision and allow the active rekey task to collect the SPI if it's not yet
* known, in which case it could be for the SA we created in an active rekeying
* that we haven't yet completed.
*/
static child_rekey_collision_t possible_rekey_collision(
private_child_delete_t *this,
child_sa_t *child, uint32_t spi)
{
enumerator_t *tasks;
task_t *task;
child_rekey_t *rekey;
child_rekey_collision_t collision = CHILD_REKEY_COLLISION_NONE;
tasks = this->ike_sa->create_task_enumerator(this->ike_sa,
TASK_QUEUE_ACTIVE);
@@ -152,76 +582,17 @@ static bool is_redundant(private_child_delete_t *this, child_sa_t *child)
{
if (task->get_type(task) == TASK_CHILD_REKEY)
{
child_rekey_t *rekey = (child_rekey_t*)task;
if (rekey->is_redundant(rekey, child))
{
tasks->destroy(tasks);
return TRUE;
}
rekey = (child_rekey_t*)task;
collision = rekey->handle_delete(rekey, child, spi);
break;
}
}
tasks->destroy(tasks);
return FALSE;
return collision;
}
/**
* Install the outbound CHILD_SA with the given SPI
*/
static void install_outbound(private_child_delete_t *this,
protocol_id_t protocol, uint32_t spi)
{
child_sa_t *child_sa;
linked_list_t *my_ts, *other_ts;
status_t status;
if (!spi)
{
return;
}
child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol,
spi, FALSE);
if (!child_sa)
{
DBG1(DBG_IKE, "CHILD_SA not found after rekeying");
return;
}
if (this->initiator && is_redundant(this, child_sa))
{ /* if we won the rekey collision we don't want to install the
* redundant SA created by the peer */
return;
}
status = child_sa->install_outbound(child_sa);
if (status != SUCCESS)
{
DBG1(DBG_IKE, "unable to install outbound IPsec SA (SAD) in kernel");
charon->bus->alert(charon->bus, ALERT_INSTALL_CHILD_SA_FAILED,
child_sa);
/* FIXME: delete the new child_sa? */
return;
}
my_ts = linked_list_create_from_enumerator(
child_sa->create_ts_enumerator(child_sa, TRUE));
other_ts = linked_list_create_from_enumerator(
child_sa->create_ts_enumerator(child_sa, FALSE));
DBG0(DBG_IKE, "outbound CHILD_SA %s{%d} established "
"with SPIs %.8x_i %.8x_o and TS %#R === %#R",
child_sa->get_name(child_sa),
child_sa->get_unique_id(child_sa),
ntohl(child_sa->get_spi(child_sa, TRUE)),
ntohl(child_sa->get_spi(child_sa, FALSE)),
my_ts, other_ts);
my_ts->destroy(my_ts);
other_ts->destroy(other_ts);
}
/**
* read in payloads and find the children to delete
* Read payloads and find the children to delete.
*/
static void process_payloads(private_child_delete_t *this, message_t *message)
{
@@ -247,8 +618,14 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
spis = delete_payload->create_spi_enumerator(delete_payload);
while (spis->enumerate(spis, &spi))
{
child_rekey_collision_t collision = CHILD_REKEY_COLLISION_NONE;
child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol,
spi, FALSE);
if (!this->initiator)
{
collision = possible_rekey_collision(this, child_sa, spi);
}
if (!child_sa)
{
DBG1(DBG_IKE, "received DELETE for unknown %N CHILD_SA with"
@@ -263,43 +640,29 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
{
continue;
}
INIT(entry,
.child_sa = child_sa
);
switch (child_sa->get_state(child_sa))
else if (this->initiator)
{
case CHILD_REKEYED:
entry->rekeyed = TRUE;
break;
case CHILD_DELETED:
/* already deleted but not yet destroyed, ignore */
case CHILD_DELETING:
/* we don't send back a delete if we already initiated
* a delete ourself */
if (!this->initiator)
{
free(entry);
continue;
}
break;
case CHILD_REKEYING:
/* we reply as usual, rekeying will fail */
case CHILD_INSTALLED:
if (!this->initiator)
{
if (is_redundant(this, child_sa))
{
entry->rekeyed = TRUE;
}
else
{
entry->check_delete_action = TRUE;
}
}
break;
default:
break;
DBG1(DBG_IKE, "ignore DELETE for %N CHILD_SA with SPI "
"%.8x in response, didn't request its deletion",
protocol_id_names, protocol, ntohl(spi));
continue;
}
INIT(entry,
.child_sa = child_sa,
.orig_state = child_sa->get_state(child_sa),
.collision = collision,
);
if (entry->orig_state == CHILD_DELETED ||
entry->orig_state == CHILD_DELETING)
{
/* we either already deleted but have not yet destroyed the
* SA, which we ignore; or we're actively deleting it, in
* which case we don't send back a DELETE either */
free(entry);
continue;
}
child_sa->set_state(child_sa, CHILD_DELETING);
this->child_sas->insert_last(this->child_sas, entry);
}
spis->destroy(spis);
@@ -308,213 +671,10 @@ static void process_payloads(private_child_delete_t *this, message_t *message)
payloads->destroy(payloads);
}
/**
* destroy the children listed in this->child_sas, reestablish by policy
*/
static status_t destroy_and_reestablish(private_child_delete_t *this)
{
child_init_args_t args = {};
enumerator_t *enumerator;
entry_t *entry;
child_sa_t *child_sa;
child_cfg_t *child_cfg;
protocol_id_t protocol;
uint32_t spi;
action_t action;
status_t status = SUCCESS;
time_t now, expire;
u_int delay;
now = time_monotonic(NULL);
delay = lib->settings->get_int(lib->settings, "%s.delete_rekeyed_delay",
DELETE_REKEYED_DELAY, lib->ns);
enumerator = this->child_sas->create_enumerator(this->child_sas);
while (enumerator->enumerate(enumerator, (void**)&entry))
{
child_sa = entry->child_sa;
child_sa->set_state(child_sa, CHILD_DELETED);
/* signal child down event if we weren't rekeying */
protocol = child_sa->get_protocol(child_sa);
if (!entry->rekeyed)
{
charon->bus->child_updown(charon->bus, child_sa, FALSE);
}
else
{
/* the following two calls are only relevant as responder/loser of
* rekeyings as the initiator/winner already did this right after
* the rekeying was completed, either way, we delay destroying
* the CHILD_SA, by default, so we can process delayed packets */
install_outbound(this, protocol, child_sa->get_rekey_spi(child_sa));
child_sa->remove_outbound(child_sa);
expire = child_sa->get_lifetime(child_sa, TRUE);
if (delay && (!expire || ((now + delay) < expire)))
{
lib->scheduler->schedule_job(lib->scheduler,
(job_t*)delete_child_sa_job_create_id(
child_sa->get_unique_id(child_sa)), delay);
continue;
}
else if (now < expire)
{ /* let it expire naturally */
continue;
}
/* no delay and no lifetime, destroy it immediately */
}
spi = child_sa->get_spi(child_sa, TRUE);
child_cfg = child_sa->get_config(child_sa);
child_cfg->get_ref(child_cfg);
args.reqid = child_sa->get_reqid_ref(child_sa);
args.label = child_sa->get_label(child_sa);
if (args.label)
{
args.label = args.label->clone(args.label);
}
action = child_sa->get_close_action(child_sa);
this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi);
if (entry->check_delete_action)
{ /* enforce child_cfg policy if deleted passively */
if (action & ACTION_TRAP)
{
charon->traps->install(charon->traps,
this->ike_sa->get_peer_cfg(this->ike_sa),
child_cfg);
}
if (action & ACTION_START)
{
child_cfg->get_ref(child_cfg);
status = this->ike_sa->initiate(this->ike_sa, child_cfg, &args);
}
}
child_cfg->destroy(child_cfg);
if (args.reqid)
{
charon->kernel->release_reqid(charon->kernel, args.reqid);
}
DESTROY_IF(args.label);
if (status != SUCCESS)
{
break;
}
}
enumerator->destroy(enumerator);
return status;
}
/**
* send closing signals for all CHILD_SAs over the bus
*/
static void log_children(private_child_delete_t *this)
{
linked_list_t *my_ts, *other_ts;
enumerator_t *enumerator;
entry_t *entry;
child_sa_t *child_sa;
uint64_t bytes_in, bytes_out;
enumerator = this->child_sas->create_enumerator(this->child_sas);
while (enumerator->enumerate(enumerator, (void**)&entry))
{
child_sa = entry->child_sa;
my_ts = linked_list_create_from_enumerator(
child_sa->create_ts_enumerator(child_sa, TRUE));
other_ts = linked_list_create_from_enumerator(
child_sa->create_ts_enumerator(child_sa, FALSE));
if (this->expired)
{
DBG0(DBG_IKE, "closing expired CHILD_SA %s{%d} "
"with SPIs %.8x_i %.8x_o and TS %#R === %#R",
child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa),
ntohl(child_sa->get_spi(child_sa, TRUE)),
ntohl(child_sa->get_spi(child_sa, FALSE)), my_ts, other_ts);
}
else
{
child_sa->get_usestats(child_sa, TRUE, NULL, &bytes_in, NULL);
child_sa->get_usestats(child_sa, FALSE, NULL, &bytes_out, NULL);
DBG0(DBG_IKE, "closing CHILD_SA %s{%d} with SPIs %.8x_i "
"(%llu bytes) %.8x_o (%llu bytes) and TS %#R === %#R",
child_sa->get_name(child_sa), child_sa->get_unique_id(child_sa),
ntohl(child_sa->get_spi(child_sa, TRUE)), bytes_in,
ntohl(child_sa->get_spi(child_sa, FALSE)), bytes_out,
my_ts, other_ts);
}
my_ts->destroy(my_ts);
other_ts->destroy(other_ts);
}
enumerator->destroy(enumerator);
}
METHOD(task_t, build_i, status_t,
private_child_delete_t *this, message_t *message)
{
child_sa_t *child_sa;
entry_t *entry;
child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol,
this->spi, TRUE);
if (!child_sa)
{ /* check if it is an outbound sa */
child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol,
this->spi, FALSE);
if (!child_sa)
{ /* child does not exist anymore */
return SUCCESS;
}
/* we work only with the inbound SPI */
this->spi = child_sa->get_spi(child_sa, TRUE);
}
if (this->expired && child_sa->get_state(child_sa) == CHILD_REKEYED)
{ /* the peer was expected to delete this SA, but if we send a DELETE
* we might cause a collision there if the CREATE_CHILD_SA response
* is delayed (the peer wouldn't know if we deleted this SA due to an
* expire or because of a forced delete by the user and might then
* ignore the CREATE_CHILD_SA response once it arrives) */
child_sa->set_state(child_sa, CHILD_DELETED);
install_outbound(this, this->protocol,
child_sa->get_rekey_spi(child_sa));
}
if (child_sa->get_state(child_sa) == CHILD_DELETED)
{ /* DELETEs for this CHILD_SA were already exchanged, but it was not yet
* destroyed to allow delayed packets to get processed */
this->ike_sa->destroy_child_sa(this->ike_sa, this->protocol, this->spi);
message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
return SUCCESS;
}
INIT(entry,
.child_sa = child_sa,
.rekeyed = child_sa->get_state(child_sa) == CHILD_REKEYED,
);
this->child_sas->insert_last(this->child_sas, entry);
log_children(this);
build_payloads(this, message);
if (!entry->rekeyed && this->expired)
{
child_cfg_t *child_cfg;
DBG1(DBG_IKE, "scheduling CHILD_SA recreate after hard expire");
child_cfg = child_sa->get_config(child_sa);
this->ike_sa->queue_task(this->ike_sa, (task_t*)
child_create_create(this->ike_sa, child_cfg->get_ref(child_cfg),
FALSE, NULL, NULL));
}
return NEED_MORE;
}
METHOD(task_t, process_i, status_t,
private_child_delete_t *this, message_t *message)
{
process_payloads(this, message);
DBG1(DBG_IKE, "CHILD_SA closed");
return destroy_and_reestablish(this);
}
@@ -530,7 +690,6 @@ METHOD(task_t, build_r, status_t,
private_child_delete_t *this, message_t *message)
{
build_payloads(this, message);
DBG1(DBG_IKE, "CHILD_SA closed");
return destroy_and_reestablish(this);
}
@@ -540,19 +699,6 @@ METHOD(task_t, get_type, task_type_t,
return TASK_CHILD_DELETE;
}
METHOD(child_delete_t , get_child, child_sa_t*,
private_child_delete_t *this)
{
child_sa_t *child_sa = NULL;
entry_t *entry;
if (this->child_sas->get_first(this->child_sas, (void**)&entry) == SUCCESS)
{
child_sa = entry->child_sa;
}
return child_sa;
}
METHOD(task_t, migrate, void,
private_child_delete_t *this, ike_sa_t *ike_sa)
{
@@ -584,7 +730,6 @@ child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol,
.migrate = _migrate,
.destroy = _destroy,
},
.get_child = _get_child,
},
.ike_sa = ike_sa,
.child_sas = linked_list_create(),
+33 -8
View File
@@ -1,4 +1,5 @@
/*
* Copyright (C) 2022 Tobias Brunner
* Copyright (C) 2007 Martin Willi
*
* Copyright (C) secunet Security Networks AG
@@ -25,8 +26,8 @@
typedef struct child_delete_t child_delete_t;
#include <library.h>
#include <sa/ike_sa.h>
#include <sa/task.h>
#include <sa/ike_sa.h>
#include <sa/child_sa.h>
/**
@@ -38,13 +39,6 @@ struct child_delete_t {
* Implements the task_t interface
*/
task_t task;
/**
* Get the CHILD_SA to delete by this task.
*
* @return child_sa
*/
child_sa_t* (*get_child) (child_delete_t *this);
};
/**
@@ -59,4 +53,35 @@ struct child_delete_t {
child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol,
uint32_t spi, bool expired);
/**
* Destroy the given CHILD_SA and trigger events and configured actions.
*
* @param ike_sa IKE_SA the child_sa belongs to
* @param child_sa CHILD_SA to destroy and potentially reestablish
* @return status of reestablishment
*/
status_t child_delete_destroy_and_reestablish(ike_sa_t *ike_sa,
child_sa_t *child_sa);
/**
* Destroy the given CHILD_SA and trigger events and force a recreation.
*
* @param ike_sa IKE_SA the child_sa belongs to
* @param child_sa CHILD_SA to destroy and reestablish
* @return status of reestablishment
*/
status_t child_delete_destroy_and_force_reestablish(ike_sa_t *ike_sa,
child_sa_t *child_sa);
/**
* Destroy the given CHILD_SA with a configured delay, so delayed inbound
* packets can still be processed.
*
* @note The outbound SA should already be uninstalled when calling this.
*
* @param ike_sa IKE_SA the child_sa belongs to
* @param child_sa CHILD_SA to destroy and potentially reestablish
*/
void child_delete_destroy_rekeyed(ike_sa_t *ike_sa, child_sa_t *child_sa);
#endif /** CHILD_DELETE_H_ @}*/
File diff suppressed because it is too large Load Diff
+41 -10
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2020 Tobias Brunner
* Copyright (C) 2016-2022 Tobias Brunner
* Copyright (C) 2007 Martin Willi
*
* Copyright (C) secunet Security Networks AG
@@ -24,12 +24,25 @@
#define CHILD_REKEY_H_
typedef struct child_rekey_t child_rekey_t;
typedef enum child_rekey_collision_t child_rekey_collision_t;
#include <library.h>
#include <sa/ike_sa.h>
#include <sa/child_sa.h>
#include <sa/task.h>
/**
* Type of collision an active rekey task may have with an inbound DELETE.
*/
enum child_rekey_collision_t {
/** Unrelated SA or unknown SPI (might be for the SA this task creates) */
CHILD_REKEY_COLLISION_NONE = 0,
/** Deleted SA is the one created by the peer in a collision */
CHILD_REKEY_COLLISION_PEER,
/** Deleted SA is the SA the active task is rekeying */
CHILD_REKEY_COLLISION_OLD,
};
/**
* Task of type TASK_CHILD_REKEY, rekey an established CHILD_SA.
*/
@@ -41,21 +54,27 @@ struct child_rekey_t {
task_t task;
/**
* Check if the given SA is the redundant CHILD_SA created during a rekey
* collision.
* Handle a DELETE for the given CHILD_SA/SPI that might be related to
* this rekeiyng if the CREATE_CHILD_SA response is delayed.
*
* This is called if the other peer deletes the redundant SA before we were
* able to handle the CREATE_CHILD_SA response.
* This checks if the given SA is the CHILD_SA created by the peer during a
* rekey collision or if it's for the old SA.
*
* @param child CHILD_SA to check
* @return TRUE if the SA is the redundant CHILD_SA
* If child is NULL, the SPI is collected as it might be for the SA this
* task is actively creating (the peer sends the inbound SA we don't know
* yet).
*
* @param child CHILD_SA to check
* @param spi SPI in case child is not known
* @return type of collision
*/
bool (*is_redundant)(child_rekey_t *this, child_sa_t *child);
child_rekey_collision_t (*handle_delete)(child_rekey_t *this,
child_sa_t *child, uint32_t spi);
/**
* Register a rekeying/delete task which collides with this one
* Register a rekey task which collides with this one.
*
* If two peers initiate rekeying at the same time, the collision must
* If two peers initiate rekeyings at the same time, the collision must
* be handled gracefully. The task manager is aware of what exchanges
* are going on and notifies the active task by passing the passive.
*
@@ -77,4 +96,16 @@ struct child_rekey_t {
child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol,
uint32_t spi);
/**
* Conclude the rekeying for the given CHILD_SAs by installing the outbound
* SA for the new CHILD_SA, uninstalling the one for the old and triggering
* an appropriate log message and event.
*
* @param old the old CHILD_SA
* @param new the new CHILD_SA
* @return TRUE if new outbound SA installed successfully
*/
bool child_rekey_conclude_rekeying(child_sa_t *old, child_sa_t *new);
#endif /** CHILD_REKEY_H_ @}*/
+103
View File
@@ -81,6 +81,21 @@ struct private_ike_auth_t {
*/
packet_t *other_packet;
/**
* IntAuth data from IKE_INTERMEDIATE exchanges: IntAuth_i | IntAuth_r | MID
*/
chunk_t int_auth;
/**
* Pointer for IntAuth_i into int_auth
*/
chunk_t int_auth_i;
/**
* Pointer for IntAuth_r into int_auth
*/
chunk_t int_auth_r;
/**
* Reserved bytes of ID payload
*/
@@ -193,6 +208,61 @@ static status_t collect_other_init_data(private_ike_auth_t *this,
return NEED_MORE;
}
/**
* Collect IntAuth data for IKE_INTERMEDIATE exchanges.
*/
static status_t collect_int_auth_data(private_ike_auth_t *this, bool verify,
message_t *message)
{
keymat_v2_t *keymat;
chunk_t int_auth_ap, prev = chunk_empty, int_auth;
if (!message->get_plain(message, &int_auth_ap))
{
return FAILED;
}
if (this->int_auth.len)
{
prev = this->initiator != verify ? this->int_auth_i : this->int_auth_r;
}
keymat = (keymat_v2_t*)this->ike_sa->get_keymat(this->ike_sa);
if (!keymat->get_int_auth(keymat, verify, int_auth_ap, prev, &int_auth))
{
chunk_free(&int_auth_ap);
return FAILED;
}
chunk_free(&int_auth_ap);
if (!this->int_auth.len)
{ /* IntAuth consists of IntAuth_i | IntAuth_r | MID */
this->int_auth = chunk_alloc(int_auth.len * 2 + sizeof(uint32_t));
this->int_auth_i = chunk_create(this->int_auth.ptr, int_auth.len);
memset(this->int_auth.ptr, 0, this->int_auth.len);
prev = this->int_auth_i;
}
else if (!this->int_auth_r.len)
{
this->int_auth_r = chunk_create(this->int_auth.ptr + int_auth.len,
int_auth.len);
prev = this->int_auth_r;
}
memcpy(prev.ptr, int_auth.ptr, int_auth.len);
chunk_free(&int_auth);
return NEED_MORE;
}
/**
* Set the MID in the IntAuth data to that of the first IKE_AUTH message.
*/
static void set_ike_auth_mid(private_ike_auth_t *this, message_t *message)
{
if (this->int_auth.len)
{
htoun32(this->int_auth.ptr + this->int_auth.len - sizeof(uint32_t),
message->get_message_id(message));
}
}
/**
* Get and store reserved bytes of id_payload, required for AUTH payload
*/
@@ -662,6 +732,8 @@ METHOD(task_t, build_i, status_t,
charon->bus->alert(charon->bus, ALERT_LOCAL_AUTH_FAILED);
return FAILED;
}
/* set MID in IntAuth data if used */
set_ike_auth_mid(this, message);
}
if (!this->do_another_auth && !this->my_auth)
@@ -733,6 +805,10 @@ METHOD(task_t, build_i, status_t,
charon->bus->alert(charon->bus, ALERT_LOCAL_AUTH_FAILED);
return FAILED;
}
if (this->int_auth.ptr && this->my_auth->set_int_auth)
{
this->my_auth->set_int_auth(this->my_auth, this->int_auth);
}
}
/* 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
@@ -784,6 +860,8 @@ METHOD(task_t, post_build_i, status_t,
{
case IKE_SA_INIT:
return collect_my_init_data(this, message);
case IKE_INTERMEDIATE:
return collect_int_auth_data(this, FALSE, message);
default:
return NEED_MORE;
}
@@ -800,6 +878,8 @@ METHOD(task_t, process_r, status_t,
{
case IKE_SA_INIT:
return collect_other_init_data(this, message);
case IKE_INTERMEDIATE:
return collect_int_auth_data(this, TRUE, message);
case IKE_AUTH:
break;
default:
@@ -841,6 +921,8 @@ METHOD(task_t, process_r, status_t,
{
this->initial_contact = TRUE;
}
/* set MID in IntAuth data if used */
set_ike_auth_mid(this, message);
this->first_auth = TRUE;
}
@@ -912,6 +994,10 @@ METHOD(task_t, process_r, status_t,
this->authentication_failed = TRUE;
return NEED_MORE;
}
if (this->int_auth.ptr && this->other_auth->set_int_auth)
{
this->other_auth->set_int_auth(this->other_auth, this->int_auth);
}
}
if (message->get_payload(message, PLV2_AUTH) &&
is_first_round(this, FALSE))
@@ -1097,6 +1183,10 @@ METHOD(task_t, build_r, status_t,
{
goto local_auth_failed;
}
if (this->int_auth.ptr && this->my_auth->set_int_auth)
{
this->my_auth->set_int_auth(this->my_auth, this->int_auth);
}
}
}
@@ -1219,6 +1309,8 @@ METHOD(task_t, post_build_r, status_t,
{
case IKE_SA_INIT:
return collect_my_init_data(this, message);
case IKE_INTERMEDIATE:
return collect_int_auth_data(this, FALSE, message);
default:
return NEED_MORE;
}
@@ -1299,6 +1391,8 @@ METHOD(task_t, process_i, status_t,
this->ike_sa->enable_extension(this->ike_sa, EXT_MULTIPLE_AUTH);
}
return collect_other_init_data(this, message);
case IKE_INTERMEDIATE:
return collect_int_auth_data(this, TRUE, message);
case IKE_AUTH:
break;
default:
@@ -1404,6 +1498,11 @@ METHOD(task_t, process_i, status_t,
{
goto peer_auth_failed;
}
if (this->int_auth.ptr && this->other_auth->set_int_auth)
{
this->other_auth->set_int_auth(this->other_auth,
this->int_auth);
}
}
else
{
@@ -1558,6 +1657,9 @@ METHOD(task_t, migrate, void,
clear_ppk(this);
chunk_free(&this->my_nonce);
chunk_free(&this->other_nonce);
chunk_free(&this->int_auth);
this->int_auth_i = chunk_empty;
this->int_auth_r = chunk_empty;
DESTROY_IF(this->my_packet);
DESTROY_IF(this->other_packet);
DESTROY_IF(this->peer_cfg);
@@ -1586,6 +1688,7 @@ METHOD(task_t, destroy, void,
clear_ppk(this);
chunk_free(&this->my_nonce);
chunk_free(&this->other_nonce);
chunk_free(&this->int_auth);
DESTROY_IF(this->my_packet);
DESTROY_IF(this->other_packet);
DESTROY_IF(this->my_auth);
+435 -124
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2019 Tobias Brunner
* Copyright (C) 2008-2020 Tobias Brunner
* Copyright (C) 2005-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter
*
@@ -30,9 +30,13 @@
#include <encoding/payloads/ke_payload.h>
#include <encoding/payloads/nonce_payload.h>
/** maximum retries to do with cookies/other dh groups */
/** maximum retries to do with cookies/other ke methods */
#define MAX_RETRIES 5
/** maximum number of key exchanges (including the initial one) */
#define MAX_KEY_EXCHANGES (ADDITIONAL_KEY_EXCHANGE_7 - \
ADDITIONAL_KEY_EXCHANGE_1 + 2)
typedef struct private_ike_init_t private_ike_init_t;
/**
@@ -56,29 +60,39 @@ struct private_ike_init_t {
bool initiator;
/**
* Whether the key exchange is done
* Key exchanges to perform
*/
bool ke_done;
struct {
transform_type_t type;
key_exchange_method_t method;
bool done;
bool derived;
} key_exchanges[MAX_KEY_EXCHANGES];
/**
* Whether keys have already been derived
* Current key exchange
*/
bool ke_derived;
int ke_index;
/**
* diffie hellman group to use
* Key exchange method from the parsed or sent KE payload
*/
key_exchange_method_t dh_group;
key_exchange_method_t ke_method;
/**
* diffie hellman key exchange
* Current key exchange object
*/
key_exchange_t *dh;
key_exchange_t *ke;
/**
* Applying DH public value failed?
* All key exchanges performed during rekeying (key_exchange_t)
*/
bool dh_failed;
array_t *kes;
/**
* Applying KE public key failed?
*/
bool ke_failed;
/**
* Keymat derivation (from IKE_SA)
@@ -86,17 +100,17 @@ struct private_ike_init_t {
keymat_v2_t *keymat;
/**
* nonce chosen by us
* Nonce chosen by us
*/
chunk_t my_nonce;
/**
* nonce chosen by peer
* Nonce chosen by peer
*/
chunk_t other_nonce;
/**
* nonce generator
* Nonce generator
*/
nonce_gen_t *nonceg;
@@ -106,17 +120,17 @@ struct private_ike_init_t {
proposal_t *proposal;
/**
* Old IKE_SA which gets rekeyed
* Old IKE_SA that gets rekeyed
*/
ike_sa_t *old_sa;
/**
* cookie received from responder
* Cookie received from responder
*/
chunk_t cookie;
/**
* retries done so far after failure (cookie or bad dh group)
* Retries done so far after failure (cookie or bad KE method)
*/
u_int retry;
@@ -131,6 +145,15 @@ struct private_ike_init_t {
bool follow_redirects;
};
/**
* Returns the exchange type for additional exchanges when using multiple key
* exchanges, depending on whether this happens initially or during a rekeying
*/
static exchange_type_t exchange_type_multi_ke(private_ike_init_t *this)
{
return this->old_sa ? IKE_FOLLOWUP_KE : IKE_INTERMEDIATE;
}
/**
* Allocate our own nonce value
*/
@@ -320,11 +343,12 @@ static bool build_payloads(private_ike_init_t *this, message_t *message)
sa_payload_t *sa_payload;
ke_payload_t *ke_payload;
nonce_payload_t *nonce_payload;
linked_list_t *proposal_list, *other_dh_groups;
linked_list_t *proposal_list, *other_ke_methods;
ike_sa_id_t *id;
proposal_t *proposal;
enumerator_t *enumerator;
ike_cfg_t *ike_cfg;
bool additional_ke = FALSE;
id = this->ike_sa->get_id(this->ike_sa);
@@ -333,7 +357,7 @@ static bool build_payloads(private_ike_init_t *this, message_t *message)
if (this->initiator)
{
proposal_list = ike_cfg->get_proposals(ike_cfg);
other_dh_groups = linked_list_create();
other_ke_methods = linked_list_create();
enumerator = proposal_list->create_enumerator(proposal_list);
while (enumerator->enumerate(enumerator, (void**)&proposal))
{
@@ -342,23 +366,25 @@ static bool build_payloads(private_ike_init_t *this, message_t *message)
{
proposal->set_spi(proposal, id->get_initiator_spi(id));
}
/* move the selected DH group to the front of the proposal */
/* move the selected KE method to the front of the proposal */
if (!proposal->promote_transform(proposal, KEY_EXCHANGE_METHOD,
this->dh_group))
this->ke_method))
{ /* the proposal does not include the group, move to the back */
proposal_list->remove_at(proposal_list, enumerator);
other_dh_groups->insert_last(other_dh_groups, proposal);
other_ke_methods->insert_last(other_ke_methods, proposal);
}
additional_ke = additional_ke ||
proposal_has_additional_ke(proposal);
}
enumerator->destroy(enumerator);
/* add proposals that don't contain the selected group */
enumerator = other_dh_groups->create_enumerator(other_dh_groups);
enumerator = other_ke_methods->create_enumerator(other_ke_methods);
while (enumerator->enumerate(enumerator, (void**)&proposal))
{ /* no need to remove from the list as we destroy it anyway*/
proposal_list->insert_last(proposal_list, proposal);
}
enumerator->destroy(enumerator);
other_dh_groups->destroy(other_dh_groups);
other_ke_methods->destroy(other_ke_methods);
sa_payload = sa_payload_create_from_proposals_v2(proposal_list);
proposal_list->destroy_offset(proposal_list, offsetof(proposal_t, destroy));
@@ -371,29 +397,22 @@ static bool build_payloads(private_ike_init_t *this, message_t *message)
this->proposal->set_spi(this->proposal, id->get_responder_spi(id));
}
sa_payload = sa_payload_create_from_proposal_v2(this->proposal);
additional_ke = proposal_has_additional_ke(this->proposal);
}
message->add_payload(message, (payload_t*)sa_payload);
ke_payload = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE,
this->dh);
this->ke);
if (!ke_payload)
{
DBG1(DBG_IKE, "creating KE payload failed");
return FALSE;
}
message->add_payload(message, (payload_t*)ke_payload);
nonce_payload = nonce_payload_create(PLV2_NONCE);
nonce_payload->set_nonce(nonce_payload, this->my_nonce);
if (this->old_sa)
{ /* payload order differs if we are rekeying */
message->add_payload(message, (payload_t*)nonce_payload);
message->add_payload(message, (payload_t*)ke_payload);
}
else
{
message->add_payload(message, (payload_t*)ke_payload);
message->add_payload(message, (payload_t*)nonce_payload);
}
message->add_payload(message, (payload_t*)nonce_payload);
/* negotiate fragmentation if we are not rekeying */
if (!this->old_sa &&
@@ -452,6 +471,16 @@ static bool build_payloads(private_ike_init_t *this, message_t *message)
message->add_notify(message, FALSE, CHILDLESS_IKEV2_SUPPORTED,
chunk_empty);
}
if (!this->old_sa && additional_ke)
{
if (this->initiator ||
this->ike_sa->supports_extension(this->ike_sa,
EXT_IKE_INTERMEDIATE))
{
message->add_notify(message, FALSE, INTERMEDIATE_EXCHANGE_SUPPORTED,
chunk_empty);
}
}
return TRUE;
}
@@ -523,6 +552,106 @@ static void process_sa_payload(private_ike_init_t *this, message_t *message,
offsetof(proposal_t, destroy));
}
/**
* Collect all key exchanges from the proposal
*/
static void determine_key_exchanges(private_ike_init_t *this)
{
transform_type_t t = KEY_EXCHANGE_METHOD;
uint16_t alg;
int i = 1;
this->proposal->get_algorithm(this->proposal, t, &alg, NULL);
this->key_exchanges[0].type = t;
this->key_exchanges[0].method = alg;
for (t = ADDITIONAL_KEY_EXCHANGE_1; t <= ADDITIONAL_KEY_EXCHANGE_7; t++)
{
if (this->proposal->get_algorithm(this->proposal, t, &alg, NULL))
{
this->key_exchanges[i].type = t;
this->key_exchanges[i].method = alg;
i++;
}
}
}
/**
* Check if additional key exchanges are required
*/
static bool additional_key_exchange_required(private_ike_init_t *this)
{
int i;
for (i = this->ke_index; i < MAX_KEY_EXCHANGES; i++)
{
if (this->key_exchanges[i].type && !this->key_exchanges[i].done)
{
return TRUE;
}
}
return FALSE;
}
/**
* Clear data on key exchanges
*/
static void clear_key_exchanges(private_ike_init_t *this)
{
int i;
for (i = 0; i < MAX_KEY_EXCHANGES; i++)
{
this->key_exchanges[i].type = 0;
this->key_exchanges[i].method = 0;
this->key_exchanges[i].done = FALSE;
}
this->ke_index = 0;
array_destroy_offset(this->kes, offsetof(key_exchange_t, destroy));
this->kes = NULL;
}
/**
* Process a KE payload
*/
static void process_ke_payload(private_ike_init_t *this, ke_payload_t *ke)
{
key_exchange_method_t method = this->key_exchanges[this->ke_index].method;
key_exchange_method_t received = ke->get_key_exchange_method(ke);
if (method != received)
{
DBG1(DBG_IKE, "key exchange method in received payload %N doesn't "
"match negotiated %N", key_exchange_method_names, received,
key_exchange_method_names, method);
this->ke_failed = TRUE;
return;
}
if (!this->initiator)
{
DESTROY_IF(this->ke);
this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
method);
if (!this->ke)
{
DBG1(DBG_IKE, "negotiated key exchange method %N not supported",
key_exchange_method_names, method);
}
}
else if (this->ke)
{
this->ke_failed = this->ke->get_method(this->ke) != received;
}
if (this->ke && !this->ke_failed)
{
this->ke_failed = !this->ke->set_public_key(this->ke,
ke->get_key_exchange_data(ke));
}
}
/**
* Read payloads from message
*/
@@ -531,7 +660,7 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
enumerator_t *enumerator;
payload_t *payload;
ike_sa_id_t *id;
ke_payload_t *ke_payload = NULL;
ke_payload_t *ke_pld = NULL;
enumerator = message->create_payload_enumerator(message);
while (enumerator->enumerate(enumerator, &payload))
@@ -545,9 +674,9 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
}
case PLV2_KEY_EXCHANGE:
{
ke_payload = (ke_payload_t*)payload;
ke_pld = (ke_payload_t*)payload;
this->dh_group = ke_payload->get_key_exchange_method(ke_payload);
this->ke_method = ke_pld->get_key_exchange_method(ke_pld);
break;
}
case PLV2_NONCE:
@@ -611,6 +740,13 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
EXT_IKE_CHILDLESS);
}
break;
case INTERMEDIATE_EXCHANGE_SUPPORTED:
if (!this->old_sa)
{
this->ike_sa->enable_extension(this->ike_sa,
EXT_IKE_INTERMEDIATE);
}
break;
default:
/* other notifies are handled elsewhere */
break;
@@ -641,29 +777,57 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
this->proposal->get_spi(this->proposal));
}
}
}
if (ke_payload && this->proposal &&
this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
this->dh_group))
{
if (!this->initiator)
determine_key_exchanges(this);
if (ke_pld)
{
this->dh = this->keymat->keymat.create_ke(
&this->keymat->keymat, this->dh_group);
}
else if (this->dh)
{
this->dh_failed = this->dh->get_method(this->dh) != this->dh_group;
}
if (this->dh && !this->dh_failed)
{
this->dh_failed = !this->dh->set_public_key(this->dh,
ke_payload->get_key_exchange_data(ke_payload));
process_ke_payload(this, ke_pld);
}
}
}
/**
* Build payloads in additional exchanges when using multiple key exchanges
*/
static bool build_payloads_multi_ke(private_ike_init_t *this,
message_t *message)
{
ke_payload_t *ke;
ke = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE, this->ke);
if (!ke)
{
DBG1(DBG_IKE, "creating KE payload failed");
return FALSE;
}
message->add_payload(message, (payload_t*)ke);
return TRUE;
}
METHOD(task_t, build_i_multi_ke, status_t,
private_ike_init_t *this, message_t *message)
{
key_exchange_method_t method;
message->set_exchange_type(message, exchange_type_multi_ke(this));
DESTROY_IF(this->ke);
method = this->key_exchanges[this->ke_index].method;
this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
method);
if (!this->ke)
{
DBG1(DBG_IKE, "negotiated key exchange method %N not supported",
key_exchange_method_names, method);
return FAILED;
}
if (!build_payloads_multi_ke(this, message))
{
return FAILED;
}
return NEED_MORE;
}
METHOD(task_t, build_i, status_t,
private_ike_init_t *this, message_t *message)
{
@@ -684,49 +848,50 @@ METHOD(task_t, build_i, status_t,
}
/* if we are retrying after an INVALID_KE_PAYLOAD we already have one */
if (!this->dh)
if (!this->ke)
{
if (this->old_sa && lib->settings->get_bool(lib->settings,
if (this->old_sa &&
lib->settings->get_bool(lib->settings,
"%s.prefer_previous_dh_group", TRUE, lib->ns))
{ /* reuse the DH group we used for the old IKE_SA when rekeying */
{ /* reuse the KE method we used for the old IKE_SA when rekeying */
proposal_t *proposal;
uint16_t dh_group;
uint16_t ke_method;
proposal = this->old_sa->get_proposal(this->old_sa);
if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD,
&dh_group, NULL))
&ke_method, NULL))
{
this->dh_group = dh_group;
this->ke_method = ke_method;
}
else
{ /* this shouldn't happen, but let's be safe */
this->dh_group = ike_cfg->get_algorithm(ike_cfg,
KEY_EXCHANGE_METHOD);
this->ke_method = ike_cfg->get_algorithm(ike_cfg,
KEY_EXCHANGE_METHOD);
}
}
else
{
this->dh_group = ike_cfg->get_algorithm(ike_cfg,
KEY_EXCHANGE_METHOD);
this->ke_method = ike_cfg->get_algorithm(ike_cfg,
KEY_EXCHANGE_METHOD);
}
this->dh = this->keymat->keymat.create_ke(&this->keymat->keymat,
this->dh_group);
if (!this->dh)
this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
this->ke_method);
if (!this->ke)
{
DBG1(DBG_IKE, "configured DH group %N not supported",
key_exchange_method_names, this->dh_group);
DBG1(DBG_IKE, "configured key exchange method %N not supported",
key_exchange_method_names, this->ke_method);
return FAILED;
}
}
else if (this->dh->get_method(this->dh) != this->dh_group)
{ /* reset DH instance if group changed (INVALID_KE_PAYLOAD) */
this->dh->destroy(this->dh);
this->dh = this->keymat->keymat.create_ke(&this->keymat->keymat,
this->dh_group);
if (!this->dh)
else if (this->ke->get_method(this->ke) != this->ke_method)
{ /* reset KE instance if method changed (INVALID_KE_PAYLOAD) */
this->ke->destroy(this->ke);
this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
this->ke_method);
if (!this->ke)
{
DBG1(DBG_IKE, "requested DH group %N not supported",
key_exchange_method_names, this->dh_group);
DBG1(DBG_IKE, "requested key exchange method %N not supported",
key_exchange_method_names, this->ke_method);
return FAILED;
}
}
@@ -763,6 +928,35 @@ METHOD(task_t, build_i, status_t,
return NEED_MORE;
}
/**
* Process payloads in additional exchanges when using multiple key exchanges
*/
static void process_payloads_multi_ke(private_ike_init_t *this,
message_t *message)
{
ke_payload_t *ke;
ke = (ke_payload_t*)message->get_payload(message, PLV2_KEY_EXCHANGE);
if (ke)
{
process_ke_payload(this, ke);
}
else
{
DBG1(DBG_IKE, "KE payload missing in message");
}
}
METHOD(task_t, process_r_multi_ke, status_t,
private_ike_init_t *this, message_t *message)
{
if (message->get_exchange_type(message) == exchange_type_multi_ke(this))
{
process_payloads_multi_ke(this, message);
}
return NEED_MORE;
}
METHOD(task_t, process_r, status_t,
private_ike_init_t *this, message_t *message)
{
@@ -798,26 +992,46 @@ METHOD(task_t, process_r, status_t,
static bool derive_keys_internal(private_ike_init_t *this, chunk_t nonce_i,
chunk_t nonce_r)
{
ike_sa_t *old_sa;
keymat_v2_t *old_keymat;
pseudo_random_function_t prf_alg = PRF_UNDEFINED;
chunk_t skd = chunk_empty;
ike_sa_id_t *id;
array_t *kes = NULL;
bool success;
id = this->ike_sa->get_id(this->ike_sa);
if (this->old_sa)
{
/* rekeying: Include old SKd, use old PRF, apply SPI */
old_keymat = (keymat_v2_t*)this->old_sa->get_keymat(this->old_sa);
prf_alg = old_keymat->get_skd(old_keymat, &skd);
if (additional_key_exchange_required(this))
{ /* when rekeying, we only derive keys once all exchanges are done */
return FALSE;
}
old_sa = this->old_sa;
kes = this->kes;
}
if (!this->keymat->derive_ike_keys(this->keymat, this->proposal, this->dh,
nonce_i, nonce_r, id, prf_alg, skd))
else
{ /* key derivation for additional key exchanges is like rekeying, so pass
* our own SA as old SA to get SK_d */
old_sa = this->ike_sa;
array_insert_create(&kes, ARRAY_HEAD, this->ke);
}
id = this->ike_sa->get_id(this->ike_sa);
old_keymat = (keymat_v2_t*)old_sa->get_keymat(old_sa);
prf_alg = old_keymat->get_skd(old_keymat, &skd);
success = this->keymat->derive_ike_keys(this->keymat, this->proposal, kes,
nonce_i, nonce_r, id, prf_alg, skd);
if (success)
{
return FALSE;
charon->bus->ike_keys(charon->bus, this->ike_sa, kes, chunk_empty,
nonce_i, nonce_r, skd.len ? old_sa : NULL, NULL,
AUTH_NONE);
}
charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, chunk_empty,
nonce_i, nonce_r, this->old_sa, NULL, AUTH_NONE);
return TRUE;
if (kes != this->kes)
{
array_destroy(kes);
}
return success;
}
METHOD(ike_init_t, derive_keys, status_t,
@@ -825,7 +1039,7 @@ METHOD(ike_init_t, derive_keys, status_t,
{
bool success;
if (!this->ke_done || this->ke_derived)
if (!this->ke_index || this->key_exchanges[this->ke_index-1].derived)
{
return NEED_MORE;
}
@@ -839,14 +1053,64 @@ METHOD(ike_init_t, derive_keys, status_t,
success = derive_keys_internal(this, this->other_nonce, this->my_nonce);
}
this->ke_derived = TRUE;
this->key_exchanges[this->ke_index-1].derived = TRUE;
if (!success)
{
DBG1(DBG_IKE, "key derivation failed");
return FAILED;
}
return SUCCESS;
return additional_key_exchange_required(this) ? NEED_MORE : SUCCESS;
}
/**
* Called when a key exchange is done
*/
static status_t key_exchange_done(private_ike_init_t *this)
{
if (this->old_sa)
{
/* during rekeying, we store all the key exchanges performed */
array_insert_create(&this->kes, ARRAY_TAIL, this->ke);
this->ke = NULL;
}
this->key_exchanges[this->ke_index++].done = TRUE;
return additional_key_exchange_required(this) ? NEED_MORE : SUCCESS;
}
METHOD(task_t, build_r_multi_ke, status_t,
private_ike_init_t *this, message_t *message)
{
if (!this->ke)
{
message->add_notify(message, FALSE, INVALID_SYNTAX, chunk_empty);
return FAILED;
}
if (this->ke_failed)
{
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
return FAILED;
}
if (!build_payloads_multi_ke(this, message))
{
return FAILED;
}
if (key_exchange_done(this) != NEED_MORE && this->old_sa)
{
/* during rekeying, we derive keys once all exchanges are done */
if (derive_keys(this) != SUCCESS)
{
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
return FAILED;
}
return SUCCESS;
}
/* when not rekeying, we derive keys after each IKE_INTERMEDIATE but only
* once we receive the next message, so IntAuth is based on the right keys */
return NEED_MORE;
}
METHOD(task_t, build_r, status_t,
@@ -879,19 +1143,20 @@ METHOD(task_t, build_r, status_t,
return FAILED;
}
if (this->dh == NULL ||
if (!this->ke ||
!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
this->dh_group))
this->ke_method))
{
uint16_t group;
if (this->proposal->get_algorithm(this->proposal, KEY_EXCHANGE_METHOD,
&group, NULL))
&group, NULL) &&
this->ke_method != group)
{
DBG1(DBG_IKE, "DH group %N unacceptable, requesting %N",
key_exchange_method_names, this->dh_group,
DBG1(DBG_IKE, "key exchange method %N unacceptable, requesting %N",
key_exchange_method_names, this->ke_method,
key_exchange_method_names, group);
this->dh_group = group;
this->ke_method = group;
group = htons(group);
message->add_notify(message, FALSE, INVALID_KE_PAYLOAD,
chunk_from_thing(group));
@@ -904,9 +1169,9 @@ METHOD(task_t, build_r, status_t,
return FAILED;
}
if (this->dh_failed)
if (this->ke_failed)
{
DBG1(DBG_IKE, "applying DH public value failed");
DBG1(DBG_IKE, "applying KE public value failed");
message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
return FAILED;
}
@@ -916,9 +1181,22 @@ METHOD(task_t, build_r, status_t,
message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
return FAILED;
}
this->ke_done = TRUE;
if (this->old_sa)
if (key_exchange_done(this) == NEED_MORE)
{
if (!this->old_sa &&
!this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_INTERMEDIATE))
{
DBG1(DBG_IKE, "peer didn't send %N while proposing multiple key "
"exchanges", notify_type_names, INTERMEDIATE_EXCHANGE_SUPPORTED);
message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
return FAILED;
}
/* use other exchange type for additional key exchanges */
this->public.task.build = _build_r_multi_ke;
this->public.task.process = _process_r_multi_ke;
}
else if (this->old_sa)
{
/* during rekeying, we derive keys here directly */
if (derive_keys(this) != SUCCESS)
@@ -1015,6 +1293,26 @@ METHOD(task_t, pre_process_i, status_t,
return SUCCESS;
}
METHOD(task_t, process_i_multi_ke, status_t,
private_ike_init_t *this, message_t *message)
{
process_payloads_multi_ke(this, message);
if (this->ke_failed)
{
return FAILED;
}
if (key_exchange_done(this) != NEED_MORE && this->old_sa)
{
/* during rekeying, we derive keys once all exchanges are done */
return derive_keys(this);
}
/* when not rekeying, we derive keys after each IKE_INTERMEDIATE but only
* once we send the next message, so IntAuth is based on the right keys */
return NEED_MORE;
}
METHOD(task_t, process_i, status_t,
private_ike_init_t *this, message_t *message)
{
@@ -1035,16 +1333,16 @@ METHOD(task_t, process_i, status_t,
case INVALID_KE_PAYLOAD:
{
chunk_t data;
key_exchange_method_t bad_group DBG_UNUSED;
key_exchange_method_t bad_method DBG_UNUSED;
bad_group = this->dh_group;
bad_method = this->ke_method;
data = notify->get_notification_data(notify);
this->dh_group = ntohs(*((uint16_t*)data.ptr));
DBG1(DBG_IKE, "peer didn't accept DH group %N, "
this->ke_method = ntohs(*((uint16_t*)data.ptr));
DBG1(DBG_IKE, "peer didn't accept key exchange method %N, "
"it requested %N", key_exchange_method_names,
bad_group, key_exchange_method_names, this->dh_group);
bad_method, key_exchange_method_names, this->ke_method);
if (this->old_sa == NULL)
if (!this->old_sa)
{ /* reset the IKE_SA if we are not rekeying */
this->ike_sa->reset(this->ike_sa, FALSE);
}
@@ -1121,29 +1419,40 @@ METHOD(task_t, process_i, status_t,
process_payloads(this, message);
/* check if we have everything */
if (this->proposal == NULL ||
if (!this->proposal ||
this->other_nonce.len == 0 || this->my_nonce.len == 0)
{
DBG1(DBG_IKE, "peers proposal selection invalid");
DBG1(DBG_IKE, "peer's proposal selection invalid");
return FAILED;
}
if (this->dh == NULL ||
!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
this->dh_group))
if (!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
this->ke_method))
{
DBG1(DBG_IKE, "peer DH group selection invalid");
DBG1(DBG_IKE, "peer's key exchange method selection invalid");
return FAILED;
}
if (this->dh_failed)
if (this->ke_failed)
{
DBG1(DBG_IKE, "applying DH public value failed");
DBG1(DBG_IKE, "applying key exchange public value failed");
return FAILED;
}
this->ke_done = TRUE;
if (this->old_sa)
if (key_exchange_done(this) == NEED_MORE)
{
if (!this->old_sa &&
!this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_INTERMEDIATE))
{
DBG1(DBG_IKE, "peer didn't send %N while accepting multiple key "
"exchanges", notify_type_names, INTERMEDIATE_EXCHANGE_SUPPORTED);
return FAILED;
}
/* use other exchange type for additional key exchanges */
this->public.task.build = _build_i_multi_ke;
this->public.task.process = _process_i_multi_ke;
}
else if (this->old_sa)
{
/* during rekeying, we derive keys here directly */
return derive_keys(this);
@@ -1163,24 +1472,26 @@ METHOD(task_t, migrate, void,
{
DESTROY_IF(this->proposal);
chunk_free(&this->other_nonce);
this->ke_done = FALSE;
this->ke_derived = FALSE;
clear_key_exchanges(this);
this->ike_sa = ike_sa;
this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
this->proposal = NULL;
this->dh_failed = FALSE;
this->ke_failed = FALSE;
this->public.task.build = _build_i;
this->public.task.process = _process_i;
}
METHOD(task_t, destroy, void,
private_ike_init_t *this)
{
DESTROY_IF(this->dh);
DESTROY_IF(this->ke);
DESTROY_IF(this->proposal);
DESTROY_IF(this->nonceg);
chunk_free(&this->my_nonce);
chunk_free(&this->other_nonce);
chunk_free(&this->cookie);
clear_key_exchanges(this);
free(this);
}
@@ -1217,7 +1528,7 @@ ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa)
},
.ike_sa = ike_sa,
.initiator = initiator,
.dh_group = KE_NONE,
.ke_method = KE_NONE,
.keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa),
.old_sa = old_sa,
.signature_authentication = lib->settings->get_bool(lib->settings,
+414 -86
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2015-2018 Tobias Brunner
* Copyright (C) 2015-2020 Tobias Brunner
* Copyright (C) 2005-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter
*
@@ -26,7 +26,6 @@
#include <processing/jobs/rekey_ike_sa_job.h>
#include <processing/jobs/initiate_tasks_job.h>
typedef struct private_ike_rekey_t private_ike_rekey_t;
/**
@@ -65,14 +64,49 @@ struct private_ike_rekey_t {
ike_delete_t *ike_delete;
/**
* colliding task detected by the task manager
* Colliding passive task if any
*/
private_ike_rekey_t *collision;
/**
* TRUE if rekeying can't be handled temporarily
* Link value for the current key exchange
*/
bool failed_temporarily;
chunk_t link;
/**
* State/error flags
*/
enum {
/**
* Set if rekeying can't be handled temporarily.
*/
IKE_REKEY_FAILED_TEMPORARILY = (1<<0),
/**
* Set if the parsed link value was invalid.
*/
IKE_REKEY_LINK_INVALID = (1<<1),
/**
* Set if we use multiple key exchanges and already processed the
* CREATE_CHILD_SA response and started sending IKE_FOLLOWUP_KEs.
*/
IKE_REKEY_FOLLOWUP_KE = (1<<2),
/**
* Set if a passive rekeying has completed successfully and we don't
* expect any further messages.
*/
IKE_REKEY_DONE = (1<<3),
/**
* Set if we adopted a completed passive task, otherwise we just
* reference it.
*/
IKE_REKEY_ADOPTED_PASSIVE = (1<<4),
} flags;
};
/**
@@ -161,10 +195,24 @@ METHOD(task_t, process_i_delete, status_t,
return this->ike_delete->task.process(&this->ike_delete->task, message);
}
METHOD(task_t, build_i_multi_ke, status_t,
private_ike_rekey_t *this, message_t *message)
{
status_t status;
charon->bus->set_sa(charon->bus, this->new_sa);
message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE, this->link);
status = this->ike_init->task.build(&this->ike_init->task, message);
charon->bus->set_sa(charon->bus, this->ike_sa);
this->flags |= IKE_REKEY_FOLLOWUP_KE;
return status;
}
METHOD(task_t, build_i, status_t,
private_ike_rekey_t *this, message_t *message)
{
ike_version_t version;
status_t status;
/* create new SA only on first try */
if (!this->new_sa)
@@ -188,9 +236,9 @@ METHOD(task_t, build_i, status_t,
this->ike_init = ike_init_create(this->new_sa, TRUE, this->ike_sa);
this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
}
this->ike_init->task.build(&this->ike_init->task, message);
return NEED_MORE;
status = this->ike_init->task.build(&this->ike_init->task, message);
charon->bus->set_sa(charon->bus, this->ike_sa);
return status;
}
/**
@@ -231,25 +279,116 @@ static bool have_half_open_children(private_ike_rekey_t *this)
return FALSE;
}
/**
* Check if we are actively rekeying and optionally, if we already sent an
* IKE_FOLLOWUP_KE message.
*/
static bool actively_rekeying(private_ike_rekey_t *this, bool *follow_up_sent)
{
enumerator_t *enumerator;
task_t *task;
bool found = FALSE;
enumerator = this->ike_sa->create_task_enumerator(this->ike_sa,
TASK_QUEUE_ACTIVE);
while (enumerator->enumerate(enumerator, (void**)&task))
{
if (task->get_type(task) == TASK_IKE_REKEY)
{
if (follow_up_sent)
{
private_ike_rekey_t *rekey = (private_ike_rekey_t*)task;
*follow_up_sent = rekey->flags & IKE_REKEY_FOLLOWUP_KE;
}
found = TRUE;
break;
}
}
enumerator->destroy(enumerator);
return found;
}
/**
* Process payloads in a IKE_FOLLOWUP_KE message or a CREATE_CHILD_SA response
*/
static void process_link(private_ike_rekey_t *this, message_t *message)
{
notify_payload_t *notify;
chunk_t link;
notify = message->get_notify(message, ADDITIONAL_KEY_EXCHANGE);
if (!notify)
{
DBG1(DBG_IKE, "%N notify missing", notify_type_names,
ADDITIONAL_KEY_EXCHANGE);
this->flags |= IKE_REKEY_LINK_INVALID;
}
else
{
link = notify->get_notification_data(notify);
if (this->initiator)
{
chunk_free(&this->link);
this->link = chunk_clone(link);
}
else if (!chunk_equals_const(this->link, link))
{
DBG1(DBG_IKE, "data in %N notify doesn't match", notify_type_names,
ADDITIONAL_KEY_EXCHANGE);
this->flags |= IKE_REKEY_LINK_INVALID;
}
}
}
METHOD(task_t, process_r_multi_ke, status_t,
private_ike_rekey_t *this, message_t *message)
{
if (message->get_exchange_type(message) != IKE_FOLLOWUP_KE)
{
return FAILED;
}
if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
{
DBG1(DBG_IKE, "peer continued rekeying, but we are deleting");
this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
return NEED_MORE;
}
charon->bus->set_sa(charon->bus, this->new_sa);
process_link(this, message);
this->ike_init->task.process(&this->ike_init->task, message);
charon->bus->set_sa(charon->bus, this->ike_sa);
return NEED_MORE;
}
METHOD(task_t, process_r, status_t,
private_ike_rekey_t *this, message_t *message)
{
bool follow_up_sent;
if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
{
DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting");
this->failed_temporarily = TRUE;
this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
return NEED_MORE;
}
if (this->ike_sa->has_condition(this->ike_sa, COND_REAUTHENTICATING))
{
DBG1(DBG_IKE, "peer initiated rekeying, but we are reauthenticating");
this->failed_temporarily = TRUE;
this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
return NEED_MORE;
}
if (have_half_open_children(this))
{
DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open");
this->failed_temporarily = TRUE;
this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
return NEED_MORE;
}
if (actively_rekeying(this, &follow_up_sent) && follow_up_sent)
{
DBG1(DBG_IKE, "peer initiated rekeying, but we did too and already "
"sent IKE_FOLLOWUP_KE");
this->flags |= IKE_REKEY_FAILED_TEMPORARILY;
return NEED_MORE;
}
@@ -269,11 +408,16 @@ METHOD(task_t, process_r, status_t,
METHOD(task_t, build_r, status_t,
private_ike_rekey_t *this, message_t *message)
{
if (this->failed_temporarily)
if (this->flags & IKE_REKEY_FAILED_TEMPORARILY)
{
message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
return SUCCESS;
}
if (this->flags & IKE_REKEY_LINK_INVALID)
{
message->add_notify(message, TRUE, STATE_NOT_FOUND, chunk_empty);
return SUCCESS;
}
if (!this->new_sa)
{
/* IKE_SA/a CHILD_SA is in an unacceptable state, deny rekeying */
@@ -282,17 +426,43 @@ METHOD(task_t, build_r, status_t,
}
charon->bus->set_sa(charon->bus, this->new_sa);
if (this->ike_init->task.build(&this->ike_init->task, message) == FAILED)
switch (this->ike_init->task.build(&this->ike_init->task, message))
{
this->ike_init->task.destroy(&this->ike_init->task);
this->ike_init = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
return SUCCESS;
case FAILED:
this->ike_init->task.destroy(&this->ike_init->task);
this->ike_init = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
return SUCCESS;
case NEED_MORE:
/* additional key exchanges, the value in the notify doesn't really
* matter to us as we have a window size of 1 */
charon->bus->set_sa(charon->bus, this->ike_sa);
if (!this->link.ptr)
{
this->link = chunk_clone(chunk_from_chars(0x42));
}
message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE,
this->link);
if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
{
this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
}
this->public.task.process = _process_r_multi_ke;
return NEED_MORE;
default:
charon->bus->set_sa(charon->bus, this->ike_sa);
if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
{
this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
}
break;
}
charon->bus->set_sa(charon->bus, this->ike_sa);
if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING)
{ /* in case of a collision we let the initiating task handle this */
this->flags |= IKE_REKEY_DONE;
/* if we are actively rekeying, we let the initiating task handle this */
if (!actively_rekeying(this, NULL))
{
establish_new(this);
/* make sure the IKE_SA is gone in case the peer fails to delete it */
lib->scheduler->schedule_job(lib->scheduler, (job_t*)
@@ -303,7 +473,7 @@ METHOD(task_t, build_r, status_t,
}
/**
* Conclude any undetected rekey collision.
* Conclude any (undetected) rekey collision.
*
* If the peer does not detect the collision it will delete this IKE_SA.
* Depending on when our request reaches the peer and we receive the delete
@@ -311,18 +481,174 @@ METHOD(task_t, build_r, status_t,
*
* Returns TRUE if there was a collision, FALSE otherwise.
*/
static bool conclude_undetected_collision(private_ike_rekey_t *this)
static bool conclude_collision(private_ike_rekey_t *this, bool maybe_undetected)
{
if (this->collision)
if (this->collision &&
this->flags & IKE_REKEY_ADOPTED_PASSIVE)
{
DBG1(DBG_IKE, "peer did not notice IKE_SA rekey collision, abort "
"active rekeying");
if (maybe_undetected)
{
DBG1(DBG_IKE, "peer may not have noticed IKE_SA rekey collision, "
"abort active rekeying");
}
establish_new(this->collision);
return TRUE;
}
return FALSE;
}
/**
* Delete the redundant IKE_SA we created.
*/
static void delete_redundant(private_ike_rekey_t *this)
{
host_t *host;
/* apply host for a proper delete */
host = this->ike_sa->get_my_host(this->ike_sa);
this->new_sa->set_my_host(this->new_sa, host->clone(host));
host = this->ike_sa->get_other_host(this->ike_sa);
this->new_sa->set_other_host(this->new_sa, host->clone(host));
this->new_sa->set_state(this->new_sa, IKE_REKEYED);
if (this->new_sa->delete(this->new_sa, FALSE) == DESTROY_ME)
{
this->new_sa->destroy(this->new_sa);
}
else
{
charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa);
}
charon->bus->set_sa(charon->bus, this->ike_sa);
this->new_sa = NULL;
}
/**
* Check in the redundant IKE_SA created by the peer and wait for its deletion.
*/
static void wait_for_redundant_delete(private_ike_rekey_t *this)
{
private_ike_rekey_t *other = this->collision;
job_t *job;
/* peer should delete the SA it created, add a timeout just in case */
job = (job_t*)delete_ike_sa_job_create(
other->new_sa->get_id(other->new_sa), TRUE);
lib->scheduler->schedule_job(lib->scheduler, job, HALF_OPEN_IKE_SA_TIMEOUT);
other->new_sa->set_state(other->new_sa, IKE_REKEYED);
charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa);
other->new_sa = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
}
/**
* Remove the passive rekey task that's waiting for IKE_FOLLOWUP_KE requests
* that will never come.
*/
static void remove_passive_rekey_task(private_ike_rekey_t *this)
{
enumerator_t *enumerator;
task_t *task;
enumerator = this->ike_sa->create_task_enumerator(this->ike_sa,
TASK_QUEUE_PASSIVE);
while (enumerator->enumerate(enumerator, &task))
{
if (task->get_type(task) == TASK_IKE_REKEY)
{
this->ike_sa->remove_task(this->ike_sa, enumerator);
task->destroy(task);
break;
}
}
enumerator->destroy(enumerator);
}
/**
* Handle any collision as necessary and report back if we lost the
* collision and should abort the active task.
*/
static bool collision_lost(private_ike_rekey_t *this, bool multi_ke)
{
private_ike_rekey_t *other = this->collision;
chunk_t this_nonce, other_nonce;
if (!this->collision)
{
return FALSE;
}
this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
other_nonce = other->ike_init->get_lower_nonce(other->ike_init);
/* the SA with the lowest nonce should be deleted (if already complete),
* check if we or the peer created that */
if (memcmp(this_nonce.ptr, other_nonce.ptr,
min(this_nonce.len, other_nonce.len)) < 0)
{
if (multi_ke)
{
DBG1(DBG_IKE, "IKE_SA rekey collision lost, abort incomplete "
"multi-KE rekeying");
}
else
{
DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant "
"IKE_SA %s[%d]", this->new_sa->get_name(this->new_sa),
this->new_sa->get_unique_id(this->new_sa));
delete_redundant(this);
}
/* establish the other SA if the passive task is done (i.e. was
* single-KE or our response was delayed and the winner continued),
* otherwise, we just let it continue independently */
conclude_collision(this, FALSE);
return TRUE;
}
/* the passive rekeying is complete only if it was single-KE. otherwise,
* the peer would either have stopped before sending IKE_FOLLOWUP_KE when it
* noticed it lost, or it responded with TEMPORARY_FAILURE to our
* CREATE_CHILD_SA request if it already started sending them.
* since the task is not completed immediately, we clean up the collision */
if (this->flags & IKE_REKEY_ADOPTED_PASSIVE)
{
if (multi_ke)
{
DBG1(DBG_IKE, "IKE_SA rekey collision won, continue with multi-KE "
"rekeying and wait for delete for redundant IKE_SA %s[%d]",
other->new_sa->get_name(other->new_sa),
other->new_sa->get_unique_id(other->new_sa));
}
else
{
DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete for "
"redundant IKE_SA %s[%d]",
other->new_sa->get_name(other->new_sa),
other->new_sa->get_unique_id(other->new_sa));
}
wait_for_redundant_delete(this);
other->public.task.destroy(&other->public.task);
}
else
{
/* the peer will not continue with its multi-KE rekeying, so we must
* remove the passive task that's waiting for IKE_FOLLOWUP_KEs */
if (multi_ke)
{
DBG1(DBG_IKE, "IKE_SA rekey collision won, continue with "
"multi-KE rekeying and remove passive %N task",
task_type_names, TASK_IKE_REKEY);
}
else
{
DBG1(DBG_IKE, "IKE_SA rekey collision won, remove passive %N task",
task_type_names, TASK_IKE_REKEY);
}
remove_passive_rekey_task(this);
}
this->collision = NULL;
return FALSE;
}
METHOD(task_t, process_i, status_t,
private_ike_rekey_t *this, message_t *message)
{
@@ -336,78 +662,67 @@ METHOD(task_t, process_i, status_t,
this->ike_sa->get_id(this->ike_sa), TRUE));
return SUCCESS;
}
if (message->get_notify(message, STATE_NOT_FOUND))
{
DBG1(DBG_IKE, "peer didn't like our %N notify data", notify_type_names,
ADDITIONAL_KEY_EXCHANGE);
if (!conclude_collision(this, TRUE))
{
schedule_delayed_rekey(this);
}
return SUCCESS;
}
charon->bus->set_sa(charon->bus, this->new_sa);
switch (this->ike_init->task.process(&this->ike_init->task, message))
{
case FAILED:
charon->bus->set_sa(charon->bus, this->ike_sa);
/* rekeying failed, fallback to old SA */
if (!conclude_undetected_collision(this))
if (!conclude_collision(this, TRUE))
{
schedule_delayed_rekey(this);
}
return SUCCESS;
case NEED_MORE:
/* bad KE method, try again */
this->ike_init->task.migrate(&this->ike_init->task, this->new_sa);
return NEED_MORE;
if (message->get_notify(message, INVALID_KE_PAYLOAD))
{ /* bad key exchange mechanism, try again */
this->ike_init->task.migrate(&this->ike_init->task,
this->new_sa);
charon->bus->set_sa(charon->bus, this->ike_sa);
return NEED_MORE;
}
/* multiple key exchanges, continue with IKE_FOLLOWUP_KE */
process_link(this, message);
charon->bus->set_sa(charon->bus, this->ike_sa);
if (this->flags & IKE_REKEY_LINK_INVALID)
{ /* we can't continue without notify, maybe the peer returns
* one later */
if (!conclude_collision(this, TRUE))
{
schedule_delayed_rekey(this);
}
return SUCCESS;
}
this->public.task.build = _build_i_multi_ke;
/* there will only be a collision if we process a CREATE_CHILD_SA
* response, if we already sent an IKE_FOLOWUP_KE, the passive task
* would just respond with TEMPORARY_FAILURE and get ignored */
return collision_lost(this, TRUE) ? SUCCESS : NEED_MORE;
default:
charon->bus->set_sa(charon->bus, this->ike_sa);
break;
}
if (this->collision)
/* there will not be a collision here if this task is for a multi-KE
* rekeying, as that would already have been handled above when processing
* the CREATE_CHILD_SA response */
if (collision_lost(this, FALSE))
{
private_ike_rekey_t *other = this->collision;
host_t *host;
chunk_t this_nonce, other_nonce;
this_nonce = this->ike_init->get_lower_nonce(this->ike_init);
other_nonce = other->ike_init->get_lower_nonce(other->ike_init);
/* the SA with the lowest nonce should be deleted, check if we or
* the peer created that */
if (memcmp(this_nonce.ptr, other_nonce.ptr,
min(this_nonce.len, other_nonce.len)) < 0)
{
DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant "
"IKE_SA %s[%d]", this->new_sa->get_name(this->new_sa),
this->new_sa->get_unique_id(this->new_sa));
/* apply host for a proper delete */
host = this->ike_sa->get_my_host(this->ike_sa);
this->new_sa->set_my_host(this->new_sa, host->clone(host));
host = this->ike_sa->get_other_host(this->ike_sa);
this->new_sa->set_other_host(this->new_sa, host->clone(host));
this->new_sa->set_state(this->new_sa, IKE_REKEYED);
if (this->new_sa->delete(this->new_sa, FALSE) == DESTROY_ME)
{
this->new_sa->destroy(this->new_sa);
}
else
{
charon->ike_sa_manager->checkin(charon->ike_sa_manager,
this->new_sa);
}
charon->bus->set_sa(charon->bus, this->ike_sa);
this->new_sa = NULL;
establish_new(other);
return SUCCESS;
}
/* peer should delete the SA it created, add a timeout just in case */
job_t *job = (job_t*)delete_ike_sa_job_create(
other->new_sa->get_id(other->new_sa), TRUE);
lib->scheduler->schedule_job(lib->scheduler, job,
HALF_OPEN_IKE_SA_TIMEOUT);
DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete for "
"redundant IKE_SA %s[%d]", other->new_sa->get_name(other->new_sa),
other->new_sa->get_unique_id(other->new_sa));
other->new_sa->set_state(other->new_sa, IKE_REKEYED);
charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa);
other->new_sa = NULL;
charon->bus->set_sa(charon->bus, this->ike_sa);
return SUCCESS;
}
establish_new(this);
/* rekeying successful, delete this IKE_SA using a subtask */
this->ike_delete = ike_delete_create(this->ike_sa, TRUE);
this->public.task.build = _build_i_delete;
@@ -437,7 +752,7 @@ METHOD(ike_rekey_t, collide, bool,
switch (other->get_type(other))
{
case TASK_IKE_DELETE:
conclude_undetected_collision(this);
conclude_collision(this, TRUE);
break;
case TASK_IKE_REKEY:
{
@@ -447,14 +762,22 @@ METHOD(ike_rekey_t, collide, bool,
{
DBG1(DBG_IKE, "colliding exchange did not result in an IKE_SA, "
"ignore");
if (this->collision == rekey)
{
this->collision = NULL;
}
break;
}
if (this->collision)
{
this->collision->public.task.destroy(&this->collision->public.task);
}
/* we keep track of the passive exchange in any case, if not
* complete yet, this method might be called again later */
this->collision = rekey;
return TRUE;
if (rekey->flags & IKE_REKEY_DONE)
{
this->flags |= IKE_REKEY_ADOPTED_PASSIVE;
return TRUE;
}
DBG1(DBG_IKE, "colliding passive exchange is not yet complete");
break;
}
default:
/* shouldn't happen */
@@ -481,10 +804,14 @@ static void cleanup(private_ike_rekey_t *this)
cur_sa = charon->bus->get_sa(charon->bus);
DESTROY_IF(this->new_sa);
charon->bus->set_sa(charon->bus, cur_sa);
if (this->collision)
/* only destroy if the passive task was adopted, otherwise it is still
* queued and might get destroyed by the task manager */
if (this->collision &&
this->flags & IKE_REKEY_ADOPTED_PASSIVE)
{
this->collision->public.task.destroy(&this->collision->public.task);
}
chunk_free(&this->link);
}
METHOD(task_t, migrate, void,
@@ -496,6 +823,7 @@ METHOD(task_t, migrate, void,
this->new_sa = NULL;
this->ike_init = NULL;
this->ike_delete = NULL;
this->flags = 0;
}
METHOD(task_t, destroy, void,
+72 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Tobias Brunner
* Copyright (C) 2016-2020 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
@@ -135,6 +135,73 @@ START_TEST(test_collision_ike_rekey)
}
END_TEST
/**
* One of the peers creates a new CHILD_SA using multiple key exchanges.
*/
START_TEST(test_multi_ke)
{
peer_cfg_t *peer_cfg;
child_cfg_t *child_cfg;
child_cfg_create_t child = {
.mode = MODE_TUNNEL,
};
ike_sa_t *a, *b;
exchange_test_helper->establish_sa(exchange_test_helper,
&a, &b, NULL);
assert_hook_not_called(child_updown);
child_cfg = child_cfg_create("child", &child);
child_cfg->add_proposal(child_cfg,
proposal_create_from_string(PROTO_ESP,
"aes256-sha256-modp3072-ke1_ecp256"));
/* as configs are selected based on TS only, use a different protocol */
child_cfg->add_traffic_selector(child_cfg, TRUE,
traffic_selector_create_dynamic(6, 0, 65535));
child_cfg->add_traffic_selector(child_cfg, FALSE,
traffic_selector_create_dynamic(6, 0, 65535));
call_ikesa(a, initiate, child_cfg, NULL);
assert_child_sa_count(a, 1);
peer_cfg = b->get_peer_cfg(b);
peer_cfg->add_child_cfg(peer_cfg, child_cfg->get_ref(child_cfg));
assert_hook();
/* CREATE_CHILD_SA { SA, Ni, KEi, TSi, TSr } --> */
assert_hook_not_called(child_updown);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_count(b, 1);
/* <-- CREATE_CHILD_SA { SA, Nr, KEr, TSi, TSr, N(ADD_KE) } */
assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_count(a, 1);
assert_hook();
/* IKE_FOLLOWUP_KE { KEi N(ADD_KE) } --> */
assert_hook_updown(child_updown, TRUE);
assert_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
assert_child_sa_count(b, 2);
assert_hook();
/* <-- IKE_FOLLOWUP_KE { KEr } */
assert_hook_updown(child_updown, TRUE);
assert_no_notify(IN, ADDITIONAL_KEY_EXCHANGE);
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
assert_child_sa_count(a, 2);
assert_hook();
/* make sure no message was sent after creating the CHILD_SA */
ck_assert(!exchange_test_helper->sender->dequeue(exchange_test_helper->sender));
assert_sa_idle(a);
assert_sa_idle(b);
call_ikesa(a, destroy);
call_ikesa(b, destroy);
}
END_TEST
Suite *child_create_suite_create()
{
Suite *s;
@@ -150,5 +217,9 @@ Suite *child_create_suite_create()
tcase_add_test(tc, test_collision_ike_rekey);
suite_add_tcase(s, tc);
tc = tcase_create("multiple key exchanges");
tcase_add_test(tc, test_multi_ke);
suite_add_tcase(s, tc);
return s;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -105,6 +105,129 @@ bool exchange_test_asserts_child_rekey(listener_t *listener, ike_sa_t *ike_sa,
return TRUE;
}
/**
* Track SAs via updown event.
*/
static void track_sa_updown(listener_track_sas_assert_t *this, char *event,
array_t *sas, uint32_t id, bool up)
{
uint32_t existing;
bool found = FALSE;
int i;
if (up)
{
for (i = 0; i < array_count(sas); i++)
{
array_get(sas, i, &existing);
assert_listener_msg(id != existing, this, "duplicate %s(up) event "
"for SA %u", event, id);
}
array_insert(sas, ARRAY_TAIL, &id);
}
else
{
for (i = 0; i < array_count(sas); i++)
{
array_get(sas, i, &existing);
if (id == existing)
{
array_remove(sas, i, NULL);
found = TRUE;
break;
}
}
assert_listener_msg(found, this, "%s(down) event for unknown SA %u",
event, id);
}
}
/**
* Track SAs via a rekey event.
*/
static void track_sa_rekey(listener_track_sas_assert_t *this, char *event,
array_t *sas, uint32_t old_id, uint32_t new_id)
{
uint32_t existing;
bool found = FALSE;
int i;
for (i = 0; i < array_count(sas); i++)
{
array_get(sas, i, &existing);
if (old_id == existing)
{
array_remove(sas, i, NULL);
found = TRUE;
break;
}
}
assert_listener_msg(found, this, "%s() event for unknown old SA %u", event,
old_id);
for (i = 0; i < array_count(sas); i++)
{
array_get(sas, i, &existing);
assert_listener_msg(new_id != existing, this, "%s() event for "
"already up new SA %u", event, new_id);
}
array_insert(sas, ARRAY_TAIL, &new_id);
}
/*
* Described in header
*/
bool exchange_test_asserts_track_ike_updown(listener_t *listener,
ike_sa_t *ike_sa, bool up)
{
listener_track_sas_assert_t *this = (listener_track_sas_assert_t*)listener;
track_sa_updown(this, "ike_updown", this->ike_sas,
ike_sa->get_unique_id(ike_sa), up);
return TRUE;
}
/*
* Described in header
*/
bool exchange_test_asserts_track_child_updown(listener_t *listener,
ike_sa_t *ike_sa,
child_sa_t *child_sa, bool up)
{
listener_track_sas_assert_t *this = (listener_track_sas_assert_t*)listener;
track_sa_updown(this, "child_updown", this->child_sas,
child_sa->get_unique_id(child_sa), up);
return TRUE;
}
/*
* Described in header
*/
bool exchange_test_asserts_track_ike_rekey(listener_t *listener, ike_sa_t *old,
ike_sa_t *new)
{
listener_track_sas_assert_t *this = (listener_track_sas_assert_t*)listener;
track_sa_rekey(this, "ike_rekey", this->ike_sas, old->get_unique_id(old),
new->get_unique_id(new));
return TRUE;
}
/*
* Described in header
*/
bool exchange_test_asserts_track_child_rekey(listener_t *listener,
ike_sa_t *ike_sa, child_sa_t *old,
child_sa_t *new)
{
listener_track_sas_assert_t *this = (listener_track_sas_assert_t*)listener;
track_sa_rekey(this, "child_rekey", this->child_sas, old->get_unique_id(old),
new->get_unique_id(new));
return TRUE;
}
/**
* Assert a given message rule
*/
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016-2017 Tobias Brunner
* Copyright (C) 2016-2022 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
@@ -27,6 +27,7 @@
#include <bus/listeners/listener.h>
typedef struct listener_hook_assert_t listener_hook_assert_t;
typedef struct listener_track_sas_assert_t listener_track_sas_assert_t;
typedef struct listener_message_assert_t listener_message_assert_t;
typedef struct listener_message_rule_t listener_message_rule_t;
typedef struct ipsec_sas_assert_t ipsec_sas_assert_t;
@@ -209,6 +210,99 @@ do { \
} \
} while(FALSE)
/**
* Track SAs by following events.
*/
struct listener_track_sas_assert_t {
/**
* Implemented interface
*/
listener_t listener;
/**
* Original source file
*/
const char *file;
/**
* Source line
*/
int line;
/**
* Tracked IKE_SAs.
*/
array_t *ike_sas;
/**
* Tracked CHILD_SAs.
*/
array_t *child_sas;
};
/**
* Implementation of listener_t::ike_updown.
*/
bool exchange_test_asserts_track_ike_updown(listener_t *this, ike_sa_t *ike_sa,
bool up);
/**
* Implementation of listener_t::child_updown.
*/
bool exchange_test_asserts_track_child_updown(listener_t *this, ike_sa_t *ike_sa,
child_sa_t *child_sa, bool up);
/**
* Implementation of listener_t::ike_rekey.
*/
bool exchange_test_asserts_track_ike_rekey(listener_t *this, ike_sa_t *old,
ike_sa_t *new);
/**
* Implementation of listener_t::child_rekey.
*/
bool exchange_test_asserts_track_child_rekey(listener_t *this, ike_sa_t *ike_sa,
child_sa_t *old, child_sa_t *new);
/**
* Start tracking SAs via their hooks.
*/
#define assert_track_sas_start() \
do { \
listener_track_sas_assert_t _track_sas_listener = { \
.listener = { \
.ike_updown = exchange_test_asserts_track_ike_updown, \
.ike_rekey = exchange_test_asserts_track_ike_rekey, \
.child_updown = exchange_test_asserts_track_child_updown, \
.child_rekey = exchange_test_asserts_track_child_rekey, \
}, \
.file = __FILE__, \
.line = __LINE__, \
.ike_sas = array_create(sizeof(uint32_t), 8), \
.child_sas = array_create(sizeof(uint32_t), 8), \
}; \
exchange_test_helper->add_listener(exchange_test_helper, &_track_sas_listener.listener)
/**
* Check if there are the right number of SAs still up.
*
* @param ike the expected number of IKE_SAs
* @param child the expected number of CHILD_SAs
*/
#define assert_track_sas(ike, child) \
charon->bus->remove_listener(charon->bus, &_track_sas_listener.listener); \
u_int _up_ike = array_count(_track_sas_listener.ike_sas); \
u_int _up_child = array_count(_track_sas_listener.child_sas); \
array_destroy(_track_sas_listener.ike_sas); \
array_destroy(_track_sas_listener.child_sas); \
assert_listener_msg(_up_ike == (ike), &_track_sas_listener, \
"%d IKE_SAs without matching down event", _up_ike); \
assert_listener_msg(_up_child == (child), &_track_sas_listener, \
"%d CHILD_SAs without matching down event", _up_child); \
} while(FALSE)
/**
* Rules regarding payloads/notifies to expect/not expect in a message
*/
@@ -98,21 +98,27 @@ static ike_cfg_t *create_ike_cfg(bool initiator, exchange_test_sa_conf_t *conf)
.remote = "127.0.0.1",
.remote_port = IKEV2_UDP_PORT,
};
enumerator_t *enumerator;
ike_cfg_t *ike_cfg;
char *proposal = NULL;
char *proposals = NULL, *proposal;
if (conf)
{
ike.childless = initiator ? conf->initiator.childless
: conf->responder.childless;
proposal = initiator ? conf->initiator.ike : conf->responder.ike;
proposals = initiator ? conf->initiator.ike : conf->responder.ike;
}
ike_cfg = ike_cfg_create(&ike);
if (proposal)
if (proposals)
{
ike_cfg->add_proposal(ike_cfg,
enumerator = enumerator_create_token(proposals, ",", "");
while (enumerator->enumerate(enumerator, &proposal))
{
ike_cfg->add_proposal(ike_cfg,
proposal_create_from_string(PROTO_IKE, proposal));
}
enumerator->destroy(enumerator);
}
else
{
@@ -124,21 +130,27 @@ static ike_cfg_t *create_ike_cfg(bool initiator, exchange_test_sa_conf_t *conf)
static child_cfg_t *create_child_cfg(bool initiator,
exchange_test_sa_conf_t *conf)
{
enumerator_t *enumerator;
child_cfg_t *child_cfg;
child_cfg_create_t child = {
.mode = MODE_TUNNEL,
};
char *proposal = NULL;
char *proposals = NULL, *proposal;
child_cfg = child_cfg_create(initiator ? "init" : "resp", &child);
if (conf)
{
proposal = initiator ? conf->initiator.esp : conf->responder.esp;
proposals = initiator ? conf->initiator.esp : conf->responder.esp;
}
if (proposal)
if (proposals)
{
child_cfg->add_proposal(child_cfg,
enumerator = enumerator_create_token(proposals, ",", "");
while (enumerator->enumerate(enumerator, &proposal))
{
child_cfg->add_proposal(child_cfg,
proposal_create_from_string(PROTO_ESP, proposal));
}
enumerator->destroy(enumerator);
}
else
{
@@ -265,6 +277,7 @@ METHOD(exchange_test_helper_t, establish_sa, void,
ike_sa_id_t *id_i, *id_r;
ike_sa_t *sa_i, *sa_r;
child_cfg_t *child_i;
proposal_t *proposal;
child_i = create_sa(this, init, resp, conf);
@@ -282,6 +295,17 @@ METHOD(exchange_test_helper_t, establish_sa, void,
/* <-- IKE_SA_INIT */
id_i->set_responder_spi(id_i, id_r->get_responder_spi(id_r));
process_message(this, sa_i, NULL);
proposal = sa_i->get_proposal(sa_i);
if (proposal->get_algorithm(proposal, ADDITIONAL_KEY_EXCHANGE_1, NULL,
NULL))
{
/* IKE_INTERMEDIATE --> */
process_message(this, sa_r, NULL);
/* <-- IKE_INTERMEDIATE */
process_message(this, sa_i, NULL);
}
/* IKE_AUTH --> */
process_message(this, sa_r, NULL);
/* <-- IKE_AUTH */
+117 -2
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010-2019 Tobias Brunner
* Copyright (C) 2010-2020 Tobias Brunner
* Copyright (C) 2005-2010 Martin Willi
* Copyright (C) 2005 Jan Hutter
*
@@ -18,6 +18,9 @@
#include "key_exchange.h"
#include <collections/hashtable.h>
#include <threading/mutex.h>
ENUM_BEGIN(key_exchange_method_names, KE_NONE, MODP_1024_BIT,
"KE_NONE",
"MODP_768",
@@ -475,10 +478,68 @@ static struct {
},
};
/**
* Proposal tokens for additional key exchanges.
*/
static hashtable_t *tokens;
/**
* Mutex to safely access cached tokens.
*/
static mutex_t *mutex;
/**
* Destroy an allocated proposal token.
*/
static void token_destroy(proposal_token_t *this)
{
free(this->name);
free(this);
}
/**
* Parse ke<1-7>_<method> for additional key exchange methods.
*/
static proposal_token_t *additional_key_exchange_parser(const char *algname)
{
proposal_token_t *token;
const proposal_token_t *base;
u_int num;
char prefix[3], alg[256];
if (!algname || sscanf(algname, "%2s%1u_%255s", &prefix, &num, alg) != 3 ||
!strcaseeq(prefix, "ke"))
{
return NULL;
}
mutex->lock(mutex);
token = tokens->get(tokens, algname);
if (token || num < 1 || num > 7)
{
goto done;
}
base = lib->proposal->get_token(lib->proposal, alg);
if (!base || base->type != KEY_EXCHANGE_METHOD)
{
goto done;
}
INIT(token,
.name = strdup(algname),
.type = ADDITIONAL_KEY_EXCHANGE_1 + num - 1,
.algorithm = base->algorithm,
.keysize = base->keysize,
);
tokens->put(tokens, token->name, token);
done:
mutex->unlock(mutex);
return token;
}
/*
* Described in header
*/
void diffie_hellman_init()
void key_exchange_init()
{
int i;
@@ -498,6 +559,20 @@ void diffie_hellman_init()
dh_params[i].public.exp_len = dh_params[i].public.prime.len;
}
}
mutex = mutex_create(MUTEX_TYPE_RECURSIVE);
tokens = hashtable_create(hashtable_hash_str, hashtable_equals_str, 4);
lib->proposal->register_algname_parser(lib->proposal,
additional_key_exchange_parser);
}
/*
* Described in header
*/
void key_exchange_deinit()
{
tokens->destroy_function(tokens, (void*)token_destroy);
mutex->destroy(mutex);
}
/*
@@ -619,3 +694,43 @@ bool key_exchange_verify_pubkey(key_exchange_method_t ke, chunk_t value)
}
return valid;
}
/*
* Described in header
*/
bool key_exchange_concat_secrets(array_t *kes, chunk_t *first,
chunk_t *others)
{
key_exchange_t *ke;
chunk_t secret;
int i;
if (!array_count(kes))
{
return FALSE;
}
*first = chunk_empty;
*others = chunk_empty;
for (i = 0; i < array_count(kes); i++)
{
if (array_get(kes, i, &ke) &&
ke->get_shared_secret(ke, &secret))
{
if (i == 0)
{
*first = secret;
}
else
{
*others = chunk_cat("ss", *others, secret);
}
}
else
{
chunk_clear(first);
chunk_clear(others);
return FALSE;
}
}
return TRUE;
}
+21 -3
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2010-2019 Tobias Brunner
* Copyright (C) 2010-2020 Tobias Brunner
* Copyright (C) 2005-2007 Martin Willi
* Copyright (C) 2005 Jan Hutter
*
@@ -29,6 +29,7 @@ typedef struct key_exchange_t key_exchange_t;
typedef struct diffie_hellman_params_t diffie_hellman_params_t;
#include <library.h>
#include <collections/array.h>
/**
* Key exchange method.
@@ -177,9 +178,14 @@ struct diffie_hellman_params_t {
};
/**
* Initialize diffie hellman parameters during startup.
* Initialize DH parameters and KE token parser during startup.
*/
void diffie_hellman_init();
void key_exchange_init();
/**
* Deinitialize KE token parser during shutdown.
*/
void key_exchange_deinit();
/**
* Get the parameters associated with the specified Diffie-Hellman group.
@@ -209,4 +215,16 @@ bool key_exchange_is_ecdh(key_exchange_method_t ke);
*/
bool key_exchange_verify_pubkey(key_exchange_method_t ke, chunk_t value);
/**
* Return the first shared secret plus the concatenated additional shared
* secrets of all the key exchange methods in the given array.
*
* @param kes array of key_exchange_t*
* @param secret first shared secret (allocated)
* @param add_secret concatenated additional shared secrets (allocated)
* @return TRUE on success
*/
bool key_exchange_concat_secrets(array_t *kes, chunk_t *secret,
chunk_t *add_secret);
#endif /** KEY_EXCHANGE_H_ @}*/
+73 -11
View File
@@ -21,6 +21,7 @@
#include "proposal.h"
#include <collections/array.h>
#include <collections/hashtable.h>
#include <utils/identification.h>
#include <crypto/transform.h>
@@ -316,15 +317,16 @@ METHOD(proposal_t, promote_transform, bool,
*/
static bool select_algo(private_proposal_t *this, proposal_t *other,
transform_type_t type, proposal_selection_flag_t flags,
bool log, uint16_t *alg, uint16_t *ks)
hashtable_t *kes, bool log, uint16_t *alg, uint16_t *ks)
{
enumerator_t *e1, *e2;
uint16_t alg1, alg2, ks1, ks2;
bool found = FALSE, optional = FALSE;
if (type == KEY_EXCHANGE_METHOD)
if (is_ke_transform(type))
{
optional = this->protocol == PROTO_ESP || this->protocol == PROTO_AH;
optional = this->protocol == PROTO_ESP || this->protocol == PROTO_AH ||
type != KEY_EXCHANGE_METHOD;
}
e1 = create_enumerator(this, type);
@@ -358,9 +360,13 @@ static bool select_algo(private_proposal_t *this, proposal_t *other,
e1->destroy(e1);
e1 = create_enumerator(this, type);
/* compare algs, order of algs in "first" is preferred */
/* compare algs, order of algs in "e1" is preferred */
while (!found && e1->enumerate(e1, &alg1, &ks1))
{
if (is_ke_transform(type) && kes->get(kes, (void*)(uintptr_t)alg1))
{
continue;
}
e2->destroy(e2);
e2 = other->create_enumerator(other, type);
while (e2->enumerate(e2, &alg2, &ks2))
@@ -389,6 +395,23 @@ static bool select_algo(private_proposal_t *this, proposal_t *other,
return found;
}
/**
* Hash an algorithm identifier
*/
static u_int hash_alg(const void *key)
{
uint16_t alg = (uint16_t)(uintptr_t)key;
return chunk_hash(chunk_from_thing(alg));
}
/**
* Compare two algorithm identifiers
*/
static bool equals_alg(const void *key, const void *other_key)
{
return (uint16_t)(uintptr_t)key == (uint16_t)(uintptr_t)other_key;
}
/**
* Select algorithms from the given proposals, if selected is given, the result
* is stored there and errors are logged.
@@ -397,10 +420,13 @@ static bool select_algos(private_proposal_t *this, proposal_t *other,
proposal_t *selected, proposal_selection_flag_t flags)
{
transform_type_t type;
hashtable_t *kes;
array_t *types;
bool skip_integrity = FALSE;
int i;
kes = hashtable_create(hash_alg, equals_alg, 8);
types = merge_types(this, (private_proposal_t*)other);
for (i = 0; i < array_count(types); i++)
{
@@ -411,11 +437,12 @@ static bool select_algos(private_proposal_t *this, proposal_t *other,
{
continue;
}
if (type == KEY_EXCHANGE_METHOD && (flags & PROPOSAL_SKIP_KE))
if (is_ke_transform(type) && (flags & PROPOSAL_SKIP_KE))
{
continue;
}
if (select_algo(this, other, type, flags, selected != NULL, &alg, &ks))
if (select_algo(this, other, type, flags, kes, selected != NULL,
&alg, &ks))
{
if (alg == 0 && type != EXTENDED_SEQUENCE_NUMBERS)
{ /* 0 is "valid" for extended sequence numbers, for other
@@ -426,6 +453,10 @@ static bool select_algos(private_proposal_t *this, proposal_t *other,
{
selected->add_algorithm(selected, type, alg, ks);
}
if (is_ke_transform(type))
{
kes->put(kes, (void*)(uintptr_t)alg, (void*)(uintptr_t)alg);
}
if (type == ENCRYPTION_ALGORITHM &&
encryption_algorithm_is_aead(alg))
{
@@ -441,10 +472,12 @@ static bool select_algos(private_proposal_t *this, proposal_t *other,
type);
}
array_destroy(types);
kes->destroy(kes);
return FALSE;
}
}
array_destroy(types);
kes->destroy(kes);
return TRUE;
}
@@ -604,7 +637,7 @@ METHOD(proposal_t, clone_, proposal_t*,
{
continue;
}
if (entry->type == KEY_EXCHANGE_METHOD && (flags & PROPOSAL_SKIP_KE))
if (is_ke_transform(entry->type) && (flags & PROPOSAL_SKIP_KE))
{
continue;
}
@@ -849,7 +882,7 @@ static int print_alg(private_proposal_t *this, printf_hook_data_t *data,
enumerator = array_create_enumerator(this->transforms);
while (enumerator->enumerate(enumerator, &entry))
{
char *prefix = "/";
char *prefix = "/", ake_prefix[5] = "";
if (type != entry->type)
{
@@ -860,14 +893,19 @@ static int print_alg(private_proposal_t *this, printf_hook_data_t *data,
prefix = "";
*first = FALSE;
}
if (is_ke_transform(type) && type != KEY_EXCHANGE_METHOD)
{
sprintf(ake_prefix, "KE%d_", type - ADDITIONAL_KEY_EXCHANGE_1 + 1);
}
if (names)
{
written += print_in_hook(data, "%s%N", prefix, names, entry->alg);
written += print_in_hook(data, "%s%s%N", prefix, ake_prefix,
names, entry->alg);
}
else
{
written += print_in_hook(data, "%sUNKNOWN_%u_%u", prefix,
entry->type, entry->alg);
written += print_in_hook(data, "%s%sUNKNOWN_%u_%u", prefix,
ake_prefix, entry->type, entry->alg);
}
if (entry->key_size)
{
@@ -1420,3 +1458,27 @@ proposal_t *proposal_select(linked_list_t *configured, linked_list_t *supplied,
}
return selected;
}
/*
* Described in header
*/
bool proposal_has_additional_ke(proposal_t *public)
{
private_proposal_t *this = (private_proposal_t*)public;
enumerator_t *enumerator;
entry_t *entry;
bool found = FALSE;
enumerator = array_create_enumerator(this->transforms);
while (enumerator->enumerate(enumerator, &entry))
{
if (entry->type != KEY_EXCHANGE_METHOD &&
is_ke_transform(entry->type))
{
found = TRUE;
break;
}
}
enumerator->destroy(enumerator);
return found;
}
@@ -287,6 +287,14 @@ proposal_t *proposal_create_from_string(protocol_id_t protocol,
proposal_t *proposal_select(linked_list_t *configured, linked_list_t *supplied,
proposal_selection_flag_t flags);
/**
* Check whether this proposal contains algorithms for any additional key
* exchange method transform types.
*
* @return TRUE if found
*/
bool proposal_has_additional_ke(proposal_t *this);
/**
* printf hook function for proposal_t.
*
+17 -3
View File
@@ -19,14 +19,21 @@
#include <crypto/rngs/rng.h>
#include <crypto/kdfs/kdf.h>
ENUM_BEGIN(transform_type_names, ENCRYPTION_ALGORITHM, EXTENDED_SEQUENCE_NUMBERS,
ENUM_BEGIN(transform_type_names, ENCRYPTION_ALGORITHM, ADDITIONAL_KEY_EXCHANGE_7,
"ENCRYPTION_ALGORITHM",
"PSEUDO_RANDOM_FUNCTION",
"INTEGRITY_ALGORITHM",
"KEY_EXCHANGE_METHOD",
"EXTENDED_SEQUENCE_NUMBERS");
"EXTENDED_SEQUENCE_NUMBERS",
"ADDITIONAL_KEY_EXCHANGE_1",
"ADDITIONAL_KEY_EXCHANGE_2",
"ADDITIONAL_KEY_EXCHANGE_3",
"ADDITIONAL_KEY_EXCHANGE_4",
"ADDITIONAL_KEY_EXCHANGE_5",
"ADDITIONAL_KEY_EXCHANGE_6",
"ADDITIONAL_KEY_EXCHANGE_7");
ENUM_NEXT(transform_type_names, HASH_ALGORITHM, KEY_DERIVATION_FUNCTION,
EXTENDED_SEQUENCE_NUMBERS,
ADDITIONAL_KEY_EXCHANGE_7,
"HASH_ALGORITHM",
"RANDOM_NUMBER_GENERATOR",
"AEAD_ALGORITHM",
@@ -60,6 +67,13 @@ enum_name_t* transform_get_enum_names(transform_type_t type)
case INTEGRITY_ALGORITHM:
return integrity_algorithm_names;
case KEY_EXCHANGE_METHOD:
case ADDITIONAL_KEY_EXCHANGE_1:
case ADDITIONAL_KEY_EXCHANGE_2:
case ADDITIONAL_KEY_EXCHANGE_3:
case ADDITIONAL_KEY_EXCHANGE_4:
case ADDITIONAL_KEY_EXCHANGE_5:
case ADDITIONAL_KEY_EXCHANGE_6:
case ADDITIONAL_KEY_EXCHANGE_7:
return key_exchange_method_names;
case EXTENDED_SEQUENCE_NUMBERS:
return extended_sequence_numbers_names;
+20
View File
@@ -1,4 +1,5 @@
/*
* Copyright (C) 2012-2019 Tobias Brunner
* Copyright (C) 2006-2009 Martin Willi
*
* Copyright (C) secunet Security Networks AG
@@ -35,6 +36,13 @@ enum transform_type_t {
INTEGRITY_ALGORITHM = 3,
KEY_EXCHANGE_METHOD = 4,
EXTENDED_SEQUENCE_NUMBERS = 5,
ADDITIONAL_KEY_EXCHANGE_1 = 6,
ADDITIONAL_KEY_EXCHANGE_2 = 7,
ADDITIONAL_KEY_EXCHANGE_3 = 8,
ADDITIONAL_KEY_EXCHANGE_4 = 9,
ADDITIONAL_KEY_EXCHANGE_5 = 10,
ADDITIONAL_KEY_EXCHANGE_6 = 11,
ADDITIONAL_KEY_EXCHANGE_7 = 12,
HASH_ALGORITHM = 256,
RANDOM_NUMBER_GENERATOR = 257,
AEAD_ALGORITHM = 258,
@@ -57,6 +65,18 @@ extern enum_name_t *transform_type_names;
*/
enum_name_t *transform_get_enum_names(transform_type_t type);
/**
* Check if the given transform type is used to negotiate a key exchange.
*
* @param type type of transform to check
* @return TRUE if the transform type negotiates a key exchange
*/
static inline bool is_ke_transform(transform_type_t type)
{
return type == KEY_EXCHANGE_METHOD || (ADDITIONAL_KEY_EXCHANGE_1 <= type &&
type <= ADDITIONAL_KEY_EXCHANGE_7);
}
/**
* Extended sequence numbers, as in IKEv2 RFC 3.3.2.
*/
+3 -1
View File
@@ -161,6 +161,8 @@ void library_deinit()
/* make sure the cache is clear before unloading plugins */
lib->credmgr->flush_cache(lib->credmgr, CERT_ANY);
key_exchange_deinit();
this->public.streams->destroy(this->public.streams);
this->public.watcher->destroy(this->public.watcher);
this->public.scheduler->destroy(this->public.scheduler);
@@ -436,7 +438,7 @@ bool library_init(char *settings, const char *namespace)
#endif /* INTEGRITY_TEST */
}
diffie_hellman_init();
key_exchange_init();
return !this->init_failed;
}
@@ -143,6 +143,28 @@ static struct {
{ PROTO_IKE, "aes128-sha256-modp3072", "aes128-sha256-modp3072", "aes128-sha256-modp3072" },
{ PROTO_IKE, "aes128-sha256-modp3072", "aes128-sha256-modp3072-none", "aes128-sha256-modp3072" },
{ PROTO_IKE, "aes128-sha256-modp3072-none", "aes128-sha256-modp3072", "aes128-sha256-modp3072" },
{ PROTO_IKE, "aes128-sha256-modp3072-ke1_modp3072",
"aes128-sha256-modp3072-ke1_modp3072", NULL },
{ PROTO_IKE, "aes128-sha256-modp3072-ecp256-ecp384-ke1_modp3072-ke1_ecp256-ke1_ecp384-ke2_modp3072-ke2_ecp256-ke2_ecp384",
"aes128-sha256-modp3072-ecp256-ecp384-ke1_modp3072-ke1_ecp256-ke1_ecp384-ke2_modp3072-ke2_ecp256-ke2_ecp384",
"aes128-sha256-modp3072-ke1_ecp256-ke2_ecp384" },
{ PROTO_IKE, "aes128-sha256-modp3072-ke1_modp3072-ke1_none",
"aes128-sha256-modp3072-ke1_modp3072-ke1_none",
"aes128-sha256-modp3072" },
{ PROTO_IKE, "aes128-sha256-modp3072-ke1_modp3072-ke1_none-ke2_modp3072-ke2_none",
"aes128-sha256-modp3072-ke1_modp3072-ke1_none-ke2_modp3072-ke2_none",
"aes128-sha256-modp3072" },
{ PROTO_IKE, "aes128-sha256-modp3072-ke1_modp3072-ke1_ecp256",
"aes128-sha256-modp3072-ke1_modp3072-ke1_ecp256",
"aes128-sha256-modp3072-ke1_ecp256" },
{ PROTO_IKE, "aes128-sha256-modp3072-ke1_modp3072-ke1_ecp256",
"aes128-sha256-modp3072-ke1_ecp256",
"aes128-sha256-modp3072-ke1_ecp256" },
{ PROTO_IKE, "aes128-sha256-modp3072-ke1_ecp256",
"aes128-sha256-modp3072-ke1_modp3072-ke1_ecp256",
"aes128-sha256-modp3072-ke1_ecp256" },
{ PROTO_IKE, "aes128-sha256-ecp256-ke1_modp3072",
"aes128-sha256-modp3072-ecp256-ke1_ecp256-ke2_ecp384", NULL },
};
START_TEST(test_select)
@@ -474,6 +496,20 @@ START_TEST(test_unknown_transform_types_select_success)
}
END_TEST
START_TEST(test_proposal_has_additional_ke)
{
proposal_t *proposal;
proposal = proposal_create_from_string(PROTO_IKE, "aes128-sha256-ecp256");
ck_assert(!proposal_has_additional_ke(proposal));
proposal->destroy(proposal);
proposal = proposal_create_from_string(PROTO_IKE, "aes128-sha256-modp3072-ke1_ecp256");
ck_assert(proposal_has_additional_ke(proposal));
proposal->destroy(proposal);
}
END_TEST
START_TEST(test_chacha20_poly1305_key_length)
{
proposal_t *proposal;
@@ -575,6 +611,10 @@ Suite *proposal_suite_create()
tcase_add_test(tc, test_unknown_transform_types_select_success);
suite_add_tcase(s, tc);
tc = tcase_create("proposal_has_additional_ke");
tcase_add_test(tc, test_proposal_has_additional_ke);
suite_add_tcase(s, tc);
tc = tcase_create("chacha20/poly1305");
tcase_add_test(tc, test_chacha20_poly1305_key_length);
suite_add_tcase(s, tc);
+22 -1
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2016-2019 Andreas Steffen
* Copyright (C) 2015-2020 Tobias Brunner
* Copyright (C) 2014 Martin Willi
* Copyright (C) 2016 Andreas Steffen
*
* Copyright (C) secunet Security Networks AG
*
@@ -100,6 +101,24 @@ CALLBACK(sa_list, int,
return 0;
}
/**
* Print additional key exchanges
*/
static void print_ake(hashtable_t *sa)
{
char ake_str[5];
int ake;
for (ake = 1; ake <= 7; ake++)
{
sprintf(ake_str, "ake%d", ake);
if (sa->get(sa, ake_str))
{
printf("/KE%d_%s", ake, sa->get(sa, ake_str));
}
}
}
CALLBACK(child_sas, int,
hashtable_t *ike, vici_res_t *res, char *name)
{
@@ -145,6 +164,7 @@ CALLBACK(child_sas, int,
{
printf("/%s", child->get(child, "dh-group"));
}
print_ake(child);
if (child->get(child, "esn"))
{
printf("/ESN");
@@ -290,6 +310,7 @@ CALLBACK(ike_sa, int,
}
printf("/%s", ike->get(ike, "prf-alg"));
printf("/%s", ike->get(ike, "dh-group"));
print_ake(ike);
if (streq(ike->get(ike, "ppk"), "yes"))
{
printf("/PPK");
+12
View File
@@ -0,0 +1,12 @@
The roadwarriors <b>carol</b> and <b>dave</b> set up a connection each
to gateway <b>moon</b>. The authentication is based on <b>X.509 certificates</b>.
To test multiple key exchanges (RFC 9370) and IKE_INTERMEDIATE exchanges (RFC 9242),
<b>carol</b> proposes MODP_2048 for the key exchange and CURVE_25519 for the
additional key exchange whereas <b>dave</b> proposes MODP_3072 and ECP_384,
respectively. The IKE and ESP SAs are then rekeyed using the same proposals.
One set of SAs is rekeyed from <b>carol</b> and another from <b>moon</b>.
<p/>
Upon the successful establishment of the IPsec tunnels, the updown script
automatically inserts iptables-based firewall rules that let pass the tunneled traffic.
In order to test both tunnel and firewall, both <b>carol</b> and <b>dave</b> ping
the client <b>alice</b> behind the gateway <b>moon</b>.
+32
View File
@@ -0,0 +1,32 @@
carol::swanctl --list-sas --raw 2> /dev/null::home.*version=2 state=ESTABLISHED local-host=192.168.0.100 local-port=4500 [email protected] remote-host=192.168.0.1 remote-port=4500 remote-id=moon.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=MODP_2048 ake1=CURVE_25519.*child-sas.*home.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[192.168.0.100/32] remote-ts=\[10.1.0.0/16]::YES
dave:: swanctl --list-sas --raw 2> /dev/null::home.*version=2 state=ESTABLISHED local-host=192.168.0.200 local-port=4500 [email protected] remote-host=192.168.0.1 remote-port=4500 remote-id=moon.strongswan.org initiator=yes.*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=MODP_3072 ake1=ECP_384.*child-sas.*home.*state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[192.168.0.200/32] remote-ts=\[10.1.0.0/16]::YES
moon:: swanctl --list-sas --ike-id 1 --raw 2> /dev/null::rw.*version=2 state=ESTABLISHED local-host=192.168.0.1 local-port=4500 local-id=moon.strongswan.org remote-host=192.168.0.100 remote-port=4500 [email protected].*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=MODP_2048 ake1=CURVE_25519.*child-sas.*net.*reqid=1 state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.1.0.0/16] remote-ts=\[192.168.0.100/32]::YES
moon:: swanctl --list-sas --ike-id 2 --raw 2> /dev/null::rw.*version=2 state=ESTABLISHED local-host=192.168.0.1 local-port=4500 local-id=moon.strongswan.org remote-host=192.168.0.200 remote-port=4500 [email protected].*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=MODP_3072 ake1=ECP_384.*child-sas.*net.*reqid=2 state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128.*local-ts=\[10.1.0.0/16] remote-ts=\[192.168.0.200/32]::YES
alice::ping -c 1 192.168.0.100::64 bytes from 192.168.0.100: icmp_.eq=1::YES
alice::ping -c 1 192.168.0.200::64 bytes from 192.168.0.200: icmp_.eq=1::YES
# rekey with carol from moon (original responder), first the IKE_SA then the CHILD_SA using the new IKE_SA
moon::swanctl --rekey --ike-id 1
moon::sleep 1
alice::ping -c 1 192.168.0.100::64 bytes from 192.168.0.100: icmp_.eq=1::YES
moon::swanctl --rekey --child-id 1
moon::sleep 1
alice::ping -c 1 192.168.0.100::64 bytes from 192.168.0.100: icmp_.eq=1::YES
# rekey from dave (original initiator)
dave::swanctl --rekey --ike home
dave::sleep 1
alice::ping -c 1 192.168.0.200::64 bytes from 192.168.0.200: icmp_.eq=1::YES
dave::swanctl --rekey --child home
dave::sleep 1
alice::ping -c 1 192.168.0.200::64 bytes from 192.168.0.200: icmp_.eq=1::YES
moon::tcpdump::IP moon.strongswan.org > carol.strongswan.org: ESP::YES
moon::tcpdump::IP carol.strongswan.org > moon.strongswan.org: ESP::YES
moon::tcpdump::IP moon.strongswan.org > dave.strongswan.org: ESP::YES
moon::tcpdump::IP dave.strongswan.org > moon.strongswan.org: ESP::YES
moon:: swanctl --list-sas --ike-id 3 --raw 2> /dev/null::rw.*version=2 state=ESTABLISHED local-host=192.168.0.1 local-port=4500 local-id=moon.strongswan.org remote-host=192.168.0.100 remote-port=4500 [email protected].*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=MODP_2048 ake1=CURVE_25519.*child-sas.*net.*reqid=1 state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128 dh-group=MODP_2048 ake1=CURVE_25519.*local-ts=\[10.1.0.0/16] remote-ts=\[192.168.0.100/32]::YES
moon:: swanctl --list-sas --ike-id 4 --raw 2> /dev/null::rw.*version=2 state=ESTABLISHED local-host=192.168.0.1 local-port=4500 local-id=moon.strongswan.org remote-host=192.168.0.200 remote-port=4500 [email protected].*encr-alg=AES_CBC encr-keysize=128 integ-alg=HMAC_SHA2_256_128 prf-alg=PRF_HMAC_SHA2_256 dh-group=MODP_3072 ake1=ECP_384.*child-sas.*net.*reqid=2 state=INSTALLED mode=TUNNEL.*ESP.*encr-alg=AES_GCM_16 encr-keysize=128 dh-group=MODP_3072 ake1=ECP_384.*local-ts=\[10.1.0.0/16] remote-ts=\[192.168.0.200/32]::YES
# from the rekeyings with carol
moon::cat /var/log/daemon.log::parsed IKE_INTERMEDIATE request 1 \[ KE \]::2
moon::cat /var/log/daemon.log::parsed IKE_FOLLOWUP_KE response 1 \[ KE \]::2
# from the rekeyings with dave
moon::cat /var/log/daemon.log::parsed IKE_FOLLOWUP_KE request 4 \[ KE N(ADD_KE) \]::1
moon::cat /var/log/daemon.log::parsed IKE_FOLLOWUP_KE request 1 \[ KE N(ADD_KE) \]::1
@@ -0,0 +1,9 @@
# /etc/strongswan.conf - strongSwan configuration file
swanctl {
load = pem pkcs1 x509 revocation constraints pubkey openssl random
}
charon-systemd {
load = random nonce openssl pem pkcs1 x509 revocation constraints curl kernel-netlink socket-default updown vici
}
@@ -0,0 +1,35 @@
connections {
home {
local_addrs = 192.168.0.100
remote_addrs = 192.168.0.1
local {
auth = pubkey
certs = carolCert.pem
id = [email protected]
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
home {
remote_ts = 10.1.0.0/16
updown = /usr/local/libexec/ipsec/_updown iptables
esp_proposals = aes128gcm128-modp2048-ke1_x25519
}
}
version = 2
proposals = aes128-sha256-modp2048-ke1_x25519
}
}
secrets {
rsa-carol {
file = carolKey.pem
secret = "nH5ZQEWtku0RJEZ6"
}
}
@@ -0,0 +1,9 @@
# /etc/strongswan.conf - strongSwan configuration file
swanctl {
load = pem pkcs1 x509 revocation constraints pubkey openssl random
}
charon-systemd {
load = random nonce openssl pem pkcs1 x509 revocation constraints curl kernel-netlink socket-default updown vici
}
@@ -0,0 +1,27 @@
connections {
home {
local_addrs = 192.168.0.200
remote_addrs = 192.168.0.1
local {
auth = pubkey
certs = daveCert.pem
id = [email protected]
}
remote {
auth = pubkey
id = moon.strongswan.org
}
children {
home {
remote_ts = 10.1.0.0/16
updown = /usr/local/libexec/ipsec/_updown iptables
esp_proposals = aes128gcm128-modp3072-ke1_ecp384
}
}
version = 2
proposals = aes128-sha256-modp3072-ke1_ecp384
}
}
@@ -0,0 +1,9 @@
# /etc/strongswan.conf - strongSwan configuration file
swanctl {
load = pem pkcs1 x509 revocation constraints pubkey openssl random
}
charon-systemd {
load = random nonce openssl pem pkcs1 x509 revocation constraints curl kernel-netlink socket-default updown vici
}
@@ -0,0 +1,25 @@
connections {
rw {
local_addrs = 192.168.0.1
local {
auth = pubkey
certs = moonCert.pem
id = moon.strongswan.org
}
remote {
auth = pubkey
}
children {
net {
local_ts = 10.1.0.0/16
updown = /usr/local/libexec/ipsec/_updown iptables
esp_proposals = aes128gcm128-modp2048-modp3072-ke1_x25519-ke1_ecp384
}
}
version = 2
proposals = aes128-sha256-modp2048-modp3072-ke1_x25519-ke1_ecp384
}
}
+8
View File
@@ -0,0 +1,8 @@
carol::swanctl --terminate --ike home
dave::swanctl --terminate --ike home
carol::systemctl stop strongswan
dave::systemctl stop strongswan
moon::systemctl stop strongswan
moon::iptables-restore < /etc/iptables.flush
carol::iptables-restore < /etc/iptables.flush
dave::iptables-restore < /etc/iptables.flush
+11
View File
@@ -0,0 +1,11 @@
moon::iptables-restore < /etc/iptables.rules
carol::iptables-restore < /etc/iptables.rules
dave::iptables-restore < /etc/iptables.rules
moon::systemctl start strongswan
carol::systemctl start strongswan
dave::systemctl start strongswan
moon::expect-connection rw
carol::expect-connection home
carol::swanctl --initiate --child home 2> /dev/null
dave::expect-connection home
dave::swanctl --initiate --child home 2> /dev/null
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
#
# This configuration file provides information on the
# guest instances used for this test
# All guest instances that are required for this test
#
VIRTHOSTS="alice moon carol winnetou dave"
# Corresponding block diagram
#
DIAGRAM="a-m-c-w-d.png"
# Guest instances on which tcpdump is to be started
#
TCPDUMPHOSTS="moon"
# Guest instances on which IPsec is started
# Used for IPsec logging purposes
#
IPSECHOSTS="moon carol dave"
# charon controlled by swanctl
#
SWANCTL=1