File: [local] / src / sbin / isakmpd / ike_quick_mode.c (download)
Revision 1.38, Mon Oct 16 23:29:07 2000 UTC (23 years, 7 months ago) by niklas
Branch: MAIN
CVS Tags: OPENBSD_2_8_BASE Branch point for: OPENBSD_2_8
Changes since 1.37: +13 -8 lines
Merge with EOM 1.135
author: provos
better referencing. okay niklas@
author: angelos
Eliminate bogus freeing of static variable.
|
/* $OpenBSD: ike_quick_mode.c,v 1.38 2000/10/16 23:29:07 niklas Exp $ */
/* $EOM: ike_quick_mode.c,v 1.135 2000/10/16 18:16:59 provos Exp $ */
/*
* Copyright (c) 1998, 1999, 2000 Niklas Hallqvist. All rights reserved.
* Copyright (c) 1999, 2000 Angelos D. Keromytis. All rights reserved.
* Copyright (c) 2000 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Ericsson Radio Systems.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This code was written under funding by Ericsson Radio Systems.
*/
#include <stdlib.h>
#include <string.h>
#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"
#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 = NULL;
int i, result = 0, nprinc = 0;
int *x509_ids = NULL, *keynote_ids = NULL;
#ifdef USE_X509
struct keynote_deckey dc;
X509_NAME *subject;
RSA *key;
#endif
/* Initialize if necessary -- e.g., if pre-shared key auth was used */
if (isakmp_sa->policy_id < 0)
{
if ((isakmp_sa->policy_id = LK (kn_init, ())) == -1)
{
log_print ("check_policy: failed to initialize policy session");
return 0;
}
}
/* Add the callback that will handle attributes. */
if (LK (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);
LK (kn_close, (isakmp_sa->policy_id));
isakmp_sa->policy_id = -1;
return 0;
}
if (keynote_policy_asserts_num)
{
keynote_ids = calloc (keynote_policy_asserts_num, sizeof(int));
if (keynote_ids == NULL)
{
log_print ("check_policy: failed to allocate %d bytes for book keeping", keynote_policy_asserts_num * sizeof(int));
return 0;
}
}
if (x509_policy_asserts_num)
{
x509_ids = calloc (x509_policy_asserts_num, sizeof(int));
if (x509_ids == NULL)
{
log_print ("check_policy: failed to allocate %d bytes for book keeping", x509_policy_asserts_num * sizeof(int));
free (keynote_ids);
return 0;
}
}
/* Add the policy assertions */
for (i = 0; i < keynote_policy_asserts_num; i++)
keynote_ids[i] = LK (kn_add_assertion, (isakmp_sa->policy_id,
keynote_policy_asserts[i],
strlen (keynote_policy_asserts[i]),
ASSERT_FLAG_LOCAL));
for (i = 0; i < x509_policy_asserts_num; i++)
x509_ids[i] = LK (kn_add_assertion, (isakmp_sa->policy_id,
x509_policy_asserts[i],
strlen (x509_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 = 1;
principal = calloc (nprinc, sizeof(*principal));
if (principal == NULL)
{
log_print ("check_policy: failed to allocate %d bytes",
nprinc * sizeof(*principal));
goto policydone;
}
principal[0] = calloc (isakmp_sa->recv_certlen + 1 +
strlen ("passphrase:"), sizeof (char));
if (principal[0] == NULL)
{
log_print ("check_policy: failed to allocate %d bytes",
isakmp_sa->recv_certlen + 1 + strlen ("passphrase:"));
free (principal);
goto policydone;
}
strcpy (principal[0], "passphrase:");
memcpy (principal[0] + strlen ("passphrase:"), isakmp_sa->recv_cert,
isakmp_sa->recv_certlen);
break;
case ISAKMP_CERTENC_KEYNOTE:
#ifdef USE_KEYNOTE
nprinc = 1;
principal = calloc (nprinc, sizeof(*principal));
if (principal == NULL)
{
log_print ("check_policy: failed to allocate %d bytes",
nprinc * sizeof(*principal));
goto policydone;
}
/* Dup the keys */
principal[0] = strdup (isakmp_sa->recv_key);
if (principal[0] == NULL)
{
log_print ("check_policy: failed to allocate %d bytes",
strlen (isakmp_sa->recv_key));
free (principal);
goto policydone;
}
#endif
break;
case ISAKMP_CERTENC_X509_SIG:
#ifdef USE_X509
/* Retrieve key from certificate. */
if (!x509_cert_get_key (isakmp_sa->recv_cert, &key))
{
log_print ("check_policy: failed to get key from X509 cert");
goto policydone;
}
principal = calloc (2, sizeof(*principal));
if (principal == NULL)
{
log_print ("check_policy: failed to get memory for principal");
goto policydone;
}
/* XXX RSA-specific. */
dc.dec_algorithm = KEYNOTE_ALGORITHM_RSA;
dc.dec_key = (void *) key;
principal[0] = LK (kn_encode_key, (&dc, INTERNAL_ENC_PKCS1, ENCODING_HEX,
KEYNOTE_PUBLIC_KEY));
if (LKV (keynote_errno) == ERROR_MEMORY)
{
log_print ("check_policy: failed to get memory for public key");
LC (RSA_free, (key));
free (principal);
goto policydone;
}
if (principal[0] == NULL)
{
log_print ("check_policy: failed to allocate memory for principal");
LC (RSA_free, (key));
free (principal);
goto policydone;
}
principal[1] = calloc (strlen (principal[0]) + strlen ("rsa-hex:") + 1,
sizeof (char));
if (principal[1] == NULL)
{
log_print ("check_policy: failed to allocate memory for principal");
free (principal[0]);
free (principal);
LC (RSA_free, (key));
goto policydone;
}
strcpy (principal[1], "rsa-hex:");
strcpy (principal[1] + strlen ("rsa-hex:"), principal[0]);
free (principal[0]);
LC (RSA_free, (key));
principal[0] = principal[1];
principal[1] = NULL;
/* Generate a "DN:" principal */
subject = LC (X509_get_subject_name, (isakmp_sa->recv_cert));
if (subject)
{
principal[1] = calloc (259, sizeof (char));
if (principal[1] == NULL)
{
log_print ("check_policy: failed to allocate memory for principal[1]");
free (principal[0]);
free (principal);
LC (RSA_free, (key));
goto policydone;
}
strcpy (principal[1], "DN:");
LC (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++)
{
if (LK (kn_add_authorizer, (isakmp_sa->policy_id, principal[i])) == -1)
{
int j;
for (j = 0; j < i; j++)
{
LK (kn_remove_authorizer, (isakmp_sa->policy_id,
principal[j]));
free (principal[j]);
}
for (; j < nprinc; j++)
free (principal[j]);
free (principal);
log_print ("check_policy: kn_add_authorizer failed");
goto policydone;
}
}
/* Ask policy. */
result = LK (kn_do_query, (isakmp_sa->policy_id, return_values,
RETVALUES_NUM));
LOG_DBG ((LOG_MISC, 40, "check_policy: kn_do_query returned %d", result));
/* Cleanup environment */
LK (kn_cleanup_action_environment, (isakmp_sa->policy_id));
/* Remove authorizers from the session. */
for (i = 0; i < nprinc; i++)
{
LK (kn_remove_authorizer, (isakmp_sa->policy_id, principal[i]));
free (principal[i]);
}
free (principal);
/* Check what policy said. */
if (result < 0)
{
LOG_DBG ((LOG_MISC, 40, "check_policy: proposal refused"));
result = 0;
goto policydone;
}
policydone:
/* Remove the policies */
for (i = 0; i < keynote_policy_asserts_num; i++)
{
if (keynote_ids[i] != -1)
LK (kn_remove_assertion, (isakmp_sa->policy_id, keynote_ids[i]));
}
for (i = 0; i < x509_policy_asserts_num; i++)
{
if (x509_ids[i] != -1)
LK (kn_remove_assertion, (isakmp_sa->policy_id, x509_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;
int *transform_cnt = 0, *new_transform_cnt;
int i, suite_no, prop_no, prot_no, xf_no, value, update_nextp, protocol_num;
int prop_cnt = 0;
struct proto *proto;
struct conf_list *suite_conf, *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);
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, %d) failed",
proposal, prop_cnt * 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, %d) failed",
transforms_len,
prop_cnt * 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, %d) failed",
transform, prop_cnt * 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, %d) failed",
transform_cnt, prop_cnt * 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, %d) failed",
transform_len, prop_cnt * 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;
/* XXX Not too beautiful, but do we have a choice? */
id_map = strcasecmp (protocol_id, "IPSEC_AH") == 0 ? ipsec_ah_cst
: strcasecmp (protocol_id, "IPSEC_ESP") == 0 ? ipsec_esp_cst
: strcasecmp (protocol_id, "IPCOMP") == 0 ? ipsec_ipcomp_cst : 0;
if (!id_map)
goto bail_out;
/* Now get each transform we offer for this protocol. */
xf_conf = conf_get_list (prot->field, "Transforms");
if (!xf_conf)
goto bail_out;
transform_cnt[prop_no] = xf_conf->cnt;
transform[prop_no] = calloc (transform_cnt[prop_no],
sizeof **transform);
if (!transform[prop_no])
{
log_error ("initiator_send_HASH_SA_NONCE: "
"calloc (%d, %d) failed",
transform_cnt[prop_no], 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, %d) failed",
transform_cnt[prop_no], 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,
(char *)&value,
sizeof value);
}
}
}
conf_free_list (life_conf);
}
attribute_set_constant (xf->field, "ENCAPSULATION_MODE",
ipsec_encap_cst,
IPSEC_ATTR_ENCAPSULATION_MODE, &attr);
attribute_set_constant (xf->field, "AUTHENTICATION_ALGORITHM",
ipsec_auth_cst,
IPSEC_ATTR_AUTHENTICATION_ALGORITHM,
&attr);
attribute_set_constant (xf->field, "GROUP_DESCRIPTION",
ike_group_desc_cst,
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);
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);
/* Record the real transform size. */
transforms_len[prop_no] += (transform_len[prop_no][xf_no]
= attr - transform[prop_no][xf_no]);
/*
* Make sure that if a group description is specified, it is
* specified for all transforms equally.
*/
attr = conf_get_str (xf->field, "GROUP_DESCRIPTION");
new_group_desc
= attr ? constant_value (ike_group_desc_cst, 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)
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 (%d) failed",
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, %d) failed",
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, %d) failed", doi->proto_size);
goto bail_out;
}
}
proto->no = suite_no + 1;
proto->proto = protocol_num;
proto->sa = TAILQ_FIRST (&exchange->sa_list);
TAILQ_INSERT_TAIL (&TAILQ_FIRST (&exchange->sa_list)->protos, proto,
link);
/* 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 (%d) failed", 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)
{
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_MISC, 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_MISC, 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)
log_print ("initiator_send_HASH_SA_NONCE: "
"Remote-ID given without Local-ID for \"%s\"",
exchange->name);
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 = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SA]);
struct payload *xf, *idp;
struct payload *hashp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_HASH]);
struct payload *kep = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_KEY_EXCH]);
struct prf *prf;
struct sa *isakmp_sa = msg->isakmp_sa;
struct ipsec_sa *isa = isakmp_sa->data;
struct hash *hash = hash_get (isa->hash);
size_t hashsize = hash->hashsize;
u_int8_t *rest;
size_t rest_len;
struct sockaddr *src, *dst;
socklen_t srclen, dstlen;
/*
* 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 = TAILQ_FIRST (&msg->payload[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 (%d) failed",
ie->id_ci_sz);
return -1;
}
memcpy (ie->id_ci, idp->p, ie->id_ci_sz);
idp->flags |= PL_MARK;
LOG_DBG_BUF ((LOG_MISC, 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 (%d) failed",
ie->id_cr_sz);
return -1;
}
memcpy (ie->id_cr, idp->p, ie->id_cr_sz);
idp->flags |= PL_MARK;
LOG_DBG_BUF ((LOG_MISC, 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 responder address. */
msg->transport->vtbl->get_dst (msg->transport, &dst, &dstlen);
ie->id_cr_sz = ISAKMP_ID_DATA_OFF
+ sizeof ((struct sockaddr_in *)dst)->sin_addr.s_addr;
ie->id_cr = calloc (ie->id_cr_sz, sizeof (char));
if (!ie->id_cr)
{
log_error ("initiator_recv_HASH_SA_NONCE: malloc (%d) failed",
ie->id_cr_sz);
return -1;
}
SET_ISAKMP_ID_TYPE (ie->id_cr, IPSEC_ID_IPV4_ADDR);
memcpy (ie->id_cr + ISAKMP_ID_DATA_OFF,
&((struct sockaddr_in *)dst)->sin_addr.s_addr,
sizeof ((struct sockaddr_in *)dst)->sin_addr.s_addr);
/* Get initiator address. */
msg->transport->vtbl->get_src (msg->transport, &src, &srclen);
ie->id_ci_sz = ISAKMP_ID_DATA_OFF
+ sizeof ((struct sockaddr_in *)dst)->sin_addr.s_addr;
ie->id_ci = calloc (ie->id_ci_sz, sizeof (char));
if (!ie->id_ci)
{
log_error ("initiator_recv_HASH_SA_NONCE: malloc (%d) failed",
ie->id_ci_sz);
return -1;
}
SET_ISAKMP_ID_TYPE (ie->id_ci, IPSEC_ID_IPV4_ADDR);
memcpy (ie->id_ci + ISAKMP_ID_DATA_OFF,
&((struct sockaddr_in *)src)->sin_addr.s_addr,
sizeof ((struct sockaddr_in *)src)->sin_addr.s_addr);
}
/* Build the protection suite in our SA. */
for (xf = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_TRANSFORM]); xf;
xf = TAILQ_NEXT (xf, link))
{
/*
* XXX We could check that the proposal each transform belongs to
* is unique.
*/
if (sa_add_transform (sa, xf, exchange->initiator, &proto))
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;
/* Allocate the prf and start calculating our HASH(1). XXX Share? */
LOG_DBG_BUF ((LOG_MISC, 90, "initiator_recv_HASH_SA_NONCE: SKEYID_a",
isa->skeyid_a, isa->skeyid_len));
prf = prf_alloc (isa->prf_type, hash->type, isa->skeyid_a, isa->skeyid_len);
if (!prf)
return -1;
prf->Init (prf->prfctx);
LOG_DBG_BUF ((LOG_MISC, 90, "initiator_recv_HASH_SA_NONCE: message_id",
exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN);
LOG_DBG_BUF ((LOG_MISC, 90, "initiator_recv_HASH_SA_NONCE: NONCE_I_b",
exchange->nonce_i, exchange->nonce_i_len));
prf->Update (prf->prfctx, exchange->nonce_i, exchange->nonce_i_len);
rest = hashp->p + GET_ISAKMP_GEN_LENGTH (hashp->p);
rest_len = (GET_ISAKMP_HDR_LENGTH (msg->iov[0].iov_base)
- (rest - (u_int8_t*)msg->iov[0].iov_base));
LOG_DBG_BUF ((LOG_MISC, 90,
"initiator_recv_HASH_SA_NONCE: payloads after HASH(2)", rest,
rest_len));
prf->Update (prf->prfctx, rest, rest_len);
prf->Final (hash->digest, prf->prfctx);
prf_free (prf);
LOG_DBG_BUF ((LOG_MISC, 80,
"initiator_recv_HASH_SA_NONCE: computed HASH(2)",
hash->digest, hashsize));
if (memcmp (hashp->p + ISAKMP_HASH_DATA_OFF, hash->digest, hashsize) != 0)
{
message_drop (msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 1, 0);
return -1;
}
/* Mark the HASH as handled. */
hashp->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 (%d) failed",
ISAKMP_HASH_SZ + 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_MISC, 90, "initiator_send_HASH: SKEYID_a", isa->skeyid_a,
isa->skeyid_len));
prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_a, isa->skeyid_len);
if (!prf)
return -1;
prf->Init (prf->prfctx);
prf->Update (prf->prfctx, "\0", 1);
LOG_DBG_BUF ((LOG_MISC, 90, "initiator_send_HASH: message_id",
exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN);
LOG_DBG_BUF ((LOG_MISC, 90, "initiator_send_HASH: NONCE_I_b",
exchange->nonce_i, exchange->nonce_i_len));
prf->Update (prf->prfctx, exchange->nonce_i, exchange->nonce_i_len);
LOG_DBG_BUF ((LOG_MISC, 90, "initiator_send_HASH: NONCE_R_b",
exchange->nonce_r, exchange->nonce_r_len));
prf->Update (prf->prfctx, exchange->nonce_r, exchange->nonce_r_len);
prf->Final (buf + ISAKMP_GEN_SZ, prf->prfctx);
prf_free (prf);
LOG_DBG_BUF ((LOG_MISC, 90, "initiator_send_HASH: HASH(3)",
buf + ISAKMP_GEN_SZ, hashsize));
if (ie->group)
message_register_post_send (msg, gen_g_xy);
message_register_post_send (msg, post_quick_mode);
return 0;
}
static void
post_quick_mode (struct message *msg)
{
struct sa *isakmp_sa = msg->isakmp_sa;
struct ipsec_sa *isa = isakmp_sa->data;
struct exchange *exchange = msg->exchange;
struct ipsec_exch *ie = exchange->data;
struct prf *prf;
struct sa *sa;
struct proto *proto;
struct ipsec_proto *iproto;
u_int8_t *keymat;
int i;
/*
* Loop over all SA negotiations and do both an in- and an outgoing SA
* per protocol.
*/
for (sa = TAILQ_FIRST (&exchange->sa_list); sa; sa = TAILQ_NEXT (sa, next))
{
for (proto = TAILQ_FIRST (&sa->protos); proto;
proto = TAILQ_NEXT (proto, link))
{
iproto = proto->data;
/*
* There are two SAs for each SA negotiation, incoming and outcoing.
*/
for (i = 0; i < 2; i++)
{
prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_d,
isa->skeyid_len);
if (!prf)
{
/* XXX What to do? */
continue;
}
ie->keymat_len = ipsec_keymat_length (proto);
/*
* We need to roundup the length of the key material buffer
* to a multiple of the PRF's blocksize as it is generated
* in chunks of that blocksize.
*/
iproto->keymat[i]
= malloc (((ie->keymat_len + prf->blocksize - 1)
/ prf->blocksize) * prf->blocksize);
if (!iproto->keymat[i])
{
log_error ("post_quick_mode: malloc (%d) failed",
((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_MISC, 90,
"post_quick_mode: last KEYMAT",
keymat - prf->blocksize,
prf->blocksize));
prf->Update (prf->prfctx, keymat - prf->blocksize,
prf->blocksize);
}
/* If PFS is used hash in g^xy. */
if (ie->g_xy)
{
LOG_DBG_BUF ((LOG_MISC, 90, "post_quick_mode: g^xy",
ie->g_xy, ie->g_x_len));
prf->Update (prf->prfctx, ie->g_xy, ie->g_x_len);
}
LOG_DBG ((LOG_MISC, 90,
"post_quick_mode: suite %d proto %d", proto->no,
proto->proto));
prf->Update (prf->prfctx, &proto->proto, 1);
LOG_DBG_BUF ((LOG_MISC, 90, "post_quick_mode: SPI",
proto->spi[i], proto->spi_sz[i]));
prf->Update (prf->prfctx, proto->spi[i], proto->spi_sz[i]);
LOG_DBG_BUF ((LOG_MISC, 90, "post_quick_mode: Ni_b",
exchange->nonce_i, exchange->nonce_i_len));
prf->Update (prf->prfctx, exchange->nonce_i,
exchange->nonce_i_len);
LOG_DBG_BUF ((LOG_MISC, 90, "post_quick_mode: Nr_b",
exchange->nonce_r, exchange->nonce_r_len));
prf->Update (prf->prfctx, exchange->nonce_r,
exchange->nonce_r_len);
prf->Final (keymat, prf->prfctx);
}
prf_free (prf);
LOG_DBG_BUF ((LOG_MISC, 90, "post_quick_mode: KEYMAT",
iproto->keymat[i], ie->keymat_len));
}
}
}
}
/*
* Accept a set of transforms offered by the initiator and chose one we can
* handle.
* XXX Describe in more detail.
*/
static int
responder_recv_HASH_SA_NONCE (struct message *msg)
{
struct payload *hashp, *kep, *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;
socklen_t srclen, dstlen;
char *name;
hashp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_HASH]);
hash = hashp->p;
hashp->flags |= PL_MARK;
/* The HASH payload should be the first one. */
if (hash != pkt + ISAKMP_HDR_SZ)
{
/* XXX Is there a better notification type? */
message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 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 (%d) failed",
hash_len - ISAKMP_GEN_SZ);
goto cleanup;
}
/*
* Check the payload's integrity.
* XXX Share with ipsec_fill_in_hash?
*/
LOG_DBG_BUF ((LOG_MISC, 90, "responder_recv_HASH_SA_NONCE: SKEYID_a",
isa->skeyid_a, isa->skeyid_len));
prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_a, isa->skeyid_len);
if (!prf)
goto cleanup;
prf->Init (prf->prfctx);
LOG_DBG_BUF ((LOG_MISC, 90, "responder_recv_HASH_SA_NONCE: message_id",
exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN);
LOG_DBG_BUF ((LOG_MISC, 90,
"responder_recv_HASH_SA_NONCE: message after HASH",
hash + hash_len,
msg->iov[0].iov_len - ISAKMP_HDR_SZ - hash_len));
prf->Update (prf->prfctx, hash + hash_len,
msg->iov[0].iov_len - ISAKMP_HDR_SZ - hash_len);
prf->Final (my_hash, prf->prfctx);
prf_free (prf);
LOG_DBG_BUF ((LOG_MISC, 90,
"responder_recv_HASH_SA_NONCE: computed HASH(1)", my_hash,
hash_len - ISAKMP_GEN_SZ));
if (memcmp (hash + ISAKMP_GEN_SZ, my_hash, hash_len - ISAKMP_GEN_SZ) != 0)
{
message_drop (msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 1, 0);
goto cleanup;
}
free (my_hash);
my_hash = 0;
kep = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_KEY_EXCH]);
if (kep)
ie->pfs = 1;
/* Handle optional client ID payloads. */
idp = TAILQ_FIRST (&msg->payload[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;
}
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 (%d) failed",
ie->id_ci_sz);
goto cleanup;
}
memcpy (ie->id_ci, idp->p, ie->id_ci_sz);
idp->flags |= PL_MARK;
LOG_DBG_BUF ((LOG_MISC, 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 (%d) failed",
ie->id_cr_sz);
goto cleanup;
}
memcpy (ie->id_cr, idp->p, ie->id_cr_sz);
idp->flags |= PL_MARK;
LOG_DBG_BUF ((LOG_MISC, 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 address. */
msg->transport->vtbl->get_dst (msg->transport, &dst, &dstlen);
ie->id_ci_sz = ISAKMP_ID_DATA_OFF
+ sizeof ((struct sockaddr_in *)dst)->sin_addr.s_addr;
ie->id_ci = calloc (ie->id_ci_sz, sizeof (char));
if (!ie->id_ci)
{
log_error ("responder_recv_HASH_SA_NONCE: malloc (%d) failed",
ie->id_ci_sz);
goto cleanup;
}
SET_ISAKMP_ID_TYPE (ie->id_ci, IPSEC_ID_IPV4_ADDR);
memcpy (ie->id_ci + ISAKMP_ID_DATA_OFF,
&((struct sockaddr_in *)dst)->sin_addr.s_addr,
sizeof ((struct sockaddr_in *)dst)->sin_addr.s_addr);
/* Get responder address. */
msg->transport->vtbl->get_src (msg->transport, &src, &srclen);
ie->id_cr_sz = ISAKMP_ID_DATA_OFF
+ sizeof ((struct sockaddr_in *)dst)->sin_addr.s_addr;
ie->id_cr = calloc (ie->id_cr_sz, sizeof (char));
if (!ie->id_cr)
{
log_error ("responder_recv_HASH_SA_NONCE: malloc (%d) failed",
ie->id_cr_sz);
goto cleanup;
}
SET_ISAKMP_ID_TYPE (ie->id_cr, IPSEC_ID_IPV4_ADDR);
memcpy (ie->id_cr + ISAKMP_ID_DATA_OFF,
&((struct sockaddr_in *)src)->sin_addr.s_addr,
sizeof ((struct sockaddr_in *)src)->sin_addr.s_addr);
}
#ifdef USE_POLICY
#ifdef USE_KEYNOTE
if (message_negotiate_sa (msg, check_policy))
goto cleanup;
#else
if (message_negotiate_sa (msg, libkeynote ? check_policy : 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:
}
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.
*/
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;
}
}
#if !defined (USE_POLICY) && !defined (USE_KEYNOTE)
#ifdef USE_POLICY
else if (!libkeynote)
#else
else
#endif
{
/*
* This code is no longer necessary, as policy determines acceptance
* of IDs/SAs. (angelos@openbsd.org)
*
* XXX Keep it if not USE_POLICY for now, though.
*/
/* XXX Notify peer and log. */
goto cleanup;
}
#endif /* !USE_POLICY && !USE_KEYNOTE */
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);
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];
int 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 (%d) failed",
ISAKMP_HASH_SZ + 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 (%d) failed", sz);
return -1;
}
memcpy (id, ie->id_ci, sz);
LOG_DBG_BUF ((LOG_MISC, 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 (%d) failed", sz);
return -1;
}
memcpy (id, ie->id_cr, sz);
LOG_DBG_BUF ((LOG_MISC, 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_MISC, 95, "responder_recv_HASH: isakmp_sa %p isa %p",
isakmp_sa, isa));
LOG_DBG_BUF ((LOG_MISC, 90, "responder_send_HASH_SA_NONCE: SKEYID_a",
isa->skeyid_a, isa->skeyid_len));
prf = prf_alloc (isa->prf_type, hash->type, isa->skeyid_a, isa->skeyid_len);
if (!prf)
return -1;
prf->Init (prf->prfctx);
LOG_DBG_BUF ((LOG_MISC, 90, "responder_send_HASH_SA_NONCE: message_id",
exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN);
LOG_DBG_BUF ((LOG_MISC, 90, "responder_send_HASH_SA_NONCE: NONCE_I_b",
exchange->nonce_i, exchange->nonce_i_len));
prf->Update (prf->prfctx, exchange->nonce_i, exchange->nonce_i_len);
/* Loop over all payloads after HASH(2). */
for (i = 2; i < msg->iovlen; i++)
{
/* XXX Misleading payload type printouts. */
snprintf (header, 80,
"responder_send_HASH_SA_NONCE: payload %d after HASH(2)",
i - 1);
LOG_DBG_BUF ((LOG_MISC, 90, header, msg->iov[i].iov_base,
msg->iov[i].iov_len));
prf->Update (prf->prfctx, msg->iov[i].iov_base, msg->iov[i].iov_len);
}
prf->Final (buf + ISAKMP_HASH_DATA_OFF, prf->prfctx);
prf_free (prf);
snprintf (header, 80, "responder_send_HASH_SA_NONCE: HASH_%c",
initiator ? 'I' : 'R');
LOG_DBG_BUF ((LOG_MISC, 80, header, buf + ISAKMP_HASH_DATA_OFF, hashsize));
if (ie->group)
message_register_post_send (msg, gen_g_xy);
return 0;
}
static void
gen_g_xy (struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct ipsec_exch *ie = exchange->data;
/* Compute Diffie-Hellman shared value. */
ie->g_xy = malloc (ie->g_x_len);
if (!ie->g_xy)
{
log_error ("gen_g_xy: malloc (%d) failed", 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_MISC, 80, "gen_g_xy: g^xy", ie->g_xy, ie->g_x_len));
}
static int
responder_recv_HASH (struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct sa *isakmp_sa = msg->isakmp_sa;
struct ipsec_sa *isa = isakmp_sa->data;
struct prf *prf;
u_int8_t *hash, *my_hash = 0;
size_t hash_len;
struct payload *hashp;
/* Find HASH(3) and create our own hash, just as big. */
hashp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_HASH]);
hash = hashp->p;
hashp->flags |= PL_MARK;
hash_len = GET_ISAKMP_GEN_LENGTH (hash);
my_hash = malloc (hash_len - ISAKMP_GEN_SZ);
if (!my_hash)
{
log_error ("responder_recv_HASH: malloc (%d) failed",
hash_len - ISAKMP_GEN_SZ);
goto cleanup;
}
/* Allocate the prf and start calculating our HASH(3). XXX Share? */
LOG_DBG ((LOG_MISC, 95, "responder_recv_HASH: isakmp_sa %p isa %p",
isakmp_sa, isa));
LOG_DBG_BUF ((LOG_MISC, 90, "responder_recv_HASH: SKEYID_a", isa->skeyid_a,
isa->skeyid_len));
prf = prf_alloc (isa->prf_type, isa->hash, isa->skeyid_a, isa->skeyid_len);
if (!prf)
goto cleanup;
prf->Init (prf->prfctx);
prf->Update (prf->prfctx, "\0", 1);
LOG_DBG_BUF ((LOG_MISC, 90, "responder_recv_HASH: message_id",
exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN));
prf->Update (prf->prfctx, exchange->message_id, ISAKMP_HDR_MESSAGE_ID_LEN);
LOG_DBG_BUF ((LOG_MISC, 90, "responder_recv_HASH: NONCE_I_b",
exchange->nonce_i, exchange->nonce_i_len));
prf->Update (prf->prfctx, exchange->nonce_i, exchange->nonce_i_len);
LOG_DBG_BUF ((LOG_MISC, 90, "responder_recv_HASH: NONCE_R_b",
exchange->nonce_r, exchange->nonce_r_len));
prf->Update (prf->prfctx, exchange->nonce_r, exchange->nonce_r_len);
prf->Final (my_hash, prf->prfctx);
prf_free (prf);
LOG_DBG_BUF ((LOG_MISC, 90,
"responder_recv_HASH: computed HASH(3)", my_hash,
hash_len - ISAKMP_GEN_SZ));
if (memcmp (hash + ISAKMP_GEN_SZ, my_hash, hash_len - ISAKMP_GEN_SZ) != 0)
{
message_drop (msg, ISAKMP_NOTIFY_INVALID_HASH_INFORMATION, 0, 1, 0);
goto cleanup;
}
free (my_hash);
post_quick_mode (msg);
return 0;
cleanup:
if (my_hash)
free (my_hash);
return -1;
}