2162 lines
58 KiB
C
2162 lines
58 KiB
C
/* $Id: gdoi_rekey.c,v 1.12.2.1 2011/10/18 03:26:55 bew Exp $ */
|
|
/* $Source: /nfs/cscbz/gdoi/gdoicvs/gdoi/src/gdoi_rekey.c,v $ */
|
|
|
|
/*
|
|
* The license applies to all software incorporated in the "Cisco GDOI reference
|
|
* implementation" except for those portions incorporating third party software
|
|
* specifically identified as being licensed under separate license.
|
|
*
|
|
*
|
|
* The Cisco Systems Public Software License, Version 1.0
|
|
* Copyright (c) 2001-2011 Cisco Systems, Inc. All rights reserved.
|
|
* Subject to the following terms and conditions, Cisco Systems, Inc.,
|
|
* hereby grants you a worldwide, royalty-free, nonexclusive, license,
|
|
* subject to third party intellectual property claims, to create
|
|
* derivative works of the Licensed Code and to reproduce, display,
|
|
* perform, sublicense, distribute such Licensed Code and derivative works.
|
|
* All rights not expressly granted herein are reserved.
|
|
* 1. Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following
|
|
* disclaimer in the documentation and/or other materials
|
|
* provided with the distribution.
|
|
* 3. The names Cisco and "Cisco GDOI reference implementation" must not
|
|
* be used to endorse or promote products derived from this software without
|
|
* prior written permission. For written permission, please contact
|
|
* opensource@cisco.com.
|
|
* 4. Products derived from this software may not be called
|
|
* "Cisco" or "Cisco GDOI reference implementation", nor may "Cisco" or
|
|
* "Cisco GDOI reference implementation" appear in
|
|
* their name, without prior written permission of Cisco Systems, Inc.
|
|
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
|
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
* PURPOSE, TITLE AND NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT
|
|
* SHALL CISCO SYSTEMS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO
|
|
* LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH
|
|
* PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH
|
|
* LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR
|
|
* LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THAT
|
|
* EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. FURTHER, YOU
|
|
* AGREE THAT IN NO EVENT WILL CISCO'S LIABILITY UNDER OR RELATED TO
|
|
* THIS AGREEMENT EXCEED AMOUNT FIVE THOUSAND DOLLARS (US)
|
|
* (US$5,000).
|
|
*
|
|
* ====================================================================
|
|
* This software consists of voluntary contributions made by Cisco Systems,
|
|
* Inc. and many individuals on behalf of Cisco Systems, Inc. For more
|
|
* information on Cisco Systems, Inc., please see <http://www.cisco.com/>.
|
|
*
|
|
* This product includes software developed by Ericsson Radio Systems.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <sys/time.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include "sysdep.h"
|
|
#include "conf.h"
|
|
#include "log.h"
|
|
#include "timer.h"
|
|
#include "transport.h"
|
|
#include "crypto.h"
|
|
#include "exchange.h"
|
|
#include "message.h"
|
|
#include "udp.h"
|
|
#include "log.h"
|
|
#include "isakmp_fld.h"
|
|
#include "gdoi_fld.h"
|
|
#include "gdoi_num.h"
|
|
#include "gdoi_phase2.h"
|
|
#include "gdoi.h"
|
|
#include "doi.h"
|
|
#include "sa.h"
|
|
#include "libcrypto.h"
|
|
#include "util.h"
|
|
#include "ipsec_num.h"
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/ip.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/rsa.h>
|
|
#include <openssl/evp.h>
|
|
|
|
#define UDP_SIZE 65536
|
|
|
|
#define REKEY_HEADER_STRING "rekey"
|
|
|
|
/* If a system doesn't have SO_REUSEPORT, SO_REUSEADDR will have to do. */
|
|
#ifndef SO_REUSEPORT
|
|
#define SO_REUSEPORT SO_REUSEADDR
|
|
#endif
|
|
|
|
static struct transport *rekey_udp_create (char *);
|
|
extern void udp_remove (struct transport *);
|
|
extern void udp_report (struct transport *);
|
|
extern int udp_fd_set (struct transport *, fd_set *, int);
|
|
extern int udp_fd_isset (struct transport *, fd_set *);
|
|
static void rekey_udp_handle_message (struct transport *);
|
|
static int rekey_udp_send_message (struct message *);
|
|
extern void udp_get_dst (struct transport *, struct sockaddr **, int *);
|
|
extern void udp_get_src (struct transport *, struct sockaddr **, int *);
|
|
extern char *udp_decode_ids (struct transport *);
|
|
extern void exchange_enter (struct exchange *);
|
|
|
|
static int initiator_send_SEQ_SA_KD_SIG (struct message *);
|
|
static int responder_recv_SEQ_SA_KD_SIG (struct message *);
|
|
|
|
struct spi_proto_arg {
|
|
u_int32_t spi;
|
|
u_int8_t proto;
|
|
};
|
|
|
|
int (*gdoi_rekey_initiator[]) (struct message *) = {
|
|
initiator_send_SEQ_SA_KD_SIG,
|
|
};
|
|
|
|
int (*gdoi_rekey_responder[]) (struct message *) = {
|
|
responder_recv_SEQ_SA_KD_SIG,
|
|
};
|
|
|
|
static struct transport_vtbl rekey_udp_transport_vtbl = {
|
|
{ 0 }, "rekey_udp",
|
|
rekey_udp_create,
|
|
udp_remove,
|
|
udp_report,
|
|
udp_fd_set,
|
|
udp_fd_isset,
|
|
rekey_udp_handle_message,
|
|
rekey_udp_send_message,
|
|
udp_get_dst,
|
|
udp_get_src,
|
|
udp_decode_ids
|
|
};
|
|
|
|
enum roles {
|
|
SENDER,
|
|
RECEIVER,
|
|
};
|
|
|
|
extern int compare_ids(u_int8_t *, u_int8_t *, size_t);
|
|
static struct transport *rekey_udp_make (struct gdoi_kek *, enum roles);
|
|
struct exchange *exchange_create (int, int, int, int);
|
|
|
|
|
|
TAILQ_HEAD (gdoi_kek_head, gdoi_kek) gdoi_kek_queue;
|
|
|
|
void
|
|
gdoi_rekey_init (void)
|
|
{
|
|
transport_method_add (&rekey_udp_transport_vtbl);
|
|
TAILQ_INIT (&gdoi_kek_queue);
|
|
}
|
|
|
|
struct gdoi_kek *
|
|
gdoi_get_kek (u_int8_t *id, size_t id_len, int create)
|
|
{
|
|
struct gdoi_kek *node;
|
|
|
|
/*
|
|
* Sanity check
|
|
*/
|
|
if (!id)
|
|
{
|
|
log_print("gdoi_get_kek: No identity payload!");
|
|
return 0;
|
|
}
|
|
|
|
for (node = TAILQ_FIRST (&gdoi_kek_queue); node;
|
|
node = TAILQ_NEXT (node, link))
|
|
{
|
|
if (compare_ids(id, node->group_id, node->group_id_len) == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!node && create)
|
|
{
|
|
node = calloc(1, sizeof(struct gdoi_kek));
|
|
if (!node)
|
|
{
|
|
return 0;
|
|
}
|
|
node->group_id_len = id_len;
|
|
node->group_id = malloc(id_len);
|
|
if (!node->group_id)
|
|
{
|
|
free(node);
|
|
return 0;
|
|
}
|
|
TAILQ_INIT(&node->deleted_sa_list);
|
|
memcpy(node->group_id, id, id_len);
|
|
TAILQ_INSERT_TAIL (&gdoi_kek_queue, node, link);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
struct gdoi_kek *
|
|
gdoi_get_kek_by_cookies (u_int8_t *cookies)
|
|
{
|
|
struct gdoi_kek *node;
|
|
|
|
for (node = TAILQ_FIRST (&gdoi_kek_queue); node;
|
|
node = TAILQ_NEXT (node, link))
|
|
{
|
|
if (strncmp((char *)cookies, (char *)node->spi, KEK_SPI_SIZE) == 0)
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct gdoi_kek *
|
|
gdoi_get_kek_by_transport (struct transport *transport)
|
|
{
|
|
struct gdoi_kek *node;
|
|
|
|
for (node = TAILQ_FIRST (&gdoi_kek_queue); node;
|
|
node = TAILQ_NEXT (node, link))
|
|
{
|
|
if (transport == node->send_transport)
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct gdoi_kek *
|
|
gdoi_get_kek_by_name (char *name)
|
|
{
|
|
struct gdoi_kek *node;
|
|
|
|
if (!name)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
for (node = TAILQ_FIRST (&gdoi_kek_queue); node;
|
|
node = TAILQ_NEXT (node, link))
|
|
{
|
|
if (node->exchange_name && !strcmp(name, node->exchange_name))
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Sender side only
|
|
* Open a socket to the multicast group for the purposes of joining the
|
|
* group. Then open the socket with which to send rekey messages to the
|
|
* multicast group. They must be unique.
|
|
*/
|
|
static int
|
|
gdoi_rekey_open_socket (struct gdoi_kek *kek, enum roles role)
|
|
{
|
|
int *s;
|
|
|
|
/*
|
|
* Sanity check the rekey fields we're going to use
|
|
*/
|
|
if ((kek->dst_addr == INADDR_NONE) || (kek->src_addr == INADDR_NONE))
|
|
{
|
|
log_error("gdoi_rekey_open_socket: No rekey address");
|
|
return -1;
|
|
}
|
|
if ((kek->dport == 0) || (kek->sport == 0))
|
|
{
|
|
log_error("gdoi_rekey_open_socket: No rekey port");
|
|
return -1;
|
|
}
|
|
|
|
if (role == SENDER)
|
|
{
|
|
s = &kek->send_sock;
|
|
kek->send_addr.sin_family = PF_INET;
|
|
kek->send_addr.sin_port = htons(kek->sport);
|
|
kek->send_addr.sin_addr.s_addr = kek->src_addr;
|
|
#ifndef USE_OLD_SOCKADDR
|
|
kek->send_addr.sin_len = sizeof(struct sockaddr_in);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
s = &kek->recv_sock;
|
|
kek->recv_addr.sin_family = PF_INET;
|
|
kek->recv_addr.sin_port = kek->dport; /* Leave in host order */
|
|
kek->recv_addr.sin_addr.s_addr = kek->dst_addr;
|
|
#ifndef USE_OLD_SOCKADDR
|
|
kek->recv_addr.sin_len = sizeof(struct sockaddr_in);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Setup sending side socket
|
|
*/
|
|
*s = socket (AF_INET, SOCK_DGRAM, 0);
|
|
if (*s < 0)
|
|
{
|
|
log_error("gdoi_rekey_open_socket: Socket open failed");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
rekey_crypto_encrypt (struct keystate *ks, u_int8_t *buf, u_int16_t len)
|
|
{
|
|
LOG_DBG_BUF ((LOG_CRYPTO, 10, "rekey_crypto_encrypt: before encryption", buf,
|
|
len));
|
|
ks->xf->encrypt (ks, buf, len);
|
|
memcpy (ks->liv, buf + len - ks->xf->blocksize, ks->xf->blocksize);
|
|
LOG_DBG_BUF ((LOG_CRYPTO, 30, "rekey_crypto_encrypt: after encryption", buf,
|
|
len));
|
|
}
|
|
|
|
void
|
|
rekey_crypto_decrypt (struct keystate *ks, u_int8_t *buf, u_int16_t len)
|
|
{
|
|
LOG_DBG_BUF ((LOG_CRYPTO, 10, "rekey_crypto_decrypt: before decryption", buf,
|
|
len));
|
|
memcpy (ks->liv, buf + len - ks->xf->blocksize, ks->xf->blocksize);
|
|
ks->xf->decrypt (ks, buf, len);;
|
|
LOG_DBG_BUF ((LOG_CRYPTO, 30, "rekey_crypto_decrypt: after decryption", buf,
|
|
len));
|
|
}
|
|
|
|
/*
|
|
* Encrypt an outgoing message MSG. As outgoing messages are represented
|
|
* with an iovec with one segment per payload, we need to coalesce them
|
|
* into just une buffer containing all payloads and some padding before
|
|
* we encrypt.
|
|
*/
|
|
static int
|
|
gdoi_rekey_message_encrypt (struct message *msg, struct gdoi_kek *stored_kek)
|
|
{
|
|
struct exchange *exchange = msg->exchange;
|
|
size_t sz = 0;
|
|
u_int8_t *buf;
|
|
int i;
|
|
enum cryptoerr err;
|
|
|
|
/* If no payloads, nothing to do. */
|
|
if (msg->iovlen == 1) {
|
|
log_print ("gdoi_rekey_message_encrypt: No payloads to encrypt!");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Setup the crypto vectors based on the algorithm. We have to translate
|
|
* The GDOI algorithm number to the IKE one in order to use the crypto
|
|
* routines....
|
|
*/
|
|
switch (stored_kek->encrypt_alg)
|
|
{
|
|
case GDOI_KEK_ALG_3DES:
|
|
exchange->crypto = crypto_get(TRIPLEDES_CBC);
|
|
break;
|
|
case GDOI_KEK_ALG_AES:
|
|
if (stored_kek->encrypt_key_len == AES128_LENGTH)
|
|
{
|
|
exchange->crypto = crypto_get(AES_CBC_128);
|
|
}
|
|
else
|
|
{
|
|
log_error ("decode_kd_kek_attribute: Unsupported AES key length %d",
|
|
stored_kek->encrypt_key_len);
|
|
return -1;
|
|
}
|
|
break;
|
|
default:
|
|
log_error ("decode_kd_kek_attribute: "
|
|
"Unknown KEK secrecy algorithm: %d", stored_kek->encrypt_alg);
|
|
return -1;
|
|
}
|
|
exchange->keystate = crypto_init (exchange->crypto, stored_kek->encrypt_key,
|
|
exchange->crypto->keymax, &err);
|
|
/*
|
|
* RFC 3547 specifies a static IV for the rekey. It is unfortuanate, but
|
|
* there isn't an easy placae to insert a dynamic IV into the ISAKMP header.
|
|
* Re-install the static IV into the crypto state each time we do an
|
|
* encryption.
|
|
*/
|
|
crypto_init_iv (exchange->keystate, stored_kek->encrypt_iv,
|
|
exchange->keystate->xf->blocksize);
|
|
|
|
/*
|
|
* For encryption we need to put all payloads together in a single buffer.
|
|
* This buffer should be padded to the current crypto transform's blocksize.
|
|
*/
|
|
for (i = 1; i < msg->iovlen; i++)
|
|
sz += msg->iov[i].iov_len;
|
|
sz = ((sz + exchange->crypto->blocksize - 1) / exchange->crypto->blocksize)
|
|
* exchange->crypto->blocksize;
|
|
buf = realloc (msg->iov[1].iov_base, sz);
|
|
if (!buf)
|
|
{
|
|
log_error ("message_encrypt: realloc (%p, %d) failed",
|
|
msg->iov[1].iov_base, sz);
|
|
return -1;
|
|
}
|
|
msg->iov[1].iov_base = buf;
|
|
for (i = 2; i < msg->iovlen; i++)
|
|
{
|
|
memcpy (buf + msg->iov[1].iov_len, msg->iov[i].iov_base,
|
|
msg->iov[i].iov_len);
|
|
msg->iov[1].iov_len += msg->iov[i].iov_len;
|
|
free (msg->iov[i].iov_base);
|
|
}
|
|
|
|
/* Pad with zeroes. */
|
|
memset (buf + msg->iov[1].iov_len, '\0', sz - msg->iov[1].iov_len);
|
|
msg->iov[1].iov_len = sz;
|
|
msg->iovlen = 2;
|
|
|
|
SET_ISAKMP_HDR_FLAGS (msg->iov[0].iov_base,
|
|
GET_ISAKMP_HDR_FLAGS (msg->iov[0].iov_base)
|
|
| ISAKMP_FLAGS_ENC);
|
|
SET_ISAKMP_HDR_LENGTH (msg->iov[0].iov_base, ISAKMP_HDR_SZ + sz);
|
|
rekey_crypto_encrypt (exchange->keystate, buf, msg->iov[1].iov_len);
|
|
msg->flags |= MSG_ENCRYPTED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Read the keypair file and stuff it into the stored KEK suitable for
|
|
* use with openssl.
|
|
*
|
|
* Also create a DER version of the public key (according to PKCS 2.0)
|
|
* for sending to the group members.
|
|
*/
|
|
int gdoi_read_keypair (u_int8_t *infile, struct gdoi_kek *stored_kek)
|
|
{
|
|
BIO *in=NULL, *out=NULL;
|
|
BUF_MEM *buf_mem=NULL;
|
|
|
|
/*
|
|
* Open the DER based key file and get the keypair.
|
|
*/
|
|
in = BIO_new (BIO_s_file());
|
|
if (!in)
|
|
{
|
|
log_print ("gdoi_read_keypair: "
|
|
"BIO_new(BIO_s_file()) failed");
|
|
return -1;
|
|
}
|
|
|
|
if (BIO_read_filename (in, infile) <= 0)
|
|
{
|
|
log_print ("gdoi_read_keypair: "
|
|
"BIO_read_filename (in, \"%s\") failed",
|
|
infile);
|
|
BIO_free (in);
|
|
return -1;
|
|
}
|
|
|
|
stored_kek->rsa_keypair = d2i_RSAPrivateKey_bio(in,NULL);
|
|
if (!stored_kek->rsa_keypair)
|
|
{
|
|
log_print ("gdoi_read_keypair: "
|
|
"d2i_RSAPrivateKey_bio failed");
|
|
BIO_free (in);
|
|
return -1;
|
|
}
|
|
|
|
BIO_free (in);
|
|
|
|
/*
|
|
* Now create a PKCS 2.0 version of the public key
|
|
*/
|
|
|
|
out = BIO_new (BIO_s_mem());
|
|
|
|
if (!i2d_RSA_PUBKEY_bio(out,stored_kek->rsa_keypair))
|
|
{
|
|
log_print ("gdoi_read_keypair: "
|
|
"i2d_RSA_PUBKEY_bio failed");
|
|
return -1;
|
|
}
|
|
|
|
BIO_get_mem_ptr(out, &buf_mem);
|
|
|
|
stored_kek->signature_key_len = buf_mem->length;
|
|
stored_kek->signature_key = calloc(1, stored_kek->signature_key_len);
|
|
if (!stored_kek->signature_key)
|
|
{
|
|
log_error ("gdoi_get_kek_policy: "
|
|
"calloc failed (%d)", stored_kek->signature_key_len);
|
|
BIO_free (out);
|
|
return -1;
|
|
}
|
|
|
|
memcpy(stored_kek->signature_key, buf_mem->data,
|
|
stored_kek->signature_key_len);
|
|
stored_kek->signature_key_modulus_size =
|
|
BN_num_bits(stored_kek->rsa_keypair->n);
|
|
BIO_free (out);
|
|
return 0;
|
|
}
|
|
|
|
int gdoi_store_pubkey (u_int8_t *der, int der_len, struct gdoi_kek *stored_kek)
|
|
{
|
|
BIO *in=NULL;
|
|
BUF_MEM *buf_mem;
|
|
u_int8_t *der_copy;
|
|
|
|
/*
|
|
* Only support RSA for now.
|
|
*/
|
|
if (stored_kek->sig_alg != GDOI_KEK_SIG_ALG_RSA)
|
|
{
|
|
log_print ("gdoi_store_keypair: Unsupported signature algorithm!");
|
|
return -1;
|
|
}
|
|
|
|
in = BIO_new (BIO_s_mem());
|
|
|
|
buf_mem = malloc(sizeof(BUF_MEM));
|
|
if (!buf_mem)
|
|
{
|
|
log_error ("gdoi_store_pubkey: "
|
|
"malloc failed (%d)", sizeof(BUF_MEM));
|
|
return -1;
|
|
}
|
|
der_copy = malloc(der_len);
|
|
if (!der_copy)
|
|
{
|
|
log_error ("gdoi_store_pubkey: "
|
|
"malloc failed (%d)", der_len);
|
|
BIO_free (in);
|
|
return -1;
|
|
}
|
|
memcpy(der_copy, der, der_len);
|
|
buf_mem->data = (char *)der_copy;
|
|
buf_mem->length = der_len;
|
|
buf_mem->max = der_len;
|
|
BIO_set_mem_buf(in, buf_mem, der_len);
|
|
|
|
/*
|
|
* Store the public key in the stored_kek. This is not really a
|
|
* "keypair", but we're re-using the key server structure so it's
|
|
* named oddly.
|
|
*/
|
|
stored_kek->rsa_keypair = d2i_RSA_PUBKEY_bio(in,NULL);
|
|
if (!stored_kek->rsa_keypair)
|
|
{
|
|
log_print ("gdoi_store_keypair: "
|
|
"d2i_RSA_PUBKEY_bio failed");
|
|
BIO_free (in);
|
|
free(der_copy);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Validate that the size of the keypair matches what we were told in
|
|
* the SA payload.
|
|
*/
|
|
if (BN_num_bits(stored_kek->rsa_keypair->n) !=
|
|
stored_kek->signature_key_modulus_size)
|
|
{
|
|
log_print ("gdoi_store_pubkey: Modulus size of signature key "
|
|
"doesn't match the SA payload policy. Expected %d "
|
|
"got %d", stored_kek->signature_key_modulus_size,
|
|
BN_num_bits(stored_kek->rsa_keypair->n));
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* The mem_buf pointer (der_copy) seems to be freed as part of BIO_free.
|
|
*/
|
|
BIO_free (in);
|
|
return 0;
|
|
}
|
|
|
|
extern int gdoi_add_sa_payload (struct message *);
|
|
extern int gdoi_add_kd_payload (struct message *);
|
|
|
|
static int gdoi_add_sig_payload (struct message *msg,
|
|
struct gdoi_kek *stored_kek)
|
|
{
|
|
struct hash *hash;
|
|
u_int8_t *buf;
|
|
u_int32_t datalen = 0, sig_bytes;
|
|
u_int8_t hdr[ISAKMP_HDR_SZ];
|
|
int i;
|
|
char header[80];
|
|
u_int8_t *data;
|
|
|
|
/*
|
|
* Calculate the hash over the "rekey" prefix, IKE header, and payloads.
|
|
*/
|
|
hash = hash_get(xlate_gdoi_hash(stored_kek->sig_hash_alg));
|
|
buf = malloc (hash->hashsize);
|
|
if (!buf)
|
|
{
|
|
log_error ("gdoi_add_sig_payload: "
|
|
"malloc (%d) failed", hash->hashsize);
|
|
}
|
|
|
|
/* Start with the characters in 'rekey' */
|
|
hash->Init (hash->ctx);
|
|
LOG_DBG_BUF ((LOG_MISC, 90, "gdoi_add_sig_payload: 'rekey'",
|
|
(u_int8_t *)REKEY_HEADER_STRING, strlen(REKEY_HEADER_STRING)));
|
|
hash->Update (hash->ctx, (u_int8_t *)REKEY_HEADER_STRING,
|
|
strlen(REKEY_HEADER_STRING));
|
|
|
|
/*
|
|
* The header must be adjusted in the following ways in order to match
|
|
* what the receiver will be hashing:
|
|
* 1) The length must include the size of the SIG payload. The size of the
|
|
* SIG payload will be the size of the modulus + 4 bytes for the SIG
|
|
* payload header.
|
|
* 2) The encrypted bit will be enabled.
|
|
*/
|
|
if (msg->iov[0].iov_len != ISAKMP_HDR_SZ)
|
|
{
|
|
log_print("gdoi_add_sig_payload: GDOI header length incorrect");
|
|
return -1;
|
|
}
|
|
memcpy(hdr, msg->iov[0].iov_base, ISAKMP_HDR_SZ);
|
|
|
|
/*
|
|
* Adjust the length
|
|
*/
|
|
sig_bytes = (BN_num_bits(stored_kek->rsa_keypair->n) / 8) + ISAKMP_GEN_SZ;
|
|
SET_ISAKMP_HDR_LENGTH(hdr, GET_ISAKMP_HDR_LENGTH(hdr) + sig_bytes);
|
|
/*
|
|
* Fix the encrypted bit
|
|
*/
|
|
SET_ISAKMP_HDR_FLAGS (hdr, GET_ISAKMP_HDR_FLAGS (hdr) | ISAKMP_FLAGS_ENC);
|
|
|
|
LOG_DBG_BUF ((LOG_MISC, 90, "gdoi_add_sig_payload: 'ISAKMP header'",
|
|
hdr, ISAKMP_HDR_SZ));
|
|
hash->Update (hash->ctx, hdr, ISAKMP_HDR_SZ);
|
|
|
|
/* Loop over all payloads including the HDR. */
|
|
for (i = 1; i < msg->iovlen; i++)
|
|
{
|
|
snprintf (header, 80, "gdoi_add_sig_payload: payload %d",
|
|
i);
|
|
LOG_DBG_BUF ((LOG_MISC, 90, header,
|
|
msg->iov[i].iov_base, msg->iov[i].iov_len));
|
|
hash->Update (hash->ctx, msg->iov[i].iov_base, msg->iov[i].iov_len);
|
|
}
|
|
|
|
hash->Final (buf, hash->ctx);
|
|
LOG_DBG_BUF ((LOG_NEGOTIATION, 80,
|
|
"gdoi_add_sig_payload: computed hash", buf, hash->hashsize));
|
|
|
|
/*
|
|
* Sign the packet following the model in rsa_sig_encode_hash()
|
|
*/
|
|
if (!stored_kek->rsa_keypair)
|
|
{
|
|
log_print("gdoi_add_sig_payload: No private key found!");
|
|
return -1;
|
|
}
|
|
|
|
data = malloc (sig_bytes);
|
|
if (!data)
|
|
{
|
|
log_error ("gdoi_add_sig_payload: malloc (%d) failed",
|
|
RSA_size (stored_kek->rsa_keypair));
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* The signing parameters aren't well specified in the GDOI draft. There
|
|
* are several PKCS#1 v2.0 parameters for padding. Here we've chosen
|
|
* the one named "EMSA-PKCS1-v1_5" in PKCS#1 v2.
|
|
*/
|
|
datalen = RSA_private_encrypt (hash->hashsize, buf, (data+ISAKMP_SIG_SZ),
|
|
stored_kek->rsa_keypair, RSA_PKCS1_PADDING);
|
|
|
|
if (datalen != (BN_num_bits(stored_kek->rsa_keypair->n) / 8))
|
|
{
|
|
log_error ("gdoi_add_sig_payload: signing failed");
|
|
}
|
|
|
|
if (message_add_payload (msg, ISAKMP_PAYLOAD_SIG, data, sig_bytes, 1))
|
|
{
|
|
free(data);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Check if SA matches what we are asking for through V_ARG. It has to
|
|
* be a finished phase 2 SA.
|
|
* Modelled after ipsec_sa_check.
|
|
*
|
|
* Note that for GDOI we don't have a "destination" to compare against, simply
|
|
* a SPI and protocol. This is accordance with RFC 4301 where the SA lookup is
|
|
* simply {SPI, protocol}.
|
|
*/
|
|
static int
|
|
gdoi_sa_check (struct sa *sa, void *v_arg)
|
|
{
|
|
struct spi_proto_arg *arg = v_arg;
|
|
struct proto *proto;
|
|
|
|
if (sa->phase != 2 || !(sa->flags & SA_FLAG_READY))
|
|
return 0;
|
|
|
|
for (proto = TAILQ_FIRST (&sa->protos); proto;
|
|
proto = TAILQ_NEXT (proto, link))
|
|
if ((arg->proto == 0 || proto->proto == arg->proto)
|
|
&& memcmp (proto->spi[0], &arg->spi, sizeof arg->spi) == 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Find an SA with a "name" of SPI & PROTO.
|
|
* Modelled after ipsec_sa_lookup
|
|
* */
|
|
struct sa *
|
|
gdoi_sa_lookup (u_int32_t spi, u_int8_t proto)
|
|
{
|
|
struct spi_proto_arg arg = { spi, proto };
|
|
|
|
return sa_find (gdoi_sa_check, &arg);
|
|
}
|
|
|
|
/*
|
|
* delete all SA's from addr with the associated proto and SPI's
|
|
* Modeled after ispec_delete_spi_list.
|
|
*
|
|
* spis[] is an array of SPIs of size 16-octet for proto ISAKMP
|
|
* or 4-octet otherwise.
|
|
*/
|
|
static void
|
|
gdoi_delete_spi_list (struct sockaddr *addr, u_int8_t proto,
|
|
u_int8_t *spis, int nspis, char *type)
|
|
{
|
|
struct sa *sa;
|
|
int i;
|
|
|
|
for (i = 0; i < nspis; i++)
|
|
{
|
|
if (proto == ISAKMP_PROTO_ISAKMP)
|
|
{
|
|
u_int8_t *spi = spis + i * ISAKMP_HDR_COOKIES_LEN;
|
|
|
|
sa = sa_lookup_isakmp_sa (addr, spi);
|
|
if (sa == NULL)
|
|
{
|
|
LOG_DBG ((LOG_SA, 30, "ipsec_delete_spi_list: "
|
|
"could not locate IKE SA (SPI %08x, proto %u)",
|
|
spi, proto));
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
u_int32_t spi = ((u_int32_t *)spis)[i];
|
|
|
|
sa = gdoi_sa_lookup (spi, proto);
|
|
if (sa == NULL)
|
|
{
|
|
LOG_DBG ((LOG_SA, 30, "ipsec_delete_spi_list: "
|
|
"could not locate IPsec SA (SPI %04x, proto %u)",
|
|
ntohl(spi), proto));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Delete the SA and search for the next */
|
|
LOG_DBG ((LOG_SA, 30, "ipsec_delete_spi_list: "
|
|
"%s made us delete SA %p (%d references) for proto %d",
|
|
type, sa, sa->refcnt, proto));
|
|
|
|
sa_free (sa);
|
|
}
|
|
}
|
|
/*
|
|
* Look for an deleted SA in the given group that matches a particular
|
|
* DOI and protocol type. (Protocol_type can be 0 for "no protocol".)
|
|
*/
|
|
static struct deleted_sa *find_deleted_sa (struct gdoi_kek *stored_kek,
|
|
u_int32_t doi,
|
|
u_int8_t protocol_type)
|
|
{
|
|
struct deleted_sa *del_sa;
|
|
|
|
for(del_sa = TAILQ_FIRST (&stored_kek->deleted_sa_list); del_sa;
|
|
del_sa = TAILQ_NEXT (del_sa, link))
|
|
{
|
|
if ((del_sa->doi == doi) && (del_sa->protocol_type == protocol_type))
|
|
{
|
|
return del_sa;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No matching SAs found.
|
|
*/
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Add a delete payload, if there are deleted SAs matching the DOI & protocol
|
|
* id.
|
|
* Return values:
|
|
* -1 = error
|
|
* 0 = no delete payloads added
|
|
* 1 = delete payloads added
|
|
*/
|
|
static int
|
|
gdoi_create_delete_payload(struct message *msg, struct gdoi_kek *stored_kek,
|
|
u_int32_t doi, u_int8_t protocol_type,
|
|
size_t spi_sz)
|
|
{
|
|
int spi_count = 0;
|
|
u_int8_t *buf;
|
|
struct deleted_sa *del_sa;
|
|
size_t sz;
|
|
|
|
if (!find_deleted_sa(stored_kek, doi, protocol_type))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Allocate the DELETE header
|
|
*/
|
|
sz = ISAKMP_DELETE_SZ; /* Allocate the DELETE header */
|
|
buf = malloc(sz);
|
|
if (!buf)
|
|
{
|
|
log_error ("gdoi_add_delete_payload: Malloc of DELETE hdr failed");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Setup as much header as possible
|
|
*/
|
|
SET_ISAKMP_DELETE_DOI (buf, GROUP_DOI_GDOI);
|
|
SET_ISAKMP_DELETE_PROTO (buf, protocol_type);
|
|
SET_ISAKMP_DELETE_SPI_SZ (buf, spi_sz);
|
|
|
|
while ((del_sa = find_deleted_sa(stored_kek, doi, protocol_type)))
|
|
{
|
|
sz += spi_sz;
|
|
buf = realloc(buf, sz);
|
|
if (!buf)
|
|
{
|
|
log_error ("gdoi_add_delete_payload: Realloc of %d failed", sz);
|
|
return -1;
|
|
}
|
|
memcpy(buf+sz-spi_sz, del_sa->spi, spi_sz);
|
|
TAILQ_REMOVE (&stored_kek->deleted_sa_list, del_sa, link);
|
|
free(del_sa);
|
|
spi_count++;
|
|
}
|
|
|
|
SET_ISAKMP_DELETE_NSPIS(buf, spi_count);
|
|
|
|
if (message_add_payload (msg, ISAKMP_PAYLOAD_DELETE, buf, sz, 1)) {
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* This function may actually create several different DELETE paylaods:
|
|
* a) One payload per DOI is required (i.e., GDOI TEKs, GDOI Rekey SA)
|
|
* b) If within the GDOI TEKs there are multiple Protocols (e.g., AH/ESP),
|
|
* there must be a unique payload per Protocol ID.
|
|
* Therefore, if a KEK SPI, ESP SPI, and AH SPI are all deleted this will
|
|
* result in 3 DELETE paylaods.
|
|
*/
|
|
static int
|
|
gdoi_add_delete_payloads(struct message *msg, struct gdoi_kek *stored_kek,
|
|
int *added)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* Deleted ESP SAs
|
|
*/
|
|
ret = gdoi_create_delete_payload(msg, stored_kek, GROUP_DOI_GDOI,
|
|
GDOI_TEK_PROT_PROTO_IPSEC_ESP, 4);
|
|
if (ret < 0) return -1;
|
|
if (ret == 1) *added += 1;
|
|
|
|
/*
|
|
* Deleted AH SAs
|
|
*/
|
|
ret = gdoi_create_delete_payload(msg, stored_kek, GROUP_DOI_GDOI,
|
|
GDOI_TEK_PROT_PROTO_IPSEC_AH, 4);
|
|
if (ret < 0) return -1;
|
|
if (ret == 1) *added +=1;
|
|
|
|
/*
|
|
* Deleted KEK SAs
|
|
*/
|
|
ret = gdoi_create_delete_payload(msg, stored_kek, ISAKMP_DOI_ISAKMP, 0,
|
|
KEK_SPI_SIZE);
|
|
if (ret < 0) return -1;
|
|
if (ret == 1) *added +=1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Handle a delete payload.
|
|
* Extracted from ipsec_handle_leftover_payload().
|
|
*/
|
|
int
|
|
gdoi_process_delete_payload (struct message *msg, struct payload *payload)
|
|
{
|
|
u_int32_t spisz, nspis;
|
|
struct sockaddr *dst;
|
|
socklen_t dstlen;
|
|
u_int8_t *spis, proto, ipsec_proto;
|
|
|
|
proto = GET_ISAKMP_DELETE_PROTO (payload->p);
|
|
nspis = GET_ISAKMP_DELETE_NSPIS (payload->p);
|
|
spisz = GET_ISAKMP_DELETE_SPI_SZ (payload->p);
|
|
|
|
payload->flags |= PL_MARK;
|
|
|
|
if (nspis == 0)
|
|
{
|
|
LOG_DBG ((LOG_SA, 60, "gdoi_process_delete_payload: message "
|
|
"specified zero SPIs, ignoring"));
|
|
return -1;
|
|
}
|
|
|
|
/* verify proper SPI size */
|
|
switch (proto)
|
|
{
|
|
case GDOI_TEK_PROT_PROTO_IPSEC_ESP:
|
|
case GDOI_TEK_PROT_PROTO_IPSEC_AH:
|
|
if (spisz != sizeof (u_int32_t))
|
|
{
|
|
log_print ("gdoi_process_delete_payload: invalid IPsec SPI size %d"
|
|
" for proto %d in DELETE payload", spisz, proto);
|
|
return -1;
|
|
}
|
|
break;
|
|
case ISAKMP_DOI_ISAKMP:
|
|
if (spisz != ISAKMP_HDR_COOKIES_LEN)
|
|
{
|
|
log_print ("gdoi_process_delete_payload: "
|
|
"invalid IKE SPI size %d for proto %d in DELETE payload",
|
|
spisz, proto);
|
|
return -1;
|
|
}
|
|
break;
|
|
default:
|
|
log_print ("gdoi_process_delete_payload: "
|
|
"Unknown proto %d in DELETE payload", proto);
|
|
return -1;
|
|
}
|
|
|
|
spis = (u_int8_t *)malloc (nspis * spisz);
|
|
if (!spis)
|
|
{
|
|
log_error ("gdoi_process_delete_payload: malloc (%d) failed",
|
|
nspis * spisz);
|
|
return -1;
|
|
}
|
|
|
|
/* extract SPI and get dst address */
|
|
memcpy (spis, payload->p + ISAKMP_DELETE_SPI_OFF, nspis * spisz);
|
|
msg->transport->vtbl->get_dst (msg->transport, &dst, (int *)&dstlen);
|
|
|
|
/* need to convert GDOI proto to IPsec proto ID */
|
|
switch (proto)
|
|
{
|
|
case GDOI_TEK_PROT_PROTO_IPSEC_ESP:
|
|
ipsec_proto = IPSEC_PROTO_IPSEC_ESP;
|
|
break;
|
|
case GDOI_TEK_PROT_PROTO_IPSEC_AH:
|
|
ipsec_proto = IPSEC_PROTO_IPSEC_AH;
|
|
break;
|
|
case ISAKMP_DOI_ISAKMP:
|
|
default: /* did error checking above */
|
|
ipsec_proto = proto;
|
|
break;
|
|
}
|
|
gdoi_delete_spi_list (dst, ipsec_proto, spis, nspis, "DELETE");
|
|
|
|
free (spis);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The current hardcoded policy for rekey is to send new SPIs and keys for
|
|
* orginal policy in the configuration file. To do that, we use the same code
|
|
* as the registration message to get the SAs, expect the behavior for the
|
|
* SPIs and keys is different.
|
|
*/
|
|
static int
|
|
initiator_send_SEQ_SA_KD_SIG (struct message *msg)
|
|
{
|
|
struct payload *p;
|
|
struct gdoi_kek *stored_kek;
|
|
u_int8_t *seq_buf = 0;
|
|
size_t sz;
|
|
int have_delete_payloads = 0;
|
|
|
|
/*
|
|
* Find the KEK. The only search value we have is the transport address,
|
|
* which is fixed in the KEK, and installed in the msg by the GDOI message
|
|
* initiating logic.
|
|
*/
|
|
stored_kek = gdoi_get_kek_by_transport(msg->transport);
|
|
if (!stored_kek)
|
|
{
|
|
log_print ("initiator_send_SEQ_SA_KD_SIG: SA not found in rekey SA list");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Add SEQ payload with the current sequence number & then increment it for
|
|
* the next time.
|
|
*/
|
|
sz = GDOI_SEQ_SEQ_NUM_OFF + GDOI_SEQ_SEQ_NUM_LEN;
|
|
seq_buf = calloc (1, sz);
|
|
if (!seq_buf)
|
|
{
|
|
log_error ("initiator_send_SEQ_SA_KD_SIG: calloc (%d) failed", sz);
|
|
goto bail_out;
|
|
}
|
|
/*
|
|
* The reciever will check that the next one is greater than the value sent
|
|
* in the registration message. Therefore we must increment the seq value
|
|
* BEFORE sending it in this message.
|
|
*/
|
|
stored_kek->current_seq_num++;
|
|
SET_GDOI_SEQ_SEQ_NUM(seq_buf, stored_kek->current_seq_num);
|
|
log_print ("SENT SEQ # of: %d (PUSH)", stored_kek->current_seq_num);
|
|
if (message_add_payload (msg, ISAKMP_PAYLOAD_SEQ, seq_buf, sz, 1)) {
|
|
return -1;
|
|
}
|
|
|
|
if (gdoi_add_delete_payloads(msg, stored_kek, &have_delete_payloads)) {
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Don't send SA/KD payloads if we're just cleaning up the group.
|
|
*/
|
|
if ((stored_kek->flags & CLEANING_UP))
|
|
{
|
|
if (!have_delete_payloads)
|
|
{
|
|
log_print ("initiator_send_SEQ_SA_KD_SIG: Cleaning up, but no"
|
|
" delete payloads found. Aborting - Nothing to do.");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Fixup the last DELETE payload "next payload" so that the hash of the
|
|
* DELETE payload is correct. This needs to be set before going to
|
|
* the signature code.
|
|
*/
|
|
p = TAILQ_LAST (&msg->payload[ISAKMP_PAYLOAD_DELETE], payload_head);
|
|
if (!p)
|
|
{
|
|
log_print("initiator_send_SEQ_SA_KD_SIG: DELETE payload missing");
|
|
return -1;
|
|
}
|
|
SET_ISAKMP_GEN_NEXT_PAYLOAD(p->p, ISAKMP_PAYLOAD_SIG);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Add the SA payload from the config file.
|
|
*/
|
|
if (gdoi_add_sa_payload(msg)) {
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Add the KD payload from the config file.
|
|
*/
|
|
if (gdoi_add_kd_payload(msg)) {
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Fixup the KD payload "next payload" so that the hash of the KD
|
|
* payload is correct. This needs to be set before going to the
|
|
* signature code.
|
|
*/
|
|
p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_KD]);
|
|
if (!p)
|
|
{
|
|
log_print("initiator_send_SEQ_SA_KD_SIG: KD payload missing");
|
|
return -1;
|
|
}
|
|
SET_ISAKMP_GEN_NEXT_PAYLOAD(p->p, ISAKMP_PAYLOAD_SIG);
|
|
}
|
|
|
|
/*
|
|
* Add the SIG payload and sign it.
|
|
*/
|
|
if (gdoi_add_sig_payload(msg, stored_kek)) {
|
|
return -1;
|
|
}
|
|
|
|
if (gdoi_rekey_message_encrypt(msg, stored_kek)) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
|
|
bail_out:
|
|
if (seq_buf) {
|
|
free(seq_buf);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
gdoi_rekey_setup_exchange (struct gdoi_kek *kek)
|
|
{
|
|
struct gdoi_exch *ie;
|
|
|
|
kek->send_exchange =
|
|
exchange_create (1, 0, GROUP_DOI_GDOI, GDOI_EXCH_PUSH_MODE);
|
|
if (!kek->send_exchange)
|
|
{
|
|
log_print("gdoi_rekey_setup_exchange: exchange creation failed");
|
|
return -1;
|
|
}
|
|
memcpy (kek->send_exchange->cookies, kek->spi, ISAKMP_HDR_COOKIES_LEN);
|
|
ie = kek->send_exchange->data;
|
|
ie->id_gdoi_sz = kek->group_id_len;
|
|
ie->id_gdoi = calloc (1, ie->id_gdoi_sz);
|
|
memcpy(ie->id_gdoi, kek->group_id, ie->id_gdoi_sz);
|
|
kek->send_exchange->initiator = 1;
|
|
exchange_enter (kek->send_exchange);
|
|
TAILQ_INIT(&ie->spis);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Delete an SA from the list, and insert it on the KEK deleted SA list.
|
|
*/
|
|
int
|
|
gdoi_add_deleted_sa (struct gdoi_kek *kek, struct sa *sa)
|
|
{
|
|
struct proto *proto;
|
|
struct deleted_sa *del_sa;
|
|
|
|
proto = TAILQ_FIRST (&sa->protos);
|
|
if (!proto)
|
|
{
|
|
log_print("gdoi_add_deleted_sa: No proto found for SA %#x", sa);
|
|
return -1;
|
|
}
|
|
|
|
log_print("gdoi_add_deleted_sa: Deleting SPI (SA) %u (%d) (%#x) for sa %#x",
|
|
decode_32(proto->spi[0]), decode_32(proto->spi[0]),
|
|
decode_32(proto->spi[0]), sa);
|
|
|
|
del_sa = malloc(sizeof(struct deleted_sa));
|
|
if (!del_sa)
|
|
{
|
|
log_print("gdoi_add_deleted_sa: deleted SA malloc failure");
|
|
return -1;
|
|
}
|
|
/*
|
|
* RFC 3547 says the DOI must be GDOI except for a KEK SPI, which
|
|
* must be zero. Protocol IDs within the GDOI DOI come from Section 5.4 of
|
|
* RFC 3547.
|
|
*/
|
|
del_sa->doi = GROUP_DOI_GDOI;
|
|
/*
|
|
* Insert the SPIs in network byte order. This is the last convenient
|
|
* place to know what size the SPI should be by protocol type.
|
|
*/
|
|
switch (proto->proto)
|
|
{
|
|
case IPSEC_PROTO_IPSEC_ESP:
|
|
del_sa->protocol_type = GDOI_TEK_PROT_PROTO_IPSEC_ESP;
|
|
if (proto->spi_sz[0] != 4)
|
|
{
|
|
log_error("gdoi_add_deleted_sa: Wrong ESP SPI size %d",
|
|
proto->spi_sz[0]);
|
|
return -1;
|
|
}
|
|
memcpy(del_sa->spi, proto->spi[0], proto->spi_sz[0]);
|
|
break;
|
|
case IPSEC_PROTO_IPSEC_AH:
|
|
del_sa->protocol_type = GDOI_TEK_PROT_PROTO_IPSEC_AH;
|
|
if (proto->spi_sz[0] != 4)
|
|
{
|
|
log_error("gdoi_add_deleted_sa: Wrong AH SPI size %d",
|
|
proto->spi_sz[0]);
|
|
return -1;
|
|
}
|
|
memcpy(del_sa->spi, proto->spi[0], proto->spi_sz[0]);
|
|
break;
|
|
default:
|
|
log_error("gdoi_add_deleted_sa: Unsupported protocol %d",
|
|
proto->proto);
|
|
free(del_sa);
|
|
return -1;
|
|
}
|
|
TAILQ_INSERT_TAIL (&kek->deleted_sa_list, del_sa, link);
|
|
sa_free(sa);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
gdoi_rekey_send_msg (struct gdoi_kek *kek)
|
|
{
|
|
struct message *msg;
|
|
|
|
if (!kek->send_sock)
|
|
{
|
|
/*
|
|
* Open a socket for sending
|
|
*/
|
|
if (gdoi_rekey_open_socket(kek, SENDER) <0)
|
|
{
|
|
log_print("gdoi_rekey_send_msg: Socket open failed");
|
|
return -1;
|
|
}
|
|
}
|
|
if (!kek->send_transport)
|
|
{
|
|
kek->send_transport = rekey_udp_make (kek, SENDER);
|
|
if (!kek->send_transport)
|
|
{
|
|
log_print("gdoi_rekey_send_msg: transport creation failed");
|
|
return -1;
|
|
}
|
|
}
|
|
if (!kek->send_exchange)
|
|
{
|
|
if (gdoi_rekey_setup_exchange(kek))
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Reset the exchange "PC" to the beginning. This is necssary because
|
|
* we're re-using the exchange structure for each rekey so that we can
|
|
* accumulate SAs in one exchange.
|
|
*
|
|
* This assumes that we are never working on more than 1 rekey message
|
|
* for a particular group at any one time ....
|
|
*/
|
|
kek->send_exchange->exch_pc = (int16_t *)exchange_script (kek->send_exchange);
|
|
kek->send_exchange->step = 0;
|
|
}
|
|
msg = message_alloc (kek->send_transport, 0, ISAKMP_HDR_SZ);
|
|
msg->exchange = kek->send_exchange;
|
|
message_setup_header (msg, GDOI_EXCH_PUSH_MODE, ISAKMP_FLAGS_ENC,
|
|
kek->send_exchange->message_id);
|
|
exchange_run (msg);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Delete GDOI SAs and send a rekey with delete payloads & new SAs matching
|
|
* the policy.
|
|
*
|
|
* Called from receiving a TERM signal.
|
|
*
|
|
* NOTE: If the group has no KEK (i.e., no rekey) then there is no point in
|
|
* "deleting" the SAs because we can't send a rekey anyway. Therefore, this
|
|
* code does not deal with group which have no rekey.
|
|
*/
|
|
void gdoi_rekey_delete_sas (fd_set *wfds)
|
|
{
|
|
struct gdoi_kek *kek;
|
|
struct sa *sa;
|
|
|
|
for (kek = TAILQ_FIRST(&gdoi_kek_queue); kek;
|
|
kek = TAILQ_NEXT (kek, link))
|
|
{
|
|
if (!kek->send_exchange)
|
|
{
|
|
/*
|
|
* Not a key server for this group.
|
|
*/
|
|
continue;
|
|
}
|
|
log_print("gdoi_rekey_delete_sas: Deleting SAs and Sending a rekey "
|
|
"with DELETE paylaods for exchange %s",
|
|
(kek->exchange_name ? kek->exchange_name : "unknown"));
|
|
/*
|
|
* Find the TEKs associated with the rekey exchange.
|
|
*/
|
|
sa = TAILQ_FIRST (&kek->send_exchange->sa_list);
|
|
while (sa)
|
|
{
|
|
gdoi_add_deleted_sa(kek, sa);
|
|
LOG_DBG ((LOG_SA, 60, "gdoi_rekey_delete_sas: "
|
|
"freeing SA %p from exchange %p",
|
|
sa, kek->send_exchange));
|
|
sa_release(sa);
|
|
sa = TAILQ_NEXT (sa, next);
|
|
}
|
|
kek->flags |= CLEANING_UP;
|
|
gdoi_rekey_send_msg(kek);
|
|
udp_fd_set(kek->send_transport, wfds, 1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
gdoi_kek_rekey_sender (void *vkek)
|
|
{
|
|
struct gdoi_kek *kek = vkek;
|
|
|
|
log_print("gdoi_kek_rekey_sender: Timer sprung!!!");
|
|
/*
|
|
* Careful! Need to generate a rekey message using the OLD KEK keys, but
|
|
* delivering the NEW key keys.
|
|
*
|
|
* TODO: We should re-transmit this a couple of times in case of packet loss.
|
|
* If we send it once and a device misses it, it won't be able to decrypt
|
|
* future KEKs and will be forced to re-register.
|
|
*
|
|
* Using seperate flags for creating and sending a new KEK allows us to
|
|
* later do re-transmits of the new KEK info.
|
|
*/
|
|
kek->flags |= CREATE_NEW_KEK|SEND_NEW_KEK;
|
|
if (gdoi_rekey_send_msg (kek) < 0)
|
|
{
|
|
log_print("gdoi_rekey_sender: Error in sending msg - Aborting");
|
|
return;
|
|
}
|
|
gdoi_kek_rekey_start (kek);
|
|
/*
|
|
* Clean up flags
|
|
*/
|
|
kek->flags &= ~(CREATE_NEW_KEK|SEND_NEW_KEK);
|
|
/*
|
|
* Install the new SPI and clean up.
|
|
*/
|
|
memcpy(kek->spi, &kek->next_kek_policy.spi, KEK_SPI_SIZE);
|
|
memset(&kek->next_kek_policy.spi, 0, KEK_SPI_SIZE);
|
|
/*
|
|
* Install the new SPI in the rekey exchange cookies too! However, the
|
|
* exchange needs to be re-linked in the echange data structures.
|
|
*/
|
|
memcpy(kek->send_exchange->cookies, &kek->spi, ISAKMP_HDR_COOKIES_LEN);
|
|
LIST_REMOVE (kek->send_exchange, link);
|
|
exchange_enter (kek->send_exchange);
|
|
/*
|
|
* Install the new keys and free old ones.
|
|
*/
|
|
kek->encrypt_iv = kek->next_kek_policy.encrypt_iv;
|
|
kek->encrypt_key = kek->next_kek_policy.encrypt_key;
|
|
kek->next_kek_policy.encrypt_iv = NULL;
|
|
kek->next_kek_policy.encrypt_key = NULL;
|
|
}
|
|
|
|
int
|
|
gdoi_kek_rekey_start (struct gdoi_kek *kek)
|
|
{
|
|
struct timeval expire_time;
|
|
|
|
gettimeofday (&expire_time, 0);
|
|
expire_time.tv_sec += kek->kek_timer_interval;
|
|
kek->tek_lifetime_ev = timer_add_event ("gdoi_kek_rekey_sender",
|
|
gdoi_kek_rekey_sender, kek, &expire_time);
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
gdoi_rekey_sender (void *vkek)
|
|
{
|
|
struct gdoi_kek *kek = vkek;
|
|
|
|
log_print("gdoi_rekey_sender: Timer sprung!!!");
|
|
gdoi_rekey_start (kek);
|
|
if (gdoi_rekey_send_msg (kek) < 0)
|
|
{
|
|
log_print("gdoi_rekey_sender: Error in sending msg - Aborting");
|
|
return;
|
|
}
|
|
}
|
|
|
|
int
|
|
gdoi_rekey_start (struct gdoi_kek *kek)
|
|
{
|
|
struct timeval expire_time;
|
|
|
|
gettimeofday (&expire_time, 0);
|
|
expire_time.tv_sec += kek->tek_timer_interval;
|
|
kek->tek_lifetime_ev = timer_add_event ("gdoi_rekey_sender",
|
|
gdoi_rekey_sender, kek, &expire_time);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
gdoi_rekey_listen (struct gdoi_kek *kek)
|
|
{
|
|
if (kek->recv_sock)
|
|
{
|
|
log_print("gdoi_rekey_listen: Already a listener for this group.");
|
|
return 0;
|
|
}
|
|
|
|
log_print("gdoi_rekey_listen: Setting up rekey listener!");
|
|
|
|
/*
|
|
* Open a socket for receiving
|
|
*/
|
|
if (gdoi_rekey_open_socket(kek, RECEIVER) <0)
|
|
{
|
|
log_print("gdoi_rekey_send_msg: Socket open failed");
|
|
return -1;
|
|
}
|
|
rekey_udp_make(kek, RECEIVER);
|
|
return 0;
|
|
}
|
|
|
|
static struct transport *
|
|
rekey_udp_make (struct gdoi_kek *kek, enum roles role)
|
|
{
|
|
int s;
|
|
struct sockaddr_in *laddr;
|
|
struct in_addr iaddr;
|
|
u_int8_t ttl = IPDEFTTL;
|
|
u_int8_t loop = 0; /* Disable loopback of our own multicast packets*/
|
|
struct udp_transport *t = 0;
|
|
int on;
|
|
struct ip_mreq maddr;
|
|
struct conf_list *listen_on;
|
|
struct conf_list_node *address;
|
|
|
|
t = calloc (1, sizeof *t);
|
|
if (!t)
|
|
{
|
|
log_print ("rekey_udp_make: malloc (%d) failed", sizeof *t);
|
|
return 0;
|
|
}
|
|
|
|
if (role == SENDER)
|
|
{
|
|
s = kek->send_sock;
|
|
laddr = &kek->send_addr;
|
|
t->dst.sin_family = PF_INET;
|
|
t->dst.sin_port = htons(kek->dport);
|
|
t->dst.sin_addr.s_addr = kek->dst_addr;
|
|
#ifndef USE_OLD_SOCKADDR
|
|
t->dst.sin_len = sizeof(struct sockaddr_in);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
s = kek->recv_sock;
|
|
laddr = &kek->recv_addr;
|
|
t->dst.sin_family = PF_INET;
|
|
t->dst.sin_port = kek->sport;
|
|
t->dst.sin_addr.s_addr = kek->src_addr;
|
|
#ifndef USE_OLD_SOCKADDR
|
|
t->dst.sin_len = sizeof(struct sockaddr_in);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* In order to have several bound specific address-port combinations
|
|
* with the same port SO_REUSEADDR is needed.
|
|
* If this is a wildcard socket and we are not listening there, but only
|
|
* sending from it make sure it is entirely reuseable with SO_REUSEPORT.
|
|
*/
|
|
on = 1;
|
|
if (setsockopt (s, SOL_SOCKET,
|
|
(laddr->sin_addr.s_addr == INADDR_ANY
|
|
&& conf_get_str ("General", "Listen-on"))
|
|
? SO_REUSEPORT : SO_REUSEADDR,
|
|
(void *)&on, sizeof on) == -1)
|
|
{
|
|
log_error ("rekey_udp_make: setsockopt (%d, %d, %d, %p, %d)", s, SOL_SOCKET,
|
|
(laddr->sin_addr.s_addr == INADDR_ANY
|
|
&& conf_get_str ("General", "Listen-on"))
|
|
? SO_REUSEPORT : SO_REUSEADDR,
|
|
&on, sizeof on);
|
|
goto err;
|
|
}
|
|
|
|
t->transport.vtbl = &rekey_udp_transport_vtbl;
|
|
memcpy (&t->src, laddr, sizeof t->src);
|
|
|
|
if (bind (s, (struct sockaddr *)&t->src, sizeof t->src))
|
|
{
|
|
log_error ("rekey_udp_make: bind (%d, %p, %d)", s, &t->src,
|
|
sizeof t->src);
|
|
log_error("rekey_udp_make: Continuing anyway");
|
|
}
|
|
|
|
if (role == RECEIVER)
|
|
{
|
|
if (IN_MULTICAST(htonl(laddr->sin_addr.s_addr)))
|
|
{
|
|
bzero(&maddr, sizeof(maddr));
|
|
maddr.imr_multiaddr.s_addr = laddr->sin_addr.s_addr;
|
|
/*
|
|
* Pick the first interface off the "Listen-on" list.
|
|
*/
|
|
listen_on = conf_get_list ("General", "Listen-on");
|
|
if (listen_on)
|
|
{
|
|
address = TAILQ_FIRST (&listen_on->fields);
|
|
if (!inet_aton (address->field, &iaddr))
|
|
{
|
|
log_print ("rekey_udp_make: "
|
|
"invalid address %s in \"Listen-on\"",
|
|
address->field);
|
|
goto err;
|
|
}
|
|
maddr.imr_interface.s_addr = iaddr.s_addr;
|
|
}
|
|
conf_free_list (listen_on);
|
|
if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
|
|
&maddr,sizeof(maddr)))
|
|
{
|
|
log_error("rekey_udp_make: setsockopt(IP_ADD_MEMBERSHIP)");
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (role == SENDER)
|
|
{
|
|
listen_on = conf_get_list ("General", "Listen-on");
|
|
if (listen_on)
|
|
{
|
|
for (address = TAILQ_FIRST (&listen_on->fields); address;
|
|
address = TAILQ_NEXT (address, link))
|
|
{
|
|
if (!inet_aton (address->field, &iaddr))
|
|
{
|
|
log_print ("rekey_udp_make: "
|
|
"invalid address %s in \"Listen-on\"",
|
|
address->field);
|
|
goto err;
|
|
}
|
|
if (setsockopt (s, IPPROTO_IP, IP_MULTICAST_IF, (void *)&iaddr,
|
|
sizeof iaddr) == -1)
|
|
{
|
|
log_error ("rekey_udp_make: Setting IP_MULTICAST_IF failed");
|
|
goto err;
|
|
}
|
|
if (setsockopt (s, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&ttl,
|
|
sizeof ttl) == -1)
|
|
{
|
|
log_error ("rekey_udp_make: Setting IP_MULTICAST_TTL failed");
|
|
goto err;
|
|
}
|
|
if (setsockopt (s, IPPROTO_IP, IP_MULTICAST_LOOP, (void *)&loop,
|
|
sizeof loop) == -1)
|
|
{
|
|
log_error ("rekey_udp_make: Setting IP_MULTICAST_LOOP failed");
|
|
goto err;
|
|
}
|
|
}
|
|
conf_free_list (listen_on);
|
|
}
|
|
}
|
|
|
|
t->s = s;
|
|
transport_add (&t->transport);
|
|
transport_reference (&t->transport);
|
|
t->transport.flags |= TRANSPORT_LISTEN;
|
|
return &t->transport;
|
|
|
|
err:
|
|
if (s != -1)
|
|
close (s);
|
|
if (t)
|
|
free (t);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Receive a rekey message. Based on message_recv().
|
|
*/
|
|
static int
|
|
rekey_message_recv (struct message *msg)
|
|
{
|
|
u_int8_t *buf = msg->iov[0].iov_base;
|
|
size_t sz = msg->iov[0].iov_len;
|
|
int exch_type;
|
|
u_int8_t flags;
|
|
struct gdoi_kek *stored_kek;
|
|
enum cryptoerr err;
|
|
u_int8_t *cookies;
|
|
struct sa *sa;
|
|
|
|
/* Possibly dump a raw hex image of the message to the log channel. */
|
|
message_dump_raw ("message_recv", msg, LOG_MESSAGE);
|
|
|
|
/* Messages shorter than an ISAKMP header are bad. */
|
|
if (sz < ISAKMP_HDR_SZ || sz != GET_ISAKMP_HDR_LENGTH (buf))
|
|
{
|
|
log_print ("message_recv: bad message length");
|
|
message_drop (msg, ISAKMP_NOTIFY_UNEQUAL_PAYLOAD_LENGTHS, 0, 1, 1);
|
|
return -1;
|
|
}
|
|
|
|
cookies = buf + ISAKMP_HDR_COOKIES_OFF;
|
|
stored_kek = gdoi_get_kek_by_cookies (cookies);
|
|
if (!stored_kek)
|
|
{
|
|
log_print ("rekey_message_recv: SA not found in rekey SA list");
|
|
log_print ("rekey_message_recv: cookie pair:): "
|
|
"%02x%02x%02x%02x%02x%02x%02x%02x "
|
|
"%02x%02x%02x%02x%02x%02x%02x%02x",
|
|
cookies[0], cookies[1], cookies[2], cookies[3], cookies[4],
|
|
cookies[5], cookies[6], cookies[7], cookies[8], cookies[9],
|
|
cookies[10], cookies[11], cookies[12], cookies[13], cookies[14],
|
|
cookies[15]);
|
|
return -1;
|
|
}
|
|
|
|
if (GET_ISAKMP_HDR_NEXT_PAYLOAD (buf) >= ISAKMP_PAYLOAD_PRIVATE_MAX)
|
|
{
|
|
log_print ("message_recv: "
|
|
"invalid payload type %d in ISAKMP header "
|
|
"(check passphrases, if applicable and in Phase 1)",
|
|
GET_ISAKMP_HDR_NEXT_PAYLOAD (buf));
|
|
return -1;
|
|
}
|
|
|
|
/* Validate that the message is of version 1.0. */
|
|
if (ISAKMP_VERSION_MAJOR (GET_ISAKMP_HDR_VERSION (buf)) != 1)
|
|
{
|
|
log_print ("message_recv: invalid version major %d",
|
|
ISAKMP_VERSION_MAJOR (GET_ISAKMP_HDR_VERSION (buf)));
|
|
return -1;
|
|
}
|
|
|
|
if (ISAKMP_VERSION_MINOR (GET_ISAKMP_HDR_VERSION (buf)) != 0)
|
|
{
|
|
log_print ("message_recv: invalid version minor %d",
|
|
ISAKMP_VERSION_MINOR (GET_ISAKMP_HDR_VERSION (buf)));
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Validate the exchange type. It must be a rekey message type. If not,
|
|
* ignore it.
|
|
*/
|
|
exch_type = GET_ISAKMP_HDR_EXCH_TYPE (buf);
|
|
if (exch_type != GDOI_EXCH_PUSH_MODE)
|
|
{
|
|
log_print ("message_recv: invalid exchange type %s",
|
|
constant_name (isakmp_exch_cst, exch_type));
|
|
return -1;
|
|
}
|
|
msg->exchange = exchange_create (1, 0, GROUP_DOI_GDOI, exch_type);
|
|
if (!msg->exchange)
|
|
{
|
|
log_print ("rekey_message_recv: failed to allocate exchange");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Save the cookies for later use in finding the stored KEK
|
|
*/
|
|
memcpy(msg->exchange->cookies, cookies, ISAKMP_HDR_COOKIES_LEN);
|
|
|
|
/*
|
|
* Check for unrecognized flags. Only the encryption flag is valid for now.
|
|
*/
|
|
flags = GET_ISAKMP_HDR_FLAGS (buf);
|
|
if (flags != ISAKMP_FLAGS_ENC)
|
|
{
|
|
log_print ("rekey_message_recv: invalid flags 0x%x",
|
|
GET_ISAKMP_HDR_FLAGS (buf));
|
|
return -1;
|
|
}
|
|
|
|
if (flags & ISAKMP_FLAGS_ENC)
|
|
{
|
|
msg->orig = malloc (sz);
|
|
if (!msg->orig)
|
|
{
|
|
message_free (msg);
|
|
return -1;
|
|
}
|
|
memcpy (msg->orig, buf, sz);
|
|
|
|
/*
|
|
* Setup the crypto vectors based on the algorithm. We have to translate
|
|
* The GDOI algorithm number to the IKE one in order to use the crypto
|
|
* routines....
|
|
*/
|
|
switch (stored_kek->encrypt_alg)
|
|
{
|
|
case GDOI_KEK_ALG_3DES:
|
|
msg->exchange->crypto = crypto_get(TRIPLEDES_CBC);
|
|
break;
|
|
case GDOI_KEK_ALG_AES:
|
|
msg->exchange->crypto = crypto_get(AES_CBC_128);
|
|
break;
|
|
default:
|
|
log_error ("decode_kd_kek_attribute: "
|
|
"Unknown KEK secrecy algorithm: %d",
|
|
stored_kek->encrypt_alg);
|
|
return -1;
|
|
}
|
|
msg->exchange->keystate = crypto_init (msg->exchange->crypto,
|
|
stored_kek->encrypt_key,
|
|
msg->exchange->crypto->keymax, &err);
|
|
/*
|
|
* Re-install the static IV into the crypto state
|
|
* each time we do an encryption.
|
|
*/
|
|
crypto_init_iv (msg->exchange->keystate, stored_kek->encrypt_iv,
|
|
msg->exchange->keystate->xf->blocksize);
|
|
|
|
rekey_crypto_decrypt (msg->exchange->keystate, buf + ISAKMP_HDR_SZ,
|
|
sz - ISAKMP_HDR_SZ);
|
|
}
|
|
else
|
|
msg->orig = buf;
|
|
msg->orig_sz = sz;
|
|
|
|
/*
|
|
* Check the overall payload structure at the same time as indexing them by
|
|
* type.
|
|
*/
|
|
if (GET_ISAKMP_HDR_NEXT_PAYLOAD (buf) != ISAKMP_PAYLOAD_NONE
|
|
&& message_sort_payloads (msg, GET_ISAKMP_HDR_NEXT_PAYLOAD (buf)))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Run generic payload tests now. If anything fails these checks, the
|
|
* message needs either to be retained for later duplicate checks or
|
|
* freed entirely.
|
|
* XXX Should SAs and even transports be cleaned up then too?
|
|
*/
|
|
if (message_validate_payloads (msg))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* HACK! message_validate_sa() Adds gratuitously create an SA payload for
|
|
* us, but we don't need it. That SA payload is intended to be used as the
|
|
sa->isakmp_sa but we don't need it for the rekey message. So remove it here.
|
|
*/
|
|
sa = TAILQ_FIRST(&msg->exchange->sa_list);
|
|
if (sa)
|
|
{
|
|
TAILQ_REMOVE(&msg->exchange->sa_list, sa, next);
|
|
sa_release(sa);
|
|
sa = NULL;
|
|
}
|
|
|
|
/*
|
|
* Now we can validate DOI-specific exchange types. If we have no SA
|
|
* DOI-specific exchange types are definitely wrong.
|
|
*/
|
|
if (exch_type >= ISAKMP_EXCH_DOI_MIN && exch_type <= ISAKMP_EXCH_DOI_MAX
|
|
&& msg->exchange->doi->validate_exchange (exch_type))
|
|
{
|
|
log_print ("message_recv: invalid DOI exchange type %d", exch_type);
|
|
return -1;
|
|
}
|
|
|
|
/* Handle the flags. */
|
|
if (flags & ISAKMP_FLAGS_ENC)
|
|
msg->exchange->flags |= EXCHANGE_FLAG_ENCRYPT;
|
|
if ((msg->exchange->flags & EXCHANGE_FLAG_COMMITTED) == 0
|
|
&& (flags & ISAKMP_FLAGS_COMMIT))
|
|
msg->exchange->flags |= EXCHANGE_FLAG_HE_COMMITTED;
|
|
|
|
/* OK let the exchange logic do the rest. */
|
|
exchange_enter (msg->exchange);
|
|
exchange_run (msg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct transport *
|
|
rekey_udp_create (char *name)
|
|
{
|
|
struct transport *t;
|
|
struct udp_transport *u;
|
|
|
|
t = malloc (sizeof *u);
|
|
if (!t)
|
|
{
|
|
log_error ("rekey_udp_create: malloc (%d) failed", sizeof *u);
|
|
return 0;
|
|
}
|
|
|
|
u = (struct udp_transport *)t;
|
|
u->transport.vtbl = &rekey_udp_transport_vtbl;
|
|
|
|
return t;
|
|
}
|
|
|
|
/*
|
|
* A message has arrived on transport T's socket. If T is single-ended,
|
|
* clone it into a double-ended transport which we will use from now on.
|
|
* Package the message as we want it and continue processing in the message
|
|
* module.
|
|
*/
|
|
static void
|
|
rekey_udp_handle_message (struct transport *t)
|
|
{
|
|
struct udp_transport *u = (struct udp_transport *)t;
|
|
u_int8_t buf[UDP_SIZE];
|
|
struct sockaddr_in from;
|
|
int len = sizeof from;
|
|
ssize_t n;
|
|
struct message *msg;
|
|
|
|
log_print("rekey_udp_handle_message: GOT A REKEY MESSAGE!!!");
|
|
|
|
n = recvfrom (u->s, buf, UDP_SIZE, 0, (struct sockaddr *)&from,(socklen_t *)&len);
|
|
if (n == -1)
|
|
{
|
|
log_error ("recvfrom (%d, %p, %d, %d, %p, %p)", u->s, buf, UDP_SIZE, 0,
|
|
&from, &len);
|
|
return;
|
|
}
|
|
|
|
msg = message_alloc (t, buf, n);
|
|
if (!msg)
|
|
{
|
|
log_print("rekey_udp_handle_message: No msg allocated");
|
|
return;
|
|
}
|
|
rekey_message_recv (msg);
|
|
transport_release (t);
|
|
}
|
|
|
|
/* Physically send the message MSG over its associated transport. */
|
|
static int
|
|
rekey_udp_send_message (struct message *msg)
|
|
{
|
|
struct udp_transport *u = (struct udp_transport *)msg->transport;
|
|
ssize_t n;
|
|
struct msghdr m;
|
|
|
|
/*
|
|
* Sending on connected sockets requires that no destination address is
|
|
* given, or else EISCONN will occur.
|
|
*/
|
|
m.msg_name = (caddr_t)&u->dst;
|
|
m.msg_namelen = sizeof u->dst;
|
|
m.msg_iov = msg->iov;
|
|
m.msg_iovlen = msg->iovlen;
|
|
m.msg_control = 0;
|
|
m.msg_controllen = 0;
|
|
m.msg_flags = 0;
|
|
n = sendmsg (u->s, &m, 0);
|
|
if (n == -1)
|
|
{
|
|
log_error ("sendmsg (%d, %p, %d)", u->s, &m, 0);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
enum {
|
|
ReplayWindowSize = 32
|
|
};
|
|
|
|
int ChkReplayWindow(u_int32_t seq);
|
|
|
|
/*
|
|
* Validate the sequence number.
|
|
* HACK! THe following does not yet match the draft
|
|
*
|
|
* Cribbed from RFC 2401 Appendix C
|
|
*
|
|
* Returns 0 if packet disallowed, 1 if packet permitted
|
|
*/
|
|
static int gdoi_seq_valid (struct gdoi_kek *stored_kek,
|
|
u_int32_t received_seq)
|
|
{
|
|
u_int32_t diff;
|
|
|
|
if (received_seq == 0) return 0; /* first == 0 or wrapped */
|
|
if (received_seq > stored_kek->current_seq_num) {
|
|
/* new larger sequence number */
|
|
diff = received_seq - stored_kek->current_seq_num;
|
|
if (diff < ReplayWindowSize) { /* In window */
|
|
stored_kek->replay_bitmap <<= diff;
|
|
stored_kek->replay_bitmap |= 1; /* set bit for this packet */
|
|
} else stored_kek->replay_bitmap = 1; /* This packet has a "way larger" */
|
|
stored_kek->current_seq_num = received_seq;
|
|
return 1; /* larger is good */
|
|
}
|
|
diff = stored_kek->current_seq_num - received_seq;
|
|
if (diff >= ReplayWindowSize) return 0; /* too old or wrapped */
|
|
if (stored_kek->replay_bitmap & ((u_int32_t)1 << diff)) return 0;
|
|
/* already seen */
|
|
stored_kek->replay_bitmap |= ((u_int32_t)1 << diff); /* mark as seen */
|
|
return 1; /* out of order but good */
|
|
}
|
|
|
|
/*
|
|
* Handle a rekey message. Note that it has already been decrypted.
|
|
*/
|
|
static int responder_recv_SEQ_SA_KD_SIG (struct message *msg)
|
|
{
|
|
struct exchange *exchange = msg->exchange;
|
|
struct payload *sigp, *p;
|
|
struct gdoi_kek *stored_kek;
|
|
u_int32_t seq;
|
|
u_int8_t *begin, *end;
|
|
struct hash *hash;
|
|
u_int8_t *computed_hash, *decrypted_hash;
|
|
int siglen, found_delete = 0;
|
|
|
|
/*
|
|
* Find the current KEK policy first.
|
|
*/
|
|
stored_kek = gdoi_get_kek_by_cookies (exchange->cookies);
|
|
if (!stored_kek)
|
|
{
|
|
log_print ("responder_recv_SEQ_SA_KD_SIG: "
|
|
"KEK policy missing from exchange");
|
|
goto cleanup;
|
|
}
|
|
/*
|
|
* Set the exchange name for reporting convienience and to match the
|
|
* SAs up with other policy by name.
|
|
*/
|
|
if (!exchange->name)
|
|
{
|
|
exchange->name = strdup(stored_kek->exchange_name);
|
|
}
|
|
|
|
/* Handle SIG payload */
|
|
sigp = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SIG]);
|
|
if (sigp)
|
|
{
|
|
sigp->flags |= PL_MARK;
|
|
/*
|
|
* Compute the hash
|
|
*/
|
|
hash = hash_get(xlate_gdoi_hash(stored_kek->sig_hash_alg));
|
|
computed_hash = malloc (hash->hashsize);
|
|
if (!computed_hash)
|
|
{
|
|
log_error ("responder_recv_SEQ_SA_KD_SIG: "
|
|
"malloc (%d) failed", hash->hashsize);
|
|
}
|
|
|
|
/* Start with the characters in 'rekey' */
|
|
hash->Init (hash->ctx);
|
|
LOG_DBG_BUF ((LOG_MISC, 90, "responder_recv_SEQ_SA_KD_SIG: 'rekey'",
|
|
(u_int8_t *)REKEY_HEADER_STRING, strlen(REKEY_HEADER_STRING)));
|
|
hash->Update (hash->ctx, (u_int8_t *)REKEY_HEADER_STRING,
|
|
strlen(REKEY_HEADER_STRING));
|
|
begin = msg->iov[0].iov_base;
|
|
end = sigp->p;
|
|
LOG_DBG_BUF ((LOG_MISC, 90,
|
|
"responder_recv_SEQ_SA_KD_SIG: packet before SIG payload",
|
|
begin, (end-begin)));
|
|
hash->Update (hash->ctx, begin, (end-begin));
|
|
hash->Final (computed_hash, hash->ctx);
|
|
LOG_DBG_BUF ((LOG_NEGOTIATION, 80,
|
|
"responder_recv_SEQ_SA_KD_SIG: computed hash",
|
|
computed_hash, hash->hashsize));
|
|
/*
|
|
* Validate the signature
|
|
* First check that the sig is of the correct size.
|
|
*/
|
|
siglen = GET_ISAKMP_GEN_LENGTH (sigp->p) - ISAKMP_SIG_SZ;
|
|
if (siglen != RSA_size (stored_kek->rsa_keypair))
|
|
{
|
|
log_print ("responder_recv_SEQ_SA_KD_SIG: "
|
|
"SIG payload length does not match public key");
|
|
return -1;
|
|
}
|
|
decrypted_hash = malloc (siglen);
|
|
if (!decrypted_hash)
|
|
{
|
|
log_error ("responder_recv_SEQ_SA_KD_SIG: "
|
|
"malloc (%d) failed", siglen);
|
|
return -1;
|
|
}
|
|
|
|
siglen = RSA_public_decrypt (siglen, sigp->p + ISAKMP_SIG_DATA_OFF,
|
|
decrypted_hash, stored_kek->rsa_keypair, RSA_PKCS1_PADDING);
|
|
if (siglen == -1)
|
|
{
|
|
ERR_load_crypto_strings();
|
|
log_print ("responder_recv_SEQ_SA_KD_SIG: "
|
|
"RSA_public_decrypt () failed: %s",
|
|
ERR_error_string(ERR_get_error(),NULL));
|
|
free(decrypted_hash);
|
|
return -1;
|
|
}
|
|
LOG_DBG_BUF ((LOG_NEGOTIATION, 80,
|
|
"responder_recv_SEQ_SA_KD_SIG: decrypted hash",
|
|
decrypted_hash, hash->hashsize));
|
|
|
|
if (memcmp(computed_hash, decrypted_hash, hash->hashsize))
|
|
{
|
|
log_print("responder_recv_SEQ_SA_KD_SIG: "
|
|
"Computed hash does not match decrypted hash!");
|
|
free(decrypted_hash);
|
|
return -1;
|
|
}
|
|
free(decrypted_hash);
|
|
}
|
|
else
|
|
{
|
|
log_print("responder_recv_SEQ_SA_KD_SIG: Missing SIG payload!");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Handle SEQ paylaod */
|
|
p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SEQ]);
|
|
if (p)
|
|
{
|
|
p->flags |= PL_MARK;
|
|
seq = GET_GDOI_SEQ_SEQ_NUM(p->p);
|
|
log_print ("GOT SEQ # of: %d (PUSH)", seq);
|
|
if (gdoi_seq_valid(stored_kek, seq))
|
|
{
|
|
stored_kek->current_seq_num = seq;
|
|
}
|
|
else
|
|
{
|
|
log_print("responder_recv_SEQ_SA_KD_SIG: "
|
|
"Sequence number out of range: previous %d, received %d",
|
|
stored_kek->current_seq_num, seq);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_print("responder_recv_SEQ_SA_KD_SIG: Missing SEQ payload!");
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* There must be either an SA/KD pair, or DELETEs in the message, or both
|
|
* (in which case the DELETEs are handled first).
|
|
*/
|
|
p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_DELETE]);
|
|
if (p)
|
|
{
|
|
found_delete=1;
|
|
/*
|
|
* Loop through the DELETE payloads and handle them.
|
|
*/
|
|
for (p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_DELETE]); p;
|
|
p = TAILQ_NEXT (p, link))
|
|
{
|
|
gdoi_process_delete_payload (msg, p);
|
|
}
|
|
}
|
|
|
|
p = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SA]);
|
|
if (p)
|
|
{
|
|
/* Handle SA payload */
|
|
if (gdoi_process_SA_payload (msg))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Handle KD payload */
|
|
if (gdoi_process_KD_payload (msg))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!found_delete)
|
|
{
|
|
log_print("responder_recv_SEQ_SA_KD_SIG: Rekey message contains "
|
|
"neither SA payload or DELETE paylaod. Aborting");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
cleanup:
|
|
/*
|
|
* Return a non-error return, otherwise the message will get torn down,
|
|
* which tears down the transport, and then we don't receive any more rekey
|
|
* messages. One bad message doesn't mean the rest will be bad (and could
|
|
* even have been sent or replayed by an attacker.
|
|
*/
|
|
log_print("responder_recv_SEQ_SA_KD_SIG: "
|
|
"Aborting processing of Rekey message");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Find the given SA on any rekey exchange SA lists and remove it.
|
|
*/
|
|
void
|
|
gdoi_rekey_free_sa (struct sa *sa_to_remove)
|
|
{
|
|
|
|
struct gdoi_kek *node;
|
|
struct sa *sa;
|
|
|
|
for (node = TAILQ_FIRST (&gdoi_kek_queue); node;
|
|
node = TAILQ_NEXT (node, link))
|
|
{
|
|
if (!node->send_exchange)
|
|
{
|
|
continue;
|
|
}
|
|
for (sa = TAILQ_FIRST (&node->send_exchange->sa_list);
|
|
sa; sa = TAILQ_NEXT (sa, next))
|
|
{
|
|
if (sa == sa_to_remove)
|
|
{
|
|
LOG_DBG ((LOG_SA, 60, "gdoi_rekey_free_sa: "
|
|
"freeing SA %p from exchange %p",
|
|
sa, node->send_exchange));
|
|
TAILQ_REMOVE (&node->send_exchange->sa_list, sa, next);
|
|
/*
|
|
* We're not deleting sa here, so it's pointer to the
|
|
* next SA should be correct.
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
}
|