diff --git a/src/libcharon/encoding/message.c b/src/libcharon/encoding/message.c index 032fc1b1c..f53be0036 100644 --- a/src/libcharon/encoding/message.c +++ b/src/libcharon/encoding/message.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -1408,7 +1409,19 @@ METHOD(message_t, generate, status_t, encrypted = this->rule->encrypted; } else - { /* if at least one payload requires encryption, encrypt the message */ + { + /* get a hash for this message, if any is required */ + chunk_t hash = keymat_v1->get_hash_phase2(keymat_v1, &this->public); + if (hash.ptr) + { /* insert a HASH payload as first payload */ + hash_payload_t *hash_payload = hash_payload_create(); + hash_payload->set_hash(hash_payload, hash); + this->payloads->insert_first(this->payloads, + (payload_t*)hash_payload); + chunk_free(&hash); + } + + /* if at least one payload requires encryption, encrypt the message */ /* TODO-IKEV1: set is_encrypted externally instead of this check? */ enumerator = this->payloads->create_enumerator(this->payloads); while (enumerator->enumerate(enumerator, (void**)&payload)) @@ -1821,7 +1834,7 @@ static status_t verify(private_message_t *this) DBG2(DBG_ENC, "verifying message structure"); - /* check for payloads with wrong count*/ + /* check for payloads with wrong count */ for (i = 0; i < this->rule->rule_count; i++) { enumerator_t *enumerator; @@ -1906,6 +1919,35 @@ METHOD(message_t, parse_body, status_t, DBG1(DBG_ENC, "parsed %s", get_string(this, str, sizeof(str))); + if (this->major_version == IKEV1_MAJOR_VERSION) + { + keymat_v1_t *keymat_v1 = (keymat_v1_t*)keymat; + chunk_t hash; + hash = keymat_v1->get_hash_phase2(keymat_v1, &this->public); + if (hash.ptr) + { + hash_payload_t *hash_payload; + chunk_t other_hash; + if (this->first_payload != HASH_V1) + { + DBG1(DBG_ENC, "expected HASH payload as first payload"); + chunk_free(&hash); + return VERIFY_ERROR; + } + hash_payload = (hash_payload_t*)get_payload(this, HASH_V1); + other_hash = hash_payload->get_hash(hash_payload); + if (!chunk_equals(hash, other_hash)) + { + DBG1(DBG_ENC, "our hash does not match received %B", + &other_hash); + chunk_free(&hash); + return VERIFY_ERROR; + } + DBG2(DBG_ENC, "verified IKEv1 message with hash %B", &hash); + chunk_free(&hash); + } + } + if (this->is_encrypted) { /* TODO-IKEv1: this should be done later when we know this is no * retransmit */ diff --git a/src/libcharon/sa/keymat_v1.c b/src/libcharon/sa/keymat_v1.c index 57b96754d..8b9ac498c 100644 --- a/src/libcharon/sa/keymat_v1.c +++ b/src/libcharon/sa/keymat_v1.c @@ -16,6 +16,8 @@ #include "keymat_v1.h" #include +#include +#include #include typedef struct private_keymat_v1_t private_keymat_v1_t; @@ -25,6 +27,11 @@ typedef struct private_keymat_v1_t private_keymat_v1_t; */ #define MAX_IV 3 +/** + * Max. number of Quick Modes to track. + */ +#define MAX_QM 2 + /** * Data stored for IVs */ @@ -97,6 +104,12 @@ struct private_keymat_v1_t { * number of IVs in an MRU sort of way. Stores iv_data_t objects. */ linked_list_t *ivs; + + /** + * Keep track of Nonces during Quick Mode exchanges. Only a limited number + * of QMs are tracked at the same time. Stores qm_data_t objects. + */ + linked_list_t *qms; }; @@ -110,6 +123,28 @@ static void iv_data_destroy(iv_data_t *this) free(this); } +/** + * Data stored for Quick Mode exchanges + */ +typedef struct { + /** message ID */ + u_int32_t mid; + /** Ni_b (Nonce from first message) */ + chunk_t n_i; + /** Nr_b (Nonce from second message) */ + chunk_t n_r; +} qm_data_t; + +/** + * Destroy a qm_data_t object. + */ +static void qm_data_destroy(qm_data_t *this) +{ + chunk_free(&this->n_i); + chunk_free(&this->n_r); + free(this); +} + /** * Constants used in key derivation. */ @@ -616,6 +651,180 @@ METHOD(keymat_v1_t, get_hash, chunk_t, return hash; } +/** + * Get the nonce value found in the given message. + * Returns FALSE if none is found. + */ +static bool get_nonce(message_t *message, chunk_t *n) +{ + nonce_payload_t *nonce; + nonce = (nonce_payload_t*)message->get_payload(message, NONCE_V1); + if (nonce) + { + *n = nonce->get_nonce(nonce); + return TRUE; + } + return FALSE; +} + +/** + * Generate the message data in order to generate the hashes. + */ +static chunk_t get_message_data(message_t *message, generator_t *generator) +{ + payload_t *payload, *next; + enumerator_t *enumerator; + u_int32_t *lenpos; + + if (message->is_encoded(message)) + { /* inbound, although the message is generated, we cannot access the + * cleartext message data, so generate it anyway */ + enumerator = message->create_payload_enumerator(message); + while (enumerator->enumerate(enumerator, &payload)) + { + if (payload->get_type(payload) == HASH_V1) + { + continue; + } + generator->generate_payload(generator, payload); + } + enumerator->destroy(enumerator); + } + else + { + /* outbound, generate the payloads (there is no HASH payload yet) */ + enumerator = message->create_payload_enumerator(message); + if (enumerator->enumerate(enumerator, &payload)) + { + while (enumerator->enumerate(enumerator, &next)) + { + payload->set_next_type(payload, next->get_type(next)); + generator->generate_payload(generator, payload); + payload = next; + } + payload->set_next_type(payload, NO_PAYLOAD); + generator->generate_payload(generator, payload); + } + enumerator->destroy(enumerator); + } + return generator->get_chunk(generator, &lenpos); +} + +/** + * Try to find data about a Quick Mode with the given message ID, + * if none is found, state is generated. + */ +static qm_data_t *lookup_quick_mode(private_keymat_v1_t *this, u_int32_t mid) +{ + enumerator_t *enumerator; + qm_data_t *qm, *found = NULL; + + enumerator = this->qms->create_enumerator(this->qms); + while (enumerator->enumerate(enumerator, &qm)) + { + if (qm->mid == mid) + { /* state gets moved to the front of the list */ + this->qms->remove_at(this->qms, enumerator); + found = qm; + break; + } + } + enumerator->destroy(enumerator); + if (!found) + { + INIT(found, + .mid = mid, + ); + } + this->qms->insert_first(this->qms, found); + /* remove least recently used state if maximum reached */ + if (this->qms->get_count(this->qms) > MAX_QM && + this->qms->remove_last(this->qms, (void**)&qm) == SUCCESS) + { + qm_data_destroy(qm); + } + return found; +} + +METHOD(keymat_v1_t, get_hash_phase2, chunk_t, + private_keymat_v1_t *this, message_t *message) +{ + u_int32_t mid = message->get_message_id(message), mid_n = htonl(mid); + chunk_t data = chunk_empty, hash = chunk_empty; + bool add_message = TRUE; + char *name = "Hash"; + + /* Hashes are simple for most exchanges in Phase 2: + * Hash = prf(SKEYID_a, M-ID | Complete message after HASH payload) + * For Quick Mode there are three hashes: + * Hash(1) = same as above + * Hash(2) = prf(SKEYID_a, M-ID | Ni_b | Message after HASH payload) + * Hash(3) = prf(SKEYID_a, 0 | M-ID | Ni_b | Nr_b) + * So, for Quick Mode we keep track of the nonce values. + */ + switch (message->get_exchange_type(message)) + { + case QUICK_MODE: + { + qm_data_t *qm = lookup_quick_mode(this, mid); + if (!qm->n_i.ptr) + { /* Hash(1) = prf(SKEYID_a, M-ID | Message after HASH payload) */ + name = "Hash(1)"; + if (!get_nonce(message, &qm->n_i)) + { + return hash; + } + data = chunk_from_thing(mid_n); + } + else if (!qm->n_r.ptr) + { /* Hash(2) = prf(SKEYID_a, M-ID | Ni_b | Message after HASH) */ + name = "Hash(2)"; + if (!get_nonce(message, &qm->n_r)) + { + return hash; + } + data = chunk_cata("cc", chunk_from_thing(mid_n), qm->n_i); + } + else + { /* Hash(3) = prf(SKEYID_a, 0 | M-ID | Ni_b | Nr_b) */ + name = "Hash(3)"; + data = chunk_cata("cccc", octet_0, chunk_from_thing(mid_n), + qm->n_i, qm->n_r); + add_message = FALSE; + /* we don't need the state anymore */ + this->qms->remove(this->qms, qm, NULL); + qm_data_destroy(qm); + } + break; + } + case TRANSACTION: + case INFORMATIONAL_V1: + /* Hash = prf(SKEYID_a, M-ID | Message after HASH payload) */ + data = chunk_from_thing(mid_n); + break; + default: + break; + } + if (data.ptr) + { + this->prf->set_key(this->prf, this->skeyid_a); + if (add_message) + { + generator_t *generator = generator_create(); + chunk_t msg = get_message_data(message, generator); + this->prf->allocate_bytes(this->prf, data, NULL); + this->prf->allocate_bytes(this->prf, msg, &hash); + generator->destroy(generator); + } + else + { + this->prf->allocate_bytes(this->prf, data, &hash); + } + DBG3(DBG_IKE, "%s %B", name, &hash); + } + return hash; +} + /** * Generate an IV */ @@ -734,6 +943,7 @@ METHOD(keymat_t, destroy, void, chunk_free(&this->phase1_iv.iv); chunk_free(&this->phase1_iv.last_block); this->ivs->destroy_function(this->ivs, (void*)iv_data_destroy); + this->qms->destroy_function(this->qms, (void*)qm_data_destroy); free(this); } @@ -754,11 +964,13 @@ keymat_v1_t *keymat_v1_create(bool initiator) .derive_ike_keys = _derive_ike_keys, .derive_child_keys = _derive_child_keys, .get_hash = _get_hash, + .get_hash_phase2 = _get_hash_phase2, .get_iv = _get_iv, .update_iv = _update_iv, .confirm_iv = _confirm_iv, }, .ivs = linked_list_create(), + .qms = linked_list_create(), .initiator = initiator, .prf_alg = PRF_UNDEFINED, ); diff --git a/src/libcharon/sa/keymat_v1.h b/src/libcharon/sa/keymat_v1.h index fb5a1b684..e2d5cf657 100644 --- a/src/libcharon/sa/keymat_v1.h +++ b/src/libcharon/sa/keymat_v1.h @@ -88,6 +88,15 @@ struct keymat_v1_t { chunk_t dh, chunk_t dh_other, ike_sa_id_t *ike_sa_id, chunk_t sa_i, identification_t *id); + /** + * Get HASH data for integrity/authentication in Phase 2 exchanges. + * + * @param message message to generate the HASH data for + * @return allocated HASH data + */ + chunk_t (*get_hash_phase2)(keymat_v1_t *this, message_t *message); + + /** * Returns the IV for a message with the given message ID. *