647 lines
19 KiB
C
647 lines
19 KiB
C
|
|
/*
|
|
******************************************************************************
|
|
* INTERPEAK SOURCE FILE
|
|
******************************************************************************
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2007 Wind River Systems Inc.
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 1 DESCRIPTION
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 2 CONFIGURATION
|
|
****************************************************************************
|
|
*/
|
|
|
|
#include "ipdhcpc6_config.h"
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 3 INCLUDE FILES
|
|
****************************************************************************
|
|
*/
|
|
|
|
#define IPCOM_USE_CLIB_PROTO
|
|
#include <ipcom_clib.h>
|
|
#include <ipcom_err.h>
|
|
#include <ipcom_hash.h>
|
|
#include <ipcom_list.h>
|
|
#include <ipcom_sock.h>
|
|
#include <ipcom_sock6.h>
|
|
#include <ipcom_syslog.h>
|
|
#include <ipcom_time.h>
|
|
#include <ipcom_tmo2.h>
|
|
|
|
#include "ipdhcpc6.h"
|
|
#include "ipdhcpc6_h.h"
|
|
#include "ipdhcpc6_msg.h"
|
|
#include "ipdhcpc6_if.h"
|
|
#include "ipdhcpc6_info.h"
|
|
#include "ipdhcpc6_ia.h"
|
|
#include "ipdhcpc6_sock.h"
|
|
#include "ipdhcpc6_msg.h"
|
|
#include "ipdhcpc6_duid.h"
|
|
#include "ipdhcpc6_authhash.h"
|
|
#include "ipdhcpc6_auth.h"
|
|
|
|
#ifdef OPENSSL_FIPS
|
|
#include <openssl/fips.h>
|
|
#endif
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 4 DEFINES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 5 TYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 6 EXTERN PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 7 LOCAL PROTOTYPES
|
|
****************************************************************************
|
|
*/
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 8 DATA
|
|
****************************************************************************
|
|
*/
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 9 STATIC FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
/* IPCOM crypto */
|
|
#ifdef IPMCRYPTO
|
|
#include <ip/openssl/md5.h>
|
|
#else
|
|
#include <openssl/md5.h>
|
|
#endif
|
|
|
|
/*
|
|
*===========================================================================
|
|
* ipdhcpc6_hmac_md5
|
|
*===========================================================================
|
|
* Description: Create a md5 hmac of key and data
|
|
* Parameters:
|
|
* Returns:
|
|
*
|
|
*/
|
|
IP_GLOBAL void
|
|
ipdhcpc6_hmac_md5(Ip_u8 *key, Ip_u32 key_len, Ip_u8 *data, Ip_u32 len, Ip_u8 *mac)
|
|
{
|
|
MD5_CTX context;
|
|
Ip_u8 k_ipad[65];
|
|
Ip_u8 k_opad[65];
|
|
Ip_u8 tk[16];
|
|
int i;
|
|
int status;
|
|
|
|
#define IPDHCPC6_HMAC_ERR_OUT(log)\
|
|
{\
|
|
IPCOM_LOG1(WARNING, "ipdhcpc6: %s error\n",log);\
|
|
(void)ipcom_errno_set(IPCOM_ERR_AUTH_FAILED);\
|
|
return;\
|
|
}
|
|
|
|
#ifdef OPENSSL_FIPS
|
|
|
|
/* MD5 cannot be used */
|
|
if (FIPS_mode())
|
|
{
|
|
/* Authentication failed. */
|
|
IPCOM_LOG0(WARNING, "ipdhcpc6: MD5 is not supported when FIPS mode is enabled!");
|
|
(void)ipcom_errno_set(IPCOM_ERR_AUTH_FAILED);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Key longer than 64 bytes reset it to key = MD5(key) */
|
|
if (key_len > 64)
|
|
{
|
|
/* MD5_XXX returns 0 when fails */
|
|
status = MD5_Init(&context);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Init");
|
|
}
|
|
status = MD5_Update(&context, key, key_len);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Update");
|
|
}
|
|
status = MD5_Final(tk, &context);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Final");
|
|
}
|
|
|
|
key = tk;
|
|
key_len = 16;
|
|
}
|
|
|
|
/* Start storing key in pads */
|
|
ipcom_memset(k_ipad, 0, sizeof(k_ipad));
|
|
ipcom_memset(k_opad, 0, sizeof(k_opad));
|
|
ipcom_memcpy(k_ipad, key, key_len);
|
|
ipcom_memcpy(k_opad, key, key_len);
|
|
|
|
/* XOR key with ipad and opad values */
|
|
for (i = 0; i < 64; i++)
|
|
{
|
|
k_ipad[i] ^= 0x36;
|
|
k_opad[i] ^= 0x5c;
|
|
}
|
|
|
|
/* Do inner MD5 */
|
|
status = MD5_Init(&context);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Init");
|
|
}
|
|
status = MD5_Update(&context, k_ipad, 64);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Update");
|
|
}
|
|
status = MD5_Update(&context, data, len);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Update");
|
|
}
|
|
status = MD5_Final(mac, &context);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Final");
|
|
}
|
|
|
|
/* Do outer MD5 */
|
|
status = MD5_Init(&context);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Init");
|
|
}
|
|
status = MD5_Update(&context, k_opad, 64);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Update");
|
|
}
|
|
status = MD5_Update(&context, mac, 16);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Update");
|
|
}
|
|
status = MD5_Final(mac, &context);
|
|
if (0 == status)
|
|
{
|
|
IPDHCPC6_HMAC_ERR_OUT("MD5_Final");
|
|
}
|
|
return;
|
|
}
|
|
|
|
IP_STATIC Ip_u32 ipdhcpc6_replay_ctr_high = 0xdeadbeef;
|
|
IP_STATIC Ip_u32 ipdhcpc6_replay_ctr_low = 0x0;
|
|
|
|
IP_GLOBAL void ipdhcpc6_get_replay_counters (Ip_u32 *high, Ip_u32 *low)
|
|
{
|
|
*high = ipdhcpc6_replay_ctr_high;
|
|
*low = ipdhcpc6_replay_ctr_low;
|
|
}
|
|
|
|
IP_GLOBAL void ipdhcpc6_set_replay_counters (Ip_u32 high, Ip_u32 low)
|
|
{
|
|
ipdhcpc6_replay_ctr_high = high;
|
|
ipdhcpc6_replay_ctr_low = low;
|
|
}
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 10 GLOBAL FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
IP_GLOBAL Ip_bool
|
|
ipdhcps6_verify_auth (Ipdhcpc6_msg_transaction_t *trans,
|
|
Ipdhcpc6_msg_hdr_t *msg,
|
|
Ip_size_t length)
|
|
{
|
|
|
|
Ipdhcpc6_msg_option_hdr_t *auth_opt = IP_NULL;
|
|
Ip_size_t auth_opt_size = 0;
|
|
|
|
Ipdhcpc6_msg_option_hdr_t *serverid_opt = IP_NULL;
|
|
Ip_size_t serverid_opt_size = 0;
|
|
|
|
Ip_u8 hmac_digest [16];
|
|
|
|
Ip_u8 *auth_key;
|
|
int auth_key_len = 0;
|
|
|
|
union Ipdhcpc6_duid_data *server_duid;
|
|
|
|
Ip_u16 hmac_offset = 0;
|
|
Ip_u16 replay_offset = 0;
|
|
Ip_u32 replay1, replay2;
|
|
|
|
Ip_u8 *auth_realm;
|
|
int auth_realm_len = 0;
|
|
|
|
Ip_u8 *msg_buf = (Ip_u8 *) msg;
|
|
|
|
ipdhcpc6_get_auth_realm (&auth_realm_len, &auth_realm);
|
|
if (0 == auth_realm_len)
|
|
{ /* no auth realm set; thus no verification can be done */
|
|
return IP_TRUE;
|
|
}
|
|
|
|
|
|
serverid_opt = ipdhcpc6_msg_option_find(IPDHCPC6_MSG_OPTION_SERVERID,
|
|
&serverid_opt_size,
|
|
msg_buf + 4,
|
|
length - 4,
|
|
IP_NULL,
|
|
IP_NULL);
|
|
|
|
auth_opt = ipdhcpc6_msg_option_find(IPDHCPC6_MSG_OPTION_AUTH,
|
|
&auth_opt_size,
|
|
msg_buf + 4,
|
|
length - 4,
|
|
IP_NULL,
|
|
IP_NULL);
|
|
|
|
if (serverid_opt)
|
|
{
|
|
server_duid = (union Ipdhcpc6_duid_data *)
|
|
(((Ip_u8 *) serverid_opt) + 4);
|
|
/* 4 == sizeof dhcpv6 option hdr */
|
|
|
|
}
|
|
else
|
|
{ return IP_FALSE; /* no server DUID; reject packet */
|
|
}
|
|
|
|
if (auth_opt)
|
|
{ Ip_u8 *buffer = msg_buf;
|
|
Ip_u8 digest [16];
|
|
|
|
Ipdhcpc6_authtrans_dbentry *server_dbentry = IP_NULL;
|
|
|
|
if (IPCOM_SUCCESS != ipdhcpc6_get_default_auth_key (&auth_key_len,
|
|
&auth_key))
|
|
{ /* no default auth key set; thus no verification can be done */
|
|
return IP_FALSE;
|
|
}
|
|
|
|
/*
|
|
* if we have a server-spcific key, get it and use it.
|
|
*/
|
|
ipdhcpc6_find_key_for_server (server_duid, (int)serverid_opt_size,
|
|
&auth_key, &auth_key_len);
|
|
|
|
server_dbentry = ipdhcpc6_get_server_authtrans_entry (server_duid,
|
|
(int)serverid_opt_size);
|
|
|
|
if (IP_NULL == server_dbentry)
|
|
{
|
|
return IP_FALSE;
|
|
}
|
|
|
|
/*
|
|
* check and verify replay info from server
|
|
*/
|
|
|
|
replay_offset = (Ip_u16)((Ip_u8 *) auth_opt - (msg_buf));
|
|
replay_offset += (Ip_u16)7; /* option hdr len (4) + protocol + alg + rdm */
|
|
buffer = msg_buf + replay_offset;
|
|
|
|
ipcom_memcpy (&replay1, buffer, 4);
|
|
ipcom_memcpy (&replay2, buffer + 4, 4);
|
|
|
|
replay1 = ipcom_ntohl (replay1);
|
|
replay2 = ipcom_ntohl (replay2);
|
|
|
|
if (replay1 < server_dbentry->server_replay1)
|
|
{
|
|
return (IP_FALSE);
|
|
}
|
|
|
|
if ( replay1 == server_dbentry->server_replay1
|
|
&& replay2 < server_dbentry->server_replay2
|
|
)
|
|
{
|
|
return (IP_FALSE);
|
|
}
|
|
|
|
server_dbentry->server_replay1 = replay1;
|
|
server_dbentry->server_replay2 = replay2;
|
|
|
|
/*
|
|
* check and verify auth info, if needed
|
|
*/
|
|
|
|
hmac_offset = (Ip_u16)((Ip_u8 *) auth_opt - (msg_buf));
|
|
hmac_offset += (Ip_u16)((auth_opt_size + 4 /* dhcp opt hdr siz */) - 16);
|
|
|
|
buffer = msg_buf + hmac_offset;
|
|
|
|
/*
|
|
* save given digest
|
|
*/
|
|
|
|
ipcom_memcpy (digest, buffer, 16);
|
|
|
|
/*
|
|
* zero it
|
|
*/
|
|
|
|
ipcom_memset (buffer, 0, 16);
|
|
|
|
|
|
/* compute actual digest */
|
|
|
|
ipdhcpc6_hmac_md5(auth_key, auth_key_len,
|
|
msg_buf,
|
|
(Ip_u32)length,
|
|
hmac_digest);
|
|
|
|
|
|
/*
|
|
* compare it to given digest
|
|
*/
|
|
|
|
if (0 == ipcom_memcmp (hmac_digest, digest, 16))
|
|
{
|
|
return IP_TRUE;
|
|
}
|
|
else
|
|
{
|
|
return IP_FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
return IP_TRUE;
|
|
}
|
|
|
|
IP_GLOBAL Ip_err
|
|
ipdhcpc6_add_auth_header (Ipdhcpc6_msg_transaction_t *trans, Ip_u16 elapsed_time)
|
|
{
|
|
|
|
Ipdhcpc6_msg_option_hdr_t *serverid_opt = IP_NULL;
|
|
Ip_size_t serverid_opt_size = 0;
|
|
|
|
union Ipdhcpc6_duid_data *server_duid;
|
|
|
|
Ip_u8 *msg_buf = (Ip_u8 *) trans->buffer.head;
|
|
Ip_size_t length = trans->buffer.offset;
|
|
|
|
Ip_u8 *auth_realm = (Ip_u8 *) "";
|
|
int auth_realm_len = 0;
|
|
|
|
ipdhcpc6_get_auth_realm (&auth_realm_len, &auth_realm);
|
|
|
|
if (0 == auth_realm_len)
|
|
{ /* no auth realm set; thus no auth handling is wanted */
|
|
/* this case is not an error. */
|
|
return IPCOM_SUCCESS;
|
|
}
|
|
|
|
/* if SOLICIT, add initial stub auth header */
|
|
|
|
if (1 == (int) trans->buffer.head [0])
|
|
{
|
|
|
|
Ip_u8 auth_req_option [] =
|
|
{
|
|
0x0, 0xb, /* option AUTH */
|
|
0x0, 0xb, /* option length (also 11) */
|
|
0x2, 0x1, /* protocol 2, algorithm 1 */
|
|
0x0, /* replay detection; monotonic counter */
|
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
|
|
};
|
|
|
|
Ipdhcpc6_msg_option_hdr_t *auth_opt = IP_NULL;
|
|
Ip_size_t auth_opt_size = 0;
|
|
|
|
auth_opt = ipdhcpc6_msg_option_find(IPDHCPC6_MSG_OPTION_AUTH,
|
|
&auth_opt_size,
|
|
trans->buffer.head + 4,
|
|
trans->buffer.offset - 4,
|
|
IP_NULL,
|
|
IP_NULL);
|
|
|
|
if (auth_opt)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
ipcom_memcpy (trans->buffer.head + trans->buffer.offset,
|
|
auth_req_option, 15);
|
|
|
|
/* adjust buffer size */
|
|
trans->buffer.offset += 15;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Ip_u16 elapsed_time_opt[3];
|
|
|
|
Ipdhcpc6_authtrans_dbentry *server_dbentry = IP_NULL;
|
|
|
|
Ip_u8 auth_req_option_hdr [] =
|
|
{
|
|
0x0, 0xb, /* option AUTH */
|
|
0x0, 0x0, /* option len (will fill in later) */
|
|
0x2, 0x1, /* protocol 2, algorithm 1 */
|
|
0x0, /* replay detection; monotonic counter */
|
|
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
|
|
};
|
|
|
|
Ip_u8 auth_key_id [] = { 0x0, 0x0, 0x0, 0x1 }; /* key id 1 */
|
|
Ip_u8 hmac_digest [16];
|
|
|
|
Ip_u8 *auth_key = (Ip_u8 *) "";
|
|
int auth_key_len = 0;
|
|
|
|
Ip_u16 option_length;
|
|
Ip_u16 hmac_offset = 0;
|
|
|
|
Ipdhcpc6_msg_option_hdr_t *auth_opt = IP_NULL;
|
|
Ip_size_t auth_opt_size = 0;
|
|
|
|
|
|
serverid_opt = ipdhcpc6_msg_option_find(IPDHCPC6_MSG_OPTION_SERVERID,
|
|
&serverid_opt_size,
|
|
msg_buf + 4, /* dhcpv6 hdr size */
|
|
length - 4,
|
|
IP_NULL,
|
|
IP_NULL);
|
|
|
|
if (serverid_opt)
|
|
{
|
|
Ip_u8 *buf;
|
|
|
|
buf = (Ip_u8 *) serverid_opt;
|
|
|
|
server_duid = (union Ipdhcpc6_duid_data *)
|
|
(buf + 4);
|
|
/* 4 == sizeof dhcpv6 option hdr */
|
|
|
|
}
|
|
else
|
|
{ return IPCOM_ERR_FAILED; /* no server DUID; we cant do anything */
|
|
}
|
|
|
|
|
|
/* RFC 3315, chapter 22.9
|
|
All DHCPv6 client message must contain the Elapsed Time Option */
|
|
|
|
elapsed_time_opt[0] = ip_htons(IPDHCPC6_MSG_OPTION_ELAPSED_TIME);
|
|
elapsed_time_opt[1] = ip_htons(sizeof(elapsed_time));
|
|
elapsed_time_opt[2] = ip_htons(elapsed_time);
|
|
|
|
|
|
if (IPCOM_SUCCESS != ipdhcpc6_get_default_auth_key (&auth_key_len, &auth_key))
|
|
{
|
|
}
|
|
|
|
/*
|
|
* if we have a server-spcific key, get it and use it.
|
|
*/
|
|
ipdhcpc6_find_key_for_server (server_duid, (int)serverid_opt_size,
|
|
&auth_key, &auth_key_len);
|
|
|
|
server_dbentry = ipdhcpc6_get_server_authtrans_entry (server_duid,
|
|
(int)serverid_opt_size);
|
|
|
|
if (IP_NULL == server_dbentry)
|
|
{
|
|
return IP_FALSE;
|
|
}
|
|
|
|
auth_opt = ipdhcpc6_msg_option_find(IPDHCPC6_MSG_OPTION_AUTH,
|
|
&auth_opt_size,
|
|
trans->buffer.head + 4,
|
|
trans->buffer.offset - 4,
|
|
IP_NULL,
|
|
IP_NULL);
|
|
|
|
if (auth_opt)
|
|
{ /*
|
|
* this transaction packet already has an auth header.
|
|
* We'll just reuse it and recompute the digest section.
|
|
*/
|
|
hmac_offset = (Ip_u8 *) auth_opt - (trans->buffer.head);
|
|
hmac_offset += ((auth_opt_size + 4 /* dhcp option hdr siz */) - 16);
|
|
}
|
|
else
|
|
{
|
|
|
|
option_length = (Ip_u16)((sizeof (auth_req_option_hdr) - 4) +
|
|
auth_realm_len +
|
|
sizeof (auth_key_id) +
|
|
sizeof (hmac_digest));
|
|
|
|
ipcom_memset (hmac_digest, 0, 16);
|
|
|
|
/* fill in actual option length */
|
|
auth_req_option_hdr [2] = (Ip_u8) (option_length >> 8);
|
|
auth_req_option_hdr [3] = (Ip_u8) (option_length & 0xFF);
|
|
|
|
/* fill in replay counter */
|
|
ipdhcpc6_replay_ctr_low++;
|
|
if (0 == ipdhcpc6_replay_ctr_low++)
|
|
ipdhcpc6_replay_ctr_high++;
|
|
|
|
ipcom_memcpy (auth_req_option_hdr + 7, &ipdhcpc6_replay_ctr_high, 4);
|
|
ipcom_memcpy (auth_req_option_hdr + 11, &ipdhcpc6_replay_ctr_low, 4);
|
|
|
|
/* add auth option header */
|
|
ipdhcpc6_util_buffer_append(&(trans->buffer),
|
|
auth_req_option_hdr, sizeof (auth_req_option_hdr));
|
|
|
|
/* add auth option realm */
|
|
ipdhcpc6_util_buffer_append(&(trans->buffer),
|
|
auth_realm, auth_realm_len);
|
|
|
|
ipdhcpc6_util_buffer_append(&(trans->buffer),
|
|
auth_key_id, sizeof (auth_key_id));
|
|
|
|
/* save ptr to hmac digest */
|
|
hmac_offset = trans->buffer.offset;
|
|
|
|
ipdhcpc6_util_buffer_append(&(trans->buffer),
|
|
hmac_digest, sizeof (hmac_digest));
|
|
|
|
/*
|
|
* before we compute the digest, we append the
|
|
* same elapsed time info that ipdhcpc6_sock_send()
|
|
* is going to add to the packet.
|
|
*/
|
|
|
|
ipcom_memcpy (trans->buffer.head + trans->buffer.offset,
|
|
(Ip_u8 *) elapsed_time_opt, 6);
|
|
trans->buffer.offset += 6; /* size of elapsed time option */
|
|
|
|
/* compute actual digest */
|
|
|
|
ipdhcpc6_hmac_md5(auth_key, 14,
|
|
trans->buffer.head,
|
|
(int)trans->buffer.offset,
|
|
hmac_digest);
|
|
/* copy it in */
|
|
ipcom_memcpy (trans->buffer.head + hmac_offset, hmac_digest, 16);
|
|
|
|
/*
|
|
* now forget that we ever added the elapsed time info.
|
|
*/
|
|
trans->buffer.offset -= 6; /* size of elapsed time option */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return IPCOM_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
****************************************************************************
|
|
* 11 PUBLIC FUNCTIONS
|
|
****************************************************************************
|
|
*/
|
|
|
|
/*
|
|
****************************************************************************
|
|
* END OF FILE
|
|
****************************************************************************
|
|
*/
|
|
|
|
|