src/sbin/isakmpd/ike_quick_mode.c

2022 lines
58 KiB
C

/* $OpenBSD: ike_quick_mode.c,v 1.115 2023/03/31 20:16:55 tb 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>
#include <sys/types.h>
#include <regex.h>
#include <keynote.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 "message.h"
#include "policy.h"
#include "prf.h"
#include "sa.h"
#include "transport.h"
#include "util.h"
#include "key.h"
#include "x509.h"
static void gen_g_xy(struct message *);
static int initiator_send_HASH_SA_NONCE(struct message *);
static int initiator_recv_HASH_SA_NONCE(struct message *);
static int initiator_send_HASH(struct message *);
static void post_quick_mode(struct message *);
static int responder_recv_HASH_SA_NONCE(struct message *);
static int responder_send_HASH_SA_NONCE(struct message *);
static int responder_recv_HASH(struct message *);
static int check_policy(struct exchange *, struct sa *, struct sa *);
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
};
/* 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 */
struct keynote_deckey dc;
X509_NAME *subject;
/* 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);
kn_close(isakmp_sa->policy_id);
isakmp_sa->policy_id = -1;
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:
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;
}
break;
case ISAKMP_CERTENC_X509_SIG:
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;
}
if (asprintf(&principal[1], "rsa-hex:%s", principal[0]) == -1) {
log_error("check_policy: asprintf() failed");
goto policydone;
}
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;
/* 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]);
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]);
}
free(keynote_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;
}
/*
* 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;
char *name;
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 = reallocarray(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 = reallocarray(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 = reallocarray(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 = reallocarray(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 = reallocarray(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);
}
if (proto_id == IPSEC_PROTO_IPSEC_ESP &&
(exchange->flags &
EXCHANGE_FLAG_NAT_T_ENABLE)) {
name = conf_get_str(xf->field,
"ENCAPSULATION_MODE");
if (name) {
value = constant_value(
ipsec_encap_cst,
name);
switch (value) {
case IPSEC_ENCAP_TUNNEL:
value = exchange->flags & EXCHANGE_FLAG_NAT_T_DRAFT ?
IPSEC_ENCAP_UDP_ENCAP_TUNNEL_DRAFT :
IPSEC_ENCAP_UDP_ENCAP_TUNNEL;
break;
case IPSEC_ENCAP_TRANSPORT:
value = exchange->flags & EXCHANGE_FLAG_NAT_T_DRAFT ?
IPSEC_ENCAP_UDP_ENCAP_TRANSPORT_DRAFT :
IPSEC_ENCAP_UDP_ENCAP_TRANSPORT;
break;
}
attr = attribute_set_basic(
attr,
IPSEC_ATTR_ENCAPSULATION_MODE,
value);
}
} else {
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) {
free(proto);
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 = calloc(1, sizeof *pa);
if (!pa) {
free(proto->data);
free(proto);
goto bail_out;
}
pa->len = transform_len[prop_no][xf_no];
pa->attrs = malloc(pa->len);
if (!pa->attrs) {
free(proto->data);
free(proto);
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);
if (!ie->group)
return -1;
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:
free(sa_buf);
if (proposal) {
for (i = 0; i < prop_no; i++) {
free(proposal[i]);
if (transform[i]) {
for (xf_no = 0; xf_no < transform_cnt[i];
xf_no++)
free(transform[i][xf_no]);
free(transform[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);
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,
hash->hashsize));
if (memcmp(hashp->p + ISAKMP_HASH_DATA_OFF, hash->digest,
hash->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;
/* Drop message when it contains ID types we do not implement yet. */
TAILQ_FOREACH(idp, &msg->payload[ISAKMP_PAYLOAD_ID], link) {
switch (GET_ISAKMP_ID_TYPE(idp->p)) {
case IPSEC_ID_IPV4_ADDR:
case IPSEC_ID_IPV4_ADDR_SUBNET:
case IPSEC_ID_IPV6_ADDR:
case IPSEC_ID_IPV6_ADDR_SUBNET:
break;
case IPSEC_ID_FQDN:
/*
* FQDN may be used for in NAT-T with transport mode.
* We can handle the message in this case. In the
* other cases we'll drop the message later.
*/
break;
default:
message_drop(msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION,
0, 1, 0);
return -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));
free(ie->id_ci);
ie->id_ci = 0;
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. */
TAILQ_FOREACH(xf, &msg->payload[ISAKMP_PAYLOAD_TRANSFORM], 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);
}
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;
}
/* 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);
/*
* We want a HASH payload to start with. XXX Share with
* ike_main_mode.c?
*/
buf = malloc(ISAKMP_HASH_SZ + hash->hashsize);
if (!buf) {
log_error("initiator_send_HASH: malloc (%lu) failed",
ISAKMP_HASH_SZ + (unsigned long)hash->hashsize);
return -1;
}
if (message_add_payload(msg, ISAKMP_PAYLOAD_HASH, buf,
ISAKMP_HASH_SZ + hash->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, hash->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 outgoing.
*/
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_xy_len));
prf->Update(prf->prfctx,
ie->g_xy, ie->g_xy_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: %s",
(exchange->initiator == 0) ? " (as responder)" : "",
!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;
/* Drop message when it contains ID types we do not implement yet. */
TAILQ_FOREACH(idp, &msg->payload[ISAKMP_PAYLOAD_ID], link) {
switch (GET_ISAKMP_ID_TYPE(idp->p)) {
case IPSEC_ID_IPV4_ADDR:
case IPSEC_ID_IPV4_ADDR_SUBNET:
case IPSEC_ID_IPV6_ADDR:
case IPSEC_ID_IPV6_ADDR_SUBNET:
break;
case IPSEC_ID_FQDN:
/*
* FQDN may be used for in NAT-T with transport mode.
* We can handle the message in this case. In the
* other cases we'll drop the message later.
*/
break;
default:
message_drop(msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION,
0, 1, 0);
goto cleanup;
}
}
/* 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));
}
if (message_negotiate_sa(msg, check_policy))
goto cleanup;
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 (
ignore_policy ||
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_INVALID_ID_INFORMATION, 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);
free(my_hash);
free(ie->id_ci);
ie->id_ci = 0;
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 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 + hash->hashsize);
if (!buf) {
log_error("responder_send_HASH_SA_NONCE: malloc (%lu) failed",
ISAKMP_HASH_SZ + (unsigned long)hash->hashsize);
return -1;
}
if (message_add_payload(msg, ISAKMP_PAYLOAD_HASH, buf,
ISAKMP_HASH_SZ + hash->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,
hash->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_len = dh_secretlen(ie->group);
ie->g_xy = malloc(ie->g_xy_len);
if (!ie->g_xy) {
log_error("gen_g_xy: malloc (%lu) failed",
(unsigned long)ie->g_xy_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_xy_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:
free(my_hash);
return -1;
}