#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
};
#define RETVALUES_NUM 2
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];
struct keynote_deckey dc;
X509_NAME *subject;
if (ignore_policy ||
strncmp("yes", conf_get_str("General", "Use-Keynote"), 3))
return 1;
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;
}
}
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;
}
}
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);
policy_exchange = exchange;
policy_sa = sa;
policy_isakmp_sa = isakmp_sa;
return_values[0] = "false";
return_values[1] = "true";
switch (isakmp_sa->recv_certtype) {
case ISAKMP_CERTENC_NONE:
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;
}
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;
}
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;
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;
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;
}
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;
}
}
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));
kn_cleanup_action_environment(isakmp_sa->policy_id);
for (i = 0; i < nprinc; i++) {
kn_remove_authorizer(isakmp_sa->policy_id, principal[i]);
free(principal[i]);
}
free(principal);
principal = 0;
nprinc = 0;
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);
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);
if (result == 0)
log_print("check_policy: negotiated SA failed policy check");
return result;
}
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;
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)) {
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)) {
if (prop_no >= prop_cnt) {
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;
}
}
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)) {
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_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);
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);
transforms_len[prop_no] +=
(transform_len[prop_no][xf_no]
= attr - transform[prop_no][xf_no]);
if (proto_id != IPSEC_PROTO_IPCOMP) {
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;
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);
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);
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;
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);
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;
ie->sa_i_b = message_copy(msg, ISAKMP_GEN_SZ, &ie->sa_i_b_len);
if (!ie->sa_i_b)
goto bail_out;
if (exchange_gen_nonce(msg, 16))
return -1;
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;
}
}
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;
}
}
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);
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;
}
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;
}
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;
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;
}
hashp->flags |= PL_MARK;
msg->flags |= MSG_AUTHENTICATED;
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);
if (kep)
ie->pfs = 1;
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:
break;
default:
message_drop(msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION,
0, 1, 0);
return -1;
}
}
idp = payload_first(msg, ISAKMP_PAYLOAD_ID);
if (idp) {
if (!TAILQ_NEXT(idp, link)) {
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("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 {
ie->flags = IPSEC_EXCH_FLAG_NO_ID;
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));
}
TAILQ_FOREACH(xf, &msg->payload[ISAKMP_PAYLOAD_TRANSFORM], link) {
if (sa_add_transform(sa, xf, exchange->initiator, &proto))
return -1;
ipsec_decode_transform(msg, sa, proto, xf->p);
}
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;
}
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;
}
if (exchange_save_nonce(msg))
return -1;
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);
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;
}
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;
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;
for (i = 0; i < 2; i++) {
prf = prf_alloc(isa->prf_type, isa->hash,
isa->skeyid_d, isa->skeyid_len);
if (!prf) {
continue;
}
ie->keymat_len = ipsec_keymat_length(proto);
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);
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]) {
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 (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));
}
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;
if (hash != pkt + ISAKMP_HDR_SZ) {
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;
}
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;
msg->flags |= MSG_AUTHENTICATED;
kep = payload_first(msg, ISAKMP_PAYLOAD_KEY_EXCH);
if (kep)
ie->pfs = 1;
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:
break;
default:
message_drop(msg, ISAKMP_NOTIFY_INVALID_ID_INFORMATION,
0, 1, 0);
goto cleanup;
}
}
idp = payload_first(msg, ISAKMP_PAYLOAD_ID);
if (idp) {
if (!TAILQ_NEXT(idp, link)) {
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 {
ie->flags = IPSEC_EXCH_FLAG_NO_ID;
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)) {
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;
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;
}
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;
}
}
retval = 0;
next_sa:
;
}
if (kep) {
ie->group = group_get(group_desc);
if (!ie->group) {
goto cleanup;
}
}
if (exchange_save_nonce(msg))
goto cleanup;
if (kep && ipsec_save_g_x(msg))
goto cleanup;
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:
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;
}
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;
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;
}
if (message_add_sa_payload(msg))
return -1;
if (exchange_gen_nonce(msg, nonce_sz))
return -1;
if (ie->group && ipsec_gen_g_x(msg))
return -1;
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;
}
}
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);
for (i = 2; i < msg->iovlen; i++) {
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;
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;
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;
}
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);
msg->flags |= MSG_AUTHENTICATED;
post_quick_mode(msg);
return 0;
cleanup:
free(my_hash);
return -1;
}