diff options
Diffstat (limited to 'third_party/heimdal/lib/gssapi/spnego/negoex_ctx.c')
-rw-r--r-- | third_party/heimdal/lib/gssapi/spnego/negoex_ctx.c | 1037 |
1 files changed, 1037 insertions, 0 deletions
diff --git a/third_party/heimdal/lib/gssapi/spnego/negoex_ctx.c b/third_party/heimdal/lib/gssapi/spnego/negoex_ctx.c new file mode 100644 index 00000000000..13e28bb59fd --- /dev/null +++ b/third_party/heimdal/lib/gssapi/spnego/negoex_ctx.c @@ -0,0 +1,1037 @@ +/* + * 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(); + + gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + major, *minor, + "NegoEx failed to initialize security context: %s", + krb5_get_error_message(context, *minor)); + } + + _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(); + + gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + major, *minor, + "NegoEx failed to accept security context: %s", + krb5_get_error_message(context, *minor)); + } + + _gss_negoex_release_context(ctx); + } + + return major; +} |