/* * Copyright (C) 2008 Martin Willi * HSR Hochschule fuer Technik Rapperswil * * 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 . * * 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 "ha_ike.h" #include #include typedef struct private_ha_ike_t private_ha_ike_t; /** * Private data of an ha_ike_t object. */ struct private_ha_ike_t { /** * Public ha_ike_t interface. */ ha_ike_t public; /** * socket we use for syncing */ ha_socket_t *socket; /** * tunnel securing sync messages */ ha_tunnel_t *tunnel; /** * message cache */ ha_cache_t *cache; }; /** * Return condition if it is set on ike_sa */ static ike_condition_t copy_condition(ike_sa_t *ike_sa, ike_condition_t cond) { if (ike_sa->has_condition(ike_sa, cond)) { return cond; } return 0; } /** * Return extension if it is supported by peers IKE_SA */ static ike_extension_t copy_extension(ike_sa_t *ike_sa, ike_extension_t ext) { if (ike_sa->supports_extension(ike_sa, ext)) { return ext; } return 0; } METHOD(listener_t, ike_keys, bool, private_ha_ike_t *this, ike_sa_t *ike_sa, diffie_hellman_t *dh, chunk_t dh_other, chunk_t nonce_i, chunk_t nonce_r, ike_sa_t *rekey, shared_key_t *shared, auth_method_t method) { ha_message_t *m; chunk_t secret; proposal_t *proposal; uint16_t alg, len; if (this->tunnel && this->tunnel->is_sa(this->tunnel, ike_sa)) { /* do not sync SA between nodes */ return TRUE; } if (!dh->get_shared_secret(dh, &secret)) { return TRUE; } m = ha_message_create(HA_IKE_ADD); m->add_attribute(m, HA_IKE_VERSION, ike_sa->get_version(ike_sa)); m->add_attribute(m, HA_IKE_ID, ike_sa->get_id(ike_sa)); if (rekey && rekey->get_version(rekey) == IKEV2) { chunk_t skd; keymat_v2_t *keymat; keymat = (keymat_v2_t*)rekey->get_keymat(rekey); m->add_attribute(m, HA_IKE_REKEY_ID, rekey->get_id(rekey)); m->add_attribute(m, HA_ALG_OLD_PRF, keymat->get_skd(keymat, &skd)); m->add_attribute(m, HA_OLD_SKD, skd); } proposal = ike_sa->get_proposal(ike_sa); if (proposal->get_algorithm(proposal, ENCRYPTION_ALGORITHM, &alg, &len)) { m->add_attribute(m, HA_ALG_ENCR, alg); if (len) { m->add_attribute(m, HA_ALG_ENCR_LEN, len); } } if (proposal->get_algorithm(proposal, INTEGRITY_ALGORITHM, &alg, NULL)) { m->add_attribute(m, HA_ALG_INTEG, alg); } if (proposal->get_algorithm(proposal, PSEUDO_RANDOM_FUNCTION, &alg, NULL)) { m->add_attribute(m, HA_ALG_PRF, alg); } if (proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP, &alg, NULL)) { m->add_attribute(m, HA_ALG_DH, alg); } m->add_attribute(m, HA_NONCE_I, nonce_i); m->add_attribute(m, HA_NONCE_R, nonce_r); m->add_attribute(m, HA_SECRET, secret); chunk_clear(&secret); if (ike_sa->get_version(ike_sa) == IKEV1) { if (dh->get_my_public_value(dh, &secret)) { m->add_attribute(m, HA_LOCAL_DH, secret); chunk_free(&secret); } m->add_attribute(m, HA_REMOTE_DH, dh_other); if (shared) { m->add_attribute(m, HA_PSK, shared->get_key(shared)); } else { m->add_attribute(m, HA_AUTH_METHOD, method); } } m->add_attribute(m, HA_REMOTE_ADDR, ike_sa->get_other_host(ike_sa)); this->socket->push(this->socket, m); this->cache->cache(this->cache, ike_sa, m); return TRUE; } METHOD(listener_t, ike_updown, bool, private_ha_ike_t *this, ike_sa_t *ike_sa, bool up) { ha_message_t *m; if (ike_sa->get_state(ike_sa) == IKE_PASSIVE) { /* only sync active IKE_SAs */ return TRUE; } if (this->tunnel && this->tunnel->is_sa(this->tunnel, ike_sa)) { /* do not sync SA between nodes */ return TRUE; } if (up) { enumerator_t *enumerator; peer_cfg_t *peer_cfg; uint32_t extension, condition; host_t *addr; ike_sa_id_t *id; identification_t *eap_id; peer_cfg = ike_sa->get_peer_cfg(ike_sa); condition = copy_condition(ike_sa, COND_NAT_ANY) | copy_condition(ike_sa, COND_NAT_HERE) | copy_condition(ike_sa, COND_NAT_THERE) | copy_condition(ike_sa, COND_NAT_FAKE) | copy_condition(ike_sa, COND_EAP_AUTHENTICATED) | copy_condition(ike_sa, COND_CERTREQ_SEEN) | copy_condition(ike_sa, COND_ORIGINAL_INITIATOR) | copy_condition(ike_sa, COND_STALE) | copy_condition(ike_sa, COND_INIT_CONTACT_SEEN) | copy_condition(ike_sa, COND_XAUTH_AUTHENTICATED); extension = copy_extension(ike_sa, EXT_NATT) | copy_extension(ike_sa, EXT_MOBIKE) | copy_extension(ike_sa, EXT_HASH_AND_URL) | copy_extension(ike_sa, EXT_MULTIPLE_AUTH) | copy_extension(ike_sa, EXT_STRONGSWAN) | copy_extension(ike_sa, EXT_EAP_ONLY_AUTHENTICATION) | copy_extension(ike_sa, EXT_MS_WINDOWS) | copy_extension(ike_sa, EXT_XAUTH) | copy_extension(ike_sa, EXT_DPD); id = ike_sa->get_id(ike_sa); m = ha_message_create(HA_IKE_UPDATE); m->add_attribute(m, HA_IKE_ID, id); m->add_attribute(m, HA_LOCAL_ID, ike_sa->get_my_id(ike_sa)); m->add_attribute(m, HA_REMOTE_ID, ike_sa->get_other_id(ike_sa)); eap_id = ike_sa->get_other_eap_id(ike_sa); if (!eap_id->equals(eap_id, ike_sa->get_other_id(ike_sa))) { m->add_attribute(m, HA_REMOTE_EAP_ID, eap_id); } m->add_attribute(m, HA_LOCAL_ADDR, ike_sa->get_my_host(ike_sa)); m->add_attribute(m, HA_REMOTE_ADDR, ike_sa->get_other_host(ike_sa)); m->add_attribute(m, HA_CONDITIONS, condition); m->add_attribute(m, HA_EXTENSIONS, extension); m->add_attribute(m, HA_CONFIG_NAME, peer_cfg->get_name(peer_cfg)); enumerator = ike_sa->create_peer_address_enumerator(ike_sa); while (enumerator->enumerate(enumerator, (void**)&addr)) { m->add_attribute(m, HA_PEER_ADDR, addr); } enumerator->destroy(enumerator); } else { m = ha_message_create(HA_IKE_DELETE); m->add_attribute(m, HA_IKE_ID, ike_sa->get_id(ike_sa)); } this->socket->push(this->socket, m); this->cache->cache(this->cache, ike_sa, m); return TRUE; } METHOD(listener_t, ike_rekey, bool, private_ha_ike_t *this, ike_sa_t *old, ike_sa_t *new) { ike_updown(this, old, FALSE); ike_updown(this, new, TRUE); return TRUE; } METHOD(listener_t, alert, bool, private_ha_ike_t *this, ike_sa_t *ike_sa, alert_t alert, va_list args) { switch (alert) { case ALERT_HALF_OPEN_TIMEOUT: ike_updown(this, ike_sa, FALSE); break; default: break; } return TRUE; } METHOD(listener_t, ike_state_change, bool, private_ha_ike_t *this, ike_sa_t *ike_sa, ike_sa_state_t new) { /* delete any remaining cache entry if IKE_SA gets destroyed */ if (new == IKE_DESTROYING) { this->cache->delete(this->cache, ike_sa); } return TRUE; } /** * Send a virtual IP sync message for remote VIPs */ static void sync_vips(private_ha_ike_t *this, ike_sa_t *ike_sa) { ha_message_t *m = NULL; enumerator_t *enumerator; host_t *vip; enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, FALSE); while (enumerator->enumerate(enumerator, &vip)) { if (!m) { m = ha_message_create(HA_IKE_UPDATE); m->add_attribute(m, HA_IKE_ID, ike_sa->get_id(ike_sa)); } m->add_attribute(m, HA_REMOTE_VIP, vip); } enumerator->destroy(enumerator); if (m) { this->socket->push(this->socket, m); this->cache->cache(this->cache, ike_sa, m); } } METHOD(listener_t, message_hook, bool, private_ha_ike_t *this, ike_sa_t *ike_sa, message_t *message, bool incoming, bool plain) { if (this->tunnel && this->tunnel->is_sa(this->tunnel, ike_sa)) { /* do not sync SA between nodes */ return TRUE; } if (plain && ike_sa->get_version(ike_sa) == IKEV2) { if (message->get_exchange_type(message) != IKE_SA_INIT && message->get_request(message)) { /* we sync on requests, but skip it on IKE_SA_INIT */ ha_message_t *m; if (incoming) { m = ha_message_create(HA_IKE_MID_RESPONDER); } else { m = ha_message_create(HA_IKE_MID_INITIATOR); } m->add_attribute(m, HA_IKE_ID, ike_sa->get_id(ike_sa)); m->add_attribute(m, HA_MID, message->get_message_id(message) + 1); this->socket->push(this->socket, m); this->cache->cache(this->cache, ike_sa, m); } if (ike_sa->get_state(ike_sa) == IKE_ESTABLISHED && message->get_exchange_type(message) == IKE_AUTH && !message->get_request(message)) { /* After IKE_SA has been established, sync peers virtual IP. * We cannot sync it in the state_change hook, it is installed later. * TODO: where to sync local VIP? */ sync_vips(this, ike_sa); } } if (ike_sa->get_version(ike_sa) == IKEV1) { ha_message_t *m; keymat_v1_t *keymat; chunk_t iv; /* we need the last block (or expected next IV) of Phase 1, which gets * updated after successful en-/decryption depending on direction */ if (incoming == plain) { if (message->get_message_id(message) == 0) { keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa); if (keymat->get_iv(keymat, 0, &iv)) { m = ha_message_create(HA_IKE_IV); m->add_attribute(m, HA_IKE_ID, ike_sa->get_id(ike_sa)); m->add_attribute(m, HA_IV, iv); this->socket->push(this->socket, m); this->cache->cache(this->cache, ike_sa, m); } } } if (!plain && !incoming && message->get_exchange_type(message) == TRANSACTION) { sync_vips(this, ike_sa); } } if (plain && ike_sa->get_version(ike_sa) == IKEV1 && message->get_exchange_type(message) == INFORMATIONAL_V1) { ha_message_t *m; notify_payload_t *notify; chunk_t data; uint32_t seq; notify = message->get_notify(message, DPD_R_U_THERE); if (notify) { data = notify->get_notification_data(notify); if (data.len == 4) { seq = untoh32(data.ptr); if (incoming) { m = ha_message_create(HA_IKE_MID_RESPONDER); } else { m = ha_message_create(HA_IKE_MID_INITIATOR); } m->add_attribute(m, HA_IKE_ID, ike_sa->get_id(ike_sa)); m->add_attribute(m, HA_MID, seq + 1); this->socket->push(this->socket, m); this->cache->cache(this->cache, ike_sa, m); } } } return TRUE; } METHOD(ha_ike_t, destroy, void, private_ha_ike_t *this) { free(this); } /** * See header */ ha_ike_t *ha_ike_create(ha_socket_t *socket, ha_tunnel_t *tunnel, ha_cache_t *cache) { private_ha_ike_t *this; INIT(this, .public = { .listener = { .alert = _alert, .ike_keys = _ike_keys, .ike_updown = _ike_updown, .ike_rekey = _ike_rekey, .ike_state_change = _ike_state_change, .message = _message_hook, }, .destroy = _destroy, }, .socket = socket, .tunnel = tunnel, .cache = cache, ); return &this->public; }