/* ****************************************************************************** * 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 #include #include #include #include #include #include #include #include #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 #endif /* **************************************************************************** * 4 DEFINES **************************************************************************** */ /* **************************************************************************** * 5 TYPES **************************************************************************** */ /* **************************************************************************** * 6 EXTERN PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 7 LOCAL PROTOTYPES **************************************************************************** */ /* **************************************************************************** * 8 DATA **************************************************************************** */ /* **************************************************************************** * 9 STATIC FUNCTIONS **************************************************************************** */ /* IPCOM crypto */ #ifdef IPMCRYPTO #include #else #include #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 **************************************************************************** */