5810 lines
150 KiB
C
5810 lines
150 KiB
C
/* IPsec DOI and Oakley resolution routines
|
|
* Copyright (C) 1997 Angelos D. Keromytis.
|
|
* Copyright (C) 1998-2002 D. Hugh Redelmeier.
|
|
* Copyright (C) 2009 Andreas Steffen - Hochschule fuer Technik Rapperswil
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <resolv.h>
|
|
#include <arpa/nameser.h> /* missing from <resolv.h> on old systems */
|
|
#include <sys/queue.h>
|
|
|
|
#include <freeswan.h>
|
|
|
|
#include <library.h>
|
|
#include <asn1/asn1.h>
|
|
#include <crypto/hashers/hasher.h>
|
|
#include <crypto/prfs/prf.h>
|
|
#include <crypto/rngs/rng.h>
|
|
#include <credentials/keys/private_key.h>
|
|
#include <credentials/keys/public_key.h>
|
|
#include <utils/identification.h>
|
|
|
|
#include "constants.h"
|
|
#include "defs.h"
|
|
#include "myid.h"
|
|
#include "state.h"
|
|
#include "x509.h"
|
|
#include "ac.h"
|
|
#include "crl.h"
|
|
#include "ca.h"
|
|
#include "certs.h"
|
|
#include "smartcard.h"
|
|
#include "connections.h"
|
|
#include "keys.h"
|
|
#include "packet.h"
|
|
#include "demux.h" /* needs packet.h */
|
|
#include "adns.h" /* needs <resolv.h> */
|
|
#include "dnskey.h" /* needs keys.h and adns.h */
|
|
#include "kernel.h"
|
|
#include "log.h"
|
|
#include "cookie.h"
|
|
#include "server.h"
|
|
#include "spdb.h"
|
|
#include "timer.h"
|
|
#include "ipsec_doi.h" /* needs demux.h and state.h */
|
|
#include "whack.h"
|
|
#include "fetch.h"
|
|
#include "pkcs7.h"
|
|
#include "crypto.h"
|
|
#include "vendor.h"
|
|
#include "alg_info.h"
|
|
#include "ike_alg.h"
|
|
#include "kernel_alg.h"
|
|
#include "nat_traversal.h"
|
|
#include "virtual.h"
|
|
|
|
/*
|
|
* are we sending Pluto's Vendor ID?
|
|
*/
|
|
#ifdef VENDORID
|
|
#define SEND_PLUTO_VID 1
|
|
#else /* !VENDORID */
|
|
#define SEND_PLUTO_VID 0
|
|
#endif /* !VENDORID */
|
|
|
|
/*
|
|
* are we sending an XAUTH VID?
|
|
*/
|
|
#ifdef XAUTH_VID
|
|
#define SEND_XAUTH_VID 1
|
|
#else /* !XAUTH_VID */
|
|
#define SEND_XAUTH_VID 0
|
|
#endif /* !XAUTH_VID */
|
|
|
|
/*
|
|
* are we sending a Cisco Unity VID?
|
|
*/
|
|
#ifdef CISCO_QUIRKS
|
|
#define SEND_CISCO_UNITY_VID 1
|
|
#else /* !CISCO_QUIRKS */
|
|
#define SEND_CISCO_UNITY_VID 0
|
|
#endif /* !CISCO_QUIRKS */
|
|
|
|
/* MAGIC: perform f, a function that returns notification_t
|
|
* and return from the ENCLOSING stf_status returning function if it fails.
|
|
*/
|
|
#define RETURN_STF_FAILURE(f) \
|
|
{ int r = (f); if (r != ISAKMP_NOTHING_WRONG) return STF_FAIL + r; }
|
|
|
|
/* create output HDR as replica of input HDR */
|
|
void echo_hdr(struct msg_digest *md, bool enc, u_int8_t np)
|
|
{
|
|
struct isakmp_hdr r_hdr = md->hdr; /* mostly same as incoming header */
|
|
|
|
r_hdr.isa_flags &= ~ISAKMP_FLAG_COMMIT; /* we won't ever turn on this bit */
|
|
if (enc)
|
|
{
|
|
r_hdr.isa_flags |= ISAKMP_FLAG_ENCRYPTION;
|
|
}
|
|
/* some day, we may have to set r_hdr.isa_version */
|
|
r_hdr.isa_np = np;
|
|
if (!out_struct(&r_hdr, &isakmp_hdr_desc, &md->reply, &md->rbody))
|
|
{
|
|
impossible(); /* surely must have room and be well-formed */
|
|
}
|
|
}
|
|
|
|
/* Compute DH shared secret from our local secret and the peer's public value.
|
|
* We make the leap that the length should be that of the group
|
|
* (see quoted passage at start of ACCEPT_KE).
|
|
*/
|
|
static void compute_dh_shared(struct state *st, const chunk_t g)
|
|
{
|
|
passert(st->st_dh);
|
|
st->st_dh->set_other_public_value(st->st_dh, g);
|
|
st->st_dh->get_shared_secret(st->st_dh, &st->st_shared);
|
|
DBG_cond_dump_chunk(DBG_CRYPT, "DH shared secret:\n", st->st_shared);
|
|
}
|
|
|
|
/* if we haven't already done so, compute a local DH secret (st->st_sec) and
|
|
* the corresponding public value (g). This is emitted as a KE payload.
|
|
*/
|
|
static bool build_and_ship_KE(struct state *st, chunk_t *g,
|
|
const struct dh_desc *group,
|
|
pb_stream *outs, u_int8_t np)
|
|
{
|
|
if (st->st_dh == NULL)
|
|
{
|
|
st->st_dh = lib->crypto->create_dh(lib->crypto, group->algo_id);
|
|
if (st->st_dh == NULL)
|
|
{
|
|
plog("Diffie Hellman group %N is not available",
|
|
diffie_hellman_group_names, group->algo_id);
|
|
return FALSE;
|
|
}
|
|
}
|
|
st->st_dh->get_my_public_value(st->st_dh, g);
|
|
DBG(DBG_CRYPT,
|
|
DBG_dump_chunk("Public DH value sent:\n", *g)
|
|
)
|
|
return out_generic_chunk(np, &isakmp_keyex_desc, outs, *g, "keyex value");
|
|
}
|
|
|
|
/* accept_ke
|
|
*
|
|
* Check and accept DH public value (Gi or Gr) from peer's message.
|
|
* According to RFC2409 "The Internet key exchange (IKE)" 5:
|
|
* The Diffie-Hellman public value passed in a KE payload, in either
|
|
* a phase 1 or phase 2 exchange, MUST be the length of the negotiated
|
|
* Diffie-Hellman group enforced, if necessary, by pre-pending the
|
|
* value with zeros.
|
|
*/
|
|
static notification_t accept_KE(chunk_t *dest, const char *val_name,
|
|
const struct dh_desc *gr,
|
|
pb_stream *pbs)
|
|
{
|
|
if (pbs_left(pbs) != gr->ke_size)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "KE has %u byte DH public value; %u required"
|
|
, (unsigned) pbs_left(pbs), gr->ke_size);
|
|
/* XXX Could send notification back */
|
|
return ISAKMP_INVALID_KEY_INFORMATION;
|
|
}
|
|
free(dest->ptr);
|
|
*dest = chunk_create(pbs->cur, pbs_left(pbs));
|
|
*dest = chunk_clone(*dest);
|
|
DBG_cond_dump_chunk(DBG_CRYPT, "DH public value received:\n", *dest);
|
|
return ISAKMP_NOTHING_WRONG;
|
|
}
|
|
|
|
/* accept_PFS_KE
|
|
*
|
|
* Check and accept optional Quick Mode KE payload for PFS.
|
|
* Extends ACCEPT_PFS to check whether KE is allowed or required.
|
|
*/
|
|
static notification_t accept_PFS_KE(struct msg_digest *md, chunk_t *dest,
|
|
const char *val_name, const char *msg_name)
|
|
{
|
|
struct state *st = md->st;
|
|
struct payload_digest *const ke_pd = md->chain[ISAKMP_NEXT_KE];
|
|
|
|
if (ke_pd == NULL)
|
|
{
|
|
if (st->st_pfs_group != NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "missing KE payload in %s message", msg_name);
|
|
return ISAKMP_INVALID_KEY_INFORMATION;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (st->st_pfs_group == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s message KE payload requires a GROUP_DESCRIPTION attribute in SA"
|
|
, msg_name);
|
|
return ISAKMP_INVALID_KEY_INFORMATION;
|
|
}
|
|
if (ke_pd->next != NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s message contains several KE payloads; we accept at most one", msg_name);
|
|
return ISAKMP_INVALID_KEY_INFORMATION; /* ??? */
|
|
}
|
|
return accept_KE(dest, val_name, st->st_pfs_group, &ke_pd->pbs);
|
|
}
|
|
return ISAKMP_NOTHING_WRONG;
|
|
}
|
|
|
|
static bool build_and_ship_nonce(chunk_t *n, pb_stream *outs, u_int8_t np,
|
|
const char *name)
|
|
{
|
|
rng_t *rng;
|
|
|
|
free(n->ptr);
|
|
*n = chunk_create(malloc(DEFAULT_NONCE_SIZE), DEFAULT_NONCE_SIZE);
|
|
rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
|
|
rng->get_bytes(rng, DEFAULT_NONCE_SIZE, n->ptr);
|
|
rng->destroy(rng);
|
|
return out_generic_chunk(np, &isakmp_nonce_desc, outs, *n, name);
|
|
}
|
|
|
|
static linked_list_t* collect_rw_ca_candidates(struct msg_digest *md)
|
|
{
|
|
linked_list_t *list = linked_list_create();
|
|
connection_t *d;
|
|
|
|
d = find_host_connection(&md->iface->addr, pluto_port, (ip_address*)NULL,
|
|
md->sender_port, LEMPTY);
|
|
|
|
for (; d != NULL; d = d->hp_next)
|
|
{
|
|
/* must be a road warrior connection */
|
|
if (d->kind == CK_TEMPLATE && !(d->policy & POLICY_OPPO) &&
|
|
d->spd.that.ca)
|
|
{
|
|
enumerator_t *enumerator;
|
|
identification_t *ca;
|
|
bool new_entry = TRUE;
|
|
|
|
enumerator = list->create_enumerator(list);
|
|
while (enumerator->enumerate(enumerator, &ca))
|
|
{
|
|
if (ca->equals(ca, d->spd.that.ca))
|
|
{
|
|
new_entry = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
|
|
if (new_entry)
|
|
{
|
|
list->insert_last(list, d->spd.that.ca->clone(d->spd.that.ca));
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
static bool build_and_ship_CR(u_int8_t type, chunk_t ca, pb_stream *outs,
|
|
u_int8_t np)
|
|
{
|
|
pb_stream cr_pbs;
|
|
struct isakmp_cr cr_hd;
|
|
cr_hd.isacr_np = np;
|
|
cr_hd.isacr_type = type;
|
|
|
|
/* build CR header */
|
|
if (!out_struct(&cr_hd, &isakmp_ipsec_cert_req_desc, outs, &cr_pbs))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (ca.ptr != NULL)
|
|
{
|
|
/* build CR body containing the distinguished name of the CA */
|
|
if (!out_chunk(ca, &cr_pbs, "CA"))
|
|
return FALSE;
|
|
}
|
|
close_output_pbs(&cr_pbs);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Send a notification to the peer. We could decide
|
|
* whether to send the notification, based on the type and the
|
|
* destination, if we care to.
|
|
*/
|
|
static void send_notification(struct state *sndst, u_int16_t type,
|
|
struct state *encst, msgid_t msgid,
|
|
u_char *icookie, u_char *rcookie,
|
|
u_char *spi, size_t spisize, u_char protoid)
|
|
{
|
|
u_char buffer[1024];
|
|
pb_stream pbs, r_hdr_pbs;
|
|
u_char *r_hashval = NULL; /* where in reply to jam hash value */
|
|
u_char *r_hash_start = NULL; /* start of what is to be hashed */
|
|
|
|
passert((sndst) && (sndst->st_connection));
|
|
|
|
plog("sending %snotification %s to %s:%u"
|
|
, encst ? "encrypted " : ""
|
|
, enum_name(¬ification_names, type)
|
|
, ip_str(&sndst->st_connection->spd.that.host_addr)
|
|
, (unsigned)sndst->st_connection->spd.that.host_port);
|
|
|
|
memset(buffer, 0, sizeof(buffer));
|
|
init_pbs(&pbs, buffer, sizeof(buffer), "ISAKMP notify");
|
|
|
|
/* HDR* */
|
|
{
|
|
struct isakmp_hdr hdr;
|
|
|
|
hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION;
|
|
hdr.isa_np = encst ? ISAKMP_NEXT_HASH : ISAKMP_NEXT_N;
|
|
hdr.isa_xchg = ISAKMP_XCHG_INFO;
|
|
hdr.isa_msgid = msgid;
|
|
hdr.isa_flags = encst ? ISAKMP_FLAG_ENCRYPTION : 0;
|
|
if (icookie)
|
|
{
|
|
memcpy(hdr.isa_icookie, icookie, COOKIE_SIZE);
|
|
}
|
|
if (rcookie)
|
|
{
|
|
memcpy(hdr.isa_rcookie, rcookie, COOKIE_SIZE);
|
|
}
|
|
if (!out_struct(&hdr, &isakmp_hdr_desc, &pbs, &r_hdr_pbs))
|
|
{
|
|
impossible();
|
|
}
|
|
}
|
|
|
|
/* HASH -- value to be filled later */
|
|
if (encst)
|
|
{
|
|
pb_stream hash_pbs;
|
|
if (!out_generic(ISAKMP_NEXT_N, &isakmp_hash_desc, &r_hdr_pbs, &hash_pbs))
|
|
{
|
|
impossible();
|
|
}
|
|
r_hashval = hash_pbs.cur; /* remember where to plant value */
|
|
if (!out_zero(
|
|
encst->st_oakley.hasher->hash_digest_size, &hash_pbs, "HASH"))
|
|
{
|
|
impossible();
|
|
}
|
|
close_output_pbs(&hash_pbs);
|
|
r_hash_start = r_hdr_pbs.cur; /* hash from after HASH */
|
|
}
|
|
|
|
/* Notification Payload */
|
|
{
|
|
pb_stream not_pbs;
|
|
struct isakmp_notification isan;
|
|
|
|
isan.isan_doi = ISAKMP_DOI_IPSEC;
|
|
isan.isan_np = ISAKMP_NEXT_NONE;
|
|
isan.isan_type = type;
|
|
isan.isan_spisize = spisize;
|
|
isan.isan_protoid = protoid;
|
|
|
|
if (!out_struct(&isan, &isakmp_notification_desc, &r_hdr_pbs, ¬_pbs)
|
|
|| !out_raw(spi, spisize, ¬_pbs, "spi"))
|
|
{
|
|
impossible();
|
|
}
|
|
close_output_pbs(¬_pbs);
|
|
}
|
|
|
|
/* calculate hash value and patch into Hash Payload */
|
|
if (encst)
|
|
{
|
|
chunk_t msgid_chunk = chunk_from_thing(msgid);
|
|
chunk_t msg_chunk = { r_hash_start, r_hdr_pbs.cur-r_hash_start };
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
|
|
prf_alg = oakley_to_prf(encst->st_oakley.hash);
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf->set_key(prf, encst->st_skeyid_a);
|
|
prf->get_bytes(prf, msgid_chunk, NULL);
|
|
prf->get_bytes(prf, msg_chunk, r_hashval);
|
|
|
|
DBG(DBG_CRYPT,
|
|
DBG_log("HASH computed:");
|
|
DBG_dump("", r_hashval, prf->get_block_size(prf));
|
|
)
|
|
prf->destroy(prf);
|
|
}
|
|
|
|
/* Encrypt message (preserve st_iv and st_new_iv) */
|
|
if (encst)
|
|
{
|
|
u_char old_iv[MAX_DIGEST_LEN];
|
|
u_char new_iv[MAX_DIGEST_LEN];
|
|
|
|
u_int old_iv_len = encst->st_iv_len;
|
|
u_int new_iv_len = encst->st_new_iv_len;
|
|
|
|
if (old_iv_len > MAX_DIGEST_LEN || new_iv_len > MAX_DIGEST_LEN)
|
|
{
|
|
impossible();
|
|
}
|
|
memcpy(old_iv, encst->st_iv, old_iv_len);
|
|
memcpy(new_iv, encst->st_new_iv, new_iv_len);
|
|
|
|
if (!IS_ISAKMP_SA_ESTABLISHED(encst->st_state))
|
|
{
|
|
memcpy(encst->st_ph1_iv, encst->st_new_iv, encst->st_new_iv_len);
|
|
encst->st_ph1_iv_len = encst->st_new_iv_len;
|
|
}
|
|
init_phase2_iv(encst, &msgid);
|
|
if (!encrypt_message(&r_hdr_pbs, encst))
|
|
{
|
|
impossible();
|
|
}
|
|
|
|
/* restore preserved st_iv and st_new_iv */
|
|
memcpy(encst->st_iv, old_iv, old_iv_len);
|
|
memcpy(encst->st_new_iv, new_iv, new_iv_len);
|
|
encst->st_iv_len = old_iv_len;
|
|
encst->st_new_iv_len = new_iv_len;
|
|
}
|
|
else
|
|
{
|
|
close_output_pbs(&r_hdr_pbs);
|
|
}
|
|
|
|
/* Send packet (preserve st_tpacket) */
|
|
{
|
|
chunk_t saved_tpacket = sndst->st_tpacket;
|
|
|
|
sndst->st_tpacket = chunk_create(pbs.start, pbs_offset(&pbs));
|
|
send_packet(sndst, "ISAKMP notify");
|
|
sndst->st_tpacket = saved_tpacket;
|
|
}
|
|
}
|
|
|
|
void send_notification_from_state(struct state *st, enum state_kind state,
|
|
u_int16_t type)
|
|
{
|
|
struct state *p1st;
|
|
|
|
passert(st);
|
|
|
|
if (state == STATE_UNDEFINED)
|
|
state = st->st_state;
|
|
|
|
if (IS_QUICK(state))
|
|
{
|
|
p1st = find_phase1_state(st->st_connection, ISAKMP_SA_ESTABLISHED_STATES);
|
|
if ((p1st == NULL) || (!IS_ISAKMP_SA_ESTABLISHED(p1st->st_state)))
|
|
{
|
|
loglog(RC_LOG_SERIOUS,
|
|
"no Phase1 state for Quick mode notification");
|
|
return;
|
|
}
|
|
send_notification(st, type, p1st, generate_msgid(p1st),
|
|
st->st_icookie, st->st_rcookie, NULL, 0, PROTO_ISAKMP);
|
|
}
|
|
else if (IS_ISAKMP_ENCRYPTED(state) && st->st_enc_key.ptr != NULL)
|
|
{
|
|
send_notification(st, type, st, generate_msgid(st),
|
|
st->st_icookie, st->st_rcookie, NULL, 0, PROTO_ISAKMP);
|
|
}
|
|
else
|
|
{
|
|
/* no ISAKMP SA established - don't encrypt notification */
|
|
send_notification(st, type, NULL, 0,
|
|
st->st_icookie, st->st_rcookie, NULL, 0, PROTO_ISAKMP);
|
|
}
|
|
}
|
|
|
|
void send_notification_from_md(struct msg_digest *md, u_int16_t type)
|
|
{
|
|
/**
|
|
* Create a dummy state to be able to use send_packet in
|
|
* send_notification
|
|
*
|
|
* we need to set:
|
|
* st_connection->that.host_addr
|
|
* st_connection->that.host_port
|
|
* st_connection->interface
|
|
*/
|
|
struct state st;
|
|
connection_t cnx;
|
|
|
|
passert(md);
|
|
|
|
memset(&st, 0, sizeof(st));
|
|
memset(&cnx, 0, sizeof(cnx));
|
|
st.st_connection = &cnx;
|
|
cnx.spd.that.host_addr = md->sender;
|
|
cnx.spd.that.host_port = md->sender_port;
|
|
cnx.interface = md->iface;
|
|
|
|
send_notification(&st, type, NULL, 0,
|
|
md->hdr.isa_icookie, md->hdr.isa_rcookie, NULL, 0, PROTO_ISAKMP);
|
|
}
|
|
|
|
/* Send a Delete Notification to announce deletion of ISAKMP SA or
|
|
* inbound IPSEC SAs. Does nothing if no such SAs are being deleted.
|
|
* Delete Notifications cannot announce deletion of outbound IPSEC/ISAKMP SAs.
|
|
*/
|
|
void send_delete(struct state *st)
|
|
{
|
|
pb_stream reply_pbs;
|
|
pb_stream r_hdr_pbs;
|
|
msgid_t msgid;
|
|
u_char buffer[8192];
|
|
struct state *p1st;
|
|
ip_said said[EM_MAXRELSPIS];
|
|
ip_said *ns = said;
|
|
u_char
|
|
*r_hashval, /* where in reply to jam hash value */
|
|
*r_hash_start; /* start of what is to be hashed */
|
|
bool isakmp_sa = FALSE;
|
|
|
|
if (IS_IPSEC_SA_ESTABLISHED(st->st_state))
|
|
{
|
|
p1st = find_phase1_state(st->st_connection, ISAKMP_SA_ESTABLISHED_STATES);
|
|
if (p1st == NULL)
|
|
{
|
|
DBG(DBG_CONTROL, DBG_log("no Phase 1 state for Delete"));
|
|
return;
|
|
}
|
|
|
|
if (st->st_ah.present)
|
|
{
|
|
ns->spi = st->st_ah.our_spi;
|
|
ns->dst = st->st_connection->spd.this.host_addr;
|
|
ns->proto = PROTO_IPSEC_AH;
|
|
ns++;
|
|
}
|
|
if (st->st_esp.present)
|
|
{
|
|
ns->spi = st->st_esp.our_spi;
|
|
ns->dst = st->st_connection->spd.this.host_addr;
|
|
ns->proto = PROTO_IPSEC_ESP;
|
|
ns++;
|
|
}
|
|
|
|
passert(ns != said); /* there must be some SAs to delete */
|
|
}
|
|
else if (IS_ISAKMP_SA_ESTABLISHED(st->st_state))
|
|
{
|
|
p1st = st;
|
|
isakmp_sa = TRUE;
|
|
}
|
|
else
|
|
{
|
|
return; /* nothing to do */
|
|
}
|
|
|
|
msgid = generate_msgid(p1st);
|
|
|
|
zero(buffer);
|
|
init_pbs(&reply_pbs, buffer, sizeof(buffer), "delete msg");
|
|
|
|
/* HDR* */
|
|
{
|
|
struct isakmp_hdr hdr;
|
|
|
|
hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION;
|
|
hdr.isa_np = ISAKMP_NEXT_HASH;
|
|
hdr.isa_xchg = ISAKMP_XCHG_INFO;
|
|
hdr.isa_msgid = msgid;
|
|
hdr.isa_flags = ISAKMP_FLAG_ENCRYPTION;
|
|
memcpy(hdr.isa_icookie, p1st->st_icookie, COOKIE_SIZE);
|
|
memcpy(hdr.isa_rcookie, p1st->st_rcookie, COOKIE_SIZE);
|
|
if (!out_struct(&hdr, &isakmp_hdr_desc, &reply_pbs, &r_hdr_pbs))
|
|
impossible();
|
|
}
|
|
|
|
/* HASH -- value to be filled later */
|
|
{
|
|
pb_stream hash_pbs;
|
|
|
|
if (!out_generic(ISAKMP_NEXT_D, &isakmp_hash_desc, &r_hdr_pbs, &hash_pbs))
|
|
{
|
|
impossible();
|
|
}
|
|
r_hashval = hash_pbs.cur; /* remember where to plant value */
|
|
if (!out_zero(p1st->st_oakley.hasher->hash_digest_size, &hash_pbs, "HASH(1)"))
|
|
{
|
|
impossible();
|
|
}
|
|
close_output_pbs(&hash_pbs);
|
|
r_hash_start = r_hdr_pbs.cur; /* hash from after HASH(1) */
|
|
}
|
|
|
|
/* Delete Payloads */
|
|
if (isakmp_sa)
|
|
{
|
|
pb_stream del_pbs;
|
|
struct isakmp_delete isad;
|
|
u_char isakmp_spi[2*COOKIE_SIZE];
|
|
|
|
isad.isad_doi = ISAKMP_DOI_IPSEC;
|
|
isad.isad_np = ISAKMP_NEXT_NONE;
|
|
isad.isad_spisize = (2 * COOKIE_SIZE);
|
|
isad.isad_protoid = PROTO_ISAKMP;
|
|
isad.isad_nospi = 1;
|
|
|
|
memcpy(isakmp_spi, st->st_icookie, COOKIE_SIZE);
|
|
memcpy(isakmp_spi+COOKIE_SIZE, st->st_rcookie, COOKIE_SIZE);
|
|
|
|
if (!out_struct(&isad, &isakmp_delete_desc, &r_hdr_pbs, &del_pbs)
|
|
|| !out_raw(&isakmp_spi, (2*COOKIE_SIZE), &del_pbs, "delete payload"))
|
|
{
|
|
impossible();
|
|
}
|
|
close_output_pbs(&del_pbs);
|
|
}
|
|
else
|
|
{
|
|
while (ns != said)
|
|
{
|
|
|
|
pb_stream del_pbs;
|
|
struct isakmp_delete isad;
|
|
|
|
ns--;
|
|
isad.isad_doi = ISAKMP_DOI_IPSEC;
|
|
isad.isad_np = ns == said? ISAKMP_NEXT_NONE : ISAKMP_NEXT_D;
|
|
isad.isad_spisize = sizeof(ipsec_spi_t);
|
|
isad.isad_protoid = ns->proto;
|
|
|
|
isad.isad_nospi = 1;
|
|
if (!out_struct(&isad, &isakmp_delete_desc, &r_hdr_pbs, &del_pbs)
|
|
|| !out_raw(&ns->spi, sizeof(ipsec_spi_t), &del_pbs, "delete payload"))
|
|
{
|
|
impossible();
|
|
}
|
|
close_output_pbs(&del_pbs);
|
|
}
|
|
}
|
|
|
|
/* calculate hash value and patch into Hash Payload */
|
|
{
|
|
chunk_t msgid_chunk = chunk_from_thing(msgid);
|
|
chunk_t msg_chunk = { r_hash_start, r_hdr_pbs.cur-r_hash_start };
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
|
|
prf_alg = oakley_to_prf(p1st->st_oakley.hash);
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf->set_key(prf, p1st->st_skeyid_a);
|
|
prf->get_bytes(prf, msgid_chunk, NULL);
|
|
prf->get_bytes(prf, msg_chunk, r_hashval);
|
|
|
|
DBG(DBG_CRYPT,
|
|
DBG_log("HASH(1) computed:");
|
|
DBG_dump("", r_hashval, prf->get_block_size(prf));
|
|
)
|
|
|
|
prf->destroy(prf);
|
|
}
|
|
|
|
/* Do a dance to avoid needing a new state object.
|
|
* We use the Phase 1 State. This is the one with right
|
|
* IV, for one thing.
|
|
* The tricky bits are:
|
|
* - we need to preserve (save/restore) st_iv (but not st_iv_new)
|
|
* - we need to preserve (save/restore) st_tpacket.
|
|
*/
|
|
{
|
|
u_char old_iv[MAX_DIGEST_LEN];
|
|
chunk_t saved_tpacket = p1st->st_tpacket;
|
|
|
|
memcpy(old_iv, p1st->st_iv, p1st->st_iv_len);
|
|
init_phase2_iv(p1st, &msgid);
|
|
|
|
if (!encrypt_message(&r_hdr_pbs, p1st))
|
|
{
|
|
impossible();
|
|
}
|
|
p1st->st_tpacket = chunk_create(reply_pbs.start, pbs_offset(&reply_pbs));
|
|
send_packet(p1st, "delete notify");
|
|
p1st->st_tpacket = saved_tpacket;
|
|
|
|
/* get back old IV for this state */
|
|
memcpy(p1st->st_iv, old_iv, p1st->st_iv_len);
|
|
}
|
|
}
|
|
|
|
void accept_delete(struct state *st, struct msg_digest *md,
|
|
struct payload_digest *p)
|
|
{
|
|
struct isakmp_delete *d = &(p->payload.delete);
|
|
size_t sizespi;
|
|
int i;
|
|
|
|
if (!md->encrypted)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: not encrypted");
|
|
return;
|
|
}
|
|
|
|
if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state))
|
|
{
|
|
/* can't happen (if msg is encrypt), but just to be sure */
|
|
loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: "
|
|
"ISAKMP SA not established");
|
|
return;
|
|
}
|
|
|
|
if (d->isad_nospi == 0)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: no SPI");
|
|
return;
|
|
}
|
|
|
|
switch (d->isad_protoid)
|
|
{
|
|
case PROTO_ISAKMP:
|
|
sizespi = 2 * COOKIE_SIZE;
|
|
break;
|
|
case PROTO_IPSEC_AH:
|
|
case PROTO_IPSEC_ESP:
|
|
sizespi = sizeof(ipsec_spi_t);
|
|
break;
|
|
case PROTO_IPCOMP:
|
|
/* nothing interesting to delete */
|
|
return;
|
|
default:
|
|
loglog(RC_LOG_SERIOUS
|
|
, "ignoring Delete SA payload: unknown Protocol ID (%s)"
|
|
, enum_show(&protocol_names, d->isad_protoid));
|
|
return;
|
|
}
|
|
|
|
if (d->isad_spisize != sizespi)
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "ignoring Delete SA payload: bad SPI size (%d) for %s"
|
|
, d->isad_spisize, enum_show(&protocol_names, d->isad_protoid));
|
|
return;
|
|
}
|
|
|
|
if (pbs_left(&p->pbs) != d->isad_nospi * sizespi)
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "ignoring Delete SA payload: invalid payload size");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < d->isad_nospi; i++)
|
|
{
|
|
u_char *spi = p->pbs.cur + (i * sizespi);
|
|
|
|
if (d->isad_protoid == PROTO_ISAKMP)
|
|
{
|
|
/**
|
|
* ISAKMP
|
|
*/
|
|
struct state *dst = find_state(spi /*iCookie*/
|
|
, spi+COOKIE_SIZE /*rCookie*/
|
|
, &st->st_connection->spd.that.host_addr
|
|
, MAINMODE_MSGID);
|
|
|
|
if (dst == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: "
|
|
"ISAKMP SA not found (maybe expired)");
|
|
}
|
|
else if (!same_peer_ids(st->st_connection, dst->st_connection, NULL))
|
|
{
|
|
/* we've not authenticated the relevant identities */
|
|
loglog(RC_LOG_SERIOUS, "ignoring Delete SA payload: "
|
|
"ISAKMP SA used to convey Delete has different IDs from ISAKMP SA it deletes");
|
|
}
|
|
else
|
|
{
|
|
connection_t *oldc;
|
|
|
|
oldc = cur_connection;
|
|
set_cur_connection(dst->st_connection);
|
|
|
|
if (nat_traversal_enabled)
|
|
{
|
|
nat_traversal_change_port_lookup(md, dst);
|
|
}
|
|
loglog(RC_LOG_SERIOUS, "received Delete SA payload: "
|
|
"deleting ISAKMP State #%lu", dst->st_serialno);
|
|
delete_state(dst);
|
|
set_cur_connection(oldc);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/**
|
|
* IPSEC (ESP/AH)
|
|
*/
|
|
bool bogus;
|
|
struct state *dst = find_phase2_state_to_delete(st
|
|
, d->isad_protoid
|
|
, *(ipsec_spi_t *)spi /* network order */
|
|
, &bogus);
|
|
|
|
if (dst == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "ignoring Delete SA payload: %s SA(0x%08lx) not found (%s)"
|
|
, enum_show(&protocol_names, d->isad_protoid)
|
|
, (unsigned long)ntohl((unsigned long)*(ipsec_spi_t *)spi)
|
|
, bogus ? "our SPI - bogus implementation" : "maybe expired");
|
|
}
|
|
else
|
|
{
|
|
connection_t *rc = dst->st_connection;
|
|
connection_t *oldc;
|
|
|
|
oldc = cur_connection;
|
|
set_cur_connection(rc);
|
|
|
|
if (nat_traversal_enabled)
|
|
{
|
|
nat_traversal_change_port_lookup(md, dst);
|
|
}
|
|
if (rc->newest_ipsec_sa == dst->st_serialno
|
|
&& (rc->policy & POLICY_UP))
|
|
{
|
|
/* Last IPSec SA for a permanent connection that we
|
|
* have initiated. Replace it in a few seconds.
|
|
*
|
|
* Useful if the other peer is rebooting.
|
|
*/
|
|
#define DELETE_SA_DELAY EVENT_RETRANSMIT_DELAY_0
|
|
if (dst->st_event != NULL
|
|
&& dst->st_event->ev_type == EVENT_SA_REPLACE
|
|
&& dst->st_event->ev_time <= DELETE_SA_DELAY + now())
|
|
{
|
|
/* Patch from Angus Lees to ignore retransmited
|
|
* Delete SA.
|
|
*/
|
|
loglog(RC_LOG_SERIOUS, "received Delete SA payload: "
|
|
"already replacing IPSEC State #%lu in %d seconds"
|
|
, dst->st_serialno, (int)(dst->st_event->ev_time - now()));
|
|
}
|
|
else
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "received Delete SA payload: "
|
|
"replace IPSEC State #%lu in %d seconds"
|
|
, dst->st_serialno, DELETE_SA_DELAY);
|
|
dst->st_margin = DELETE_SA_DELAY;
|
|
delete_event(dst);
|
|
event_schedule(EVENT_SA_REPLACE, DELETE_SA_DELAY, dst);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "received Delete SA(0x%08lx) payload: "
|
|
"deleting IPSEC State #%lu"
|
|
, (unsigned long)ntohl((unsigned long)*(ipsec_spi_t *)spi)
|
|
, dst->st_serialno);
|
|
delete_state(dst);
|
|
}
|
|
|
|
/* reset connection */
|
|
set_cur_connection(oldc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The whole message must be a multiple of 4 octets.
|
|
* I'm not sure where this is spelled out, but look at
|
|
* rfc2408 3.6 Transform Payload.
|
|
* Note: it talks about 4 BYTE boundaries!
|
|
*/
|
|
void close_message(pb_stream *pbs)
|
|
{
|
|
size_t padding = pad_up(pbs_offset(pbs), 4);
|
|
|
|
if (padding != 0)
|
|
{
|
|
(void) out_zero(padding, pbs, "message padding");
|
|
}
|
|
close_output_pbs(pbs);
|
|
}
|
|
|
|
/* Initiate an Oakley Main Mode exchange.
|
|
* --> HDR;SA
|
|
* Note: this is not called from demux.c
|
|
*/
|
|
static stf_status
|
|
main_outI1(int whack_sock, connection_t *c, struct state *predecessor
|
|
, lset_t policy, unsigned long try)
|
|
{
|
|
struct state *st = new_state();
|
|
pb_stream reply; /* not actually a reply, but you know what I mean */
|
|
pb_stream rbody;
|
|
int vids_to_send = 0;
|
|
|
|
/* set up new state */
|
|
st->st_connection = c;
|
|
set_cur_state(st); /* we must reset before exit */
|
|
st->st_policy = policy & ~POLICY_IPSEC_MASK;
|
|
st->st_whack_sock = whack_sock;
|
|
st->st_try = try;
|
|
st->st_state = STATE_MAIN_I1;
|
|
|
|
/* determine how many Vendor ID payloads we will be sending */
|
|
if (SEND_PLUTO_VID)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
if (SEND_CISCO_UNITY_VID)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
if (c->spd.this.cert &&
|
|
c->spd.this.cert->cert->get_type(c->spd.this.cert->cert) == CERT_GPG)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
if (SEND_XAUTH_VID)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
|
|
/* always send DPD Vendor ID */
|
|
vids_to_send++;
|
|
|
|
if (nat_traversal_enabled)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
|
|
get_cookie(TRUE, st->st_icookie, COOKIE_SIZE, &c->spd.that.host_addr);
|
|
|
|
insert_state(st); /* needs cookies, connection, and msgid (0) */
|
|
|
|
if (HAS_IPSEC_POLICY(policy))
|
|
{
|
|
add_pending(dup_any(whack_sock), st, c, policy, 1
|
|
, predecessor == NULL? SOS_NOBODY : predecessor->st_serialno);
|
|
}
|
|
if (predecessor == NULL)
|
|
{
|
|
plog("initiating Main Mode");
|
|
}
|
|
else
|
|
{
|
|
plog("initiating Main Mode to replace #%lu", predecessor->st_serialno);
|
|
}
|
|
|
|
/* set up reply */
|
|
init_pbs(&reply, reply_buffer, sizeof(reply_buffer), "reply packet");
|
|
|
|
/* HDR out */
|
|
{
|
|
struct isakmp_hdr hdr;
|
|
|
|
zero(&hdr); /* default to 0 */
|
|
hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION;
|
|
hdr.isa_np = ISAKMP_NEXT_SA;
|
|
hdr.isa_xchg = ISAKMP_XCHG_IDPROT;
|
|
memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE);
|
|
/* R-cookie, flags and MessageID are left zero */
|
|
|
|
if (!out_struct(&hdr, &isakmp_hdr_desc, &reply, &rbody))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* SA out */
|
|
{
|
|
u_char *sa_start = rbody.cur;
|
|
|
|
if (!out_sa(&rbody, &oakley_sadb, st, TRUE
|
|
, vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* save initiator SA for later HASH */
|
|
passert(st->st_p1isa.ptr == NULL); /* no leak! (MUST be first time) */
|
|
st->st_p1isa = chunk_create(sa_start, rbody.cur - sa_start);
|
|
st->st_p1isa = chunk_clone(st->st_p1isa);
|
|
}
|
|
|
|
/* if enabled send Pluto Vendor ID */
|
|
if (SEND_PLUTO_VID)
|
|
{
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &rbody, VID_STRONGSWAN))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* if enabled send Cisco Unity Vendor ID */
|
|
if (SEND_CISCO_UNITY_VID)
|
|
{
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &rbody, VID_CISCO_UNITY))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
/* if we have an OpenPGP certificate we assume an
|
|
* OpenPGP peer and have to send the Vendor ID
|
|
*/
|
|
if (c->spd.this.cert &&
|
|
c->spd.this.cert->cert->get_type(c->spd.this.cert->cert) == CERT_GPG)
|
|
{
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &rbody, VID_OPENPGP))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Announce our ability to do eXtended AUTHentication to the peer */
|
|
if (SEND_XAUTH_VID)
|
|
{
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &rbody, VID_MISC_XAUTH))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Announce our ability to do Dead Peer Detection to the peer */
|
|
{
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &rbody, VID_MISC_DPD))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
if (nat_traversal_enabled)
|
|
{
|
|
/* Add supported NAT-Traversal VID */
|
|
if (!nat_traversal_add_vid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &rbody))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
close_message(&rbody);
|
|
close_output_pbs(&reply);
|
|
st->st_tpacket = chunk_create(reply.start, pbs_offset(&reply));
|
|
st->st_tpacket = chunk_clone(st->st_tpacket);
|
|
|
|
/* Transmit */
|
|
|
|
send_packet(st, "main_outI1");
|
|
|
|
/* Set up a retransmission event, half a minute henceforth */
|
|
delete_event(st);
|
|
event_schedule(EVENT_RETRANSMIT, EVENT_RETRANSMIT_DELAY_0, st);
|
|
|
|
if (predecessor != NULL)
|
|
{
|
|
update_pending(predecessor, st);
|
|
whack_log(RC_NEW_STATE + STATE_MAIN_I1
|
|
, "%s: initiate, replacing #%lu"
|
|
, enum_name(&state_names, st->st_state)
|
|
, predecessor->st_serialno);
|
|
}
|
|
else
|
|
{
|
|
whack_log(RC_NEW_STATE + STATE_MAIN_I1
|
|
, "%s: initiate", enum_name(&state_names, st->st_state));
|
|
}
|
|
reset_cur_state();
|
|
return STF_OK;
|
|
}
|
|
|
|
void ipsecdoi_initiate(int whack_sock, connection_t *c, lset_t policy,
|
|
unsigned long try, so_serial_t replacing)
|
|
{
|
|
/* If there's already an ISAKMP SA established, use that and
|
|
* go directly to Quick Mode. We are even willing to use one
|
|
* that is still being negotiated, but only if we are the Initiator
|
|
* (thus we can be sure that the IDs are not going to change;
|
|
* other issues around intent might matter).
|
|
* Note: there is no way to initiate with a Road Warrior.
|
|
*/
|
|
struct state *st = find_phase1_state(c
|
|
, ISAKMP_SA_ESTABLISHED_STATES | PHASE1_INITIATOR_STATES);
|
|
|
|
if (st == NULL)
|
|
{
|
|
(void) main_outI1(whack_sock, c, NULL, policy, try);
|
|
}
|
|
else if (HAS_IPSEC_POLICY(policy))
|
|
{
|
|
if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state))
|
|
{
|
|
/* leave our Phase 2 negotiation pending */
|
|
add_pending(whack_sock, st, c, policy, try, replacing);
|
|
}
|
|
else
|
|
{
|
|
/* ??? we assume that peer_nexthop_sin isn't important:
|
|
* we already have it from when we negotiated the ISAKMP SA!
|
|
* It isn't clear what to do with the error return.
|
|
*/
|
|
(void) quick_outI1(whack_sock, st, c, policy, try, replacing);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
close_any(whack_sock);
|
|
}
|
|
}
|
|
|
|
/* Replace SA with a fresh one that is similar
|
|
*
|
|
* Shares some logic with ipsecdoi_initiate, but not the same!
|
|
* - we must not reuse the ISAKMP SA if we are trying to replace it!
|
|
* - if trying to replace IPSEC SA, use ipsecdoi_initiate to build
|
|
* ISAKMP SA if needed.
|
|
* - duplicate whack fd, if live.
|
|
* Does not delete the old state -- someone else will do that.
|
|
*/
|
|
void ipsecdoi_replace(struct state *st, unsigned long try)
|
|
{
|
|
int whack_sock = dup_any(st->st_whack_sock);
|
|
lset_t policy = st->st_policy;
|
|
|
|
if (IS_PHASE1(st->st_state))
|
|
{
|
|
passert(!HAS_IPSEC_POLICY(policy));
|
|
(void) main_outI1(whack_sock, st->st_connection, st, policy, try);
|
|
}
|
|
else
|
|
{
|
|
/* Add features of actual old state to policy. This ensures
|
|
* that rekeying doesn't downgrade security. I admit that
|
|
* this doesn't capture everything.
|
|
*/
|
|
if (st->st_pfs_group != NULL)
|
|
policy |= POLICY_PFS;
|
|
if (st->st_ah.present)
|
|
{
|
|
policy |= POLICY_AUTHENTICATE;
|
|
if (st->st_ah.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL)
|
|
policy |= POLICY_TUNNEL;
|
|
}
|
|
if (st->st_esp.present && st->st_esp.attrs.transid != ESP_NULL)
|
|
{
|
|
policy |= POLICY_ENCRYPT;
|
|
if (st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL)
|
|
policy |= POLICY_TUNNEL;
|
|
}
|
|
if (st->st_ipcomp.present)
|
|
{
|
|
policy |= POLICY_COMPRESS;
|
|
if (st->st_ipcomp.attrs.encapsulation == ENCAPSULATION_MODE_TUNNEL)
|
|
policy |= POLICY_TUNNEL;
|
|
}
|
|
passert(HAS_IPSEC_POLICY(policy));
|
|
ipsecdoi_initiate(whack_sock, st->st_connection, policy, try
|
|
, st->st_serialno);
|
|
}
|
|
}
|
|
|
|
/* SKEYID for preshared keys.
|
|
* See draft-ietf-ipsec-ike-01.txt 4.1
|
|
*/
|
|
static bool skeyid_preshared(struct state *st)
|
|
{
|
|
const chunk_t *pss = get_preshared_secret(st->st_connection);
|
|
|
|
if (pss == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "preshared secret disappeared!");
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
|
|
prf_alg = oakley_to_prf(st->st_oakley.hash);
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
if (prf == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%N not available to compute skeyid",
|
|
pseudo_random_function_names, prf_alg);
|
|
return FALSE;
|
|
}
|
|
free(st->st_skeyid.ptr);
|
|
prf->set_key(prf, *pss);
|
|
prf->allocate_bytes(prf, st->st_ni, NULL);
|
|
prf->allocate_bytes(prf, st->st_nr, &st->st_skeyid);
|
|
prf->destroy(prf);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static bool skeyid_digisig(struct state *st)
|
|
{
|
|
chunk_t nir;
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
|
|
prf_alg = oakley_to_prf(st->st_oakley.hash);
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
if (prf == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%N not available to compute skeyid",
|
|
pseudo_random_function_names, prf_alg);
|
|
return FALSE;
|
|
}
|
|
free(st->st_skeyid.ptr);
|
|
nir = chunk_cat("cc", st->st_ni, st->st_nr);
|
|
prf->set_key(prf, nir);
|
|
prf->allocate_bytes(prf, st->st_shared, &st->st_skeyid);
|
|
prf->destroy(prf);
|
|
free(nir.ptr);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Generate the SKEYID_* and new IV
|
|
* See draft-ietf-ipsec-ike-01.txt 4.1
|
|
*/
|
|
static bool generate_skeyids_iv(struct state *st)
|
|
{
|
|
/* Generate the SKEYID */
|
|
switch (st->st_oakley.auth)
|
|
{
|
|
case OAKLEY_PRESHARED_KEY:
|
|
case XAUTHInitPreShared:
|
|
case XAUTHRespPreShared:
|
|
if (!skeyid_preshared(st))
|
|
{
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
case OAKLEY_RSA_SIG:
|
|
case OAKLEY_ECDSA_256:
|
|
case OAKLEY_ECDSA_384:
|
|
case OAKLEY_ECDSA_521:
|
|
case XAUTHInitRSA:
|
|
case XAUTHRespRSA:
|
|
if (!skeyid_digisig(st))
|
|
{
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
case OAKLEY_DSS_SIG:
|
|
/* XXX */
|
|
|
|
case OAKLEY_RSA_ENC:
|
|
case OAKLEY_RSA_ENC_REV:
|
|
case OAKLEY_ELGAMAL_ENC:
|
|
case OAKLEY_ELGAMAL_ENC_REV:
|
|
/* XXX */
|
|
|
|
default:
|
|
bad_case(st->st_oakley.auth);
|
|
}
|
|
|
|
/* generate SKEYID_* from SKEYID */
|
|
{
|
|
chunk_t seed_skeyid_d = chunk_from_chars(0x00);
|
|
chunk_t seed_skeyid_a = chunk_from_chars(0x01);
|
|
chunk_t seed_skeyid_e = chunk_from_chars(0x02);
|
|
chunk_t icookie = { st->st_icookie, COOKIE_SIZE };
|
|
chunk_t rcookie = { st->st_rcookie, COOKIE_SIZE };
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
|
|
prf_alg = oakley_to_prf(st->st_oakley.hash);
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf->set_key(prf, st->st_skeyid);
|
|
|
|
/* SKEYID_D */
|
|
free(st->st_skeyid_d.ptr);
|
|
prf->allocate_bytes(prf, st->st_shared, NULL);
|
|
prf->allocate_bytes(prf, icookie, NULL);
|
|
prf->allocate_bytes(prf, rcookie, NULL);
|
|
prf->allocate_bytes(prf, seed_skeyid_d, &st->st_skeyid_d);
|
|
|
|
/* SKEYID_A */
|
|
free(st->st_skeyid_a.ptr);
|
|
prf->allocate_bytes(prf, st->st_skeyid_d, NULL);
|
|
prf->allocate_bytes(prf, st->st_shared, NULL);
|
|
prf->allocate_bytes(prf, icookie, NULL);
|
|
prf->allocate_bytes(prf, rcookie, NULL);
|
|
prf->allocate_bytes(prf, seed_skeyid_a, &st->st_skeyid_a);
|
|
|
|
/* SKEYID_E */
|
|
free(st->st_skeyid_e.ptr);
|
|
prf->allocate_bytes(prf, st->st_skeyid_a, NULL);
|
|
prf->allocate_bytes(prf, st->st_shared, NULL);
|
|
prf->allocate_bytes(prf, icookie, NULL);
|
|
prf->allocate_bytes(prf, rcookie, NULL);
|
|
prf->allocate_bytes(prf, seed_skeyid_e, &st->st_skeyid_e);
|
|
|
|
prf->destroy(prf);
|
|
}
|
|
|
|
/* generate IV */
|
|
{
|
|
hash_algorithm_t hash_alg;
|
|
hasher_t *hasher;
|
|
|
|
hash_alg = oakley_to_hash_algorithm(st->st_oakley.hash);
|
|
hasher = lib->crypto->create_hasher(lib->crypto, hash_alg);
|
|
st->st_new_iv_len = hasher->get_hash_size(hasher);
|
|
passert(st->st_new_iv_len <= sizeof(st->st_new_iv));
|
|
|
|
DBG(DBG_CRYPT,
|
|
DBG_dump_chunk("DH_i:", st->st_gi);
|
|
DBG_dump_chunk("DH_r:", st->st_gr);
|
|
);
|
|
|
|
hasher->get_hash(hasher, st->st_gi, NULL);
|
|
hasher->get_hash(hasher, st->st_gr, st->st_new_iv);
|
|
hasher->destroy(hasher);
|
|
}
|
|
|
|
/* Oakley Keying Material
|
|
* Derived from Skeyid_e: if it is not big enough, generate more
|
|
* using the PRF.
|
|
* See RFC 2409 "IKE" Appendix B
|
|
*/
|
|
{
|
|
size_t keysize = st->st_oakley.enckeylen/BITS_PER_BYTE;
|
|
|
|
/* free any existing key */
|
|
free(st->st_enc_key.ptr);
|
|
|
|
if (keysize > st->st_skeyid_e.len)
|
|
{
|
|
u_char keytemp[MAX_OAKLEY_KEY_LEN + MAX_DIGEST_LEN];
|
|
chunk_t seed = chunk_from_chars(0x00);
|
|
size_t prf_block_size, i;
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
|
|
prf_alg = oakley_to_prf(st->st_oakley.hash);
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf->set_key(prf, st->st_skeyid_e);
|
|
prf_block_size = prf->get_block_size(prf);
|
|
|
|
for (i = 0;;)
|
|
{
|
|
prf->get_bytes(prf, seed, &keytemp[i]);
|
|
i += prf_block_size;
|
|
if (i >= keysize)
|
|
{
|
|
break;
|
|
}
|
|
seed = chunk_create(&keytemp[i-prf_block_size], prf_block_size);
|
|
}
|
|
prf->destroy(prf);
|
|
st->st_enc_key = chunk_create(keytemp, keysize);
|
|
}
|
|
else
|
|
{
|
|
st->st_enc_key = chunk_create(st->st_skeyid_e.ptr, keysize);
|
|
}
|
|
st->st_enc_key = chunk_clone(st->st_enc_key);
|
|
}
|
|
|
|
DBG(DBG_CRYPT,
|
|
DBG_dump_chunk("Skeyid: ", st->st_skeyid);
|
|
DBG_dump_chunk("Skeyid_d:", st->st_skeyid_d);
|
|
DBG_dump_chunk("Skeyid_a:", st->st_skeyid_a);
|
|
DBG_dump_chunk("Skeyid_e:", st->st_skeyid_e);
|
|
DBG_dump_chunk("enc key:", st->st_enc_key);
|
|
DBG_dump("IV:", st->st_new_iv, st->st_new_iv_len));
|
|
return TRUE;
|
|
}
|
|
|
|
/* Generate HASH_I or HASH_R for ISAKMP Phase I.
|
|
* This will *not* generate other hash payloads (eg. Phase II or Quick Mode,
|
|
* New Group Mode, or ISAKMP Informational Exchanges).
|
|
* If the hashi argument is TRUE, generate HASH_I; if FALSE generate HASH_R.
|
|
* If hashus argument is TRUE, we're generating a hash for our end.
|
|
* See RFC2409 IKE 5.
|
|
*/
|
|
static void main_mode_hash(struct state *st, chunk_t *hash, bool hashi,
|
|
const pb_stream *idpl)
|
|
{
|
|
chunk_t icookie = { st->st_icookie, COOKIE_SIZE };
|
|
chunk_t rcookie = { st->st_rcookie, COOKIE_SIZE };
|
|
chunk_t sa_body = { st->st_p1isa.ptr + sizeof(struct isakmp_generic),
|
|
st->st_p1isa.len - sizeof(struct isakmp_generic) };
|
|
chunk_t id_body = { idpl->start + sizeof(struct isakmp_generic),
|
|
pbs_offset(idpl) - sizeof(struct isakmp_generic) };
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
|
|
switch (st->st_oakley.auth)
|
|
{
|
|
case OAKLEY_ECDSA_256:
|
|
prf_alg = PRF_HMAC_SHA2_256;
|
|
break;
|
|
case OAKLEY_ECDSA_384:
|
|
prf_alg = PRF_HMAC_SHA2_384;
|
|
break;
|
|
case OAKLEY_ECDSA_521:
|
|
prf_alg = PRF_HMAC_SHA2_512;
|
|
break;
|
|
default:
|
|
prf_alg = oakley_to_prf(st->st_oakley.hash);
|
|
}
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf->set_key(prf, st->st_skeyid);
|
|
|
|
if (hashi)
|
|
{
|
|
prf->get_bytes(prf, st->st_gi, NULL);
|
|
prf->get_bytes(prf, st->st_gr, NULL);
|
|
prf->get_bytes(prf, icookie, NULL);
|
|
prf->get_bytes(prf, rcookie, NULL);
|
|
}
|
|
else
|
|
{
|
|
prf->get_bytes(prf, st->st_gr, NULL);
|
|
prf->get_bytes(prf, st->st_gi, NULL);
|
|
prf->get_bytes(prf, rcookie, NULL);
|
|
prf->get_bytes(prf, icookie, NULL);
|
|
}
|
|
|
|
DBG(DBG_CRYPT,
|
|
DBG_log("hashing %u bytes of SA", sa_body.len)
|
|
)
|
|
prf->get_bytes(prf, sa_body, NULL);
|
|
|
|
/* Hash identification payload, without generic payload header.
|
|
* We used to reconstruct ID Payload for this purpose, but now
|
|
* we use the bytes as they appear on the wire to avoid
|
|
* "spelling problems".
|
|
*/
|
|
prf->get_bytes(prf, id_body, hash->ptr);
|
|
hash->len = prf->get_block_size(prf);
|
|
prf->destroy(prf);
|
|
}
|
|
|
|
/* Create a public key signature of a hash.
|
|
* Poorly specified in draft-ietf-ipsec-ike-01.txt 6.1.1.2.
|
|
* Use PKCS#1 version 1.5 encryption of hash (called
|
|
* RSAES-PKCS1-V1_5) in PKCS#2.
|
|
*/
|
|
static size_t sign_hash(signature_scheme_t scheme, connection_t *c,
|
|
u_char sig_val[RSA_MAX_OCTETS], chunk_t hash)
|
|
{
|
|
size_t sz = 0;
|
|
smartcard_t *sc = c->spd.this.sc;
|
|
|
|
if (sc == NULL) /* no smartcard */
|
|
{
|
|
chunk_t sig;
|
|
private_key_t *private = get_private_key(c);
|
|
|
|
if (private == NULL)
|
|
{
|
|
return 0; /* failure: no key to use */
|
|
}
|
|
if (!private->sign(private, scheme, hash, &sig))
|
|
{
|
|
return 0;
|
|
}
|
|
memcpy(sig_val, sig.ptr, sig.len);
|
|
sz = sig.len;
|
|
free(sig.ptr);
|
|
}
|
|
else if (sc->valid) /* if valid pin then sign hash on the smartcard */
|
|
{
|
|
lock_certs_and_keys("sign_hash");
|
|
if (!scx_establish_context(sc) || !scx_login(sc))
|
|
{
|
|
scx_release_context(sc);
|
|
unlock_certs_and_keys("sign_hash");
|
|
return 0;
|
|
}
|
|
|
|
sz = scx_get_keylength(sc);
|
|
if (sz == 0)
|
|
{
|
|
plog("failed to get keylength from smartcard");
|
|
scx_release_context(sc);
|
|
unlock_certs_and_keys("sign_hash");
|
|
return 0;
|
|
}
|
|
|
|
DBG(DBG_CONTROL | DBG_CRYPT,
|
|
DBG_log("signing hash with private key from smartcard (slot: %d, id: %s)"
|
|
, (int)sc->slot, sc->id)
|
|
)
|
|
sz = scx_sign_hash(sc, hash.ptr, hash.len, sig_val, sz) ? sz : 0;
|
|
if (!pkcs11_keep_state)
|
|
{
|
|
scx_release_context(sc);
|
|
}
|
|
unlock_certs_and_keys("sign_hash");
|
|
}
|
|
return sz;
|
|
}
|
|
|
|
/* Check signature against all public keys we can find.
|
|
* If we need keys from DNS KEY records, and they haven't been fetched,
|
|
* return STF_SUSPEND to ask for asynch DNS lookup.
|
|
*
|
|
* Note: parameter keys_from_dns contains results of DNS lookup for key
|
|
* or is NULL indicating lookup not yet tried.
|
|
*
|
|
* take_a_crack is a helper function. Mostly forensic.
|
|
* If only we had coroutines.
|
|
*/
|
|
struct tac_state {
|
|
struct state *st;
|
|
chunk_t hash;
|
|
chunk_t sig;
|
|
int tried_cnt; /* number of keys tried */
|
|
};
|
|
|
|
static bool take_a_crack(struct tac_state *s, pubkey_t *kr)
|
|
{
|
|
public_key_t *pub_key = kr->public_key;
|
|
chunk_t keyid = chunk_empty;
|
|
signature_scheme_t scheme;
|
|
|
|
s->tried_cnt++;
|
|
scheme = oakley_to_signature_scheme(s->st->st_oakley.auth);
|
|
pub_key->get_fingerprint(pub_key, KEY_ID_PUBKEY_INFO_SHA1, &keyid);
|
|
|
|
if (pub_key->verify(pub_key, scheme, s->hash, s->sig))
|
|
{
|
|
DBG(DBG_CRYPT | DBG_CONTROL,
|
|
DBG_log("%s check passed with keyid %#B",
|
|
enum_show(&oakley_auth_names, s->st->st_oakley.auth), &keyid)
|
|
)
|
|
unreference_key(&s->st->st_peer_pubkey);
|
|
s->st->st_peer_pubkey = reference_key(kr);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
DBG(DBG_CRYPT,
|
|
DBG_log("%s check failed with keyid %#B",
|
|
enum_show(&oakley_auth_names, s->st->st_oakley.auth), &keyid)
|
|
)
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static stf_status check_signature(key_type_t key_type, identification_t* peer,
|
|
struct state *st, chunk_t hash,
|
|
const pb_stream *sig_pbs,
|
|
#ifdef USE_KEYRR
|
|
const pubkey_list_t *keys_from_dns,
|
|
#endif /* USE_KEYRR */
|
|
const struct gw_info *gateways_from_dns)
|
|
{
|
|
const connection_t *c = st->st_connection;
|
|
struct tac_state s;
|
|
|
|
s.st = st;
|
|
s.hash = hash;
|
|
s.sig = chunk_create(sig_pbs->cur, pbs_left(sig_pbs));
|
|
s.tried_cnt = 0;
|
|
|
|
/* try all gateway records hung off c */
|
|
if (c->policy & POLICY_OPPO)
|
|
{
|
|
struct gw_info *gw;
|
|
|
|
for (gw = c->gw_info; gw != NULL; gw = gw->next)
|
|
{
|
|
/* only consider entries that have a key and are for our peer */
|
|
if (gw->gw_key_present &&
|
|
gw->gw_id->equals(gw->gw_id, c->spd.that.id) &&
|
|
take_a_crack(&s, gw->key))
|
|
{
|
|
return STF_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* try all appropriate Public keys */
|
|
{
|
|
pubkey_list_t *p, **pp;
|
|
|
|
pp = &pubkeys;
|
|
|
|
for (p = pubkeys; p != NULL; p = *pp)
|
|
{
|
|
pubkey_t *key = p->key;
|
|
key_type_t type = key->public_key->get_type(key->public_key);
|
|
|
|
if (type == key_type && peer->equals(peer, key->id))
|
|
{
|
|
time_t now = time(NULL);
|
|
|
|
/* check if found public key has expired */
|
|
if (key->until_time != UNDEFINED_TIME && key->until_time < now)
|
|
{
|
|
loglog(RC_LOG_SERIOUS,
|
|
"cached public key has expired and has been deleted");
|
|
*pp = free_public_keyentry(p);
|
|
continue; /* continue with next public key */
|
|
}
|
|
if (take_a_crack(&s, key))
|
|
{
|
|
return STF_OK;
|
|
}
|
|
}
|
|
pp = &p->next;
|
|
}
|
|
}
|
|
|
|
/* if no key was found and that side of connection is
|
|
* key_from_DNS_on_demand then go search DNS for keys for peer.
|
|
*/
|
|
if (s.tried_cnt == 0 && c->spd.that.key_from_DNS_on_demand)
|
|
{
|
|
if (gateways_from_dns != NULL)
|
|
{
|
|
/* TXT keys */
|
|
const struct gw_info *gwp;
|
|
|
|
for (gwp = gateways_from_dns; gwp != NULL; gwp = gwp->next)
|
|
{
|
|
if (gwp->gw_key_present && take_a_crack(&s, gwp->key))
|
|
{
|
|
return STF_OK;
|
|
}
|
|
}
|
|
}
|
|
#ifdef USE_KEYRR
|
|
else if (keys_from_dns != NULL)
|
|
{
|
|
/* KEY keys */
|
|
const pubkey_list_t *kr;
|
|
|
|
for (kr = keys_from_dns; kr != NULL; kr = kr->next)
|
|
{
|
|
if (kr->key->alg == PUBKEY_ALG_RSA && take_a_crack(&s, kr->key))
|
|
{
|
|
return STF_OK;
|
|
}
|
|
}
|
|
}
|
|
#endif /* USE_KEYRR */
|
|
else
|
|
{
|
|
/* nothing yet: ask for asynch DNS lookup */
|
|
return STF_SUSPEND;
|
|
}
|
|
}
|
|
|
|
/* no acceptable key was found: diagnose */
|
|
{
|
|
if (s.tried_cnt == 0)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "no public key known for '%Y'", peer);
|
|
}
|
|
else if (s.tried_cnt == 1)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "signature check for '%Y' failed: "
|
|
" wrong key?; tried %d", peer, s.tried_cnt);
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("public key for '%Y' failed: "
|
|
"decrypted SIG payload into a malformed ECB", peer)
|
|
)
|
|
}
|
|
else
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "signature check for '%Y' failed: "
|
|
"tried %d keys but none worked.", peer, s.tried_cnt);
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("all %d public keys for '%Y' failed: "
|
|
"best decrypted SIG payload into a malformed ECB",
|
|
s.tried_cnt, peer)
|
|
)
|
|
}
|
|
return STF_FAIL + ISAKMP_INVALID_KEY_INFORMATION;
|
|
}
|
|
}
|
|
|
|
static notification_t accept_nonce(struct msg_digest *md, chunk_t *dest,
|
|
const char *name)
|
|
{
|
|
pb_stream *nonce_pbs = &md->chain[ISAKMP_NEXT_NONCE]->pbs;
|
|
size_t len = pbs_left(nonce_pbs);
|
|
|
|
if (len < MINIMUM_NONCE_SIZE || MAXIMUM_NONCE_SIZE < len)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s length not between %d and %d"
|
|
, name , MINIMUM_NONCE_SIZE, MAXIMUM_NONCE_SIZE);
|
|
return ISAKMP_PAYLOAD_MALFORMED; /* ??? */
|
|
}
|
|
free(dest->ptr);
|
|
*dest = chunk_create(nonce_pbs->cur, len);
|
|
*dest = chunk_clone(*dest);
|
|
return ISAKMP_NOTHING_WRONG;
|
|
}
|
|
|
|
/* encrypt message, sans fixed part of header
|
|
* IV is fetched from st->st_new_iv and stored into st->st_iv.
|
|
* The theory is that there will be no "backing out", so we commit to IV.
|
|
* We also close the pbs.
|
|
*/
|
|
bool encrypt_message(pb_stream *pbs, struct state *st)
|
|
{
|
|
u_int8_t *enc_start = pbs->start + sizeof(struct isakmp_hdr);
|
|
size_t enc_len = pbs_offset(pbs) - sizeof(struct isakmp_hdr);
|
|
chunk_t data, iv;
|
|
char *new_iv;
|
|
size_t crypter_block_size;
|
|
encryption_algorithm_t enc_alg;
|
|
crypter_t *crypter;
|
|
|
|
DBG_cond_dump(DBG_CRYPT | DBG_RAW, "encrypting:\n", enc_start, enc_len);
|
|
enc_alg = oakley_to_encryption_algorithm(st->st_oakley.encrypt);
|
|
crypter = lib->crypto->create_crypter(lib->crypto, enc_alg, st->st_enc_key.len);
|
|
crypter_block_size = crypter->get_block_size(crypter);
|
|
|
|
/* Pad up to multiple of encryption blocksize.
|
|
* See the description associated with the definition of
|
|
* struct isakmp_hdr in packet.h.
|
|
*/
|
|
{
|
|
size_t padding = pad_up(enc_len, crypter_block_size);
|
|
|
|
if (padding != 0)
|
|
{
|
|
if (!out_zero(padding, pbs, "encryption padding"))
|
|
return FALSE;
|
|
enc_len += padding;
|
|
}
|
|
}
|
|
|
|
DBG(DBG_CRYPT, DBG_log("encrypting using %s", enum_show(&oakley_enc_names, st->st_oakley.encrypt)));
|
|
data = chunk_create(enc_start, enc_len);
|
|
|
|
/* form iv by truncation */
|
|
st->st_new_iv_len = crypter_block_size;
|
|
iv = chunk_create(st->st_new_iv, st->st_new_iv_len);
|
|
|
|
crypter->set_key(crypter, st->st_enc_key);
|
|
crypter->encrypt(crypter, data, iv, NULL);
|
|
crypter->destroy(crypter);
|
|
|
|
new_iv = data.ptr + data.len - crypter_block_size;
|
|
memcpy(st->st_new_iv, new_iv, crypter_block_size);
|
|
update_iv(st);
|
|
DBG_cond_dump(DBG_CRYPT, "next IV:", st->st_iv, st->st_iv_len);
|
|
close_message(pbs);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Compute HASH(1), HASH(2) of Quick Mode.
|
|
* HASH(1) is part of Quick I1 message.
|
|
* HASH(2) is part of Quick R1 message.
|
|
* Used by: quick_outI1, quick_inI1_outR1 (twice), quick_inR1_outI2
|
|
* (see RFC 2409 "IKE" 5.5, pg. 18 or draft-ietf-ipsec-ike-01.txt 6.2 pg 25)
|
|
*/
|
|
static size_t quick_mode_hash12(u_char *dest, u_char *start, u_char *roof,
|
|
const struct state *st, const msgid_t *msgid,
|
|
bool hash2)
|
|
{
|
|
chunk_t msgid_chunk = chunk_from_thing(*msgid);
|
|
chunk_t msg_chunk = { start, roof - start };
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
size_t prf_block_size;
|
|
|
|
prf_alg = oakley_to_prf(st->st_oakley.hash);
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf->set_key(prf, st->st_skeyid_a);
|
|
prf->get_bytes(prf, msgid_chunk, NULL);
|
|
if (hash2)
|
|
{
|
|
prf->get_bytes(prf, st->st_ni, NULL); /* include Ni_b in the hash */
|
|
}
|
|
prf->get_bytes(prf, msg_chunk, dest);
|
|
prf_block_size = prf->get_block_size(prf);
|
|
prf->destroy(prf);
|
|
|
|
DBG(DBG_CRYPT,
|
|
DBG_log("HASH(%d) computed:", hash2 + 1);
|
|
DBG_dump("", dest, prf_block_size)
|
|
)
|
|
return prf_block_size;
|
|
}
|
|
|
|
/* Compute HASH(3) in Quick Mode (part of Quick I2 message).
|
|
* Used by: quick_inR1_outI2, quick_inI2
|
|
* See RFC2409 "The Internet Key Exchange (IKE)" 5.5.
|
|
* NOTE: this hash (unlike HASH(1) and HASH(2)) ONLY covers the
|
|
* Message ID and Nonces. This is a mistake.
|
|
*/
|
|
static size_t quick_mode_hash3(u_char *dest, struct state *st)
|
|
{
|
|
chunk_t seed_chunk = chunk_from_chars(0x00);
|
|
chunk_t msgid_chunk = chunk_from_thing(st->st_msgid);
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
size_t prf_block_size;
|
|
|
|
prf_alg = oakley_to_prf(st->st_oakley.hash);
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf->set_key(prf, st->st_skeyid_a);
|
|
prf->get_bytes(prf, seed_chunk, NULL );
|
|
prf->get_bytes(prf, msgid_chunk, NULL);
|
|
prf->get_bytes(prf, st->st_ni, NULL);
|
|
prf->get_bytes(prf, st->st_nr, dest);
|
|
prf_block_size = prf->get_block_size(prf);
|
|
prf->destroy(prf);
|
|
|
|
DBG_cond_dump(DBG_CRYPT, "HASH(3) computed:", dest, prf_block_size);
|
|
return prf_block_size;
|
|
}
|
|
|
|
/* Compute Phase 2 IV.
|
|
* Uses Phase 1 IV from st_iv; puts result in st_new_iv.
|
|
*/
|
|
void init_phase2_iv(struct state *st, const msgid_t *msgid)
|
|
{
|
|
chunk_t iv_chunk = { st->st_ph1_iv, st->st_ph1_iv_len };
|
|
chunk_t msgid_chunk = chunk_from_thing(*msgid);
|
|
hash_algorithm_t hash_alg;
|
|
hasher_t *hasher;
|
|
|
|
hash_alg = oakley_to_hash_algorithm(st->st_oakley.hash);
|
|
hasher = lib->crypto->create_hasher(lib->crypto, hash_alg);
|
|
|
|
DBG_cond_dump(DBG_CRYPT, "last Phase 1 IV:",
|
|
st->st_ph1_iv, st->st_ph1_iv_len);
|
|
|
|
st->st_new_iv_len = hasher->get_hash_size(hasher);
|
|
passert(st->st_new_iv_len <= sizeof(st->st_new_iv));
|
|
|
|
hasher->get_hash(hasher, iv_chunk, NULL);
|
|
hasher->get_hash(hasher, msgid_chunk, st->st_new_iv);
|
|
hasher->destroy(hasher);
|
|
|
|
DBG_cond_dump(DBG_CRYPT, "computed Phase 2 IV:",
|
|
st->st_new_iv, st->st_new_iv_len);
|
|
}
|
|
|
|
/* Initiate quick mode.
|
|
* --> HDR*, HASH(1), SA, Nr [, KE ] [, IDci, IDcr ]
|
|
* (see RFC 2409 "IKE" 5.5)
|
|
* Note: this is not called from demux.c
|
|
*/
|
|
|
|
static bool emit_subnet_id(ip_subnet *net, u_int8_t np, u_int8_t protoid,
|
|
u_int16_t port, pb_stream *outs)
|
|
{
|
|
struct isakmp_ipsec_id id;
|
|
pb_stream id_pbs;
|
|
ip_address ta;
|
|
const unsigned char *tbp;
|
|
size_t tal;
|
|
|
|
id.isaiid_np = np;
|
|
id.isaiid_idtype = subnetishost(net)
|
|
? aftoinfo(subnettypeof(net))->id_addr
|
|
: aftoinfo(subnettypeof(net))->id_subnet;
|
|
id.isaiid_protoid = protoid;
|
|
id.isaiid_port = port;
|
|
|
|
if (!out_struct(&id, &isakmp_ipsec_identification_desc, outs, &id_pbs))
|
|
{
|
|
return FALSE;
|
|
}
|
|
networkof(net, &ta);
|
|
tal = addrbytesptr(&ta, &tbp);
|
|
if (!out_raw(tbp, tal, &id_pbs, "client network"))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (!subnetishost(net))
|
|
{
|
|
maskof(net, &ta);
|
|
tal = addrbytesptr(&ta, &tbp);
|
|
if (!out_raw(tbp, tal, &id_pbs, "client mask"))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
close_output_pbs(&id_pbs);
|
|
return TRUE;
|
|
}
|
|
|
|
stf_status quick_outI1(int whack_sock, struct state *isakmp_sa,
|
|
connection_t *c, lset_t policy, unsigned long try,
|
|
so_serial_t replacing)
|
|
{
|
|
struct state *st = duplicate_state(isakmp_sa);
|
|
pb_stream reply; /* not really a reply */
|
|
pb_stream rbody;
|
|
u_char /* set by START_HASH_PAYLOAD: */
|
|
*r_hashval, /* where in reply to jam hash value */
|
|
*r_hash_start; /* start of what is to be hashed */
|
|
bool has_client = c->spd.this.has_client || c->spd.that.has_client ||
|
|
c->spd.this.protocol || c->spd.that.protocol ||
|
|
c->spd.this.port || c->spd.that.port;
|
|
|
|
bool send_natoa = FALSE;
|
|
u_int8_t np = ISAKMP_NEXT_NONE;
|
|
|
|
if (c->spd.this.modecfg && !c->spd.this.has_client &&
|
|
isanyaddr(&c->spd.this.host_srcip))
|
|
{
|
|
connection_t *ph1_c = isakmp_sa->st_connection;
|
|
|
|
if (ph1_c->spd.this.modecfg && !isanyaddr(&ph1_c->spd.this.host_srcip))
|
|
{
|
|
char srcip[ADDRTOT_BUF];
|
|
|
|
c->spd.this.host_srcip = ph1_c->spd.this.host_srcip;
|
|
c->spd.this.client = ph1_c->spd.this.client;
|
|
c->spd.this.has_client = TRUE;
|
|
addrtot(&c->spd.this.host_srcip, 0, srcip, sizeof(srcip));
|
|
plog("inheriting virtual IP source address %s from ModeCfg", srcip);
|
|
}
|
|
}
|
|
|
|
st->st_whack_sock = whack_sock;
|
|
st->st_connection = c;
|
|
set_cur_state(st); /* we must reset before exit */
|
|
st->st_policy = policy;
|
|
st->st_try = try;
|
|
|
|
st->st_myuserprotoid = c->spd.this.protocol;
|
|
st->st_peeruserprotoid = c->spd.that.protocol;
|
|
st->st_myuserport = c->spd.this.port;
|
|
st->st_peeruserport = c->spd.that.port;
|
|
|
|
st->st_msgid = generate_msgid(isakmp_sa);
|
|
st->st_state = STATE_QUICK_I1;
|
|
|
|
insert_state(st); /* needs cookies, connection, and msgid */
|
|
|
|
if (replacing == SOS_NOBODY)
|
|
{
|
|
plog("initiating Quick Mode %s {using isakmp#%lu}",
|
|
prettypolicy(policy), isakmp_sa->st_serialno);
|
|
}
|
|
else
|
|
{
|
|
plog("initiating Quick Mode %s to replace #%lu {using isakmp#%lu}",
|
|
prettypolicy(policy), replacing, isakmp_sa->st_serialno);
|
|
}
|
|
if (isakmp_sa->nat_traversal & NAT_T_DETECTED)
|
|
{
|
|
/* Duplicate nat_traversal status in new state */
|
|
st->nat_traversal = isakmp_sa->nat_traversal;
|
|
|
|
if (isakmp_sa->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME))
|
|
{
|
|
has_client = TRUE;
|
|
}
|
|
nat_traversal_change_port_lookup(NULL, st);
|
|
}
|
|
else
|
|
{
|
|
st->nat_traversal = 0;
|
|
}
|
|
|
|
/* are we going to send a NAT-OA payload? */
|
|
if ((st->nat_traversal & NAT_T_WITH_NATOA)
|
|
&& !(st->st_policy & POLICY_TUNNEL)
|
|
&& (st->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME)))
|
|
{
|
|
send_natoa = TRUE;
|
|
np = (st->nat_traversal & NAT_T_WITH_RFC_VALUES) ?
|
|
ISAKMP_NEXT_NATOA_RFC : ISAKMP_NEXT_NATOA_DRAFTS;
|
|
}
|
|
|
|
/* set up reply */
|
|
init_pbs(&reply, reply_buffer, sizeof(reply_buffer), "reply packet");
|
|
|
|
/* HDR* out */
|
|
{
|
|
struct isakmp_hdr hdr;
|
|
|
|
hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION;
|
|
hdr.isa_np = ISAKMP_NEXT_HASH;
|
|
hdr.isa_xchg = ISAKMP_XCHG_QUICK;
|
|
hdr.isa_msgid = st->st_msgid;
|
|
hdr.isa_flags = ISAKMP_FLAG_ENCRYPTION;
|
|
memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE);
|
|
memcpy(hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE);
|
|
if (!out_struct(&hdr, &isakmp_hdr_desc, &reply, &rbody))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* HASH(1) -- create and note space to be filled later */
|
|
START_HASH_PAYLOAD(rbody, ISAKMP_NEXT_SA);
|
|
|
|
/* SA out */
|
|
|
|
/*
|
|
* See if pfs_group has been specified for this conn,
|
|
* if not, fallback to old use-same-as-P1 behaviour
|
|
*/
|
|
#ifndef NO_IKE_ALG
|
|
if (st->st_connection)
|
|
{
|
|
st->st_pfs_group = ike_alg_pfsgroup(st->st_connection, policy);
|
|
}
|
|
if (!st->st_pfs_group)
|
|
#endif
|
|
/* If PFS specified, use the same group as during Phase 1:
|
|
* since no negotiation is possible, we pick one that is
|
|
* very likely supported.
|
|
*/
|
|
st->st_pfs_group = policy & POLICY_PFS? isakmp_sa->st_oakley.group : NULL;
|
|
|
|
/* Emit SA payload based on a subset of the policy bits.
|
|
* POLICY_COMPRESS is considered iff we can do IPcomp.
|
|
*/
|
|
{
|
|
lset_t pm = POLICY_ENCRYPT | POLICY_AUTHENTICATE;
|
|
|
|
if (can_do_IPcomp)
|
|
{
|
|
pm |= POLICY_COMPRESS;
|
|
}
|
|
if (!out_sa(&rbody,
|
|
&ipsec_sadb[(st->st_policy & pm) >> POLICY_IPSEC_SHIFT],
|
|
st, FALSE, ISAKMP_NEXT_NONCE))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Ni out */
|
|
if (!build_and_ship_nonce(&st->st_ni, &rbody
|
|
, policy & POLICY_PFS? ISAKMP_NEXT_KE : has_client? ISAKMP_NEXT_ID : np
|
|
, "Ni"))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* [ KE ] out (for PFS) */
|
|
|
|
if (st->st_pfs_group != NULL)
|
|
{
|
|
if (!build_and_ship_KE(st, &st->st_gi, st->st_pfs_group
|
|
, &rbody, has_client? ISAKMP_NEXT_ID : np))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* [ IDci, IDcr ] out */
|
|
if (has_client)
|
|
{
|
|
/* IDci (we are initiator), then IDcr (peer is responder) */
|
|
if (!emit_subnet_id(&c->spd.this.client
|
|
, ISAKMP_NEXT_ID, st->st_myuserprotoid, st->st_myuserport, &rbody)
|
|
|| !emit_subnet_id(&c->spd.that.client
|
|
, np, st->st_peeruserprotoid, st->st_peeruserport, &rbody))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Send NAT-OA if our address is NATed */
|
|
if (send_natoa)
|
|
{
|
|
if (!nat_traversal_add_natoa(ISAKMP_NEXT_NONE, &rbody, st))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* finish computing HASH(1), inserting it in output */
|
|
(void) quick_mode_hash12(r_hashval, r_hash_start, rbody.cur
|
|
, st, &st->st_msgid, FALSE);
|
|
|
|
/* encrypt message, except for fixed part of header */
|
|
|
|
init_phase2_iv(isakmp_sa, &st->st_msgid);
|
|
st->st_new_iv_len = isakmp_sa->st_new_iv_len;
|
|
memcpy(st->st_new_iv, isakmp_sa->st_new_iv, st->st_new_iv_len);
|
|
|
|
if (!encrypt_message(&rbody, st))
|
|
{
|
|
reset_cur_state();
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* save packet, now that we know its size */
|
|
st->st_tpacket = chunk_create(reply.start, pbs_offset(&reply));
|
|
st->st_tpacket = chunk_clone(st->st_tpacket);
|
|
|
|
/* send the packet */
|
|
|
|
send_packet(st, "quick_outI1");
|
|
|
|
delete_event(st);
|
|
event_schedule(EVENT_RETRANSMIT, EVENT_RETRANSMIT_DELAY_0, st);
|
|
|
|
if (replacing == SOS_NOBODY)
|
|
{
|
|
whack_log(RC_NEW_STATE + STATE_QUICK_I1
|
|
, "%s: initiate"
|
|
, enum_name(&state_names, st->st_state));
|
|
}
|
|
else
|
|
{
|
|
whack_log(RC_NEW_STATE + STATE_QUICK_I1
|
|
, "%s: initiate to replace #%lu"
|
|
, enum_name(&state_names, st->st_state)
|
|
, replacing);
|
|
}
|
|
reset_cur_state();
|
|
return STF_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
* Decode the CERT payload of Phase 1.
|
|
*/
|
|
static void decode_cert(struct msg_digest *md)
|
|
{
|
|
struct payload_digest *p;
|
|
|
|
for (p = md->chain[ISAKMP_NEXT_CERT]; p != NULL; p = p->next)
|
|
{
|
|
struct isakmp_cert *const cert = &p->payload.cert;
|
|
chunk_t blob;
|
|
time_t valid_until;
|
|
blob.ptr = p->pbs.cur;
|
|
blob.len = pbs_left(&p->pbs);
|
|
if (cert->isacert_type == CERT_X509_SIGNATURE)
|
|
{
|
|
cert_t x509cert = cert_empty;
|
|
|
|
x509cert.cert = lib->creds->create(lib->creds,
|
|
CRED_CERTIFICATE, CERT_X509,
|
|
BUILD_BLOB_ASN1_DER, blob,
|
|
BUILD_END);
|
|
if (x509cert.cert)
|
|
{
|
|
if (verify_x509cert(&x509cert, strict_crl_policy, &valid_until))
|
|
{
|
|
DBG(DBG_PARSING,
|
|
DBG_log("Public key validated")
|
|
)
|
|
add_public_key_from_cert(&x509cert, valid_until, DAL_SIGNED);
|
|
}
|
|
else
|
|
{
|
|
plog("X.509 certificate rejected");
|
|
}
|
|
x509cert.cert->destroy(x509cert.cert);
|
|
}
|
|
else
|
|
{
|
|
plog("Syntax error in X.509 certificate");
|
|
}
|
|
}
|
|
else if (cert->isacert_type == CERT_PKCS7_WRAPPED_X509)
|
|
{
|
|
linked_list_t *certs = linked_list_create();
|
|
|
|
if (pkcs7_parse_signedData(blob, NULL, certs, NULL, NULL))
|
|
{
|
|
store_x509certs(certs, strict_crl_policy);
|
|
}
|
|
else
|
|
{
|
|
plog("Syntax error in PKCS#7 wrapped X.509 certificates");
|
|
}
|
|
certs->destroy_offset(certs, offsetof(certificate_t, destroy));
|
|
}
|
|
else
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "ignoring %s certificate payload",
|
|
enum_show(&cert_type_names, cert->isacert_type));
|
|
DBG_cond_dump_chunk(DBG_PARSING, "CERT:\n", blob);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Decode the CR payload of Phase 1.
|
|
*/
|
|
static void decode_cr(struct msg_digest *md, connection_t *c)
|
|
{
|
|
struct payload_digest *p;
|
|
|
|
for (p = md->chain[ISAKMP_NEXT_CR]; p != NULL; p = p->next)
|
|
{
|
|
struct isakmp_cr *const cr = &p->payload.cr;
|
|
chunk_t ca_name;
|
|
|
|
ca_name.len = pbs_left(&p->pbs);
|
|
ca_name.ptr = (ca_name.len > 0)? p->pbs.cur : NULL;
|
|
|
|
DBG_cond_dump_chunk(DBG_PARSING, "CR", ca_name);
|
|
|
|
if (cr->isacr_type == CERT_X509_SIGNATURE)
|
|
{
|
|
if (ca_name.len > 0)
|
|
{
|
|
identification_t *ca;
|
|
|
|
if (!is_asn1(ca_name))
|
|
{
|
|
continue;
|
|
}
|
|
if (c->requested_ca == NULL)
|
|
{
|
|
c->requested_ca = linked_list_create();
|
|
}
|
|
ca = identification_create_from_encoding(ID_DER_ASN1_DN, ca_name);
|
|
c->requested_ca->insert_last(c->requested_ca, ca);
|
|
DBG(DBG_PARSING | DBG_CONTROL,
|
|
DBG_log("requested CA: \"%Y\"", ca)
|
|
)
|
|
}
|
|
else
|
|
{
|
|
DBG(DBG_PARSING | DBG_CONTROL,
|
|
DBG_log("requested CA: %%any")
|
|
)
|
|
}
|
|
c->got_certrequest = TRUE;
|
|
}
|
|
else
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "ignoring %s certificate request payload",
|
|
enum_show(&cert_type_names, cr->isacr_type));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Decode the ID payload of Phase 1 (main_inI3_outR3 and main_inR3)
|
|
* Note: we may change connections as a result.
|
|
* We must be called before SIG or HASH are decoded since we
|
|
* may change the peer's public key or ID.
|
|
*/
|
|
static bool decode_peer_id(struct msg_digest *md, identification_t **peer)
|
|
{
|
|
struct state *const st = md->st;
|
|
struct payload_digest *const id_pld = md->chain[ISAKMP_NEXT_ID];
|
|
const pb_stream *const id_pbs = &id_pld->pbs;
|
|
struct isakmp_id *const id = &id_pld->payload.id;
|
|
chunk_t id_payload;
|
|
|
|
/* I think that RFC2407 (IPSEC DOI) 4.6.2 is confused.
|
|
* It talks about the protocol ID and Port fields of the ID
|
|
* Payload, but they don't exist as such in Phase 1.
|
|
* We use more appropriate names.
|
|
* isaid_doi_specific_a is in place of Protocol ID.
|
|
* isaid_doi_specific_b is in place of Port.
|
|
* Besides, there is no good reason for allowing these to be
|
|
* other than 0 in Phase 1.
|
|
*/
|
|
if ((st->nat_traversal & NAT_T_WITH_PORT_FLOATING)
|
|
&& id->isaid_doi_specific_a == IPPROTO_UDP
|
|
&& (id->isaid_doi_specific_b == 0 || id->isaid_doi_specific_b == NAT_T_IKE_FLOAT_PORT))
|
|
{
|
|
DBG_log("protocol/port in Phase 1 ID Payload is %d/%d. "
|
|
"accepted with port_floating NAT-T",
|
|
id->isaid_doi_specific_a, id->isaid_doi_specific_b);
|
|
}
|
|
else if (!(id->isaid_doi_specific_a == 0 && id->isaid_doi_specific_b == 0)
|
|
&& !(id->isaid_doi_specific_a == IPPROTO_UDP && id->isaid_doi_specific_b == IKE_UDP_PORT))
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "protocol/port in Phase 1 ID Payload must be 0/0 or %d/%d"
|
|
" but are %d/%d"
|
|
, IPPROTO_UDP, IKE_UDP_PORT
|
|
, id->isaid_doi_specific_a, id->isaid_doi_specific_b);
|
|
return FALSE;
|
|
}
|
|
|
|
id_payload = chunk_create(id_pbs->cur, pbs_left(id_pbs));
|
|
|
|
switch (id->isaid_idtype)
|
|
{
|
|
case ID_IPV4_ADDR:
|
|
if (id_payload.len != 4)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "improper %s Phase 1 ID payload",
|
|
enum_show(&ident_names, id->isaid_idtype));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case ID_IPV6_ADDR:
|
|
if (id_payload.len != 16)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "improper %s Phase 1 ID payload",
|
|
enum_show(&ident_names, id->isaid_idtype));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case ID_USER_FQDN:
|
|
case ID_FQDN:
|
|
if (memchr(id_payload.ptr, '\0', id_payload.len) != NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s Phase 1 ID payload contains "
|
|
"a NUL character",
|
|
enum_show(&ident_names, id->isaid_idtype));
|
|
return FALSE;
|
|
}
|
|
break;
|
|
case ID_KEY_ID:
|
|
case ID_DER_ASN1_DN:
|
|
break;
|
|
default:
|
|
/* XXX Could send notification back */
|
|
loglog(RC_LOG_SERIOUS, "unacceptable identity type (%s) "
|
|
"in Phase 1 ID payload",
|
|
enum_show(&ident_names, id->isaid_idtype));
|
|
return FALSE;
|
|
}
|
|
*peer = identification_create_from_encoding(id->isaid_idtype, id_payload);
|
|
|
|
plog("Peer ID is %s: '%Y'", enum_show(&ident_names, id->isaid_idtype),
|
|
*peer);
|
|
|
|
/* check for certificates */
|
|
decode_cert(md);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Now that we've decoded the ID payload, let's see if we
|
|
* need to switch connections.
|
|
* We must not switch horses if we initiated:
|
|
* - if the initiation was explicit, we'd be ignoring user's intent
|
|
* - if opportunistic, we'll lose our HOLD info
|
|
*/
|
|
static bool switch_connection(struct msg_digest *md, identification_t *peer,
|
|
bool initiator)
|
|
{
|
|
struct state *const st = md->st;
|
|
connection_t *c = st->st_connection;
|
|
identification_t *peer_ca;
|
|
|
|
peer_ca = st->st_peer_pubkey ? st->st_peer_pubkey->issuer : NULL;
|
|
if (peer_ca)
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("peer CA: \"%Y\"", peer_ca)
|
|
)
|
|
}
|
|
else
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("peer CA: %%none")
|
|
)
|
|
}
|
|
|
|
if (initiator)
|
|
{
|
|
int pathlen;
|
|
|
|
if (!peer->equals(peer, c->spd.that.id))
|
|
{
|
|
loglog(RC_LOG_SERIOUS,
|
|
"we require peer to have ID '%Y', but peer declares '%Y'",
|
|
c->spd.that.id, peer);
|
|
return FALSE;
|
|
}
|
|
|
|
if (c->spd.that.ca)
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("required CA: \"%s\"", c->spd.that.ca);
|
|
)
|
|
}
|
|
else
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("required CA: %%none");
|
|
)
|
|
}
|
|
|
|
if (!trusted_ca(peer_ca, c->spd.that.ca, &pathlen))
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "we don't accept the peer's CA");
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
connection_t *r;
|
|
|
|
/* check for certificate requests */
|
|
decode_cr(md, c);
|
|
|
|
r = refine_host_connection(st, peer, peer_ca);
|
|
|
|
/* delete the collected certificate requests */
|
|
if (c->requested_ca)
|
|
{
|
|
c->requested_ca->destroy_offset(c->requested_ca,
|
|
offsetof(identification_t, destroy));
|
|
c->requested_ca = NULL;
|
|
}
|
|
|
|
if (r == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "no suitable connection for peer '%Y'", peer);
|
|
return FALSE;
|
|
}
|
|
|
|
if (r->spd.this.ca)
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("offered CA: \"%Y\"", r->spd.this.ca)
|
|
)
|
|
}
|
|
else
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("offered CA: %%none")
|
|
)
|
|
}
|
|
|
|
if (r != c)
|
|
{
|
|
/* apparently, r is an improvement on c -- replace */
|
|
|
|
DBG(DBG_CONTROL
|
|
, DBG_log("switched from \"%s\" to \"%s\"", c->name, r->name));
|
|
if (r->kind == CK_TEMPLATE)
|
|
{
|
|
/* instantiate it, filling in peer's ID */
|
|
r = rw_instantiate(r, &c->spd.that.host_addr
|
|
, c->spd.that.host_port, NULL, peer);
|
|
}
|
|
|
|
/* copy certificate request info */
|
|
r->got_certrequest = c->got_certrequest;
|
|
|
|
st->st_connection = r; /* kill reference to c */
|
|
set_cur_connection(r);
|
|
connection_discard(c);
|
|
}
|
|
else if (c->spd.that.has_id_wildcards)
|
|
{
|
|
c->spd.that.id->destroy(c->spd.that.id);
|
|
c->spd.that.id = peer->clone(peer);
|
|
c->spd.that.has_id_wildcards = FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* Decode the variable part of an ID packet (during Quick Mode).
|
|
* This is designed for packets that identify clients, not peers.
|
|
* Rejects 0.0.0.0/32 or IPv6 equivalent because
|
|
* (1) it is wrong and (2) we use this value for inband signalling.
|
|
*/
|
|
static bool decode_net_id(struct isakmp_ipsec_id *id, pb_stream *id_pbs,
|
|
ip_subnet *net, const char *which)
|
|
{
|
|
const struct af_info *afi = NULL;
|
|
|
|
/* Note: the following may be a pointer into static memory
|
|
* that may be recycled, but only if the type is not known.
|
|
* That case is disposed of very early -- in the first switch.
|
|
*/
|
|
const char *idtypename = enum_show(&ident_names, id->isaiid_idtype);
|
|
|
|
switch (id->isaiid_idtype)
|
|
{
|
|
case ID_IPV4_ADDR:
|
|
case ID_IPV4_ADDR_SUBNET:
|
|
case ID_IPV4_ADDR_RANGE:
|
|
afi = &af_inet4_info;
|
|
break;
|
|
case ID_IPV6_ADDR:
|
|
case ID_IPV6_ADDR_SUBNET:
|
|
case ID_IPV6_ADDR_RANGE:
|
|
afi = &af_inet6_info;
|
|
break;
|
|
case ID_FQDN:
|
|
return TRUE;
|
|
default:
|
|
/* XXX support more */
|
|
loglog(RC_LOG_SERIOUS, "unsupported ID type %s"
|
|
, idtypename);
|
|
/* XXX Could send notification back */
|
|
return FALSE;
|
|
}
|
|
|
|
switch (id->isaiid_idtype)
|
|
{
|
|
case ID_IPV4_ADDR:
|
|
case ID_IPV6_ADDR:
|
|
{
|
|
ip_address temp_address;
|
|
err_t ugh;
|
|
|
|
ugh = initaddr(id_pbs->cur, pbs_left(id_pbs), afi->af, &temp_address);
|
|
|
|
if (ugh != NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s ID payload %s has wrong length in Quick I1 (%s)"
|
|
, which, idtypename, ugh);
|
|
/* XXX Could send notification back */
|
|
return FALSE;
|
|
}
|
|
if (isanyaddr(&temp_address))
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s ID payload %s is invalid (%s) in Quick I1"
|
|
, which, idtypename, ip_str(&temp_address));
|
|
/* XXX Could send notification back */
|
|
return FALSE;
|
|
}
|
|
happy(addrtosubnet(&temp_address, net));
|
|
DBG(DBG_PARSING | DBG_CONTROL
|
|
, DBG_log("%s is %s", which, ip_str(&temp_address)));
|
|
break;
|
|
}
|
|
|
|
case ID_IPV4_ADDR_SUBNET:
|
|
case ID_IPV6_ADDR_SUBNET:
|
|
{
|
|
ip_address temp_address, temp_mask;
|
|
err_t ugh;
|
|
|
|
if (pbs_left(id_pbs) != 2 * afi->ia_sz)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s ID payload %s wrong length in Quick I1"
|
|
, which, idtypename);
|
|
/* XXX Could send notification back */
|
|
return FALSE;
|
|
}
|
|
ugh = initaddr(id_pbs->cur
|
|
, afi->ia_sz, afi->af, &temp_address);
|
|
if (ugh == NULL)
|
|
{
|
|
ugh = initaddr(id_pbs->cur + afi->ia_sz
|
|
, afi->ia_sz, afi->af, &temp_mask);
|
|
}
|
|
if (ugh == NULL)
|
|
{
|
|
ugh = initsubnet(&temp_address, masktocount(&temp_mask)
|
|
, '0', net);
|
|
}
|
|
if (ugh == NULL && subnetisnone(net))
|
|
{
|
|
ugh = "contains only anyaddr";
|
|
}
|
|
if (ugh != NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s ID payload %s bad subnet in Quick I1 (%s)"
|
|
, which, idtypename, ugh);
|
|
/* XXX Could send notification back */
|
|
return FALSE;
|
|
}
|
|
DBG(DBG_PARSING | DBG_CONTROL,
|
|
{
|
|
char temp_buff[SUBNETTOT_BUF];
|
|
|
|
subnettot(net, 0, temp_buff, sizeof(temp_buff));
|
|
DBG_log("%s is subnet %s", which, temp_buff);
|
|
});
|
|
break;
|
|
}
|
|
|
|
case ID_IPV4_ADDR_RANGE:
|
|
case ID_IPV6_ADDR_RANGE:
|
|
{
|
|
ip_address temp_address_from, temp_address_to;
|
|
err_t ugh;
|
|
|
|
if (pbs_left(id_pbs) != 2 * afi->ia_sz)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s ID payload %s wrong length in Quick I1"
|
|
, which, idtypename);
|
|
/* XXX Could send notification back */
|
|
return FALSE;
|
|
}
|
|
ugh = initaddr(id_pbs->cur, afi->ia_sz, afi->af, &temp_address_from);
|
|
if (ugh == NULL)
|
|
{
|
|
ugh = initaddr(id_pbs->cur + afi->ia_sz
|
|
, afi->ia_sz, afi->af, &temp_address_to);
|
|
}
|
|
if (ugh != NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s ID payload %s malformed (%s) in Quick I1"
|
|
, which, idtypename, ugh);
|
|
/* XXX Could send notification back */
|
|
return FALSE;
|
|
}
|
|
|
|
ugh = rangetosubnet(&temp_address_from, &temp_address_to, net);
|
|
if (ugh == NULL && subnetisnone(net))
|
|
{
|
|
ugh = "contains only anyaddr";
|
|
}
|
|
if (ugh != NULL)
|
|
{
|
|
char temp_buff1[ADDRTOT_BUF], temp_buff2[ADDRTOT_BUF];
|
|
|
|
addrtot(&temp_address_from, 0, temp_buff1, sizeof(temp_buff1));
|
|
addrtot(&temp_address_to, 0, temp_buff2, sizeof(temp_buff2));
|
|
loglog(RC_LOG_SERIOUS, "%s ID payload in Quick I1, %s"
|
|
" %s - %s unacceptable: %s"
|
|
, which, idtypename, temp_buff1, temp_buff2, ugh);
|
|
return FALSE;
|
|
}
|
|
DBG(DBG_PARSING | DBG_CONTROL,
|
|
{
|
|
char temp_buff[SUBNETTOT_BUF];
|
|
|
|
subnettot(net, 0, temp_buff, sizeof(temp_buff));
|
|
DBG_log("%s is subnet %s (received as range)"
|
|
, which, temp_buff);
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* set the port selector */
|
|
setportof(htons(id->isaiid_port), &net->addr);
|
|
|
|
DBG(DBG_PARSING | DBG_CONTROL,
|
|
DBG_log("%s protocol/port is %d/%d", which, id->isaiid_protoid, id->isaiid_port)
|
|
)
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* like decode, but checks that what is received matches what was sent */
|
|
static bool check_net_id(struct isakmp_ipsec_id *id, pb_stream *id_pbs,
|
|
u_int8_t *protoid, u_int16_t *port, ip_subnet *net,
|
|
const char *which)
|
|
{
|
|
ip_subnet net_temp;
|
|
|
|
if (!decode_net_id(id, id_pbs, &net_temp, which))
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (!samesubnet(net, &net_temp)
|
|
|| *protoid != id->isaiid_protoid || *port != id->isaiid_port)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "%s ID returned doesn't match my proposal", which);
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* look for the existence of a non-expiring preloaded public key
|
|
*/
|
|
static bool has_preloaded_public_key(struct state *st)
|
|
{
|
|
connection_t *c = st->st_connection;
|
|
|
|
/* do not consider rw connections since
|
|
* the peer's identity must be known
|
|
*/
|
|
if (c->kind == CK_PERMANENT)
|
|
{
|
|
pubkey_list_t *p;
|
|
|
|
/* look for a matching RSA public key */
|
|
for (p = pubkeys; p != NULL; p = p->next)
|
|
{
|
|
pubkey_t *key = p->key;
|
|
key_type_t type = key->public_key->get_type(key->public_key);
|
|
|
|
if (type == KEY_RSA &&
|
|
c->spd.that.id->equals(c->spd.that.id, key->id) &&
|
|
key->until_time == UNDEFINED_TIME)
|
|
{
|
|
/* found a preloaded public key */
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Produce the new key material of Quick Mode.
|
|
* RFC 2409 "IKE" section 5.5
|
|
* specifies how this is to be done.
|
|
*/
|
|
static void compute_proto_keymat(struct state *st, u_int8_t protoid,
|
|
struct ipsec_proto_info *pi)
|
|
{
|
|
size_t needed_len = 0; /* bytes of keying material needed */
|
|
|
|
/* Add up the requirements for keying material
|
|
* (It probably doesn't matter if we produce too much!)
|
|
*/
|
|
switch (protoid)
|
|
{
|
|
case PROTO_IPSEC_ESP:
|
|
{
|
|
needed_len = kernel_alg_esp_enc_keylen(pi->attrs.transid);
|
|
|
|
if (needed_len && pi->attrs.key_len)
|
|
{
|
|
needed_len = pi->attrs.key_len / BITS_PER_BYTE;
|
|
}
|
|
|
|
switch (pi->attrs.transid)
|
|
{
|
|
case ESP_NULL:
|
|
needed_len = 0;
|
|
break;
|
|
case ESP_AES_CCM_8:
|
|
case ESP_AES_CCM_12:
|
|
case ESP_AES_CCM_16:
|
|
needed_len += 3;
|
|
break;
|
|
case ESP_AES_GCM_8:
|
|
case ESP_AES_GCM_12:
|
|
case ESP_AES_GCM_16:
|
|
case ESP_AES_CTR:
|
|
case ESP_AES_GMAC:
|
|
needed_len += 4;
|
|
break;
|
|
default:
|
|
if (needed_len == 0)
|
|
{
|
|
bad_case(pi->attrs.transid);
|
|
}
|
|
}
|
|
|
|
if (kernel_alg_esp_auth_ok(pi->attrs.auth, NULL))
|
|
{
|
|
needed_len += kernel_alg_esp_auth_keylen(pi->attrs.auth);
|
|
}
|
|
else
|
|
{
|
|
switch (pi->attrs.auth)
|
|
{
|
|
case AUTH_ALGORITHM_NONE:
|
|
break;
|
|
case AUTH_ALGORITHM_HMAC_MD5:
|
|
needed_len += HMAC_MD5_KEY_LEN;
|
|
break;
|
|
case AUTH_ALGORITHM_HMAC_SHA1:
|
|
needed_len += HMAC_SHA1_KEY_LEN;
|
|
break;
|
|
case AUTH_ALGORITHM_DES_MAC:
|
|
default:
|
|
bad_case(pi->attrs.auth);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case PROTO_IPSEC_AH:
|
|
{
|
|
switch (pi->attrs.transid)
|
|
{
|
|
case AH_MD5:
|
|
needed_len = HMAC_MD5_KEY_LEN;
|
|
break;
|
|
case AH_SHA:
|
|
needed_len = HMAC_SHA1_KEY_LEN;
|
|
break;
|
|
default:
|
|
bad_case(pi->attrs.transid);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
bad_case(protoid);
|
|
}
|
|
|
|
pi->keymat_len = needed_len;
|
|
|
|
/* Allocate space for the keying material. Although only needed_len bytes
|
|
* are desired, we must round up to a multiple of hash_size
|
|
* so that our buffer isn't overrun.
|
|
*/
|
|
{
|
|
size_t needed_space; /* space needed for keying material (rounded up) */
|
|
size_t i, prf_block_size;
|
|
chunk_t protoid_chunk = chunk_from_thing(protoid);
|
|
chunk_t spi_our = chunk_from_thing(pi->our_spi);
|
|
chunk_t spi_peer = chunk_from_thing(pi->attrs.spi);
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf_our, *prf_peer;
|
|
|
|
prf_alg = oakley_to_prf(st->st_oakley.hash);
|
|
prf_our = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf_peer = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf_our->set_key(prf_our, st->st_skeyid_d);
|
|
prf_peer->set_key(prf_peer, st->st_skeyid_d);
|
|
prf_block_size = prf_our->get_block_size(prf_our);
|
|
|
|
needed_space = needed_len + pad_up(needed_len, prf_block_size);
|
|
replace(pi->our_keymat, malloc(needed_space));
|
|
replace(pi->peer_keymat, malloc(needed_space));
|
|
|
|
for (i = 0;; )
|
|
{
|
|
char *keymat_i_our = pi->our_keymat + i;
|
|
char *keymat_i_peer = pi->peer_keymat + i;
|
|
chunk_t keymat_our = { keymat_i_our, prf_block_size };
|
|
chunk_t keymat_peer = { keymat_i_peer, prf_block_size };
|
|
|
|
if (st->st_shared.ptr != NULL)
|
|
{
|
|
/* PFS: include the g^xy */
|
|
prf_our->get_bytes(prf_our, st->st_shared, NULL);
|
|
prf_peer->get_bytes(prf_peer, st->st_shared, NULL);
|
|
}
|
|
prf_our->get_bytes(prf_our, protoid_chunk, NULL);
|
|
prf_peer->get_bytes(prf_peer, protoid_chunk, NULL);
|
|
|
|
prf_our->get_bytes(prf_our, spi_our, NULL);
|
|
prf_peer->get_bytes(prf_peer, spi_peer, NULL);
|
|
|
|
prf_our->get_bytes(prf_our, st->st_ni, NULL);
|
|
prf_peer->get_bytes(prf_peer, st->st_ni, NULL);
|
|
|
|
prf_our->get_bytes(prf_our, st->st_nr, keymat_i_our);
|
|
prf_peer->get_bytes(prf_peer, st->st_nr, keymat_i_peer);
|
|
|
|
i += prf_block_size;
|
|
if (i >= needed_space)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* more keying material needed: prepare to go around again */
|
|
prf_our->get_bytes(prf_our, keymat_our, NULL);
|
|
prf_peer->get_bytes(prf_peer, keymat_peer, NULL);
|
|
}
|
|
prf_our->destroy(prf_our);
|
|
prf_peer->destroy(prf_peer);
|
|
}
|
|
DBG(DBG_CRYPT,
|
|
DBG_dump("KEYMAT computed:\n", pi->our_keymat, pi->keymat_len);
|
|
DBG_dump("Peer KEYMAT computed:\n", pi->peer_keymat, pi->keymat_len));
|
|
}
|
|
|
|
static void compute_keymats(struct state *st)
|
|
{
|
|
if (st->st_ah.present)
|
|
{
|
|
compute_proto_keymat(st, PROTO_IPSEC_AH, &st->st_ah);
|
|
}
|
|
if (st->st_esp.present)
|
|
{
|
|
compute_proto_keymat(st, PROTO_IPSEC_ESP, &st->st_esp);
|
|
}
|
|
}
|
|
|
|
static bool uses_pubkey_auth(int auth)
|
|
{
|
|
switch (auth)
|
|
{
|
|
case OAKLEY_RSA_SIG:
|
|
case OAKLEY_ECDSA_SIG:
|
|
case OAKLEY_ECDSA_256:
|
|
case OAKLEY_ECDSA_384:
|
|
case OAKLEY_ECDSA_521:
|
|
case XAUTHInitRSA:
|
|
case XAUTHRespRSA:
|
|
return TRUE;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* build an ID payload
|
|
* Note: no memory is allocated for the body of the payload (tl->ptr).
|
|
* We assume it will end up being a pointer into a sufficiently
|
|
* stable datastructure. It only needs to last a short time.
|
|
*/
|
|
static void build_id_payload(struct isakmp_ipsec_id *hd, chunk_t *tl, struct end *end)
|
|
{
|
|
identification_t *id = resolve_myid(end->id);
|
|
|
|
zero(hd);
|
|
hd->isaiid_idtype = id->get_type(id);
|
|
|
|
switch (id->get_type(id))
|
|
{
|
|
case ID_ANY:
|
|
hd->isaiid_idtype = aftoinfo(addrtypeof(&end->host_addr))->id_addr;
|
|
tl->len = addrbytesptr(&end->host_addr,
|
|
(const unsigned char **)&tl->ptr); /* sets tl->ptr too */
|
|
break;
|
|
case ID_IPV4_ADDR:
|
|
case ID_IPV6_ADDR:
|
|
case ID_FQDN:
|
|
case ID_USER_FQDN:
|
|
case ID_DER_ASN1_DN:
|
|
case ID_KEY_ID:
|
|
*tl = id->get_encoding(id);
|
|
break;
|
|
default:
|
|
bad_case(id->get_type(id));
|
|
}
|
|
}
|
|
|
|
/* State Transition Functions.
|
|
*
|
|
* The definition of state_microcode_table in demux.c is a good
|
|
* overview of these routines.
|
|
*
|
|
* - Called from process_packet; result handled by complete_state_transition
|
|
* - struct state_microcode member "processor" points to these
|
|
* - these routine definitionss are in state order
|
|
* - these routines must be restartable from any point of error return:
|
|
* beware of memory allocated before any error.
|
|
* - output HDR is usually emitted by process_packet (if state_microcode
|
|
* member first_out_payload isn't ISAKMP_NEXT_NONE).
|
|
*
|
|
* The transition functions' functions include:
|
|
* - process and judge payloads
|
|
* - update st_iv (result of decryption is in st_new_iv)
|
|
* - build reply packet
|
|
*/
|
|
|
|
/* Handle a Main Mode Oakley first packet (responder side).
|
|
* HDR;SA --> HDR;SA
|
|
*/
|
|
stf_status main_inI1_outR1(struct msg_digest *md)
|
|
{
|
|
struct payload_digest *const sa_pd = md->chain[ISAKMP_NEXT_SA];
|
|
struct state *st;
|
|
connection_t *c;
|
|
struct isakmp_proposal proposal;
|
|
pb_stream proposal_pbs;
|
|
pb_stream r_sa_pbs;
|
|
u_int32_t ipsecdoisit;
|
|
lset_t policy = LEMPTY;
|
|
int vids_to_send = 0;
|
|
|
|
/* We preparse the peer's proposal in order to determine
|
|
* the requested authentication policy (RSA or PSK)
|
|
*/
|
|
RETURN_STF_FAILURE(preparse_isakmp_sa_body(&sa_pd->payload.sa
|
|
, &sa_pd->pbs, &ipsecdoisit, &proposal_pbs, &proposal));
|
|
|
|
backup_pbs(&proposal_pbs);
|
|
RETURN_STF_FAILURE(parse_isakmp_policy(&proposal_pbs
|
|
, proposal.isap_notrans, &policy));
|
|
restore_pbs(&proposal_pbs);
|
|
|
|
/* We are only considering candidate connections that match
|
|
* the requested authentication policy (RSA or PSK)
|
|
*/
|
|
c = find_host_connection(&md->iface->addr, pluto_port
|
|
, &md->sender, md->sender_port, policy);
|
|
|
|
if (c == NULL && md->iface->ike_float)
|
|
{
|
|
c = find_host_connection(&md->iface->addr, NAT_T_IKE_FLOAT_PORT
|
|
, &md->sender, md->sender_port, policy);
|
|
}
|
|
|
|
if (c == NULL)
|
|
{
|
|
/* See if a wildcarded connection can be found.
|
|
* We cannot pick the right connection, so we're making a guess.
|
|
* All Road Warrior connections are fair game:
|
|
* we pick the first we come across (if any).
|
|
* If we don't find any, we pick the first opportunistic
|
|
* with the smallest subnet that includes the peer.
|
|
* There is, of course, no necessary relationship between
|
|
* an Initiator's address and that of its client,
|
|
* but Food Groups kind of assumes one.
|
|
*/
|
|
{
|
|
connection_t *d;
|
|
|
|
d = find_host_connection(&md->iface->addr
|
|
, pluto_port, (ip_address*)NULL, md->sender_port, policy);
|
|
|
|
for (; d != NULL; d = d->hp_next)
|
|
{
|
|
if (d->kind == CK_GROUP)
|
|
{
|
|
/* ignore */
|
|
}
|
|
else
|
|
{
|
|
if (d->kind == CK_TEMPLATE && !(d->policy & POLICY_OPPO))
|
|
{
|
|
/* must be Road Warrior: we have a winner */
|
|
c = d;
|
|
break;
|
|
}
|
|
|
|
/* Opportunistic or Shunt: pick tightest match */
|
|
if (addrinsubnet(&md->sender, &d->spd.that.client)
|
|
&& (c == NULL || !subnetinsubnet(&c->spd.that.client, &d->spd.that.client)))
|
|
c = d;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (c == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "initial Main Mode message received on %s:%u"
|
|
" but no connection has been authorized%s%s"
|
|
, ip_str(&md->iface->addr), ntohs(portof(&md->iface->addr))
|
|
, (policy != LEMPTY) ? " with policy=" : ""
|
|
, (policy != LEMPTY) ? bitnamesof(sa_policy_bit_names, policy) : "");
|
|
/* XXX notification is in order! */
|
|
return STF_IGNORE;
|
|
}
|
|
else if (c->kind != CK_TEMPLATE)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "initial Main Mode message received on %s:%u"
|
|
" but \"%s\" forbids connection"
|
|
, ip_str(&md->iface->addr), pluto_port, c->name);
|
|
/* XXX notification is in order! */
|
|
return STF_IGNORE;
|
|
}
|
|
else
|
|
{
|
|
/* Create a temporary connection that is a copy of this one.
|
|
* His ID isn't declared yet.
|
|
*/
|
|
c = rw_instantiate(c, &md->sender, md->sender_port, NULL, NULL);
|
|
}
|
|
}
|
|
else if (c->kind == CK_TEMPLATE)
|
|
{
|
|
/* Create an instance
|
|
* This is a rare case: wildcard peer ID but static peer IP address
|
|
*/
|
|
c = rw_instantiate(c, &md->sender, md->sender_port, NULL, c->spd.that.id);
|
|
}
|
|
|
|
/* Set up state */
|
|
md->st = st = new_state();
|
|
st->st_connection = c;
|
|
set_cur_state(st); /* (caller will reset cur_state) */
|
|
st->st_try = 0; /* not our job to try again from start */
|
|
st->st_policy = c->policy & ~POLICY_IPSEC_MASK; /* only as accurate as connection */
|
|
|
|
memcpy(st->st_icookie, md->hdr.isa_icookie, COOKIE_SIZE);
|
|
get_cookie(FALSE, st->st_rcookie, COOKIE_SIZE, &md->sender);
|
|
|
|
insert_state(st); /* needs cookies, connection, and msgid (0) */
|
|
|
|
st->st_doi = ISAKMP_DOI_IPSEC;
|
|
st->st_situation = SIT_IDENTITY_ONLY; /* We only support this */
|
|
|
|
if ((c->kind == CK_INSTANCE) && (c->spd.that.host_port != pluto_port))
|
|
{
|
|
plog("responding to Main Mode from unknown peer %s:%u"
|
|
, ip_str(&c->spd.that.host_addr), c->spd.that.host_port);
|
|
}
|
|
else if (c->kind == CK_INSTANCE)
|
|
{
|
|
plog("responding to Main Mode from unknown peer %s"
|
|
, ip_str(&c->spd.that.host_addr));
|
|
}
|
|
else
|
|
{
|
|
plog("responding to Main Mode");
|
|
}
|
|
|
|
/* parse_isakmp_sa also spits out a winning SA into our reply,
|
|
* so we have to build our md->reply and emit HDR before calling it.
|
|
*/
|
|
|
|
/* determine how many Vendor ID payloads we will be sending */
|
|
if (SEND_PLUTO_VID)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
if (SEND_CISCO_UNITY_VID)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
if (md->openpgp)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
if (SEND_XAUTH_VID)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
/* always send DPD Vendor ID */
|
|
vids_to_send++;
|
|
if (md->nat_traversal_vid && nat_traversal_enabled)
|
|
{
|
|
vids_to_send++;
|
|
}
|
|
|
|
/* HDR out.
|
|
* We can't leave this to comm_handle() because we must
|
|
* fill in the cookie.
|
|
*/
|
|
{
|
|
struct isakmp_hdr r_hdr = md->hdr;
|
|
|
|
r_hdr.isa_flags &= ~ISAKMP_FLAG_COMMIT; /* we won't ever turn on this bit */
|
|
memcpy(r_hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE);
|
|
r_hdr.isa_np = ISAKMP_NEXT_SA;
|
|
if (!out_struct(&r_hdr, &isakmp_hdr_desc, &md->reply, &md->rbody))
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* start of SA out */
|
|
{
|
|
struct isakmp_sa r_sa = sa_pd->payload.sa;
|
|
|
|
r_sa.isasa_np = vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE;
|
|
|
|
if (!out_struct(&r_sa, &isakmp_sa_desc, &md->rbody, &r_sa_pbs))
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* SA body in and out */
|
|
RETURN_STF_FAILURE(parse_isakmp_sa_body(ipsecdoisit, &proposal_pbs
|
|
,&proposal, &r_sa_pbs, st, FALSE));
|
|
|
|
/* if enabled send Pluto Vendor ID */
|
|
if (SEND_PLUTO_VID)
|
|
{
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &md->rbody, VID_STRONGSWAN))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* if enabled send Cisco Unity Vendor ID */
|
|
if (SEND_CISCO_UNITY_VID)
|
|
{
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &md->rbody, VID_CISCO_UNITY))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* if the peer sent an OpenPGP Vendor ID we offer the same capability
|
|
*/
|
|
if (md->openpgp)
|
|
{
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &md->rbody, VID_OPENPGP))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Announce our ability to do eXtended AUTHentication to the peer */
|
|
if (SEND_XAUTH_VID)
|
|
{
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &md->rbody, VID_MISC_XAUTH))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* Announce our ability to do Dead Peer Detection to the peer */
|
|
if (!out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &md->rbody, VID_MISC_DPD))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (md->nat_traversal_vid && nat_traversal_enabled)
|
|
{
|
|
/* reply if NAT-Traversal draft is supported */
|
|
st->nat_traversal = nat_traversal_vid_to_method(md->nat_traversal_vid);
|
|
|
|
if (st->nat_traversal
|
|
&& !out_vendorid(vids_to_send-- ? ISAKMP_NEXT_VID : ISAKMP_NEXT_NONE
|
|
, &md->rbody, md->nat_traversal_vid))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
close_message(&md->rbody);
|
|
|
|
/* save initiator SA for HASH */
|
|
free(st->st_p1isa.ptr);
|
|
st->st_p1isa = chunk_create(sa_pd->pbs.start, pbs_room(&sa_pd->pbs));
|
|
st->st_p1isa = chunk_clone(st->st_p1isa);
|
|
|
|
return STF_OK;
|
|
}
|
|
|
|
/* STATE_MAIN_I1: HDR, SA --> auth dependent
|
|
* PSK_AUTH, DS_AUTH: --> HDR, KE, Ni
|
|
*
|
|
* The following are not yet implemented:
|
|
* PKE_AUTH: --> HDR, KE, [ HASH(1), ] <IDi1_b>PubKey_r, <Ni_b>PubKey_r
|
|
* RPKE_AUTH: --> HDR, [ HASH(1), ] <Ni_b>Pubkey_r, <KE_b>Ke_i,
|
|
* <IDi1_b>Ke_i [,<<Cert-I_b>Ke_i]
|
|
*
|
|
* We must verify that the proposal received matches one we sent.
|
|
*/
|
|
stf_status main_inR1_outI2(struct msg_digest *md)
|
|
{
|
|
struct state *const st = md->st;
|
|
|
|
u_int8_t np = ISAKMP_NEXT_NONE;
|
|
|
|
/* verify echoed SA */
|
|
{
|
|
u_int32_t ipsecdoisit;
|
|
pb_stream proposal_pbs;
|
|
struct isakmp_proposal proposal;
|
|
struct payload_digest *const sapd = md->chain[ISAKMP_NEXT_SA];
|
|
|
|
RETURN_STF_FAILURE(preparse_isakmp_sa_body(&sapd->payload.sa
|
|
,&sapd->pbs, &ipsecdoisit, &proposal_pbs, &proposal));
|
|
if (proposal.isap_notrans != 1)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "a single Transform is required in a selecting Oakley Proposal; found %u"
|
|
, (unsigned)proposal.isap_notrans);
|
|
RETURN_STF_FAILURE(ISAKMP_BAD_PROPOSAL_SYNTAX);
|
|
}
|
|
RETURN_STF_FAILURE(parse_isakmp_sa_body(ipsecdoisit
|
|
, &proposal_pbs, &proposal, NULL, st, TRUE));
|
|
}
|
|
|
|
if (nat_traversal_enabled && md->nat_traversal_vid)
|
|
{
|
|
st->nat_traversal = nat_traversal_vid_to_method(md->nat_traversal_vid);
|
|
plog("enabling possible NAT-traversal with method %s"
|
|
, bitnamesof(natt_type_bitnames, st->nat_traversal));
|
|
}
|
|
if (st->nat_traversal & NAT_T_WITH_NATD)
|
|
{
|
|
np = (st->nat_traversal & NAT_T_WITH_RFC_VALUES) ?
|
|
ISAKMP_NEXT_NATD_RFC : ISAKMP_NEXT_NATD_DRAFTS;
|
|
}
|
|
|
|
/**************** build output packet HDR;KE;Ni ****************/
|
|
|
|
/* HDR out.
|
|
* We can't leave this to comm_handle() because the isa_np
|
|
* depends on the type of Auth (eventually).
|
|
*/
|
|
echo_hdr(md, FALSE, ISAKMP_NEXT_KE);
|
|
|
|
/* KE out */
|
|
if (!build_and_ship_KE(st, &st->st_gi, st->st_oakley.group
|
|
, &md->rbody, ISAKMP_NEXT_NONCE))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* Ni out */
|
|
if (!build_and_ship_nonce(&st->st_ni, &md->rbody
|
|
, (cur_debugging & IMPAIR_BUST_MI2)? ISAKMP_NEXT_VID : np, "Ni"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
if (cur_debugging & IMPAIR_BUST_MI2)
|
|
{
|
|
/* generate a pointless large VID payload to push message over MTU */
|
|
pb_stream vid_pbs;
|
|
|
|
if (!out_generic(np, &isakmp_vendor_id_desc, &md->rbody, &vid_pbs))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
if (!out_zero(1500 /*MTU?*/, &vid_pbs, "Filler VID"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
close_output_pbs(&vid_pbs);
|
|
}
|
|
#else
|
|
/* Ni out */
|
|
if (!build_and_ship_nonce(&st->st_ni, &md->rbody, np, "Ni"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
#endif
|
|
|
|
if (st->nat_traversal & NAT_T_WITH_NATD)
|
|
{
|
|
if (!nat_traversal_add_natd(ISAKMP_NEXT_NONE, &md->rbody, md))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* finish message */
|
|
close_message(&md->rbody);
|
|
|
|
/* Reinsert the state, using the responder cookie we just received */
|
|
unhash_state(st);
|
|
memcpy(st->st_rcookie, md->hdr.isa_rcookie, COOKIE_SIZE);
|
|
insert_state(st); /* needs cookies, connection, and msgid (0) */
|
|
|
|
return STF_OK;
|
|
}
|
|
|
|
/* STATE_MAIN_R1:
|
|
* PSK_AUTH, DS_AUTH: HDR, KE, Ni --> HDR, KE, Nr
|
|
*
|
|
* The following are not yet implemented:
|
|
* PKE_AUTH: HDR, KE, [ HASH(1), ] <IDi1_b>PubKey_r, <Ni_b>PubKey_r
|
|
* --> HDR, KE, <IDr1_b>PubKey_i, <Nr_b>PubKey_i
|
|
* RPKE_AUTH:
|
|
* HDR, [ HASH(1), ] <Ni_b>Pubkey_r, <KE_b>Ke_i, <IDi1_b>Ke_i [,<<Cert-I_b>Ke_i]
|
|
* --> HDR, <Nr_b>PubKey_i, <KE_b>Ke_r, <IDr1_b>Ke_r
|
|
*/
|
|
stf_status main_inI2_outR2(struct msg_digest *md)
|
|
{
|
|
struct state *const st = md->st;
|
|
pb_stream *keyex_pbs = &md->chain[ISAKMP_NEXT_KE]->pbs;
|
|
|
|
/* send CR if auth is RSA or ECDSA and no preloaded public key exists*/
|
|
bool pubkey_auth = uses_pubkey_auth(st->st_oakley.auth);
|
|
bool send_cr = !no_cr_send && pubkey_auth && !has_preloaded_public_key(st);
|
|
|
|
u_int8_t np = ISAKMP_NEXT_NONE;
|
|
|
|
/* KE in */
|
|
RETURN_STF_FAILURE(accept_KE(&st->st_gi, "Gi", st->st_oakley.group, keyex_pbs));
|
|
|
|
/* Ni in */
|
|
RETURN_STF_FAILURE(accept_nonce(md, &st->st_ni, "Ni"));
|
|
|
|
if (st->nat_traversal & NAT_T_WITH_NATD)
|
|
{
|
|
nat_traversal_natd_lookup(md);
|
|
|
|
np = (st->nat_traversal & NAT_T_WITH_RFC_VALUES) ?
|
|
ISAKMP_NEXT_NATD_RFC : ISAKMP_NEXT_NATD_DRAFTS;
|
|
}
|
|
if (st->nat_traversal)
|
|
{
|
|
nat_traversal_show_result(st->nat_traversal, md->sender_port);
|
|
}
|
|
if (st->nat_traversal & NAT_T_WITH_KA)
|
|
{
|
|
nat_traversal_new_ka_event();
|
|
}
|
|
|
|
/* decode certificate requests */
|
|
st->st_connection->got_certrequest = FALSE;
|
|
decode_cr(md, st->st_connection);
|
|
|
|
/**************** build output packet HDR;KE;Nr ****************/
|
|
|
|
/* HDR out done */
|
|
|
|
/* KE out */
|
|
if (!build_and_ship_KE(st, &st->st_gr, st->st_oakley.group
|
|
, &md->rbody, ISAKMP_NEXT_NONCE))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* Nr out */
|
|
if (!build_and_ship_nonce(&st->st_nr, &md->rbody,
|
|
(cur_debugging & IMPAIR_BUST_MR2)? ISAKMP_NEXT_VID
|
|
: (send_cr? ISAKMP_NEXT_CR : np), "Nr"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
if (cur_debugging & IMPAIR_BUST_MR2)
|
|
{
|
|
/* generate a pointless large VID payload to push message over MTU */
|
|
pb_stream vid_pbs;
|
|
|
|
if (!out_generic((send_cr)? ISAKMP_NEXT_CR : np,
|
|
&isakmp_vendor_id_desc, &md->rbody, &vid_pbs))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
if (!out_zero(1500 /*MTU?*/, &vid_pbs, "Filler VID"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
close_output_pbs(&vid_pbs);
|
|
}
|
|
#else
|
|
/* Nr out */
|
|
if (!build_and_ship_nonce(&st->st_nr, &md->rbody,
|
|
(send_cr)? ISAKMP_NEXT_CR : np, "Nr"))
|
|
return STF_INTERNAL_ERROR;
|
|
#endif
|
|
|
|
/* CR out */
|
|
if (send_cr)
|
|
{
|
|
if (st->st_connection->kind == CK_PERMANENT)
|
|
{
|
|
identification_t *ca = st->st_connection->spd.that.ca;
|
|
chunk_t cr = (ca) ? ca->get_encoding(ca) : chunk_empty;
|
|
|
|
if (!build_and_ship_CR(CERT_X509_SIGNATURE, cr, &md->rbody, np))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
linked_list_t *list = collect_rw_ca_candidates(md);
|
|
int count = list->get_count(list);
|
|
bool error = FALSE;
|
|
|
|
if (count)
|
|
{
|
|
enumerator_t *enumerator;
|
|
identification_t *ca;
|
|
|
|
enumerator = list->create_enumerator(list);
|
|
while (enumerator->enumerate(enumerator, &ca))
|
|
{
|
|
if (!build_and_ship_CR(CERT_X509_SIGNATURE,
|
|
ca->get_encoding(ca), &md->rbody,
|
|
--count ? ISAKMP_NEXT_CR : np))
|
|
{
|
|
error = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
}
|
|
else
|
|
{
|
|
if (!build_and_ship_CR(CERT_X509_SIGNATURE, chunk_empty,
|
|
&md->rbody, np))
|
|
{
|
|
error = TRUE;
|
|
}
|
|
}
|
|
list->destroy_offset(list, offsetof(identification_t, destroy));
|
|
if (error)
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (st->nat_traversal & NAT_T_WITH_NATD)
|
|
{
|
|
if (!nat_traversal_add_natd(ISAKMP_NEXT_NONE, &md->rbody, md))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* finish message */
|
|
close_message(&md->rbody);
|
|
|
|
/* next message will be encrypted, but not this one.
|
|
* We could defer this calculation.
|
|
*/
|
|
compute_dh_shared(st, st->st_gi);
|
|
if (!generate_skeyids_iv(st))
|
|
{
|
|
return STF_FAIL + ISAKMP_AUTHENTICATION_FAILED;
|
|
}
|
|
update_iv(st);
|
|
|
|
return STF_OK;
|
|
}
|
|
|
|
/* STATE_MAIN_I2:
|
|
* SMF_PSK_AUTH: HDR, KE, Nr --> HDR*, IDi1, HASH_I
|
|
* SMF_DS_AUTH: HDR, KE, Nr --> HDR*, IDi1, [ CERT, ] SIG_I
|
|
*
|
|
* The following are not yet implemented.
|
|
* SMF_PKE_AUTH: HDR, KE, <IDr1_b>PubKey_i, <Nr_b>PubKey_i
|
|
* --> HDR*, HASH_I
|
|
* SMF_RPKE_AUTH: HDR, <Nr_b>PubKey_i, <KE_b>Ke_r, <IDr1_b>Ke_r
|
|
* --> HDR*, HASH_I
|
|
*/
|
|
stf_status main_inR2_outI3(struct msg_digest *md)
|
|
{
|
|
struct state *const st = md->st;
|
|
pb_stream *const keyex_pbs = &md->chain[ISAKMP_NEXT_KE]->pbs;
|
|
pb_stream id_pbs; /* ID Payload; also used for hash calculation */
|
|
|
|
connection_t *c = st->st_connection;
|
|
certpolicy_t cert_policy = c->spd.this.sendcert;
|
|
cert_t *mycert = c->spd.this.cert;
|
|
bool requested, send_cert, send_cr;
|
|
bool pubkey_auth = uses_pubkey_auth(st->st_oakley.auth);
|
|
|
|
int auth_payload = pubkey_auth ? ISAKMP_NEXT_SIG : ISAKMP_NEXT_HASH;
|
|
|
|
/* KE in */
|
|
RETURN_STF_FAILURE(accept_KE(&st->st_gr, "Gr", st->st_oakley.group, keyex_pbs));
|
|
|
|
/* Nr in */
|
|
RETURN_STF_FAILURE(accept_nonce(md, &st->st_nr, "Nr"));
|
|
|
|
/* decode certificate requests */
|
|
c->got_certrequest = FALSE;
|
|
decode_cr(md, c);
|
|
|
|
/* free collected certificate requests since as initiator
|
|
* we don't heed them anyway
|
|
*/
|
|
if (c->requested_ca)
|
|
{
|
|
c->requested_ca->destroy_offset(c->requested_ca,
|
|
offsetof(identification_t, destroy));
|
|
c->requested_ca = NULL;
|
|
}
|
|
|
|
/* send certificate if auth is RSA, we have one and we want
|
|
* or are requested to send it
|
|
*/
|
|
requested = cert_policy == CERT_SEND_IF_ASKED && c->got_certrequest;
|
|
send_cert = pubkey_auth && mycert &&
|
|
mycert->cert->get_type(mycert->cert) == CERT_X509 &&
|
|
(cert_policy == CERT_ALWAYS_SEND || requested);
|
|
|
|
/* send certificate request if we don't have a preloaded RSA public key */
|
|
send_cr = !no_cr_send && send_cert && !has_preloaded_public_key(st);
|
|
|
|
/* done parsing; initialize crypto */
|
|
compute_dh_shared(st, st->st_gr);
|
|
if (!generate_skeyids_iv(st))
|
|
{
|
|
return STF_FAIL + ISAKMP_AUTHENTICATION_FAILED;
|
|
}
|
|
if (st->nat_traversal & NAT_T_WITH_NATD)
|
|
{
|
|
nat_traversal_natd_lookup(md);
|
|
}
|
|
if (st->nat_traversal)
|
|
{
|
|
nat_traversal_show_result(st->nat_traversal, md->sender_port);
|
|
}
|
|
if (st->nat_traversal & NAT_T_WITH_KA)
|
|
{
|
|
nat_traversal_new_ka_event();
|
|
}
|
|
|
|
/*************** build output packet HDR*;IDii;HASH/SIG_I ***************/
|
|
/* ??? NOTE: this is almost the same as main_inI3_outR3's code */
|
|
|
|
/* HDR* out done */
|
|
|
|
/* IDii out */
|
|
{
|
|
struct isakmp_ipsec_id id_hd;
|
|
chunk_t id_b;
|
|
|
|
build_id_payload(&id_hd, &id_b, &c->spd.this);
|
|
id_hd.isaiid_np = (send_cert)? ISAKMP_NEXT_CERT : auth_payload;
|
|
if (!out_struct(&id_hd, &isakmp_ipsec_identification_desc, &md->rbody, &id_pbs)
|
|
|| !out_chunk(id_b, &id_pbs, "my identity"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
close_output_pbs(&id_pbs);
|
|
}
|
|
|
|
/* CERT out */
|
|
if (pubkey_auth)
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("our certificate policy is %N", cert_policy_names, cert_policy)
|
|
)
|
|
if (mycert && mycert->cert->get_type(mycert->cert) == CERT_X509)
|
|
{
|
|
const char *request_text = "";
|
|
|
|
if (cert_policy == CERT_SEND_IF_ASKED)
|
|
{
|
|
request_text = (send_cert)? "upon request":"without request";
|
|
}
|
|
plog("we have a cert %s sending it %s"
|
|
, send_cert? "and are":"but are not", request_text);
|
|
}
|
|
else
|
|
{
|
|
plog("we don't have a cert");
|
|
}
|
|
}
|
|
if (send_cert)
|
|
{
|
|
bool success;
|
|
chunk_t cert_encoding;
|
|
pb_stream cert_pbs;
|
|
|
|
struct isakmp_cert cert_hd;
|
|
cert_hd.isacert_np = (send_cr)? ISAKMP_NEXT_CR : ISAKMP_NEXT_SIG;
|
|
cert_hd.isacert_type = CERT_X509_SIGNATURE;
|
|
|
|
if (!out_struct(&cert_hd, &isakmp_ipsec_certificate_desc, &md->rbody, &cert_pbs))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
cert_encoding = mycert->cert->get_encoding(mycert->cert);
|
|
success = out_chunk(cert_encoding, &cert_pbs, "CERT");
|
|
free(cert_encoding.ptr);
|
|
if (!success)
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
close_output_pbs(&cert_pbs);
|
|
}
|
|
|
|
/* CR out */
|
|
if (send_cr)
|
|
{
|
|
identification_t *ca = st->st_connection->spd.that.ca;
|
|
chunk_t cr = (ca) ? ca->get_encoding(ca) : chunk_empty;
|
|
|
|
if (!build_and_ship_CR(CERT_X509_SIGNATURE, cr, &md->rbody, ISAKMP_NEXT_SIG))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
/* HASH_I or SIG_I out */
|
|
{
|
|
chunk_t hash = chunk_alloca(MAX_DIGEST_LEN);
|
|
|
|
main_mode_hash(st, &hash, TRUE, &id_pbs);
|
|
|
|
if (auth_payload == ISAKMP_NEXT_HASH)
|
|
{
|
|
/* HASH_I out */
|
|
if (!out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_hash_desc, &md->rbody,
|
|
hash.ptr, hash.len, "HASH_I"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* SIG_I out */
|
|
u_char sig_val[RSA_MAX_OCTETS];
|
|
signature_scheme_t scheme;
|
|
size_t sig_len;
|
|
|
|
scheme = oakley_to_signature_scheme(st->st_oakley.auth);
|
|
|
|
sig_len = sign_hash(scheme, c, sig_val, hash);
|
|
if (sig_len == 0)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "unable to locate my private key for signature");
|
|
return STF_FAIL + ISAKMP_AUTHENTICATION_FAILED;
|
|
}
|
|
|
|
if (!out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_signature_desc
|
|
, &md->rbody, sig_val, sig_len, "SIG_I"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* encrypt message, except for fixed part of header */
|
|
|
|
/* st_new_iv was computed by generate_skeyids_iv */
|
|
if (!encrypt_message(&md->rbody, st))
|
|
{
|
|
return STF_INTERNAL_ERROR; /* ??? we may be partly committed */
|
|
}
|
|
return STF_OK;
|
|
}
|
|
|
|
/* Shared logic for asynchronous lookup of DNS KEY records.
|
|
* Used for STATE_MAIN_R2 and STATE_MAIN_I3.
|
|
*/
|
|
|
|
enum key_oppo_step {
|
|
kos_null,
|
|
kos_his_txt
|
|
#ifdef USE_KEYRR
|
|
, kos_his_key
|
|
#endif
|
|
};
|
|
|
|
struct key_continuation {
|
|
struct adns_continuation ac; /* common prefix */
|
|
struct msg_digest *md;
|
|
enum key_oppo_step step;
|
|
bool failure_ok;
|
|
err_t last_ugh;
|
|
};
|
|
|
|
typedef stf_status (key_tail_fn)(struct msg_digest *md
|
|
, struct key_continuation *kc);
|
|
|
|
static void report_key_dns_failure(identification_t *id, err_t ugh)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "no RSA public key known for '%Y'"
|
|
"; DNS search for KEY failed (%s)", id, ugh);
|
|
}
|
|
|
|
|
|
/* Processs the Main Mode ID Payload and the Authenticator
|
|
* (Hash or Signature Payload).
|
|
* If a DNS query is still needed to get the other host's public key,
|
|
* the query is initiated and STF_SUSPEND is returned.
|
|
* Note: parameter kc is a continuation containing the results from
|
|
* the previous DNS query, or NULL indicating no query has been issued.
|
|
*/
|
|
static stf_status
|
|
main_id_and_auth(struct msg_digest *md
|
|
, bool initiator /* are we the Initiator? */
|
|
, cont_fn_t cont_fn /* continuation function */
|
|
, const struct key_continuation *kc /* current state, can be NULL */
|
|
)
|
|
{
|
|
chunk_t hash = chunk_alloca(MAX_DIGEST_LEN);
|
|
struct state *st = md->st;
|
|
identification_t *peer;
|
|
stf_status r = STF_OK;
|
|
|
|
/* ID Payload in */
|
|
if (!decode_peer_id(md, &peer))
|
|
{
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
|
|
/* Hash the ID Payload.
|
|
* main_mode_hash requires idpl->cur to be at end of payload
|
|
* so we temporarily set if so.
|
|
*/
|
|
{
|
|
pb_stream *idpl = &md->chain[ISAKMP_NEXT_ID]->pbs;
|
|
u_int8_t *old_cur = idpl->cur;
|
|
|
|
idpl->cur = idpl->roof;
|
|
main_mode_hash(st, &hash, !initiator, idpl);
|
|
idpl->cur = old_cur;
|
|
}
|
|
|
|
switch (st->st_oakley.auth)
|
|
{
|
|
case OAKLEY_PRESHARED_KEY:
|
|
case XAUTHInitPreShared:
|
|
case XAUTHRespPreShared:
|
|
{
|
|
pb_stream *const hash_pbs = &md->chain[ISAKMP_NEXT_HASH]->pbs;
|
|
|
|
if (pbs_left(hash_pbs) != hash.len
|
|
|| memcmp(hash_pbs->cur, hash.ptr, hash.len) != 0)
|
|
{
|
|
DBG_cond_dump(DBG_CRYPT, "received HASH:"
|
|
, hash_pbs->cur, pbs_left(hash_pbs));
|
|
loglog(RC_LOG_SERIOUS, "received Hash Payload does not match computed value");
|
|
/* XXX Could send notification back */
|
|
r = STF_FAIL + ISAKMP_INVALID_HASH_INFORMATION;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OAKLEY_RSA_SIG:
|
|
case XAUTHInitRSA:
|
|
case XAUTHRespRSA:
|
|
r = check_signature(KEY_RSA, peer, st, hash,
|
|
&md->chain[ISAKMP_NEXT_SIG]->pbs,
|
|
#ifdef USE_KEYRR
|
|
kc == NULL ? NULL : kc->ac.keys_from_dns,
|
|
#endif /* USE_KEYRR */
|
|
kc == NULL ? NULL : kc->ac.gateways_from_dns
|
|
);
|
|
|
|
if (r == STF_SUSPEND)
|
|
{
|
|
/* initiate/resume asynchronous DNS lookup for key */
|
|
struct key_continuation *nkc = malloc_thing(struct key_continuation);
|
|
enum key_oppo_step step_done = kc == NULL? kos_null : kc->step;
|
|
err_t ugh = NULL;
|
|
|
|
/* Record that state is used by a suspended md */
|
|
passert(st->st_suspended_md == NULL);
|
|
st->st_suspended_md = md;
|
|
|
|
nkc->failure_ok = FALSE;
|
|
nkc->md = md;
|
|
|
|
switch (step_done)
|
|
{
|
|
case kos_null:
|
|
/* first try: look for the TXT records */
|
|
nkc->step = kos_his_txt;
|
|
#ifdef USE_KEYRR
|
|
nkc->failure_ok = TRUE;
|
|
#endif
|
|
ugh = start_adns_query(peer, peer, T_TXT, cont_fn, &nkc->ac);
|
|
break;
|
|
|
|
#ifdef USE_KEYRR
|
|
case kos_his_txt:
|
|
/* second try: look for the KEY records */
|
|
nkc->step = kos_his_key;
|
|
ugh = start_adns_query(peer, NULL, T_KEY, cont_fn, &nkc->ac);
|
|
break;
|
|
#endif /* USE_KEYRR */
|
|
|
|
default:
|
|
bad_case(step_done);
|
|
}
|
|
|
|
if (ugh != NULL)
|
|
{
|
|
report_key_dns_failure(peer, ugh);
|
|
st->st_suspended_md = NULL;
|
|
r = STF_FAIL + ISAKMP_INVALID_KEY_INFORMATION;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case OAKLEY_ECDSA_256:
|
|
case OAKLEY_ECDSA_384:
|
|
case OAKLEY_ECDSA_521:
|
|
r = check_signature(KEY_ECDSA, peer, st, hash,
|
|
&md->chain[ISAKMP_NEXT_SIG]->pbs,
|
|
#ifdef USE_KEYRR
|
|
NULL,
|
|
#endif /* USE_KEYRR */
|
|
NULL);
|
|
break;
|
|
|
|
default:
|
|
bad_case(st->st_oakley.auth);
|
|
}
|
|
if (r != STF_OK)
|
|
{
|
|
peer->destroy(peer);
|
|
return r;
|
|
}
|
|
DBG(DBG_CRYPT, DBG_log("authentication succeeded"));
|
|
|
|
/*
|
|
* With the peer ID known, let's see if we need to switch connections.
|
|
*/
|
|
if (!switch_connection(md, peer, initiator))
|
|
{
|
|
r = STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
peer->destroy(peer);
|
|
return r;
|
|
}
|
|
|
|
/* This continuation is called as part of either
|
|
* the main_inI3_outR3 state or main_inR3 state.
|
|
*
|
|
* The "tail" function is the corresponding tail
|
|
* function main_inI3_outR3_tail | main_inR3_tail,
|
|
* either directly when the state is started, or via
|
|
* adns continuation.
|
|
*
|
|
* Basically, we go around in a circle:
|
|
* main_in?3* -> key_continue
|
|
* ^ \
|
|
* / V
|
|
* adns main_in?3*_tail
|
|
* ^ |
|
|
* \ V
|
|
* main_id_and_auth
|
|
*
|
|
* until such time as main_id_and_auth is able
|
|
* to find authentication, or we run out of things
|
|
* to try.
|
|
*/
|
|
static void key_continue(struct adns_continuation *cr, err_t ugh,
|
|
key_tail_fn *tail)
|
|
{
|
|
struct key_continuation *kc = (void *)cr;
|
|
struct state *st = kc->md->st;
|
|
|
|
passert(cur_state == NULL);
|
|
|
|
/* if st == NULL, our state has been deleted -- just clean up */
|
|
if (st != NULL)
|
|
{
|
|
stf_status r;
|
|
|
|
passert(st->st_suspended_md == kc->md);
|
|
st->st_suspended_md = NULL; /* no longer connected or suspended */
|
|
cur_state = st;
|
|
|
|
if (!kc->failure_ok && ugh != NULL)
|
|
{
|
|
report_key_dns_failure(st->st_connection->spd.that.id, ugh);
|
|
r = STF_FAIL + ISAKMP_INVALID_KEY_INFORMATION;
|
|
}
|
|
else
|
|
{
|
|
|
|
#ifdef USE_KEYRR
|
|
passert(kc->step == kos_his_txt || kc->step == kos_his_key);
|
|
#else
|
|
passert(kc->step == kos_his_txt);
|
|
#endif
|
|
kc->last_ugh = ugh; /* record previous error in case we need it */
|
|
r = (*tail)(kc->md, kc);
|
|
}
|
|
complete_state_transition(&kc->md, r);
|
|
}
|
|
if (kc->md != NULL)
|
|
{
|
|
release_md(kc->md);
|
|
}
|
|
cur_state = NULL;
|
|
}
|
|
|
|
/* STATE_MAIN_R2:
|
|
* PSK_AUTH: HDR*, IDi1, HASH_I --> HDR*, IDr1, HASH_R
|
|
* DS_AUTH: HDR*, IDi1, [ CERT, ] SIG_I --> HDR*, IDr1, [ CERT, ] SIG_R
|
|
* PKE_AUTH, RPKE_AUTH: HDR*, HASH_I --> HDR*, HASH_R
|
|
*
|
|
* Broken into parts to allow asynchronous DNS lookup.
|
|
*
|
|
* - main_inI3_outR3 to start
|
|
* - main_inI3_outR3_tail to finish or suspend for DNS lookup
|
|
* - main_inI3_outR3_continue to start main_inI3_outR3_tail again
|
|
*/
|
|
static key_tail_fn main_inI3_outR3_tail; /* forward */
|
|
|
|
stf_status main_inI3_outR3(struct msg_digest *md)
|
|
{
|
|
return main_inI3_outR3_tail(md, NULL);
|
|
}
|
|
|
|
static void main_inI3_outR3_continue(struct adns_continuation *cr, err_t ugh)
|
|
{
|
|
key_continue(cr, ugh, main_inI3_outR3_tail);
|
|
}
|
|
|
|
static stf_status
|
|
main_inI3_outR3_tail(struct msg_digest *md
|
|
, struct key_continuation *kc)
|
|
{
|
|
struct state *const st = md->st;
|
|
u_int8_t auth_payload;
|
|
pb_stream r_id_pbs; /* ID Payload; also used for hash calculation */
|
|
certpolicy_t cert_policy;
|
|
cert_t *mycert;
|
|
bool pubkey_auth, send_cert, requested;
|
|
|
|
/* ID and HASH_I or SIG_I in
|
|
* Note: this may switch the connection being used!
|
|
*/
|
|
{
|
|
stf_status r = main_id_and_auth(md, FALSE
|
|
, main_inI3_outR3_continue
|
|
, kc);
|
|
|
|
if (r != STF_OK)
|
|
{
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/* send certificate if pubkey authentication is used, we have one
|
|
* and we want or are requested to send it
|
|
*/
|
|
cert_policy = st->st_connection->spd.this.sendcert;
|
|
mycert = st->st_connection->spd.this.cert;
|
|
requested = cert_policy == CERT_SEND_IF_ASKED
|
|
&& st->st_connection->got_certrequest;
|
|
pubkey_auth = uses_pubkey_auth(st->st_oakley.auth);
|
|
send_cert = pubkey_auth && mycert &&
|
|
mycert->cert->get_type(mycert->cert) == CERT_X509 &&
|
|
(cert_policy == CERT_ALWAYS_SEND || requested);
|
|
|
|
/*************** build output packet HDR*;IDir;HASH/SIG_R ***************/
|
|
/* proccess_packet() would automatically generate the HDR*
|
|
* payload if smc->first_out_payload is not ISAKMP_NEXT_NONE.
|
|
* We don't do this because we wish there to be no partially
|
|
* built output packet if we need to suspend for asynch DNS.
|
|
*/
|
|
/* ??? NOTE: this is almost the same as main_inR2_outI3's code */
|
|
|
|
/* HDR* out
|
|
* If auth were PKE_AUTH or RPKE_AUTH, ISAKMP_NEXT_HASH would
|
|
* be first payload.
|
|
*/
|
|
echo_hdr(md, TRUE, ISAKMP_NEXT_ID);
|
|
|
|
auth_payload = pubkey_auth ? ISAKMP_NEXT_SIG : ISAKMP_NEXT_HASH;
|
|
|
|
/* IDir out */
|
|
{
|
|
/* id_hd should be struct isakmp_id, but struct isakmp_ipsec_id
|
|
* allows build_id_payload() to work for both phases.
|
|
*/
|
|
struct isakmp_ipsec_id id_hd;
|
|
chunk_t id_b;
|
|
|
|
build_id_payload(&id_hd, &id_b, &st->st_connection->spd.this);
|
|
id_hd.isaiid_np = (send_cert)? ISAKMP_NEXT_CERT : auth_payload;
|
|
if (!out_struct(&id_hd, &isakmp_ipsec_identification_desc, &md->rbody, &r_id_pbs)
|
|
|| !out_chunk(id_b, &r_id_pbs, "my identity"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
close_output_pbs(&r_id_pbs);
|
|
}
|
|
|
|
/* CERT out */
|
|
if (pubkey_auth)
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("our certificate policy is %N", cert_policy_names, cert_policy)
|
|
)
|
|
if (mycert && mycert->cert->get_type(mycert->cert) == CERT_X509)
|
|
{
|
|
const char *request_text = "";
|
|
|
|
if (cert_policy == CERT_SEND_IF_ASKED)
|
|
{
|
|
request_text = (send_cert)? "upon request":"without request";
|
|
}
|
|
plog("we have a cert %s sending it %s"
|
|
, send_cert? "and are":"but are not", request_text);
|
|
}
|
|
else
|
|
{
|
|
plog("we don't have a cert");
|
|
}
|
|
}
|
|
if (send_cert)
|
|
{
|
|
bool success;
|
|
chunk_t cert_encoding;
|
|
pb_stream cert_pbs;
|
|
struct isakmp_cert cert_hd;
|
|
|
|
cert_hd.isacert_np = ISAKMP_NEXT_SIG;
|
|
cert_hd.isacert_type = CERT_X509_SIGNATURE;
|
|
|
|
if (!out_struct(&cert_hd, &isakmp_ipsec_certificate_desc, &md->rbody, &cert_pbs))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
cert_encoding = mycert->cert->get_encoding(mycert->cert);
|
|
success = out_chunk(cert_encoding, &cert_pbs, "CERT");
|
|
free(cert_encoding.ptr);
|
|
if (!success)
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
close_output_pbs(&cert_pbs);
|
|
}
|
|
|
|
/* HASH_R or SIG_R out */
|
|
{
|
|
chunk_t hash = chunk_alloca(MAX_DIGEST_LEN);
|
|
|
|
main_mode_hash(st, &hash, FALSE, &r_id_pbs);
|
|
|
|
if (auth_payload == ISAKMP_NEXT_HASH)
|
|
{
|
|
/* HASH_R out */
|
|
if (!out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_hash_desc, &md->rbody,
|
|
hash.ptr, hash.len, "HASH_R"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* SIG_R out */
|
|
u_char sig_val[RSA_MAX_OCTETS];
|
|
signature_scheme_t scheme;
|
|
size_t sig_len;
|
|
|
|
scheme = oakley_to_signature_scheme(st->st_oakley.auth);
|
|
|
|
sig_len = sign_hash(scheme, st->st_connection, sig_val, hash);
|
|
if (sig_len == 0)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "unable to locate my private key for signature");
|
|
return STF_FAIL + ISAKMP_AUTHENTICATION_FAILED;
|
|
}
|
|
|
|
if (!out_generic_raw(ISAKMP_NEXT_NONE, &isakmp_signature_desc
|
|
, &md->rbody, sig_val, sig_len, "SIG_R"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* encrypt message, sans fixed part of header */
|
|
|
|
if (!encrypt_message(&md->rbody, st))
|
|
{
|
|
return STF_INTERNAL_ERROR; /* ??? we may be partly committed */
|
|
}
|
|
|
|
/* Last block of Phase 1 (R3), kept for Phase 2 IV generation */
|
|
DBG_cond_dump(DBG_CRYPT, "last encrypted block of Phase 1:"
|
|
, st->st_new_iv, st->st_new_iv_len);
|
|
|
|
ISAKMP_SA_established(st->st_connection, st->st_serialno);
|
|
|
|
/* Save Phase 1 IV */
|
|
st->st_ph1_iv_len = st->st_new_iv_len;
|
|
set_ph1_iv(st, st->st_new_iv);
|
|
|
|
return STF_OK;
|
|
}
|
|
|
|
/* STATE_MAIN_I3:
|
|
* Handle HDR*;IDir;HASH/SIG_R from responder.
|
|
*
|
|
* Broken into parts to allow asynchronous DNS for KEY records.
|
|
*
|
|
* - main_inR3 to start
|
|
* - main_inR3_tail to finish or suspend for DNS lookup
|
|
* - main_inR3_continue to start main_inR3_tail again
|
|
*/
|
|
|
|
static key_tail_fn main_inR3_tail; /* forward */
|
|
|
|
stf_status main_inR3(struct msg_digest *md)
|
|
{
|
|
return main_inR3_tail(md, NULL);
|
|
}
|
|
|
|
static void main_inR3_continue(struct adns_continuation *cr, err_t ugh)
|
|
{
|
|
key_continue(cr, ugh, main_inR3_tail);
|
|
}
|
|
|
|
static stf_status main_inR3_tail(struct msg_digest *md,
|
|
struct key_continuation *kc)
|
|
{
|
|
struct state *const st = md->st;
|
|
|
|
/* ID and HASH_R or SIG_R in
|
|
* Note: this may switch the connection being used!
|
|
*/
|
|
{
|
|
stf_status r = main_id_and_auth(md, TRUE, main_inR3_continue, kc);
|
|
|
|
if (r != STF_OK)
|
|
{
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/**************** done input ****************/
|
|
|
|
ISAKMP_SA_established(st->st_connection, st->st_serialno);
|
|
|
|
/* Save Phase 1 IV */
|
|
st->st_ph1_iv_len = st->st_new_iv_len;
|
|
set_ph1_iv(st, st->st_new_iv);
|
|
|
|
|
|
update_iv(st); /* finalize our Phase 1 IV */
|
|
|
|
return STF_OK;
|
|
}
|
|
|
|
/* Handle first message of Phase 2 -- Quick Mode.
|
|
* HDR*, HASH(1), SA, Ni [, KE ] [, IDci, IDcr ] -->
|
|
* HDR*, HASH(2), SA, Nr [, KE ] [, IDci, IDcr ]
|
|
* (see RFC 2409 "IKE" 5.5)
|
|
* Installs inbound IPsec SAs.
|
|
* Although this seems early, we know enough to do so, and
|
|
* this way we know that it is soon enough to catch all
|
|
* packets that other side could send using this IPsec SA.
|
|
*
|
|
* Broken into parts to allow asynchronous DNS for TXT records:
|
|
*
|
|
* - quick_inI1_outR1 starts the ball rolling.
|
|
* It checks and parses enough to learn the Phase 2 IDs
|
|
*
|
|
* - quick_inI1_outR1_tail does the rest of the job
|
|
* unless DNS must be consulted. In that case,
|
|
* it starts a DNS query, salts away what is needed
|
|
* to continue, and suspends. Calls
|
|
* + quick_inI1_outR1_start_query
|
|
* + quick_inI1_outR1_process_answer
|
|
*
|
|
* - quick_inI1_outR1_continue will restart quick_inI1_outR1_tail
|
|
* when DNS comes back with an answer.
|
|
*
|
|
* A big chunk of quick_inI1_outR1_tail is executed twice.
|
|
* This is necessary because the set of connections
|
|
* might change while we are awaiting DNS.
|
|
* When first called, gateways_from_dns == NULL. If DNS is
|
|
* consulted asynchronously, gateways_from_dns != NULL the second time.
|
|
* Remember that our state object might disappear too!
|
|
*
|
|
*
|
|
* If the connection is opportunistic, we must verify delegation.
|
|
*
|
|
* 1. Check that we are authorized to be SG for
|
|
* our client. We look for the TXT record that
|
|
* delegates us. We also check that the public
|
|
* key (if present) matches the private key we used.
|
|
* Eventually, we should probably require DNSsec
|
|
* authentication for our side.
|
|
*
|
|
* 2. If our client TXT record did not include a
|
|
* public key, check the KEY record indicated
|
|
* by the identity in the TXT record.
|
|
*
|
|
* 3. If the peer's client is the peer itself, we
|
|
* consider it authenticated. Otherwise, we check
|
|
* the TXT record for the client to see that
|
|
* the identity of the SG matches the peer and
|
|
* that some public key (if present in the TXT)
|
|
* matches. We need not check the public key if
|
|
* it isn't in the TXT record.
|
|
*
|
|
* Since p isn't yet instantiated, we need to look
|
|
* in c for description of peer.
|
|
*
|
|
* We cannot afford to block waiting for a DNS query.
|
|
* The code here is structured as two halves:
|
|
* - process the result of just completed
|
|
* DNS query (if any)
|
|
* - if another query is needed, initiate the next
|
|
* DNS query and suspend
|
|
*/
|
|
|
|
enum verify_oppo_step {
|
|
vos_fail,
|
|
vos_start,
|
|
vos_our_client,
|
|
vos_our_txt,
|
|
#ifdef USE_KEYRR
|
|
vos_our_key,
|
|
#endif /* USE_KEYRR */
|
|
vos_his_client,
|
|
vos_done
|
|
};
|
|
|
|
static const char *const verify_step_name[] = {
|
|
"vos_fail",
|
|
"vos_start",
|
|
"vos_our_client",
|
|
"vos_our_txt",
|
|
#ifdef USE_KEYRR
|
|
"vos_our_key",
|
|
#endif /* USE_KEYRR */
|
|
"vos_his_client",
|
|
"vos_done"
|
|
};
|
|
|
|
/* hold anything we can handle of a Phase 2 ID */
|
|
struct p2id {
|
|
ip_subnet net;
|
|
u_int8_t proto;
|
|
u_int16_t port;
|
|
};
|
|
|
|
struct verify_oppo_bundle {
|
|
enum verify_oppo_step step;
|
|
bool failure_ok; /* if true, quick_inI1_outR1_continue will try
|
|
* other things on DNS failure */
|
|
struct msg_digest *md;
|
|
struct p2id my, his;
|
|
unsigned int new_iv_len; /* p1st's might change */
|
|
u_char new_iv[MAX_DIGEST_LEN];
|
|
/* int whackfd; */ /* not needed because we are Responder */
|
|
};
|
|
|
|
struct verify_oppo_continuation {
|
|
struct adns_continuation ac; /* common prefix */
|
|
struct verify_oppo_bundle b;
|
|
};
|
|
|
|
static stf_status quick_inI1_outR1_tail(struct verify_oppo_bundle *b
|
|
, struct adns_continuation *ac);
|
|
|
|
stf_status quick_inI1_outR1(struct msg_digest *md)
|
|
{
|
|
const struct state *const p1st = md->st;
|
|
connection_t *c = p1st->st_connection;
|
|
struct payload_digest *const id_pd = md->chain[ISAKMP_NEXT_ID];
|
|
struct verify_oppo_bundle b;
|
|
|
|
/* HASH(1) in */
|
|
CHECK_QUICK_HASH(md
|
|
, quick_mode_hash12(hash_val, hash_pbs->roof, md->message_pbs.roof
|
|
, p1st, &md->hdr.isa_msgid, FALSE)
|
|
, "HASH(1)", "Quick I1");
|
|
|
|
/* [ IDci, IDcr ] in
|
|
* We do this now (probably out of physical order) because
|
|
* we wish to select the correct connection before we consult
|
|
* it for policy.
|
|
*/
|
|
|
|
if (id_pd != NULL)
|
|
{
|
|
/* ??? we are assuming IPSEC_DOI */
|
|
|
|
/* IDci (initiator is peer) */
|
|
|
|
if (!decode_net_id(&id_pd->payload.ipsec_id, &id_pd->pbs
|
|
, &b.his.net, "peer client"))
|
|
{
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
|
|
/* Hack for MS 818043 NAT-T Update */
|
|
|
|
if (id_pd->payload.ipsec_id.isaiid_idtype == ID_FQDN)
|
|
{
|
|
happy(addrtosubnet(&c->spd.that.host_addr, &b.his.net));
|
|
}
|
|
|
|
/* End Hack for MS 818043 NAT-T Update */
|
|
|
|
b.his.proto = id_pd->payload.ipsec_id.isaiid_protoid;
|
|
b.his.port = id_pd->payload.ipsec_id.isaiid_port;
|
|
b.his.net.addr.u.v4.sin_port = htons(b.his.port);
|
|
|
|
/* IDcr (we are responder) */
|
|
|
|
if (!decode_net_id(&id_pd->next->payload.ipsec_id, &id_pd->next->pbs
|
|
, &b.my.net, "our client"))
|
|
{
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
b.my.proto = id_pd->next->payload.ipsec_id.isaiid_protoid;
|
|
b.my.port = id_pd->next->payload.ipsec_id.isaiid_port;
|
|
b.my.net.addr.u.v4.sin_port = htons(b.my.port);
|
|
}
|
|
else
|
|
{
|
|
/* implicit IDci and IDcr: peer and self */
|
|
if (!sameaddrtype(&c->spd.this.host_addr, &c->spd.that.host_addr))
|
|
{
|
|
return STF_FAIL;
|
|
}
|
|
happy(addrtosubnet(&c->spd.this.host_addr, &b.my.net));
|
|
happy(addrtosubnet(&c->spd.that.host_addr, &b.his.net));
|
|
b.his.proto = b.my.proto = 0;
|
|
b.his.port = b.my.port = 0;
|
|
}
|
|
b.step = vos_start;
|
|
b.md = md;
|
|
b.new_iv_len = p1st->st_new_iv_len;
|
|
memcpy(b.new_iv, p1st->st_new_iv, p1st->st_new_iv_len);
|
|
return quick_inI1_outR1_tail(&b, NULL);
|
|
}
|
|
|
|
static void
|
|
report_verify_failure(struct verify_oppo_bundle *b, err_t ugh)
|
|
{
|
|
struct state *st = b->md->st;
|
|
char fgwb[ADDRTOT_BUF]
|
|
, cb[ADDRTOT_BUF];
|
|
ip_address client;
|
|
err_t which = NULL;
|
|
|
|
switch (b->step)
|
|
{
|
|
case vos_our_client:
|
|
case vos_our_txt:
|
|
#ifdef USE_KEYRR
|
|
case vos_our_key:
|
|
#endif /* USE_KEYRR */
|
|
which = "our";
|
|
networkof(&b->my.net, &client);
|
|
break;
|
|
|
|
case vos_his_client:
|
|
which = "his";
|
|
networkof(&b->his.net, &client);
|
|
break;
|
|
|
|
case vos_start:
|
|
case vos_done:
|
|
case vos_fail:
|
|
default:
|
|
bad_case(b->step);
|
|
}
|
|
|
|
addrtot(&st->st_connection->spd.that.host_addr, 0, fgwb, sizeof(fgwb));
|
|
addrtot(&client, 0, cb, sizeof(cb));
|
|
loglog(RC_OPPOFAILURE
|
|
, "gateway %s wants connection with %s as %s client, but DNS fails to confirm delegation: %s"
|
|
, fgwb, cb, which, ugh);
|
|
}
|
|
|
|
static void quick_inI1_outR1_continue(struct adns_continuation *cr, err_t ugh)
|
|
{
|
|
stf_status r;
|
|
struct verify_oppo_continuation *vc = (void *)cr;
|
|
struct verify_oppo_bundle *b = &vc->b;
|
|
struct state *st = b->md->st;
|
|
|
|
passert(cur_state == NULL);
|
|
/* if st == NULL, our state has been deleted -- just clean up */
|
|
if (st != NULL)
|
|
{
|
|
passert(st->st_suspended_md == b->md);
|
|
st->st_suspended_md = NULL; /* no longer connected or suspended */
|
|
cur_state = st;
|
|
if (!b->failure_ok && ugh != NULL)
|
|
{
|
|
report_verify_failure(b, ugh);
|
|
r = STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
else
|
|
{
|
|
r = quick_inI1_outR1_tail(b, cr);
|
|
}
|
|
complete_state_transition(&b->md, r);
|
|
}
|
|
if (b->md != NULL)
|
|
{
|
|
release_md(b->md);
|
|
}
|
|
cur_state = NULL;
|
|
}
|
|
|
|
static stf_status quick_inI1_outR1_start_query(struct verify_oppo_bundle *b,
|
|
enum verify_oppo_step next_step)
|
|
{
|
|
struct msg_digest *md = b->md;
|
|
struct state *p1st = md->st;
|
|
connection_t *c = p1st->st_connection;
|
|
struct verify_oppo_continuation *vc = malloc_thing(struct verify_oppo_continuation);
|
|
identification_t *id; /* subject of query */
|
|
identification_t *our_id; /* needed for myid playing */
|
|
identification_t *our_id_space; /* ephemeral: no need for unshare_id_content */
|
|
ip_address client;
|
|
err_t ugh = NULL;
|
|
|
|
/* Record that state is used by a suspended md */
|
|
b->step = next_step; /* not just vc->b.step */
|
|
vc->b = *b;
|
|
passert(p1st->st_suspended_md == NULL);
|
|
p1st->st_suspended_md = b->md;
|
|
|
|
DBG(DBG_CONTROL,
|
|
{
|
|
char ours[SUBNETTOT_BUF];
|
|
char his[SUBNETTOT_BUF];
|
|
|
|
subnettot(&c->spd.this.client, 0, ours, sizeof(ours));
|
|
subnettot(&c->spd.that.client, 0, his, sizeof(his));
|
|
|
|
DBG_log("responding with DNS query - from %s to %s new state: %s"
|
|
, ours, his, verify_step_name[b->step]);
|
|
});
|
|
|
|
/* Resolve %myid in a cheesy way.
|
|
* We have to do the resolution because start_adns_query
|
|
* et al have insufficient information to do so.
|
|
* If %myid is already known, we'll use that value
|
|
* (XXX this may be a mistake: it could be stale).
|
|
* If %myid is unknown, we should check to see if
|
|
* there are credentials for the IP address or the FQDN.
|
|
* Instead, we'll just assume the IP address since we are
|
|
* acting as the responder and only the IP address would
|
|
* have gotten it to us.
|
|
* We don't even try to do this for the other side:
|
|
* %myid makes no sense for the other side (but it is syntactically
|
|
* legal).
|
|
*/
|
|
our_id = resolve_myid(c->spd.this.id);
|
|
if (our_id->get_type(our_id) == ID_ANY)
|
|
{
|
|
our_id_space = identification_create_from_sockaddr((sockaddr_t*)&c->spd.this.host_addr);
|
|
our_id = our_id_space;
|
|
}
|
|
|
|
switch (next_step)
|
|
{
|
|
case vos_our_client:
|
|
networkof(&b->my.net, &client);
|
|
id = identification_create_from_sockaddr((sockaddr_t*)&client);
|
|
vc->b.failure_ok = b->failure_ok = FALSE;
|
|
ugh = start_adns_query(id
|
|
, our_id
|
|
, T_TXT
|
|
, quick_inI1_outR1_continue
|
|
, &vc->ac);
|
|
break;
|
|
|
|
case vos_our_txt:
|
|
vc->b.failure_ok = b->failure_ok = TRUE;
|
|
ugh = start_adns_query(our_id
|
|
, our_id /* self as SG */
|
|
, T_TXT
|
|
, quick_inI1_outR1_continue
|
|
, &vc->ac);
|
|
break;
|
|
|
|
#ifdef USE_KEYRR
|
|
case vos_our_key:
|
|
vc->b.failure_ok = b->failure_ok = FALSE;
|
|
ugh = start_adns_query(our_id
|
|
, NULL
|
|
, T_KEY
|
|
, quick_inI1_outR1_continue
|
|
, &vc->ac);
|
|
break;
|
|
#endif
|
|
|
|
case vos_his_client:
|
|
networkof(&b->his.net, &client);
|
|
id = identification_create_from_sockaddr((sockaddr_t*)&client);
|
|
vc->b.failure_ok = b->failure_ok = FALSE;
|
|
ugh = start_adns_query(id
|
|
, c->spd.that.id
|
|
, T_TXT
|
|
, quick_inI1_outR1_continue
|
|
, &vc->ac);
|
|
break;
|
|
|
|
default:
|
|
bad_case(next_step);
|
|
}
|
|
|
|
if (ugh != NULL)
|
|
{
|
|
/* note: we'd like to use vc->b but vc has been freed
|
|
* so we have to use b. This is why we plunked next_state
|
|
* into b, not just vc->b.
|
|
*/
|
|
report_verify_failure(b, ugh);
|
|
p1st->st_suspended_md = NULL;
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
else
|
|
{
|
|
return STF_SUSPEND;
|
|
}
|
|
}
|
|
|
|
static enum verify_oppo_step quick_inI1_outR1_process_answer(
|
|
struct verify_oppo_bundle *b,
|
|
struct adns_continuation *ac,
|
|
struct state *p1st)
|
|
{
|
|
connection_t *c = p1st->st_connection;
|
|
enum verify_oppo_step next_step = vos_our_client;
|
|
err_t ugh = NULL;
|
|
|
|
DBG(DBG_CONTROL,
|
|
{
|
|
char ours[SUBNETTOT_BUF];
|
|
char his[SUBNETTOT_BUF];
|
|
|
|
subnettot(&c->spd.this.client, 0, ours, sizeof(ours));
|
|
subnettot(&c->spd.that.client, 0, his, sizeof(his));
|
|
DBG_log("responding on demand from %s to %s state: %s"
|
|
, ours, his, verify_step_name[b->step]);
|
|
});
|
|
|
|
/* process just completed DNS query (if any) */
|
|
switch (b->step)
|
|
{
|
|
case vos_start:
|
|
/* no query to digest */
|
|
next_step = vos_our_client;
|
|
break;
|
|
|
|
case vos_our_client:
|
|
next_step = vos_his_client;
|
|
{
|
|
private_key_t *private = get_private_key(c);
|
|
struct gw_info *gwp;
|
|
|
|
if (private == NULL)
|
|
{
|
|
ugh = "we don't know our own key";
|
|
break;
|
|
}
|
|
ugh = "our client does not delegate us as its Security Gateway";
|
|
for (gwp = ac->gateways_from_dns; gwp != NULL; gwp = gwp->next)
|
|
{
|
|
ugh = "our client delegates us as its Security Gateway but with the wrong public key";
|
|
/* If there is no key in the TXT record,
|
|
* we count it as a win, but we will have
|
|
* to separately fetch and check the KEY record.
|
|
* If there is a key from the TXT record,
|
|
* we count it as a win if we match the key.
|
|
*/
|
|
if (!gwp->gw_key_present)
|
|
{
|
|
next_step = vos_our_txt;
|
|
ugh = NULL; /* good! */
|
|
break;
|
|
}
|
|
else if (private->belongs_to(private, gwp->key->public_key))
|
|
{
|
|
ugh = NULL; /* good! */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case vos_our_txt:
|
|
next_step = vos_his_client;
|
|
{
|
|
private_key_t *private = get_private_key(c);
|
|
|
|
if (private == NULL)
|
|
{
|
|
ugh = "we don't know our own key";
|
|
break;
|
|
}
|
|
{
|
|
struct gw_info *gwp;
|
|
|
|
for (gwp = ac->gateways_from_dns; gwp != NULL; gwp = gwp->next)
|
|
{
|
|
#ifdef USE_KEYRR
|
|
/* not an error yet, because we have to check KEY RR as well */
|
|
ugh = NULL;
|
|
#else
|
|
ugh = "our client delegation depends on our " RRNAME " record, but it has the wrong public key";
|
|
#endif
|
|
if (gwp->gw_key_present
|
|
&& private->belongs_to(private, gwp->key->public_key))
|
|
{
|
|
ugh = NULL; /* good! */
|
|
break;
|
|
}
|
|
#ifdef USE_KEYRR
|
|
next_step = vos_our_key;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
#ifdef USE_KEYRR
|
|
case vos_our_key:
|
|
next_step = vos_his_client;
|
|
{
|
|
private_key_t *private = get_private_key(c);
|
|
|
|
if (private == NULL)
|
|
{
|
|
ugh = "we don't know our own key";
|
|
break;
|
|
}
|
|
{
|
|
pubkey_list_t *kp;
|
|
|
|
ugh = "our client delegation depends on our missing " RRNAME " record";
|
|
for (kp = ac->keys_from_dns; kp != NULL; kp = kp->next)
|
|
{
|
|
ugh = "our client delegation depends on our " RRNAME " record, but it has the wrong public key";
|
|
if (private->belongs_to(private, kp->key->public_key))
|
|
{
|
|
/* do this only once a day */
|
|
if (!logged_txt_warning)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "found KEY RR but not TXT RR. See http://www.freeswan.org/err/txt-change.html.");
|
|
logged_txt_warning = TRUE;
|
|
}
|
|
ugh = NULL; /* good! */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
#endif /* USE_KEYRR */
|
|
|
|
case vos_his_client:
|
|
next_step = vos_done;
|
|
{
|
|
public_key_t *pub_key;
|
|
struct gw_info *gwp;
|
|
|
|
/* check that the public key that authenticated
|
|
* the ISAKMP SA (p1st) will do for this gateway.
|
|
*/
|
|
pub_key = p1st->st_peer_pubkey->public_key;
|
|
|
|
ugh = "peer's client does not delegate to peer";
|
|
for (gwp = ac->gateways_from_dns; gwp != NULL; gwp = gwp->next)
|
|
{
|
|
ugh = "peer and its client disagree about public key";
|
|
/* If there is a key from the TXT record,
|
|
* we count it as a win if we match the key.
|
|
* If there was no key, we claim a match since
|
|
* it implies fetching a KEY from the same
|
|
* place we must have gotten it.
|
|
*/
|
|
if (!gwp->gw_key_present ||
|
|
pub_key->equals(pub_key, gwp->key->public_key))
|
|
{
|
|
ugh = NULL; /* good! */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
bad_case(b->step);
|
|
}
|
|
|
|
if (ugh != NULL)
|
|
{
|
|
report_verify_failure(b, ugh);
|
|
next_step = vos_fail;
|
|
}
|
|
return next_step;
|
|
}
|
|
|
|
static stf_status quick_inI1_outR1_tail(struct verify_oppo_bundle *b,
|
|
struct adns_continuation *ac)
|
|
{
|
|
struct msg_digest *md = b->md;
|
|
struct state *const p1st = md->st;
|
|
connection_t *c = p1st->st_connection;
|
|
struct payload_digest *const id_pd = md->chain[ISAKMP_NEXT_ID];
|
|
ip_subnet *our_net = &b->my.net
|
|
, *his_net = &b->his.net;
|
|
|
|
u_char /* set by START_HASH_PAYLOAD: */
|
|
*r_hashval, /* where in reply to jam hash value */
|
|
*r_hash_start; /* from where to start hashing */
|
|
|
|
/* Now that we have identities of client subnets, we must look for
|
|
* a suitable connection (our current one only matches for hosts).
|
|
*/
|
|
{
|
|
connection_t *p = find_client_connection(c
|
|
, our_net, his_net, b->my.proto, b->my.port, b->his.proto, b->his.port);
|
|
|
|
if (p == NULL)
|
|
{
|
|
/* This message occurs in very puzzling circumstances
|
|
* so we must add as much information and beauty as we can.
|
|
*/
|
|
struct end
|
|
me = c->spd.this,
|
|
he = c->spd.that;
|
|
char buf[2*SUBNETTOT_BUF + 2*ADDRTOT_BUF + 2*BUF_LEN + 2*ADDRTOT_BUF + 12]; /* + 12 for separating */
|
|
size_t l;
|
|
|
|
me.client = *our_net;
|
|
me.has_client = !subnetisaddr(our_net, &me.host_addr);
|
|
me.protocol = b->my.proto;
|
|
me.port = b->my.port;
|
|
|
|
he.client = *his_net;
|
|
he.has_client = !subnetisaddr(his_net, &he.host_addr);
|
|
he.protocol = b->his.proto;
|
|
he.port = b->his.port;
|
|
|
|
l = format_end(buf, sizeof(buf), &me, NULL, TRUE, LEMPTY);
|
|
l += snprintf(buf + l, sizeof(buf) - l, "...");
|
|
(void)format_end(buf + l, sizeof(buf) - l, &he, NULL, FALSE, LEMPTY);
|
|
plog("cannot respond to IPsec SA request"
|
|
" because no connection is known for %s"
|
|
, buf);
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
else if (p != c)
|
|
{
|
|
/* We've got a better connection: it can support the
|
|
* specified clients. But it may need instantiation.
|
|
*/
|
|
if (p->kind == CK_TEMPLATE)
|
|
{
|
|
/* Yup, it needs instantiation. How much?
|
|
* Is it a Road Warrior connection (simple)
|
|
* or is it an Opportunistic connection (needing gw validation)?
|
|
*/
|
|
if (p->policy & POLICY_OPPO)
|
|
{
|
|
/* Opportunistic case: delegation must be verified.
|
|
* Here be dragons.
|
|
*/
|
|
enum verify_oppo_step next_step;
|
|
ip_address our_client, his_client;
|
|
|
|
passert(subnetishost(our_net) && subnetishost(his_net));
|
|
networkof(our_net, &our_client);
|
|
networkof(his_net, &his_client);
|
|
|
|
next_step = quick_inI1_outR1_process_answer(b, ac, p1st);
|
|
if (next_step == vos_fail)
|
|
{
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
|
|
/* short circuit: if peer's client is self,
|
|
* accept that we've verified delegation in Phase 1
|
|
*/
|
|
if (next_step == vos_his_client
|
|
&& sameaddr(&c->spd.that.host_addr, &his_client))
|
|
{
|
|
next_step = vos_done;
|
|
}
|
|
|
|
/* the second chunk: initiate the next DNS query (if any) */
|
|
DBG(DBG_CONTROL,
|
|
{
|
|
char ours[SUBNETTOT_BUF];
|
|
char his[SUBNETTOT_BUF];
|
|
|
|
subnettot(&c->spd.this.client, 0, ours, sizeof(ours));
|
|
subnettot(&c->spd.that.client, 0, his, sizeof(his));
|
|
|
|
DBG_log("responding on demand from %s to %s new state: %s"
|
|
, ours, his, verify_step_name[next_step]);
|
|
});
|
|
|
|
/* start next DNS query and suspend (if necessary) */
|
|
if (next_step != vos_done)
|
|
{
|
|
return quick_inI1_outR1_start_query(b, next_step);
|
|
}
|
|
|
|
/* Instantiate inbound Opportunistic connection,
|
|
* carrying over authenticated peer ID
|
|
* and filling in a few more details.
|
|
* We used to include gateways_from_dns, but that
|
|
* seems pointless at this stage of negotiation.
|
|
* We should record DNS sec use, if any -- belongs in
|
|
* state during perhaps.
|
|
*/
|
|
p = oppo_instantiate(p, &c->spd.that.host_addr, c->spd.that.id
|
|
, NULL, &our_client, &his_client);
|
|
}
|
|
else
|
|
{
|
|
/* Plain Road Warrior:
|
|
* instantiate, carrying over authenticated peer ID
|
|
*/
|
|
p = rw_instantiate(p, &c->spd.that.host_addr, md->sender_port
|
|
, his_net, c->spd.that.id);
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
/* temporarily bump up cur_debugging to get "using..." message
|
|
* printed if we'd want it with new connection.
|
|
*/
|
|
{
|
|
lset_t old_cur_debugging = cur_debugging;
|
|
|
|
cur_debugging |= p->extra_debugging;
|
|
DBG(DBG_CONTROL, DBG_log("using connection \"%s\"", p->name));
|
|
cur_debugging = old_cur_debugging;
|
|
}
|
|
#endif
|
|
c = p;
|
|
}
|
|
/* fill in the client's true ip address/subnet */
|
|
if (p->spd.that.has_client_wildcard)
|
|
{
|
|
p->spd.that.client = *his_net;
|
|
p->spd.that.has_client_wildcard = FALSE;
|
|
}
|
|
else if (is_virtual_connection(c))
|
|
{
|
|
c->spd.that.client = *his_net;
|
|
c->spd.that.virt = NULL;
|
|
if (subnetishost(his_net) && addrinsubnet(&c->spd.that.host_addr, his_net))
|
|
{
|
|
c->spd.that.has_client = FALSE;
|
|
}
|
|
}
|
|
|
|
/* fill in the client's true port */
|
|
if (p->spd.that.has_port_wildcard)
|
|
{
|
|
int port = htons(b->his.port);
|
|
|
|
setportof(port, &p->spd.that.host_addr);
|
|
setportof(port, &p->spd.that.client.addr);
|
|
|
|
p->spd.that.port = b->his.port;
|
|
p->spd.that.has_port_wildcard = FALSE;
|
|
}
|
|
}
|
|
|
|
/* now that we are sure of our connection, create our new state */
|
|
{
|
|
struct state *const st = duplicate_state(p1st);
|
|
|
|
/* first: fill in missing bits of our new state object
|
|
* note: we don't copy over st_peer_pubkey, the public key
|
|
* that authenticated the ISAKMP SA. We only need it in this
|
|
* routine, so we can "reach back" to p1st to get it.
|
|
*/
|
|
|
|
if (st->st_connection != c)
|
|
{
|
|
connection_t *t = st->st_connection;
|
|
|
|
st->st_connection = c;
|
|
set_cur_connection(c);
|
|
connection_discard(t);
|
|
}
|
|
|
|
st->st_try = 0; /* not our job to try again from start */
|
|
|
|
st->st_msgid = md->hdr.isa_msgid;
|
|
|
|
st->st_new_iv_len = b->new_iv_len;
|
|
memcpy(st->st_new_iv, b->new_iv, b->new_iv_len);
|
|
|
|
set_cur_state(st); /* (caller will reset) */
|
|
md->st = st; /* feed back new state */
|
|
|
|
st->st_peeruserprotoid = b->his.proto;
|
|
st->st_peeruserport = b->his.port;
|
|
st->st_myuserprotoid = b->my.proto;
|
|
st->st_myuserport = b->my.port;
|
|
|
|
insert_state(st); /* needs cookies, connection, and msgid */
|
|
|
|
/* copy the connection's
|
|
* IPSEC policy into our state. The ISAKMP policy is water under
|
|
* the bridge, I think. It will reflect the ISAKMP SA that we
|
|
* are using.
|
|
*/
|
|
st->st_policy = (p1st->st_policy & POLICY_ISAKMP_MASK)
|
|
| (c->policy & ~POLICY_ISAKMP_MASK);
|
|
|
|
if (p1st->nat_traversal & NAT_T_DETECTED)
|
|
{
|
|
st->nat_traversal = p1st->nat_traversal;
|
|
nat_traversal_change_port_lookup(md, md->st);
|
|
}
|
|
else
|
|
{
|
|
st->nat_traversal = 0;
|
|
}
|
|
if ((st->nat_traversal & NAT_T_DETECTED)
|
|
&& (st->nat_traversal & NAT_T_WITH_NATOA))
|
|
{
|
|
nat_traversal_natoa_lookup(md);
|
|
}
|
|
|
|
/* Start the output packet.
|
|
*
|
|
* proccess_packet() would automatically generate the HDR*
|
|
* payload if smc->first_out_payload is not ISAKMP_NEXT_NONE.
|
|
* We don't do this because we wish there to be no partially
|
|
* built output packet if we need to suspend for asynch DNS.
|
|
*
|
|
* We build the reply packet as we parse the message since
|
|
* the parse_ipsec_sa_body emits the reply SA
|
|
*/
|
|
|
|
/* HDR* out */
|
|
echo_hdr(md, TRUE, ISAKMP_NEXT_HASH);
|
|
|
|
/* HASH(2) out -- first pass */
|
|
START_HASH_PAYLOAD(md->rbody, ISAKMP_NEXT_SA);
|
|
|
|
/* process SA (in and out) */
|
|
{
|
|
struct payload_digest *const sapd = md->chain[ISAKMP_NEXT_SA];
|
|
pb_stream r_sa_pbs;
|
|
struct isakmp_sa sa = sapd->payload.sa;
|
|
|
|
/* sa header is unchanged -- except for np */
|
|
sa.isasa_np = ISAKMP_NEXT_NONCE;
|
|
if (!out_struct(&sa, &isakmp_sa_desc, &md->rbody, &r_sa_pbs))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* parse and accept body */
|
|
st->st_pfs_group = &unset_group;
|
|
RETURN_STF_FAILURE(parse_ipsec_sa_body(&sapd->pbs
|
|
, &sapd->payload.sa, &r_sa_pbs, FALSE, st));
|
|
}
|
|
|
|
passert(st->st_pfs_group != &unset_group);
|
|
|
|
if ((st->st_policy & POLICY_PFS) && st->st_pfs_group == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "we require PFS but Quick I1 SA specifies no GROUP_DESCRIPTION");
|
|
return STF_FAIL + ISAKMP_NO_PROPOSAL_CHOSEN;
|
|
}
|
|
|
|
/* Ni in */
|
|
RETURN_STF_FAILURE(accept_nonce(md, &st->st_ni, "Ni"));
|
|
|
|
/* [ KE ] in (for PFS) */
|
|
RETURN_STF_FAILURE(accept_PFS_KE(md, &st->st_gi, "Gi", "Quick Mode I1"));
|
|
|
|
plog("responding to Quick Mode");
|
|
|
|
/**** finish reply packet: Nr [, KE ] [, IDci, IDcr ] ****/
|
|
|
|
/* Nr out */
|
|
if (!build_and_ship_nonce(&st->st_nr, &md->rbody
|
|
, st->st_pfs_group != NULL? ISAKMP_NEXT_KE : id_pd != NULL? ISAKMP_NEXT_ID : ISAKMP_NEXT_NONE
|
|
, "Nr"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* [ KE ] out (for PFS) */
|
|
|
|
if (st->st_pfs_group != NULL)
|
|
{
|
|
if (!build_and_ship_KE(st, &st->st_gr, st->st_pfs_group
|
|
, &md->rbody, id_pd != NULL? ISAKMP_NEXT_ID : ISAKMP_NEXT_NONE))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* MPZ-Operations might be done after sending the packet... */
|
|
compute_dh_shared(st, st->st_gi);
|
|
}
|
|
|
|
/* [ IDci, IDcr ] out */
|
|
if (id_pd != NULL)
|
|
{
|
|
struct isakmp_ipsec_id *p = (void *)md->rbody.cur; /* UGH! */
|
|
|
|
if (!out_raw(id_pd->pbs.start, pbs_room(&id_pd->pbs), &md->rbody, "IDci"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
p->isaiid_np = ISAKMP_NEXT_ID;
|
|
|
|
p = (void *)md->rbody.cur; /* UGH! */
|
|
|
|
if (!out_raw(id_pd->next->pbs.start, pbs_room(&id_pd->next->pbs), &md->rbody, "IDcr"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
p->isaiid_np = ISAKMP_NEXT_NONE;
|
|
}
|
|
|
|
if ((st->nat_traversal & NAT_T_WITH_NATOA)
|
|
&& (st->nat_traversal & LELEM(NAT_TRAVERSAL_NAT_BHND_ME))
|
|
&& (st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TRANSPORT))
|
|
{
|
|
/** Send NAT-OA if our address is NATed and if we use Transport Mode */
|
|
if (!nat_traversal_add_natoa(ISAKMP_NEXT_NONE, &md->rbody, md->st))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
if ((st->nat_traversal & NAT_T_DETECTED)
|
|
&& (st->st_esp.attrs.encapsulation == ENCAPSULATION_MODE_TRANSPORT)
|
|
&& (c->spd.that.has_client))
|
|
{
|
|
/** Remove client **/
|
|
addrtosubnet(&c->spd.that.host_addr, &c->spd.that.client);
|
|
c->spd.that.has_client = FALSE;
|
|
}
|
|
|
|
/* Compute reply HASH(2) and insert in output */
|
|
(void)quick_mode_hash12(r_hashval, r_hash_start, md->rbody.cur
|
|
, st, &st->st_msgid, TRUE);
|
|
|
|
/* Derive new keying material */
|
|
compute_keymats(st);
|
|
|
|
/* Tell the kernel to establish the new inbound SA
|
|
* (unless the commit bit is set -- which we don't support).
|
|
* We do this before any state updating so that
|
|
* failure won't look like success.
|
|
*/
|
|
if (!install_inbound_ipsec_sa(st))
|
|
{
|
|
return STF_INTERNAL_ERROR; /* ??? we may be partly committed */
|
|
}
|
|
|
|
/* encrypt message, except for fixed part of header */
|
|
|
|
if (!encrypt_message(&md->rbody, st))
|
|
{
|
|
return STF_INTERNAL_ERROR; /* ??? we may be partly committed */
|
|
}
|
|
|
|
return STF_OK;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Initialize RFC 3706 Dead Peer Detection
|
|
*/
|
|
static void dpd_init(struct state *st)
|
|
{
|
|
struct state *p1st = find_state(st->st_icookie, st->st_rcookie
|
|
, &st->st_connection->spd.that.host_addr, 0);
|
|
|
|
if (p1st == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "could not find phase 1 state for DPD");
|
|
}
|
|
else if (p1st->st_dpd)
|
|
{
|
|
plog("Dead Peer Detection (RFC 3706) enabled");
|
|
/* randomize the first DPD event */
|
|
|
|
event_schedule(EVENT_DPD
|
|
, (0.5 + rand()/(RAND_MAX + 1.E0)) * st->st_connection->dpd_delay
|
|
, st);
|
|
}
|
|
}
|
|
|
|
/* Handle (the single) message from Responder in Quick Mode.
|
|
* HDR*, HASH(2), SA, Nr [, KE ] [, IDci, IDcr ] -->
|
|
* HDR*, HASH(3)
|
|
* (see RFC 2409 "IKE" 5.5)
|
|
* Installs inbound and outbound IPsec SAs, routing, etc.
|
|
*/
|
|
stf_status quick_inR1_outI2(struct msg_digest *md)
|
|
{
|
|
struct state *const st = md->st;
|
|
const connection_t *c = st->st_connection;
|
|
|
|
/* HASH(2) in */
|
|
CHECK_QUICK_HASH(md
|
|
, quick_mode_hash12(hash_val, hash_pbs->roof, md->message_pbs.roof
|
|
, st, &st->st_msgid, TRUE)
|
|
, "HASH(2)", "Quick R1");
|
|
|
|
/* SA in */
|
|
{
|
|
struct payload_digest *const sa_pd = md->chain[ISAKMP_NEXT_SA];
|
|
|
|
RETURN_STF_FAILURE(parse_ipsec_sa_body(&sa_pd->pbs
|
|
, &sa_pd->payload.sa, NULL, TRUE, st));
|
|
}
|
|
|
|
/* Nr in */
|
|
RETURN_STF_FAILURE(accept_nonce(md, &st->st_nr, "Nr"));
|
|
|
|
/* [ KE ] in (for PFS) */
|
|
RETURN_STF_FAILURE(accept_PFS_KE(md, &st->st_gr, "Gr", "Quick Mode R1"));
|
|
|
|
if (st->st_pfs_group != NULL)
|
|
{
|
|
compute_dh_shared(st, st->st_gr);
|
|
}
|
|
|
|
/* [ IDci, IDcr ] in; these must match what we sent */
|
|
|
|
{
|
|
struct payload_digest *const id_pd = md->chain[ISAKMP_NEXT_ID];
|
|
|
|
if (id_pd != NULL)
|
|
{
|
|
/* ??? we are assuming IPSEC_DOI */
|
|
|
|
/* IDci (we are initiator) */
|
|
|
|
if (!check_net_id(&id_pd->payload.ipsec_id, &id_pd->pbs
|
|
, &st->st_myuserprotoid, &st->st_myuserport
|
|
, &st->st_connection->spd.this.client
|
|
, "our client"))
|
|
{
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
|
|
/* IDcr (responder is peer) */
|
|
|
|
if (!check_net_id(&id_pd->next->payload.ipsec_id, &id_pd->next->pbs
|
|
, &st->st_peeruserprotoid, &st->st_peeruserport
|
|
, &st->st_connection->spd.that.client
|
|
, "peer client"))
|
|
{
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* no IDci, IDcr: we must check that the defaults match our proposal */
|
|
if (!subnetisaddr(&c->spd.this.client, &c->spd.this.host_addr)
|
|
|| !subnetisaddr(&c->spd.that.client, &c->spd.that.host_addr))
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "IDci, IDcr payloads missing in message"
|
|
" but default does not match proposal");
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* check the peer's group attributes */
|
|
{
|
|
identification_t *peer_ca = NULL;
|
|
ietf_attributes_t *peer_attributes = NULL;
|
|
bool match;
|
|
|
|
get_peer_ca_and_groups(st->st_connection, &peer_ca, &peer_attributes);
|
|
match = match_group_membership(peer_attributes,
|
|
st->st_connection->name,
|
|
st->st_connection->spd.that.groups);
|
|
DESTROY_IF(peer_attributes);
|
|
|
|
if (!match)
|
|
{
|
|
ietf_attributes_t *groups = st->st_connection->spd.that.groups;
|
|
|
|
loglog(RC_LOG_SERIOUS,
|
|
"peer with attributes '%s' is not a member of the groups '%s'",
|
|
peer_attributes->get_string(peer_attributes),
|
|
groups->get_string(groups));
|
|
return STF_FAIL + ISAKMP_INVALID_ID_INFORMATION;
|
|
}
|
|
}
|
|
|
|
if ((st->nat_traversal & NAT_T_DETECTED)
|
|
&& (st->nat_traversal & NAT_T_WITH_NATOA))
|
|
{
|
|
nat_traversal_natoa_lookup(md);
|
|
}
|
|
|
|
/* ??? We used to copy the accepted proposal into the state, but it was
|
|
* never used. From sa_pd->pbs.start, length pbs_room(&sa_pd->pbs).
|
|
*/
|
|
|
|
/**************** build reply packet HDR*, HASH(3) ****************/
|
|
|
|
/* HDR* out done */
|
|
|
|
/* HASH(3) out -- since this is the only content, no passes needed */
|
|
{
|
|
u_char /* set by START_HASH_PAYLOAD: */
|
|
*r_hashval, /* where in reply to jam hash value */
|
|
*r_hash_start; /* start of what is to be hashed */
|
|
|
|
START_HASH_PAYLOAD(md->rbody, ISAKMP_NEXT_NONE);
|
|
(void)quick_mode_hash3(r_hashval, st);
|
|
}
|
|
|
|
/* Derive new keying material */
|
|
compute_keymats(st);
|
|
|
|
/* Tell the kernel to establish the inbound, outbound, and routing part
|
|
* of the new SA (unless the commit bit is set -- which we don't support).
|
|
* We do this before any state updating so that
|
|
* failure won't look like success.
|
|
*/
|
|
if (!install_ipsec_sa(st, TRUE))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* encrypt message, except for fixed part of header */
|
|
|
|
if (!encrypt_message(&md->rbody, st))
|
|
{
|
|
return STF_INTERNAL_ERROR; /* ??? we may be partly committed */
|
|
}
|
|
DBG(DBG_CONTROLMORE,
|
|
DBG_log("inR1_outI2: instance %s[%ld], setting newest_ipsec_sa to #%ld (was #%ld) (spd.eroute=#%ld)"
|
|
, st->st_connection->name
|
|
, st->st_connection->instance_serial
|
|
, st->st_serialno
|
|
, st->st_connection->newest_ipsec_sa
|
|
, st->st_connection->spd.eroute_owner)
|
|
)
|
|
st->st_connection->newest_ipsec_sa = st->st_serialno;
|
|
|
|
/* note (presumed) success */
|
|
if (c->gw_info != NULL)
|
|
{
|
|
c->gw_info->key->last_worked_time = now();
|
|
}
|
|
|
|
/* If we want DPD on this connection then initialize it */
|
|
if (st->st_connection->dpd_action != DPD_ACTION_NONE)
|
|
{
|
|
dpd_init(st);
|
|
}
|
|
return STF_OK;
|
|
}
|
|
|
|
/* Handle last message of Quick Mode.
|
|
* HDR*, HASH(3) -> done
|
|
* (see RFC 2409 "IKE" 5.5)
|
|
* Installs outbound IPsec SAs, routing, etc.
|
|
*/
|
|
stf_status quick_inI2(struct msg_digest *md)
|
|
{
|
|
struct state *const st = md->st;
|
|
|
|
/* HASH(3) in */
|
|
CHECK_QUICK_HASH(md, quick_mode_hash3(hash_val, st)
|
|
, "HASH(3)", "Quick I2");
|
|
|
|
/* Tell the kernel to establish the outbound and routing part of the new SA
|
|
* (the previous state established inbound)
|
|
* (unless the commit bit is set -- which we don't support).
|
|
* We do this before any state updating so that
|
|
* failure won't look like success.
|
|
*/
|
|
if (!install_ipsec_sa(st, FALSE))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
DBG(DBG_CONTROLMORE,
|
|
DBG_log("inI2: instance %s[%ld], setting newest_ipsec_sa to #%ld (was #%ld) (spd.eroute=#%ld)"
|
|
, st->st_connection->name
|
|
, st->st_connection->instance_serial
|
|
, st->st_serialno
|
|
, st->st_connection->newest_ipsec_sa
|
|
, st->st_connection->spd.eroute_owner)
|
|
)
|
|
st->st_connection->newest_ipsec_sa = st->st_serialno;
|
|
|
|
update_iv(st); /* not actually used, but tidy */
|
|
|
|
/* note (presumed) success */
|
|
{
|
|
struct gw_info *gw = st->st_connection->gw_info;
|
|
|
|
if (gw != NULL)
|
|
{
|
|
gw->key->last_worked_time = now();
|
|
}
|
|
}
|
|
|
|
/* If we want DPD on this connection then initialize it */
|
|
if (st->st_connection->dpd_action != DPD_ACTION_NONE)
|
|
{
|
|
dpd_init(st);
|
|
}
|
|
return STF_OK;
|
|
}
|
|
|
|
static stf_status send_isakmp_notification(struct state *st, u_int16_t type,
|
|
const void *data, size_t len)
|
|
{
|
|
msgid_t msgid;
|
|
pb_stream reply;
|
|
pb_stream rbody;
|
|
u_char
|
|
*r_hashval, /* where in reply to jam hash value */
|
|
*r_hash_start; /* start of what is to be hashed */
|
|
|
|
msgid = generate_msgid(st);
|
|
|
|
init_pbs(&reply, reply_buffer, sizeof(reply_buffer), "ISAKMP notify");
|
|
|
|
/* HDR* */
|
|
{
|
|
struct isakmp_hdr hdr;
|
|
|
|
hdr.isa_version = ISAKMP_MAJOR_VERSION << ISA_MAJ_SHIFT | ISAKMP_MINOR_VERSION;
|
|
hdr.isa_np = ISAKMP_NEXT_HASH;
|
|
hdr.isa_xchg = ISAKMP_XCHG_INFO;
|
|
hdr.isa_msgid = msgid;
|
|
hdr.isa_flags = ISAKMP_FLAG_ENCRYPTION;
|
|
memcpy(hdr.isa_icookie, st->st_icookie, COOKIE_SIZE);
|
|
memcpy(hdr.isa_rcookie, st->st_rcookie, COOKIE_SIZE);
|
|
if (!out_struct(&hdr, &isakmp_hdr_desc, &reply, &rbody))
|
|
{
|
|
impossible();
|
|
}
|
|
}
|
|
/* HASH -- create and note space to be filled later */
|
|
START_HASH_PAYLOAD(rbody, ISAKMP_NEXT_N);
|
|
|
|
/* NOTIFY */
|
|
{
|
|
pb_stream notify_pbs;
|
|
struct isakmp_notification isan;
|
|
|
|
isan.isan_np = ISAKMP_NEXT_NONE;
|
|
isan.isan_doi = ISAKMP_DOI_IPSEC;
|
|
isan.isan_protoid = PROTO_ISAKMP;
|
|
isan.isan_spisize = COOKIE_SIZE * 2;
|
|
isan.isan_type = type;
|
|
if (!out_struct(&isan, &isakmp_notification_desc, &rbody, ¬ify_pbs))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
if (!out_raw(st->st_icookie, COOKIE_SIZE, ¬ify_pbs, "notify icookie"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
if (!out_raw(st->st_rcookie, COOKIE_SIZE, ¬ify_pbs, "notify rcookie"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
if (data != NULL && len > 0)
|
|
{
|
|
if (!out_raw(data, len, ¬ify_pbs, "notify data"))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
close_output_pbs(¬ify_pbs);
|
|
}
|
|
|
|
{
|
|
/* finish computing HASH */
|
|
chunk_t msgid_chunk = chunk_from_thing(msgid);
|
|
chunk_t msg_chunk = { r_hash_start, rbody.cur-r_hash_start };
|
|
pseudo_random_function_t prf_alg;
|
|
prf_t *prf;
|
|
|
|
prf_alg = oakley_to_prf(st->st_oakley.hash);
|
|
prf = lib->crypto->create_prf(lib->crypto, prf_alg);
|
|
prf->set_key(prf, st->st_skeyid_a);
|
|
prf->get_bytes(prf, msgid_chunk, NULL);
|
|
prf->get_bytes(prf, msg_chunk, r_hashval);
|
|
|
|
DBG(DBG_CRYPT,
|
|
DBG_log("HASH computed:");
|
|
DBG_dump("", r_hashval, prf->get_block_size(prf));
|
|
)
|
|
prf->destroy(prf);
|
|
}
|
|
|
|
/* Encrypt message (preserve st_iv and st_new_iv) */
|
|
{
|
|
u_char old_iv[MAX_DIGEST_LEN];
|
|
u_char new_iv[MAX_DIGEST_LEN];
|
|
|
|
u_int old_iv_len = st->st_iv_len;
|
|
u_int new_iv_len = st->st_new_iv_len;
|
|
|
|
if (old_iv_len > MAX_DIGEST_LEN || new_iv_len > MAX_DIGEST_LEN)
|
|
return STF_INTERNAL_ERROR;
|
|
|
|
memcpy(old_iv, st->st_iv, old_iv_len);
|
|
memcpy(new_iv, st->st_new_iv, new_iv_len);
|
|
|
|
init_phase2_iv(st, &msgid);
|
|
if (!encrypt_message(&rbody, st))
|
|
{
|
|
return STF_INTERNAL_ERROR;
|
|
}
|
|
|
|
/* restore preserved st_iv and st_new_iv */
|
|
memcpy(st->st_iv, old_iv, old_iv_len);
|
|
memcpy(st->st_new_iv, new_iv, new_iv_len);
|
|
st->st_iv_len = old_iv_len;
|
|
st->st_new_iv_len = new_iv_len;
|
|
}
|
|
|
|
/* Send packet (preserve st_tpacket) */
|
|
{
|
|
chunk_t saved_tpacket = st->st_tpacket;
|
|
|
|
st->st_tpacket = chunk_create(reply.start, pbs_offset(&reply));
|
|
send_packet(st, "ISAKMP notify");
|
|
st->st_tpacket = saved_tpacket;
|
|
}
|
|
|
|
return STF_IGNORE;
|
|
}
|
|
|
|
/*
|
|
* DPD Out Initiator
|
|
*/
|
|
void dpd_outI(struct state *p2st)
|
|
{
|
|
struct state *st;
|
|
u_int32_t seqno;
|
|
time_t tm;
|
|
time_t idle_time;
|
|
time_t delay = p2st->st_connection->dpd_delay;
|
|
time_t timeout = p2st->st_connection->dpd_timeout;
|
|
|
|
/* find the newest related Phase 1 state */
|
|
st = find_phase1_state(p2st->st_connection, ISAKMP_SA_ESTABLISHED_STATES);
|
|
|
|
if (st == NULL)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "DPD: Could not find newest phase 1 state");
|
|
return;
|
|
}
|
|
|
|
/* If no DPD, then get out of here */
|
|
if (!st->st_dpd)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* schedule the next periodic DPD event */
|
|
event_schedule(EVENT_DPD, delay, p2st);
|
|
|
|
/* Current time */
|
|
tm = now();
|
|
|
|
/* Make sure we really need to invoke DPD */
|
|
if (!was_eroute_idle(p2st, delay, &idle_time))
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("recent eroute activity %u seconds ago, "
|
|
"no need to send DPD notification"
|
|
, (int)idle_time)
|
|
)
|
|
st->st_last_dpd = tm;
|
|
delete_dpd_event(st);
|
|
return;
|
|
}
|
|
|
|
/* If an R_U_THERE has been sent or received recently, or if a
|
|
* companion Phase 2 SA has shown eroute activity,
|
|
* then we don't need to invoke DPD.
|
|
*/
|
|
if (tm < st->st_last_dpd + delay)
|
|
{
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("recent DPD activity %u seconds ago, "
|
|
"no need to send DPD notification"
|
|
, (int)(tm - st->st_last_dpd))
|
|
)
|
|
return;
|
|
}
|
|
|
|
if (!IS_ISAKMP_SA_ESTABLISHED(st->st_state))
|
|
return;
|
|
|
|
if (!st->st_dpd_seqno)
|
|
{
|
|
rng_t *rng;
|
|
|
|
/* Get a non-zero random value that has room to grow */
|
|
rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
|
|
rng->get_bytes(rng, sizeof(st->st_dpd_seqno), (u_char *)&st->st_dpd_seqno);
|
|
rng->destroy(rng);
|
|
st->st_dpd_seqno &= 0x7fff;
|
|
st->st_dpd_seqno++;
|
|
}
|
|
seqno = htonl(st->st_dpd_seqno);
|
|
|
|
if (send_isakmp_notification(st, R_U_THERE, &seqno, sizeof(seqno)) != STF_IGNORE)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "DPD: Could not send R_U_THERE");
|
|
return;
|
|
}
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("sent DPD notification R_U_THERE with seqno = %u", st->st_dpd_seqno)
|
|
)
|
|
st->st_dpd_expectseqno = st->st_dpd_seqno++;
|
|
st->st_last_dpd = tm;
|
|
/* Only schedule a new timeout if there isn't one currently,
|
|
* or if it would be sooner than the current timeout. */
|
|
if (st->st_dpd_event == NULL
|
|
|| st->st_dpd_event->ev_time > tm + timeout)
|
|
{
|
|
delete_dpd_event(st);
|
|
event_schedule(EVENT_DPD_TIMEOUT, timeout, st);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* DPD in Initiator, out Responder
|
|
*/
|
|
stf_status
|
|
dpd_inI_outR(struct state *st, struct isakmp_notification *const n, pb_stream *pbs)
|
|
{
|
|
time_t tm = now();
|
|
u_int32_t seqno;
|
|
|
|
if (st == NULL || !IS_ISAKMP_SA_ESTABLISHED(st->st_state))
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "DPD: Received R_U_THERE for unestablished ISAKMP SA");
|
|
return STF_IGNORE;
|
|
}
|
|
if (n->isan_spisize != COOKIE_SIZE * 2 || pbs_left(pbs) < COOKIE_SIZE * 2)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE has invalid SPI length (%d)", n->isan_spisize);
|
|
return STF_FAIL + ISAKMP_PAYLOAD_MALFORMED;
|
|
}
|
|
|
|
if (memcmp(pbs->cur, st->st_icookie, COOKIE_SIZE) != 0)
|
|
{
|
|
#ifdef APPLY_CRISCO
|
|
/* Ignore it, cisco sends odd icookies */
|
|
#else
|
|
loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE has invalid icookie (broken Cisco?)");
|
|
return STF_FAIL + ISAKMP_INVALID_COOKIE;
|
|
#endif
|
|
}
|
|
pbs->cur += COOKIE_SIZE;
|
|
|
|
if (memcmp(pbs->cur, st->st_rcookie, COOKIE_SIZE) != 0)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE has invalid rcookie (broken Cisco?)");
|
|
return STF_FAIL + ISAKMP_INVALID_COOKIE;
|
|
}
|
|
pbs->cur += COOKIE_SIZE;
|
|
|
|
if (pbs_left(pbs) != sizeof(seqno))
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE has invalid data length (%d)"
|
|
, (int) pbs_left(pbs));
|
|
return STF_FAIL + ISAKMP_PAYLOAD_MALFORMED;
|
|
}
|
|
|
|
seqno = ntohl(*(u_int32_t *)pbs->cur);
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("received DPD notification R_U_THERE with seqno = %u", seqno)
|
|
)
|
|
|
|
if (st->st_dpd_peerseqno && seqno <= st->st_dpd_peerseqno) {
|
|
loglog(RC_LOG_SERIOUS, "DPD: Received old or duplicate R_U_THERE");
|
|
return STF_IGNORE;
|
|
}
|
|
|
|
st->st_dpd_peerseqno = seqno;
|
|
delete_dpd_event(st);
|
|
|
|
if (send_isakmp_notification(st, R_U_THERE_ACK, pbs->cur, pbs_left(pbs)) != STF_IGNORE)
|
|
{
|
|
loglog(RC_LOG_SERIOUS, "DPD Info: could not send R_U_THERE_ACK");
|
|
return STF_IGNORE;
|
|
}
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("sent DPD notification R_U_THERE_ACK with seqno = %u", seqno)
|
|
)
|
|
|
|
st->st_last_dpd = tm;
|
|
return STF_IGNORE;
|
|
}
|
|
|
|
/*
|
|
* DPD out Responder
|
|
*/
|
|
stf_status dpd_inR(struct state *st, struct isakmp_notification *const n,
|
|
pb_stream *pbs)
|
|
{
|
|
u_int32_t seqno;
|
|
|
|
if (st == NULL || !IS_ISAKMP_SA_ESTABLISHED(st->st_state))
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "DPD: Received R_U_THERE_ACK for unestablished ISAKMP SA");
|
|
return STF_FAIL;
|
|
}
|
|
|
|
if (n->isan_spisize != COOKIE_SIZE * 2 || pbs_left(pbs) < COOKIE_SIZE * 2)
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "DPD: R_U_THERE_ACK has invalid SPI length (%d)"
|
|
, n->isan_spisize);
|
|
return STF_FAIL + ISAKMP_PAYLOAD_MALFORMED;
|
|
}
|
|
|
|
if (memcmp(pbs->cur, st->st_icookie, COOKIE_SIZE) != 0)
|
|
{
|
|
#ifdef APPLY_CRISCO
|
|
/* Ignore it, cisco sends odd icookies */
|
|
#else
|
|
loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE_ACK has invalid icookie");
|
|
return STF_FAIL + ISAKMP_INVALID_COOKIE;
|
|
#endif
|
|
}
|
|
pbs->cur += COOKIE_SIZE;
|
|
|
|
if (memcmp(pbs->cur, st->st_rcookie, COOKIE_SIZE) != 0)
|
|
{
|
|
#ifdef APPLY_CRISCO
|
|
/* Ignore it, cisco sends odd icookies */
|
|
#else
|
|
loglog(RC_LOG_SERIOUS, "DPD: R_U_THERE_ACK has invalid rcookie");
|
|
return STF_FAIL + ISAKMP_INVALID_COOKIE;
|
|
#endif
|
|
}
|
|
pbs->cur += COOKIE_SIZE;
|
|
|
|
if (pbs_left(pbs) != sizeof(seqno))
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, " DPD: R_U_THERE_ACK has invalid data length (%d)"
|
|
, (int) pbs_left(pbs));
|
|
return STF_FAIL + ISAKMP_PAYLOAD_MALFORMED;
|
|
}
|
|
|
|
seqno = ntohl(*(u_int32_t *)pbs->cur);
|
|
DBG(DBG_CONTROL,
|
|
DBG_log("received DPD notification R_U_THERE_ACK with seqno = %u"
|
|
, seqno)
|
|
)
|
|
|
|
if (!st->st_dpd_expectseqno && seqno != st->st_dpd_expectseqno)
|
|
{
|
|
loglog(RC_LOG_SERIOUS
|
|
, "DPD: R_U_THERE_ACK has unexpected sequence number %u (expected %u)"
|
|
, seqno, st->st_dpd_expectseqno);
|
|
return STF_FAIL + ISAKMP_PAYLOAD_MALFORMED;
|
|
}
|
|
|
|
st->st_dpd_expectseqno = 0;
|
|
delete_dpd_event(st);
|
|
return STF_IGNORE;
|
|
}
|
|
|
|
/*
|
|
* DPD Timeout Function
|
|
*
|
|
* This function is called when a timeout DPD_EVENT occurs. We set clear/trap
|
|
* both the SA and the eroutes, depending on what the connection definition
|
|
* tells us (either 'hold' or 'clear')
|
|
*/
|
|
void
|
|
dpd_timeout(struct state *st)
|
|
{
|
|
struct state *newest_phase1_st;
|
|
connection_t *c = st->st_connection;
|
|
int action = st->st_connection->dpd_action;
|
|
char cname[BUF_LEN];
|
|
|
|
passert(action == DPD_ACTION_HOLD
|
|
|| action == DPD_ACTION_CLEAR
|
|
|| DPD_ACTION_RESTART);
|
|
|
|
/* is there a newer phase1_state? */
|
|
newest_phase1_st = find_phase1_state(c, ISAKMP_SA_ESTABLISHED_STATES);
|
|
if (newest_phase1_st != NULL && newest_phase1_st != st)
|
|
{
|
|
plog("DPD: Phase1 state #%ld has been superseded by #%ld"
|
|
" - timeout ignored"
|
|
, st->st_serialno, newest_phase1_st->st_serialno);
|
|
return;
|
|
}
|
|
|
|
loglog(RC_LOG_SERIOUS, "DPD: No response from peer - declaring peer dead");
|
|
|
|
/* delete the state, which is probably in phase 2 */
|
|
set_cur_connection(c);
|
|
plog("DPD: Terminating all SAs using this connection");
|
|
delete_states_by_connection(c, TRUE);
|
|
reset_cur_connection();
|
|
|
|
switch (action)
|
|
{
|
|
case DPD_ACTION_HOLD:
|
|
/* dpdaction=hold - Wipe the SA's but %trap the eroute so we don't
|
|
* leak traffic. Also, being in %trap means new packets will
|
|
* force an initiation of the conn again.
|
|
*/
|
|
loglog(RC_LOG_SERIOUS, "DPD: Putting connection \"%s\" into %%trap", c->name);
|
|
if (c->kind == CK_INSTANCE)
|
|
{
|
|
delete_connection(c, TRUE);
|
|
}
|
|
break;
|
|
case DPD_ACTION_CLEAR:
|
|
/* dpdaction=clear - Wipe the SA & eroute - everything */
|
|
loglog(RC_LOG_SERIOUS, "DPD: Clearing connection \"%s\"", c->name);
|
|
unroute_connection(c);
|
|
if (c->kind == CK_INSTANCE)
|
|
{
|
|
delete_connection(c, TRUE);
|
|
}
|
|
break;
|
|
case DPD_ACTION_RESTART:
|
|
/* dpdaction=restart - Restart connection,
|
|
* except if roadwarrior connection
|
|
*/
|
|
loglog(RC_LOG_SERIOUS, "DPD: Restarting connection \"%s\"", c->name);
|
|
unroute_connection(c);
|
|
|
|
/* caching the connection name before deletion */
|
|
strncpy(cname, c->name, BUF_LEN);
|
|
|
|
if (c->kind == CK_INSTANCE)
|
|
{
|
|
delete_connection(c, TRUE);
|
|
}
|
|
initiate_connection(cname, NULL_FD);
|
|
break;
|
|
default:
|
|
loglog(RC_LOG_SERIOUS, "DPD: unknown action");
|
|
}
|
|
}
|
|
|