File: [local] / src / sbin / isakmpd / ike_phase_1.c (download)
Revision 1.23, Tue Mar 13 14:05:18 2001 UTC (23 years, 2 months ago) by ho
Branch: MAIN
CVS Tags: OPENBSD_2_9_BASE, OPENBSD_2_9 Changes since 1.22: +38 -29 lines
Add logging classes for Negotiation and Policy, and change a number of
debug messages to use these instead. Change a number of 'log_print'
to debug messages to keep the noise down. Use 'log_error' instead of
'log_print' in some cases when we have errno. Some indentation fixes.
(niklas@ ok)
|
/* $OpenBSD: ike_phase_1.c,v 1.23 2001/03/13 14:05:18 ho Exp $ */
/* $EOM: ike_phase_1.c,v 1.31 2000/12/11 23:47:56 niklas Exp $ */
/*
* Copyright (c) 1999, 2000 Niklas Hallqvist. All rights reserved.
* Copyright (c) 1999, 2000 Angelos D. Keromytis. 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 <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include "sysdep.h"
#include "attribute.h"
#include "conf.h"
#include "constants.h"
#include "crypto.h"
#include "dh.h"
#include "doi.h"
#include "exchange.h"
#include "hash.h"
#include "ike_auth.h"
#include "ike_phase_1.h"
#include "ipsec.h"
#include "ipsec_doi.h"
#include "isakmp.h"
#include "log.h"
#include "math_group.h"
#include "message.h"
#include "prf.h"
#include "sa.h"
#include "transport.h"
#include "util.h"
static int attribute_unacceptable (u_int16_t, u_int8_t *, u_int16_t, void *);
static int ike_phase_1_validate_prop (struct exchange *, struct sa *,
struct sa *);
/* Offer a set of transforms to the responder in the MSG message. */
int
ike_phase_1_initiator_send_SA (struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct ipsec_exch *ie = exchange->data;
u_int8_t *proposal = 0, *sa_buf = 0, *saved_nextp, *attr;
u_int8_t **transform = 0;
size_t transforms_len = 0, proposal_len, sa_len;
size_t *transform_len = 0;
struct conf_list *conf, *life_conf;
struct conf_list_node *xf, *life;
int i, value, update_nextp;
struct payload *p;
struct proto *proto;
int group_desc = -1, new_group_desc;
/* Get the list of transforms. */
conf = conf_get_list (exchange->policy, "Transforms");
if (!conf)
return -1;
transform = calloc (conf->cnt, sizeof *transform);
if (!transform)
{
log_error ("ike_phase_1_initiator_send_SA: calloc (%d, %d) failed",
conf->cnt, sizeof *transform);
goto bail_out;
}
transform_len = calloc (conf->cnt, sizeof *transform_len);
if (!transform_len)
{
log_error ("ike_phase_1_initiator_send_SA: calloc (%d, %d) failed",
conf->cnt, sizeof *transform_len);
goto bail_out;
}
for (xf = TAILQ_FIRST (&conf->fields), i = 0; i < conf->cnt;
i++, xf = TAILQ_NEXT (xf, link))
{
/* XXX The sizing needs to be dynamic. */
transform[i]
= malloc (ISAKMP_TRANSFORM_SA_ATTRS_OFF + 16 * ISAKMP_ATTR_VALUE_OFF);
if (!transform[i])
{
log_error ("ike_phase_1_initiator_send_SA: malloc (%d) failed",
ISAKMP_TRANSFORM_SA_ATTRS_OFF
+ 16 * ISAKMP_ATTR_VALUE_OFF);
goto bail_out;
}
SET_ISAKMP_TRANSFORM_NO (transform[i], i);
SET_ISAKMP_TRANSFORM_ID (transform[i], IPSEC_TRANSFORM_KEY_IKE);
SET_ISAKMP_TRANSFORM_RESERVED (transform[i], 0);
attr = transform[i] + ISAKMP_TRANSFORM_SA_ATTRS_OFF;
if (attribute_set_constant (xf->field, "ENCRYPTION_ALGORITHM",
ike_encrypt_cst,
IKE_ATTR_ENCRYPTION_ALGORITHM, &attr))
goto bail_out;
if (attribute_set_constant (xf->field, "HASH_ALGORITHM", ike_hash_cst,
IKE_ATTR_HASH_ALGORITHM, &attr))
goto bail_out;
if (attribute_set_constant (xf->field, "AUTHENTICATION_METHOD",
ike_auth_cst, IKE_ATTR_AUTHENTICATION_METHOD,
&attr))
goto bail_out;
if (attribute_set_constant (xf->field, "GROUP_DESCRIPTION",
ike_group_desc_cst,
IKE_ATTR_GROUP_DESCRIPTION, &attr))
{
/*
* If no group description exists, try looking for a user-defined
* one.
*/
if (attribute_set_constant (xf->field, "GROUP_TYPE", ike_group_cst,
IKE_ATTR_GROUP_TYPE, &attr))
goto bail_out;
#if 0
if (attribute_set_bignum (xf->field, "GROUP_PRIME",
IKE_ATTR_GROUP_PRIME, &attr))
goto bail_out;
if (attribute_set_bignum (xf->field, "GROUP_GENERATOR_2",
IKE_ATTR_GROUP_GENERATOR_2, &attr))
goto bail_out;
if (attribute_set_bignum (xf->field, "GROUP_GENERATOR_2",
IKE_ATTR_GROUP_GENERATOR_2, &attr))
goto bail_out;
if (attribute_set_bignum (xf->field, "GROUP_CURVE_A",
IKE_ATTR_GROUP_CURVE_A, &attr))
goto bail_out;
if (attribute_set_bignum (xf->field, "GROUP_CURVE_B",
IKE_ATTR_GROUP_CURVE_B, &attr))
goto bail_out;
#endif
}
/*
* 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",
ike_duration_cst, IKE_ATTR_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, IKE_ATTR_LIFE_DURATION,
value);
else
{
value = htonl (value);
attr = attribute_set_var (attr, IKE_ATTR_LIFE_DURATION,
(char *)&value, sizeof value);
}
}
}
conf_free_list (life_conf);
}
attribute_set_constant (xf->field, "PRF", ike_prf_cst, IKE_ATTR_PRF,
&attr);
value = conf_get_num (xf->field, "KEY_LENGTH", 0);
if (value)
attr = attribute_set_basic (attr, IKE_ATTR_KEY_LENGTH, value);
value = conf_get_num (xf->field, "FIELD_SIZE", 0);
if (value)
attr = attribute_set_basic (attr, IKE_ATTR_FIELD_SIZE, value);
value = conf_get_num (xf->field, "GROUP_ORDER", 0);
if (value)
attr = attribute_set_basic (attr, IKE_ATTR_GROUP_ORDER, value);
/* Record the real transform size. */
transforms_len += transform_len[i] = attr - transform[i];
/* XXX I don't like exchange-specific stuff in here. */
if (exchange->type == ISAKMP_EXCH_AGGRESSIVE)
{
/*
* 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 ("ike_phase_1_inititor_send_SA: "
"differing group descriptions in a proposal");
goto bail_out;
}
}
/* We need to check that we actually support our configuration. */
if (attribute_map (transform[i] + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
transform_len[i] - ISAKMP_TRANSFORM_SA_ATTRS_OFF,
exchange->doi->is_attribute_incompatible, msg))
{
log_print ("ike_phase_1_initiator_send_SA: "
"section [%s] has unsupported attribute(s)",
xf->field);
goto bail_out;
}
}
/* XXX I don't like exchange-specific stuff in here. */
if (exchange->type == ISAKMP_EXCH_AGGRESSIVE)
ie->group = group_get (group_desc);
proposal_len = ISAKMP_PROP_SPI_OFF;
proposal = malloc (proposal_len);
if (!proposal)
{
log_error ("ike_phase_1_initiator_send_SA: malloc (%d) failed",
proposal_len);
goto bail_out;
}
SET_ISAKMP_PROP_NO (proposal, 1);
SET_ISAKMP_PROP_PROTO (proposal, ISAKMP_PROTO_ISAKMP);
SET_ISAKMP_PROP_SPI_SZ (proposal, 0);
SET_ISAKMP_PROP_NTRANSFORMS (proposal, conf->cnt);
/* XXX I would like to see this factored out. */
proto = calloc (1, sizeof *proto);
if (!proto)
{
log_error ("ike_phase_1_initiator_send_SA: calloc (1, %d) failed",
sizeof *proto);
goto bail_out;
}
proto->no = 1;
proto->proto = ISAKMP_PROTO_ISAKMP;
proto->sa = TAILQ_FIRST (&exchange->sa_list);
TAILQ_INSERT_TAIL (&TAILQ_FIRST (&exchange->sa_list)->protos, proto,
link);
sa_len = ISAKMP_SA_SIT_OFF + IPSEC_SIT_SIT_LEN;
sa_buf = malloc (sa_len);
if (!sa_buf)
{
log_error ("ike_phase_1_initiator_send_SA: 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.
*/
if (message_add_payload (msg, ISAKMP_PAYLOAD_SA, sa_buf, sa_len, 1))
goto bail_out;
SET_ISAKMP_GEN_LENGTH (sa_buf,
sa_len + proposal_len + transforms_len);
sa_buf = 0;
saved_nextp = msg->nextp;
if (message_add_payload (msg, ISAKMP_PAYLOAD_PROPOSAL, proposal,
proposal_len, 0))
goto bail_out;
SET_ISAKMP_GEN_LENGTH (proposal, proposal_len + transforms_len);
proposal = 0;
update_nextp = 0;
for (i = 0; i < conf->cnt; i++)
{
if (message_add_payload (msg, ISAKMP_PAYLOAD_TRANSFORM, transform[i],
transform_len[i], update_nextp))
goto bail_out;
update_nextp = 1;
transform[i] = 0;
}
msg->nextp = saved_nextp;
/* Save SA payload body in ie->sa_i_b, length ie->sa_i_b_len. */
ie->sa_i_b_len = sa_len + proposal_len + transforms_len - ISAKMP_GEN_SZ;
ie->sa_i_b = malloc (ie->sa_i_b_len);
if (!ie->sa_i_b)
{
log_error ("ike_phase_1_initiator_send_SA: malloc (%d) failed",
ie->sa_i_b_len);
goto bail_out;
}
memcpy (ie->sa_i_b,
TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SA])->p + ISAKMP_GEN_SZ,
sa_len - ISAKMP_GEN_SZ);
memcpy (ie->sa_i_b + sa_len - ISAKMP_GEN_SZ,
TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_PROPOSAL])->p,
proposal_len);
transforms_len = 0;
for (i = 0, p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_TRANSFORM]);
i < conf->cnt; i++, p = TAILQ_NEXT (p, link))
{
memcpy (ie->sa_i_b + sa_len + proposal_len + transforms_len
- ISAKMP_GEN_SZ,
p->p, transform_len[i]);
transforms_len += transform_len[i];
}
conf_free_list (conf);
free (transform);
free (transform_len);
return 0;
bail_out:
if (sa_buf)
free (sa_buf);
if (proposal)
free (proposal);
if (transform)
{
for (i = 0; i < conf->cnt; i++)
if (transform[i])
free (transform[i]);
free (transform);
}
if (transform_len)
free (transform_len);
conf_free_list (conf);
return -1;
}
/* Figure out what transform the responder chose. */
int
ike_phase_1_initiator_recv_SA (struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct sa *sa = TAILQ_FIRST (&exchange->sa_list);
struct ipsec_exch *ie = exchange->data;
struct ipsec_sa *isa = sa->data;
struct payload *sa_p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SA]);
struct payload *prop = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_PROPOSAL]);
struct payload *xf = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_TRANSFORM]);
/*
* IKE requires that only one SA with only one proposal exists and since
* we are getting an answer on our transform offer, only one transform.
*/
if (TAILQ_NEXT (sa_p, link) || TAILQ_NEXT (prop, link)
|| TAILQ_NEXT (xf, link))
{
log_print ("ike_phase_1_initiator_recv_SA: "
"multiple SA, proposal or transform payloads in phase 1");
/* XXX Is there a better notification type? */
message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
return -1;
}
/* Check that the chosen transform matches an offer. */
if (message_negotiate_sa (msg, ike_phase_1_validate_prop)
|| !TAILQ_FIRST (&sa->protos))
return -1;
ipsec_decode_transform (msg, sa, TAILQ_FIRST (&sa->protos), xf->p);
/* XXX I don't like exchange-specific stuff in here. */
if (exchange->type != ISAKMP_EXCH_AGGRESSIVE)
ie->group = group_get (isa->group_desc);
/* Mark the SA as handled. */
sa_p->flags |= PL_MARK;
return 0;
}
/* Send our public DH value and a nonce to the responder. */
int
ike_phase_1_initiator_send_KE_NONCE (struct message *msg)
{
struct ipsec_exch *ie = msg->exchange->data;
ie->g_x_len = dh_getlen (ie->group);
/* XXX I want a better way to specify the nonce's size. */
return ike_phase_1_send_KE_NONCE (msg, 16);
}
/* Accept responder's public DH value and nonce. */
int
ike_phase_1_initiator_recv_KE_NONCE (struct message *msg)
{
if (ike_phase_1_recv_KE_NONCE (msg))
return -1;
return ike_phase_1_post_exchange_KE_NONCE (msg);
}
/*
* Accept a set of transforms offered by the initiator and chose one we can
* handle.
*/
int
ike_phase_1_responder_recv_SA (struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct sa *sa = TAILQ_FIRST (&exchange->sa_list);
struct ipsec_sa *isa = sa->data;
struct payload *sa_p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SA]);
struct payload *prop = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_PROPOSAL]);
struct ipsec_exch *ie = exchange->data;
/* Mark the SA as handled. */
sa_p->flags |= PL_MARK;
/* IKE requires that only one SA with only one proposal exists. */
if (TAILQ_NEXT (sa_p, link) || TAILQ_NEXT (prop, link))
{
log_print ("ike_phase_1_responder_recv_SA: "
"multiple SA or proposal payloads in phase 1");
/* XXX Is there a better notification type? */
message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
return -1;
}
/* Chose a transform from the SA. */
if (message_negotiate_sa (msg, ike_phase_1_validate_prop)
|| !TAILQ_FIRST (&sa->protos))
return -1;
/* XXX Move into message_negotiate_sa? */
ipsec_decode_transform (msg, sa, TAILQ_FIRST (&sa->protos),
TAILQ_FIRST (&sa->protos)->chosen->p);
ie->group = group_get (isa->group_desc);
/*
* Check that the mandatory attributes: encryption, hash, authentication
* method and Diffie-Hellman group description, has been supplied.
*/
if (!exchange->crypto || !ie->hash || !ie->ike_auth || !ie->group)
{
message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 1, 0);
return -1;
}
/* Save the body for later hash computation. */
ie->sa_i_b_len = GET_ISAKMP_GEN_LENGTH (sa_p->p) - ISAKMP_GEN_SZ;
ie->sa_i_b = malloc (ie->sa_i_b_len);
if (!ie->sa_i_b)
{
/* XXX How to notify peer? */
log_error ("ike_phase_1_responder_recv_SA: malloc (%d) failed",
ie->sa_i_b_len);
return -1;
}
memcpy (ie->sa_i_b, sa_p->p + ISAKMP_GEN_SZ, ie->sa_i_b_len);
return 0;
}
/* Reply with the transform we chose. */
int
ike_phase_1_responder_send_SA (struct message *msg)
{
/* Add the SA payload with the transform that was chosen. */
return message_add_sa_payload (msg);
}
/* Send our public DH value and a nonce to the peer. */
int
ike_phase_1_send_KE_NONCE (struct message *msg, size_t nonce_sz)
{
/* Public DH key. */
if (ipsec_gen_g_x (msg))
{
/* XXX How to log and notify peer? */
return -1;
}
/* Generate a nonce, and add it to the message. */
if (exchange_gen_nonce (msg, nonce_sz))
{
/* XXX Log? */
return -1;
}
/* Try to add certificates which are acceptable for the CERTREQs */
if (exchange_add_certs (msg))
{
/* XXX Log? */
return -1;
}
return 0;
}
/* Receive our peer's public DH value and nonce. */
int
ike_phase_1_recv_KE_NONCE (struct message *msg)
{
/* Copy out the initiator's DH public value. */
if (ipsec_save_g_x (msg))
{
/* XXX How to log and notify peer? */
return -1;
}
/* Copy out the initiator's nonce. */
if (exchange_save_nonce (msg))
{
/* XXX How to log and notify peer? */
return -1;
}
/* Copy out the initiator's cert requests. */
if (exchange_save_certreq (msg))
{
/* XXX How to log and notify peer? */
return -1;
}
return 0;
}
/*
* Compute DH values and key material. This is done in a post-send function
* as that means we can do parallel work in both the initiator and responder
* thus speeding up exchanges.
*/
int
ike_phase_1_post_exchange_KE_NONCE (struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct ipsec_exch *ie = exchange->data;
struct prf *prf;
struct hash *hash = ie->hash;
enum cryptoerr err;
/* Compute Diffie-Hellman shared value. */
ie->g_xy = malloc (ie->g_x_len);
if (!ie->g_xy)
{
/* XXX How to notify peer? */
log_error ("ike_phase_1_post_exchange_KE_NONCE: malloc (%d) failed",
ie->g_x_len);
return -1;
}
if (dh_create_shared (ie->group, ie->g_xy,
exchange->initiator ? ie->g_xr : ie->g_xi))
{
log_print ("ike_phase_1_post_exchange_KE_NONCE: "
"dh_create_shared failed");
return -1;
}
LOG_DBG_BUF ((LOG_NEGOTIATION, 80,
"ike_phase_1_post_exchange_KE_NONCE: g^xy", ie->g_xy,
ie->g_x_len));
/* Compute the SKEYID depending on the authentication method. */
ie->skeyid = ie->ike_auth->gen_skeyid (exchange, &ie->skeyid_len);
if (!ie->skeyid)
{
/* XXX Log and teardown? */
return -1;
}
LOG_DBG_BUF ((LOG_NEGOTIATION, 80,
"ike_phase_1_post_exchange_KE_NONCE: SKEYID", ie->skeyid,
ie->skeyid_len));
/* SKEYID_d. */
ie->skeyid_d = malloc (ie->skeyid_len);
if (!ie->skeyid_d)
{
/* XXX How to notify peer? */
log_error ("ike_phase_1_post_exchange_KE_NONCE: malloc (%d) failed",
ie->skeyid_len);
return -1;
}
prf = prf_alloc (ie->prf_type, hash->type, ie->skeyid, ie->skeyid_len);
if (!prf)
{
/* XXX Log and teardown? */
return -1;
}
prf->Init (prf->prfctx);
prf->Update (prf->prfctx, ie->g_xy, ie->g_x_len);
prf->Update (prf->prfctx, exchange->cookies, ISAKMP_HDR_COOKIES_LEN);
prf->Update (prf->prfctx, "\0", 1);
prf->Final (ie->skeyid_d, prf->prfctx);
LOG_DBG_BUF ((LOG_NEGOTIATION, 80,
"ike_phase_1_post_exchange_KE_NONCE: SKEYID_d", ie->skeyid_d,
ie->skeyid_len));
/* SKEYID_a. */
ie->skeyid_a = malloc (ie->skeyid_len);
if (!ie->skeyid_a)
{
log_error ("ike_phase_1_post_exchange_KE_NONCE: malloc (%d) failed",
ie->skeyid_len);
prf_free (prf);
return -1;
}
prf->Init (prf->prfctx);
prf->Update (prf->prfctx, ie->skeyid_d, ie->skeyid_len);
prf->Update (prf->prfctx, ie->g_xy, ie->g_x_len);
prf->Update (prf->prfctx, exchange->cookies, ISAKMP_HDR_COOKIES_LEN);
prf->Update (prf->prfctx, "\1", 1);
prf->Final (ie->skeyid_a, prf->prfctx);
LOG_DBG_BUF ((LOG_NEGOTIATION, 80,
"ike_phase_1_post_exchange_KE_NONCE: SKEYID_a", ie->skeyid_a,
ie->skeyid_len));
/* SKEYID_e. */
ie->skeyid_e = malloc (ie->skeyid_len);
if (!ie->skeyid_e)
{
/* XXX How to notify peer? */
log_error ("ike_phase_1_post_exchange_KE_NONCE: malloc (%d) failed",
ie->skeyid_len);
prf_free (prf);
return -1;
}
prf->Init (prf->prfctx);
prf->Update (prf->prfctx, ie->skeyid_a, ie->skeyid_len);
prf->Update (prf->prfctx, ie->g_xy, ie->g_x_len);
prf->Update (prf->prfctx, exchange->cookies, ISAKMP_HDR_COOKIES_LEN);
prf->Update (prf->prfctx, "\2", 1);
prf->Final (ie->skeyid_e, prf->prfctx);
prf_free (prf);
LOG_DBG_BUF ((LOG_NEGOTIATION, 80,
"ike_phase_1_post_exchange_KE_NONCE: SKEYID_e", ie->skeyid_e,
ie->skeyid_len));
/* Key length determination. */
if (!exchange->key_length)
exchange->key_length = exchange->crypto->keymax;
/* Derive a longer key from skeyid_e */
if (ie->skeyid_len < exchange->key_length)
{
u_int16_t len, keylen;
u_int8_t *key, *p;
prf = prf_alloc (ie->prf_type, hash->type, ie->skeyid_e, ie->skeyid_len);
if (!prf)
{
/* XXX - notify peer */
return -1;
}
/* Make keylen a multiple of prf->blocksize */
keylen = exchange->key_length;
if (keylen % prf->blocksize)
keylen += prf->blocksize - (keylen % prf->blocksize);
key = malloc (keylen);
if (!key)
{
/* XXX - Notify peer. */
log_error ("ike_phase_1_post_exchange_KE_NONCE: malloc (%d) failed",
keylen);
return -1;
}
prf->Init (prf->prfctx);
prf->Update (prf->prfctx, "\0", 1);
prf->Final (key, prf->prfctx);
for (len = prf->blocksize, p = key; len < exchange->key_length;
len += prf->blocksize, p += prf->blocksize)
{
prf->Init (prf->prfctx);
prf->Update (prf->prfctx, p, prf->blocksize);
prf->Final (p + prf->blocksize, prf->prfctx);
}
prf_free (prf);
/* Setup our keystate using the derived encryption key. */
exchange->keystate
= crypto_init (exchange->crypto, key, exchange->key_length, &err);
free (key);
}
else
/* Setup our keystate using the raw skeyid_e. */
exchange->keystate = crypto_init (exchange->crypto, ie->skeyid_e,
exchange->key_length, &err);
/* Special handling for DES weak keys. */
if (!exchange->keystate && err == EWEAKKEY
&& (exchange->key_length << 1) <= ie->skeyid_len)
{
log_print ("ike_phase_1_post_exchange_KE_NONCE: "
"weak key, trying subseq. skeyid_e");
exchange->keystate
= crypto_init (exchange->crypto, ie->skeyid_e + exchange->key_length,
exchange->key_length, &err);
}
if (!exchange->keystate)
{
log_print ("ike_phase_1_post_exchange_KE_NONCE: "
"exchange->crypto->init () failed: %d", err);
/*
* XXX We really need to know if problems are of transient nature
* or fatal (like failed assertions etc.)
*/
return -1;
}
/* Setup IV. XXX Only for CBC transforms, no? */
hash->Init (hash->ctx);
hash->Update (hash->ctx, ie->g_xi, ie->g_x_len);
hash->Update (hash->ctx, ie->g_xr, ie->g_x_len);
hash->Final (hash->digest, hash->ctx);
crypto_init_iv (exchange->keystate, hash->digest,
exchange->crypto->blocksize);
return 0;
}
int
ike_phase_1_responder_send_ID_AUTH (struct message *msg)
{
if (ike_phase_1_send_ID (msg))
return -1;
return ike_phase_1_send_AUTH (msg);
}
int
ike_phase_1_send_ID (struct message *msg)
{
struct exchange *exchange = msg->exchange;
u_int8_t *buf;
char header[80];
ssize_t sz;
struct sockaddr *src;
int src_len;
int initiator = exchange->initiator;
u_int8_t **id;
size_t *id_len;
char *my_id = 0;
u_int8_t id_type;
/* Choose the right fields to fill-in. */
id = initiator ? &exchange->id_i : &exchange->id_r;
id_len = initiator ? &exchange->id_i_len : &exchange->id_r_len;
if (exchange->name)
my_id = conf_get_str (exchange->name, "ID");
if (!my_id)
my_id = conf_get_str ("General", "Default-phase-1-ID");
sz = my_id ? ipsec_id_size (my_id, &id_type) : sizeof (in_addr_t);
if (sz == -1)
return -1;
sz += ISAKMP_ID_DATA_OFF;
buf = malloc (sz);
if (!buf)
{
log_error ("ike_phase_1_send_ID: malloc (%d) failed", sz);
return -1;
}
SET_IPSEC_ID_PROTO (buf + ISAKMP_ID_DOI_DATA_OFF, 0);
SET_IPSEC_ID_PORT (buf + ISAKMP_ID_DOI_DATA_OFF, 0);
if (my_id)
{
SET_ISAKMP_ID_TYPE (buf, id_type);
switch (id_type)
{
case IPSEC_ID_IPV4_ADDR:
msg->transport->vtbl->get_src (msg->transport, &src, &src_len);
/* Already in network byteorder. */
memcpy (buf + ISAKMP_ID_DATA_OFF,
&((struct sockaddr_in *)src)->sin_addr.s_addr,
sizeof (in_addr_t));
break;
case IPSEC_ID_FQDN:
case IPSEC_ID_USER_FQDN:
case IPSEC_ID_KEY_ID:
memcpy (buf + ISAKMP_ID_DATA_OFF, conf_get_str (my_id, "Name"),
sz - ISAKMP_ID_DATA_OFF);
break;
default:
log_print ("ike_phase_1_send_ID: unsupported ID type %d", id_type);
free (buf);
return -1;
}
}
else
{
msg->transport->vtbl->get_src (msg->transport, &src, &src_len);
/* XXX Assumes IPv4. */
SET_ISAKMP_ID_TYPE (buf, IPSEC_ID_IPV4_ADDR);
/* Already in network byteorder. */
memcpy (buf + ISAKMP_ID_DATA_OFF,
&((struct sockaddr_in *)src)->sin_addr.s_addr,
sizeof (in_addr_t));
}
if (message_add_payload (msg, ISAKMP_PAYLOAD_ID, buf, sz, 1))
{
free (buf);
return -1;
}
*id_len = sz - ISAKMP_GEN_SZ;
*id = malloc (*id_len);
if (!*id)
{
log_error ("ike_phase_1_send_ID: malloc (%d) failed", *id_len);
return -1;
}
memcpy (*id, buf + ISAKMP_GEN_SZ, *id_len);
snprintf (header, 80, "ike_phase_1_send_ID: %s",
constant_name (ipsec_id_cst, GET_ISAKMP_ID_TYPE (buf)));
LOG_DBG_BUF ((LOG_NEGOTIATION, 40, header, buf + ISAKMP_ID_DATA_OFF,
sz - ISAKMP_ID_DATA_OFF));
return 0;
}
int
ike_phase_1_send_AUTH (struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct ipsec_exch *ie = exchange->data;
if (ie->ike_auth->encode_hash (msg))
{
/* XXX Log? */
return -1;
}
/*
* XXX Many people say the COMMIT flag is just junk, especially in Phase 1.
*/
#ifdef notyet
if ((exchange->flags & EXCHANGE_FLAG_COMMITTED) == 0)
exchange->flags |= EXCHANGE_FLAG_I_COMMITTED;
#endif
return 0;
}
/* Receive ID and HASH and check that the exchange has been consistent. */
int
ike_phase_1_recv_ID_AUTH (struct message *msg)
{
if (ike_phase_1_recv_ID (msg))
return -1;
return ike_phase_1_recv_AUTH (msg);
}
/* Receive ID. */
int
ike_phase_1_recv_ID (struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct payload *payload;
char header[80];
int initiator = exchange->initiator;
u_int8_t **id;
size_t *id_len;
/*
* XXX Here, we could be checking that the received ID matches what
* we expect it to be (if anything). That information is contained
* in the [[exchange->name]:Remote-ID] section.
*/
/* Choose the right fields to fill in */
id = initiator ? &exchange->id_r : &exchange->id_i;
id_len = initiator ? &exchange->id_r_len : &exchange->id_i_len;
/* XXX Do I really have to save the ID in the SA? */
payload = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_ID]);
*id_len = GET_ISAKMP_GEN_LENGTH (payload->p) - ISAKMP_GEN_SZ;
*id = malloc (*id_len);
if (!*id)
{
log_error ("ike_phase_1_recv_ID: malloc (%d) failed", *id_len);
return -1;
}
memcpy (*id, payload->p + ISAKMP_GEN_SZ, *id_len);
snprintf (header, 80, "ike_phase_1_recv_ID: %s",
constant_name (ipsec_id_cst, GET_ISAKMP_ID_TYPE (payload->p)));
LOG_DBG_BUF ((LOG_NEGOTIATION, 40, header, payload->p + ISAKMP_ID_DATA_OFF,
*id_len + ISAKMP_GEN_SZ - ISAKMP_ID_DATA_OFF));
payload->flags |= PL_MARK;
return 0;
}
/* Receive HASH and check that the exchange has been consistent. */
int
ike_phase_1_recv_AUTH (struct message *msg)
{
struct exchange *exchange = msg->exchange;
struct ipsec_exch *ie = exchange->data;
struct prf *prf;
struct hash *hash = ie->hash;
char header[80];
size_t hashsize = hash->hashsize;
int initiator = exchange->initiator;
u_int8_t **hash_p, *id;
size_t id_len;
/* Choose the right fields to fill in */
hash_p = initiator ? &ie->hash_r : &ie->hash_i;
id = initiator ? exchange->id_r : exchange->id_i;
id_len = initiator ? exchange->id_r_len : exchange->id_i_len;
/* The decoded hash will be in ie->hash_r or ie->hash_i */
if (ie->ike_auth->decode_hash (msg))
{
message_drop (msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION, 0, 1, 0);
return -1;
}
/* Allocate the prf and start calculating his HASH. */
prf = prf_alloc (ie->prf_type, hash->type, ie->skeyid, ie->skeyid_len);
if (!prf)
{
/* XXX Log? */
return -1;
}
prf->Init (prf->prfctx);
prf->Update (prf->prfctx, initiator ? ie->g_xr : ie->g_xi, ie->g_x_len);
prf->Update (prf->prfctx, initiator ? ie->g_xi : ie->g_xr, ie->g_x_len);
prf->Update (prf->prfctx,
exchange->cookies
+ (initiator ? ISAKMP_HDR_RCOOKIE_OFF : ISAKMP_HDR_ICOOKIE_OFF),
ISAKMP_HDR_ICOOKIE_LEN);
prf->Update (prf->prfctx,
exchange->cookies
+ (initiator ? ISAKMP_HDR_ICOOKIE_OFF : ISAKMP_HDR_RCOOKIE_OFF),
ISAKMP_HDR_ICOOKIE_LEN);
prf->Update (prf->prfctx, ie->sa_i_b, ie->sa_i_b_len);
prf->Update (prf->prfctx, id, id_len);
prf->Final (hash->digest, prf->prfctx);
prf_free (prf);
snprintf (header, 80, "ike_phase_1_recv_AUTH: computed HASH_%c",
initiator ? 'R' : 'I');
LOG_DBG_BUF ((LOG_NEGOTIATION, 80, header, hash->digest, hashsize));
/* Check that the hash we got matches the one we computed. */
if (memcmp (*hash_p, hash->digest, hashsize) != 0)
{
/* XXX Log? */
return -1;
}
return 0;
}
struct attr_node {
LIST_ENTRY (attr_node) link;
u_int16_t type;
};
struct validation_state {
struct conf_list_node *xf;
LIST_HEAD (attr_head, attr_node) attrs;
char *life;
};
/* Validate a proposal inside SA according to EXCHANGE's policy. */
static int
ike_phase_1_validate_prop (struct exchange *exchange, struct sa *sa,
struct sa *isakmp_sa)
{
struct conf_list *conf, *tags;
struct conf_list_node *xf, *tag;
struct proto *proto;
struct validation_state vs;
struct attr_node *node, *next_node;
/* Get the list of transforms. */
conf = conf_get_list (exchange->policy, "Transforms");
if (!conf)
return 0;
for (xf = TAILQ_FIRST (&conf->fields); xf; xf = TAILQ_NEXT (xf, link))
{
for (proto = TAILQ_FIRST (&sa->protos); proto;
proto = TAILQ_NEXT (proto, link))
{
/* Mark all attributes in our policy as unseen. */
LIST_INIT (&vs.attrs);
vs.xf = xf;
vs.life = 0;
if (attribute_map (proto->chosen->p + ISAKMP_TRANSFORM_SA_ATTRS_OFF,
GET_ISAKMP_GEN_LENGTH (proto->chosen->p)
- ISAKMP_TRANSFORM_SA_ATTRS_OFF,
attribute_unacceptable, &vs))
goto try_next;
/* Sweep over unseen tags in this section. */
tags = conf_get_tag_list (xf->field);
if (tags)
{
for (tag = TAILQ_FIRST (&tags->fields); tag;
tag = TAILQ_NEXT (tag, link))
/*
* XXX Should we care about attributes we have, they do not
* provide?
*/
for (node = LIST_FIRST (&vs.attrs); node;
node = next_node)
{
next_node = LIST_NEXT (node, link);
if (node->type
== constant_value (ike_attr_cst, tag->field))
{
LIST_REMOVE (node, link);
free (node);
}
}
conf_free_list (tags);
}
/* Are there leftover tags in this section? */
node = LIST_FIRST (&vs.attrs);
if (node)
goto try_next;
}
/* All protocols were OK, we succeeded. */
LOG_DBG ((LOG_NEGOTIATION, 20, "ike_phase_1_validate_prop: success"));
conf_free_list (conf);
if (vs.life)
free (vs.life);
return 1;
try_next:
/* Are there leftover tags in this section? */
node = LIST_FIRST (&vs.attrs);
while (node)
{
LIST_REMOVE (node, link);
free (node);
node = LIST_FIRST (&vs.attrs);
}
if (vs.life)
free (vs.life);
}
LOG_DBG ((LOG_NEGOTIATION, 20, "ike_phase_1_validate_prop: failure"));
conf_free_list (conf);
return 0;
}
/*
* Look at the attribute of type TYPE, located at VALUE for LEN bytes forward.
* The VVS argument holds a validation state kept across invocations.
* If the attribute is unacceptable to use, return non-zero, otherwise zero.
*/
static int
attribute_unacceptable (u_int16_t type, u_int8_t *value, u_int16_t len,
void *vvs)
{
struct validation_state *vs = vvs;
struct conf_list *life_conf;
struct conf_list_node *xf = vs->xf, *life;
char *tag = constant_lookup (ike_attr_cst, type);
char *str;
struct constant_map *map;
struct attr_node *node;
int rv;
if (!tag)
{
LOG_DBG ((LOG_NEGOTIATION, 60,
"attribute_unacceptable: attribute type %d not known", type));
return 1;
}
switch (type)
{
case IKE_ATTR_ENCRYPTION_ALGORITHM:
case IKE_ATTR_HASH_ALGORITHM:
case IKE_ATTR_AUTHENTICATION_METHOD:
case IKE_ATTR_GROUP_DESCRIPTION:
case IKE_ATTR_GROUP_TYPE:
case IKE_ATTR_PRF:
str = conf_get_str (xf->field, tag);
if (!str)
{
/* This attribute does not exist in this policy. */
LOG_DBG ((LOG_NEGOTIATION, 70,
"attribute_unacceptable: attr %s does not exist in %s",
tag, xf->field));
return 1;
}
map = constant_link_lookup (ike_attr_cst, type);
if (!map)
return 1;
if ((constant_value (map, str) == decode_16 (value)) ||
(!strcmp (str, "ANY")))
{
/* Mark this attribute as seen. */
node = malloc (sizeof *node);
if (!node)
{
log_error ("attribute_unacceptable: malloc (%d) failed",
sizeof *node);
return 1;
}
node->type = type;
LIST_INSERT_HEAD (&vs->attrs, node, link);
return 0;
}
LOG_DBG ((LOG_NEGOTIATION, 70,
"attribute_unacceptable: %s: got %s, expected %s", tag,
constant_lookup (map, decode_16 (value)), str));
return 1;
case IKE_ATTR_GROUP_PRIME:
case IKE_ATTR_GROUP_GENERATOR_1:
case IKE_ATTR_GROUP_GENERATOR_2:
case IKE_ATTR_GROUP_CURVE_A:
case IKE_ATTR_GROUP_CURVE_B:
/* XXX Bignums not handled yet. */
return 1;
case IKE_ATTR_LIFE_TYPE:
case IKE_ATTR_LIFE_DURATION:
life_conf = conf_get_list (xf->field, "Life");
if (life_conf && !strcmp (conf_get_str (xf->field, "Life"), "ANY"))
return 0;
rv = 1;
if (!life_conf)
{
/* Life attributes given, but not in our policy. */
LOG_DBG ((LOG_NEGOTIATION, 70, "attribute_unacceptable: "
"received unexpected life attribute"));
return 1;
}
/*
* Each lifetime type must match, otherwise we turn the proposal down.
* In order to do this we need to find the specific section of our
* policy's "Life" list and match its duration
*/
switch (type)
{
case IKE_ATTR_LIFE_TYPE:
for (life = TAILQ_FIRST (&life_conf->fields); life;
life = TAILQ_NEXT (life, link))
{
str = conf_get_str (life->field, "LIFE_TYPE");
if (!str)
{
LOG_DBG ((LOG_NEGOTIATION, 70, "attribute_unacceptable: "
"section [%s] has no LIFE_TYPE", life->field));
continue;
}
/*
* If this is the type we are looking at, save a pointer
* to this section in vs->life.
*/
if (constant_value (ike_duration_cst, str) == decode_16 (value))
{
vs->life = strdup (life->field);
rv = 0;
goto bail_out;
}
}
LOG_DBG ((LOG_NEGOTIATION, 70,
"attribute_unacceptable: unrecognized LIFE_TYPE %d",
decode_16 (value)));
vs->life = 0;
break;
case IKE_ATTR_LIFE_DURATION:
if (!vs->life)
{
LOG_DBG ((LOG_NEGOTIATION, 70, "attribute_unacceptable: "
"LIFE_DURATION without LIFE_TYPE"));
rv = 1;
goto bail_out;
}
if (!strcmp (conf_get_str (vs->life, "LIFE_DURATION"), "ANY"))
rv = 0;
else
rv = !conf_match_num (vs->life, "LIFE_DURATION",
len == 4 ? decode_32 (value) :
decode_16 (value));
free (vs->life);
vs->life = 0;
break;
}
bail_out:
conf_free_list (life_conf);
return rv;
case IKE_ATTR_KEY_LENGTH:
case IKE_ATTR_FIELD_SIZE:
case IKE_ATTR_GROUP_ORDER:
if (conf_match_num (xf->field, tag, decode_16 (value)))
{
/* Mark this attribute as seen. */
node = malloc (sizeof *node);
if (!node)
{
log_error ("attribute_unacceptable: malloc (%d) failed",
sizeof *node);
return 1;
}
node->type = type;
LIST_INSERT_HEAD (&vs->attrs, node, link);
return 0;
}
return 1;
}
return 1;
}