From 3183006de229901b15385f7a39976987c49f630d Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 23 Aug 2006 07:30:43 +0000 Subject: [PATCH] implement proper handling of most simultaneous IKE_SA rekeying cases --- src/charon/queues/jobs/incoming_packet_job.c | 4 +- src/charon/sa/ike_sa.c | 65 ++++++-- src/charon/sa/ike_sa.h | 78 ++++++--- src/charon/sa/transactions/create_child_sa.c | 13 +- src/charon/sa/transactions/delete_child_sa.c | 11 ++ src/charon/sa/transactions/ike_sa_init.c | 4 +- src/charon/sa/transactions/rekey_ike_sa.c | 166 +++++++++++++++++-- src/charon/sa/transactions/transaction.c | 4 +- 8 files changed, 288 insertions(+), 57 deletions(-) diff --git a/src/charon/queues/jobs/incoming_packet_job.c b/src/charon/queues/jobs/incoming_packet_job.c index 69ef141ec..549d062dd 100644 --- a/src/charon/queues/jobs/incoming_packet_job.c +++ b/src/charon/queues/jobs/incoming_packet_job.c @@ -79,12 +79,14 @@ static void send_notify_response(private_incoming_packet_job_t *this, src = request->get_destination(request); response->set_source(response, src->clone(src)); response->set_destination(response, dst->clone(dst)); - response->set_exchange_type(response, IKE_SA_INIT); + response->set_exchange_type(response, request->get_exchange_type(request)); response->set_request(response, FALSE); response->set_message_id(response, 0); response->set_ike_sa_id(response, ike_sa_id); notify = notify_payload_create_from_protocol_and_type(PROTO_NONE, type); response->add_payload(response, (payload_t *)notify); + /* generation may fail, as most messages need a crypter/signer. + * TODO: Use transforms implementing the "NULL" algorithm */ if (response->generate(response, NULL, NULL, &packet) != SUCCESS) { response->destroy(response); diff --git a/src/charon/sa/ike_sa.c b/src/charon/sa/ike_sa.c index fba94cce0..d129cceed 100644 --- a/src/charon/sa/ike_sa.c +++ b/src/charon/sa/ike_sa.c @@ -64,6 +64,7 @@ mapping_t ike_sa_state_m[] = { {IKE_CREATED, "CREATED"}, {IKE_CONNECTING, "CONNECTING"}, {IKE_ESTABLISHED, "ESTABLISHED"}, + {IKE_REKEYING, "REKEYING"}, {IKE_DELETING, "DELETING"}, {MAPPING_END, NULL} }; @@ -225,6 +226,11 @@ struct private_ike_sa_t { * do multi transaction operations. */ transaction_t *transaction_in_next; + + /** + * Transaction which rekeys this IKE_SA, used do detect simultaneus rekeying + */ + rekey_ike_sa_t *rekeying_transaction; }; /** @@ -840,10 +846,12 @@ static status_t initiate(private_ike_sa_t *this, return queue_transaction(this, (transaction_t*)ike_sa_init, TRUE); } case IKE_DELETING: + case IKE_REKEYING: { - /* if we are in DELETING, we deny set up of a policy. */ + /* if we are in DELETING/REKEYING, we deny set up of a policy. */ this->logger->log(this->logger, CONTROL, - "creating CHILD_SA discarded, as IKE_SA is deleting"); + "creating CHILD_SA discarded, as IKE_SA is in state %s", + mapping_find(ike_sa_state_m, this->state)); policy->destroy(policy); connection->destroy(connection); return FAILED; @@ -1084,7 +1092,9 @@ static status_t route(private_ike_sa_t *this, connection_t *connection, policy_t set_name(this, connection->get_name(connection)); break; case IKE_ESTABLISHED: - /* nothing to do */ + case IKE_REKEYING: + /* nothing to do. We allow it for rekeying, as it will be + * adopted by the new IKE_SA */ break; case IKE_DELETING: /* deny */ @@ -1331,7 +1341,7 @@ static void set_other_id(private_ike_sa_t *this, identification_t *other) static status_t derive_keys(private_ike_sa_t *this, proposal_t *proposal, diffie_hellman_t *dh, chunk_t nonce_i, chunk_t nonce_r, - bool initiator, prf_t *rekey_prf) + bool initiator, prf_t *child_prf, prf_t *old_prf) { prf_plus_t *prf_plus; chunk_t skeyseed, secret, key, nonces, prf_plus_seed; @@ -1368,7 +1378,7 @@ static status_t derive_keys(private_ike_sa_t *this, * * if we are rekeying, SKEYSEED built on another way */ - if (rekey_prf == NULL) /* not rekeying */ + if (child_prf == NULL) /* not rekeying */ { /* SKEYSEED = prf(Ni | Nr, g^ir) */ this->prf->set_key(this->prf, nonces); @@ -1381,14 +1391,15 @@ static status_t derive_keys(private_ike_sa_t *this, } else { - /* SKEYSEED = prf(SK_d (old), [g^ir (new)] | Ni | Nr) */ + /* SKEYSEED = prf(SK_d (old), [g^ir (new)] | Ni | Nr) + * use OLD SAs PRF functions for both prf_plus and prf */ secret = chunk_cat("mc", secret, nonces); - rekey_prf->allocate_bytes(rekey_prf, secret, &skeyseed); + child_prf->allocate_bytes(child_prf, secret, &skeyseed); this->logger->log_chunk(this->logger, PRIVATE|LEVEL1, "SKEYSEED", skeyseed); - rekey_prf->set_key(rekey_prf, skeyseed); /* abuse of rekeyed SAs child_prf */ + old_prf->set_key(old_prf, skeyseed); chunk_free(&skeyseed); chunk_free(&secret); - prf_plus = prf_plus_create(rekey_prf, prf_plus_seed); + prf_plus = prf_plus_create(old_prf, prf_plus_seed); } chunk_free(&nonces); chunk_free(&prf_plus_seed); @@ -1421,12 +1432,12 @@ static status_t derive_keys(private_ike_sa_t *this, key_size = signer_i->get_key_size(signer_i); prf_plus->allocate_bytes(prf_plus, key_size, &key); - this->logger->log_chunk(this->logger, CONTROL|LEVEL1, "Sk_ai secret", key); + this->logger->log_chunk(this->logger, PRIVATE, "Sk_ai secret", key); signer_i->set_key(signer_i, key); chunk_free(&key); prf_plus->allocate_bytes(prf_plus, key_size, &key); - this->logger->log_chunk(this->logger, CONTROL|LEVEL1, "Sk_ar secret", key); + this->logger->log_chunk(this->logger, PRIVATE, "Sk_ar secret", key); signer_r->set_key(signer_r, key); chunk_free(&key); @@ -1547,7 +1558,7 @@ static child_sa_t* get_child_sa(private_ike_sa_t *this, protocol_id_t protocol, { iterator->current(iterator, (void**)¤t); if (current->get_spi(current, inbound) == spi && - current->get_protocol(current) == protocol) + current->get_protocol(current) == protocol) { found = current; } @@ -1556,6 +1567,14 @@ static child_sa_t* get_child_sa(private_ike_sa_t *this, protocol_id_t protocol, return found; } +/** + * Implementation of ike_sa_t.create_child_sa_iterator. + */ +static iterator_t* create_child_sa_iterator(private_ike_sa_t *this) +{ + return this->child_sas->create_iterator(this->child_sas, TRUE); +} + /** * Implementation of ike_sa_t.rekey_child_sa. */ @@ -1670,6 +1689,22 @@ static status_t rekey(private_ike_sa_t *this) return queue_transaction(this, (transaction_t*)rekey_ike_sa, FALSE); } +/** + * Implementation of ike_sa_t.get_rekeying_transaction. + */ +static rekey_ike_sa_t* get_rekeying_transaction(private_ike_sa_t *this) +{ + return this->rekeying_transaction; +} + +/** + * Implementation of ike_sa_t.set_rekeying_transaction. + */ +static void set_rekeying_transaction(private_ike_sa_t *this, rekey_ike_sa_t *rekey) +{ + this->rekeying_transaction = rekey; +} + /** * Implementation of ike_sa_t.adopt_children. */ @@ -1883,10 +1918,11 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) this->public.get_child_prf = (prf_t *(*) (ike_sa_t *)) get_child_prf; this->public.get_prf_auth_i = (prf_t *(*) (ike_sa_t *)) get_prf_auth_i; this->public.get_prf_auth_r = (prf_t *(*) (ike_sa_t *)) get_prf_auth_r; - this->public.derive_keys = (status_t (*) (ike_sa_t *,proposal_t*,diffie_hellman_t*,chunk_t,chunk_t,bool,prf_t*)) derive_keys; + this->public.derive_keys = (status_t (*) (ike_sa_t *,proposal_t*,diffie_hellman_t*,chunk_t,chunk_t,bool,prf_t*,prf_t*)) derive_keys; this->public.add_child_sa = (void (*) (ike_sa_t*,child_sa_t*)) add_child_sa; this->public.has_child_sa = (bool(*)(ike_sa_t*,u_int32_t)) has_child_sa; this->public.get_child_sa = (child_sa_t* (*)(ike_sa_t*,protocol_id_t,u_int32_t,bool)) get_child_sa; + this->public.create_child_sa_iterator = (iterator_t* (*)(ike_sa_t*)) create_child_sa_iterator; this->public.rekey_child_sa = (status_t(*)(ike_sa_t*,protocol_id_t,u_int32_t)) rekey_child_sa; this->public.delete_child_sa = (status_t(*)(ike_sa_t*,protocol_id_t,u_int32_t)) delete_child_sa; this->public.destroy_child_sa = (status_t (*)(ike_sa_t*,protocol_id_t,u_int32_t))destroy_child_sa; @@ -1894,6 +1930,8 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) this->public.is_natt_enabled = (bool(*)(ike_sa_t*)) is_natt_enabled; this->public.set_lifetimes = (void(*)(ike_sa_t*,u_int32_t,u_int32_t))set_lifetimes; this->public.rekey = (status_t(*)(ike_sa_t*))rekey; + this->public.get_rekeying_transaction = (void*(*)(ike_sa_t*))get_rekeying_transaction; + this->public.set_rekeying_transaction = (void(*)(ike_sa_t*,void*))set_rekeying_transaction; this->public.adopt_children = (void(*)(ike_sa_t*,ike_sa_t*))adopt_children; /* initialize private fields */ @@ -1919,6 +1957,7 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) this->transaction_in = NULL; this->transaction_in_next = NULL; this->transaction_out = NULL; + this->rekeying_transaction = NULL; this->state = IKE_CREATED; this->message_id_out = 0; /* set to NOW, as when we rekey an existing IKE_SA no message is exchanged */ diff --git a/src/charon/sa/ike_sa.h b/src/charon/sa/ike_sa.h index b1b9a8fcd..39536d5d5 100644 --- a/src/charon/sa/ike_sa.h +++ b/src/charon/sa/ike_sa.h @@ -51,30 +51,32 @@ typedef enum ike_sa_state_t ike_sa_state_t; * SA is in the state CREATED. * @verbatim +----------------+ - ¦ SA_CREATED ¦ + ¦ SA_CREATED ¦ +----------------+ - ¦ - on initiate()---> ¦ <----- on IKE_SA_INIT received - ¦ + ¦ + on initiate()---> ¦ <----- on IKE_SA_INIT received + V +----------------+ - ¦ SA_CONNECTING ¦ + ¦ SA_CONNECTING ¦ +----------------+ - ¦ - ¦ <----- on IKE_AUTH successfully completed - ¦ + ¦ + ¦ <----- on IKE_AUTH successfully completed + V +----------------+ - ¦ SA_ESTABLISHED ¦ + ¦ SA_ESTABLISHED ¦-------------------------+ <-- on rekeying + +----------------+ ¦ + ¦ V + on delete()---> ¦ <----- on IKE_SA +-------------+ + ¦ delete request ¦ SA_REKEYING ¦ + ¦ received +-------------+ + V ¦ + +----------------+ ¦ + ¦ SA_DELETING ¦<------------------------+ <-- after rekeying +----------------+ - ¦ - on delete()---> ¦ <----- on IKE_SA delete request received - ¦ - +----------------+ - ¦ SA_DELETING ¦ - +----------------+ - ¦ - ¦ <----- after delete() acknowledged - ¦ - \¦/ + ¦ + ¦ <----- after delete() acknowledged + ¦ + \V/ X / \ @endverbatim @@ -98,6 +100,11 @@ enum ike_sa_state_t { */ IKE_ESTABLISHED, + /** + * IKE_SA rekeying in progress + */ + IKE_REKEYING, + /** * IKE_SA is in progress of deletion */ @@ -279,7 +286,7 @@ struct ike_sa_t { * @brief Acquire connection setup for a policy. * * If an installed policy raises an acquire, the kernel calls - * this function to establsh the CHILD_SA (and maybe the IKE_SA). + * this function to establish the CHILD_SA (and maybe the IKE_SA). * * @param this calling object * @param reqid reqid of the CHILD_SA the policy belongs to. @@ -418,12 +425,13 @@ struct ike_sa_t { * @param nonce_i initiators nonce * @param nonce_r responders nonce * @param initiator TRUE if initiator, FALSE otherwise - * @param rekey_prf PRF with SK_d key when rekeying, NULL otherwise + * @param child_prf PRF with SK_d key when rekeying, NULL otherwise + * @param old_prf general purpose PRF of old SA when rekeying */ status_t (*derive_keys)(ike_sa_t *this, proposal_t* proposal, diffie_hellman_t *dh, chunk_t nonce_i, chunk_t nonce_r, - bool initiator, prf_t *rekey_prf); + bool initiator, prf_t *child_prf, prf_t *old_prf); /** * @brief Get the multi purpose prf. @@ -486,6 +494,14 @@ struct ike_sa_t { child_sa_t* (*get_child_sa) (ike_sa_t *this, protocol_id_t protocol, u_int32_t spi, bool inbound); + /** + * @brief Create an iterator over all CHILD_SAs. + * + * @param this calling object + * @return iterator + */ + iterator_t* (*create_child_sa_iterator) (ike_sa_t *this); + /** * @brief Rekey the CHILD SA with the specified reqid. * @@ -555,6 +571,24 @@ struct ike_sa_t { */ status_t (*rekey) (ike_sa_t *this); + /** + * @brief Get the transaction which rekeys this IKE_SA. + * + * @todo Fix include for rekey_ike_sa.h + * + * @param this calling object + * @return rekey_ike_sa_t transaction or NULL + */ + void* (*get_rekeying_transaction) (ike_sa_t *this); + + /** + * @brief Set the transaction which rekeys this IKE_SA. + * + * @param this calling object + * @param rekey rekey_ike_sa_t transaction or NULL + */ + void (*set_rekeying_transaction) (ike_sa_t *this, void *rekey); + /** * @brief Move all children from other IKE_SA to this IKE_SA. * diff --git a/src/charon/sa/transactions/create_child_sa.c b/src/charon/sa/transactions/create_child_sa.c index 7e34be0ba..aa1d8b3b3 100644 --- a/src/charon/sa/transactions/create_child_sa.c +++ b/src/charon/sa/transactions/create_child_sa.c @@ -406,7 +406,7 @@ static status_t process_notifys(private_create_child_sa_t *this, notify_payload_ if (notify_type < 16383) { this->logger->log(this->logger, AUDIT, - "received %s notify error (%d), deleting IKE_SA", + "received %s notify error (%d), CHILD_SA creation failed", mapping_find(notify_type_m, notify_type), notify_type); return FAILED; @@ -540,6 +540,17 @@ static status_t get_response(private_create_child_sa_t *this, message_t *request return FAILED; } + /* we do not allow the creation of new CHILDren/rekeying when IKE_SA is + * rekeying */ + if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING || + this->ike_sa->get_state(this->ike_sa) == IKE_DELETING) + { + build_notify(NO_ADDITIONAL_SAS, CHUNK_INITIALIZER, response, TRUE); + this->logger->log(this->logger, AUDIT, + "unable to create new CHILD_SAs, as rekeying in progress"); + return FAILED; + } + /* Iterate over all payloads. */ payloads = request->get_payload_iterator(request); while (payloads->has_next(payloads)) diff --git a/src/charon/sa/transactions/delete_child_sa.c b/src/charon/sa/transactions/delete_child_sa.c index f8399628b..b577e42a4 100644 --- a/src/charon/sa/transactions/delete_child_sa.c +++ b/src/charon/sa/transactions/delete_child_sa.c @@ -256,6 +256,17 @@ static status_t get_response(private_delete_child_sa_t *this, message_t *request return FAILED; } + /* we can't handle a delete for a CHILD when we are rekeying. There + * is no proper solution for this. We send a empty informational response, + * as described in ikev2-clarifications draft */ + if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING || + this->ike_sa->get_state(this->ike_sa) == IKE_DELETING) + { + this->logger->log(this->logger, AUDIT, + "unable to delete CHILD_SA, as rekeying in progress"); + return FAILED; + } + /* iterate over all payloads */ payloads = request->get_payload_iterator(request); while (payloads->has_next(payloads)) diff --git a/src/charon/sa/transactions/ike_sa_init.c b/src/charon/sa/transactions/ike_sa_init.c index b86420cac..6dda7eb14 100644 --- a/src/charon/sa/transactions/ike_sa_init.c +++ b/src/charon/sa/transactions/ike_sa_init.c @@ -775,7 +775,7 @@ static status_t get_response(private_ike_sa_init_t *this, if (this->ike_sa->derive_keys(this->ike_sa, this->proposal, this->diffie_hellman, this->nonce_i, this->nonce_r, - FALSE, NULL) != SUCCESS) + FALSE, NULL, NULL) != SUCCESS) { notify_payload_t *notify = notify_payload_create(); notify->set_notify_type(notify, NO_PROPOSAL_CHOSEN); @@ -1019,7 +1019,7 @@ static status_t conclude(private_ike_sa_init_t *this, message_t *response, if (this->ike_sa->derive_keys(this->ike_sa, this->proposal, this->diffie_hellman, this->nonce_i, this->nonce_r, - TRUE, NULL) != SUCCESS) + TRUE, NULL, NULL) != SUCCESS) { this->logger->log(this->logger, AUDIT, "transform objects could not be created from selected proposal, deleting IKE_SA"); diff --git a/src/charon/sa/transactions/rekey_ike_sa.c b/src/charon/sa/transactions/rekey_ike_sa.c index f546540a5..bc75e6ba9 100644 --- a/src/charon/sa/transactions/rekey_ike_sa.c +++ b/src/charon/sa/transactions/rekey_ike_sa.c @@ -190,6 +190,15 @@ static status_t get_request(private_rekey_ike_sa_t *this, message_t **result) return SUCCESS; } + if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED) + { + this->logger->log(this->logger, ERROR, + "tried to rekey in state %s, aborted", + mapping_find(ike_sa_state_m, + this->ike_sa->get_state(this->ike_sa))); + return FAILED; + } + me = this->ike_sa->get_my_host(this->ike_sa); other = this->ike_sa->get_other_host(this->ike_sa); @@ -274,7 +283,7 @@ static status_t get_request(private_rekey_ike_sa_t *this, message_t **result) this->logger->log(this->logger, AUDIT, "DH group %s (%d) not supported, aborting", mapping_find(diffie_hellman_group_m, dh_group), dh_group); - return DESTROY_ME; + return FAILED; } } @@ -288,6 +297,10 @@ static status_t get_request(private_rekey_ike_sa_t *this, message_t **result) this->message_id = this->ike_sa->get_next_message_id(this->ike_sa); request->set_message_id(request, this->message_id); + /* register us as rekeying to detect multiple rekeying */ + this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); + this->ike_sa->set_rekeying_transaction(this->ike_sa, &this->public); + return SUCCESS; } @@ -380,7 +393,8 @@ static status_t switchto_new_sa(private_rekey_ike_sa_t* this, bool initiator) if (this->new_sa->derive_keys(this->new_sa, this->proposal, this->diffie_hellman, this->nonce_i, this->nonce_r, initiator, - this->ike_sa->get_child_prf(this->ike_sa) + this->ike_sa->get_child_prf(this->ike_sa), + this->ike_sa->get_prf(this->ike_sa) ) != SUCCESS) { return FAILED; @@ -388,14 +402,9 @@ static status_t switchto_new_sa(private_rekey_ike_sa_t* this, bool initiator) this->new_sa->set_state(this->new_sa, IKE_ESTABLISHED); - this->new_sa->adopt_children(this->new_sa, this->ike_sa); - this->new_sa->set_lifetimes(this->new_sa, this->connection->get_soft_lifetime(this->connection), this->connection->get_hard_lifetime(this->connection)); - - charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa); - this->new_sa = NULL; return SUCCESS; } @@ -433,7 +442,8 @@ static status_t get_response(private_rekey_ike_sa_t *this, message_t *request, host_t *me, *other; message_t *response; status_t status; - iterator_t *payloads; + iterator_t *payloads, *iterator; + child_sa_t *child_sa; sa_payload_t *sa_request = NULL; nonce_payload_t *nonce_request = NULL; ke_payload_t *ke_request = NULL; @@ -469,6 +479,33 @@ static status_t get_response(private_rekey_ike_sa_t *this, message_t *request, return FAILED; } + /* if we already initiate a delete, we do not allow rekeying */ + if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING) + { + build_notify(NO_PROPOSAL_CHOSEN, CHUNK_INITIALIZER, response, TRUE); + this->logger->log(this->logger, CONTROL, + "unable to rekey, as delete in progress. Sending NO_PROPOSAL_CHOSEN"); + return FAILED; + } + + /* if we have a CHILD which is "half-open", we do not allow rekeying */ + iterator = this->ike_sa->create_child_sa_iterator(this->ike_sa); + while (iterator->iterate(iterator, (void**)&child_sa)) + { + child_sa_state_t state = child_sa->get_state(child_sa); + if (state == CHILD_CREATED || + state == CHILD_REKEYING || + state == CHILD_DELETING) + { + build_notify(NO_PROPOSAL_CHOSEN, CHUNK_INITIALIZER, response, TRUE); + this->logger->log(this->logger, CONTROL, + "unable to rekey, one CHILD_SA is half open. Sending NO_PROPOSAL_CHOSEN"); + iterator->destroy(iterator); + return FAILED; + } + } + iterator->destroy(iterator); + /* apply for notify processing */ this->next = next; @@ -622,7 +659,49 @@ static status_t get_response(private_rekey_ike_sa_t *this, message_t *request, response->add_payload(response, (payload_t*)ke_response); } - return switchto_new_sa(this, FALSE); + status = switchto_new_sa(this, FALSE); + if (status != SUCCESS) + { + return status; + } + + /* IKE_SA successfully created. If another transaction is already rekeying + * this SA, our lower nonce must be registered for a later nonce compare. */ + { + private_rekey_ike_sa_t *other; + + other = this->ike_sa->get_rekeying_transaction(this->ike_sa); + if (other) + { + /* store our lower nonce in the simultaneus transaction, we + * will later compare it against his nonces when we calls conclude(). + * We do not adopt childrens yet, as we don't know if we'll win + * the race... + */ + if (memcmp(this->nonce_i.ptr, this->nonce_r.ptr, + min(this->nonce_i.len, this->nonce_r.len)) < 0) + { + other->nonce_s = chunk_clone(this->nonce_i); + } + else + { + other->nonce_s = chunk_clone(this->nonce_r); + } + /* overwrite "other" in IKE_SA, allows "other" to access "this" */ + this->ike_sa->set_rekeying_transaction(this->ike_sa, &this->public); + } + else + { + /* if we have no simultaneus transaction, we can safely adopt + * all children and complete. */ + this->new_sa->adopt_children(this->new_sa, this->ike_sa); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa); + this->new_sa = NULL; + } + this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); + } + + return SUCCESS; } /** @@ -636,6 +715,7 @@ static status_t conclude(private_rekey_ike_sa_t *this, message_t *response, sa_payload_t *sa_payload = NULL; nonce_payload_t *nonce_payload = NULL; ke_payload_t *ke_payload = NULL; + private_rekey_ike_sa_t *other_trans; status_t status; /* check message type */ @@ -726,18 +806,72 @@ static status_t conclude(private_rekey_ike_sa_t *this, message_t *response, ke_payload->get_key_exchange_data(ke_payload)); } - if (switchto_new_sa(this, TRUE) == SUCCESS) - { - /* new IKE_SA is in use now, delete old */ - *next = (transaction_t*)delete_ike_sa_create(this->ike_sa); - return SUCCESS; - } - else + if (switchto_new_sa(this, TRUE) != SUCCESS) { /* this should not happen. But if, we destroy both SAs */ *next = (transaction_t*)delete_ike_sa_create(this->new_sa); return DESTROY_ME; } + + /* IKE_SA successfully created. If the other peer initiated rekeying + * in the meantime, we detect this by comparing the rekeying_transaction + * of the SA. If it changed, we are not alone. Then we must compare the nonces. + * If no simultaneous rekeying is going on, we just initiate the delete of + * the superseded SA. */ + other_trans = this->ike_sa->get_rekeying_transaction(this->ike_sa); + this->ike_sa->set_rekeying_transaction(this->ike_sa, NULL); + + if (this->nonce_s.ptr) + { /* simlutaneous rekeying is going on, not so good */ + chunk_t this_lowest; + + /* first get our lowest nonce */ + if (memcmp(this->nonce_i.ptr, this->nonce_r.ptr, + min(this->nonce_i.len, this->nonce_r.len)) < 0) + { + this_lowest = this->nonce_i; + } + else + { + this_lowest = this->nonce_r; + } + /* then compare against other lowest nonce */ + if (memcmp(this_lowest.ptr, this->nonce_s.ptr, + min(this_lowest.len, this->nonce_s.len)) < 0) + { + this->logger->log(this->logger, ERROR, + "detected simultaneous IKE_SA rekeying, deleting ours"); + this->lost = TRUE; + } + else + { + this->logger->log(this->logger, ERROR, + "detected simultaneous IKE_SA rekeying, but ours is preferred"); + } + if (this->lost) + { + /* the other has won, he gets our children */ + other_trans->new_sa->adopt_children(other_trans->new_sa, this->ike_sa); + /* we have lost simlutaneous rekeying, delete the SA we just have created */ + this->new_sa->delete(this->new_sa); + } + /* other trans' SA is still not checked in, so do it now. It's SA will get + * deleted by remote peer. */ + charon->ike_sa_manager->checkin(charon->ike_sa_manager, other_trans->new_sa); + other_trans->new_sa = NULL; + } + + if (!this->lost) + { + /* we have won. delete old IKE_SA, and migrate all children */ + *next = (transaction_t*)delete_ike_sa_create(this->ike_sa); + this->new_sa->adopt_children(this->new_sa, this->ike_sa); + } + + charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa); + this->new_sa = NULL; + + return SUCCESS; } /** diff --git a/src/charon/sa/transactions/transaction.c b/src/charon/sa/transactions/transaction.c index b42b2c84f..adc8ce23c 100644 --- a/src/charon/sa/transactions/transaction.c +++ b/src/charon/sa/transactions/transaction.c @@ -70,7 +70,7 @@ transaction_t *transaction_create(ike_sa_t *ike_sa, message_t *request) } case CREATE_CHILD_SA: { - if (ike_sa->get_state(ike_sa) != IKE_ESTABLISHED) + if (ike_sa->get_state(ike_sa) < IKE_ESTABLISHED) { break; } @@ -114,7 +114,7 @@ transaction_t *transaction_create(ike_sa_t *ike_sa, message_t *request) } case INFORMATIONAL: { - if (ike_sa->get_state(ike_sa) == IKE_CREATED) + if (ike_sa->get_state(ike_sa) < IKE_ESTABLISHED) { break; }