diff options
author | Niklas Hallqvist <niklas@cvs.openbsd.org> | 1998-11-15 00:03:51 +0000 |
---|---|---|
committer | Niklas Hallqvist <niklas@cvs.openbsd.org> | 1998-11-15 00:03:51 +0000 |
commit | e79150e203c82fbba87b52246155ba5155b9f1e8 (patch) | |
tree | 7f85b06a6b243ecd185041e9212d7ea0b6ce4480 /sbin/isakmpd/ike_quick_mode.c | |
parent | 23065b1eddaddf11c9e23b9193afd76cf35de24d (diff) |
Initial import of isakmpd, an IKE (ISAKMP/Oakley) implementation for the
OpenBSD IPSEC stack by me, Niklas Hallqvist and Niels Provos, funded by
Ericsson Radio Systems. It is not yet complete or usable in a real scenario
but the missing pieces will soon be there. The early commit is for people
who wants early access and who are not afraid of looking at source.
isakmpd interops with Cisco, Timestep, SSH & Pluto (Linux FreeS/WAN) so
far, so it is not that incomplete. It is really mostly configuration that
is lacking.
Diffstat (limited to 'sbin/isakmpd/ike_quick_mode.c')
-rw-r--r-- | sbin/isakmpd/ike_quick_mode.c | 1069 |
1 files changed, 1069 insertions, 0 deletions
diff --git a/sbin/isakmpd/ike_quick_mode.c b/sbin/isakmpd/ike_quick_mode.c new file mode 100644 index 00000000000..0f566c6bbe0 --- /dev/null +++ b/sbin/isakmpd/ike_quick_mode.c @@ -0,0 +1,1069 @@ +/* $Id: ike_quick_mode.c,v 1.1 1998/11/15 00:03:48 niklas Exp $ */ + +/* + * Copyright (c) 1998 Niklas Hallqvist. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Ericsson Radio Systems. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code was written under funding by Ericsson Radio Systems. + */ + +#include <stdlib.h> +#include <string.h> + +#include "attribute.h" +#include "conf.h" +#include "dh.h" +#include "doi.h" +#include "exchange.h" +#include "hash.h" +#include "ike_quick_mode.h" +#include "ipsec.h" +#include "log.h" +#include "math_group.h" +#include "message.h" +#include "prf.h" +#include "sa.h" +#include "transport.h" + +static void gen_g_xy (struct message *); +static int initiator_send_HASH_SA_NONCE (struct message *); +static int initiator_recv_HASH_SA_NONCE (struct message *); +static int initiator_send_HASH (struct message *); +static void post_quick_mode (struct message *); +static int responder_recv_HASH_SA_NONCE (struct message *); +static int responder_send_HASH_SA_NONCE (struct message *); +static int responder_recv_HASH (struct message *); + +int (*ike_quick_mode_initiator[]) (struct message *) = { + initiator_send_HASH_SA_NONCE, + initiator_recv_HASH_SA_NONCE, + initiator_send_HASH +}; + +int (*ike_quick_mode_responder[]) (struct message *) = { + responder_recv_HASH_SA_NONCE, + responder_send_HASH_SA_NONCE, + responder_recv_HASH +}; + +/* + * Offer several sets of transforms to the responder. + * XXX Split this huge function up and look for common code with main mode. + */ +static int +initiator_send_HASH_SA_NONCE (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct doi *doi = exchange->doi; + struct ipsec_exch *ie = exchange->data; + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa = isakmp_sa->data; + u_int8_t ***transform = 0, ***new_transform; + u_int8_t **proposal = 0, **new_proposal; + u_int8_t *sa_buf = 0, *attr, *saved_nextp_sa, *saved_nextp_prop; + u_int8_t *buf; + u_int8_t *spi; + size_t spi_sz; + struct prf *prf; + struct hash *hash = hash_get (isa->hash); + size_t hashsize = hash->hashsize; + size_t proposal_len = 0, proposals_len = 0, sa_len; + size_t **transform_len = 0, **new_transform_len; + size_t *transforms_len = 0, *new_transforms_len; + int *transform_cnt = 0, *new_transform_cnt; + char header[80]; + int i, suite_no, prop_no, prot_no, xf_no, value, update_nextp, protocol_num; + int prop_cnt = 0; + struct proto *proto; + struct conf_list *suite_conf = 0, *prot_conf = 0, *xf_conf = 0, *life_conf; + struct conf_list_node *suite, *prot, *xf, *life; + struct constant_map *id_map; + char *protocol_id, *transform_id; + + /* We want a HASH payload to start with. XXX Share with ike_main_mode.c? */ + buf = malloc (ISAKMP_HASH_SZ + hashsize); + if (!buf) + { + /* XXX Log? */ + return -1; + } + if (message_add_payload (msg, ISAKMP_PAYLOAD_HASH, buf, + ISAKMP_HASH_SZ + hashsize, 1)) + { + /* XXX Log? */ + free (buf); + return -1; + } + + /* Get the list of protocol suites. */ + suite_conf = conf_get_list ("Quick mode initiator", "Offered-suites"); + if (!suite_conf) + return -1; + + for (suite = TAILQ_FIRST (&suite_conf->fields), suite_no = prop_no = 0; + suite_no < suite_conf->cnt; + suite_no++, suite = TAILQ_NEXT (suite, link)) + { + /* Now get each protocol in this specific protocol suite. */ + prot_conf = conf_get_list (suite->field, "Protocols"); + if (!prot_conf) + goto bail_out; + + for (prot = TAILQ_FIRST (&prot_conf->fields), prot_no = 0; + prot_no < prot_conf->cnt; + prot_no++, prot = TAILQ_NEXT (prot, link)) + { + /* Make sure we have a proposal/transform vectors. */ + if (prop_no >= prop_cnt) + { + /* This resize algorithm is completely arbitrary. */ + prop_cnt = 2 * prop_cnt + 10; + new_proposal = realloc (proposal, prop_cnt * sizeof *proposal); + if (!new_proposal) + goto bail_out; + proposal = new_proposal; + + new_transforms_len = realloc (transforms_len, + prop_cnt * sizeof *transforms_len); + if (!new_transforms_len) + goto bail_out; + transforms_len = new_transforms_len; + + new_transform = realloc (transform, + prop_cnt * sizeof *transform); + if (!new_transform) + goto bail_out; + transform = new_transform; + + new_transform_cnt = realloc (transform_cnt, + prop_cnt * sizeof *transform_cnt); + if (!new_transform_cnt) + goto bail_out; + transform_cnt = new_transform_cnt; + + new_transform_len = realloc (transform_len, + prop_cnt * sizeof *transform_len); + if (!new_transform_len) + goto bail_out; + transform_len = new_transform_len; + } + + protocol_id = conf_get_str (prot->field, "PROTOCOL_ID"); + if (!protocol_id) + goto bail_out; + + /* XXX Not too beautiful, but do we have a choice? */ + id_map = strcasecmp (protocol_id, "IPSEC_AH") == 0 ? ipsec_ah_cst + : strcasecmp (protocol_id, "IPSEC_ESP") == 0 ? ipsec_esp_cst + : strcasecmp (protocol_id, "IPCOMP") == 0 ? ipsec_ipcomp_cst : 0; + if (!id_map) + goto bail_out; + + /* Now get each transform we offer for this protocol. */ + xf_conf = conf_get_list (prot->field, "Transforms"); + if (!xf_conf) + goto bail_out; + transform_cnt[prop_no] = xf_conf->cnt; + + transform[prop_no] = calloc (transform_cnt[prop_no], + sizeof **transform); + if (!transform[prop_no]) + goto bail_out; + transform_len[prop_no] + = calloc (transform_cnt[prop_no], sizeof **transform_len); + if (!transform_len[prop_no]) + goto bail_out; + + transforms_len[prop_no] = 0; + for (xf = TAILQ_FIRST (&xf_conf->fields), xf_no = 0; + xf_no < transform_cnt[prop_no]; + xf_no++, xf = TAILQ_NEXT (xf, link)) + { + + /* XXX The sizing needs to be dynamic. */ + transform[prop_no][xf_no] = calloc (ISAKMP_TRANSFORM_SA_ATTRS_OFF + + 9 * ISAKMP_ATTR_VALUE_OFF, + 1); + if (!transform[prop_no][xf_no]) + goto bail_out; + SET_ISAKMP_TRANSFORM_NO (transform[prop_no][xf_no], xf_no + 1); + + transform_id = conf_get_str (xf->field, "TRANSFORM_ID"); + if (!transform_id) + goto bail_out; + SET_ISAKMP_TRANSFORM_ID (transform[prop_no][xf_no], + constant_value (id_map, transform_id)); + SET_ISAKMP_TRANSFORM_RESERVED (transform[prop_no][xf_no], 0); + + attr = transform[prop_no][xf_no] + ISAKMP_TRANSFORM_SA_ATTRS_OFF; + + /* + * Life durations are special, we should be able to specify + * several, one per type. + */ + life_conf = conf_get_list (xf->field, "Life"); + if (life_conf) + { + for (life = TAILQ_FIRST (&life_conf->fields); life; + life = TAILQ_NEXT (life, link)) + { + attribute_set_constant (life->field, "SA_LIFE_TYPE", + ipsec_duration_cst, + IPSEC_ATTR_SA_LIFE_TYPE, &attr); + + /* XXX Does only handle 16-bit entities! */ + value = conf_get_num (life->field, "SA_LIFE_DURATION"); + if (value) + attr + = attribute_set_basic (attr, + IPSEC_ATTR_SA_LIFE_DURATION, + value); + } + } + + attribute_set_constant (xf->field, "ENCAPSULATION_MODE", + ipsec_encap_cst, + IPSEC_ATTR_ENCAPSULATION_MODE, &attr); + + attribute_set_constant (xf->field, "AUTHENTICATION_ALGORITHM", + ipsec_auth_cst, + IPSEC_ATTR_AUTHENTICATION_ALGORITHM, + &attr); + + attribute_set_constant (xf->field, "GROUP_DESCRIPTION", + ike_group_desc_cst, + IKE_ATTR_GROUP_DESCRIPTION, &attr); + + value = conf_get_num (xf->field, "KEY_LENGTH"); + if (value) + attr = attribute_set_basic (attr, IPSEC_ATTR_KEY_LENGTH, + value); + + value = conf_get_num (xf->field, "KEY_ROUNDS"); + if (value) + attr = attribute_set_basic (attr, IPSEC_ATTR_KEY_ROUNDS, + value); + + value = conf_get_num (xf->field, "COMPRESS_DICTIONARY_SIZE"); + if (value) + attr + = attribute_set_basic (attr, + IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE, + value); + + value = conf_get_num (xf->field, "COMPRESS_PRIVATE_ALGORITHM"); + if (value) + attr + = attribute_set_basic (attr, + IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM, + value); + + /* Record the real transform size. */ + transforms_len[prop_no] += (transform_len[prop_no][xf_no] + = attr - transform[prop_no][xf_no]); + } + + /* + * Get SPI from application. + * XXX Should we care about unknown constants? + */ + protocol_num = constant_value (ipsec_proto_cst, protocol_id); + spi = doi->get_spi (&spi_sz, protocol_num, msg); + if (spi_sz && !spi) + goto bail_out; + + proposal_len = ISAKMP_PROP_SPI_OFF + spi_sz; + proposals_len += proposal_len + transforms_len[prop_no]; + proposal[prop_no] = malloc (proposal_len); + if (!proposal[prop_no]) + goto bail_out; + SET_ISAKMP_PROP_NO (proposal[prop_no], suite_no + 1); + SET_ISAKMP_PROP_PROTO (proposal[prop_no], protocol_num); + + /* XXX I would like to see this factored out. */ + proto = calloc (1, sizeof *proto); + if (!proto) + goto bail_out; + if (doi->proto_size) + { + proto->data = calloc (1, doi->proto_size); + if (!proto->data) + goto bail_out; + } + proto->no = suite_no + 1; + proto->proto = protocol_num; + proto->sa = TAILQ_FIRST (&exchange->sa_list); + TAILQ_INSERT_TAIL (&TAILQ_FIRST (&exchange->sa_list)->protos, proto, + link); + + SET_ISAKMP_PROP_SPI_SZ (proposal[prop_no], spi_sz); + memcpy (proposal[prop_no] + ISAKMP_PROP_SPI_OFF, spi, spi_sz); + proto->spi_sz[exchange->initiator] = spi_sz; + proto->spi[exchange->initiator] = spi; + + SET_ISAKMP_PROP_NTRANSFORMS (proposal[prop_no], + transform_cnt[prop_no]); + prop_no++; + } + } + + sa_len = ISAKMP_SA_SIT_OFF + IPSEC_SIT_SIT_LEN; + sa_buf = malloc (sa_len); + if (!sa_buf) + goto bail_out; + SET_ISAKMP_SA_DOI (sa_buf, IPSEC_DOI_IPSEC); + SET_IPSEC_SIT_SIT (sa_buf + ISAKMP_SA_SIT_OFF, IPSEC_SIT_IDENTITY_ONLY); + + /* + * Add the payloads. As this is a SA, we need to recompute the + * lengths of the payloads containing others. We also need to + * reset these payload's "next payload type" field. + */ + if (message_add_payload (msg, ISAKMP_PAYLOAD_SA, sa_buf, sa_len, 1)) + goto bail_out; + SET_ISAKMP_GEN_LENGTH (sa_buf, sa_len + proposals_len); + sa_buf = 0; + + update_nextp = 0; + saved_nextp_sa = msg->nextp; + for (i = 0; i < prop_no; i++) + { + if (message_add_payload (msg, ISAKMP_PAYLOAD_PROPOSAL, proposal[i], + proposal_len, update_nextp)) + goto bail_out; + SET_ISAKMP_GEN_LENGTH (proposal[i], proposal_len + transforms_len[i]); + proposal[i] = 0; + + update_nextp = 0; + saved_nextp_prop = msg->nextp; + for (xf_no = 0; xf_no < transform_cnt[i]; xf_no++) + { + if (message_add_payload (msg, ISAKMP_PAYLOAD_TRANSFORM, + transform[i][xf_no], + transform_len[i][xf_no], update_nextp)) + goto bail_out; + update_nextp = 1; + transform[i][xf_no] = 0; + } + msg->nextp = saved_nextp_prop; + update_nextp = 1; + } + msg->nextp = saved_nextp_sa; + + /* + * Save SA payload body in ie->sa_i_b, length ie->sa_i_b_len. + */ + ie->sa_i_b = message_copy (msg, ISAKMP_GEN_SZ, &ie->sa_i_b_len); + if (!ie->sa_i_b) + goto bail_out; + + /* + * Generate a nonce, and add it to the message. + * XXX I want a better way to specify the nonce's size. + */ + if (exchange_gen_nonce (msg, 16)) + { + /* XXX Log? */ + return -1; + } + + /* Generate optional KEY_EXCH payload. */ + if (ie->group) + { + ie->g_x_len = dh_getlen (ie->group); + + if (ipsec_gen_g_x (msg)) + { + /* XXX Log? */ + return -1; + } + } + + /* XXX Generate optional client ID payloads. */ + + /* Allocate the prf and start calculating our HASH(1). XXX Share? */ + log_debug_buf (LOG_MISC, 90, "initiator_send_HASH_SA_NONCE: SKEYID_a", + isa->skeyid_a, isa->skeyid_len); + prf = prf_alloc (isa->prf_type, hash->type, isa->skeyid_a, isa->skeyid_len); + if (!prf) + { + /* XXX Log? */ + return -1; + } + prf->Init (prf->prfctx); + log_debug_buf (LOG_MISC, 90, "initiator_send_HASH_SA_NONCE: message_id", + exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + + /* Loop over all payloads after HASH(1). */ + for (i = 2; i < msg->iovlen; i++) + { + /* XXX Misleading payload type printouts. */ + snprintf (header, 80, + "initiator_send_HASH_SA_NONCE: payload %d after HASH(1)", + i - 1); + log_debug_buf (LOG_MISC, 90, header, msg->iov[i].iov_base, + msg->iov[i].iov_len); + prf->Update (prf->prfctx, msg->iov[i].iov_base, msg->iov[i].iov_len); + } + prf->Final (buf + ISAKMP_HASH_DATA_OFF, prf->prfctx); + prf_free (prf); + log_debug_buf (LOG_MISC, 80, "initiator_send_HASH_SA_NONCE: HASH(1)", + buf + ISAKMP_HASH_DATA_OFF, hashsize); + + return 0; + + bail_out: + if (sa_buf) + free (sa_buf); + if (proposal) + { + for (i = 0; i < prop_no; i++) + { + if (proposal[i]) + free (proposal[i]); + if (transform[i]) + { + for (xf_no = 0; xf_no < xf_conf->cnt; xf_no++) + if (transform[i][xf_no]) + free (transform[i][xf_no]); + free (transform[i]); + } + if (transform_len[i]) + free (transform_len[i]); + } + free (proposal); + free (transforms_len); + free (transform); + free (transform_len); + free (transform_cnt); + } + if (xf_conf) + conf_free_list (xf_conf); + if (prot_conf) + conf_free_list (prot_conf); + if (suite_conf) + conf_free_list (suite_conf); + return -1; +} + +/* Figure out what transform the responder chose. */ +static int +initiator_recv_HASH_SA_NONCE (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + struct sa *sa; + struct proto *proto; + struct payload *sa_p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SA]); + struct payload *xf; + struct payload *hashp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_HASH]); + struct payload *kep = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_KEY_EXCH]); + struct prf *prf; + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa = isakmp_sa->data; + struct hash *hash = hash_get (isa->hash); + size_t hashsize = hash->hashsize; + u_int8_t *rest; + size_t rest_len; + + /* + * As we are getting an answer on our transform offer, only one transform + * should be given. + * + * XXX Currently we only support negotiating one SA per quick mode run. + */ + if (TAILQ_NEXT (sa_p, link)) + { + log_print ("initiator_recv_HASH_SA_NONCE: " + "multiple SA payloads in quick mode"); + /* XXX Is there a better notification type? */ + message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 0, 0); + return -1; + } + + sa = TAILQ_FIRST (&msg->exchange->sa_list); + + /* Build the protection suite in our SA. */ + for (xf = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_TRANSFORM]); xf; + xf = TAILQ_NEXT (xf, link)) + { + /* + * XXX We could check that the proposal each transform belongs to + * is unique. + */ + + if (sa_add_transform (sa, xf, exchange->initiator, &proto)) + { + /* XXX Log? */ + return -1; + } + + /* XXX Check that the chosen transform matches an offer. */ + + ipsec_decode_transform (msg, sa, proto, xf->p); + } + + /* Now remove offers that we don't need anymore. */ + for (proto = TAILQ_FIRST (&sa->protos); proto; + proto = TAILQ_NEXT (proto, link)) + if (!proto->chosen) + proto_free (proto); + + /* Mark the SA as handled. */ + sa_p->flags |= PL_MARK; + + /* Allocate the prf and start calculating our HASH(1). XXX Share? */ + log_debug_buf (LOG_MISC, 90, "initiator_recv_HASH_SA_NONCE: SKEYID_a", + isa->skeyid_a, isa->skeyid_len); + prf = prf_alloc (isa->prf_type, hash->type, isa->skeyid_a, isa->skeyid_len); + if (!prf) + { + /* XXX Log? */ + return -1; + } + prf->Init (prf->prfctx); + log_debug_buf (LOG_MISC, 90, "initiator_recv_HASH_SA_NONCE: message_id", + exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + log_debug_buf (LOG_MISC, 90, "initiator_recv_HASH_SA_NONCE: NONCE_I_b", + exchange->nonce_i, exchange->nonce_i_len); + prf->Update (prf->prfctx, exchange->nonce_i, exchange->nonce_i_len); + rest = hashp->p + GET_ISAKMP_GEN_LENGTH (hashp->p); + rest_len = (GET_ISAKMP_HDR_LENGTH (msg->iov[0].iov_base) + - (rest - (u_int8_t*)msg->iov[0].iov_base)); + log_debug_buf (LOG_MISC, 90, + "initiator_recv_HASH_SA_NONCE: payloads after HASH(2)", rest, + rest_len); + prf->Update (prf->prfctx, rest, rest_len); + prf->Final (hash->digest, prf->prfctx); + prf_free (prf); + log_debug_buf (LOG_MISC, 80, + "initiator_recv_HASH_SA_NONCE: computed HASH(2)", + hash->digest, hashsize); + if (memcmp (hashp->p + ISAKMP_HASH_DATA_OFF, hash->digest, hashsize) != 0) + { + /* XXX Log & notify? */ + return -1; + } + /* Mark the HASH as handled. */ + hashp->flags |= PL_MARK; + + /* XXX Errors possible? */ + ie->group = group_get (isa->group_desc); + + /* Copy out the initiator's nonce. */ + if (exchange_save_nonce (msg)) + { + /* XXX How to log and notify peer? */ + return -1; + } + + /* Handle the optional KEY_EXCH payload. */ + if (kep && ipsec_save_g_x (msg)) + { + /* XXX How to log and notify peer? */ + return -1; + } + + /* XXX Handle optional client ID payloads. */ + + return 0; +} + +static int +initiator_send_HASH (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa = isakmp_sa->data; + struct prf *prf; + u_int8_t *buf; + struct hash *hash = hash_get (isa->hash); + size_t hashsize = hash->hashsize; + + /* We want a HASH payload to start with. XXX Share with ike_main_mode.c? */ + buf = malloc (ISAKMP_HASH_SZ + hashsize); + if (!buf) + { + /* XXX Log? */ + return -1; + } + if (message_add_payload (msg, ISAKMP_PAYLOAD_HASH, buf, + ISAKMP_HASH_SZ + hashsize, 1)) + { + /* XXX Log? */ + free (buf); + return -1; + } + + /* Allocate the prf and start calculating our HASH(3). XXX Share? */ + log_debug_buf (LOG_MISC, 90, "initiator_send_HASH: SKEYID_a", isa->skeyid_a, + isa->skeyid_len); + prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_a, isa->skeyid_len); + if (!prf) + { + /* XXX Log? */ + return -1; + } + prf->Init (prf->prfctx); + prf->Update (prf->prfctx, "\0", 1); + log_debug_buf (LOG_MISC, 90, "initiator_send_HASH: message_id", + exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + log_debug_buf (LOG_MISC, 90, "initiator_send_HASH: NONCE_I_b", + exchange->nonce_i, exchange->nonce_i_len); + prf->Update (prf->prfctx, exchange->nonce_i, exchange->nonce_i_len); + log_debug_buf (LOG_MISC, 90, "initiator_send_HASH: NONCE_R_b", + exchange->nonce_r, exchange->nonce_r_len); + prf->Update (prf->prfctx, exchange->nonce_r, exchange->nonce_r_len); + prf->Final (buf + ISAKMP_GEN_SZ, prf->prfctx); + prf_free (prf); + log_debug_buf (LOG_MISC, 90, "initiator_send_HASH: HASH(3)", + buf + ISAKMP_GEN_SZ, hashsize); + + if (ie->group) + message_register_post_send (msg, gen_g_xy); + message_register_post_send (msg, post_quick_mode); + + return 0; +} + +static void +post_quick_mode (struct message *msg) +{ + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa = isakmp_sa->data; + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + struct prf *prf; + struct sa *sa; + struct proto *proto; + struct ipsec_proto *iproto = proto->data; + u_int8_t *keymat; + int i; + + /* + * Loop over all SA negotiations and do both an in- and an outgoing SA + * per protocol. + */ + for (sa = TAILQ_FIRST (&exchange->sa_list); sa; sa = TAILQ_NEXT (sa, next)) + { + for (proto = TAILQ_FIRST (&sa->protos); proto; + proto = TAILQ_NEXT (proto, link)) + { + iproto = proto->data; + + /* + * There are two SAs for each SA negotiation, incoming and outcoing. + */ + for (i = 0; i < 2; i++) + { + prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_d, + isa->skeyid_len); + if (!prf) + { + /* XXX What to do? */ + continue; + } + + ie->keymat_len = ipsec_keymat_length (proto); + + /* + * We need to roundup the length of the key material buffer + * to a multiple of the PRF´s blocksize as it is generated + * in chunks of that blocksize. + */ + iproto->keymat[i] + = malloc (((ie->keymat_len + prf->blocksize - 1) + / prf->blocksize) * prf->blocksize); + if (!iproto->keymat[i]) + { + /* XXX What to do? */ + free (prf); + continue; + } + + for (keymat = iproto->keymat[i]; + keymat < iproto->keymat[i] + ie->keymat_len; + keymat += prf->blocksize) + { + prf->Init (prf->prfctx); + + if (keymat != iproto->keymat[i]) + { + /* Hash in last round's KEYMAT. */ + log_debug_buf (LOG_MISC, 90, + "post_quick_mode: last KEYMAT", + keymat - prf->blocksize, prf->blocksize); + prf->Update (prf->prfctx, keymat - prf->blocksize, + prf->blocksize); + } + + /* If PFS is used hash in g^xy. */ + if (ie->g_xy) + { + log_debug_buf (LOG_MISC, 90, "post_quick_mode: g^xy", + ie->g_xy, ie->g_x_len); + prf->Update (prf->prfctx, ie->g_xy, ie->g_x_len); + } + log_debug (LOG_MISC, 90, + "post_quick_mode: suite %d proto %d", proto->no, + proto->proto); + prf->Update (prf->prfctx, &proto->proto, 1); + log_debug_buf (LOG_MISC, 90, "post_quick_mode: SPI", + proto->spi[i], proto->spi_sz[i]); + prf->Update (prf->prfctx, proto->spi[i], proto->spi_sz[i]); + log_debug_buf (LOG_MISC, 90, "post_quick_mode: Ni_b", + exchange->nonce_i, exchange->nonce_i_len); + prf->Update (prf->prfctx, exchange->nonce_i, + exchange->nonce_i_len); + log_debug_buf (LOG_MISC, 90, "post_quick_mode: Nr_b", + exchange->nonce_r, exchange->nonce_r_len); + prf->Update (prf->prfctx, exchange->nonce_r, + exchange->nonce_r_len); + prf->Final (keymat, prf->prfctx); + } + prf_free (prf); + log_debug_buf (LOG_MISC, 90, "post_quick_mode: KEYMAT", + iproto->keymat[i], ie->keymat_len); + } + } + } +} + +/* + * Accept a set of transforms offered by the initiator and chose one we can + * handle. + * XXX Describe in more detail. + */ +static int +responder_recv_HASH_SA_NONCE (struct message *msg) +{ + struct payload *hashp, *kep; + struct sa *sa; + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa = isakmp_sa->data; + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + struct prf *prf; + u_int8_t *hash, *my_hash; + size_t hash_len; + u_int8_t *pkt = msg->iov[0].iov_base; + u_int8_t group_desc = 0; + int retval = -1; + struct proto *proto; + + hashp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_HASH]); + hash = hashp->p; + hashp->flags |= PL_MARK; + + /* The HASH payload should be the first one. */ + if (hash != pkt + ISAKMP_HDR_SZ) + { + /* XXX Is there a better notification type? */ + message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 0, 0); + return -1; + } + hash_len = GET_ISAKMP_GEN_LENGTH (hash); + my_hash = malloc (hash_len - ISAKMP_GEN_SZ); + if (!my_hash) + { + /* XXX Log? */ + return -1; + } + + /* Check the payload's integrity. */ + log_debug_buf (LOG_MISC, 90, "responder_recv_HASH_SA_NONCE: SKEYID_a", + isa->skeyid_a, isa->skeyid_len); + prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_a, isa->skeyid_len); + if (!prf) + { + /* XXX Log? */ + return -1; + } + prf->Init (prf->prfctx); + log_debug_buf (LOG_MISC, 90, "responder_recv_HASH_SA_NONCE: message_id", + exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + log_debug_buf (LOG_MISC, 90, + "responder_recv_HASH_SA_NONCE: message after HASH", + hash + hash_len, + msg->iov[0].iov_len - ISAKMP_HDR_SZ - hash_len); + prf->Update (prf->prfctx, hash + hash_len, + msg->iov[0].iov_len - ISAKMP_HDR_SZ - hash_len); + prf->Final (my_hash, prf->prfctx); + prf_free (prf); + log_debug_buf (LOG_MISC, 90, + "responder_recv_HASH_SA_NONCE: computed HASH(1)", my_hash, + hash_len - ISAKMP_GEN_SZ); + if (memcmp (hash + ISAKMP_GEN_SZ, my_hash, hash_len - ISAKMP_GEN_SZ) != 0) + { + /* XXX Is there a better notification type? */ + message_drop (msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 0, 0); + return -1; + } + + if (message_negotiate_sa (msg)) + return -1; + + kep = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_KEY_EXCH]); + + for (sa = TAILQ_FIRST (&exchange->sa_list); sa; + sa = TAILQ_NEXT (sa, next)) + { + for (proto = TAILQ_FIRST (&sa->protos); proto; + proto = TAILQ_NEXT (proto, link)) + /* XXX we need to have some attributes per proto, not all per SA. */ + ipsec_decode_transform (msg, sa, proto, proto->chosen->p); + + + isa = sa->data; + + /* Check the SA for reasonableness. */ + + /* The group description is mandatory if we got a KEY_EXCH payload. */ + if (kep) + { + if (!isa->group_desc) + { + message_drop (msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 0, 0); + continue; + } + + /* Also, all SAs must have equal groups. */ + if (!group_desc) + group_desc = isa->group_desc; + else if (group_desc != isa->group_desc) + { + message_drop (msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 0, 0); + continue; + } + } + + /* At least one SA was accepted. */ + retval = 0; + } + + /* XXX Errors possible? */ + ie->group = group_get (group_desc); + + /* Copy out the initiator's nonce. */ + if (exchange_save_nonce (msg)) + { + /* XXX How to log and notify peer? */ + return -1; + } + + /* Handle the optional KEY_EXCH payload. */ + if (kep && ipsec_save_g_x (msg)) + { + /* XXX How to log and notify peer? */ + return -1; + } + + /* XXX Handle optional client ID payloads. */ + + return retval; +} + +/* Reply with the transform we chose. */ +static int +responder_send_HASH_SA_NONCE (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa = isakmp_sa->data; + struct prf *prf; + struct hash *hash = hash_get (isa->hash); + size_t hashsize = hash->hashsize; + size_t nonce_sz = exchange->nonce_i_len; + u_int8_t *buf; + int initiator = exchange->initiator; + char header[80]; + int i; + + /* We want a HASH payload to start with. XXX Share with ike_main_mode.c? */ + buf = malloc (ISAKMP_HASH_SZ + hashsize); + if (!buf) + { + /* XXX Log? */ + return -1; + } + if (message_add_payload (msg, ISAKMP_PAYLOAD_HASH, buf, + ISAKMP_HASH_SZ + hashsize, 1)) + { + /* XXX Log? */ + free (buf); + return -1; + } + + /* Add the SA payload(s) with the transform(s) that was/were chosen. */ + if (message_add_sa_payload (msg)) + { + /* XXX Log? */ + return -1; + } + + /* Generate a nonce, and add it to the message. */ + if (exchange_gen_nonce (msg, nonce_sz)) + { + /* XXX Log? */ + return -1; + } + + /* Generate optional KEY_EXCH payload. */ + if (ie->group && ipsec_gen_g_x (msg)) + { + /* XXX Log? */ + return -1; + } + + /* XXX Generate optional client ID payload. */ + + /* Allocate the prf and start calculating our HASH(2). XXX Share? */ + log_debug_buf (LOG_MISC, 90, "responder_send_HASH_SA_NONCE: SKEYID_a", + isa->skeyid_a, isa->skeyid_len); + prf = prf_alloc (isa->prf_type, hash->type, isa->skeyid_a, isa->skeyid_len); + if (!prf) + { + /* XXX Log? */ + return -1; + } + prf->Init (prf->prfctx); + log_debug_buf (LOG_MISC, 90, "responder_send_HASH_SA_NONCE: message_id", + exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + log_debug_buf (LOG_MISC, 90, "responder_send_HASH_SA_NONCE: NONCE_I_b", + exchange->nonce_i, exchange->nonce_i_len); + prf->Update (prf->prfctx, exchange->nonce_i, exchange->nonce_i_len); + + /* Loop over all payloads after HASH(2). */ + for (i = 2; i < msg->iovlen; i++) + { + /* XXX Misleading payload type printouts. */ + snprintf (header, 80, + "responder_send_HASH_SA_NONCE: payload %d after HASH(2)", + i - 1); + log_debug_buf (LOG_MISC, 90, header, msg->iov[i].iov_base, + msg->iov[i].iov_len); + prf->Update (prf->prfctx, msg->iov[i].iov_base, msg->iov[i].iov_len); + } + prf->Final (buf + ISAKMP_HASH_DATA_OFF, prf->prfctx); + prf_free (prf); + snprintf (header, 80, "responder_send_HASH_SA_NONCE: HASH_%c", + initiator ? 'I' : 'R'); + log_debug_buf (LOG_MISC, 80, header, buf + ISAKMP_HASH_DATA_OFF, hashsize); + + if (ie->group) + message_register_post_send (msg, gen_g_xy); + + return 0; +} + +static void +gen_g_xy (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + + /* Compute Diffie-Hellman shared value. */ + ie->g_xy = malloc (ie->g_x_len); + if (!ie->g_xy) + { + /* XXX How to log and notify peer? */ + return; + } + dh_create_shared (ie->group, ie->g_xy, + exchange->initiator ? ie->g_xr : ie->g_xi); + log_debug_buf (LOG_MISC, 80, "gen_g_xy: g^xy", ie->g_xy, ie->g_x_len); +} + +static int +responder_recv_HASH (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa = isakmp_sa->data; + struct prf *prf; + u_int8_t *hash, *my_hash; + size_t hash_len; + struct payload *hashp; + + /* Find HASH(3) and create our own hash, just as big. */ + hashp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_HASH]); + hash = hashp->p; + hashp->flags |= PL_MARK; + hash_len = GET_ISAKMP_GEN_LENGTH (hash); + my_hash = malloc (hash_len - ISAKMP_GEN_SZ); + if (!my_hash) + { + /* XXX Log? */ + return -1; + } + + /* Allocate the prf and start calculating our HASH(3). XXX Share? */ + log_debug_buf (LOG_MISC, 90, "responder_recv_HASH: SKEYID_a", isa->skeyid_a, + isa->skeyid_len); + prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_a, isa->skeyid_len); + if (!prf) + { + /* XXX Log? */ + free (my_hash); + return -1; + } + prf->Init (prf->prfctx); + prf->Update (prf->prfctx, "\0", 1); + log_debug_buf (LOG_MISC, 90, "responder_recv_HASH: message_id", + exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN); + log_debug_buf (LOG_MISC, 90, "responder_recv_HASH: NONCE_I_b", + exchange->nonce_i, exchange->nonce_i_len); + prf->Update (prf->prfctx, exchange->nonce_i, exchange->nonce_i_len); + log_debug_buf (LOG_MISC, 90, "responder_recv_HASH: NONCE_R_b", + exchange->nonce_r, exchange->nonce_r_len); + prf->Update (prf->prfctx, exchange->nonce_r, exchange->nonce_r_len); + prf->Final (my_hash, prf->prfctx); + prf_free (prf); + log_debug_buf (LOG_MISC, 90, + "responder_recv_HASH: computed HASH(3)", my_hash, + hash_len - ISAKMP_GEN_SZ); + if (memcmp (hash + ISAKMP_GEN_SZ, my_hash, hash_len - ISAKMP_GEN_SZ) != 0) + { + /* XXX Is there a better notification type? */ + message_drop (msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 0, 0); + return -1; + } + + post_quick_mode (msg); + + return 0; +} |