diff options
Diffstat (limited to 'src/libcharon/sa/ike_sa.c')
-rw-r--r-- | src/libcharon/sa/ike_sa.c | 557 |
1 files changed, 464 insertions, 93 deletions
diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index dcf9d5f2c..bcbff3211 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -46,7 +46,6 @@ #include "ike_sa.h" #include <library.h> -#include <hydra.h> #include <daemon.h> #include <collections/array.h> #include <utils/lexparser.h> @@ -57,6 +56,9 @@ #include <processing/jobs/rekey_ike_sa_job.h> #include <processing/jobs/retry_initiate_job.h> #include <sa/ikev2/tasks/ike_auth_lifetime.h> +#include <sa/ikev2/tasks/ike_reauth_complete.h> +#include <sa/ikev2/tasks/ike_redirect.h> +#include <credentials/sets/auth_cfg_wrapper.h> #ifdef ME #include <sa/ikev2/tasks/ike_me.h> @@ -239,6 +241,11 @@ struct private_ike_sa_t { u_int32_t keepalive_interval; /** + * The schedueld keep alive job, if any + */ + send_keepalive_job_t *keepalive_job; + + /** * interval for retries during initiation (e.g. if DNS resolution failed), * 0 to disable (default) */ @@ -278,6 +285,21 @@ struct private_ike_sa_t { * Maximum length of a single fragment, 0 for address-specific defaults */ size_t fragment_size; + + /** + * Whether to follow IKEv2 redirects + */ + bool follow_redirects; + + /** + * Original gateway address from which we got redirected + */ + host_t *redirected_from; + + /** + * Timestamps of redirect attempts to handle loops + */ + array_t *redirected_at; }; /** @@ -382,6 +404,12 @@ METHOD(ike_sa_t, set_other_host, void, this->other_host = other; } +METHOD(ike_sa_t, get_redirected_from, host_t*, + private_ike_sa_t *this) +{ + return this->redirected_from; +} + METHOD(ike_sa_t, get_peer_cfg, peer_cfg_t*, private_ike_sa_t *this) { @@ -455,6 +483,113 @@ static void flush_auth_cfgs(private_ike_sa_t *this) } } +METHOD(ike_sa_t, verify_peer_certificate, bool, + private_ike_sa_t *this) +{ + enumerator_t *e1, *e2, *certs; + auth_cfg_t *cfg, *cfg_done; + certificate_t *peer, *cert; + public_key_t *key; + auth_cfg_t *auth; + auth_cfg_wrapper_t *wrapper; + time_t not_before, not_after; + bool valid = TRUE, found; + + if (this->state != IKE_ESTABLISHED) + { + DBG1(DBG_IKE, "unable to verify peer certificate in state %N", + ike_sa_state_names, this->state); + return FALSE; + } + + if (!this->flush_auth_cfg && + lib->settings->get_bool(lib->settings, + "%s.flush_auth_cfg", FALSE, lib->ns)) + { /* we can do this check only once if auth configs are flushed */ + DBG1(DBG_IKE, "unable to verify peer certificate as authentication " + "information has been flushed"); + return FALSE; + } + this->public.set_condition(&this->public, COND_ONLINE_VALIDATION_SUSPENDED, + FALSE); + + e1 = this->peer_cfg->create_auth_cfg_enumerator(this->peer_cfg, FALSE); + e2 = array_create_enumerator(this->other_auths); + while (e1->enumerate(e1, &cfg)) + { + if (!e2->enumerate(e2, &cfg_done)) + { /* this should not happen as the authentication should never have + * succeeded */ + valid = FALSE; + break; + } + if ((uintptr_t)cfg_done->get(cfg_done, + AUTH_RULE_AUTH_CLASS) != AUTH_CLASS_PUBKEY) + { + continue; + } + peer = cfg_done->get(cfg_done, AUTH_RULE_SUBJECT_CERT); + if (!peer) + { + DBG1(DBG_IKE, "no subject certificate found, skipping certificate " + "verification"); + continue; + } + if (!peer->get_validity(peer, NULL, ¬_before, ¬_after)) + { + DBG1(DBG_IKE, "peer certificate invalid (valid from %T to %T)", + ¬_before, FALSE, ¬_after, FALSE); + valid = FALSE; + break; + } + key = peer->get_public_key(peer); + if (!key) + { + DBG1(DBG_IKE, "unable to retrieve public key, skipping certificate " + "verification"); + continue; + } + DBG1(DBG_IKE, "verifying peer certificate"); + /* serve received certificates */ + wrapper = auth_cfg_wrapper_create(cfg_done); + lib->credmgr->add_local_set(lib->credmgr, &wrapper->set, FALSE); + certs = lib->credmgr->create_trusted_enumerator(lib->credmgr, + key->get_type(key), peer->get_subject(peer), TRUE); + key->destroy(key); + + found = FALSE; + while (certs->enumerate(certs, &cert, &auth)) + { + if (peer->equals(peer, cert)) + { + cfg_done->add(cfg_done, AUTH_RULE_CERT_VALIDATION_SUSPENDED, + FALSE); + cfg_done->merge(cfg_done, auth, FALSE); + valid = cfg_done->complies(cfg_done, cfg, TRUE); + found = TRUE; + break; + } + } + certs->destroy(certs); + lib->credmgr->remove_local_set(lib->credmgr, &wrapper->set); + wrapper->destroy(wrapper); + if (!found || !valid) + { + valid = FALSE; + break; + } + } + e1->destroy(e1); + e2->destroy(e2); + + if (this->flush_auth_cfg) + { + this->flush_auth_cfg = FALSE; + flush_auth_cfgs(this); + } + return valid; +} + METHOD(ike_sa_t, get_proposal, proposal_t*, private_ike_sa_t *this) { @@ -482,14 +617,20 @@ METHOD(ike_sa_t, set_message_id, void, } METHOD(ike_sa_t, send_keepalive, void, - private_ike_sa_t *this) + private_ike_sa_t *this, bool scheduled) { - send_keepalive_job_t *job; time_t last_out, now, diff; - if (!(this->conditions & COND_NAT_HERE) || this->keepalive_interval == 0 || - this->state == IKE_PASSIVE) - { /* disable keep alives if we are not NATed anymore, or we are passive */ + if (scheduled) + { + this->keepalive_job = NULL; + } + if (!this->keepalive_interval || this->state == IKE_PASSIVE) + { /* keepalives disabled either by configuration or for passive IKE_SAs */ + return; + } + if (!(this->conditions & COND_NAT_HERE) || (this->conditions & COND_STALE)) + { /* disable keepalives if we are not NATed anymore, or the SA is stale */ return; } @@ -514,9 +655,12 @@ METHOD(ike_sa_t, send_keepalive, void, charon->sender->send_no_marker(charon->sender, packet); diff = 0; } - job = send_keepalive_job_create(this->ike_sa_id); - lib->scheduler->schedule_job(lib->scheduler, (job_t*)job, - this->keepalive_interval - diff); + if (!this->keepalive_job) + { + this->keepalive_job = send_keepalive_job_create(this->ike_sa_id); + lib->scheduler->schedule_job(lib->scheduler, (job_t*)this->keepalive_job, + this->keepalive_interval - diff); + } } METHOD(ike_sa_t, get_ike_cfg, ike_cfg_t*, @@ -563,7 +707,7 @@ METHOD(ike_sa_t, set_condition, void, case COND_NAT_HERE: DBG1(DBG_IKE, "local host is behind NAT, sending keep alives"); this->conditions |= COND_NAT_ANY; - send_keepalive(this); + send_keepalive(this, FALSE); break; case COND_NAT_THERE: DBG1(DBG_IKE, "remote host is behind NAT"); @@ -590,6 +734,9 @@ METHOD(ike_sa_t, set_condition, void, has_condition(this, COND_NAT_THERE) || has_condition(this, COND_NAT_FAKE)); break; + case COND_STALE: + send_keepalive(this, FALSE); + break; default: break; } @@ -727,6 +874,8 @@ METHOD(ike_sa_t, set_state, void, { keepalives = TRUE; } + DESTROY_IF(this->redirected_from); + this->redirected_from = NULL; } break; } @@ -749,7 +898,7 @@ METHOD(ike_sa_t, set_state, void, } if (keepalives) { - send_keepalive(this); + send_keepalive(this, FALSE); } } @@ -786,12 +935,12 @@ METHOD(ike_sa_t, add_virtual_ip, void, { char *iface; - if (hydra->kernel_interface->get_interface(hydra->kernel_interface, - this->my_host, &iface)) + if (charon->kernel->get_interface(charon->kernel, this->my_host, + &iface)) { DBG1(DBG_IKE, "installing new virtual IP %H", ip); - if (hydra->kernel_interface->add_ip(hydra->kernel_interface, - ip, -1, iface) == SUCCESS) + if (charon->kernel->add_ip(charon->kernel, ip, -1, + iface) == SUCCESS) { array_insert_create(&this->my_vips, ARRAY_TAIL, ip->clone(ip)); } @@ -828,8 +977,7 @@ METHOD(ike_sa_t, clear_virtual_ips, void, { if (local) { - hydra->kernel_interface->del_ip(hydra->kernel_interface, - vip, -1, TRUE); + charon->kernel->del_ip(charon->kernel, vip, -1, TRUE); } vip->destroy(vip); } @@ -1265,8 +1413,8 @@ static void resolve_hosts(private_ike_sa_t *this) !this->other_host->is_anyaddr(this->other_host)) { host->destroy(host); - host = hydra->kernel_interface->get_source_addr( - hydra->kernel_interface, this->other_host, NULL); + host = charon->kernel->get_source_addr(charon->kernel, + this->other_host, NULL); if (host) { host->set_port(host, this->ike_cfg->get_my_port(this->ike_cfg)); @@ -1401,9 +1549,14 @@ METHOD(ike_sa_t, process_message, status_t, status = this->task_manager->process_message(this->task_manager, message); if (this->flush_auth_cfg && this->state == IKE_ESTABLISHED) { - /* authentication completed */ - this->flush_auth_cfg = FALSE; - flush_auth_cfgs(this); + /* authentication completed but if the online validation is suspended we + * need the auth cfgs until we did the delayed verification, we flush + * them afterwards */ + if (!has_condition(this, COND_ONLINE_VALIDATION_SUSPENDED)) + { + this->flush_auth_cfg = FALSE; + flush_auth_cfgs(this); + } } return status; } @@ -1735,6 +1888,86 @@ static bool is_child_queued(private_ike_sa_t *this, task_queue_t queue) return found; } +/** + * Reestablish CHILD_SAs and migrate queued tasks. + * + * If force is true all SAs are restarted, otherwise their close/dpd_action + * is followed. + */ +static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new, + bool force) +{ + enumerator_t *enumerator; + child_sa_t *child_sa; + child_cfg_t *child_cfg; + action_t action; + status_t status = FAILED; + + /* handle existing CHILD_SAs */ + enumerator = create_child_sa_enumerator(this); + while (enumerator->enumerate(enumerator, (void**)&child_sa)) + { + if (force) + { + switch (child_sa->get_state(child_sa)) + { + case CHILD_ROUTED: + { /* move routed child directly */ + remove_child_sa(this, enumerator); + new->add_child_sa(new, child_sa); + action = ACTION_NONE; + break; + } + default: + { /* initiate/queue all other CHILD_SAs */ + action = ACTION_RESTART; + break; + } + } + } + else + { /* only restart CHILD_SAs that are configured accordingly */ + if (this->state == IKE_DELETING) + { + action = child_sa->get_close_action(child_sa); + } + else + { + action = child_sa->get_dpd_action(child_sa); + } + } + switch (action) + { + case ACTION_RESTART: + child_cfg = child_sa->get_config(child_sa); + DBG1(DBG_IKE, "restarting CHILD_SA %s", + child_cfg->get_name(child_cfg)); + child_cfg->get_ref(child_cfg); + status = new->initiate(new, child_cfg, + child_sa->get_reqid(child_sa), NULL, NULL); + break; + default: + continue; + } + if (status == DESTROY_ME) + { + break; + } + } + enumerator->destroy(enumerator); + /* adopt any active or queued CHILD-creating tasks */ + if (status != DESTROY_ME) + { + task_manager_t *other_tasks = ((private_ike_sa_t*)new)->task_manager; + other_tasks->adopt_child_tasks(other_tasks, this->task_manager); + if (new->get_state(new) == IKE_CREATED) + { + status = new->initiate(new, NULL, 0, NULL, NULL); + } + } + return status; +} + METHOD(ike_sa_t, reestablish, status_t, private_ike_sa_t *this) { @@ -1743,7 +1976,6 @@ METHOD(ike_sa_t, reestablish, status_t, action_t action; enumerator_t *enumerator; child_sa_t *child_sa; - child_cfg_t *child_cfg; bool restart = FALSE; status_t status = FAILED; @@ -1836,8 +2068,11 @@ METHOD(ike_sa_t, reestablish, status_t, host = this->my_host; new->set_my_host(new, host->clone(host)); charon->bus->ike_reestablish_pre(charon->bus, &this->public, new); - /* resolve hosts but use the old addresses above as fallback */ - resolve_hosts((private_ike_sa_t*)new); + if (!has_condition(this, COND_REAUTHENTICATING)) + { /* reauthenticate to the same addresses, but resolve hosts if + * reestablishing (old addresses serve as fallback) */ + resolve_hosts((private_ike_sa_t*)new); + } /* if we already have a virtual IP, we reuse it */ enumerator = array_create_enumerator(this->my_vips); while (enumerator->enumerate(enumerator, &host)) @@ -1854,68 +2089,8 @@ METHOD(ike_sa_t, reestablish, status_t, else #endif /* ME */ { - /* handle existing CHILD_SAs */ - enumerator = create_child_sa_enumerator(this); - while (enumerator->enumerate(enumerator, (void**)&child_sa)) - { - if (has_condition(this, COND_REAUTHENTICATING)) - { - switch (child_sa->get_state(child_sa)) - { - case CHILD_ROUTED: - { /* move routed child directly */ - remove_child_sa(this, enumerator); - new->add_child_sa(new, child_sa); - action = ACTION_NONE; - break; - } - default: - { /* initiate/queue all other CHILD_SAs */ - action = ACTION_RESTART; - break; - } - } - } - else - { /* only restart CHILD_SAs that are configured accordingly */ - if (this->state == IKE_DELETING) - { - action = child_sa->get_close_action(child_sa); - } - else - { - action = child_sa->get_dpd_action(child_sa); - } - } - switch (action) - { - case ACTION_RESTART: - child_cfg = child_sa->get_config(child_sa); - DBG1(DBG_IKE, "restarting CHILD_SA %s", - child_cfg->get_name(child_cfg)); - child_cfg->get_ref(child_cfg); - status = new->initiate(new, child_cfg, - child_sa->get_reqid(child_sa), NULL, NULL); - break; - default: - continue; - } - if (status == DESTROY_ME) - { - break; - } - } - enumerator->destroy(enumerator); - /* adopt any active or queued CHILD-creating tasks */ - if (status != DESTROY_ME) - { - task_manager_t *other_tasks = ((private_ike_sa_t*)new)->task_manager; - other_tasks->adopt_child_tasks(other_tasks, this->task_manager); - if (new->get_state(new) == IKE_CREATED) - { - status = new->initiate(new, NULL, 0, NULL, NULL); - } - } + status = reestablish_children(this, new, + has_condition(this, COND_REAUTHENTICATING)); } if (status == DESTROY_ME) @@ -1936,6 +2111,195 @@ METHOD(ike_sa_t, reestablish, status_t, return status; } +/** + * Resolve the given gateway ID + */ +static host_t *resolve_gateway_id(identification_t *gateway) +{ + char gw[BUF_LEN]; + host_t *addr; + + snprintf(gw, sizeof(gw), "%Y", gateway); + gw[sizeof(gw)-1] = '\0'; + addr = host_create_from_dns(gw, AF_UNSPEC, IKEV2_UDP_PORT); + if (!addr) + { + DBG1(DBG_IKE, "unable to resolve gateway ID '%Y', redirect failed", + gateway); + } + return addr; +} + +/** + * Redirect the current SA to the given target host + */ +static bool redirect_established(private_ike_sa_t *this, identification_t *to) +{ + private_ike_sa_t *new_priv; + ike_sa_t *new; + host_t *other; + time_t redirect; + + new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, + this->version, TRUE); + if (!new) + { + return FALSE; + } + new_priv = (private_ike_sa_t*)new; + new->set_peer_cfg(new, this->peer_cfg); + new_priv->redirected_from = this->other_host->clone(this->other_host); + charon->bus->ike_reestablish_pre(charon->bus, &this->public, new); + other = resolve_gateway_id(to); + if (other) + { + set_my_host(new_priv, this->my_host->clone(this->my_host)); + /* this allows us to force the remote address while we still properly + * resolve the local address */ + new_priv->remote_host = other; + resolve_hosts(new_priv); + new_priv->redirected_at = array_create(sizeof(time_t), MAX_REDIRECTS); + while (array_remove(this->redirected_at, ARRAY_HEAD, &redirect)) + { + array_insert(new_priv->redirected_at, ARRAY_TAIL, &redirect); + } + if (reestablish_children(this, new, TRUE) != DESTROY_ME) + { +#ifdef USE_IKEV2 + new->queue_task(new, (task_t*)ike_reauth_complete_create(new, + this->ike_sa_id)); +#endif + charon->bus->ike_reestablish_post(charon->bus, &this->public, new, + TRUE); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, new); + charon->bus->set_sa(charon->bus, &this->public); + return TRUE; + } + } + charon->bus->ike_reestablish_post(charon->bus, &this->public, new, + FALSE); + charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, new); + charon->bus->set_sa(charon->bus, &this->public); + return FALSE; +} + +/** + * Redirect the current connecting SA to the given target host + */ +static bool redirect_connecting(private_ike_sa_t *this, identification_t *to) +{ + host_t *other; + + other = resolve_gateway_id(to); + if (!other) + { + return FALSE; + } + reset(this); + DESTROY_IF(this->redirected_from); + this->redirected_from = this->other_host->clone(this->other_host); + DESTROY_IF(this->remote_host); + /* this allows us to force the remote address while we still properly + * resolve the local address */ + this->remote_host = other; + resolve_hosts(this); + return TRUE; +} + +/** + * Check if the current redirect exceeds the limits for redirects + */ +static bool redirect_count_exceeded(private_ike_sa_t *this) +{ + time_t now, redirect; + + now = time_monotonic(NULL); + /* remove entries outside the defined period */ + while (array_get(this->redirected_at, ARRAY_HEAD, &redirect) && + now - redirect >= REDIRECT_LOOP_DETECT_PERIOD) + { + array_remove(this->redirected_at, ARRAY_HEAD, NULL); + } + if (array_count(this->redirected_at) < MAX_REDIRECTS) + { + if (!this->redirected_at) + { + this->redirected_at = array_create(sizeof(time_t), MAX_REDIRECTS); + } + array_insert(this->redirected_at, ARRAY_TAIL, &now); + return FALSE; + } + return TRUE; +} + +METHOD(ike_sa_t, handle_redirect, bool, + private_ike_sa_t *this, identification_t *gateway) +{ + DBG1(DBG_IKE, "redirected to %Y", gateway); + if (!this->follow_redirects) + { + DBG1(DBG_IKE, "server sent REDIRECT even though we disabled it"); + return FALSE; + } + if (redirect_count_exceeded(this)) + { + DBG1(DBG_IKE, "only %d redirects are allowed within %d seconds", + MAX_REDIRECTS, REDIRECT_LOOP_DETECT_PERIOD); + return FALSE; + } + + switch (this->state) + { + case IKE_CONNECTING: + return redirect_connecting(this, gateway); + case IKE_ESTABLISHED: + return redirect_established(this, gateway); + default: + DBG1(DBG_IKE, "unable to handle redirect for IKE_SA in state %N", + ike_sa_state_names, this->state); + return FALSE; + } +} + +METHOD(ike_sa_t, redirect, status_t, + private_ike_sa_t *this, identification_t *gateway) +{ + switch (this->state) + { + case IKE_CONNECTING: + case IKE_ESTABLISHED: + case IKE_REKEYING: + if (has_condition(this, COND_REDIRECTED)) + { /* IKE_SA already got redirected */ + return SUCCESS; + } + if (has_condition(this, COND_ORIGINAL_INITIATOR)) + { + DBG1(DBG_IKE, "unable to redirect IKE_SA as initiator"); + return FAILED; + } + if (this->version == IKEV1) + { + DBG1(DBG_IKE, "unable to redirect IKEv1 SA"); + return FAILED; + } + if (!supports_extension(this, EXT_IKE_REDIRECTION)) + { + DBG1(DBG_IKE, "client does not support IKE redirection"); + return FAILED; + } +#ifdef USE_IKEV2 + this->task_manager->queue_task(this->task_manager, + (task_t*)ike_redirect_create(&this->public, gateway)); +#endif + return this->task_manager->initiate(this->task_manager); + default: + DBG1(DBG_IKE, "unable to redirect IKE_SA in state %N", + ike_sa_state_names, this->state); + return INVALID_STATE; + } +} + METHOD(ike_sa_t, retransmit, status_t, private_ike_sa_t *this, u_int32_t message_id) { @@ -2067,8 +2431,8 @@ static bool is_current_path_valid(private_ike_sa_t *this) { bool valid = FALSE; host_t *src; - src = hydra->kernel_interface->get_source_addr(hydra->kernel_interface, - this->other_host, this->my_host); + src = charon->kernel->get_source_addr(charon->kernel, this->other_host, + this->my_host); if (src) { if (src->ip_equals(src, this->my_host)) @@ -2112,8 +2476,7 @@ static bool is_any_path_valid(private_ike_sa_t *this) continue; } DBG1(DBG_IKE, "looking for a route to %H ...", addr); - src = hydra->kernel_interface->get_source_addr( - hydra->kernel_interface, addr, NULL); + src = charon->kernel->get_source_addr(charon->kernel, addr, NULL); if (src) { break; @@ -2323,7 +2686,7 @@ METHOD(ike_sa_t, inherit_post, void, this->conditions = other->conditions; if (this->conditions & COND_NAT_HERE) { - send_keepalive(this); + send_keepalive(this, FALSE); } #ifdef ME @@ -2401,7 +2764,7 @@ METHOD(ike_sa_t, destroy, void, } while (array_remove(this->my_vips, ARRAY_TAIL, &vip)) { - hydra->kernel_interface->del_ip(hydra->kernel_interface, vip, -1, TRUE); + charon->kernel->del_ip(charon->kernel, vip, -1, TRUE); vip->destroy(vip); } if (array_count(this->other_vips)) @@ -2450,6 +2813,8 @@ METHOD(ike_sa_t, destroy, void, DESTROY_IF(this->other_id); DESTROY_IF(this->local_host); DESTROY_IF(this->remote_host); + DESTROY_IF(this->redirected_from); + array_destroy(this->redirected_at); DESTROY_IF(this->ike_cfg); DESTROY_IF(this->peer_cfg); @@ -2498,6 +2863,7 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, .set_peer_cfg = _set_peer_cfg, .get_auth_cfg = _get_auth_cfg, .create_auth_cfg_enumerator = _create_auth_cfg_enumerator, + .verify_peer_certificate = _verify_peer_certificate, .add_auth_cfg = _add_auth_cfg, .get_proposal = _get_proposal, .set_proposal = _set_proposal, @@ -2529,6 +2895,9 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, .destroy = _destroy, .send_dpd = _send_dpd, .send_keepalive = _send_keepalive, + .redirect = _redirect, + .handle_redirect = _handle_redirect, + .get_redirected_from = _get_redirected_from, .get_keymat = _get_keymat, .add_child_sa = _add_child_sa, .get_child_sa = _get_child_sa, @@ -2594,6 +2963,8 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator, "%s.flush_auth_cfg", FALSE, lib->ns), .fragment_size = lib->settings->get_int(lib->settings, "%s.fragment_size", 0, lib->ns), + .follow_redirects = lib->settings->get_bool(lib->settings, + "%s.follow_redirects", TRUE, lib->ns), ); if (version == IKEV2) |