/* * Copyright (C) 2011-2021 PADL Software Pty Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * 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. * * 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. */ #include "spnego_locl.h" /* * The initial context token emitted by the initiator is a INITIATOR_NEGO * message followed by zero or more INITIATOR_META_DATA tokens, and zero * or one AP_REQUEST tokens. * * Upon receiving this, the acceptor computes the list of mutually supported * authentication mechanisms and performs the metadata exchange. The output * token is ACCEPTOR_NEGO followed by zero or more ACCEPTOR_META_DATA tokens, * and zero or one CHALLENGE tokens. * * Once the metadata exchange is complete and a mechanism is selected, the * selected mechanism's context token exchange continues with AP_REQUEST and * CHALLENGE messages. * * Once the context token exchange is complete, VERIFY messages are sent to * authenticate the entire exchange. */ static OM_uint32 buffer_set_to_crypto(OM_uint32 *minor, krb5_context context, gss_buffer_set_t buffers, krb5_crypto *crypto) { krb5_error_code ret; krb5_keyblock keyblock; OM_uint32 tmp; /* * Returned keys must be in two buffers, with the key contents in * the first and the enctype as a 32-bit little-endian integer in * the second. */ if (buffers->count != 2 || buffers->elements[1].length != sizeof(tmp)) { *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY; return GSS_S_FAILURE; } if (*crypto != NULL) { krb5_crypto_destroy(context, *crypto); *crypto = NULL; } keyblock.keyvalue.data = buffers->elements[0].value; keyblock.keyvalue.length = buffers->elements[0].length; _gss_mg_decode_le_uint32(buffers->elements[1].value, &tmp); keyblock.keytype = tmp; ret = krb5_crypto_init(context, &keyblock, 0, crypto); if (ret) { *minor = ret; return GSS_S_FAILURE; } return GSS_S_COMPLETE; } #define NEGOEX_SIGN_KEY 1 #define NEGOEX_VERIFY_KEY 2 #define NEGOEX_BOTH_KEYS (NEGOEX_SIGN_KEY|NEGOEX_VERIFY_KEY) static OM_uint32 get_session_keys(OM_uint32 *minor, krb5_context context, OM_uint32 flags, struct negoex_auth_mech *mech) { OM_uint32 major, tmpMinor; gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET; if (flags & NEGOEX_SIGN_KEY) { major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context, GSS_C_INQ_NEGOEX_KEY, &buffers); if (major == GSS_S_COMPLETE) { major = buffer_set_to_crypto(minor, context, buffers, &mech->crypto); _gss_secure_release_buffer_set(&tmpMinor, &buffers); if (major != GSS_S_COMPLETE) return major; } } if (flags & NEGOEX_VERIFY_KEY) { major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context, GSS_C_INQ_NEGOEX_VERIFY_KEY, &buffers); if (major == GSS_S_COMPLETE) { major = buffer_set_to_crypto(minor, context, buffers, &mech->verify_crypto); _gss_secure_release_buffer_set(&tmpMinor, &buffers); if (major != GSS_S_COMPLETE) return major; } } return GSS_S_COMPLETE; } static OM_uint32 emit_initiator_nego(OM_uint32 *minor, gssspnego_ctx ctx) { uint8_t random[32]; struct negoex_auth_mech *mech; size_t i = 0; krb5_generate_random_block(random, sizeof(random)); HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) _gss_negoex_log_auth_scheme(ctx->flags.local, ++i, mech->scheme); return _gss_negoex_add_nego_message(minor, ctx, INITIATOR_NEGO, random); } static OM_uint32 process_initiator_nego(OM_uint32 *minor, gssspnego_ctx ctx, struct negoex_message *messages, size_t nmessages) { struct nego_message *msg; size_t i; heim_assert(!ctx->flags.local && ctx->negoex_step == 1, "NegoEx INITIATOR_NEGO token received after first leg"); msg = _gss_negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO); if (msg == NULL) { *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE; return GSS_S_DEFECTIVE_TOKEN; } for (i = 0; i < msg->nschemes; i++) _gss_negoex_log_auth_scheme(ctx->flags.local, i + 1, &msg->schemes[i * GUID_LENGTH]); _gss_negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes); return GSS_S_COMPLETE; } static OM_uint32 emit_acceptor_nego(OM_uint32 *minor, gssspnego_ctx ctx) { uint8_t random[32]; krb5_generate_random_block(random, 32); return _gss_negoex_add_nego_message(minor, ctx, ACCEPTOR_NEGO, random); } static OM_uint32 process_acceptor_nego(OM_uint32 *minor, gssspnego_ctx ctx, struct negoex_message *messages, size_t nmessages) { struct nego_message *msg; msg = _gss_negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO); if (msg == NULL) { *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE; return GSS_S_DEFECTIVE_TOKEN; } /* * Reorder and prune our mech list to match the acceptor's list (or a * subset of it). */ _gss_negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes); return GSS_S_COMPLETE; } static void query_meta_data(gssspnego_ctx ctx, struct gssspnego_optimistic_ctx *opt, gss_cred_id_t cred, OM_uint32 req_flags) { OM_uint32 major, minor; struct negoex_auth_mech *p, *next; /* * Note that if we received an optimistic context token from SPNEGO, * then we will call QMD after ISC, rather than before. Mechanisms * must be prepared to handle this and must not assume the context * will be NULL on entry. */ HEIM_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) { if (opt != NULL && memcmp(opt->scheme, p->scheme, GUID_LENGTH) == 0) p->mech_context = opt->gssctx;; major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context, ctx->target_name, req_flags, &p->metadata); /* GSS_Query_meta_data failure removes mechanism from list. */ if (major != GSS_S_COMPLETE) _gss_negoex_delete_auth_mech(ctx, p); } } static void exchange_meta_data(gssspnego_ctx ctx, gss_cred_id_t cred, OM_uint32 req_flags, struct negoex_message *messages, size_t nmessages) { OM_uint32 major, minor; struct negoex_auth_mech *mech; enum message_type type; struct exchange_message *msg; uint32_t i; type = ctx->flags.local ? ACCEPTOR_META_DATA : INITIATOR_META_DATA; for (i = 0; i < nmessages; i++) { if (messages[i].type != type) continue; msg = &messages[i].u.e; mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme); if (mech == NULL) continue; major = gssspi_exchange_meta_data(&minor, mech->oid, cred, &mech->mech_context, ctx->target_name, req_flags, &msg->token); /* GSS_Exchange_meta_data failure removes mechanism from list. */ if (major != GSS_S_COMPLETE) _gss_negoex_delete_auth_mech(ctx, mech); } } static void release_mech_crypto(struct negoex_auth_mech *mech) { krb5_context context = NULL; if (mech->crypto || mech->verify_crypto) context = _gss_mg_krb5_context(); if (mech->crypto) { krb5_crypto_destroy(context, mech->crypto); mech->crypto = NULL; } if (mech->verify_crypto) { krb5_crypto_destroy(context, mech->verify_crypto); mech->verify_crypto = NULL; } mech->sent_checksum = FALSE; } /* * In the initiator, if we are processing the acceptor's first reply, discard * the optimistic context if the acceptor ignored the optimistic token. If the * acceptor continued the optimistic mech, discard all other mechs. */ static void check_optimistic_result(gssspnego_ctx ctx, struct negoex_message *messages, size_t nmessages) { struct negoex_auth_mech *mech; OM_uint32 tmpMinor; heim_assert(ctx->flags.local && ctx->negoex_step == 2, "NegoEx optimistic result should only be checked in second leg"); /* Do nothing if we didn't make an optimistic context. */ mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT) return; /* * If the acceptor used the optimistic token, it will send an acceptor * token or a checksum (or both) in its first reply. */ if (_gss_negoex_locate_exchange_message(messages, nmessages, CHALLENGE) != NULL || _gss_negoex_locate_verify_message(messages, nmessages) != NULL) { /* * The acceptor continued the optimistic mech, and metadata exchange * didn't remove it. Commit to this mechanism. */ _gss_negoex_select_auth_mech(ctx, mech); } else { /* * The acceptor ignored the optimistic token. Restart the mech. */ gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER); release_mech_crypto(mech); mech->complete = FALSE; } } /* Perform an initiator step of the underlying mechanism exchange. */ static OM_uint32 mech_init(OM_uint32 *minor, struct gssspnego_optimistic_ctx *opt, gssspnego_ctx ctx, gss_cred_id_t cred, OM_uint32 req_flags, OM_uint32 time_req, const gss_channel_bindings_t input_chan_bindings, struct negoex_message *messages, size_t nmessages, gss_buffer_t output_token, int *mech_error) { OM_uint32 major, first_major = GSS_S_COMPLETE, first_minor = 0; struct negoex_auth_mech *mech = NULL; gss_buffer_t input_token = GSS_C_NO_BUFFER; struct exchange_message *msg; int first_mech; krb5_context context = _gss_mg_krb5_context(); output_token->value = NULL; output_token->length = 0; *mech_error = FALSE; /* Allow disabling of optimistic token for testing. */ if (ctx->negoex_step == 1 && secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL) return GSS_S_COMPLETE; if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS; return GSS_S_FAILURE; } /* * Get the input token. The challenge could be for the optimistic mech, * which we might have discarded in metadata exchange, so ignore the * challenge if it doesn't match the first auth mech. */ mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); msg = _gss_negoex_locate_exchange_message(messages, nmessages, CHALLENGE); if (msg != NULL && GUID_EQ(msg->scheme, mech->scheme)) input_token = &msg->token; if (mech->complete) return GSS_S_COMPLETE; first_mech = TRUE; major = GSS_S_BAD_MECH; while (!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); /* * If SPNEGO generated an optimistic token when probing available * mechanisms, we can reuse it here. This avoids a potentially * expensive and redundant call to GSS_Init_sec_context(); */ if (opt != NULL && memcmp(opt->scheme, mech->scheme, GUID_LENGTH) == 0) { heim_assert(ctx->negoex_step == 1, "SPNEGO optimistic token only valid for NegoEx first leg"); major = _gss_copy_buffer(minor, &opt->optimistic_token, output_token); if (GSS_ERROR(major)) return major; ctx->negotiated_mech_type = opt->negotiated_mech_type; ctx->mech_flags = opt->optimistic_flags; ctx->mech_time_rec = opt->optimistic_time_rec; mech->mech_context = opt->gssctx; opt->gssctx = NULL; /* steal it */ mech->complete = opt->complete; major = GSS_S_COMPLETE; } else { major = gss_init_sec_context(minor, cred, &mech->mech_context, ctx->target_name, mech->oid, req_flags, time_req, input_chan_bindings, input_token, &ctx->negotiated_mech_type, output_token, &ctx->mech_flags, &ctx->mech_time_rec); if (major == GSS_S_COMPLETE) mech->complete = 1; else if (GSS_ERROR(major)) { gss_mg_collect_error(mech->oid, major, *minor); *mech_error = TRUE; } } if (!GSS_ERROR(major)) return get_session_keys(minor, context, NEGOEX_BOTH_KEYS, mech); /* Remember the error we got from the first mech. */ if (first_mech) { first_major = major; first_minor = *minor; } /* If we still have multiple mechs to try, move on to the next one. */ _gss_negoex_delete_auth_mech(ctx, mech); first_mech = FALSE; input_token = GSS_C_NO_BUFFER; } if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { major = first_major; *minor = first_minor; } return major; } /* Perform an acceptor step of the underlying mechanism exchange. */ static OM_uint32 mech_accept(OM_uint32 *minor, gssspnego_ctx ctx, gss_cred_id_t cred, const gss_channel_bindings_t input_chan_bindings, struct negoex_message *messages, size_t nmessages, gss_buffer_t output_token, gss_cred_id_t *deleg_cred, int *mech_error) { OM_uint32 major, tmpMinor; struct negoex_auth_mech *mech; struct exchange_message *msg; krb5_context context = _gss_mg_krb5_context(); heim_assert(!ctx->flags.local && !HEIM_TAILQ_EMPTY(&ctx->negoex_mechs), "Acceptor NegoEx function called in wrong sequence"); *mech_error = FALSE; msg = _gss_negoex_locate_exchange_message(messages, nmessages, AP_REQUEST); if (msg == NULL) { /* * No input token is okay on the first request or if the mech is * complete. */ if (ctx->negoex_step == 1 || HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->complete) return GSS_S_COMPLETE; *minor = (OM_uint32)NEGOEX_MISSING_AP_REQUEST_MESSAGE; return GSS_S_DEFECTIVE_TOKEN; } if (ctx->negoex_step == 1) { /* * Ignore the optimistic token if it isn't for our most preferred * mech. */ mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); if (!GUID_EQ(msg->scheme, mech->scheme)) { _gss_mg_log(10, "negoex ignored optimistic token as not for preferred mech"); return GSS_S_COMPLETE; } } else { /* The initiator has selected a mech; discard other entries. */ mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme); if (mech == NULL) { *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS; return GSS_S_FAILURE; } _gss_negoex_select_auth_mech(ctx, mech); } if (mech->complete) return GSS_S_COMPLETE; if (ctx->mech_src_name != GSS_C_NO_NAME) gss_release_name(&tmpMinor, &ctx->mech_src_name); if (deleg_cred && *deleg_cred != GSS_C_NO_CREDENTIAL) gss_release_cred(&tmpMinor, deleg_cred); major = gss_accept_sec_context(minor, &mech->mech_context, cred, &msg->token, input_chan_bindings, &ctx->mech_src_name, &ctx->negotiated_mech_type, output_token, &ctx->mech_flags, &ctx->mech_time_rec, deleg_cred); if (major == GSS_S_COMPLETE) mech->complete = 1; if (!GSS_ERROR(major)) { if (major == GSS_S_COMPLETE && !gss_oid_equal(ctx->negotiated_mech_type, mech->oid)) _gss_mg_log(1, "negoex client didn't send the mech they said they would"); major = get_session_keys(minor, context, NEGOEX_BOTH_KEYS, mech); } else if (ctx->negoex_step == 1) { gss_mg_collect_error(ctx->negotiated_mech_type, major, *minor); *mech_error = TRUE; /* This was an optimistic token; pretend this never happened. */ major = GSS_S_COMPLETE; *minor = 0; gss_release_buffer(&tmpMinor, output_token); gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER); } return major; } static krb5_keyusage verify_keyusage(gssspnego_ctx ctx, int make_checksum) { /* Of course, these are the wrong way around in the spec. */ return (ctx->flags.local ^ !make_checksum) ? NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM; } static OM_uint32 verify_key_flags(gssspnego_ctx ctx, int make_checksum) { return (ctx->flags.local ^ make_checksum) ? NEGOEX_SIGN_KEY : NEGOEX_VERIFY_KEY; } static OM_uint32 verify_checksum(OM_uint32 *minor, gssspnego_ctx ctx, struct negoex_message *messages, size_t nmessages, gss_const_buffer_t input_token, int *send_alert_out) { krb5_error_code ret; struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); struct verify_message *msg; krb5_context context = _gss_mg_krb5_context(); krb5_crypto_iov iov[3]; krb5_keyusage usage = verify_keyusage(ctx, FALSE); *send_alert_out = FALSE; heim_assert(mech != NULL, "Invalid null mech when verifying NegoEx checksum"); /* * The other party may not be ready to send a verify token yet, or (in the * first initiator step) may send one for a mechanism we don't support. */ msg = _gss_negoex_locate_verify_message(messages, nmessages); if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme)) return GSS_S_COMPLETE; /* * Last chance attempt to obtain session key for imported exported partial * contexts (which do not carry the session key at the NegoEx layer). */ if (mech->verify_crypto == NULL) get_session_keys(minor, context, verify_key_flags(ctx, FALSE), mech); /* * A recoverable error may cause us to be unable to verify a token from the * other party. In this case we should send an alert. */ if (mech->verify_crypto == NULL) { *send_alert_out = TRUE; return GSS_S_COMPLETE; } if (!krb5_checksum_is_keyed(context, msg->cksum_type)) { *minor = (OM_uint32)NEGOEX_INVALID_CHECKSUM; return GSS_S_BAD_SIG; } /* * Verify the checksum over the existing transcript and the portion of the * input token leading up to the verify message. */ iov[0].flags = KRB5_CRYPTO_TYPE_DATA; ret = krb5_storage_to_data(ctx->negoex_transcript, &iov[0].data); if (ret) { *minor = ret; return GSS_S_FAILURE; } iov[1].flags = KRB5_CRYPTO_TYPE_DATA; iov[1].data.data = input_token->value; iov[1].data.length = msg->offset_in_token; iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM; iov[2].data.data = (uint8_t *)msg->cksum; iov[2].data.length = msg->cksum_len; ret = krb5_verify_checksum_iov(context, mech->verify_crypto, usage, iov, sizeof(iov) / sizeof(iov[0]), NULL); if (ret == 0) mech->verified_checksum = TRUE; else *minor = ret; krb5_data_free(&iov[0].data); return (ret == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE; } static OM_uint32 make_checksum(OM_uint32 *minor, gssspnego_ctx ctx) { krb5_error_code ret; krb5_context context = _gss_mg_krb5_context(); krb5_data d; krb5_keyusage usage = verify_keyusage(ctx, TRUE); krb5_checksum cksum; struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); OM_uint32 major; heim_assert(mech != NULL, "Invalid null mech when making NegoEx checksum"); if (mech->crypto == NULL) { if (mech->complete) { /* * Last chance attempt to obtain session key for imported exported partial * contexts (which do not carry the session key at the NegoEx layer). */ get_session_keys(minor, context, verify_key_flags(ctx, TRUE), mech); if (mech->crypto == NULL) { *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY; return GSS_S_UNAVAILABLE; } } else { return GSS_S_COMPLETE; } } ret = krb5_storage_to_data(ctx->negoex_transcript, &d); if (ret) { *minor = ret; return GSS_S_FAILURE; } ret = krb5_create_checksum(context, mech->crypto, usage, 0, d.data, d.length, &cksum); krb5_data_free(&d); if (ret) { *minor = ret; return GSS_S_FAILURE; } major = _gss_negoex_add_verify_message(minor, ctx, mech->scheme, cksum.cksumtype, cksum.checksum.data, cksum.checksum.length); free_Checksum(&cksum); if (major == GSS_S_COMPLETE) mech->sent_checksum = TRUE; return major; } /* * If the other side sent a VERIFY_NO_KEY pulse alert, clear the checksum state * on the mechanism so that we send another VERIFY message. */ static void process_alerts(gssspnego_ctx ctx, struct negoex_message *messages, uint32_t nmessages) { struct alert_message *msg; struct negoex_auth_mech *mech; msg = _gss_negoex_locate_alert_message(messages, nmessages); if (msg != NULL && msg->verify_no_key) { mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme); if (mech != NULL) release_mech_crypto(mech); } } static OM_uint32 make_output_token(OM_uint32 *minor, gssspnego_ctx ctx, gss_buffer_t mech_output_token, int send_alert, gss_buffer_t output_token) { OM_uint32 major, tmpMinor; struct negoex_auth_mech *mech; enum message_type type; off_t old_transcript_len; output_token->length = 0; output_token->value = NULL; old_transcript_len = krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR); /* * If the mech is complete and we previously sent a checksum, we just * processed the last leg and don't need to send another token. */ if (mech_output_token->length == 0 && HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum) return GSS_S_COMPLETE; if (ctx->negoex_step == 1) { if (ctx->flags.local) major = emit_initiator_nego(minor, ctx); else major = emit_acceptor_nego(minor, ctx); if (major != GSS_S_COMPLETE) return major; type = ctx->flags.local ? INITIATOR_META_DATA : ACCEPTOR_META_DATA; HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) { if (mech->metadata.length > 0) { major = _gss_negoex_add_exchange_message(minor, ctx, type, mech->scheme, &mech->metadata); if (major != GSS_S_COMPLETE) return major; } } } mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); if (mech_output_token->length > 0) { type = ctx->flags.local ? AP_REQUEST : CHALLENGE; major = _gss_negoex_add_exchange_message(minor, ctx, type, mech->scheme, mech_output_token); if (major != GSS_S_COMPLETE) return major; } if (send_alert) { major = _gss_negoex_add_verify_no_key_alert(minor, ctx, mech->scheme); if (major != GSS_S_COMPLETE) return major; } /* Try to add a VERIFY message if we haven't already done so. */ if (!mech->sent_checksum) { major = make_checksum(minor, ctx); if (major != GSS_S_COMPLETE) return major; } heim_assert(ctx->negoex_transcript != NULL, "NegoEx context uninitialized"); output_token->length = krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR) - old_transcript_len; output_token->value = malloc(output_token->length); if (output_token->value == NULL) { *minor = ENOMEM; return GSS_S_FAILURE; } krb5_storage_seek(ctx->negoex_transcript, old_transcript_len, SEEK_SET); if (krb5_storage_read(ctx->negoex_transcript, output_token->value, output_token->length) != output_token->length) { *minor = ERANGE; gss_release_buffer(&tmpMinor, output_token); return GSS_S_FAILURE; } krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_END); return GSS_S_COMPLETE; } OM_uint32 _gss_negoex_init(OM_uint32 *minor, struct gssspnego_optimistic_ctx *opt, gssspnego_ctx ctx, gss_cred_id_t cred, OM_uint32 req_flags, OM_uint32 time_req, const gss_channel_bindings_t input_chan_bindings, gss_const_buffer_t input_token, gss_buffer_t output_token) { OM_uint32 major, tmpMinor; gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; struct negoex_message *messages = NULL; struct negoex_auth_mech *mech; size_t nmessages = 0; int send_alert = FALSE, mech_error = FALSE; output_token->length = 0; output_token->value = NULL; if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER && input_token->length != 0) return GSS_S_DEFECTIVE_TOKEN; major = _gss_negoex_begin(minor, ctx); if (major != GSS_S_COMPLETE) goto cleanup; ctx->negoex_step++; if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) { major = _gss_negoex_parse_token(minor, ctx, input_token, &messages, &nmessages); if (major != GSS_S_COMPLETE) goto cleanup; } process_alerts(ctx, messages, nmessages); if (ctx->negoex_step == 1) { /* Choose a random conversation ID. */ krb5_generate_random_block(ctx->negoex_conv_id, GUID_LENGTH); /* Query each mech for its metadata (this may prune the mech list). */ query_meta_data(ctx, opt, cred, req_flags); } else if (ctx->negoex_step == 2) { /* See if the mech processed the optimistic token. */ check_optimistic_result(ctx, messages, nmessages); /* Pass the acceptor metadata to each mech to prune the list. */ exchange_meta_data(ctx, cred, req_flags, messages, nmessages); /* Process the ACCEPTOR_NEGO message. */ major = process_acceptor_nego(minor, ctx, messages, nmessages); if (major != GSS_S_COMPLETE) goto cleanup; } /* * Process the input token and/or produce an output token. This may prune * the mech list, but on success there will be at least one mech entry. */ major = mech_init(minor, opt, ctx, cred, req_flags, time_req, input_chan_bindings, messages, nmessages, &mech_output_token, &mech_error); if (major != GSS_S_COMPLETE) goto cleanup; heim_assert(!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs), "Invalid empty NegoEx mechanism list"); /* * At this point in step 2 we have performed the metadata exchange and * chosen a mech we can use, so discard any fallback mech entries. */ if (ctx->negoex_step == 2) _gss_negoex_select_auth_mech(ctx, HEIM_TAILQ_FIRST(&ctx->negoex_mechs)); major = verify_checksum(minor, ctx, messages, nmessages, input_token, &send_alert); if (major != GSS_S_COMPLETE) goto cleanup; if (input_token != GSS_C_NO_BUFFER) { if (krb5_storage_write(ctx->negoex_transcript, input_token->value, input_token->length) != input_token->length) { major = GSS_S_FAILURE; *minor = ENOMEM; goto cleanup; } } major = make_output_token(minor, ctx, &mech_output_token, send_alert, output_token); if (major != GSS_S_COMPLETE) goto cleanup; mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE : GSS_S_CONTINUE_NEEDED; cleanup: free(messages); gss_release_buffer(&tmpMinor, &mech_output_token); _gss_negoex_end(ctx); if (GSS_ERROR(major)) { if (!mech_error) { krb5_context context = _gss_mg_krb5_context(); const char *emsg = krb5_get_error_message(context, *minor); gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, major, *minor, "NegoEx failed to initialize security context: %s", emsg); krb5_free_error_message(context, emsg); } _gss_negoex_release_context(ctx); } return major; } OM_uint32 _gss_negoex_accept(OM_uint32 *minor, gssspnego_ctx ctx, gss_cred_id_t cred, gss_const_buffer_t input_token, const gss_channel_bindings_t input_chan_bindings, gss_buffer_t output_token, gss_cred_id_t *deleg_cred) { OM_uint32 major, tmpMinor; gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; struct negoex_message *messages = NULL; struct negoex_auth_mech *mech; size_t nmessages; int send_alert = FALSE, mech_error = FALSE; output_token->length = 0; output_token->value = NULL; if (deleg_cred) *deleg_cred = GSS_C_NO_CREDENTIAL; if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) { major = GSS_S_DEFECTIVE_TOKEN; goto cleanup; } major = _gss_negoex_begin(minor, ctx); if (major != GSS_S_COMPLETE) goto cleanup; ctx->negoex_step++; major = _gss_negoex_parse_token(minor, ctx, input_token, &messages, &nmessages); if (major != GSS_S_COMPLETE) goto cleanup; process_alerts(ctx, messages, nmessages); if (ctx->negoex_step == 1) { /* * Read the INITIATOR_NEGO message to prune the candidate mech list. */ major = process_initiator_nego(minor, ctx, messages, nmessages); if (major != GSS_S_COMPLETE) goto cleanup; /* * Pass the initiator metadata to each mech to prune the list, and * query each mech for its acceptor metadata (which may also prune the * list). */ exchange_meta_data(ctx, cred, 0, messages, nmessages); query_meta_data(ctx, NULL, cred, 0); if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS; major = GSS_S_FAILURE; goto cleanup; } } /* * Process the input token and possibly produce an output token. This may * prune the list to a single mech. Continue on error if an output token * is generated, so that we send the token to the initiator. */ major = mech_accept(minor, ctx, cred, input_chan_bindings, messages, nmessages, &mech_output_token, deleg_cred, &mech_error); if (major != GSS_S_COMPLETE && mech_output_token.length == 0) goto cleanup; if (major == GSS_S_COMPLETE) { major = verify_checksum(minor, ctx, messages, nmessages, input_token, &send_alert); if (major != GSS_S_COMPLETE) goto cleanup; } if (krb5_storage_write(ctx->negoex_transcript, input_token->value, input_token->length) != input_token->length) { major = GSS_S_FAILURE; *minor = ENOMEM; goto cleanup; } major = make_output_token(minor, ctx, &mech_output_token, send_alert, output_token); if (major != GSS_S_COMPLETE) goto cleanup; mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE : GSS_S_CONTINUE_NEEDED; cleanup: free(messages); gss_release_buffer(&tmpMinor, &mech_output_token); _gss_negoex_end(ctx); if (GSS_ERROR(major)) { if (!mech_error) { krb5_context context = _gss_mg_krb5_context(); const char *emsg = krb5_get_error_message(context, *minor); gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, major, *minor, "NegoEx failed to accept security context: %s", emsg); krb5_free_error_message(context, emsg); } _gss_negoex_release_context(ctx); } return major; }