Files
strongswan-ext/src/libcharon/sa/ikev2/tasks/child_create.c
T
Tobias Brunner 00830ddc36 ikev2: Make sure to correctly compare nonces of unequal length
The previous code would not correctly compare nonces of unequal length
where the shorter is the prefix of the longer one.
2026-07-24 08:47:39 +02:00

3120 lines
78 KiB
C

/*
* Copyright (C) 2008-2025 Tobias Brunner
* Copyright (C) 2005-2008 Martin Willi
* Copyright (C) 2005 Jan Hutter
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <unistd.h>
#include "child_create.h"
#include <daemon.h>
#include <sa/ikev2/keymat_v2.h>
#include <crypto/key_exchange.h>
#include <credentials/certificates/x509.h>
#include <collections/hashtable.h>
#include <encoding/payloads/sa_payload.h>
#include <encoding/payloads/ke_payload.h>
#include <encoding/payloads/ts_payload.h>
#include <encoding/payloads/nonce_payload.h>
#include <encoding/payloads/notify_payload.h>
#include <encoding/payloads/delete_payload.h>
#include <processing/jobs/delete_ike_sa_job.h>
#include <processing/jobs/inactivity_job.h>
#include <processing/jobs/initiate_tasks_job.h>
/** Maximum number of key exchanges (including the initial one, if any) */
#define MAX_KEY_EXCHANGES (MAX_ADDITIONAL_KEY_EXCHANGES + 1)
typedef struct private_child_create_t private_child_create_t;
/** Assumed minimum CPU ID, used when searching for a CPU without SA */
#define CPU_ID_MIN 0
/**
* Flags for IP-TFS
*/
typedef enum {
/** Indicates that fragmentation across packets is not supported */
IPTFS_DONT_FRAGMENT = (1 << 0),
/** Requests periodic congestion control information from the peer */
IPTFS_CONGESTION_CONTROL = (1 << 1),
} iptfs_flags_t;
/**
* Private members of a child_create_t task.
*/
struct private_child_create_t {
/**
* Public methods and task_t interface.
*/
child_create_t public;
/**
* Assigned IKE_SA.
*/
ike_sa_t *ike_sa;
/**
* Are we the initiator?
*/
bool initiator;
/**
* nonce chosen by us
*/
chunk_t my_nonce;
/**
* nonce chosen by peer
*/
chunk_t other_nonce;
/**
* nonce generator
*/
nonce_gen_t *nonceg;
/**
* config to create the CHILD_SA from
*/
child_cfg_t *config;
/**
* list of proposal candidates
*/
linked_list_t *proposals;
/**
* selected proposal to use for CHILD_SA
*/
proposal_t *proposal;
/**
* traffic selectors for initiator side
*/
linked_list_t *tsi;
/**
* traffic selectors for responder side
*/
linked_list_t *tsr;
/**
* labels for initiator side
*/
linked_list_t *labels_i;
/**
* labels for responder side
*/
linked_list_t *labels_r;
/**
* source of triggering packet
*/
traffic_selector_t *packet_tsi;
/**
* destination of triggering packet
*/
traffic_selector_t *packet_tsr;
/**
* Local traffic selectors as configured or previously negotiated
*/
traffic_selector_list_t *my_ts;
/**
* Remote traffic selectors as configured or previously negotiated
*/
traffic_selector_list_t *other_ts;
/**
* Resource info sent by the peer
*/
chunk_t resource_info;
/**
* Whether we received resource info (might be empty)
*/
bool resource_info_seen;
/**
* Key exchanges to perform
*/
struct {
transform_type_t type;
key_exchange_method_t method;
bool done;
} key_exchanges[MAX_KEY_EXCHANGES];
/**
* Current key exchange
*/
int ke_index;
/**
* Kex exchange method from the parsed or sent KE payload
*/
key_exchange_method_t ke_method;
/**
* Current key exchange object (if any)
*/
key_exchange_t *ke;
/**
* All key exchanges performed (key_exchange_t)
*/
array_t *kes;
/**
* Applying KE public key failed?
*/
bool ke_failed;
/**
* Link value for current key exchange
*/
chunk_t link;
/**
* IKE_SAs keymat
*/
keymat_v2_t *keymat;
/**
* mode the new CHILD_SA uses (transport/tunnel/beet)
*/
ipsec_mode_t mode;
/**
* peer accepts TFC padding for this SA
*/
bool tfcv3;
/**
* IPComp transform to use
*/
ipcomp_transform_t ipcomp;
/**
* IPComp transform proposed or accepted by the other peer
*/
ipcomp_transform_t ipcomp_received;
/**
* IP-TFS flags received from the peer
*/
uint8_t iptfs_flags;
/**
* IPsec protocol
*/
protocol_id_t proto;
/**
* Own allocated SPI
*/
uint32_t my_spi;
/**
* SPI received in proposal
*/
uint32_t other_spi;
/**
* Own allocated Compression Parameter Index (CPI)
*/
uint16_t my_cpi;
/**
* Other Compression Parameter Index (CPI), received via IPCOMP_SUPPORTED
*/
uint16_t other_cpi;
/**
* Data collected to create the CHILD_SA
*/
child_sa_create_t child;
/**
* CHILD_SA which gets established
*/
child_sa_t *child_sa;
/**
* successfully established the CHILD?
*/
bool established;
/**
* whether the CHILD_SA rekeys an existing one
*/
bool rekey;
/**
* whether we are retrying with another KE method
*/
bool retry;
/**
* Whether the task was aborted
*/
bool aborted;
};
/**
* Schedule a retry if creating the CHILD_SA temporary failed
*/
static void schedule_delayed_retry(private_child_create_t *this)
{
child_create_t *task;
uint32_t retry;
retry = RETRY_INTERVAL - (random() % RETRY_JITTER);
task = child_create_create(this->ike_sa,
this->config->get_ref(this->config), FALSE,
this->packet_tsi, this->packet_tsr,
this->child.seq);
task->use_reqid(task, this->child.reqid);
task->use_marks(task, this->child.mark_in, this->child.mark_out);
task->use_if_ids(task, this->child.if_id_in, this->child.if_id_out);
task->use_label(task, this->child.label);
task->use_per_cpu(task, this->child.per_cpu, this->child.cpu);
/* clone these directly as we don't have a public method */
if (this->my_ts && this->other_ts)
{
private_child_create_t *priv = (private_child_create_t*)task;
priv->my_ts = this->my_ts->clone(this->my_ts);
priv->other_ts = this->other_ts->clone(this->other_ts);
}
DBG1(DBG_IKE, "creating CHILD_SA failed, trying again in %d seconds",
retry);
this->ike_sa->queue_task_delayed(this->ike_sa, (task_t*)task, retry);
}
/**
* get the nonce from a message
*/
static status_t get_nonce(message_t *message, chunk_t *nonce)
{
nonce_payload_t *payload;
payload = (nonce_payload_t*)message->get_payload(message, PLV2_NONCE);
if (payload == NULL)
{
return FAILED;
}
*nonce = payload->get_nonce(payload);
return NEED_MORE;
}
/**
* generate a new nonce to include in a CREATE_CHILD_SA message
*/
static bool generate_nonce(private_child_create_t *this)
{
this->nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat);
if (!this->nonceg)
{
DBG1(DBG_IKE, "no nonce generator found to create nonce");
return FALSE;
}
if (!this->nonceg->allocate_nonce(this->nonceg, NONCE_SIZE,
&this->my_nonce))
{
DBG1(DBG_IKE, "nonce allocation failed");
return FALSE;
}
return TRUE;
}
/**
* Check a list of traffic selectors if any selector belongs to host
*/
static bool ts_list_is_host(linked_list_t *list, host_t *host)
{
traffic_selector_t *ts;
bool is_host = TRUE;
enumerator_t *enumerator = list->create_enumerator(list);
while (is_host && enumerator->enumerate(enumerator, (void**)&ts))
{
is_host = is_host && ts->is_host(ts, host);
}
enumerator->destroy(enumerator);
return is_host;
}
/**
* Allocate local SPI
*/
static bool allocate_spi(private_child_create_t *this)
{
proposal_t *proposal;
if (this->initiator)
{
this->proto = PROTO_ESP;
/* we just get a SPI for the first protocol. TODO: If we ever support
* proposal lists with mixed protocols, we'd need multiple SPIs */
if (this->proposals->get_first(this->proposals,
(void**)&proposal) == SUCCESS)
{
this->proto = proposal->get_protocol(proposal);
}
}
else
{
this->proto = this->proposal->get_protocol(this->proposal);
}
this->my_spi = this->child_sa->alloc_spi(this->child_sa, this->proto);
if (!this->my_spi)
{
DBG1(DBG_IKE, "unable to allocate SPI from kernel");
}
return this->my_spi != 0;
}
/**
* Update the proposals with the allocated SPIs as initiator and check the KE
* method and promote it if necessary
*/
static bool update_and_check_proposals(private_child_create_t *this)
{
enumerator_t *enumerator;
proposal_t *proposal;
linked_list_t *other_ke_methods;
bool found = FALSE;
other_ke_methods = linked_list_create();
enumerator = this->proposals->create_enumerator(this->proposals);
while (enumerator->enumerate(enumerator, &proposal))
{
proposal->set_spi(proposal, this->my_spi);
/* move the selected KE method to the front, if any */
if (this->ke_method != KE_NONE)
{ /* proposals that don't contain the selected group are
* moved to the back */
if (!proposal->promote_transform(proposal, KEY_EXCHANGE_METHOD,
this->ke_method))
{
this->proposals->remove_at(this->proposals, enumerator);
other_ke_methods->insert_last(other_ke_methods, proposal);
}
else
{
found = TRUE;
}
}
}
enumerator->destroy(enumerator);
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*/
this->proposals->insert_last(this->proposals, proposal);
}
enumerator->destroy(enumerator);
other_ke_methods->destroy(other_ke_methods);
return this->ke_method == KE_NONE || found;
}
/**
* Schedule inactivity timeout for CHILD_SA with reqid, if enabled
*/
static void schedule_inactivity_timeout(private_child_create_t *this)
{
uint32_t timeout, id;
bool close_ike;
timeout = this->config->get_inactivity(this->config);
if (timeout)
{
close_ike = lib->settings->get_bool(lib->settings,
"%s.inactivity_close_ike", FALSE, lib->ns);
id = this->child_sa->get_unique_id(this->child_sa);
lib->scheduler->schedule_job(lib->scheduler, (job_t*)
inactivity_job_create(id, timeout, close_ike), timeout);
}
}
/**
* Substitute any host address with NATed address in traffic selector
*/
static linked_list_t* get_transport_nat_ts(private_child_create_t *this,
bool local, linked_list_t *in)
{
enumerator_t *enumerator;
linked_list_t *out;
traffic_selector_t *ts;
host_t *ike, *first = NULL;
uint8_t mask;
if (local)
{
ike = this->ike_sa->get_my_host(this->ike_sa);
}
else
{
ike = this->ike_sa->get_other_host(this->ike_sa);
}
out = linked_list_create();
enumerator = in->create_enumerator(in);
while (enumerator->enumerate(enumerator, &ts))
{
/* require that all selectors match the first "host" selector */
if (ts->is_host(ts, first))
{
if (!first)
{
ts->to_subnet(ts, &first, &mask);
}
ts = ts->clone(ts);
ts->set_address(ts, ike);
out->insert_last(out, ts);
}
}
enumerator->destroy(enumerator);
DESTROY_IF(first);
return out;
}
/**
* Ensure we have traffic selector lists when not recreating an SA.
*/
static void ensure_ts_lists(private_child_create_t *this)
{
linked_list_t *ts;
if (!this->my_ts)
{
ts = this->config->get_traffic_selectors(this->config, TRUE, NULL);
this->my_ts = traffic_selector_list_create_from_list(ts);
}
if (!this->other_ts)
{
ts = this->config->get_traffic_selectors(this->config, FALSE, NULL);
this->other_ts = traffic_selector_list_create_from_list(ts);
}
}
/**
* Narrow received traffic selectors with configuration
*/
static linked_list_t* narrow_ts(private_child_create_t *this, bool local,
linked_list_t *in)
{
traffic_selector_list_t *ts;
linked_list_t *hosts, *nat, *result;
ike_condition_t cond;
ensure_ts_lists(this);
ts = local ? this->my_ts : this->other_ts;
cond = local ? COND_NAT_HERE : COND_NAT_THERE;
hosts = ike_sa_get_dynamic_hosts(this->ike_sa, local);
if (this->mode == MODE_TRANSPORT &&
this->ike_sa->has_condition(this->ike_sa, cond))
{
nat = get_transport_nat_ts(this, local, in);
result = child_cfg_select_ts(this->config, local, ts, nat, hosts);
nat->destroy_offset(nat, offsetof(traffic_selector_t, destroy));
}
else
{
result = child_cfg_select_ts(this->config, local, ts, in, hosts);
}
hosts->destroy(hosts);
return result;
}
/**
* Check if requested mode is acceptable
*/
static bool check_mode(private_child_create_t *this, host_t *i, host_t *r)
{
switch (this->mode)
{
case MODE_TRANSPORT:
if (!this->config->has_option(this->config, OPT_PROXY_MODE) &&
(!ts_list_is_host(this->tsi, i) ||
!ts_list_is_host(this->tsr, r))
)
{
DBG1(DBG_IKE, "not using transport mode, not host-to-host");
return FALSE;
}
if (this->config->get_mode(this->config) != MODE_TRANSPORT)
{
return FALSE;
}
break;
case MODE_BEET:
if (!ts_list_is_host(this->tsi, NULL) ||
!ts_list_is_host(this->tsr, NULL))
{
DBG1(DBG_IKE, "not using BEET mode, not host-to-host");
return FALSE;
}
if (this->config->get_mode(this->config) != MODE_BEET)
{
return FALSE;
}
break;
case MODE_IPTFS:
if (this->config->get_mode(this->config) != MODE_IPTFS)
{
return FALSE;
}
if ((this->iptfs_flags & IPTFS_CONGESTION_CONTROL) ||
(this->iptfs_flags & 0xfc))
{
/* congestion control is not supported, neither are any of
* the 6 reserved flags, so we disable IP-TFS if they are set */
DBG1(DBG_IKE, "not using IP-TFS mode, peer sent unsupported "
"flags (0x%.2x) in %N notify", this->iptfs_flags,
notify_type_names, USE_AGGFRAG);
return FALSE;
}
break;
default:
break;
}
return TRUE;
}
/**
* Do traffic selector narrowing and check mode:
* - FAILED: mode mismatch
* - NOT_FOUND: TS unacceptable
*/
static status_t narrow_and_check_ts(private_child_create_t *this, bool ike_auth)
{
linked_list_t *my_ts, *other_ts;
host_t *me, *other;
me = this->ike_sa->get_my_host(this->ike_sa);
other = this->ike_sa->get_other_host(this->ike_sa);
this->child_sa->set_proposal(this->child_sa, this->proposal);
if (this->initiator)
{
my_ts = narrow_ts(this, TRUE, this->tsi);
other_ts = narrow_ts(this, FALSE, this->tsr);
}
else
{
my_ts = narrow_ts(this, TRUE, this->tsr);
other_ts = narrow_ts(this, FALSE, this->tsi);
}
if (this->initiator)
{
if (ike_auth)
{
charon->bus->narrow(charon->bus, this->child_sa,
NARROW_INITIATOR_POST_NOAUTH, my_ts, other_ts);
}
else
{
charon->bus->narrow(charon->bus, this->child_sa,
NARROW_INITIATOR_POST_AUTH, my_ts, other_ts);
}
}
else
{
charon->bus->narrow(charon->bus, this->child_sa,
NARROW_RESPONDER, my_ts, other_ts);
}
if (my_ts->get_count(my_ts) == 0 || other_ts->get_count(other_ts) == 0)
{
charon->bus->alert(charon->bus, ALERT_TS_MISMATCH, this->tsi, this->tsr);
my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy));
other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy));
DBG1(DBG_IKE, "no acceptable traffic selectors found");
return NOT_FOUND;
}
this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy));
this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy));
if (this->initiator)
{
this->tsi = my_ts;
this->tsr = other_ts;
if (!check_mode(this, me, other))
{
DBG1(DBG_IKE, "%N mode requested by responder is unacceptable",
ipsec_mode_names, this->mode);
return FAILED;
}
}
else
{
this->tsr = my_ts;
this->tsi = other_ts;
if (!check_mode(this, other, me))
{
this->mode = MODE_TUNNEL;
}
}
return SUCCESS;
}
/**
* Install a CHILD_SA:
* - FAILED: failure to install SAs
* - NOT_FOUND: TS unacceptable (only responder), or failure to install policies
*/
static status_t install_child_sa(private_child_create_t *this)
{
status_t status, status_i, status_o;
chunk_t nonce_i, nonce_r;
chunk_t encr_i = chunk_empty, encr_r = chunk_empty;
chunk_t integ_i = chunk_empty, integ_r = chunk_empty;
linked_list_t *my_ts, *other_ts;
if (this->initiator)
{
nonce_i = this->my_nonce;
nonce_r = this->other_nonce;
my_ts = this->tsi;
other_ts = this->tsr;
}
else
{
nonce_i = this->other_nonce;
nonce_r = this->my_nonce;
/* use a copy of the traffic selectors, as the POST hook should not
* change payloads */
my_ts = this->tsr->clone_offset(this->tsr,
offsetof(traffic_selector_t, clone));
other_ts = this->tsi->clone_offset(this->tsi,
offsetof(traffic_selector_t, clone));
charon->bus->narrow(charon->bus, this->child_sa,
NARROW_RESPONDER_POST, my_ts, other_ts);
if (my_ts->get_count(my_ts) == 0 || other_ts->get_count(other_ts) == 0)
{
my_ts->destroy_offset(my_ts,
offsetof(traffic_selector_t, destroy));
other_ts->destroy_offset(other_ts,
offsetof(traffic_selector_t, destroy));
return NOT_FOUND;
}
}
this->child_sa->set_ipcomp(this->child_sa, this->ipcomp);
this->child_sa->set_mode(this->child_sa, this->mode);
if (this->mode == MODE_IPTFS && this->iptfs_flags & IPTFS_DONT_FRAGMENT)
{
this->child_sa->set_iptfs_dont_fragment(this->child_sa);
}
this->child_sa->set_protocol(this->child_sa,
this->proposal->get_protocol(this->proposal));
/* addresses might have changed since we originally sent the request, update
* them before we configure any policies and install the SAs */
this->child_sa->update(this->child_sa,
this->ike_sa->get_my_host(this->ike_sa),
this->ike_sa->get_other_host(this->ike_sa), NULL,
this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY));
this->child_sa->set_policies(this->child_sa, my_ts, other_ts);
if (!this->initiator)
{
my_ts->destroy_offset(my_ts,
offsetof(traffic_selector_t, destroy));
other_ts->destroy_offset(other_ts,
offsetof(traffic_selector_t, destroy));
}
this->child_sa->set_state(this->child_sa, CHILD_INSTALLING);
if (this->my_cpi == 0 || this->other_cpi == 0 || this->ipcomp == IPCOMP_NONE)
{
this->my_cpi = this->other_cpi = 0;
this->ipcomp = IPCOMP_NONE;
}
status_i = status_o = FAILED;
if (this->keymat->derive_child_keys(this->keymat, this->proposal,
this->kes, nonce_i, nonce_r, &encr_i, &integ_i, &encr_r, &integ_r))
{
if (this->initiator)
{
status_i = this->child_sa->install(this->child_sa, encr_r, integ_r,
this->my_spi, this->my_cpi, this->initiator,
TRUE, this->tfcv3);
}
else
{
status_i = this->child_sa->install(this->child_sa, encr_i, integ_i,
this->my_spi, this->my_cpi, this->initiator,
TRUE, this->tfcv3);
}
if (this->rekey)
{
/* during rekeyings we install the outbound SA and/or policies
* separately: as responder, when we receive the delete for the old
* SA, as initiator, pretty much immediately in the ike-rekey task,
* unless there was a rekey collision that we lost */
if (this->initiator)
{
status_o = this->child_sa->register_outbound(this->child_sa,
encr_i, integ_i, this->other_spi, this->other_cpi,
this->initiator, this->tfcv3);
}
else
{
status_o = this->child_sa->register_outbound(this->child_sa,
encr_r, integ_r, this->other_spi, this->other_cpi,
this->initiator, this->tfcv3);
}
}
else if (this->initiator)
{
status_o = this->child_sa->install(this->child_sa, encr_i, integ_i,
this->other_spi, this->other_cpi, this->initiator,
FALSE, this->tfcv3);
}
else
{
status_o = this->child_sa->install(this->child_sa, encr_r, integ_r,
this->other_spi, this->other_cpi, this->initiator,
FALSE, this->tfcv3);
}
}
if (status_i != SUCCESS || status_o != SUCCESS)
{
DBG1(DBG_IKE, "unable to install %s%s%sIPsec SA (SAD) in kernel",
(status_i != SUCCESS) ? "inbound " : "",
(status_i != SUCCESS && status_o != SUCCESS) ? "and ": "",
(status_o != SUCCESS) ? "outbound " : "");
charon->bus->alert(charon->bus, ALERT_INSTALL_CHILD_SA_FAILED,
this->child_sa);
status = FAILED;
}
else
{
status = this->child_sa->install_policies(this->child_sa);
if (status != SUCCESS)
{
DBG1(DBG_IKE, "unable to install IPsec policies (SPD) in kernel");
charon->bus->alert(charon->bus, ALERT_INSTALL_CHILD_POLICY_FAILED,
this->child_sa);
status = NOT_FOUND;
}
else
{
charon->bus->child_derived_keys(charon->bus, this->child_sa,
this->initiator, encr_i, encr_r,
integ_i, integ_r);
charon->bus->child_keys(charon->bus, this->child_sa,
this->initiator, this->kes, nonce_i, nonce_r);
}
}
chunk_clear(&integ_i);
chunk_clear(&integ_r);
chunk_clear(&encr_i);
chunk_clear(&encr_r);
if (status != SUCCESS)
{
return status;
}
#if DEBUG_LEVEL >= 0
child_sa_outbound_state_t out_state;
out_state = this->child_sa->get_outbound_state(this->child_sa);
my_ts = linked_list_create_from_enumerator(
this->child_sa->create_ts_enumerator(this->child_sa, TRUE));
other_ts = linked_list_create_from_enumerator(
this->child_sa->create_ts_enumerator(this->child_sa, FALSE));
DBG0(DBG_IKE, "%sCHILD_SA %s{%u} established "
"with SPIs %.8x_i %.8x_o and TS %#R === %#R",
(out_state == CHILD_OUTBOUND_INSTALLED) ? "" : "inbound ",
this->child_sa->get_name(this->child_sa),
this->child_sa->get_unique_id(this->child_sa),
ntohl(this->child_sa->get_spi(this->child_sa, TRUE)),
ntohl(this->child_sa->get_spi(this->child_sa, FALSE)),
my_ts, other_ts);
my_ts->destroy(my_ts);
other_ts->destroy(other_ts);
#endif
this->child_sa->set_state(this->child_sa, CHILD_INSTALLED);
this->ike_sa->add_child_sa(this->ike_sa, this->child_sa);
this->established = TRUE;
schedule_inactivity_timeout(this);
return SUCCESS;
}
/**
* Select a proposal
*/
static bool select_proposal(private_child_create_t *this, bool no_ke)
{
proposal_selection_flag_t flags = 0;
if (!this->proposals)
{
DBG1(DBG_IKE, "SA payload missing in message");
return FALSE;
}
if (no_ke)
{
flags |= PROPOSAL_SKIP_KE;
}
if (!this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN) &&
!lib->settings->get_bool(lib->settings, "%s.accept_private_algs",
FALSE, lib->ns))
{
flags |= PROPOSAL_SKIP_PRIVATE;
}
if (!lib->settings->get_bool(lib->settings,
"%s.prefer_configured_proposals", TRUE, lib->ns))
{
flags |= PROPOSAL_PREFER_SUPPLIED;
}
this->proposal = this->config->select_proposal(this->config,
this->proposals, flags);
if (!this->proposal)
{
DBG1(DBG_IKE, "no acceptable proposal found");
charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_CHILD,
this->proposals);
return FALSE;
}
return TRUE;
}
/**
* Returns the number of CPUs that are configured on the system or only those
* that are currently online (if we can distinguish this).
*/
static uint32_t get_system_cpus(bool online)
{
long cpus;
#ifdef WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
cpus = sysinfo.dwNumberOfProcessors;
#else /* WIN32 */
cpus = sysconf(online ? _SC_NPROCESSORS_ONLN : _SC_NPROCESSORS_CONF);
if (cpus < 0)
{
DBG1(DBG_IKE, "failed to determine number of CPUs for this system");
/* assume we have at least one CPU */
cpus = 1;
}
#endif /* WIN32 */
return cpus;
}
/**
* Finds an unused CPU ID to be assigned to a responder's CHILD_SA, or return
* CPU_ID_MAX if we don't have a fallback yet.
*/
static uint32_t get_cpu(private_child_create_t *this)
{
enumerator_t *enumerator;
hashtable_t *used;
child_sa_t *child_sa;
bool found_fallback = FALSE;
uint32_t cpu;
used = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 32);
enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
while (enumerator->enumerate(enumerator, (void**)&child_sa))
{
if (child_sa->get_state(child_sa) == CHILD_INSTALLED &&
this->config->equals(this->config, child_sa->get_config(child_sa)))
{
cpu = child_sa->get_cpu(child_sa);
if (cpu != CPU_ID_MAX)
{
used->put(used, (void*)(uintptr_t)cpu, child_sa);
}
else
{
found_fallback = TRUE;
}
}
}
enumerator->destroy(enumerator);
if (!found_fallback)
{
cpu = CPU_ID_MAX;
}
else
{
cpu = get_system_cpus(TRUE) + CPU_ID_MIN - 1;
for (; cpu > CPU_ID_MIN; cpu--)
{
if (!used->get(used, (void*)(uintptr_t)cpu))
{
break;
}
}
}
used->destroy(used);
return cpu;
}
/**
* Add a KE payload if a key exchange is used. As responder we might already
* have stored the object in the list of completed exchanges.
*/
static bool add_ke_payload(private_child_create_t *this,
message_t *message)
{
key_exchange_t *ke;
ke_payload_t *pld;
if (this->ke)
{
ke = this->ke;
}
else if (!array_get(this->kes, ARRAY_TAIL, &ke))
{
return TRUE;
}
pld = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE, ke);
if (!pld)
{
DBG1(DBG_IKE, "creating KE payload failed");
return FALSE;
}
message->add_payload(message, (payload_t*)pld);
return TRUE;
}
/**
* Build payloads in additional exchanges when using multiple key exchanges
*/
static bool build_payloads_multi_ke(private_child_create_t *this,
message_t *message)
{
if (!add_ke_payload(this, message))
{
return FALSE;
}
if (this->link.ptr)
{
message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE, this->link);
}
return TRUE;
}
/**
* build the payloads for the message
*/
static bool build_payloads(private_child_create_t *this, message_t *message)
{
sa_payload_t *sa_payload;
nonce_payload_t *nonce_payload;
ts_payload_t *ts_payload;
kernel_feature_t features;
if (message->get_exchange_type(message) == IKE_FOLLOWUP_KE)
{
return build_payloads_multi_ke(this, message);
}
if (this->initiator)
{
sa_payload = sa_payload_create_from_proposals_v2(this->proposals);
}
else
{
sa_payload = sa_payload_create_from_proposal_v2(this->proposal);
}
message->add_payload(message, (payload_t*)sa_payload);
/* add nonce payload if not in IKE_AUTH */
if (message->get_exchange_type(message) == CREATE_CHILD_SA)
{
nonce_payload = nonce_payload_create(PLV2_NONCE);
nonce_payload->set_nonce(nonce_payload, this->my_nonce);
message->add_payload(message, (payload_t*)nonce_payload);
}
if (this->link.ptr)
{
message->add_notify(message, FALSE, ADDITIONAL_KEY_EXCHANGE, this->link);
}
if (!add_ke_payload(this, message))
{
return FALSE;
}
/* add TSi/TSr payloads */
ts_payload = ts_payload_create_from_traffic_selectors(TRUE, this->tsi,
this->child.label);
message->add_payload(message, (payload_t*)ts_payload);
ts_payload = ts_payload_create_from_traffic_selectors(FALSE, this->tsr,
this->child.label);
message->add_payload(message, (payload_t*)ts_payload);
/* add a notify if we are not in tunnel mode */
switch (this->mode)
{
case MODE_TRANSPORT:
message->add_notify(message, FALSE, USE_TRANSPORT_MODE, chunk_empty);
break;
case MODE_BEET:
message->add_notify(message, FALSE, USE_BEET_MODE, chunk_empty);
break;
case MODE_IPTFS:
{
uint8_t iptfs_flags = 0;
if (!lib->settings->get_bool(lib->settings,
"%s.iptfs.accept_fragments", TRUE, lib->ns))
{
iptfs_flags |= IPTFS_DONT_FRAGMENT;
}
message->add_notify(message, FALSE, USE_AGGFRAG,
chunk_from_thing(iptfs_flags));
break;
}
default:
break;
}
features = charon->kernel->get_features(charon->kernel);
if (!(features & KERNEL_ESP_V3_TFC))
{
message->add_notify(message, FALSE, ESP_TFC_PADDING_NOT_SUPPORTED,
chunk_empty);
}
if (!this->rekey && this->child.per_cpu)
{
chunk_t cpu_id = chunk_empty;
uint32_t cpu;
/* always send the notify, but possibly without CPU ID */
if (this->child.cpu != CPU_ID_MAX)
{
cpu = htonl(this->child.cpu);
cpu_id = chunk_from_thing(cpu);
}
message->add_notify(message, FALSE, SA_RESOURCE_INFO,
cpu_id);
}
return TRUE;
}
/**
* Adds an IPCOMP_SUPPORTED notify to the message, allocating a CPI
*/
static void add_ipcomp_notify(private_child_create_t *this,
message_t *message, uint8_t ipcomp)
{
this->my_cpi = this->child_sa->alloc_cpi(this->child_sa);
if (this->my_cpi)
{
this->ipcomp = ipcomp;
message->add_notify(message, FALSE, IPCOMP_SUPPORTED,
chunk_cata("cc", chunk_from_thing(this->my_cpi),
chunk_from_thing(ipcomp)));
}
else
{
DBG1(DBG_IKE, "unable to allocate a CPI from kernel, IPComp disabled");
}
}
/**
* handle a received notify payload
*/
static void handle_notify(private_child_create_t *this, notify_payload_t *notify)
{
chunk_t data;
switch (notify->get_notify_type(notify))
{
case USE_TRANSPORT_MODE:
this->mode = MODE_TRANSPORT;
break;
case USE_BEET_MODE:
if (this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN))
{ /* handle private use notify only if we know its meaning */
this->mode = MODE_BEET;
}
else
{
DBG1(DBG_IKE, "received a notify strongSwan uses for BEET "
"mode, but peer implementation unknown, skipped");
}
break;
case USE_AGGFRAG:
this->mode = MODE_IPTFS;
data = notify->get_notification_data(notify);
this->iptfs_flags = *data.ptr;
break;
case IPCOMP_SUPPORTED:
{
ipcomp_transform_t ipcomp;
uint16_t cpi;
data = notify->get_notification_data(notify);
cpi = *(uint16_t*)data.ptr;
ipcomp = (ipcomp_transform_t)(*(data.ptr + 2));
switch (ipcomp)
{
case IPCOMP_DEFLATE:
this->other_cpi = cpi;
this->ipcomp_received = ipcomp;
break;
case IPCOMP_LZS:
case IPCOMP_LZJH:
default:
DBG1(DBG_IKE, "received IPCOMP_SUPPORTED notify with a "
"transform ID we don't support %N",
ipcomp_transform_names, ipcomp);
break;
}
break;
}
case ESP_TFC_PADDING_NOT_SUPPORTED:
DBG1(DBG_IKE, "received %N, not using ESPv3 TFC padding",
notify_type_names, notify->get_notify_type(notify));
this->tfcv3 = FALSE;
break;
case SA_RESOURCE_INFO:
chunk_free(&this->resource_info);
this->resource_info = chunk_clone(notify->get_notification_data(notify));
this->resource_info_seen = TRUE;
break;
default:
break;
}
}
/**
* Collect all key exchanges from the proposal
*/
static void determine_key_exchanges(private_child_create_t *this)
{
transform_type_t t = KEY_EXCHANGE_METHOD;
uint16_t alg;
int i = 1;
if (!this->proposal->get_algorithm(this->proposal, t, &alg, NULL))
{ /* no PFS */
return;
}
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_child_create_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_child_create_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_child_create_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);
/* the proposal is selected after processing the KE payload, so this is
* only relevant for additional key exchanges */
if (method && 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;
}
this->ke_method = received;
if (!this->initiator)
{
DESTROY_IF(this->ke);
this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
received);
if (!this->ke)
{
DBG1(DBG_IKE, "key exchange method %N not supported",
key_exchange_method_names, received);
}
}
else if (this->ke)
{
if (this->ke->get_method(this->ke) != received)
{
DBG1(DBG_IKE, "key exchange method %N in received payload doesn't "
"match %N", key_exchange_method_names, received,
key_exchange_method_names, this->ke->get_method(this->ke));
this->ke_failed = TRUE;
}
}
if (this->ke && !this->ke_failed)
{
if (!this->ke->set_public_key(this->ke, ke->get_key_exchange_data(ke)))
{
DBG1(DBG_IKE, "applying key exchange public key failed");
this->ke_failed = TRUE;
}
}
}
/**
* Check if the proposed KE method in CREATE_CHILD_SA (received via KE payload)
* is valid according to the selected proposal.
*/
static bool check_ke_method(private_child_create_t *this, uint16_t *req)
{
uint16_t alg;
if (!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD,
this->ke_method))
{
if (this->proposal->get_algorithm(this->proposal, KEY_EXCHANGE_METHOD,
&alg, NULL))
{
if (req)
{
*req = alg;
}
return FALSE;
}
/* the selected proposal does not use a key exchange method */
DBG1(DBG_IKE, "ignoring KE payload, agreed on a non-PFS proposal");
DESTROY_IF(this->ke);
this->ke = NULL;
this->ke_method = KE_NONE;
/* ignore errors that occurred while handling the KE payload */
this->ke_failed = FALSE;
}
return TRUE;
}
/**
* Check if the proposed key exchange method is valid as responder or whether
* we should request another KE payload.
*/
static bool check_ke_method_r(private_child_create_t *this, message_t *message)
{
uint16_t alg;
if (!check_ke_method(this, &alg))
{
DBG1(DBG_IKE, "key exchange method %N unacceptable, requesting %N",
key_exchange_method_names, this->ke_method,
key_exchange_method_names, alg);
alg = htons(alg);
message->add_notify(message, FALSE, INVALID_KE_PAYLOAD,
chunk_from_thing(alg));
return FALSE;
}
else if (this->ke_method != KE_NONE && !this->ke)
{
message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty);
return FALSE;
}
return TRUE;
}
/**
* Read payloads from message
*/
static void process_payloads(private_child_create_t *this, message_t *message)
{
enumerator_t *enumerator;
payload_t *payload;
sa_payload_t *sa_payload;
ts_payload_t *ts_payload;
/* defaults to TUNNEL mode */
this->mode = MODE_TUNNEL;
enumerator = message->create_payload_enumerator(message);
while (enumerator->enumerate(enumerator, &payload))
{
switch (payload->get_type(payload))
{
case PLV2_SECURITY_ASSOCIATION:
sa_payload = (sa_payload_t*)payload;
this->proposals = sa_payload->get_proposals(sa_payload);
break;
case PLV2_KEY_EXCHANGE:
process_ke_payload(this, (ke_payload_t*)payload);
break;
case PLV2_TS_INITIATOR:
ts_payload = (ts_payload_t*)payload;
this->tsi = ts_payload->get_traffic_selectors(ts_payload);
this->labels_i = ts_payload->get_sec_labels(ts_payload);
break;
case PLV2_TS_RESPONDER:
ts_payload = (ts_payload_t*)payload;
this->tsr = ts_payload->get_traffic_selectors(ts_payload);
this->labels_r = ts_payload->get_sec_labels(ts_payload);
break;
case PLV2_NOTIFY:
handle_notify(this, (notify_payload_t*)payload);
break;
default:
break;
}
}
enumerator->destroy(enumerator);
}
/**
* Check if we have only the generic label available when using SELinux and not
* a specific one from an acquire.
*/
static bool generic_label_only(private_child_create_t *this)
{
return this->config->get_label(this->config) && !this->child.label &&
this->config->get_label_mode(this->config) == SEC_LABEL_MODE_SELINUX;
}
/**
* Check if we should defer the creation of this CHILD_SA until after the
* IKE_SA has been established childless.
*/
static status_t defer_child_sa(private_child_create_t *this)
{
ike_cfg_t *ike_cfg;
childless_t policy;
ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
policy = ike_cfg->childless(ike_cfg);
if (this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_CHILDLESS))
{
/* with SELinux, we prefer not to create a CHILD_SA when we only have
* the generic label available. if the peer does not support it,
* creating the SA will most likely fail */
if (policy == CHILDLESS_PREFER ||
policy == CHILDLESS_FORCE ||
generic_label_only(this))
{
return NEED_MORE;
}
}
else if (policy == CHILDLESS_FORCE)
{
DBG1(DBG_IKE, "peer does not support childless IKE_SA initiation");
return DESTROY_ME;
}
return NOT_SUPPORTED;
}
/**
* Compare the reqids and possibly traffic selectors of two CHILD_SAs for
* equality.
*
* The second CHILD_SA is assumed to be the one newly created.
*/
static bool reqid_and_ts_equals(private_child_create_t *this, child_sa_t *a,
child_sa_t *b)
{
/* reqids are allocated based on the traffic selectors. if all the other
* selectors are the same, they can only differ if narrowing occurred.
*
* if the new SA has no reqid assigned, it was initiated manually or due to
* a start action and we assume the peer will do the same narrowing it
* possibly did before, so we treat the SAs as equal.
*
* if the new SA has a reqid, it was either triggered by an acquire or
* during a reestablishment. if they are equal, we are done */
if (!b->get_reqid(b) || a->get_reqid(a) == b->get_reqid(b))
{
return TRUE;
}
/* if the reqids differ, the one of the established SA was changed due to
* narrowing. in this case, we check if we have either triggering TS or
* previous TS. if so, we check whether the available TS match the TS of
* the existing SA. if they do, there is no point to negotiate another SA.
* if not, the peer will potentially narrow the TS to a different set for
* the new SA. */
if (this->packet_tsi && this->packet_tsr)
{
return child_sa_ts_match(a, this->packet_tsi, this->packet_tsr);
}
if (this->my_ts && this->other_ts)
{
return child_sa_ts_lists_match(a, this->my_ts, this->other_ts);
}
/* if we don't have any TS to compare, we assume the peer will do the same
* narrowing and treat the SAs equal.*/
return TRUE;
}
/**
* Compare two CHILD_SA objects for equality.
*
* The second CHILD_SA is assumed to be the one newly created.
*/
static bool child_sa_equals(private_child_create_t *this, child_sa_t *a,
child_sa_t *b)
{
child_cfg_t *cfg = a->get_config(a);
return cfg->equals(cfg, b->get_config(b)) &&
a->get_mark(a, TRUE).value == b->get_mark(b, TRUE).value &&
a->get_mark(a, FALSE).value == b->get_mark(b, FALSE).value &&
a->get_if_id(a, TRUE) == b->get_if_id(b, TRUE) &&
a->get_if_id(a, FALSE) == b->get_if_id(b, FALSE) &&
a->get_cpu(a) == b->get_cpu(b) &&
sec_labels_equal(a->get_label(a), b->get_label(b)) &&
reqid_and_ts_equals(this, a, b);
}
/**
* Check if there is a duplicate CHILD_SA already established and we can abort
* initiating this one.
*/
static bool check_for_duplicate(private_child_create_t *this)
{
enumerator_t *enumerator;
child_sa_t *child_sa, *found = NULL;
enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
while (enumerator->enumerate(enumerator, (void**)&child_sa))
{
if (child_sa->get_state(child_sa) == CHILD_INSTALLED &&
child_sa_equals(this, child_sa, this->child_sa))
{
found = child_sa;
break;
}
}
enumerator->destroy(enumerator);
if (found)
{
linked_list_t *my_ts, *other_ts;
my_ts = linked_list_create_from_enumerator(
found->create_ts_enumerator(found, TRUE));
other_ts = linked_list_create_from_enumerator(
found->create_ts_enumerator(found, FALSE));
DBG1(DBG_IKE, "not establishing CHILD_SA %s{%u} due to existing "
"duplicate {%u} with SPIs %.8x_i %.8x_o and TS %#R === %#R",
this->child_sa->get_name(this->child_sa),
this->child_sa->get_unique_id(this->child_sa),
found->get_unique_id(found),
ntohl(found->get_spi(found, TRUE)),
ntohl(found->get_spi(found, FALSE)), my_ts, other_ts);
my_ts->destroy(my_ts);
other_ts->destroy(other_ts);
}
return found;
}
/**
* Check if this is an attempt to create an SA with generic label and should
* be aborted.
*/
static bool check_for_generic_label(private_child_create_t *this)
{
if (generic_label_only(this))
{
#if DEBUG_LEVEL >= 1
sec_label_t *label = this->config->get_label(this->config);
DBG1(DBG_IKE, "not establishing CHILD_SA %s{%u} with generic "
"label '%s'", this->child_sa->get_name(this->child_sa),
this->child_sa->get_unique_id(this->child_sa),
label->get_string(label));
#endif
return TRUE;
}
return FALSE;
}
METHOD(task_t, build_i_multi_ke, status_t,
private_child_create_t *this, message_t *message)
{
key_exchange_method_t method;
message->set_exchange_type(message, IKE_FOLLOWUP_KE);
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 (!this->link.ptr)
{
DBG1(DBG_IKE, "%N notify missing", notify_type_names,
ADDITIONAL_KEY_EXCHANGE);
return FAILED;
}
if (!build_payloads_multi_ke(this, message))
{
return FAILED;
}
return NEED_MORE;
}
/**
* Prepare proposed traffic selectors as initiator.
*/
static void prepare_proposed_ts(private_child_create_t *this)
{
enumerator_t *enumerator;
peer_cfg_t *peer_cfg;
linked_list_t *list;
host_t *vip;
ensure_ts_lists(this);
list = linked_list_create();
if (!this->rekey)
{
/* check if we want a virtual IP */
peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
enumerator = peer_cfg->create_virtual_ip_enumerator(peer_cfg);
while (enumerator->enumerate(enumerator, &vip))
{
/* propose a 0.0.0.0/0 or ::/0 subnet when we use a virtual IP */
vip = host_create_any(vip->get_family(vip));
list->insert_last(list, vip);
}
enumerator->destroy(enumerator);
}
if (list->get_count(list))
{
this->tsi = child_cfg_select_ts(this->config, TRUE, this->my_ts, NULL,
list);
list->destroy_offset(list, offsetof(host_t, destroy));
}
else
{
list->destroy(list);
list = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE);
this->tsi = child_cfg_select_ts(this->config, TRUE, this->my_ts, NULL,
list);
list->destroy(list);
}
list = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE);
this->tsr = child_cfg_select_ts(this->config, FALSE, this->other_ts, NULL,
list);
list->destroy(list);
if (this->packet_tsi)
{
this->tsi->insert_first(this->tsi,
this->packet_tsi->clone(this->packet_tsi));
}
if (this->packet_tsr)
{
this->tsr->insert_first(this->tsr,
this->packet_tsr->clone(this->packet_tsr));
}
}
METHOD(task_t, build_i, status_t,
private_child_create_t *this, message_t *message)
{
bool no_ke = TRUE;
switch (message->get_exchange_type(message))
{
case IKE_SA_INIT:
return get_nonce(message, &this->my_nonce);
case CREATE_CHILD_SA:
if (!generate_nonce(this))
{
message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
return SUCCESS;
}
no_ke = FALSE;
break;
case IKE_AUTH:
switch (defer_child_sa(this))
{
case DESTROY_ME:
/* config mismatch */
return DESTROY_ME;
case NEED_MORE:
/* defer until after IKE_SA has been established */
chunk_free(&this->my_nonce);
return NEED_MORE;
default:
/* just continue to establish the CHILD_SA */
break;
}
/* send only in the first request, not in subsequent rounds */
this->public.task.build = (void*)return_need_more;
break;
default:
return NEED_MORE;
}
prepare_proposed_ts(this);
if (!generic_label_only(this) && !this->child.label)
{ /* in the simple label mode we propose the configured label as we
* won't have labels from acquires */
this->child.label = this->config->get_label(this->config);
if (this->child.label)
{
this->child.label = this->child.label->clone(this->child.label);
}
}
if (this->child.label)
{
DBG2(DBG_CFG, "proposing security label '%s'",
this->child.label->get_string(this->child.label));
}
if (!this->rekey)
{
this->child.per_cpu = this->config->has_option(this->config,
OPT_PER_CPU_SAS);
}
this->proposals = this->config->get_proposals(this->config, no_ke, TRUE);
this->mode = this->config->get_mode(this->config);
this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE);
this->child.if_id_out_def = this->ike_sa->get_if_id(this->ike_sa, FALSE);
this->child.encap = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);
this->child_sa = child_sa_create(this->ike_sa->get_my_host(this->ike_sa),
this->ike_sa->get_other_host(this->ike_sa),
this->config, &this->child);
/* check this after creating the object so that its destruction is detected
* by controller and trap manager */
if (!this->rekey &&
message->get_exchange_type(message) == CREATE_CHILD_SA &&
(check_for_generic_label(this) || check_for_duplicate(this)))
{
message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED);
return SUCCESS;
}
if (this->child.reqid)
{
DBG0(DBG_IKE, "establishing CHILD_SA %s{%u} reqid %d",
this->child_sa->get_name(this->child_sa),
this->child_sa->get_unique_id(this->child_sa), this->child.reqid);
}
else
{
DBG0(DBG_IKE, "establishing CHILD_SA %s{%u}",
this->child_sa->get_name(this->child_sa),
this->child_sa->get_unique_id(this->child_sa));
}
if (!allocate_spi(this))
{
return FAILED;
}
if (no_ke)
{ /* we might have one set if we are recreating this SA */
this->ke_method = KE_NONE;
}
else if (!this->retry)
{ /* during a rekeying the method might already be set */
if (this->ke_method == KE_NONE)
{
this->ke_method = this->config->get_algorithm(this->config,
KEY_EXCHANGE_METHOD);
}
}
if (!update_and_check_proposals(this))
{
DBG1(DBG_IKE, "requested key exchange method %N not contained in any "
"of our proposals", key_exchange_method_names, this->ke_method);
return FAILED;
}
if (this->ke_method != KE_NONE)
{
this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat,
this->ke_method);
if (!this->ke)
{
DBG1(DBG_IKE, "selected key exchange method %N not supported",
key_exchange_method_names, this->ke_method);
return FAILED;
}
}
if (this->config->has_option(this->config, OPT_IPCOMP))
{
/* IPCOMP_DEFLATE is the only transform we support at the moment */
add_ipcomp_notify(this, message, IPCOMP_DEFLATE);
}
if (message->get_exchange_type(message) == IKE_AUTH)
{
charon->bus->narrow(charon->bus, this->child_sa,
NARROW_INITIATOR_PRE_NOAUTH, this->tsi, this->tsr);
}
else
{
charon->bus->narrow(charon->bus, this->child_sa,
NARROW_INITIATOR_PRE_AUTH, this->tsi, this->tsr);
}
if (!build_payloads(this, message))
{
return FAILED;
}
this->tsi->destroy_offset(this->tsi, offsetof(traffic_selector_t, destroy));
this->tsr->destroy_offset(this->tsr, offsetof(traffic_selector_t, destroy));
this->proposals->destroy_offset(this->proposals, offsetof(proposal_t, destroy));
this->tsi = NULL;
this->tsr = NULL;
this->proposals = NULL;
return NEED_MORE;
}
/**
* Process payloads in a IKE_FOLLOWUP_KE message or a CREATE_CHILD_SA response
*/
static void process_link(private_child_create_t *this, message_t *message)
{
notify_payload_t *notify;
chunk_t link;
notify = message->get_notify(message, ADDITIONAL_KEY_EXCHANGE);
if (notify)
{
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);
chunk_free(&this->link);
}
}
else
{
chunk_free(&this->link);
}
}
/**
* Process payloads in additional exchanges when using multiple key exchanges
*/
static void process_payloads_multi_ke(private_child_create_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");
this->ke_failed = TRUE;
}
process_link(this, message);
}
METHOD(task_t, process_r_multi_ke, status_t,
private_child_create_t *this, message_t *message)
{
if (message->get_exchange_type(message) == IKE_FOLLOWUP_KE)
{
process_payloads_multi_ke(this, message);
}
return NEED_MORE;
}
METHOD(task_t, process_r, status_t,
private_child_create_t *this, message_t *message)
{
switch (message->get_exchange_type(message))
{
case IKE_SA_INIT:
return get_nonce(message, &this->other_nonce);
case CREATE_CHILD_SA:
get_nonce(message, &this->other_nonce);
break;
case IKE_AUTH:
/* only handle first AUTH payload, not additional rounds */
this->public.task.process = (void*)return_need_more;
break;
default:
return NEED_MORE;
}
process_payloads(this, message);
return NEED_MORE;
}
/**
* handle CHILD_SA setup failure
*/
static void handle_child_sa_failure(private_child_create_t *this,
message_t *message)
{
bool is_first;
if (this->aborted)
{
return;
}
is_first = message->get_exchange_type(message) == IKE_AUTH;
if (is_first &&
lib->settings->get_bool(lib->settings,
"%s.close_ike_on_child_failure", FALSE, lib->ns))
{
/* we delay the delete for 100ms, as the IKE_AUTH response must arrive
* first */
DBG1(DBG_IKE, "closing IKE_SA due CHILD_SA setup failure");
lib->scheduler->schedule_job_ms(lib->scheduler, (job_t*)
delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE),
100);
}
else
{
DBG1(DBG_IKE, "failed to establish CHILD_SA, keeping IKE_SA");
charon->bus->alert(charon->bus, ALERT_KEEP_ON_CHILD_SA_FAILURE,
is_first);
}
}
/**
* Substitute transport mode NAT selectors, if applicable
*/
static linked_list_t* get_ts_if_nat_transport(private_child_create_t *this,
bool local, linked_list_t *in)
{
linked_list_t *out = NULL;
ike_condition_t cond;
if (this->mode == MODE_TRANSPORT)
{
cond = local ? COND_NAT_HERE : COND_NAT_THERE;
if (this->ike_sa->has_condition(this->ike_sa, cond))
{
out = get_transport_nat_ts(this, local, in);
if (out->get_count(out) == 0)
{
out->destroy(out);
out = NULL;
}
}
}
return out;
}
/**
* Select a matching CHILD config as responder
*/
static child_cfg_t* select_child_cfg(private_child_create_t *this)
{
peer_cfg_t *peer_cfg;
child_cfg_t *child_cfg = NULL;
peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
if (peer_cfg && this->tsi && this->tsr)
{
linked_list_t *listr, *listi, *tsr, *tsi;
tsr = get_ts_if_nat_transport(this, TRUE, this->tsr);
tsi = get_ts_if_nat_transport(this, FALSE, this->tsi);
listr = ike_sa_get_dynamic_hosts(this->ike_sa, TRUE);
listi = ike_sa_get_dynamic_hosts(this->ike_sa, FALSE);
child_cfg = peer_cfg->select_child_cfg(peer_cfg,
tsr ?: this->tsr, tsi ?: this->tsi,
listr, listi, this->labels_r, this->labels_i);
if ((tsi || tsr) && child_cfg &&
child_cfg->get_mode(child_cfg) != MODE_TRANSPORT)
{
/* found a CHILD config, but it doesn't use transport mode */
child_cfg->destroy(child_cfg);
child_cfg = NULL;
}
if (!child_cfg && (tsi || tsr))
{
/* no match for the substituted NAT selectors, try it without */
child_cfg = peer_cfg->select_child_cfg(peer_cfg,
this->tsr, this->tsi,
listr, listi, this->labels_r, this->labels_i);
}
listr->destroy(listr);
listi->destroy(listi);
DESTROY_OFFSET_IF(tsi, offsetof(traffic_selector_t, destroy));
DESTROY_OFFSET_IF(tsr, offsetof(traffic_selector_t, destroy));
}
return child_cfg;
}
/**
* Check how to handle a possibly childless IKE_SA
*/
static status_t handle_childless(private_child_create_t *this)
{
ike_cfg_t *ike_cfg;
ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
if (!this->proposals && !this->tsi && !this->tsr)
{
/* looks like a childless IKE_SA, check if we allow it */
if (ike_cfg->childless(ike_cfg) == CHILDLESS_NEVER)
{
/* we don't allow childless initiation */
DBG1(DBG_IKE, "peer tried to initiate a childless IKE_SA");
return INVALID_STATE;
}
return SUCCESS;
}
/* the peer apparently wants to create a regular IKE_SA */
if (ike_cfg->childless(ike_cfg) == CHILDLESS_FORCE)
{
/* reject it if we only allow childless initiation */
DBG1(DBG_IKE, "peer did not initiate a childless IKE_SA");
return INVALID_STATE;
}
return NOT_SUPPORTED;
}
/**
* Select a security label.
*
* We already know that the proposed labels match the selected config, just make
* sure that the proposed/returned labels are the same.
*/
static bool select_label(private_child_create_t *this)
{
sec_label_t *li, *lr;
if (!this->config->select_label(this->config, this->labels_i, FALSE, &li, NULL) ||
!this->config->select_label(this->config, this->labels_r, FALSE, &lr, NULL))
{ /* sanity check */
return FALSE;
}
if (li)
{
if (!li->equals(li, lr))
{
DBG1(DBG_CHD, "security labels in TSi and TSr don't match");
return FALSE;
}
else if (!this->child.label)
{
this->child.label = li->clone(li);
}
else if (!this->child.label->equals(this->child.label, li))
{
DBG1(DBG_CHD, "returned security label '%s' doesn't match proposed "
"'%s'", li->get_string(li),
this->child.label->get_string(this->child.label));
return FALSE;
}
}
if (this->child.label)
{
DBG1(DBG_CFG, "selected security label: %s",
this->child.label->get_string(this->child.label));
}
return TRUE;
}
/**
* Handle per-resource information.
*/
static void handle_per_resource(private_child_create_t *this)
{
if (!this->resource_info_seen)
{
DBG1(DBG_IKE, "peer didn't send %N notify, creating regular SA",
notify_type_names, SA_RESOURCE_INFO);
this->child.per_cpu = FALSE;
this->child.cpu = CPU_ID_MAX;
return;
}
/* as responder, assign a CPU ID (or CPU_ID_MAX if we don't have a
* fallback SA yet) */
if (!this->initiator)
{
this->child.per_cpu = TRUE;
this->child.cpu = get_cpu(this);
}
if (this->child.cpu != CPU_ID_MAX && this->resource_info.len)
{
DBG1(DBG_IKE, "creating per-resource SA as one of several (our ID %u, "
"peer's ID %+B)", this->child.cpu, &this->resource_info);
}
else if (this->child.cpu != CPU_ID_MAX)
{
DBG1(DBG_IKE, "creating per-resource SA as one of several (our ID %u)",
this->child.cpu);
}
else if (this->resource_info.len)
{
DBG1(DBG_IKE, "creating SA as one of several per-resource SAs (peer's "
"ID %+B)", &this->resource_info);
}
else
{
DBG1(DBG_IKE, "creating SA as one of several per-resource SAs");
}
}
/**
* Called when a key exchange is done, returns TRUE once all are done.
*/
static bool key_exchange_done(private_child_create_t *this)
{
bool additional_ke;
if (!this->ke)
{
return TRUE;
}
this->key_exchanges[this->ke_index++].done = TRUE;
additional_ke = additional_key_exchange_required(this);
array_insert_create(&this->kes, ARRAY_TAIL, this->ke);
this->ke = NULL;
return additional_ke ? FALSE : TRUE;
}
/**
* Complete the current key exchange and install the CHILD_SA if all are done
* as responder.
*/
static bool key_exchange_done_and_install_r(private_child_create_t *this,
message_t *message, bool ike_auth)
{
bool all_done = FALSE;
if (key_exchange_done(this))
{
chunk_clear(&this->link);
all_done = TRUE;
}
else if (!this->link.ptr)
{
this->link = chunk_clone(chunk_from_chars(0x42));
}
if (!build_payloads(this, message))
{
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
handle_child_sa_failure(this, message);
return TRUE;
}
if (all_done)
{
switch (install_child_sa(this))
{
case SUCCESS:
break;
case NOT_FOUND:
message->add_notify(message, FALSE, TS_UNACCEPTABLE,
chunk_empty);
handle_child_sa_failure(this, message);
return TRUE;
case FAILED:
default:
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN,
chunk_empty);
handle_child_sa_failure(this, message);
return TRUE;
}
if (!this->rekey)
{ /* invoke the child_up() hook if we are not rekeying */
charon->bus->child_updown(charon->bus, this->child_sa, TRUE);
}
}
return all_done;
}
METHOD(task_t, build_r_multi_ke, status_t,
private_child_create_t *this, message_t *message)
{
if (!this->ke)
{
message->add_notify(message, FALSE, INVALID_SYNTAX, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
if (this->ke_failed)
{
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
if (!this->link.ptr)
{
DBG1(DBG_IKE, "%N notify missing", notify_type_names,
ADDITIONAL_KEY_EXCHANGE);
message->add_notify(message, FALSE, STATE_NOT_FOUND, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
if (!key_exchange_done_and_install_r(this, message, FALSE))
{
return NEED_MORE;
}
return SUCCESS;
}
METHOD(task_t, build_r, status_t,
private_child_create_t *this, message_t *message)
{
payload_t *payload;
enumerator_t *enumerator;
bool no_ke = TRUE, ike_auth = FALSE;
switch (message->get_exchange_type(message))
{
case IKE_SA_INIT:
return get_nonce(message, &this->my_nonce);
case CREATE_CHILD_SA:
if (!generate_nonce(this))
{
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN,
chunk_empty);
return SUCCESS;
}
no_ke = FALSE;
break;
case IKE_AUTH:
if (!this->ike_sa->has_condition(this->ike_sa, COND_AUTHENTICATED))
{ /* wait until all authentication rounds completed */
return NEED_MORE;
}
if (this->ike_sa->has_condition(this->ike_sa, COND_REDIRECTED))
{ /* no CHILD_SA is created for redirected SAs */
return SUCCESS;
}
switch (handle_childless(this))
{
case SUCCESS:
/* no CHILD_SA built */
return SUCCESS;
case INVALID_STATE:
message->add_notify(message, FALSE, INVALID_SYNTAX,
chunk_empty);
return FAILED;
default:
/* continue with regular initiation */
break;
}
ike_auth = TRUE;
break;
default:
return NEED_MORE;
}
if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING)
{
DBG1(DBG_IKE, "unable to create CHILD_SA while rekeying IKE_SA");
message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
return SUCCESS;
}
if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
{
DBG1(DBG_IKE, "unable to create CHILD_SA while deleting IKE_SA");
message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty);
return SUCCESS;
}
if (!this->config)
{
this->config = select_child_cfg(this);
}
if (!this->config || !this->tsi || !this->tsr)
{
if (!this->tsi || !this->tsr)
{
DBG1(DBG_IKE, "TS payloads missing in message");
}
else
{
DBG1(DBG_IKE, "traffic selectors %#R === %#R unacceptable",
this->tsr, this->tsi);
charon->bus->alert(charon->bus, ALERT_TS_MISMATCH, this->tsi,
this->tsr);
}
message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
/* check if ike_config_t task included non-critical error notifies */
enumerator = message->create_payload_enumerator(message);
while (enumerator->enumerate(enumerator, &payload))
{
if (payload->get_type(payload) == PLV2_NOTIFY)
{
notify_payload_t *notify = (notify_payload_t*)payload;
switch (notify->get_notify_type(notify))
{
case INTERNAL_ADDRESS_FAILURE:
case FAILED_CP_REQUIRED:
{
DBG1(DBG_IKE,"configuration payload negotiation "
"failed, no CHILD_SA built");
enumerator->destroy(enumerator);
handle_child_sa_failure(this, message);
return SUCCESS;
}
default:
break;
}
}
}
enumerator->destroy(enumerator);
if (!select_proposal(this, no_ke))
{
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
if (!check_ke_method_r(this, message))
{ /* the peer will retry, we don't handle this as failure */
return SUCCESS;
}
/* this flag might get reset if the check above notices a proposal without
* KE was selected */
if (this->ke_failed)
{
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
determine_key_exchanges(this);
if (!select_label(this))
{
message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
this->child.if_id_in_def = this->ike_sa->get_if_id(this->ike_sa, TRUE);
this->child.if_id_out_def = this->ike_sa->get_if_id(this->ike_sa, FALSE);
this->child.encap = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);
if (!this->rekey && this->config->has_option(this->config, OPT_PER_CPU_SAS))
{
handle_per_resource(this);
/* if it's a per-resource SA, we could check if we already have
* "too many" and reject this one with TS_MAX_QUEUE, but for now we just
* keep it */
}
this->child_sa = child_sa_create(this->ike_sa->get_my_host(this->ike_sa),
this->ike_sa->get_other_host(this->ike_sa),
this->config, &this->child);
this->other_spi = this->proposal->get_spi(this->proposal);
if (!allocate_spi(this))
{
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
this->proposal->set_spi(this->proposal, this->my_spi);
if (this->ipcomp_received != IPCOMP_NONE)
{
if (this->config->has_option(this->config, OPT_IPCOMP))
{
add_ipcomp_notify(this, message, this->ipcomp_received);
}
else
{
DBG1(DBG_IKE, "received %N notify but IPComp is disabled, ignoring",
notify_type_names, IPCOMP_SUPPORTED);
}
}
switch (narrow_and_check_ts(this, ike_auth))
{
case SUCCESS:
break;
case NOT_FOUND:
message->add_notify(message, FALSE, TS_UNACCEPTABLE, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
case FAILED:
default:
message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty);
handle_child_sa_failure(this, message);
return SUCCESS;
}
if (!key_exchange_done_and_install_r(this, message, ike_auth))
{
this->public.task.build = _build_r_multi_ke;
this->public.task.process = _process_r_multi_ke;
return NEED_MORE;
}
return SUCCESS;
}
/**
* Raise alerts for received notify errors
*/
static void raise_alerts(private_child_create_t *this, notify_type_t type)
{
linked_list_t *list;
switch (type)
{
case NO_PROPOSAL_CHOSEN:
list = this->config->get_proposals(this->config, FALSE, FALSE);
charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_CHILD, list);
list->destroy_offset(list, offsetof(proposal_t, destroy));
break;
case TS_UNACCEPTABLE:
charon->bus->alert(charon->bus, ALERT_TS_MISMATCH,
this->tsi, this->tsr);
break;
default:
break;
}
}
METHOD(task_t, build_i_delete, status_t,
private_child_create_t *this, message_t *message)
{
message->set_exchange_type(message, INFORMATIONAL);
if (this->my_spi && this->proto)
{
delete_payload_t *del;
del = delete_payload_create(PLV2_DELETE, this->proto);
del->add_spi(del, this->my_spi);
message->add_payload(message, (payload_t*)del);
DBG1(DBG_IKE, "sending DELETE for %N CHILD_SA with SPI %.8x",
protocol_id_names, this->proto, ntohl(this->my_spi));
}
return SUCCESS;
}
/**
* Change task to delete the failed CHILD_SA as initiator
*/
static status_t delete_failed_sa(private_child_create_t *this)
{
if (this->my_spi && this->proto)
{
this->public.task.build = _build_i_delete;
/* destroying it here allows the rekey task to differentiate between
* this and the multi-KE case */
this->child_sa->destroy(this->child_sa);
this->child_sa = NULL;
return NEED_MORE;
}
return SUCCESS;
}
/**
* Complete the current key exchange and install the CHILD_SA if all are done
* as initiator.
*/
static status_t key_exchange_done_and_install_i(private_child_create_t *this,
message_t *message, bool ike_auth)
{
if (key_exchange_done(this))
{
if (install_child_sa(this) == SUCCESS)
{
if (!this->rekey)
{ /* invoke the child_up() hook if we are not rekeying */
charon->bus->child_updown(charon->bus, this->child_sa,
TRUE);
}
return SUCCESS;
}
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
return NEED_MORE;
}
METHOD(task_t, process_i_multi_ke, status_t,
private_child_create_t *this, message_t *message)
{
if (message->get_notify(message, TEMPORARY_FAILURE))
{
DBG1(DBG_IKE, "received %N notify", notify_type_names,
TEMPORARY_FAILURE);
if (!this->rekey && !this->aborted)
{ /* the rekey task will retry itself if necessary */
schedule_delayed_retry(this);
}
return SUCCESS;
}
process_payloads_multi_ke(this, message);
if (this->ke_failed || this->aborted)
{
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
return key_exchange_done_and_install_i(this, message, FALSE);
}
METHOD(task_t, process_i, status_t,
private_child_create_t *this, message_t *message)
{
enumerator_t *enumerator;
payload_t *payload;
bool no_ke = TRUE, ike_auth = FALSE;
switch (message->get_exchange_type(message))
{
case IKE_SA_INIT:
return get_nonce(message, &this->other_nonce);
case CREATE_CHILD_SA:
get_nonce(message, &this->other_nonce);
no_ke = FALSE;
break;
case IKE_AUTH:
if (!this->ike_sa->has_condition(this->ike_sa, COND_AUTHENTICATED))
{ /* wait until all authentication rounds completed */
return NEED_MORE;
}
if (defer_child_sa(this) == NEED_MORE)
{ /* defer until after IKE_SA has been established */
chunk_free(&this->other_nonce);
return NEED_MORE;
}
ike_auth = TRUE;
break;
default:
return NEED_MORE;
}
/* check for erroneous notifies */
enumerator = message->create_payload_enumerator(message);
while (enumerator->enumerate(enumerator, &payload))
{
if (payload->get_type(payload) == PLV2_NOTIFY)
{
notify_payload_t *notify = (notify_payload_t*)payload;
notify_type_t type = notify->get_notify_type(notify);
switch (type)
{
/* handle notify errors related to CHILD_SA only */
case NO_PROPOSAL_CHOSEN:
case SINGLE_PAIR_REQUIRED:
case NO_ADDITIONAL_SAS:
case INTERNAL_ADDRESS_FAILURE:
case FAILED_CP_REQUIRED:
case TS_UNACCEPTABLE:
case INVALID_SELECTORS:
{
DBG1(DBG_IKE, "received %N notify, no CHILD_SA built",
notify_type_names, type);
enumerator->destroy(enumerator);
raise_alerts(this, type);
handle_child_sa_failure(this, message);
/* an error in CHILD_SA creation is not critical */
return SUCCESS;
}
case TEMPORARY_FAILURE:
{
DBG1(DBG_IKE, "received %N notify", notify_type_names, type);
enumerator->destroy(enumerator);
if (!this->rekey && !this->aborted)
{ /* the rekey task will retry itself if necessary */
schedule_delayed_retry(this);
}
return SUCCESS;
}
case INVALID_KE_PAYLOAD:
{
chunk_t data;
uint16_t alg = KE_NONE;
if (this->aborted)
{ /* nothing to do if the task was aborted */
DBG1(DBG_IKE, "received %N notify in aborted %N task",
notify_type_names, type, task_type_names,
TASK_CHILD_CREATE);
enumerator->destroy(enumerator);
return SUCCESS;
}
data = notify->get_notification_data(notify);
if (data.len == sizeof(alg))
{
alg = untoh16(data.ptr);
}
if (this->retry)
{
DBG1(DBG_IKE, "already retried with key exchange method "
"%N, ignore requested %N", key_exchange_method_names,
this->ke_method, key_exchange_method_names, alg);
handle_child_sa_failure(this, message);
/* an error in CHILD_SA creation is not critical */
return SUCCESS;
}
DBG1(DBG_IKE, "peer didn't accept key exchange method %N, "
"it requested %N", key_exchange_method_names,
this->ke_method, key_exchange_method_names, alg);
this->retry = TRUE;
this->ke_method = alg;
this->child_sa->set_state(this->child_sa, CHILD_RETRYING);
this->public.task.migrate(&this->public.task, this->ike_sa);
enumerator->destroy(enumerator);
return NEED_MORE;
}
default:
{
if (message->get_exchange_type(message) == CREATE_CHILD_SA)
{ /* handle notifies if not handled in IKE_AUTH */
if (type <= 16383)
{
DBG1(DBG_IKE, "received %N notify error",
notify_type_names, type);
enumerator->destroy(enumerator);
return SUCCESS;
}
DBG2(DBG_IKE, "received %N notify",
notify_type_names, type);
}
break;
}
}
}
}
enumerator->destroy(enumerator);
process_payloads(this, message);
if (!this->tsi || !this->tsr)
{
DBG1(DBG_IKE, "TS payloads missing in message");
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
if (!select_proposal(this, no_ke))
{
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
this->other_spi = this->proposal->get_spi(this->proposal);
this->proposal->set_spi(this->proposal, this->my_spi);
if (this->aborted)
{
DBG1(DBG_IKE, "deleting CHILD_SA %s{%u} with SPIs %.8x_i %.8x_o of "
"aborted %N task",
this->child_sa->get_name(this->child_sa),
this->child_sa->get_unique_id(this->child_sa),
ntohl(this->my_spi), ntohl(this->other_spi),
task_type_names, TASK_CHILD_CREATE);
return delete_failed_sa(this);
}
if (this->ipcomp == IPCOMP_NONE && this->ipcomp_received != IPCOMP_NONE)
{
DBG1(DBG_IKE, "received an IPCOMP_SUPPORTED notify without requesting"
" one, no CHILD_SA built");
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
else if (this->ipcomp != IPCOMP_NONE && this->ipcomp_received == IPCOMP_NONE)
{
DBG1(DBG_IKE, "peer didn't accept our proposed IPComp transforms, "
"IPComp is disabled");
this->ipcomp = IPCOMP_NONE;
}
else if (this->ipcomp != IPCOMP_NONE && this->ipcomp != this->ipcomp_received)
{
DBG1(DBG_IKE, "received an IPCOMP_SUPPORTED notify we didn't propose, "
"no CHILD_SA built");
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
if (!check_ke_method(this, NULL))
{
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
if (this->ke_failed)
{
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
determine_key_exchanges(this);
if (!select_label(this))
{
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
if (narrow_and_check_ts(this, ike_auth) != SUCCESS)
{
handle_child_sa_failure(this, message);
return delete_failed_sa(this);
}
if (!this->rekey && this->config->has_option(this->config, OPT_PER_CPU_SAS))
{
handle_per_resource(this);
/* the peer might not have returned the notify, update the flag */
this->child_sa->set_per_cpu(this->child_sa, this->child.per_cpu);
}
if (key_exchange_done_and_install_i(this, message, ike_auth) == NEED_MORE)
{
/* if the installation failed, we delete the failed SA, i.e. build() was
* changed, otherwise, we switch to multi-KE mode */
if (this->public.task.build == _build_i)
{
/* if we don't have the notify, we handle it in build() */
process_link(this, message);
this->public.task.build = _build_i_multi_ke;
this->public.task.process = _process_i_multi_ke;
}
return NEED_MORE;
}
return SUCCESS;
}
METHOD(child_create_t, use_reqid, void,
private_child_create_t *this, uint32_t reqid)
{
uint32_t existing_reqid = this->child.reqid;
if (!reqid || charon->kernel->ref_reqid(charon->kernel, reqid) == SUCCESS)
{
this->child.reqid = reqid;
if (existing_reqid)
{
charon->kernel->release_reqid(charon->kernel, existing_reqid);
}
}
}
METHOD(child_create_t, use_marks, void,
private_child_create_t *this, uint32_t in, uint32_t out)
{
this->child.mark_in = in;
this->child.mark_out = out;
}
METHOD(child_create_t, use_if_ids, void,
private_child_create_t *this, uint32_t in, uint32_t out)
{
this->child.if_id_in = in;
this->child.if_id_out = out;
}
METHOD(child_create_t, use_label, void,
private_child_create_t *this, sec_label_t *label)
{
DESTROY_IF(this->child.label);
this->child.label = label ? label->clone(label) : NULL;
}
METHOD(child_create_t, use_per_cpu, void,
private_child_create_t *this, bool per_cpu, uint32_t cpu)
{
this->child.per_cpu = per_cpu ? per_cpu : cpu != CPU_ID_MAX;
this->child.cpu = cpu;
}
/**
* Prepare traffic selectors for reuse when recreating a CHILD_SA.
*/
static void reuse_ts(private_child_create_t *this, bool local, child_sa_t *old,
traffic_selector_list_t **target)
{
enumerator_t *old_ts, *hosts_enum;
linked_list_t *hosts, *list;
traffic_selector_t *ts, *new_ts;
host_t *host;
old_ts = old->create_ts_enumerator(old, local);
if (this->rekey)
{
/* when rekeying, we just reuse the previous TS. this is also the only
* way a responder reuses TS */
*target = traffic_selector_list_create_from_enumerator(old_ts);
return;
}
/* when recreating/reauthenticating, we check whether the dynamic IPs of
* the IKE_SA (as copied from the old SA) match the TS and replace
* them with dynamic TS (reusing protocol/ports in case of narrowing) so
* they get updated to possibly new IPs when the TS are prepared later */
list = linked_list_create();
hosts = ike_sa_get_dynamic_hosts(this->ike_sa, local);
hosts_enum = hosts->create_enumerator(hosts);
while (old_ts->enumerate(old_ts, &ts))
{
new_ts = NULL;
while (hosts_enum->enumerate(hosts_enum, &host))
{
if (ts->is_host(ts, host))
{
new_ts = traffic_selector_create_dynamic(ts->get_protocol(ts),
ts->get_from_port(ts),
ts->get_to_port(ts));
break;
}
}
hosts->reset_enumerator(hosts, hosts_enum);
if (!new_ts)
{
new_ts = ts->clone(ts);
}
list->insert_last(list, new_ts);
}
hosts_enum->destroy(hosts_enum);
hosts->destroy(hosts);
old_ts->destroy(old_ts);
*target = traffic_selector_list_create_from_list(list);
}
METHOD(child_create_t, recreate_sa, void,
private_child_create_t *this, child_sa_t *old)
{
if (this->initiator)
{
proposal_t *proposal;
uint16_t ke_method;
proposal = old->get_proposal(old);
if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD,
&ke_method, NULL))
{
/* reuse the KE method negotiated previously */
this->ke_method = ke_method;
}
}
/* use previously negotiated traffic selectors */
reuse_ts(this, TRUE, old, &this->my_ts);
reuse_ts(this, FALSE, old, &this->other_ts);
}
METHOD(child_create_t, get_child, child_sa_t*,
private_child_create_t *this)
{
return this->child_sa;
}
METHOD(child_create_t, get_other_spi, uint32_t,
private_child_create_t *this)
{
return this->other_spi;
}
METHOD(child_create_t, set_config, void,
private_child_create_t *this, child_cfg_t *cfg)
{
DESTROY_IF(this->config);
this->config = cfg;
}
METHOD(child_create_t, get_config, child_cfg_t*,
private_child_create_t *this)
{
return this->initiator ? this->config : NULL;
}
METHOD(child_create_t, get_lower_nonce, chunk_t,
private_child_create_t *this)
{
if (chunk_compare_prefix(this->my_nonce, this->other_nonce) < 0)
{
return this->my_nonce;
}
else
{
return this->other_nonce;
}
}
METHOD(child_create_t, abort_, void,
private_child_create_t *this)
{
this->aborted = TRUE;
}
METHOD(task_t, get_type, task_type_t,
private_child_create_t *this)
{
return TASK_CHILD_CREATE;
}
METHOD(task_t, migrate, void,
private_child_create_t *this, ike_sa_t *ike_sa)
{
chunk_free(&this->my_nonce);
chunk_free(&this->other_nonce);
chunk_free(&this->link);
chunk_free(&this->resource_info);
DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy));
DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy));
DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy));
DESTROY_OFFSET_IF(this->labels_r, offsetof(sec_label_t, destroy));
DESTROY_IF(this->child_sa);
DESTROY_IF(this->proposal);
DESTROY_IF(this->nonceg);
DESTROY_IF(this->ke);
this->ke_failed = FALSE;
clear_key_exchanges(this);
DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy));
if (!this->rekey && !this->retry)
{
this->ke_method = KE_NONE;
}
this->ike_sa = ike_sa;
this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa);
this->proposal = NULL;
this->proposals = NULL;
this->tsi = NULL;
this->tsr = NULL;
this->labels_i = NULL;
this->labels_r = NULL;
this->ke = NULL;
this->nonceg = NULL;
this->child_sa = NULL;
this->mode = MODE_TUNNEL;
this->ipcomp = IPCOMP_NONE;
this->ipcomp_received = IPCOMP_NONE;
this->other_cpi = 0;
this->established = FALSE;
this->resource_info_seen = FALSE;
this->public.task.build = _build_i;
this->public.task.process = _process_i;
}
METHOD(task_t, destroy, void,
private_child_create_t *this)
{
chunk_free(&this->my_nonce);
chunk_free(&this->other_nonce);
chunk_free(&this->link);
chunk_free(&this->resource_info);
DESTROY_OFFSET_IF(this->tsr, offsetof(traffic_selector_t, destroy));
DESTROY_OFFSET_IF(this->tsi, offsetof(traffic_selector_t, destroy));
DESTROY_OFFSET_IF(this->labels_i, offsetof(sec_label_t, destroy));
DESTROY_OFFSET_IF(this->labels_r, offsetof(sec_label_t, destroy));
if (!this->established)
{
DESTROY_IF(this->child_sa);
}
if (this->child.reqid)
{
charon->kernel->release_reqid(charon->kernel, this->child.reqid);
}
DESTROY_IF(this->packet_tsi);
DESTROY_IF(this->packet_tsr);
DESTROY_IF(this->my_ts);
DESTROY_IF(this->other_ts);
DESTROY_IF(this->proposal);
DESTROY_IF(this->ke);
clear_key_exchanges(this);
DESTROY_OFFSET_IF(this->proposals, offsetof(proposal_t, destroy));
DESTROY_IF(this->config);
DESTROY_IF(this->nonceg);
DESTROY_IF(this->child.label);
free(this);
}
/*
* Described in header.
*/
child_create_t *child_create_create(ike_sa_t *ike_sa,
child_cfg_t *config, bool rekey,
traffic_selector_t *tsi,
traffic_selector_t *tsr, uint32_t seq)
{
private_child_create_t *this;
INIT(this,
.public = {
.get_child = _get_child,
.get_other_spi = _get_other_spi,
.set_config = _set_config,
.get_config = _get_config,
.get_lower_nonce = _get_lower_nonce,
.use_reqid = _use_reqid,
.use_marks = _use_marks,
.use_if_ids = _use_if_ids,
.use_label = _use_label,
.use_per_cpu = _use_per_cpu,
.recreate_sa = _recreate_sa,
.abort = _abort_,
.task = {
.get_type = _get_type,
.migrate = _migrate,
.destroy = _destroy,
},
},
.child = {
.cpu = CPU_ID_MAX,
.seq = seq,
},
.ike_sa = ike_sa,
.config = config,
.packet_tsi = tsi ? tsi->clone(tsi) : NULL,
.packet_tsr = tsr ? tsr->clone(tsr) : NULL,
.ke_method = KE_NONE,
.keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa),
.mode = MODE_TUNNEL,
.tfcv3 = TRUE,
.ipcomp = IPCOMP_NONE,
.ipcomp_received = IPCOMP_NONE,
.rekey = rekey,
.retry = FALSE,
);
if (config)
{
this->public.task.build = _build_i;
this->public.task.process = _process_i;
this->initiator = TRUE;
}
else
{
this->public.task.build = _build_r;
this->public.task.process = _process_r;
this->initiator = FALSE;
}
return &this->public;
}