diff options
Diffstat (limited to 'keyexchange/isakmpd-20041012/ike_quick_mode.c')
-rw-r--r-- | keyexchange/isakmpd-20041012/ike_quick_mode.c | 1989 |
1 files changed, 1989 insertions, 0 deletions
diff --git a/keyexchange/isakmpd-20041012/ike_quick_mode.c b/keyexchange/isakmpd-20041012/ike_quick_mode.c new file mode 100644 index 0000000..75bec87 --- /dev/null +++ b/keyexchange/isakmpd-20041012/ike_quick_mode.c @@ -0,0 +1,1989 @@ +/* $OpenBSD: ike_quick_mode.c,v 1.87 2004/09/17 13:53:08 ho Exp $ */ +/* $EOM: ike_quick_mode.c,v 1.139 2001/01/26 10:43:17 niklas Exp $ */ + +/* + * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved. + * Copyright (c) 1999, 2000, 2001 Angelos D. Keromytis. All rights reserved. + * Copyright (c) 2000, 2001, 2004 Håkan Olsson. 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. + * + * 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> + +#if defined (USE_POLICY) || defined (USE_KEYNOTE) +#include <sys/types.h> +#include <regex.h> +#include <keynote.h> +#endif + +#include "sysdep.h" + +#include "attribute.h" +#include "conf.h" +#include "connection.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 "policy.h" +#include "prf.h" +#include "sa.h" +#include "transport.h" +#include "util.h" +#include "key.h" + +#ifdef USE_X509 +#include "x509.h" +#endif + +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 *); + +#ifdef USE_POLICY +static int check_policy(struct exchange *, struct sa *, struct sa *); +#endif + +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 +}; + +#ifdef USE_POLICY + +/* How many return values will policy handle -- true/false for now */ +#define RETVALUES_NUM 2 + +/* + * Given an exchange and our policy, check whether the SA and IDs are + * acceptable. + */ +static int +check_policy(struct exchange *exchange, struct sa *sa, struct sa *isakmp_sa) +{ + char *return_values[RETVALUES_NUM]; + char **principal = 0; + int i, len, result = 0, nprinc = 0; + int *x509_ids = 0, *keynote_ids = 0; + unsigned char hashbuf[20]; /* Set to the largest digest result */ +#ifdef USE_X509 + struct keynote_deckey dc; + X509_NAME *subject; +#endif + + /* Do we want to use keynote policies? */ + if (ignore_policy || + strncmp("yes", conf_get_str("General", "Use-Keynote"), 3)) + return 1; + + /* Initialize if necessary -- e.g., if pre-shared key auth was used */ + if (isakmp_sa->policy_id < 0) { + if ((isakmp_sa->policy_id = kn_init()) == -1) { + log_print("check_policy: " + "failed to initialize policy session"); + return 0; + } + } + /* Add the callback that will handle attributes. */ + if (kn_add_action(isakmp_sa->policy_id, ".*", (char *)policy_callback, + ENVIRONMENT_FLAG_FUNC | ENVIRONMENT_FLAG_REGEX) == -1) { + log_print("check_policy: " + "kn_add_action (%d, \".*\", %p, FUNC | REGEX) failed", + isakmp_sa->policy_id, policy_callback); + kn_close(isakmp_sa->policy_id); + isakmp_sa->policy_id = -1; + return 0; + } + if (policy_asserts_num) { + keynote_ids = calloc(policy_asserts_num, sizeof *keynote_ids); + if (!keynote_ids) { + log_error("check_policy: calloc (%d, %lu) failed", + policy_asserts_num, + (unsigned long)sizeof *keynote_ids); + return 0; + } + } + /* Add the policy assertions */ + for (i = 0; i < policy_asserts_num; i++) + keynote_ids[i] = kn_add_assertion(isakmp_sa->policy_id, + policy_asserts[i], + strlen(policy_asserts[i]), ASSERT_FLAG_LOCAL); + + /* Initialize -- we'll let the callback do all the work. */ + policy_exchange = exchange; + policy_sa = sa; + policy_isakmp_sa = isakmp_sa; + + /* Set the return values; true/false for now at least. */ + return_values[0] = "false"; /* Order of values in array is + * important. */ + return_values[1] = "true"; + + /* Create a principal (authorizer) for the SA/ID request. */ + switch (isakmp_sa->recv_certtype) { + case ISAKMP_CERTENC_NONE: + /* + * For shared keys, just duplicate the passphrase with the + * appropriate prefix tag. + */ + nprinc = 3; + principal = calloc(nprinc, sizeof *principal); + if (!principal) { + log_error("check_policy: calloc (%d, %lu) failed", + nprinc, (unsigned long)sizeof *principal); + goto policydone; + } + len = strlen(isakmp_sa->recv_key) + sizeof "passphrase:"; + principal[0] = calloc(len, sizeof(char)); + if (!principal[0]) { + log_error("check_policy: calloc (%d, %lu) failed", len, + (unsigned long)sizeof(char)); + goto policydone; + } + /* + * XXX Consider changing the magic hash lengths with + * constants. + */ + strlcpy(principal[0], "passphrase:", len); + memcpy(principal[0] + sizeof "passphrase:" - 1, + isakmp_sa->recv_key, strlen(isakmp_sa->recv_key)); + + len = sizeof "passphrase-md5-hex:" + 2 * 16; + principal[1] = calloc(len, sizeof(char)); + if (!principal[1]) { + log_error("check_policy: calloc (%d, %lu) failed", len, + (unsigned long)sizeof(char)); + goto policydone; + } + strlcpy(principal[1], "passphrase-md5-hex:", len); + MD5(isakmp_sa->recv_key, strlen(isakmp_sa->recv_key), hashbuf); + for (i = 0; i < 16; i++) + snprintf(principal[1] + 2 * i + + sizeof "passphrase-md5-hex:" - 1, 3, "%02x", + hashbuf[i]); + + len = sizeof "passphrase-sha1-hex:" + 2 * 20; + principal[2] = calloc(len, sizeof(char)); + if (!principal[2]) { + log_error("check_policy: calloc (%d, %lu) failed", len, + (unsigned long)sizeof(char)); + goto policydone; + } + strlcpy(principal[2], "passphrase-sha1-hex:", len); + SHA1(isakmp_sa->recv_key, strlen(isakmp_sa->recv_key), + hashbuf); + for (i = 0; i < 20; i++) + snprintf(principal[2] + 2 * i + + sizeof "passphrase-sha1-hex:" - 1, 3, "%02x", + hashbuf[i]); + break; + + case ISAKMP_CERTENC_KEYNOTE: +#ifdef USE_KEYNOTE + nprinc = 1; + + principal = calloc(nprinc, sizeof *principal); + if (!principal) { + log_error("check_policy: calloc (%d, %lu) failed", + nprinc, (unsigned long)sizeof *principal); + goto policydone; + } + /* Dup the keys */ + principal[0] = strdup(isakmp_sa->keynote_key); + if (!principal[0]) { + log_error("check_policy: calloc (%lu, %lu) failed", + (unsigned long)strlen(isakmp_sa->keynote_key), + (unsigned long)sizeof(char)); + goto policydone; + } +#endif + break; + + case ISAKMP_CERTENC_X509_SIG: +#ifdef USE_X509 + principal = calloc(2, sizeof *principal); + if (!principal) { + log_error("check_policy: calloc (2, %lu) failed", + (unsigned long)sizeof *principal); + goto policydone; + } + if (isakmp_sa->recv_keytype == ISAKMP_KEY_RSA) + dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA; + else { + log_error("check_policy: " + "unknown/unsupported public key algorithm %d", + isakmp_sa->recv_keytype); + goto policydone; + } + + dc.dec_key = isakmp_sa->recv_key; + principal[0] = kn_encode_key(&dc, INTERNAL_ENC_PKCS1, + ENCODING_HEX, KEYNOTE_PUBLIC_KEY); + if (keynote_errno == ERROR_MEMORY) { + log_print("check_policy: " + "failed to get memory for public key"); + goto policydone; + } + if (!principal[0]) { + log_print("check_policy: " + "failed to allocate memory for principal"); + goto policydone; + } + len = strlen(principal[0]) + sizeof "rsa-hex:"; + principal[1] = calloc(len, sizeof(char)); + if (!principal[1]) { + log_error("check_policy: calloc (%d, %lu) failed", len, + (unsigned long)sizeof(char)); + goto policydone; + } + snprintf(principal[1], len, "rsa-hex:%s", principal[0]); + free(principal[0]); + principal[0] = principal[1]; + principal[1] = 0; + + /* Generate a "DN:" principal. */ + subject = X509_get_subject_name(isakmp_sa->recv_cert); + if (subject) { + principal[1] = calloc(259, sizeof(char)); + if (!principal[1]) { + log_error("check_policy: " + "calloc (259, %lu) failed", + (unsigned long)sizeof(char)); + goto policydone; + } + strlcpy(principal[1], "DN:", 259); + X509_NAME_oneline(subject, principal[1] + 3, 256); + nprinc = 2; + } else { + nprinc = 1; + } + break; +#endif + + /* XXX Eventually handle these. */ + case ISAKMP_CERTENC_PKCS: + case ISAKMP_CERTENC_PGP: + case ISAKMP_CERTENC_DNS: + case ISAKMP_CERTENC_X509_KE: + case ISAKMP_CERTENC_KERBEROS: + case ISAKMP_CERTENC_CRL: + case ISAKMP_CERTENC_ARL: + case ISAKMP_CERTENC_SPKI: + case ISAKMP_CERTENC_X509_ATTR: + default: + log_print("check_policy: " + "unknown/unsupported certificate/authentication method %d", + isakmp_sa->recv_certtype); + goto policydone; + } + + /* + * Add the authorizer (who is requesting the SA/ID); + * this may be a public or a secret key, depending on + * what mode of authentication we used in Phase 1. + */ + for (i = 0; i < nprinc; i++) { + LOG_DBG((LOG_POLICY, 40, "check_policy: " + "adding authorizer [%s]", principal[i])); + + if (kn_add_authorizer(isakmp_sa->policy_id, principal[i]) + == -1) { + int j; + + for (j = 0; j < i; j++) + kn_remove_authorizer(isakmp_sa->policy_id, + principal[j]); + log_print("check_policy: kn_add_authorizer failed"); + goto policydone; + } + } + + /* Ask policy */ + result = kn_do_query(isakmp_sa->policy_id, return_values, + RETVALUES_NUM); + LOG_DBG((LOG_POLICY, 40, "check_policy: kn_do_query returned %d", + result)); + + /* Cleanup environment */ + kn_cleanup_action_environment(isakmp_sa->policy_id); + + /* Remove authorizers from the session */ + for (i = 0; i < nprinc; i++) { + kn_remove_authorizer(isakmp_sa->policy_id, principal[i]); + free(principal[i]); + } + + free(principal); + principal = 0; + nprinc = 0; + + /* Check what policy said. */ + if (result < 0) { + LOG_DBG((LOG_POLICY, 40, "check_policy: proposal refused")); + result = 0; + goto policydone; + } +policydone: + for (i = 0; i < nprinc; i++) + if (principal && principal[i]) + free(principal[i]); + + if (principal) + free(principal); + + /* Remove the policies */ + for (i = 0; i < policy_asserts_num; i++) { + if (keynote_ids[i] != -1) + kn_remove_assertion(isakmp_sa->policy_id, + keynote_ids[i]); + } + + if (keynote_ids) + free(keynote_ids); + + if (x509_ids) + free(x509_ids); + + /* + * XXX Currently, check_policy() is only called from + * message_negotiate_sa(), and so this log message reflects this. + * Change to something better? + */ + if (result == 0) + log_print("check_policy: negotiated SA failed policy check"); + + /* + * Given that we have only 2 return values from policy (true/false) + * we can just return the query result directly (no pre-processing + * needed). + */ + return result; +} +#endif /* USE_POLICY */ + +/* + * 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; + 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, + *id, *spi; + size_t spi_sz, sz; + 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; + u_int32_t *transform_cnt = 0, *new_transform_cnt; + u_int32_t suite_no, prop_no, prot_no, xf_no, prop_cnt = 0; + u_int32_t i; + int value, update_nextp, protocol_num, proto_id; + struct proto *proto; + struct conf_list *suite_conf, *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; + char *local_id, *remote_id; + int group_desc = -1, new_group_desc; + struct ipsec_sa *isa = msg->isakmp_sa->data; + struct hash *hash = hash_get(isa->hash); + struct sockaddr *src; + struct proto_attr *pa; + + if (!ipsec_add_hash_payload(msg, hash->hashsize)) + return -1; + + /* Get the list of protocol suites. */ + suite_conf = conf_get_list(exchange->policy, "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) { + log_error( + "initiator_send_HASH_SA_NONCE: " + "realloc (%p, %lu) failed", + proposal, + prop_cnt * (unsigned long)sizeof *proposal); + goto bail_out; + } + proposal = new_proposal; + + new_transforms_len = realloc(transforms_len, + prop_cnt * sizeof *transforms_len); + if (!new_transforms_len) { + log_error( + "initiator_send_HASH_SA_NONCE: " + "realloc (%p, %lu) failed", + transforms_len, + prop_cnt * (unsigned long)sizeof *transforms_len); + goto bail_out; + } + transforms_len = new_transforms_len; + + new_transform = realloc(transform, + prop_cnt * sizeof *transform); + if (!new_transform) { + log_error( + "initiator_send_HASH_SA_NONCE: " + "realloc (%p, %lu) failed", + transform, + prop_cnt * (unsigned long)sizeof *transform); + goto bail_out; + } + transform = new_transform; + + new_transform_cnt = realloc(transform_cnt, + prop_cnt * sizeof *transform_cnt); + if (!new_transform_cnt) { + log_error( + "initiator_send_HASH_SA_NONCE: " + "realloc (%p, %lu) failed", + transform_cnt, + prop_cnt * (unsigned long)sizeof *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) { + log_error( + "initiator_send_HASH_SA_NONCE: " + "realloc (%p, %lu) failed", + transform_len, + prop_cnt * (unsigned long)sizeof *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; + + proto_id = constant_value(ipsec_proto_cst, + protocol_id); + switch (proto_id) { + case IPSEC_PROTO_IPSEC_AH: + id_map = ipsec_ah_cst; + break; + + case IPSEC_PROTO_IPSEC_ESP: + id_map = ipsec_esp_cst; + break; + + case IPSEC_PROTO_IPCOMP: + id_map = ipsec_ipcomp_cst; + break; + + default: + { + log_print("initiator_send_HASH_SA_NONCE: " + "invalid PROTCOL_ID: %s", protocol_id); + 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]) { + log_error("initiator_send_HASH_SA_NONCE: " + "calloc (%d, %lu) failed", + transform_cnt[prop_no], + (unsigned long)sizeof **transform); + goto bail_out; + } + transform_len[prop_no] = calloc(transform_cnt[prop_no], + sizeof **transform_len); + if (!transform_len[prop_no]) { + log_error("initiator_send_HASH_SA_NONCE: " + "calloc (%d, %lu) failed", + transform_cnt[prop_no], + (unsigned long)sizeof **transform_len); + 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]) { + log_error( + "initiator_send_HASH_SA_NONCE: " + "calloc (%d, 1) failed", + ISAKMP_TRANSFORM_SA_ATTRS_OFF + + 9 * ISAKMP_ATTR_VALUE_OFF); + 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, "LIFE_TYPE", + ipsec_duration_cst, + IPSEC_ATTR_SA_LIFE_TYPE, + &attr); + + /* + * XXX Deals with 16 and 32 + * bit lifetimes only + */ + value = + conf_get_num(life->field, + "LIFE_DURATION", 0); + if (value) { + if (value <= 0xffff) + attr = + attribute_set_basic( + attr, + IPSEC_ATTR_SA_LIFE_DURATION, + value); + else { + value = htonl(value); + attr = + attribute_set_var( + attr, + IPSEC_ATTR_SA_LIFE_DURATION, + (u_int8_t *)&value, + sizeof value); + } + } + } + conf_free_list(life_conf); + } + attribute_set_constant(xf->field, + "ENCAPSULATION_MODE", ipsec_encap_cst, + IPSEC_ATTR_ENCAPSULATION_MODE, &attr); + + if (proto_id != IPSEC_PROTO_IPCOMP) { + 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, + IPSEC_ATTR_GROUP_DESCRIPTION, &attr); + + value = conf_get_num(xf->field, + "KEY_LENGTH", 0); + if (value) + attr = attribute_set_basic( + attr, + IPSEC_ATTR_KEY_LENGTH, + value); + + value = conf_get_num(xf->field, + "KEY_ROUNDS", 0); + if (value) + attr = attribute_set_basic( + attr, + IPSEC_ATTR_KEY_ROUNDS, + value); + } else { + value = conf_get_num(xf->field, + "COMPRESS_DICTIONARY_SIZE", 0); + if (value) + attr = attribute_set_basic( + attr, + IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE, + value); + + value = conf_get_num(xf->field, + "COMPRESS_PRIVATE_ALGORITHM", 0); + if (value) + attr = attribute_set_basic( + attr, + IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM, + value); + } + + value = conf_get_num(xf->field, "ECN_TUNNEL", + 0); + if (value) + attr = attribute_set_basic(attr, + IPSEC_ATTR_ECN_TUNNEL, value); + + /* Record the real transform size. */ + transforms_len[prop_no] += + (transform_len[prop_no][xf_no] + = attr - transform[prop_no][xf_no]); + + if (proto_id != IPSEC_PROTO_IPCOMP) { + /* + * Make sure that if a group + * description is specified, it is + * specified for all transforms + * equally. + */ + attr = + (u_int8_t *)conf_get_str(xf->field, + "GROUP_DESCRIPTION"); + new_group_desc + = attr ? constant_value(ike_group_desc_cst, + (char *)attr) : 0; + if (group_desc == -1) + group_desc = new_group_desc; + else if (group_desc != new_group_desc) { + log_print("initiator_send_HASH_SA_NONCE: " + "differing group descriptions in a proposal"); + goto bail_out; + } + } + } + conf_free_list(xf_conf); + xf_conf = 0; + + /* + * 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) { + log_print("initiator_send_HASH_SA_NONCE: " + "doi->get_spi failed"); + 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]) { + log_error("initiator_send_HASH_SA_NONCE: " + "malloc (%lu) failed", + (unsigned long)proposal_len); + 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) { + log_error("initiator_send_HASH_SA_NONCE: " + "calloc (1, %lu) failed", + (unsigned long)sizeof *proto); + goto bail_out; + } + if (doi->proto_size) { + proto->data = calloc(1, doi->proto_size); + if (!proto->data) { + log_error( + "initiator_send_HASH_SA_NONCE: " + "calloc (1, %lu) failed", + (unsigned long)doi->proto_size); + goto bail_out; + } + } + proto->no = suite_no + 1; + proto->proto = protocol_num; + proto->sa = TAILQ_FIRST(&exchange->sa_list); + proto->xf_cnt = transform_cnt[prop_no]; + TAILQ_INIT(&proto->xfs); + for (xf_no = 0; xf_no < proto->xf_cnt; xf_no++) { + pa = (struct proto_attr *)calloc(1, + sizeof *pa); + if (!pa) + goto bail_out; + pa->len = transform_len[prop_no][xf_no]; + pa->attrs = (u_int8_t *)malloc(pa->len); + if (!pa->attrs) { + free(pa); + goto bail_out; + } + memcpy(pa->attrs, transform[prop_no][xf_no], + pa->len); + TAILQ_INSERT_TAIL(&proto->xfs, pa, next); + } + TAILQ_INSERT_TAIL(&TAILQ_FIRST(&exchange->sa_list)->protos, + proto, link); + + /* Setup the incoming SPI. */ + SET_ISAKMP_PROP_SPI_SZ(proposal[prop_no], spi_sz); + memcpy(proposal[prop_no] + ISAKMP_PROP_SPI_OFF, spi, + spi_sz); + proto->spi_sz[1] = spi_sz; + proto->spi[1] = spi; + + /* + * Let the DOI get at proto for initializing its own + * data. + */ + if (doi->proto_init) + doi->proto_init(proto, prot->field); + + SET_ISAKMP_PROP_NTRANSFORMS(proposal[prop_no], + transform_cnt[prop_no]); + prop_no++; + } + conf_free_list(prot_conf); + prot_conf = 0; + } + + sa_len = ISAKMP_SA_SIT_OFF + IPSEC_SIT_SIT_LEN; + sa_buf = malloc(sa_len); + if (!sa_buf) { + log_error("initiator_send_HASH_SA_NONCE: malloc (%lu) failed", + (unsigned long)sa_len); + 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)) + return -1; + + /* Generate optional KEY_EXCH payload. */ + if (group_desc > 0) { + ie->group = group_get(group_desc); + ie->g_x_len = dh_getlen(ie->group); + + if (ipsec_gen_g_x(msg)) { + group_free(ie->group); + ie->group = 0; + return -1; + } + } + /* Generate optional client ID payloads. XXX Share with responder. */ + local_id = conf_get_str(exchange->name, "Local-ID"); + remote_id = conf_get_str(exchange->name, "Remote-ID"); + if (local_id && remote_id) { + id = ipsec_build_id(local_id, &sz); + if (!id) + return -1; + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "initiator_send_HASH_SA_NONCE: IDic", id, sz)); + if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) { + free(id); + return -1; + } + id = ipsec_build_id(remote_id, &sz); + if (!id) + return -1; + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "initiator_send_HASH_SA_NONCE: IDrc", id, sz)); + if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) { + free(id); + return -1; + } + } + /* XXX I do not judge these as errors, are they? */ + else if (local_id) + log_print("initiator_send_HASH_SA_NONCE: " + "Local-ID given without Remote-ID for \"%s\"", + exchange->name); + else if (remote_id) + /* + * This code supports the "road warrior" case, where the + * initiator doesn't have a fixed IP address, but wants to + * specify a particular remote network to talk to. -- Adrian + * Close <adrian@esec.com.au> + */ + { + log_print("initiator_send_HASH_SA_NONCE: " + "Remote-ID given without Local-ID for \"%s\"", + exchange->name); + + /* + * If we're here, then we are the initiator, so use initiator + * address for local ID + */ + msg->transport->vtbl->get_src(msg->transport, &src); + sz = ISAKMP_ID_SZ + sockaddr_addrlen(src); + + id = calloc(sz, sizeof(char)); + if (!id) { + log_error("initiator_send_HASH_SA_NONCE: " + "calloc (%lu, %lu) failed", (unsigned long)sz, + (unsigned long)sizeof(char)); + return -1; + } + switch (src->sa_family) { + case AF_INET6: + SET_ISAKMP_ID_TYPE(id, IPSEC_ID_IPV6_ADDR); + break; + case AF_INET: + SET_ISAKMP_ID_TYPE(id, IPSEC_ID_IPV4_ADDR); + break; + default: + log_error("initiator_send_HASH_SA_NONCE: " + "unknown sa_family %d", src->sa_family); + free(id); + return -1; + } + memcpy(id + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(src), + sockaddr_addrlen(src)); + + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "initiator_send_HASH_SA_NONCE: IDic", id, sz)); + if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) { + free(id); + return -1; + } + /* Send supplied remote_id */ + id = ipsec_build_id(remote_id, &sz); + if (!id) + return -1; + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "initiator_send_HASH_SA_NONCE: IDrc", id, sz)); + if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) { + free(id); + return -1; + } + } + if (ipsec_fill_in_hash(msg)) + goto bail_out; + + conf_free_list(suite_conf); + for (i = 0; i < prop_no; i++) { + free(transform[i]); + free(transform_len[i]); + } + free(proposal); + free(transform); + free(transforms_len); + free(transform_len); + free(transform_cnt); + 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 < transform_cnt[i]; + 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); + 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, *next_proto; + struct payload *sa_p = payload_first(msg, ISAKMP_PAYLOAD_SA); + struct payload *xf, *idp; + struct payload *hashp = payload_first(msg, ISAKMP_PAYLOAD_HASH); + struct payload *kep = payload_first(msg, 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; + struct sockaddr *src, *dst; + + /* Allocate the prf and start calculating our HASH(1). XXX Share? */ + LOG_DBG_BUF((LOG_NEGOTIATION, 90, "initiator_recv_HASH_SA_NONCE: " + "SKEYID_a", (u_int8_t *)isa->skeyid_a, isa->skeyid_len)); + prf = prf_alloc(isa->prf_type, hash->type, isa->skeyid_a, + isa->skeyid_len); + if (!prf) + return -1; + + prf->Init(prf->prfctx); + LOG_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 80, + "initiator_recv_HASH_SA_NONCE: computed HASH(2)", hash->digest, + hashsize)); + if (memcmp(hashp->p + ISAKMP_HASH_DATA_OFF, hash->digest, hashsize) + != 0) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 1, + 0); + return -1; + } + /* Mark the HASH as handled. */ + hashp->flags |= PL_MARK; + + /* Mark message as authenticated. */ + msg->flags |= MSG_AUTHENTICATED; + + /* + * 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 not supported yet"); + return -1; + } + sa = TAILQ_FIRST(&exchange->sa_list); + + /* This is here for the policy check */ + if (kep) + ie->pfs = 1; + + /* Handle optional client ID payloads. */ + idp = payload_first(msg, ISAKMP_PAYLOAD_ID); + if (idp) { + /* If IDci is there, IDcr must be too. */ + if (!TAILQ_NEXT(idp, link)) { + /* XXX Is this a good notify type? */ + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, + 1, 0); + return -1; + } + /* XXX We should really compare, not override. */ + ie->id_ci_sz = GET_ISAKMP_GEN_LENGTH(idp->p); + ie->id_ci = malloc(ie->id_ci_sz); + if (!ie->id_ci) { + log_error("initiator_recv_HASH_SA_NONCE: " + "malloc (%lu) failed", + (unsigned long)ie->id_ci_sz); + return -1; + } + memcpy(ie->id_ci, idp->p, ie->id_ci_sz); + idp->flags |= PL_MARK; + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "initiator_recv_HASH_SA_NONCE: IDci", + ie->id_ci + ISAKMP_GEN_SZ, ie->id_ci_sz - ISAKMP_GEN_SZ)); + + idp = TAILQ_NEXT(idp, link); + ie->id_cr_sz = GET_ISAKMP_GEN_LENGTH(idp->p); + ie->id_cr = malloc(ie->id_cr_sz); + if (!ie->id_cr) { + log_error("initiator_recv_HASH_SA_NONCE: " + "malloc (%lu) failed", + (unsigned long)ie->id_cr_sz); + return -1; + } + memcpy(ie->id_cr, idp->p, ie->id_cr_sz); + idp->flags |= PL_MARK; + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "initiator_recv_HASH_SA_NONCE: IDcr", + ie->id_cr + ISAKMP_GEN_SZ, ie->id_cr_sz - ISAKMP_GEN_SZ)); + } else { + /* + * If client identifiers are not present in the exchange, + * we fake them. RFC 2409 states: + * The identities of the SAs negotiated in Quick Mode are + * implicitly assumed to be the IP addresses of the ISAKMP + * peers, without any constraints on the protocol or port + * numbers allowed, unless client identifiers are specified + * in Quick Mode. + * + * -- Michael Paddon (mwp@aba.net.au) + */ + + ie->flags = IPSEC_EXCH_FLAG_NO_ID; + + /* Get initiator and responder addresses. */ + msg->transport->vtbl->get_src(msg->transport, &src); + msg->transport->vtbl->get_dst(msg->transport, &dst); + ie->id_ci_sz = ISAKMP_ID_DATA_OFF + sockaddr_addrlen(src); + ie->id_cr_sz = ISAKMP_ID_DATA_OFF + sockaddr_addrlen(dst); + ie->id_ci = calloc(ie->id_ci_sz, sizeof(char)); + ie->id_cr = calloc(ie->id_cr_sz, sizeof(char)); + + if (!ie->id_ci || !ie->id_cr) { + log_error("initiator_recv_HASH_SA_NONCE: " + "calloc (%lu, %lu) failed", + (unsigned long)ie->id_cr_sz, + (unsigned long)sizeof(char)); + if (ie->id_ci) { + free(ie->id_ci); + ie->id_ci = 0; + } + if (ie->id_cr) { + free(ie->id_cr); + ie->id_cr = 0; + } + return -1; + } + if (src->sa_family != dst->sa_family) { + log_error("initiator_recv_HASH_SA_NONCE: " + "sa_family mismatch"); + free(ie->id_ci); + ie->id_ci = 0; + free(ie->id_cr); + ie->id_cr = 0; + return -1; + } + switch (src->sa_family) { + case AF_INET: + SET_ISAKMP_ID_TYPE(ie->id_ci, IPSEC_ID_IPV4_ADDR); + SET_ISAKMP_ID_TYPE(ie->id_cr, IPSEC_ID_IPV4_ADDR); + break; + + case AF_INET6: + SET_ISAKMP_ID_TYPE(ie->id_ci, IPSEC_ID_IPV6_ADDR); + SET_ISAKMP_ID_TYPE(ie->id_cr, IPSEC_ID_IPV6_ADDR); + break; + + default: + log_error("initiator_recv_HASH_SA_NONCE: " + "unknown sa_family %d", src->sa_family); + free(ie->id_ci); + ie->id_ci = 0; + free(ie->id_cr); + ie->id_cr = 0; + return -1; + } + memcpy(ie->id_ci + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(src), + sockaddr_addrlen(src)); + memcpy(ie->id_cr + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(dst), + sockaddr_addrlen(dst)); + } + + /* Build the protection suite in our SA. */ + for (xf = payload_first(msg, 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)) + 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 = next_proto) { + next_proto = TAILQ_NEXT(proto, link); + if (!proto->chosen) + proto_free(proto); + } + +#ifdef USE_POLICY + if (!check_policy(exchange, sa, msg->isakmp_sa)) { + message_drop(msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0); + log_print("initiator_recv_HASH_SA_NONCE: policy check failed"); + return -1; + } +#endif + + /* Mark the SA as handled. */ + sa_p->flags |= PL_MARK; + + isa = sa->data; + if ((isa->group_desc && + (!ie->group || ie->group->id != isa->group_desc)) || + (!isa->group_desc && ie->group)) { + log_print("initiator_recv_HASH_SA_NONCE: disagreement on PFS"); + return -1; + } + /* Copy out the initiator's nonce. */ + if (exchange_save_nonce(msg)) + return -1; + + /* Handle the optional KEY_EXCH payload. */ + if (kep && ipsec_save_g_x(msg)) + return -1; + + 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) { + log_error("initiator_send_HASH: malloc (%lu) failed", + ISAKMP_HASH_SZ + (unsigned long)hashsize); + return -1; + } + if (message_add_payload(msg, ISAKMP_PAYLOAD_HASH, buf, + ISAKMP_HASH_SZ + hashsize, 1)) { + free(buf); + return -1; + } + /* Allocate the prf and start calculating our HASH(3). XXX Share? */ + LOG_DBG_BUF((LOG_NEGOTIATION, 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) + return -1; + prf->Init(prf->prfctx); + prf->Update(prf->prfctx, (unsigned char *)"\0", 1); + LOG_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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; + 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)) { + if (proto->proto == IPSEC_PROTO_IPCOMP) + continue; + + 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]) { + log_error("post_quick_mode: " + "malloc (%lu) failed", + (((unsigned long)ie->keymat_len + + prf->blocksize - 1) / prf->blocksize) * + prf->blocksize); + /* XXX What more 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_DBG_BUF((LOG_NEGOTIATION, + 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_DBG_BUF((LOG_NEGOTIATION, + 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_DBG((LOG_NEGOTIATION, 90, + "post_quick_mode: " + "suite %d proto %d", proto->no, + proto->proto)); + prf->Update(prf->prfctx, &proto->proto, + 1); + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "post_quick_mode: SPI", + proto->spi[i], proto->spi_sz[i])); + prf->Update(prf->prfctx, + proto->spi[i], proto->spi_sz[i]); + LOG_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 90, + "post_quick_mode: KEYMAT", + iproto->keymat[i], ie->keymat_len)); + } + } + } + + log_verbose("isakmpd: quick mode done: %s", + !msg->isakmp_sa || !msg->isakmp_sa->transport ? "<no transport>" + : msg->isakmp_sa->transport->vtbl->decode_ids + (msg->isakmp_sa->transport)); +} + +/* + * 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, *idp; + 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 = 0; + 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; + struct sockaddr *src, *dst; + char *name; + + hashp = payload_first(msg, 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, 1, 0); + goto cleanup; + } + hash_len = GET_ISAKMP_GEN_LENGTH(hash); + my_hash = malloc(hash_len - ISAKMP_GEN_SZ); + if (!my_hash) { + log_error("responder_recv_HASH_SA_NONCE: malloc (%lu) failed", + (unsigned long)hash_len - ISAKMP_GEN_SZ); + goto cleanup; + } + /* + * Check the payload's integrity. + * XXX Share with ipsec_fill_in_hash? + */ + LOG_DBG_BUF((LOG_NEGOTIATION, 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) + goto cleanup; + prf->Init(prf->prfctx); + LOG_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, + 1, 0); + goto cleanup; + } + free(my_hash); + my_hash = 0; + + /* Mark message as authenticated. */ + msg->flags |= MSG_AUTHENTICATED; + + kep = payload_first(msg, ISAKMP_PAYLOAD_KEY_EXCH); + if (kep) + ie->pfs = 1; + + /* Handle optional client ID payloads. */ + idp = payload_first(msg, ISAKMP_PAYLOAD_ID); + if (idp) { + /* If IDci is there, IDcr must be too. */ + if (!TAILQ_NEXT(idp, link)) { + /* XXX Is this a good notify type? */ + message_drop(msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, + 1, 0); + goto cleanup; + } + ie->id_ci_sz = GET_ISAKMP_GEN_LENGTH(idp->p); + ie->id_ci = malloc(ie->id_ci_sz); + if (!ie->id_ci) { + log_error("responder_recv_HASH_SA_NONCE: " + "malloc (%lu) failed", + (unsigned long)ie->id_ci_sz); + goto cleanup; + } + memcpy(ie->id_ci, idp->p, ie->id_ci_sz); + idp->flags |= PL_MARK; + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "responder_recv_HASH_SA_NONCE: IDci", + ie->id_ci + ISAKMP_GEN_SZ, ie->id_ci_sz - ISAKMP_GEN_SZ)); + + idp = TAILQ_NEXT(idp, link); + ie->id_cr_sz = GET_ISAKMP_GEN_LENGTH(idp->p); + ie->id_cr = malloc(ie->id_cr_sz); + if (!ie->id_cr) { + log_error("responder_recv_HASH_SA_NONCE: " + "malloc (%lu) failed", + (unsigned long)ie->id_cr_sz); + goto cleanup; + } + memcpy(ie->id_cr, idp->p, ie->id_cr_sz); + idp->flags |= PL_MARK; + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "responder_recv_HASH_SA_NONCE: IDcr", + ie->id_cr + ISAKMP_GEN_SZ, ie->id_cr_sz - ISAKMP_GEN_SZ)); + } else { + /* + * If client identifiers are not present in the exchange, + * we fake them. RFC 2409 states: + * The identities of the SAs negotiated in Quick Mode are + * implicitly assumed to be the IP addresses of the ISAKMP + * peers, without any constraints on the protocol or port + * numbers allowed, unless client identifiers are specified + * in Quick Mode. + * + * -- Michael Paddon (mwp@aba.net.au) + */ + + ie->flags = IPSEC_EXCH_FLAG_NO_ID; + + /* Get initiator and responder addresses. */ + msg->transport->vtbl->get_src(msg->transport, &src); + msg->transport->vtbl->get_dst(msg->transport, &dst); + ie->id_ci_sz = ISAKMP_ID_DATA_OFF + sockaddr_addrlen(src); + ie->id_cr_sz = ISAKMP_ID_DATA_OFF + sockaddr_addrlen(dst); + ie->id_ci = calloc(ie->id_ci_sz, sizeof(char)); + ie->id_cr = calloc(ie->id_cr_sz, sizeof(char)); + + if (!ie->id_ci || !ie->id_cr) { + log_error("responder_recv_HASH_SA_NONCE: " + "calloc (%lu, %lu) failed", + (unsigned long)ie->id_ci_sz, + (unsigned long)sizeof(char)); + goto cleanup; + } + if (src->sa_family != dst->sa_family) { + log_error("initiator_recv_HASH_SA_NONCE: " + "sa_family mismatch"); + goto cleanup; + } + switch (src->sa_family) { + case AF_INET: + SET_ISAKMP_ID_TYPE(ie->id_ci, IPSEC_ID_IPV4_ADDR); + SET_ISAKMP_ID_TYPE(ie->id_cr, IPSEC_ID_IPV4_ADDR); + break; + + case AF_INET6: + SET_ISAKMP_ID_TYPE(ie->id_ci, IPSEC_ID_IPV6_ADDR); + SET_ISAKMP_ID_TYPE(ie->id_cr, IPSEC_ID_IPV6_ADDR); + break; + + default: + log_error("initiator_recv_HASH_SA_NONCE: " + "unknown sa_family %d", src->sa_family); + goto cleanup; + } + + memcpy(ie->id_cr + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(src), + sockaddr_addrlen(src)); + memcpy(ie->id_ci + ISAKMP_ID_DATA_OFF, sockaddr_addrdata(dst), + sockaddr_addrlen(dst)); + } + +#ifdef USE_POLICY +#ifdef USE_KEYNOTE + if (message_negotiate_sa(msg, check_policy)) + goto cleanup; +#else + if (message_negotiate_sa(msg, 0)) + goto cleanup; +#endif +#else + if (message_negotiate_sa(msg, 0)) + goto cleanup; +#endif /* USE_POLICY */ + + 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); + if (proto->proto == IPSEC_PROTO_IPSEC_AH + && !((struct ipsec_proto *)proto->data)->auth) { + log_print("responder_recv_HASH_SA_NONCE: " + "AH proposed without an algorithm " + "attribute"); + message_drop(msg, + ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0); + goto next_sa; + } + } + + isa = sa->data; + + /* + * The group description is mandatory if we got a KEY_EXCH + * payload. + */ + if (kep) { + if (!isa->group_desc) { + log_print("responder_recv_HASH_SA_NONCE: " + "KEY_EXCH payload without a group " + "desc. attribute"); + message_drop(msg, + ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0); + continue; + } + /* Also, all SAs must have equal groups. */ + if (!group_desc) + group_desc = isa->group_desc; + else if (group_desc != isa->group_desc) { + log_print("responder_recv_HASH_SA_NONCE: " + "differing group descriptions in one QM"); + message_drop(msg, + ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0); + continue; + } + } + /* At least one SA was accepted. */ + retval = 0; + +next_sa: + ; /* XXX gcc3 wants this. */ + } + + if (kep) { + ie->group = group_get(group_desc); + if (!ie->group) { + /* + * XXX If the error was due to an out-of-range group + * description we should notify our peer, but this + * should probably be done by the attribute + * validation. Is it? + */ + goto cleanup; + } + } + /* Copy out the initiator's nonce. */ + if (exchange_save_nonce(msg)) + goto cleanup; + + /* Handle the optional KEY_EXCH payload. */ + if (kep && ipsec_save_g_x(msg)) + goto cleanup; + + /* + * Try to find and set the connection name on the exchange. + */ + + /* + * Check for accepted identities as well as lookup the connection + * name and set it on the exchange. + * + * When not using policies make sure the peer proposes sane IDs. + * Otherwise this is done by KeyNote. + */ + name = connection_passive_lookup_by_ids(ie->id_ci, ie->id_cr); + if (name) { + exchange->name = strdup(name); + if (!exchange->name) { + log_error("responder_recv_HASH_SA_NONCE: " + "strdup (\"%s\") failed", name); + goto cleanup; + } + } else if ( +#ifdef USE_X509 + ignore_policy || +#endif + strncmp("yes", conf_get_str("General", "Use-Keynote"), 3)) { + log_print("responder_recv_HASH_SA_NONCE: peer proposed " + "invalid phase 2 IDs: %s", + (exchange->doi->decode_ids("initiator id %s, responder" + " id %s", ie->id_ci, ie->id_ci_sz, ie->id_cr, + ie->id_cr_sz, 1))); + message_drop(msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 1, 0); + goto cleanup; + } + + return retval; + +cleanup: + /* Remove all potential protocols that have been added to the SAs. */ + for (sa = TAILQ_FIRST(&exchange->sa_list); sa; + sa = TAILQ_NEXT(sa, next)) + while ((proto = TAILQ_FIRST(&sa->protos)) != 0) + proto_free(proto); + if (my_hash) + free(my_hash); + if (ie->id_ci) { + free(ie->id_ci); + ie->id_ci = 0; + } + if (ie->id_cr) { + free(ie->id_cr); + ie->id_cr = 0; + } + return -1; +} + +/* 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]; + u_int32_t i; + u_int8_t *id; + size_t sz; + + /* + * We want a HASH payload to start with. XXX Share with + * ike_main_mode.c? + */ + buf = malloc(ISAKMP_HASH_SZ + hashsize); + if (!buf) { + log_error("responder_send_HASH_SA_NONCE: malloc (%lu) failed", + ISAKMP_HASH_SZ + (unsigned long)hashsize); + return -1; + } + if (message_add_payload(msg, ISAKMP_PAYLOAD_HASH, buf, + ISAKMP_HASH_SZ + hashsize, 1)) { + free(buf); + return -1; + } + /* Add the SA payload(s) with the transform(s) that was/were chosen. */ + if (message_add_sa_payload(msg)) + return -1; + + /* Generate a nonce, and add it to the message. */ + if (exchange_gen_nonce(msg, nonce_sz)) + return -1; + + /* Generate optional KEY_EXCH payload. This is known as PFS. */ + if (ie->group && ipsec_gen_g_x(msg)) + return -1; + + /* + * If the initiator client ID's were acceptable, just mirror them + * back. + */ + if (!(ie->flags & IPSEC_EXCH_FLAG_NO_ID)) { + sz = ie->id_ci_sz; + id = malloc(sz); + if (!id) { + log_error("responder_send_HASH_SA_NONCE: " + "malloc (%lu) failed", (unsigned long)sz); + return -1; + } + memcpy(id, ie->id_ci, sz); + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "responder_send_HASH_SA_NONCE: IDic", id, sz)); + if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) { + free(id); + return -1; + } + sz = ie->id_cr_sz; + id = malloc(sz); + if (!id) { + log_error("responder_send_HASH_SA_NONCE: " + "malloc (%lu) failed", (unsigned long)sz); + return -1; + } + memcpy(id, ie->id_cr, sz); + LOG_DBG_BUF((LOG_NEGOTIATION, 90, + "responder_send_HASH_SA_NONCE: IDrc", id, sz)); + if (message_add_payload(msg, ISAKMP_PAYLOAD_ID, id, sz, 1)) { + free(id); + return -1; + } + } + /* Allocate the prf and start calculating our HASH(2). XXX Share? */ + LOG_DBG((LOG_NEGOTIATION, 90, "responder_recv_HASH: " + "isakmp_sa %p isa %p", isakmp_sa, isa)); + LOG_DBG_BUF((LOG_NEGOTIATION, 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) + return -1; + prf->Init(prf->prfctx); + LOG_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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, sizeof header, + "responder_send_HASH_SA_NONCE: payload %d after HASH(2)", + i - 1); + LOG_DBG_BUF((LOG_NEGOTIATION, 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, sizeof header, "responder_send_HASH_SA_NONCE: " + "HASH_%c", initiator ? 'I' : 'R'); + LOG_DBG_BUF((LOG_NEGOTIATION, 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) { + log_error("gen_g_xy: malloc (%lu) failed", + (unsigned long)ie->g_x_len); + return; + } + if (dh_create_shared(ie->group, ie->g_xy, + exchange->initiator ? ie->g_xr : ie->g_xi)) { + log_print("gen_g_xy: dh_create_shared failed"); + return; + } + LOG_DBG_BUF((LOG_NEGOTIATION, 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 = 0; + size_t hash_len; + struct payload *hashp; + + /* Find HASH(3) and create our own hash, just as big. */ + hashp = payload_first(msg, 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) { + log_error("responder_recv_HASH: malloc (%lu) failed", + (unsigned long)hash_len - ISAKMP_GEN_SZ); + goto cleanup; + } + /* Allocate the prf and start calculating our HASH(3). XXX Share? */ + LOG_DBG((LOG_NEGOTIATION, 90, "responder_recv_HASH: " + "isakmp_sa %p isa %p", isakmp_sa, isa)); + LOG_DBG_BUF((LOG_NEGOTIATION, 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) + goto cleanup; + prf->Init(prf->prfctx); + prf->Update(prf->prfctx, (unsigned char *)"\0", 1); + LOG_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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_DBG_BUF((LOG_NEGOTIATION, 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) { + message_drop(msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, + 1, 0); + goto cleanup; + } + free(my_hash); + + /* Mark message as authenticated. */ + msg->flags |= MSG_AUTHENTICATED; + + post_quick_mode(msg); + + return 0; + +cleanup: + if (my_hash) + free(my_hash); + return -1; +} |