diff options
Diffstat (limited to 'sbin/isakmpd/ipsec.c')
-rw-r--r-- | sbin/isakmpd/ipsec.c | 1067 |
1 files changed, 1067 insertions, 0 deletions
diff --git a/sbin/isakmpd/ipsec.c b/sbin/isakmpd/ipsec.c new file mode 100644 index 00000000000..f8fe5553b86 --- /dev/null +++ b/sbin/isakmpd/ipsec.c @@ -0,0 +1,1067 @@ +/* $Id: ipsec.c,v 1.1 1998/11/15 00:03:48 niklas Exp $ */ + +/* + * Copyright (c) 1998 Niklas Hallqvist. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Ericsson Radio Systems. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code was written under funding by Ericsson Radio Systems. + */ + +#include <sys/types.h> +#include <netinet/in.h> +#include <stdlib.h> +#include <string.h> + +#include "attribute.h" +#include "constants.h" +#include "crypto.h" +#include "dh.h" +#include "doi.h" +#include "exchange.h" +#include "hash.h" +#include "ike_auth.h" +#include "ike_main_mode.h" +#include "ike_quick_mode.h" +#include "ipsec.h" +#include "ipsec_doi.h" +#include "isakmp.h" +#include "log.h" +#include "math_group.h" +#include "message.h" +#include "prf.h" +#include "sa.h" +#include "sysdep.h" +#include "timer.h" +#include "transport.h" +#include "util.h" + +struct ipsec_decode_arg { + struct message *msg; + struct sa *sa; + struct proto *proto; +}; + +static int ipsec_debug_attribute (u_int16_t, u_int8_t *, u_int16_t, void *); +static void ipsec_delete_spi (struct sa *, struct proto *, int); +static u_int16_t *ipsec_exchange_script (u_int8_t); +static void ipsec_finalize_exchange (struct message *); +static void ipsec_free_exchange_data (void *); +static void ipsec_free_proto_data (void *); +static void ipsec_free_sa_data (void *); +static struct keystate *ipsec_get_keystate (struct message *); +static u_int8_t *ipsec_get_spi (size_t *, u_int8_t, struct message *); +static int ipsec_initiator (struct message *); +static int ipsec_responder (struct message *); +static void ipsec_setup_situation (u_int8_t *); +static size_t ipsec_situation_size (void); +static u_int8_t ipsec_spi_size (u_int8_t); +static int ipsec_validate_attribute (u_int16_t, u_int8_t *, u_int16_t, void *); +static int ipsec_validate_exchange (u_int8_t); +static int ipsec_validate_id_information (u_int8_t, u_int8_t *, u_int8_t *, + size_t, struct exchange *); +static int ipsec_validate_key_information (u_int8_t *, size_t); +static int ipsec_validate_notification (u_int16_t); +static int ipsec_validate_proto (u_int8_t); +static int ipsec_validate_situation (u_int8_t *, size_t *); +static int ipsec_validate_transform_id (u_int8_t, u_int8_t); + +static struct doi ipsec_doi = { + { 0 }, IPSEC_DOI_IPSEC, + sizeof (struct ipsec_exch), sizeof (struct ipsec_sa), + sizeof (struct ipsec_proto), + ipsec_debug_attribute, + ipsec_delete_spi, + ipsec_exchange_script, + ipsec_finalize_exchange, + ipsec_free_exchange_data, + ipsec_free_proto_data, + ipsec_free_sa_data, + ipsec_get_keystate, + ipsec_get_spi, + ipsec_is_attribute_incompatible, + ipsec_setup_situation, + ipsec_situation_size, + ipsec_spi_size, + ipsec_validate_attribute, + ipsec_validate_exchange, + ipsec_validate_id_information, + ipsec_validate_key_information, + ipsec_validate_notification, + ipsec_validate_proto, + ipsec_validate_situation, + ipsec_validate_transform_id, + ipsec_initiator, + ipsec_responder +}; + +int16_t script_quick_mode[] = { + ISAKMP_PAYLOAD_HASH, /* Initiator -> responder. */ + ISAKMP_PAYLOAD_SA, + ISAKMP_PAYLOAD_NONCE, + EXCHANGE_SCRIPT_SWITCH, + ISAKMP_PAYLOAD_HASH, /* Responder -> initiator. */ + ISAKMP_PAYLOAD_SA, + ISAKMP_PAYLOAD_NONCE, + EXCHANGE_SCRIPT_SWITCH, + ISAKMP_PAYLOAD_HASH, /* Initiator -> responder. */ + EXCHANGE_SCRIPT_END +}; + +int16_t script_new_group_mode[] = { + ISAKMP_PAYLOAD_HASH, /* Initiator -> responder. */ + ISAKMP_PAYLOAD_SA, + EXCHANGE_SCRIPT_SWITCH, + ISAKMP_PAYLOAD_HASH, /* Responder -> initiator. */ + ISAKMP_PAYLOAD_SA, + EXCHANGE_SCRIPT_END +}; + +static void +ipsec_finalize_exchange (struct message *msg) +{ + struct sa *isakmp_sa = msg->isakmp_sa; + struct ipsec_sa *isa = isakmp_sa->data; + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + struct sa *sa; + struct proto *proto, *last_proto = 0; + int initiator = exchange->initiator; + struct timeval expiration; + + switch (exchange->phase) + { + case 1: + switch (exchange->type) + { + case ISAKMP_EXCH_ID_PROT: + case ISAKMP_EXCH_AGGRESSIVE: + isa->hash = ie->hash->type; + isa->prf_type = ie->prf_type; + isa->skeyid_len = ie->skeyid_len; + isa->skeyid_d = ie->skeyid_d; + isa->skeyid_a = ie->skeyid_a; + /* Prevents early free of SKEYID_*. */ + ie->skeyid_a = ie->skeyid_d = 0; + break; + } + + /* If a lifetime was negotiated sutup the death timer. */ + if (isakmp_sa->seconds) + { + gettimeofday(&expiration, 0); + expiration.tv_sec += isakmp_sa->seconds; + isakmp_sa->death = timer_add_event ("sa_rekey_p1", + (void (*) (void *))sa_rekey_p1, + isakmp_sa, &expiration); + if (!isakmp_sa->death) + { + /* If we don't give up we might start leaking... */ + sa_delete (isakmp_sa, 1); + return; + } + } + break; + + case 2: + switch (exchange->type) + { + case IKE_EXCH_QUICK_MODE: + /* + * Tell the application(s) about the SPIs and key material. + */ + for (sa = TAILQ_FIRST (&exchange->sa_list); sa; + sa = TAILQ_NEXT (sa, next)) + { + for (proto = TAILQ_FIRST (&sa->protos), last_proto = 0; proto; + proto = TAILQ_NEXT (proto, link)) + { + if (sysdep_ipsec_set_spi (sa, proto, 0, initiator) + || (last_proto + && sysdep_ipsec_group_spis (sa, last_proto, proto, + 0)) + || sysdep_ipsec_set_spi (sa, proto, 1, initiator) + || (last_proto + && sysdep_ipsec_group_spis (sa, last_proto, proto, + 1))) + /* XXX Tear down this exchange. */ + return; + last_proto = proto; + } + if (sysdep_ipsec_enable_spi (sa, initiator)) + /* XXX Tear down this exchange. */ + return; + } + break; + } + } +} + +static void +ipsec_free_exchange_data (void *vie) +{ + struct ipsec_exch *ie = vie; + + if (ie->sa_i_b) + free (ie->sa_i_b); + if (ie->g_xi) + free (ie->g_xi); + if (ie->g_xr) + free (ie->g_xr); + if (ie->g_xy) + free (ie->g_xy); + if (ie->skeyid) + free (ie->skeyid); + if (ie->skeyid_d) + free (ie->skeyid_d); + if (ie->skeyid_a) + free (ie->skeyid_a); + if (ie->skeyid_e) + free (ie->skeyid_e); + if (ie->hash_i) + free (ie->hash_i); + if (ie->hash_r) + free (ie->hash_r); +} + +static void +ipsec_free_sa_data (void *visa) +{ + struct ipsec_sa *isa = visa; + + if (isa->skeyid_a) + free (isa->skeyid_a); + if (isa->skeyid_d) + free (isa->skeyid_d); +} + +static void +ipsec_free_proto_data (void *viproto) +{ + struct ipsec_proto *iproto = viproto; + int i; + + for (i = 0; i < 2; i++) + if (iproto->keymat[i]) + free (iproto->keymat[i]); +} + +static u_int16_t * +ipsec_exchange_script (u_int8_t type) +{ + switch (type) + { + case IKE_EXCH_QUICK_MODE: + return script_quick_mode; + case IKE_EXCH_NEW_GROUP_MODE: + return script_new_group_mode; + } + return 0; +} + +/* Requires doi_init to already have been called. */ +void +ipsec_init () +{ + doi_register (&ipsec_doi); +} + +/* Given a message MSG, return a suitable IV (or rather keystate). */ +static struct keystate * +ipsec_get_keystate (struct message *msg) +{ + struct keystate *ks; + struct hash *hash; + + /* If we have already have an IV, use it. */ + if (msg->exchange && msg->exchange->keystate) + return msg->exchange->keystate; + + /* + * For phase 2 when no SA yet is setup we need to hash the IV used by + * the ISAKMP SA concatenated with the message ID, and use that as an + * IV for further cryptographic operations. + */ + ks = crypto_clone_keystate (msg->isakmp_sa->keystate); + if (!ks) + return 0; + + hash = hash_get (((struct ipsec_sa *)msg->isakmp_sa->data)->hash); + hash->Init (hash->ctx); + log_debug_buf (LOG_CRYPTO, 80, "ipsec_get_keystate: final phase 1 IV", + ks->riv, ks->xf->blocksize); + hash->Update (hash->ctx, ks->riv, ks->xf->blocksize); + log_debug_buf (LOG_CRYPTO, 80, "ipsec_get_keystate: message ID", + ((u_int8_t *)msg->iov[0].iov_base) + + ISAKMP_HDR_MESSAGE_ID_OFF, + ISAKMP_HDR_MESSAGE_ID_LEN); + hash->Update (hash->ctx, + ((u_int8_t *)msg->iov[0].iov_base) + ISAKMP_HDR_MESSAGE_ID_OFF, + ISAKMP_HDR_MESSAGE_ID_LEN); + hash->Final (hash->digest, hash->ctx); + crypto_init_iv (ks, hash->digest, ks->xf->blocksize); + log_debug_buf (LOG_CRYPTO, 80, "ipsec_get_keystate: phase 2 IV", + hash->digest, ks->xf->blocksize); + return ks; +} + +static void +ipsec_setup_situation (u_int8_t *buf) +{ + SET_IPSEC_SIT_SIT (buf + ISAKMP_SA_SIT_OFF, IPSEC_SIT_IDENTITY_ONLY); +} + +static size_t +ipsec_situation_size (void) +{ + return IPSEC_SIT_SIT_LEN; +} + +static u_int8_t +ipsec_spi_size (u_int8_t proto) +{ + return IPSEC_SPI_SIZE; +} + +static int +ipsec_validate_attribute (u_int16_t type, u_int8_t *value, u_int16_t len, + void *vmsg) +{ + struct message *msg = vmsg; + + if ((msg->exchange->phase == 1 + && (type < IKE_ATTR_ENCRYPTION_ALGORITHM + || type > IKE_ATTR_GROUP_ORDER)) + || (msg->exchange->phase == 2 + && (type < IPSEC_ATTR_SA_LIFE_TYPE + || type > IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM))) + return -1; + return 0; +} + +static int +ipsec_validate_exchange (u_int8_t exch) +{ + return exch != IKE_EXCH_QUICK_MODE && exch != IKE_EXCH_NEW_GROUP_MODE; +} + +static int +ipsec_validate_id_information (u_int8_t type, u_int8_t *extra, u_int8_t *buf, + size_t sz, struct exchange *exchange) +{ + u_int8_t proto = GET_IPSEC_ID_PROTO (extra); + u_int16_t port = GET_IPSEC_ID_PORT (extra); + + log_debug (LOG_MESSAGE, 0, + "ipsec_validate_id_information: proto %d port %d type %d", + proto, port, type); + if (type < IPSEC_ID_IPV4_ADDR || type > IPSEC_ID_KEY_ID) + return -1; + + if (exchange->phase == 1 + && (proto != IPPROTO_UDP || port != UDP_DEFAULT_PORT) + && (proto != 0 || port != 0)) + { +/* XXX SSH's ISAKMP tester fails this test (proto 17 - port 0). */ +#ifdef notyet + return -1; +#else + log_print ("ipsec_validate_id_information: " + "dubious ID information accepted"); +#endif + } + + /* XXX More checks? */ + + return 0; +} + +static int +ipsec_validate_key_information (u_int8_t *buf, size_t sz) +{ + /* XXX Not implemented yet. */ + return 0; +} + +static int +ipsec_validate_notification (u_int16_t type) +{ + return type < IPSEC_NOTIFY_RESPONDER_LIFETIME + || type > IPSEC_NOTIFY_INITIAL_CONTACT ? -1 : 0; +} + +static int +ipsec_validate_proto (u_int8_t proto) +{ + return proto < IPSEC_PROTO_IPSEC_AH || proto > IPSEC_PROTO_IPCOMP ? -1 : 0; +} + +static int +ipsec_validate_situation (u_int8_t *buf, size_t *sz) +{ + int sit = GET_IPSEC_SIT_SIT (buf); + int off; + + if (sit & (IPSEC_SIT_SECRECY | IPSEC_SIT_INTEGRITY)) + { + /* + * XXX All the roundups below, round up to 32 bit boundaries given + * that the situation field is aligned. This is not necessarily so, + * but I interpret the drafts as this is like this they want it. + */ + off = ROUNDUP_32 (GET_IPSEC_SIT_SECRECY_LENGTH (buf)); + off += ROUNDUP_32 (GET_IPSEC_SIT_SECRECY_CAT_LENGTH (buf + off)); + off += ROUNDUP_32 (GET_IPSEC_SIT_INTEGRITY_LENGTH (buf + off)); + off += ROUNDUP_32 (GET_IPSEC_SIT_INTEGRITY_CAT_LENGTH (buf + off)); + *sz = off + IPSEC_SIT_SZ; + } + else + *sz = IPSEC_SIT_SIT_LEN; + + /* Currently only "identity only" situations are supported. */ +#ifdef notdef + return + sit & ~(IPSEC_SIT_IDENTITY_ONLY | IPSEC_SIT_SECRECY | IPSEC_SIT_INTEGRITY); +#else + return sit & ~IPSEC_SIT_IDENTITY_ONLY; +#endif + return 1; + return 0; +} + +static int +ipsec_validate_transform_id (u_int8_t proto, u_int8_t transform_id) +{ + switch (proto) + { + /* + * As no unexpected protocols can occur, we just tie the default case + * to the first case, in orer to silence a GCC warning. + */ + default: + case ISAKMP_PROTO_ISAKMP: + return transform_id != IPSEC_TRANSFORM_KEY_IKE; + case IPSEC_PROTO_IPSEC_AH: + return + transform_id < IPSEC_AH_MD5 || transform_id > IPSEC_AH_DES ? -1 : 0; + case IPSEC_PROTO_IPSEC_ESP: + return transform_id < IPSEC_ESP_DES_IV64 + || transform_id > IPSEC_ESP_NULL ? -1 : 0; + case IPSEC_PROTO_IPCOMP: + return transform_id < IPSEC_IPCOMP_OUI + || transform_id > IPSEC_IPCOMP_V42BIS ? -1 : 0; + } +} + +static int +ipsec_initiator (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + int (**script) (struct message *msg) = 0; + + /* XXX Mostly not implemented yet. */ + + /* Check that the SA is coherent with the IKE rules. */ + if ((exchange->phase == 1 && exchange->type != ISAKMP_EXCH_ID_PROT + && exchange->type != ISAKMP_EXCH_INFO) + || (exchange->phase == 2 && exchange->type == ISAKMP_EXCH_ID_PROT)) + { + log_print ("ipsec_initiator: unsupported exchange type %d in phase %d", + exchange->type, exchange->phase); + return -1; + } + + switch (exchange->type) + { + case ISAKMP_EXCH_BASE: + break; + case ISAKMP_EXCH_ID_PROT: + script = ike_main_mode_initiator; + break; + case ISAKMP_EXCH_AUTH_ONLY: + log_print ("ipsec_initiator: unuspported exchange type %d", + exchange->type); + return -1; + case ISAKMP_EXCH_AGGRESSIVE: + break; + case ISAKMP_EXCH_INFO: + message_send_info (msg); + break; + case IKE_EXCH_QUICK_MODE: + script = ike_quick_mode_initiator; + break; + case IKE_EXCH_NEW_GROUP_MODE: + break; + } + + /* Run the script code for this step. */ + if (script) + return script[exchange->step] (msg); + + return 0; +} + +static int +ipsec_responder (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + int (**script) (struct message *msg) = 0; + + /* Check that a new exchange is coherent with the IKE rules. */ + if (exchange->step == 0 + && ((exchange->phase == 1 && exchange->type != ISAKMP_EXCH_ID_PROT + && exchange->type != ISAKMP_EXCH_INFO) + || (exchange->phase == 2 && exchange->type == ISAKMP_EXCH_ID_PROT))) + { + message_drop (msg, ISAKMP_NOTIFY_UNSUPPORTED_EXCHANGE_TYPE, 0, 0, 0); + return -1; + } + + log_debug (LOG_MISC, 30, + "ipsec_responder: phase %d exchange %d step %d", exchange->phase, + exchange->type, exchange->step); + switch (exchange->type) + { + case ISAKMP_EXCH_BASE: + case ISAKMP_EXCH_AUTH_ONLY: + message_drop (msg, ISAKMP_NOTIFY_UNSUPPORTED_EXCHANGE_TYPE, 0, 0, 0); + return -1; + + case ISAKMP_EXCH_ID_PROT: + script = ike_main_mode_responder; + break; + + case ISAKMP_EXCH_AGGRESSIVE: + /* XXX Not implemented yet. */ + break; + + case ISAKMP_EXCH_INFO: + /* XXX Not implemented yet. */ + break; + + case IKE_EXCH_QUICK_MODE: + script = ike_quick_mode_responder; + break; + + case IKE_EXCH_NEW_GROUP_MODE: + /* XXX Not implemented yet. */ + break; + } + + /* Run the script code for this step. */ + if (script) + return script[exchange->step] (msg); + + /* + * XXX So far we don't accept any proposals for exchanges we don't support. + */ + if (TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_SA])) + { + message_drop (msg, ISAKMP_NOTIFY_NO_PROPOSAL_CHOSEN, 0, 0, 0); + return -1; + } + return 0; +} + +static enum hashes from_ike_hash (u_int16_t hash) +{ + switch (hash) + { + case IKE_HASH_MD5: + return HASH_MD5; + case IKE_HASH_SHA: + return HASH_SHA1; + } + return -1; +} + +static enum transform from_ike_crypto (u_int16_t crypto) +{ + /* Coincidentally this is the null operation :-) */ + return crypto; +} + +int +ipsec_is_attribute_incompatible (u_int16_t type, u_int8_t *value, + u_int16_t len, void *vmsg) +{ + struct message *msg = vmsg; + + if (msg->exchange->phase == 1) + { + switch (type) + { + case IKE_ATTR_ENCRYPTION_ALGORITHM: + return !crypto_get (from_ike_crypto (decode_16 (value))); + case IKE_ATTR_HASH_ALGORITHM: + return !hash_get (from_ike_hash (decode_16 (value))); + case IKE_ATTR_AUTHENTICATION_METHOD: + return !ike_auth_get (decode_16 (value)); + case IKE_ATTR_GROUP_DESCRIPTION: + return decode_16 (value) < IKE_GROUP_DESC_MODP_768 + || decode_16 (value) > IKE_GROUP_DESC_EC2N_185; + case IKE_ATTR_GROUP_TYPE: + return 1; + case IKE_ATTR_GROUP_PRIME: + return 1; + case IKE_ATTR_GROUP_GENERATOR_1: + return 1; + case IKE_ATTR_GROUP_GENERATOR_2: + return 1; + case IKE_ATTR_GROUP_CURVE_A: + return 1; + case IKE_ATTR_GROUP_CURVE_B: + return 1; + case IKE_ATTR_LIFE_TYPE: + return decode_16 (value) < IKE_DURATION_SECONDS + || decode_16 (value) > IKE_DURATION_KILOBYTES; + case IKE_ATTR_LIFE_DURATION: + return 0; + case IKE_ATTR_PRF: + return 1; + case IKE_ATTR_KEY_LENGTH: + /* + * Our crypto routines only allows key-lengths which are multiples + * of an octet. + */ + return decode_16 (value) % 8 != 0; + case IKE_ATTR_FIELD_SIZE: + return 1; + case IKE_ATTR_GROUP_ORDER: + return 1; + } + } + else + { + switch (type) + { + case IPSEC_ATTR_SA_LIFE_TYPE: + return decode_16 (value) < IPSEC_DURATION_SECONDS + || decode_16 (value) > IPSEC_DURATION_KILOBYTES; + case IPSEC_ATTR_SA_LIFE_DURATION: + return 0; + case IPSEC_ATTR_GROUP_DESCRIPTION: + return decode_16 (value) < IKE_GROUP_DESC_MODP_768 + || decode_16 (value) > IKE_GROUP_DESC_EC2N_185; + case IPSEC_ATTR_ENCAPSULATION_MODE: + return decode_16 (value) < IPSEC_ENCAP_TUNNEL + || decode_16 (value) > IPSEC_ENCAP_TRANSPORT; + case IPSEC_ATTR_AUTHENTICATION_ALGORITHM: + return decode_16 (value) < IPSEC_AUTH_HMAC_MD5 + || decode_16 (value) > IPSEC_AUTH_KPDK; + case IPSEC_ATTR_KEY_LENGTH: + return 1; + case IPSEC_ATTR_KEY_ROUNDS: + return 1; + case IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE: + return 1; + case IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM: + return 1; + } + } + /* XXX Silence gcc. */ + return 1; +} + +int +ipsec_debug_attribute (u_int16_t type, u_int8_t *value, u_int16_t len, + void *vmsg) +{ + struct message *msg = vmsg; + char val[20]; + + /* XXX Transient solution. */ + if (len == 2) + sprintf (val, "%d", decode_16 (value)); + else if (len == 4) + sprintf (val, "%d", decode_32 (value)); + else + sprintf (val, "unrepresentable"); + + log_debug (LOG_MESSAGE, 50, "Attribute %s value %s", + constant_name (msg->exchange->phase == 1 + ? ike_attr_cst : ipsec_attr_cst, type), + val); + return 0; +} + +int +ipsec_decode_attribute (u_int16_t type, u_int8_t *value, u_int16_t len, + void *vida) +{ + struct ipsec_decode_arg *ida = vida; + struct message *msg = ida->msg; + struct sa *sa = ida->sa; + struct ipsec_sa *isa = sa->data; + struct proto *proto = ida->proto; + struct ipsec_proto *iproto = proto->data; + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + static int lifetype = 0; + + if (exchange->phase == 1) + { + switch (type) + { + case IKE_ATTR_ENCRYPTION_ALGORITHM: + /* XXX Errors possible? */ + exchange->crypto = crypto_get (from_ike_crypto (decode_16 (value))); + break; + case IKE_ATTR_HASH_ALGORITHM: + /* XXX Errors possible? */ + ie->hash = hash_get (from_ike_hash (decode_16 (value))); + break; + case IKE_ATTR_AUTHENTICATION_METHOD: + /* XXX Errors possible? */ + ie->ike_auth = ike_auth_get (decode_16 (value)); + break; + case IKE_ATTR_GROUP_DESCRIPTION: + /* XXX Errors possible? */ + ie->group = group_get (decode_16 (value)); + break; + case IKE_ATTR_GROUP_TYPE: + break; + case IKE_ATTR_GROUP_PRIME: + break; + case IKE_ATTR_GROUP_GENERATOR_1: + break; + case IKE_ATTR_GROUP_GENERATOR_2: + break; + case IKE_ATTR_GROUP_CURVE_A: + break; + case IKE_ATTR_GROUP_CURVE_B: + break; + case IKE_ATTR_LIFE_TYPE: + lifetype = decode_16 (value); + return 0; + case IKE_ATTR_LIFE_DURATION: + switch (lifetype) + { + case IKE_DURATION_SECONDS: + switch (len) + { + case 2: + sa->seconds = decode_16 (value); + break; + case 4: + sa->seconds = decode_32 (value); + break; + default: + /* XXX Log. */ + } + break; + case IKE_DURATION_KILOBYTES: + switch (len) + { + case 2: + sa->kilobytes = decode_16 (value); + break; + case 4: + sa->kilobytes = decode_32 (value); + break; + default: + /* XXX Log. */ + } + break; + default: + /* XXX Log! */ + } + break; + case IKE_ATTR_PRF: + break; + case IKE_ATTR_KEY_LENGTH: + exchange->key_length = decode_16 (value) / 8; + break; + case IKE_ATTR_FIELD_SIZE: + break; + case IKE_ATTR_GROUP_ORDER: + break; + } + } + else + { + switch (type) + { + case IPSEC_ATTR_SA_LIFE_TYPE: + lifetype = decode_16 (value); + return 0; + case IPSEC_ATTR_SA_LIFE_DURATION: + switch (lifetype) + { + case IPSEC_DURATION_SECONDS: + switch (len) + { + case 2: + sa->seconds = decode_16 (value); + break; + case 4: + sa->seconds = decode_32 (value); + break; + default: + /* XXX Log. */ + } + break; + case IPSEC_DURATION_KILOBYTES: + switch (len) + { + case 2: + sa->kilobytes = decode_16 (value); + break; + case 4: + sa->kilobytes = decode_32 (value); + break; + default: + /* XXX Log. */ + } + break; + default: + /* XXX Log! */ + } + break; + case IPSEC_ATTR_GROUP_DESCRIPTION: + isa->group_desc = decode_16 (value); + break; + case IPSEC_ATTR_ENCAPSULATION_MODE: + /* XXX Multiple protocols must have same encapsulation mode, no? */ + iproto->encap_mode = decode_16 (value); + break; + case IPSEC_ATTR_AUTHENTICATION_ALGORITHM: + iproto->auth = decode_16 (value); + break; + case IPSEC_ATTR_KEY_LENGTH: + iproto->keylen = decode_16 (value); + break; + case IPSEC_ATTR_KEY_ROUNDS: + iproto->keyrounds = decode_16 (value); + break; + case IPSEC_ATTR_COMPRESS_DICTIONARY_SIZE: + break; + case IPSEC_ATTR_COMPRESS_PRIVATE_ALGORITHM: + break; + } + } + lifetype = 0; + return 0; +} + +/* + * Walk over the attributes of the transform payload found in BUF, and + * fill out the fields of the SA attached to MSG. Also mark the SA as + * processed. + */ +void +ipsec_decode_transform (struct message *msg, struct sa *sa, + struct proto *proto, u_int8_t *buf) +{ + struct ipsec_exch *ie = msg->exchange->data; + struct ipsec_decode_arg ida; + + log_debug (LOG_MISC, 20, "ipsec_decode_transform: transform %d chosen", + GET_ISAKMP_TRANSFORM_NO (buf)); + + ida.msg = msg; + ida.sa = sa; + ida.proto = proto; + + /* The default IKE lifetime is 8 hours. */ + if (sa->phase == 1) + sa->seconds = 28800; + + /* Extract the attributes and stuff them into the SA. */ + attribute_map (buf + ISAKMP_TRANSFORM_SA_ATTRS_OFF, + GET_ISAKMP_GEN_LENGTH (buf) - ISAKMP_TRANSFORM_SA_ATTRS_OFF, + ipsec_decode_attribute, &ida); + + /* + * If no pseudo-random function was negotiated, it's HMAC. + * XXX As PRF_HMAC currently is zero, this is a no-op. + */ + if (!ie->prf_type) + ie->prf_type = PRF_HMAC; +} + +static void +ipsec_delete_spi (struct sa *sa, struct proto *proto, int initiator) +{ + if (sa->phase == 1) + return; + /* XXX Error handling? Is it interesting? */ + sysdep_ipsec_delete_spi (sa, proto, initiator); +} + +static int +ipsec_g_x (struct message *msg, int peer, u_int8_t *buf) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + u_int8_t **g_x; + int initiator = exchange->initiator ^ peer; + char header[32]; + + g_x = initiator ? &ie->g_xi : &ie->g_xr; + *g_x = malloc (ie->g_x_len); + if (!*g_x) + return -1; + memcpy (*g_x, buf, ie->g_x_len); + snprintf (header, 32, "ipsec_g_x: g^x%c", initiator ? 'i' : 'r'); + log_debug_buf (LOG_MISC, 80, header, *g_x, ie->g_x_len); + return 0; +} + +/* Generate our DH value. */ +int +ipsec_gen_g_x (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + u_int8_t *buf; + + buf = malloc (ISAKMP_KE_SZ + ie->g_x_len); + if (!buf) + return -1; + + if (message_add_payload (msg, ISAKMP_PAYLOAD_KEY_EXCH, buf, + ISAKMP_KE_SZ + ie->g_x_len, 1)) + { + free (buf); + return -1; + } + + dh_create_exchange (ie->group, buf + ISAKMP_KE_DATA_OFF); + return ipsec_g_x (msg, 0, buf + ISAKMP_KE_DATA_OFF); +} + +/* Save the peer's DH value. */ +int +ipsec_save_g_x (struct message *msg) +{ + struct exchange *exchange = msg->exchange; + struct ipsec_exch *ie = exchange->data; + struct payload *kep; + + kep = TAILQ_FIRST (&msg->payload[ISAKMP_PAYLOAD_KEY_EXCH]); + kep->flags |= PL_MARK; + ie->g_x_len = GET_ISAKMP_GEN_LENGTH (kep->p) - ISAKMP_KE_DATA_OFF; + + /* Check that the given length matches the group's expectancy. */ + if (ie->g_x_len != dh_getlen (ie->group)) + { + /* XXX Is this a good notify type? */ + message_drop (msg, ISAKMP_NOTIFY_PAYLOAD_MALFORMED, 0, 0, 0); + return -1; + } + + return ipsec_g_x (msg, 1, kep->p + ISAKMP_KE_DATA_OFF); +} + +/* + * Get a SPI for PROTO and the transport MSG passed over. Store the + * size where SZ points. NB! A zero return is OK if *SZ is zero. + */ +static u_int8_t * +ipsec_get_spi (size_t *sz, u_int8_t proto, struct message *msg) +{ + struct sockaddr *dst; + int dstlen; + struct transport *transport = msg->transport; + + if (msg->exchange->phase == 1) + { + *sz = 0; + return 0; + } + else + { + /* We are the destination in the SA we want a SPI for. */ + transport->vtbl->get_src (transport, &dst, &dstlen); + return sysdep_ipsec_get_spi (sz, proto, dst, dstlen); + } +} + +int +ipsec_esp_enckeylength (struct proto *proto) +{ + struct ipsec_proto *iproto = proto->data; + + /* Compute the keylength to use. */ + switch (proto->id) + { + case IPSEC_ESP_DES: + case IPSEC_ESP_DES_IV32: + case IPSEC_ESP_DES_IV64: + return 8; + case IPSEC_ESP_3DES: + return 24; + default: + return iproto->keylen / 8; + } +} + +int +ipsec_esp_authkeylength (struct proto *proto) +{ + struct ipsec_proto *iproto = proto->data; + + switch (iproto->auth) + { + case IPSEC_AUTH_HMAC_MD5: + return 16; + case IPSEC_AUTH_HMAC_SHA: + return 20; + default: + return 0; + } +} + +int +ipsec_ah_keylength (struct proto *proto) +{ + switch (proto->id) + { + case IPSEC_AH_MD5: + return 16; + case IPSEC_AH_SHA: + return 20; + default: + return -1; + } +} + +int +ipsec_keymat_length (struct proto *proto) +{ + switch (proto->proto) + { + case IPSEC_PROTO_IPSEC_ESP: + return ipsec_esp_enckeylength (proto) + ipsec_esp_authkeylength (proto); + case IPSEC_PROTO_IPSEC_AH: + return ipsec_ah_keylength (proto); + default: + return -1; + } +} |