/* Security Policy Data Base (such as it is) * Copyright (C) 1998, 1999 D. Hugh Redelmeier. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * RCSID $Id: spdb.c,v 1.50 2000/06/21 18:24:35 dhr Exp $ */ #include #include #include #include #include #include #include #include "constants.h" #include "defs.h" #include "id.h" #include "connections.h" /* needs id.h */ #include "state.h" #include "packet.h" #include "preshared.h" #include "kernel.h" #include "log.h" #include "spdb.h" #include "whack.h" /* for RC_LOG_SERIOUS */ #include "sha1.h" #include "md5.h" #include "crypto.h" /* requires sha1.h and md5.h */ #define AD(x) x, elemsof(x) /* Array Description */ /**************** Oakely (main mode) SA database ****************/ /* arrays of attributes for transforms, preshared key */ static struct db_attr otpsk768des3md5[] = { { OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC }, { OAKLEY_HASH_ALGORITHM, OAKLEY_MD5 }, { OAKLEY_AUTHENTICATION_METHOD, OAKLEY_PRESHARED_KEY }, { OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP768 }, }; static struct db_attr otpsk1024des3md5[] = { { OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC }, { OAKLEY_HASH_ALGORITHM, OAKLEY_MD5 }, { OAKLEY_AUTHENTICATION_METHOD, OAKLEY_PRESHARED_KEY }, { OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP1024 }, }; static struct db_attr otpsk768des3sha[] = { { OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC }, { OAKLEY_HASH_ALGORITHM, OAKLEY_SHA }, { OAKLEY_AUTHENTICATION_METHOD, OAKLEY_PRESHARED_KEY }, { OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP768 }, }; static struct db_attr otpsk1024des3sha[] = { { OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC }, { OAKLEY_HASH_ALGORITHM, OAKLEY_SHA }, { OAKLEY_AUTHENTICATION_METHOD, OAKLEY_PRESHARED_KEY }, { OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP1024 }, }; /* arrays of attributes for transforms, RSA signatures */ static struct db_attr otrsasig768des3md5[] = { { OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC }, { OAKLEY_HASH_ALGORITHM, OAKLEY_MD5 }, { OAKLEY_AUTHENTICATION_METHOD, OAKLEY_RSA_SIG }, { OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP768 }, }; static struct db_attr otrsasig1024des3md5[] = { { OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC }, { OAKLEY_HASH_ALGORITHM, OAKLEY_MD5 }, { OAKLEY_AUTHENTICATION_METHOD, OAKLEY_RSA_SIG }, { OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP1024 }, }; static struct db_attr otrsasig768des3sha[] = { { OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC }, { OAKLEY_HASH_ALGORITHM, OAKLEY_SHA }, { OAKLEY_AUTHENTICATION_METHOD, OAKLEY_RSA_SIG }, { OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP768 }, }; static struct db_attr otrsasig1024des3sha[] = { { OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC }, { OAKLEY_HASH_ALGORITHM, OAKLEY_SHA }, { OAKLEY_AUTHENTICATION_METHOD, OAKLEY_RSA_SIG }, { OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP1024 }, }; /* We won't accept this, but by proposing it, we get to test * our rejection. We better not propose it to an IKE daemon * that will accept it! */ #ifdef TEST_INDECENT_PROPOSAL static struct db_attr otpsk1024des3tiger[] = { { OAKLEY_ENCRYPTION_ALGORITHM, OAKLEY_3DES_CBC }, { OAKLEY_HASH_ALGORITHM, OAKLEY_TIGER }, { OAKLEY_AUTHENTICATION_METHOD, OAKLEY_PRESHARED_KEY }, { OAKLEY_GROUP_DESCRIPTION, OAKLEY_GROUP_MODP1024 }, }; #endif /* TEST_INDECENT_PROPOSAL */ /* tables of transforms, in preference order (select based on AUTH) */ static struct db_trans oakley_trans_psk[] = { #ifdef TEST_INDECENT_PROPOSAL { KEY_IKE, AD(otpsk1024des3tiger) }, #endif { KEY_IKE, AD(otpsk1024des3sha) }, { KEY_IKE, AD(otpsk1024des3md5) }, { KEY_IKE, AD(otpsk768des3md5) }, { KEY_IKE, AD(otpsk768des3sha) }, }; static struct db_trans oakley_trans_rsasig[] = { { KEY_IKE, AD(otrsasig1024des3sha) }, { KEY_IKE, AD(otrsasig1024des3md5) }, { KEY_IKE, AD(otrsasig768des3md5) }, { KEY_IKE, AD(otrsasig768des3sha) }, }; /* In this table, either PSK or RSA sig is accepted. * The order matters, but I don't know what would be best. */ static struct db_trans oakley_trans_pskrsasig[] = { #ifdef TEST_INDECENT_PROPOSAL { KEY_IKE, AD(otpsk1024des3tiger) }, #endif { KEY_IKE, AD(otrsasig1024des3sha) }, { KEY_IKE, AD(otpsk1024des3sha) }, { KEY_IKE, AD(otrsasig1024des3md5) }, { KEY_IKE, AD(otpsk1024des3md5) }, { KEY_IKE, AD(otrsasig768des3md5) }, { KEY_IKE, AD(otpsk768des3md5) }, { KEY_IKE, AD(otrsasig768des3sha) }, { KEY_IKE, AD(otpsk768des3sha) }, }; /* array of proposals to be conjoined (can only be one for Oakley) */ static struct db_prop oakley_pc_psk[] = { { PROTO_ISAKMP, AD(oakley_trans_psk) } }; static struct db_prop oakley_pc_rsasig[] = { { PROTO_ISAKMP, AD(oakley_trans_rsasig) } }; static struct db_prop oakley_pc_pskrsasig[] = { { PROTO_ISAKMP, AD(oakley_trans_pskrsasig) } }; /* array of proposal conjuncts (can only be one) */ static struct db_prop_conj oakley_props_psk[] = { { AD(oakley_pc_psk) } }; static struct db_prop_conj oakley_props_rsasig[] = { { AD(oakley_pc_rsasig) } }; static struct db_prop_conj oakley_props_pskrsasig[] = { { AD(oakley_pc_pskrsasig) } }; /* the sadb entry, subscripted by POLICY_PSK and POLICY_RSASIG bits */ struct db_sa oakley_sadb[] = { { NULL, 0 }, /* none */ { AD(oakley_props_psk) }, /* POLICY_PSK */ { AD(oakley_props_rsasig) }, /* POLICY_RSASIG */ { AD(oakley_props_pskrsasig) }, /* POLICY_PSK + POLICY_RSASIG */ }; /**************** IPsec (quick mode) SA database ****************/ /* arrays of attributes for transforms */ static struct db_attr esp_attr_tunnel[] = { { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TUNNEL }, }; static struct db_attr esp_attr_transport[] = { { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TRANSPORT }, }; static struct db_attr espmd5_attr_tunnel[] = { { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_MD5 }, { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TUNNEL }, }; static struct db_attr espmd5_attr_transport[] = { { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_MD5 }, { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TRANSPORT }, }; static struct db_attr espsha1_attr_tunnel[] = { { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_SHA1 }, { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TUNNEL }, }; static struct db_attr espsha1_attr_transport[] = { { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_SHA1 }, { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TRANSPORT }, }; static struct db_attr ah_HMAC_MD5_attr_tunnel[] = { { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_MD5 }, { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TUNNEL }, }; static struct db_attr ah_HMAC_MD5_attr_transport[] = { { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_MD5 }, { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TRANSPORT }, }; static struct db_attr ah_HMAC_SHA1_attr_tunnel[] = { { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_SHA1 }, { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TUNNEL }, }; static struct db_attr ah_HMAC_SHA1_attr_transport[] = { { AUTH_ALGORITHM, AUTH_ALGORITHM_HMAC_SHA1 }, { ENCAPSULATION_MODE, ENCAPSULATION_MODE_TRANSPORT }, }; /* arrays of transforms, each in in preference order */ static struct db_trans espa_trans_tunnel[] = { { ESP_3DES, AD(espmd5_attr_tunnel) }, { ESP_3DES, AD(espsha1_attr_tunnel) }, }; static struct db_trans espa_trans_transport[] = { { ESP_3DES, AD(espmd5_attr_transport) }, { ESP_3DES, AD(espsha1_attr_transport) }, }; static struct db_trans esp_trans_tunnel[] = { { ESP_3DES, AD(esp_attr_tunnel) }, }; static struct db_trans esp_trans_transport[] = { { ESP_3DES, AD(esp_attr_transport) }, }; #ifdef SUPPORT_ESP_NULL static struct db_trans espnull_trans_tunnel[] = { { ESP_NULL, AD(espmd5_attr_tunnel) }, { ESP_NULL, AD(espsha1_attr_tunnel) }, }; static struct db_trans espnull_trans_transport[] = { { ESP_NULL, AD(espmd5_attr_transport) }, { ESP_NULL, AD(espsha1_attr_transport) }, }; #endif /* SUPPORT_ESP_NULL */ static struct db_trans ah_trans_tunnel[] = { { AH_MD5, AD(ah_HMAC_MD5_attr_tunnel) }, { AH_SHA, AD(ah_HMAC_SHA1_attr_tunnel) }, }; static struct db_trans ah_trans_transport[] = { { AH_MD5, AD(ah_HMAC_MD5_attr_transport) }, { AH_SHA, AD(ah_HMAC_SHA1_attr_transport) }, }; /* arrays of proposals to be conjoined */ static struct db_prop ah_pc_tunnel[] = { { PROTO_IPSEC_AH, AD(ah_trans_tunnel) }, }; static struct db_prop ah_pc_transport[] = { { PROTO_IPSEC_AH, AD(ah_trans_transport) }, }; #ifdef SUPPORT_ESP_NULL static struct db_prop espnull_pc_tunnel[] = { { PROTO_IPSEC_ESP, AD(espnull_trans_tunnel) }, }; static struct db_prop espnull_pc_transport[] = { { PROTO_IPSEC_ESP, AD(espnull_trans_transport) }, }; #endif /* SUPPORT_ESP_NULL */ static struct db_prop esp_pc_tunnel[] = { { PROTO_IPSEC_ESP, AD(espa_trans_tunnel) }, }; static struct db_prop esp_pc_transport[] = { { PROTO_IPSEC_ESP, AD(espa_trans_transport) }, }; static struct db_prop ah_esp_pc_tunnel[] = { { PROTO_IPSEC_AH, AD(ah_trans_tunnel) }, { PROTO_IPSEC_ESP, AD(esp_trans_tunnel) }, }; static struct db_prop ah_esp_pc_transport[] = { { PROTO_IPSEC_AH, AD(ah_trans_transport) }, { PROTO_IPSEC_ESP, AD(esp_trans_transport) }, }; /* arrays of proposal alternatives (each element is a conjunction) */ static struct db_prop_conj ah_tunnel_props[] = { { AD(ah_pc_tunnel) }, #ifdef SUPPORT_ESP_NULL { AD(espnull_pc_tunnel) } #endif }; static struct db_prop_conj ah_transport_props[] = { { AD(ah_pc_transport) }, #ifdef SUPPORT_ESP_NULL { AD(espnull_pc_transport) } #endif }; static struct db_prop_conj esp_tunnel_props[] = { { AD(esp_pc_tunnel) } }; static struct db_prop_conj esp_transport_props[] = { { AD(esp_pc_transport) } }; static struct db_prop_conj ah_esp_tunnel_props[] = { { AD(ah_esp_pc_tunnel) } }; static struct db_prop_conj ah_esp_transport_props[] = { { AD(ah_esp_pc_transport) } }; /* The IPsec sadb is subscripted by a bitset (subset of policy) * with members from { POLICY_ENCRYPT, POLICY_AUTHENTICATE, POLICY_TUNNEL } * shifted left by POLICY_IPSEC_SHIFT. */ struct db_sa ipsec_sadb[1 << 3] = { { NULL, 0 }, /* none */ { AD(esp_transport_props) }, /* POLICY_ENCRYPT */ { AD(ah_transport_props) }, /* POLICY_AUTHENTICATE */ { AD(ah_esp_transport_props) }, /* POLICY_ENCRYPT+POLICY_AUTHENTICATE */ { NULL, 0 }, /* POLICY_TUNNEL */ { AD(esp_tunnel_props) }, /* POLICY_ENCRYPT+POLICY_TUNNEL */ { AD(ah_tunnel_props) }, /* POLICY_AUTHENTICATE+POLICY_TUNNEL */ { AD(ah_esp_tunnel_props) }, /* POLICY_ENCRYPT+POLICY_AUTHENTICATE+POLICY_TUNNEL */ }; #undef AD /* output an attribute (within an SA) */ static bool out_attr( int type, unsigned long val, struct_desc *attr_desc, enum_names **attr_val_descs UNUSED, pb_stream *pbs) { struct isakmp_attribute attr; if (val >> 16 == 0) { /* short value: use TV form */ attr.isaat_af_type = type | ISAKMP_ATTR_AF_TV; attr.isaat_lv = val; if (!out_struct(&attr, attr_desc, pbs, NULL)) return FALSE; } else { /* This is a real fudge! Since we rarely use long attributes * and since this is the only place where we can cause an * ISAKMP message length to be other than a multiple of 4 octets, * we force the length of the value to be a multiple of 4 octets. * Furthermore, we only handle values up to 4 octets in length. * Voila: a fixed format! */ pb_stream val_pbs; u_int32_t nval = htonl(val); attr.isaat_af_type = type | ISAKMP_ATTR_AF_TLV; if (!out_struct(&attr, attr_desc, pbs, &val_pbs) || !out_raw(&nval, sizeof(nval), &val_pbs, "long attribute value")) return FALSE; close_output_pbs(&val_pbs); } DBG(DBG_EMITTING, enum_names *d = attr_val_descs[type]; if (d != NULL) DBG_log(" [%lu is %s]", val, enum_show(d, val))); return TRUE; } /* Output an SA, as described by a db_sa. * This has the side-effect of allocating SPIs for us. */ bool out_sa( pb_stream *outs, struct db_sa *sadb, struct state *st, bool oakley_mode, u_int8_t np) { pb_stream sa_pbs; int pcn; bool ah_spi_generated = FALSE; bool esp_spi_generated = FALSE; /* SA header out */ { struct isakmp_sa sa; sa.isasa_np = np; st->st_doi = sa.isasa_doi = ISAKMP_DOI_IPSEC; /* all we know */ if (!out_struct(&sa, &isakmp_sa_desc, outs, &sa_pbs)) return FALSE; } /* within SA: situation out */ st->st_situation = SIT_IDENTITY_ONLY; if (!out_struct(&st->st_situation, &ipsec_sit_desc, &sa_pbs, NULL)) return FALSE; /* within SA: Proposal Payloads * * Multiple Proposals with the same number are simultaneous * (conjuncts) and must deal with different protocols (AH or ESP). * Proposals with different numbers are alternatives (disjuncts), * in preference order. * Proposal numbers must be monotonic. * See draft-ietf-ipsec-isakmp-09.txt 4.2 */ for (pcn = 0; pcn != sadb->prop_conj_cnt; pcn++) { struct db_prop_conj *pc = &sadb->prop_conjs[pcn]; int pn; for (pn = 0; pn != pc->prop_cnt; pn++) { struct db_prop *p = &pc->props[pn]; pb_stream proposal_pbs; struct isakmp_proposal proposal; struct_desc *trans_desc; struct_desc *attr_desc; enum_names **attr_val_descs; int tn; /* Proposal header */ proposal.isap_np = pcn == sadb->prop_conj_cnt-1 && pn == pc->prop_cnt-1 ? ISAKMP_NEXT_NONE : ISAKMP_NEXT_P; proposal.isap_proposal = pcn; proposal.isap_protoid = p->protoid; proposal.isap_spisize = oakley_mode? 0 : IPSEC_DOI_SPI_SIZE; proposal.isap_notrans = p->trans_cnt; if (!out_struct(&proposal, &isakmp_proposal_desc, &sa_pbs, &proposal_pbs)) return FALSE; /* Per-protocols stuff: * Set trans_desc. * Set attr_desc. * Set attr_val_descs. * If not oakley_mode, emit SPI. * We allocate SPIs on demand. * All ESPs in an SA will share a single SPI. * All AHs in an SAwill share a single SPI. * AHs' SPI will be distinct from ESPs'. * This latter is needed because KLIPS doesn't * use the protocol when looking up a (dest, protocol, spi). * ??? If multiple ESPs are composed, how should their SPIs * be allocated? */ { ipsec_spi_t *spi_ptr = NULL; bool *spi_generated; switch (p->protoid) { case PROTO_ISAKMP: passert(oakley_mode); trans_desc = &isakmp_isakmp_transform_desc; attr_desc = &isakmp_oakley_attribute_desc; attr_val_descs = oakley_attr_val_descs; /* no SPI needed */ break; case PROTO_IPSEC_AH: passert(!oakley_mode); trans_desc = &isakmp_ah_transform_desc; attr_desc = &isakmp_ipsec_attribute_desc; attr_val_descs = ipsec_attr_val_descs; spi_ptr = &st->st_ah.our_spi; spi_generated = &ah_spi_generated; break; case PROTO_IPSEC_ESP: passert(!oakley_mode); trans_desc = &isakmp_esp_transform_desc; attr_desc = &isakmp_ipsec_attribute_desc; attr_val_descs = ipsec_attr_val_descs; spi_ptr = &st->st_esp.our_spi; spi_generated = &esp_spi_generated; break; default: passert(FALSE); } if (spi_ptr != NULL) { if (!*spi_generated) { *spi_ptr = get_ipsec_spi(0); *spi_generated = TRUE; } if (!out_raw((u_char *)spi_ptr, sizeof(*spi_ptr) , &proposal_pbs, "SPI")) return FALSE; } } /* within proposal: Transform Payloads */ for (tn = 0; tn != p->trans_cnt; tn++) { struct db_trans *t = &p->trans[tn]; pb_stream trans_pbs; struct isakmp_transform trans; int an; trans.isat_np = (tn == p->trans_cnt - 1) ? ISAKMP_NEXT_NONE : ISAKMP_NEXT_T; trans.isat_transnum = tn; trans.isat_transid = t->transid; if (!out_struct(&trans, trans_desc, &proposal_pbs, &trans_pbs)) return FALSE; /* Within tranform: Attributes. */ /* For Phase 2 / Quick Mode, GROUP_DESCRIPTION is * automatically generated because it must be the same * in every transform. */ if (st->st_pfs_group != NULL) { passert(!oakley_mode); passert(st->st_pfs_group != &unset_group); out_attr(GROUP_DESCRIPTION, st->st_pfs_group->group , attr_desc, attr_val_descs , &trans_pbs); } /* automatically generate duration */ if (oakley_mode) { out_attr(OAKLEY_LIFE_TYPE, OAKLEY_LIFE_SECONDS , attr_desc, attr_val_descs , &trans_pbs); out_attr(OAKLEY_LIFE_DURATION , st->st_connection->sa_ike_life_seconds , attr_desc, attr_val_descs , &trans_pbs); } else { out_attr(SA_LIFE_TYPE, SA_LIFE_TYPE_SECONDS , attr_desc, attr_val_descs , &trans_pbs); out_attr(SA_LIFE_DURATION , st->st_connection->sa_ipsec_life_seconds , attr_desc, attr_val_descs , &trans_pbs); } /* spit out attributes from table */ for (an = 0; an != t->attr_cnt; an++) { struct db_attr *a = &t->attrs[an]; out_attr(a->type, a->val , attr_desc, attr_val_descs , &trans_pbs); } close_output_pbs(&trans_pbs); } close_output_pbs(&proposal_pbs); } /* end of a conjunction of proposals */ } close_output_pbs(&sa_pbs); return TRUE; } /* Handle long form of duration attribute. * The code is can only handle values that can fit in unsigned long. * "Clamping" is probably an acceptable way to impose this limitation. */ static u_int32_t decode_long_duration(pb_stream *pbs) { u_int32_t val = 0; /* ignore leading zeros */ while (pbs_left(pbs) != 0 && *pbs->cur == '\0') pbs->cur++; if (pbs_left(pbs) > sizeof(val)) { /* "clamp" too large value to max representable value */ val -= 1; /* portable way to get to maximum value */ DBG(DBG_PARSING, DBG_log(" too large duration clamped to: %lu" , (unsigned long)val)); } else { /* decode number */ while (pbs_left(pbs) != 0) val = (val << BITS_PER_BYTE) | *pbs->cur++; DBG(DBG_PARSING, DBG_log(" long duration: %lu", (unsigned long)val)); } return val; } /* Parse the body of an ISAKMP SA Payload (i.e. Phase 1 / Main Mode). * Various shortcuts are taken. In particular, the policy, such as * it is, is hardwired. * * If r_sa is non-NULL, the body of an SA representing the selected * proposal is emitted. * * If "selection" is true, the SA is supposed to represent the * single tranform that the peer has accepted. * ??? We only check that it is acceptable, not that it is one that we offered! * * Only IPsec DOI is accepted (what is the ISAKMP DOI?). * Error response is rudimentary. * * This routine is used by main_inI1_outR1() and main_inR1_outI2(). */ notification_t parse_isakmp_sa_body( pb_stream *sa_pbs, /* body of input SA Payload */ const struct isakmp_sa *sa, /* header of input SA Payload */ pb_stream *r_sa_pbs, /* if non-NULL, where to emit winning SA */ bool selection, /* if this SA is a selection, only one tranform can appear */ struct state *st) /* current state object */ { u_int32_t ipsecdoisit; pb_stream proposal_pbs; struct isakmp_proposal proposal; unsigned no_trans_left; int last_transnum; /* DOI */ if (sa->isasa_doi != ISAKMP_DOI_IPSEC) { loglog(RC_LOG_SERIOUS, "Unknown/unsupported DOI %s", enum_show(&doi_names, sa->isasa_doi)); /* XXX Could send notification back */ return DOI_NOT_SUPPORTED; } /* Situation */ if (!in_struct(&ipsecdoisit, &ipsec_sit_desc, sa_pbs, NULL)) { return SITUATION_NOT_SUPPORTED; } if (ipsecdoisit != SIT_IDENTITY_ONLY) { loglog(RC_LOG_SERIOUS, "unsupported IPsec DOI situation (%s)" , bitnamesof(sit_bit_names, ipsecdoisit)); /* XXX Could send notification back */ return SITUATION_NOT_SUPPORTED; } /* The rules for ISAKMP SAs are scattered. * draft-ietf-ipsec-isakmp-oakley-07.txt section 5 says that there * can only be SA, and it can have only one proposal in it. * There may well be multiple transforms. */ if (!in_struct(&proposal, &isakmp_proposal_desc, sa_pbs, &proposal_pbs)) return PAYLOAD_MALFORMED; if (proposal.isap_np != ISAKMP_NEXT_NONE) { loglog(RC_LOG_SERIOUS, "Proposal Payload must be alone in Oakely SA; found %s in Proposal" , enum_show(&payload_names, proposal.isap_np)); return PAYLOAD_MALFORMED; } if (proposal.isap_protoid != PROTO_ISAKMP) { loglog(RC_LOG_SERIOUS, "unexpected Protocol ID (%s) found in Oakley Proposal" , enum_show(&protocol_names, proposal.isap_protoid)); return INVALID_PROTOCOL_ID; } /* Just what should we accept for the SPI field? * The RFC is sort of contradictory. We will ignore the SPI * as long as it is of the proper size. * * From RFC2408 2.4 Identifying Security Associations: * During phase 1 negotiations, the initiator and responder cookies * determine the ISAKMP SA. Therefore, the SPI field in the Proposal * payload is redundant and MAY be set to 0 or it MAY contain the * transmitting entity's cookie. * * From RFC2408 3.5 Proposal Payload: * o SPI Size (1 octet) - Length in octets of the SPI as defined by * the Protocol-Id. In the case of ISAKMP, the Initiator and * Responder cookie pair from the ISAKMP Header is the ISAKMP SPI, * therefore, the SPI Size is irrelevant and MAY be from zero (0) to * sixteen (16). If the SPI Size is non-zero, the content of the * SPI field MUST be ignored. If the SPI Size is not a multiple of * 4 octets it will have some impact on the SPI field and the * alignment of all payloads in the message. The Domain of * Interpretation (DOI) will dictate the SPI Size for other * protocols. */ if (proposal.isap_spisize == 0) { /* empty (0) SPI -- fine */ } else if (proposal.isap_spisize <= MAX_ISAKMP_SPI_SIZE) { u_char junk_spi[MAX_ISAKMP_SPI_SIZE]; if (!in_raw(junk_spi, proposal.isap_spisize, &proposal_pbs, "Oakley SPI")) return PAYLOAD_MALFORMED; } else { loglog(RC_LOG_SERIOUS, "invalid SPI size (%u) in Oakley Proposal" , (unsigned)proposal.isap_spisize); return INVALID_SPI; } if (selection && proposal.isap_notrans != 1) { loglog(RC_LOG_SERIOUS, "a single Transform is required in a selecting Oakley Proposal; found %u", (unsigned)proposal.isap_notrans); return BAD_PROPOSAL_SYNTAX; } /* for each transform payload... */ last_transnum = -1; no_trans_left = proposal.isap_notrans; for (;;) { pb_stream trans_pbs; u_char *attr_start; size_t attr_len; struct isakmp_transform trans; lset_t seen_attrs = 0, seen_durations = 0; u_int16_t life_type; struct oakley_trans_attrs ta; char ugh[100]; /* mailbox for diagnostics */ ugh[0] = '\0'; /* empty mailbox */ /* initialize only optional field in ta */ ta.life_seconds = OAKLEY_ISAKMP_SA_LIFETIME_DEFAULT; /* When this SA expires (seconds) */ if (no_trans_left == 0) { loglog(RC_LOG_SERIOUS, "number of Transform Payloads disagrees with Oakley Proposal Payload"); return BAD_PROPOSAL_SYNTAX; } if (!in_struct(&trans, &isakmp_isakmp_transform_desc, &proposal_pbs, &trans_pbs)) return BAD_PROPOSAL_SYNTAX; if (trans.isat_transnum <= last_transnum) { /* picky, picky, picky */ loglog(RC_LOG_SERIOUS, "Transform Numbers are not monotonically increasing" " in Oakley Proposal"); return BAD_PROPOSAL_SYNTAX; } last_transnum = trans.isat_transnum; if (trans.isat_transid != KEY_IKE) { loglog(RC_LOG_SERIOUS, "expected KEY_IKE but found %s in Oakley Transform" , enum_show(&isakmp_transformid_names, trans.isat_transid)); return INVALID_TRANSFORM_ID; } attr_start = trans_pbs.cur; attr_len = pbs_left(&trans_pbs); /* process all the attributes that make up the transform */ while (pbs_left(&trans_pbs) != 0) { struct isakmp_attribute a; pb_stream attr_pbs; enum_names *vdesc; u_int32_t val; /* room for larger values */ if (!in_struct(&a, &isakmp_oakley_attribute_desc, &trans_pbs, &attr_pbs)) return BAD_PROPOSAL_SYNTAX; passert((a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK) < 32); if (seen_attrs & LELEM(a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK)) { loglog(RC_LOG_SERIOUS, "repeated %s attribute in Oakley Transform %u" , enum_show(&oakley_attr_names, a.isaat_af_type) , trans.isat_transnum); return BAD_PROPOSAL_SYNTAX; } seen_attrs |= LELEM(a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK); val = a.isaat_lv; vdesc = oakley_attr_val_descs[a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK]; if (vdesc != NULL) { if (enum_name(vdesc, val) == NULL) { loglog(RC_LOG_SERIOUS, "invalid value %u for attribute %s in Oakley Transform" , (unsigned)val, enum_show(&oakley_attr_names, a.isaat_af_type)); return ATTRIBUTES_NOT_SUPPORTED; } DBG(DBG_PARSING, if ((a.isaat_af_type & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV) DBG_log(" [%u is %s]", (unsigned)val, enum_show(vdesc, val))); } switch (a.isaat_af_type) { case OAKLEY_ENCRYPTION_ALGORITHM | ISAKMP_ATTR_AF_TV: switch (val) { #if 0 /* we don't feel DES is safe */ case OAKLEY_DES_CBC: #endif case OAKLEY_3DES_CBC: ta.encrypt = val; ta.encrypter = &oakley_encrypter[val]; break; default: snprintf(ugh, sizeof(ugh), "%s is not supported" , enum_show(&oakley_enc_names, val)); } break; case OAKLEY_HASH_ALGORITHM | ISAKMP_ATTR_AF_TV: switch (val) { case OAKLEY_MD5: case OAKLEY_SHA: ta.hash = val; ta.hasher = &oakley_hasher[val]; break; default: snprintf(ugh, sizeof(ugh), "%s is not supported", enum_show(&oakley_hash_names, val)); } break; case OAKLEY_AUTHENTICATION_METHOD | ISAKMP_ATTR_AF_TV: switch (val) { case OAKLEY_PRESHARED_KEY: /* Accept if policy specifies PSK or is default */ if ((st->st_policy & POLICY_PSK) || (st->st_policy & POLICY_ISAKMP_MASK) == LEMPTY) { /* check that we can find a preshared secret */ if (get_preshared_secret(st->st_connection) == NULL) snprintf(ugh, sizeof(ugh) , "Can't authenticate: no preshared key"); ta.auth = val; } else { snprintf(ugh, sizeof(ugh) , "policy does not allow OAKLEY_PRESHARED_KEY authentication"); } break; #ifndef NO_RSA /* if this is defined, don't accept OAKLEY_RSA_SIG */ case OAKLEY_RSA_SIG: /* Accept if policy specifies RSASIG or is default */ if ((st->st_policy & POLICY_RSASIG) || (st->st_policy & POLICY_ISAKMP_MASK) == LEMPTY) { /* We'd like to check that we can find a public * key for him and a private key for us that is * suitable, but we don't yet have his * Id Payload, so it seems futile to try. * We can assume that if he proposes it, he * thinks we've got it. If we proposed it, * perhaps we know what we're doing. */ ta.auth = val; } else { snprintf(ugh, sizeof(ugh) , "policy does not allow OAKLEY_RSA_SIG authentication"); } break; #endif default: snprintf(ugh, sizeof(ugh) , "Pluto does not support %s authentication" , enum_show(&oakley_auth_names, val)); break; } break; case OAKLEY_GROUP_DESCRIPTION | ISAKMP_ATTR_AF_TV: ta.group = lookup_group(val); if (ta.group == NULL) { snprintf(ugh, sizeof(ugh), "only OAKLEY_GROUP_MODP768, OAKLEY_GROUP_MODP1024, and OAKLEY_GROUP_MODP1536 supported"); break; } break; case OAKLEY_LIFE_TYPE | ISAKMP_ATTR_AF_TV: if (seen_durations & LELEM(val)) { loglog(RC_LOG_SERIOUS, "attribute OAKLEY_LIFE_TYPE value %s repeated" , enum_show(&oakley_lifetime_names, val)); return FALSE; } seen_durations |= LELEM(val); life_type = val; break; case OAKLEY_LIFE_DURATION | ISAKMP_ATTR_AF_TLV: val = decode_long_duration(&attr_pbs); /* fall through */ case OAKLEY_LIFE_DURATION | ISAKMP_ATTR_AF_TV: if ((seen_attrs & LELEM(OAKLEY_LIFE_DURATION)) == 0) { snprintf(ugh, sizeof(ugh), "OAKLEY_LIFE_DURATION attribute not preceded by OAKLEY_LIFE_TYPE attribute"); break; } seen_attrs &= ~(LELEM(OAKLEY_LIFE_DURATION) | LELEM(OAKLEY_LIFE_TYPE)); switch (life_type) { case OAKLEY_LIFE_SECONDS: if (val > OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM) snprintf(ugh, sizeof(ugh), "peer requested %lu seconds" " which exceeds our limit %d seconds" , (long) val , OAKLEY_ISAKMP_SA_LIFETIME_MAXIMUM); ta.life_seconds = val; break; case OAKLEY_LIFE_KILOBYTES: ta.life_kilobytes = val; break; default: passert(FALSE); } break; #if 0 /* not yet supported */ case OAKLEY_GROUP_TYPE | ISAKMP_ATTR_AF_TV: case OAKLEY_PRF | ISAKMP_ATTR_AF_TV: case OAKLEY_KEY_LENGTH | ISAKMP_ATTR_AF_TV: case OAKLEY_FIELD_SIZE | ISAKMP_ATTR_AF_TV: case OAKLEY_GROUP_PRIME | ISAKMP_ATTR_AF_TV: case OAKLEY_GROUP_PRIME | ISAKMP_ATTR_AF_TLV: case OAKLEY_GROUP_GENERATOR_ONE | ISAKMP_ATTR_AF_TV: case OAKLEY_GROUP_GENERATOR_ONE | ISAKMP_ATTR_AF_TLV: case OAKLEY_GROUP_GENERATOR_TWO | ISAKMP_ATTR_AF_TV: case OAKLEY_GROUP_GENERATOR_TWO | ISAKMP_ATTR_AF_TLV: case OAKLEY_GROUP_CURVE_A | ISAKMP_ATTR_AF_TV: case OAKLEY_GROUP_CURVE_A | ISAKMP_ATTR_AF_TLV: case OAKLEY_GROUP_CURVE_B | ISAKMP_ATTR_AF_TV: case OAKLEY_GROUP_CURVE_B | ISAKMP_ATTR_AF_TLV: case OAKLEY_GROUP_ORDER | ISAKMP_ATTR_AF_TV: case OAKLEY_GROUP_ORDER | ISAKMP_ATTR_AF_TLV: #endif default: snprintf(ugh, sizeof(ugh), "unsupported OAKLEY attribute"); break; } if (ugh[0] != '\0') { loglog(RC_LOG_SERIOUS, "%s. Attribute %s" , ugh, enum_show(&oakley_attr_names, a.isaat_af_type)); break; } } if (ugh[0] == '\0') { /* a little more checking is in order */ { unsigned long missing = ~seen_attrs & (LELEM(OAKLEY_ENCRYPTION_ALGORITHM) | LELEM(OAKLEY_HASH_ALGORITHM) | LELEM(OAKLEY_AUTHENTICATION_METHOD) | LELEM(OAKLEY_GROUP_DESCRIPTION)); if (missing) { loglog(RC_LOG_SERIOUS, "missing mandatory attribute(s) %s in Oakley Transform %u" , bitnamesof(oakley_attr_bit_names, missing) , trans.isat_transnum); return BAD_PROPOSAL_SYNTAX; } } /* We must have liked this transform. * Lets finish early and leave. */ DBG(DBG_PARSING | DBG_CRYPT, DBG_log("Oakley Transform %u accepted", trans.isat_transnum)); if (r_sa_pbs != NULL) { struct isakmp_proposal r_proposal = proposal; pb_stream r_proposal_pbs; struct isakmp_transform r_trans = trans; pb_stream r_trans_pbs; /* Situation */ if (!out_struct(&ipsecdoisit, &ipsec_sit_desc, r_sa_pbs, NULL)) passert(FALSE); /* Proposal */ #ifdef EMIT_ISAKMP_SPI r_proposal.isap_spisize = COOKIE_SIZE; #else r_proposal.isap_spisize = 0; #endif r_proposal.isap_notrans = 1; if (!out_struct(&r_proposal, &isakmp_proposal_desc, r_sa_pbs, &r_proposal_pbs)) passert(FALSE); /* SPI */ #ifdef EMIT_ISAKMP_SPI if (!out_raw(my_cookie, COOKIE_SIZE, &r_proposal_pbs, "SPI")) passert(FALSE); r_proposal.isap_spisize = COOKIE_SIZE; #else /* none (0) */ #endif /* Transform */ r_trans.isat_np = ISAKMP_NEXT_NONE; if (!out_struct(&r_trans, &isakmp_isakmp_transform_desc, &r_proposal_pbs, &r_trans_pbs)) passert(FALSE); if (!out_raw(attr_start, attr_len, &r_trans_pbs, "attributes")) passert(FALSE); close_output_pbs(&r_trans_pbs); close_output_pbs(&r_proposal_pbs); close_output_pbs(r_sa_pbs); } /* ??? If selection, we used to save the proposal in state. * We never used it. From proposal_pbs.start, * length pbs_room(&proposal_pbs) */ /* copy over the results */ st->st_oakley = ta; return NOTHING_WRONG; } /* on to next transform */ no_trans_left--; if (trans.isat_np == ISAKMP_NEXT_NONE) { if (no_trans_left != 0) { loglog(RC_LOG_SERIOUS, "number of Transform Payloads disagrees with Oakley Proposal Payload"); return BAD_PROPOSAL_SYNTAX; } break; } if (trans.isat_np != ISAKMP_NEXT_T) { loglog(RC_LOG_SERIOUS, "unexpected %s payload in Oakley Proposal" , enum_show(&payload_names, proposal.isap_np)); return BAD_PROPOSAL_SYNTAX; } } loglog(RC_LOG_SERIOUS, "no acceptable Oakley Transform"); return NO_PROPOSAL_CHOSEN; } /* Parse the body of an IPsec SA Payload (i.e. Phase 2 / Quick Mode). * * The main routine is parse_ipsec_sa_body; other functions defined * between here and there are just helpers. * * Various shortcuts are taken. In particular, the policy, such as * it is, is hardwired. * * If r_sa is non-NULL, the body of an SA representing the selected * proposal is emitted into it. * * If "selection" is true, the SA is supposed to represent the * single tranform that the peer has accepted. * ??? We only check that it is acceptable, not that it is one that we offered! * * Only IPsec DOI is accepted (what is the ISAKMP DOI?). * Error response is rudimentary. * * Since all ISAKMP groups in all SA Payloads must match, st->st_pfs_group * holds this across multiple payloads. * &unset_group signifies not yet "set"; NULL signifies NONE. * * This routine is used by quick_inI1_outR1() and quick_inR1_outI2(). */ static const struct ipsec_trans_attrs null_ipsec_trans_attrs = { 0, /* transid (NULL, for now) */ 0, /* spi */ SA_LIFE_DURATION_DEFAULT, /* life_seconds */ SA_LIFE_DURATION_K_DEFAULT, /* life_kilobytes */ ENCAPSULATION_MODE_UNSPECIFIED, /* encapsulation */ AUTH_ALGORITHM_NONE, /* auth */ 0, /* key_len */ 0, /* key_rounds */ }; static bool parse_ipsec_transform( struct isakmp_transform *trans, struct ipsec_trans_attrs *attrs, pb_stream *prop_pbs, pb_stream *trans_pbs, struct_desc *trans_desc, int previous_transnum, /* or -1 if none */ bool selection, bool is_last, struct state *st) /* current state object */ { lset_t seen_attrs = 0, seen_durations = 0; u_int16_t life_type; const struct oakley_group_desc *pfs_group = NULL; if (!in_struct(trans, trans_desc, prop_pbs, trans_pbs)) return FALSE; if (trans->isat_transnum <= previous_transnum) { loglog(RC_LOG_SERIOUS, "Transform Numbers in Proposal are not monotonically increasing"); return FALSE; } switch (trans->isat_np) { case ISAKMP_NEXT_T: if (is_last) { loglog(RC_LOG_SERIOUS, "Proposal Payload has more Transforms than specified"); return FALSE; } break; case ISAKMP_NEXT_NONE: if (!is_last) { loglog(RC_LOG_SERIOUS, "Proposal Payload has fewer Transforms than specified"); return FALSE; } break; default: loglog(RC_LOG_SERIOUS, "expecting Transform Payload, but found %s in Proposal" , enum_show(&payload_names, trans->isat_np)); return FALSE; } *attrs = null_ipsec_trans_attrs; attrs->transid = trans->isat_transid; while (pbs_left(trans_pbs) != 0) { struct isakmp_attribute a; pb_stream attr_pbs; enum_names *vdesc; u_int32_t val; /* room for larger value */ if (!in_struct(&a, &isakmp_ipsec_attribute_desc, trans_pbs, &attr_pbs)) return FALSE; passert((a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK) < 32); if (seen_attrs & LELEM(a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK)) { loglog(RC_LOG_SERIOUS, "repeated %s attribute in IPsec Transform %u" , enum_show(&ipsec_attr_names, a.isaat_af_type) , trans->isat_transnum); return FALSE; } seen_attrs |= LELEM(a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK); val = a.isaat_lv; vdesc = ipsec_attr_val_descs[a.isaat_af_type & ISAKMP_ATTR_RTYPE_MASK]; if (vdesc != NULL) { if (enum_name(vdesc, val) == NULL) { loglog(RC_LOG_SERIOUS, "invalid value %u for attribute %s in IPsec Transform" , (unsigned)val, enum_show(&ipsec_attr_names, a.isaat_af_type)); return FALSE; } DBG(DBG_PARSING, if ((a.isaat_af_type & ISAKMP_ATTR_AF_MASK) == ISAKMP_ATTR_AF_TV) DBG_log(" [%u is %s]", (unsigned)val, enum_show(vdesc, val))); } switch (a.isaat_af_type) { case SA_LIFE_TYPE | ISAKMP_ATTR_AF_TV: if (seen_durations & LELEM(val)) { loglog(RC_LOG_SERIOUS, "attribute SA_LIFE_TYPE value %s repeated in message" , enum_show(&sa_lifetime_names, val)); return FALSE; } seen_durations |= LELEM(val); life_type = val; break; case SA_LIFE_DURATION | ISAKMP_ATTR_AF_TLV: val = decode_long_duration(&attr_pbs); /* fall through */ case SA_LIFE_DURATION | ISAKMP_ATTR_AF_TV: if ((seen_attrs & LELEM(SA_LIFE_DURATION)) == 0) { loglog(RC_LOG_SERIOUS, "SA_LIFE_DURATION IPsec attribute not preceded by SA_LIFE_TYPE attribute"); return FALSE; } seen_attrs &= ~(LELEM(SA_LIFE_DURATION) | LELEM(SA_LIFE_TYPE)); switch (life_type) { case SA_LIFE_TYPE_SECONDS: /* silently limit duration to our maximum */ attrs->life_seconds = val <= SA_LIFE_DURATION_MAXIMUM ? val : SA_LIFE_DURATION_MAXIMUM; break; case SA_LIFE_TYPE_KBYTES: attrs->life_kilobytes = val; break; default: passert(FALSE); } break; case GROUP_DESCRIPTION | ISAKMP_ATTR_AF_TV: pfs_group = lookup_group(val); if (pfs_group == NULL) { loglog(RC_LOG_SERIOUS, "only OAKLEY_GROUP_MODP768, OAKLEY_GROUP_MODP1024, and OAKLEY_GROUP_MODP1536 supported for PFS"); return FALSE; } break; case ENCAPSULATION_MODE | ISAKMP_ATTR_AF_TV: attrs->encapsulation = val; break; case AUTH_ALGORITHM | ISAKMP_ATTR_AF_TV: attrs->auth = val; break; case KEY_LENGTH | ISAKMP_ATTR_AF_TV: attrs->key_len = val; break; case KEY_ROUNDS | ISAKMP_ATTR_AF_TV: attrs->key_rounds = val; break; #if 0 /* not yet implemented */ case COMPRESS_DICT_SIZE | ISAKMP_ATTR_AF_TV: break; case COMPRESS_PRIVATE_ALG | ISAKMP_ATTR_AF_TV: break; case SA_LIFE_DURATION | ISAKMP_ATTR_AF_TLV: break; case COMPRESS_PRIVATE_ALG | ISAKMP_ATTR_AF_TLV: break; #endif default: loglog(RC_LOG_SERIOUS, "unsupported IPsec attribute %s" , enum_show(&ipsec_attr_names, a.isaat_af_type)); return FALSE; } } if (st->st_pfs_group == &unset_group) st->st_pfs_group = pfs_group; if (st->st_pfs_group != pfs_group) { loglog(RC_LOG_SERIOUS, "GROUP_DESCRIPTION inconsistent with that of %s in IPsec SA" , selection? "the Proposal" : "a previous Transform"); return FALSE; } if ((seen_attrs & LELEM(SA_LIFE_DURATION)) != 0) { loglog(RC_LOG_SERIOUS, "SA_LIFE_TYPE IPsec attribute not followed by SA_LIFE_DURATION attribute in message"); return FALSE; } if ((seen_attrs & LELEM(ENCAPSULATION_MODE)) == 0) { /* ??? Technically, draft-ietf-ipsec-ipsec-doi-08.txt 4.5 * specifies that the default is "unspecified (host-dependent)". * This makes little sense, so we demand that it be specified. */ loglog(RC_LOG_SERIOUS, "IPsec Transform must specify ENCAPSULATION_MODE"); return FALSE; } /* ??? should check for key_len and/or key_rounds if required */ return TRUE; } static void echo_proposal( struct isakmp_proposal r_proposal, /* proposal to emit */ struct isakmp_transform r_trans, /* winning transformation within it */ u_int8_t np, /* Next Payload for proposal */ pb_stream *r_sa_pbs, /* SA PBS into which to emit */ struct ipsec_proto_info *pi, /* info about this protocol instance */ struct_desc *trans_desc, /* descriptor for this transformation */ pb_stream *trans_pbs) /* PBS for incoming transform */ { pb_stream r_proposal_pbs; pb_stream r_trans_pbs; /* Proposal */ r_proposal.isap_np = np; r_proposal.isap_notrans = 1; if (!out_struct(&r_proposal, &isakmp_proposal_desc, r_sa_pbs, &r_proposal_pbs)) passert(FALSE); /* allocate and emit our SPI */ pi->our_spi = get_ipsec_spi(pi->attrs.spi); out_raw((u_char *) &pi->our_spi, sizeof(pi->our_spi), &r_proposal_pbs, "SPI"); /* Transform */ r_trans.isat_np = ISAKMP_NEXT_NONE; if (!out_struct(&r_trans, trans_desc, &r_proposal_pbs, &r_trans_pbs)) passert(FALSE); /* Transform Attributes: pure echo */ trans_pbs->cur = trans_pbs->start + sizeof(struct isakmp_transform); if (!out_raw(trans_pbs->cur, pbs_left(trans_pbs) , &r_trans_pbs, "attributes")) passert(FALSE); close_output_pbs(&r_trans_pbs); close_output_pbs(&r_proposal_pbs); } notification_t parse_ipsec_sa_body( pb_stream *sa_pbs, /* body of input SA Payload */ const struct isakmp_sa *sa, /* header of input SA Payload */ pb_stream *r_sa_pbs, /* if non-NULL, where to emit body of winning SA */ bool selection, /* if this SA is a selection, only one transform may appear */ struct state *st) /* current state object */ { #ifdef DEBUG const struct connection *c = st->st_connection; #endif u_int32_t ipsecdoisit; pb_stream next_proposal_pbs; struct isakmp_proposal next_proposal; ipsec_spi_t next_spi; bool next_full = TRUE; /* DOI */ if (sa->isasa_doi != ISAKMP_DOI_IPSEC) { loglog(RC_LOG_SERIOUS, "Unknown or unsupported DOI %s", enum_show(&doi_names, sa->isasa_doi)); /* XXX Could send notification back */ return DOI_NOT_SUPPORTED; } /* Situation */ if (!in_struct(&ipsecdoisit, &ipsec_sit_desc, sa_pbs, NULL)) return SITUATION_NOT_SUPPORTED; if (ipsecdoisit != SIT_IDENTITY_ONLY) { loglog(RC_LOG_SERIOUS, "unsupported IPsec DOI situation (%s)" , bitnamesof(sit_bit_names, ipsecdoisit)); /* XXX Could send notification back */ return SITUATION_NOT_SUPPORTED; } /* The rules for IPsec SAs are scattered. * draft-ietf-ipsec-isakmp-09.txt section 4.2 gives some info. * There may be multiple proposals. Those with identical proposal * numbers must be considered as conjuncts. Those with different * numbers are disjuncts. * Each proposal may have several transforms, each considered * an alternative. * Each transform may have several attributes, all applying. * * To handle the way proposals are combined, we need to do a * look-ahead. */ if (!in_struct(&next_proposal, &isakmp_proposal_desc, sa_pbs, &next_proposal_pbs)) return BAD_PROPOSAL_SYNTAX; /* for each conjunction of proposals... */ while (next_full) { int propno = next_proposal.isap_proposal; pb_stream ah_prop_pbs, esp_prop_pbs; struct isakmp_proposal ah_proposal, esp_proposal; ipsec_spi_t ah_spi, esp_spi; bool ah_seen = FALSE, esp_seen = FALSE; pb_stream ah_trans_pbs, esp_trans_pbs; struct isakmp_transform ah_trans, esp_trans; struct ipsec_trans_attrs ah_attrs, esp_attrs; /* for each proposal in the conjunction */ do { if (next_proposal.isap_spisize != IPSEC_DOI_SPI_SIZE) { loglog(RC_LOG_SERIOUS, "IPsec Proposal with improper SPI size (%u)" , next_proposal.isap_spisize); return INVALID_SPI; } if (!in_raw((u_char *)&next_spi, sizeof(next_spi), &next_proposal_pbs, "SPI")) return INVALID_SPI; /* SPI value 0 is invalid and values 1-255 are reserved to IANA. * RFC 2402 (ESP) 2.4, RFC 2406 (AH) 2.1 * IPCOMP??? */ if (ntohl(next_spi) < IPSEC_DOI_SPI_MIN) { loglog(RC_LOG_SERIOUS, "IPsec Proposal contains invalid SPI (0x%lx)" , (unsigned long) ntohl(next_spi)); return INVALID_SPI; } if (next_proposal.isap_notrans == 0) { loglog(RC_LOG_SERIOUS, "IPsec Proposal contains no Transforms"); return BAD_PROPOSAL_SYNTAX; } switch (next_proposal.isap_protoid) { case PROTO_IPSEC_AH: if (ah_seen) { loglog(RC_LOG_SERIOUS, "IPsec SA contains two simultaneous AH Proposals"); return BAD_PROPOSAL_SYNTAX; } ah_seen = TRUE; ah_prop_pbs = next_proposal_pbs; ah_proposal = next_proposal; ah_spi = next_spi; break; case PROTO_IPSEC_ESP: if (esp_seen) { loglog(RC_LOG_SERIOUS, "IPsec SA contains two simultaneous ESP Proposals"); return BAD_PROPOSAL_SYNTAX; } esp_seen = TRUE; esp_prop_pbs = next_proposal_pbs; esp_proposal = next_proposal; esp_spi = next_spi; break; default: loglog(RC_LOG_SERIOUS, "unexpected Protocol ID (%s) in IPsec Proposal" , enum_show(&protocol_names, next_proposal.isap_protoid)); return INVALID_PROTOCOL_ID; } /* refill next_proposal */ if (next_proposal.isap_np == ISAKMP_NEXT_NONE) { next_full = FALSE; break; } else if (next_proposal.isap_np != ISAKMP_NEXT_P) { loglog(RC_LOG_SERIOUS, "unexpected in Proposal: %s" , enum_show(&payload_names, next_proposal.isap_np)); return BAD_PROPOSAL_SYNTAX; } if (!in_struct(&next_proposal, &isakmp_proposal_desc, sa_pbs, &next_proposal_pbs)) return BAD_PROPOSAL_SYNTAX; } while (next_proposal.isap_proposal == propno); /* Now that we have all conjuncts, we should try * the Cartesian product of eachs tranforms! * At the moment, we take short-cuts on account of * our rudimentary hard-wired policy. * For now, we find an acceptable AH (if any) * and then an acceptable ESP. The only interaction * is that the ESP acceptance can know whether there * was an acceptable AH and hence not require an AUTH. */ if (ah_seen) { int previous_transnum = -1; int tn; for (tn = 0; tn != ah_proposal.isap_notrans; tn++) { int ok_transid = 0; bool ok_auth = FALSE; if (!parse_ipsec_transform(&ah_trans , &ah_attrs , &ah_prop_pbs , &ah_trans_pbs , &isakmp_ah_transform_desc , previous_transnum , selection , tn == ah_proposal.isap_notrans - 1 , st)) return BAD_PROPOSAL_SYNTAX; previous_transnum = ah_trans.isat_transnum; /* we must understand ah_attrs.transid * COMBINED with ah_attrs.auth. * See draft-ietf-ipsec-ipsec-doi-08.txt 4.4.3 * The following combinations are legal, * but we don't implement all of them: * It seems as if each auth algorithm * only applies to one ah transid. * AH_MD5, AUTH_ALGORITHM_HMAC_MD5 * AH_MD5, AUTH_ALGORITHM_KPDK (unimplemented) * AH_SHA, AUTH_ALGORITHM_HMAC_SHA1 * AH_DES, AUTH_ALGORITHM_DES_MAC (unimplemented) */ switch (ah_attrs.auth) { case AUTH_ALGORITHM_NONE: loglog(RC_LOG_SERIOUS, "AUTH_ALGORITHM attribute missing in AH Transform"); return BAD_PROPOSAL_SYNTAX; case AUTH_ALGORITHM_HMAC_MD5: ok_auth = TRUE; /* fall through */ case AUTH_ALGORITHM_KPDK: ok_transid = AH_MD5; break; case AUTH_ALGORITHM_HMAC_SHA1: ok_auth = TRUE; ok_transid = AH_SHA; break; case AUTH_ALGORITHM_DES_MAC: ok_transid = AH_DES; break; } if (ah_attrs.transid != ok_transid) { loglog(RC_LOG_SERIOUS, "%s attribute inappropriate in %s Transform" , enum_name(&auth_alg_names, ah_attrs.auth) , enum_show(&ah_transformid_names, ah_attrs.transid)); return BAD_PROPOSAL_SYNTAX; } if (!ok_auth) { DBG(DBG_CONTROL | DBG_CRYPT, DBG_log("%s attribute unsupported" " in %s Transform from %s" , enum_name(&auth_alg_names, ah_attrs.auth) , enum_show(&ah_transformid_names, ah_attrs.transid) , inet_ntoa(c->that.host_addr))); continue; /* try another */ } break; /* we seem to be happy */ } if (tn == ah_proposal.isap_notrans) continue; /* we didn't find a nice one */ ah_attrs.spi = ah_spi; } if (esp_seen) { int previous_transnum = -1; int tn; for (tn = 0; tn != esp_proposal.isap_notrans; tn++) { if (!parse_ipsec_transform(&esp_trans , &esp_attrs , &esp_prop_pbs , &esp_trans_pbs , &isakmp_esp_transform_desc , previous_transnum , selection , tn == esp_proposal.isap_notrans - 1 , st)) return BAD_PROPOSAL_SYNTAX; previous_transnum = esp_trans.isat_transnum; switch (esp_attrs.transid) { #if 0 /* we don't feel single DES is safe */ case ESP_DES: #endif case ESP_3DES: break; #ifdef SUPPORT_ESP_NULL /* should be about as secure as AH */ case ESP_NULL: if (esp_attrs.auth == AUTH_ALGORITHM_NONE) { loglog(RC_LOG_SERIOUS, "ESP_NULL requires auth algorithm"); return BAD_PROPOSAL_SYNTAX; } if (st->st_policy & POLICY_ENCRYPT) { DBG(DBG_CONTROL | DBG_CRYPT, DBG_log("ESP_NULL Transform Proposal from %s" " does not satisfy POLICY_ENCRYPT" , inet_ntoa(c->that.host))); continue; /* try another */ } break; #endif default: DBG(DBG_CONTROL | DBG_CRYPT, DBG_log("unsupported ESP Transform %s from %s", enum_show(&esp_transformid_names, esp_attrs.transid), inet_ntoa(c->that.host_addr))); continue; /* try another */ } switch (esp_attrs.auth) { case AUTH_ALGORITHM_NONE: if (!ah_seen) { DBG(DBG_CONTROL | DBG_CRYPT, DBG_log("ESP from %s must either have AUTH or be combined with AH" , inet_ntoa(c->that.host_addr))); continue; /* try another */ } break; case AUTH_ALGORITHM_HMAC_MD5: case AUTH_ALGORITHM_HMAC_SHA1: break; default: DBG(DBG_CONTROL | DBG_CRYPT, DBG_log("unsupported ESP auth alg %s from %s", enum_show(&auth_alg_names, esp_attrs.auth), inet_ntoa(c->that.host_addr))); continue; /* try another */ } break; /* we seem to be happy */ } if (tn == esp_proposal.isap_notrans) continue; /* we didn't find a nice one */ esp_attrs.spi = esp_spi; } else if (st->st_policy & POLICY_ENCRYPT) { DBG(DBG_CONTROL | DBG_CRYPT, DBG_log("policy for \"%s\" requires encryption but ESP not in Proposal from %s" , c->name, inet_ntoa(c->that.host_addr))); continue; /* we needed encryption, but didn't find ESP */ } else if ((st->st_policy & POLICY_AUTHENTICATE) && !ah_seen) { DBG(DBG_CONTROL | DBG_CRYPT, DBG_log("policy for \"%s\" requires authentication" " but none in Proposal from %s" , c->name, inet_ntoa(c->that.host_addr))); continue; /* we need authentication, but we found neither ESP nor AH */ } /* Eureka: we liked what we saw -- accept it. */ if (r_sa_pbs != NULL) { /* emit what we've accepted */ /* Situation */ if (!out_struct(&ipsecdoisit, &ipsec_sit_desc, r_sa_pbs, NULL)) passert(FALSE); /* AH proposal */ if (ah_seen) echo_proposal(ah_proposal, ah_trans, esp_seen? ISAKMP_NEXT_P : ISAKMP_NEXT_NONE, r_sa_pbs, &st->st_ah, &isakmp_ah_transform_desc, &ah_trans_pbs); /* ESP proposal */ if (esp_seen) echo_proposal(esp_proposal, esp_trans, ISAKMP_NEXT_NONE, r_sa_pbs, &st->st_esp, &isakmp_esp_transform_desc, &esp_trans_pbs); close_output_pbs(r_sa_pbs); } /* save decoded version of winning SA in state */ st->st_ah.present = ah_seen; if (ah_seen) st->st_ah.attrs = ah_attrs; st->st_esp.present = esp_seen; if (esp_seen) st->st_esp.attrs = esp_attrs; return NOTHING_WRONG; } loglog(RC_LOG_SERIOUS, "no acceptable Proposal in IPsec SA"); return NO_PROPOSAL_CHOSEN; }