From: Luke Howard Date: Sat, 12 Mar 2011 04:19:25 +0000 (+1100) Subject: Merge branch 'master' into tlv X-Git-Tag: 0.9.2~342 X-Git-Url: http://www.project-moonshot.org/gitweb/?p=mech_eap.git;a=commitdiff_plain;h=203b509a0240995c98f04119906f84b280b3c429;hp=360166c7359173c01758a2d24aa7e8985c1190c7 Merge branch 'master' into tlv --- diff --git a/Makefile.am b/Makefile.am index 17757f4..eaa3fc1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,9 +7,9 @@ gssdir = $(libdir)/gss gss_LTLIBRARIES = mech_eap.la mech_eap_la_CPPFLAGS = -DBUILD_GSSEAP_LIB -DSYSCONFDIR=\"${sysconfdir}\" -DDATAROOTDIR=\"${datarootdir}\" -mech_eap_la_CFLAGS = -g -Wall -fno-strict-aliasing \ +mech_eap_la_CFLAGS = -g -Wunused-parameter -Wall -fno-strict-aliasing \ @KRB5_CFLAGS@ @EAP_CFLAGS@ @RADSEC_CFLAGS@ @TARGET_CFLAGS@ -mech_eap_la_CXXFLAGS = -g -Wall \ +mech_eap_la_CXXFLAGS = -g -Wunused-parameter -Wall \ @KRB5_CFLAGS@ @EAP_CFLAGS@ @RADSEC_CFLAGS@ \ @SHIBRESOLVER_CXXFLAGS@ @SHIBSP_CXXFLAGS@ @TARGET_CFLAGS@ mech_eap_la_LDFLAGS = -avoid-version -module \ @@ -74,7 +74,6 @@ mech_eap_la_SOURCES = \ util_cksum.c \ util_cred.c \ util_crypt.c \ - util_exts.c \ util_krb.c \ util_lucid.c \ util_mech.c \ @@ -84,6 +83,7 @@ mech_eap_la_SOURCES = \ util_radius.cpp \ util_saml.cpp \ util_shib.cpp \ + util_sm.c \ util_token.c \ verify_mic.c \ wrap.c \ @@ -114,8 +114,6 @@ endif gsseap_err.h gsseap_err.c: gsseap_err.et $(COMPILE_ET) $< - - radsec_err.h radsec_err.c: radsec_err.et $(COMPILE_ET) $< diff --git a/NOTES b/NOTES index cce432f..849ce4e 100644 --- a/NOTES +++ b/NOTES @@ -1,2 +1,9 @@ - gss_xxx routines acquire lock, gssXxx don't +- git + +If you do want to update with a rebase, deletethe branch from the +server first then push the rebased branch + +to delete a branch from a server git push origin :branch_to_del + diff --git a/README b/README index c02d8ce..7b4d867 100644 --- a/README +++ b/README @@ -2,7 +2,7 @@ Overview ======== This is an implementation of the GSS EAP mechanism, as described in -draft-ietf-abfab-gss-eap-00.txt. +draft-ietf-abfab-gss-eap-01.txt. Building ======== @@ -17,37 +17,88 @@ so not all features will be available. Installing ========== +GSS mechglue +------------ + When installing, be sure to edit $prefix/etc/gss/mech to register the EAP mechanisms. A sample configuration file is in this directory. +You may need to specify an absolute path. + +RADIUS client library +--------------------- Make sure your RADIUS library is configured to talk to the server of -your choice: see the example radsec.conf in this directory. +your choice: see the example radsec.conf in this directory. If you +want to use TCP or TLS, you'll need to run radsecproxy in front of +your RADIUS server. -On the RADIUS server side, you need to install dictionary.ukerna and -include it from the main dictionary file. +RADIUS server +------------- -If you want the acceptor be able to identify the user, the RADIUS -server needs to echo back the EAP username from the inner tunnel; -for privacy, mech_eap only sends the realm in the EAP Identity -response. To configure this with FreeRADIUS, add: +These instructions apply to FreeRADIUS only, which is downloadable +from http://freeradius.org/. After configure, make, install, do the +following: - update outer.reply { - User-Name = "%{request:User-Name}" - } +On the RADIUS server side, you need to install dictionary.ukerna to +$prefix/etc/raddb and include it from the main dictionary file, by +adding: + + $INCLUDE dictionary.ukerna + +to $prefix/etc/raddb/dictionary. Make sure these files are world- +readable; they weren't in my installation. -to $prefix/etc/raddb/sites-enabled/inner-tunnel, and ensure that +Edit $prefix/etc/raddb/users to add your test user and password: - virtual_server = "inner-tunnel" + bob@PROJECT-MOONSHOT.ORG Cleartext-Password := secret -is set in eap.conf for the desired EAP types. +Add an entry for your acceptor to $prefix/etc/raddb/clients.conf: -To test the SAML assertion code path, you can place a fixed SAML -assertion in the update reply block of the default configuration. + client somehost { + ipaddr = 127.0.0.1 + secret = testing123 + require_message_authenticator = yes + } - update reply { - SAML-AAA-Assertion = ' % server -c -p 5556 -s host -h +To test fast reauthentication support, add the following to +/etc/krb5.conf: + +[appdefaults] + eap_gss = { + reauth_use_ccache = TRUE + } + +This will store a Kerberos ticket for a GSS-EAP authenticated user +in a credentials cache, which can then be used for re-authentication +to the same acceptor. You must have a valid keytab configured. diff --git a/accept_sec_context.c b/accept_sec_context.c index d192d71..5da5ea6 100644 --- a/accept_sec_context.c +++ b/accept_sec_context.c @@ -40,11 +40,16 @@ #ifdef GSSEAP_ENABLE_REAUTH static OM_uint32 eapGssSmAcceptGssReauth(OM_uint32 *minor, - gss_ctx_id_t ctx, gss_cred_id_t cred, - gss_buffer_t inputToken, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, gss_channel_bindings_t chanBindings, - gss_buffer_t outputToken); + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags); #endif /* @@ -119,35 +124,87 @@ acceptReadyEap(OM_uint32 *minor, gss_ctx_id_t ctx, gss_cred_id_t cred) return GSS_S_COMPLETE; } +static OM_uint32 +eapGssSmAcceptAcceptorName(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) +{ + OM_uint32 major; + + /* XXX TODO import and validate name from inputToken */ + + if (ctx->acceptorName != GSS_C_NO_NAME) { + /* Send desired target name to acceptor */ + major = gssEapDisplayName(minor, ctx->acceptorName, + outputToken, NULL); + if (GSS_ERROR(major)) + return major; + } + + return GSS_S_CONTINUE_NEEDED; +} + +#ifdef GSSEAP_DEBUG +static OM_uint32 +eapGssSmAcceptVendorInfo(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) +{ + fprintf(stderr, "GSS-EAP: vendor: %.*s\n", + (int)inputToken->length, (char *)inputToken->value); + + return GSS_S_CONTINUE_NEEDED; +} +#endif + + /* * Emit a identity EAP request to force the initiator (peer) to identify * itself. */ static OM_uint32 eapGssSmAcceptIdentity(OM_uint32 *minor, - gss_ctx_id_t ctx, gss_cred_id_t cred, - gss_buffer_t inputToken, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, gss_channel_bindings_t chanBindings, - gss_buffer_t outputToken) + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) { OM_uint32 major; struct wpabuf *reqData; gss_buffer_desc pktBuffer; + if (!gssEapCredAvailable(cred, ctx->mechanismUsed)) { + *minor = GSSEAP_CRED_MECH_MISMATCH; + return GSS_S_BAD_MECH; + } + if (inputToken != GSS_C_NO_BUFFER && inputToken->length != 0) { *minor = GSSEAP_WRONG_SIZE; return GSS_S_DEFECTIVE_TOKEN; } - assert(ctx->acceptorName == GSS_C_NO_NAME); - - if (cred->name != GSS_C_NO_NAME) { - major = gssEapDuplicateName(minor, cred->name, &ctx->acceptorName); - if (GSS_ERROR(major)) - return major; - } - reqData = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_IDENTITY, 0, EAP_CODE_REQUEST, 0); if (reqData == NULL) { @@ -162,11 +219,13 @@ eapGssSmAcceptIdentity(OM_uint32 *minor, if (GSS_ERROR(major)) return major; - ctx->state = GSSEAP_STATE_AUTHENTICATE; - wpabuf_free(reqData); + GSSEAP_SM_TRANSITION_NEXT(ctx); + *minor = 0; + *smFlags |= SM_FLAG_OUTPUT_TOKEN_CRITICAL; + return GSS_S_CONTINUE_NEEDED; } @@ -406,11 +465,16 @@ fail: */ static OM_uint32 eapGssSmAcceptAuthenticate(OM_uint32 *minor, - gss_ctx_id_t ctx, gss_cred_id_t cred, - gss_buffer_t inputToken, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, gss_channel_bindings_t chanBindings, - gss_buffer_t outputToken) + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) { OM_uint32 major, tmpMinor; struct rs_connection *rconn; @@ -478,9 +542,8 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor, frresp = rs_packet_frpkt(resp); switch (frresp->code) { - case PW_AUTHENTICATION_ACK: case PW_ACCESS_CHALLENGE: - major = GSS_S_CONTINUE_NEEDED; + case PW_AUTHENTICATION_ACK: break; case PW_AUTHENTICATION_REJECT: *minor = GSSEAP_RADIUS_AUTH_FAILURE; @@ -519,11 +582,12 @@ eapGssSmAcceptAuthenticate(OM_uint32 *minor, if (GSS_ERROR(major)) goto cleanup; - ctx->state = GSSEAP_STATE_EXTENSIONS_REQ; + GSSEAP_SM_TRANSITION_NEXT(ctx); } - *minor = 0; major = GSS_S_CONTINUE_NEEDED; + *minor = 0; + *smFlags |= SM_FLAG_OUTPUT_TOKEN_CRITICAL; cleanup: if (request != NULL) @@ -535,114 +599,188 @@ cleanup: } static OM_uint32 -eapGssSmAcceptExtensionsReq(OM_uint32 *minor, - gss_ctx_id_t ctx, - gss_cred_id_t cred, - gss_buffer_t inputToken, - gss_channel_bindings_t chanBindings, - gss_buffer_t outputToken) +eapGssSmAcceptGssChannelBindings(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) { - OM_uint32 major; + OM_uint32 major, tmpMinor; + gss_iov_buffer_desc iov[2]; + + iov[0].type = GSS_IOV_BUFFER_TYPE_DATA | GSS_IOV_BUFFER_FLAG_ALLOCATE; + iov[0].buffer.length = 0; + iov[0].buffer.value = NULL; - major = gssEapVerifyExtensions(minor, cred, ctx, chanBindings, inputToken); + iov[1].type = GSS_IOV_BUFFER_TYPE_STREAM; + iov[1].buffer = *inputToken; + + major = gssEapUnwrapOrVerifyMIC(minor, ctx, NULL, NULL, + iov, 2, TOK_TYPE_WRAP); if (GSS_ERROR(major)) - return major; + return GSS_S_BAD_BINDINGS; - outputToken->length = 0; - outputToken->value = NULL; + if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS && + !bufferEqual(&iov[0].buffer, &chanBindings->application_data)) { + major = GSS_S_BAD_BINDINGS; + *minor = GSSEAP_BINDINGS_MISMATCH; + } else { + major = GSS_S_CONTINUE_NEEDED; + *minor = 0; + } - ctx->state = GSSEAP_STATE_EXTENSIONS_RESP; + gss_release_buffer(&tmpMinor, &iov[0].buffer); - *minor = 0; - return GSS_S_CONTINUE_NEEDED; + return major; } +#ifdef GSSEAP_ENABLE_REAUTH static OM_uint32 -eapGssSmAcceptExtensionsResp(OM_uint32 *minor, - gss_ctx_id_t ctx, - gss_cred_id_t cred, - gss_buffer_t inputToken, - gss_channel_bindings_t chanBindings, - gss_buffer_t outputToken) +eapGssSmAcceptReauthCreds(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) { OM_uint32 major; - major = gssEapMakeExtensions(minor, cred, ctx, chanBindings, outputToken); - if (GSS_ERROR(major)) - return major; - - ctx->state = GSSEAP_STATE_ESTABLISHED; + /* + * If we're built with fast reauthentication enabled, then + * fabricate a ticket from the initiator to ourselves. + */ + major = gssEapMakeReauthCreds(minor, ctx, cred, outputToken); + if (major == GSS_S_UNAVAILABLE) + major = GSS_S_COMPLETE; + if (major == GSS_S_COMPLETE) + major = GSS_S_CONTINUE_NEEDED; - *minor = 0; - return GSS_S_COMPLETE; + return major; } +#endif static OM_uint32 -eapGssSmAcceptEstablished(OM_uint32 *minor, - gss_ctx_id_t ctx, - gss_cred_id_t cred, - gss_buffer_t inputToken, - gss_channel_bindings_t chanBindings, - gss_buffer_t outputToken) +eapGssSmAcceptCompleteInitiatorExts(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) { - /* Called with already established context */ - *minor = GSSEAP_CONTEXT_ESTABLISHED; - return GSS_S_BAD_STATUS; + GSSEAP_SM_TRANSITION_NEXT(ctx); + + *minor = 0; + + return GSS_S_CONTINUE_NEEDED; } static OM_uint32 -makeErrorToken(OM_uint32 *minor, - OM_uint32 majorStatus, - OM_uint32 minorStatus, - gss_buffer_t outputToken) +eapGssSmAcceptCompleteAcceptorExts(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) { - unsigned char errorData[8]; - gss_buffer_desc errorBuffer; + GSSEAP_SM_TRANSITION(ctx, GSSEAP_STATE_ESTABLISHED); - assert(GSS_ERROR(majorStatus)); - - /* - * Only return error codes that the initiator could have caused, - * to avoid information leakage. - */ - if (IS_RADIUS_ERROR(minorStatus)) { - /* Squash RADIUS error codes */ - minorStatus = GSSEAP_RADIUS_PROT_FAILURE; - } else if (!IS_WIRE_ERROR(minorStatus)) { - /* Don't return non-wire error codes */ - return GSS_S_COMPLETE; - } - - minorStatus -= ERROR_TABLE_BASE_eapg; - - store_uint32_be(majorStatus, &errorData[0]); - store_uint32_be(minorStatus, &errorData[4]); - - errorBuffer.length = sizeof(errorData); - errorBuffer.value = errorData; + *minor = 0; + *smFlags |= SM_FLAG_FORCE_SEND_TOKEN; - return duplicateBuffer(minor, &errorBuffer, outputToken); + return GSS_S_COMPLETE; } -static struct gss_eap_acceptor_sm { - enum gss_eap_token_type inputTokenType; - enum gss_eap_token_type outputTokenType; - OM_uint32 (*processToken)(OM_uint32 *, - gss_ctx_id_t, - gss_cred_id_t, - gss_buffer_t, - gss_channel_bindings_t, - gss_buffer_t); -} eapGssAcceptorSm[] = { - { TOK_TYPE_EAP_RESP, TOK_TYPE_EAP_REQ, eapGssSmAcceptIdentity }, - { TOK_TYPE_EAP_RESP, TOK_TYPE_EAP_REQ, eapGssSmAcceptAuthenticate }, - { TOK_TYPE_EXT_REQ, TOK_TYPE_NONE, eapGssSmAcceptExtensionsReq }, - { TOK_TYPE_NONE, TOK_TYPE_EXT_RESP, eapGssSmAcceptExtensionsResp }, - { TOK_TYPE_NONE, TOK_TYPE_NONE, eapGssSmAcceptEstablished }, - { TOK_TYPE_NONE, TOK_TYPE_CONTEXT_ERR, NULL }, +static struct gss_eap_sm eapGssAcceptorSm[] = { + { + ITOK_TYPE_ACCEPTOR_NAME_REQ, + ITOK_TYPE_ACCEPTOR_NAME_RESP, + GSSEAP_STATE_INITIAL, + 0, + eapGssSmAcceptAcceptorName + }, +#ifdef GSSEAP_DEBUG + { + ITOK_TYPE_VENDOR_INFO, + ITOK_TYPE_NONE, + GSSEAP_STATE_INITIAL, + 0, + eapGssSmAcceptVendorInfo, + }, +#endif +#ifdef GSSEAP_ENABLE_REAUTH + { + ITOK_TYPE_REAUTH_REQ, + ITOK_TYPE_REAUTH_RESP, + GSSEAP_STATE_INITIAL, + 0, + eapGssSmAcceptGssReauth, + }, +#endif + { + ITOK_TYPE_NONE, + ITOK_TYPE_EAP_REQ, + GSSEAP_STATE_INITIAL, + SM_ITOK_FLAG_REQUIRED, + eapGssSmAcceptIdentity, + }, + { + ITOK_TYPE_EAP_RESP, + ITOK_TYPE_EAP_REQ, + GSSEAP_STATE_AUTHENTICATE, + SM_ITOK_FLAG_REQUIRED, + eapGssSmAcceptAuthenticate + }, + { + ITOK_TYPE_GSS_CHANNEL_BINDINGS, + ITOK_TYPE_NONE, + GSSEAP_STATE_INITIATOR_EXTS, + SM_ITOK_FLAG_REQUIRED, + eapGssSmAcceptGssChannelBindings, + }, + { + ITOK_TYPE_NONE, + ITOK_TYPE_NONE, + GSSEAP_STATE_INITIATOR_EXTS, + 0, + eapGssSmAcceptCompleteInitiatorExts, + }, #ifdef GSSEAP_ENABLE_REAUTH - { TOK_TYPE_GSS_REAUTH, TOK_TYPE_GSS_REAUTH, eapGssSmAcceptGssReauth }, + { + ITOK_TYPE_NONE, + ITOK_TYPE_REAUTH_CREDS, + GSSEAP_STATE_ACCEPTOR_EXTS, + 0, + eapGssSmAcceptReauthCreds, + }, #endif + { + ITOK_TYPE_NONE, + ITOK_TYPE_NONE, + GSSEAP_STATE_ACCEPTOR_EXTS, + 0, + eapGssSmAcceptCompleteAcceptorExts + }, }; OM_uint32 @@ -658,14 +796,8 @@ gss_accept_sec_context(OM_uint32 *minor, OM_uint32 *time_rec, gss_cred_id_t *delegated_cred_handle) { - OM_uint32 major; - OM_uint32 tmpMajor, tmpMinor; + OM_uint32 major, tmpMinor; gss_ctx_id_t ctx = *context_handle; - struct gss_eap_acceptor_sm *sm = NULL; - gss_buffer_desc innerInputToken = GSS_C_EMPTY_BUFFER; - gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER; - enum gss_eap_token_type tokType; - int initialContextToken = 0; *minor = 0; @@ -685,7 +817,6 @@ gss_accept_sec_context(OM_uint32 *minor, if (GSS_ERROR(major)) return major; - initialContextToken = 1; *context_handle = ctx; } @@ -711,57 +842,26 @@ gss_accept_sec_context(OM_uint32 *minor, GSSEAP_MUTEX_LOCK(&cred->mutex); - sm = &eapGssAcceptorSm[ctx->state]; - - major = gssEapVerifyToken(minor, ctx, input_token, - &tokType, &innerInputToken); - if (GSS_ERROR(major)) - goto cleanup; - - if (!gssEapCredAvailable(cred, ctx->mechanismUsed)) { - *minor = GSSEAP_CRED_MECH_MISMATCH; - major = GSS_S_BAD_MECH; - goto cleanup; + if (cred->name != GSS_C_NO_NAME) { + major = gssEapDuplicateName(minor, cred->name, &ctx->acceptorName); + if (GSS_ERROR(major)) + goto cleanup; } -#ifdef GSSEAP_ENABLE_REAUTH - /* - * If we're built with fast reauthentication support, it's valid - * for an initiator to send a GSS reauthentication token as its - * initial context token, causing us to short-circuit the state - * machine and process Kerberos GSS messages instead. - */ - if (tokType == TOK_TYPE_GSS_REAUTH && initialContextToken) { - ctx->state = GSSEAP_STATE_KRB_REAUTH; - } else -#endif - if (tokType != sm->inputTokenType) { - *minor = GSSEAP_WRONG_TOK_ID; - major = GSS_S_DEFECTIVE_TOKEN; + major = gssEapSmStep(minor, + cred, + ctx, + GSS_C_NO_NAME, + GSS_C_NO_OID, + 0, + GSS_C_INDEFINITE, + input_chan_bindings, + input_token, + output_token, + eapGssAcceptorSm, + sizeof(eapGssAcceptorSm) / sizeof(eapGssAcceptorSm[0])); + if (GSS_ERROR(major)) goto cleanup; - } - - do { - sm = &eapGssAcceptorSm[ctx->state]; - - major = (sm->processToken)(minor, - ctx, - cred, - &innerInputToken, - input_chan_bindings, - &innerOutputToken); - if (GSS_ERROR(major)) { - /* Possibly generate an error token */ - tmpMajor = makeErrorToken(&tmpMinor, major, *minor, &innerOutputToken); - if (GSS_ERROR(tmpMajor)) { - major = tmpMajor; - goto cleanup; - } - - sm = &eapGssAcceptorSm[GSSEAP_STATE_ERROR]; - goto send_token; - } - } while (major == GSS_S_CONTINUE_NEEDED && innerOutputToken.length == 0); if (mech_type != NULL) { if (!gssEapInternalizeOid(ctx->mechanismUsed, mech_type)) @@ -785,18 +885,7 @@ gss_accept_sec_context(OM_uint32 *minor, } } - assert(ctx->state == GSSEAP_STATE_ESTABLISHED || major == GSS_S_CONTINUE_NEEDED); - -send_token: - if (innerOutputToken.value != NULL) { - tmpMajor = gssEapMakeToken(&tmpMinor, ctx, &innerOutputToken, - sm->outputTokenType, output_token); - if (GSS_ERROR(tmpMajor)) { - major = tmpMajor; - *minor = tmpMinor; - goto cleanup; - } - } + assert(CTX_IS_ESTABLISHED(ctx) || major == GSS_S_CONTINUE_NEEDED); cleanup: if (cred != GSS_C_NO_CREDENTIAL) @@ -806,8 +895,6 @@ cleanup: if (GSS_ERROR(major)) gssEapReleaseContext(&tmpMinor, context_handle); - gss_release_buffer(&tmpMinor, &innerOutputToken); - return major; } @@ -826,35 +913,38 @@ acceptReadyKrb(OM_uint32 *minor, if (GSS_ERROR(major)) return major; - if (cred->name != GSS_C_NO_NAME) { - major = gssEapDuplicateName(minor, cred->name, &ctx->acceptorName); - if (GSS_ERROR(major)) - return major; - } - major = gssEapReauthComplete(minor, ctx, cred, mech, timeRec); if (GSS_ERROR(major)) return major; - ctx->state = GSSEAP_STATE_ESTABLISHED; - *minor = 0; return GSS_S_COMPLETE; } static OM_uint32 eapGssSmAcceptGssReauth(OM_uint32 *minor, - gss_ctx_id_t ctx, gss_cred_id_t cred, - gss_buffer_t inputToken, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, gss_channel_bindings_t chanBindings, - gss_buffer_t outputToken) + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) { OM_uint32 major, tmpMinor; gss_name_t krbInitiator = GSS_C_NO_NAME; - gss_OID mech = GSS_C_NO_OID; OM_uint32 gssFlags, timeRec = GSS_C_INDEFINITE; + /* + * If we're built with fast reauthentication support, it's valid + * for an initiator to send a GSS reauthentication token as its + * initial context token, causing us to short-circuit the state + * machine and process Kerberos GSS messages instead. + */ + ctx->flags |= CTX_FLAG_KRB_REAUTH; major = gssAcceptSecContext(minor, @@ -871,10 +961,19 @@ eapGssSmAcceptGssReauth(OM_uint32 *minor, if (major == GSS_S_COMPLETE) { major = acceptReadyKrb(minor, ctx, cred, krbInitiator, mech, timeRec); + if (major == GSS_S_COMPLETE) { + GSSEAP_SM_TRANSITION(ctx, GSSEAP_STATE_ESTABLISHED); + } + ctx->gssFlags = gssFlags; + } else if (GSS_ERROR(major) && + (*smFlags & SM_FLAG_INPUT_TOKEN_CRITICAL) == 0) { + /* pretend reauthentication attempt never happened */ + gssDeleteSecContext(&tmpMinor, &ctx->kerberosCtx, GSS_C_NO_BUFFER); + ctx->flags &= ~(CTX_FLAG_KRB_REAUTH); + GSSEAP_SM_TRANSITION(ctx, GSSEAP_STATE_INITIAL); + major = GSS_S_CONTINUE_NEEDED; } - ctx->gssFlags = gssFlags; - gssReleaseName(&tmpMinor, &krbInitiator); return major; diff --git a/export_sec_context.c b/export_sec_context.c index e50b6e5..2ea2c5c 100644 --- a/export_sec_context.c +++ b/export_sec_context.c @@ -165,7 +165,7 @@ gssEapExportSecContext(OM_uint32 *minor, p = (unsigned char *)token->value; store_uint32_be(EAP_EXPORT_CONTEXT_V1, &p[0]); /* version */ - store_uint32_be(ctx->state, &p[4]); + store_uint32_be(GSSEAP_SM_STATE(ctx), &p[4]); store_uint32_be(ctx->flags, &p[8]); store_uint32_be(ctx->gssFlags, &p[12]); p = store_oid(ctx->mechanismUsed, &p[16]); diff --git a/gssapiP_eap.h b/gssapiP_eap.h index 18137a9..b7740da 100644 --- a/gssapiP_eap.h +++ b/gssapiP_eap.h @@ -140,18 +140,6 @@ struct gss_cred_id_struct #define CTX_IS_INITIATOR(ctx) (((ctx)->flags & CTX_FLAG_INITIATOR) != 0) -enum gss_eap_state { - GSSEAP_STATE_IDENTITY = 0, /* identify peer */ - GSSEAP_STATE_AUTHENTICATE, /* exchange EAP messages */ - GSSEAP_STATE_EXTENSIONS_REQ, /* initiator extensions */ - GSSEAP_STATE_EXTENSIONS_RESP, /* acceptor extensions */ - GSSEAP_STATE_ESTABLISHED, /* context established */ - GSSEAP_STATE_ERROR, /* context error */ -#ifdef GSSEAP_ENABLE_REAUTH - GSSEAP_STATE_KRB_REAUTH /* fast reauthentication */ -#endif -}; - #define CTX_IS_ESTABLISHED(ctx) ((ctx)->state == GSSEAP_STATE_ESTABLISHED) /* Initiator context flags */ diff --git a/gsseap_err.et b/gsseap_err.et index eb5a948..f76b7d9 100644 --- a/gsseap_err.et +++ b/gsseap_err.et @@ -43,8 +43,10 @@ error_code GSSEAP_BAD_TOK_HEADER, "Token header is malformed or co error_code GSSEAP_TOK_TRUNC, "Token is missing data" error_code GSSEAP_BAD_DIRECTION, "Packet was replayed in wrong direction" error_code GSSEAP_WRONG_TOK_ID, "Received token ID does not match expected token ID" -error_code GSSEAP_CRIT_EXT_UNAVAILABLE, "Critical extension unavailable" -error_code GSSEAP_MISSING_REQUIRED_EXT, "Missing required extension" +error_code GSSEAP_CRIT_ITOK_UNAVAILABLE, "Critical inner token type unavailable" +error_code GSSEAP_MISSING_REQUIRED_ITOK, "Missing required inner token" +error_code GSSEAP_DUPLICATE_ITOK, "Duplicate inner token received" +error_code GSSEAP_WRONG_ITOK, "Recieved invalid inner token for current state" error_code GSSEAP_KEY_UNAVAILABLE, "EAP key unavailable" error_code GSSEAP_KEY_TOO_SHORT, "EAP key too short" error_code GSSEAP_RADIUS_AUTH_FAILURE, "Authentication rejected by RADIUS server" @@ -67,6 +69,7 @@ error_code GSSEAP_BAD_CONTEXT_OPTION, "Bad context option" error_code GSSEAP_BAD_SERVICE_NAME, "Name is not a valid service name" 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" # # Credential errors @@ -93,6 +96,7 @@ error_code GSSEAP_BAD_PRF_KEY, "PRF key usage type is unknown" # error_code GSSEAP_LIBEAP_INIT_FAILURE, "Failed to initialize EAP library" error_code GSSEAP_PEER_SM_INIT_FAILURE, "Failed to create EAP state machine" +error_code GSSEAP_PEER_SM_STEP_FAILURE, "Failed to step EAP state machine" error_code GSSEAP_PEER_AUTH_FAILURE, "EAP peer authentication failure" error_code GSSEAP_PEER_BAD_MESSAGE, "Received bad EAP message" diff --git a/import_sec_context.c b/import_sec_context.c index cf46d6a..08c5005 100644 --- a/import_sec_context.c +++ b/import_sec_context.c @@ -251,8 +251,8 @@ gssEapImportContext(OM_uint32 *minor, remain -= 16; /* Validate state */ - if (ctx->state < GSSEAP_STATE_IDENTITY || - ctx->state > GSSEAP_STATE_ESTABLISHED) + if (GSSEAP_SM_STATE(ctx) < GSSEAP_STATE_INITIAL || + GSSEAP_SM_STATE(ctx) > GSSEAP_STATE_ESTABLISHED) return GSS_S_DEFECTIVE_TOKEN; /* Only acceptor can export partial context tokens */ diff --git a/init_sec_context.c b/init_sec_context.c index b80ab41..e2aa259 100644 --- a/init_sec_context.c +++ b/init_sec_context.c @@ -37,20 +37,6 @@ #include "gssapiP_eap.h" -#ifdef GSSEAP_ENABLE_REAUTH -static OM_uint32 -eapGssSmInitGssReauth(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_name_t target, - gss_OID mech, - OM_uint32 reqFlags, - OM_uint32 timeReq, - gss_channel_bindings_t chanBindings, - gss_buffer_t inputToken, - gss_buffer_t outputToken); -#endif - static OM_uint32 policyVariableToFlag(enum eapol_bool_var variable) { @@ -340,9 +326,7 @@ initBegin(OM_uint32 *minor, gss_OID mech, OM_uint32 reqFlags, OM_uint32 timeReq, - gss_channel_bindings_t chanBindings, - gss_buffer_t inputToken, - gss_buffer_t outputToken) + gss_channel_bindings_t chanBindings) { OM_uint32 major; @@ -364,16 +348,18 @@ initBegin(OM_uint32 *minor, if (GSS_ERROR(major)) return major; - GSSEAP_MUTEX_LOCK(&target->mutex); + if (target != GSS_C_NO_NAME) { + GSSEAP_MUTEX_LOCK(&target->mutex); + + major = gssEapDuplicateName(minor, target, &ctx->acceptorName); + if (GSS_ERROR(major)) { + GSSEAP_MUTEX_UNLOCK(&target->mutex); + return major; + } - major = gssEapDuplicateName(minor, target, &ctx->acceptorName); - if (GSS_ERROR(major)) { GSSEAP_MUTEX_UNLOCK(&target->mutex); - return major; } - GSSEAP_MUTEX_UNLOCK(&target->mutex); - if (mech == GSS_C_NULL_OID) { major = gssEapDefaultMech(minor, &ctx->mechanismUsed); } else if (gssEapIsConcreteMechanismOid(mech)) { @@ -397,6 +383,182 @@ initBegin(OM_uint32 *minor, } static OM_uint32 +eapGssSmInitError(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) +{ + OM_uint32 major; + unsigned char *p; + + if (inputToken->length < 8) { + *minor = GSSEAP_TOK_TRUNC; + return GSS_S_DEFECTIVE_TOKEN; + } + + p = (unsigned char *)inputToken->value; + + major = load_uint32_be(&p[0]); + *minor = ERROR_TABLE_BASE_eapg + load_uint32_be(&p[4]); + + if (!GSS_ERROR(major) || !IS_WIRE_ERROR(*minor)) { + major = GSS_S_FAILURE; + *minor = GSSEAP_BAD_ERROR_TOKEN; + } + + assert(GSS_ERROR(major)); + + return major; +} + +#ifdef GSSEAP_ENABLE_REAUTH +static OM_uint32 +eapGssSmInitGssReauth(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) +{ + OM_uint32 major, tmpMinor; + gss_name_t mechTarget = GSS_C_NO_NAME; + gss_OID actualMech = GSS_C_NO_OID; + OM_uint32 gssFlags, timeRec; + + assert(cred != GSS_C_NO_CREDENTIAL); + + if (GSSEAP_SM_STATE(ctx) == GSSEAP_STATE_INITIAL) { + if (!gssEapCanReauthP(cred, target, timeReq)) + return GSS_S_CONTINUE_NEEDED; + + ctx->flags |= CTX_FLAG_KRB_REAUTH; + } else if ((ctx->flags & CTX_FLAG_KRB_REAUTH) == 0) { + major = GSS_S_DEFECTIVE_TOKEN; + *minor = GSSEAP_WRONG_ITOK; + goto cleanup; + } + + major = gssEapMechToGlueName(minor, target, &mechTarget); + if (GSS_ERROR(major)) + goto cleanup; + + major = gssInitSecContext(minor, + cred->krbCred, + &ctx->kerberosCtx, + mechTarget, + (gss_OID)gss_mech_krb5, + reqFlags | GSS_C_MUTUAL_FLAG, + timeReq, + chanBindings, + inputToken, + &actualMech, + outputToken, + &gssFlags, + &timeRec); + if (GSS_ERROR(major)) + goto cleanup; + + ctx->gssFlags = gssFlags; + + if (major == GSS_S_COMPLETE) { + assert(GSSEAP_SM_STATE(ctx) == GSSEAP_STATE_REAUTHENTICATE); + + major = gssEapReauthComplete(minor, ctx, cred, actualMech, timeRec); + if (GSS_ERROR(major)) + goto cleanup; + GSSEAP_SM_TRANSITION(ctx, GSSEAP_STATE_ESTABLISHED); + } else { + GSSEAP_SM_TRANSITION(ctx, GSSEAP_STATE_REAUTHENTICATE); + } + +cleanup: + gssReleaseName(&tmpMinor, &mechTarget); + + return major; +} +#endif /* GSSEAP_ENABLE_REAUTH */ + +#ifdef GSSEAP_DEBUG +static OM_uint32 +eapGssSmInitVendorInfo(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) +{ + OM_uint32 major; + + major = makeStringBuffer(minor, "JANET(UK)", outputToken); + if (GSS_ERROR(major)) + return major; + + return GSS_S_CONTINUE_NEEDED; +} +#endif + +static OM_uint32 +eapGssSmInitAcceptorName(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) +{ + OM_uint32 major; + + if (GSSEAP_SM_STATE(ctx) == GSSEAP_STATE_INITIAL && + ctx->acceptorName != GSS_C_NO_NAME) { + + /* Send desired target name to acceptor */ + major = gssEapDisplayName(minor, ctx->acceptorName, + 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 */ + major = gssEapImportName(minor, inputToken, + GSS_C_NT_USER_NAME, &ctx->acceptorName); + if (GSS_ERROR(major)) + return major; + } + + /* + * Currently, other parts of the code assume that the acceptor name + * is available, hence this check. + */ + if (ctx->acceptorName == GSS_C_NO_NAME) { + *minor = GSSEAP_NO_ACCEPTOR_NAME; + return GSS_S_FAILURE; + } + + return GSS_S_CONTINUE_NEEDED; +} + +static OM_uint32 eapGssSmInitIdentity(OM_uint32 *minor, gss_cred_id_t cred, gss_ctx_id_t ctx, @@ -406,31 +568,52 @@ eapGssSmInitIdentity(OM_uint32 *minor, OM_uint32 timeReq, gss_channel_bindings_t chanBindings, gss_buffer_t inputToken, - gss_buffer_t outputToken) + gss_buffer_t outputToken, + OM_uint32 *smFlags) { - OM_uint32 major; - int initialContextToken; + struct eap_config eapConfig; - initialContextToken = (inputToken->length == 0); - if (!initialContextToken) { - *minor = GSSEAP_WRONG_SIZE; - return GSS_S_DEFECTIVE_TOKEN; +#ifdef GSSEAP_ENABLE_REAUTH + if (GSSEAP_SM_STATE(ctx) == GSSEAP_STATE_REAUTHENTICATE) { + OM_uint32 tmpMinor; + + /* server didn't support reauthentication, sent EAP request */ + gssDeleteSecContext(&tmpMinor, &ctx->kerberosCtx, GSS_C_NO_BUFFER); + ctx->flags &= ~(CTX_FLAG_KRB_REAUTH); + GSSEAP_SM_TRANSITION(ctx, GSSEAP_STATE_INITIAL); + } else +#endif + *smFlags |= SM_FLAG_FORCE_SEND_TOKEN; + + assert((ctx->flags & CTX_FLAG_KRB_REAUTH) == 0); + assert(inputToken == GSS_C_NO_BUFFER); + + memset(&eapConfig, 0, sizeof(eapConfig)); + + ctx->initiatorCtx.eap = eap_peer_sm_init(ctx, + &gssEapPolicyCallbacks, + ctx, + &eapConfig); + if (ctx->initiatorCtx.eap == NULL) { + *minor = GSSEAP_PEER_SM_INIT_FAILURE; + return GSS_S_FAILURE; } - major = initBegin(minor, cred, ctx, target, mech, - reqFlags, timeReq, chanBindings, - inputToken, outputToken); - if (GSS_ERROR(major)) - return major; + ctx->flags |= CTX_FLAG_EAP_RESTART | CTX_FLAG_EAP_PORT_ENABLED; + + /* poke EAP state machine */ + if (eap_peer_sm_step(ctx->initiatorCtx.eap) != 0) { + *minor = GSSEAP_PEER_SM_STEP_FAILURE; + return GSS_S_FAILURE; + } - ctx->state = GSSEAP_STATE_AUTHENTICATE; + GSSEAP_SM_TRANSITION_NEXT(ctx); *minor = 0; + return GSS_S_CONTINUE_NEEDED; } -static struct wpabuf emptyWpaBuffer; - static OM_uint32 eapGssSmInitAuthenticate(OM_uint32 *minor, gss_cred_id_t cred, @@ -441,40 +624,24 @@ eapGssSmInitAuthenticate(OM_uint32 *minor, OM_uint32 timeReq, gss_channel_bindings_t chanBindings, gss_buffer_t inputToken, - gss_buffer_t outputToken) + gss_buffer_t outputToken, + OM_uint32 *smFlags) { OM_uint32 major; OM_uint32 tmpMinor; int code; struct wpabuf *resp = NULL; - int initialContextToken; *minor = 0; - initialContextToken = (inputToken == GSS_C_NO_BUFFER || - inputToken->length == 0); + assert(inputToken != GSS_C_NO_BUFFER); major = peerConfigInit(minor, cred, ctx); if (GSS_ERROR(major)) goto cleanup; - if (ctx->initiatorCtx.eap == NULL) { - struct eap_config eapConfig; - - memset(&eapConfig, 0, sizeof(eapConfig)); - - ctx->initiatorCtx.eap = eap_peer_sm_init(ctx, - &gssEapPolicyCallbacks, - ctx, - &eapConfig); - if (ctx->initiatorCtx.eap == NULL) { - major = GSS_S_FAILURE; - *minor = GSSEAP_PEER_SM_INIT_FAILURE; - goto cleanup; - } - - ctx->flags |= CTX_FLAG_EAP_RESTART | CTX_FLAG_EAP_PORT_ENABLED; - } + assert(ctx->initiatorCtx.eap != NULL); + assert(ctx->flags & CTX_FLAG_EAP_PORT_ENABLED); ctx->flags |= CTX_FLAG_EAP_REQ; /* we have a Request from the acceptor */ @@ -495,13 +662,10 @@ eapGssSmInitAuthenticate(OM_uint32 *minor, ctx->flags &= ~(CTX_FLAG_EAP_SUCCESS); major = GSS_S_CONTINUE_NEEDED; - ctx->state = GSSEAP_STATE_EXTENSIONS_REQ; + GSSEAP_SM_TRANSITION_NEXT(ctx); } else if (ctx->flags & CTX_FLAG_EAP_FAIL) { major = GSS_S_DEFECTIVE_CREDENTIAL; *minor = GSSEAP_PEER_AUTH_FAILURE; - } else if (code == 0 && initialContextToken) { - resp = &emptyWpaBuffer; - major = GSS_S_CONTINUE_NEEDED; } else { major = GSS_S_DEFECTIVE_TOKEN; *minor = GSSEAP_PEER_BAD_MESSAGE; @@ -522,6 +686,8 @@ cleanup: major = tmpMajor; *minor = tmpMinor; } + + *smFlags |= SM_FLAG_OUTPUT_TOKEN_CRITICAL; } wpabuf_set(&ctx->initiatorCtx.reqData, NULL, 0); @@ -531,57 +697,40 @@ cleanup: } static OM_uint32 -eapGssSmInitExtensionsReq(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_name_t target, - gss_OID mech, - OM_uint32 reqFlags, - OM_uint32 timeReq, - gss_channel_bindings_t chanBindings, - gss_buffer_t inputToken, - gss_buffer_t outputToken) +eapGssSmInitGssChannelBindings(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) { OM_uint32 major; + gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; + + if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS) + buffer = chanBindings->application_data; - major = gssEapMakeExtensions(minor, cred, ctx, chanBindings, outputToken); + major = gssEapWrap(minor, ctx, TRUE, GSS_C_QOP_DEFAULT, + &buffer, NULL, outputToken); if (GSS_ERROR(major)) return major; assert(outputToken->value != NULL); - ctx->state = GSSEAP_STATE_EXTENSIONS_RESP; - *minor = 0; - return GSS_S_CONTINUE_NEEDED; -} - -static OM_uint32 -eapGssSmInitExtensionsResp(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_name_t target, - gss_OID mech, - OM_uint32 reqFlags, - OM_uint32 timeReq, - gss_channel_bindings_t chanBindings, - gss_buffer_t inputToken, - gss_buffer_t outputToken) -{ - OM_uint32 major; + *smFlags |= SM_FLAG_OUTPUT_TOKEN_CRITICAL; - major = gssEapVerifyExtensions(minor, cred, ctx, chanBindings, inputToken); - if (GSS_ERROR(major)) - return major; - - ctx->state = GSSEAP_STATE_ESTABLISHED; - - *minor = 0; - return GSS_S_COMPLETE; + return GSS_S_CONTINUE_NEEDED; } +#ifdef GSSEAP_ENABLE_REAUTH static OM_uint32 -eapGssSmInitEstablished(OM_uint32 *minor, +eapGssSmInitReauthCreds(OM_uint32 *minor, gss_cred_id_t cred, gss_ctx_id_t ctx, gss_name_t target, @@ -590,69 +739,144 @@ eapGssSmInitEstablished(OM_uint32 *minor, OM_uint32 timeReq, gss_channel_bindings_t chanBindings, gss_buffer_t inputToken, - gss_buffer_t outputToken) + gss_buffer_t outputToken, + OM_uint32 *smFlags) { - /* Called with already established context */ - *minor = GSSEAP_CONTEXT_ESTABLISHED; - return GSS_S_BAD_STATUS; + OM_uint32 major; + + if (ctx->gssFlags & GSS_C_MUTUAL_FLAG) { + major = gssEapStoreReauthCreds(minor, ctx, cred, inputToken); + if (GSS_ERROR(major)) + return major; + } + + *minor = 0; + return GSS_S_CONTINUE_NEEDED; } +#endif /* GSSEAP_ENABLE_REAUTH */ static OM_uint32 -eapGssSmInitError(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_name_t target, - gss_OID mech, - OM_uint32 reqFlags, - OM_uint32 timeReq, - gss_channel_bindings_t chanBindings, - gss_buffer_t inputToken, - gss_buffer_t outputToken) +eapGssSmInitCompleteInitiatorExts(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) { - OM_uint32 major; - unsigned char *p; + GSSEAP_SM_TRANSITION_NEXT(ctx); - if (inputToken->length < 8) { - *minor = GSSEAP_TOK_TRUNC; - return GSS_S_DEFECTIVE_TOKEN; - } + *minor = 0; + *smFlags |= SM_FLAG_FORCE_SEND_TOKEN; - p = (unsigned char *)inputToken->value; + return GSS_S_CONTINUE_NEEDED; +} - major = load_uint32_be(&p[0]); - *minor = ERROR_TABLE_BASE_eapg + load_uint32_be(&p[4]); +static OM_uint32 +eapGssSmInitCompleteAcceptorExts(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + OM_uint32 *smFlags) +{ + GSSEAP_SM_TRANSITION(ctx, GSSEAP_STATE_ESTABLISHED); - if (!GSS_ERROR(major) || !IS_WIRE_ERROR(*minor)) { - major = GSS_S_FAILURE; - *minor = GSSEAP_BAD_ERROR_TOKEN; - } + *minor = 0; - return major; + return GSS_S_COMPLETE; } -static struct gss_eap_initiator_sm { - enum gss_eap_token_type inputTokenType; - enum gss_eap_token_type outputTokenType; - OM_uint32 (*processToken)(OM_uint32 *, - gss_cred_id_t, - gss_ctx_id_t, - gss_name_t, - gss_OID, - OM_uint32, - OM_uint32, - gss_channel_bindings_t, - gss_buffer_t, - gss_buffer_t); -} eapGssInitiatorSm[] = { - { TOK_TYPE_NONE, TOK_TYPE_EAP_RESP, eapGssSmInitIdentity }, - { TOK_TYPE_EAP_REQ, TOK_TYPE_EAP_RESP, eapGssSmInitAuthenticate }, - { TOK_TYPE_NONE, TOK_TYPE_EXT_REQ, eapGssSmInitExtensionsReq }, - { TOK_TYPE_EXT_RESP, TOK_TYPE_NONE, eapGssSmInitExtensionsResp }, - { TOK_TYPE_NONE, TOK_TYPE_NONE, eapGssSmInitEstablished }, - { TOK_TYPE_CONTEXT_ERR, TOK_TYPE_NONE, eapGssSmInitError }, +static struct gss_eap_sm eapGssInitiatorSm[] = { + { + ITOK_TYPE_CONTEXT_ERR, + ITOK_TYPE_NONE, + GSSEAP_STATE_ALL & ~(GSSEAP_STATE_INITIAL), + 0, + eapGssSmInitError + }, + { + ITOK_TYPE_ACCEPTOR_NAME_RESP, + ITOK_TYPE_ACCEPTOR_NAME_REQ, + GSSEAP_STATE_INITIAL | GSSEAP_STATE_AUTHENTICATE, + 0, + eapGssSmInitAcceptorName + }, +#ifdef GSSEAP_DEBUG + { + ITOK_TYPE_NONE, + ITOK_TYPE_VENDOR_INFO, + GSSEAP_STATE_INITIAL, + 0, + eapGssSmInitVendorInfo + }, +#endif +#ifdef GSSEAP_ENABLE_REAUTH + { + ITOK_TYPE_REAUTH_RESP, + ITOK_TYPE_REAUTH_REQ, + GSSEAP_STATE_INITIAL | GSSEAP_STATE_REAUTHENTICATE, + 0, + eapGssSmInitGssReauth + }, +#endif + { + ITOK_TYPE_NONE, + ITOK_TYPE_NONE, +#ifdef GSSEAP_ENABLE_REAUTH + GSSEAP_STATE_REAUTHENTICATE | +#endif + GSSEAP_STATE_INITIAL, + SM_ITOK_FLAG_REQUIRED, + eapGssSmInitIdentity + }, + { + ITOK_TYPE_EAP_REQ, + ITOK_TYPE_EAP_RESP, + GSSEAP_STATE_AUTHENTICATE, + SM_ITOK_FLAG_REQUIRED, + eapGssSmInitAuthenticate + }, + { + ITOK_TYPE_NONE, + ITOK_TYPE_GSS_CHANNEL_BINDINGS, + GSSEAP_STATE_INITIATOR_EXTS, + SM_ITOK_FLAG_REQUIRED, + eapGssSmInitGssChannelBindings + }, + { + ITOK_TYPE_NONE, + ITOK_TYPE_NONE, + GSSEAP_STATE_INITIATOR_EXTS, + 0, + eapGssSmInitCompleteInitiatorExts + }, #ifdef GSSEAP_ENABLE_REAUTH - { TOK_TYPE_GSS_REAUTH, TOK_TYPE_GSS_REAUTH, eapGssSmInitGssReauth }, + { + ITOK_TYPE_REAUTH_CREDS, + ITOK_TYPE_NONE, + GSSEAP_STATE_ACCEPTOR_EXTS, + 0, + eapGssSmInitReauthCreds + }, #endif + /* other extensions go here */ + { + ITOK_TYPE_NONE, + ITOK_TYPE_NONE, + GSSEAP_STATE_ACCEPTOR_EXTS, + 0, + eapGssSmInitCompleteAcceptorExts + } }; OM_uint32 @@ -670,14 +894,8 @@ gss_init_sec_context(OM_uint32 *minor, OM_uint32 *ret_flags, OM_uint32 *time_rec) { - OM_uint32 major; - OM_uint32 tmpMajor, tmpMinor; + OM_uint32 major, tmpMinor; gss_ctx_id_t ctx = *context_handle; - struct gss_eap_initiator_sm *sm = NULL; - gss_buffer_desc innerInputToken; - gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER; - enum gss_eap_token_type tokType; - int initialContextToken = 0; *minor = 0; @@ -696,7 +914,13 @@ gss_init_sec_context(OM_uint32 *minor, ctx->flags |= CTX_FLAG_INITIATOR; - initialContextToken = 1; + major = initBegin(minor, cred, ctx, target_name, mech_type, + req_flags, time_req, input_chan_bindings); + if (GSS_ERROR(major)) { + gssEapReleaseContext(minor, &ctx); + return major; + } + *context_handle = ctx; } @@ -722,10 +946,6 @@ gss_init_sec_context(OM_uint32 *minor, GSSEAP_MUTEX_LOCK(&cred->mutex); -#ifdef GSSEAP_ENABLE_REAUTH - if (initialContextToken && gssEapCanReauthP(cred, target_name, time_req)) - ctx->state = GSSEAP_STATE_KRB_REAUTH; -#endif if ((cred->flags & CRED_FLAG_INITIATE) == 0) { major = GSS_S_NO_CRED; @@ -733,66 +953,31 @@ gss_init_sec_context(OM_uint32 *minor, goto cleanup; } - sm = &eapGssInitiatorSm[ctx->state]; - - if (input_token != GSS_C_NO_BUFFER && input_token->length != 0) { - major = gssEapVerifyToken(minor, ctx, input_token, - &tokType, &innerInputToken); - if (GSS_ERROR(major)) - goto cleanup; - - if (tokType == TOK_TYPE_CONTEXT_ERR) { - ctx->state = GSSEAP_STATE_ERROR; - } else if (tokType != sm->inputTokenType) { - major = GSS_S_DEFECTIVE_TOKEN; - *minor = GSSEAP_WRONG_TOK_ID; - goto cleanup; - } - } else { - innerInputToken.length = 0; - innerInputToken.value = NULL; - } - - /* - * Advance through state machine whilst empty tokens are emitted and - * the status is not GSS_S_COMPLETE or an error status. - */ - do { - sm = &eapGssInitiatorSm[ctx->state]; - - major = (sm->processToken)(minor, - cred, - ctx, - target_name, - mech_type, - req_flags, - time_req, - input_chan_bindings, - &innerInputToken, - &innerOutputToken); - if (GSS_ERROR(major)) - goto cleanup; - } while (major == GSS_S_CONTINUE_NEEDED && innerOutputToken.value == NULL); + major = gssEapSmStep(minor, + cred, + ctx, + target_name, + mech_type, + req_flags, + time_req, + input_chan_bindings, + input_token, + output_token, + eapGssInitiatorSm, + sizeof(eapGssInitiatorSm) / sizeof(eapGssInitiatorSm[0])); + if (GSS_ERROR(major)) + goto cleanup; if (actual_mech_type != NULL) { if (!gssEapInternalizeOid(ctx->mechanismUsed, actual_mech_type)) duplicateOid(&tmpMinor, ctx->mechanismUsed, actual_mech_type); } - if (innerOutputToken.value != NULL) { - tmpMajor = gssEapMakeToken(&tmpMinor, ctx, &innerOutputToken, - sm->outputTokenType, output_token); - if (GSS_ERROR(tmpMajor)) { - major = tmpMajor; - *minor = tmpMinor; - goto cleanup; - } - } if (ret_flags != NULL) *ret_flags = ctx->gssFlags; if (time_rec != NULL) gssEapContextTime(&tmpMinor, ctx, time_rec); - assert(ctx->state == GSSEAP_STATE_ESTABLISHED || major == GSS_S_CONTINUE_NEEDED); + assert(CTX_IS_ESTABLISHED(ctx) || major == GSS_S_CONTINUE_NEEDED); cleanup: if (cred != GSS_C_NO_CREDENTIAL) @@ -802,73 +987,5 @@ cleanup: if (GSS_ERROR(major)) gssEapReleaseContext(&tmpMinor, context_handle); - gss_release_buffer(&tmpMinor, &innerOutputToken); - return major; } - -#ifdef GSSEAP_ENABLE_REAUTH -static OM_uint32 -eapGssSmInitGssReauth(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_name_t target, - gss_OID mech, - OM_uint32 reqFlags, - OM_uint32 timeReq, - gss_channel_bindings_t chanBindings, - gss_buffer_t inputToken, - gss_buffer_t outputToken) -{ - OM_uint32 major, tmpMinor; - gss_name_t mechTarget = GSS_C_NO_NAME; - gss_OID actualMech = GSS_C_NO_OID; - OM_uint32 gssFlags, timeRec; - - assert(cred != GSS_C_NO_CREDENTIAL); - - ctx->flags |= CTX_FLAG_KRB_REAUTH; - - if (inputToken->length == 0) { - major = initBegin(minor, cred, ctx, target, mech, - reqFlags, timeReq, chanBindings, - inputToken, outputToken); - if (GSS_ERROR(major)) - goto cleanup; - } - - major = gssEapMechToGlueName(minor, target, &mechTarget); - if (GSS_ERROR(major)) - goto cleanup; - - major = gssInitSecContext(minor, - cred->krbCred, - &ctx->kerberosCtx, - mechTarget, - (gss_OID)gss_mech_krb5, - reqFlags, /* | GSS_C_DCE_STYLE, */ - timeReq, - chanBindings, - inputToken, - &actualMech, - outputToken, - &gssFlags, - &timeRec); - if (GSS_ERROR(major)) - goto cleanup; - - ctx->gssFlags = gssFlags; - - if (major == GSS_S_COMPLETE) { - major = gssEapReauthComplete(minor, ctx, cred, actualMech, timeRec); - if (GSS_ERROR(major)) - goto cleanup; - ctx->state = GSSEAP_STATE_ESTABLISHED; - } - -cleanup: - gssReleaseName(&tmpMinor, &mechTarget); - - return major; -} -#endif /* GSSEAP_ENABLE_REAUTH */ diff --git a/unwrap_iov.c b/unwrap_iov.c index d5c3a52..fa9a6a5 100644 --- a/unwrap_iov.c +++ b/unwrap_iov.c @@ -70,7 +70,7 @@ unwrapToken(OM_uint32 *minor, #ifdef HAVE_HEIMDAL_VERSION krb5_crypto krbCrypto, #else - krb5_keyblock *unused __attribute__((__unused__)), + krb5_keyblock *unused, #endif int *conf_state, gss_qop_t *qop_state, diff --git a/util.h b/util.h index 004c47e..e6c08ef 100644 --- a/util.h +++ b/util.h @@ -154,14 +154,29 @@ enum gss_eap_token_type { TOK_TYPE_EXPORT_NAME = 0x0401, /* RFC 2743 exported name */ TOK_TYPE_EXPORT_NAME_COMPOSITE = 0x0402, /* exported composite name */ TOK_TYPE_DELETE_CONTEXT = 0x0405, /* RFC 2743 delete context */ - TOK_TYPE_EAP_RESP = 0x0601, /* EAP response */ - TOK_TYPE_EAP_REQ = 0x0602, /* EAP request */ - TOK_TYPE_EXT_REQ = 0x0603, /* GSS EAP extensions request */ - TOK_TYPE_EXT_RESP = 0x0604, /* GSS EAP extensions response */ - TOK_TYPE_GSS_REAUTH = 0x0605, /* GSS EAP fast reauthentication token */ - TOK_TYPE_CONTEXT_ERR = 0x0606, /* context error */ + TOK_TYPE_INITIATOR_CONTEXT = 0x0601, /* initiator-sent context token */ + TOK_TYPE_ACCEPTOR_CONTEXT = 0x0602, /* acceptor-sent context token */ }; +/* inner token types and flags */ +#define ITOK_TYPE_NONE 0x00000000 +#define ITOK_TYPE_CONTEXT_ERR 0x00000001 /* critical */ +#define ITOK_TYPE_ACCEPTOR_NAME_REQ 0x00000002 /* TBD */ +#define ITOK_TYPE_ACCEPTOR_NAME_RESP 0x00000003 /* TBD */ +#define ITOK_TYPE_EAP_RESP 0x00000004 /* critical, required, if not reauth */ +#define ITOK_TYPE_EAP_REQ 0x00000005 /* critical, required, if not reauth */ +#define ITOK_TYPE_GSS_CHANNEL_BINDINGS 0x00000006 /* critical, required, if not reauth */ +#define ITOK_TYPE_REAUTH_CREDS 0x00000007 /* optional */ +#define ITOK_TYPE_REAUTH_REQ 0x00000008 /* optional */ +#define ITOK_TYPE_REAUTH_RESP 0x00000009 /* optional */ +#define ITOK_TYPE_VERSION_INFO 0x0000000A /* optional */ +#define ITOK_TYPE_VENDOR_INFO 0x0000000B /* optional */ + +#define ITOK_FLAG_CRITICAL 0x80000000 /* critical, wire flag */ +#define ITOK_FLAG_VERIFIED 0x40000000 /* verified, API flag */ + +#define ITOK_TYPE_MASK (~(ITOK_FLAG_CRITICAL | ITOK_FLAG_VERIFIED)) + OM_uint32 gssEapAllocContext(OM_uint32 *minor, gss_ctx_id_t *pCtx); OM_uint32 gssEapReleaseContext(OM_uint32 *minor, gss_ctx_id_t *pCtx); @@ -260,44 +275,6 @@ gssEapDeriveRfc3961Key(OM_uint32 *minor, krb5_enctype enctype, krb5_keyblock *pKey); -/* util_exts.c */ -#define EXT_FLAG_CRITICAL 0x80000000 /* critical, wire flag */ -#define EXT_FLAG_VERIFIED 0x40000000 /* verified, API flag */ - -#define EXT_TYPE_GSS_CHANNEL_BINDINGS 0x00000000 -#define EXT_TYPE_REAUTH_CREDS 0x00000001 -#define EXT_TYPE_MASK (~(EXT_FLAG_CRITICAL | EXT_FLAG_VERIFIED)) - -struct gss_eap_extension_provider { - OM_uint32 type; - int critical; /* client */ - int required; /* server */ - OM_uint32 (*make)(OM_uint32 *, - gss_cred_id_t, - gss_ctx_id_t, - gss_channel_bindings_t, - gss_buffer_t); - OM_uint32 (*verify)(OM_uint32 *, - gss_cred_id_t, - gss_ctx_id_t, - gss_channel_bindings_t, - const gss_buffer_t); -}; - -OM_uint32 -gssEapMakeExtensions(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_channel_bindings_t chanBindings, - gss_buffer_t buffer); - -OM_uint32 -gssEapVerifyExtensions(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_channel_bindings_t chanBindings, - const gss_buffer_t buffer); - /* util_krb.c */ #ifdef HAVE_HEIMDAL_VERSION @@ -557,7 +534,89 @@ OM_uint32 sequenceInit(OM_uint32 *minor, void **vqueue, uint64_t seqnum, int do_replay, int do_sequence, int wide_nums); +/* util_sm.c */ +enum gss_eap_state { + GSSEAP_STATE_INITIAL = 0x01, /* initial state */ + GSSEAP_STATE_AUTHENTICATE = 0x02, /* exchange EAP messages */ + GSSEAP_STATE_INITIATOR_EXTS = 0x04, /* initiator extensions */ + GSSEAP_STATE_ACCEPTOR_EXTS = 0x08, /* acceptor extensions */ +#ifdef GSSEAP_ENABLE_REAUTH + GSSEAP_STATE_REAUTHENTICATE = 0x10, /* GSS reauthentication messages */ +#endif + GSSEAP_STATE_ESTABLISHED = 0x20, /* context established */ + GSSEAP_STATE_ALL = 0x3F +}; + +#define GSSEAP_STATE_NEXT(s) ((s) << 1) + +#define GSSEAP_SM_STATE(ctx) ((ctx)->state) + +#ifdef GSSEAP_DEBUG +void gssEapSmTransition(gss_ctx_id_t ctx, enum gss_eap_state state); +#define GSSEAP_SM_TRANSITION(ctx, state) gssEapSmTransition((ctx), (state)) +#else +#define GSSEAP_SM_TRANSITION(ctx, newstate) do { (ctx)->state = (newstate); } while (0) +#endif + +#define GSSEAP_SM_TRANSITION_NEXT(ctx) GSSEAP_SM_TRANSITION((ctx), GSSEAP_STATE_NEXT(GSSEAP_SM_STATE((ctx)))) + +/* state machine entry */ +struct gss_eap_sm { + OM_uint32 inputTokenType; + OM_uint32 outputTokenType; + enum gss_eap_state validStates; + OM_uint32 itokFlags; + OM_uint32 (*processToken)(OM_uint32 *, + gss_cred_id_t, + gss_ctx_id_t, + gss_name_t, + gss_OID, + OM_uint32, + OM_uint32, + gss_channel_bindings_t, + gss_buffer_t, + gss_buffer_t, + OM_uint32 *); +}; + +/* state machine flags, set by handler */ +#define SM_FLAG_FORCE_SEND_TOKEN 0x00000001 /* send token even if no inner tokens */ +#define SM_FLAG_OUTPUT_TOKEN_CRITICAL 0x00000002 /* output token is critical */ + +/* state machine flags, set by state machine */ +#define SM_FLAG_INPUT_TOKEN_CRITICAL 0x10000000 /* input token was critical */ + +#define SM_ITOK_FLAG_REQUIRED 0x00000001 /* received tokens must be present */ + +OM_uint32 +gssEapSmStep(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + struct gss_eap_sm *sm, + size_t smCount); + +void +gssEapSmTransition(gss_ctx_id_t ctx, enum gss_eap_state state); + /* util_token.c */ +OM_uint32 +gssEapEncodeInnerTokens(OM_uint32 *minor, + gss_buffer_set_t extensions, + OM_uint32 *types, + gss_buffer_t buffer); +OM_uint32 +gssEapDecodeInnerTokens(OM_uint32 *minor, + const gss_buffer_t buffer, + gss_buffer_set_t *pExtensions, + OM_uint32 **pTypes); + size_t tokenSize(const gss_OID_desc *mech, size_t body_size); diff --git a/util_context.c b/util_context.c index 6aedf17..4fb8161 100644 --- a/util_context.c +++ b/util_context.c @@ -57,7 +57,7 @@ gssEapAllocContext(OM_uint32 *minor, return GSS_S_FAILURE; } - ctx->state = GSSEAP_STATE_IDENTITY; + ctx->state = GSSEAP_STATE_INITIAL; /* * Integrity, confidentiality, sequencing and replay detection are diff --git a/util_exts.c b/util_exts.c deleted file mode 100644 index 88cec5b..0000000 --- a/util_exts.c +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright (c) 2011, JANET(UK) - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of JANET(UK) nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* - * Extension token support. - */ - -#include "gssapiP_eap.h" - -static OM_uint32 -encodeExtensions(OM_uint32 *minor, - gss_buffer_set_t extensions, - OM_uint32 *types, - gss_buffer_t buffer); - -static OM_uint32 -decodeExtensions(OM_uint32 *minor, - const gss_buffer_t buffer, - gss_buffer_set_t *pExtensions, - OM_uint32 **pTypes); - -/* - * Initiator extensions - */ -static OM_uint32 -makeGssChannelBindings(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_channel_bindings_t chanBindings, - gss_buffer_t outputToken) -{ - OM_uint32 major; - gss_buffer_desc buffer = GSS_C_EMPTY_BUFFER; - - if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS) - buffer = chanBindings->application_data; - - major = gssEapWrap(minor, ctx, TRUE, GSS_C_QOP_DEFAULT, - &buffer, NULL, outputToken); - if (GSS_ERROR(major)) - return major; - - return GSS_S_COMPLETE; -} - -static OM_uint32 -verifyGssChannelBindings(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_channel_bindings_t chanBindings, - gss_buffer_t inputToken) -{ - OM_uint32 major, tmpMinor; - gss_iov_buffer_desc iov[2]; - - iov[0].type = GSS_IOV_BUFFER_TYPE_DATA | GSS_IOV_BUFFER_FLAG_ALLOCATE; - iov[0].buffer.length = 0; - iov[0].buffer.value = NULL; - - iov[1].type = GSS_IOV_BUFFER_TYPE_STREAM; - iov[1].buffer = *inputToken; - - major = gssEapUnwrapOrVerifyMIC(minor, ctx, NULL, NULL, - iov, 2, TOK_TYPE_WRAP); - if (GSS_ERROR(major)) - return GSS_S_BAD_BINDINGS; - - if (chanBindings != GSS_C_NO_CHANNEL_BINDINGS && - !bufferEqual(&iov[0].buffer, &chanBindings->application_data)) { - major = GSS_S_BAD_BINDINGS; - *minor = GSSEAP_BINDINGS_MISMATCH; - } else { - major = GSS_S_COMPLETE; - } - - gss_release_buffer(&tmpMinor, &iov[0].buffer); - - return major; -} - -static struct gss_eap_extension_provider -eapGssInitExtensions[] = { - { - EXT_TYPE_GSS_CHANNEL_BINDINGS, - 1, /* critical */ - 1, /* required */ - makeGssChannelBindings, - verifyGssChannelBindings - }, -}; - -/* - * Acceptor extensions - */ -static OM_uint32 -makeReauthCreds(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_channel_bindings_t chanBindings, - gss_buffer_t outputToken) -{ - OM_uint32 major = GSS_S_UNAVAILABLE; - -#ifdef GSSEAP_ENABLE_REAUTH - /* - * If we're built with fast reauthentication enabled, then - * fabricate a ticket from the initiator to ourselves. - */ - major = gssEapMakeReauthCreds(minor, ctx, cred, outputToken); -#endif - - return major; -} - -static OM_uint32 -verifyReauthCreds(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_channel_bindings_t chanBindings, - gss_buffer_t inputToken) -{ - OM_uint32 major = GSS_S_UNAVAILABLE; - -#ifdef GSSEAP_ENABLE_REAUTH - major = gssEapStoreReauthCreds(minor, ctx, cred, inputToken); -#endif - - return major; -} - -static struct gss_eap_extension_provider -eapGssAcceptExtensions[] = { - { - EXT_TYPE_REAUTH_CREDS, - 0, /* critical */ - 0, /* required */ - makeReauthCreds, - verifyReauthCreds - }, -}; - -OM_uint32 -makeExtensions(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - const struct gss_eap_extension_provider *exts, - size_t nexts, - gss_channel_bindings_t chanBindings, - gss_buffer_t buffer) -{ - OM_uint32 major, tmpMinor; - size_t i, j; - gss_buffer_set_t extensions = GSS_C_NO_BUFFER_SET; - OM_uint32 *types; - - assert(buffer != GSS_C_NO_BUFFER); - - buffer->length = 0; - buffer->value = NULL; - - types = GSSEAP_CALLOC(nexts, sizeof(OM_uint32)); - if (types == NULL) { - major = GSS_S_FAILURE; - *minor = ENOMEM; - goto cleanup; - } - - for (i = 0, j = 0; i < nexts; i++) { - const struct gss_eap_extension_provider *ext = &exts[i]; - gss_buffer_desc extension = GSS_C_EMPTY_BUFFER; - - types[j] = ext->type; - if (ext->critical) - types[j] |= EXT_FLAG_CRITICAL; - - major = ext->make(minor, cred, ctx, chanBindings, &extension); - if (GSS_ERROR(major)) { - if (ext->critical) - goto cleanup; - else - continue; - } - - major = gss_add_buffer_set_member(minor, &extension, &extensions); - if (GSS_ERROR(major)) - goto cleanup; - - j++; - } - - assert(j == (extensions == GSS_C_NO_BUFFER_SET ? 0 : extensions->count)); - - major = encodeExtensions(minor, extensions, types, buffer); - if (GSS_ERROR(major)) - goto cleanup; - -cleanup: - gss_release_buffer_set(&tmpMinor, &extensions); - if (types != NULL) - GSSEAP_FREE(types); - - return major; -} - -OM_uint32 -gssEapMakeExtensions(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_channel_bindings_t chanBindings, - gss_buffer_t buffer) -{ - size_t nexts; - const struct gss_eap_extension_provider *exts; - - if (CTX_IS_INITIATOR(ctx)) { - exts = eapGssInitExtensions; - nexts = sizeof(eapGssInitExtensions) / sizeof(eapGssInitExtensions[0]); - } else { - exts = eapGssAcceptExtensions; - nexts = sizeof(eapGssAcceptExtensions) / sizeof(eapGssAcceptExtensions[0]); - } - - return makeExtensions(minor, cred, ctx, exts, nexts, chanBindings, buffer); -} - -static OM_uint32 -verifyExtensions(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - const struct gss_eap_extension_provider *exts, - size_t nexts, - gss_channel_bindings_t chanBindings, - const gss_buffer_t buffer) -{ - OM_uint32 major, tmpMinor; - gss_buffer_set_t extensions = GSS_C_NO_BUFFER_SET; - OM_uint32 *types = NULL; - size_t i; - - major = decodeExtensions(minor, buffer, &extensions, &types); - if (GSS_ERROR(major)) - goto cleanup; - - for (i = 0; i < nexts; i++) { - const struct gss_eap_extension_provider *ext = &exts[i]; - gss_buffer_t extension = GSS_C_NO_BUFFER; - size_t j; - - for (j = 0; j < extensions->count; j++) { - if ((types[j] & EXT_TYPE_MASK) == ext->type) { - extension = &extensions->elements[j]; - break; - } - } - - if (extension != GSS_C_NO_BUFFER) { - /* Process extension and mark as verified */ - major = ext->verify(minor, cred, ctx, chanBindings, - &extensions->elements[j]); - if (GSS_ERROR(major)) - goto cleanup; - - types[j] |= EXT_FLAG_VERIFIED; - } else if (ext->required) { - /* Required extension missing */ - major = GSS_S_UNAVAILABLE; - *minor = GSSEAP_MISSING_REQUIRED_EXT; - goto cleanup; - } - } - - /* Check we processed all critical extensions */ - for (i = 0; i < extensions->count; i++) { - if ((types[i] & EXT_FLAG_CRITICAL) && - (types[i] & EXT_FLAG_VERIFIED) == 0) { - major = GSS_S_UNAVAILABLE; - *minor = GSSEAP_CRIT_EXT_UNAVAILABLE; - goto cleanup; - } - } - - major = GSS_S_COMPLETE; - *minor = 0; - -cleanup: - gss_release_buffer_set(&tmpMinor, &extensions); - if (types != NULL) - GSSEAP_FREE(types); - - return major; -} - -OM_uint32 -gssEapVerifyExtensions(OM_uint32 *minor, - gss_cred_id_t cred, - gss_ctx_id_t ctx, - gss_channel_bindings_t chanBindings, - const gss_buffer_t buffer) -{ - size_t nexts; - const struct gss_eap_extension_provider *exts; - - if (CTX_IS_INITIATOR(ctx)) { - exts = eapGssAcceptExtensions; - nexts = sizeof(eapGssAcceptExtensions) / sizeof(eapGssAcceptExtensions[0]); - } else { - exts = eapGssInitExtensions; - nexts = sizeof(eapGssInitExtensions) / sizeof(eapGssInitExtensions[0]); - } - - return verifyExtensions(minor, cred, ctx, exts, nexts, chanBindings, buffer); -} - -static OM_uint32 -encodeExtensions(OM_uint32 *minor, - gss_buffer_set_t extensions, - OM_uint32 *types, - gss_buffer_t buffer) -{ - OM_uint32 major, tmpMinor; - size_t required = 0, i; - unsigned char *p; - - buffer->value = NULL; - buffer->length = 0; - - if (extensions != GSS_C_NO_BUFFER_SET) { - for (i = 0; i < extensions->count; i++) { - required += 8 + extensions->elements[i].length; - } - } - - /* - * We must always return a non-NULL token otherwise the calling state - * machine assumes we are finished. Hence care in case malloc(0) does - * return NULL. - */ - buffer->value = GSSEAP_MALLOC(required ? required : 1); - if (buffer->value == NULL) { - major = GSS_S_FAILURE; - *minor = ENOMEM; - goto cleanup; - } - - buffer->length = required; - p = (unsigned char *)buffer->value; - - if (extensions != GSS_C_NO_BUFFER_SET) { - for (i = 0; i < extensions->count; i++) { - gss_buffer_t extension = &extensions->elements[i]; - - assert((types[i] & EXT_FLAG_VERIFIED) == 0); /* private flag */ - - /* - * Extensions are encoded as type-length-value, where the upper - * bit of the type indicates criticality. - */ - store_uint32_be(types[i], &p[0]); - store_uint32_be(extension->length, &p[4]); - memcpy(&p[8], extension->value, extension->length); - - p += 8 + extension->length; - } - } - - assert(p == (unsigned char *)buffer->value + required); - assert(buffer->value != NULL); - - major = GSS_S_COMPLETE; - *minor = 0; - -cleanup: - if (GSS_ERROR(major)) { - gss_release_buffer(&tmpMinor, buffer); - } - - return major; -} - -static OM_uint32 -decodeExtensions(OM_uint32 *minor, - const gss_buffer_t buffer, - gss_buffer_set_t *pExtensions, - OM_uint32 **pTypes) -{ - OM_uint32 major, tmpMinor; - gss_buffer_set_t extensions = GSS_C_NO_BUFFER_SET; - OM_uint32 *types = NULL; - unsigned char *p; - size_t remain; - - *pExtensions = GSS_C_NO_BUFFER_SET; - *pTypes = NULL; - - major = gss_create_empty_buffer_set(minor, &extensions); - if (GSS_ERROR(major)) - goto cleanup; - - if (buffer->length == 0) { - major = GSS_S_COMPLETE; - goto cleanup; - } - - p = (unsigned char *)buffer->value; - remain = buffer->length; - - do { - OM_uint32 *ntypes; - gss_buffer_desc extension; - - if (remain < 8) { - major = GSS_S_DEFECTIVE_TOKEN; - *minor = GSSEAP_TOK_TRUNC; - goto cleanup; - } - - ntypes = GSSEAP_REALLOC(types, - (extensions->count + 1) * sizeof(OM_uint32)); - if (ntypes == NULL) { - major = GSS_S_FAILURE; - *minor = ENOMEM; - goto cleanup; - } - types = ntypes; - - types[extensions->count] = load_uint32_be(&p[0]); - extension.length = load_uint32_be(&p[4]); - - if (remain < 8 + extension.length) { - major = GSS_S_DEFECTIVE_TOKEN; - *minor = GSSEAP_TOK_TRUNC; - goto cleanup; - } - extension.value = &p[8]; - - major = gss_add_buffer_set_member(minor, &extension, &extensions); - if (GSS_ERROR(major)) - goto cleanup; - - p += 8 + extension.length; - remain -= 8 + extension.length; - } while (remain != 0); - -cleanup: - if (GSS_ERROR(major)) { - gss_release_buffer_set(&tmpMinor, &extensions); - if (types != NULL) - GSSEAP_FREE(types); - } else { - *pExtensions = extensions; - *pTypes = types; - } - - return major; -} diff --git a/util_radius.cpp b/util_radius.cpp index 90ce876..f730ddb 100644 --- a/util_radius.cpp +++ b/util_radius.cpp @@ -446,9 +446,25 @@ gss_eap_radius_attr_provider::releaseAnyNameMapping(gss_buffer_t type_id, bool gss_eap_radius_attr_provider::init(void) { + struct rs_context *radContext; + gss_eap_attr_ctx::registerProvider(ATTR_TYPE_RADIUS, "urn:ietf:params:gss-eap:radius-avp", createAttrContext); + +#if 1 + /* + * This hack is necessary in order to force the loading of the global + * dictionary, otherwise accepting reauthentication tokens fails unless + * the acceptor has already accepted a normal authentication token. + */ + if (rs_context_create(&radContext, RS_DICT_FILE) != 0) { + return false; + } + + rs_context_destroy(radContext); +#endif + return true; } @@ -660,10 +676,11 @@ avpImport(VALUE_PAIR **pVp, remain -= 4; da = dict_attrbyvalue(attrid); - if (da == NULL) - goto fail; - - vp = pairalloc(da); + if (da != NULL) { + vp = pairalloc(da); + } else { + vp = paircreate(attrid, PW_TYPE_STRING); + } if (vp == NULL) { throw new std::bad_alloc; goto fail; @@ -707,7 +724,9 @@ avpImport(VALUE_PAIR **pVp, return true; fail: - pairbasicfree(vp); + if (vp != NULL) + pairbasicfree(vp); + *pVp = NULL; return false; } diff --git a/util_radius.h b/util_radius.h index cc54f9d..eaec68e 100644 --- a/util_radius.h +++ b/util_radius.h @@ -149,7 +149,7 @@ gssEapRadiusMapError(OM_uint32 *minor, struct rs_error *err); #define RS_CONFIG_FILE SYSCONFDIR "/radsec.conf" -#define RS_DICT_FILE DATAROOTDIR "/freeradius/dictionary" +#define RS_DICT_FILE SYSCONFDIR "/raddb/dictionary" #define VENDORPEC_MS 311 /* RFC 2548 */ diff --git a/util_reauth.c b/util_reauth.c index 37506e0..a03f285 100644 --- a/util_reauth.c +++ b/util_reauth.c @@ -785,10 +785,10 @@ cleanup: */ OM_uint32 gssEapReauthComplete(OM_uint32 *minor, - gss_ctx_id_t ctx, - gss_cred_id_t cred, - const gss_OID mech, - OM_uint32 timeRec) + gss_ctx_id_t ctx, + gss_cred_id_t cred, + const gss_OID mech, + OM_uint32 timeRec) { OM_uint32 major, tmpMinor; gss_buffer_set_t keyData = GSS_C_NO_BUFFER_SET; diff --git a/util_sm.c b/util_sm.c new file mode 100644 index 0000000..ca69923 --- /dev/null +++ b/util_sm.c @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2011, JANET(UK) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of JANET(UK) nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Context establishment state machine. + */ + +#include "gssapiP_eap.h" + +/* private flags */ +#define SM_FLAG_TRANSITED 0x80000000 + +#define SM_ASSERT_VALID(ctx, status) do { \ + assert(GSS_ERROR((status)) || \ + ((status) == GSS_S_CONTINUE_NEEDED && ((ctx)->state > GSSEAP_STATE_INITIAL && (ctx)->state < GSSEAP_STATE_ESTABLISHED)) || \ + ((status) == GSS_S_COMPLETE && (ctx)->state == GSSEAP_STATE_ESTABLISHED)); \ + } while (0) + +#ifdef GSSEAP_DEBUG +static const char * +gssEapStateToString(enum gss_eap_state state) +{ + const char *s; + + switch (state) { + case GSSEAP_STATE_INITIAL: + s = "INITIAL"; + break; + case GSSEAP_STATE_AUTHENTICATE: + s = "AUTHENTICATE"; + break; + case GSSEAP_STATE_INITIATOR_EXTS: + s = "INITIATOR_EXTS"; + break; + case GSSEAP_STATE_ACCEPTOR_EXTS: + s = "ACCEPTOR_EXTS"; + break; +#ifdef GSSEAP_ENABLE_REAUTH + case GSSEAP_STATE_REAUTHENTICATE: + s = "REAUTHENTICATE"; + break; +#endif + case GSSEAP_STATE_ESTABLISHED: + s = "ESTABLISHED"; + break; + default: + s = "INVALID"; + break; + } + + return s; +} + +void +gssEapSmTransition(gss_ctx_id_t ctx, enum gss_eap_state state) +{ + assert(state >= GSSEAP_STATE_INITIAL); + assert(state <= GSSEAP_STATE_ESTABLISHED); + + fprintf(stderr, "GSS-EAP: state transition %s->%s\n", + gssEapStateToString(GSSEAP_SM_STATE(ctx)), + gssEapStateToString(state)); + + ctx->state = state; +} +#endif /* GSSEAP_DEBUG */ + +static OM_uint32 +makeErrorToken(OM_uint32 *minor, + OM_uint32 majorStatus, + OM_uint32 minorStatus, + gss_buffer_set_t *outputToken) +{ + OM_uint32 major; + unsigned char errorData[8]; + gss_buffer_desc errorBuffer; + + assert(GSS_ERROR(majorStatus)); + + major = gss_create_empty_buffer_set(minor, outputToken); + if (GSS_ERROR(major)) + return major; + + /* + * Only return error codes that the initiator could have caused, + * to avoid information leakage. + */ + if (IS_RADIUS_ERROR(minorStatus)) { + /* Squash RADIUS error codes */ + minorStatus = GSSEAP_RADIUS_PROT_FAILURE; + } else if (!IS_WIRE_ERROR(minorStatus)) { + /* Don't return non-wire error codes */ + return GSS_S_COMPLETE; + } + + minorStatus -= ERROR_TABLE_BASE_eapg; + + store_uint32_be(majorStatus, &errorData[0]); + store_uint32_be(minorStatus, &errorData[4]); + + errorBuffer.length = sizeof(errorData); + errorBuffer.value = errorData; + + major = gss_add_buffer_set_member(minor, &errorBuffer, outputToken); + if (GSS_ERROR(major)) + return major; + + return GSS_S_COMPLETE; +} + +static OM_uint32 +allocInnerTokens(OM_uint32 *minor, + size_t count, + gss_buffer_set_t *pTokens, + OM_uint32 **pTokenTypes) +{ + OM_uint32 major, tmpMinor; + gss_buffer_set_t tokens = GSS_C_NO_BUFFER_SET; + OM_uint32 *tokenTypes = NULL; + + major = gss_create_empty_buffer_set(minor, &tokens); + if (GSS_ERROR(major)) + goto cleanup; + + assert(tokens->count == 0); + assert(tokens->elements == NULL); + + tokens->elements = (gss_buffer_desc *)GSSEAP_CALLOC(count, sizeof(gss_buffer_desc)); + if (tokens->elements == NULL) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; + } + + tokenTypes = (OM_uint32 *)GSSEAP_CALLOC(count, sizeof(OM_uint32)); + if (tokenTypes == NULL) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; + } + + major = GSS_S_COMPLETE; + *minor = 0; + +cleanup: + if (GSS_ERROR(major)) { + gss_release_buffer_set(&tmpMinor, &tokens); + tokens = GSS_C_NO_BUFFER_SET; + if (tokenTypes != NULL) { + GSSEAP_FREE(tokenTypes); + tokenTypes = NULL; + } + } + + *pTokens = tokens; + *pTokenTypes = tokenTypes; + + return major; +} + +OM_uint32 +gssEapSmStep(OM_uint32 *minor, + gss_cred_id_t cred, + gss_ctx_id_t ctx, + gss_name_t target, + gss_OID mech, + OM_uint32 reqFlags, + OM_uint32 timeReq, + gss_channel_bindings_t chanBindings, + gss_buffer_t inputToken, + gss_buffer_t outputToken, + struct gss_eap_sm *sm, /* ordered by state */ + size_t smCount) +{ + OM_uint32 major, tmpMajor, tmpMinor; + gss_buffer_desc unwrappedInputToken = GSS_C_EMPTY_BUFFER; + gss_buffer_desc unwrappedOutputToken = GSS_C_EMPTY_BUFFER; + gss_buffer_set_t innerInputTokens = GSS_C_NO_BUFFER_SET; + gss_buffer_set_t innerOutputTokens = GSS_C_NO_BUFFER_SET; + OM_uint32 *inputTokenTypes = NULL, *outputTokenTypes = NULL; + unsigned int smFlags = 0; + size_t i, j; + int initialContextToken = 0; + enum gss_eap_token_type tokType; + + assert(smCount > 0); + + *minor = 0; + + outputToken->length = 0; + outputToken->value = NULL; + + if (inputToken != GSS_C_NO_BUFFER && inputToken->length != 0) { + major = gssEapVerifyToken(minor, ctx, inputToken, &tokType, + &unwrappedInputToken); + if (GSS_ERROR(major)) + goto cleanup; + + if (tokType != (CTX_IS_INITIATOR(ctx) + ? TOK_TYPE_ACCEPTOR_CONTEXT : TOK_TYPE_INITIATOR_CONTEXT)) { + major = GSS_S_DEFECTIVE_TOKEN; + *minor = GSSEAP_WRONG_TOK_ID; + goto cleanup; + } + } else if (!CTX_IS_INITIATOR(ctx) || ctx->state != GSSEAP_STATE_INITIAL) { + major = GSS_S_DEFECTIVE_TOKEN; + *minor = GSSEAP_WRONG_SIZE; + goto cleanup; + } else { + initialContextToken = 1; + } + + if (CTX_IS_ESTABLISHED(ctx)) { + major = GSS_S_BAD_STATUS; + *minor = GSSEAP_CONTEXT_ESTABLISHED; + goto cleanup; + } + + assert(ctx->state < GSSEAP_STATE_ESTABLISHED); + + major = gssEapDecodeInnerTokens(minor, &unwrappedInputToken, + &innerInputTokens, &inputTokenTypes); + if (GSS_ERROR(major)) + goto cleanup; + + assert(innerInputTokens != GSS_C_NO_BUFFER_SET); + + major = allocInnerTokens(minor, smCount, &innerOutputTokens, &outputTokenTypes); + if (GSS_ERROR(major)) + goto cleanup; + + /* Process all the tokens that are valid for the current state. */ + for (i = 0; i < smCount; i++) { + struct gss_eap_sm *smp = &sm[i]; + int processToken = 0; + gss_buffer_t innerInputToken = GSS_C_NO_BUFFER; + OM_uint32 *inputTokenType = NULL; + gss_buffer_desc innerOutputToken = GSS_C_EMPTY_BUFFER; + + if ((smp->validStates & ctx->state) == 0) + continue; + + /* + * We special case the first call to gss_init_sec_context so that + * all token providers have the opportunity to generate an initial + * context token. Providers where inputTokenType is ITOK_TYPE_NONE + * are always called and generally act on state transition boundaries, + * for example to advance the state after a series of optional tokens + * (as is the case with the extension token exchange) or to generate + * a new token after the state was advanced by a provider which did + * not emit a token. + */ + if (smp->inputTokenType == ITOK_TYPE_NONE || initialContextToken) { + processToken = 1; + } else if ((smFlags & SM_FLAG_TRANSITED) == 0) { + /* Don't regurgitate a token which belonds to a previous state. */ + for (j = 0; j < innerInputTokens->count; j++) { + if ((inputTokenTypes[j] & ITOK_TYPE_MASK) == smp->inputTokenType) { + if (processToken) { + /* Check for duplicate inner tokens */ + major = GSS_S_DEFECTIVE_TOKEN; + *minor = GSSEAP_DUPLICATE_ITOK; + break; + } + processToken = 1; + innerInputToken = &innerInputTokens->elements[j]; + inputTokenType = &inputTokenTypes[j]; + } + } + if (GSS_ERROR(major)) + break; + } + + if (processToken) { + enum gss_eap_state oldState = ctx->state; + + smFlags = 0; + if (inputTokenType != NULL && (*inputTokenType & ITOK_FLAG_CRITICAL)) + smFlags |= SM_FLAG_INPUT_TOKEN_CRITICAL; + + major = smp->processToken(minor, cred, ctx, target, mech, reqFlags, + timeReq, chanBindings, innerInputToken, + &innerOutputToken, &smFlags); + if (GSS_ERROR(major)) + break; + + if (inputTokenType != NULL) + *inputTokenType |= ITOK_FLAG_VERIFIED; + if (ctx->state < oldState) + i = 0; /* restart */ + else if (ctx->state != oldState) + smFlags |= SM_FLAG_TRANSITED; + + if (innerOutputToken.value != NULL) { + innerOutputTokens->elements[innerOutputTokens->count] = innerOutputToken; + assert(smp->outputTokenType != ITOK_TYPE_NONE); + outputTokenTypes[innerOutputTokens->count] = smp->outputTokenType; + if (smFlags & SM_FLAG_OUTPUT_TOKEN_CRITICAL) + outputTokenTypes[innerOutputTokens->count] |= ITOK_FLAG_CRITICAL; + innerOutputTokens->count++; + } + /* + * Break out if we made a state transition and have some tokens to send. + */ + if ((smFlags & SM_FLAG_TRANSITED) && + ((smFlags & SM_FLAG_FORCE_SEND_TOKEN) || innerOutputTokens->count != 0)) { + SM_ASSERT_VALID(ctx, major); + break; + } + } else if ((smp->itokFlags & SM_ITOK_FLAG_REQUIRED) && + smp->inputTokenType != ITOK_TYPE_NONE) { + /* Check for required inner tokens */ + major = GSS_S_DEFECTIVE_TOKEN; + *minor = GSSEAP_MISSING_REQUIRED_ITOK; + break; + } + } + + assert(innerOutputTokens->count <= smCount); + + /* Check we understood all critical tokens sent by peer */ + if (!GSS_ERROR(major)) { + for (j = 0; j < innerInputTokens->count; j++) { + if ((inputTokenTypes[j] & ITOK_FLAG_CRITICAL) && + (inputTokenTypes[j] & ITOK_FLAG_VERIFIED) == 0) { + major = GSS_S_UNAVAILABLE; + *minor = GSSEAP_CRIT_ITOK_UNAVAILABLE; + goto cleanup; + } + } + } + + /* Optionaly emit an error token if we are the acceptor */ + if (GSS_ERROR(major)) { + if (CTX_IS_INITIATOR(ctx)) + goto cleanup; /* return error directly to caller */ + + /* replace any emitted tokens with error token */ + gss_release_buffer_set(&tmpMinor, &innerOutputTokens); + + tmpMajor = makeErrorToken(&tmpMinor, major, *minor, &innerOutputTokens); + if (GSS_ERROR(tmpMajor)) { + major = tmpMajor; + *minor = tmpMinor; + goto cleanup; + } + + if (innerOutputTokens->count != 0) + outputTokenTypes[0] = ITOK_TYPE_CONTEXT_ERR | ITOK_FLAG_CRITICAL; + } + + /* Format output token from inner tokens */ + if (innerOutputTokens->count != 0 || /* inner tokens to send */ + !CTX_IS_INITIATOR(ctx) || /* any leg acceptor */ + !CTX_IS_ESTABLISHED(ctx)) { /* non-last leg initiator */ + tmpMajor = gssEapEncodeInnerTokens(&tmpMinor, innerOutputTokens, + outputTokenTypes, &unwrappedOutputToken); + if (tmpMajor == GSS_S_COMPLETE) { + if (CTX_IS_INITIATOR(ctx)) + tokType = TOK_TYPE_INITIATOR_CONTEXT; + else + tokType = TOK_TYPE_ACCEPTOR_CONTEXT; + + tmpMajor = gssEapMakeToken(&tmpMinor, ctx, &unwrappedOutputToken, + tokType, outputToken); + if (GSS_ERROR(tmpMajor)) { + major = tmpMajor; + *minor = tmpMinor; + goto cleanup; + } + } + } + + /* If the context is established, empty tokens only to be emitted by initiator */ + assert(!CTX_IS_ESTABLISHED(ctx) || ((outputToken->length == 0) == CTX_IS_INITIATOR(ctx))); + + SM_ASSERT_VALID(ctx, major); + +cleanup: + gss_release_buffer_set(&tmpMinor, &innerInputTokens); + gss_release_buffer_set(&tmpMinor, &innerOutputTokens); + if (inputTokenTypes != NULL) + GSSEAP_FREE(inputTokenTypes); + if (outputTokenTypes != NULL) + gss_release_buffer(&tmpMinor, &unwrappedOutputToken); + GSSEAP_FREE(outputTokenTypes); + + return major; +} diff --git a/util_token.c b/util_token.c index 5231ae2..a929198 100644 --- a/util_token.c +++ b/util_token.c @@ -30,7 +30,7 @@ * SUCH DAMAGE. */ /* - * Copyright 1993 by OpenVision Technologies, Inc. + * Portions Copyright 1993 by OpenVision Technologies, Inc. * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without fee, @@ -57,6 +57,149 @@ #include "gssapiP_eap.h" +OM_uint32 +gssEapEncodeInnerTokens(OM_uint32 *minor, + gss_buffer_set_t extensions, + OM_uint32 *types, + gss_buffer_t buffer) +{ + OM_uint32 major, tmpMinor; + size_t required = 0, i; + unsigned char *p; + + buffer->value = NULL; + buffer->length = 0; + + if (extensions != GSS_C_NO_BUFFER_SET) { + for (i = 0; i < extensions->count; i++) { + required += 8 + extensions->elements[i].length; + } + } + + /* + * We must always return a non-NULL token otherwise the calling state + * machine assumes we are finished. Hence care in case malloc(0) does + * return NULL. + */ + buffer->value = GSSEAP_MALLOC(required ? required : 1); + if (buffer->value == NULL) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; + } + + buffer->length = required; + p = (unsigned char *)buffer->value; + + if (extensions != GSS_C_NO_BUFFER_SET) { + for (i = 0; i < extensions->count; i++) { + gss_buffer_t extension = &extensions->elements[i]; + + assert((types[i] & ITOK_FLAG_VERIFIED) == 0); /* private flag */ + + /* + * Extensions are encoded as type-length-value, where the upper + * bit of the type indicates criticality. + */ + store_uint32_be(types[i], &p[0]); + store_uint32_be(extension->length, &p[4]); + memcpy(&p[8], extension->value, extension->length); + + p += 8 + extension->length; + } + } + + assert(p == (unsigned char *)buffer->value + required); + assert(buffer->value != NULL); + + major = GSS_S_COMPLETE; + *minor = 0; + +cleanup: + if (GSS_ERROR(major)) { + gss_release_buffer(&tmpMinor, buffer); + } + + return major; +} + +OM_uint32 +gssEapDecodeInnerTokens(OM_uint32 *minor, + const gss_buffer_t buffer, + gss_buffer_set_t *pExtensions, + OM_uint32 **pTypes) +{ + OM_uint32 major, tmpMinor; + gss_buffer_set_t extensions = GSS_C_NO_BUFFER_SET; + OM_uint32 *types = NULL; + unsigned char *p; + size_t remain; + + *pExtensions = GSS_C_NO_BUFFER_SET; + *pTypes = NULL; + + major = gss_create_empty_buffer_set(minor, &extensions); + if (GSS_ERROR(major)) + goto cleanup; + + if (buffer->length == 0) { + major = GSS_S_COMPLETE; + goto cleanup; + } + + p = (unsigned char *)buffer->value; + remain = buffer->length; + + do { + OM_uint32 *ntypes; + gss_buffer_desc extension; + + if (remain < 8) { + major = GSS_S_DEFECTIVE_TOKEN; + *minor = GSSEAP_TOK_TRUNC; + goto cleanup; + } + + ntypes = GSSEAP_REALLOC(types, + (extensions->count + 1) * sizeof(OM_uint32)); + if (ntypes == NULL) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; + } + types = ntypes; + + types[extensions->count] = load_uint32_be(&p[0]); + extension.length = load_uint32_be(&p[4]); + + if (remain < 8 + extension.length) { + major = GSS_S_DEFECTIVE_TOKEN; + *minor = GSSEAP_TOK_TRUNC; + goto cleanup; + } + extension.value = &p[8]; + + major = gss_add_buffer_set_member(minor, &extension, &extensions); + if (GSS_ERROR(major)) + goto cleanup; + + p += 8 + extension.length; + remain -= 8 + extension.length; + } while (remain != 0); + +cleanup: + if (GSS_ERROR(major)) { + gss_release_buffer_set(&tmpMinor, &extensions); + if (types != NULL) + GSSEAP_FREE(types); + } else { + *pExtensions = extensions; + *pTypes = types; + } + + return major; +} + /* * $Id: util_token.c 23457 2009-12-08 00:04:48Z tlyu $ */ @@ -164,6 +307,8 @@ der_read_length(unsigned char **buf, ssize_t *bufsize) size_t tokenSize(const gss_OID_desc *mech, size_t body_size) { + assert(mech != GSS_C_NO_OID); + /* set body_size to sequence contents size */ body_size += 4 + (size_t) mech->length; /* NEED overflow check */ return 1 + der_length_size(body_size) + body_size;