5420 lines
164 KiB
C
5420 lines
164 KiB
C
/* -*- Mode:cc-mode; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
|
|
/* vim: set ai sw=2 ts=2 et cindent cino={1s: */
|
|
/*
|
|
* Host Identity Protocol
|
|
* Copyright (c) 2002-2012 the Boeing Company
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*
|
|
* \file hip_input.c
|
|
*
|
|
* \authors Jeff Ahrenholz, <jeffrey.m.ahrenholz@boeing.com>
|
|
* Tom Henderson, <thomas.r.henderson@boeing.com>
|
|
*
|
|
* \brief Receving functions for handling HIP control packets.
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h> /* stderr, etc */
|
|
#include <stdlib.h> /* rand() */
|
|
#include <errno.h> /* strerror(), errno */
|
|
#include <string.h> /* memset() */
|
|
#include <time.h> /* time() */
|
|
#include <ctype.h> /* tolower() */
|
|
#include <sys/types.h> /* getpid() support, etc */
|
|
#ifdef __WIN32__
|
|
#include <process.h> /* _beginthread() */
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <win32/types.h>
|
|
#include <win32/ip.h>
|
|
#else
|
|
#ifdef __MACOSX__
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/in.h>
|
|
#else
|
|
#include <asm/types.h>
|
|
#endif /* __MACOSX__ */
|
|
#include <netinet/ip.h> /* struct iphdr */
|
|
#include <netinet/ip_icmp.h> /* struct icmp */
|
|
#include <sys/time.h> /* gettimeofday() */
|
|
#include <pthread.h> /* for RVS lifetime thread */
|
|
#include <unistd.h> /* sleep() */
|
|
#endif /* __WIN32__ */
|
|
#include <openssl/crypto.h> /* OpenSSL's crypto library */
|
|
#include <openssl/bn.h> /* Big Numbers */
|
|
#include <openssl/des.h> /* 3DES support */
|
|
#include <openssl/blowfish.h> /* BLOWFISH support */
|
|
#include <openssl/aes.h> /* AES support */
|
|
#include <openssl/dsa.h> /* DSA support */
|
|
#include <openssl/asn1.h> /* DSAparams_dup() */
|
|
#include <openssl/dh.h> /* Diffie-Hellman contexts */
|
|
#include <openssl/sha.h> /* SHA1 algorithms */
|
|
#include <openssl/rand.h> /* RAND_bytes() */
|
|
#include <hip/hip_types.h>
|
|
#include <hip/hip_proto.h>
|
|
#include <hip/hip_globals.h>
|
|
#include <hip/hip_funcs.h>
|
|
#include <hip/hip_sadb.h>
|
|
#include <stdbool.h>
|
|
#include <openssl/x509.h>
|
|
|
|
#ifdef HIP_VPLS
|
|
#include <hip/hip_cfg_api.h>
|
|
#endif /* HIP_VPLS */
|
|
|
|
/*
|
|
* Local function declarations
|
|
*/
|
|
int hip_parse_I1(hip_assoc *hip_a, const __u8 *data, hip_hit *hiti,
|
|
hip_hit *hitr);
|
|
int hip_parse_R1(const __u8 *data, hip_assoc *hip_a);
|
|
int hip_parse_I2(const __u8 *data, hip_assoc **hip_ar, hi_node *my_host_id,
|
|
struct sockaddr *src, struct sockaddr *dst);
|
|
int hip_parse_R2(__u8 *data, hip_assoc *hip_a);
|
|
int hip_parse_close(const __u8 *data, hip_assoc *hip_a, __u32 *nonce);
|
|
int validate_hmac(const __u8 *data, int data_len, __u8 *hmac, int hmac_len,
|
|
__u8 *key, int type);
|
|
hi_node *check_if_my_hit(hip_hit *hit);
|
|
int handle_transforms(hip_assoc *hip_a, __u16 *transforms, int length, int esp);
|
|
int handle_hip_cipher(hip_assoc *hip_a, __u16 *transforms, int length);
|
|
int handle_cert(hip_assoc *hip_a, const __u8 *data);
|
|
int handle_dh(hip_assoc *hip_a, const __u8 *data, __u8 *g, EVP_PKEY *evp_dh);
|
|
int handle_acks(hip_assoc *hip_a, tlv_ack *ack);
|
|
int handle_esp_info(tlv_esp_info *ei, __u32 spi_out, struct rekey_info *rk);
|
|
int handle_locators(hip_assoc *hip_a, locator **locators,
|
|
int num, struct sockaddr *src,
|
|
__u32 new_spi);
|
|
void finish_address_check(hip_assoc *hip_a, __u32 nonce, struct sockaddr *src);
|
|
int handle_update_rekey(hip_assoc *hip_a);
|
|
int handle_update_readdress(hip_assoc *hip_a, struct sockaddr **addrcheck);
|
|
void update_peer_list(hip_assoc *hip_a);
|
|
void log_sa_info(hip_assoc *hip_a);
|
|
int check_tlv_type_length(int type, int length, int last_type, char *p);
|
|
int check_tlv_length(int type, int length);
|
|
int check_tlv_unknown_critical(int type, int length);
|
|
#ifdef __MACOSX__
|
|
extern int next_divert_rule();
|
|
extern void add_divert_rule(int,int,char *);
|
|
extern void del_divert_rule(int);
|
|
#endif
|
|
int handle_reg_info(hip_assoc *hip_a, const __u8 *data);
|
|
int handle_reg_request(hip_assoc *hip_a, const __u8 *data);
|
|
int handle_reg_response(hip_assoc *hip_a, const __u8 *data);
|
|
int handle_reg_failed(hip_assoc *hip_a, const __u8 *data);
|
|
int add_reg_info(struct reg_entry *regs, __u8 type, int state, __u8 lifetime);
|
|
int delete_reg_info(struct reg_entry *regs, __u8 type);
|
|
int add_from_via(hip_assoc *hip_a, __u16 type, struct sockaddr *addr,
|
|
__u8* address);
|
|
int handle_dh_groups(__u8 *dh_group_id, int length, bool is_responder);
|
|
|
|
|
|
int handle_hit_suite_list(hip_assoc *hip_a, __u8 *id, __u16 length);
|
|
|
|
/*
|
|
*
|
|
* function hip_parse_hdr()
|
|
*
|
|
* in: data = raw socket bytes
|
|
* len = length of data
|
|
* src, dst = pointer for storing addresses for IPv4,
|
|
* or supplying them for IPv6, in network byte order
|
|
* family = address family, AF_INET or AF_INET6
|
|
*
|
|
* - parse raw socket data to get a HIP packet
|
|
* - sanity check the HIP header
|
|
* - checksum the packet
|
|
* - return addresses (for IPv4) and a pointer to the HIP header
|
|
*
|
|
*/
|
|
int hip_parse_hdr(__u8 *data, int len, struct sockaddr *src,
|
|
struct sockaddr *dst, __u16 family, hiphdr **hdr)
|
|
{
|
|
hiphdr* hiph;
|
|
__u16 checksum;
|
|
struct sockaddr_in addr;
|
|
struct ip *iph;
|
|
udphdr *udph = NULL;
|
|
char typestr[12];
|
|
|
|
/* IPv4 - get source and destination addresses */
|
|
if (family == AF_INET)
|
|
{
|
|
iph = (struct ip*) &data[0];
|
|
hiph = (hiphdr*) &data[hip_header_offset(data)];
|
|
*hdr = hiph;
|
|
memset(&addr, 0, sizeof(struct sockaddr_in));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = iph->ip_src.s_addr;
|
|
if (iph->ip_p == IPPROTO_UDP)
|
|
{
|
|
udph = (udphdr*)&data[sizeof(struct ip)];
|
|
addr.sin_port = udph->src_port;
|
|
}
|
|
memcpy(src, &addr, sizeof(struct sockaddr_in));
|
|
addr.sin_addr.s_addr = iph->ip_dst.s_addr;
|
|
if (udph)
|
|
{
|
|
addr.sin_port = udph->dst_port;
|
|
}
|
|
memcpy(dst, &addr, sizeof(struct sockaddr_in));
|
|
/* IPv6 - source and destination addresses already supplied */
|
|
}
|
|
else if (family == AF_INET6)
|
|
{
|
|
iph = NULL;
|
|
hiph = (hiphdr*) &data[0];
|
|
*hdr = hiph;
|
|
/* TODO: detect HIP over UDP as with the IPv4 case */
|
|
}
|
|
else
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
if ((hiph->nxt_hdr != 0) &&
|
|
(hiph->nxt_hdr != IPPROTO_NONE))
|
|
{
|
|
log_(WARN, "Packet warning: nxt_hdr %u, ", hiph->nxt_hdr);
|
|
log_(NORM, "trailing data will be ignored!\n");
|
|
}
|
|
if ((hiph->hdr_len < 4) || ((hiph->hdr_len + 1) * 8 > len))
|
|
{
|
|
log_(WARN, "Packet error: hdr_len %u\n", hiph->hdr_len);
|
|
return(-2);
|
|
}
|
|
if ((hiph->packet_type < HIP_I1) || (hiph->packet_type > CLOSE_ACK))
|
|
{
|
|
log_(WARN, "Packet error: type %u\n", hiph->packet_type);
|
|
return(-2);
|
|
}
|
|
if (hiph->version != HIP_PROTO_VER)
|
|
{
|
|
int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
|
|
if(sockfd<0) {
|
|
log_(WARN, "Socket file desciptor not received\n");
|
|
return(-2);
|
|
}
|
|
|
|
struct icmp pckt;
|
|
pckt.icmp_type = ICMP_PARAMPROB;
|
|
pckt.icmp_hun.ih_pptr = 3;
|
|
pckt.icmp_cksum = checksum_packet((__u8*)&pckt, SA(&addr), dst);
|
|
if( sendto(sockfd, &pckt, sizeof(pckt), 0, (struct sockaddr*)&addr, sizeof(addr)) <= 0 ) {
|
|
log_(WARN, "Packet error: failed to send icmp\n");
|
|
}
|
|
log_(WARN, "Packet error: version %u res %u\n",
|
|
hiph->version, hiph->res);
|
|
return(-2);
|
|
}
|
|
if (hiph->res != HIP_RES_SHIM6_BITS)
|
|
{
|
|
log_(WARN, "Packet warning: version %u res %u, ",
|
|
hiph->version, hiph->res);
|
|
log_(NORM, "unknown reserved bits set in HIP header!\n");
|
|
}
|
|
|
|
if (hiph->control != 0)
|
|
{
|
|
/* Parse control bits */
|
|
/* TODO: check (hiph->control & CTL_ANON) against global
|
|
* policy for allowing ANONYMOUS HIs */
|
|
log_(WARN, "Ignoring control bits 0x%x in the HIP header.\n",
|
|
hiph->control);
|
|
}
|
|
|
|
/* HIP encapsulated in UDP, skip checksum verification */
|
|
if (iph && (iph->ip_p == IPPROTO_UDP))
|
|
{
|
|
/* assume RAW socket has already enforced the UDP checksum */
|
|
if (hiph->checksum != 0) /* checksum MUST be zero */
|
|
{
|
|
log_(WARN, "HIP header encapsulated in UDP contains a "
|
|
"non-zero checksum 0x%x\n", hiph->checksum);
|
|
return(-4);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
checksum = checksum_packet((__u8*)hiph, src, dst);
|
|
if (checksum != 0)
|
|
{
|
|
hip_packet_type(hiph->packet_type, typestr);
|
|
log_(WARN, "HIP %s packet has bad checksum: sum=0x%x, should"
|
|
" be 0.\n", typestr, checksum);
|
|
return(-3);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* function hip_parse_I1()
|
|
*
|
|
* in: hip_a = pointer to any pre-existing association with RVS for
|
|
* verifying the RVS_HMAC parameter
|
|
* data = pointer to hip header in received data
|
|
* hiti = pointer to Initiator's HIT to extract
|
|
* hitr = pointer to Responder's HIT to extract
|
|
*
|
|
* out: returns 0 if successful, -1 otherwise
|
|
*
|
|
* parse HIP Initiator packet-- has already passed initial just
|
|
*
|
|
*/
|
|
int hip_parse_I1(hip_assoc *hip_a, const __u8 *data, hip_hit *hiti,
|
|
hip_hit *hitr)
|
|
{
|
|
int location = 0, data_len = 0, rec_num = 0;
|
|
int last_type, type, length, len;
|
|
tlv_head *tlv;
|
|
tlv_from *from;
|
|
unsigned char *rvs_hmac;
|
|
|
|
hiphdr *hiph = (hiphdr*) data;
|
|
memcpy(hiti, hiph->hit_sndr, sizeof(hip_hit)); /* Initiator's HIT */
|
|
memcpy(hitr, hiph->hit_rcvr, sizeof(hip_hit)); /* Responder's HIT */
|
|
data_len = location + ((hiph->hdr_len + 1) * 8);
|
|
location += sizeof(hiphdr);
|
|
last_type = 0;
|
|
|
|
if (hits_equal(*hiti, zero_hit))
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
while (location < data_len)
|
|
{
|
|
tlv = (tlv_head*) &data[location];
|
|
type = ntohs(tlv->type);
|
|
length = ntohs(tlv->length);
|
|
rec_num++;
|
|
log_(NORM, " I1 TLV type = %d length = %d \n", type, length);
|
|
if (last_type > type)
|
|
{
|
|
log_(WARN, "Out of order TLV parameter, (%d > %d) ",
|
|
last_type, type);
|
|
log_(NORM, "malformed packet.\n");
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
last_type = type;
|
|
}
|
|
|
|
if (type == PARAM_FROM)
|
|
{
|
|
from = (tlv_from*) &data[location];
|
|
if (!hip_a)
|
|
{
|
|
log_(WARN, "I1 contains FROM but there " \
|
|
"is no association.\n");
|
|
return(-1);
|
|
}
|
|
if (length > (sizeof(tlv_from) - 4))
|
|
{
|
|
log_(NORM, "Ignoring extra address data.\n");
|
|
}
|
|
add_from_via(hip_a, PARAM_FROM, NULL, from->address);
|
|
}
|
|
else if (type == PARAM_RVS_HMAC)
|
|
{
|
|
if (!hip_a)
|
|
{
|
|
log_(WARN, "I1 contains RVS_HMAC but there " \
|
|
"is no association.\n");
|
|
return(-1);
|
|
}
|
|
rvs_hmac = ((tlv_hmac*)tlv)->hmac;
|
|
/* reset the length and checksum for the HMAC */
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
log_(NORM, "RVS_HMAC verify over %d bytes. ",len);
|
|
log_(NORM, "hdr length=%d \n", hiph->hdr_len);
|
|
if (validate_hmac(data, len, rvs_hmac, length,
|
|
get_key(hip_a, HIP_INTEGRITY, TRUE),
|
|
hip_a->hit_suite))
|
|
{
|
|
log_(WARN, "Invalid RVS_HMAC.\n");
|
|
if (hip_a->from_via)
|
|
{
|
|
free(hip_a->from_via);
|
|
hip_a->from_via = NULL;
|
|
}
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "RVS_HMAC verified OK.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_DH_GROUP_LIST){
|
|
|
|
tlv_dh_group_list *dhGroupList = (tlv_dh_group_list*) &data[location];
|
|
|
|
if ((handle_dh_groups(&dhGroupList->group_id, length, true)) < 0)
|
|
{
|
|
hip_send_notify(
|
|
hip_a,
|
|
NOTIFY_NO_DH_PROPOSAL_CHOSEN,
|
|
NULL,
|
|
0);
|
|
return(-1);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if (check_tlv_unknown_critical(type, length) < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
location += tlv_length_to_parameter_length(length);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Parse received I1. Look for HIT in local table. If in table,
|
|
* or if opportunistic, accept it if we have not reached MAX_CONNECTIONS.
|
|
*/
|
|
int hip_handle_I1(__u8 *buff, hip_assoc* hip_a, struct sockaddr *src,
|
|
struct sockaddr *dst)
|
|
{
|
|
hip_hit hiti, hitr;
|
|
struct sockaddr_storage addr;
|
|
hi_node* my_host_id = NULL;
|
|
hi_node* peer_host_id = NULL;
|
|
int err = 0;
|
|
int state = UNASSOCIATED;
|
|
hiphdr *hiph;
|
|
__u8 *addrp;
|
|
hip_assoc *hip_a_rvs = NULL, *hip_a_client = NULL;
|
|
|
|
hiph = (hiphdr*) buff;
|
|
|
|
if (hip_a)
|
|
{
|
|
state = hip_a->state;
|
|
}
|
|
|
|
/* send R1 in any state, except E_FAILED */
|
|
if (state < E_FAILED)
|
|
{
|
|
/* Find hip_assoc between RVS and Responder for RVS_HMAC
|
|
* verification if this is a relayed I1 */
|
|
hip_a_rvs = find_hip_association3(src, dst);
|
|
if (hip_parse_I1(hip_a_rvs, buff, &hiti, &hitr) < 0)
|
|
{
|
|
log_(WARN, "Error while processing I1, dropping.\n");
|
|
return(-1);
|
|
}
|
|
/*
|
|
* Is this my HIT? If so, get corresponding HI.
|
|
* If not and this is an RVS, check for the hit in the reg_table
|
|
*/
|
|
my_host_id = check_if_my_hit(&hitr);
|
|
if (!my_host_id && OPT.rvs)
|
|
{
|
|
hip_a_client = search_registrations(hitr, REGTYPE_RVS);
|
|
if (!hip_a_client)
|
|
{
|
|
log_ (NORMT, "Received I1 with a receiver "
|
|
"HIT that does not have a current"
|
|
"registration with this RVS.\n");
|
|
return(-1);
|
|
}
|
|
if (add_from_via(hip_a_client, PARAM_FROM, src,
|
|
NULL) < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
/* relay the I1 packet, and don't send any R1 */
|
|
hip_send_I1(&hiph->hit_sndr, hip_a_client);
|
|
return(0);
|
|
}
|
|
else if (!my_host_id) /* not in RVS mode, drop I1 */
|
|
{
|
|
log_(NORMT, "Received I1 with unknown receiver HIT:\n");
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.ss_family = AF_INET6;
|
|
memcpy(SA2IP(&addr), &hiph->hit_rcvr, HIT_SIZE);
|
|
log_(NORM, " Receiver HIT = %s\n", logaddr(SA(&addr)));
|
|
return(-1);
|
|
}
|
|
|
|
/* Find peer HIT */
|
|
peer_host_id = find_host_identity(peer_hi_head, hiti);
|
|
#ifdef HIP_VPLS
|
|
if (!hipcfg_allowed_peers(hitr, hiti))
|
|
{
|
|
log_(NORMT,"ACL denied for HIP peer\n");
|
|
return(-1);
|
|
}
|
|
if (hipcfg_verifyCert(NULL, hiti) <= 0)
|
|
{
|
|
log_(NORMT,"Cert verification failed for HIP peer\n");
|
|
return(-1);
|
|
}
|
|
log_(NORMT,"Accepted an allowed peer Endbox HIT in I1\n");
|
|
if (!peer_host_id)
|
|
{
|
|
/* Read in initiator's HIT to table */
|
|
add_peer_hit(hiti, src);
|
|
}
|
|
#else
|
|
if (!peer_host_id)
|
|
{
|
|
/* could be opportunistic */
|
|
if (!OPT.allow_any)
|
|
{
|
|
log_(NORMT, "Received I1 with unknown sender's"
|
|
" HIT, dropping (Try turning on allow any"
|
|
" option or adding peer's HIT to the %s "
|
|
"file.)\n", HIP_KNOWNID_FILENAME);
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
/* We accept opportunistic HIT */
|
|
log_(NORMT,"Accepted an unknown HIT in I1\n");
|
|
/* Read in initiator's HIT to table */
|
|
add_peer_hit(hiti, src);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
log_(NORMT, "Out of order HIP_I1 packet, state %d\n", state);
|
|
return(0);
|
|
}
|
|
if (state == I1_SENT)
|
|
{
|
|
/* peer HIT larger than my HIT */
|
|
if (compare_hits(hiti, hitr) > 0)
|
|
{
|
|
log_(NORMT, "Dropping I1 in state I1_SENT because ");
|
|
log_(NORM, "local HIT is smaller than peer HIT.\n");
|
|
return(0);
|
|
}
|
|
/* local HIT is greater than peer HIT, send R1... */
|
|
}
|
|
/*
|
|
* Relayed I1 from RVS, send to address in FROM parameter and fill
|
|
* in address for VIA_RVS parameter.
|
|
*/
|
|
if (hip_a_rvs && hip_a_rvs->from_via)
|
|
{
|
|
/* get address from FROM parameter */
|
|
memset(&addr, 0, sizeof(addr));
|
|
if (IN6_IS_ADDR_V4MAPPED((struct in6_addr*)
|
|
hip_a_rvs->from_via->address))
|
|
{
|
|
addr.ss_family = AF_INET;
|
|
addrp = &hip_a_rvs->from_via->address[12];
|
|
}
|
|
else
|
|
{
|
|
addr.ss_family = AF_INET6;
|
|
addrp = &hip_a_rvs->from_via->address[0];
|
|
}
|
|
memcpy(SA2IP(&addr), addrp, SAIPLEN(&addr));
|
|
src = SA(&addr);
|
|
if (HIPA_DST(hip_a_rvs)->sa_family == AF_INET)
|
|
{
|
|
((struct sockaddr_in *)src)->sin_port =
|
|
((struct sockaddr_in *)HIPA_DST(hip_a_rvs))->
|
|
sin_port;
|
|
}
|
|
log_(NORM, "Relayed I1 from RVS %s, ",
|
|
logaddr(HIPA_DST(hip_a_rvs)));
|
|
log_(NORM, "using %s as new destination address.\n",
|
|
logaddr(src));
|
|
/* store RVS address for VIA_RVS parameter */
|
|
add_from_via(hip_a_rvs, PARAM_VIA_RVS,
|
|
HIPA_DST(hip_a_rvs), NULL);
|
|
}
|
|
|
|
/*
|
|
* Send a pre-computed R1
|
|
*/
|
|
if ((err = hip_send_R1(dst, src, &hiti, my_host_id, hip_a_rvs)) > 0)
|
|
{
|
|
log_(NORMT, "Sent R1 (%d bytes)\n", err);
|
|
}
|
|
else
|
|
{
|
|
log_(NORMT, "Failed to send R1: %s.\n", strerror(errno));
|
|
return(-1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* function hip_parse_R1()
|
|
*
|
|
* in: data = raw socket bytes
|
|
* hip_a = contains storage for cookie, peer_hi, dh, transforms,
|
|
* opaque data, etc.
|
|
*
|
|
* out: returns 0 if successful, -1 otherwise
|
|
*
|
|
* parse HIP Responder packet
|
|
*
|
|
*/
|
|
int hip_parse_R1(const __u8 *data, hip_assoc *hip_a)
|
|
{
|
|
hiphdr *hiph;
|
|
tlv_head *tlv;
|
|
int location, data_len;
|
|
int len, type, length;
|
|
unsigned char *dh_secret_key;
|
|
int last_type = 0, status = -1, sig_verified = FALSE;
|
|
__u8 g_id = 0;
|
|
__u16 *p;
|
|
tlv_puzzle *tlv_pz = NULL;
|
|
dh_cache_entry *dh_entry;
|
|
hi_node saved_peer_hi;
|
|
hipcookie cookie_tmp;
|
|
__u64 gen;
|
|
__u8 valid_cert = FALSE;
|
|
tlv_via_rvs *via;
|
|
struct sockaddr_storage rvs_addr;
|
|
int hit_suite_id = (int)hip_a->peer_hi->hit_suite_id;
|
|
size_t rhash_len = auth_key_len_hit_suite(hit_suite_id);
|
|
|
|
location = 0;
|
|
hiph = (hiphdr*) &data[location];
|
|
data_len = location + ((hiph->hdr_len + 1) * 8);
|
|
location += sizeof(hiphdr);
|
|
memset(&saved_peer_hi, 0, sizeof(saved_peer_hi));
|
|
|
|
while (location < data_len)
|
|
{
|
|
tlv = (tlv_head*) &data[location];
|
|
type = ntohs(tlv->type);
|
|
length = ntohs(tlv->length);
|
|
if (check_tlv_type_length(type, length, last_type, "R1") < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
last_type = type;
|
|
}
|
|
/* First retrieve the HOST_ID and SIGNATURE before accepting
|
|
* the rest of the R1 packet */
|
|
if (!sig_verified && (type == PARAM_PUZZLE))
|
|
{
|
|
/* save the cookie, then zero fields for signature */
|
|
tlv_pz = (tlv_puzzle*) tlv;
|
|
/* Calculate real lenght of the cookie including the I variable */
|
|
size_t hipcookie_len = sizeof(hipcookie) - sizeof(unsigned char *) +
|
|
rhash_len;
|
|
cookie_tmp.i = (unsigned char *) malloc(rhash_len);
|
|
|
|
memcpy(&cookie_tmp, &tlv_pz->cookie, sizeof(hipcookie) -
|
|
sizeof(unsigned char *));
|
|
memcpy(cookie_tmp.i, (unsigned char *)&tlv_pz->cookie.i, rhash_len);
|
|
|
|
memset(&tlv_pz->cookie, 0, hipcookie_len);
|
|
tlv_pz->cookie.k = cookie_tmp.k;
|
|
tlv_pz->cookie.lifetime = cookie_tmp.lifetime;
|
|
}
|
|
else if (!sig_verified && (type == PARAM_HOST_ID))
|
|
{
|
|
if (hip_a->peer_hi) /* save HI in case of error */
|
|
{
|
|
memcpy(&saved_peer_hi, hip_a->peer_hi,
|
|
sizeof(saved_peer_hi));
|
|
}
|
|
if (handle_hi(&hip_a->peer_hi, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Error with HI from R1.\n");
|
|
/* no error yet, check HIT and R1 counter */
|
|
}
|
|
if (!validate_hit(hiph->hit_sndr, hip_a->peer_hi))
|
|
{
|
|
log_(WARN, "HI in R1 does not match the "
|
|
"sender's HIT\n");
|
|
hip_send_notify(hip_a, NOTIFY_INVALID_HIT,
|
|
NULL, 0);
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "HI in R1 validates the sender's "
|
|
"HIT.\n");
|
|
}
|
|
}
|
|
else if (!sig_verified && (type == PARAM_HIP_SIGNATURE_2))
|
|
{
|
|
if ((hip_a == NULL) || (hip_a->peer_hi == NULL))
|
|
{
|
|
log_(WARN, "Received signature parameter "
|
|
"without any Host Identity context for "
|
|
"verification.\n");
|
|
return(-1);
|
|
}
|
|
len = eight_byte_align(location);
|
|
memset(hiph->hit_rcvr, 0, sizeof(hip_hit));
|
|
/* cookie has already been zeroed */
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
if (validate_signature(data, len, tlv,
|
|
hip_a->peer_hi->dsa,
|
|
hip_a->peer_hi->rsa,
|
|
hip_a->peer_hi->ecdsa,
|
|
hip_a->peer_hi->hit_suite_id) < 0)
|
|
{
|
|
log_(WARN, "Invalid signature.\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_AUTHENTICATION_FAILED,
|
|
NULL, 0);
|
|
if (!OPT.permissive)
|
|
{
|
|
/* restore HI as if nothing happened */
|
|
goto restore_saved_peer_hi;
|
|
}
|
|
}
|
|
/* rewind, now accept packet parameters */
|
|
sig_verified = TRUE;
|
|
status = 0;
|
|
last_type = 0;
|
|
location = sizeof(hiphdr);
|
|
continue;
|
|
}
|
|
if (!sig_verified) /* skip all parameters until sig verified
|
|
*/
|
|
{
|
|
location += tlv_length_to_parameter_length(length);
|
|
continue;
|
|
}
|
|
|
|
if (type == PARAM_R1_COUNTER)
|
|
{
|
|
gen = ntoh64(((tlv_r1_counter*)tlv)->r1_gen_counter);
|
|
if (hip_a->state == I2_SENT)
|
|
{
|
|
/* generation counter too small */
|
|
if ((saved_peer_hi.r1_gen_count > 0) &&
|
|
(saved_peer_hi.r1_gen_count <= gen))
|
|
{
|
|
log_(WARN, "R1 generation counter too "
|
|
"small, dropping packet.\n");
|
|
/* restore HI as if nothing happened */
|
|
goto restore_saved_peer_hi;
|
|
}
|
|
/* generation counter OK, discard old state */
|
|
log_(NORM, "R1 generation counter "
|
|
"accepted, removing old state.\n");
|
|
if (hip_a->peer_dh)
|
|
{
|
|
EVP_PKEY_free(hip_a->peer_dh);
|
|
hip_a->peer_dh = NULL;
|
|
}
|
|
if (hip_a->dh_secret)
|
|
{
|
|
free(hip_a->dh_secret);
|
|
hip_a->dh_secret = NULL;
|
|
}
|
|
if (saved_peer_hi.rsa &&
|
|
(saved_peer_hi.rsa !=
|
|
hip_a->peer_hi->rsa))
|
|
{
|
|
RSA_free(saved_peer_hi.rsa);
|
|
}
|
|
if (saved_peer_hi.dsa &&
|
|
(saved_peer_hi.dsa !=
|
|
hip_a->peer_hi->dsa))
|
|
{
|
|
DSA_free(saved_peer_hi.dsa);
|
|
}
|
|
memset(hip_a->keymat, 0, KEYMAT_SIZE);
|
|
hip_a->keymat_index = 0;
|
|
}
|
|
else /* state I1_SENT or CLOSED */
|
|
{
|
|
hip_a->peer_hi->r1_gen_count = gen;
|
|
}
|
|
}
|
|
else if (type == PARAM_PUZZLE)
|
|
{
|
|
memcpy(&hip_a->cookie_r, &cookie_tmp, sizeof(hipcookie));
|
|
log_(NORM, "Got the R1 cookie: ");
|
|
print_cookie(&hip_a->cookie_r, rhash_len);
|
|
}
|
|
else if (type == PARAM_DIFFIE_HELLMAN)
|
|
{
|
|
if (handle_dh(hip_a, &data[location], &g_id,
|
|
NULL) < 0)
|
|
{
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_NO_DH_PROPOSAL_CHOSEN,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
|
|
/* group ID chosen by responder
|
|
* get a DH entry from cache or generate a new one */
|
|
dh_entry = get_dh_entry(g_id, FALSE);
|
|
dh_entry->ref_count++;
|
|
hip_a->dh_group_id = g_id;
|
|
hip_a->evp_dh = dh_entry->evp_dh;
|
|
|
|
/* compute key from our dh and peer's pub_key and
|
|
* store in dh_secret_key */
|
|
|
|
logdh(hip_a->evp_dh);
|
|
|
|
|
|
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(hip_a->evp_dh, NULL);
|
|
EVP_PKEY_derive_init(ctx);
|
|
EVP_PKEY_derive_set_peer(ctx,hip_a->peer_dh);
|
|
EVP_PKEY_derive(ctx, NULL, &hip_a->dh_secret_len);
|
|
dh_secret_key = (unsigned char *)OPENSSL_malloc(hip_a -> dh_secret_len);
|
|
EVP_PKEY_derive(ctx, dh_secret_key, &hip_a -> dh_secret_len);
|
|
set_secret_key(dh_secret_key, hip_a);
|
|
/* Do not free(dh_secret_key), which is now
|
|
* dh->dh_secret */
|
|
}
|
|
else if (type == PARAM_HIP_CIPHER)
|
|
{
|
|
p = &((tlv_hip_cipher*)tlv)->cipher_id;
|
|
if ((handle_hip_cipher(hip_a, p, length)) < 0)
|
|
{
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_NO_HIP_PROPOSAL_CHOSEN,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
}
|
|
else if (type == PARAM_ESP_TRANSFORM)
|
|
{
|
|
/* first check E bit */
|
|
if (((tlv_esp_transform*)tlv)->reserved && 0x01)
|
|
{
|
|
log_(NORM, "64-bit ESP sequence numbers reques");
|
|
log_(NORM, "ted but unsupported by kernel!\n");
|
|
if (OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
p = &((tlv_esp_transform*)tlv)->suite_id;
|
|
if ((handle_transforms(hip_a, p, length - 2,
|
|
TRUE)) < 0)
|
|
{
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_NO_ESP_PROPOSAL_CHOSEN,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
}
|
|
else if ((type == PARAM_ECHO_REQUEST) ||
|
|
(type == PARAM_ECHO_REQUEST_NOSIG))
|
|
{
|
|
/* prevent excessive memory consumption */
|
|
if (length > MAX_OPAQUE_SIZE)
|
|
{
|
|
log_(WARN,"ECHO_REQUEST in R1 is too large.\n");
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hip_a->opaque = (struct opaque_entry*)
|
|
malloc(sizeof(struct
|
|
opaque_entry));
|
|
if (hip_a->opaque == NULL)
|
|
{
|
|
log_(NORM,"Malloc err: ECHO_REQUEST\n");
|
|
return(-1);
|
|
}
|
|
hip_a->opaque->opaque_len = (__u16)length;
|
|
memcpy(hip_a->opaque->opaque_data,
|
|
((tlv_echo*)tlv)->opaque_data, length);
|
|
hip_a->opaque->opaque_nosig =
|
|
(type == PARAM_ECHO_REQUEST_NOSIG);
|
|
}
|
|
}
|
|
else if (type == PARAM_REG_INFO) /* R1 packet */
|
|
{
|
|
log_(NORM,
|
|
"Peer is a registrar providing registration "
|
|
"info in its R1 packet.\n");
|
|
if (handle_reg_info(hip_a, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Problem with registration info.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_VIA_RVS)
|
|
{
|
|
via = (tlv_via_rvs *) &data[location];
|
|
if (IN6_IS_ADDR_V4MAPPED(
|
|
(struct in6_addr*)via->address))
|
|
{
|
|
rvs_addr.ss_family = AF_INET;
|
|
memcpy(SA2IP(&rvs_addr), &via->address[12],
|
|
SAIPLEN(&rvs_addr));
|
|
}
|
|
else
|
|
{
|
|
rvs_addr.ss_family = AF_INET;
|
|
memcpy(SA2IP(&rvs_addr), via->address,
|
|
SAIPLEN(&rvs_addr));
|
|
}
|
|
log_(NORM,
|
|
"R1 packet relayed by the Rendezvous Server "
|
|
"address %s.\n",
|
|
logaddr(SA(&rvs_addr)));
|
|
/* we could check if the VIA RVS address is the same one
|
|
* that we used for the relay */
|
|
}
|
|
else if ((type == PARAM_HOST_ID) ||
|
|
(type == PARAM_HIP_SIGNATURE_2))
|
|
{
|
|
/* these parameters already processed */
|
|
}
|
|
else if(type == PARAM_HIT_SUITE_LIST){
|
|
tlv_hit_suite *hit_suite_list = (tlv_hit_suite*) &data[location];
|
|
|
|
if(handle_hit_suite_list(hip_a, &hit_suite_list->hit_suite_id, length) < 0){
|
|
log_(ERR, "Peer HIT Suites unsupported, aborting base exchange...\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_UNSUPPORTED_HIT_SUITE,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
}
|
|
else if (type == PARAM_CERT)
|
|
{
|
|
if (HCNF.peer_certificate_required &&
|
|
(handle_cert(hip_a, &data[location]) < 0))
|
|
{
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_AUTHENTICATION_FAILED,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
valid_cert = TRUE;
|
|
/* validate certificate */
|
|
}
|
|
else if (type == PARAM_LOCATOR)
|
|
{
|
|
;
|
|
}
|
|
else if(type == PARAM_DH_GROUP_LIST){
|
|
tlv_dh_group_list *dhGroupList = (tlv_dh_group_list*) &data[location];
|
|
if ((handle_dh_groups(&dhGroupList->group_id, length, false)) < 0)
|
|
{
|
|
hip_send_notify(
|
|
hip_a,
|
|
NOTIFY_NO_DH_PROPOSAL_CHOSEN,
|
|
NULL,
|
|
0);
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (check_tlv_unknown_critical(type, length) < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
location += tlv_length_to_parameter_length(length);
|
|
}
|
|
if (HCNF.peer_certificate_required && !valid_cert)
|
|
{
|
|
hip_send_notify(hip_a, NOTIFY_AUTHENTICATION_FAILED, NULL, 0);
|
|
return(-1);
|
|
}
|
|
|
|
return(status);
|
|
restore_saved_peer_hi:
|
|
if (hip_a->peer_hi->dsa &&
|
|
(hip_a->peer_hi->dsa != saved_peer_hi.dsa))
|
|
{
|
|
DSA_free(hip_a->peer_hi->dsa);
|
|
}
|
|
if (hip_a->peer_hi->rsa &&
|
|
(hip_a->peer_hi->rsa != saved_peer_hi.rsa))
|
|
{
|
|
RSA_free(hip_a->peer_hi->rsa);
|
|
}
|
|
memcpy(hip_a->peer_hi, &saved_peer_hi, sizeof(saved_peer_hi));
|
|
return(-1);
|
|
}
|
|
|
|
int handle_hit_suite_list(hip_assoc *hip_a, __u8 *id, __u16 length) {
|
|
/* TODO: TDDE21 Determine if priority should be handled in this manner. */
|
|
for(__u8 *i = id; i-id < length; ++i){
|
|
for (int j = 0; j < sizeof(HCNF.hit_suite_list)/sizeof(HCNF.hit_suite_list[0]); ++j) {
|
|
/* Right shift to convert from 8bit to 4bit. */
|
|
if((*i >> 4) == HCNF.hit_suite_list[j]){
|
|
hip_a->hit_suite = (*i >> 4);
|
|
return(0);
|
|
}
|
|
}
|
|
}
|
|
return(-1);
|
|
}
|
|
|
|
int hip_handle_R1(__u8 *buff, hip_assoc *hip_a, struct sockaddr *src)
|
|
{
|
|
int err = 0;
|
|
|
|
/* R1 only accepted in these states */
|
|
if ((hip_a->state != I1_SENT) && (hip_a->state != I2_SENT) &&
|
|
(hip_a->state != CLOSING) && (hip_a->state != CLOSED))
|
|
{
|
|
log_(NORMT,"HIP_R1 packet not accepted in state=%d.\n",
|
|
hip_a->state);
|
|
return(-1);
|
|
}
|
|
/* Assert hip_a->peer_hi was created in HIP_ACQUIRE */
|
|
if (!hip_a->peer_hi)
|
|
{
|
|
log_(NORMT, "HIP_ACQUIRE failed to make peer_hi.\n");
|
|
return(-1);
|
|
}
|
|
/* may want to set hip_a->available_transforms bitmask here,
|
|
* to control which suites are used with which host */
|
|
/* Parse R1 */
|
|
if (hip_parse_R1(buff, hip_a) < 0)
|
|
{
|
|
log_(NORMT, "Error while processing R1, dropping.\n");
|
|
if (hip_a->state == I1_SENT)
|
|
{
|
|
clear_retransmissions(hip_a);
|
|
set_state(hip_a, E_FAILED);
|
|
}
|
|
return(-1);
|
|
}
|
|
/* Set ip, hit, size, hi_t of peer_hi */
|
|
if (hip_a->evp_dh == NULL)
|
|
{
|
|
log_(WARN, "Error: after parsing R1, DH is null.\n");
|
|
}
|
|
/* Need to send an SPI to peer */
|
|
hip_a->spi_in = get_next_spi();
|
|
/* Fill in the destination address for when an RVS was used, */
|
|
if (VALID_FAM(&hip_a->peer_hi->lsi))
|
|
{
|
|
memcpy(HIPA_DST(hip_a), src, SALEN(src));
|
|
}
|
|
/* Update peer_hi_head and fill in LSI*/
|
|
update_peer_list(hip_a);
|
|
/* hip_send_I2 takes cookie from R1 */
|
|
if ((err = hip_send_I2(hip_a)) > 0)
|
|
{
|
|
log_(NORMT, "Sent I2 (%d bytes)\n", err);
|
|
set_state(hip_a, I2_SENT);
|
|
}
|
|
else if (err == -ERANGE)
|
|
{
|
|
log_(NORMT, "Couldn't solve R1 cookie, ");
|
|
if (OPT.no_retransmit)
|
|
{
|
|
log_(NORM,"retransmission off, aborting exchange.\n");
|
|
}
|
|
else if (hip_a->rexmt_cache.retransmits < HCNF.max_retries)
|
|
{
|
|
log_(NORM, "retransmitting I1.\n");
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "maximum retransmissions reached.\n");
|
|
}
|
|
return(0);
|
|
}
|
|
else
|
|
{
|
|
log_(NORMT, "Failed to send I2: %s.\n", strerror(errno));
|
|
return(-1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* function hip_parse_I2()
|
|
*
|
|
* in: data = raw socket bytes
|
|
* hip_a is pointer to HIP connection instance
|
|
* cookie = pointer to store extracted cookie
|
|
*
|
|
* out: Returns -1 if failure, packet length otherwise.
|
|
*
|
|
* parse HIP Second Initiator packet
|
|
*
|
|
*/
|
|
int hip_parse_I2(const __u8 *data, hip_assoc **hip_ar, hi_node *my_host_id,
|
|
struct sockaddr *src, struct sockaddr *dst)
|
|
{
|
|
hiphdr *hiph;
|
|
int location, data_len;
|
|
int i, j, len, key_len, iv_len, last_type = 0, err = 0;
|
|
int type, length;
|
|
hip_assoc *hip_a = NULL, *hip_a_existing;
|
|
__u16 proposed_keymat_index = 0;
|
|
__u32 proposed_spi_out = 0;
|
|
tlv_head *tlv;
|
|
tlv_esp_info *esp_info;
|
|
unsigned char *hmac;
|
|
hipcookie cookie;
|
|
tlv_solution *tlv_sol = NULL;
|
|
unsigned char *solution;
|
|
size_t rhash_len = auth_key_len_hit_suite((int)my_host_id->hit_suite_id);
|
|
__u64 r1count = 0;
|
|
__u16 *p;
|
|
__u8 g_id = 0;
|
|
unsigned char *dh_secret_key;
|
|
dh_cache_entry *dh_entry = NULL;
|
|
unsigned char *key, *enc_data = NULL, *unenc_data = NULL;
|
|
AES_KEY aes_key;
|
|
unsigned char cbc_iv[16];
|
|
int got_dh = 0, comp_keys = 0, status;
|
|
__u8 valid_cert = FALSE;
|
|
|
|
hip_a_existing = *hip_ar;
|
|
|
|
/* Find hip header */
|
|
location = 0;
|
|
hiph = (hiphdr*) &data[location];
|
|
data_len = location + ((hiph->hdr_len + 1) * 8);
|
|
location += sizeof(hiphdr);
|
|
|
|
status = -1;
|
|
/* Parse TLVs */
|
|
while (location < data_len)
|
|
{
|
|
tlv = (tlv_head*) &data[location];
|
|
type = ntohs(tlv->type);
|
|
length = ntohs(tlv->length);
|
|
/* check if hip_a has been initalized*/
|
|
if ((type > PARAM_SOLUTION) && !hip_a)
|
|
{
|
|
log_(NORM,
|
|
"I2 packet does not contain puzzle solution.\n");
|
|
return(-1);
|
|
}
|
|
if (check_tlv_type_length(type, length, last_type, "I2") < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
last_type = type;
|
|
}
|
|
|
|
if (type == PARAM_ESP_INFO)
|
|
{
|
|
esp_info = (tlv_esp_info*)tlv;
|
|
proposed_keymat_index = ntohs(esp_info->keymat_index);
|
|
proposed_spi_out = ntohl(esp_info->new_spi);
|
|
}
|
|
else if (type == PARAM_R1_COUNTER)
|
|
{
|
|
r1count = ntoh64(((tlv_r1_counter*)tlv)->r1_gen_counter);
|
|
if ((my_host_id->r1_gen_count - r1count) >
|
|
ACCEPTABLE_R1_COUNT_RANGE)
|
|
{
|
|
log_(NORM, "Got R1 count of %llu, my R1 count",
|
|
r1count);
|
|
log_(NORM, "er is %llu, outside range (%d), ",
|
|
my_host_id->r1_gen_count,
|
|
ACCEPTABLE_R1_COUNT_RANGE);
|
|
if (!OPT.permissive)
|
|
{
|
|
log_(NORM, "dropping.\n");
|
|
return(-1);
|
|
}
|
|
}
|
|
log_(NORM,"R1 counter %llu (%llu) acceptable.\n",
|
|
r1count, my_host_id->r1_gen_count);
|
|
}
|
|
else if (type == PARAM_SOLUTION)
|
|
{
|
|
/* handle the puzzle solution */
|
|
tlv_sol = (tlv_solution*) tlv;
|
|
/* retrieve everything in the cookie except the I parameter */
|
|
memcpy(&cookie, &tlv_sol->cookie,
|
|
sizeof(hipcookie)-sizeof(unsigned char *));
|
|
/* set the I parameter */
|
|
cookie.i = (unsigned char *)malloc(rhash_len);
|
|
memcpy(cookie.i, &tlv_sol->cookie.i, rhash_len);
|
|
/* retrieve the solution parameter J */
|
|
solution = (unsigned char *)malloc(rhash_len);
|
|
memcpy(solution, (unsigned char *)&tlv_sol->cookie.i+rhash_len,
|
|
rhash_len);
|
|
log_(NORM, "Got the I2 cookie: ");
|
|
print_cookie(&cookie, rhash_len);
|
|
log_(NORM, "with solution: 0x");
|
|
print_hex(solution, rhash_len);
|
|
log_(NORM, "\n");
|
|
i = compute_R1_cache_index(&hiph->hit_sndr, TRUE);
|
|
j = compute_R1_cache_index(&hiph->hit_sndr, FALSE);
|
|
/* locate cookie using current random number */
|
|
if ((validate_solution(
|
|
my_host_id->r1_cache[HCNF.dh_group][i].current_puzzle,
|
|
&cookie,
|
|
&hiph->hit_sndr, &hiph->hit_rcvr,
|
|
solution, rhash_len) == 0) ||
|
|
(validate_solution(
|
|
my_host_id->r1_cache[HCNF.dh_group][i].previous_puzzle,
|
|
&cookie,
|
|
&hiph->hit_sndr, &hiph->hit_rcvr,
|
|
solution, rhash_len) == 0))
|
|
{
|
|
dh_entry = my_host_id->r1_cache[HCNF.dh_group][i].dh_entry;
|
|
/* locate cookie using previous random number */
|
|
}
|
|
else if ((validate_solution(
|
|
my_host_id->r1_cache[HCNF.dh_group][j].
|
|
current_puzzle,
|
|
&cookie,
|
|
&hiph->hit_sndr, &hiph->hit_rcvr,
|
|
solution, rhash_len) == 0) ||
|
|
(validate_solution(
|
|
my_host_id->r1_cache[HCNF.dh_group][j].
|
|
previous_puzzle,
|
|
&cookie,
|
|
&hiph->hit_sndr, &hiph->hit_rcvr,
|
|
solution, rhash_len) == 0))
|
|
{
|
|
dh_entry = my_host_id->r1_cache[HCNF.dh_group][j].dh_entry;
|
|
}
|
|
else
|
|
{
|
|
log_(WARN,"Invalid solution received in I2.\n");
|
|
if (!OPT.permissive)
|
|
{
|
|
free(cookie.i);
|
|
free(solution);
|
|
return(-1);
|
|
}
|
|
}
|
|
/* create HIP association state here */
|
|
hip_a = init_hip_assoc(my_host_id,
|
|
(const hip_hit *)&hiph->hit_sndr);
|
|
if (!hip_a)
|
|
{
|
|
log_(WARN,
|
|
"Unable to create a HIP association "
|
|
"while receiving I2.\n");
|
|
free(cookie.i);
|
|
free(solution);
|
|
return(-1);
|
|
}
|
|
hip_a->dh_group_id = dh_entry->group_id;
|
|
hip_a->evp_dh = dh_entry->evp_dh;
|
|
dh_entry->ref_count++;
|
|
dh_entry->is_current = FALSE; /* mark the entry so it
|
|
* will not be used again
|
|
* */
|
|
hip_a->spi_out = proposed_spi_out;
|
|
memcpy(&hip_a->cookie_r, &cookie, sizeof(hipcookie));
|
|
hip_a->cookie_j = solution;
|
|
/* fill in the addresses */
|
|
memcpy(HIPA_SRC(hip_a), dst, SALEN(dst));
|
|
hip_a->hi->addrs.if_index = is_my_address(dst);
|
|
make_address_active(&hip_a->hi->addrs);
|
|
memcpy(HIPA_DST(hip_a), src, SALEN(src));
|
|
if ((src->sa_family == AF_INET) &&
|
|
(((struct sockaddr_in*)src)->sin_port > 0))
|
|
{
|
|
hip_a->udp = TRUE;
|
|
}
|
|
}
|
|
else if (type == PARAM_DIFFIE_HELLMAN)
|
|
{
|
|
if (handle_dh(hip_a, &data[location], &g_id,
|
|
NULL) < 0)
|
|
{
|
|
hip_send_notify(hip_a, NOTIFY_INVALID_DH_CHOSEN,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
/* We chose g_id in R1, so I2 should match */
|
|
if (g_id != hip_a->dh_group_id)
|
|
{
|
|
log_(NORM, "Got DH group %d, expected %d.",
|
|
g_id, hip_a->dh_group_id);
|
|
hip_send_notify(hip_a, NOTIFY_INVALID_DH_CHOSEN,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
/* compute key from our dh and peer's pub_key and
|
|
* store in dh_secret_key */
|
|
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(hip_a->evp_dh, NULL);
|
|
EVP_PKEY_derive_init(ctx);
|
|
EVP_PKEY_derive_set_peer(ctx,hip_a->peer_dh);
|
|
EVP_PKEY_derive(ctx, NULL, &hip_a -> dh_secret_len);
|
|
dh_secret_key = (unsigned char *)OPENSSL_malloc(hip_a -> dh_secret_len);
|
|
EVP_PKEY_derive(ctx, dh_secret_key, &hip_a -> dh_secret_len);
|
|
set_secret_key(dh_secret_key, hip_a);
|
|
got_dh = 1;
|
|
/* Do not free(dh_secret_key), which is now
|
|
* dh->dh_secret */
|
|
}
|
|
else if (type == PARAM_HIP_CIPHER)
|
|
{
|
|
p = &((tlv_hip_cipher*)tlv)->cipher_id;
|
|
if ((handle_hip_cipher(hip_a, p, length)) < 0)
|
|
{
|
|
hip_send_notify(
|
|
hip_a,
|
|
NOTIFY_INVALID_HIP_CIPHER_CHOSEN,
|
|
NULL,
|
|
0);
|
|
return(-1);
|
|
}
|
|
//hip_a->hip_transform = ESP_AES128_CBC_HMAC_SHA1; //TODO: remove
|
|
/* Must compute keys here so we can use them below. */
|
|
if (got_dh)
|
|
{
|
|
// TODO Look in to how to decide the suite in
|
|
// I2 packet.
|
|
hip_a -> hit_suite = hip_a->hi->hit_suite_id;
|
|
compute_keys(hip_a);
|
|
if (proposed_keymat_index >
|
|
hip_a->keymat_index)
|
|
{
|
|
hip_a->keymat_index =
|
|
proposed_keymat_index;
|
|
}
|
|
comp_keys = 1;
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "Couldn't do compute_keys() ");
|
|
log_(NORM, "because DH is not set yet.\n");
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
}
|
|
else if (type == PARAM_ESP_TRANSFORM)
|
|
{
|
|
/* check E bit */
|
|
if (((tlv_esp_transform*)tlv)->reserved && 0x01)
|
|
{
|
|
log_(NORM, "64-bit ESP sequence numbers reque");
|
|
log_(NORM, "sted but unsupported by kernel!\n");
|
|
if (OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
p = &((tlv_esp_transform*)tlv)->suite_id;
|
|
if ((handle_transforms(hip_a, p, length - 2,
|
|
TRUE)) < 0)
|
|
{
|
|
hip_send_notify(
|
|
hip_a,
|
|
NOTIFY_INVALID_ESP_TRANSFORM_CHOSEN,
|
|
NULL,
|
|
0);
|
|
return(-1);
|
|
}
|
|
}
|
|
else if (type == PARAM_ENCRYPTED)
|
|
{
|
|
err = 0;
|
|
/* NULL encryption */
|
|
if (ENCR_NULL(hip_a->hip_cipher))
|
|
{
|
|
len = length - 8; /* tlv - type,length,reserv */
|
|
len = eight_byte_align(len);
|
|
enc_data = NULL;
|
|
unenc_data = malloc(len);
|
|
memset(unenc_data, 0, len);
|
|
memcpy(unenc_data, ((tlv_encrypted*)tlv)->iv,
|
|
len);
|
|
/* Cipher decryption */
|
|
}
|
|
else
|
|
{
|
|
if (!comp_keys)
|
|
{
|
|
log_(NORM, "Got ENCRYPTED TLV, but ");
|
|
log_(NORM, "keys not computed yet.\n");
|
|
err = NOTIFY_ENCRYPTION_FAILED;
|
|
goto I2_ERROR;
|
|
}
|
|
/* prepare the data */
|
|
/* tlv length - reserved,iv */
|
|
iv_len = enc_iv_len(hip_a->hip_cipher);
|
|
len = length - (4 + iv_len);
|
|
len = eight_byte_align(len);
|
|
enc_data = malloc(len);
|
|
unenc_data = malloc(len);
|
|
memset(enc_data, 0, len);
|
|
memset(unenc_data, 0, len);
|
|
/* AES uses a 128-bit IV, 3-DES and Blowfish
|
|
* use 64-bits. */
|
|
memcpy(enc_data,
|
|
((tlv_encrypted*)tlv)->iv + iv_len, len);
|
|
memcpy(cbc_iv, ((tlv_encrypted*)tlv)->iv,
|
|
iv_len);
|
|
key = get_key(hip_a, HIP_ENCRYPTION, TRUE);
|
|
key_len = enc_key_len_hip_cipher(hip_a->hip_cipher);
|
|
|
|
/* prepare keys and decrypt based on cipher */
|
|
switch (hip_a->hip_cipher)
|
|
{
|
|
case HIP_CIPHER_AES128_CBC:
|
|
case HIP_CIPHER_AES256_CBC:
|
|
log_(NORM, "AES decryption key: 0x");
|
|
print_hex(key, key_len);
|
|
log_(NORM, "\n");
|
|
if (AES_set_decrypt_key(key, 8 *
|
|
key_len,
|
|
&aes_key))
|
|
{
|
|
log_(WARN, "Unable to use cal");
|
|
log_(NORM, "ulated DH secret ");
|
|
log_(NORM, "for AES key.\n");
|
|
err = NOTIFY_ENCRYPTION_FAILED;
|
|
goto I2_ERROR;
|
|
}
|
|
log_(NORM, "Decrypting %d bytes ", len);
|
|
log_(NORM, "using AES.\n");
|
|
AES_cbc_encrypt(enc_data,
|
|
unenc_data,
|
|
len,
|
|
&aes_key,
|
|
cbc_iv,
|
|
AES_DECRYPT);
|
|
break;
|
|
default:
|
|
log_(WARN, "Unsupported transform ");
|
|
log_(NORM, "for decryption\n");
|
|
err = NOTIFY_ENCRYPTION_FAILED;
|
|
goto I2_ERROR;
|
|
break;
|
|
} /* end switch(hip_a->hip_transform) */
|
|
} /* end if */
|
|
/* parse HIi */
|
|
tlv = (tlv_head*) unenc_data;
|
|
if (ntohs(tlv->type) == PARAM_HOST_ID)
|
|
{
|
|
if (handle_hi(&hip_a->peer_hi,
|
|
unenc_data) < 0)
|
|
{
|
|
log_(WARN, "Error with I2 HI.\n");
|
|
err = NOTIFY_ENCRYPTION_FAILED;
|
|
goto I2_ERROR;
|
|
}
|
|
if (!validate_hit(hiph->hit_sndr,
|
|
hip_a->peer_hi))
|
|
{
|
|
log_(WARN, "HI in I2 does not match ");
|
|
log_(NORM, "the sender's HIT\n");
|
|
err = NOTIFY_INVALID_HIT;
|
|
goto I2_ERROR;
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "HI in I2 validates the ");
|
|
log_(NORM, "sender's HIT.\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_(WARN, "Invalid HI decrypted type: %x.\n",
|
|
ntohs(tlv->type));
|
|
err = NOTIFY_ENCRYPTION_FAILED;
|
|
goto I2_ERROR;
|
|
}
|
|
I2_ERROR:
|
|
if (enc_data) /* NULL encryption doesn't use this */
|
|
{
|
|
free(enc_data);
|
|
}
|
|
free(unenc_data);
|
|
if ((err) && (!OPT.permissive))
|
|
{
|
|
hip_send_notify(hip_a, err, NULL, 0);
|
|
return(-1);
|
|
}
|
|
}
|
|
else if (type == PARAM_HOST_ID)
|
|
{
|
|
if (handle_hi(&hip_a->peer_hi, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Error with I2 HI.\n");
|
|
hip_send_notify(hip_a, NOTIFY_INVALID_SYNTAX,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
if (!validate_hit(hiph->hit_sndr,
|
|
hip_a->peer_hi))
|
|
{
|
|
log_(WARN, "HI in I2 does not match ");
|
|
log_(NORM, "the sender's HIT\n");
|
|
hip_send_notify(hip_a, NOTIFY_INVALID_HIT,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "HI in I2 validates the ");
|
|
log_(NORM, "sender's HIT.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_CERT)
|
|
{
|
|
if (HCNF.peer_certificate_required &&
|
|
(handle_cert(hip_a, &data[location]) < 0))
|
|
{
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_AUTHENTICATION_FAILED,
|
|
NULL, 0);
|
|
return(-1);
|
|
}
|
|
valid_cert = TRUE;
|
|
}
|
|
else if ((type == PARAM_ECHO_RESPONSE) ||
|
|
(type == PARAM_ECHO_RESPONSE_NOSIG))
|
|
{
|
|
log_(NORM, "Warning: received unrequested ECHO_RESPON");
|
|
log_(NORM, "SE from I2 packet.\n");
|
|
}
|
|
else if (type == PARAM_HMAC)
|
|
{
|
|
hmac = ((tlv_hmac*)tlv)->hmac;
|
|
/* reset the length and checksum for the HMAC */
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
log_(NORM, "HMAC verify over %d bytes. ",len);
|
|
log_(NORM, "hdr length=%d \n", hiph->hdr_len);
|
|
/* TODO: TDDE21 Get the actual hit_suite from hip peer (previous assoc in hip_arr). */
|
|
hip_a->hit_suite = HIT_SUITE_4BIT_RSA_DSA_SHA256;
|
|
if (validate_hmac(data, len,
|
|
hmac, length,
|
|
get_key(hip_a, HIP_INTEGRITY, TRUE),
|
|
hip_a->hit_suite))
|
|
{
|
|
log_(WARN, "Invalid HMAC.\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_HMAC_FAILED,
|
|
NULL, 0);
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "HMAC verified OK.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_HIP_SIGNATURE)
|
|
{
|
|
if ((hip_a == NULL) || (hip_a->peer_hi == NULL))
|
|
{
|
|
log_(WARN, "Received signature parameter "
|
|
"without any Host Identity context for "
|
|
"verification.\n");
|
|
return(-1);
|
|
}
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
if (validate_signature(data, len, tlv,
|
|
hip_a->peer_hi->dsa,
|
|
hip_a->peer_hi->rsa,
|
|
hip_a->peer_hi->ecdsa,
|
|
hip_a->peer_hi->hit_suite_id) < 0)
|
|
{
|
|
log_(WARN, "Invalid signature.\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_AUTHENTICATION_FAILED,
|
|
NULL, 0);
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
/* adopt the new hip_assoc now */
|
|
if (hip_a_existing)
|
|
{
|
|
log_(NORM, "Replacing old association.\n");
|
|
replace_hip_assoc(hip_a_existing, hip_a);
|
|
*hip_ar = hip_a_existing;
|
|
}
|
|
else
|
|
{
|
|
*hip_ar = hip_a;
|
|
}
|
|
/* exit w/OK */
|
|
status = 0;
|
|
}
|
|
else if (type == PARAM_REG_REQUEST) /* I2 packet */
|
|
{
|
|
log_(NORM, "Peer has requested registration(s) in its"
|
|
" I2 packet.\n");
|
|
if (handle_reg_request(hip_a, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Problem with registration "
|
|
"request.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_ESP_INFO_NOSIG)
|
|
{
|
|
esp_info = (tlv_esp_info*)tlv;
|
|
hip_a->spi_nat = ntohl(esp_info->new_spi);
|
|
log_(NORMT, "Adding SPI NAT 0x%x\n", hip_a->spi_nat);
|
|
}
|
|
else
|
|
{
|
|
if (check_tlv_unknown_critical(type, length) < 0)
|
|
{
|
|
/* cookie has been solved, send NOTIFY */
|
|
if (hip_a)
|
|
{
|
|
__u16 t;
|
|
t = (__u16)type;
|
|
hip_send_notify(
|
|
hip_a,
|
|
NOTIFY_UNSUPPORTED_CRITICAL_PARAMETER_TYPE,
|
|
(__u8*)&t,
|
|
sizeof(__u16));
|
|
}
|
|
return(-1);
|
|
}
|
|
}
|
|
location += tlv_length_to_parameter_length(length);
|
|
}
|
|
if (HCNF.peer_certificate_required && !valid_cert)
|
|
{
|
|
hip_send_notify(hip_a, NOTIFY_AUTHENTICATION_FAILED, NULL, 0);
|
|
return(-1);
|
|
}
|
|
|
|
return(status);
|
|
}
|
|
|
|
int hip_handle_I2(__u8 *buff, hip_assoc *hip_a_existing,
|
|
struct sockaddr *src, struct sockaddr *dst)
|
|
{
|
|
int err = 0;
|
|
hi_node *my_host_id = NULL;
|
|
hip_assoc *hip_a = NULL;
|
|
__u32 old_spi_in = 0, old_spi_out = 0;
|
|
struct timeval time1;
|
|
hiphdr *hiph;
|
|
int old_state = 0;
|
|
/* this is the SPI from the last received I2 packet, so we know
|
|
* whether or not it is a retransmission */
|
|
static __u32 last_I2_spi = 0;
|
|
|
|
/* Accept I2 in all states but E_FAILED.
|
|
* Special treatment when in ESTABLISHED. */
|
|
if (hip_a_existing && (hip_a_existing->state == E_FAILED))
|
|
{
|
|
log_(NORM, "HIP_I2 packet not accepted in state=%d.\n",
|
|
hip_a_existing->state);
|
|
return(-1);
|
|
}
|
|
hiph = (hiphdr*) buff;
|
|
|
|
/*
|
|
* Is this my HIT? If so, get corresponding HI. If not, fail
|
|
*/
|
|
if ((my_host_id = check_if_my_hit(&hiph->hit_rcvr)) == NULL)
|
|
{
|
|
log_(NORM, "Received I2 with a recv. HIT that is not mine.\n");
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* Retransmit R2 if in state R2_SENT and this is a similar
|
|
* I2 to the one that triggered moving to R2_SENT
|
|
*/
|
|
if (hip_a_existing && (hip_a_existing->state == R2_SENT))
|
|
{
|
|
/* XXX should we instead process, then send R2? */
|
|
if ((hip_a_existing->rexmt_cache.packet != NULL) &&
|
|
(hip_a_existing->rexmt_cache.retransmits <
|
|
HCNF.max_retries) && (0 == OPT.no_retransmit) &&
|
|
(last_I2_spi == ntohl(
|
|
((tlv_esp_info*)&buff[sizeof(hiphdr)])->new_spi )))
|
|
{
|
|
log_(NORM, "Received I2 in R2_SENT, retransmitting ");
|
|
log_(NORM, "R2...\n");
|
|
hip_retransmit(hip_a,hip_a_existing->rexmt_cache.packet,
|
|
hip_a_existing->rexmt_cache.len,
|
|
dst, src);
|
|
gettimeofday(&time1, NULL);
|
|
hip_a_existing->rexmt_cache.xmit_time.tv_sec
|
|
= time1.tv_sec;
|
|
hip_a_existing->rexmt_cache.xmit_time.tv_usec
|
|
= time1.tv_usec;
|
|
hip_a_existing->rexmt_cache.retransmits++;
|
|
set_state(hip_a_existing, R2_SENT); /* update time */
|
|
return(0);
|
|
}
|
|
/* If we get here, then we already have SAs that need to be
|
|
* dropped because we are in R2_SENT, but this is a new I2
|
|
* packet from a new HIP exchange. So we simply rush our
|
|
* R2_SENT timer to become ESTABLISHED now.
|
|
*/
|
|
log_(NORM, "Moving from state R2_SENT=>ESTABLISHED because ");
|
|
log_(NORM, "a new HIP association requested.\n");
|
|
set_state(hip_a_existing, ESTABLISHED);
|
|
/* Compare HITs in state I2_SENT
|
|
*/
|
|
}
|
|
else if (hip_a_existing && (hip_a_existing->state == I2_SENT))
|
|
{
|
|
/* peer HIT larger than my HIT */
|
|
if (compare_hits(hiph->hit_sndr, hiph->hit_rcvr) > 0)
|
|
{
|
|
log_(NORMT, "Dropping I2 in state I2_SENT because ");
|
|
log_(NORM, "local HIT is smaller than peer HIT.\n");
|
|
return(0);
|
|
}
|
|
/* local HIT is greater than peer HIT, send R2... */
|
|
}
|
|
|
|
/*
|
|
* Prepare to drop old SAs
|
|
*/
|
|
if (hip_a_existing && (hip_a_existing->state == ESTABLISHED))
|
|
{
|
|
old_state = hip_a_existing->state;
|
|
old_spi_in = hip_a_existing->spi_in;
|
|
old_spi_out = hip_a_existing->spi_out;
|
|
log_(NORM, "Existing association already in ESTABLISHED, ");
|
|
log_(NORM, "preparing to drop SAs.\n");
|
|
}
|
|
else
|
|
{
|
|
old_state = UNASSOCIATED;
|
|
}
|
|
|
|
|
|
/*
|
|
* Process the I2, with appropriate checks
|
|
*/
|
|
hip_a = hip_a_existing; /* may be NULL */
|
|
if (hip_parse_I2(buff, &hip_a, my_host_id, src, dst) < 0)
|
|
{
|
|
log_(WARN, "Error while processing I2, dropping.\n");
|
|
/* stay in same state here */
|
|
return(-1);
|
|
}
|
|
|
|
clear_retransmissions(hip_a);
|
|
make_address_active(&hip_a->peer_hi->addrs);
|
|
add_other_addresses_to_hi(hip_a->hi, TRUE);
|
|
/* Need to send an SPI to peer */
|
|
hip_a->spi_in = get_next_spi();
|
|
/* build R2 and Responder's SA */
|
|
if ((err = hip_send_R2(hip_a)) > 0)
|
|
{
|
|
last_I2_spi = hip_a->spi_out; /* remember that this I2 put us
|
|
* into R2_SENT */
|
|
draw_keys(hip_a, FALSE, hip_a->keymat_index); /* draw ESP keys
|
|
*/
|
|
set_state(hip_a, R2_SENT);
|
|
log_(NORM, "Sent R2 (%d bytes)\n", err);
|
|
|
|
/* fill in LSI, update peer_hi_head */
|
|
update_peer_list(hip_a);
|
|
|
|
if (old_state == ESTABLISHED)
|
|
{
|
|
log_(NORM, "Dropping old SAs with SPIs of");
|
|
log_(NORM, " 0x%x and 0x%x.\n",
|
|
old_spi_out, old_spi_in);
|
|
err = delete_associations(hip_a, old_spi_in,
|
|
old_spi_out);
|
|
}
|
|
|
|
err = complete_base_exchange(hip_a);
|
|
|
|
if (hip_a->spi_nat)
|
|
{
|
|
hip_assoc *hip_mr;
|
|
__u16 keymat_index = hip_a->keymat_index;
|
|
hip_mr = search_registrations2(REGTYPE_MR, REG_GRANTED);
|
|
if (draw_mr_key(hip_a, hip_a->keymat_index) < 0)
|
|
{
|
|
log_(WARN, "Failed to draw mobile "
|
|
"router key");
|
|
}
|
|
else
|
|
{
|
|
hip_a->mr_keymat_index = keymat_index;
|
|
log_(NORM, "Drawing MR proxy key %d\n",
|
|
hip_a->mr_keymat_index);
|
|
/* If we are a mobile router client */
|
|
/* use the same key for our mobile router */
|
|
if (hip_mr &&
|
|
!hits_equal(hip_mr->peer_hi->hit,
|
|
hip_a->peer_hi->hit))
|
|
{
|
|
hip_send_update_proxy_ticket(hip_mr,
|
|
hip_a);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!err)
|
|
{
|
|
#ifdef __MACOSX__
|
|
hip_a->ipfw_rule = next_divert_rule();
|
|
add_divert_rule(hip_a->ipfw_rule,
|
|
IPPROTO_ESP,logaddr(HIPA_DST(hip_a)));
|
|
#endif
|
|
/* stay in state R2_SENT, awaiting
|
|
* timeout or incoming ESP data */
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "SA incomplete (%d).\n", err);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "Failed to send R2: %s.\n", strerror(errno));
|
|
}
|
|
return(err);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* function hip_parse_R2()
|
|
*
|
|
* in: data = raw socket bytes
|
|
* hip_a = pointer to HIP connection instance
|
|
*
|
|
* out: Returns -1 if error, packet length otherwise.
|
|
*
|
|
* parse HIP Second Responder packet
|
|
*
|
|
*/
|
|
int hip_parse_R2(__u8 *data, hip_assoc *hip_a)
|
|
{
|
|
hiphdr *hiph;
|
|
int location, hi_loc, len, data_len, next_location;
|
|
int type, length, last_type = 0;
|
|
tlv_head *tlv;
|
|
char sig_tlv_tmp[sizeof(tlv_hip_sig) + MAX_SIG_SIZE + 2];
|
|
tlv_esp_info *esp_info;
|
|
tlv_hmac hmac_tlv_tmp;
|
|
unsigned char *hmac;
|
|
__u16 proposed_keymat_index = 0;
|
|
__u32 proposed_spi_out = 0;
|
|
|
|
memset(&hmac_tlv_tmp, 0, sizeof(tlv_hmac));
|
|
|
|
location = 0;
|
|
hi_loc = 0;
|
|
hiph = (hiphdr*) &data[location];
|
|
data_len = location + ((hiph->hdr_len + 1) * 8);
|
|
location += sizeof(hiphdr);
|
|
while (location < data_len)
|
|
{
|
|
tlv = (tlv_head*) &data[location];
|
|
type = ntohs(tlv->type);
|
|
length = ntohs(tlv->length);
|
|
if (check_tlv_type_length(type, length, last_type, "R2") < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
last_type = type;
|
|
}
|
|
|
|
if (type == PARAM_ESP_INFO)
|
|
{
|
|
esp_info = (tlv_esp_info*)tlv;
|
|
proposed_keymat_index = ntohs(esp_info->keymat_index);
|
|
proposed_spi_out = ntohl(esp_info->new_spi);
|
|
}
|
|
else if (type == PARAM_HMAC_2)
|
|
{
|
|
/* save the HMAC_2 for processing after SIG is saved */
|
|
memcpy(&hmac_tlv_tmp, tlv, sizeof(tlv_hmac));
|
|
hi_loc = eight_byte_align(location);
|
|
}
|
|
else if (type == PARAM_HIP_SIGNATURE)
|
|
{
|
|
if ((hip_a == NULL) || (hip_a->peer_hi == NULL))
|
|
{
|
|
log_(WARN, "Received signature parameter "
|
|
"without any Host Identity context for "
|
|
"verification.\n");
|
|
return(-1);
|
|
}
|
|
/* The PARAM_ESP_INFO_NOSIG seems to get overwritten
|
|
* Should be reworked in future.
|
|
* OTB (2/22/2010) */
|
|
next_location = location +
|
|
tlv_length_to_parameter_length(length);
|
|
if (next_location < data_len)
|
|
{
|
|
tlv_head *temp_tlv =
|
|
(tlv_head*) &data[next_location];
|
|
if (ntohs(temp_tlv->type) ==
|
|
PARAM_ESP_INFO_NOSIG)
|
|
{
|
|
esp_info = (tlv_esp_info*)temp_tlv;
|
|
hip_a->spi_nat =
|
|
ntohl(esp_info->new_spi);
|
|
log_(NORMT, "Adding SPI NAT 0x%x\n",
|
|
hip_a->spi_nat);
|
|
}
|
|
}
|
|
/* save SIG and do HMAC_2 verification */
|
|
memcpy(sig_tlv_tmp, tlv, length + 4);
|
|
/* When building host identity tlv for HMAC_2 verify,
|
|
* use the DI (FQDN) regardless of HCNF.send_hi_name.
|
|
* If no DI was rcvd in R1, then no DI will be used. */
|
|
memset(&data[hi_loc], 0,
|
|
build_tlv_hostid_len(hip_a->peer_hi, TRUE));
|
|
len = hi_loc + build_tlv_hostid(&data[hi_loc],
|
|
hip_a->peer_hi, TRUE);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
hmac = hmac_tlv_tmp.hmac;
|
|
log_(NORM, "HMAC_2 verify over %d bytes. ",len);
|
|
log_(NORM, "hdr length=%d \n", hiph->hdr_len);
|
|
if (validate_hmac(data, len,
|
|
hmac, htons(hmac_tlv_tmp.length),
|
|
get_key(hip_a, HIP_INTEGRITY, TRUE),
|
|
hip_a->hit_suite))
|
|
{
|
|
log_(WARN, "Invalid HMAC_2.\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_HMAC_FAILED,
|
|
NULL, 0);
|
|
if (OPT.permissive)
|
|
{
|
|
return(0);
|
|
}
|
|
else
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "HMAC_2 verified OK.\n");
|
|
}
|
|
/* restore the HMAC_2 and SIG tlvs */
|
|
memcpy(&data[hi_loc], &hmac_tlv_tmp, sizeof(tlv_hmac));
|
|
memcpy(tlv, sig_tlv_tmp, length + 4);
|
|
/* now do signature processing */
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
if (validate_signature(data, len, tlv,
|
|
hip_a->peer_hi->dsa,
|
|
hip_a->peer_hi->rsa,
|
|
hip_a->peer_hi->ecdsa,
|
|
hip_a->peer_hi->hit_suite_id) < 0)
|
|
{
|
|
log_(WARN, "Invalid signature.\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_AUTHENTICATION_FAILED,
|
|
NULL, 0);
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
/* packet is OK, accept SPI and keymat index */
|
|
hip_a->spi_out = proposed_spi_out;
|
|
if (proposed_keymat_index > hip_a->keymat_index)
|
|
{
|
|
hip_a->keymat_index = proposed_keymat_index;
|
|
}
|
|
return(0);
|
|
}
|
|
else if (type == PARAM_REG_RESPONSE) /* R2 packet */
|
|
{
|
|
log_(NORM,
|
|
"Received response from registrar in the R2 "
|
|
"packet.\n");
|
|
if (handle_reg_response(hip_a, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Problem with registration "
|
|
"response.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_REG_FAILED) /* R2 packet */
|
|
{
|
|
log_(NORM, "Received failure from registrar in "
|
|
"UPDATE packet.\n");
|
|
if (handle_reg_failed(hip_a, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Problem with registration "
|
|
"failure.\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (check_tlv_unknown_critical(type, length) < 0)
|
|
{
|
|
__u16 t;
|
|
t = (__u16)type;
|
|
hip_send_notify(
|
|
hip_a,
|
|
NOTIFY_UNSUPPORTED_CRITICAL_PARAMETER_TYPE,
|
|
(__u8 *)&t,
|
|
sizeof(__u16));
|
|
return(-1);
|
|
}
|
|
}
|
|
location += tlv_length_to_parameter_length(length);
|
|
}
|
|
/* if we get here, no valid signature has been found */
|
|
return(-1);
|
|
}
|
|
|
|
int hip_handle_R2(__u8 *buff, hip_assoc *hip_a)
|
|
{
|
|
int err = 0;
|
|
hip_assoc *hip_mr;
|
|
|
|
/* R2 is only accepted in state I2_SENT */
|
|
if (hip_a->state != I2_SENT)
|
|
{
|
|
log_(WARN, "HIP_R2 packet not accepted in state=%d.\n",
|
|
hip_a->state);
|
|
return(-1);
|
|
}
|
|
if (hip_parse_R2(buff, hip_a) < 0)
|
|
{
|
|
log_(WARN, "Error while processing R2, dropping.\n");
|
|
clear_retransmissions(hip_a);
|
|
set_state(hip_a, E_FAILED);
|
|
return(-1);
|
|
}
|
|
clear_retransmissions(hip_a);
|
|
make_address_active(&hip_a->peer_hi->addrs);
|
|
/* draw new ESP keys using received/my keymat index */
|
|
draw_keys(hip_a, FALSE, hip_a->keymat_index);
|
|
|
|
err = complete_base_exchange(hip_a);
|
|
if (!err)
|
|
{
|
|
set_state(hip_a, ESTABLISHED);
|
|
if (OPT.mh && (hip_send_update_locators(hip_a) < 0))
|
|
{
|
|
log_(WARN, "Failed to send UPDATE with locators after "
|
|
"receiving R2.\n");
|
|
}
|
|
hip_mr = search_registrations2(REGTYPE_MR, REG_GRANTED);
|
|
if (hip_mr &&
|
|
!hits_equal(hip_mr->peer_hi->hit, hip_a->peer_hi->hit))
|
|
{
|
|
/* we are registered with a mobile router service and
|
|
* this association is not the one with the mr, so
|
|
* create a proxy ticket */
|
|
__u16 keymat_index = hip_a->keymat_index;
|
|
if (draw_mr_key(hip_a, hip_a->keymat_index) < 0)
|
|
{
|
|
log_(WARN, "Failed to draw mobile router key");
|
|
}
|
|
else
|
|
{
|
|
hip_a->mr_keymat_index = keymat_index;
|
|
log_(NORM, "Drawing MR proxy key %d\n",
|
|
hip_a->mr_keymat_index);
|
|
hip_send_update_proxy_ticket(hip_mr, hip_a);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "SA incomplete (%d).\n", err);
|
|
}
|
|
#ifdef __MACOSX__
|
|
hip_a->ipfw_rule = next_divert_rule();
|
|
add_divert_rule(hip_a->ipfw_rule,IPPROTO_ESP,logaddr(HIPA_DST(hip_a)));
|
|
#endif
|
|
return(err);
|
|
}
|
|
|
|
/*
|
|
* function hip_parse_update()
|
|
*
|
|
* in: data = the data to be parsed
|
|
* hip_a = the existing HIP association, for HMAC verification
|
|
* rk = struct for storing the peer's rekeying data
|
|
* nonce =
|
|
* src = source of received UPDATE, for UDP port
|
|
*
|
|
* out:
|
|
*/
|
|
int hip_parse_update(const __u8 *data, hip_assoc *hip_a, struct rekey_info *rk,
|
|
__u32 *nonce, struct sockaddr *src)
|
|
{
|
|
hiphdr *hiph;
|
|
int location, len, data_len;
|
|
int type, length, last_type = 0, status;
|
|
int loc_count, loc_len;
|
|
int sig_verified = FALSE, hmac_verified = FALSE, ticket_verified =
|
|
FALSE;
|
|
tlv_head *tlv;
|
|
__u32 new_spi = 0;
|
|
__u8 g_id = 0, *hmac, *p;
|
|
locator *locators[MAX_LOCATORS];
|
|
tlv_esp_info *esp_info = NULL;
|
|
tlv_from *from;
|
|
tlv_via_rvs *via;
|
|
struct sockaddr_storage rvs_addr;
|
|
unsigned char *rvs_hmac;
|
|
hip_assoc *hip_a_rvs;
|
|
|
|
if (!hip_a)
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
memset(locators, 0, sizeof(locators));
|
|
loc_count = 0;
|
|
*nonce = 0;
|
|
location = 0;
|
|
hiph = (hiphdr*) &data[location];
|
|
data_len = location + ((hiph->hdr_len + 1) * 8);
|
|
location += sizeof(hiphdr);
|
|
|
|
status = -1;
|
|
if (hip_a->spi_nat)
|
|
{
|
|
log_(NORM, "Received update for spinat connection\n");
|
|
}
|
|
|
|
while (location < data_len)
|
|
{
|
|
tlv = (tlv_head*) &data[location];
|
|
type = ntohs(tlv->type);
|
|
length = ntohs(tlv->length);
|
|
if (check_tlv_type_length(type, length, last_type,
|
|
"UPDATE") < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
last_type = type;
|
|
}
|
|
/* first verify HMAC and SIGNATURE */
|
|
if (!hmac_verified && (type == PARAM_HMAC))
|
|
{
|
|
__u8 *key;
|
|
hmac = ((tlv_hmac*)tlv)->hmac;
|
|
/* reset the length and checksum for the HMAC */
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
log_(NORM, "HMAC verify over %d bytes. ",len);
|
|
log_(NORM, "hdr length=%d \n", hiph->hdr_len);
|
|
if (ticket_verified)
|
|
{
|
|
key = hip_a->mr_key.key;
|
|
}
|
|
else
|
|
{
|
|
key = get_key(hip_a, HIP_INTEGRITY, TRUE);
|
|
}
|
|
if (validate_hmac(data, len,
|
|
hmac, length,
|
|
key,
|
|
hip_a->hit_suite))
|
|
{
|
|
log_(WARN, "Invalid HMAC.\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_HMAC_FAILED,
|
|
NULL, 0);
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
log_(NORM, "HMAC verified OK.\n");
|
|
hmac_verified = TRUE;
|
|
location += tlv_length_to_parameter_length(length);
|
|
if (ticket_verified) /* No SIG yet */
|
|
{
|
|
status = 0;
|
|
sig_verified = TRUE;
|
|
last_type = 0;
|
|
location = sizeof(hiphdr);
|
|
}
|
|
continue;
|
|
}
|
|
else if (!sig_verified && (type == PARAM_HIP_SIGNATURE))
|
|
{
|
|
if (hip_a->peer_hi == NULL)
|
|
{
|
|
log_(WARN, "Received signature parameter "
|
|
"without any Host Identity context for "
|
|
"verification.\n");
|
|
return(-1);
|
|
}
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
if (validate_signature(data, len, tlv,
|
|
hip_a->peer_hi->dsa,
|
|
hip_a->peer_hi->rsa,
|
|
hip_a->peer_hi->ecdsa,
|
|
hip_a->peer_hi->hit_suite_id) < 0)
|
|
{
|
|
log_(WARN, "Invalid signature.\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_AUTHENTICATION_FAILED,
|
|
NULL, 0);
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
/* now we may save changes from this UPDATE */
|
|
status = 0;
|
|
sig_verified = TRUE;
|
|
last_type = 0;
|
|
location = sizeof(hiphdr);
|
|
continue;
|
|
}
|
|
else if (!ticket_verified && (type == PARAM_AUTH_TICKET) &&
|
|
hip_a->spi_nat)
|
|
{
|
|
tlv_auth_ticket *auth_ticket =
|
|
(tlv_auth_ticket *) &data[location];
|
|
if (hip_a->mr_keymat_index !=
|
|
ntohs(auth_ticket->hmac_key_index))
|
|
{
|
|
log_(WARN, "Keymat indices do not match!\n");
|
|
log_(WARN, "My mr_keymat_index is %d\n",
|
|
hip_a->mr_keymat_index);
|
|
log_(WARN, "Proxy keymat_index is %d\n",
|
|
ntohs(auth_ticket->hmac_key_index));
|
|
/* Generate new keymat? */
|
|
}
|
|
len = sizeof(auth_ticket->hmac_key_index) +
|
|
sizeof(auth_ticket->transform_type) +
|
|
sizeof(auth_ticket->action) +
|
|
sizeof(auth_ticket->lifetime);
|
|
log_(NORM, "HMAC verify ticket over %d bytes.\n",len);
|
|
if (validate_hmac(
|
|
(const __u8*)&auth_ticket->hmac_key_index,
|
|
len,
|
|
auth_ticket->hmac, sizeof(auth_ticket->hmac),
|
|
get_key(hip_a, HIP_INTEGRITY, TRUE),
|
|
hip_a->hit_suite))
|
|
{
|
|
log_(WARN, "Invalid HMAC over ticket.\n");
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "HMAC over ticket succeeded\n");
|
|
ticket_verified = TRUE;
|
|
}
|
|
}
|
|
|
|
/* skip all parameters until verification */
|
|
if (!hmac_verified && !sig_verified)
|
|
{
|
|
location += tlv_length_to_parameter_length(length);
|
|
continue;
|
|
}
|
|
|
|
if (type == PARAM_LOCATOR)
|
|
{
|
|
/* get location of first locator */
|
|
locators[loc_count] = ((tlv_locator*)tlv)->locator1;
|
|
loc_len = 8 + (4 * locators[loc_count]->locator_length);
|
|
len = length - loc_len;
|
|
loc_count++;
|
|
/* read additional locators */
|
|
while (len > 0)
|
|
{
|
|
if (loc_count >= MAX_LOCATORS)
|
|
{
|
|
log_(WARN, "Only handling first %d loc",
|
|
"ators, dropping %d.\n",
|
|
MAX_LOCATORS, loc_count + 1);
|
|
break;
|
|
}
|
|
/* p used to point to next locator */
|
|
p = (__u8*)locators[loc_count - 1];
|
|
p += loc_len;
|
|
locators[loc_count] = (locator*)p;
|
|
/* calculate length of this locator */
|
|
if ((locators[loc_count]->locator_length != 5)
|
|
&&
|
|
(locators[loc_count]->locator_length !=
|
|
4))
|
|
{
|
|
log_(WARN, "Invalid locator length of");
|
|
log_(
|
|
NORM,
|
|
" %d found.\n",
|
|
locators[loc_count]->
|
|
locator_length);
|
|
return(-1);
|
|
}
|
|
loc_len = 8;
|
|
loc_len += 4 *
|
|
locators[loc_count]->locator_length;
|
|
len -= loc_len;
|
|
loc_count++;
|
|
}
|
|
}
|
|
else if (type == PARAM_ESP_INFO)
|
|
{
|
|
esp_info = (tlv_esp_info *) tlv;
|
|
new_spi = ntohl(esp_info->new_spi);
|
|
if (handle_esp_info(esp_info, hip_a->spi_out,
|
|
rk) < 0)
|
|
{
|
|
log_(WARN, "Problem with ESP_INFO.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_SEQ)
|
|
{
|
|
rk->update_id = ntohl(((tlv_seq*)tlv)->update_id);
|
|
rk->need_ack = TRUE;
|
|
if (rk->update_id < hip_a->peer_hi->update_id)
|
|
{
|
|
log_(WARN, "Received Update ID is smaller ");
|
|
log_(NORM, "than stored Update ID.\n");
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else if (rk->update_id ==
|
|
hip_a->peer_hi->update_id)
|
|
{
|
|
/* probably a retransmission, but SHOULD
|
|
* rate-limit against DOS here*/
|
|
}
|
|
/* save new update ID */
|
|
hip_a->peer_hi->update_id = rk->update_id;
|
|
}
|
|
else if (type == PARAM_ACK)
|
|
{
|
|
if (handle_acks(hip_a, (tlv_ack*)tlv))
|
|
{
|
|
hip_a->rekey->need_ack = FALSE;
|
|
}
|
|
}
|
|
else if (type == PARAM_DIFFIE_HELLMAN)
|
|
{
|
|
if (rk->keymat_index != 0)
|
|
{
|
|
log_(WARN, "Diffie-Hellman found in UPDATE, ");
|
|
log_(NORM, "but keymat_index=%d.\n",
|
|
rk->keymat_index);
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
/* Save the DH context in rk->dh for later use */
|
|
rk->dh = EVP_PKEY_new();
|
|
if (handle_dh(NULL, &data[location], &g_id,
|
|
rk->dh) < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else if ((type == PARAM_ECHO_RESPONSE) ||
|
|
(type == PARAM_ECHO_RESPONSE_NOSIG))
|
|
{
|
|
if (length != sizeof(__u32))
|
|
{
|
|
log_(WARN, "ECHO_RESPONSE has wrong length.\n");
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
memcpy(nonce, ((tlv_echo*)tlv)->opaque_data, length);
|
|
}
|
|
else if ((type == PARAM_ECHO_REQUEST) ||
|
|
(type == PARAM_ECHO_REQUEST_NOSIG))
|
|
{
|
|
/* prevent excessive memory consumption */
|
|
if (length > MAX_OPAQUE_SIZE)
|
|
{
|
|
log_(WARN,"ECHO_REQUEST in UPDATE is ");
|
|
log_(NORM,"too large.\n");
|
|
if (!OPT.permissive &&
|
|
(type & PARAM_CRITICAL_BIT))
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hip_a->opaque = (struct opaque_entry*)
|
|
malloc(sizeof(struct
|
|
opaque_entry));
|
|
if (hip_a->opaque == NULL)
|
|
{
|
|
log_(NORM,"Malloc err: ECHO_REQUEST\n");
|
|
return(-1);
|
|
}
|
|
hip_a->opaque->opaque_len = (__u16)length;
|
|
memcpy(hip_a->opaque->opaque_data,
|
|
((tlv_echo*)tlv)->opaque_data, length);
|
|
hip_a->opaque->opaque_nosig =
|
|
(type == PARAM_ECHO_REQUEST_NOSIG);
|
|
}
|
|
}
|
|
else if (type == PARAM_PROXY_TICKET)
|
|
{
|
|
#ifndef __WIN32__
|
|
if (OPT.mr)
|
|
{
|
|
add_proxy_ticket(&data[location]);
|
|
}
|
|
else
|
|
#endif /* !__WIN32__ */
|
|
{
|
|
log_(WARN, "Ignoring proxy ticket in UPDATE packet.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_REG_INFO) /* update packet */
|
|
{
|
|
log_(NORM,
|
|
"Peer is a registrar providing registration "
|
|
"info in its UPDATE packet.\n");
|
|
if (handle_reg_info(hip_a, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Problem with registration info.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_REG_REQUEST) /* update packet */
|
|
{
|
|
log_(NORM, "Peer has requested registration(s) in its "
|
|
"UPDATE packet.\n");
|
|
if (handle_reg_request(hip_a, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Problem with registration "
|
|
"request.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_REG_RESPONSE) /* update packet */
|
|
{
|
|
log_(NORM, "Received response from registrar in "
|
|
"UPDATE packet.\n");
|
|
if (handle_reg_response(hip_a, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Problem with registration "
|
|
"response.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_REG_FAILED) /* update packet */
|
|
{
|
|
log_(NORM, "Received failure from registrar in "
|
|
"UPDATE packet.\n");
|
|
if (handle_reg_failed(hip_a, &data[location]) < 0)
|
|
{
|
|
log_(WARN, "Problem with registration "
|
|
"failure.\n");
|
|
}
|
|
}
|
|
else if (type == PARAM_FROM)
|
|
{
|
|
from = (tlv_from*) &data[location];
|
|
if (length > (sizeof(tlv_from) - 4))
|
|
{
|
|
log_(NORM, "Ignoring extra address data.\n");
|
|
}
|
|
add_from_via(hip_a, PARAM_FROM, NULL, from->address);
|
|
}
|
|
else if (type == PARAM_VIA_RVS)
|
|
{
|
|
via = (tlv_via_rvs *) &data[location];
|
|
if (IN6_IS_ADDR_V4MAPPED(
|
|
(struct in6_addr*)via->address))
|
|
{
|
|
rvs_addr.ss_family = AF_INET;
|
|
memcpy(SA2IP(&rvs_addr), &via->address[12],
|
|
SAIPLEN(&rvs_addr));
|
|
}
|
|
else
|
|
{
|
|
rvs_addr.ss_family = AF_INET;
|
|
memcpy(SA2IP(&rvs_addr), via->address,
|
|
SAIPLEN(&rvs_addr));
|
|
}
|
|
log_(NORM, "UPDATE packet relayed by the Rendezvous "
|
|
"Server address %s.\n", logaddr(SA(&rvs_addr)));
|
|
}
|
|
else if (type == PARAM_RVS_HMAC)
|
|
{
|
|
hip_a_rvs = search_registrations2(REGTYPE_RVS,
|
|
REG_GRANTED);
|
|
if (!hip_a_rvs)
|
|
{
|
|
log_(WARN, "Received UPDATE with RVS_HMAC, "
|
|
"but could not find an association "
|
|
"with a Rendezvous Server.\n");
|
|
if (hip_a->from_via)
|
|
{
|
|
free(hip_a->from_via);
|
|
hip_a->from_via = NULL;
|
|
}
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rvs_hmac = ((tlv_hmac*)tlv)->hmac;
|
|
/* reset the length and checksum for the HMAC */
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
log_(NORM, "RVS_HMAC verify over %d bytes. ",
|
|
len);
|
|
log_(NORM, "hdr length=%d \n", hiph->hdr_len);
|
|
if (validate_hmac(data, len, rvs_hmac, length,
|
|
get_key(hip_a_rvs,
|
|
HIP_INTEGRITY, TRUE),
|
|
hip_a_rvs->hit_suite))
|
|
{
|
|
log_(WARN, "Invalid RVS_HMAC.\n");
|
|
if (hip_a->from_via)
|
|
{
|
|
free(hip_a->from_via);
|
|
hip_a->from_via = NULL;
|
|
}
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "RVS_HMAC verified OK.\n");
|
|
}
|
|
}
|
|
}
|
|
else if ((type == PARAM_HMAC) ||
|
|
(type == PARAM_HIP_SIGNATURE) ||
|
|
(type == PARAM_AUTH_TICKET))
|
|
{
|
|
/* these parameters already processed */
|
|
}
|
|
else
|
|
{
|
|
if (check_tlv_unknown_critical(type, length) < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
location += tlv_length_to_parameter_length(length);
|
|
}
|
|
|
|
/* update peer address list */
|
|
if (loc_count > 0)
|
|
{
|
|
if (handle_locators(hip_a, locators, loc_count, src,
|
|
new_spi) < 0)
|
|
{
|
|
log_(WARN, "Problem with LOCATOR.\n");
|
|
status = -1;
|
|
}
|
|
}
|
|
return(status);
|
|
}
|
|
|
|
int hip_handle_update(__u8 *data, hip_assoc *hip_a, struct sockaddr *src,
|
|
struct sockaddr *dst)
|
|
{
|
|
int err;
|
|
struct rekey_info rk;
|
|
struct sockaddr *addrcheck = NULL;
|
|
int need_to_send_update = FALSE;
|
|
__u32 nonce;
|
|
struct reg_info *reg;
|
|
hip_assoc *hip_a_rvs;
|
|
struct sockaddr_storage addr;
|
|
__u8 *addrp;
|
|
hip_assoc *hip_a_client;
|
|
hip_hit *hitr;
|
|
|
|
/* If RVS, check for RVS client */
|
|
if (!hip_a)
|
|
{
|
|
if (OPT.rvs)
|
|
{
|
|
hitr = &(((hiphdr *)(data))->hit_rcvr);
|
|
hip_a_client = search_registrations(*hitr, REGTYPE_RVS);
|
|
if (!hip_a_client)
|
|
{
|
|
return(-1);
|
|
}
|
|
if (add_from_via(hip_a_client, PARAM_FROM, src, NULL)
|
|
< 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
if (hip_send_update_relay(data, hip_a_client) < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
return(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* UPDATE only accepted in ESTABLISHED and R2_SENT states
|
|
*/
|
|
if ((hip_a->state != ESTABLISHED) && (hip_a->state != R2_SENT))
|
|
{
|
|
log_(WARN, "UPDATE not accepted in state %d, dropping.\n",
|
|
hip_a->state);
|
|
return(-1);
|
|
}
|
|
|
|
memset(&rk, 0, sizeof(struct rekey_info));
|
|
nonce = 0;
|
|
if ((err = hip_parse_update(data, hip_a, &rk, &nonce, src)) < 0)
|
|
{
|
|
log_(WARN, "Error while processing UPDATE, dropping.\n");
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* If received an UPDATE in R2_SENT state, move to ESTABLISHED state
|
|
*/
|
|
if (hip_a->state == R2_SENT)
|
|
{
|
|
set_state(hip_a, ESTABLISHED);
|
|
}
|
|
|
|
/*
|
|
* Only save the peer's rekeying state after the UPDATE
|
|
* has been verified as OK.
|
|
*/
|
|
if (!hip_a->peer_rekey)
|
|
{
|
|
/* No current peer_rekey; create new structure and
|
|
* copy any rekey information from the UPDATE message.
|
|
*/
|
|
hip_a->peer_rekey = malloc(sizeof(struct rekey_info));
|
|
if (hip_a->peer_rekey)
|
|
{
|
|
memcpy(hip_a->peer_rekey, &rk,
|
|
sizeof(struct rekey_info));
|
|
}
|
|
else
|
|
{
|
|
log_(WARN, "Malloc error\n");
|
|
return(-1);
|
|
}
|
|
}
|
|
else if (rk.new_spi > 0)
|
|
{
|
|
/* A new SPI has been proposed, e.g. during readdress,
|
|
* replace old peer information.
|
|
*/
|
|
if (hip_a->peer_rekey->dh)
|
|
{
|
|
EVP_PKEY_free(hip_a->peer_rekey->dh);
|
|
}
|
|
memcpy(hip_a->peer_rekey, &rk, sizeof(struct rekey_info));
|
|
}
|
|
|
|
/*
|
|
* Update address status if we received our
|
|
* address verification nonce
|
|
*/
|
|
if (nonce)
|
|
{
|
|
finish_address_check(hip_a, nonce, src);
|
|
}
|
|
|
|
/*
|
|
* Handle rekeying, based on current state
|
|
*/
|
|
if ((err = handle_update_rekey(hip_a)) < 0)
|
|
{
|
|
log_(WARN, "Problem with UPDATE rekey processing.\n");
|
|
return(-1);
|
|
}
|
|
else if (err == 1)
|
|
{
|
|
need_to_send_update = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Generate a new UPDATE because of ACK?
|
|
*/
|
|
if (hip_a->peer_rekey && hip_a->peer_rekey->need_ack)
|
|
{
|
|
log_(NORM, "Send new UPDATE to ack update ID %d.\n",
|
|
hip_a->peer_rekey->update_id);
|
|
need_to_send_update = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Generate a new UPDATE because of REG_REQUEST?
|
|
*/
|
|
if (hip_a->regs)
|
|
{
|
|
for (reg = hip_a->regs->reginfos; reg; reg = reg->next)
|
|
{
|
|
if ((reg->state == REG_SEND_RESP) ||
|
|
(reg->state == REG_SEND_CANCELLED) ||
|
|
(reg->state == REG_SEND_FAILED))
|
|
{
|
|
log_(NORM,
|
|
"Send new UPDATE due to registration"
|
|
"request (state=%d).\n",
|
|
reg->state);
|
|
need_to_send_update = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Handle readdress and address verification.
|
|
* Look for new preferred addresses that may have been added,
|
|
* and for addresses that need verifying
|
|
*/
|
|
if ((err = handle_update_readdress(hip_a, &addrcheck)) < 0)
|
|
{
|
|
log_(WARN, "Problem with UPDATE readdress processing.\n");
|
|
return(-1);
|
|
}
|
|
else if (err == 1)
|
|
{
|
|
need_to_send_update = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Relayed UPDATE from RVS, send to address in FROM parameter and fill
|
|
* in address for VIA_RVS parameter.
|
|
*/
|
|
if (need_to_send_update && hip_a->from_via &&
|
|
(ntohs(hip_a->from_via->type) == PARAM_FROM))
|
|
{
|
|
/* get RVS */
|
|
hip_a_rvs = search_registrations2(REGTYPE_RVS, REG_GRANTED);
|
|
if (hip_a_rvs)
|
|
{
|
|
/* get address from FROM parameter */
|
|
memset(&addr, 0, sizeof(addr));
|
|
if (IN6_IS_ADDR_V4MAPPED((struct in6_addr*)
|
|
hip_a->from_via->address))
|
|
{
|
|
addr.ss_family = AF_INET;
|
|
addrp = &hip_a->from_via->address[12];
|
|
}
|
|
else
|
|
{
|
|
addr.ss_family = AF_INET6;
|
|
addrp = &hip_a->from_via->address[0];
|
|
}
|
|
memcpy(SA2IP(&addr), addrp, SAIPLEN(&addr));
|
|
src = SA(&addr);
|
|
if (HIPA_DST(hip_a)->sa_family == AF_INET)
|
|
{
|
|
((struct sockaddr_in *)src)->sin_port =
|
|
((struct sockaddr_in *)
|
|
HIPA_DST(hip_a))->sin_port;
|
|
}
|
|
log_(NORM, "Relayed UPDATE from RVS %s, ",
|
|
logaddr(HIPA_DST(hip_a_rvs)));
|
|
log_(NORM, "using %s as new destination address.\n",
|
|
logaddr(src));
|
|
/* store RVS address for VIA_RVS parameter */
|
|
add_from_via(hip_a, PARAM_VIA_RVS,
|
|
HIPA_DST(hip_a_rvs), NULL);
|
|
}
|
|
}
|
|
|
|
if (need_to_send_update)
|
|
{
|
|
if ((err = hip_send_update(hip_a, NULL, dst, addrcheck)) > 0)
|
|
{
|
|
log_(NORM, "Sent UPDATE (%d bytes)\n", err);
|
|
}
|
|
else
|
|
{
|
|
log_(WARN, "Failed to send UPDATE: %s.\n",
|
|
strerror(errno));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* cleanup unused structures
|
|
*/
|
|
if ((hip_a->rekey) && !hip_a->rekey->need_ack &&
|
|
!hip_a->rekey->new_spi && !hip_a->rekey->dh)
|
|
{
|
|
free(hip_a->rekey);
|
|
hip_a->rekey = NULL;
|
|
}
|
|
if ((hip_a->peer_rekey) && !hip_a->peer_rekey->need_ack &&
|
|
!hip_a->peer_rekey->new_spi && !hip_a->peer_rekey->dh)
|
|
{
|
|
free(hip_a->peer_rekey);
|
|
hip_a->peer_rekey = NULL;
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* handle_update_rekey()
|
|
*
|
|
* in: hip_a = the association containing peer_rekey, rekey structs
|
|
* for building the UPDATE message, and the DH
|
|
*
|
|
* out: Returns 0 if no update needs to be sent, 1 if update is needed,
|
|
* or -1 on error.
|
|
*
|
|
* Take care of the rekeying portion of UPDATE messages.
|
|
* Uses hip_a->rekey and hip_a->peer_rekey for managing rekeying.
|
|
*/
|
|
int handle_update_rekey(hip_assoc *hip_a)
|
|
{
|
|
int need_to_send_update = FALSE;
|
|
|
|
if (!hip_a)
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
/* Did we initiate the rekey? */
|
|
if (hip_a->rekey)
|
|
{
|
|
if (!hip_a->rekey->new_spi)
|
|
{
|
|
/* finish_address_check() should've already taken
|
|
* care of this case
|
|
*/
|
|
log_(WARN, "handle_update_rekey(): unexpected ");
|
|
log_(NORM, "rekey state reached!\n");
|
|
|
|
}
|
|
/* We initiated the rekey, which will finish in
|
|
* hip_handle_state_timeouts()
|
|
*/
|
|
return(need_to_send_update);
|
|
}
|
|
|
|
if ((hip_a->state == ESTABLISHED) &&
|
|
(hip_a->peer_rekey) &&
|
|
(hip_a->peer_rekey->new_spi > 0)) /* 8.11.1 */
|
|
{ /* use hip_a->rekey for the new update
|
|
* keymat_index = index to use in ESP_INFO
|
|
* dh_group_id, dh = new DH key to send
|
|
*/
|
|
if (build_rekey(hip_a) < 0)
|
|
{
|
|
log_(WARN, "handle_update_rekey() failed to build a "
|
|
"new rekey structure for response to peer "
|
|
"rekeying event.\n");
|
|
return(-1);
|
|
}
|
|
need_to_send_update = TRUE;
|
|
}
|
|
/* 8.11.2
|
|
* At this point the rekey can be finished if the updates
|
|
* have been properly acked. However, we cannot call
|
|
* hip_finish_rekey() here because there may be pending
|
|
* expires on the ESP socket.
|
|
*
|
|
* Thus, defer until hip_handle_state_timeouts()
|
|
*/
|
|
return(need_to_send_update);
|
|
}
|
|
|
|
/*
|
|
* handle_update_readdress()
|
|
*
|
|
* in: hip_a = the association containing peer_hi with the peer's
|
|
* address list
|
|
*
|
|
* out: Returns 0 if no update needs to be sent, 1 if update is needed,
|
|
* or -1 on error.
|
|
*
|
|
* Take care of the address verification and readdressing tasks when
|
|
* receiving UPDATE messages. hip_a->peer_hi->addrs is a list of peer
|
|
* addresses, and when the preferred flag is set, readdressing is needed;
|
|
* when an address has a status of UNVERIFIED, we need to do an address check.
|
|
*/
|
|
int handle_update_readdress(hip_assoc *hip_a, struct sockaddr **addrcheck)
|
|
{
|
|
int need_to_send_update = FALSE;
|
|
sockaddr_list *l, *l_next, *peer_list, *my_list, *new_af = NULL;
|
|
struct sockaddr *pref_addr, *new_af_addr = NULL;
|
|
__u32 nonce, new_spi, new_peer_spi;
|
|
|
|
if (!hip_a)
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
l = peer_list = &hip_a->peer_hi->addrs;
|
|
while (l)
|
|
{
|
|
l_next = l->next;
|
|
/* new preferred address, do readdressing tasks */
|
|
if (l->preferred &&
|
|
(hip_a->peer_hi->skip_addrcheck ||
|
|
(l->status == ACTIVE)))
|
|
{
|
|
pref_addr = (struct sockaddr*)&l->addr;
|
|
log_(NORMT, "Making new preferred address active: %s\n",
|
|
logaddr(pref_addr));
|
|
/* draw new keys, adopt new SPIs if necessary */
|
|
new_spi = 0;
|
|
new_peer_spi = 0;
|
|
if ((hip_a->rekey) && (!hip_a->rekey->need_ack) &&
|
|
(hip_a->peer_rekey) &&
|
|
(hip_a->peer_rekey->new_spi > 0))
|
|
{
|
|
log_(NORMT, "Performing rekey...\n");
|
|
new_spi = hip_a->rekey->new_spi;
|
|
new_peer_spi = hip_a->peer_rekey->new_spi;
|
|
hip_finish_rekey(hip_a, FALSE);
|
|
}
|
|
my_list = &hip_a->hi->addrs;
|
|
if (l->addr.ss_family != my_list->addr.ss_family)
|
|
{
|
|
for (new_af = my_list;
|
|
new_af;
|
|
new_af = new_af->next)
|
|
{
|
|
if (new_af->addr.ss_family ==
|
|
l->addr.ss_family)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!new_af)
|
|
{
|
|
log_(
|
|
WARN,
|
|
"Could not find an address of family %d\n",
|
|
l->addr.ss_family);
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
log_(NORMT,
|
|
"Found new address family %s.\n",
|
|
logaddr((struct sockaddr*)&new_af
|
|
->addr));
|
|
}
|
|
}
|
|
if (!new_af)
|
|
{
|
|
rebuild_sa(hip_a, pref_addr, new_spi,
|
|
TRUE, TRUE);
|
|
rebuild_sa(hip_a, pref_addr, new_peer_spi,
|
|
FALSE, TRUE);
|
|
}
|
|
else
|
|
{
|
|
new_af_addr = SA(&new_af->addr);
|
|
rebuild_sa_x2(hip_a, pref_addr, new_af_addr,
|
|
new_peer_spi, TRUE);
|
|
rebuild_sa_x2(hip_a, new_af_addr, pref_addr,
|
|
new_spi, FALSE);
|
|
}
|
|
log_hipa_fromto(QOUT, "Update completed (readdress)",
|
|
hip_a, FALSE, TRUE);
|
|
if (l != peer_list)
|
|
{
|
|
memcpy(HIPA_DST(hip_a), pref_addr,
|
|
SALEN(pref_addr));
|
|
hip_a->peer_hi->addrs.lifetime = l->lifetime;
|
|
make_address_active(&hip_a->peer_hi->addrs);
|
|
delete_address_entry_from_list(&peer_list, l);
|
|
}
|
|
if (new_af)
|
|
{
|
|
struct sockaddr_storage temp_addr;
|
|
int temp_lifetime;
|
|
log_hipa_fromto(QOUT,
|
|
"Update completed (readdress)",
|
|
hip_a,
|
|
TRUE,
|
|
FALSE);
|
|
/* Swap addrs instead of just overwriting */
|
|
memcpy(&temp_addr, HIPA_SRC(hip_a),
|
|
SALEN(HIPA_SRC(hip_a)));
|
|
temp_lifetime = hip_a->hi->addrs.lifetime;
|
|
memcpy(HIPA_SRC(hip_a), new_af_addr,
|
|
SALEN(new_af_addr));
|
|
hip_a->hi->addrs.lifetime = new_af->lifetime;
|
|
make_address_active(&hip_a->hi->addrs);
|
|
memcpy(new_af_addr, &temp_addr,
|
|
SALEN(&temp_addr));
|
|
new_af->lifetime = temp_lifetime;
|
|
}
|
|
/* adopt new SPIs; rekey structures freed later */
|
|
if (new_spi && new_peer_spi)
|
|
{
|
|
hip_a->spi_in = new_spi;
|
|
hip_a->spi_out = new_peer_spi;
|
|
}
|
|
/* choose address to verify */
|
|
}
|
|
else if (!hip_a->peer_hi->skip_addrcheck &&
|
|
(l->status == UNVERIFIED))
|
|
{
|
|
/* XXX for now, verify only the first address */
|
|
*addrcheck = (struct sockaddr *)&l->addr;
|
|
log_(NORMT, "Performing address check for: %s\n",
|
|
logaddr(*addrcheck));
|
|
RAND_bytes((__u8*)&nonce, sizeof(__u32));
|
|
l->nonce = nonce;
|
|
/* add SEQ parameter to UPDATE if it doesn't have one */
|
|
if (!hip_a->rekey)
|
|
{
|
|
hip_a->rekey = malloc(sizeof(struct rekey_info));
|
|
memset(hip_a->rekey, 0,
|
|
sizeof(struct rekey_info));
|
|
hip_a->rekey->update_id =
|
|
hip_a->hi->update_id++;
|
|
hip_a->rekey->need_ack = TRUE;
|
|
gettimeofday(&hip_a->rekey->rk_time, NULL);
|
|
}
|
|
need_to_send_update = TRUE;
|
|
}
|
|
l = l_next;
|
|
}
|
|
|
|
return (need_to_send_update);
|
|
}
|
|
|
|
void finish_address_check(hip_assoc *hip_a, __u32 nonce, struct sockaddr *src)
|
|
{
|
|
sockaddr_list *l;
|
|
|
|
if (!hip_a || !src)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* find peer address */
|
|
for (l = &hip_a->peer_hi->addrs; l; l = l->next)
|
|
{
|
|
if ((l->addr.ss_family == src->sa_family) &&
|
|
(!memcmp(SA2IP(&l->addr),SA2IP(src),SAIPLEN(src))))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!l)
|
|
{
|
|
log_(WARN, "Could not find address check address %s.\n",
|
|
logaddr(src));
|
|
/* check that echoed nonce matches, and update address status */
|
|
}
|
|
else if (nonce == l->nonce)
|
|
{
|
|
log_(NORM, "Address check succeeded for %s (preferred=%s).\n",
|
|
logaddr(src), l->preferred ? "yes" : "no");
|
|
make_address_active(l);
|
|
/* cleanup structures if they have no more use */
|
|
if ((!hip_a->rekey->need_ack) && (!hip_a->rekey->new_spi))
|
|
{
|
|
free(hip_a->rekey);
|
|
hip_a->rekey = NULL;
|
|
}
|
|
if ((hip_a->peer_rekey) && (!hip_a->peer_rekey->need_ack) &&
|
|
(!hip_a->peer_rekey->new_spi))
|
|
{
|
|
free(hip_a->peer_rekey);
|
|
hip_a->peer_rekey = NULL;
|
|
}
|
|
clear_retransmissions(hip_a);
|
|
if (hip_a->icmp_update_status == ICMP_UPDATE_TRIGGERED)
|
|
{
|
|
hip_a->icmp_update_status = ICMP_UPDATE_SUCCESSFUL;
|
|
gettimeofday(&hip_a->icmp_update_time, NULL);
|
|
}
|
|
/* readdressing occurs later, when address list is
|
|
* scanned for new ACTIVE addresses */
|
|
}
|
|
else
|
|
{
|
|
log_(WARN, "Address check failed for source %s with nonce 0x%x"
|
|
" (0x%x).\n", logaddr(src), nonce, l->nonce);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* hip_finish_rekey()
|
|
*
|
|
* in: hip_a = HIP association with rekey and peer_rekey data
|
|
* rebuild = TRUE if SAs should be rebuilt (rekey only)
|
|
* FALSE to leave SAs alone (readdress + rekey)
|
|
*
|
|
* Completes the rekey process (UPDATE exchange) by drawing or generating
|
|
* new keying material, adding SAs with new SPIs, and dropping the old SAs.
|
|
*/
|
|
int hip_finish_rekey(hip_assoc *hip_a, int rebuild)
|
|
{
|
|
int keymat_index, err;
|
|
unsigned char *dh_secret_key = NULL;
|
|
|
|
/*
|
|
* Rekey from section 8.11.3
|
|
*/
|
|
|
|
/*
|
|
* 1. if new DH from peer or me, generate new keying material
|
|
*/
|
|
err = 0;
|
|
if (hip_a->rekey->dh || hip_a->peer_rekey->dh)
|
|
{
|
|
log_(NORM, "At least one DH found in UPDATE exchange, ");
|
|
log_(NORM, "computing new secret key.\n");
|
|
if ((hip_a->rekey->dh && hip_a->peer_rekey->dh) &&
|
|
(hip_a->rekey->dh_group_id !=
|
|
hip_a->peer_rekey->dh_group_id))
|
|
{
|
|
log_(WARN, "Warning: UPDATE DH group mismatch!\n");
|
|
}
|
|
|
|
if (hip_a->rekey->dh)
|
|
{
|
|
unuse_dh_entry(hip_a->evp_dh);
|
|
hip_a->dh_group_id = hip_a->rekey->dh_group_id;
|
|
hip_a->evp_dh = hip_a->rekey->dh;
|
|
hip_a->rekey->dh = NULL; /* moved to hip_a->dh */
|
|
}
|
|
if (hip_a->peer_rekey->dh)
|
|
{
|
|
hip_a->peer_dh = hip_a->peer_rekey->dh;
|
|
hip_a->peer_rekey->dh = NULL; /* moved to ->peer_dh*/
|
|
}
|
|
|
|
/*
|
|
* compute a new secret key from our dh and peer's pub_key
|
|
* and recompute the keymat
|
|
*/
|
|
if (!dh_secret_key)
|
|
{
|
|
log_(WARN, "hip_finish_rekey() malloc() error");
|
|
return(-1);
|
|
}
|
|
|
|
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(hip_a->evp_dh, NULL);
|
|
EVP_PKEY_derive_init(ctx);
|
|
EVP_PKEY_derive_set_peer(ctx,hip_a->peer_dh);
|
|
EVP_PKEY_derive(ctx, NULL, &hip_a -> dh_secret_len);
|
|
dh_secret_key = (unsigned char *)OPENSSL_malloc(hip_a -> dh_secret_len);
|
|
EVP_PKEY_derive(ctx, dh_secret_key, &hip_a -> dh_secret_len);
|
|
|
|
set_secret_key(dh_secret_key, hip_a);
|
|
keymat_index = 0;
|
|
compute_keymat(hip_a);
|
|
/* 2. set new keymat_index to 0, or choose lowest keymat index
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
if (hip_a->rekey->keymat_index <
|
|
hip_a->peer_rekey->keymat_index)
|
|
{
|
|
keymat_index = hip_a->rekey->keymat_index;
|
|
}
|
|
else
|
|
{
|
|
keymat_index = hip_a->peer_rekey->keymat_index;
|
|
}
|
|
}
|
|
|
|
log_(NORM, "Using keymat index = %d for drawing new keys.\n",
|
|
keymat_index);
|
|
|
|
/*
|
|
* 3. draw keys for new incoming/outgoing ESP SAs
|
|
* do not draw HIP keys!
|
|
*/
|
|
if (draw_keys(hip_a, FALSE, keymat_index) < 0)
|
|
{
|
|
log_(WARN, "Error drawing new keys, old SAs retained.\n");
|
|
return(-1);
|
|
}
|
|
|
|
/* 4. move to ESTABLISHED
|
|
* 5. add the NEW outgoing/incoming SA
|
|
*/
|
|
err = rebuild_sa(hip_a, NULL, hip_a->rekey->new_spi, TRUE, TRUE);
|
|
err += rebuild_sa(hip_a, NULL, hip_a->peer_rekey->new_spi, FALSE, TRUE);
|
|
/* no need to call sadb_readdress(), since address is not changing */
|
|
|
|
if (!err)
|
|
{
|
|
log_hipa_fromto(QOUT, "Update completed (rekey)",
|
|
hip_a, FALSE, TRUE);
|
|
hip_a->spi_out = hip_a->peer_rekey->new_spi;
|
|
hip_a->spi_in = hip_a->rekey->new_spi;
|
|
free(hip_a->peer_rekey);
|
|
free(hip_a->rekey); /* any DH already unused */
|
|
hip_a->peer_rekey = NULL;
|
|
hip_a->rekey = NULL;
|
|
}
|
|
return(err);
|
|
}
|
|
|
|
/*
|
|
*
|
|
* function hip_parse_close()
|
|
*
|
|
* in: data = raw socket bytes
|
|
* hip_a = pointer to HIP connection instance
|
|
* *nonce = pointer for storing echo response
|
|
*
|
|
* out: Returns -1 if error, packet length otherwise.
|
|
*
|
|
* parse HIP CLOSE and CLOSE_ACK packets
|
|
*
|
|
*/
|
|
int hip_parse_close(const __u8 *data, hip_assoc *hip_a, __u32 *nonce)
|
|
{
|
|
hiphdr *hiph;
|
|
int location, len, data_len;
|
|
int type, length, last_type = 0;
|
|
tlv_head *tlv;
|
|
__u8 *hmac;
|
|
int is_ack;
|
|
__u32 received_nonce = 0;
|
|
|
|
location = 0;
|
|
hiph = (hiphdr*) &data[location];
|
|
data_len = location + ((hiph->hdr_len + 1) * 8);
|
|
location += sizeof(hiphdr);
|
|
is_ack = (hiph->packet_type == CLOSE_ACK);
|
|
if (is_ack)
|
|
{
|
|
*nonce = 0;
|
|
}
|
|
|
|
while (location < data_len)
|
|
{
|
|
tlv = (tlv_head*) &data[location];
|
|
type = ntohs(tlv->type);
|
|
length = ntohs(tlv->length);
|
|
if (check_tlv_type_length(type, length, last_type,
|
|
is_ack ? "CLOSE_ACK" : "CLOSE") <
|
|
0)
|
|
{
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
last_type = type;
|
|
}
|
|
|
|
if (type == PARAM_ECHO_REQUEST)
|
|
{
|
|
if (is_ack)
|
|
{
|
|
log_(WARN, "Found ECHO_REQUEST in CLOSE_ACK ");
|
|
log_(NORM, "packet, dropping.\n");
|
|
return(-1);
|
|
}
|
|
/* prevent excessive memory consumption */
|
|
if (length > MAX_OPAQUE_SIZE)
|
|
{
|
|
log_(WARN,"ECHO_REQUEST in CLOSE too large.\n");
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hip_a->opaque = (struct opaque_entry*)
|
|
malloc(sizeof(struct
|
|
opaque_entry));
|
|
if (hip_a->opaque == NULL)
|
|
{
|
|
log_(NORM,"Malloc err: ECHO_REQUEST\n");
|
|
return(-1);
|
|
}
|
|
hip_a->opaque->opaque_len = (__u16)length;
|
|
memcpy(hip_a->opaque->opaque_data,
|
|
((tlv_echo*)tlv)->opaque_data, length);
|
|
hip_a->opaque->opaque_nosig = FALSE;
|
|
}
|
|
}
|
|
else if (type == PARAM_ECHO_RESPONSE)
|
|
{
|
|
if (!is_ack)
|
|
{
|
|
log_(WARN, "Found ECHO_RESPONSE in CLOSE ");
|
|
log_(NORM, "packet, dropping.\n");
|
|
return(-1);
|
|
}
|
|
if (length != sizeof(__u32))
|
|
{
|
|
log_(WARN, "ECHO_RESPONSE has wrong length.\n");
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
memcpy(&received_nonce,
|
|
((tlv_echo*)tlv)->opaque_data, length);
|
|
}
|
|
else if (type == PARAM_HIP_SIGNATURE)
|
|
{
|
|
if ((hip_a == NULL) || (hip_a->peer_hi == NULL))
|
|
{
|
|
log_(WARN, "Received signature parameter "
|
|
"without any Host Identity context for "
|
|
"verification.\n");
|
|
return(-1);
|
|
}
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
if (validate_signature(data, len, tlv,
|
|
hip_a->peer_hi->dsa,
|
|
hip_a->peer_hi->rsa,
|
|
hip_a->peer_hi->ecdsa,
|
|
hip_a->peer_hi->hit_suite_id) < 0)
|
|
{
|
|
log_(WARN, "Invalid signature.\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_AUTHENTICATION_FAILED,
|
|
NULL, 0);
|
|
if (OPT.permissive)
|
|
{
|
|
return(0);
|
|
}
|
|
else
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* save nonce from echo reply */
|
|
if (received_nonce > 0)
|
|
{
|
|
*nonce = received_nonce;
|
|
}
|
|
return(0);
|
|
}
|
|
}
|
|
else if (type == PARAM_HMAC)
|
|
{
|
|
hmac = ((tlv_hmac*)tlv)->hmac;
|
|
/* reset the length and checksum for the HMAC */
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
log_(NORM, "HMAC verify over %d bytes. ",len);
|
|
log_(NORM, "hdr length=%d \n", hiph->hdr_len);
|
|
if (validate_hmac(data, len,
|
|
hmac, length,
|
|
get_key(hip_a, HIP_INTEGRITY, TRUE),
|
|
hip_a->hit_suite))
|
|
{
|
|
log_(WARN, "Invalid HMAC.\n");
|
|
hip_send_notify(hip_a,
|
|
NOTIFY_HMAC_FAILED,
|
|
NULL, 0);
|
|
if (OPT.permissive)
|
|
{
|
|
return(0);
|
|
}
|
|
else
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "HMAC verified OK.\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (check_tlv_unknown_critical(type, length) < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
location += tlv_length_to_parameter_length(length);
|
|
}
|
|
/* if we get here, no valid signature has been found */
|
|
return(-1);
|
|
}
|
|
|
|
int hip_handle_close(__u8 *buff, hip_assoc *hip_a)
|
|
{
|
|
int err = 0;
|
|
int is_ack = (((hiphdr*)buff)->packet_type == CLOSE_ACK);
|
|
__u32 nonce, saved_nonce;
|
|
|
|
/* CLOSE_ACK is only accepted in state CLOSING or CLOSED */
|
|
if (is_ack && (hip_a->state != CLOSING) && (hip_a->state != CLOSED))
|
|
{
|
|
log_(WARN, "CLOSE_ACK packet not accepted in state=%d.\n",
|
|
hip_a->state);
|
|
return(-1);
|
|
/* CLOSE is only accepted in states ESTABLISHED, CLOSING, CLOSED
|
|
*/
|
|
}
|
|
else if (!is_ack && (hip_a->state != ESTABLISHED) &&
|
|
(hip_a->state != CLOSING) && (hip_a->state != CLOSED) &&
|
|
(hip_a->state != R2_SENT))
|
|
{
|
|
log_(WARN, "CLOSE packet not accepted in state=%d.\n",
|
|
hip_a->state);
|
|
return(-1);
|
|
}
|
|
if (hip_parse_close(buff, hip_a, &nonce) < 0)
|
|
{
|
|
log_(WARN, "Error while processing CLOSE%s, dropping.\n",
|
|
is_ack ? "_ACK" : "");
|
|
/* stay in the same state */
|
|
return(-1);
|
|
}
|
|
clear_retransmissions(hip_a);
|
|
#ifdef __MACOSX__
|
|
if (hip_a->ipfw_rule > 0)
|
|
{
|
|
del_divert_rule(hip_a->ipfw_rule);
|
|
hip_a->ipfw_rule = 0;
|
|
}
|
|
#endif
|
|
if (!is_ack) /* respond with CLOSE_ACK */
|
|
{
|
|
if ((err = hip_send_close(hip_a, TRUE)) > 0)
|
|
{
|
|
log_(NORM, "Sent CLOSE_ACK (%d bytes)\n", err);
|
|
}
|
|
else
|
|
{
|
|
log_(WARN, "Failed to send CLOSE_ACK: %s.\n",
|
|
strerror(errno));
|
|
}
|
|
}
|
|
else /* check CLOSE_ACK echo response */
|
|
{
|
|
if (!hip_a->opaque)
|
|
{
|
|
log_(WARN, "CLOSE_ACK received but nonce has already "
|
|
"been freed, dropping.");
|
|
return(-1);
|
|
}
|
|
memcpy(&saved_nonce, hip_a->opaque->opaque_data, sizeof(__u32));
|
|
if (nonce != saved_nonce)
|
|
{
|
|
log_(WARN, "CLOSE_ACK echo response did not match ");
|
|
log_(NORM, "echo request.\n");
|
|
return(-1);
|
|
}
|
|
/* ACK successful, discard state and go to UNASSOCIATED.
|
|
* SAs have already been deleted upon transition to CLOSED or
|
|
* CLOSING.
|
|
*/
|
|
log_hipa_fromto(QOUT, "Close completed (received ack)",
|
|
hip_a, TRUE, TRUE);
|
|
set_state(hip_a, UNASSOCIATED);
|
|
free_hip_assoc(hip_a);
|
|
return(0);
|
|
}
|
|
|
|
/* In the HIP state diagram, in ESTABLISHED we do not discard state,
|
|
* but in the packet processing section of the draft, we see that
|
|
* new data packets need to trigger new exchanges -- so here we
|
|
* proceed with the deletes. */
|
|
err = 0;
|
|
set_state(hip_a, CLOSED);
|
|
log_hipa_fromto(QOUT, "Close completed (sent ack)",
|
|
hip_a, TRUE, TRUE);
|
|
err = delete_associations(hip_a, 0, 0);
|
|
return(err);
|
|
}
|
|
|
|
int hip_parse_notify(__u8 *data,
|
|
hip_assoc *hip_a,
|
|
__u16 *code,
|
|
__u8 **nd,
|
|
__u16 *nd_len)
|
|
{
|
|
hiphdr *hiph;
|
|
int location, len, data_len;
|
|
int type, length, last_type = 0;
|
|
tlv_head *tlv;
|
|
tlv_notify *notify;
|
|
|
|
location = 0;
|
|
hiph = (hiphdr*) &data[location];
|
|
data_len = location + ((hiph->hdr_len + 1) * 8);
|
|
location += sizeof(hiphdr);
|
|
|
|
while (location < data_len)
|
|
{
|
|
tlv = (tlv_head*) &data[location];
|
|
type = ntohs(tlv->type);
|
|
length = ntohs(tlv->length);
|
|
if (check_tlv_type_length(type, length, last_type,
|
|
"NOTIFY") < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
last_type = type;
|
|
}
|
|
|
|
if (type == PARAM_NOTIFY)
|
|
{
|
|
notify = (tlv_notify*)tlv;
|
|
*code = ntohs(notify->notify_type);
|
|
if (length > (sizeof(tlv_notify) - 4))
|
|
{
|
|
*nd_len = length - (sizeof(tlv_notify) - 4);
|
|
if ((*nd_len > data_len) || (*nd_len < 1))
|
|
{
|
|
log_(WARN,"Bad notify data length:%d\n",
|
|
*nd_len);
|
|
*nd_len = 0;
|
|
return(-1);
|
|
}
|
|
*nd = notify->notify_data;
|
|
}
|
|
}
|
|
else if (type == PARAM_HIP_SIGNATURE)
|
|
{
|
|
if ((hip_a == NULL) || (hip_a->peer_hi == NULL))
|
|
{
|
|
log_(WARN, "Received signature parameter "
|
|
"without any Host Identity context for "
|
|
"verification.\n");
|
|
return(-1);
|
|
}
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
if (validate_signature(data, len, tlv,
|
|
hip_a->peer_hi->dsa,
|
|
hip_a->peer_hi->rsa,
|
|
hip_a->peer_hi->ecdsa,
|
|
hip_a->peer_hi->hit_suite_id) < 0)
|
|
{
|
|
log_(WARN, "Invalid signature.\n");
|
|
/* Don't send NOTIFY responding to a NOTIFY
|
|
* (this might create a NOTIFY war) */
|
|
if (OPT.permissive)
|
|
{
|
|
return(0);
|
|
}
|
|
else
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (check_tlv_unknown_critical(type, length) < 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
location += tlv_length_to_parameter_length(length);
|
|
}
|
|
/* if we get here, no valid signature has been found */
|
|
return(-1);
|
|
}
|
|
|
|
int hip_handle_notify(__u8 *buff, hip_assoc *hip_a)
|
|
{
|
|
int err = 0;
|
|
__u16 code = 0, data_len = 0;
|
|
__u8* data = NULL;
|
|
/* NOTIFY accepted in all states? */
|
|
if (!hip_a)
|
|
{
|
|
log_(WARN, "Received NOTIFY with no association.\n");
|
|
return(-1);
|
|
}
|
|
|
|
if (hip_parse_notify(buff, hip_a, &code, &data, &data_len) < 0)
|
|
{
|
|
log_(WARN, "Error while processing NOTIFY, dropping.\n");
|
|
/* stay in the same state */
|
|
return(-1);
|
|
}
|
|
/* Do not change state based on NOTIFY... so assuming we don't want
|
|
* to clear retransmissions here. */
|
|
/* clear_retransmissions(hip_a); */
|
|
log_(WARN, "Received NOTIFY from %s: ", logaddr(HIPA_SRC(hip_a)));
|
|
|
|
switch (code)
|
|
{
|
|
case NOTIFY_UNSUPPORTED_CRITICAL_PARAMETER_TYPE:
|
|
log_(NORM, "Unsupported critical parameter type.\n");
|
|
break;
|
|
case NOTIFY_INVALID_SYNTAX:
|
|
log_(NORM, "Invalid syntax.\n");
|
|
break;
|
|
case NOTIFY_NO_DH_PROPOSAL_CHOSEN:
|
|
log_(NORM, "No acceptable DH group ID proposed.\n");
|
|
break;
|
|
case NOTIFY_INVALID_DH_CHOSEN:
|
|
log_(NORM, "Invalid DH group ID chosen.\n");
|
|
break;
|
|
case NOTIFY_NO_HIP_PROPOSAL_CHOSEN:
|
|
log_(NORM, "No acceptable HIP Transform was proposed.\n");
|
|
break;
|
|
case NOTIFY_INVALID_HIP_CIPHER_CHOSEN:
|
|
log_(NORM, "Invalid HIP Transform chosen.\n");
|
|
break;
|
|
case NOTIFY_NO_ESP_PROPOSAL_CHOSEN:
|
|
log_(NORM, "No acceptable ESP Transform was proposed.\n");
|
|
break;
|
|
case NOTIFY_INVALID_ESP_TRANSFORM_CHOSEN:
|
|
log_(NORM, "Invalid ESP Transform chosen.\n");
|
|
break;
|
|
case NOTIFY_AUTHENTICATION_FAILED:
|
|
log_(NORM, "Authentication (signature) failed.\n");
|
|
break;
|
|
case NOTIFY_CHECKSUM_FAILED:
|
|
log_(NORM, "Checksum failed.\n");
|
|
break;
|
|
case NOTIFY_HMAC_FAILED:
|
|
log_(NORM, "Authentication (HMAC) failed.\n");
|
|
break;
|
|
case NOTIFY_ENCRYPTION_FAILED:
|
|
log_(NORM, "Failed to decrypt the ENCRYPTED TLV.\n");
|
|
break;
|
|
case NOTIFY_INVALID_HIT:
|
|
log_(NORM, "HI does not validate HIT.\n");
|
|
break;
|
|
case NOTIFY_BLOCKED_BY_POLICY:
|
|
log_(NORM, "Blocked by policy.\n");
|
|
break;
|
|
case NOTIFY_SERVER_BUSY_PLEASE_RETRY:
|
|
log_(NORM, "Server busy -- please retry.\n");
|
|
break;
|
|
case NOTIFY_LOCATOR_TYPE_UNSUPPORTED:
|
|
log_(NORM, "Unsupported locator type.\n");
|
|
break;
|
|
case NOTIFY_I2_ACKNOWLEDGEMENT:
|
|
log_(NORM, "I2 received but queued for later processing.\n");
|
|
break;
|
|
case NOTIFY_LOSS_DETECT:
|
|
log_(NORM, "loss detected.\n");
|
|
return(handle_notify_loss(data, data_len));
|
|
break;
|
|
default:
|
|
log_(NORM, "Unknown notify code: %d\n", code);
|
|
break;
|
|
}
|
|
|
|
if (data_len > 0)
|
|
{
|
|
log_(NORM, "NOTIFY data: ");
|
|
print_hex(data, data_len);
|
|
log_(NORM, "\n");
|
|
}
|
|
|
|
/*
|
|
* This was deprecated in draft-06: do not change state based on
|
|
* a received NOTIFY packet.
|
|
*/
|
|
#if 0
|
|
/* NOTIFY is in response to a packet, assume request has failed. */
|
|
if ((hip_a->state == I1_SENT) || (hip_a->state == I2_SENT) ||
|
|
(hip_a->state == R2_SENT))
|
|
{
|
|
log_(WARN, "Association with %s moving from state %d to ",
|
|
logaddr((hip_a->state == R2_SENT) ?
|
|
HIPA_SRC(hip_a) : HIPA_DST(hip_a)), hip_a->state);
|
|
log_(NORM, "E_FAILED because of received NOTIFY.\n");
|
|
clear_retransmissions(hip_a);
|
|
set_state(hip_a, E_FAILED);
|
|
}
|
|
#endif
|
|
|
|
return(err);
|
|
}
|
|
|
|
int hip_handle_BOS(__u8 *data, struct sockaddr *src)
|
|
{
|
|
hiphdr *hiph;
|
|
hi_node *peer_hi;
|
|
int location, len, err = 0;
|
|
tlv_head *tlv;
|
|
|
|
|
|
hiph = (hiphdr *) data;
|
|
|
|
/* is HIT already known? */
|
|
peer_hi = find_host_identity(peer_hi_head, hiph->hit_sndr);
|
|
if (peer_hi)
|
|
{
|
|
log_(NORMT, "Received BOS from %s, with a HIT ", logaddr(src));
|
|
log_(NORM, "that we already have.\n");
|
|
return(0);
|
|
}
|
|
peer_hi = NULL;
|
|
location = sizeof(hiphdr);
|
|
|
|
/* validate the packet first */
|
|
/* Host Identity */
|
|
tlv = (tlv_head*) &data[location];
|
|
if (ntohs(tlv->type) != PARAM_HOST_ID)
|
|
{
|
|
log_(NORM, "Expected HOST_ID in BOS, dropping.\n");
|
|
return(-1);
|
|
}
|
|
|
|
if (handle_hi(&peer_hi, &data[location]) < 0)
|
|
{
|
|
log_(NORM, "Problem with HOST_ID in BOS, dropping.\n");
|
|
free_hi_node(peer_hi);
|
|
return(-1);
|
|
}
|
|
if (!validate_hit(hiph->hit_sndr, peer_hi))
|
|
{
|
|
log_(WARN, "HI in BOS does not match sender's HIT\n");
|
|
if (!OPT.permissive)
|
|
{
|
|
err = -1;
|
|
}
|
|
goto bos_cleanup;
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "HI in BOS validates the sender's HIT.\n");
|
|
}
|
|
|
|
/* signature */
|
|
location += tlv_length_to_parameter_length(ntohs(tlv->length));
|
|
tlv = (tlv_head*) &data[location];
|
|
if (ntohs(tlv->type) != PARAM_HIP_SIGNATURE)
|
|
{
|
|
log_(NORM, "Expected SIGNATURE in BOS, dropping.\n");
|
|
err = -1;
|
|
goto bos_cleanup;
|
|
}
|
|
len = eight_byte_align(location);
|
|
hiph->checksum = 0;
|
|
hiph->hdr_len = (len / 8) - 1;
|
|
if (validate_signature( data, location, tlv, peer_hi->dsa,
|
|
peer_hi->rsa, peer_hi->ecdsa, peer_hi->hit_suite_id) < 0)
|
|
{
|
|
log_(WARN, "Invalid signature in BOS.\n");
|
|
err = -1;
|
|
goto bos_cleanup;
|
|
}
|
|
else /* adopt the new HIT into our peer list */
|
|
{
|
|
log_(NORM, "BOS signature is good. Adding HIT from %s.\n",
|
|
logaddr(src));
|
|
add_peer_hit(hiph->hit_sndr, src);
|
|
err = 0;
|
|
}
|
|
bos_cleanup:
|
|
free_hi_node(peer_hi);
|
|
return(err);
|
|
}
|
|
|
|
int hip_handle_CER(__u8 *data, hip_assoc *hip_a)
|
|
{
|
|
log_(NORM, "The CER packet has not been implemented.\n");
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* function validate_signature()
|
|
*
|
|
* in: data = data to sign
|
|
* data_len = bytes of data to sign
|
|
* tlv = sig tlv of signature to verify
|
|
*
|
|
*
|
|
* out: Returns 0 if signature is correct, -1 if incorrect or error.
|
|
*/
|
|
int validate_signature(const __u8 *data, int data_len, tlv_head *tlv,
|
|
DSA *dsa, RSA *rsa, EC_KEY* ecdsa, int type)
|
|
{
|
|
int err;
|
|
SHA_CTX sha1_ctx;
|
|
SHA256_CTX sha256_ctx;
|
|
SHA512_CTX sha512_ctx;
|
|
unsigned char md[SHA512_DIGEST_LENGTH];
|
|
DSA_SIG *dsa_sig;
|
|
dsa_sig = DSA_SIG_new();
|
|
ECDSA_SIG *ecdsa_sig;
|
|
ecdsa_sig = ECDSA_SIG_new();
|
|
int length, sig_len;
|
|
tlv_hip_sig *sig = (tlv_hip_sig*)tlv;
|
|
__u8 alg;
|
|
|
|
length = ntohs(sig->length);
|
|
alg = sig->algorithm;
|
|
log_(WARN, "Validating signature of type %d \n", type);
|
|
switch (alg)
|
|
{
|
|
case HI_ALG_DSA:
|
|
if (!dsa)
|
|
{
|
|
log_(WARN, "validate_signature(): ");
|
|
log_(NORM, "no DSA context!\n");
|
|
return(-1);
|
|
}
|
|
if (length != (1 + HIP_DSA_SIG_SIZE))
|
|
{
|
|
log_(WARN, "Invalid DSA signature size of %d ",
|
|
length);
|
|
log_(NORM, "(should be %d).\n",
|
|
1 + HIP_DSA_SIG_SIZE);
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
break;
|
|
case HI_ALG_RSA:
|
|
if (!rsa)
|
|
{
|
|
log_(WARN, "validate_signature(): ");
|
|
log_(NORM, "no RSA context!\n");
|
|
return(-1);
|
|
}
|
|
if (length > (1 + RSA_size(rsa)))
|
|
{
|
|
log_(WARN, "Invalid RSA signature size of %d ",
|
|
length);
|
|
log_(NORM, "(should be %d).\n",
|
|
1 + RSA_size(rsa));
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
break;
|
|
case HI_ALG_ECDSA:
|
|
if (!ecdsa)
|
|
{
|
|
log_(WARN, "validate_signature(): ");
|
|
log_(NORM, "no ECDSA context!\n");
|
|
return(-1);
|
|
}
|
|
if (length != HIP_ECDSA384_SIG_SIZE)
|
|
{
|
|
log_(WARN, "Invalid ECDSA signature size of %d ",
|
|
length);
|
|
log_(NORM, "(should be %d).\n", HIP_ECDSA384_SIG_SIZE);
|
|
if (!OPT.permissive)
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
log_(WARN, "Invalid signature algorithm.\n");
|
|
return(-1);
|
|
}
|
|
sig_len = length - 1;
|
|
/* calculate SHA1 hash of the HIP message */
|
|
switch (type)
|
|
{
|
|
case HIT_SUITE_4BIT_RSA_DSA_SHA256:
|
|
SHA256_Init(&sha256_ctx);
|
|
SHA256_Update(&sha256_ctx, data, data_len);
|
|
SHA256_Final(md, &sha256_ctx);
|
|
break;
|
|
case HIT_SUITE_4BIT_ECDSA_SHA384:
|
|
SHA384_Init(&sha512_ctx);
|
|
SHA384_Update(&sha512_ctx, data, data_len);
|
|
SHA384_Final(md, &sha512_ctx);
|
|
break;
|
|
case HIT_SUITE_4BIT_ECDSA_LOW_SHA1:
|
|
SHA1_Init(&sha1_ctx);
|
|
SHA1_Update(&sha1_ctx, data, data_len);
|
|
SHA1_Final(md, &sha1_ctx);
|
|
break;
|
|
default:
|
|
// Default to SHA256 for backwards compatibility
|
|
SHA256_Init(&sha256_ctx);
|
|
SHA256_Update(&sha256_ctx, data, data_len);
|
|
SHA256_Final(md, &sha256_ctx);
|
|
}
|
|
|
|
/* for debugging, print out md or signature */
|
|
log_(NORM, "SHA1: ");
|
|
print_hex(md, SHA256_DIGEST_LENGTH);
|
|
log_(NORM, "\n");
|
|
BIGNUM *dsa_sig_r, *dsa_sig_s;
|
|
BIGNUM *ecdsa_sig_r, *ecdsa_sig_s;
|
|
switch (alg)
|
|
{
|
|
case HI_ALG_DSA:
|
|
/* build the DSA structure */
|
|
dsa_sig_r = BN_bin2bn(&sig->signature[1], 20, NULL);
|
|
dsa_sig_s = BN_bin2bn(&sig->signature[21], 20, NULL);
|
|
/* verify the DSA signature */
|
|
err = DSA_do_verify(md, SHA256_DIGEST_LENGTH, dsa_sig, dsa);
|
|
BN_free(dsa_sig_r);
|
|
BN_free(dsa_sig_s);
|
|
break;
|
|
case HI_ALG_RSA:
|
|
/* verify the RSA signature */
|
|
err = RSA_verify(NID_sha1, md, SHA256_DIGEST_LENGTH,
|
|
sig->signature, sig_len, rsa);
|
|
break;
|
|
case HI_ALG_ECDSA:
|
|
{
|
|
int curve_name = ECDSA_get_curve_id(ecdsa);
|
|
if (curve_name == -1) {
|
|
log_(WARN, "Curve not implemented.\n");
|
|
return -1;
|
|
}
|
|
int curve_param_size = ECDSA_curve_PARAM_SIZE[curve_name];
|
|
ecdsa_sig_r = BN_bin2bn(&sig->signature[0], curve_param_size, NULL);
|
|
ecdsa_sig_s = BN_bin2bn(&sig->signature[curve_param_size], curve_param_size, NULL);
|
|
ECDSA_SIG_set0(ecdsa_sig,ecdsa_sig_r,ecdsa_sig_s);
|
|
err = ECDSA_do_verify(md, curve_param_size, ecdsa_sig, ecdsa);
|
|
BN_free(ecdsa_sig_r);
|
|
BN_free(ecdsa_sig_s);
|
|
}
|
|
break;
|
|
default:
|
|
err = -1;
|
|
break;
|
|
}
|
|
|
|
if (err < 0)
|
|
{
|
|
log_(WARN, "Error with verifying %s signature.\n",
|
|
HI_TYPESTR(alg));
|
|
return(-1);
|
|
}
|
|
else if (err == 0)
|
|
{
|
|
log_(WARN, "Incorrect %s signature found.\n", HI_TYPESTR(alg));
|
|
log_(NORM, "Signature text (len=%d): ", sig_len);
|
|
print_hex(sig->signature, sig_len);
|
|
log_(NORM, "\n");
|
|
return(-1);
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "%s HIP signature is good.\n", HI_TYPESTR(alg));
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* function validate_hmac()
|
|
*
|
|
* in: data = data to hash
|
|
* data_len = number of bytes of data to hash
|
|
* hmac = the hmac sent in the packet, to verify
|
|
* hmac_len = length of the above hmac
|
|
* key = key used for the HMAC keyed hashing algorithm
|
|
* key_len = length of the above key
|
|
*
|
|
* out: Returns 0 if HMAC is correct, -1 if incorrect or error.
|
|
*/
|
|
int validate_hmac(const __u8 *data, int data_len, __u8 *hmac, int hmac_len,
|
|
__u8 *key, int type)
|
|
{
|
|
log_(NORM, "validate hmac: %d \n", type);
|
|
unsigned char hmac_md[EVP_MAX_MD_SIZE] = {0};
|
|
unsigned int hmac_md_len = EVP_MAX_MD_SIZE;
|
|
int key_len = auth_key_len_hit_suite(type);
|
|
|
|
switch (type)
|
|
{
|
|
case HIT_SUITE_4BIT_RSA_DSA_SHA256:
|
|
HMAC( EVP_sha256(),
|
|
key, key_len,
|
|
data, data_len,
|
|
hmac_md, &hmac_md_len );
|
|
break;
|
|
case HIT_SUITE_4BIT_ECDSA_SHA384:
|
|
HMAC( EVP_sha384(),
|
|
key, key_len,
|
|
data, data_len,
|
|
hmac_md, &hmac_md_len );
|
|
break;
|
|
case HIT_SUITE_4BIT_ECDSA_LOW_SHA1:
|
|
HMAC( EVP_sha1(),
|
|
key, key_len,
|
|
data, data_len,
|
|
hmac_md, &hmac_md_len );
|
|
break;
|
|
/* in case someone tries to use unsupported mac/signature algorithm */
|
|
default:
|
|
return(-1);
|
|
}
|
|
/*
|
|
* note that hmac_md_len may be < hmac_len,
|
|
* i.e. for MD5 hmac_md_len==16
|
|
*/
|
|
/* compare lower bits of received HMAC versus calculated HMAC
|
|
* for MD5, this is the lower 128 bits; for SHA-1 it's 160-bits */
|
|
log_(NORM, "computed hmac: (%d) ", hmac_len);
|
|
if ((memcmp(&hmac[hmac_len - hmac_md_len], hmac_md,
|
|
hmac_md_len) == 0))
|
|
{
|
|
return(0);
|
|
}
|
|
print_hex(hmac_md, hmac_md_len);
|
|
log_(NORM, "\n received hmac: (%d) ", hmac_len);
|
|
print_hex(hmac, hmac_len);
|
|
log_(NORM, "\n");
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* check_if_my_hit()
|
|
*
|
|
* in: hit = the HIT to check
|
|
*
|
|
* out: returns NULL if HIT is not known, pointer to HI otherwise
|
|
*
|
|
* Checks if the specified HIT corresponds to one of this machine's
|
|
* Host Identities, and returns a pointer to it. Allows for NULL
|
|
* HIT in opportunisitc mode (then the preferred HI is stored.)
|
|
*/
|
|
hi_node *check_if_my_hit(hip_hit *hit)
|
|
{
|
|
hi_node *my_host_id = NULL;
|
|
|
|
if ((OPT.opportunistic) &&
|
|
(memcmp(hit, &zero_hit, sizeof(hip_hit)) == 0))
|
|
{
|
|
/* NULL HIT accepted with opportunistic */
|
|
my_host_id = get_preferred_hi(my_hi_head);
|
|
}
|
|
else
|
|
{
|
|
/* lookup HIT in my HIT list */
|
|
my_host_id = find_host_identity(my_hi_head, *hit);
|
|
}
|
|
|
|
return(my_host_id);
|
|
}
|
|
|
|
int handle_hip_cipher(hip_assoc *hip_a, __u16 *transforms, int length)
|
|
{
|
|
__u16 *transform_id_packet;
|
|
int transforms_left;
|
|
__u16 transform_id;
|
|
__u16 *chosen = &hip_a->hip_cipher;
|
|
transforms_left = length / sizeof(__u16);
|
|
transform_id_packet = transforms;
|
|
*chosen = 0;
|
|
if (transforms_left >= HIP_CIPHER_MAX)
|
|
{
|
|
log_(WARN, "Warning: There are %d transforms present but the "
|
|
"maximum number is %d.\n",
|
|
transforms_left, HIP_CIPHER_MAX - 1);
|
|
/* continue to read the transforms... */
|
|
}
|
|
|
|
for (; (transforms_left > 0); transform_id_packet++,
|
|
transforms_left--)
|
|
{
|
|
transform_id = ntohs(*transform_id_packet);
|
|
|
|
if ((transform_id <= HIP_CIPHER_RESERVED) ||
|
|
(transform_id >= HIP_CIPHER_MAX))
|
|
{
|
|
log_(WARN, "Ignoring invalid transform (%d).\n",
|
|
transform_id);
|
|
continue;
|
|
}
|
|
if ((hip_a->available_transforms >> transform_id) & 0x1)
|
|
{
|
|
*chosen = transform_id;
|
|
break;
|
|
}
|
|
}
|
|
if (*chosen == 0)
|
|
{
|
|
log_(
|
|
WARN,
|
|
"Couldn't find a suitable HIP transform. This error could indicate that hip.conf was not successfully loaded.\n");
|
|
if (OPT.permissive) /* AES is mandatory */
|
|
{
|
|
log_(WARN, "Continuing using AES.\n");
|
|
*chosen = HIP_CIPHER_AES128_CBC;
|
|
}
|
|
else
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
|
|
/*
|
|
* handle_transforms()
|
|
*
|
|
* in: hip_a = hip association where chosen transform is stored
|
|
* and available_transforms bit mask resides
|
|
* transforms = pointer to a list of transforms
|
|
* length = byte length of transform list
|
|
*
|
|
* out: Returns 0 if a transform was found, -1 on error.
|
|
* Stores the best transform into the hip association.
|
|
* Note that the signature should be verified beforehand, since
|
|
* hip_a is modified.
|
|
*/
|
|
int handle_transforms(hip_assoc *hip_a, __u16 *transforms, int length, int esp)
|
|
{
|
|
__u16 *transform_id_packet;
|
|
int transforms_left, offset;
|
|
__u16 transform_id;
|
|
__u16 *chosen = esp ? &hip_a->esp_transform : &hip_a->hip_transform;
|
|
|
|
offset = esp ? ESP_OFFSET : 0;
|
|
transforms_left = length / sizeof(__u16);
|
|
transform_id_packet = transforms;
|
|
*chosen = 0;
|
|
if (transforms_left >= ESP_MAX)
|
|
{
|
|
log_(WARN, "Warning: There are %d transforms present but the "
|
|
"maximum number is %d.\n",
|
|
transforms_left, ESP_MAX - 1);
|
|
/* continue to read the transforms... */
|
|
}
|
|
|
|
for (; (transforms_left > 0); transform_id_packet++,
|
|
transforms_left--)
|
|
{
|
|
transform_id = ntohs(*transform_id_packet);
|
|
|
|
if ((transform_id <= RESERVED) ||
|
|
(transform_id >= ESP_MAX))
|
|
{
|
|
log_(WARN, "Ignoring invalid transform (%d).\n",
|
|
transform_id);
|
|
continue;
|
|
}
|
|
if ((hip_a->available_transforms >>
|
|
(transform_id + offset)) & 0x1)
|
|
{
|
|
*chosen = transform_id;
|
|
break;
|
|
}
|
|
}
|
|
if (*chosen == 0)
|
|
{
|
|
log_(
|
|
WARN,
|
|
"Couldn't find a suitable HIP transform. This error could indicate that hip.conf was not successfully loaded.\n");
|
|
if (OPT.permissive) /* AES is mandatory */
|
|
{
|
|
log_(WARN, "Continuing using AES.\n");
|
|
*chosen = ESP_AES128_CBC_HMAC_SHA1;
|
|
}
|
|
else
|
|
{
|
|
return(-1);
|
|
}
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* handle_dh()
|
|
*
|
|
*
|
|
* Parse a Diffie-Hellman parameter, storing its group ID into g and
|
|
* the public key into hip_a->peer_dh or dh
|
|
*/
|
|
int handle_dh(hip_assoc *hip_a, const __u8 *data, __u8 *g, EVP_PKEY *evp_dh)
|
|
{
|
|
__u8 g_id;
|
|
int len;
|
|
unsigned char *pub_key, *pub;
|
|
tlv_diffie_hellman *tlv_dh;
|
|
|
|
tlv_dh = (tlv_diffie_hellman*) data;
|
|
|
|
g_id = tlv_dh->group_id;
|
|
*g = g_id;
|
|
if ((g_id <= DH_RESERVED) ||
|
|
(g_id >= DH_MAX))
|
|
{
|
|
log_(WARN, "DH group unsupported %d\n", g_id);
|
|
return(-1);
|
|
}
|
|
|
|
len = ntohs(tlv_dh->pub_len);
|
|
if (len > (ntohs(tlv_dh->length) - 3))
|
|
{
|
|
log_(WARN, "Error: public key length specified (%d) was longer"
|
|
" than TLV length!\n", len);
|
|
return(-1);
|
|
}
|
|
/* DH_size = BN_num_bytes(dh->p) */
|
|
if (len != dhprime_len[g_id])
|
|
{
|
|
log_(WARN, "Warning: public key len = %d, ", len);
|
|
log_(NORM, "expected %d for this group id (%d)\n",
|
|
dhprime_len[g_id], g_id);
|
|
}
|
|
pub = tlv_dh->pub;
|
|
|
|
/* g_id, len, pub are set before this */
|
|
pub_key = malloc(len);
|
|
memcpy(pub_key, pub, len);
|
|
|
|
#ifndef HIP_VPLS
|
|
log_(NORM, "Got DH public value of len %d: 0x", len);
|
|
print_hex(pub_key, len);
|
|
log_(NORM, "\n");
|
|
#endif
|
|
|
|
/* store the public key in hip_a->peer_dh */
|
|
if (evp_dh == NULL)
|
|
{
|
|
if (hip_a->peer_dh)
|
|
{
|
|
EVP_PKEY_free(hip_a->peer_dh);
|
|
}
|
|
|
|
hip_a->peer_dh = d2i_PUBKEY(NULL, (const unsigned char**)&pub_key, len);
|
|
log_(NORM, "EVP_PKEY type: %d", EVP_PKEY_base_id(hip_a->peer_dh));
|
|
}
|
|
else
|
|
{
|
|
evp_dh = d2i_PUBKEY(NULL, (const unsigned char**)&pub_key, len);
|
|
}
|
|
//TODO: fix free problem
|
|
//free(pub_key);
|
|
return(0);
|
|
}
|
|
|
|
int handle_cert(hip_assoc *hip_a, const __u8 *data)
|
|
{
|
|
int len;
|
|
tlv_cert *cert;
|
|
char cert_buf[MAX_CERT_LEN];
|
|
|
|
hi_node *peer_hi;
|
|
peer_hi = hip_a->peer_hi;
|
|
if (!peer_hi)
|
|
{
|
|
return(-1);
|
|
}
|
|
memset(cert_buf, '\0', sizeof(cert_buf));
|
|
cert = (tlv_cert*) data;
|
|
len = ntohs(cert->length) - 4;
|
|
memcpy(cert_buf, cert->certificate, len);
|
|
#ifdef HIP_VPLS
|
|
len = hipcfg_verifyCert(cert_buf, peer_hi->hit);
|
|
if (len == 1)
|
|
{
|
|
log_(NORM, "validated certificate with url: %s\n", cert_buf);
|
|
return(0);
|
|
}
|
|
#endif
|
|
log_(WARN, "Fail to validate certificate with url: %s\n", cert_buf);
|
|
return(-1);
|
|
}
|
|
|
|
/*
|
|
* function handle_hi()
|
|
*
|
|
* in: hi_p = pointer to pointer that will store new Host ID
|
|
* data = pointer to start of HI TLV
|
|
*
|
|
* out: *hi_p is created or modified,
|
|
* (*hi_p)->dsa or (*hi_p)->rsa must not exist, and is created
|
|
* Returns length of HI TLV used, -1 if error.
|
|
*
|
|
* Reads HI TLV into a hi_node structure.
|
|
*/
|
|
int handle_hi(hi_node **hi_p, const __u8 *data)
|
|
{
|
|
/* Get received host id into a hi_p
|
|
* value number of bytes
|
|
* type/length 4
|
|
* HI length 2
|
|
* DI type 4 bits
|
|
* DI length 12 bits
|
|
* RDATA header 4
|
|
*/
|
|
|
|
int length, hi_length, di_length;
|
|
char di_type;
|
|
tlv_host_id *tlv = (tlv_host_id*)data;
|
|
__u32 hi_hdr;
|
|
__u8 alg;
|
|
|
|
length = ntohs(tlv->length);
|
|
hi_length = ntohs(tlv->hi_length);
|
|
di_type = ntohs(tlv->di_type_length) >> 12; /* 4 bits type */
|
|
di_type &= 0x000F;
|
|
di_length = ntohs(tlv->di_type_length) & 0x0FFF; /* 12 bits length */
|
|
|
|
memcpy(&hi_hdr, tlv->hi_hdr, 4);
|
|
hi_hdr = ntohl(hi_hdr);
|
|
alg = hi_hdr & 0xFF; /* get algorithm from last byte of RDATA header */
|
|
hi_length -= 4; /* subtract RDATA length from HI length */
|
|
length -= 8; /* subtract TLV fields and RDATA length */
|
|
return(key_data_to_hi( &data[sizeof(tlv_host_id)],
|
|
alg, hi_length, (__u8)di_type, di_length,
|
|
hi_p, length ));
|
|
}
|
|
|
|
int handle_acks(hip_assoc *hip_a, tlv_ack *ack)
|
|
{
|
|
__u32 *p_ack, ack_update_id;
|
|
int length, ret = FALSE;
|
|
|
|
/* We may receive multiple ACKs, search them
|
|
* for the update id matching hip_a->rekey->update_id */
|
|
p_ack = &ack->peer_update_id;
|
|
for (length = ntohs(ack->length);
|
|
length > 0;
|
|
p_ack++, length -= sizeof(__u32))
|
|
{
|
|
ack_update_id = ntohl(*p_ack);
|
|
/* check if ACK corresponds to a previously sent UPDATE */
|
|
if (hip_a->rekey &&
|
|
(ack_update_id == hip_a->rekey->update_id))
|
|
{
|
|
log_(NORM, "Update id=0x%x has been acked.\n",
|
|
ack_update_id);
|
|
ret = TRUE;
|
|
/* continue parsing so that ignored IDs are logged */
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "Ignoring unknown ID (0x%x) in ACK.\n",
|
|
ack_update_id);
|
|
}
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
/*
|
|
* build rekeying state
|
|
*/
|
|
int handle_esp_info(tlv_esp_info *ei, __u32 spi_out, struct rekey_info *rk)
|
|
{
|
|
__u32 old_spi, new_spi;
|
|
|
|
old_spi = ntohl(ei->old_spi);
|
|
new_spi = ntohl(ei->new_spi);
|
|
|
|
/* add a new SPI/SA only */
|
|
if ((old_spi == 0) && (new_spi > 0))
|
|
{
|
|
log_(NORM, "New SA requested for SPI 0x%x.\n", new_spi);
|
|
log_(WARN, "Warning: creating additional new SAs is "
|
|
"currently unsupported.\n");
|
|
/* XXX May add a new SPI here, but need a way to
|
|
* keep track of SPIs so SA can be later deleted.
|
|
* Also, if this multi-homed host uses the SA as
|
|
* a fallback, it is pointless to create without
|
|
* a corresponding SPD rule. */
|
|
return(0);
|
|
/* Gratuitous */
|
|
}
|
|
else if (old_spi == new_spi)
|
|
{
|
|
log_(NORM, "Gratuitous ESP_INFO: keeping old SPI of 0x%x.\n",
|
|
new_spi);
|
|
rk->keymat_index = ntohs(ei->keymat_index);
|
|
rk->new_spi = new_spi;
|
|
return(0);
|
|
/* Rekey with unknown SPI */
|
|
}
|
|
else if (old_spi != spi_out)
|
|
{
|
|
log_(WARN, "Old SPI in ESP_INFO (0x%x) is unknown.\n", old_spi);
|
|
return(-1);
|
|
/* Deprecating SA */
|
|
}
|
|
else if (new_spi == 0)
|
|
{
|
|
log_(NORM, "Request to deprecate SA with SPI 0x%x\n", old_spi);
|
|
log_(WARN, "Warning: deprecating SAs is currently "
|
|
"unsupported.\n");
|
|
/* XXX Deprecate all locators uniquely bound to this SPI */
|
|
/* change current SA */
|
|
}
|
|
else
|
|
{
|
|
rk->keymat_index = ntohs(ei->keymat_index);
|
|
rk->new_spi = new_spi;
|
|
log_(NORM, "Rekeying due to new SPI of 0x%x.\n", new_spi);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* handle_locators()
|
|
*
|
|
* Following HIP packet parsing, handle the locator TLVs contained in the HIP
|
|
* packet.
|
|
*/
|
|
int handle_locators(hip_assoc *hip_a,
|
|
locator **locators,
|
|
int num,
|
|
struct sockaddr *src,
|
|
__u32 new_spi)
|
|
{
|
|
int i, preferred, first;
|
|
locator *loc;
|
|
struct sockaddr_storage ss_addr;
|
|
struct sockaddr *addr;
|
|
sockaddr_list *l, *peer_list;
|
|
__u8 *p_addr;
|
|
__u32 spi;
|
|
struct timeval now;
|
|
|
|
memset(&ss_addr, 0, sizeof(struct sockaddr_storage));
|
|
addr = (struct sockaddr*)&ss_addr;
|
|
first = TRUE;
|
|
gettimeofday(&now, NULL);
|
|
|
|
for (i = 0; i < num; i++)
|
|
{
|
|
loc = locators[i];
|
|
if (loc->traffic_type == LOCATOR_TRAFFIC_TYPE_SIGNALING)
|
|
{
|
|
log_(WARN, "Warning: Ignoring signaling locator.\n");
|
|
continue;
|
|
}
|
|
else if ((loc->traffic_type != LOCATOR_TRAFFIC_TYPE_BOTH) &&
|
|
(loc->traffic_type != LOCATOR_TRAFFIC_TYPE_DATA))
|
|
{
|
|
log_(WARN, "Warning Ignoring unknown locator traffic ");
|
|
log_(NORM, "type: %d.\n", loc->traffic_type);
|
|
continue;
|
|
}
|
|
if ((loc->locator_type == LOCATOR_TYPE_IPV6) &&
|
|
(loc->locator_length == 4))
|
|
{
|
|
p_addr = &loc->locator[0];
|
|
spi = new_spi;
|
|
}
|
|
else if ((loc->locator_type == LOCATOR_TYPE_SPI_IPV6) &&
|
|
(loc->locator_length == 5))
|
|
{
|
|
memcpy(&spi, &loc->locator[0], 4);
|
|
spi = ntohl(spi);
|
|
p_addr = &loc->locator[4];
|
|
}
|
|
else
|
|
{
|
|
/* send NOTIFY whether or not preferred; only include
|
|
* maximum 20 bytes of locators to prevent overflows */
|
|
log_(WARN, "Locator type %d unsupported (length %d)\n",
|
|
loc->locator_type, loc->locator_length);
|
|
hip_send_notify(hip_a, NOTIFY_LOCATOR_TYPE_UNSUPPORTED,
|
|
loc->locator,
|
|
(loc->locator_length <= 5) ?
|
|
4 * loc->locator_length : 20);
|
|
continue;
|
|
}
|
|
|
|
if ((new_spi > 0) && (new_spi != spi))
|
|
{
|
|
log_(WARN, "SPIs in ESP_INFO and LOCATOR parameters "
|
|
"do not match (0x%x, 0x%x)\n", new_spi, spi);
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Read in address from LOCATOR
|
|
*/
|
|
/* get address and check validity */
|
|
if (IN6_IS_ADDR_V4MAPPED(
|
|
(struct in6_addr*)p_addr))
|
|
{
|
|
addr->sa_family = AF_INET;
|
|
memcpy(SA2IP(addr), p_addr + 12, SAIPLEN(addr));
|
|
if (IN_MULTICAST(*(__u32 *)(SA2IP(addr))))
|
|
{
|
|
continue;
|
|
}
|
|
if (((struct sockaddr_in*)addr)->sin_addr.s_addr
|
|
== INADDR_BROADCAST)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned char *p = SA2IP(addr);
|
|
addr->sa_family = AF_INET6;
|
|
memcpy(SA2IP(addr), p_addr, SAIPLEN(addr));
|
|
if (IN6_IS_ADDR_MULTICAST((struct in6_addr*)p))
|
|
{
|
|
continue;
|
|
}
|
|
/* IPv6 doesn't have broadcast addresses */
|
|
}
|
|
|
|
/* only check preferred (P) bit for
|
|
* the first address in LOCATOR */
|
|
preferred = FALSE;
|
|
if (first && (loc->reserved & LOCATOR_PREFERRED))
|
|
{
|
|
preferred = TRUE;
|
|
}
|
|
first = FALSE;
|
|
|
|
/* check the new preferred address against the source address
|
|
* of the packet */
|
|
if (src && preferred)
|
|
{
|
|
if ((!hip_a->from_via) &&
|
|
((addr->sa_family != src->sa_family) ||
|
|
(memcmp(SA2IP(addr), SA2IP(src),
|
|
SAIPLEN(src)))))
|
|
{
|
|
log_(WARN, "Warning: source address is %s and ",
|
|
logaddr(src));
|
|
log_(NORM, "new preferred LOCATOR is %s.\n",
|
|
logaddr(addr));
|
|
}
|
|
else if (src->sa_family == AF_INET)
|
|
{
|
|
/* addresses are equal, copy the port number
|
|
* so UDP will work */
|
|
((struct sockaddr_in *)addr)->sin_port =
|
|
((struct sockaddr_in *)src)->sin_port;
|
|
/* TODO: IPv6 UDP here */
|
|
}
|
|
}
|
|
|
|
/* address already may already exists in peer list */
|
|
peer_list = &hip_a->peer_hi->addrs;
|
|
l = add_address_to_list(&peer_list, addr, 0);
|
|
if (!l)
|
|
{
|
|
log_(WARN, "Unable to add new address (%s) to " \
|
|
"peer list.\n", logaddr(addr));
|
|
continue;
|
|
}
|
|
else if (preferred && (l == peer_list))
|
|
{
|
|
/* not a new preferred address */
|
|
log_(NORM, "Preferred address %s for SPI=0x%x remains "
|
|
"the same.\n", logaddr(addr), spi);
|
|
continue;
|
|
}
|
|
l->status = UNVERIFIED;
|
|
l->lifetime = ntohl(loc->locator_lifetime);
|
|
l->creation_time.tv_sec = now.tv_sec; /* update creation time */
|
|
l->creation_time.tv_usec = now.tv_usec;
|
|
log_(NORM, "New %saddress %s for SPI=0x%x\n",
|
|
(preferred) ? "preferred " : "", logaddr(addr), spi);
|
|
/* handle new preferred address */
|
|
if (preferred && (l != peer_list))
|
|
{
|
|
l->preferred = TRUE; /* this flags a readdress */
|
|
}
|
|
else if (!preferred)
|
|
{
|
|
l->preferred = FALSE;
|
|
}
|
|
}
|
|
|
|
/* mark all unlisted addresses for this peer as deprecated */
|
|
for (l = &hip_a->peer_hi->addrs; l; l = l->next)
|
|
{
|
|
if (l->creation_time.tv_sec != now.tv_sec)
|
|
{
|
|
l->status = DEPRECATED;
|
|
}
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* complete_base_exchange()
|
|
*
|
|
* in: hip_a = association containing all the necessary data
|
|
*
|
|
* Build incoming and outgoing SAs and display a message about
|
|
* completing a successful base exchange.
|
|
*/
|
|
int complete_base_exchange(hip_assoc *hip_a)
|
|
{
|
|
int err = 0;
|
|
struct sockaddr_storage src_hit, dst_hit;
|
|
|
|
log_(NORM, "---------- HIP exchange complete. ----------\n");
|
|
log_sa_info(hip_a);
|
|
|
|
hit_to_sockaddr(SA(&src_hit), hip_a->hi->hit);
|
|
hit_to_sockaddr(SA(&dst_hit), hip_a->peer_hi->hit);
|
|
|
|
if (hip_sadb_add(hip_a->udp ? 3 : 0, 2,
|
|
SA(&src_hit), SA(&dst_hit),
|
|
HIPA_SRC(hip_a), HIPA_DST(hip_a),
|
|
HIPA_SRC_LSI(hip_a), HIPA_DST_LSI(hip_a),
|
|
hip_a->spi_out, hip_a->spi_nat,
|
|
get_key(hip_a, ESP_ENCRYPTION, 0),
|
|
transform_to_ealg(hip_a->esp_transform),
|
|
enc_key_len(hip_a->esp_transform),
|
|
get_key(hip_a, ESP_AUTH, 0),
|
|
transform_to_aalg(hip_a->esp_transform),
|
|
auth_key_len(hip_a->esp_transform),
|
|
HCNF.sa_lifetime)
|
|
< 0)
|
|
{
|
|
err = -1;
|
|
log_(WARN, "Error building outgoing SA: %s.\n",
|
|
strerror(errno));
|
|
}
|
|
if (hip_sadb_add(hip_a->udp ? 3 : 0, 1,
|
|
SA(&dst_hit), SA(&src_hit),
|
|
HIPA_DST(hip_a), HIPA_SRC(hip_a),
|
|
HIPA_DST_LSI(hip_a), HIPA_SRC_LSI(hip_a),
|
|
hip_a->spi_in, hip_a->spi_nat,
|
|
get_key(hip_a, ESP_ENCRYPTION, 1),
|
|
transform_to_ealg(hip_a->esp_transform),
|
|
enc_key_len(hip_a->esp_transform),
|
|
get_key(hip_a, ESP_AUTH, 1),
|
|
transform_to_aalg(hip_a->esp_transform),
|
|
auth_key_len(hip_a->esp_transform),
|
|
HCNF.sa_lifetime)
|
|
< 0)
|
|
{
|
|
err = -2;
|
|
log_(WARN, "Error building incoming SA: %s.\n",
|
|
strerror(errno));
|
|
}
|
|
|
|
if (!err)
|
|
{
|
|
log_hipa_fromto(QOUT, "Base exchange completed",
|
|
hip_a, TRUE, TRUE);
|
|
}
|
|
return(err);
|
|
}
|
|
|
|
/*
|
|
* rebuild_sa()
|
|
*
|
|
* in: hip_a = association containing old addresses, SPIs, keys
|
|
* newaddr = the new address, or NULL if rekey only
|
|
* newspi = the new SPI, or zero if readdress only
|
|
* in = TRUE for incoming, FALSE for outgoing
|
|
* peer = TRUE if the new address is the peer's, FALSE if we have
|
|
* changed addresses
|
|
*
|
|
* A single function to take care of rebuilding an SA and its SPD entry.
|
|
* Handles both readdress and rekey cases.
|
|
*/
|
|
int rebuild_sa(hip_assoc *hip_a, struct sockaddr *newaddr, __u32 newspi,
|
|
int in, int peer)
|
|
{
|
|
__u32 spi;
|
|
int err = 0, direction;
|
|
struct sockaddr_storage src_hit_s, dst_hit_s;
|
|
struct sockaddr *src_new, *dst_new, *src_old, *dst_old;
|
|
struct sockaddr *src_hit = SA(&src_hit_s), *dst_hit = SA(&dst_hit_s);
|
|
struct sockaddr *src_lsi, *dst_lsi;
|
|
|
|
if (in) /* incoming */
|
|
{
|
|
direction = 1;
|
|
spi = hip_a->spi_in;
|
|
hit_to_sockaddr(src_hit, hip_a->peer_hi->hit);
|
|
hit_to_sockaddr(dst_hit, hip_a->hi->hit);
|
|
src_old = HIPA_DST(hip_a);
|
|
dst_old = HIPA_SRC(hip_a);
|
|
src_lsi = HIPA_DST_LSI(hip_a);
|
|
dst_lsi = HIPA_SRC_LSI(hip_a);
|
|
}
|
|
else /* outgoing */
|
|
{
|
|
direction = 2;
|
|
spi = hip_a->spi_out;
|
|
hit_to_sockaddr(src_hit, hip_a->hi->hit);
|
|
hit_to_sockaddr(dst_hit, hip_a->peer_hi->hit);
|
|
src_old = HIPA_SRC(hip_a);
|
|
dst_old = HIPA_DST(hip_a);
|
|
src_lsi = HIPA_SRC_LSI(hip_a);
|
|
dst_lsi = HIPA_DST_LSI(hip_a);
|
|
}
|
|
if (newaddr && (peer == in))
|
|
{
|
|
src_new = newaddr;
|
|
dst_new = dst_old;
|
|
}
|
|
else if (newaddr)
|
|
{
|
|
src_new = src_old;
|
|
dst_new = newaddr;
|
|
}
|
|
else /* no change in address */
|
|
{
|
|
src_new = src_old;
|
|
dst_new = dst_old;
|
|
}
|
|
|
|
if (hip_sadb_delete(spi) < 0)
|
|
{
|
|
log_(WARN, "Error removing old SA: %s\n",
|
|
strerror(errno));
|
|
err--;
|
|
}
|
|
|
|
/* new SPI is used if it is nonzero */
|
|
if (hip_sadb_add(hip_a->udp ? 3 : 0, direction,
|
|
src_hit, dst_hit, src_new, dst_new, src_lsi, dst_lsi,
|
|
(newspi > 0) ? newspi : spi,
|
|
hip_a->spi_nat,
|
|
get_key(hip_a, ESP_ENCRYPTION, in),
|
|
transform_to_ealg(hip_a->esp_transform),
|
|
enc_key_len(hip_a->esp_transform),
|
|
get_key(hip_a, ESP_AUTH, in),
|
|
transform_to_aalg(hip_a->esp_transform),
|
|
auth_key_len(hip_a->esp_transform),
|
|
HCNF.sa_lifetime)
|
|
< 0)
|
|
{
|
|
err = -1;
|
|
log_(WARN, "Error building new SA: %s.\n",
|
|
strerror(errno));
|
|
}
|
|
|
|
hip_a->used_bytes_in = 0;
|
|
hip_a->used_bytes_out = 0;
|
|
return(err);
|
|
}
|
|
|
|
/*
|
|
* rebuild_sa_x2()
|
|
*
|
|
* in: hip_a = association containing old addresses, SPIs, keys
|
|
* src_new = the new source address
|
|
* dst_new = the new destination address
|
|
* newspi = the new SPI, or zero if readdress only
|
|
* in = TRUE for incoming, FALSE for outgoing
|
|
*
|
|
* This function takes care of rebuilding an SA
|
|
* when readdress involves a change of address family.
|
|
*/
|
|
int rebuild_sa_x2(hip_assoc *hip_a, struct sockaddr *src_new,
|
|
struct sockaddr *dst_new, __u32 newspi, int in)
|
|
{
|
|
__u32 spi;
|
|
int err = 0, direction;
|
|
struct sockaddr_storage src_hit_s, dst_hit_s;
|
|
struct sockaddr *src_hit = SA(&src_hit_s), *dst_hit = SA(&dst_hit_s);
|
|
struct sockaddr *src_lsi, *dst_lsi;
|
|
|
|
if (in) /* incoming */
|
|
{
|
|
direction = 1;
|
|
spi = hip_a->spi_in;
|
|
hit_to_sockaddr(src_hit, hip_a->peer_hi->hit);
|
|
hit_to_sockaddr(dst_hit, hip_a->hi->hit);
|
|
src_lsi = HIPA_DST_LSI(hip_a);
|
|
dst_lsi = HIPA_SRC_LSI(hip_a);
|
|
}
|
|
else /* outgoing */
|
|
{
|
|
direction = 2;
|
|
spi = hip_a->spi_out;
|
|
hit_to_sockaddr(src_hit, hip_a->hi->hit);
|
|
hit_to_sockaddr(dst_hit, hip_a->peer_hi->hit);
|
|
src_lsi = HIPA_SRC_LSI(hip_a);
|
|
dst_lsi = HIPA_DST_LSI(hip_a);
|
|
}
|
|
|
|
if (hip_sadb_delete(spi) < 0)
|
|
{
|
|
log_(WARN, "Error removing old outgoing SA: %s\n",
|
|
strerror(errno));
|
|
err--;
|
|
}
|
|
|
|
if (hip_sadb_add(hip_a->udp ? 3 : 0, direction,
|
|
src_hit, dst_hit, src_new, dst_new,
|
|
src_lsi, dst_lsi,
|
|
(newspi > 0) ? newspi : spi,
|
|
hip_a->spi_nat,
|
|
get_key(hip_a, ESP_ENCRYPTION, in),
|
|
transform_to_ealg(hip_a->esp_transform),
|
|
enc_key_len(hip_a->esp_transform),
|
|
get_key(hip_a, ESP_AUTH, in),
|
|
transform_to_aalg(hip_a->esp_transform),
|
|
auth_key_len(hip_a->esp_transform),
|
|
HCNF.sa_lifetime)
|
|
< 0)
|
|
{
|
|
log_(WARN, "Error building new outgoing SA: %s\n",
|
|
strerror(errno));
|
|
err--;
|
|
}
|
|
|
|
hip_a->used_bytes_in = 0;
|
|
hip_a->used_bytes_out = 0;
|
|
return(err);
|
|
}
|
|
|
|
void update_peer_list(hip_assoc *hip_a)
|
|
{
|
|
hi_node *peer, *tmp, *prev;
|
|
__u32 lsi_hit;
|
|
|
|
/* copy attributes learned from peer's HI back into peer_hi_head */
|
|
peer = find_host_identity(peer_hi_head, hip_a->peer_hi->hit);
|
|
if (VALID_FAM(&peer->lsi) && !VALID_FAM(&hip_a->peer_hi->lsi))
|
|
{
|
|
memcpy(&hip_a->peer_hi->lsi, &peer->lsi, SALEN(&peer->lsi));
|
|
}
|
|
if (peer->size == 0)
|
|
{
|
|
peer->size = hip_a->peer_hi->size;
|
|
}
|
|
if (peer->algorithm_id == 0)
|
|
{
|
|
peer->algorithm_id = hip_a->peer_hi->algorithm_id;
|
|
peer->anonymous = hip_a->peer_hi->anonymous;
|
|
peer->allow_incoming = hip_a->peer_hi->allow_incoming;
|
|
/* could copy public key (RSA, DSA) if desired */
|
|
}
|
|
if ((strlen(peer->name) == 0) && (strlen(hip_a->peer_hi->name) > 0))
|
|
{
|
|
strncpy(peer->name, hip_a->peer_hi->name, sizeof(peer->name));
|
|
peer->name_len = hip_a->peer_hi->name_len;
|
|
}
|
|
|
|
if (VALID_FAM(&hip_a->peer_hi->lsi))
|
|
{
|
|
return;
|
|
}
|
|
/* need to fill in LSI */
|
|
prev = NULL;
|
|
for (tmp = peer_hi_head; tmp; prev = tmp, tmp = tmp->next)
|
|
{
|
|
/* search for LSI-only entry bearing the same name */
|
|
if (strcmp(tmp->name, hip_a->peer_hi->name) == 0)
|
|
{
|
|
if (!VALID_FAM(&tmp->lsi))
|
|
{
|
|
continue;
|
|
}
|
|
log_(NORM, "Found LSI %s for this association.\n",
|
|
logaddr(SA(&tmp->lsi)));
|
|
/* fill-in LSI for hip_assoc */
|
|
memcpy( &hip_a->peer_hi->lsi, &tmp->lsi,
|
|
SALEN(&tmp->lsi));
|
|
/* fill-in LSI for other peer_hi_head entry
|
|
* that has no LSI */
|
|
memcpy( &peer->lsi, &tmp->lsi, SALEN(&tmp->lsi));
|
|
/* phantom entry is no longer needed */
|
|
if (hits_equal(tmp->hit, zero_hit))
|
|
{
|
|
if (tmp == peer_hi_head)
|
|
{
|
|
peer_hi_head = tmp->next;
|
|
}
|
|
else
|
|
{
|
|
prev->next = tmp->next;
|
|
}
|
|
free(tmp);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!VALID_FAM(&hip_a->peer_hi->lsi))
|
|
{
|
|
log_(WARN, "Searched for corresponding LSI but none found.\n");
|
|
lsi_hit = htonl(HIT2LSI(hip_a->peer_hi->hit));
|
|
hip_a->peer_hi->lsi.ss_family = AF_INET;
|
|
memcpy(SA2IP(&hip_a->peer_hi->lsi), &lsi_hit, sizeof(__u32));
|
|
log_(NORM, "Falling back to HIT-based LSI: %s\n",
|
|
logaddr(SA(&hip_a->peer_hi->lsi)));
|
|
memcpy(&peer->lsi, &hip_a->peer_hi->lsi,
|
|
SALEN(&hip_a->peer_hi->lsi));
|
|
}
|
|
|
|
}
|
|
|
|
void log_sa_info(hip_assoc *hip_a)
|
|
{
|
|
log_(NORMT, "Adding security association:\n\tsrc ip = %s",
|
|
logaddr(HIPA_SRC(hip_a)));
|
|
log_(NORM, " dst ip = %s\n\tSPIs in = 0x%x out = 0x%x\n",
|
|
logaddr(HIPA_DST(hip_a)), hip_a->spi_in, hip_a->spi_out);
|
|
}
|
|
|
|
/*
|
|
* returns -1 if there is a problem with the type or length field
|
|
* also performs some logging
|
|
*/
|
|
int check_tlv_type_length(int type, int length, int last_type, char *p)
|
|
{
|
|
log_(NORM, " %s TLV type = %d length = %d \n", p, type, length);
|
|
|
|
/* TLV type strictly defines the order, except for types 2048-4095 */
|
|
/* XXX this should only apply if both lastype and type are within
|
|
* the range 2048-4095
|
|
*/
|
|
if ((last_type > type) &&
|
|
(type >= PARAM_TRANSFORM_LOW) &&
|
|
(type <= PARAM_TRANSFORM_HIGH))
|
|
{
|
|
log_(WARN, "Out of order TLV parameter, (%d > %d) ",
|
|
last_type, type);
|
|
log_(NORM, "malformed %s packet.\n", p);
|
|
return(-1);
|
|
}
|
|
if (!check_tlv_length(type, length))
|
|
{
|
|
log_(WARN, "TLV parameter %d has invalid length %d ",
|
|
type, length);
|
|
log_(NORM, "malformed %s packet.\n", p);
|
|
return(-1);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* returns false if parameter has invalid length
|
|
*/
|
|
int check_tlv_length(int type, int length)
|
|
{
|
|
if ((length < 0) || (length == 0))
|
|
{
|
|
return(FALSE);
|
|
}
|
|
|
|
/* some parameters have fixed lengths, enforce them */
|
|
switch (type)
|
|
{
|
|
case PARAM_R1_COUNTER:
|
|
return(length == 12);
|
|
case PARAM_HMAC:
|
|
case PARAM_HMAC_2:
|
|
return(length == 64);
|
|
case PARAM_SEQ:
|
|
return(length == 4);
|
|
/* not checking variable length */
|
|
default:
|
|
return(TRUE);
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
/*
|
|
* check an unknown TLV for the critical bit, returning -1 if critical
|
|
* also performs some logging
|
|
*/
|
|
int check_tlv_unknown_critical(int type, int length)
|
|
{
|
|
log_(NORM,"Unknown TLV type %d, length %d.\n", type, length);
|
|
|
|
if ((type & PARAM_CRITICAL_BIT) && (!OPT.permissive))
|
|
{
|
|
log_(WARN, "Unknown TLV has critical bit set, ");
|
|
log_(NORM, "dropping packet.\n");
|
|
return(-1);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* handle_reg_info()
|
|
*
|
|
* Parse registration info received from a registrar in the R1 or UPDATE
|
|
* packets.
|
|
*/
|
|
int handle_reg_info(hip_assoc *hip_a, const __u8 *data)
|
|
{
|
|
tlv_reg_info *info = (tlv_reg_info *) data;
|
|
int length, i, num_regs;
|
|
__u8 *reg_types, lifetime;
|
|
char str[128];
|
|
|
|
if (ntohs(info->type) != PARAM_REG_INFO)
|
|
{
|
|
return(-1);
|
|
}
|
|
length = ntohs(info->length);
|
|
num_regs = length - 2;
|
|
reg_types = &(info->reg_type);
|
|
|
|
if (!hip_a->regs)
|
|
{
|
|
hip_a->regs = (struct reg_entry *)
|
|
malloc(sizeof(struct reg_entry));
|
|
if (!hip_a->regs)
|
|
{
|
|
return(-1);
|
|
}
|
|
memset(hip_a->regs, 0, sizeof(struct reg_entry));
|
|
hip_a->regs->reginfos = NULL;
|
|
hip_a->regs->number = 0;
|
|
}
|
|
hip_a->regs->min_lifetime = info->min_lifetime;
|
|
hip_a->regs->max_lifetime = info->max_lifetime;
|
|
lifetime = info->max_lifetime; /* request the max lifetime */
|
|
|
|
for (i = 0; i < num_regs; i++)
|
|
{
|
|
if (regtype_to_string(reg_types[i], str, sizeof(str)) < 0)
|
|
{
|
|
log_(NORM, "Skipping registration type %d: %s\n",
|
|
reg_types[i], str);
|
|
continue;
|
|
}
|
|
log_(NORM,"Registration type %d offered: %s\n",
|
|
reg_types[i], str);
|
|
add_reg_info(hip_a->regs, reg_types[i], REG_OFFERED, lifetime);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* handle_reg_request()
|
|
*
|
|
* As a registrar, handle requests to register from the I2 or UPDATE packets.
|
|
*/
|
|
int handle_reg_request(hip_assoc *hip_a, const __u8 *data)
|
|
{
|
|
tlv_reg_request *req = (tlv_reg_request *)data;
|
|
int i, num_regs, length, state;
|
|
__u8 *reg_types, lifetime;
|
|
char str[128];
|
|
|
|
if (ntohs(req->type) != PARAM_REG_REQUEST)
|
|
{
|
|
return(-1);
|
|
}
|
|
length = ntohs(req->length);
|
|
lifetime = req->lifetime;
|
|
num_regs = length - 1; /* lifetime occupies first byte */
|
|
reg_types = &(req->reg_type);
|
|
|
|
/* process canceled registrations here */
|
|
if (lifetime == 0)
|
|
{
|
|
log_(NORM,"Request to cancel registration(s).\n");
|
|
if (!hip_a->regs)
|
|
{
|
|
log_(WARN, "No registrations exist with this peer.\n");
|
|
return(-1);
|
|
}
|
|
for (i = 0; i < num_regs; i++)
|
|
{
|
|
regtype_to_string(reg_types[i], str, sizeof(str));
|
|
log_(NORM,"Registration type %d canceled: %s\n",
|
|
reg_types[i], str);
|
|
add_reg_info(hip_a->regs, reg_types[i],
|
|
REG_SEND_CANCELLED, 0);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/* prepare reg_entry structure */
|
|
if (hip_a->regs)
|
|
{
|
|
log_(WARN, "Already have pending registration request(s), "
|
|
"ignoring new registration request(s).\n");
|
|
return(-1);
|
|
}
|
|
hip_a->regs = (struct reg_entry *) malloc(sizeof(struct reg_entry));
|
|
if (!hip_a->regs)
|
|
{
|
|
return(-1);
|
|
}
|
|
hip_a->regs->reginfos = NULL;
|
|
hip_a->regs->number = 0;
|
|
/* as registrar, we enforce min/max lifetimes specified
|
|
* in the conf file */
|
|
if (lifetime < HCNF.min_reg_lifetime)
|
|
{
|
|
lifetime = HCNF.min_reg_lifetime;
|
|
}
|
|
else if (lifetime > HCNF.max_reg_lifetime)
|
|
{
|
|
lifetime = HCNF.max_reg_lifetime;
|
|
}
|
|
|
|
for (i = 0; i < num_regs; i++)
|
|
{
|
|
regtype_to_string(reg_types[i], str, sizeof(str));
|
|
log_(NORM,"Registration type %d requested: %s\n",
|
|
reg_types[i], str);
|
|
|
|
state = REG_SEND_FAILED;
|
|
if ((reg_types[i] == REGTYPE_RVS) && OPT.rvs)
|
|
{
|
|
state = REG_SEND_RESP;
|
|
log_(NORM, "Registration with Rendezvous Service "
|
|
"accepted.\n");
|
|
}
|
|
#ifndef __WIN32__
|
|
else if ((reg_types[i] == REGTYPE_MR) && OPT.mr)
|
|
{
|
|
if (init_hip_mr_client( hip_a->peer_hi->hit,
|
|
HIPA_DST(hip_a)) < 0)
|
|
{
|
|
log_(WARN,"Error initializing mobile router "
|
|
"client\n");
|
|
}
|
|
log_(NORM, "Registration with Mobile Router Service "
|
|
"accepted.\n");
|
|
state = REG_SEND_RESP;
|
|
}
|
|
#endif /* !__WIN32__ */
|
|
else /* Unknown or unsupported type */
|
|
{
|
|
state = REG_SEND_FAILED;
|
|
}
|
|
|
|
add_reg_info(hip_a->regs, reg_types[i], state, lifetime);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* handle_reg_response()
|
|
*
|
|
* Parse the registration response from the registrar in the R2 or
|
|
* UPDATE packets.
|
|
*/
|
|
int handle_reg_response(hip_assoc *hip_a, const __u8 *data)
|
|
{
|
|
int i, length, num_regs;
|
|
tlv_reg_response *resp = (tlv_reg_response *)data;
|
|
__u8 *reg_types = &(resp->reg_type);
|
|
char str[128];
|
|
|
|
if (ntohs(resp->type) != PARAM_REG_RESPONSE)
|
|
{
|
|
return(-1);
|
|
}
|
|
length = ntohs(resp->length);
|
|
num_regs = length - 1;
|
|
|
|
for (i = 0; i < num_regs; i++)
|
|
{
|
|
if (regtype_to_string(reg_types[i], str, sizeof(str)) < 0)
|
|
{
|
|
log_(NORM, "Skipping unknown registration type %d: "
|
|
"%s\n", reg_types[i], str);
|
|
continue;
|
|
}
|
|
if (resp->lifetime == 0)
|
|
{
|
|
log_(NORM, "Registration type %d %s canceled.\n",
|
|
reg_types[i], str);
|
|
if (delete_reg_info(hip_a->regs, reg_types[i]) < 0)
|
|
{
|
|
log_(NORM, "Registration not found.\n");
|
|
}
|
|
else
|
|
{
|
|
log_(NORM, "Registration removed OK.\n");
|
|
}
|
|
continue;
|
|
}
|
|
log_(NORM,"Registration type %d %s succeeded with "
|
|
"lifetime %d.\n", reg_types[i], str, resp->lifetime);
|
|
|
|
add_reg_info(hip_a->regs, reg_types[i], REG_GRANTED,
|
|
resp->lifetime);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* handle_reg_failed()
|
|
*
|
|
* Parse the registration failed response from the registrar.
|
|
*/
|
|
int handle_reg_failed(hip_assoc *hip_a, const __u8 *data)
|
|
{
|
|
tlv_reg_failed *fail = (tlv_reg_failed *)data;
|
|
int i, length, num_regs;
|
|
__u8 *reg_types = &(fail->reg_type);
|
|
struct reg_info *reg;
|
|
char str[128];
|
|
|
|
length = ntohs(fail->length);
|
|
num_regs = length - 1;
|
|
|
|
for (i = 0; i < num_regs; i++)
|
|
{
|
|
regtype_to_string(reg_types[i], str, sizeof(str));
|
|
for (reg = hip_a->regs->reginfos; reg; reg = reg->next)
|
|
{
|
|
if (reg->type == reg_types[i])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (!reg)
|
|
{
|
|
log_(NORM,
|
|
"Registration type %d %s failed with code %d"
|
|
" and there is no registration state.\n",
|
|
reg_types[i],
|
|
str,
|
|
fail->fail_type);
|
|
continue;
|
|
}
|
|
log_(NORM, "Registration type %d %s failed with failure "
|
|
"code %d.\n", reg_types[i], str, fail->fail_type);
|
|
reg->state = REG_FAILED;
|
|
reg->failure_code = fail->fail_type;
|
|
gettimeofday(®->state_time, NULL);
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* add_reg_info()
|
|
*
|
|
* Add or update a reg_info structure to the given reg_entry.
|
|
*/
|
|
int add_reg_info(struct reg_entry *regs, __u8 type, int state, __u8 lifetime)
|
|
{
|
|
struct reg_info *reg;
|
|
|
|
if (!regs)
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
/* search for existing registration */
|
|
for (reg = regs->reginfos; reg; reg = reg->next)
|
|
{
|
|
if (type == reg->type)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* allocate new reg_info if it doesn't already exist */
|
|
if (!reg)
|
|
{
|
|
reg = (struct reg_info*) malloc(sizeof(struct reg_info));
|
|
if (reg == NULL)
|
|
{
|
|
return(-1);
|
|
}
|
|
memset(reg, 0, sizeof(struct reg_info));
|
|
reg->type = type;
|
|
reg->next = regs->reginfos; /* link it into the list */
|
|
regs->reginfos = reg;
|
|
regs->number++;
|
|
}
|
|
reg->state = state;
|
|
reg->lifetime = lifetime;
|
|
gettimeofday(®->state_time, NULL);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Remove a reg_info structure from the given reg_entry.
|
|
*/
|
|
int delete_reg_info(struct reg_entry *regs, __u8 type)
|
|
{
|
|
struct reg_info *reg, *prev = NULL;
|
|
|
|
if (!regs)
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
/* search for existing registration */
|
|
for (reg = regs->reginfos; reg; reg = reg->next)
|
|
{
|
|
if (type == reg->type)
|
|
{
|
|
break;
|
|
}
|
|
prev = reg;
|
|
}
|
|
|
|
if (!reg)
|
|
{
|
|
return(-1);
|
|
}
|
|
if (!prev)
|
|
{
|
|
regs->reginfos = reg->next;
|
|
}
|
|
else
|
|
{
|
|
prev->next = reg->next;
|
|
}
|
|
memset(reg, 0, sizeof(struct reg_info));
|
|
free(reg);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Add the from_via structure to a HIP association. This takes the form of the
|
|
* FROM or VIA RVS TLVs and contains the address given in sockaddr or byte
|
|
* string format. This is used for input functions to signal adding the FROM
|
|
* or VIA RVS rendevous parameters on I1 or R1 output.
|
|
*/
|
|
int add_from_via(hip_assoc *hip_a, __u16 type, struct sockaddr *addr,
|
|
__u8* address)
|
|
{
|
|
if (!addr && !address) /* must specify either type of address */
|
|
{
|
|
return(-1);
|
|
}
|
|
if (!hip_a->from_via)
|
|
{
|
|
hip_a->from_via = malloc(eight_byte_align(sizeof(tlv_from)));
|
|
}
|
|
if (!hip_a->from_via)
|
|
{
|
|
return(-1); /* malloc error */
|
|
}
|
|
memset(hip_a->from_via, 0, eight_byte_align(sizeof(tlv_from)));
|
|
hip_a->from_via->type = htons(type);
|
|
hip_a->from_via->length = htons(sizeof(tlv_from) - 4);
|
|
if (addr && (addr->sa_family == AF_INET6))
|
|
{
|
|
memcpy(hip_a->from_via->address, SA2IP(addr), SAIPLEN(addr));
|
|
}
|
|
else if (addr && (addr->sa_family == AF_INET))
|
|
{
|
|
/* IPv4-in-IPv6 address format */
|
|
memset(&hip_a->from_via->address[10], 0xFF, 2);
|
|
memcpy(&hip_a->from_via->address[12], SA2IP(addr),
|
|
SAIPLEN(addr));
|
|
}
|
|
else if (address)
|
|
{
|
|
memcpy(hip_a->from_via->address, address,
|
|
sizeof(hip_a->from_via->address));
|
|
}
|
|
return(0);
|
|
}
|
|
|
|
int handle_dh_groups(__u8 *dh_group_ids, int length, bool is_responder){
|
|
__u8 *dh_group_list;
|
|
__u8 dh_group_id;
|
|
__u16 available_group_id;
|
|
|
|
|
|
if(is_responder){
|
|
dh_group_list = HCNF.dh_group_list;
|
|
available_group_id = conf_dh_group_ids_to_mask(dh_group_ids,length);
|
|
length = DH_MAX-1;
|
|
} else{
|
|
|
|
dh_group_list = dh_group_ids;
|
|
available_group_id = conf_dh_group_ids_to_mask(HCNF.dh_group_list, DH_MAX-1);
|
|
}
|
|
|
|
HCNF.dh_group = 0;
|
|
if (length >= DH_MAX)
|
|
{
|
|
log_(WARN, "Warning: There are %d dh group ids present but the "
|
|
"maximum number is %d.\n",
|
|
length, DH_MAX - 1);
|
|
/* continue to read the group ids... */
|
|
}
|
|
|
|
for (int i = 0; (i < length); dh_group_list++,
|
|
i++)
|
|
{
|
|
dh_group_id = *dh_group_list;
|
|
if ((dh_group_id <= DH_RESERVED) ||
|
|
(dh_group_id >= DH_MAX))
|
|
{
|
|
log_(WARN, "Ignoring invalid DH groups (%d).\n",
|
|
dh_group_id);
|
|
continue;
|
|
}
|
|
if ((available_group_id >> dh_group_id) & 0x1)
|
|
{
|
|
HCNF.dh_group = dh_group_id;
|
|
break;
|
|
}
|
|
}
|
|
if (HCNF.dh_group == 0)
|
|
{
|
|
log_(
|
|
WARN,
|
|
"Couldn't find a suitable DH group. This error could indicate that hip.conf was not successfully loaded.\n");
|
|
return(-1);
|
|
}
|
|
return(0);
|
|
}
|