From: Tobias Brunner Date: Thu, 31 Oct 2019 16:16:44 +0000 (+0100) Subject: wip: ike-init: Prototypical IKE_INTERMEDIATE exchange for additional KE methods X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=a1b070ec1ebfe9358aac0920cd77962d08ad585c;p=thirdparty%2Fstrongswan.git wip: ike-init: Prototypical IKE_INTERMEDIATE exchange for additional KE methods --- diff --git a/src/libcharon/sa/ikev2/tasks/ike_init.c b/src/libcharon/sa/ikev2/tasks/ike_init.c index e3ee9d7892..d5933ee139 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_init.c +++ b/src/libcharon/sa/ikev2/tasks/ike_init.c @@ -32,6 +32,10 @@ /** maximum retries to do with cookies/other dh groups */ #define MAX_RETRIES 5 +/** maximum number of key exchanges (including the initial one) */ +#define MAX_KEY_EXCHANGES (ADDITIONAL_KEY_EXCHANGE_7 - \ + ADDITIONAL_KEY_EXCHANGE_1 + 2) + typedef struct private_ike_init_t private_ike_init_t; /** @@ -55,19 +59,33 @@ struct private_ike_init_t { bool initiator; /** - * diffie hellman group to use + * 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; + + /** + * Key exchange method from the parsed or sent KE payload */ - key_exchange_method_t dh_group; + key_exchange_method_t ke_method; /** - * diffie hellman key exchange + * Key exchange object */ - key_exchange_t *dh; + key_exchange_t *ke; /** - * Applying DH public value failed? + * Applying KE public key failed? */ - bool dh_failed; + bool ke_failed; /** * Keymat derivation (from IKE_SA) @@ -333,7 +351,7 @@ static bool build_payloads(private_ike_init_t *this, message_t *message) } /* move the selected DH group to the front of the proposal */ if (!proposal->promote_transform(proposal, KEY_EXCHANGE_METHOD, - this->dh_group)) + this->ke_method)) { /* the proposal does not include the group, move to the back */ proposal_list->remove_at(proposal_list, enumerator); other_dh_groups->insert_last(other_dh_groups, proposal); @@ -364,25 +382,17 @@ static bool build_payloads(private_ike_init_t *this, message_t *message) message->add_payload(message, (payload_t*)sa_payload); ke_payload = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE, - this->dh); + this->ke); if (!ke_payload) { DBG1(DBG_IKE, "creating KE payload failed"); return FALSE; } + message->add_payload(message, (payload_t*)ke_payload); + nonce_payload = nonce_payload_create(PLV2_NONCE); nonce_payload->set_nonce(nonce_payload, this->my_nonce); - - if (this->old_sa) - { /* payload order differs if we are rekeying */ - message->add_payload(message, (payload_t*)nonce_payload); - message->add_payload(message, (payload_t*)ke_payload); - } - else - { - message->add_payload(message, (payload_t*)ke_payload); - message->add_payload(message, (payload_t*)nonce_payload); - } + message->add_payload(message, (payload_t*)nonce_payload); /* negotiate fragmentation if we are not rekeying */ if (!this->old_sa && @@ -512,6 +522,102 @@ static void process_sa_payload(private_ike_init_t *this, message_t *message, offsetof(proposal_t, destroy)); } +/** + * Collect all key exchanges from the proposal + */ +static void determine_key_exchanges(private_ike_init_t *this) +{ + transform_type_t t = KEY_EXCHANGE_METHOD; + uint16_t alg; + int i = 1; + + this->proposal->get_algorithm(this->proposal, t, &alg, NULL); + this->key_exchanges[0].type = t; + this->key_exchanges[0].method = alg; + + for (t = ADDITIONAL_KEY_EXCHANGE_1; t < ADDITIONAL_KEY_EXCHANGE_7; t++) + { + if (this->proposal->get_algorithm(this->proposal, t, &alg, NULL)) + { + this->key_exchanges[i].type = t; + this->key_exchanges[i].method = alg; + i++; + } + } +} + +/** + * Check if additional key exchanges are required + */ +static bool additional_key_exchange_required(private_ike_init_t *this) +{ + int i; + + for (i = this->ke_index; i < MAX_KEY_EXCHANGES; i++) + { + if (this->key_exchanges[i].type && !this->key_exchanges[i].done) + { + return TRUE; + } + } + return FALSE; +} + +/** + * Clear data on key exchanges + */ +static void clear_key_exchanges(private_ike_init_t *this) +{ + int i; + + for (i = 0; i < MAX_KEY_EXCHANGES; i++) + { + this->key_exchanges[i].type = 0; + this->key_exchanges[i].method = 0; + this->key_exchanges[i].done = FALSE; + } + this->ke_index = 0; +} + +/** + * Process a KE payload + */ +static void process_ke_payload(private_ike_init_t *this, ke_payload_t *ke) +{ + key_exchange_method_t method = this->key_exchanges[this->ke_index].method; + key_exchange_method_t received = ke->get_key_exchange_method(ke); + + if (method != received) + { + DBG1(DBG_IKE, "key exchange method in received payload %N doesn't " + "match negotiated %N", key_exchange_method_names, received, + key_exchange_method_names, method); + this->ke_failed = TRUE; + return; + } + + if (!this->initiator) + { + this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat, + method); + if (!this->ke) + { + DBG1(DBG_IKE, "negotiated key exchange method %N not supported", + key_exchange_method_names, method); + } + } + else if (this->ke) + { + this->ke_failed = this->ke->get_method(this->ke) != received; + } + + if (this->ke && !this->ke_failed) + { + this->ke_failed = !this->ke->set_public_key(this->ke, + ke->get_key_exchange_data(ke)); + } +} + /** * Read payloads from message */ @@ -519,7 +625,7 @@ static void process_payloads(private_ike_init_t *this, message_t *message) { enumerator_t *enumerator; payload_t *payload; - ke_payload_t *ke_payload = NULL; + ke_payload_t *ke_pld = NULL; enumerator = message->create_payload_enumerator(message); while (enumerator->enumerate(enumerator, &payload)) @@ -533,9 +639,9 @@ static void process_payloads(private_ike_init_t *this, message_t *message) } case PLV2_KEY_EXCHANGE: { - ke_payload = (ke_payload_t*)payload; + ke_pld = (ke_payload_t*)payload; - this->dh_group = ke_payload->get_key_exchange_method(ke_payload); + this->ke_method = ke_pld->get_key_exchange_method(ke_pld); break; } case PLV2_NONCE: @@ -614,29 +720,69 @@ static void process_payloads(private_ike_init_t *this, message_t *message) if (this->proposal) { this->ike_sa->set_proposal(this->ike_sa, this->proposal); - } + determine_key_exchanges(this); - if (ke_payload && this->proposal && - this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD, - this->dh_group)) - { - if (!this->initiator) + if (ke_pld) { - this->dh = this->keymat->keymat.create_ke( - &this->keymat->keymat, this->dh_group); - } - else if (this->dh) - { - this->dh_failed = this->dh->get_method(this->dh) != this->dh_group; - } - if (this->dh && !this->dh_failed) - { - this->dh_failed = !this->dh->set_public_key(this->dh, - ke_payload->get_key_exchange_data(ke_payload)); + process_ke_payload(this, ke_pld); } } } +/** + * Build payloads during IKE_INTERMEDIATE exchanges + */ +static bool build_payloads_intermediate(private_ike_init_t *this, + message_t *message) +{ + ke_payload_t *ke; + nonce_payload_t *nonce; + + ke = ke_payload_create_from_key_exchange(PLV2_KEY_EXCHANGE, this->ke); + if (!ke) + { + DBG1(DBG_IKE, "creating KE payload failed"); + return FALSE; + } + message->add_payload(message, (payload_t*)ke); + + nonce = nonce_payload_create(PLV2_NONCE); + nonce->set_nonce(nonce, this->my_nonce); + message->add_payload(message, (payload_t*)nonce); + return TRUE; +} + +METHOD(task_t, build_i_intermediate, status_t, + private_ike_init_t *this, message_t *message) +{ + key_exchange_method_t method; + + message->set_exchange_type(message, IKE_INTERMEDIATE); + + 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; + } + + chunk_free(&this->my_nonce); + if (!generate_nonce(this)) + { + return FAILED; + } + + if (!build_payloads_intermediate(this, message)) + { + return FAILED; + } + return NEED_MORE; +} + METHOD(task_t, build_i, status_t, private_ike_init_t *this, message_t *message) { @@ -657,9 +803,10 @@ METHOD(task_t, build_i, status_t, } /* if we are retrying after an INVALID_KE_PAYLOAD we already have one */ - if (!this->dh) + if (!this->ke) { - if (this->old_sa && lib->settings->get_bool(lib->settings, + if (this->old_sa && + lib->settings->get_bool(lib->settings, "%s.prefer_previous_dh_group", TRUE, lib->ns)) { /* reuse the DH group we used for the old IKE_SA when rekeying */ proposal_t *proposal; @@ -669,37 +816,37 @@ METHOD(task_t, build_i, status_t, if (proposal->get_algorithm(proposal, KEY_EXCHANGE_METHOD, &dh_group, NULL)) { - this->dh_group = dh_group; + this->ke_method = dh_group; } else { /* this shouldn't happen, but let's be safe */ - this->dh_group = ike_cfg->get_algorithm(ike_cfg, - KEY_EXCHANGE_METHOD); + this->ke_method = ike_cfg->get_algorithm(ike_cfg, + KEY_EXCHANGE_METHOD); } } else { - this->dh_group = ike_cfg->get_algorithm(ike_cfg, - KEY_EXCHANGE_METHOD); + this->ke_method = ike_cfg->get_algorithm(ike_cfg, + KEY_EXCHANGE_METHOD); } - this->dh = this->keymat->keymat.create_ke(&this->keymat->keymat, - this->dh_group); - if (!this->dh) + this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat, + this->ke_method); + if (!this->ke) { DBG1(DBG_IKE, "configured DH group %N not supported", - key_exchange_method_names, this->dh_group); + key_exchange_method_names, this->ke_method); return FAILED; } } - else if (this->dh->get_method(this->dh) != this->dh_group) + else if (this->ke->get_method(this->ke) != this->ke_method) { /* reset DH instance if group changed (INVALID_KE_PAYLOAD) */ - this->dh->destroy(this->dh); - this->dh = this->keymat->keymat.create_ke(&this->keymat->keymat, - this->dh_group); - if (!this->dh) + this->ke->destroy(this->ke); + this->ke = this->keymat->keymat.create_ke(&this->keymat->keymat, + this->ke_method); + if (!this->ke) { DBG1(DBG_IKE, "requested DH group %N not supported", - key_exchange_method_names, this->dh_group); + key_exchange_method_names, this->ke_method); return FAILED; } } @@ -736,6 +883,53 @@ METHOD(task_t, build_i, status_t, return NEED_MORE; } +/** + * Process payloads in IKE_INTERMEDIATE exchanges + */ +static void process_payloads_intermediate(private_ike_init_t *this, + message_t *message) +{ + ke_payload_t *ke; + nonce_payload_t *nonce; + + chunk_clear(&this->other_nonce); + nonce = (nonce_payload_t*)message->get_payload(message, PLV2_NONCE); + if (nonce) + { + this->other_nonce = nonce->get_nonce(nonce); + } + else + { + DBG1(DBG_IKE, "nonce payload missing in message"); + } + + ke = (ke_payload_t*)message->get_payload(message, PLV2_KEY_EXCHANGE); + if (ke) + { + process_ke_payload(this, ke); + } + else + { + DBG1(DBG_IKE, "KE payload missing in message"); + } +} + +METHOD(task_t, process_r_intermediate, status_t, + private_ike_init_t *this, message_t *message) +{ + if (message->get_exchange_type(message) == IKE_INTERMEDIATE) + { + DESTROY_IF(this->ke); + chunk_clear(&this->my_nonce); + if (!generate_nonce(this)) + { + return FAILED; + } + process_payloads_intermediate(this, message); + } + return NEED_MORE; +} + METHOD(task_t, process_r, status_t, private_ike_init_t *this, message_t *message) { @@ -777,11 +971,8 @@ static bool derive_keys(private_ike_init_t *this, ike_sa_id_t *id; id = this->ike_sa->get_id(this->ike_sa); - if (this->old_sa) - { - /* rekeying: Include old SKd, use old PRF, apply SPI */ - old_keymat = (keymat_v2_t*)this->old_sa->get_keymat(this->old_sa); - prf_alg = old_keymat->get_skd(old_keymat, &skd); + if (this->old_sa && this->old_sa != this->ike_sa) + { /* only for rekeying, not for additional key exchanges */ if (this->initiator) { id->set_responder_spi(id, this->proposal->get_spi(this->proposal)); @@ -791,16 +982,62 @@ static bool derive_keys(private_ike_init_t *this, id->set_initiator_spi(id, this->proposal->get_spi(this->proposal)); } } - if (!this->keymat->derive_ike_keys(this->keymat, this->proposal, this->dh, + if (this->old_sa) + { + old_keymat = (keymat_v2_t*)this->old_sa->get_keymat(this->old_sa); + prf_alg = old_keymat->get_skd(old_keymat, &skd); + } + + if (!this->keymat->derive_ike_keys(this->keymat, this->proposal, this->ke, nonce_i, nonce_r, id, prf_alg, skd)) { return FALSE; } - charon->bus->ike_keys(charon->bus, this->ike_sa, this->dh, chunk_empty, + this->key_exchanges[this->ke_index++].done = TRUE; + + charon->bus->ike_keys(charon->bus, this->ike_sa, this->ke, chunk_empty, nonce_i, nonce_r, this->old_sa, NULL, AUTH_NONE); return TRUE; } +METHOD(task_t, post_build_r, status_t, + private_ike_init_t *this, message_t *message) +{ + /* key derivation for additional key exchanges is like rekeying, so set our + * own SA as old SA to get SK_d */ + this->old_sa = this->ike_sa; + if (!derive_keys(this, this->other_nonce, this->my_nonce)) + { + DBG1(DBG_IKE, "%N key derivation failed", transform_type_names, + this->key_exchanges[this->ke_index].type); + return FAILED; + } + return additional_key_exchange_required(this) ? NEED_MORE : SUCCESS; +} + +METHOD(task_t, build_r_intermediate, status_t, + private_ike_init_t *this, message_t *message) +{ + if (!this->ke || !this->other_nonce.ptr) + { + message->add_notify(message, FALSE, INVALID_SYNTAX, chunk_empty); + return FAILED; + } + if (this->ke_failed) + { + message->add_notify(message, FALSE, NO_PROPOSAL_CHOSEN, chunk_empty); + return FAILED; + } + if (!build_payloads_intermediate(this, message)) + { + return FAILED; + } + /* we do the key derivation in post_build(), otherwise the response + * would already be generated using the new keys */ + this->public.task.post_build = _post_build_r; + return NEED_MORE; +} + METHOD(task_t, build_r, status_t, private_ike_init_t *this, message_t *message) { @@ -831,19 +1068,20 @@ METHOD(task_t, build_r, status_t, return FAILED; } - if (this->dh == NULL || + if (!this->ke || !this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD, - this->dh_group)) + this->ke_method)) { uint16_t group; if (this->proposal->get_algorithm(this->proposal, KEY_EXCHANGE_METHOD, - &group, NULL)) + &group, NULL) && + this->ke_method != group) { DBG1(DBG_IKE, "DH group %N unacceptable, requesting %N", - key_exchange_method_names, this->dh_group, + key_exchange_method_names, this->ke_method, key_exchange_method_names, group); - this->dh_group = group; + this->ke_method = group; group = htons(group); message->add_notify(message, FALSE, INVALID_KE_PAYLOAD, chunk_from_thing(group)); @@ -856,7 +1094,7 @@ METHOD(task_t, build_r, status_t, return FAILED; } - if (this->dh_failed) + if (this->ke_failed) { DBG1(DBG_IKE, "applying DH public value failed"); message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); @@ -874,6 +1112,12 @@ METHOD(task_t, build_r, status_t, message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); return FAILED; } + if (additional_key_exchange_required(this)) + { /* use IKE_INTERMEDIATE for additional key exchanges */ + this->public.task.build = _build_r_intermediate; + this->public.task.process = _process_r_intermediate; + return NEED_MORE; + } return SUCCESS; } @@ -960,6 +1204,36 @@ METHOD(task_t, pre_process_i, status_t, return SUCCESS; } +METHOD(task_t, post_process_i, status_t, + private_ike_init_t *this, message_t *message) +{ + /* key derivation for additional key exchanges is like rekeying, so set our + * own SA as old SA to get SK_d */ + this->old_sa = this->ike_sa; + if (!derive_keys(this, this->my_nonce, this->other_nonce)) + { + DBG1(DBG_IKE, "%N key derivation failed", transform_type_names, + this->key_exchanges[this->ke_index].type); + return FAILED; + } + return additional_key_exchange_required(this) ? NEED_MORE : SUCCESS; +} + +METHOD(task_t, process_i_intermediate, status_t, + private_ike_init_t *this, message_t *message) +{ + process_payloads_intermediate(this, message); + + if (this->ke_failed || !this->other_nonce.ptr) + { + return FAILED; + } + /* we do the key derivation in post_process(), otherwise calculating IntAuth + * would be done with the wrong keys */ + this->public.task.post_process = _post_process_i; + return NEED_MORE; +} + METHOD(task_t, process_i, status_t, private_ike_init_t *this, message_t *message) { @@ -982,14 +1256,14 @@ METHOD(task_t, process_i, status_t, chunk_t data; key_exchange_method_t bad_group; - bad_group = this->dh_group; + bad_group = this->ke_method; data = notify->get_notification_data(notify); - this->dh_group = ntohs(*((uint16_t*)data.ptr)); + this->ke_method = ntohs(*((uint16_t*)data.ptr)); DBG1(DBG_IKE, "peer didn't accept DH group %N, " "it requested %N", key_exchange_method_names, - bad_group, key_exchange_method_names, this->dh_group); + bad_group, key_exchange_method_names, this->ke_method); - if (this->old_sa == NULL) + if (!this->old_sa) { /* reset the IKE_SA if we are not rekeying */ this->ike_sa->reset(this->ike_sa, FALSE); } @@ -1063,19 +1337,18 @@ METHOD(task_t, process_i, status_t, if (this->proposal == NULL || this->other_nonce.len == 0 || this->my_nonce.len == 0) { - DBG1(DBG_IKE, "peers proposal selection invalid"); + DBG1(DBG_IKE, "peer's proposal selection invalid"); return FAILED; } - if (this->dh == NULL || - !this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD, - this->dh_group)) + if (!this->proposal->has_transform(this->proposal, KEY_EXCHANGE_METHOD, + this->ke_method)) { - DBG1(DBG_IKE, "peer DH group selection invalid"); + DBG1(DBG_IKE, "peer's DH group selection invalid"); return FAILED; } - if (this->dh_failed) + if (this->ke_failed) { DBG1(DBG_IKE, "applying DH public value failed"); return FAILED; @@ -1086,6 +1359,12 @@ METHOD(task_t, process_i, status_t, DBG1(DBG_IKE, "key derivation failed"); return FAILED; } + if (additional_key_exchange_required(this)) + { /* use IKE_INTERMEDIATE for additional key exchanges */ + this->public.task.build = _build_i_intermediate; + this->public.task.process = _process_i_intermediate; + return NEED_MORE; + } return SUCCESS; } @@ -1104,13 +1383,14 @@ METHOD(task_t, migrate, void, this->ike_sa = ike_sa; this->keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa); this->proposal = NULL; - this->dh_failed = FALSE; + this->ke_failed = FALSE; + clear_key_exchanges(this); } METHOD(task_t, destroy, void, private_ike_init_t *this) { - DESTROY_IF(this->dh); + DESTROY_IF(this->ke); DESTROY_IF(this->proposal); DESTROY_IF(this->nonceg); chunk_free(&this->my_nonce); @@ -1151,7 +1431,7 @@ ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa) }, .ike_sa = ike_sa, .initiator = initiator, - .dh_group = MODP_NONE, + .ke_method = MODP_NONE, .keymat = (keymat_v2_t*)ike_sa->get_keymat(ike_sa), .old_sa = old_sa, .signature_authentication = lib->settings->get_bool(lib->settings,