1000 lines
29 KiB
C
1000 lines
29 KiB
C
/*
|
|
* Copyright (C) 2011-2019 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;
|
|
}
|
|
|
|
static OM_uint32
|
|
get_session_keys(OM_uint32 *minor,
|
|
krb5_context context,
|
|
struct negoex_auth_mech *mech)
|
|
{
|
|
OM_uint32 major, tmpMinor;
|
|
gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET;
|
|
|
|
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;
|
|
}
|
|
|
|
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, 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, 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_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;
|
|
|
|
/*
|
|
* 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) {
|
|
*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;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|