summaryrefslogtreecommitdiff
path: root/keyexchange/isakmpd-20041012/ike_quick_mode.c
diff options
context:
space:
mode:
authorOthmar Gsenger <otti@anytun.org>2007-07-30 19:37:53 +0000
committerOthmar Gsenger <otti@anytun.org>2007-07-30 19:37:53 +0000
commit6585e5ad764ee2414d9b01f30784b6549bc8f58e (patch)
tree4ea258d5327838363dc3ac66d09ecc94686f3e26 /keyexchange/isakmpd-20041012/ike_quick_mode.c
parentripe requests, final (diff)
added keyexchange
Diffstat (limited to 'keyexchange/isakmpd-20041012/ike_quick_mode.c')
-rw-r--r--keyexchange/isakmpd-20041012/ike_quick_mode.c1989
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;
+}