gss: port NegoEx implementation from MIT

An implementation of draft-zhu-negoex-04 for MIT Kerberos was developed in
2011. This has been recently integrated, with many fixes from Greg Hudson. This
commit ports it to Heimdal. The implementation has been interoperability tested
with MIT Kerberos and Windows, using the GSS EAP mechanism developed as part of
the Moonshot project.

The SPNEGO code was also updated to import the state machine from Apple which
improves mechListMIC processing and avoids discarding initial context tokens
generated during mechanism probing, that can be used for optimistic tokens.

Finally, to aid in testing, the GSS-API mechanism glue configuration file can
be changed using the environment variable GSS_MECH_CONFIG. This environment
variable name, along with the format of the configuration file, is compatible
with MIT (although it would be difficult for a single mechanism binary to
support both implementations).
This commit is contained in:
Luke Howard
2019-12-30 13:34:10 +11:00
parent ae5c60286a
commit 4fb6a6adc9
45 changed files with 6241 additions and 863 deletions

View File

@@ -2,6 +2,8 @@
* Copyright (c) 2004, PADL Software Pty Ltd.
* All rights reserved.
*
* Portions Copyright (c) 2009 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
@@ -43,9 +45,6 @@
gss_OID_desc _gss_spnego_mskrb_mechanism_oid_desc =
{9, rk_UNCONST("\x2a\x86\x48\x82\xf7\x12\x01\x02\x02")};
gss_OID_desc _gss_spnego_krb5_mechanism_oid_desc =
{9, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02")};
/*
* Allocate a SPNEGO context handle
*/
@@ -61,28 +60,33 @@ _gss_spnego_alloc_sec_context (OM_uint32 * minor_status,
return GSS_S_FAILURE;
}
ctx->initiator_mech_types.len = 0;
ctx->initiator_mech_types.val = NULL;
ctx->NegTokenInit_mech_types.value = NULL;
ctx->NegTokenInit_mech_types.length = 0;
ctx->preferred_mech_type = GSS_C_NO_OID;
ctx->selected_mech_type = GSS_C_NO_OID;
ctx->negotiated_mech_type = GSS_C_NO_OID;
ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT;
/*
* Cache these so we can return them before returning
* GSS_S_COMPLETE, even if the mechanism has itself
* completed earlier
*/
ctx->mech_flags = 0;
ctx->mech_time_rec = 0;
ctx->mech_src_name = GSS_C_NO_NAME;
ctx->open = 0;
ctx->local = 0;
ctx->require_mic = 0;
ctx->verified_mic = 0;
ctx->flags.open = 0;
ctx->flags.local = 0;
ctx->flags.peer_require_mic = 0;
ctx->flags.require_mic = 0;
ctx->flags.verified_mic = 0;
HEIMDAL_MUTEX_init(&ctx->ctx_id_mutex);
ctx->negoex_step = 0;
ctx->negoex_transcript = NULL;
ctx->negoex_seqnum = 0;
HEIM_TAILQ_INIT(&ctx->negoex_mechs);
memset(ctx->negoex_conv_id, 0, GUID_LENGTH);
*context_handle = (gss_ctx_id_t)ctx;
return GSS_S_COMPLETE;
@@ -119,11 +123,12 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context
return GSS_S_NO_CONTEXT;
}
if (ctx->initiator_mech_types.val != NULL)
free_MechTypeList(&ctx->initiator_mech_types);
if (ctx->NegTokenInit_mech_types.value)
free(ctx->NegTokenInit_mech_types.value);
gss_release_oid(&minor, &ctx->preferred_mech_type);
ctx->preferred_mech_type = GSS_C_NO_OID;
ctx->negotiated_mech_type = GSS_C_NO_OID;
ctx->selected_mech_type = GSS_C_NO_OID;
gss_release_name(&minor, &ctx->target_name);
gss_release_name(&minor, &ctx->mech_src_name);
@@ -137,6 +142,8 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context
ret = GSS_S_COMPLETE;
}
_gss_negoex_release_context(ctx);
HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
HEIMDAL_MUTEX_destroy(&ctx->ctx_id_mutex);
@@ -146,93 +153,121 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context
}
/*
* For compatability with the Windows SPNEGO implementation, the
* default is to ignore the mechListMIC unless CFX is used and
* a non-preferred mechanism was negotiated
* Returns TRUE if it is safe to omit mechListMIC because the preferred
* mechanism was selected, and the peer did not require it.
*/
OM_uint32 GSSAPI_CALLCONV
_gss_spnego_require_mechlist_mic(OM_uint32 *minor_status,
gssspnego_ctx ctx,
int *require_mic)
int
_gss_spnego_safe_omit_mechlist_mic(gssspnego_ctx ctx)
{
gss_buffer_set_t buffer_set = GSS_C_NO_BUFFER_SET;
OM_uint32 minor;
int safe_omit = 0;
*minor_status = 0;
*require_mic = 0;
if (ctx->flags.peer_require_mic == FALSE)
safe_omit = gss_oid_equal(ctx->selected_mech_type, ctx->preferred_mech_type);
if (ctx == NULL) {
return GSS_S_COMPLETE;
if (safe_omit)
_gss_mg_log(10, "spnego: safe to omit mechListMIC, as preferred mechanism selected");
else
_gss_mg_log(10, "spnego: mechListMIC required");
return safe_omit;
}
static OM_uint32
add_mech_type(OM_uint32 *minor_status,
gss_OID mech_type,
MechTypeList *mechtypelist)
{
MechType mech;
int ret;
heim_assert(!gss_oid_equal(mech_type, GSS_SPNEGO_MECHANISM),
"SPNEGO mechanism not filtered");
ret = der_get_oid(mech_type->elements, mech_type->length, &mech, NULL);
if (ret == 0) {
ret = add_MechTypeList(mechtypelist, &mech);
free_MechType(&mech);
}
if (ctx->require_mic) {
/* Acceptor requested it: mandatory to honour */
*require_mic = 1;
return GSS_S_COMPLETE;
}
/*
* Check whether peer indicated implicit support for updated SPNEGO
* (eg. in the Kerberos case by using CFX)
*/
if (gss_inquire_sec_context_by_oid(&minor, ctx->negotiated_ctx_id,
GSS_C_PEER_HAS_UPDATED_SPNEGO,
&buffer_set) == GSS_S_COMPLETE) {
*require_mic = 1;
gss_release_buffer_set(&minor, &buffer_set);
}
/* Safe-to-omit MIC rules follow */
if (*require_mic) {
if (gss_oid_equal(ctx->negotiated_mech_type, ctx->preferred_mech_type)) {
*require_mic = 0;
} else if (gss_oid_equal(ctx->negotiated_mech_type, &_gss_spnego_krb5_mechanism_oid_desc) &&
gss_oid_equal(ctx->preferred_mech_type, &_gss_spnego_mskrb_mechanism_oid_desc)) {
*require_mic = 0;
}
if (ret) {
*minor_status = ret;
return GSS_S_FAILURE;
}
return GSS_S_COMPLETE;
}
static int
add_mech_type(gss_OID mech_type,
int includeMSCompatOID,
MechTypeList *mechtypelist)
add_mech_if_approved(OM_uint32 *minor_status,
gss_const_name_t target_name,
OM_uint32 (*func)(OM_uint32 *, void *, gss_const_name_t, gss_const_cred_id_t, gss_OID),
void *userptr,
int includeMSCompatOID,
gss_const_cred_id_t cred_handle,
MechTypeList *mechtypelist,
gss_OID mech_oid,
gss_OID *first_mech,
OM_uint32 *first_major,
OM_uint32 *first_minor,
int *added_negoex)
{
MechType mech;
int ret;
OM_uint32 major, minor;
if (gss_oid_equal(mech_type, GSS_SPNEGO_MECHANISM))
return 0;
if (includeMSCompatOID &&
gss_oid_equal(mech_type, &_gss_spnego_krb5_mechanism_oid_desc)) {
ret = der_get_oid(_gss_spnego_mskrb_mechanism_oid_desc.elements,
_gss_spnego_mskrb_mechanism_oid_desc.length,
&mech,
NULL);
if (ret)
return ret;
ret = add_MechTypeList(mechtypelist, &mech);
free_MechType(&mech);
if (ret)
return ret;
/*
* Unapproved mechanisms are ignored, but we capture their result
* code in case we didn't find any other mechanisms, in which case
* we return that to the caller of _gss_spnego_indicate_mechtypelist().
*/
major = (*func)(&minor, userptr, target_name, cred_handle, mech_oid);
if (major != GSS_S_COMPLETE) {
if (*first_mech == GSS_C_NO_OID) {
*first_major = major;
*first_minor = minor;
}
return GSS_S_COMPLETE;
}
ret = der_get_oid(mech_type->elements, mech_type->length, &mech, NULL);
if (ret)
return ret;
ret = add_MechTypeList(mechtypelist, &mech);
free_MechType(&mech);
return ret;
}
if (_gss_negoex_mech_p(mech_oid)) {
if (*added_negoex == FALSE) {
major = add_mech_type(minor_status, GSS_NEGOEX_MECHANISM, mechtypelist);
if (major != GSS_S_COMPLETE)
return major;
*added_negoex = TRUE;
}
if (*first_mech == GSS_C_NO_OID)
*first_mech = GSS_NEGOEX_MECHANISM;
/* if NegoEx-only mech, we are done */
if (!_gss_negoex_and_spnego_mech_p(mech_oid))
return GSS_S_COMPLETE;
}
if (includeMSCompatOID && gss_oid_equal(mech_oid, GSS_KRB5_MECHANISM)) {
major = add_mech_type(minor_status,
&_gss_spnego_mskrb_mechanism_oid_desc,
mechtypelist);
if (major != GSS_S_COMPLETE)
return major;
}
major = add_mech_type(minor_status, mech_oid, mechtypelist);
if (major != GSS_S_COMPLETE)
return major;
if (*first_mech == GSS_C_NO_OID)
*first_mech = mech_oid;
return GSS_S_COMPLETE;
}
OM_uint32 GSSAPI_CALLCONV
_gss_spnego_indicate_mechtypelist (OM_uint32 *minor_status,
gss_name_t target_name,
OM_uint32 (*func)(gss_const_cred_id_t, gss_name_t, gss_OID),
gss_const_name_t target_name,
OM_uint32 (*func)(OM_uint32 *, void *, gss_const_name_t, gss_const_cred_id_t, gss_OID),
void *userptr,
int includeMSCompatOID,
gss_const_cred_id_t cred_handle,
MechTypeList *mechtypelist,
@@ -240,94 +275,297 @@ _gss_spnego_indicate_mechtypelist (OM_uint32 *minor_status,
{
gss_OID_set supported_mechs = GSS_C_NO_OID_SET;
gss_OID first_mech = GSS_C_NO_OID;
OM_uint32 ret;
OM_uint32 ret, minor;
OM_uint32 first_major = GSS_S_BAD_MECH, first_minor = 0;
size_t i;
int present = FALSE;
int added_negoex = FALSE;
mechtypelist->len = 0;
mechtypelist->val = NULL;
if (cred_handle) {
ret = gss_inquire_cred(minor_status,
cred_handle,
NULL,
NULL,
NULL,
&supported_mechs);
} else {
ret = gss_indicate_mechs(minor_status, &supported_mechs);
}
if (ret != GSS_S_COMPLETE) {
if (cred_handle != GSS_C_NO_CREDENTIAL)
ret = _gss_spnego_inquire_cred_mechs(minor_status,
cred_handle, &supported_mechs);
else
ret = _gss_spnego_indicate_mechs(minor_status, &supported_mechs);
if (ret != GSS_S_COMPLETE)
return ret;
heim_assert(supported_mechs != GSS_C_NO_OID_SET,
"NULL mech set returned by SPNEGO inquire/indicate mechs");
/*
* Propose Kerberos mech first if we have Kerberos credentials/supported mechs
*/
ret = gss_test_oid_set_member(minor_status, GSS_KRB5_MECHANISM,
supported_mechs, &present);
if (ret == GSS_S_COMPLETE && present) {
ret = add_mech_if_approved(minor_status, target_name,
func, userptr, includeMSCompatOID,
cred_handle, mechtypelist,
GSS_KRB5_MECHANISM, &first_mech,
&first_major, &first_minor,
&added_negoex);
}
if (supported_mechs->count == 0) {
*minor_status = ENOENT;
gss_release_oid_set(minor_status, &supported_mechs);
return GSS_S_FAILURE;
}
ret = (*func)(cred_handle, target_name, GSS_KRB5_MECHANISM);
if (ret == GSS_S_COMPLETE) {
ret = add_mech_type(GSS_KRB5_MECHANISM,
includeMSCompatOID,
mechtypelist);
if (!GSS_ERROR(ret))
first_mech = GSS_KRB5_MECHANISM;
}
ret = GSS_S_COMPLETE;
/*
* Now let's check all other mechs
*/
for (i = 0; i < supported_mechs->count; i++) {
OM_uint32 subret;
if (gss_oid_equal(&supported_mechs->elements[i], GSS_SPNEGO_MECHANISM))
continue;
if (gss_oid_equal(&supported_mechs->elements[i], GSS_KRB5_MECHANISM))
continue;
subret = (*func)(cred_handle, target_name, &supported_mechs->elements[i]);
if (subret != GSS_S_COMPLETE)
continue;
ret = add_mech_type(&supported_mechs->elements[i],
includeMSCompatOID,
mechtypelist);
if (ret != 0) {
*minor_status = ret;
ret = GSS_S_FAILURE;
break;
ret = add_mech_if_approved(minor_status, target_name,
func, userptr, FALSE,
cred_handle, mechtypelist,
&supported_mechs->elements[i],
&first_mech,
&first_major, &first_minor,
&added_negoex);
if (ret != GSS_S_COMPLETE) {
gss_release_oid_set(&minor, &supported_mechs);
return ret;
}
if (first_mech == GSS_C_NO_OID)
first_mech = &supported_mechs->elements[i];
}
if (mechtypelist->len == 0) {
gss_release_oid_set(minor_status, &supported_mechs);
*minor_status = 0;
return GSS_S_BAD_MECH;
}
heim_assert(mechtypelist->len == 0 || first_mech != GSS_C_NO_OID,
"mechtypelist non-empty but no mech selected");
if (preferred_mech != NULL) {
ret = gss_duplicate_oid(minor_status, first_mech, preferred_mech);
if (ret != GSS_S_COMPLETE)
free_MechTypeList(mechtypelist);
}
gss_release_oid_set(minor_status, &supported_mechs);
if (first_mech != GSS_C_NO_OID)
ret = _gss_intern_oid(minor_status, first_mech, &first_mech);
else if (GSS_ERROR(first_major)) {
ret = first_major;
*minor_status = first_minor;
} else
ret = GSS_S_BAD_MECH;
if (preferred_mech != NULL)
*preferred_mech = first_mech;
gss_release_oid_set(&minor, &supported_mechs);
return ret;
}
/*
*
*/
OM_uint32
_gss_spnego_verify_mechtypes_mic(OM_uint32 *minor_status,
gssspnego_ctx ctx,
heim_octet_string *mic)
{
gss_buffer_desc mic_buf;
OM_uint32 major_status;
if (mic == NULL) {
*minor_status = 0;
return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
GSS_S_DEFECTIVE_TOKEN, 0,
"SPNEGO peer failed to send mechListMIC");
}
if (ctx->flags.verified_mic) {
/* This doesn't make sense, we've already verified it? */
*minor_status = 0;
return GSS_S_DUPLICATE_TOKEN;
}
mic_buf.length = mic->length;
mic_buf.value = mic->data;
major_status = gss_verify_mic(minor_status,
ctx->negotiated_ctx_id,
&ctx->NegTokenInit_mech_types,
&mic_buf,
NULL);
if (major_status == GSS_S_COMPLETE) {
_gss_spnego_ntlm_reset_crypto(minor_status, ctx, TRUE);
} else if (major_status == GSS_S_UNAVAILABLE) {
_gss_mg_log(10, "mech doesn't support MIC, allowing anyway");
} else if (major_status) {
return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
GSS_S_DEFECTIVE_TOKEN, *minor_status,
"SPNEGO peer sent invalid mechListMIC");
}
ctx->flags.verified_mic = 1;
*minor_status = 0;
return GSS_S_COMPLETE;
}
/*
* According to [MS-SPNG] 3.3.5.1 the crypto state for NTLM is reset
* before the completed context is returned to the application.
*/
OM_uint32
_gss_spnego_ntlm_reset_crypto(OM_uint32 *minor_status,
gssspnego_ctx ctx,
OM_uint32 verify)
{
gss_buffer_desc value;
if (gss_oid_equal(ctx->negotiated_mech_type, GSS_NTLM_MECHANISM)) {
gss_buffer_desc value;
value.length = sizeof(verify);
value.value = &verify;
value.length = sizeof(verify);
value.value = &verify;
return gss_set_sec_context_option(minor_status,
&ctx->negotiated_ctx_id,
GSS_C_NTLM_RESET_CRYPTO,
&value);
return gss_set_sec_context_option(minor_status,
&ctx->negotiated_ctx_id,
GSS_C_NTLM_RESET_CRYPTO,
&value);
}
return GSS_S_COMPLETE;
}
void
_gss_spnego_log_mech(const char *prefix, gss_const_OID oid)
{
gss_buffer_desc oidbuf = GSS_C_EMPTY_BUFFER;
OM_uint32 junk;
const char *name = NULL;
if (!_gss_mg_log_level(10))
return;
if (oid == GSS_C_NO_OID ||
gss_oid_to_str(&junk, (gss_OID)oid, &oidbuf) != GSS_S_COMPLETE) {
_gss_mg_log(10, "spnego: %s (null)", prefix);
return;
}
if (gss_oid_equal(oid, GSS_NEGOEX_MECHANISM))
name = "negoex"; /* not a real mech */
else if (gss_oid_equal(oid, &_gss_spnego_mskrb_mechanism_oid_desc))
name = "mskrb";
else {
gssapi_mech_interface m = __gss_get_mechanism(oid);
if (m)
name = m->gm_name;
}
_gss_mg_log(10, "spnego: %s %s { %.*s }",
prefix,
name ? name : "unknown",
(int)oidbuf.length, (char *)oidbuf.value);
gss_release_buffer(&junk, &oidbuf);
}
void
_gss_spnego_log_mechTypes(MechTypeList *mechTypes)
{
size_t i;
char mechbuf[64];
size_t mech_len;
gss_OID_desc oid;
int ret;
if (!_gss_mg_log_level(10))
return;
for (i = 0; i < mechTypes->len; i++) {
ret = der_put_oid ((unsigned char *)mechbuf + sizeof(mechbuf) - 1,
sizeof(mechbuf),
&mechTypes->val[i],
&mech_len);
if (ret)
continue;
oid.length = (OM_uint32)mech_len;
oid.elements = mechbuf + sizeof(mechbuf) - mech_len;
_gss_spnego_log_mech("initiator proposed mech", &oid);
}
}
/*
* Indicate mechs negotiable by SPNEGO
*/
OM_uint32
_gss_spnego_indicate_mechs(OM_uint32 *minor_status,
gss_OID_set *mechs_p)
{
gss_OID_desc oids[3];
gss_OID_set_desc except;
*mechs_p = GSS_C_NO_OID_SET;
oids[0] = *GSS_C_MA_DEPRECATED;
oids[1] = *GSS_C_MA_NOT_DFLT_MECH;
oids[2] = *GSS_C_MA_MECH_NEGO;
except.count = sizeof(oids) / sizeof(oids[0]);
except.elements = oids;
return gss_indicate_mechs_by_attrs(minor_status,
GSS_C_NO_OID_SET,
&except,
GSS_C_NO_OID_SET,
mechs_p);
}
/*
* Indicate mechs in cred negotiatble by SPNEGO
*/
OM_uint32
_gss_spnego_inquire_cred_mechs(OM_uint32 *minor_status,
gss_const_cred_id_t cred,
gss_OID_set *mechs_p)
{
OM_uint32 ret, junk;
gss_OID_set cred_mechs = GSS_C_NO_OID_SET;
gss_OID_set mechs = GSS_C_NO_OID_SET;
size_t i;
*mechs_p = GSS_C_NO_OID_SET;
heim_assert(cred != GSS_C_NO_CREDENTIAL, "Invalid null credential handle");
ret = gss_inquire_cred(minor_status, cred, NULL, NULL, NULL, &cred_mechs);
if (ret != GSS_S_COMPLETE)
goto out;
heim_assert(cred_mechs != GSS_C_NO_OID_SET,
"gss_inquire_cred succeeded but returned null OID set");
ret = _gss_spnego_indicate_mechs(minor_status, &mechs);
if (ret != GSS_S_COMPLETE)
goto out;
heim_assert(mechs != GSS_C_NO_OID_SET,
"_gss_spnego_indicate_mechs succeeded but returned null OID set");
ret = gss_create_empty_oid_set(minor_status, mechs_p);
if (ret != GSS_S_COMPLETE)
goto out;
for (i = 0; i < cred_mechs->count; i++) {
gss_OID cred_mech = &cred_mechs->elements[i];
int present = 0;
gss_test_oid_set_member(&junk, cred_mech, mechs, &present);
if (!present)
continue;
ret = gss_add_oid_set_member(minor_status, cred_mech, mechs_p);
if (ret != GSS_S_COMPLETE)
break;
}
out:
if (ret != GSS_S_COMPLETE)
gss_release_oid_set(&junk, mechs_p);
gss_release_oid_set(&junk, &cred_mechs);
gss_release_oid_set(&junk, &mechs);
return ret;
}