From: Sam Hartman Date: Fri, 20 Sep 2013 17:04:12 +0000 (-0400) Subject: Merge remote-tracking branch 'origin/eap-tls' X-Git-Tag: 0.9.2~39 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=mech_eap.git;a=commitdiff_plain;h=26311844916784cc0781b1a304b590dff5742fcb;hp=0a1171f9f4a213ba20c740c4ba2fac7a8ff423b4 Merge remote-tracking branch 'origin/eap-tls' The eap-tls branch includes build dependencies on openssl which we need for the sha2 hash support in IDP certs. The eap-tls changes are not widely exposed, but to the extent they are present are harmless. Conflicts: libeap/Makefile.am mech_eap/Makefile.am mech_eap/gssapiP_eap.h mech_eap/init_sec_context.c --- diff --git a/.gitignore b/.gitignore index 57a94cd..3674727 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ build-aux !build-aux/compile mech_eap.spec mech_eap*tar* +*.lo +*# diff --git a/acinclude.m4 b/acinclude.m4 index 401ad2a..9da57ed 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -17,17 +17,19 @@ AC_ARG_WITH(krb5, [Use krb5 (in specified installation directory)]), [check_krb5_dir="$withval"], [check_krb5_dir=]) -for dir in $check_krb5_dir $prefix /usr /usr/local ; do +for dir in $check_krb5_dir $prefix /usr/local /usr ; do krb5dir="$dir" if test -x "$dir/bin/krb5-config"; then found_krb5="yes"; if test "x$target_windows" = "xyes"; then KRB5_CFLAGS=-I"$check_krb5_dir/include"; - KRB5_LIBS="-L$check_krb5_dir/lib/ -lkrb5_32 -lgssapi32"; + KRB5_LDFLAGS="-L$check_krb5_dir/lib/"; + KRB5_LIBS="-lkrb5_32 -lgssapi32"; COMPILE_ET="$check_krb5_dir/bin/compile_et"; AC_MSG_RESULT([yes]) else KRB5_CFLAGS=`$dir/bin/krb5-config gssapi --cflags`; + KRB5_LDFLAGS="-L$dir/lib"; KRB5_LIBS=`$dir/bin/krb5-config gssapi --libs` AC_MSG_RESULT([yes]) AC_PATH_PROG(COMPILE_ET, [compile_et], [compile_et], [$dir/bin$PATH_SEPARATOr]) @@ -48,6 +50,7 @@ if test x_$found_krb5 != x_yes; then else printf "Kerberos found in $krb5dir\n"; AC_SUBST(KRB5_CFLAGS) + AC_SUBST(KRB5_LDFLAGS) AC_SUBST(KRB5_LIBS) AC_SUBST(COMPILE_ET) AC_CHECK_LIB(krb5, GSS_C_NT_COMPOSITE_EXPORT, [AC_DEFINE_UNQUOTED([HAVE_GSS_C_NT_COMPOSITE_EXPORT], 1, [Define if GSS-API library supports recent naming extensions draft])], [], "$KRB5_LIBS") diff --git a/build-aux/compile b/build-aux/compile index 5360806..c0096a7 100755 --- a/build-aux/compile +++ b/build-aux/compile @@ -18,8 +18,7 @@ scriptversion=2009-10-06.20; # UTC # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a diff --git a/configure.ac b/configure.ac index 1049dd7..4b8bfde 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ -AC_PREREQ([2.61]) -AC_INIT([mech_eap], [0.1], [bugs@project-moonshot.org]) +/AC_PREREQ([2.61]) +AC_INIT([mech_eap], [0.9], [bugs@project-moonshot.org]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR([build-aux]) diff --git a/libeap/Makefile.am b/libeap/Makefile.am index 8cc9fb5..a51424e 100644 --- a/libeap/Makefile.am +++ b/libeap/Makefile.am @@ -64,38 +64,38 @@ SOURCES_peer += src/eap_peer/eap_tls_common.c \ src/eap_peer/mschapv2.h \ src/eap_peer/tncc.h -CFLAGS += -DEAP_TLS -CFLAGS += -DEAP_PEAP -CFLAGS += -DEAP_TTLS -CFLAGS += -DEAP_MD5 -CFLAGS += -DEAP_MSCHAPv2 -CFLAGS += -DEAP_GTC -CFLAGS += -DEAP_OTP -CFLAGS += -DEAP_LEAP -CFLAGS += -DEAP_PSK -CFLAGS += -DEAP_PAX -CFLAGS += -DEAP_SAKE -CFLAGS += -DEAP_GPSK -DEAP_GPSK_SHA256 +AM_CFLAGS = -DEAP_TLS +AM_CFLAGS += -DEAP_PEAP +AM_CFLAGS += -DEAP_TTLS +AM_CFLAGS += -DEAP_MD5 +AM_CFLAGS += -DEAP_MSCHAPv2 +AM_CFLAGS += -DEAP_GTC +AM_CFLAGS += -DEAP_OTP +AM_CFLAGS += -DEAP_LEAP +AM_CFLAGS += -DEAP_PSK +AM_CFLAGS += -DEAP_PAX +AM_CFLAGS += -DEAP_SAKE +AM_CFLAGS += -DEAP_GPSK -DEAP_GPSK_SHA256 -CFLAGS += -DEAP_SERVER_IDENTITY -CFLAGS += -DEAP_SERVER_TLS -CFLAGS += -DEAP_SERVER_PEAP -CFLAGS += -DEAP_SERVER_TTLS -CFLAGS += -DEAP_SERVER_MD5 -CFLAGS += -DEAP_SERVER_MSCHAPV2 -CFLAGS += -DEAP_SERVER_GTC -CFLAGS += -DEAP_SERVER_PSK -CFLAGS += -DEAP_SERVER_PAX -CFLAGS += -DEAP_SERVER_SAKE -CFLAGS += -DEAP_SERVER_GPSK -DEAP_SERVER_GPSK_SHA256 +AM_CFLAGS += -DEAP_SERVER_IDENTITY +AM_CFLAGS += -DEAP_SERVER_TLS +AM_CFLAGS += -DEAP_SERVER_PEAP +AM_CFLAGS += -DEAP_SERVER_TTLS +AM_CFLAGS += -DEAP_SERVER_MD5 +AM_CFLAGS += -DEAP_SERVER_MSCHAPV2 +AM_CFLAGS += -DEAP_SERVER_GTC +AM_CFLAGS += -DEAP_SERVER_PSK +AM_CFLAGS += -DEAP_SERVER_PAX +AM_CFLAGS += -DEAP_SERVER_SAKE +AM_CFLAGS += -DEAP_SERVER_GPSK -DEAP_SERVER_GPSK_SHA256 -CFLAGS += -DIEEE8021X_EAPOL -CFLAGS += -DCONFIG_IPV6 +AM_CFLAGS += -DIEEE8021X_EAPOL +AM_CFLAGS += -DCONFIG_IPV6 -CFLAGS += -DCONFIG_INTERNAL_LIBTOMMATH -CFLAGS += -DCONFIG_INTERNAL_SHA1 -CFLAGS += -DEAP_TLS_OPENSSL -CFLAGS += -DPKCS12_FUNCS +AM_CFLAGS += -DCONFIG_INTERNAL_LIBTOMMATH +AM_CFLAGS += -DCONFIG_INTERNAL_SHA1 +AM_CFLAGS += -DEAP_TLS_OPENSSL +AM_CFLAGS += -DPKCS12_FUNCS UTILS_SRCS = src/utils/base64.c \ src/utils/common.c \ @@ -106,6 +106,8 @@ UTILS_SRCS = src/utils/base64.c \ src/utils/wpa_debug.c \ src/utils/wpabuf.c \ src/utils/os_unix.c \ + src/utils/radius_utils.c \ +src/utils/radius_utils.h \ src/utils/base64.h \ src/utils/build_config.h \ src/utils/common.h \ diff --git a/libeap/src/eap_common/eap_ttls.h b/libeap/src/eap_common/eap_ttls.h index 09b6083..08f371c 100644 --- a/libeap/src/eap_common/eap_ttls.h +++ b/libeap/src/eap_common/eap_ttls.h @@ -35,6 +35,7 @@ struct ttls_avp_vendor { /* Data */ }; + #define AVP_FLAGS_VENDOR 0x80 #define AVP_FLAGS_MANDATORY 0x40 @@ -52,6 +53,7 @@ do { \ #define RADIUS_ATTR_USER_PASSWORD 2 #define RADIUS_ATTR_CHAP_PASSWORD 3 #define RADIUS_ATTR_REPLY_MESSAGE 18 +#define RADIUS_ATTR_VENDOR_SPECIFIC 26 #define RADIUS_ATTR_CHAP_CHALLENGE 60 #define RADIUS_ATTR_EAP_MESSAGE 79 @@ -72,6 +74,9 @@ do { \ #define EAP_TTLS_CHAP_CHALLENGE_LEN 16 #define EAP_TTLS_CHAP_PASSWORD_LEN 16 +#define RADIUS_VENDOR_ID_UKERNA 25622 +#define RADIUS_ATTR_UKERNA_CHBIND 135 + #ifdef __cplusplus } #endif diff --git a/libeap/src/eap_peer/eap_config.h b/libeap/src/eap_peer/eap_config.h index cb381bc..c78fba1 100644 --- a/libeap/src/eap_peer/eap_config.h +++ b/libeap/src/eap_peer/eap_config.h @@ -19,6 +19,30 @@ extern "C" { #endif +/* http://tools.ietf.org/html/draft-ietf-emu-chbind-13#section-5.3.1 */ +#define CHBIND_CODE_REQUEST 1 +#define CHBIND_CODE_SUCCESS 2 +#define CHBIND_CODE_FAILURE 3 +/* http://tools.ietf.org/html/draft-ietf-emu-chbind-13#section-5.3. */ +#define CHBIND_NSID_RADIUS 1 + +struct eap_peer_chbind_config +{ + /* namespace id for this channel binding info */ + int nsid; + + /* data to be sent in channel binding request */ + u8 *req_data; + + size_t req_data_len; + + /* lower level callback invoked when response is received */ + void (*response_cb)(void *ctx, int code, int nsid, u8 *resp_data, size_t resp_data_len); + + /* context for response callback */ + void *ctx; +}; + /** * struct eap_peer_config - EAP peer configuration/credentials */ @@ -628,6 +652,16 @@ struct eap_peer_config { */ int fragment_size; + /** + * chbind_config - eap channel binding config data + */ + struct eap_peer_chbind_config *chbind_config; + + /** + * chbind_config_len - channel binding config data count + */ + size_t chbind_config_len; + #define EAP_CONFIG_FLAGS_PASSWORD_NTHASH BIT(0) /** * flags - Network configuration flags (bitfield) diff --git a/libeap/src/eap_peer/eap_ttls.c b/libeap/src/eap_peer/eap_ttls.c index 2573780..70827e4 100644 --- a/libeap/src/eap_peer/eap_ttls.c +++ b/libeap/src/eap_peer/eap_ttls.c @@ -15,6 +15,7 @@ #include "includes.h" #include "common.h" +#include "radius/radius.h" #include "crypto/ms_funcs.h" #include "crypto/sha1.h" #include "crypto/tls.h" @@ -73,7 +74,9 @@ struct eap_ttls_data { u8 *key_data; struct wpabuf *pending_phase2_req; - + int chbind_req_sent; /* channel binding request was sent */ + int done_butfor_cb; /*we turned METHOD_DONE into METHOD_MAY_CONT to receive cb*/ + EapDecision cbDecision; #ifdef EAP_TNC int ready_for_tnc; int tnc_started; @@ -81,6 +84,23 @@ struct eap_ttls_data { }; +/* draft-ietf-emu-chbind-13 section 5.3 */ + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif /* _MSC_VER */ + +struct chbind_hdr { + u16 len; + u8 nsid; +} STRUCT_PACKED; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif /* _MSC_VER */ + + + static void * eap_ttls_init(struct eap_sm *sm) { struct eap_ttls_data *data; @@ -245,6 +265,48 @@ static int eap_ttls_avp_encapsulate(struct wpabuf **resp, u32 avp_code, return 0; } +/* chop up resp into multiple vsa's as necessary*/ +static int eap_ttls_avp_radius_vsa_encapsulate(struct wpabuf **resp, u32 vendor, + u8 attr, int mandatory) +{ + struct wpabuf *msg; + u8 *avp, *pos, *src, *final; + size_t size = wpabuf_len(*resp); + size_t num_msgs = 1 + (size / 248); + size_t msg_wrapper_size = sizeof(struct ttls_avp_vendor) + 6; + size_t allocated_total = num_msgs * (4 + msg_wrapper_size) + size; + + msg = wpabuf_alloc(allocated_total); + if (msg == NULL) { + wpabuf_free(*resp); + *resp = NULL; + return -1; + } + src = wpabuf_mhead(*resp); + avp = wpabuf_mhead(msg); + while (size > 0) { + int avp_size = size > 248 ? 248 : size; + size -= avp_size; + pos = eap_ttls_avp_hdr(avp, RADIUS_ATTR_VENDOR_SPECIFIC, 0, mandatory, + avp_size+6); + wpabuf_put(msg, pos-avp); + wpabuf_put_be32(msg, vendor); + wpabuf_put_u8(msg, (u8) attr); + wpabuf_put_u8(msg, (u8) avp_size+2); + wpabuf_put_data(msg, src, avp_size); + src += avp_size; + pos = wpabuf_mhead_u8(msg) + wpabuf_len(msg); + final = pos; /*keep pos so we know how much padding is added*/ + AVP_PAD(avp, final); /*final modified*/ + if (final > pos) + wpabuf_put(msg, final-pos); + avp = final; + } + /* check avp-wpabuf_mhead(msg) < allocated_total */ + wpabuf_free(*resp); + *resp = msg; + return 0; +} #if EAP_TTLS_VERSION > 0 static int eap_ttls_ia_permute_inner_secret(struct eap_sm *sm, @@ -1061,6 +1123,8 @@ struct ttls_parse_avp { u8 *mschapv2; u8 *eapdata; size_t eap_len; + u8 *chbind_data; + size_t chbind_len; int mschapv2_error; }; @@ -1094,6 +1158,38 @@ static int eap_ttls_parse_attr_eap(const u8 *dpos, size_t dlen, } +static int eap_ttls_parse_attr_chbind(const u8 *dpos, size_t dlen, + struct ttls_parse_avp *parse) +{ + wpa_printf(MSG_DEBUG, "EAP-TTLS: AVP - Channel Binding Message"); + + if (parse->chbind_data == NULL) { + parse->chbind_data = os_malloc(dlen); + if (parse->chbind_data == NULL) { + wpa_printf(MSG_WARNING, "EAP-TTLS: Failed to allocate " + "memory for Phase 2 channel binding data"); + return -1; + } + os_memcpy(parse->chbind_data, dpos, dlen); + parse->chbind_len = dlen; + } else { + /* TODO: can this really happen? maybe just make this an error? */ + u8 *newchbind = os_realloc(parse->chbind_data, + parse->chbind_len + dlen); + if (newchbind == NULL) { + wpa_printf(MSG_WARNING, "EAP-TTLS: Failed to allocate " + "memory for Phase 2 channel binding data"); + return -1; + } + os_memcpy(newchbind + parse->chbind_len, dpos, dlen); + parse->chbind_data = newchbind; + parse->chbind_len += dlen; + } + + return 0; +} + + static int eap_ttls_parse_avp(u8 *pos, size_t left, struct ttls_parse_avp *parse) { @@ -1144,6 +1240,11 @@ static int eap_ttls_parse_avp(u8 *pos, size_t left, if (vendor_id == 0 && avp_code == RADIUS_ATTR_EAP_MESSAGE) { if (eap_ttls_parse_attr_eap(dpos, dlen, parse) < 0) return -1; + } else if (vendor_id == RADIUS_VENDOR_ID_UKERNA && + avp_code == RADIUS_ATTR_UKERNA_CHBIND) { + /* message containing channel binding data */ + if (eap_ttls_parse_attr_chbind(dpos, dlen, parse) < 0) + return -1; } else if (vendor_id == 0 && avp_code == RADIUS_ATTR_REPLY_MESSAGE) { /* This is an optional message that can be displayed to * the user. */ @@ -1265,6 +1366,100 @@ static int eap_ttls_encrypt_response(struct eap_sm *sm, return 0; } +static int eap_ttls_add_chbind_request(struct eap_sm *sm, + struct eap_ttls_data *data, + struct wpabuf **resp) +{ + struct wpabuf *chbind_req, *res; + int length = 1, i; + struct eap_peer_config *config = eap_get_config(sm); + + if (!config->chbind_config || config->chbind_config_len <= 0) + return -1; + + for (i=0; ichbind_config_len; i++) { + length += 3 + config->chbind_config[i].req_data_len; + } + + chbind_req = wpabuf_alloc(length); + if (!chbind_req) + return -1; + + wpabuf_put_u8(chbind_req, CHBIND_CODE_REQUEST); + for (i=0; ichbind_config_len; i++) { + struct eap_peer_chbind_config *chbind_config = + &config->chbind_config[i]; + wpabuf_put_be16(chbind_req, chbind_config->req_data_len); + wpabuf_put_u8(chbind_req, chbind_config->nsid); + wpabuf_put_data(chbind_req, chbind_config->req_data, + chbind_config->req_data_len); + } + if (eap_ttls_avp_radius_vsa_encapsulate(&chbind_req, + RADIUS_VENDOR_ID_UKERNA, + RADIUS_ATTR_UKERNA_CHBIND, 0) < 0) + return -1; + + /* bleh. This will free *resp regardless of whether combined buffer + alloc succeeds, which is not consistent with the other error + condition behavior in this function */ + *resp = wpabuf_concat(chbind_req, *resp); + + return (*resp) ? 0 : -1; +} + + +static int eap_ttls_process_chbind(struct eap_sm *sm, + struct eap_ttls_data *data, + struct eap_method_ret *ret, + struct ttls_parse_avp *parse, + struct wpabuf **resp) +{ + size_t pos=0; + u8 code; + u16 len; + struct chbind_hdr *hdr; + struct eap_peer_config *config = eap_get_config(sm); + + if (parse->chbind_data == NULL) { + wpa_printf(MSG_WARNING, "EAP-TTLS: No channel binding message " + "in the packet - dropped"); + return -1; + } + if (parse->chbind_len < 1 + sizeof(*hdr)) { + wpa_printf(MSG_WARNING, "EAP-TTLS: bad channel binding response " + "frame (len=%lu, expected %lu or more) - dropped", + (unsigned long) parse->chbind_len, + (unsigned long) sizeof(*hdr)); + return -1; + } + code = parse->chbind_data[pos++]; + while (pos+sizeof(*hdr) < parse->chbind_len) { + hdr = (struct chbind_hdr *)(&parse->chbind_data[pos]); + pos += sizeof(*hdr); + len = be_to_host16(hdr->len); + if (pos + len <= parse->chbind_len) { + int i; + for (i=0; ichbind_config_len; i++) { + struct eap_peer_chbind_config *chbind_config = + &config->chbind_config[i]; + if (chbind_config->nsid == hdr->nsid) + chbind_config->response_cb( + chbind_config->ctx, + code, hdr->nsid, + &parse->chbind_data[pos], len); + } + } + pos += len; + } + if (pos != parse->chbind_len) { + wpa_printf(MSG_WARNING, "EAP-TTLS: bad channel binding response " + "frame (parsed len=%lu, expected %lu) - dropped", + (unsigned long) pos, + (unsigned long) parse->chbind_len); + return -1; + } + return 0; +} static int eap_ttls_process_phase2_eap(struct eap_sm *sm, struct eap_ttls_data *data, @@ -1438,6 +1633,19 @@ static int eap_ttls_process_decrypted(struct eap_sm *sm, phase2_type = EAP_TTLS_PHASE2_EAP; #endif /* EAP_TNC */ + /* handle channel binding response here */ + if (parse->chbind_data) { + /* received channel binding repsonse */ + if (eap_ttls_process_chbind(sm, data, ret, parse, &resp) < 0) + return -1; + if (data->done_butfor_cb) { + ret->methodState = METHOD_DONE; + ret->decision = data->cbDecision; + data->phase2_success = 1; + return 1; /*request ack*/ + } + } + switch (phase2_type) { case EAP_TTLS_PHASE2_EAP: if (eap_ttls_process_phase2_eap(sm, data, ret, parse, &resp) < @@ -1477,16 +1685,32 @@ static int eap_ttls_process_decrypted(struct eap_sm *sm, #endif /* EAP_TNC */ } + if (!resp && (config->pending_req_identity || + config->pending_req_password || + config->pending_req_otp || + config->pending_req_new_password)) { + wpabuf_free(data->pending_phase2_req); + data->pending_phase2_req = wpabuf_dup(in_decrypted); + return 0; + } + + /* issue channel binding request when appropriate */ + if (config->chbind_config && config->chbind_config_len > 0 && + !data->chbind_req_sent) { + if (eap_ttls_add_chbind_request(sm, data, &resp) < 0) + return -1; + data->chbind_req_sent = 1; + if (ret->methodState == METHOD_DONE) { + data->done_butfor_cb = 1; + data->cbDecision = ret->decision; + ret->methodState = METHOD_MAY_CONT; + } + } + if (resp) { if (eap_ttls_encrypt_response(sm, data, resp, identifier, out_data) < 0) return -1; - } else if (config->pending_req_identity || - config->pending_req_password || - config->pending_req_otp || - config->pending_req_new_password) { - wpabuf_free(data->pending_phase2_req); - data->pending_phase2_req = wpabuf_dup(in_decrypted); } return 0; diff --git a/libeap/src/utils/common.h b/libeap/src/utils/common.h index 96ff59c..2868df7 100644 --- a/libeap/src/utils/common.h +++ b/libeap/src/utils/common.h @@ -76,7 +76,12 @@ static inline unsigned int bswap_32(unsigned int v) #endif /* __SYMBIAN32__ */ #ifdef CONFIG_NATIVE_WINDOWS +#ifdef CONFIG_IPV6 +#include +#include +#else #include +#endif typedef int socklen_t; @@ -87,7 +92,9 @@ typedef int socklen_t; #endif /* CONFIG_NATIVE_WINDOWS */ #ifdef _MSC_VER +#ifndef __cplusplus #define inline __inline +#endif #undef vsnprintf #define vsnprintf _vsnprintf diff --git a/libeap/src/utils/radius_utils.c b/libeap/src/utils/radius_utils.c new file mode 100644 index 0000000..b5b8e80 --- /dev/null +++ b/libeap/src/utils/radius_utils.c @@ -0,0 +1,133 @@ +/* + * RADIUS tlv construction and parsing utilites + * Copyright (c) 2012, Painless Security, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" + +#include "common.h" + +#include "radius/radius.h" +#include "radius_utils.h" +#include "wpabuf.h" + +int radius_add_tlv(struct wpabuf **buf, u32 type, u32 vendor, u8 *data, + size_t len) +{ + u8 base_type; + u8 total; + if (vendor) { + if (len + 6 > RADIUS_MAX_ATTR_LEN) + return -1; + total = len + 2 + 6; + base_type = RADIUS_ATTR_VENDOR_SPECIFIC; + } else { + if (len > RADIUS_MAX_ATTR_LEN) + return -1; + total = len + 2; + base_type = type; + } + + /* ensure buffer has enough space */ + if (wpabuf_resize(buf, total)) + return -1; + + /* write into buffer */ + wpabuf_put_u8(*buf, base_type); + wpabuf_put_u8(*buf, total); + if (vendor) { + wpabuf_put_be32(*buf, vendor); + wpabuf_put_u8(*buf, (u8 )type); + wpabuf_put_u8(*buf, (u8 )len+2); + } + wpabuf_put_data(*buf, data, len); + return 0; +} + +struct radius_parser_struct +{ + u8 *data; + size_t len; + size_t pos; +}; + +radius_parser radius_parser_start(void *tlvdata, size_t len) +{ + radius_parser parser = malloc(sizeof(struct radius_parser_struct)); + if (parser) { + parser->data = (u8 *)tlvdata; + parser->len = len; + parser->pos = 0; + } + return parser; +} + +void radius_parser_finish(radius_parser parser) +{ + free(parser); +} + +int radius_parser_parse_tlv(radius_parser parser, u8 *type, u32 *vendor_id, + void **value, size_t *len) +{ + u8 rawtype, rawlen; + if (!parser) + return -1; + if (parser->len < parser->pos + 3) + return -1; + rawtype = parser->data[parser->pos]; + rawlen = parser->data[parser->pos+1]; + if (parser->len < parser->pos + rawlen) + return -1; + + if (rawtype == RADIUS_ATTR_VENDOR_SPECIFIC) { + if (rawlen < 7) + return -1; + *vendor_id = WPA_GET_BE24(&parser->data[parser->pos + 3]); + *value = &parser->data[parser->pos + 6]; + *len = rawlen - 6; + } else { + if (rawlen < 3) + return -1; + + *value = &parser->data[parser->pos + 2]; + *len = rawlen - 2; + } + *type = rawtype; + + parser->pos += rawlen; + return 0; +} + +int radius_parser_parse_vendor_specific(radius_parser parser, u8 *vendor_type, + void **value, size_t *len) +{ + u8 rawtype, rawlen; + if (!parser) + return -1; + if (parser->len < parser->pos + 3) + return -1; + rawtype = parser->data[parser->pos]; + rawlen = parser->data[parser->pos+1]; + if (parser->len < parser->pos + rawlen) + return -1; + + if (rawlen < 3) + return -1; + + *value = &parser->data[parser->pos + 2]; + *len = rawlen - 2; + *vendor_type = rawtype; + + parser->pos += rawlen; + return 0; +} diff --git a/libeap/src/utils/radius_utils.h b/libeap/src/utils/radius_utils.h new file mode 100644 index 0000000..b0560a4 --- /dev/null +++ b/libeap/src/utils/radius_utils.h @@ -0,0 +1,75 @@ +/* + * RADIUS tlv construction and parsing utilites + * Copyright (c) 2012, Painless Security, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#ifndef RADIUS_UTILS_H +#define RADIUS_UTILS_H + +struct wpabuf; +struct radius_parser_struct; +typedef struct radius_parser_struct *radius_parser; + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Simple utility to add a single type-length-value attribute to a buffer. + * Currently, there is no dictionary support: 'type' and 'len' are always + * assumed to be octets, and data is placed directly into buf untranslated + * for byte order. If vendor is zero, len should be no greater than 253 + * otherwise, no greater than 247. + * returns 0 on success, -1 on failure (allocation failure or len too large) + */ +int radius_add_tlv(struct wpabuf **buf, u32 type, u32 vendor, u8 *data, + size_t len); + +/* + * simple radius parser + * Could be made considerably simpler by dropping support for parsing multiple + * sub-attributes from a vsa. + */ + +/* + * create parser object + */ +radius_parser radius_parser_start(void *tlvdata, size_t len); + +/* + * parse a single tlv; + * There is no dictionary support; if the tlv is a vsa (attribute 26), + * sub-attributes are not immediately parsed: instead, the raw data is returned + * in 'value'. + * returns 0 on success, -1 on failure (malformed buffer or end of buffer) + */ +int radius_parser_parse_tlv(radius_parser parser, u8 *type, u32 *vendor_id, + void **value, size_t *len); + +/* + * parse a single sub-attribute of a vsa: assumes octets for + * vendor_type and len + * returns 0 on success, -1 on failure (malformed buffer or end of buffer) + */ +int radius_parser_parse_vendor_specific(radius_parser parser, u8 *vendor_type, + void **value, size_t *len); + +/* + * destroy parser object + */ +void radius_parser_finish(radius_parser parser); + +#ifdef __cplusplus +} +#endif + +#endif /* RADIUS_UTILS_H */ \ No newline at end of file diff --git a/mech_eap.spec.in b/mech_eap.spec.in index 90ac6cf..83984c6 100644 --- a/mech_eap.spec.in +++ b/mech_eap.spec.in @@ -14,8 +14,8 @@ BuildRequires: %{_moonshot_krb5} >= 1.9.1 BuildRequires: moonshot-ui-devel BuildRequires: jansson-devel Requires: moonshot-ui -BuildRequires: libradsec-devel -BuildRequires: shibboleth-devel >= 2.5 +BuildRequires: libradsec-devel >= 0.0.3 +BuildRequires: shibboleth-sp-devel >= 2.5 BuildRequires: libshibresolver-devel @@ -29,8 +29,7 @@ Project Moonshot provides federated access management. %build - export LDFLAGS='-L/usr/%{_lib}/freeradius -Wl,--rpath=/usr/%{_lib}/freeradius' -%configure --with-libmoonshot=%{_prefix} --with-krb5=%{_prefix} --disable-reauth +%configure --with-libmoonshot=%{_prefix} --with-krb5=%{_prefix} --enable-reauth LDFLAGs="${LDFLAGS} -L/opt/shibboleth/%{_lib} -Wl,--rpath=/opt/shibboleth/%{_lib}" CPPFLAGS="${CPPFLAGS} -I/opt/shibboleth/include" make %{?_smp_mflags} @@ -51,7 +50,7 @@ rm -rf $RPM_BUILD_ROOT %{_libdir}/gss/mech_eap.so %exclude %{_libdir}/gss/mech_eap.la %{_includedir}/gssapi/*.h -#%exclude %{_libdir}/krb5/plugins/authdata/*la +%exclude %{_libdir}/krb5/plugins/authdata/* #%{_libdir}/krb5/plugins/authdata/*.so diff --git a/mech_eap/Makefile.am b/mech_eap/Makefile.am index a652182..145188f 100644 --- a/mech_eap/Makefile.am +++ b/mech_eap/Makefile.am @@ -42,7 +42,8 @@ mech_eap_la_CXXFLAGS += \ @TARGET_CFLAGS@ $(EAP_CFLAGS) mech_eap_la_LDFLAGS = -avoid-version -module \ -export-symbols $(GSSEAP_EXPORTS) -no-undefined \ - @RADSEC_LDFLAGS@ @OPENSSL_LDFLAGS@ @TARGET_LDFLAGS@ + @KRB5_LDFLAGS@ @RADSEC_LDFLAGS@ @TARGET_LDFLAGS@ @OPENSSL_LDFLAGS@ + if TARGET_WINDOWS mech_eap_la_LDFLAGS += -debug endif @@ -108,6 +109,7 @@ mech_eap_la_SOURCES = \ util_name.c \ util_oid.c \ util_ordering.c \ + util_radius.cpp \ util_sm.c \ util_tld.c \ util_token.c \ @@ -147,8 +149,7 @@ mech_eap_la_SOURCES += \ set_name_attribute.c \ util_attr.cpp \ util_base64.c \ - util_json.cpp \ - util_radius.cpp + util_json.cpp if OPENSAML mech_eap_la_SOURCES += util_saml.cpp diff --git a/mech_eap/README b/mech_eap/README index 3cb2d50..c16ef62 100644 --- a/mech_eap/README +++ b/mech_eap/README @@ -111,7 +111,7 @@ Sample usage is given below. Substitute , and appropriately ( is the name of the host running the server, not the RADIUS server). -% gss-client -port 5555 -spnego -mech "{1 3 6 1 4 1 5322 22 1 18}" \ +% gss-client -port 5555 -spnego -mech "{1 3 6 1 5 5 15 1 1 18}" \ -user @ -pass host@ \ "Testing GSS EAP" % gss-server -port 5555 -export host@ diff --git a/mech_eap/README.samba4 b/mech_eap/README.samba4 index d0a94d1..cf58667 100644 --- a/mech_eap/README.samba4 +++ b/mech_eap/README.samba4 @@ -16,8 +16,8 @@ Shibboleth * Add a mapping from the PAC RADIUS attribute to urn:mspac: in the file /usr/local/etc/shibboleth/attribute-map.xml: - + FreeRADIUS ---------- @@ -46,7 +46,7 @@ on OS X it was necessary to start it with -M single to function under gdb. For the client, the GSS EAP mechanism can be specified on the command line: -smbclient --password samba --mechanism 1.3.6.1.4.1.5322.22.1.18 '\\host\share'". +smbclient --password samba --mechanism 1.3.6.1.5.5.15.1.1.18 '\\host\share'". There is no Moonshot SSPI implementation as yet, so it is not possible to test with a Windows client. diff --git a/mech_eap/TODO b/mech_eap/TODO index 0111459..78d92c8 100644 --- a/mech_eap/TODO +++ b/mech_eap/TODO @@ -1,3 +1,4 @@ +- draft-ietf-radext-radius-extensions - integration with initiator-side EAP channel bindings - investigate initiator-side credential locking - always intern OIDs so they never need to be freed diff --git a/mech_eap/accept_sec_context.c b/mech_eap/accept_sec_context.c index b089bae..95e0e0d 100644 --- a/mech_eap/accept_sec_context.c +++ b/mech_eap/accept_sec_context.c @@ -59,7 +59,7 @@ static OM_uint32 acceptReadyEap(OM_uint32 *minor, gss_ctx_id_t ctx, gss_cred_id_t cred) { OM_uint32 major, tmpMinor; - VALUE_PAIR *vp; + rs_const_avp *vp; gss_buffer_desc nameBuf = GSS_C_EMPTY_BUFFER; /* Cache encryption type derived from selected mechanism OID */ @@ -72,9 +72,10 @@ acceptReadyEap(OM_uint32 *minor, gss_ctx_id_t ctx, gss_cred_id_t cred) major = gssEapRadiusGetRawAvp(minor, ctx->acceptorCtx.vps, PW_USER_NAME, 0, &vp); - if (major == GSS_S_COMPLETE && vp->length) { - nameBuf.length = vp->length; - nameBuf.value = vp->vp_strvalue; + if (major == GSS_S_COMPLETE && rs_avp_length(vp) != 0) { + rs_avp_octets_value_byref((rs_avp *)vp, + (unsigned char **)&nameBuf.value, + &nameBuf.length); } else { ctx->gssFlags |= GSS_C_ANON_FLAG; } @@ -88,15 +89,15 @@ acceptReadyEap(OM_uint32 *minor, gss_ctx_id_t ctx, gss_cred_id_t cred) return major; major = gssEapRadiusGetRawAvp(minor, ctx->acceptorCtx.vps, - PW_MS_MPPE_SEND_KEY, VENDORPEC_MS, &vp); + PW_MS_MPPE_SEND_KEY, VENDORPEC_MICROSOFT, &vp); if (GSS_ERROR(major)) { *minor = GSSEAP_KEY_UNAVAILABLE; return GSS_S_UNAVAILABLE; } major = gssEapDeriveRfc3961Key(minor, - vp->vp_octets, - vp->length, + rs_avp_octets_value_const_ptr(vp), + rs_avp_length(vp), ctx->encryptionType, &ctx->rfc3961Key); if (GSS_ERROR(major)) @@ -287,7 +288,7 @@ importInitiatorIdentity(OM_uint32 *minor, static OM_uint32 setInitiatorIdentity(OM_uint32 *minor, gss_ctx_id_t ctx, - VALUE_PAIR **vps) + struct rs_packet *req) { OM_uint32 major, tmpMinor; gss_buffer_desc nameBuf; @@ -303,7 +304,7 @@ setInitiatorIdentity(OM_uint32 *minor, if (GSS_ERROR(major)) return major; - major = gssEapRadiusAddAvp(minor, vps, PW_USER_NAME, 0, &nameBuf); + major = gssEapRadiusAddAvp(minor, req, PW_USER_NAME, 0, &nameBuf); if (GSS_ERROR(major)) return major; @@ -320,7 +321,7 @@ setInitiatorIdentity(OM_uint32 *minor, static OM_uint32 setAcceptorIdentity(OM_uint32 *minor, gss_ctx_id_t ctx, - VALUE_PAIR **vps) + struct rs_packet *req) { OM_uint32 major; gss_buffer_desc nameBuf; @@ -349,9 +350,9 @@ setAcceptorIdentity(OM_uint32 *minor, /* Acceptor-Service-Name */ krbPrincComponentToGssBuffer(krbPrinc, 0, &nameBuf); - major = gssEapRadiusAddAvp(minor, vps, + major = gssEapRadiusAddAvp(minor, req, PW_GSS_ACCEPTOR_SERVICE_NAME, - VENDORPEC_UKERNA, + 0, &nameBuf); if (GSS_ERROR(major)) return major; @@ -359,47 +360,35 @@ setAcceptorIdentity(OM_uint32 *minor, /* Acceptor-Host-Name */ krbPrincComponentToGssBuffer(krbPrinc, 1, &nameBuf); - major = gssEapRadiusAddAvp(minor, vps, + major = gssEapRadiusAddAvp(minor, req, PW_GSS_ACCEPTOR_HOST_NAME, - VENDORPEC_UKERNA, + 0, &nameBuf); if (GSS_ERROR(major)) return major; if (KRB_PRINC_LENGTH(krbPrinc) > 2) { /* Acceptor-Service-Specific */ - krb5_principal_data ssiPrinc = *krbPrinc; - char *ssi; - - KRB_PRINC_LENGTH(&ssiPrinc) -= 2; - KRB_PRINC_NAME(&ssiPrinc) += 2; - - *minor = krb5_unparse_name_flags(krbContext, &ssiPrinc, - KRB5_PRINCIPAL_UNPARSE_NO_REALM, &ssi); + *minor = krbPrincUnparseServiceSpecifics(krbContext, + krbPrinc, &nameBuf); if (*minor != 0) return GSS_S_FAILURE; - nameBuf.value = ssi; - nameBuf.length = strlen(ssi); - - major = gssEapRadiusAddAvp(minor, vps, - PW_GSS_ACCEPTOR_SERVICE_SPECIFIC, - VENDORPEC_UKERNA, + major = gssEapRadiusAddAvp(minor, req, + PW_GSS_ACCEPTOR_SERVICE_SPECIFICS, + 0, &nameBuf); - - if (GSS_ERROR(major)) { - krb5_free_unparsed_name(krbContext, ssi); + krbFreeUnparsedName(krbContext, &nameBuf); + if (GSS_ERROR(major)) return major; - } - krb5_free_unparsed_name(krbContext, ssi); } krbPrincRealmToGssBuffer(krbPrinc, &nameBuf); if (nameBuf.length != 0) { /* Acceptor-Realm-Name */ - major = gssEapRadiusAddAvp(minor, vps, + major = gssEapRadiusAddAvp(minor, req, PW_GSS_ACCEPTOR_REALM_NAME, - VENDORPEC_UKERNA, + 0, &nameBuf); if (GSS_ERROR(major)) return major; @@ -469,7 +458,7 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor, struct rs_connection *rconn; struct rs_request *request = NULL; struct rs_packet *req = NULL, *resp = NULL; - struct radius_packet *frreq, *frresp; + int isAccessChallenge; if (ctx->acceptorCtx.radContext == NULL) { /* May be NULL from an imported partial context */ @@ -490,23 +479,22 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor, major = gssEapRadiusMapError(minor, rs_err_conn_pop(rconn)); goto cleanup; } - frreq = rs_packet_frpkt(req); - major = setInitiatorIdentity(minor, ctx, &frreq->vps); + major = setInitiatorIdentity(minor, ctx, req); if (GSS_ERROR(major)) goto cleanup; - major = setAcceptorIdentity(minor, ctx, &frreq->vps); + major = setAcceptorIdentity(minor, ctx, req); if (GSS_ERROR(major)) goto cleanup; - major = gssEapRadiusAddAvp(minor, &frreq->vps, + major = gssEapRadiusAddAvp(minor, req, PW_EAP_MESSAGE, 0, inputToken); if (GSS_ERROR(major)) goto cleanup; if (ctx->acceptorCtx.state.length != 0) { - major = gssEapRadiusAddAvp(minor, &frreq->vps, PW_STATE, 0, + major = gssEapRadiusAddAvp(minor, req, PW_STATE, 0, &ctx->acceptorCtx.state); if (GSS_ERROR(major)) goto cleanup; @@ -529,12 +517,15 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor, GSSEAP_ASSERT(resp != NULL); - frresp = rs_packet_frpkt(resp); - switch (frresp->code) { + isAccessChallenge = 0; + + switch (rs_packet_code(resp)) { case PW_ACCESS_CHALLENGE: - case PW_AUTHENTICATION_ACK: + isAccessChallenge = 1; + break; + case PW_ACCESS_ACCEPT: break; - case PW_AUTHENTICATION_REJECT: + case PW_ACCESS_REJECT: *minor = GSSEAP_RADIUS_AUTH_FAILURE; major = GSS_S_DEFECTIVE_CREDENTIAL; goto cleanup; @@ -546,23 +537,27 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor, break; } - major = gssEapRadiusGetAvp(minor, frresp->vps, PW_EAP_MESSAGE, 0, + major = gssEapRadiusGetAvp(minor, resp, PW_EAP_MESSAGE, 0, outputToken, TRUE); - if (major == GSS_S_UNAVAILABLE && frresp->code == PW_ACCESS_CHALLENGE) { + if (major == GSS_S_UNAVAILABLE && isAccessChallenge) { *minor = GSSEAP_MISSING_EAP_REQUEST; major = GSS_S_DEFECTIVE_TOKEN; goto cleanup; } else if (GSS_ERROR(major)) goto cleanup; - if (frresp->code == PW_ACCESS_CHALLENGE) { - major = gssEapRadiusGetAvp(minor, frresp->vps, PW_STATE, 0, + if (isAccessChallenge) { + major = gssEapRadiusGetAvp(minor, resp, PW_STATE, 0, &ctx->acceptorCtx.state, TRUE); if (GSS_ERROR(major) && *minor != GSSEAP_NO_SUCH_ATTR) goto cleanup; } else { - ctx->acceptorCtx.vps = frresp->vps; - frresp->vps = NULL; + rs_avp **vps; + + rs_packet_avps(resp, &vps); + + ctx->acceptorCtx.vps = *vps; + *vps = NULL; major = acceptReadyEap(minor, ctx, cred); if (GSS_ERROR(major)) @@ -639,39 +634,41 @@ eapGssSmAcceptGssChannelBindings(OM_uint32 *minor, gss_buffer_t outputToken GSSEAP_UNUSED, OM_uint32 *smFlags GSSEAP_UNUSED) { - OM_uint32 major; - gss_iov_buffer_desc iov[2]; + krb5_error_code code; + krb5_context krbContext; + krb5_data data; + krb5_checksum cksum; + krb5_boolean valid = FALSE; - iov[0].type = GSS_IOV_BUFFER_TYPE_DATA | GSS_IOV_BUFFER_FLAG_ALLOCATE; - iov[0].buffer.length = 0; - iov[0].buffer.value = NULL; + if (chanBindings == GSS_C_NO_CHANNEL_BINDINGS || + chanBindings->application_data.length == 0) + return GSS_S_CONTINUE_NEEDED; - iov[1].type = GSS_IOV_BUFFER_TYPE_STREAM | GSS_IOV_BUFFER_FLAG_ALLOCATED; + GSSEAP_KRB_INIT(&krbContext); - /* XXX necessary because decrypted in place and we verify it later */ - major = duplicateBuffer(minor, inputToken, &iov[1].buffer); - if (GSS_ERROR(major)) - return major; + KRB_DATA_INIT(&data); - major = gssEapUnwrapOrVerifyMIC(minor, ctx, NULL, NULL, - iov, 2, TOK_TYPE_WRAP); - if (GSS_ERROR(major)) { - gssEapReleaseIov(iov, 2); - return major; + gssBufferToKrbData(&chanBindings->application_data, &data); + + KRB_CHECKSUM_INIT(&cksum, ctx->checksumType, inputToken); + + code = krb5_c_verify_checksum(krbContext, &ctx->rfc3961Key, + KEY_USAGE_GSSEAP_CHBIND_MIC, + &data, &cksum, &valid); + if (code != 0) { + *minor = code; + return GSS_S_FAILURE; } - if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS && - !bufferEqual(&iov[0].buffer, &chanBindings->application_data)) { - major = GSS_S_BAD_BINDINGS; + if (valid == FALSE) { *minor = GSSEAP_BINDINGS_MISMATCH; - } else { - major = GSS_S_CONTINUE_NEEDED; - *minor = 0; + return GSS_S_BAD_BINDINGS; } - gssEapReleaseIov(iov, 2); + ctx->flags |= CTX_FLAG_CHANNEL_BINDINGS_VERIFIED; - return major; + *minor = 0; + return GSS_S_CONTINUE_NEEDED; } static OM_uint32 @@ -682,13 +679,27 @@ eapGssSmAcceptInitiatorMIC(OM_uint32 *minor, gss_OID mech GSSEAP_UNUSED, OM_uint32 reqFlags GSSEAP_UNUSED, OM_uint32 timeReq GSSEAP_UNUSED, - gss_channel_bindings_t chanBindings GSSEAP_UNUSED, + gss_channel_bindings_t chanBindings, gss_buffer_t inputToken, gss_buffer_t outputToken GSSEAP_UNUSED, OM_uint32 *smFlags GSSEAP_UNUSED) { OM_uint32 major; + /* + * The channel binding token is optional, however if the caller indicated + * bindings we must raise an error if it was absent. + * + * In the future, we might use a context option to allow the caller to + * indicate that missing bindings are acceptable. + */ + if (chanBindings != NULL && + chanBindings->application_data.length != 0 && + (ctx->flags & CTX_FLAG_CHANNEL_BINDINGS_VERIFIED) == 0) { + *minor = GSSEAP_MISSING_BINDINGS; + return GSS_S_BAD_BINDINGS; + } + major = gssEapVerifyTokenMIC(minor, ctx, inputToken); if (GSS_ERROR(major)) return major; @@ -807,7 +818,7 @@ static struct gss_eap_sm eapGssAcceptorSm[] = { ITOK_TYPE_GSS_CHANNEL_BINDINGS, ITOK_TYPE_NONE, GSSEAP_STATE_INITIATOR_EXTS, - SM_ITOK_FLAG_REQUIRED, + 0, eapGssSmAcceptGssChannelBindings, }, { @@ -828,6 +839,13 @@ static struct gss_eap_sm eapGssAcceptorSm[] = { #endif { ITOK_TYPE_NONE, + ITOK_TYPE_ACCEPTOR_NAME_RESP, + GSSEAP_STATE_ACCEPTOR_EXTS, + 0, + eapGssSmAcceptAcceptorName + }, + { + ITOK_TYPE_NONE, ITOK_TYPE_ACCEPTOR_MIC, GSSEAP_STATE_ACCEPTOR_EXTS, 0, @@ -873,13 +891,11 @@ gssEapAcceptSecContext(OM_uint32 *minor, * credential handle. */ - /* - * Calling gssEapInquireCred() forces the default acceptor credential name - * to be resolved. - */ - major = gssEapInquireCred(minor, cred, &ctx->acceptorName, NULL, NULL, NULL); - if (GSS_ERROR(major)) - goto cleanup; + if (cred->name != GSS_C_NO_NAME) { + major = gssEapDuplicateName(minor, cred->name, &ctx->acceptorName); + if (GSS_ERROR(major)) + goto cleanup; + } major = gssEapSmStep(minor, cred, diff --git a/mech_eap/compare_name.c b/mech_eap/compare_name.c index edadf3e..1da8354 100644 --- a/mech_eap/compare_name.c +++ b/mech_eap/compare_name.c @@ -42,5 +42,5 @@ gss_compare_name(OM_uint32 *minor, gss_name_t name2, int *name_equal) { - return gssEapCompareName(minor, name1, name2, name_equal); + return gssEapCompareName(minor, name1, name2, 0, name_equal); } diff --git a/mech_eap/dictionary.ukerna b/mech_eap/dictionary.ukerna index 1694566..235606f 100644 --- a/mech_eap/dictionary.ukerna +++ b/mech_eap/dictionary.ukerna @@ -9,11 +9,13 @@ VENDOR UKERNA 25622 BEGIN-VENDOR UKERNA -ATTRIBUTE GSS-Acceptor-Service-Name 128 string -ATTRIBUTE GSS-Acceptor-Host-Name 129 string -ATTRIBUTE GSS-Acceptor-Service-Specific 130 string -ATTRIBUTE GSS-Acceptor-Realm-Name 131 string -ATTRIBUTE SAML-AAA-Assertion 132 string -ATTRIBUTE MS-Windows-Auth-Data 133 octets +ATTRIBUTE GSS-Acceptor-Service-Name-VS 128 string +ATTRIBUTE GSS-Acceptor-Host-Name-VS 129 string +ATTRIBUTE GSS-Acceptor-Service-Specific-VS 130 string +ATTRIBUTE GSS-Acceptor-Realm-Name-VS 131 string +ATTRIBUTE SAML-AAA-Assertion 132 string +ATTRIBUTE MS-Windows-Auth-Data 133 octets +ATTRIBUTE MS-Windows-Group-Sid 134 string +ATTRIBUTE EAP-Channel-Binding-Message 135 octets END-VENDOR UKERNA diff --git a/mech_eap/export_sec_context.c b/mech_eap/export_sec_context.c index e5be6d8..3b74366 100644 --- a/mech_eap/export_sec_context.c +++ b/mech_eap/export_sec_context.c @@ -121,6 +121,11 @@ gssEapExportSecContext(OM_uint32 *minor, key.length = KRB_KEY_LENGTH(&ctx->rfc3961Key); key.value = KRB_KEY_DATA(&ctx->rfc3961Key); + /* + * As a shortcut, we omit the mechanism OID of the initiator name because + * we know it will match the context mechanism. The acceptor name mech OID + * is always included. + */ if (ctx->initiatorName != GSS_C_NO_NAME) { major = gssEapExportNameInternal(minor, ctx->initiatorName, &initiatorName, @@ -132,7 +137,7 @@ gssEapExportSecContext(OM_uint32 *minor, if (ctx->acceptorName != GSS_C_NO_NAME) { major = gssEapExportNameInternal(minor, ctx->acceptorName, &acceptorName, - EXPORT_NAME_FLAG_COMPOSITE); + EXPORT_NAME_FLAG_OID | EXPORT_NAME_FLAG_COMPOSITE); if (GSS_ERROR(major)) goto cleanup; } diff --git a/mech_eap/gssapiP_eap.h b/mech_eap/gssapiP_eap.h index eb7e7db..8a997d5 100644 --- a/mech_eap/gssapiP_eap.h +++ b/mech_eap/gssapiP_eap.h @@ -90,28 +90,11 @@ typedef const gss_OID_desc *gss_const_OID; #include #ifdef GSSEAP_ENABLE_ACCEPTOR -/* FreeRADIUS headers */ -#ifdef __cplusplus -extern "C" { -#ifndef WIN32 -#define operator fr_operator -#endif -#endif -#include -#include - -#undef pid_t - /* libradsec headers */ #include #include -#ifdef __cplusplus -#ifndef WIN32 -#undef operator +#include #endif -} -#endif -#endif /* GSSEAP_ENABLE_ACCEPTOR */ #include "gsseap_err.h" #include "radsec_err.h" @@ -182,6 +165,7 @@ struct gss_cred_id_struct #define CTX_FLAG_INITIATOR 0x00000001 #define CTX_FLAG_KRB_REAUTH 0x00000002 +#define CTX_FLAG_CHANNEL_BINDINGS_VERIFIED 0x00000004 #define CTX_IS_INITIATOR(ctx) (((ctx)->flags & CTX_FLAG_INITIATOR) != 0) @@ -197,6 +181,7 @@ struct gss_cred_id_struct #define CTX_FLAG_EAP_PORT_ENABLED 0x00400000 #define CTX_FLAG_EAP_ALT_ACCEPT 0x00800000 #define CTX_FLAG_EAP_ALT_REJECT 0x01000000 +#define CTX_FLAG_EAP_CHBIND_ACCEPT 0x02000000 #define CTX_FLAG_EAP_MASK 0xFFFF0000 #define CONFIG_BLOB_CLIENT_CERT 0 @@ -208,6 +193,8 @@ struct gss_eap_initiator_ctx { struct eap_peer_config eapPeerConfig; struct eap_sm *eap; struct wpabuf reqData; + struct wpabuf *chbindData; + unsigned int chbindReqFlags; struct wpa_config_blob configBlobs[CONFIG_BLOB_MAX]; }; @@ -217,7 +204,7 @@ struct gss_eap_acceptor_ctx { struct rs_connection *radConn; char *radServer; gss_buffer_desc state; - VALUE_PAIR *vps; + rs_avp *vps; }; #endif @@ -266,6 +253,10 @@ struct gss_ctx_id_struct #define KEY_USAGE_INITIATOR_SEAL 24 #define KEY_USAGE_INITIATOR_SIGN 25 +#define KEY_USAGE_GSSEAP_CHBIND_MIC 60 +#define KEY_USAGE_GSSEAP_ACCTOKEN_MIC 61 +#define KEY_USAGE_GSSEAP_INITOKEN_MIC 62 + /* accept_sec_context.c */ OM_uint32 gssEapAcceptSecContext(OM_uint32 *minor, @@ -347,9 +338,12 @@ gssEapDisplayStatus(OM_uint32 *minor, #define IS_WIRE_ERROR(err) ((err) > GSSEAP_RESERVED && \ (err) <= GSSEAP_RADIUS_PROT_FAILURE) -/* upper bound of RADIUS error range must be kept in sync with radsec.h */ +#ifdef GSSEAP_ENABLE_ACCEPTOR #define IS_RADIUS_ERROR(err) ((err) >= ERROR_TABLE_BASE_rse && \ - (err) <= ERROR_TABLE_BASE_rse + 20) + (err) <= ERROR_TABLE_BASE_rse + RSE_MAX) +#else +#define IS_RADIUS_ERROR(err) (0) +#endif /* exchange_meta_data.c */ OM_uint32 GSSAPI_CALLCONV @@ -386,7 +380,6 @@ gssEapPseudoRandom(OM_uint32 *minor, gss_ctx_id_t ctx, int prf_key, const gss_buffer_t prf_in, - ssize_t desired_output_len, gss_buffer_t prf_out); /* query_mechanism_info.c */ diff --git a/mech_eap/gsseap_err.et b/mech_eap/gsseap_err.et index d60c2c7..2f0774b 100644 --- a/mech_eap/gsseap_err.et +++ b/mech_eap/gsseap_err.et @@ -70,6 +70,8 @@ error_code GSSEAP_BAD_SERVICE_NAME, "Name is not a valid service nam error_code GSSEAP_BAD_INITIATOR_NAME, "Initiator identity must be a valid name" error_code GSSEAP_NO_HOSTNAME, "Could not determine local host name" error_code GSSEAP_NO_ACCEPTOR_NAME, "Could not determine acceptor identity" +error_code GSSEAP_WRONG_ACCEPTOR_NAME, "Acceptor identity different than expected" +error_code GSSEAP_BAD_ACCEPTOR_NAME, "Acceptor name is too long or has too many components" error_code GSSEAP_BAD_NAME_TOKEN, "Name token is malformed or corrupt" error_code GSSEAP_NO_LOCAL_MAPPING, "Unable to map name to a local identity" @@ -156,6 +158,7 @@ error_code GSSEAP_SHIB_LISTENER_FAILURE, "Failed to communicate with loca # Extensions # error_code GSSEAP_BINDINGS_MISMATCH, "Channel bindings do not match" +error_code GSSEAP_MISSING_BINDINGS, "Channel binding token missing" error_code GSSEAP_NO_MECHGLUE_SYMBOL, "Could not find symbol in mechanism glue" error_code GSSEAP_BAD_INVOCATION, "Bad mechanism invoke OID" diff --git a/mech_eap/import_sec_context.c b/mech_eap/import_sec_context.c index 1533a16..a0ebb8c 100644 --- a/mech_eap/import_sec_context.c +++ b/mech_eap/import_sec_context.c @@ -209,11 +209,12 @@ importKerberosKey(OM_uint32 *minor, static OM_uint32 importName(OM_uint32 *minor, + gss_OID mech, unsigned char **pBuf, size_t *pRemain, gss_name_t *pName) { - OM_uint32 major; + OM_uint32 major, tmpMinor, flags; unsigned char *p = *pBuf; size_t remain = *pRemain; gss_buffer_desc tmp; @@ -232,10 +233,21 @@ importName(OM_uint32 *minor, tmp.value = p + 4; - major = gssEapImportNameInternal(minor, &tmp, pName, - EXPORT_NAME_FLAG_COMPOSITE); + flags = EXPORT_NAME_FLAG_COMPOSITE; + if (mech == GSS_C_NO_OID) + flags |= EXPORT_NAME_FLAG_OID; + + major = gssEapImportNameInternal(minor, &tmp, pName, flags); if (GSS_ERROR(major)) return major; + + if ((flags & EXPORT_NAME_FLAG_OID) == 0) { + major = gssEapCanonicalizeOid(minor, mech, 0, &(*pName)->mechanismUsed); + if (GSS_ERROR(major)) { + gssEapReleaseName(&tmpMinor, pName); + return major; + } + } } *pBuf += 4 + tmp.length; @@ -288,11 +300,12 @@ gssEapImportContext(OM_uint32 *minor, if (GSS_ERROR(major)) return major; - major = importName(minor, &p, &remain, &ctx->initiatorName); + /* Initiator name OID matches the context mechanism, so it's not encoded */ + major = importName(minor, ctx->mechanismUsed, &p, &remain, &ctx->initiatorName); if (GSS_ERROR(major)) return major; - major = importName(minor, &p, &remain, &ctx->acceptorName); + major = importName(minor, GSS_C_NO_OID, &p, &remain, &ctx->acceptorName); if (GSS_ERROR(major)) return major; diff --git a/mech_eap/init_sec_context.c b/mech_eap/init_sec_context.c index a67d381..39929a6 100644 --- a/mech_eap/init_sec_context.c +++ b/mech_eap/init_sec_context.c @@ -36,6 +36,9 @@ */ #include "gssapiP_eap.h" +#include "radius/radius.h" +#include "util_radius.h" +#include "utils/radius_utils.h" static OM_uint32 policyVariableToFlag(enum eapol_bool_var variable) @@ -204,6 +207,143 @@ static struct eapol_callbacks gssEapPolicyCallbacks = { extern int wpa_debug_level; #endif +#define CHBIND_SERVICE_NAME_FLAG 0x01 +#define CHBIND_HOST_NAME_FLAG 0x02 +#define CHBIND_SERVICE_SPECIFIC_FLAG 0x04 +#define CHBIND_REALM_NAME_FLAG 0x08 + +static OM_uint32 +peerInitEapChannelBinding(OM_uint32 *minor, gss_ctx_id_t ctx) +{ + struct wpabuf *buf = NULL; + unsigned int chbindReqFlags = 0; + krb5_principal princ = NULL; + gss_buffer_desc nameBuf = GSS_C_EMPTY_BUFFER; + OM_uint32 major = GSS_S_COMPLETE; + krb5_context krbContext = NULL; + + /* XXX is this check redundant? */ + if (ctx->acceptorName == GSS_C_NO_NAME) { + major = GSS_S_BAD_NAME; + *minor = GSSEAP_NO_ACCEPTOR_NAME; + goto cleanup; + } + + princ = ctx->acceptorName->krbPrincipal; + + krbPrincComponentToGssBuffer(princ, 0, &nameBuf); + if (nameBuf.length > 0) { + major = gssEapRadiusAddAttr(minor, &buf, PW_GSS_ACCEPTOR_SERVICE_NAME, + 0, &nameBuf); + if (GSS_ERROR(major)) + goto cleanup; + + chbindReqFlags |= CHBIND_SERVICE_NAME_FLAG; + } + + krbPrincComponentToGssBuffer(princ, 1, &nameBuf); + if (nameBuf.length > 0) { + major = gssEapRadiusAddAttr(minor, &buf, PW_GSS_ACCEPTOR_HOST_NAME, + 0, &nameBuf); + if (GSS_ERROR(major)) + goto cleanup; + + chbindReqFlags |= CHBIND_HOST_NAME_FLAG; + } + + GSSEAP_KRB_INIT(&krbContext); + + *minor = krbPrincUnparseServiceSpecifics(krbContext, princ, &nameBuf); + if (*minor != 0) + goto cleanup; + + if (nameBuf.length > 0) { + major = gssEapRadiusAddAttr(minor, &buf, + PW_GSS_ACCEPTOR_SERVICE_SPECIFICS, + 0, &nameBuf); + if (GSS_ERROR(major)) + goto cleanup; + + chbindReqFlags |= CHBIND_SERVICE_SPECIFIC_FLAG; + } + + krbFreeUnparsedName(krbContext, &nameBuf); + krbPrincRealmToGssBuffer(princ, &nameBuf); + + if (nameBuf.length > 0) { + major = gssEapRadiusAddAttr(minor, &buf, + PW_GSS_ACCEPTOR_REALM_NAME, + 0, &nameBuf); + chbindReqFlags |= CHBIND_REALM_NAME_FLAG; + } + + if (chbindReqFlags == 0) { + major = GSS_S_BAD_NAME; + *minor = GSSEAP_BAD_ACCEPTOR_NAME; + goto cleanup; + } + + ctx->initiatorCtx.chbindData = buf; + ctx->initiatorCtx.chbindReqFlags = chbindReqFlags; + + buf = NULL; + + major = GSS_S_COMPLETE; + *minor = 0; + +cleanup: + krbFreeUnparsedName(krbContext, &nameBuf); + wpabuf_free(buf); + + return major; +} + +static void +peerProcessChbindResponse(void *context, int code, int nsid, + u8 *data, size_t len) +{ + radius_parser msg; + gss_ctx_id_t ctx = (gss_ctx_id_t )context; + void *vsadata; + u8 type; + u32 vendor_id; + u32 chbindRetFlags = 0; + size_t vsadata_len; + + if (nsid != CHBIND_NSID_RADIUS) + return; + + msg = radius_parser_start(data, len); + if (msg == NULL) + return; + + while (radius_parser_parse_tlv(msg, &type, &vendor_id, &vsadata, + &vsadata_len) == 0) { + switch (type) { + case PW_GSS_ACCEPTOR_SERVICE_NAME: + chbindRetFlags |= CHBIND_SERVICE_NAME_FLAG; + break; + case PW_GSS_ACCEPTOR_HOST_NAME: + chbindRetFlags |= CHBIND_HOST_NAME_FLAG; + break; + case PW_GSS_ACCEPTOR_SERVICE_SPECIFICS: + chbindRetFlags |= CHBIND_SERVICE_SPECIFIC_FLAG; + break; + case PW_GSS_ACCEPTOR_REALM_NAME: + chbindRetFlags |= CHBIND_REALM_NAME_FLAG; + break; + } + } + + radius_parser_finish(msg); + + if (code == CHBIND_CODE_SUCCESS && + ((chbindRetFlags & ctx->initiatorCtx.chbindReqFlags) == ctx->initiatorCtx.chbindReqFlags)) { + ctx->flags |= CTX_FLAG_EAP_CHBIND_ACCEPT; + ctx->gssFlags |= GSS_C_MUTUAL_FLAG; + } /* else log failures? */ +} + static OM_uint32 peerConfigInit(OM_uint32 *minor, gss_ctx_id_t ctx) { @@ -271,6 +411,26 @@ peerConfigInit(OM_uint32 *minor, gss_ctx_id_t ctx) eapPeerConfig->subject_match = (unsigned char *)cred->subjectNameConstraint.value; eapPeerConfig->altsubject_match = (unsigned char *)cred->subjectAltNameConstraint.value; + /* eap channel binding */ + if (ctx->initiatorCtx.chbindData != NULL) { + struct eap_peer_chbind_config *chbind_config = + (struct eap_peer_chbind_config *)GSSEAP_MALLOC(sizeof(struct eap_peer_chbind_config)); + if (chbind_config == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + chbind_config->req_data = wpabuf_mhead_u8(ctx->initiatorCtx.chbindData); + chbind_config->req_data_len = wpabuf_len(ctx->initiatorCtx.chbindData); + chbind_config->nsid = CHBIND_NSID_RADIUS; + chbind_config->response_cb = &peerProcessChbindResponse; + chbind_config->ctx = ctx; + eapPeerConfig->chbind_config = chbind_config; + eapPeerConfig->chbind_config_len = 1; + } else { + eapPeerConfig->chbind_config = NULL; + eapPeerConfig->chbind_config_len = 0; + } if (cred->flags & CRED_FLAG_CERTIFICATE) { /* * CRED_FLAG_CONFIG_BLOB is an internal flag which will be used in the @@ -322,18 +482,12 @@ peerConfigFree(OM_uint32 *minor, * Mark an initiator context as ready for cryptographic operations */ static OM_uint32 -initReady(OM_uint32 *minor, gss_ctx_id_t ctx, OM_uint32 reqFlags) +initReady(OM_uint32 *minor, gss_ctx_id_t ctx) { OM_uint32 major; const unsigned char *key; size_t keyLength; -#if 1 - /* XXX actually check for mutual auth */ - if (reqFlags & GSS_C_MUTUAL_FLAG) - ctx->gssFlags |= GSS_C_MUTUAL_FLAG; -#endif - /* Cache encryption type derived from selected mechanism OID */ major = gssEapOidToEnctype(minor, ctx->mechanismUsed, &ctx->encryptionType); if (GSS_ERROR(major)) @@ -595,17 +749,45 @@ eapGssSmInitAcceptorName(OM_uint32 *minor, outputToken, NULL); if (GSS_ERROR(major)) return major; - } else if (inputToken != GSS_C_NO_BUFFER && - ctx->acceptorName == GSS_C_NO_NAME) { - /* Accept target name hint from acceptor */ + } else if (inputToken != GSS_C_NO_BUFFER) { + OM_uint32 tmpMinor; + gss_name_t nameHint; + int equal; + + /* Accept target name hint from acceptor or verify acceptor */ major = gssEapImportName(minor, inputToken, GSS_C_NT_USER_NAME, ctx->mechanismUsed, - &ctx->acceptorName); + &nameHint); if (GSS_ERROR(major)) return major; + + if (ctx->acceptorName != GSS_C_NO_NAME) { + /* verify name hint matched asserted acceptor name */ + major = gssEapCompareName(minor, + nameHint, + ctx->acceptorName, + COMPARE_NAME_FLAG_IGNORE_EMPTY_REALMS, + &equal); + if (GSS_ERROR(major)) { + gssEapReleaseName(&tmpMinor, &nameHint); + return major; + } + + gssEapReleaseName(&tmpMinor, &nameHint); + + if (!equal) { + *minor = GSSEAP_WRONG_ACCEPTOR_NAME; + return GSS_S_DEFECTIVE_TOKEN; + } + } else { /* acceptor name is no_name */ + /* accept acceptor name hint */ + ctx->acceptorName = nameHint; + nameHint = GSS_C_NO_NAME; + } } + /* * Currently, other parts of the code assume that the acceptor name * is available, hence this check. @@ -615,6 +797,15 @@ eapGssSmInitAcceptorName(OM_uint32 *minor, return GSS_S_FAILURE; } + /* + * Generate channel binding data + */ + if (ctx->initiatorCtx.chbindData == NULL) { + major = peerInitEapChannelBinding(minor, ctx); + if (GSS_ERROR(major)) + return major; + } + return GSS_S_CONTINUE_NEEDED; } @@ -715,7 +906,7 @@ eapGssSmInitAuthenticate(OM_uint32 *minor, resp = eap_get_eapRespData(ctx->initiatorCtx.eap); } else if (ctx->flags & CTX_FLAG_EAP_SUCCESS) { - major = initReady(minor, ctx, reqFlags); + major = initReady(minor, ctx); if (GSS_ERROR(major)) goto cleanup; @@ -793,21 +984,45 @@ eapGssSmInitGssChannelBindings(OM_uint32 *minor, OM_uint32 *smFlags) { OM_uint32 major; - gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; + krb5_error_code code; + krb5_context krbContext; + krb5_data data; + krb5_checksum cksum; + gss_buffer_desc cksumBuffer; - if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS) - buffer = chanBindings->application_data; + if (chanBindings == GSS_C_NO_CHANNEL_BINDINGS || + chanBindings->application_data.length == 0) + return GSS_S_CONTINUE_NEEDED; - major = gssEapWrap(minor, ctx, TRUE, GSS_C_QOP_DEFAULT, - &buffer, NULL, outputToken); - if (GSS_ERROR(major)) - return major; + GSSEAP_KRB_INIT(&krbContext); - GSSEAP_ASSERT(outputToken->value != NULL); + KRB_DATA_INIT(&data); + + gssBufferToKrbData(&chanBindings->application_data, &data); + + code = krb5_c_make_checksum(krbContext, ctx->checksumType, + &ctx->rfc3961Key, + KEY_USAGE_GSSEAP_CHBIND_MIC, + &data, &cksum); + if (code != 0) { + *minor = code; + return GSS_S_FAILURE; + } + + cksumBuffer.length = KRB_CHECKSUM_LENGTH(&cksum); + cksumBuffer.value = KRB_CHECKSUM_DATA(&cksum); + + major = duplicateBuffer(minor, &cksumBuffer, outputToken); + if (GSS_ERROR(major)) { + krb5_free_checksum_contents(krbContext, &cksum); + return major; + } *minor = 0; *smFlags |= SM_FLAG_OUTPUT_TOKEN_CRITICAL; + krb5_free_checksum_contents(krbContext, &cksum); + return GSS_S_CONTINUE_NEEDED; } @@ -837,7 +1052,7 @@ eapGssSmInitInitiatorMIC(OM_uint32 *minor, return GSS_S_CONTINUE_NEEDED; } - + #ifdef GSSEAP_ENABLE_REAUTH static OM_uint32 eapGssSmInitReauthCreds(OM_uint32 *minor, @@ -884,6 +1099,11 @@ eapGssSmInitAcceptorMIC(OM_uint32 *minor, if (GSS_ERROR(major)) return major; + /* + * As a temporary measure, force mutual authentication until channel binding is + * more widely deployed. + */ + ctx->gssFlags |= GSS_C_MUTUAL_FLAG; GSSEAP_SM_TRANSITION(ctx, GSSEAP_STATE_ESTABLISHED); *minor = 0; @@ -902,7 +1122,8 @@ static struct gss_eap_sm eapGssInitiatorSm[] = { { ITOK_TYPE_ACCEPTOR_NAME_RESP, ITOK_TYPE_ACCEPTOR_NAME_REQ, - GSSEAP_STATE_INITIAL | GSSEAP_STATE_AUTHENTICATE, + GSSEAP_STATE_INITIAL | GSSEAP_STATE_AUTHENTICATE | + GSSEAP_STATE_ACCEPTOR_EXTS, 0, eapGssSmInitAcceptorName }, @@ -952,7 +1173,7 @@ static struct gss_eap_sm eapGssInitiatorSm[] = { ITOK_TYPE_NONE, ITOK_TYPE_GSS_CHANNEL_BINDINGS, GSSEAP_STATE_INITIATOR_EXTS, - SM_ITOK_FLAG_REQUIRED, + 0, eapGssSmInitGssChannelBindings }, { @@ -1052,8 +1273,10 @@ gssEapInitSecContext(OM_uint32 *minor, goto cleanup; } } + if (ret_flags != NULL) *ret_flags = ctx->gssFlags; + if (time_rec != NULL) gssEapContextTime(&tmpMinor, ctx, time_rec); diff --git a/mech_eap/inquire_context.c b/mech_eap/inquire_context.c index d37818d..305145c 100644 --- a/mech_eap/inquire_context.c +++ b/mech_eap/inquire_context.c @@ -57,15 +57,21 @@ gss_inquire_context(OM_uint32 *minor, GSSEAP_MUTEX_LOCK(&ctx->mutex); if (src_name != NULL) { - major = gssEapDuplicateName(minor, ctx->initiatorName, src_name); - if (GSS_ERROR(major)) - goto cleanup; + if (ctx->initiatorName != GSS_C_NO_NAME) { + major = gssEapDuplicateName(minor, ctx->initiatorName, src_name); + if (GSS_ERROR(major)) + goto cleanup; + } else + *src_name = GSS_C_NO_NAME; } if (targ_name != NULL) { - major = gssEapDuplicateName(minor, ctx->acceptorName, targ_name); - if (GSS_ERROR(major)) - goto cleanup; + if (ctx->acceptorName != GSS_C_NO_NAME) { + major = gssEapDuplicateName(minor, ctx->acceptorName, targ_name); + if (GSS_ERROR(major)) + goto cleanup; + } else + *targ_name = GSS_C_NO_NAME; } if (lifetime_rec != NULL) { diff --git a/mech_eap/inquire_sec_context_by_oid.c b/mech_eap/inquire_sec_context_by_oid.c index 7435f2e..bde7e1c 100644 --- a/mech_eap/inquire_sec_context_by_oid.c +++ b/mech_eap/inquire_sec_context_by_oid.c @@ -156,8 +156,16 @@ inquireNegoExKey(OM_uint32 *minor, keySize = KRB_KEY_LENGTH(&ctx->rfc3961Key); - major = gssEapPseudoRandom(minor, ctx, GSS_C_PRF_KEY_FULL, &salt, - keySize, &key); + key.value = GSSEAP_MALLOC(keySize); + if (key.value == NULL) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; + } + + key.length = keySize; + + major = gssEapPseudoRandom(minor, ctx, GSS_C_PRF_KEY_FULL, &salt, &key); if (GSS_ERROR(major)) goto cleanup; diff --git a/mech_eap/mech b/mech_eap/mech index 258a43a..3d2a2a8 100644 --- a/mech_eap/mech +++ b/mech_eap/mech @@ -4,5 +4,5 @@ # Any encryption type supported by Kerberos can be defined as the # last element of the OID arc. # -eap-aes128 1.3.6.1.4.1.5322.22.1.17 mech_eap.so -eap-aes256 1.3.6.1.4.1.5322.22.1.18 mech_eap.so +eap-aes128 1.3.6.1.5.5.15.1.1.17 mech_eap.so +eap-aes256 1.3.6.1.5.5.15.1.1.18 mech_eap.so diff --git a/mech_eap/pseudo_random.c b/mech_eap/pseudo_random.c index 61d1f2a..ad079b4 100644 --- a/mech_eap/pseudo_random.c +++ b/mech_eap/pseudo_random.c @@ -64,7 +64,6 @@ gssEapPseudoRandom(OM_uint32 *minor, gss_ctx_id_t ctx, int prf_key, const gss_buffer_t prf_in, - ssize_t desired_output_len, gss_buffer_t prf_out) { krb5_error_code code; @@ -74,9 +73,7 @@ gssEapPseudoRandom(OM_uint32 *minor, krb5_data t, ns; unsigned char *p; krb5_context krbContext; - - prf_out->length = 0; - prf_out->value = NULL; + ssize_t desired_output_len = prf_out->length; *minor = 0; @@ -91,13 +88,6 @@ gssEapPseudoRandom(OM_uint32 *minor, goto cleanup; } - prf_out->value = GSSEAP_MALLOC(desired_output_len); - if (prf_out->value == NULL) { - code = ENOMEM; - goto cleanup; - } - prf_out->length = desired_output_len; - code = krb5_c_prf_length(krbContext, ctx->encryptionType, &prflen); @@ -146,7 +136,7 @@ cleanup: GSSEAP_FREE(ns.data); } #ifdef HAVE_HEIMDAL_VERSION - krb5_free_data_contents(krbContext, &t); + krb5_data_free(&t); #else if (t.data != NULL) { memset(t.data, 0, t.length); @@ -181,14 +171,25 @@ gss_pseudo_random(OM_uint32 *minor, GSSEAP_MUTEX_LOCK(&ctx->mutex); - if (CTX_IS_ESTABLISHED(ctx)) { - major = gssEapPseudoRandom(minor, ctx, prf_key, - prf_in, desired_output_len, prf_out); - } else { + if (!CTX_IS_ESTABLISHED(ctx)) { major = GSS_S_NO_CONTEXT; *minor = GSSEAP_CONTEXT_INCOMPLETE; + goto cleanup; + } + + prf_out->value = GSSEAP_MALLOC(desired_output_len); + if (prf_out->value == NULL) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; } + prf_out->length = desired_output_len; + + major = gssEapPseudoRandom(minor, ctx, prf_key, + prf_in, prf_out); + +cleanup: GSSEAP_MUTEX_UNLOCK(&ctx->mutex); return major; diff --git a/mech_eap/radsec.conf b/mech_eap/radsec.conf index 27f895a..cd3a163 100644 --- a/mech_eap/radsec.conf +++ b/mech_eap/radsec.conf @@ -1,5 +1,3 @@ -dictionary = "/usr/local/etc/raddb/dictionary" - realm gss-eap { type = "UDP" timeout = 5 diff --git a/mech_eap/util.h b/mech_eap/util.h index 7a6c094..0aa2479 100644 --- a/mech_eap/util.h +++ b/mech_eap/util.h @@ -73,7 +73,9 @@ #include #ifdef WIN32 -#define inline __inline +# ifndef __cplusplus +# define inline __inline +# endif #define snprintf _snprintf #endif @@ -385,6 +387,16 @@ gssEapDeriveRfc3961Key(OM_uint32 *minor, #define KRB_DATA_INIT(d) krb5_data_zero((d)) +#define KRB_CHECKSUM_TYPE(c) ((c)->cksumtype) +#define KRB_CHECKSUM_LENGTH(c) ((c)->checksum.length) +#define KRB_CHECKSUM_DATA(c) ((c)->checksum.data) + +#define KRB_CHECKSUM_INIT(cksum, type, d) do { \ + (cksum)->cksumtype = (type); \ + (cksum)->checksum.length = (d)->length; \ + (cksum)->checksum.data = (d)->value; \ + } while (0) + #else #define KRB_TIME_FOREVER KRB5_INT32_MAX @@ -397,6 +409,8 @@ gssEapDeriveRfc3961Key(OM_uint32 *minor, #define KRB_PRINC_TYPE(princ) (krb5_princ_type(NULL, (princ))) #define KRB_PRINC_NAME(princ) (krb5_princ_name(NULL, (princ))) #define KRB_PRINC_REALM(princ) (krb5_princ_realm(NULL, (princ))) +#define KRB_PRINC_COMPONENT(princ, component) \ + (krb5_princ_component(NULL, (princ), (component))) #define KRB_KT_ENT_KEYBLOCK(e) (&(e)->key) #define KRB_KT_ENT_FREE(c, e) krb5_free_keytab_entry_contents((c), (e)) @@ -409,6 +423,16 @@ gssEapDeriveRfc3961Key(OM_uint32 *minor, (d)->data = NULL; \ } while (0) +#define KRB_CHECKSUM_TYPE(c) ((c)->checksum_type) +#define KRB_CHECKSUM_LENGTH(c) ((c)->length) +#define KRB_CHECKSUM_DATA(c) ((c)->contents) + +#define KRB_CHECKSUM_INIT(cksum, type, d) do { \ + (cksum)->checksum_type = (type); \ + (cksum)->length = (d)->length; \ + (cksum)->contents = (d)->value; \ + } while (0) + #endif /* HAVE_HEIMDAL_VERSION */ #define KRB_KEY_INIT(key) do { \ @@ -597,10 +621,13 @@ gssEapDisplayName(OM_uint32 *minor, gss_buffer_t output_name_buffer, gss_OID *output_name_type); +#define COMPARE_NAME_FLAG_IGNORE_EMPTY_REALMS 0x1 + OM_uint32 gssEapCompareName(OM_uint32 *minor, gss_name_t name1, gss_name_t name2, + OM_uint32 flags, int *name_equal); /* util_oid.c */ @@ -780,13 +807,20 @@ verifyTokenHeader(OM_uint32 *minor, enum gss_eap_token_type *ret_tok_type); /* Helper macros */ - #ifndef GSSEAP_MALLOC +#if _WIN32 +#include +#define GSSEAP_MALLOC gssalloc_malloc +#define GSSEAP_CALLOC gssalloc_calloc +#define GSSEAP_FREE gssalloc_free +#define GSSEAP_REALLOC gssalloc_realloc +#else #define GSSEAP_CALLOC calloc #define GSSEAP_MALLOC malloc #define GSSEAP_FREE free #define GSSEAP_REALLOC realloc -#endif +#endif /* _WIN32 */ +#endif /* !GSSEAP_MALLOC */ #ifndef GSSAPI_CALLCONV #define GSSAPI_CALLCONV KRB5_CALLCONV @@ -978,13 +1012,58 @@ static inline void krbPrincComponentToGssBuffer(krb5_principal krbPrinc, int index, gss_buffer_t buffer) { + if (KRB_PRINC_LENGTH(krbPrinc) < index) { + buffer->value = NULL; + buffer->length = 0; + } else { #ifdef HAVE_HEIMDAL_VERSION - buffer->value = (void *)KRB_PRINC_NAME(krbPrinc)[index]; - buffer->length = strlen((char *)buffer->value); + buffer->value = (void *)KRB_PRINC_NAME(krbPrinc)[index]; + buffer->length = strlen((char *)buffer->value); #else - buffer->value = (void *)krb5_princ_component(NULL, krbPrinc, index)->data; - buffer->length = krb5_princ_component(NULL, krbPrinc, index)->length; + buffer->value = (void *)krb5_princ_component(NULL, krbPrinc, index)->data; + buffer->length = krb5_princ_component(NULL, krbPrinc, index)->length; #endif /* HAVE_HEIMDAL_VERSION */ + } +} + +static inline krb5_error_code +krbPrincUnparseServiceSpecifics(krb5_context krbContext, krb5_principal krbPrinc, + gss_buffer_t nameBuf) +{ + krb5_error_code result = 0; + if (KRB_PRINC_LENGTH(krbPrinc) > 2) { + /* Acceptor-Service-Specific */ + krb5_principal_data ssiPrinc = *krbPrinc; + char *ssi; + + KRB_PRINC_LENGTH(&ssiPrinc) -= 2; + KRB_PRINC_NAME(&ssiPrinc) += 2; + + result = krb5_unparse_name_flags(krbContext, &ssiPrinc, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, &ssi); + if (result != 0) + return result; + + nameBuf->value = ssi; + nameBuf->length = strlen(ssi); + } else { + nameBuf->value = NULL; + nameBuf->length = 0; + } + + return result; +} + +static inline void +krbFreeUnparsedName(krb5_context krbContext, gss_buffer_t nameBuf) +{ +#ifdef HAVE_HEIMDAL_VERSION + krb5_xfree((char *) nameBuf->value); +#else + krb5_free_unparsed_name(krbContext, (char *)(nameBuf->value)); +#endif + nameBuf->value = NULL; + nameBuf->length = 0; } static inline void diff --git a/mech_eap/util_adshim.c b/mech_eap/util_adshim.c index 513a1a8..0dabd55 100644 --- a/mech_eap/util_adshim.c +++ b/mech_eap/util_adshim.c @@ -46,7 +46,7 @@ struct radius_ad_context { }; static krb5_data radius_ad_attr = { - KV5M_DATA, sizeof("urn:authdata-radius-avp") - 1, "urn:authdata-radius-avp" }; + KV5M_DATA, sizeof("urn:authdata-aaa-radius") - 1, "urn:authdata-aaa-radius" }; static krb5_error_code radius_ad_init(krb5_context kcontext GSSEAP_UNUSED, diff --git a/mech_eap/util_attr.cpp b/mech_eap/util_attr.cpp index 3bfe785..d1c0075 100644 --- a/mech_eap/util_attr.cpp +++ b/mech_eap/util_attr.cpp @@ -58,16 +58,16 @@ GSSEAP_ONCE_CALLBACK(gssEapAttrProvidersInitInternal) if (GSS_ERROR(major)) goto cleanup; -#ifdef HAVE_OPENSAML - major = gssEapSamlAttrProvidersInit(&minor); - if (GSS_ERROR(major)) - goto cleanup; -#endif #ifdef HAVE_SHIBRESOLVER /* Allow Shibboleth initialization failure to be non-fatal */ gssEapLocalAttrProviderInit(&minor); #endif +#ifdef HAVE_OPENSAML + major = gssEapSamlAttrProvidersInit(&minor); + if (GSS_ERROR(major)) + goto cleanup; +#endif cleanup: #ifdef GSSEAP_DEBUG diff --git a/mech_eap/util_context.c b/mech_eap/util_context.c index e18edc5..57bd151 100644 --- a/mech_eap/util_context.c +++ b/mech_eap/util_context.c @@ -243,20 +243,45 @@ gssEapMakeOrVerifyTokenMIC(OM_uint32 *minor, int verifyMIC) { OM_uint32 major; - gss_iov_buffer_desc *iov = NULL; size_t i = 0, j; enum gss_eap_token_type tokType; OM_uint32 micTokType; unsigned char wireTokType[2]; unsigned char *innerTokTypes = NULL, *innerTokLengths = NULL; const struct gss_eap_token_buffer_set *tokens; + ssize_t checksumIndex = -1; + + krb5_keyusage usage; + krb5_error_code code = 0; + krb5_context krbContext; + krb5_crypto_iov *kiov = NULL; +#ifdef HAVE_HEIMDAL_VERSION + krb5_crypto krbCrypto = NULL; + krb5_cksumtype cksumType; +#endif + size_t kiovCount; + + GSSEAP_KRB_INIT(&krbContext); tokens = verifyMIC ? ctx->inputTokens : ctx->outputTokens; GSSEAP_ASSERT(tokens != NULL); - iov = GSSEAP_CALLOC(2 + (3 * tokens->buffers.count) + 1, sizeof(*iov)); - if (iov == NULL) { +#ifdef HAVE_HEIMDAL_VERSION + code = krb5_crypto_init(krbContext, &ctx->rfc3961Key, ETYPE_NULL, &krbCrypto); + if (code != 0) + goto cleanup; +#endif + + kiovCount = 2 + (3 * tokens->buffers.count) + 1; + + if (verifyMIC) { + assert(tokens->buffers.count != 0); + kiovCount -= 3; + } + + kiov = GSSEAP_CALLOC(kiovCount, sizeof(*kiov)); + if (kiov == NULL) { major = GSS_S_FAILURE; *minor = ENOMEM; goto cleanup; @@ -278,76 +303,134 @@ gssEapMakeOrVerifyTokenMIC(OM_uint32 *minor, /* Mechanism OID */ GSSEAP_ASSERT(ctx->mechanismUsed != GSS_C_NO_OID); - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer.length = ctx->mechanismUsed->length; - iov[i].buffer.value = ctx->mechanismUsed->elements; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + kiov[i].data.length = ctx->mechanismUsed->length; + kiov[i].data.data = ctx->mechanismUsed->elements; i++; /* Token type */ if (CTX_IS_INITIATOR(ctx) ^ verifyMIC) { tokType = TOK_TYPE_INITIATOR_CONTEXT; micTokType = ITOK_TYPE_INITIATOR_MIC; + usage = KEY_USAGE_GSSEAP_INITOKEN_MIC; } else { tokType = TOK_TYPE_ACCEPTOR_CONTEXT; micTokType = ITOK_TYPE_ACCEPTOR_MIC; + usage = KEY_USAGE_GSSEAP_ACCTOKEN_MIC; } store_uint16_be(tokType, wireTokType); - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer.length = sizeof(wireTokType); - iov[i].buffer.value = wireTokType; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + kiov[i].data.length = sizeof(wireTokType); + kiov[i].data.data = (char *)wireTokType; i++; for (j = 0; j < tokens->buffers.count; j++) { if (verifyMIC && - (tokens->types[j] & ITOK_TYPE_MASK) == micTokType) - continue; /* will use this slot for trailer */ + (tokens->types[j] & ITOK_TYPE_MASK) == micTokType) { + continue; + } - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer.length = 4; - iov[i].buffer.value = &innerTokTypes[j * 4]; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + kiov[i].data.length = 4; + kiov[i].data.data = (char *)&innerTokTypes[j * 4]; store_uint32_be(tokens->types[j] & ~(ITOK_FLAG_VERIFIED), - iov[i].buffer.value); + kiov[i].data.data); i++; - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer.length = 4; - iov[i].buffer.value = &innerTokLengths[j * 4]; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + kiov[i].data.length = 4; + kiov[i].data.data = (char *)&innerTokLengths[j * 4]; store_uint32_be(tokens->buffers.elements[j].length, - iov[i].buffer.value); + kiov[i].data.data); i++; - iov[i].type = GSS_IOV_BUFFER_TYPE_DATA; - iov[i].buffer = tokens->buffers.elements[j]; + kiov[i].flags = KRB5_CRYPTO_TYPE_SIGN_ONLY; + gssBufferToKrbData(&tokens->buffers.elements[j], &kiov[i].data); i++; } + kiov[i].flags = KRB5_CRYPTO_TYPE_CHECKSUM; if (verifyMIC) { - GSSEAP_ASSERT(tokenMIC->length >= 16); + gssBufferToKrbData(tokenMIC, &kiov[i].data); + } else { + size_t checksumSize; - GSSEAP_ASSERT(i < 2 + (3 * tokens->buffers.count)); + code = krb5_c_checksum_length(krbContext, ctx->checksumType, + &checksumSize); + if (code != 0) + goto cleanup; - iov[i].type = GSS_IOV_BUFFER_TYPE_HEADER; - iov[i].buffer = *tokenMIC; - i++; + kiov[i].data.data = GSSEAP_MALLOC(checksumSize); + if (kiov[i].data.data == NULL) { + code = ENOMEM; + goto cleanup; + } + kiov[i].data.length = checksumSize; + checksumIndex = i; + } + i++; + GSSEAP_ASSERT(i == kiovCount); - major = gssEapUnwrapOrVerifyMIC(minor, ctx, NULL, NULL, - iov, i, TOK_TYPE_MIC); +#ifdef HAVE_HEIMDAL_VERSION + cksumType = ctx->checksumType; + + if (verifyMIC) { + code = krb5_verify_checksum_iov(krbContext, krbCrypto, usage, + kiov, i, &cksumType); + } else { + code = krb5_create_checksum_iov(krbContext, krbCrypto, usage, + kiov, i, &cksumType); + } +#else + if (verifyMIC) { + krb5_boolean kvalid = FALSE; + + code = krb5_c_verify_checksum_iov(krbContext, ctx->checksumType, + &ctx->rfc3961Key, + usage, kiov, i, &kvalid); + if (code == 0 && !kvalid) { + code = KRB5KRB_AP_ERR_BAD_INTEGRITY; + } } else { - iov[i++].type = GSS_IOV_BUFFER_TYPE_HEADER | GSS_IOV_BUFFER_FLAG_ALLOCATE; - major = gssEapWrapOrGetMIC(minor, ctx, FALSE, NULL, - iov, i, TOK_TYPE_MIC); - if (!GSS_ERROR(major)) - *tokenMIC = iov[i - 1].buffer; + code = krb5_c_make_checksum_iov(krbContext, ctx->checksumType, + &ctx->rfc3961Key, + usage, kiov, i); + } +#endif /* HAVE_HEIMDAL_VERSION */ + + if (code == 0 && !verifyMIC) { + krbDataToGssBuffer(&kiov[checksumIndex].data, tokenMIC); + checksumIndex = -1; } cleanup: - if (iov != NULL) - gssEapReleaseIov(iov, tokens->buffers.count); + if (checksumIndex != -1) + GSSEAP_FREE(kiov[checksumIndex].data.data); + if (kiov != NULL) + GSSEAP_FREE(kiov); if (innerTokTypes != NULL) GSSEAP_FREE(innerTokTypes); if (innerTokLengths != NULL) GSSEAP_FREE(innerTokLengths); +#ifdef HAVE_HEIMDAL_VERSION + if (krbCrypto != NULL) + krb5_crypto_destroy(krbContext, krbCrypto); +#endif + + *minor = code; + + switch (code) { + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + major = GSS_S_BAD_SIG; + break; + case 0: + major = GSS_S_COMPLETE; + break; + default: + major = GSS_S_FAILURE; + break; + } return major; } @@ -368,10 +451,5 @@ gssEapVerifyTokenMIC(OM_uint32 *minor, gss_ctx_id_t ctx, const gss_buffer_t tokenMIC) { - if (tokenMIC->length < 16) { - *minor = GSSEAP_TOK_TRUNC; - return GSS_S_BAD_SIG; - } - return gssEapMakeOrVerifyTokenMIC(minor, ctx, tokenMIC, TRUE); } diff --git a/mech_eap/util_cred.c b/mech_eap/util_cred.c index 5973ebe..487aa47 100644 --- a/mech_eap/util_cred.c +++ b/mech_eap/util_cred.c @@ -243,13 +243,14 @@ cleanup: gss_OID gssEapPrimaryMechForCred(gss_cred_id_t cred) { - gss_OID nameMech = GSS_C_NO_OID; + gss_OID credMech = GSS_C_NO_OID; - if (cred->mechanisms != GSS_C_NO_OID_SET && + if (cred != GSS_C_NO_CREDENTIAL && + cred->mechanisms != GSS_C_NO_OID_SET && cred->mechanisms->count == 1) - nameMech = &cred->mechanisms->elements[0]; + credMech = &cred->mechanisms->elements[0]; - return nameMech; + return credMech; } OM_uint32 @@ -735,7 +736,8 @@ staticIdentityFileResolveInitiatorCred(OM_uint32 *minor, gss_cred_id_t cred) isDefaultIdentity = TRUE; } else { major = gssEapCompareName(minor, cred->name, - defaultIdentityName, &isDefaultIdentity); + defaultIdentityName, 0, + &isDefaultIdentity); if (GSS_ERROR(major)) goto cleanup; } diff --git a/mech_eap/util_krb.c b/mech_eap/util_krb.c index 5eaa31e..78064f3 100644 --- a/mech_eap/util_krb.c +++ b/mech_eap/util_krb.c @@ -229,7 +229,7 @@ cleanup: if (code != 0) krb5_free_keyblock_contents(krbContext, &kd); #ifdef HAVE_HEIMDAL_VERSION - krb5_free_data_contents(krbContext, &t); + krb5_data_free(&t); #else if (t.data != NULL) { memset(t.data, 0, t.length); @@ -283,11 +283,7 @@ rfc3961ChecksumTypeForKey(OM_uint32 *minor, if (*minor != 0) return GSS_S_FAILURE; -#ifdef HAVE_HEIMDAL_VERSION - *cksumtype = cksum.cksumtype; -#else - *cksumtype = cksum.checksum_type; -#endif + *cksumtype = KRB_CHECKSUM_TYPE(&cksum); krb5_free_checksum_contents(krbContext, &cksum); #endif /* HAVE_KRB5INT_C_MANDATORY_CKSUMTYPE */ diff --git a/mech_eap/util_mech.c b/mech_eap/util_mech.c index 958e43d..8cb7e74 100644 --- a/mech_eap/util_mech.c +++ b/mech_eap/util_mech.c @@ -37,12 +37,19 @@ #include "gssapiP_eap.h" /* - * 1.3.6.1.4.1.5322(padl) - * gssEap(22) + * Mechanism and name types are taken from 1.3.6.1.5.5(mechanisms) + * assigned at http://www.iana.org/assignments/smi-numbers + * + * abfab(15) * mechanisms(1) - * eap-aes128-cts-hmac-sha1-96(17) - * eap-aes256-cts-hmac-sha1-96(18) - * nameTypes(2) + * gss-eap-v1(1) + * eap-aes128-cts-hmac-sha1-96(17) + * eap-aes256-cts-hmac-sha1-96(18) + * nametypes(2) + * GSS_EAP_NT_EAP_NAME(1) + * + * Implementation-internal OIDs are taken from 1.3.6.1.4.1.5322(padl) + * gssEap(22) * apiExtensions(3) * inquireSecContextByOid(1) * inquireCredByOid(2) @@ -56,12 +63,12 @@ * canonicalized exported names. */ static gss_OID_desc gssEapMechOids[] = { - /* 1.3.6.1.4.1.5322.22.1 */ - { 9, "\x2B\x06\x01\x04\x01\xA9\x4A\x16\x01" }, - /* 1.3.6.1.4.1.5322.22.1.17 */ - { 10, "\x2B\x06\x01\x04\x01\xA9\x4A\x16\x01\x11" }, - /* 1.3.6.1.4.1.5322.22.1.18 */ - { 10, "\x2B\x06\x01\x04\x01\xA9\x4A\x16\x01\x12" } + /* 1.3.6.1.5.5.15.1.1 */ + { 8, "\x2B\x06\x01\x05\x05\x0f\x01\x01" }, + /* 1.3.6.1.5.5.15.1.1.17 */ + { 9, "\x2B\x06\x01\x05\x05\x0f\x01\x01\x11" }, + /* 1.3.6.1.5.5.15.1.1.18 */ + { 9, "\x2B\x06\x01\x05\x05\x0f\x01\x01\x12" }, }; gss_OID GSS_EAP_MECHANISM = &gssEapMechOids[0]; diff --git a/mech_eap/util_moonshot.c b/mech_eap/util_moonshot.c index dc0c35e..3e7d14f 100644 --- a/mech_eap/util_moonshot.c +++ b/mech_eap/util_moonshot.c @@ -129,6 +129,15 @@ cleanup: return major; } +static int stringEmpty(const char * s) +{ + if (s == NULL) + return 1; + if (strlen(s) > 0) + return 0; + return 1; +} + OM_uint32 libMoonshotResolveInitiatorCred(OM_uint32 *minor, gss_cred_id_t cred, @@ -148,13 +157,13 @@ libMoonshotResolveInitiatorCred(OM_uint32 *minor, MoonshotError *error = NULL; if (cred->name != GSS_C_NO_NAME) { - major = gssEapExportName(minor, cred->name, &initiator); + major = gssEapDisplayName(minor, cred->name, &initiator, NULL); if (GSS_ERROR(major)) goto cleanup; } if (targetName != GSS_C_NO_NAME) { - major = gssEapExportName(minor, targetName, &target); + major = gssEapDisplayName(minor, targetName, &target, NULL); if (GSS_ERROR(major)) goto cleanup; } @@ -194,7 +203,7 @@ libMoonshotResolveInitiatorCred(OM_uint32 *minor, gss_release_buffer(&tmpMinor, &cred->subjectNameConstraint); gss_release_buffer(&tmpMinor, &cred->subjectAltNameConstraint); - if (serverCertificateHash != NULL) { + if (!stringEmpty(serverCertificateHash)) { size_t len = strlen(serverCertificateHash); #define HASH_PREFIX "hash://server/sha256/" @@ -213,13 +222,13 @@ libMoonshotResolveInitiatorCred(OM_uint32 *minor, ((char *)cred->caCertificate.value)[HASH_PREFIX_LEN + len] = '\0'; cred->caCertificate.length = HASH_PREFIX_LEN + len; - } else if (caCertificate != NULL) { + } else if (!stringEmpty(caCertificate)) { makeStringBufferOrCleanup(caCertificate, &cred->caCertificate); } - if (subjectNameConstraint != NULL) + if (!stringEmpty(subjectNameConstraint)) makeStringBufferOrCleanup(subjectNameConstraint, &cred->subjectNameConstraint); - if (subjectAltNameConstraint != NULL) + if (!stringEmpty(subjectAltNameConstraint)) makeStringBufferOrCleanup(subjectAltNameConstraint, &cred->subjectAltNameConstraint); cleanup: diff --git a/mech_eap/util_name.c b/mech_eap/util_name.c index 6045724..e60156c 100644 --- a/mech_eap/util_name.c +++ b/mech_eap/util_name.c @@ -60,8 +60,8 @@ #include "gssapiP_eap.h" static gss_OID_desc gssEapNtEapName = { - /* 1.3.6.1.4.1.5322.22.2.1 */ - 10, "\x2B\x06\x01\x04\x01\xA9\x4A\x16\x02\x01" + /* 1.3.6.1.5.5.15.2.1 */ + 8, "\x2B\x06\x01\x05\x05\x0f\x02\x01" }; gss_OID GSS_EAP_NT_EAP_NAME = &gssEapNtEapName; @@ -702,6 +702,20 @@ gssEapDuplicateName(OM_uint32 *minor, GSS_C_NO_OID, dest_name); } +static int +hasRealmP(gss_name_t name) +{ +#ifdef HAVE_HEIMDAL_VERSION + if (KRB_PRINC_REALM(name->krbPrincipal) != NULL && + KRB_PRINC_REALM(name->krbPrincipal)[0] != '\0') +#else + if (KRB_PRINC_REALM(name->krbPrincipal)->length != 0) +#endif + return TRUE; + + return FALSE; +} + OM_uint32 gssEapDisplayName(OM_uint32 *minor, gss_name_t name, @@ -728,12 +742,7 @@ gssEapDisplayName(OM_uint32 *minor, * According to draft-ietf-abfab-gss-eap-01, when the realm is * absent the trailing '@' is not included. */ -#ifdef HAVE_HEIMDAL_VERSION - if (KRB_PRINC_REALM(name->krbPrincipal) == NULL || - KRB_PRINC_REALM(name->krbPrincipal)[0] == '\0') -#else - if (KRB_PRINC_REALM(name->krbPrincipal)->length == 0) -#endif + if (!hasRealmP(name)) flags |= KRB5_PRINCIPAL_UNPARSE_NO_REALM; *minor = krb5_unparse_name_flags(krbContext, name->krbPrincipal, @@ -743,12 +752,13 @@ gssEapDisplayName(OM_uint32 *minor, } major = makeStringBuffer(minor, krbName, output_name_buffer); - if (GSS_ERROR(major)) { - krb5_free_unparsed_name(krbContext, krbName); - return major; - } - +#ifdef HAVE_HEIMDAL_VERSION + krb5_xfree(krbName); +#else krb5_free_unparsed_name(krbContext, krbName); +#endif + if (GSS_ERROR(major)) + return major; if (output_name_buffer->length == 0) { name_type = GSS_C_NT_ANONYMOUS; @@ -768,6 +778,7 @@ OM_uint32 gssEapCompareName(OM_uint32 *minor, gss_name_t name1, gss_name_t name2, + OM_uint32 flags, int *name_equal) { krb5_context krbContext; @@ -780,9 +791,18 @@ gssEapCompareName(OM_uint32 *minor, GSSEAP_KRB_INIT(&krbContext); /* krbPrincipal is immutable, so lock not required */ - *name_equal = krb5_principal_compare(krbContext, - name1->krbPrincipal, - name2->krbPrincipal); + if ((flags & COMPARE_NAME_FLAG_IGNORE_EMPTY_REALMS) && + (hasRealmP(name1) == FALSE || hasRealmP(name2) == FALSE)) { + *name_equal = krb5_principal_compare_any_realm(krbContext, + name1->krbPrincipal, + name2->krbPrincipal); + } else { + *name_equal = krb5_principal_compare(krbContext, + name1->krbPrincipal, + name2->krbPrincipal); + } + } else { + *name_equal = 0; } return GSS_S_COMPLETE; diff --git a/mech_eap/util_ordering.c b/mech_eap/util_ordering.c index 71ebfb5..bb7e4d5 100644 --- a/mech_eap/util_ordering.c +++ b/mech_eap/util_ordering.c @@ -266,7 +266,10 @@ sequenceExternalize(OM_uint32 *minor, *minor = GSSEAP_WRONG_SIZE; return GSS_S_FAILURE; } - memcpy(*buf, vqueue, sizeof(queue)); + if (vqueue != NULL) + memcpy(*buf, vqueue, sizeof(queue)); + else + memset(*buf, 0, sizeof(queue)); *buf += sizeof(queue); *lenremain -= sizeof(queue); diff --git a/mech_eap/util_radius.cpp b/mech_eap/util_radius.cpp index 9111e20..d8ec3df 100644 --- a/mech_eap/util_radius.cpp +++ b/mech_eap/util_radius.cpp @@ -35,20 +35,37 @@ */ #include "gssapiP_eap.h" +#include "util_radius.h" +#include "utils/radius_utils.h" -/* stuff that should be provided by libradsec/libfreeradius-radius */ -#define VENDORATTR(vendor, attr) (((vendor) << 16) | (attr)) +#ifdef GSSEAP_ENABLE_ACCEPTOR -#ifndef ATTRID -#define ATTRID(attr) ((attr) & 0xFFFF) -#endif +#define RS_MAP_ERROR(code) (ERROR_TABLE_BASE_rse + (code)) -static gss_buffer_desc radiusUrnPrefix = { - sizeof("urn:x-radius:") - 1, - (void *)"urn:x-radius:" -}; +static rs_avp *copyAvps(rs_const_avp *src); -static VALUE_PAIR *copyAvps(const VALUE_PAIR *src); +static OM_uint32 +gssEapRadiusGetAvp(OM_uint32 *minor, + rs_avp *vps, + const gss_eap_attrid &attrid, + gss_buffer_t buffer, + int concat); + +static OM_uint32 +gssEapRadiusAddAvp(OM_uint32 *minor, + rs_avp **vps, + const gss_eap_attrid &attrid, + const gss_buffer_t buffer); + +static gss_eap_attrid +avpToAttrId(rs_const_avp *vp) +{ + gss_eap_attrid attrid; + + rs_avp_attrid(vp, &attrid.second, &attrid.first); + + return attrid; +} gss_eap_radius_attr_provider::gss_eap_radius_attr_provider(void) { @@ -59,7 +76,7 @@ gss_eap_radius_attr_provider::gss_eap_radius_attr_provider(void) gss_eap_radius_attr_provider::~gss_eap_radius_attr_provider(void) { if (m_vps != NULL) - pairfree(&m_vps); + rs_avp_free(&m_vps); } bool @@ -74,7 +91,7 @@ gss_eap_radius_attr_provider::initWithExistingContext(const gss_eap_attr_ctx *ma radius = static_cast(ctx); if (radius->m_vps != NULL) - m_vps = copyAvps(const_cast(radius->getAvps())); + m_vps = copyAvps(radius->getAvps()); m_authenticated = radius->m_authenticated; @@ -96,7 +113,7 @@ gss_eap_radius_attr_provider::initWithGssContext(const gss_eap_attr_ctx *manager return false; /* We assume libradsec validated this for us */ - GSSEAP_ASSERT(pairfind(m_vps, PW_MESSAGE_AUTHENTICATOR) != NULL); + GSSEAP_ASSERT(rs_avp_find(m_vps, PW_MESSAGE_AUTHENTICATOR, 0) != NULL); m_authenticated = true; } } @@ -105,12 +122,14 @@ gss_eap_radius_attr_provider::initWithGssContext(const gss_eap_attr_ctx *manager } static bool -alreadyAddedAttributeP(std::vector &attrs, VALUE_PAIR *vp) +alreadyAddedAttributeP(std::vector &attrs, + gss_eap_attrid &attrid) { - for (std::vector::const_iterator a = attrs.begin(); + for (std::vector::const_iterator a = attrs.begin(); a != attrs.end(); ++a) { - if (strcmp(vp->name, (*a).c_str()) == 0) + if (attrid.first == (*a).first && + attrid.second == (*a).second) return true; } @@ -118,13 +137,13 @@ alreadyAddedAttributeP(std::vector &attrs, VALUE_PAIR *vp) } static bool -isSecretAttributeP(uint16_t attrid, uint16_t vendor) +isSecretAttributeP(const gss_eap_attrid &attrid) { bool bSecretAttribute = false; - switch (vendor) { - case VENDORPEC_MS: - switch (attrid) { + switch (attrid.first) { + case VENDORPEC_MICROSOFT: + switch (attrid.second) { case PW_MS_MPPE_SEND_KEY: case PW_MS_MPPE_RECV_KEY: bSecretAttribute = true; @@ -140,27 +159,35 @@ isSecretAttributeP(uint16_t attrid, uint16_t vendor) } static bool -isSecretAttributeP(uint32_t attribute) +isSecretAttributeP(rs_const_avp *vp) { - return isSecretAttributeP(ATTRID(attribute), VENDOR(attribute)); + return isSecretAttributeP(avpToAttrId(vp)); } static bool -isInternalAttributeP(uint16_t attrid, uint16_t vendor) +isInternalAttributeP(const gss_eap_attrid &attrid) { bool bInternalAttribute = false; /* should have been filtered */ - GSSEAP_ASSERT(!isSecretAttributeP(attrid, vendor)); + GSSEAP_ASSERT(!isSecretAttributeP(attrid)); - switch (vendor) { + switch (attrid.first) { case VENDORPEC_UKERNA: - switch (attrid) { + switch (attrid.second) { + case PW_SAML_AAA_ASSERTION: + bInternalAttribute = true; + break; + default: + break; + } + break; + case 0: + switch (attrid.second) { case PW_GSS_ACCEPTOR_SERVICE_NAME: case PW_GSS_ACCEPTOR_HOST_NAME: - case PW_GSS_ACCEPTOR_SERVICE_SPECIFIC: + case PW_GSS_ACCEPTOR_SERVICE_SPECIFICS: case PW_GSS_ACCEPTOR_REALM_NAME: - case PW_SAML_AAA_ASSERTION: bInternalAttribute = true; break; default: @@ -175,48 +202,42 @@ isInternalAttributeP(uint16_t attrid, uint16_t vendor) } static bool -isInternalAttributeP(uint32_t attribute) +isInternalAttributeP(rs_const_avp *vp) { - return isInternalAttributeP(ATTRID(attribute), VENDOR(attribute)); + return isInternalAttributeP(avpToAttrId(vp)); } static bool -isFragmentedAttributeP(uint16_t attrid, uint16_t vendor) +isFragmentedAttributeP(const gss_eap_attrid &attrid) { /* A bit of a hack for the PAC for now. Should be configurable. */ - return (vendor == VENDORPEC_UKERNA) && - !isInternalAttributeP(attrid, vendor); -} - -static bool -isFragmentedAttributeP(uint32_t attribute) -{ - return isFragmentedAttributeP(ATTRID(attribute), VENDOR(attribute)); + return (attrid.first == VENDORPEC_UKERNA) && + !isInternalAttributeP(attrid); } /* * Copy AVP list, same as paircopy except it filters out attributes * containing keys. */ -static VALUE_PAIR * -copyAvps(const VALUE_PAIR *src) +static rs_avp * +copyAvps(rs_const_avp *src) { - const VALUE_PAIR *vp; - VALUE_PAIR *dst = NULL, **pDst = &dst; + rs_const_avp *vp; + rs_avp *dst = NULL; - for (vp = src; vp != NULL; vp = vp->next) { - VALUE_PAIR *vpcopy; + for (vp = src; vp != NULL; vp = rs_avp_next_const(vp)) { + rs_avp *vpcopy; - if (isSecretAttributeP(vp->attribute)) + if (isSecretAttributeP(vp)) continue; - vpcopy = paircopyvp(vp); + vpcopy = rs_avp_dup(vp); if (vpcopy == NULL) { - pairfree(&dst); + rs_avp_free(&dst); throw std::bad_alloc(); } - *pDst = vpcopy; - pDst = &vpcopy->next; + + rs_avp_append(&dst, vpcopy); } return dst; @@ -226,68 +247,78 @@ bool gss_eap_radius_attr_provider::getAttributeTypes(gss_eap_attr_enumeration_cb addAttribute, void *data) const { - VALUE_PAIR *vp; - std::vector seen; + rs_avp *vp; + std::vector seen; - for (vp = m_vps; vp != NULL; vp = vp->next) { - gss_buffer_desc attribute; - char attrid[64]; + for (vp = m_vps; vp != NULL; vp = rs_avp_next(vp)) { + gss_buffer_desc desc; + gss_eap_attrid attrid; + char buf[64]; /* Don't advertise attributes that are internal to the GSS-EAP mechanism */ - if (isInternalAttributeP(vp->attribute)) + if (isInternalAttributeP(vp)) continue; - if (alreadyAddedAttributeP(seen, vp)) + rs_avp_attrid(vp, &attrid.second, &attrid.first); + + if (alreadyAddedAttributeP(seen, attrid)) continue; - snprintf(attrid, sizeof(attrid), "%s%d", - (char *)radiusUrnPrefix.value, vp->attribute); + if (rs_attr_display_name(attrid.second, attrid.first, + buf, sizeof(buf), TRUE) != RSE_OK || + strncmp(buf, "Attr-", 5) != 0) + continue; - attribute.value = attrid; - attribute.length = strlen(attrid); + desc.value = &buf[5]; + desc.length = strlen((char *)desc.value); - if (!addAttribute(m_manager, this, &attribute, data)) + if (!addAttribute(m_manager, this, &desc, data)) return false; - seen.push_back(std::string(vp->name)); + seen.push_back(attrid); } return true; } -uint32_t -getAttributeId(const gss_buffer_t attr) +static bool +getAttributeId(const gss_buffer_t desc, + gss_eap_attrid *attrid) { - OM_uint32 tmpMinor; - gss_buffer_desc strAttr = GSS_C_EMPTY_BUFFER; - DICT_ATTR *da; - char *s; - uint32_t attrid = 0; + char *strAttr, *s; + int canon, code; - if (attr->length < radiusUrnPrefix.length || - memcmp(attr->value, radiusUrnPrefix.value, radiusUrnPrefix.length) != 0) - return 0; + if (desc->length == 0) + return false; + + canon = isdigit(*(char *)desc->value); /* need to duplicate because attr may not be NUL terminated */ - duplicateBuffer(*attr, &strAttr); - s = (char *)strAttr.value + radiusUrnPrefix.length; + strAttr = (char *)GSSEAP_MALLOC((canon ? 5 : 0) + desc->length + 1); + if (strAttr == NULL) + throw new std::bad_alloc(); - if (isdigit(*s)) { - attrid = strtoul(s, NULL, 10); - } else { - da = dict_attrbyname(s); - if (da != NULL) - attrid = da->attr; + s = strAttr; + + if (canon) { + memcpy(s, "Attr-", 5); + s += 5; } - gss_release_buffer(&tmpMinor, &strAttr); + memcpy(s, desc->value, desc->length); + s += desc->length; + *s = '\0'; - return attrid; + code = rs_attr_parse_name(strAttr, &attrid->second, &attrid->first); + + GSSEAP_FREE(strAttr); + + return (code == RSE_OK); } bool gss_eap_radius_attr_provider::setAttribute(int complete GSSEAP_UNUSED, - uint32_t attrid, + const gss_eap_attrid &attrid, const gss_buffer_t value) { OM_uint32 major = GSS_S_UNAVAILABLE, minor; @@ -296,9 +327,7 @@ gss_eap_radius_attr_provider::setAttribute(int complete GSSEAP_UNUSED, !isInternalAttributeP(attrid)) { deleteAttribute(attrid); - major = gssEapRadiusAddAvp(&minor, &m_vps, - ATTRID(attrid), VENDOR(attrid), - value); + major = gssEapRadiusAddAvp(&minor, &m_vps, attrid, value); } return !GSS_ERROR(major); @@ -309,32 +338,31 @@ gss_eap_radius_attr_provider::setAttribute(int complete, const gss_buffer_t attr, const gss_buffer_t value) { - uint32_t attrid = getAttributeId(attr); + gss_eap_attrid attrid; - if (!attrid) + if (!getAttributeId(attr, &attrid)) return false; return setAttribute(complete, attrid, value); } bool -gss_eap_radius_attr_provider::deleteAttribute(uint32_t attrid) +gss_eap_radius_attr_provider::deleteAttribute(const gss_eap_attrid &attrid) { - if (isSecretAttributeP(attrid) || isInternalAttributeP(attrid) || - pairfind(m_vps, attrid) == NULL) + if (isSecretAttributeP(attrid) || + isInternalAttributeP(attrid) || + rs_avp_find(m_vps, attrid.second, attrid.first) == NULL) return false; - pairdelete(&m_vps, attrid); - - return true; + return (rs_avp_delete(&m_vps, attrid.second, attrid.first) == RSE_OK); } bool gss_eap_radius_attr_provider::deleteAttribute(const gss_buffer_t attr) { - uint32_t attrid = getAttributeId(attr); + gss_eap_attrid attrid; - if (!attrid) + if (!getAttributeId(attr, &attrid)) return false; return deleteAttribute(attrid); @@ -348,25 +376,25 @@ gss_eap_radius_attr_provider::getAttribute(const gss_buffer_t attr, gss_buffer_t display_value, int *more) const { - uint32_t attrid; + gss_eap_attrid attrid; - attrid = getAttributeId(attr); - if (!attrid) + if (!getAttributeId(attr, &attrid)) return false; - return getAttribute(attrid, authenticated, complete, + return getAttribute(attrid, + authenticated, complete, value, display_value, more); } bool -gss_eap_radius_attr_provider::getAttribute(uint32_t attrid, +gss_eap_radius_attr_provider::getAttribute(const gss_eap_attrid &attrid, int *authenticated, int *complete, gss_buffer_t value, gss_buffer_t display_value, int *more) const { - VALUE_PAIR *vp; + rs_const_avp *vp; int i = *more, count = 0; *more = 0; @@ -374,7 +402,8 @@ gss_eap_radius_attr_provider::getAttribute(uint32_t attrid, if (i == -1) i = 0; - if (isSecretAttributeP(attrid) || isInternalAttributeP(attrid)) { + if (isSecretAttributeP(attrid) || + isInternalAttributeP(attrid)) { return false; } else if (isFragmentedAttributeP(attrid)) { return getFragmentedAttribute(attrid, @@ -383,11 +412,11 @@ gss_eap_radius_attr_provider::getAttribute(uint32_t attrid, value); } - for (vp = pairfind(m_vps, attrid); + for (vp = rs_avp_find_const(m_vps, attrid.second, attrid.first); vp != NULL; - vp = pairfind(vp->next, attrid)) { + vp = rs_avp_find_const(rs_avp_next_const(vp), attrid.second, attrid.first)) { if (count++ == i) { - if (pairfind(vp->next, attrid) != NULL) + if (rs_avp_find_const(rs_avp_next_const(vp), attrid.second, attrid.first) != NULL) *more = count; break; } @@ -399,19 +428,20 @@ gss_eap_radius_attr_provider::getAttribute(uint32_t attrid, if (value != GSS_C_NO_BUFFER) { gss_buffer_desc valueBuf; - valueBuf.value = (void *)vp->vp_octets; - valueBuf.length = vp->length; + rs_avp_octets_value_byref((rs_avp *)vp, + (unsigned char **)&valueBuf.value, + &valueBuf.length); duplicateBuffer(valueBuf, value); } if (display_value != GSS_C_NO_BUFFER && - vp->type != PW_TYPE_OCTETS) { - char displayString[MAX_STRING_LEN]; + !rs_avp_is_octets(vp)) { + char displayString[RS_MAX_STRING_LEN]; gss_buffer_desc displayBuf; - displayBuf.length = vp_prints_value(displayString, - sizeof(displayString), vp, 0); + displayBuf.length = rs_avp_display_value(vp, displayString, + sizeof(displayString)); displayBuf.value = (void *)displayString; duplicateBuffer(displayBuf, display_value); @@ -426,15 +456,14 @@ gss_eap_radius_attr_provider::getAttribute(uint32_t attrid, } bool -gss_eap_radius_attr_provider::getFragmentedAttribute(uint16_t attribute, - uint16_t vendor, +gss_eap_radius_attr_provider::getFragmentedAttribute(const gss_eap_attrid &attrid, int *authenticated, int *complete, gss_buffer_t value) const { OM_uint32 major, minor; - major = gssEapRadiusGetAvp(&minor, m_vps, attribute, vendor, value, TRUE); + major = gssEapRadiusGetAvp(&minor, m_vps, attrid, value, TRUE); if (authenticated != NULL) *authenticated = m_authenticated; @@ -444,31 +473,6 @@ gss_eap_radius_attr_provider::getFragmentedAttribute(uint16_t attribute, return !GSS_ERROR(major); } -bool -gss_eap_radius_attr_provider::getFragmentedAttribute(uint32_t attrid, - int *authenticated, - int *complete, - gss_buffer_t value) const -{ - return getFragmentedAttribute(ATTRID(attrid), VENDOR(attrid), - authenticated, complete, value); -} - -bool -gss_eap_radius_attr_provider::getAttribute(uint16_t attribute, - uint16_t vendor, - int *authenticated, - int *complete, - gss_buffer_t value, - gss_buffer_t display_value, - int *more) const -{ - - return getAttribute(VENDORATTR(attribute, vendor), - authenticated, complete, - value, display_value, more); -} - gss_any_t gss_eap_radius_attr_provider::mapToAny(int authenticated, gss_buffer_t type_id GSSEAP_UNUSED) const @@ -483,8 +487,8 @@ void gss_eap_radius_attr_provider::releaseAnyNameMapping(gss_buffer_t type_id GSSEAP_UNUSED, gss_any_t input) const { - VALUE_PAIR *vp = (VALUE_PAIR *)input; - pairfree(&vp); + rs_avp *vp = (rs_avp *)input; + rs_avp_free(&vp); } bool @@ -507,38 +511,35 @@ gss_eap_radius_attr_provider::createAttrContext(void) return new gss_eap_radius_attr_provider; } -OM_uint32 +static OM_uint32 gssEapRadiusAddAvp(OM_uint32 *minor, - VALUE_PAIR **vps, - uint16_t attribute, - uint16_t vendor, + rs_avp **vps, + const gss_eap_attrid &attrid, const gss_buffer_t buffer) { - uint32_t attrid = VENDORATTR(vendor, attribute); unsigned char *p = (unsigned char *)buffer->value; size_t remain = buffer->length; do { - VALUE_PAIR *vp; + rs_avp *vp; size_t n = remain; /* * There's an extra byte of padding; RADIUS AVPs can only * be 253 octets. */ - if (n >= MAX_STRING_LEN) - n = MAX_STRING_LEN - 1; + if (n >= RS_MAX_STRING_LEN) + n = RS_MAX_STRING_LEN - 1; - vp = paircreate(attrid, PW_TYPE_OCTETS); + vp = rs_avp_alloc(attrid.second, attrid.first); if (vp == NULL) { *minor = ENOMEM; return GSS_S_FAILURE; } - memcpy(vp->vp_octets, p, n); - vp->length = n; + rs_avp_octets_set(vp, p, n); - pairadd(vps, vp); + rs_avp_append(vps, vp); p += n; remain -= n; @@ -548,15 +549,34 @@ gssEapRadiusAddAvp(OM_uint32 *minor, } OM_uint32 -gssEapRadiusGetRawAvp(OM_uint32 *minor, - VALUE_PAIR *vps, - uint16_t attribute, - uint16_t vendor, - VALUE_PAIR **vp) +gssEapRadiusAddAvp(OM_uint32 *minor, + struct rs_packet *pkt, + unsigned int attribute, + unsigned int vendor, + const gss_buffer_t buffer) { - uint32_t attr = VENDORATTR(vendor, attribute); + gss_eap_attrid attrid(vendor, attribute); + int code; + + code = rs_packet_append_avp(pkt, attrid.second, attrid.first, + buffer->value, buffer->length); + if (code != RSE_OK) { + *minor = RS_MAP_ERROR(code); + return GSS_S_FAILURE; + } - *vp = pairfind(vps, attr); + *minor = 0; + return GSS_S_COMPLETE; +} + +OM_uint32 +gssEapRadiusGetRawAvp(OM_uint32 *minor, + rs_const_avp *vps, + unsigned int attribute, + unsigned int vendor, + rs_const_avp **vp) +{ + *vp = rs_avp_find_const(vps, attribute, vendor); if (*vp == NULL) { *minor = GSSEAP_NO_SUCH_ATTR; return GSS_S_UNAVAILABLE; @@ -565,33 +585,32 @@ gssEapRadiusGetRawAvp(OM_uint32 *minor, return GSS_S_COMPLETE; } -OM_uint32 +static OM_uint32 gssEapRadiusGetAvp(OM_uint32 *minor, - VALUE_PAIR *vps, - uint16_t attribute, - uint16_t vendor, + rs_avp *vps, + const gss_eap_attrid &attrid, gss_buffer_t buffer, int concat) { - VALUE_PAIR *vp; - unsigned char *p; - uint32_t attr = VENDORATTR(vendor, attribute); + rs_const_avp *vp; + int err; if (buffer != GSS_C_NO_BUFFER) { buffer->length = 0; buffer->value = NULL; } - vp = pairfind(vps, attr); + vp = rs_avp_find_const(vps, attrid.second, attrid.first); if (vp == NULL) { *minor = GSSEAP_NO_SUCH_ATTR; return GSS_S_UNAVAILABLE; } if (buffer != GSS_C_NO_BUFFER) { - do { - buffer->length += vp->length; - } while (concat && (vp = pairfind(vp->next, attr)) != NULL); + if (concat) + rs_avp_fragmented_value(vp, NULL, &buffer->length); + else + buffer->length = rs_avp_length(vp); buffer->value = GSSEAP_MALLOC(buffer->length); if (buffer->value == NULL) { @@ -599,13 +618,14 @@ gssEapRadiusGetAvp(OM_uint32 *minor, return GSS_S_FAILURE; } - p = (unsigned char *)buffer->value; + if (concat) + err = rs_avp_fragmented_value(vp, (unsigned char *)buffer->value, &buffer->length); + else + err = rs_avp_octets_value(vp, (unsigned char *)buffer->value, &buffer->length); - for (vp = pairfind(vps, attr); - concat && vp != NULL; - vp = pairfind(vp->next, attr)) { - memcpy(p, vp->vp_octets, vp->length); - p += vp->length; + if (err != 0) { + *minor = RS_MAP_ERROR(err); + return GSS_S_FAILURE; } } @@ -614,10 +634,26 @@ gssEapRadiusGetAvp(OM_uint32 *minor, } OM_uint32 +gssEapRadiusGetAvp(OM_uint32 *minor, + struct rs_packet *pkt, + unsigned int attribute, + unsigned int vendor, + gss_buffer_t buffer, + int concat) +{ + rs_avp **vps; + gss_eap_attrid attrid(vendor, attribute); + + rs_packet_avps(pkt, &vps); + + return gssEapRadiusGetAvp(minor, *vps, attrid, buffer, concat); +} + +OM_uint32 gssEapRadiusFreeAvps(OM_uint32 *minor, - VALUE_PAIR **vps) + rs_avp **vps) { - pairfree(vps); + rs_avp_free(vps); *minor = 0; return GSS_S_COMPLETE; } @@ -643,25 +679,28 @@ gssEapRadiusAttrProviderFinalize(OM_uint32 *minor) } static JSONObject -avpToJson(const VALUE_PAIR *vp) +avpToJson(rs_const_avp *vp) { JSONObject obj; + gss_eap_attrid attrid; - GSSEAP_ASSERT(vp->length <= MAX_STRING_LEN); + GSSEAP_ASSERT(rs_avp_length(vp) <= RS_MAX_STRING_LEN); - switch (vp->type) { - case PW_TYPE_INTEGER: - case PW_TYPE_IPADDR: - case PW_TYPE_DATE: - obj.set("value", vp->lvalue); + switch (rs_avp_typeof(vp)) { + case RS_TYPE_INTEGER: + obj.set("value", rs_avp_integer_value(vp)); break; - case PW_TYPE_STRING: - obj.set("value", vp->vp_strvalue); + case RS_TYPE_DATE: + obj.set("value", rs_avp_date_value(vp)); + break; + case RS_TYPE_STRING: + obj.set("value", rs_avp_string_value(vp)); break; default: { char *b64; - if (base64Encode(vp->vp_octets, vp->length, &b64) < 0) + if (base64Encode(rs_avp_octets_value_const_ptr(vp), + rs_avp_length(vp), &b64) < 0) throw std::bad_alloc(); obj.set("value", b64); @@ -670,62 +709,65 @@ avpToJson(const VALUE_PAIR *vp) } } - obj.set("type", vp->attribute); + attrid = avpToAttrId(vp); + + obj.set("type", attrid.second); + if (attrid.first != 0) + obj.set("vendor", attrid.first); return obj; } static bool -jsonToAvp(VALUE_PAIR **pVp, JSONObject &obj) +jsonToAvp(rs_avp **pVp, JSONObject &obj) { - VALUE_PAIR *vp = NULL; - DICT_ATTR *da; - uint32_t attrid; + rs_avp *vp = NULL; + gss_eap_attrid attrid; JSONObject type = obj["type"]; + JSONObject vendor = obj["vendor"]; JSONObject value = obj["value"]; if (!type.isInteger()) goto fail; + attrid.second = type.integer(); - attrid = type.integer(); - da = dict_attrbyvalue(attrid); - if (da != NULL) { - vp = pairalloc(da); + if (!vendor.isNull()) { + if (!vendor.isInteger()) + goto fail; + attrid.first = vendor.integer(); } else { - int type = base64Valid(value.string()) ? - PW_TYPE_OCTETS : PW_TYPE_STRING; - vp = paircreate(attrid, type); + attrid.first = 0; } + + vp = rs_avp_alloc(attrid.second, attrid.first); if (vp == NULL) throw std::bad_alloc(); - switch (vp->type) { - case PW_TYPE_INTEGER: - case PW_TYPE_IPADDR: - case PW_TYPE_DATE: + switch (rs_avp_typeof(vp)) { + case RS_TYPE_INTEGER: + case RS_TYPE_IPADDR: + case RS_TYPE_DATE: if (!value.isInteger()) goto fail; - vp->length = 4; - vp->lvalue = value.integer(); + if (rs_avp_integer_set(vp, value.integer()) != RSE_OK) + goto fail; + break; - case PW_TYPE_STRING: { + case RS_TYPE_STRING: { if (!value.isString()) goto fail; - const char *str = value.string(); - size_t len = strlen(str); - - if (len >= MAX_STRING_LEN) + if (rs_avp_string_set(vp, value.string()) != RSE_OK) goto fail; - vp->length = len; - memcpy(vp->vp_strvalue, str, len + 1); break; } - case PW_TYPE_OCTETS: + case RS_TYPE_OCTETS: default: { + unsigned char buf[RS_MAX_STRING_LEN]; + if (!value.isString()) goto fail; @@ -733,14 +775,16 @@ jsonToAvp(VALUE_PAIR **pVp, JSONObject &obj) ssize_t len = strlen(str); /* this optimization requires base64Decode only understand packed encoding */ - if (len >= BASE64_EXPAND(MAX_STRING_LEN)) + if (len >= BASE64_EXPAND(RS_MAX_STRING_LEN)) goto fail; - len = base64Decode(str, vp->vp_octets); + len = base64Decode(str, buf); if (len < 0) goto fail; - vp->length = len; + if (rs_avp_octets_set(vp, buf, len) != RSE_OK) + goto fail; + break; } } @@ -751,7 +795,7 @@ jsonToAvp(VALUE_PAIR **pVp, JSONObject &obj) fail: if (vp != NULL) - pairbasicfree(vp); + rs_avp_free(&vp); *pVp = NULL; return false; } @@ -766,8 +810,6 @@ bool gss_eap_radius_attr_provider::initWithJsonObject(const gss_eap_attr_ctx *ctx, JSONObject &obj) { - VALUE_PAIR **pNext = &m_vps; - if (!gss_eap_attr_provider::initWithJsonObject(ctx, obj)) return false; @@ -776,13 +818,12 @@ gss_eap_radius_attr_provider::initWithJsonObject(const gss_eap_attr_ctx *ctx, for (size_t i = 0; i < nelems; i++) { JSONObject attr = attrs[i]; - VALUE_PAIR *vp; + rs_avp *vp; if (!jsonToAvp(&vp, attr)) return false; - *pNext = vp; - pNext = &vp->next; + rs_avp_append(&m_vps, vp); } m_authenticated = obj["authenticated"].integer() ? true : false; @@ -793,7 +834,7 @@ gss_eap_radius_attr_provider::initWithJsonObject(const gss_eap_attr_ctx *ctx, const char * gss_eap_radius_attr_provider::prefix(void) const { - return "urn:ietf:params:gss-eap:radius-avp"; + return "urn:ietf:params:gss:radius-attribute"; } JSONObject @@ -801,7 +842,7 @@ gss_eap_radius_attr_provider::jsonRepresentation(void) const { JSONObject obj, attrs = JSONObject::array(); - for (VALUE_PAIR *vp = m_vps; vp != NULL; vp = vp->next) { + for (rs_avp *vp = m_vps; vp != NULL; vp = rs_avp_next(vp)) { JSONObject attr = avpToJson(vp); attrs.append(attr); } @@ -816,13 +857,18 @@ gss_eap_radius_attr_provider::jsonRepresentation(void) const time_t gss_eap_radius_attr_provider::getExpiryTime(void) const { - VALUE_PAIR *vp; + rs_const_avp *vp; + uint32_t value; - vp = pairfind(m_vps, PW_SESSION_TIMEOUT); - if (vp == NULL || vp->lvalue == 0) + vp = rs_avp_find(m_vps, PW_SESSION_TIMEOUT, 0); + if (vp == NULL) return 0; - return time(NULL) + vp->lvalue; + value = rs_avp_integer_value(vp); + if (value == 0) + return 0; + + return time(NULL) + value; } OM_uint32 @@ -840,7 +886,7 @@ gssEapRadiusMapError(OM_uint32 *minor, return GSS_S_COMPLETE; } - *minor = ERROR_TABLE_BASE_rse + code; + *minor = RS_MAP_ERROR(code); gssEapSaveStatusInfo(*minor, "%s", rs_err_msg(err)); rs_err_free(err); @@ -881,11 +927,6 @@ gssEapCreateRadiusContext(OM_uint32 *minor, goto fail; } - if (rs_context_init_freeradius_dict(radContext, NULL) != 0) { - err = rs_err_ctx_pop(radContext); - goto fail; - } - *pRadContext = radContext; *minor = 0; @@ -897,3 +938,17 @@ fail: return major; } + +#endif /* GSSEAP_ENABLE_ACCEPTOR */ + +OM_uint32 +gssEapRadiusAddAttr(OM_uint32 *minor, struct wpabuf **buf, uint16_t attr, + uint16_t vendor, gss_buffer_t buffer) +{ + if (radius_add_tlv(buf, attr, vendor, (u8 *)buffer->value, + buffer->length) < 0) { + *minor = ENOMEM; /* could be length too long, though */ + return GSS_S_FAILURE; + } + return GSS_S_COMPLETE; +} diff --git a/mech_eap/util_radius.h b/mech_eap/util_radius.h index 481876a..d4f86ec 100644 --- a/mech_eap/util_radius.h +++ b/mech_eap/util_radius.h @@ -39,6 +39,10 @@ #ifdef __cplusplus +typedef std::pair gss_eap_attrid; +#ifdef GSSEAP_ENABLE_ACCEPTOR + + struct gss_eap_radius_attr_provider : gss_eap_attr_provider { public: gss_eap_radius_attr_provider(void); @@ -72,30 +76,18 @@ public: JSONObject &obj); JSONObject jsonRepresentation(void) const; - bool getAttribute(uint32_t attribute, - int *authenticated, - int *complete, - gss_buffer_t value, - gss_buffer_t display_value, - int *more) const; - bool getAttribute(uint16_t attribute, - uint16_t vendor, + bool getAttribute(const gss_eap_attrid &attrid, int *authenticated, int *complete, gss_buffer_t value, gss_buffer_t display_value, int *more) const; bool setAttribute(int complete, - uint32_t attribute, + const gss_eap_attrid &attrid, const gss_buffer_t value); - bool deleteAttribute(uint32_t attribute); + bool deleteAttribute(const gss_eap_attrid &attrid); - bool getFragmentedAttribute(uint16_t attribute, - uint16_t vendor, - int *authenticated, - int *complete, - gss_buffer_t value) const; - bool getFragmentedAttribute(uint32_t attrid, + bool getFragmentedAttribute(const gss_eap_attrid &attrid, int *authenticated, int *complete, gss_buffer_t value) const; @@ -110,42 +102,46 @@ public: static gss_eap_attr_provider *createAttrContext(void); private: - const VALUE_PAIR *getAvps(void) const { + rs_const_avp *getAvps(void) const { return m_vps; } - VALUE_PAIR *m_vps; + rs_avp *m_vps; bool m_authenticated; }; +#endif /* GSSEAP_ENABLE_ACCEPTOR */ + /* For now */ extern "C" { #endif +#ifdef GSSEAP_ENABLE_ACCEPTOR + OM_uint32 gssEapRadiusAddAvp(OM_uint32 *minor, - VALUE_PAIR **vp, - uint16_t type, - uint16_t vendor, + struct rs_packet *pkt, + unsigned int type, + unsigned int vendor, const gss_buffer_t buffer); OM_uint32 gssEapRadiusGetAvp(OM_uint32 *minor, - VALUE_PAIR *vps, - uint16_t type, - uint16_t vendor, + struct rs_packet *pkt, + unsigned int type, + unsigned int vendor, gss_buffer_t buffer, int concat); OM_uint32 gssEapRadiusGetRawAvp(OM_uint32 *minor, - VALUE_PAIR *vps, - uint16_t type, - uint16_t vendor, - VALUE_PAIR **vp); + rs_const_avp *vps, + unsigned int type, + unsigned int vendor, + rs_const_avp **vp); OM_uint32 gssEapRadiusFreeAvps(OM_uint32 *minor, - VALUE_PAIR **vps); + rs_avp **vps); OM_uint32 gssEapRadiusAttrProviderInit(OM_uint32 *minor); OM_uint32 gssEapRadiusAttrProviderFinalize(OM_uint32 *minor); @@ -159,22 +155,20 @@ gssEapCreateRadiusContext(OM_uint32 *minor, gss_cred_id_t cred, struct rs_context **pRadContext); -/* This really needs to be a function call on Windows */ -#define RS_CONFIG_FILE SYSCONFDIR "/radsec.conf" +#endif /* GSSEAP_ENABLE_ACCEPTOR */ -#define VENDORPEC_MS 311 /* RFC 2548 */ +/* initiator utilities that require only libeap, and not freeradius */ +struct wpabuf; -#define PW_MS_MPPE_SEND_KEY 16 -#define PW_MS_MPPE_RECV_KEY 17 - -#define VENDORPEC_UKERNA 25622 +OM_uint32 +gssEapRadiusAddAttr(OM_uint32 *minor, + struct wpabuf **dst, + uint16_t type, + uint16_t vendor, + gss_buffer_t value); -#define PW_GSS_ACCEPTOR_SERVICE_NAME 128 -#define PW_GSS_ACCEPTOR_HOST_NAME 129 -#define PW_GSS_ACCEPTOR_SERVICE_SPECIFIC 130 -#define PW_GSS_ACCEPTOR_REALM_NAME 131 -#define PW_SAML_AAA_ASSERTION 132 -#define PW_MS_WINDOWS_AUTH_DATA 133 +/* This really needs to be a function call on Windows */ +#define RS_CONFIG_FILE SYSCONFDIR "/radsec.conf" #ifdef __cplusplus } diff --git a/mech_eap/util_reauth.c b/mech_eap/util_reauth.c index 50011ca..40e3de1 100644 --- a/mech_eap/util_reauth.c +++ b/mech_eap/util_reauth.c @@ -214,6 +214,11 @@ gssEapMakeReauthCreds(OM_uint32 *minor, credBuf->length = 0; credBuf->value = NULL; + if (ctx->acceptorName == GSS_C_NO_NAME) { + *minor = GSSEAP_NO_ACCEPTOR_NAME; + return GSS_S_UNAVAILABLE; + } + GSSEAP_KRB_INIT(&krbContext); code = getAcceptorKey(krbContext, ctx, cred, &server, &acceptorKey); @@ -635,7 +640,7 @@ cleanup: #ifndef HAVE_HEIMDAL_VERSION static gss_buffer_desc radiusAvpKrbAttr = { - sizeof("urn:authdata-radius-avp") - 1, "urn:authdata-radius-avp" + sizeof("urn:authdata-aaa-radius") - 1, "urn:authdata-aaa-radius" }; #endif diff --git a/mech_eap/util_saml.cpp b/mech_eap/util_saml.cpp index ce7582e..bf82a57 100644 --- a/mech_eap/util_saml.cpp +++ b/mech_eap/util_saml.cpp @@ -103,6 +103,7 @@ gss_eap_saml_assertion_provider::initWithGssContext(const gss_eap_attr_ctx *mana gss_buffer_desc value = GSS_C_EMPTY_BUFFER; int authenticated, complete; OM_uint32 minor; + gss_eap_attrid attrid(VENDORPEC_UKERNA, PW_SAML_AAA_ASSERTION); GSSEAP_ASSERT(m_assertion == NULL); @@ -115,9 +116,7 @@ gss_eap_saml_assertion_provider::initWithGssContext(const gss_eap_attr_ctx *mana radius = static_cast (m_manager->getProvider(ATTR_TYPE_RADIUS)); if (radius != NULL && - radius->getFragmentedAttribute(PW_SAML_AAA_ASSERTION, - VENDORPEC_UKERNA, - &authenticated, &complete, &value)) { + radius->getFragmentedAttribute(attrid, &authenticated, &complete, &value)) { setAssertion(&value, authenticated); gss_release_buffer(&minor, &value); } else { @@ -317,7 +316,7 @@ gss_eap_saml_assertion_provider::releaseAnyNameMapping(gss_buffer_t type_id GSSE const char * gss_eap_saml_assertion_provider::prefix(void) const { - return "urn:ietf:params:gss-eap:saml-aaa-assertion"; + return "urn:ietf:params:gss:federated-saml-assertion"; } bool @@ -414,7 +413,7 @@ gss_eap_saml_attr_provider::getAttributeTypes(gss_eap_attr_enumeration_cb addAtt * Each attribute carried in the assertion SHOULD also be a GSS name * attribute. The name of this attribute has three parts, all separated * by an ASCII space character. The first part is - * urn:ietf:params:gss-eap:saml-attr. The second part is the URI for + * urn:ietf:params:gss:federated-saml-attribute. The second part is the URI for * the SAML attribute name format. The final part is the name of the * SAML attribute. If the mechanism performs an additional attribute * query, the retrieved attributes SHOULD be GSS-API name attributes @@ -730,7 +729,7 @@ gss_eap_saml_attr_provider::releaseAnyNameMapping(gss_buffer_t type_id GSSEAP_UN const char * gss_eap_saml_attr_provider::prefix(void) const { - return "urn:ietf:params:gss-eap:saml-attr"; + return "urn:ietf:params:gss:federated-saml-attribute"; } bool diff --git a/mech_eap/util_shib.cpp b/mech_eap/util_shib.cpp index f8c702b..7b62484 100644 --- a/mech_eap/util_shib.cpp +++ b/mech_eap/util_shib.cpp @@ -163,13 +163,12 @@ gss_eap_shib_attr_provider::initWithGssContext(const gss_eap_attr_ctx *manager, const gss_eap_radius_attr_provider *radius; int authenticated, complete; gss_buffer_desc value = GSS_C_EMPTY_BUFFER; + gss_eap_attrid attrid(VENDORPEC_UKERNA, PW_SAML_AAA_ASSERTION); radius = static_cast (m_manager->getProvider(ATTR_TYPE_RADIUS)); if (radius != NULL && - radius->getFragmentedAttribute(PW_SAML_AAA_ASSERTION, - VENDORPEC_UKERNA, - &authenticated, &complete, &value)) { + radius->getFragmentedAttribute(attrid, &authenticated, &complete, &value)) { string str((char *)value.value, value.length); istringstream istream(str); DOMDocument *doc = XMLToolingConfig::getConfig().getParser().parse(istream);