/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright (C) 2011-2018 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 "k5-platform.h"
#include "gssapiP_spnego.h"
#include <generic/gssapiP_generic.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 void
zero_and_release_buffer_set(gss_buffer_set_t *pbuffers)
{
OM_uint32 tmpmin;
gss_buffer_set_t buffers = *pbuffers;
uint32_t i;
if (buffers != GSS_C_NO_BUFFER_SET) {
for (i = 0; i < buffers->count; i++)
zap(buffers->elements[i].value, buffers->elements[i].length);
gss_release_buffer_set(&tmpmin, &buffers);
}
*pbuffers = GSS_C_NO_BUFFER_SET;
}
static OM_uint32
buffer_set_to_key(OM_uint32 *minor, gss_buffer_set_t buffers,
krb5_keyblock *key)
{
krb5_error_code ret;
/* 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 != 4) {
*minor = ERR_NEGOEX_NO_VERIFY_KEY;
return GSS_S_FAILURE;
}
krb5_free_keyblock_contents(NULL, key);
key->contents = k5memdup(buffers->elements[0].value,
buffers->elements[0].length, &ret);
if (key->contents == NULL) {
*minor = ret;
return GSS_S_FAILURE;
}
key->length = buffers->elements[0].length;
key->enctype = load_32_le(buffers->elements[1].value);
return GSS_S_COMPLETE;
}
static OM_uint32
get_session_keys(OM_uint32 *minor, struct negoex_auth_mech *mech)
{
OM_uint32 major, tmpmin;
gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET;
major = gss_inquire_sec_context_by_oid(&tmpmin, mech->mech_context,
GSS_C_INQ_NEGOEX_KEY, &buffers);
if (major == GSS_S_COMPLETE) {
major = buffer_set_to_key(minor, buffers, &mech->key);
zero_and_release_buffer_set(&buffers);
if (major != GSS_S_COMPLETE)
return major;
}
major = gss_inquire_sec_context_by_oid(&tmpmin, mech->mech_context,
GSS_C_INQ_NEGOEX_VERIFY_KEY,
&buffers);
if (major == GSS_S_COMPLETE) {
major = buffer_set_to_key(minor, buffers, &mech->verify_key);
zero_and_release_buffer_set(&buffers);
if (major != GSS_S_COMPLETE)
return major;
}
return GSS_S_COMPLETE;
}
static OM_uint32
emit_initiator_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
{
OM_uint32 major;
uint8_t random[32];
major = negoex_random(minor, ctx, random, 32);
if (major != GSS_S_COMPLETE)
return major;
negoex_add_nego_message(ctx, INITIATOR_NEGO, random);
return GSS_S_COMPLETE;
}
static OM_uint32
process_initiator_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
struct negoex_message *messages, size_t nmessages)
{
struct nego_message *msg;
assert(!ctx->initiate && ctx->negoex_step == 1);
msg = negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO);
if (msg == NULL) {
*minor = ERR_NEGOEX_MISSING_NEGO_MESSAGE;
return GSS_S_DEFECTIVE_TOKEN;
}
negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes);
return GSS_S_COMPLETE;
}
static OM_uint32
emit_acceptor_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
{
OM_uint32 major;
uint8_t random[32];
major = negoex_random(minor, ctx, random, 32);
if (major != GSS_S_COMPLETE)
return major;
negoex_add_nego_message(ctx, ACCEPTOR_NEGO, random);
return GSS_S_COMPLETE;
}
static OM_uint32
process_acceptor_nego(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
struct negoex_message *messages, size_t nmessages)
{
struct nego_message *msg;
msg = negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO);
if (msg == NULL) {
*minor = ERR_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). */
negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes);
return GSS_S_COMPLETE;
}
static void
query_meta_data(spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
gss_name_t target, OM_uint32 req_flags)
{
OM_uint32 major, minor;
struct negoex_auth_mech *p, *next;
K5_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) {
major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context,
target, req_flags, &p->metadata);
/* GSS_Query_meta_data failure removes mechanism from list. */
if (major != GSS_S_COMPLETE)
negoex_delete_auth_mech(ctx, p);
}
}
static void
exchange_meta_data(spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
gss_name_t target, 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->initiate ? ACCEPTOR_META_DATA : INITIATOR_META_DATA;
for (i = 0; i < nmessages; i++) {
if (messages[i].type != type)
continue;
msg = &messages[i].u.e;
mech = negoex_locate_auth_scheme(ctx, msg->scheme);
if (mech == NULL)
continue;
major = gssspi_exchange_meta_data(&minor, mech->oid, cred,
&mech->mech_context, target,
req_flags, &msg->token);
/* GSS_Exchange_meta_data failure removes mechanism from list. */
if (major != GSS_S_COMPLETE)
negoex_delete_auth_mech(ctx, mech);
}
}
/*
* 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(spnego_gss_ctx_id_t ctx,
struct negoex_message *messages, size_t nmessages)
{
struct negoex_auth_mech *mech;
OM_uint32 tmpmin;
assert(ctx->initiate && ctx->negoex_step == 2);
/* Do nothing if we didn't make an optimistic context. */
mech = K5_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 (negoex_locate_exchange_message(messages, nmessages,
CHALLENGE) != NULL ||
negoex_locate_verify_message(messages, nmessages) != NULL) {
/* The acceptor continued the optimistic mech, and metadata exchange
* didn't remove it. Commit to this mechanism. */
negoex_select_auth_mech(ctx, mech);
} else {
/* The acceptor ignored the optimistic token. Restart the mech. */
(void)gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL);
krb5_free_keyblock_contents(NULL, &mech->key);
krb5_free_keyblock_contents(NULL, &mech->verify_key);
mech->complete = mech->sent_checksum = FALSE;
}
}
/* Perform an initiator step of the underlying mechanism exchange. */
static OM_uint32
mech_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
gss_name_t target, OM_uint32 req_flags, OM_uint32 time_req,
struct negoex_message *messages, size_t nmessages,
gss_buffer_t output_token, OM_uint32 *time_rec)
{
OM_uint32 major, first_major = 0, 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;
output_token->value = NULL;
output_token->length = 0;
/* Allow disabling of optimistic token for testing. */
if (ctx->negoex_step == 1 &&
secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL)
return GSS_S_COMPLETE;
if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
*minor = ERR_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 = K5_TAILQ_FIRST(&ctx->negoex_mechs);
msg = 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;
while (!K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
major = gss_init_sec_context(minor, cred, &mech->mech_context, target,
mech->oid, req_flags, time_req,
GSS_C_NO_CHANNEL_BINDINGS, input_token,
&ctx->actual_mech, output_token,
&ctx->ctx_flags, time_rec);
if (major == GSS_S_COMPLETE)
mech->complete = 1;
if (!GSS_ERROR(major))
return get_session_keys(minor, 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. */
negoex_delete_auth_mech(ctx, mech);
first_mech = FALSE;
input_token = GSS_C_NO_BUFFER;
}
if (K5_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, spnego_gss_ctx_id_t ctx,
gss_cred_id_t cred, struct negoex_message *messages,
size_t nmessages, gss_buffer_t output_token, OM_uint32 *time_rec)
{
OM_uint32 major, tmpmin;
struct negoex_auth_mech *mech;
struct exchange_message *msg;
assert(!ctx->initiate && !K5_TAILQ_EMPTY(&ctx->negoex_mechs));
msg = 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 ||
K5_TAILQ_FIRST(&ctx->negoex_mechs)->complete)
return GSS_S_COMPLETE;
*minor = ERR_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 = K5_TAILQ_FIRST(&ctx->negoex_mechs);
if (!GUID_EQ(msg->scheme, mech->scheme))
return GSS_S_COMPLETE;
} else {
/* The initiator has selected a mech; discard other entries. */
mech = negoex_locate_auth_scheme(ctx, msg->scheme);
if (mech == NULL) {
*minor = ERR_NEGOEX_NO_AVAILABLE_MECHS;
return GSS_S_FAILURE;
}
negoex_select_auth_mech(ctx, mech);
}
if (mech->complete)
return GSS_S_COMPLETE;
if (ctx->internal_name != GSS_C_NO_NAME)
gss_release_name(&tmpmin, &ctx->internal_name);
if (ctx->deleg_cred != GSS_C_NO_CREDENTIAL)
gss_release_cred(&tmpmin, &ctx->deleg_cred);
major = gss_accept_sec_context(minor, &mech->mech_context, cred,
&msg->token, GSS_C_NO_CHANNEL_BINDINGS,
&ctx->internal_name, &ctx->actual_mech,
output_token, &ctx->ctx_flags,
time_rec, &ctx->deleg_cred);
if (major == GSS_S_COMPLETE)
mech->complete = 1;
if (!GSS_ERROR(major)) {
major = get_session_keys(minor, mech);
} else if (ctx->negoex_step == 1) {
/* This was an optimistic token; pretend this never happened. */
major = GSS_S_COMPLETE;
*minor = 0;
gss_release_buffer(&tmpmin, output_token);
gss_delete_sec_context(&tmpmin, &mech->mech_context, GSS_C_NO_BUFFER);
}
return major;
}
static krb5_keyusage
verify_keyusage(spnego_gss_ctx_id_t ctx, int make_checksum)
{
/* Of course, these are the wrong way around in the spec. */
return (ctx->initiate ^ !make_checksum) ?
NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM;
}
static OM_uint32
verify_checksum(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
struct negoex_message *messages, size_t nmessages,
gss_buffer_t input_token, int *send_alert_out)
{
krb5_error_code ret;
struct negoex_auth_mech *mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
struct verify_message *msg;
krb5_crypto_iov iov[3];
krb5_keyusage usage = verify_keyusage(ctx, FALSE);
krb5_boolean valid;
*send_alert_out = FALSE;
assert(mech != NULL);
/* 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 = negoex_locate_verify_message(messages, nmessages);
if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme))
return GSS_S_COMPLETE;
/* 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_key.enctype == ENCTYPE_NULL) {
*send_alert_out = TRUE;
return GSS_S_COMPLETE;
}
/* Verify the checksum over the existing transcript and the portion of the
* input token leading up to the verify message. */
assert(input_token != NULL);
iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
iov[0].data = make_data(ctx->negoex_transcript.data,
ctx->negoex_transcript.len);
iov[1].flags = KRB5_CRYPTO_TYPE_DATA;
iov[1].data = make_data(input_token->value, msg->offset_in_token);
iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
iov[2].data = make_data((uint8_t *)msg->cksum, msg->cksum_len);
ret = krb5_c_verify_checksum_iov(ctx->kctx, msg->cksum_type,
&mech->verify_key, usage, iov, 3, &valid);
if (ret) {
*minor = ret;
return GSS_S_FAILURE;
}
if (!valid || !krb5_c_is_keyed_cksum(msg->cksum_type)) {
*minor = ERR_NEGOEX_INVALID_CHECKSUM;
return GSS_S_BAD_SIG;
}
mech->verified_checksum = TRUE;
return GSS_S_COMPLETE;
}
static OM_uint32
make_checksum(OM_uint32 *minor, spnego_gss_ctx_id_t ctx)
{
krb5_error_code ret;
krb5_data d;
krb5_keyusage usage = verify_keyusage(ctx, TRUE);
krb5_checksum cksum;
struct negoex_auth_mech *mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
assert(mech != NULL);
if (mech->key.enctype == ENCTYPE_NULL) {
if (mech->complete) {
*minor = ERR_NEGOEX_NO_VERIFY_KEY;
return GSS_S_UNAVAILABLE;
} else {
return GSS_S_COMPLETE;
}
}
d = make_data(ctx->negoex_transcript.data, ctx->negoex_transcript.len);
ret = krb5_c_make_checksum(ctx->kctx, 0, &mech->key, usage, &d, &cksum);
if (ret) {
*minor = ret;
return GSS_S_FAILURE;
}
negoex_add_verify_message(ctx, mech->scheme, cksum.checksum_type,
cksum.contents, cksum.length);
mech->sent_checksum = TRUE;
krb5_free_checksum_contents(ctx->kctx, &cksum);
return GSS_S_COMPLETE;
}
/* 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(spnego_gss_ctx_id_t ctx,
struct negoex_message *messages, uint32_t nmessages)
{
struct alert_message *msg;
struct negoex_auth_mech *mech;
msg = negoex_locate_alert_message(messages, nmessages);
if (msg != NULL && msg->verify_no_key) {
mech = negoex_locate_auth_scheme(ctx, msg->scheme);
if (mech != NULL) {
mech->sent_checksum = FALSE;
krb5_free_keyblock_contents(NULL, &mech->key);
krb5_free_keyblock_contents(NULL, &mech->verify_key);
}
}
}
static OM_uint32
make_output_token(OM_uint32 *minor, spnego_gss_ctx_id_t ctx,
gss_buffer_t mech_output_token, int send_alert,
gss_buffer_t output_token)
{
OM_uint32 major;
struct negoex_auth_mech *mech;
enum message_type type;
size_t old_transcript_len = ctx->negoex_transcript.len;
output_token->length = 0;
output_token->value = NULL;
/* 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 &&
K5_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum)
return GSS_S_COMPLETE;
if (ctx->negoex_step == 1) {
if (ctx->initiate)
major = emit_initiator_nego(minor, ctx);
else
major = emit_acceptor_nego(minor, ctx);
if (major != GSS_S_COMPLETE)
return major;
type = ctx->initiate ? INITIATOR_META_DATA : ACCEPTOR_META_DATA;
K5_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) {
if (mech->metadata.length > 0) {
negoex_add_exchange_message(ctx, type, mech->scheme,
&mech->metadata);
}
}
}
mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
if (mech_output_token->length > 0) {
type = ctx->initiate ? AP_REQUEST : CHALLENGE;
negoex_add_exchange_message(ctx, type, mech->scheme,
mech_output_token);
}
if (send_alert)
negoex_add_verify_no_key_alert(ctx, mech->scheme);
/* 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;
}
if (ctx->negoex_transcript.data == NULL) {
*minor = ENOMEM;
return GSS_S_FAILURE;
}
/* Copy what we added to the transcript into the output token. */
output_token->length = ctx->negoex_transcript.len - old_transcript_len;
output_token->value = gssalloc_malloc(output_token->length);
if (output_token->value == NULL) {
*minor = ENOMEM;
return GSS_S_FAILURE;
}
memcpy(output_token->value,
(uint8_t *)ctx->negoex_transcript.data + old_transcript_len,
output_token->length);
return GSS_S_COMPLETE;
}
OM_uint32
negoex_init(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
gss_name_t target_name, OM_uint32 req_flags, OM_uint32 time_req,
gss_buffer_t input_token, gss_buffer_t output_token,
OM_uint32 *time_rec)
{
OM_uint32 major, tmpmin;
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;
if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER &&
input_token->length != 0)
return GSS_S_DEFECTIVE_TOKEN;
major = negoex_prep_context_for_negoex(minor, ctx);
if (major != GSS_S_COMPLETE)
goto cleanup;
ctx->negoex_step++;
if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) {
major = 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. */
major = negoex_random(minor, ctx, ctx->negoex_conv_id, GUID_LENGTH);
if (major != GSS_S_COMPLETE)
goto cleanup;
/* Query each mech for its metadata (this may prune the mech list). */
query_meta_data(ctx, cred, target_name, 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, target_name, 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, ctx, cred, target_name, req_flags, time_req,
messages, nmessages, &mech_output_token, time_rec);
if (major != GSS_S_COMPLETE)
goto cleanup;
assert(!K5_TAILQ_EMPTY(&ctx->negoex_mechs));
/* 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)
negoex_select_auth_mech(ctx, K5_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) {
k5_buf_add_len(&ctx->negoex_transcript, input_token->value,
input_token->length);
}
major = make_output_token(minor, ctx, &mech_output_token, send_alert,
output_token);
if (major != GSS_S_COMPLETE)
goto cleanup;
mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
GSS_S_CONTINUE_NEEDED;
cleanup:
free(messages);
gss_release_buffer(&tmpmin, &mech_output_token);
negoex_prep_context_for_spnego(ctx);
return major;
}
OM_uint32
negoex_accept(OM_uint32 *minor, spnego_gss_ctx_id_t ctx, gss_cred_id_t cred,
gss_buffer_t input_token, gss_buffer_t output_token,
OM_uint32 *time_rec)
{
OM_uint32 major, tmpmin;
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;
if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) {
major = GSS_S_DEFECTIVE_TOKEN;
goto cleanup;
}
major = negoex_prep_context_for_negoex(minor, ctx);
if (major != GSS_S_COMPLETE)
goto cleanup;
ctx->negoex_step++;
major = 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, GSS_C_NO_NAME, 0, messages, nmessages);
query_meta_data(ctx, cred, GSS_C_NO_NAME, 0);
if (K5_TAILQ_EMPTY(&ctx->negoex_mechs)) {
*minor = ERR_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, messages, nmessages,
&mech_output_token, time_rec);
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;
}
k5_buf_add_len(&ctx->negoex_transcript,
input_token->value, input_token->length);
major = make_output_token(minor, ctx, &mech_output_token, send_alert,
output_token);
if (major != GSS_S_COMPLETE)
goto cleanup;
mech = K5_TAILQ_FIRST(&ctx->negoex_mechs);
major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE :
GSS_S_CONTINUE_NEEDED;
cleanup:
free(messages);
gss_release_buffer(&tmpmin, &mech_output_token);
negoex_prep_context_for_spnego(ctx);
return major;
}