diff options
author | Niklas Hallqvist <niklas@cvs.openbsd.org> | 1998-11-15 00:03:51 +0000 |
---|---|---|
committer | Niklas Hallqvist <niklas@cvs.openbsd.org> | 1998-11-15 00:03:51 +0000 |
commit | e79150e203c82fbba87b52246155ba5155b9f1e8 (patch) | |
tree | 7f85b06a6b243ecd185041e9212d7ea0b6ce4480 /sbin/isakmpd/ipsec.c | |
parent | 23065b1eddaddf11c9e23b9193afd76cf35de24d (diff) |
Initial import of isakmpd, an IKE (ISAKMP/Oakley) implementation for the
OpenBSD IPSEC stack by me, Niklas Hallqvist and Niels Provos, funded by
Ericsson Radio Systems. It is not yet complete or usable in a real scenario
but the missing pieces will soon be there. The early commit is for people
who wants early access and who are not afraid of looking at source.
isakmpd interops with Cisco, Timestep, SSH & Pluto (Linux FreeS/WAN) so
far, so it is not that incomplete. It is really mostly configuration that
is lacking.
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; + } +} |