diff options
| author | Cy Schubert <cy@FreeBSD.org> | 2023-06-26 22:56:52 +0000 |
|---|---|---|
| committer | Cy Schubert <cy@FreeBSD.org> | 2023-06-26 22:56:52 +0000 |
| commit | b6a943f7197af1a5eb6bb028b9b808ec5016e30c (patch) | |
| tree | cfbb91e940dd89d0e1d46095f43c228d7d079fa0 /lib/gssapi/spnego | |
| parent | 6f4e10db3298f6d65e1e646fe52aaafc3682b788 (diff) | |
heimdal: Vendor import f62e2f278vendor/heimdal/7.8.0-2023-06-10-f62e2f278vendor/heimdal
Heimdal 7.8.0 does not support OpenSSL 3.0. 7.9.0 will but it hasn't
been released yet. We are importing f62e2f278 for its OpenSSL 3.0
support.
Diffstat (limited to 'lib/gssapi/spnego')
| -rw-r--r-- | lib/gssapi/spnego/accept_sec_context.c | 705 | ||||
| -rw-r--r-- | lib/gssapi/spnego/compat.c | 619 | ||||
| -rw-r--r-- | lib/gssapi/spnego/context_storage.c | 498 | ||||
| -rw-r--r-- | lib/gssapi/spnego/context_stubs.c | 321 | ||||
| -rw-r--r-- | lib/gssapi/spnego/cred_stubs.c | 271 | ||||
| -rw-r--r-- | lib/gssapi/spnego/external.c | 69 | ||||
| -rw-r--r-- | lib/gssapi/spnego/init_sec_context.c | 887 | ||||
| -rw-r--r-- | lib/gssapi/spnego/negoex_ctx.c | 1041 | ||||
| -rw-r--r-- | lib/gssapi/spnego/negoex_err.et | 25 | ||||
| -rw-r--r-- | lib/gssapi/spnego/negoex_locl.h | 127 | ||||
| -rw-r--r-- | lib/gssapi/spnego/negoex_util.c | 1047 | ||||
| -rw-r--r-- | lib/gssapi/spnego/spnego-private.h | 323 | ||||
| -rw-r--r-- | lib/gssapi/spnego/spnego.asn1 | 21 | ||||
| -rw-r--r-- | lib/gssapi/spnego/spnego_locl.h | 86 |
14 files changed, 4332 insertions, 1708 deletions
diff --git a/lib/gssapi/spnego/accept_sec_context.c b/lib/gssapi/spnego/accept_sec_context.c index 5fe1a1a649a4..c4ac7455cf6e 100644 --- a/lib/gssapi/spnego/accept_sec_context.c +++ b/lib/gssapi/spnego/accept_sec_context.c @@ -35,26 +35,36 @@ static OM_uint32 send_reject (OM_uint32 *minor_status, + gss_const_buffer_t mech_token, gss_buffer_t output_token) { NegotiationToken nt; size_t size; + heim_octet_string responseToken; nt.element = choice_NegotiationToken_negTokenResp; - ALLOC(nt.u.negTokenResp.negResult, 1); - if (nt.u.negTokenResp.negResult == NULL) { + ALLOC(nt.u.negTokenResp.negState, 1); + if (nt.u.negTokenResp.negState == NULL) { *minor_status = ENOMEM; return GSS_S_FAILURE; } - *(nt.u.negTokenResp.negResult) = reject; + *(nt.u.negTokenResp.negState) = reject; nt.u.negTokenResp.supportedMech = NULL; nt.u.negTokenResp.responseToken = NULL; + + if (mech_token != GSS_C_NO_BUFFER && mech_token->value != NULL) { + responseToken.length = mech_token->length; + responseToken.data = mech_token->value; + nt.u.negTokenResp.responseToken = &responseToken; + } else + nt.u.negTokenResp.responseToken = NULL; nt.u.negTokenResp.mechListMIC = NULL; ASN1_MALLOC_ENCODE(NegotiationToken, output_token->value, output_token->length, &nt, &size, *minor_status); + nt.u.negTokenResp.responseToken = NULL; /* allocated on stack */ free_NegotiationToken(&nt); if (*minor_status != 0) return GSS_S_FAILURE; @@ -63,46 +73,77 @@ send_reject (OM_uint32 *minor_status, } static OM_uint32 -acceptor_approved(gss_name_t target_name, gss_OID mech) +acceptor_approved(OM_uint32 *minor_status, + void *userptr, + gss_const_name_t target_name, + gss_const_cred_id_t cred_handle, + gss_OID mech) { gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; - gss_OID_set oidset; + gss_OID_set oidset = GSS_C_NO_OID_SET; OM_uint32 junk, ret; if (target_name == GSS_C_NO_NAME) return GSS_S_COMPLETE; - gss_create_empty_oid_set(&junk, &oidset); - gss_add_oid_set_member(&junk, mech, &oidset); + if (gss_oid_equal(mech, GSS_NEGOEX_MECHANISM)) { + size_t i; + + ret = _gss_spnego_indicate_mechs(minor_status, &oidset); + if (ret != GSS_S_COMPLETE) + return ret; + + /* before committing to NegoEx, check we can negotiate a mech */ + for (i = 0; i < oidset->count; i++) { + gss_OID inner_mech = &oidset->elements[i]; + + if (_gss_negoex_mech_p(inner_mech)) { + ret = acceptor_approved(minor_status, userptr, + target_name, cred_handle, + inner_mech); + if (ret == GSS_S_COMPLETE) + break; + } + } + } else if (cred_handle != GSS_C_NO_CREDENTIAL) { + ret = gss_inquire_cred_by_mech(minor_status, cred_handle, mech, + NULL, NULL, NULL, NULL); + } else { + ret = gss_create_empty_oid_set(minor_status, &oidset); + if (ret == GSS_S_COMPLETE) + ret = gss_add_oid_set_member(minor_status, mech, &oidset); + if (ret == GSS_S_COMPLETE) + ret = gss_acquire_cred(minor_status, target_name, + GSS_C_INDEFINITE, oidset, + GSS_C_ACCEPT, &cred, NULL, NULL); + } - ret = gss_acquire_cred(&junk, target_name, GSS_C_INDEFINITE, oidset, - GSS_C_ACCEPT, &cred, NULL, NULL); gss_release_oid_set(&junk, &oidset); - if (ret != GSS_S_COMPLETE) - return ret; gss_release_cred(&junk, &cred); - return GSS_S_COMPLETE; + return ret; } static OM_uint32 send_supported_mechs (OM_uint32 *minor_status, + gssspnego_ctx ctx, + gss_const_cred_id_t acceptor_cred, gss_buffer_t output_token) { - NegotiationTokenWin nt; + NegotiationToken2 nt; size_t buf_len = 0; gss_buffer_desc data; OM_uint32 ret; memset(&nt, 0, sizeof(nt)); - nt.element = choice_NegotiationTokenWin_negTokenInit; + nt.element = choice_NegotiationToken2_negTokenInit; nt.u.negTokenInit.reqFlags = NULL; nt.u.negTokenInit.mechToken = NULL; nt.u.negTokenInit.negHints = NULL; - ret = _gss_spnego_indicate_mechtypelist(minor_status, GSS_C_NO_NAME, - acceptor_approved, 1, NULL, + ret = _gss_spnego_indicate_mechtypelist(minor_status, GSS_C_NO_NAME, 0, + acceptor_approved, ctx, 1, acceptor_cred, &nt.u.negTokenInit.mechTypes, NULL); if (ret != GSS_S_COMPLETE) { return ret; @@ -111,23 +152,23 @@ send_supported_mechs (OM_uint32 *minor_status, ALLOC(nt.u.negTokenInit.negHints, 1); if (nt.u.negTokenInit.negHints == NULL) { *minor_status = ENOMEM; - free_NegotiationTokenWin(&nt); + free_NegotiationToken2(&nt); return GSS_S_FAILURE; } ALLOC(nt.u.negTokenInit.negHints->hintName, 1); if (nt.u.negTokenInit.negHints->hintName == NULL) { *minor_status = ENOMEM; - free_NegotiationTokenWin(&nt); + free_NegotiationToken2(&nt); return GSS_S_FAILURE; } *nt.u.negTokenInit.negHints->hintName = strdup("not_defined_in_RFC4178@please_ignore"); nt.u.negTokenInit.negHints->hintAddress = NULL; - ASN1_MALLOC_ENCODE(NegotiationTokenWin, + ASN1_MALLOC_ENCODE(NegotiationToken2, data.value, data.length, &nt, &buf_len, ret); - free_NegotiationTokenWin(&nt); + free_NegotiationToken2(&nt); if (ret) { *minor_status = ret; return GSS_S_FAILURE; @@ -152,13 +193,15 @@ send_supported_mechs (OM_uint32 *minor_status, static OM_uint32 send_accept (OM_uint32 *minor_status, gssspnego_ctx context_handle, + int optimistic_mech_ok, gss_buffer_t mech_token, - int initial_response, + gss_const_OID selected_mech, /* valid on initial response only */ gss_buffer_t mech_buf, gss_buffer_t output_token) { + int initial_response = (selected_mech != GSS_C_NO_OID); NegotiationToken nt; - OM_uint32 ret; + OM_uint32 ret, minor; gss_buffer_desc mech_mic_buf; size_t size; @@ -166,43 +209,45 @@ send_accept (OM_uint32 *minor_status, nt.element = choice_NegotiationToken_negTokenResp; - ALLOC(nt.u.negTokenResp.negResult, 1); - if (nt.u.negTokenResp.negResult == NULL) { + ALLOC(nt.u.negTokenResp.negState, 1); + if (nt.u.negTokenResp.negState == NULL) { *minor_status = ENOMEM; return GSS_S_FAILURE; } - if (context_handle->open) { + if (context_handle->flags.open) { if (mech_token != GSS_C_NO_BUFFER && mech_token->length != 0 && mech_buf != GSS_C_NO_BUFFER) - *(nt.u.negTokenResp.negResult) = accept_incomplete; + *(nt.u.negTokenResp.negState) = accept_incomplete; else - *(nt.u.negTokenResp.negResult) = accept_completed; + *(nt.u.negTokenResp.negState) = accept_completed; } else { - if (initial_response && context_handle->require_mic) - *(nt.u.negTokenResp.negResult) = request_mic; + if (initial_response && !optimistic_mech_ok) + *(nt.u.negTokenResp.negState) = request_mic; else - *(nt.u.negTokenResp.negResult) = accept_incomplete; + *(nt.u.negTokenResp.negState) = accept_incomplete; } if (initial_response) { ALLOC(nt.u.negTokenResp.supportedMech, 1); if (nt.u.negTokenResp.supportedMech == NULL) { - free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + ret = GSS_S_FAILURE; + goto out; } - ret = der_get_oid(context_handle->preferred_mech_type->elements, - context_handle->preferred_mech_type->length, + ret = der_get_oid(selected_mech->elements, + selected_mech->length, nt.u.negTokenResp.supportedMech, NULL); if (ret) { - free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + ret = GSS_S_FAILURE; + goto out; } + + _gss_spnego_log_mech("acceptor sending selected mech", selected_mech); } else { nt.u.negTokenResp.supportedMech = NULL; } @@ -210,9 +255,9 @@ send_accept (OM_uint32 *minor_status, if (mech_token != GSS_C_NO_BUFFER && mech_token->length != 0) { ALLOC(nt.u.negTokenResp.responseToken, 1); if (nt.u.negTokenResp.responseToken == NULL) { - free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + ret = GSS_S_FAILURE; + goto out; } nt.u.negTokenResp.responseToken->length = mech_token->length; nt.u.negTokenResp.responseToken->data = mech_token->value; @@ -229,20 +274,21 @@ send_accept (OM_uint32 *minor_status, mech_buf, &mech_mic_buf); if (ret == GSS_S_COMPLETE) { + _gss_spnego_ntlm_reset_crypto(&minor, context_handle, FALSE); + ALLOC(nt.u.negTokenResp.mechListMIC, 1); if (nt.u.negTokenResp.mechListMIC == NULL) { gss_release_buffer(minor_status, &mech_mic_buf); - free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + ret = GSS_S_FAILURE; + goto out; } nt.u.negTokenResp.mechListMIC->length = mech_mic_buf.length; nt.u.negTokenResp.mechListMIC->data = mech_mic_buf.value; } else if (ret == GSS_S_UNAVAILABLE) { nt.u.negTokenResp.mechListMIC = NULL; } else { - free_NegotiationToken(&nt); - return ret; + goto out; } } else @@ -252,9 +298,9 @@ send_accept (OM_uint32 *minor_status, output_token->value, output_token->length, &nt, &size, ret); if (ret) { - free_NegotiationToken(&nt); - *minor_status = ret; - return GSS_S_FAILURE; + *minor_status = ENOMEM; + ret = GSS_S_FAILURE; + goto out; } /* @@ -263,138 +309,166 @@ send_accept (OM_uint32 *minor_status, * specifies encapsulation for all _Kerberos_ tokens). */ - if (*(nt.u.negTokenResp.negResult) == accept_completed) + if (*(nt.u.negTokenResp.negState) == accept_completed) ret = GSS_S_COMPLETE; else ret = GSS_S_CONTINUE_NEEDED; + + out: free_NegotiationToken(&nt); return ret; } +/* + * Return the default acceptor identity based on the local hostname + * or the GSSAPI_SPNEGO_NAME environment variable. + */ static OM_uint32 -verify_mechlist_mic - (OM_uint32 *minor_status, - gssspnego_ctx context_handle, - gss_buffer_t mech_buf, - heim_octet_string *mechListMIC - ) +default_acceptor_name(OM_uint32 *minor_status, + gss_name_t *namep) { - OM_uint32 ret; - gss_buffer_desc mic_buf; + OM_uint32 major_status; + gss_buffer_desc namebuf; + char *str = NULL, *host, hostname[MAXHOSTNAMELEN]; - if (context_handle->verified_mic) { - /* This doesn't make sense, we've already verified it? */ - *minor_status = 0; - return GSS_S_DUPLICATE_TOKEN; - } + *namep = GSS_C_NO_NAME; - if (mechListMIC == NULL) { - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; + host = secure_getenv("GSSAPI_SPNEGO_NAME"); + if (host == NULL) { + int rv; + + if (gethostname(hostname, sizeof(hostname)) != 0) { + *minor_status = errno; + return GSS_S_FAILURE; + } + + rv = asprintf(&str, "host@%s", hostname); + if (rv < 0 || str == NULL) { + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + host = str; } - mic_buf.length = mechListMIC->length; - mic_buf.value = mechListMIC->data; + namebuf.length = strlen(host); + namebuf.value = host; - ret = gss_verify_mic(minor_status, - context_handle->negotiated_ctx_id, - mech_buf, - &mic_buf, - NULL); + major_status = gss_import_name(minor_status, &namebuf, + GSS_C_NT_HOSTBASED_SERVICE, namep); - if (ret != GSS_S_COMPLETE) - ret = GSS_S_DEFECTIVE_TOKEN; + free(str); - return ret; + return major_status; } +/* + * Determine whether the mech in mechType can be negotiated. If the + * mech is NegoEx, make NegoEx mechanisms available for negotiation. + */ + static OM_uint32 -select_mech(OM_uint32 *minor_status, MechType *mechType, int verify_p, - gss_OID *mech_p) +select_mech(OM_uint32 *minor_status, + gssspnego_ctx ctx, + gss_const_cred_id_t cred, + gss_const_OID_set supported_mechs, + MechType *mechType, + int verify_p, /* set on non-optimistic tokens */ + gss_const_OID *advertised_mech_p) { char mechbuf[64]; size_t mech_len; gss_OID_desc oid; - gss_OID oidp; - gss_OID_set mechs; - size_t i; + gss_OID selected_mech = GSS_C_NO_OID; OM_uint32 ret, junk; + int negoex_proposed = FALSE, negoex_selected = FALSE; + int includeMSCompatOID = FALSE; + size_t i; + + *minor_status = 0; + *advertised_mech_p = GSS_C_NO_OID; /* deals with broken MS OID */ + + ctx->selected_mech_type = GSS_C_NO_OID; ret = der_put_oid ((unsigned char *)mechbuf + sizeof(mechbuf) - 1, sizeof(mechbuf), mechType, &mech_len); - if (ret) { + if (ret) return GSS_S_DEFECTIVE_TOKEN; - } - oid.length = mech_len; + oid.length = (OM_uint32)mech_len; oid.elements = mechbuf + sizeof(mechbuf) - mech_len; - if (gss_oid_equal(&oid, GSS_SPNEGO_MECHANISM)) { - return GSS_S_BAD_MECH; - } + if (gss_oid_equal(&oid, GSS_NEGOEX_MECHANISM)) + negoex_proposed = TRUE; + else if (gss_oid_equal(&oid, &_gss_spnego_mskrb_mechanism_oid_desc)) + includeMSCompatOID = TRUE; - *minor_status = 0; - - /* Translate broken MS Kebreros OID */ - if (gss_oid_equal(&oid, &_gss_spnego_mskrb_mechanism_oid_desc)) - oidp = &_gss_spnego_krb5_mechanism_oid_desc; - else - oidp = &oid; + for (i = 0; i < supported_mechs->count; i++) { + gss_OID iter = &supported_mechs->elements[i]; + auth_scheme scheme; + int is_negoex_mech = /* mechanism is negotiable under NegoEx */ + gssspi_query_mechanism_info(&junk, iter, scheme) == GSS_S_COMPLETE; + if (is_negoex_mech && negoex_proposed) { + ret = _gss_negoex_add_auth_mech(minor_status, ctx, iter, scheme); + if (ret != GSS_S_COMPLETE) + break; - ret = gss_indicate_mechs(&junk, &mechs); - if (ret) - return (ret); + negoex_selected = TRUE; + } - for (i = 0; i < mechs->count; i++) - if (gss_oid_equal(&mechs->elements[i], oidp)) - break; + if (gss_oid_equal(includeMSCompatOID ? GSS_KRB5_MECHANISM : &oid, iter)) { + ret = _gss_intern_oid(minor_status, iter, &selected_mech); + if (ret != GSS_S_COMPLETE) + return ret; - if (i == mechs->count) { - gss_release_oid_set(&junk, &mechs); - return GSS_S_BAD_MECH; + break; + } } - gss_release_oid_set(&junk, &mechs); - ret = gss_duplicate_oid(minor_status, - &oid, /* possibly this should be oidp */ - mech_p); + /* always prefer NegoEx if a mechanism supported both */ + if (negoex_selected) + selected_mech = GSS_NEGOEX_MECHANISM; + if (selected_mech == GSS_C_NO_OID) + ret = GSS_S_BAD_MECH; + if (ret != GSS_S_COMPLETE) + return ret; + + heim_assert(!gss_oid_equal(selected_mech, GSS_SPNEGO_MECHANISM), + "SPNEGO should not be able to negotiate itself"); if (verify_p) { gss_name_t name = GSS_C_NO_NAME; - gss_buffer_desc namebuf; - char *str = NULL, *host, hostname[MAXHOSTNAMELEN]; - - host = getenv("GSSAPI_SPNEGO_NAME"); - if (host == NULL || issuid()) { - int rv; - if (gethostname(hostname, sizeof(hostname)) != 0) { - *minor_status = errno; - return GSS_S_FAILURE; - } - rv = asprintf(&str, "host@%s", hostname); - if (rv < 0 || str == NULL) { - *minor_status = ENOMEM; - return GSS_S_FAILURE; - } - host = str; - } - namebuf.length = strlen(host); - namebuf.value = host; + /* + * If we do not have a credential, acquire a default name as a hint + * to acceptor_approved() so it can attempt to acquire a default + * credential. + */ + if (cred == GSS_C_NO_CREDENTIAL) { + ret = default_acceptor_name(minor_status, &name); + if (ret != GSS_S_COMPLETE) + return ret; + } - ret = gss_import_name(minor_status, &namebuf, - GSS_C_NT_HOSTBASED_SERVICE, &name); - if (str) - free(str); - if (ret != GSS_S_COMPLETE) - return ret; + ret = acceptor_approved(minor_status, ctx, name, cred, selected_mech); - ret = acceptor_approved(name, *mech_p); gss_release_name(&junk, &name); + } else { + /* Stash optimistic mech for use by _gss_spnego_require_mechlist_mic() */ + ret = gss_duplicate_oid(minor_status, &oid, &ctx->preferred_mech_type); + } + + if (ret == GSS_S_COMPLETE) { + *minor_status = 0; + + *advertised_mech_p = ctx->selected_mech_type = selected_mech; + + /* if the initiator used the broken MS OID, send that instead */ + if (includeMSCompatOID && gss_oid_equal(selected_mech, GSS_KRB5_MECHANISM)) + *advertised_mech_p = &_gss_spnego_mskrb_mechanism_oid_desc; } return ret; @@ -405,25 +479,19 @@ static OM_uint32 acceptor_complete(OM_uint32 * minor_status, gssspnego_ctx ctx, int *get_mic, - gss_buffer_t mech_buf, gss_buffer_t mech_input_token, gss_buffer_t mech_output_token, heim_octet_string *mic, gss_buffer_t output_token) { + gss_buffer_desc buf = GSS_C_EMPTY_BUFFER; OM_uint32 ret; - int require_mic, verify_mic; - - ret = _gss_spnego_require_mechlist_mic(minor_status, ctx, &require_mic); - if (ret) - return ret; - - ctx->require_mic = require_mic; + int verify_mic; - if (mic != NULL) - require_mic = 1; + ctx->flags.require_mic = 1; + ctx->flags.safe_omit = _gss_spnego_safe_omit_mechlist_mic(ctx); - if (ctx->open && require_mic) { + if (ctx->flags.open) { if (mech_input_token == GSS_C_NO_BUFFER) { /* Even/One */ verify_mic = 1; *get_mic = 0; @@ -435,29 +503,30 @@ acceptor_complete(OM_uint32 * minor_status, *get_mic = 1; } - if (verify_mic || *get_mic) { - int eret; - size_t buf_len = 0; - - ASN1_MALLOC_ENCODE(MechTypeList, - mech_buf->value, mech_buf->length, - &ctx->initiator_mech_types, &buf_len, eret); - if (eret) { - *minor_status = eret; - return GSS_S_FAILURE; - } - heim_assert(mech_buf->length == buf_len, "Internal ASN.1 error"); - UNREACHABLE(return GSS_S_FAILURE); - } + /* + * Change from previous versions: do not generate a MIC if not + * necessary. This conforms to RFC4178 s.5 ("if the accepted + * mechanism is the most preferred mechanism of both the initiator + * and acceptor, then the MIC token exchange... is OPTIONAL"), + * and is consistent with MIT and Windows behavior. + */ + if (ctx->flags.safe_omit) + *get_mic = 0; - if (verify_mic) { - ret = verify_mechlist_mic(minor_status, ctx, mech_buf, mic); + if (verify_mic && mic == NULL && ctx->flags.safe_omit) { + /* + * Peer is old and didn't send a mic while we expected + * one, but since it safe to omit, let do that + */ + } else if (verify_mic) { + ret = _gss_spnego_verify_mechtypes_mic(minor_status, ctx, mic); if (ret) { if (*get_mic) - send_reject (minor_status, output_token); + send_reject(minor_status, GSS_C_NO_BUFFER, output_token); + if (buf.value) + free(buf.value); return ret; } - ctx->verified_mic = 1; } } else *get_mic = 0; @@ -465,6 +534,57 @@ acceptor_complete(OM_uint32 * minor_status, return GSS_S_COMPLETE; } +/* + * Call gss_accept_sec_context() via mechglue or NegoEx, depending on + * whether mech_oid is NegoEx. + */ + +static OM_uint32 +mech_accept(OM_uint32 *minor_status, + gssspnego_ctx ctx, + gss_const_cred_id_t acceptor_cred_handle, + gss_const_buffer_t input_token_buffer, + const gss_channel_bindings_t input_chan_bindings, + gss_buffer_t output_token, + gss_cred_id_t *delegated_cred_handle) +{ + OM_uint32 ret, junk; + + heim_assert(ctx->selected_mech_type != GSS_C_NO_OID, + "mech_accept called with no selected mech"); + + if (gss_oid_equal(ctx->selected_mech_type, GSS_NEGOEX_MECHANISM)) { + ret = _gss_negoex_accept(minor_status, + ctx, + (gss_cred_id_t)acceptor_cred_handle, + input_token_buffer, + input_chan_bindings, + output_token, + delegated_cred_handle); + } else { + if (ctx->mech_src_name != GSS_C_NO_NAME) + gss_release_name(&junk, &ctx->mech_src_name); + + ret = gss_accept_sec_context(minor_status, + &ctx->negotiated_ctx_id, + acceptor_cred_handle, + (gss_buffer_t)input_token_buffer, + input_chan_bindings, + &ctx->mech_src_name, + &ctx->negotiated_mech_type, + output_token, + &ctx->mech_flags, + &ctx->mech_time_rec, + delegated_cred_handle); + if (GSS_ERROR(ret)) + gss_mg_collect_error(ctx->negotiated_mech_type, ret, *minor_status); + else if (ctx->negotiated_mech_type != GSS_C_NO_OID && + !gss_oid_equal(ctx->negotiated_mech_type, ctx->selected_mech_type)) + _gss_mg_log(1, "spnego client didn't send the mech they said they would"); + } + + return ret; +} static OM_uint32 GSSAPI_CALLCONV acceptor_start @@ -483,23 +603,24 @@ acceptor_start { OM_uint32 ret, junk; NegotiationToken nt; - size_t nt_len; + gss_OID_set supported_mechs = GSS_C_NO_OID_SET; + size_t size; NegTokenInit *ni; gss_buffer_desc data; gss_buffer_t mech_input_token = GSS_C_NO_BUFFER; gss_buffer_desc mech_output_token; - gss_buffer_desc mech_buf; - gss_OID preferred_mech_type = GSS_C_NO_OID; gssspnego_ctx ctx; - int get_mic = 0; - int first_ok = 0; + int get_mic = 0, first_ok = 0, canonical_order; + gss_const_OID advertised_mech = GSS_C_NO_OID; + + memset(&nt, 0, sizeof(nt)); mech_output_token.value = NULL; mech_output_token.length = 0; - mech_buf.value = NULL; if (input_token_buffer->length == 0) - return send_supported_mechs (minor_status, output_token); + return send_supported_mechs (minor_status, NULL, + acceptor_cred_handle, output_token); ret = _gss_spnego_alloc_sec_context(minor_status, context_handle); if (ret != GSS_S_COMPLETE) @@ -507,6 +628,8 @@ acceptor_start ctx = (gssspnego_ctx)*context_handle; + HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); + /* * The GSS-API encapsulation is only present on the initial * context token (negTokenInit). @@ -515,36 +638,59 @@ acceptor_start GSS_SPNEGO_MECHANISM, &data); if (ret) - return ret; + goto out; - ret = decode_NegotiationToken(data.value, data.length, &nt, &nt_len); + ret = decode_NegotiationToken(data.value, data.length, &nt, &size); gss_release_buffer(minor_status, &data); if (ret) { *minor_status = ret; - return GSS_S_DEFECTIVE_TOKEN; + ret = GSS_S_DEFECTIVE_TOKEN; + goto out; } if (nt.element != choice_NegotiationToken_negTokenInit) { *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; + ret = GSS_S_DEFECTIVE_TOKEN; + goto out; } ni = &nt.u.negTokenInit; if (ni->mechTypes.len < 1) { free_NegotiationToken(&nt); *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; + ret = GSS_S_DEFECTIVE_TOKEN; + goto out; } - HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); + _gss_spnego_log_mechTypes(&ni->mechTypes); - ret = copy_MechTypeList(&ni->mechTypes, &ctx->initiator_mech_types); - if (ret) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free_NegotiationToken(&nt); - *minor_status = ret; - return GSS_S_FAILURE; + { + MechTypeList mt; + int kret; + + mt.len = ni->mechTypes.len; + mt.val = ni->mechTypes.val; + + ASN1_MALLOC_ENCODE(MechTypeList, + ctx->NegTokenInit_mech_types.value, + ctx->NegTokenInit_mech_types.length, + &mt, &size, kret); + if (kret) { + *minor_status = kret; + ret = GSS_S_FAILURE; + goto out; + } } + if (acceptor_cred_handle != GSS_C_NO_CREDENTIAL) + ret = _gss_spnego_inquire_cred_mechs(minor_status, + acceptor_cred_handle, + &supported_mechs, + &canonical_order); + else + ret = _gss_spnego_indicate_mechs(minor_status, &supported_mechs); + if (ret != GSS_S_COMPLETE) + goto out; + /* * First we try the opportunistic token if we have support for it, * don't try to verify we have credential for the token, @@ -553,41 +699,39 @@ acceptor_start */ ret = select_mech(minor_status, + ctx, + acceptor_cred_handle, + supported_mechs, &ni->mechTypes.val[0], - 0, - &preferred_mech_type); + 0, /* optimistic token */ + &advertised_mech); - if (ret == 0 && ni->mechToken != NULL) { + if (ret == GSS_S_COMPLETE && ni->mechToken != NULL) { gss_buffer_desc ibuf; ibuf.length = ni->mechToken->length; ibuf.value = ni->mechToken->data; mech_input_token = &ibuf; - if (ctx->mech_src_name != GSS_C_NO_NAME) - gss_release_name(&junk, &ctx->mech_src_name); - - ret = gss_accept_sec_context(minor_status, - &ctx->negotiated_ctx_id, - acceptor_cred_handle, - mech_input_token, - input_chan_bindings, - &ctx->mech_src_name, - &ctx->negotiated_mech_type, - &mech_output_token, - &ctx->mech_flags, - &ctx->mech_time_rec, - delegated_cred_handle); + _gss_spnego_log_mech("acceptor selected opportunistic mech", ctx->selected_mech_type); + ret = mech_accept(&junk, + ctx, + acceptor_cred_handle, + mech_input_token, + input_chan_bindings, + &mech_output_token, + delegated_cred_handle); if (ret == GSS_S_COMPLETE || ret == GSS_S_CONTINUE_NEEDED) { - ctx->preferred_mech_type = preferred_mech_type; - if (ret == GSS_S_COMPLETE) - ctx->open = 1; + first_ok = 1; + } else { + ctx->selected_mech_type = GSS_C_NO_OID; + } + if (ret == GSS_S_COMPLETE) { ret = acceptor_complete(minor_status, ctx, &get_mic, - &mech_buf, mech_input_token, &mech_output_token, ni->mechListMIC, @@ -595,75 +739,69 @@ acceptor_start if (ret != GSS_S_COMPLETE) goto out; - first_ok = 1; - } else { - gss_mg_collect_error(preferred_mech_type, ret, *minor_status); + ctx->flags.open = 1; } + } else { + *minor_status = 0; + gss_release_oid_set(&junk, &supported_mechs); + HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); + return gss_mg_set_error_string(GSS_C_NO_OID, GSS_S_NO_CONTEXT, + *minor_status, + "SPNEGO acceptor didn't find a prefered mechanism"); } /* * If opportunistic token failed, lets try the other mechs. */ - if (!first_ok && ni->mechToken != NULL) { + if (!first_ok) { size_t j; - preferred_mech_type = GSS_C_NO_OID; - /* Call glue layer to find first mech we support */ for (j = 1; j < ni->mechTypes.len; ++j) { - ret = select_mech(minor_status, + ret = select_mech(&junk, + ctx, + acceptor_cred_handle, + supported_mechs, &ni->mechTypes.val[j], - 1, - &preferred_mech_type); - if (ret == 0) + 1, /* not optimistic token */ + &advertised_mech); + if (ret == GSS_S_COMPLETE) { + _gss_spnego_log_mech("acceptor selected non-opportunistic mech", + ctx->selected_mech_type); break; + } } } - - ctx->preferred_mech_type = preferred_mech_type; - - if (preferred_mech_type == GSS_C_NO_OID) { - send_reject(minor_status, output_token); - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free_NegotiationToken(&nt); - return ret; + if (ctx->selected_mech_type == GSS_C_NO_OID) { + heim_assert(ret != GSS_S_COMPLETE, "no oid and no error code?"); + *minor_status = junk; + goto out; } - /* - * The initial token always have a response - */ - - ret = send_accept (minor_status, - ctx, - &mech_output_token, - 1, - get_mic ? &mech_buf : NULL, - output_token); + /* The initial token always has a response */ + ret = send_accept(minor_status, + ctx, + first_ok, + &mech_output_token, + advertised_mech, + get_mic ? &ctx->NegTokenInit_mech_types : NULL, + output_token); if (ret) goto out; out: + gss_release_oid_set(&junk, &supported_mechs); if (mech_output_token.value != NULL) gss_release_buffer(&junk, &mech_output_token); - if (mech_buf.value != NULL) { - free(mech_buf.value); - mech_buf.value = NULL; - } free_NegotiationToken(&nt); if (ret == GSS_S_COMPLETE) { - if (src_name != NULL && ctx->mech_src_name != NULL) { - spnego_name name; - - name = calloc(1, sizeof(*name)); - if (name) { - name->mech = ctx->mech_src_name; - ctx->mech_src_name = NULL; - *src_name = (gss_name_t)name; - } - } + if (src_name != NULL && ctx->mech_src_name != GSS_C_NO_NAME) + ret = gss_duplicate_name(minor_status, + ctx->mech_src_name, + src_name); } if (mech_type != NULL) @@ -700,18 +838,15 @@ acceptor_continue gss_cred_id_t *delegated_cred_handle ) { - OM_uint32 ret, ret2, minor; + OM_uint32 ret, ret2, minor, junk; NegotiationToken nt; size_t nt_len; NegTokenResp *na; - unsigned int negResult = accept_incomplete; + unsigned int negState = accept_incomplete; gss_buffer_t mech_input_token = GSS_C_NO_BUFFER; gss_buffer_t mech_output_token = GSS_C_NO_BUFFER; - gss_buffer_desc mech_buf; gssspnego_ctx ctx; - mech_buf.value = NULL; - ctx = (gssspnego_ctx)*context_handle; /* @@ -732,17 +867,16 @@ acceptor_continue } na = &nt.u.negTokenResp; - if (na->negResult != NULL) { - negResult = *(na->negResult); + if (na->negState != NULL) { + negState = *(na->negState); } HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); { gss_buffer_desc ibuf, obuf; - int require_mic, get_mic = 0; + int get_mic = 0; int require_response; - heim_octet_string *mic; if (na->responseToken != NULL) { ibuf.length = na->responseToken->length; @@ -755,60 +889,37 @@ acceptor_continue if (mech_input_token != GSS_C_NO_BUFFER) { - if (ctx->mech_src_name != GSS_C_NO_NAME) - gss_release_name(&minor, &ctx->mech_src_name); - - ret = gss_accept_sec_context(&minor, - &ctx->negotiated_ctx_id, - acceptor_cred_handle, - mech_input_token, - input_chan_bindings, - &ctx->mech_src_name, - &ctx->negotiated_mech_type, - &obuf, - &ctx->mech_flags, - &ctx->mech_time_rec, - delegated_cred_handle); - - if (ret == GSS_S_COMPLETE || ret == GSS_S_CONTINUE_NEEDED) { - mech_output_token = &obuf; - } + ret = mech_accept(minor_status, + ctx, + acceptor_cred_handle, + mech_input_token, + input_chan_bindings, + &obuf, + delegated_cred_handle); + mech_output_token = &obuf; if (ret != GSS_S_COMPLETE && ret != GSS_S_CONTINUE_NEEDED) { free_NegotiationToken(&nt); - gss_mg_collect_error(ctx->negotiated_mech_type, ret, minor); - send_reject (minor_status, output_token); + send_reject(&junk, mech_output_token, output_token); + gss_release_buffer(&junk, mech_output_token); HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return ret; } if (ret == GSS_S_COMPLETE) - ctx->open = 1; + ctx->flags.open = 1; } else ret = GSS_S_COMPLETE; - ret2 = _gss_spnego_require_mechlist_mic(minor_status, - ctx, - &require_mic); - if (ret2) - goto out; - - ctx->require_mic = require_mic; - - mic = na->mechListMIC; - if (mic != NULL) - require_mic = 1; - if (ret == GSS_S_COMPLETE) ret = acceptor_complete(minor_status, ctx, &get_mic, - &mech_buf, mech_input_token, mech_output_token, na->mechListMIC, output_token); if (ctx->mech_flags & GSS_C_DCE_STYLE) - require_response = (negResult != accept_completed); + require_response = (negState != accept_completed); else require_response = 0; @@ -818,40 +929,34 @@ acceptor_continue */ if ((mech_output_token != GSS_C_NO_BUFFER && mech_output_token->length != 0) - || (ctx->open && negResult == accept_incomplete) + || (ctx->flags.open && negState == accept_incomplete) || require_response || get_mic) { ret2 = send_accept (minor_status, ctx, + 0, /* ignored on subsequent tokens */ mech_output_token, - 0, - get_mic ? &mech_buf : NULL, + GSS_C_NO_OID, + get_mic ? &ctx->NegTokenInit_mech_types : NULL, output_token); if (ret2) goto out; - } + } else + ret2 = GSS_S_COMPLETE; out: if (ret2 != GSS_S_COMPLETE) ret = ret2; if (mech_output_token != NULL) gss_release_buffer(&minor, mech_output_token); - if (mech_buf.value != NULL) - free(mech_buf.value); free_NegotiationToken(&nt); } if (ret == GSS_S_COMPLETE) { - if (src_name != NULL && ctx->mech_src_name != NULL) { - spnego_name name; - - name = calloc(1, sizeof(*name)); - if (name) { - name->mech = ctx->mech_src_name; - ctx->mech_src_name = NULL; - *src_name = (gss_name_t)name; - } - } + if (src_name != NULL && ctx->mech_src_name != GSS_C_NO_NAME) + ret = gss_duplicate_name(minor_status, + ctx->mech_src_name, + src_name); } if (mech_type != NULL) diff --git a/lib/gssapi/spnego/compat.c b/lib/gssapi/spnego/compat.c index 6e90fe6faf86..6cfe5526631c 100644 --- a/lib/gssapi/spnego/compat.c +++ b/lib/gssapi/spnego/compat.c @@ -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); @@ -145,94 +152,241 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context return ret; } +static int +inq_context_by_oid_bool(gssspnego_ctx ctx, gss_OID oid) +{ + OM_uint32 major, minor; + gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET; + uint8_t ret = 0; + + major = gss_inquire_sec_context_by_oid(&minor, ctx->negotiated_ctx_id, + oid, &data_set); + if (major != GSS_S_COMPLETE) + return FALSE; + + if (data_set != GSS_C_NO_BUFFER_SET && + data_set->count == 1 && + data_set->elements[0].length == 1) + ret = *((uint8_t *)data_set->elements[0].value); + + gss_release_buffer_set(&minor, &data_set); + + return ret != 0; +} + /* - * 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. */ -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 = FALSE; + + if (ctx->flags.peer_require_mic) { + _gss_mg_log(10, "spnego: mechListMIC required by peer"); + } else if (inq_context_by_oid_bool(ctx, GSS_C_INQ_PEER_HAS_BUGGY_SPNEGO)) { + /* [MS-SPNG] Appendix A <7> Section 3.1.5.1: may be old peer with buggy SPNEGO */ + safe_omit = TRUE; + _gss_mg_log(10, "spnego: mechListMIC omitted for legacy interoperability"); + } else if (inq_context_by_oid_bool(ctx, GSS_C_INQ_REQUIRE_MECHLIST_MIC)) { + /* [MS-SPNG] Appendix A <7> Section 3.1.5.1: allow NTLM to force MIC */ + _gss_mg_log(10, "spnego: mechListMIC required by mechanism"); + } else if (gss_oid_equal(ctx->selected_mech_type, ctx->preferred_mech_type)) { + safe_omit = TRUE; + _gss_mg_log(10, "spnego: mechListMIC omitted as preferred mechanism selected"); + } else { + _gss_mg_log(10, "spnego: mechListMIC required by default"); + } - *minor_status = 0; - *require_mic = 0; + return safe_omit; +} - if (ctx == NULL) { - return GSS_S_COMPLETE; - } +/* + * A map between a GSS-API flag and a (mechanism attribute, weight) + * tuple. The list of mechanisms is re-ordered by aggregate weight + * (highest weight is more preferred, e.g. if GSS_C_MUTUAL_FLAG and + * GSS_C_ANON_FLAG are set, we prefer a mechanism that supports + * mutual authentication over one that only supports anonymous). + */ +static struct { + OM_uint32 flag; + gss_OID ma; + int weight; +} flag_to_ma_map[] = { + { GSS_C_MUTUAL_FLAG, GSS_C_MA_AUTH_TARG, 2 }, + { GSS_C_ANON_FLAG, GSS_C_MA_AUTH_INIT_ANON, 1 }, +}; - if (ctx->require_mic) { - /* Acceptor requested it: mandatory to honour */ - *require_mic = 1; - return GSS_S_COMPLETE; - } +/* + * Returns a bitmask indicating GSS flags we can sort on. + */ +static inline OM_uint32 +mech_flag_mask(void) +{ + size_t i; + OM_uint32 mask = 0; - /* - * 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); - } + for (i = 0; i < sizeof(flag_to_ma_map)/sizeof(flag_to_ma_map[0]); i++) + mask |= flag_to_ma_map[i].flag; - /* 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; + return mask; +} + +/* + * Returns an integer representing the preference weighting for a + * mechanism, based on the requested GSS flags. + */ +static int +mech_weight(gss_const_OID mech, OM_uint32 req_flags) +{ + OM_uint32 major, minor; + gss_OID_set mech_attrs = GSS_C_NO_OID_SET; + int weight = 0; + size_t i, j; + + major = gss_inquire_attrs_for_mech(&minor, mech, &mech_attrs, NULL); + if (GSS_ERROR(major)) + return 0; + + for (i = 0; i < sizeof(flag_to_ma_map)/sizeof(flag_to_ma_map[0]); i++) { + if ((req_flags & flag_to_ma_map[i].flag) == 0) + continue; + + for (j = 0; j < mech_attrs->count; j++) { + if (gss_oid_equal(flag_to_ma_map[i].ma, &mech_attrs->elements[j])) { + weight += flag_to_ma_map[i].weight; + continue; + } } } - return GSS_S_COMPLETE; + gss_release_oid_set(&minor, &mech_attrs); + + return weight; } static int -add_mech_type(gss_OID mech_type, - int includeMSCompatOID, +mech_compare(const void *mech1, const void *mech2, void *req_flags_p) +{ + OM_uint32 req_flags = *((OM_uint32 *)req_flags_p); + int mech1_weight = mech_weight(mech1, req_flags); + int mech2_weight = mech_weight(mech2, req_flags); + + return mech2_weight - mech1_weight; +} + +/* + * Order a list of mechanisms by weight based on requested GSS flags. + */ +static void +order_mechs_by_flags(gss_OID_set mechs, OM_uint32 req_flags) +{ + if (req_flags & mech_flag_mask()) { /* skip if flags irrelevant */ + /* + * NB: must be a stable sort to preserve the existing order + * of mechanisms that are equally weighted. + */ + mergesort_r(mechs->elements, mechs->count, + sizeof(gss_OID_desc), mech_compare, &req_flags); + } +} + +static OM_uint32 +add_mech_type(OM_uint32 *minor_status, + gss_OID mech_type, MechTypeList *mechtypelist) { MechType mech; int ret; - if (gss_oid_equal(mech_type, GSS_SPNEGO_MECHANISM)) - return 0; + heim_assert(!gss_oid_equal(mech_type, GSS_SPNEGO_MECHANISM), + "SPNEGO mechanism not filtered"); - 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 = der_get_oid(mech_type->elements, mech_type->length, &mech, NULL); + if (ret == 0) { ret = add_MechTypeList(mechtypelist, &mech); free_MechType(&mech); - if (ret) - return ret; } - 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 (ret) { + *minor_status = ret; + return GSS_S_FAILURE; + } + + return GSS_S_COMPLETE; } +static int +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) +{ + OM_uint32 major, minor; + + /* + * 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; + } + + 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_name_t, gss_OID), + gss_const_name_t target_name, + OM_uint32 req_flags, + 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,78 +394,291 @@ _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 added_negoex = FALSE, canonical_order = 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 (cred_handle != GSS_C_NO_CREDENTIAL) + ret = _gss_spnego_inquire_cred_mechs(minor_status, cred_handle, + &supported_mechs, &canonical_order); + else + ret = _gss_spnego_indicate_mechs(minor_status, &supported_mechs); + if (ret != GSS_S_COMPLETE) + return ret; + + if (!canonical_order) + order_mechs_by_flags(supported_mechs, req_flags); + + heim_assert(supported_mechs != GSS_C_NO_OID_SET, + "NULL mech set returned by SPNEGO inquire/indicate mechs"); + + /* + * Previously krb5 was tried explicitly, but now the internal mech + * list is reordered so that krb5 is first, this should no longer + * be required. This permits an application to specify another + * mechanism as preferred over krb5 using gss_set_neg_mechs(). + */ + for (i = 0; i < supported_mechs->count; i++) { + ret = add_mech_if_approved(minor_status, target_name, + func, userptr, includeMSCompatOID, + 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 (ret != GSS_S_COMPLETE) { - return ret; + heim_assert(mechtypelist->len == 0 || first_mech != GSS_C_NO_OID, + "mechtypelist non-empty but no mech selected"); + + 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 (supported_mechs->count == 0) { - *minor_status = ENOENT; - gss_release_oid_set(minor_status, &supported_mechs); - return GSS_S_FAILURE; + if (ctx->flags.verified_mic) { + /* This doesn't make sense, we've already verified it? */ + *minor_status = 0; + return GSS_S_DUPLICATE_TOKEN; } - ret = (*func)(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; + 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"); } - ret = GSS_S_COMPLETE; + ctx->flags.verified_mic = 1; - 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; + *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) +{ + if (gss_oid_equal(ctx->negotiated_mech_type, GSS_NTLM_MECHANISM)) { + gss_buffer_desc value; + + 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_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; - subret = (*func)(target_name, &supported_mechs->elements[i]); - if (subret != GSS_S_COMPLETE) + 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; - ret = add_mech_type(&supported_mechs->elements[i], - includeMSCompatOID, - mechtypelist); - if (ret != 0) { - *minor_status = ret; - ret = GSS_S_FAILURE; - break; - } - if (first_mech == GSS_C_NO_OID) - first_mech = &supported_mechs->elements[i]; + oid.length = (OM_uint32)mech_len; + oid.elements = mechbuf + sizeof(mechbuf) - mech_len; + + _gss_spnego_log_mech("initiator proposed mech", &oid); } +} - if (mechtypelist->len == 0) { - gss_release_oid_set(minor_status, &supported_mechs); - *minor_status = 0; - return GSS_S_BAD_MECH; +/* + * 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, + int *canonical_order) +{ + OM_uint32 ret, junk; + gss_OID_set cred_mechs = GSS_C_NO_OID_SET; + gss_OID_set negotiable_mechs = GSS_C_NO_OID_SET; + size_t i; + + *mechs_p = GSS_C_NO_OID_SET; + *canonical_order = FALSE; + + heim_assert(cred != GSS_C_NO_CREDENTIAL, "Invalid null credential handle"); + + ret = gss_get_neg_mechs(minor_status, cred, &cred_mechs); + if (ret == GSS_S_COMPLETE) { + *canonical_order = TRUE; + } else { + ret = gss_inquire_cred(minor_status, cred, NULL, NULL, NULL, &cred_mechs); + if (ret != GSS_S_COMPLETE) + goto out; } - if (preferred_mech != NULL) { - ret = gss_duplicate_oid(minor_status, first_mech, preferred_mech); + heim_assert(cred_mechs != GSS_C_NO_OID_SET && cred_mechs->count > 0, + "gss_inquire_cred succeeded but returned no mechanisms"); + + ret = _gss_spnego_indicate_mechs(minor_status, &negotiable_mechs); + if (ret != GSS_S_COMPLETE) + goto out; + + heim_assert(negotiable_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; + + /* Filter credential mechs by negotiable mechs, order by credential mechs */ + 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, negotiable_mechs, &present); + if (!present) + continue; + + ret = gss_add_oid_set_member(minor_status, cred_mech, mechs_p); if (ret != GSS_S_COMPLETE) - free_MechTypeList(mechtypelist); + break; } - gss_release_oid_set(minor_status, &supported_mechs); + +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, &negotiable_mechs); return ret; } + diff --git a/lib/gssapi/spnego/context_storage.c b/lib/gssapi/spnego/context_storage.c new file mode 100644 index 000000000000..3924bd383953 --- /dev/null +++ b/lib/gssapi/spnego/context_storage.c @@ -0,0 +1,498 @@ +/* + * Copyright (C) 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" + +#define SC_MECH_TYPES 0x0001 +#define SC_PREFERRED_MECH_TYPE 0x0002 +#define SC_SELECTED_MECH_TYPE 0x0004 +#define SC_NEGOTIATED_MECH_TYPE 0x0008 +#define SC_NEGOTIATED_CTX_ID 0x0010 +#define SC_MECH_FLAGS 0x0020 +#define SC_MECH_TIME_REC 0x0040 +#define SC_MECH_SRC_NAME 0x0080 +#define SC_TARGET_NAME 0x0100 +#define SC_NEGOEX 0x0200 + +#define SNC_OID 0x01 +#define SNC_MECH_CONTEXT 0x02 +#define SNC_METADATA 0x04 + +static krb5_error_code +ret_spnego_context(krb5_storage *sp, gssspnego_ctx *ctxp); +static krb5_error_code +store_spnego_context(krb5_storage *sp, gssspnego_ctx ctx); + +static krb5_error_code +ret_negoex_auth_mech(krb5_storage *sp, struct negoex_auth_mech **mechp); +static krb5_error_code +store_negoex_auth_mech(krb5_storage *sp, struct negoex_auth_mech *mech); + +#ifdef sc_flags +#undef sc_flags +#endif + +static uint16_t +spnego_flags_to_int(struct spnego_flags flags); +static struct spnego_flags +int_to_spnego_flags(uint16_t f); + +OM_uint32 GSSAPI_CALLCONV +_gss_spnego_import_sec_context_internal(OM_uint32 *minor, + gss_const_buffer_t buffer, + gssspnego_ctx *ctxp) +{ + krb5_error_code ret; + krb5_storage *sp; + + sp = krb5_storage_from_readonly_mem(buffer->value, buffer->length); + if (sp == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_PACKED); + + ret = ret_spnego_context(sp, ctxp); + + krb5_storage_free(sp); + + *minor = ret; + return ret ? GSS_S_FAILURE : GSS_S_COMPLETE; +} + +OM_uint32 GSSAPI_CALLCONV +_gss_spnego_export_sec_context_internal(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_buffer_t buffer) +{ + krb5_error_code ret; + krb5_storage *sp; + krb5_data data; + + sp = krb5_storage_emem(); + if (sp == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + krb5_data_zero(&data); + + krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_PACKED); + + ret = store_spnego_context(sp, ctx); + if (ret == 0) + ret = krb5_storage_to_data(sp, &data); + if (ret == 0) { + buffer->length = data.length; + buffer->value = data.data; + } + + krb5_storage_free(sp); + + *minor = ret; + return ret ? GSS_S_FAILURE : GSS_S_COMPLETE; +} + +static krb5_error_code +ret_spnego_context(krb5_storage *sp, gssspnego_ctx *ctxp) +{ + OM_uint32 major = GSS_S_COMPLETE, minor; + gssspnego_ctx ctx = NULL; + krb5_error_code ret = 0; + krb5_data data; + gss_buffer_desc buf = GSS_C_EMPTY_BUFFER; + uint16_t sc_flags, spnego_flags; + + *ctxp = NULL; + krb5_data_zero(&data); + + CHECK(major, _gss_spnego_alloc_sec_context(&minor, (gss_ctx_id_t *)&ctx)); + + CHECK(ret, krb5_ret_uint16(sp, &sc_flags)); + CHECK(ret, krb5_ret_uint16(sp, &spnego_flags)); + ctx->flags = int_to_spnego_flags(spnego_flags); + + if (sc_flags & SC_MECH_TYPES) + CHECK(major, _gss_mg_ret_buffer(&minor, sp, &ctx->NegTokenInit_mech_types)); + if (sc_flags & SC_PREFERRED_MECH_TYPE) + CHECK(major, _gss_mg_ret_oid(&minor, sp, &ctx->preferred_mech_type)); + if (sc_flags & SC_SELECTED_MECH_TYPE) + CHECK(major, _gss_mg_ret_oid(&minor, sp, &ctx->selected_mech_type)); + if (sc_flags & SC_NEGOTIATED_MECH_TYPE) + CHECK(major, _gss_mg_ret_oid(&minor, sp, &ctx->negotiated_mech_type)); + + if (sc_flags & SC_NEGOTIATED_CTX_ID) { + CHECK(major, _gss_mg_ret_buffer(&minor, sp, &buf)); + CHECK(major, gss_import_sec_context(&minor, &buf, + &ctx->negotiated_ctx_id)); + gss_release_buffer(&minor, &buf); + } + + if (sc_flags & SC_MECH_FLAGS) + CHECK(ret, krb5_ret_uint32(sp, &ctx->mech_flags)); + if (sc_flags & SC_MECH_TIME_REC) + CHECK(ret, krb5_ret_uint32(sp, &ctx->mech_time_rec)); + else + ctx->mech_time_rec = GSS_C_INDEFINITE; + + if (sc_flags & SC_MECH_SRC_NAME) { + CHECK(major, _gss_mg_ret_buffer(&minor, sp, &buf)); + CHECK(major, gss_import_name(&minor, &buf, GSS_C_NT_EXPORT_NAME, + &ctx->mech_src_name)); + gss_release_buffer(&minor, &buf); + } + + if (sc_flags & SC_TARGET_NAME) { + CHECK(major, _gss_mg_ret_buffer(&minor, sp, &buf)); + CHECK(major, gss_import_name(&minor, &buf, GSS_C_NT_EXPORT_NAME, + &ctx->target_name)); + gss_release_buffer(&minor, &buf); + } + + if (sc_flags & SC_NEGOEX) { + uint8_t i, nschemes; + + CHECK(ret, krb5_ret_uint8(sp, &ctx->negoex_step)); + + CHECK(ret, krb5_ret_data(sp, &data)); + ctx->negoex_transcript = krb5_storage_emem(); + if (ctx->negoex_transcript == NULL) { + ret = ENOMEM; + goto fail; + } + + krb5_storage_set_byteorder(ctx->negoex_transcript, + KRB5_STORAGE_BYTEORDER_LE); + if (krb5_storage_write(ctx->negoex_transcript, + data.data, data.length) != data.length) { + ret = ENOMEM; + goto fail; + } + krb5_data_free(&data); + + CHECK(ret, krb5_ret_uint32(sp, &ctx->negoex_seqnum)); + + if (krb5_storage_read(sp, ctx->negoex_conv_id, + GUID_LENGTH) != GUID_LENGTH) { + ret = KRB5_BAD_MSIZE; + goto fail; + } + + CHECK(ret, krb5_ret_uint8(sp, &nschemes)); + for (i = 0; i < nschemes; i++) { + struct negoex_auth_mech *mech; + + CHECK(ret, ret_negoex_auth_mech(sp, &mech)); + /* `mech' will not be NULL here, but quiet scan-build */ + if (mech) + HEIM_TAILQ_INSERT_TAIL(&ctx->negoex_mechs, mech, links); + } + } + + *ctxp = ctx; + +fail: + if (ret == 0 && GSS_ERROR(major)) + ret = minor ? minor : KRB5_BAD_MSIZE; + if (ret) + _gss_spnego_delete_sec_context(&minor, (gss_ctx_id_t *)&ctx, + GSS_C_NO_BUFFER); + krb5_data_free(&data); + gss_release_buffer(&minor, &buf); + + return ret; +} + +static krb5_error_code +store_spnego_context(krb5_storage *sp, gssspnego_ctx ctx) +{ + OM_uint32 major = GSS_S_COMPLETE, minor; + krb5_error_code ret = 0; + krb5_data data; + gss_buffer_desc buf = GSS_C_EMPTY_BUFFER; + uint16_t sc_flags = 0, spnego_flags; + + krb5_data_zero(&data); + + if (ctx->NegTokenInit_mech_types.length) + sc_flags |= SC_MECH_TYPES; + if (ctx->preferred_mech_type) + sc_flags |= SC_PREFERRED_MECH_TYPE; + if (ctx->selected_mech_type) + sc_flags |= SC_SELECTED_MECH_TYPE; + if (ctx->negotiated_mech_type) + sc_flags |= SC_NEGOTIATED_MECH_TYPE; + if (ctx->negotiated_ctx_id) + sc_flags |= SC_NEGOTIATED_CTX_ID; + if (ctx->mech_flags) + sc_flags |= SC_MECH_FLAGS; + if (ctx->mech_time_rec != GSS_C_INDEFINITE) + sc_flags |= SC_MECH_TIME_REC; + if (ctx->mech_src_name) + sc_flags |= SC_MECH_SRC_NAME; + if (ctx->target_name) + sc_flags |= SC_TARGET_NAME; + if (ctx->negoex_step) + sc_flags |= SC_NEGOEX; + + CHECK(ret, krb5_store_uint16(sp, sc_flags)); + spnego_flags = spnego_flags_to_int(ctx->flags); + CHECK(ret, krb5_store_uint16(sp, spnego_flags)); + + if (sc_flags & SC_MECH_TYPES) + CHECK(major, _gss_mg_store_buffer(&minor, sp, &ctx->NegTokenInit_mech_types)); + if (sc_flags & SC_PREFERRED_MECH_TYPE) + CHECK(major, _gss_mg_store_oid(&minor, sp, ctx->preferred_mech_type)); + if (sc_flags & SC_SELECTED_MECH_TYPE) + CHECK(major, _gss_mg_store_oid(&minor, sp, ctx->selected_mech_type)); + if (sc_flags & SC_NEGOTIATED_MECH_TYPE) + CHECK(major, _gss_mg_store_oid(&minor, sp, ctx->negotiated_mech_type)); + if (sc_flags & SC_NEGOTIATED_CTX_ID) { + CHECK(major, gss_export_sec_context(&minor, &ctx->negotiated_ctx_id, + &buf)); + CHECK(major, _gss_mg_store_buffer(&minor, sp, &buf)); + gss_release_buffer(&minor, &buf); + } + if (sc_flags & SC_MECH_FLAGS) + CHECK(ret, krb5_store_uint32(sp, ctx->mech_flags)); + if (sc_flags & SC_MECH_TIME_REC) + CHECK(ret, krb5_store_uint32(sp, ctx->mech_time_rec)); + if (sc_flags & SC_MECH_SRC_NAME) { + CHECK(major, gss_export_name(&minor, ctx->mech_src_name, &buf)); + CHECK(major, _gss_mg_store_buffer(&minor, sp, &buf)); + gss_release_buffer(&minor, &buf); + } + + if (sc_flags & SC_TARGET_NAME) { + CHECK(major, gss_export_name(&minor, ctx->target_name, &buf)); + CHECK(major, _gss_mg_store_buffer(&minor, sp, &buf)); + gss_release_buffer(&minor, &buf); + } + + if (sc_flags & SC_NEGOEX) { + uint32_t nschemes; + struct negoex_auth_mech *mech; + + CHECK(ret, krb5_store_uint8(sp, ctx->negoex_step)); + + if (ctx->negoex_transcript) { + CHECK(ret, krb5_storage_to_data(ctx->negoex_transcript, &data)); + } + CHECK(ret, krb5_store_data(sp, data)); + krb5_data_free(&data); + + CHECK(ret, krb5_store_uint32(sp, ctx->negoex_seqnum)); + CHECK(ret, krb5_store_bytes(sp, ctx->negoex_conv_id, GUID_LENGTH)); + + nschemes = 0; + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) + nschemes++; + + if (nschemes > 0xff) { + ret = ERANGE; + goto fail; + } + CHECK(ret, krb5_store_uint8(sp, nschemes)); + + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) + CHECK(ret, store_negoex_auth_mech(sp, mech)); + } + +fail: + if (ret == 0 && GSS_ERROR(major)) + ret = minor ? minor : KRB5_BAD_MSIZE; + krb5_data_free(&data); + gss_release_buffer(&minor, &buf); + + return ret; +} + +static krb5_error_code +ret_negoex_auth_mech(krb5_storage *sp, struct negoex_auth_mech **mechp) +{ + krb5_error_code ret; + OM_uint32 major = GSS_S_COMPLETE, minor; + gss_buffer_desc buf = GSS_C_EMPTY_BUFFER; + struct negoex_auth_mech *mech; + krb5_context context = _gss_mg_krb5_context(); + uint8_t snc_flags, negoex_flags; + + *mechp = NULL; + + mech = calloc(1, sizeof(*mech)); + if (mech == NULL) { + ret = ENOMEM; + goto fail; + } + + CHECK(ret, krb5_ret_uint8(sp, &snc_flags)); + CHECK(ret, krb5_ret_uint8(sp, &negoex_flags)); + if (negoex_flags & (1 << 0)) + mech->complete = 1; + if (negoex_flags & (1 << 1)) + mech->sent_checksum = 1; + if (negoex_flags & (1 << 2)) + mech->verified_checksum = 1; + + if (snc_flags & SNC_OID) + CHECK(major, _gss_mg_ret_oid(&minor, sp, &mech->oid)); + + if (krb5_storage_read(sp, mech->scheme, GUID_LENGTH) != GUID_LENGTH) { + ret = KRB5_BAD_MSIZE; + goto fail; + } + + if (snc_flags & SNC_MECH_CONTEXT) { + CHECK(major, _gss_mg_ret_buffer(&minor, sp, &buf)); + CHECK(major, gss_import_sec_context(&minor, &buf, + &mech->mech_context)); + gss_release_buffer(&minor, &buf); + } + + if (snc_flags & SNC_METADATA) + CHECK(major, _gss_mg_ret_buffer(&minor, sp, &mech->metadata)); + +fail: + if (ret == 0 && GSS_ERROR(major)) + ret = minor ? minor : KRB5_BAD_MSIZE; + if (ret) + _gss_negoex_release_auth_mech(context, mech); + else + *mechp = mech; + + gss_release_buffer(&minor, &buf); + return ret; +} + +static krb5_error_code +store_negoex_auth_mech(krb5_storage *sp, struct negoex_auth_mech *mech) +{ + krb5_error_code ret; + OM_uint32 major = GSS_S_COMPLETE, minor; + gss_buffer_desc buf = GSS_C_EMPTY_BUFFER; + uint8_t negoex_flags = 0, snc_flags = 0; + + negoex_flags = 0; + if (mech->complete) + negoex_flags |= (1 << 0); + if (mech->sent_checksum) + negoex_flags |= (1 << 1); + if (mech->verified_checksum) + negoex_flags |= (1 << 2); + + if (mech->oid) + snc_flags |= SNC_OID; + if (mech->mech_context) + snc_flags |= SNC_MECH_CONTEXT; + if (mech->metadata.length) + snc_flags |= SNC_METADATA; + + CHECK(ret, krb5_store_uint8(sp, snc_flags)); + CHECK(ret, krb5_store_uint8(sp, negoex_flags)); + + if (snc_flags & SNC_OID) + CHECK(major, _gss_mg_store_oid(&minor, sp, mech->oid)); + + CHECK(ret, krb5_store_bytes(sp, mech->scheme, GUID_LENGTH)); + + if (snc_flags & SNC_MECH_CONTEXT) { + CHECK(major, gss_export_sec_context(&minor, &mech->mech_context, + &buf)); + CHECK(major, _gss_mg_store_buffer(&minor, sp, &buf)); + gss_release_buffer(&minor, &buf); + } + + if (snc_flags & SNC_METADATA) + CHECK(major, _gss_mg_store_buffer(&minor, sp, &mech->metadata)); + +fail: + if (ret == 0 && GSS_ERROR(major)) + ret = minor ? minor : KRB5_BAD_MSIZE; + gss_release_buffer(&minor, &buf); + + return ret; +} + +static uint16_t +spnego_flags_to_int(struct spnego_flags flags) +{ + uint16_t f = 0; + + if (flags.open) + f |= (1 << 0); + if (flags.local) + f |= (1 << 1); + if (flags.require_mic) + f |= (1 << 2); + if (flags.peer_require_mic) + f |= (1 << 3); + if (flags.sent_mic) + f |= (1 << 4); + if (flags.verified_mic) + f |= (1 << 5); + if (flags.safe_omit) + f |= (1 << 6); + if (flags.maybe_open) + f |= (1 << 7); + if (flags.seen_supported_mech) + f |= (1 << 8); + + return f; +} + +static struct spnego_flags +int_to_spnego_flags(uint16_t f) +{ + struct spnego_flags flags; + + memset(&flags, 0, sizeof(flags)); + + if (f & (1 << 0)) + flags.open = 1; + if (f & (1 << 1)) + flags.local = 1; + if (f & (1 << 2)) + flags.require_mic = 1; + if (f & (1 << 3)) + flags.peer_require_mic = 1; + if (f & (1 << 4)) + flags.sent_mic = 1; + if (f & (1 << 5)) + flags.verified_mic = 1; + if (f & (1 << 6)) + flags.safe_omit = 1; + if (f & (1 << 7)) + flags.maybe_open = 1; + if (f & (1 << 8)) + flags.seen_supported_mech = 1; + + return flags; +} diff --git a/lib/gssapi/spnego/context_stubs.c b/lib/gssapi/spnego/context_stubs.c index 836ce5859e67..638e90d7ba3a 100644 --- a/lib/gssapi/spnego/context_stubs.c +++ b/lib/gssapi/spnego/context_stubs.c @@ -32,40 +32,6 @@ #include "spnego_locl.h" -static OM_uint32 -spnego_supported_mechs(OM_uint32 *minor_status, gss_OID_set *mechs) -{ - OM_uint32 ret, junk; - gss_OID_set m; - size_t i; - - ret = gss_indicate_mechs(minor_status, &m); - if (ret != GSS_S_COMPLETE) - return ret; - - ret = gss_create_empty_oid_set(minor_status, mechs); - if (ret != GSS_S_COMPLETE) { - gss_release_oid_set(&junk, &m); - return ret; - } - - for (i = 0; i < m->count; i++) { - if (gss_oid_equal(&m->elements[i], GSS_SPNEGO_MECHANISM)) - continue; - - ret = gss_add_oid_set_member(minor_status, &m->elements[i], mechs); - if (ret) { - gss_release_oid_set(&junk, &m); - gss_release_oid_set(&junk, mechs); - return ret; - } - } - gss_release_oid_set(&junk, &m); - return ret; -} - - - OM_uint32 GSSAPI_CALLCONV _gss_spnego_process_context_token (OM_uint32 *minor_status, gss_const_ctx_id_t context_handle, @@ -262,124 +228,6 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_unwrap qop_state); } -OM_uint32 GSSAPI_CALLCONV _gss_spnego_compare_name - (OM_uint32 *minor_status, - gss_const_name_t name1, - gss_const_name_t name2, - int * name_equal - ) -{ - spnego_name n1 = (spnego_name)name1; - spnego_name n2 = (spnego_name)name2; - - *name_equal = 0; - - if (!gss_oid_equal(&n1->type, &n2->type)) - return GSS_S_COMPLETE; - if (n1->value.length != n2->value.length) - return GSS_S_COMPLETE; - if (memcmp(n1->value.value, n2->value.value, n2->value.length) != 0) - return GSS_S_COMPLETE; - - *name_equal = 1; - - return GSS_S_COMPLETE; -} - -OM_uint32 GSSAPI_CALLCONV _gss_spnego_display_name - (OM_uint32 * minor_status, - gss_const_name_t input_name, - gss_buffer_t output_name_buffer, - gss_OID * output_name_type - ) -{ - spnego_name name = (spnego_name)input_name; - - *minor_status = 0; - - if (name == NULL || name->mech == GSS_C_NO_NAME) - return GSS_S_FAILURE; - - return gss_display_name(minor_status, name->mech, - output_name_buffer, output_name_type); -} - -OM_uint32 GSSAPI_CALLCONV _gss_spnego_import_name - (OM_uint32 * minor_status, - const gss_buffer_t name_buffer, - const gss_OID name_type, - gss_name_t * output_name - ) -{ - spnego_name name; - OM_uint32 maj_stat; - - *minor_status = 0; - - name = calloc(1, sizeof(*name)); - if (name == NULL) { - *minor_status = ENOMEM; - return GSS_S_FAILURE; - } - - maj_stat = _gss_copy_oid(minor_status, name_type, &name->type); - if (maj_stat) { - free(name); - return GSS_S_FAILURE; - } - - maj_stat = _gss_copy_buffer(minor_status, name_buffer, &name->value); - if (maj_stat) { - gss_name_t rname = (gss_name_t)name; - _gss_spnego_release_name(minor_status, &rname); - return GSS_S_FAILURE; - } - name->mech = GSS_C_NO_NAME; - *output_name = (gss_name_t)name; - - return GSS_S_COMPLETE; -} - -OM_uint32 GSSAPI_CALLCONV _gss_spnego_export_name - (OM_uint32 * minor_status, - gss_const_name_t input_name, - gss_buffer_t exported_name - ) -{ - spnego_name name; - *minor_status = 0; - - if (input_name == GSS_C_NO_NAME) - return GSS_S_BAD_NAME; - - name = (spnego_name)input_name; - if (name->mech == GSS_C_NO_NAME) - return GSS_S_BAD_NAME; - - return gss_export_name(minor_status, name->mech, exported_name); -} - -OM_uint32 GSSAPI_CALLCONV _gss_spnego_release_name - (OM_uint32 * minor_status, - gss_name_t * input_name - ) -{ - *minor_status = 0; - - if (*input_name != GSS_C_NO_NAME) { - OM_uint32 junk; - spnego_name name = (spnego_name)*input_name; - _gss_free_oid(&junk, &name->type); - gss_release_buffer(&junk, &name->value); - if (name->mech != GSS_C_NO_NAME) - gss_release_name(&junk, &name->mech); - free(name); - - *input_name = GSS_C_NO_NAME; - } - return GSS_S_COMPLETE; -} - OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_context ( OM_uint32 * minor_status, gss_const_ctx_id_t context_handle, @@ -393,8 +241,7 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_context ( ) { gssspnego_ctx ctx; - OM_uint32 maj_stat, junk; - gss_name_t src_mn, targ_mn; + OM_uint32 maj_stat; *minor_status = 0; @@ -408,43 +255,18 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_context ( maj_stat = gss_inquire_context(minor_status, ctx->negotiated_ctx_id, - &src_mn, - &targ_mn, + src_name, + targ_name, lifetime_rec, mech_type, ctx_flags, locally_initiated, open_context); - if (maj_stat != GSS_S_COMPLETE) - return maj_stat; - - if (src_name) { - spnego_name name = calloc(1, sizeof(*name)); - if (name == NULL) - goto enomem; - name->mech = src_mn; - *src_name = (gss_name_t)name; - } else - gss_release_name(&junk, &src_mn); - - if (targ_name) { - spnego_name name = calloc(1, sizeof(*name)); - if (name == NULL) { - gss_release_name(minor_status, src_name); - goto enomem; - } - name->mech = targ_mn; - *targ_name = (gss_name_t)name; - } else - gss_release_name(&junk, &targ_mn); - - return GSS_S_COMPLETE; - -enomem: - gss_release_name(&junk, &targ_mn); - gss_release_name(&junk, &src_mn); - *minor_status = ENOMEM; - return GSS_S_FAILURE; + + if (open_context) + *open_context = gssspnego_ctx_complete_p(ctx); + + return maj_stat; } OM_uint32 GSSAPI_CALLCONV _gss_spnego_wrap_size_limit ( @@ -485,13 +307,12 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_export_sec_context ( ) { gssspnego_ctx ctx; - OM_uint32 ret; + OM_uint32 major_status; *minor_status = 0; - if (context_handle == NULL) { + if (context_handle == NULL) return GSS_S_NO_CONTEXT; - } ctx = (gssspnego_ctx)*context_handle; @@ -500,25 +321,30 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_export_sec_context ( HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); - if (ctx->negotiated_ctx_id == GSS_C_NO_CONTEXT) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - return GSS_S_NO_CONTEXT; + /* + * Partial context export is only supported on the acceptor side, as we + * cannot represent the initiator function pointer state in an exported + * token, and also because it is mostly useful for acceptors which need + * to manage multiple initiator states. + */ + if (ctx->flags.local && !gssspnego_ctx_complete_p(ctx)) { + major_status = GSS_S_NO_CONTEXT; + goto out; } - ret = gss_export_sec_context(minor_status, - &ctx->negotiated_ctx_id, - interprocess_token); - if (ret == GSS_S_COMPLETE) { - ret = _gss_spnego_internal_delete_sec_context(minor_status, - context_handle, - GSS_C_NO_BUFFER); - if (ret == GSS_S_COMPLETE) - return GSS_S_COMPLETE; - } + major_status = _gss_spnego_export_sec_context_internal(minor_status, + ctx, + interprocess_token); - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); +out: + if (major_status == GSS_S_COMPLETE) + major_status = _gss_spnego_internal_delete_sec_context(minor_status, + context_handle, + GSS_C_NO_BUFFER); + else + HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - return ret; + return major_status; } OM_uint32 GSSAPI_CALLCONV _gss_spnego_import_sec_context ( @@ -527,35 +353,9 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_import_sec_context ( gss_ctx_id_t *context_handle ) { - OM_uint32 ret, minor; - gss_ctx_id_t context; - gssspnego_ctx ctx; - - *context_handle = GSS_C_NO_CONTEXT; - ret = _gss_spnego_alloc_sec_context(minor_status, &context); - if (ret != GSS_S_COMPLETE) { - return ret; - } - ctx = (gssspnego_ctx)context; - - HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); - - ret = gss_import_sec_context(minor_status, - interprocess_token, - &ctx->negotiated_ctx_id); - if (ret != GSS_S_COMPLETE) { - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); - return ret; - } - - ctx->open = 1; - /* don't bother filling in the rest of the fields */ - - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - - *context_handle = (gss_ctx_id_t)ctx; - - return GSS_S_COMPLETE; + return _gss_spnego_import_sec_context_internal(minor_status, + interprocess_token, + (gssspnego_ctx *)context_handle); } OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_names_for_mech ( @@ -570,7 +370,7 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_names_for_mech ( *name_types = NULL; - ret = spnego_supported_mechs(minor_status, &mechs); + ret = _gss_spnego_indicate_mechs(minor_status, &mechs); if (ret != GSS_S_COMPLETE) return ret; @@ -601,47 +401,6 @@ out: return ret; } -OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_mechs_for_name ( - OM_uint32 * minor_status, - gss_const_name_t input_name, - gss_OID_set * mech_types - ) -{ - OM_uint32 ret, junk; - - ret = gss_create_empty_oid_set(minor_status, mech_types); - if (ret) - return ret; - - ret = gss_add_oid_set_member(minor_status, - GSS_SPNEGO_MECHANISM, - mech_types); - if (ret) - gss_release_oid_set(&junk, mech_types); - - return ret; -} - -OM_uint32 GSSAPI_CALLCONV _gss_spnego_canonicalize_name ( - OM_uint32 * minor_status, - gss_const_name_t input_name, - const gss_OID mech_type, - gss_name_t * output_name - ) -{ - /* XXX */ - return gss_duplicate_name(minor_status, input_name, output_name); -} - -OM_uint32 GSSAPI_CALLCONV _gss_spnego_duplicate_name ( - OM_uint32 * minor_status, - gss_const_name_t src_name, - gss_name_t * dest_name - ) -{ - return gss_duplicate_name(minor_status, src_name, dest_name); -} - OM_uint32 GSSAPI_CALLCONV _gss_spnego_wrap_iov(OM_uint32 * minor_status, gss_ctx_id_t context_handle, @@ -767,9 +526,15 @@ OM_uint32 GSSAPI_CALLCONV _gss_spnego_set_sec_context_option *minor_status = 0; - if (context_handle == NULL || *context_handle == GSS_C_NO_CONTEXT) { - return GSS_S_NO_CONTEXT; - } + /* + * Return GSS_S_UNAVAILABLE with a NULL context handle as at + * present no context options can be set globally on SPNEGO + * itself. Global mechanism context options are set directly + * on the mechanism; per-context context options are set below + * if ctx->negotiated_ctx_id != GSS_C_NO_CONTEXT. + */ + if (context_handle == NULL || *context_handle == GSS_C_NO_CONTEXT) + return GSS_S_UNAVAILABLE; ctx = (gssspnego_ctx)*context_handle; diff --git a/lib/gssapi/spnego/cred_stubs.c b/lib/gssapi/spnego/cred_stubs.c deleted file mode 100644 index f82c4d1e80cf..000000000000 --- a/lib/gssapi/spnego/cred_stubs.c +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright (c) 2004, 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: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. 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. - * - * 3. Neither the name of PADL Software nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE 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 PADL SOFTWARE 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" - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_release_cred(OM_uint32 *minor_status, gss_cred_id_t *cred_handle) -{ - OM_uint32 ret; - - *minor_status = 0; - - if (cred_handle == NULL || *cred_handle == GSS_C_NO_CREDENTIAL) - return GSS_S_COMPLETE; - - ret = gss_release_cred(minor_status, cred_handle); - - *cred_handle = GSS_C_NO_CREDENTIAL; - - return ret; -} - -/* - * For now, just a simple wrapper that avoids recursion. When - * we support gss_{get,set}_neg_mechs() we will need to expose - * more functionality. - */ -OM_uint32 GSSAPI_CALLCONV _gss_spnego_acquire_cred -(OM_uint32 *minor_status, - gss_const_name_t desired_name, - OM_uint32 time_req, - const gss_OID_set desired_mechs, - gss_cred_usage_t cred_usage, - gss_cred_id_t * output_cred_handle, - gss_OID_set * actual_mechs, - OM_uint32 * time_rec - ) -{ - const spnego_name dname = (const spnego_name)desired_name; - gss_name_t name = GSS_C_NO_NAME; - OM_uint32 ret, tmp; - gss_OID_set_desc actual_desired_mechs; - gss_OID_set mechs; - size_t i, j; - - *output_cred_handle = GSS_C_NO_CREDENTIAL; - - if (dname) { - ret = gss_import_name(minor_status, &dname->value, &dname->type, &name); - if (ret) { - return ret; - } - } - - ret = gss_indicate_mechs(minor_status, &mechs); - if (ret != GSS_S_COMPLETE) { - gss_release_name(minor_status, &name); - return ret; - } - - /* Remove ourselves from this list */ - actual_desired_mechs.count = mechs->count; - actual_desired_mechs.elements = malloc(actual_desired_mechs.count * - sizeof(gss_OID_desc)); - if (actual_desired_mechs.elements == NULL) { - *minor_status = ENOMEM; - ret = GSS_S_FAILURE; - goto out; - } - - for (i = 0, j = 0; i < mechs->count; i++) { - if (gss_oid_equal(&mechs->elements[i], GSS_SPNEGO_MECHANISM)) - continue; - - actual_desired_mechs.elements[j] = mechs->elements[i]; - j++; - } - actual_desired_mechs.count = j; - - ret = gss_acquire_cred(minor_status, name, - time_req, &actual_desired_mechs, - cred_usage, - output_cred_handle, - actual_mechs, time_rec); - if (ret != GSS_S_COMPLETE) - goto out; - -out: - gss_release_name(minor_status, &name); - gss_release_oid_set(&tmp, &mechs); - if (actual_desired_mechs.elements != NULL) { - free(actual_desired_mechs.elements); - } - if (ret != GSS_S_COMPLETE) { - _gss_spnego_release_cred(&tmp, output_cred_handle); - } - - return ret; -} - -OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_cred - (OM_uint32 * minor_status, - gss_const_cred_id_t cred_handle, - gss_name_t * name, - OM_uint32 * lifetime, - gss_cred_usage_t * cred_usage, - gss_OID_set * mechanisms - ) -{ - spnego_name sname = NULL; - OM_uint32 ret; - - if (cred_handle == GSS_C_NO_CREDENTIAL) { - *minor_status = 0; - return GSS_S_NO_CRED; - } - - if (name) { - sname = calloc(1, sizeof(*sname)); - if (sname == NULL) { - *minor_status = ENOMEM; - return GSS_S_FAILURE; - } - } - - ret = gss_inquire_cred(minor_status, - cred_handle, - sname ? &sname->mech : NULL, - lifetime, - cred_usage, - mechanisms); - if (ret) { - if (sname) - free(sname); - return ret; - } - if (name) - *name = (gss_name_t)sname; - - return ret; -} - -OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_cred_by_mech ( - OM_uint32 * minor_status, - gss_const_cred_id_t cred_handle, - const gss_OID mech_type, - gss_name_t * name, - OM_uint32 * initiator_lifetime, - OM_uint32 * acceptor_lifetime, - gss_cred_usage_t * cred_usage - ) -{ - spnego_name sname = NULL; - OM_uint32 ret; - - if (cred_handle == GSS_C_NO_CREDENTIAL) { - *minor_status = 0; - return GSS_S_NO_CRED; - } - - if (name) { - sname = calloc(1, sizeof(*sname)); - if (sname == NULL) { - *minor_status = ENOMEM; - return GSS_S_FAILURE; - } - } - - ret = gss_inquire_cred_by_mech(minor_status, - cred_handle, - mech_type, - sname ? &sname->mech : NULL, - initiator_lifetime, - acceptor_lifetime, - cred_usage); - - if (ret) { - if (sname) - free(sname); - return ret; - } - if (name) - *name = (gss_name_t)sname; - - return GSS_S_COMPLETE; -} - -OM_uint32 GSSAPI_CALLCONV _gss_spnego_inquire_cred_by_oid - (OM_uint32 * minor_status, - gss_const_cred_id_t cred_handle, - const gss_OID desired_object, - gss_buffer_set_t *data_set) -{ - OM_uint32 ret; - - if (cred_handle == GSS_C_NO_CREDENTIAL) { - *minor_status = 0; - return GSS_S_NO_CRED; - } - - ret = gss_inquire_cred_by_oid(minor_status, - cred_handle, - desired_object, - data_set); - - return ret; -} - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_set_cred_option (OM_uint32 *minor_status, - gss_cred_id_t *cred_handle, - const gss_OID object, - const gss_buffer_t value) -{ - if (cred_handle == NULL || *cred_handle == GSS_C_NO_CREDENTIAL) { - *minor_status = 0; - return GSS_S_NO_CRED; - } - - return gss_set_cred_option(minor_status, - cred_handle, - object, - value); -} - - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_export_cred (OM_uint32 *minor_status, - gss_cred_id_t cred_handle, - gss_buffer_t value) -{ - return gss_export_cred(minor_status, cred_handle, value); -} - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_import_cred (OM_uint32 *minor_status, - gss_buffer_t value, - gss_cred_id_t *cred_handle) -{ - return gss_import_cred(minor_status, value, cred_handle); -} - diff --git a/lib/gssapi/spnego/external.c b/lib/gssapi/spnego/external.c index 03678f9989b3..2a5121efa83a 100644 --- a/lib/gssapi/spnego/external.c +++ b/lib/gssapi/spnego/external.c @@ -1,5 +1,7 @@ /* * Copyright (c) 2004, PADL Software Pty Ltd. + * Copyright (c) 2018 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -86,9 +88,9 @@ static gssapi_mech_interface_desc spnego_mech = { GMI_VERSION, "spnego", {6, rk_UNCONST("\x2b\x06\x01\x05\x05\x02") }, - 0, - _gss_spnego_acquire_cred, - _gss_spnego_release_cred, + GM_USE_MG_CRED | GM_USE_MG_NAME, + NULL, /* gm_acquire_cred */ + NULL, /* gm_release_cred */ _gss_spnego_init_sec_context, _gss_spnego_accept_sec_context, _gss_spnego_process_context_token, @@ -100,34 +102,35 @@ static gssapi_mech_interface_desc spnego_mech = { _gss_spnego_unwrap, NULL, /* gm_display_status */ NULL, /* gm_indicate_mechs */ - _gss_spnego_compare_name, - _gss_spnego_display_name, - _gss_spnego_import_name, - _gss_spnego_export_name, - _gss_spnego_release_name, - _gss_spnego_inquire_cred, + NULL, /* gm_compare_name */ + NULL, /* gm_display_name */ + NULL, /* gm_import_name */ + NULL, /* gm_export_name */ + NULL, /* gm_release_name */ + NULL, /* gm_inquire_cred */ _gss_spnego_inquire_context, _gss_spnego_wrap_size_limit, - gss_add_cred, - _gss_spnego_inquire_cred_by_mech, + NULL, /* gm_add_cred */ + NULL, /* gm_inquire_cred_by_mech */ _gss_spnego_export_sec_context, _gss_spnego_import_sec_context, - NULL /* _gss_spnego_inquire_names_for_mech */, - _gss_spnego_inquire_mechs_for_name, - _gss_spnego_canonicalize_name, - _gss_spnego_duplicate_name, + NULL, /* gm_spnego_inquire_names_for_mech */ + NULL, /* gm_spnego_inquire_mechs_for_name */ + NULL, /* gm_spnego_canonicalize_name */ + NULL, /* gm_spnego_duplicate_name */ _gss_spnego_inquire_sec_context_by_oid, - _gss_spnego_inquire_cred_by_oid, + NULL, /* gm_inquire_cred_by_oid */ _gss_spnego_set_sec_context_option, - _gss_spnego_set_cred_option, + NULL, /* gm_set_cred_option */ _gss_spnego_pseudo_random, _gss_spnego_wrap_iov, _gss_spnego_unwrap_iov, _gss_spnego_wrap_iov_length, NULL, - _gss_spnego_export_cred, - _gss_spnego_import_cred, - NULL, + NULL, /* gm_export_cred */ + NULL, /* gm_import_cred */ + NULL, /* gm_acquire_cred_from */ + NULL, /* gm_acquire_cred_impersonate_name */ NULL, NULL, NULL, @@ -136,15 +139,22 @@ static gssapi_mech_interface_desc spnego_mech = { NULL, spnego_mo, sizeof(spnego_mo) / sizeof(spnego_mo[0]), - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, + NULL, /* gm_localname */ + NULL, /* gm_authorize_localname */ + NULL, /* gm_display_name_ext */ + NULL, /* gm_inquire_name */ + NULL, /* gm_get_name_attribute */ + NULL, /* gm_set_name_attribute */ + NULL, /* gm_delete_name_attribute */ + NULL, /* gm_export_name_composite */ + NULL, /* gm_duplicate_cred */ + NULL, /* gm_add_cred_from */ + NULL, /* gm_store_cred_into */ + NULL, /* gm_query_mechanism_info */ + NULL, /* gm_query_meta_data */ + NULL, /* gm_exchange_meta_data */ + NULL, /* gm_store_cred_into2 */ + NULL /* gm_compat */ }; gssapi_mech_interface @@ -152,3 +162,4 @@ __gss_spnego_initialize(void) { return &spnego_mech; } + diff --git a/lib/gssapi/spnego/init_sec_context.c b/lib/gssapi/spnego/init_sec_context.c index a12ce3847573..12ec0ea41060 100644 --- a/lib/gssapi/spnego/init_sec_context.c +++ b/lib/gssapi/spnego/init_sec_context.c @@ -33,38 +33,86 @@ #include "spnego_locl.h" -/* - * Is target_name an sane target for `mech´. - */ +#define GSISC(name) \ +static \ +OM_uint32 name(OM_uint32 *, gss_const_cred_id_t, gssspnego_ctx, \ + gss_const_name_t, gss_const_OID, \ + OM_uint32, OM_uint32, const gss_channel_bindings_t, \ + gss_const_buffer_t, gss_buffer_t, \ + OM_uint32 *, OM_uint32 *) + +GSISC(spnego_initial); +GSISC(spnego_reply); +GSISC(wait_server_mic); +GSISC(step_completed); + + + /* + * Is target_name an sane target for `mech´. + */ static OM_uint32 -initiator_approved(gss_name_t target_name, gss_OID mech) +initiator_approved(OM_uint32 *minor_status, + void *userptr, + gss_const_name_t target_name, + gss_const_cred_id_t cred, + gss_OID mech) { OM_uint32 min_stat, maj_stat; gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; gss_buffer_desc out; + struct gssspnego_optimistic_ctx *sel = userptr; + gss_OID negotiated_mech_type = GSS_C_NO_OID; + OM_uint32 flags = 0, time_rec = 0; + auth_scheme scheme; + int negoex = 0; maj_stat = gss_init_sec_context(&min_stat, - GSS_C_NO_CREDENTIAL, + cred, &ctx, - target_name, + sel->target_name, mech, - 0, - GSS_C_INDEFINITE, - GSS_C_NO_CHANNEL_BINDINGS, + sel->req_flags, + sel->time_req, + sel->input_chan_bindings, GSS_C_NO_BUFFER, - NULL, + &negotiated_mech_type, &out, - NULL, - NULL); + &flags, + &time_rec); if (GSS_ERROR(maj_stat)) { gss_mg_collect_error(mech, maj_stat, min_stat); - return GSS_S_BAD_MECH; + *minor_status = min_stat; + return maj_stat; } - gss_release_buffer(&min_stat, &out); - gss_delete_sec_context(&min_stat, &ctx, NULL); - return GSS_S_COMPLETE; + if (gssspi_query_mechanism_info(&min_stat, mech, scheme) == GSS_S_COMPLETE) + negoex = 1; + + if (sel->preferred_mech_type == GSS_C_NO_OID) { + sel->preferred_mech_type = mech; + sel->negotiated_mech_type = negotiated_mech_type; + sel->optimistic_token = out; + sel->optimistic_flags = flags; + sel->optimistic_time_rec = time_rec; + sel->gssctx = ctx; + if (maj_stat == GSS_S_COMPLETE) + sel->complete = 1; + if (negoex) + memcpy(sel->scheme, scheme, GUID_LENGTH); + } else { + gss_release_buffer(&min_stat, &out); + gss_delete_sec_context(&min_stat, &ctx, NULL); + } + + maj_stat = GSS_S_COMPLETE; + + if (negoex) { + maj_stat = _gss_negoex_add_auth_mech(minor_status, sel->spnegoctx, + mech, scheme); + } + + return maj_stat; } /* @@ -76,42 +124,40 @@ initiator_approved(gss_name_t target_name, gss_OID mech) * must return GSS_S_CONTINUE_NEEDED if a token was generated. */ static OM_uint32 -spnego_reply_internal(OM_uint32 *minor_status, - gssspnego_ctx context_handle, - const gss_buffer_t mech_buf, - gss_buffer_t mech_token, - gss_buffer_t output_token) +make_reply(OM_uint32 *minor_status, + gssspnego_ctx ctx, + gss_buffer_t mech_token, + gss_buffer_t output_token) { NegotiationToken nt; gss_buffer_desc mic_buf; - OM_uint32 ret; + OM_uint32 ret, minor; size_t size; - - if (mech_buf == GSS_C_NO_BUFFER && mech_token->length == 0) { - output_token->length = 0; - output_token->value = NULL; - - return context_handle->open ? GSS_S_COMPLETE : GSS_S_FAILURE; - } + NegStateEnum state; memset(&nt, 0, sizeof(nt)); nt.element = choice_NegotiationToken_negTokenResp; - ALLOC(nt.u.negTokenResp.negResult, 1); - if (nt.u.negTokenResp.negResult == NULL) { - *minor_status = ENOMEM; - return GSS_S_FAILURE; - } - + nt.u.negTokenResp.negState = NULL; nt.u.negTokenResp.supportedMech = NULL; output_token->length = 0; output_token->value = NULL; + /* figure out our status */ + + if (ctx->flags.open) { + if (ctx->flags.verified_mic == 1 || ctx->flags.require_mic == 0) + state = accept_completed; + else + state = accept_incomplete; + } else { + state = accept_incomplete; + } + if (mech_token->length == 0) { nt.u.negTokenResp.responseToken = NULL; - *(nt.u.negTokenResp.negResult) = accept_completed; } else { ALLOC(nt.u.negTokenResp.responseToken, 1); if (nt.u.negTokenResp.responseToken == NULL) { @@ -123,18 +169,23 @@ spnego_reply_internal(OM_uint32 *minor_status, nt.u.negTokenResp.responseToken->data = mech_token->value; mech_token->length = 0; mech_token->value = NULL; - - *(nt.u.negTokenResp.negResult) = accept_incomplete; } - if (mech_buf != GSS_C_NO_BUFFER) { + /* + * XXX should limit when we send the MIC ? + */ + if (ctx->flags.open && ctx->flags.sent_mic == 0) { + + ctx->flags.sent_mic = 1; ret = gss_get_mic(minor_status, - context_handle->negotiated_ctx_id, + ctx->negotiated_ctx_id, 0, - mech_buf, + &ctx->NegTokenInit_mech_types, &mic_buf); if (ret == GSS_S_COMPLETE) { + _gss_spnego_ntlm_reset_crypto(&minor, ctx, FALSE); + ALLOC(nt.u.negTokenResp.mechListMIC, 1); if (nt.u.negTokenResp.mechListMIC == NULL) { gss_release_buffer(minor_status, &mic_buf); @@ -145,337 +196,343 @@ spnego_reply_internal(OM_uint32 *minor_status, nt.u.negTokenResp.mechListMIC->length = mic_buf.length; nt.u.negTokenResp.mechListMIC->data = mic_buf.value; + /* mic_buf free()d with nt */ } else if (ret == GSS_S_UNAVAILABLE) { + /* lets hope that its ok to not send te mechListMIC for broken mechs */ nt.u.negTokenResp.mechListMIC = NULL; - } if (ret) { + ctx->flags.require_mic = 0; + } else { free_NegotiationToken(&nt); *minor_status = ENOMEM; - return GSS_S_FAILURE; + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + ret, *minor_status, + "SPNEGO failed to sign MIC"); } } else { nt.u.negTokenResp.mechListMIC = NULL; } + ALLOC(nt.u.negTokenResp.negState, 1); + if (nt.u.negTokenResp.negState == NULL) { + free_NegotiationToken(&nt); + *minor_status = ENOMEM; + return GSS_S_FAILURE; + } + *nt.u.negTokenResp.negState = state; + ASN1_MALLOC_ENCODE(NegotiationToken, output_token->value, output_token->length, &nt, &size, ret); + free_NegotiationToken(&nt); if (ret) { - free_NegotiationToken(&nt); *minor_status = ret; return GSS_S_FAILURE; } - if (*(nt.u.negTokenResp.negResult) == accept_completed) - ret = GSS_S_COMPLETE; - else - ret = GSS_S_CONTINUE_NEEDED; + if (state != accept_completed) + return GSS_S_CONTINUE_NEEDED; - free_NegotiationToken(&nt); - return ret; + return GSS_S_COMPLETE; } static OM_uint32 -spnego_initial - (OM_uint32 * minor_status, - gss_const_cred_id_t cred, - gss_ctx_id_t * context_handle, - gss_const_name_t target_name, - const gss_OID mech_type, - OM_uint32 req_flags, - OM_uint32 time_req, - const gss_channel_bindings_t input_chan_bindings, - const gss_buffer_t input_token, - gss_OID * actual_mech_type, - gss_buffer_t output_token, - OM_uint32 * ret_flags, - OM_uint32 * time_rec - ) +spnego_initial(OM_uint32 * minor_status, + gss_const_cred_id_t cred, + gssspnego_ctx ctx, + gss_const_name_t target_name, + gss_const_OID mech_type, + 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 * ret_flags, + OM_uint32 * time_rec) { - NegTokenInit ni; + NegotiationToken nt; int ret; OM_uint32 sub, minor; gss_buffer_desc mech_token; - u_char *buf; - size_t buf_size, buf_len; + size_t size = 0; gss_buffer_desc data; - size_t ni_len; - gss_ctx_id_t context; - gssspnego_ctx ctx; - spnego_name name = (spnego_name)target_name; + struct gssspnego_optimistic_ctx sel; *minor_status = 0; - memset (&ni, 0, sizeof(ni)); - - *context_handle = GSS_C_NO_CONTEXT; + memset(&nt, 0, sizeof(nt)); if (target_name == GSS_C_NO_NAME) return GSS_S_BAD_NAME; - sub = _gss_spnego_alloc_sec_context(&minor, &context); + sub = gss_duplicate_name(&minor, target_name, &ctx->target_name); if (GSS_ERROR(sub)) { *minor_status = minor; return sub; } - ctx = (gssspnego_ctx)context; - HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); + nt.element = choice_NegotiationToken_negTokenInit; - ctx->local = 1; + ctx->flags.local = 1; - sub = gss_import_name(&minor, &name->value, &name->type, &ctx->target_name); - if (GSS_ERROR(sub)) { - *minor_status = minor; - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); - return sub; - } + memset(&sel, 0, sizeof(sel)); + + sel.spnegoctx = ctx; + sel.target_name = ctx->target_name; + sel.preferred_mech_type = GSS_C_NO_OID; + sel.req_flags = req_flags; + sel.time_req = time_req; + sel.input_chan_bindings = (gss_channel_bindings_t)input_chan_bindings; sub = _gss_spnego_indicate_mechtypelist(&minor, ctx->target_name, + req_flags, initiator_approved, + &sel, 0, cred, - &ni.mechTypes, + &nt.u.negTokenInit.mechTypes, &ctx->preferred_mech_type); if (GSS_ERROR(sub)) { *minor_status = minor; - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); return sub; } - ni.reqFlags = NULL; + _gss_spnego_log_mechTypes(&nt.u.negTokenInit.mechTypes); - /* - * If we have a credential handle, use it to select the mechanism - * that we will use - */ + nt.u.negTokenInit.reqFlags = NULL; - /* generate optimistic token */ - sub = gss_init_sec_context(&minor, - cred, - &ctx->negotiated_ctx_id, - ctx->target_name, - ctx->preferred_mech_type, + if (gss_oid_equal(ctx->preferred_mech_type, GSS_NEGOEX_MECHANISM)) { + struct negoex_auth_mech *mech; + + sub = _gss_negoex_init(&minor, + &sel, + ctx, + (gss_cred_id_t)cred, req_flags, time_req, input_chan_bindings, - input_token, - &ctx->negotiated_mech_type, - &mech_token, - &ctx->mech_flags, - &ctx->mech_time_rec); - if (GSS_ERROR(sub)) { - free_NegTokenInit(&ni); - *minor_status = minor; - gss_mg_collect_error(ctx->preferred_mech_type, sub, minor); - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); - return sub; + GSS_C_NO_BUFFER, + &mech_token); + if (GSS_ERROR(sub)) { + free_NegotiationToken(&nt); + return gss_mg_set_error_string(GSS_C_NO_OID, sub, minor, + "NegoEx could not generate a context token"); + } + mech = _gss_negoex_negotiated_mech(ctx); + ctx->flags.maybe_open = mech && mech->complete; + gss_release_buffer(&minor, &sel.optimistic_token); + } else { + /* optimistic token from selection context */ + mech_token = sel.optimistic_token; + ctx->mech_flags = sel.optimistic_flags; + ctx->mech_time_rec = sel.optimistic_time_rec; + ctx->negotiated_mech_type = sel.negotiated_mech_type; + ctx->negotiated_ctx_id = sel.gssctx; + ctx->flags.maybe_open = sel.complete; } - if (sub == GSS_S_COMPLETE) - ctx->maybe_open = 1; + + if (ctx->preferred_mech_type == GSS_C_NO_OID) { + free_NegotiationToken(&nt); + *minor_status = 0; + return gss_mg_set_error_string(GSS_C_NO_OID, GSS_S_NO_CONTEXT, 0, + "SPNEGO could not find a preferred mechanism"); + } + if (mech_token.length != 0) { - ALLOC(ni.mechToken, 1); - if (ni.mechToken == NULL) { - free_NegTokenInit(&ni); + ALLOC(nt.u.negTokenInit.mechToken, 1); + if (nt.u.negTokenInit.mechToken == NULL) { + free_NegotiationToken(&nt); gss_release_buffer(&minor, &mech_token); - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); *minor_status = ENOMEM; return GSS_S_FAILURE; } - ni.mechToken->length = mech_token.length; - ni.mechToken->data = malloc(mech_token.length); - if (ni.mechToken->data == NULL && mech_token.length != 0) { - free_NegTokenInit(&ni); + nt.u.negTokenInit.mechToken->length = mech_token.length; + nt.u.negTokenInit.mechToken->data = malloc(mech_token.length); + if (nt.u.negTokenInit.mechToken->data == NULL && mech_token.length != 0) { + free_NegotiationToken(&nt); gss_release_buffer(&minor, &mech_token); *minor_status = ENOMEM; - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); return GSS_S_FAILURE; } - memcpy(ni.mechToken->data, mech_token.value, mech_token.length); + memcpy(nt.u.negTokenInit.mechToken->data, mech_token.value, mech_token.length); gss_release_buffer(&minor, &mech_token); } else - ni.mechToken = NULL; + nt.u.negTokenInit.mechToken = NULL; - ni.mechListMIC = NULL; + nt.u.negTokenInit.mechListMIC = NULL; - ni_len = length_NegTokenInit(&ni); - buf_size = 1 + der_length_len(ni_len) + ni_len; + { + MechTypeList mt; - buf = malloc(buf_size); - if (buf == NULL) { - free_NegTokenInit(&ni); - *minor_status = ENOMEM; - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); - return GSS_S_FAILURE; - } + mt.len = nt.u.negTokenInit.mechTypes.len; + mt.val = nt.u.negTokenInit.mechTypes.val; - ret = encode_NegTokenInit(buf + buf_size - 1, - ni_len, - &ni, &buf_len); - if (ret == 0 && ni_len != buf_len) - abort(); - - if (ret == 0) { - size_t tmp; - - ret = der_put_length_and_tag(buf + buf_size - buf_len - 1, - buf_size - buf_len, - buf_len, - ASN1_C_CONTEXT, - CONS, - 0, - &tmp); - if (ret == 0 && tmp + buf_len != buf_size) - abort(); + ASN1_MALLOC_ENCODE(MechTypeList, + ctx->NegTokenInit_mech_types.value, + ctx->NegTokenInit_mech_types.length, + &mt, &size, ret); + if (ret) { + *minor_status = ret; + free_NegotiationToken(&nt); + return GSS_S_FAILURE; + } } + + ASN1_MALLOC_ENCODE(NegotiationToken, data.value, data.length, &nt, &size, ret); + free_NegotiationToken(&nt); if (ret) { - *minor_status = ret; - free(buf); - free_NegTokenInit(&ni); - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); return GSS_S_FAILURE; } - - data.value = buf; - data.length = buf_size; - - ctx->initiator_mech_types.len = ni.mechTypes.len; - ctx->initiator_mech_types.val = ni.mechTypes.val; - ni.mechTypes.len = 0; - ni.mechTypes.val = NULL; - - free_NegTokenInit(&ni); + if (data.length != size) + abort(); sub = gss_encapsulate_token(&data, GSS_SPNEGO_MECHANISM, output_token); - free (buf); + free (data.value); if (sub) { - _gss_spnego_internal_delete_sec_context(&minor, &context, GSS_C_NO_BUFFER); return sub; } - if (actual_mech_type) - *actual_mech_type = ctx->negotiated_mech_type; if (ret_flags) *ret_flags = ctx->mech_flags; if (time_rec) *time_rec = ctx->mech_time_rec; - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - - *context_handle = context; + ctx->initiator_state = spnego_reply; return GSS_S_CONTINUE_NEEDED; } +/* + * + */ + static OM_uint32 -spnego_reply - (OM_uint32 * minor_status, - gss_const_cred_id_t cred, - gss_ctx_id_t * context_handle, - gss_const_name_t target_name, - const gss_OID mech_type, - OM_uint32 req_flags, - OM_uint32 time_req, - const gss_channel_bindings_t input_chan_bindings, - const gss_buffer_t input_token, - gss_OID * actual_mech_type, - gss_buffer_t output_token, - OM_uint32 * ret_flags, - OM_uint32 * time_rec - ) +spnego_reply(OM_uint32 * minor_status, + gss_const_cred_id_t cred, + gssspnego_ctx ctx, + gss_const_name_t target_name, + gss_const_OID mech_type, + 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 * ret_flags, + OM_uint32 * time_rec) { OM_uint32 ret, minor; NegotiationToken resp; - gss_OID_desc mech; - int require_mic; - size_t buf_len = 0; - gss_buffer_desc mic_buf, mech_buf; gss_buffer_desc mech_output_token; - gssspnego_ctx ctx; + NegStateEnum negState; *minor_status = 0; - ctx = (gssspnego_ctx)*context_handle; - output_token->length = 0; output_token->value = NULL; mech_output_token.length = 0; mech_output_token.value = NULL; - mech_buf.value = NULL; - mech_buf.length = 0; - ret = decode_NegotiationToken(input_token->value, input_token->length, &resp, NULL); if (ret) return ret; + /* The SPNEGO token must be a negTokenResp */ if (resp.element != choice_NegotiationToken_negTokenResp) { free_NegotiationToken(&resp); *minor_status = 0; return GSS_S_BAD_MECH; } - if (resp.u.negTokenResp.negResult == NULL - || *(resp.u.negTokenResp.negResult) == reject - /* || resp.u.negTokenResp.supportedMech == NULL */ - ) - { - free_NegotiationToken(&resp); - return GSS_S_BAD_MECH; - } + /* + * When negState is absent, the actual state should be inferred from + * the state of the negotiated mechanism context. (RFC 4178 4.2.2.) + */ + if (resp.u.negTokenResp.negState != NULL) + negState = *resp.u.negTokenResp.negState; + else + negState = accept_incomplete; /* - * Pick up the mechanism that the acceptor selected, only allow it - * to be sent in packet. + * Pick up the mechanism that the acceptor selected, only pick up + * the first selection. */ - HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); + if (ctx->selected_mech_type == GSS_C_NO_OID && resp.u.negTokenResp.supportedMech) { + gss_OID_desc oid; + size_t len; - if (resp.u.negTokenResp.supportedMech) { + ctx->flags.seen_supported_mech = 1; - if (ctx->oidlen) { + oid.length = (OM_uint32)der_length_oid(resp.u.negTokenResp.supportedMech); + oid.elements = malloc(oid.length); + if (oid.elements == NULL) { free_NegotiationToken(&resp); - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return GSS_S_BAD_MECH; } - ret = der_put_oid(ctx->oidbuf + sizeof(ctx->oidbuf) - 1, - sizeof(ctx->oidbuf), + ret = der_put_oid(((uint8_t *)oid.elements) + oid.length - 1, + oid.length, resp.u.negTokenResp.supportedMech, - &ctx->oidlen); - /* Avoid recursively embedded SPNEGO */ - if (ret || (ctx->oidlen == GSS_SPNEGO_MECHANISM->length && - memcmp(ctx->oidbuf + sizeof(ctx->oidbuf) - ctx->oidlen, - GSS_SPNEGO_MECHANISM->elements, - ctx->oidlen) == 0)) - { + &len); + if (ret || len != oid.length) { + free(oid.elements); free_NegotiationToken(&resp); - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return GSS_S_BAD_MECH; } + if (gss_oid_equal(GSS_SPNEGO_MECHANISM, &oid)) { + free(oid.elements); + free_NegotiationToken(&resp); + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "SPNEGO acceptor picked SPNEGO??"); + } + /* check if the acceptor took our optimistic token */ - if (ctx->oidlen != ctx->preferred_mech_type->length || - memcmp(ctx->oidbuf + sizeof(ctx->oidbuf) - ctx->oidlen, - ctx->preferred_mech_type->elements, - ctx->oidlen) != 0) - { + if (gss_oid_equal(ctx->preferred_mech_type, &oid)) { + ctx->selected_mech_type = ctx->preferred_mech_type; + } else if (gss_oid_equal(ctx->preferred_mech_type, GSS_KRB5_MECHANISM) && + gss_oid_equal(&oid, &_gss_spnego_mskrb_mechanism_oid_desc)) { + /* mis-encoded asn1 type from msft servers */ + ctx->selected_mech_type = ctx->preferred_mech_type; + } else { + /* nope, lets start over */ gss_delete_sec_context(&minor, &ctx->negotiated_ctx_id, GSS_C_NO_BUFFER); ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT; + + if (gss_oid_equal(&oid, GSS_NEGOEX_MECHANISM)) + ctx->selected_mech_type = GSS_NEGOEX_MECHANISM; + else + ctx->selected_mech_type = _gss_mg_support_mechanism(&oid); + + /* XXX check that server pick a mechanism we proposed */ + if (ctx->selected_mech_type == GSS_C_NO_OID) { + free(oid.elements); + free_NegotiationToken(&resp); + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "SPNEGO acceptor sent unsupported supportedMech"); + } } - } else if (ctx->oidlen == 0) { + + _gss_spnego_log_mech("initiator selected mechanism", ctx->selected_mech_type); + + free(oid.elements); + + } else if (ctx->selected_mech_type == NULL) { free_NegotiationToken(&resp); - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - return GSS_S_BAD_MECH; + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "SPNEGO acceptor didn't send supportedMech"); } - /* if a token (of non zero length), or no context, pass to underlaying mech */ + /* if a token (of non zero length) pass to underlaying mech */ if ((resp.u.negTokenResp.responseToken != NULL && resp.u.negTokenResp.responseToken->length) || ctx->negotiated_ctx_id == GSS_C_NO_CONTEXT) { gss_buffer_desc mech_input_token; @@ -488,173 +545,297 @@ spnego_reply mech_input_token.value = NULL; } - - mech.length = ctx->oidlen; - mech.elements = ctx->oidbuf + sizeof(ctx->oidbuf) - ctx->oidlen; - /* Fall through as if the negotiated mechanism was requested explicitly */ - ret = gss_init_sec_context(&minor, - cred, - &ctx->negotiated_ctx_id, - ctx->target_name, - &mech, + if (gss_oid_equal(ctx->selected_mech_type, GSS_NEGOEX_MECHANISM)) { + ret = _gss_negoex_init(&minor, + NULL, /* no optimistic token */ + ctx, + (gss_cred_id_t)cred, req_flags, time_req, input_chan_bindings, &mech_input_token, - &ctx->negotiated_mech_type, - &mech_output_token, - &ctx->mech_flags, - &ctx->mech_time_rec); + &mech_output_token); + } else { + ret = gss_init_sec_context(&minor, + cred, + &ctx->negotiated_ctx_id, + ctx->target_name, + ctx->selected_mech_type, + req_flags, + time_req, + input_chan_bindings, + &mech_input_token, + &ctx->negotiated_mech_type, + &mech_output_token, + &ctx->mech_flags, + &ctx->mech_time_rec); + if (GSS_ERROR(ret)) { + gss_mg_collect_error(ctx->selected_mech_type, ret, minor); + } + } + /* + * If the acceptor rejected, we're out even if the inner context is + * now complete. Note that the rejection is not integrity-protected. + */ + if (negState == reject) + ret = GSS_S_BAD_MECH; if (GSS_ERROR(ret)) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); free_NegotiationToken(&resp); - gss_mg_collect_error(&mech, ret, minor); *minor_status = minor; return ret; } if (ret == GSS_S_COMPLETE) { - ctx->open = 1; + ctx->flags.open = 1; + } + } else if (negState == reject) { + free_NegotiationToken(&resp); + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EPERM), + "SPNEGO acceptor rejected initiator token"); + } else if (negState == accept_completed) { + /* + * Note that the accept_completed isn't integrity-protected, but + * ctx->maybe_open can only be true if the inner context is fully + * established. + */ + if (ctx->flags.maybe_open) + ctx->flags.open = 1; + + if (!ctx->flags.open) { + free_NegotiationToken(&resp); + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "SPNEGO acceptor sent acceptor complete, " + "but we are not complete yet"); } - } else if (*(resp.u.negTokenResp.negResult) == accept_completed) { - if (ctx->maybe_open) - ctx->open = 1; } - if (*(resp.u.negTokenResp.negResult) == request_mic) { - ctx->require_mic = 1; + if (negState == request_mic) { + ctx->flags.peer_require_mic = 1; } - if (ctx->open) { + if (ctx->flags.open && ctx->flags.verified_mic == 0) { + + ctx->flags.require_mic = 1; /* default is to require a MIC */ + ctx->flags.safe_omit = _gss_spnego_safe_omit_mechlist_mic(ctx); + /* - * Verify the mechListMIC if one was provided or CFX was - * used and a non-preferred mechanism was selected + * If the peer sent mechListMIC, require it to verify ... */ - if (resp.u.negTokenResp.mechListMIC != NULL) { - require_mic = 1; - } else { - ret = _gss_spnego_require_mechlist_mic(minor_status, ctx, - &require_mic); - if (ret) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free_NegotiationToken(&resp); - gss_release_buffer(&minor, &mech_output_token); - return ret; + if (resp.u.negTokenResp.mechListMIC) { + heim_octet_string *m = resp.u.negTokenResp.mechListMIC; + + /* ...unless its a windows 2000 server that sends the + * responseToken inside the mechListMIC too. We only + * accept this condition if would have been safe to omit + * anyway. */ + + if (ctx->flags.safe_omit + && resp.u.negTokenResp.responseToken + && der_heim_octet_string_cmp(m, resp.u.negTokenResp.responseToken) == 0) + { + ctx->flags.require_mic = 0; } } + } else { - require_mic = 0; + ctx->flags.require_mic = 0; } - if (require_mic) { - ASN1_MALLOC_ENCODE(MechTypeList, mech_buf.value, mech_buf.length, - &ctx->initiator_mech_types, &buf_len, ret); + /* + * If we are supposed to check mic and have it, force checking now. + */ + + if (ctx->flags.require_mic && resp.u.negTokenResp.mechListMIC) { + + ret = _gss_spnego_verify_mechtypes_mic(minor_status, ctx, + resp.u.negTokenResp.mechListMIC); if (ret) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); free_NegotiationToken(&resp); - gss_release_buffer(&minor, &mech_output_token); - *minor_status = ret; - return GSS_S_FAILURE; - } - if (mech_buf.length != buf_len) { - abort(); - UNREACHABLE(return GSS_S_FAILURE); - } - - if (resp.u.negTokenResp.mechListMIC == NULL) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free(mech_buf.value); - free_NegotiationToken(&resp); - *minor_status = 0; - return GSS_S_DEFECTIVE_TOKEN; - } - mic_buf.length = resp.u.negTokenResp.mechListMIC->length; - mic_buf.value = resp.u.negTokenResp.mechListMIC->data; - - if (mech_output_token.length == 0) { - ret = gss_verify_mic(minor_status, - ctx->negotiated_ctx_id, - &mech_buf, - &mic_buf, - NULL); - if (ret) { - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); - free(mech_buf.value); - gss_release_buffer(&minor, &mech_output_token); - free_NegotiationToken(&resp); - return GSS_S_DEFECTIVE_TOKEN; - } - ctx->verified_mic = 1; + return ret; } } - ret = spnego_reply_internal(minor_status, ctx, - require_mic ? &mech_buf : NULL, - &mech_output_token, - output_token); + /* + * Now that underlaying mech is open (conncted), we can figure out + * what nexd step to go to. + */ - if (mech_buf.value != NULL) - free(mech_buf.value); + if (ctx->flags.open) { + + if (negState == accept_completed && ctx->flags.safe_omit) { + ctx->initiator_state = step_completed; + ret = GSS_S_COMPLETE; + } else if (ctx->flags.require_mic != 0 && ctx->flags.verified_mic == 0) { + ctx->initiator_state = wait_server_mic; + ret = GSS_S_CONTINUE_NEEDED; + } else { + ctx->initiator_state = step_completed; + ret = GSS_S_COMPLETE; + } + } + + if (negState != accept_completed || + ctx->initiator_state != step_completed || + mech_output_token.length) + { + OM_uint32 ret2; + ret2 = make_reply(minor_status, ctx, + &mech_output_token, + output_token); + if (ret2) + ret = ret2; + } free_NegotiationToken(&resp); + gss_release_buffer(&minor, &mech_output_token); - if (actual_mech_type) - *actual_mech_type = ctx->negotiated_mech_type; if (ret_flags) *ret_flags = ctx->mech_flags; if (time_rec) *time_rec = ctx->mech_time_rec; - HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return ret; } +static OM_uint32 +wait_server_mic(OM_uint32 * minor_status, + gss_const_cred_id_t cred, + gssspnego_ctx ctx, + gss_const_name_t target_name, + gss_const_OID mech_type, + 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 * ret_flags, + OM_uint32 * time_rec) +{ + OM_uint32 major_status; + NegotiationToken resp; + int ret; + + ret = decode_NegotiationToken(input_token->value, input_token->length, &resp, NULL); + if (ret) + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, ret, + "Failed to decode NegotiationToken"); + + if (resp.element != choice_NegotiationToken_negTokenResp + || resp.u.negTokenResp.negState == NULL + || *resp.u.negTokenResp.negState != accept_completed) + { + free_NegotiationToken(&resp); + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "NegToken not accept_completed"); + } + + if (resp.u.negTokenResp.mechListMIC) { + major_status = _gss_spnego_verify_mechtypes_mic(minor_status, ctx, + resp.u.negTokenResp.mechListMIC); + } else if (ctx->flags.safe_omit == 0) { + free_NegotiationToken(&resp); + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_MECH, (*minor_status = EINVAL), + "Waiting for MIC, but its missing in server request"); + } else { + major_status = GSS_S_COMPLETE; + } + + free_NegotiationToken(&resp); + if (major_status != GSS_S_COMPLETE) + return major_status; + + ctx->flags.verified_mic = 1; + ctx->initiator_state = step_completed; + + if (ret_flags) + *ret_flags = ctx->mech_flags; + if (time_rec) + *time_rec = ctx->mech_time_rec; + + *minor_status = 0; + return GSS_S_COMPLETE; +} + +static OM_uint32 +step_completed(OM_uint32 * minor_status, + gss_const_cred_id_t cred, + gssspnego_ctx ctx, + gss_const_name_t name, + gss_const_OID mech_type, + 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 * ret_flags, + OM_uint32 * time_rec) +{ + return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + GSS_S_BAD_STATUS, (*minor_status = EINVAL), + "SPNEGO called got ISC call one too many"); +} + OM_uint32 GSSAPI_CALLCONV -_gss_spnego_init_sec_context - (OM_uint32 * minor_status, - gss_const_cred_id_t initiator_cred_handle, - gss_ctx_id_t * context_handle, - gss_const_name_t target_name, - const gss_OID mech_type, - OM_uint32 req_flags, - OM_uint32 time_req, - const gss_channel_bindings_t input_chan_bindings, - const gss_buffer_t input_token, - gss_OID * actual_mech_type, - gss_buffer_t output_token, - OM_uint32 * ret_flags, - OM_uint32 * time_rec - ) +_gss_spnego_init_sec_context(OM_uint32 * minor_status, + gss_const_cred_id_t initiator_cred_handle, + gss_ctx_id_t * context_handle, + gss_const_name_t target_name, + const gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + const gss_buffer_t input_token, + gss_OID * actual_mech_type, + gss_buffer_t output_token, + OM_uint32 * ret_flags, + OM_uint32 * time_rec) { - if (*context_handle == GSS_C_NO_CONTEXT) - return spnego_initial (minor_status, - initiator_cred_handle, - context_handle, - target_name, - mech_type, - req_flags, - time_req, - input_chan_bindings, - input_token, - actual_mech_type, - output_token, - ret_flags, - time_rec); - else - return spnego_reply (minor_status, - initiator_cred_handle, - context_handle, - target_name, - mech_type, - req_flags, - time_req, - input_chan_bindings, - input_token, - actual_mech_type, - output_token, - ret_flags, - time_rec); + gssspnego_ctx ctx; + OM_uint32 ret; + + if (*context_handle == GSS_C_NO_CONTEXT) { + ret = _gss_spnego_alloc_sec_context(minor_status, context_handle); + if (GSS_ERROR(ret)) + return ret; + + ctx = (gssspnego_ctx)*context_handle; + + ctx->initiator_state = spnego_initial; + } else { + ctx = (gssspnego_ctx)*context_handle; + } + + + HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); + + do { + ret = ctx->initiator_state(minor_status, initiator_cred_handle, ctx, target_name, + mech_type, req_flags, time_req, input_chan_bindings, input_token, + output_token, ret_flags, time_rec); + + } while (ret == GSS_S_COMPLETE && + ctx->initiator_state != step_completed && + output_token->length == 0); + + /* destroy context in case of error */ + if (GSS_ERROR(ret)) { + OM_uint32 junk; + _gss_spnego_internal_delete_sec_context(&junk, context_handle, GSS_C_NO_BUFFER); + } else { + + HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); + + if (actual_mech_type) + *actual_mech_type = ctx->negotiated_mech_type; + } + + return ret; } diff --git a/lib/gssapi/spnego/negoex_ctx.c b/lib/gssapi/spnego/negoex_ctx.c new file mode 100644 index 000000000000..3f8aa5c3e7b1 --- /dev/null +++ b/lib/gssapi/spnego/negoex_ctx.c @@ -0,0 +1,1041 @@ +/* + * Copyright (C) 2011-2021 PADL Software Pty Ltd. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spnego_locl.h" + +/* + * The initial context token emitted by the initiator is a INITIATOR_NEGO + * message followed by zero or more INITIATOR_META_DATA tokens, and zero + * or one AP_REQUEST tokens. + * + * Upon receiving this, the acceptor computes the list of mutually supported + * authentication mechanisms and performs the metadata exchange. The output + * token is ACCEPTOR_NEGO followed by zero or more ACCEPTOR_META_DATA tokens, + * and zero or one CHALLENGE tokens. + * + * Once the metadata exchange is complete and a mechanism is selected, the + * selected mechanism's context token exchange continues with AP_REQUEST and + * CHALLENGE messages. + * + * Once the context token exchange is complete, VERIFY messages are sent to + * authenticate the entire exchange. + */ + +static OM_uint32 +buffer_set_to_crypto(OM_uint32 *minor, + krb5_context context, + gss_buffer_set_t buffers, + krb5_crypto *crypto) +{ + krb5_error_code ret; + krb5_keyblock keyblock; + OM_uint32 tmp; + + /* + * Returned keys must be in two buffers, with the key contents in + * the first and the enctype as a 32-bit little-endian integer in + * the second. + */ + if (buffers->count != 2 || + buffers->elements[1].length != sizeof(tmp)) { + *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY; + return GSS_S_FAILURE; + } + + if (*crypto != NULL) { + krb5_crypto_destroy(context, *crypto); + *crypto = NULL; + } + + keyblock.keyvalue.data = buffers->elements[0].value; + keyblock.keyvalue.length = buffers->elements[0].length; + _gss_mg_decode_le_uint32(buffers->elements[1].value, &tmp); + keyblock.keytype = tmp; + + ret = krb5_crypto_init(context, &keyblock, 0, crypto); + if (ret) { + *minor = ret; + return GSS_S_FAILURE; + } + + return GSS_S_COMPLETE; +} + +#define NEGOEX_SIGN_KEY 1 +#define NEGOEX_VERIFY_KEY 2 +#define NEGOEX_BOTH_KEYS (NEGOEX_SIGN_KEY|NEGOEX_VERIFY_KEY) + +static OM_uint32 +get_session_keys(OM_uint32 *minor, + krb5_context context, + OM_uint32 flags, + struct negoex_auth_mech *mech) +{ + OM_uint32 major, tmpMinor; + gss_buffer_set_t buffers = GSS_C_NO_BUFFER_SET; + + if (flags & NEGOEX_SIGN_KEY) { + major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context, + GSS_C_INQ_NEGOEX_KEY, &buffers); + if (major == GSS_S_COMPLETE) { + major = buffer_set_to_crypto(minor, context, + buffers, &mech->crypto); + _gss_secure_release_buffer_set(&tmpMinor, &buffers); + if (major != GSS_S_COMPLETE) + return major; + } + } + + if (flags & NEGOEX_VERIFY_KEY) { + major = gss_inquire_sec_context_by_oid(&tmpMinor, mech->mech_context, + GSS_C_INQ_NEGOEX_VERIFY_KEY, + &buffers); + if (major == GSS_S_COMPLETE) { + major = buffer_set_to_crypto(minor, context, + buffers, &mech->verify_crypto); + _gss_secure_release_buffer_set(&tmpMinor, &buffers); + if (major != GSS_S_COMPLETE) + return major; + } + } + + return GSS_S_COMPLETE; +} + +static OM_uint32 +emit_initiator_nego(OM_uint32 *minor, gssspnego_ctx ctx) +{ + uint8_t random[32]; + struct negoex_auth_mech *mech; + size_t i = 0; + + krb5_generate_random_block(random, sizeof(random)); + + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) + _gss_negoex_log_auth_scheme(ctx->flags.local, ++i, mech->scheme); + + return _gss_negoex_add_nego_message(minor, ctx, INITIATOR_NEGO, random); +} + +static OM_uint32 +process_initiator_nego(OM_uint32 *minor, + gssspnego_ctx ctx, + struct negoex_message *messages, + size_t nmessages) +{ + struct nego_message *msg; + size_t i; + + heim_assert(!ctx->flags.local && ctx->negoex_step == 1, + "NegoEx INITIATOR_NEGO token received after first leg"); + + msg = _gss_negoex_locate_nego_message(messages, nmessages, INITIATOR_NEGO); + if (msg == NULL) { + *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE; + return GSS_S_DEFECTIVE_TOKEN; + } + + for (i = 0; i < msg->nschemes; i++) + _gss_negoex_log_auth_scheme(ctx->flags.local, i + 1, &msg->schemes[i * GUID_LENGTH]); + + _gss_negoex_restrict_auth_schemes(ctx, msg->schemes, msg->nschemes); + + return GSS_S_COMPLETE; +} + +static OM_uint32 +emit_acceptor_nego(OM_uint32 *minor, gssspnego_ctx ctx) +{ + uint8_t random[32]; + + krb5_generate_random_block(random, 32); + + return _gss_negoex_add_nego_message(minor, ctx, ACCEPTOR_NEGO, random); +} + +static OM_uint32 +process_acceptor_nego(OM_uint32 *minor, + gssspnego_ctx ctx, + struct negoex_message *messages, + size_t nmessages) +{ + struct nego_message *msg; + + msg = _gss_negoex_locate_nego_message(messages, nmessages, ACCEPTOR_NEGO); + if (msg == NULL) { + *minor = (OM_uint32)NEGOEX_MISSING_NEGO_MESSAGE; + return GSS_S_DEFECTIVE_TOKEN; + } + + /* + * Reorder and prune our mech list to match the acceptor's list (or a + * subset of it). + */ + _gss_negoex_common_auth_schemes(ctx, msg->schemes, msg->nschemes); + + return GSS_S_COMPLETE; +} + +static void +query_meta_data(gssspnego_ctx ctx, + struct gssspnego_optimistic_ctx *opt, + gss_cred_id_t cred, + OM_uint32 req_flags) +{ + OM_uint32 major, minor; + struct negoex_auth_mech *p, *next; + + /* + * Note that if we received an optimistic context token from SPNEGO, + * then we will call QMD after ISC, rather than before. Mechanisms + * must be prepared to handle this and must not assume the context + * will be NULL on entry. + */ + HEIM_TAILQ_FOREACH_SAFE(p, &ctx->negoex_mechs, links, next) { + if (opt != NULL && memcmp(opt->scheme, p->scheme, GUID_LENGTH) == 0) + p->mech_context = opt->gssctx;; + + major = gssspi_query_meta_data(&minor, p->oid, cred, &p->mech_context, + ctx->target_name, req_flags, &p->metadata); + /* GSS_Query_meta_data failure removes mechanism from list. */ + if (major != GSS_S_COMPLETE) + _gss_negoex_delete_auth_mech(ctx, p); + } +} + +static void +exchange_meta_data(gssspnego_ctx ctx, + gss_cred_id_t cred, + OM_uint32 req_flags, + struct negoex_message *messages, + size_t nmessages) +{ + OM_uint32 major, minor; + struct negoex_auth_mech *mech; + enum message_type type; + struct exchange_message *msg; + uint32_t i; + + type = ctx->flags.local ? ACCEPTOR_META_DATA : INITIATOR_META_DATA; + + for (i = 0; i < nmessages; i++) { + if (messages[i].type != type) + continue; + msg = &messages[i].u.e; + + mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme); + if (mech == NULL) + continue; + + major = gssspi_exchange_meta_data(&minor, mech->oid, cred, + &mech->mech_context, + ctx->target_name, + req_flags, &msg->token); + /* GSS_Exchange_meta_data failure removes mechanism from list. */ + if (major != GSS_S_COMPLETE) + _gss_negoex_delete_auth_mech(ctx, mech); + } +} + +static void +release_mech_crypto(struct negoex_auth_mech *mech) +{ + krb5_context context = NULL; + + if (mech->crypto || mech->verify_crypto) + context = _gss_mg_krb5_context(); + + if (mech->crypto) { + krb5_crypto_destroy(context, mech->crypto); + mech->crypto = NULL; + } + + if (mech->verify_crypto) { + krb5_crypto_destroy(context, mech->verify_crypto); + mech->verify_crypto = NULL; + } + + mech->sent_checksum = FALSE; +} + +/* + * In the initiator, if we are processing the acceptor's first reply, discard + * the optimistic context if the acceptor ignored the optimistic token. If the + * acceptor continued the optimistic mech, discard all other mechs. + */ +static void +check_optimistic_result(gssspnego_ctx ctx, + struct negoex_message *messages, + size_t nmessages) +{ + struct negoex_auth_mech *mech; + OM_uint32 tmpMinor; + + heim_assert(ctx->flags.local && ctx->negoex_step == 2, + "NegoEx optimistic result should only be checked in second leg"); + + /* Do nothing if we didn't make an optimistic context. */ + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT) + return; + + /* + * If the acceptor used the optimistic token, it will send an acceptor + * token or a checksum (or both) in its first reply. + */ + if (_gss_negoex_locate_exchange_message(messages, nmessages, + CHALLENGE) != NULL || + _gss_negoex_locate_verify_message(messages, nmessages) != NULL) { + /* + * The acceptor continued the optimistic mech, and metadata exchange + * didn't remove it. Commit to this mechanism. + */ + _gss_negoex_select_auth_mech(ctx, mech); + } else { + /* + * The acceptor ignored the optimistic token. Restart the mech. + */ + gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER); + release_mech_crypto(mech); + mech->complete = FALSE; + } +} + +/* Perform an initiator step of the underlying mechanism exchange. */ +static OM_uint32 +mech_init(OM_uint32 *minor, + struct gssspnego_optimistic_ctx *opt, + gssspnego_ctx ctx, + gss_cred_id_t cred, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + struct negoex_message *messages, + size_t nmessages, + gss_buffer_t output_token, + int *mech_error) +{ + OM_uint32 major, first_major = GSS_S_COMPLETE, first_minor = 0; + struct negoex_auth_mech *mech = NULL; + gss_buffer_t input_token = GSS_C_NO_BUFFER; + struct exchange_message *msg; + int first_mech; + krb5_context context = _gss_mg_krb5_context(); + + output_token->value = NULL; + output_token->length = 0; + + *mech_error = FALSE; + + /* Allow disabling of optimistic token for testing. */ + if (ctx->negoex_step == 1 && + secure_getenv("NEGOEX_NO_OPTIMISTIC_TOKEN") != NULL) + return GSS_S_COMPLETE; + + if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { + *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS; + return GSS_S_FAILURE; + } + + /* + * Get the input token. The challenge could be for the optimistic mech, + * which we might have discarded in metadata exchange, so ignore the + * challenge if it doesn't match the first auth mech. + */ + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + msg = _gss_negoex_locate_exchange_message(messages, nmessages, CHALLENGE); + if (msg != NULL && GUID_EQ(msg->scheme, mech->scheme)) + input_token = &msg->token; + + if (mech->complete) + return GSS_S_COMPLETE; + + first_mech = TRUE; + major = GSS_S_BAD_MECH; + + while (!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + + /* + * If SPNEGO generated an optimistic token when probing available + * mechanisms, we can reuse it here. This avoids a potentially + * expensive and redundant call to GSS_Init_sec_context(); + */ + if (opt != NULL && memcmp(opt->scheme, mech->scheme, GUID_LENGTH) == 0) { + heim_assert(ctx->negoex_step == 1, + "SPNEGO optimistic token only valid for NegoEx first leg"); + + major = _gss_copy_buffer(minor, &opt->optimistic_token, output_token); + if (GSS_ERROR(major)) + return major; + + ctx->negotiated_mech_type = opt->negotiated_mech_type; + ctx->mech_flags = opt->optimistic_flags; + ctx->mech_time_rec = opt->optimistic_time_rec; + + mech->mech_context = opt->gssctx; + opt->gssctx = NULL; /* steal it */ + + mech->complete = opt->complete; + major = GSS_S_COMPLETE; + } else { + major = gss_init_sec_context(minor, cred, &mech->mech_context, + ctx->target_name, mech->oid, + req_flags, time_req, + input_chan_bindings, input_token, + &ctx->negotiated_mech_type, output_token, + &ctx->mech_flags, &ctx->mech_time_rec); + if (major == GSS_S_COMPLETE) + mech->complete = 1; + else if (GSS_ERROR(major)) { + gss_mg_collect_error(mech->oid, major, *minor); + *mech_error = TRUE; + } + } + if (!GSS_ERROR(major)) + return get_session_keys(minor, context, NEGOEX_BOTH_KEYS, mech); + + /* Remember the error we got from the first mech. */ + if (first_mech) { + first_major = major; + first_minor = *minor; + } + + /* If we still have multiple mechs to try, move on to the next one. */ + _gss_negoex_delete_auth_mech(ctx, mech); + first_mech = FALSE; + input_token = GSS_C_NO_BUFFER; + } + + if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { + major = first_major; + *minor = first_minor; + } + + return major; +} + +/* Perform an acceptor step of the underlying mechanism exchange. */ +static OM_uint32 +mech_accept(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_cred_id_t cred, + const gss_channel_bindings_t input_chan_bindings, + struct negoex_message *messages, + size_t nmessages, + gss_buffer_t output_token, + gss_cred_id_t *deleg_cred, + int *mech_error) +{ + OM_uint32 major, tmpMinor; + struct negoex_auth_mech *mech; + struct exchange_message *msg; + krb5_context context = _gss_mg_krb5_context(); + + heim_assert(!ctx->flags.local && !HEIM_TAILQ_EMPTY(&ctx->negoex_mechs), + "Acceptor NegoEx function called in wrong sequence"); + + *mech_error = FALSE; + + msg = _gss_negoex_locate_exchange_message(messages, nmessages, AP_REQUEST); + if (msg == NULL) { + /* + * No input token is okay on the first request or if the mech is + * complete. + */ + if (ctx->negoex_step == 1 || + HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->complete) + return GSS_S_COMPLETE; + *minor = (OM_uint32)NEGOEX_MISSING_AP_REQUEST_MESSAGE; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (ctx->negoex_step == 1) { + /* + * Ignore the optimistic token if it isn't for our most preferred + * mech. + */ + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + if (!GUID_EQ(msg->scheme, mech->scheme)) { + _gss_mg_log(10, "negoex ignored optimistic token as not for preferred mech"); + return GSS_S_COMPLETE; + } + } else { + /* The initiator has selected a mech; discard other entries. */ + mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme); + if (mech == NULL) { + *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS; + return GSS_S_FAILURE; + } + _gss_negoex_select_auth_mech(ctx, mech); + } + + if (mech->complete) + return GSS_S_COMPLETE; + + if (ctx->mech_src_name != GSS_C_NO_NAME) + gss_release_name(&tmpMinor, &ctx->mech_src_name); + if (deleg_cred && *deleg_cred != GSS_C_NO_CREDENTIAL) + gss_release_cred(&tmpMinor, deleg_cred); + + major = gss_accept_sec_context(minor, &mech->mech_context, cred, + &msg->token, input_chan_bindings, + &ctx->mech_src_name, &ctx->negotiated_mech_type, + output_token, &ctx->mech_flags, + &ctx->mech_time_rec, deleg_cred); + if (major == GSS_S_COMPLETE) + mech->complete = 1; + + if (!GSS_ERROR(major)) { + if (major == GSS_S_COMPLETE && + !gss_oid_equal(ctx->negotiated_mech_type, mech->oid)) + _gss_mg_log(1, "negoex client didn't send the mech they said they would"); + + major = get_session_keys(minor, context, NEGOEX_BOTH_KEYS, mech); + } else if (ctx->negoex_step == 1) { + gss_mg_collect_error(ctx->negotiated_mech_type, major, *minor); + *mech_error = TRUE; + + /* This was an optimistic token; pretend this never happened. */ + major = GSS_S_COMPLETE; + *minor = 0; + gss_release_buffer(&tmpMinor, output_token); + gss_delete_sec_context(&tmpMinor, &mech->mech_context, GSS_C_NO_BUFFER); + } + + return major; +} + +static krb5_keyusage +verify_keyusage(gssspnego_ctx ctx, int make_checksum) +{ + /* Of course, these are the wrong way around in the spec. */ + return (ctx->flags.local ^ !make_checksum) ? + NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM : NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM; +} + +static OM_uint32 +verify_key_flags(gssspnego_ctx ctx, int make_checksum) +{ + return (ctx->flags.local ^ make_checksum) ? + NEGOEX_SIGN_KEY : NEGOEX_VERIFY_KEY; +} + +static OM_uint32 +verify_checksum(OM_uint32 *minor, + gssspnego_ctx ctx, + struct negoex_message *messages, + size_t nmessages, + gss_const_buffer_t input_token, + int *send_alert_out) +{ + krb5_error_code ret; + struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + struct verify_message *msg; + krb5_context context = _gss_mg_krb5_context(); + krb5_crypto_iov iov[3]; + krb5_keyusage usage = verify_keyusage(ctx, FALSE); + + *send_alert_out = FALSE; + heim_assert(mech != NULL, "Invalid null mech when verifying NegoEx checksum"); + + /* + * The other party may not be ready to send a verify token yet, or (in the + * first initiator step) may send one for a mechanism we don't support. + */ + msg = _gss_negoex_locate_verify_message(messages, nmessages); + if (msg == NULL || !GUID_EQ(msg->scheme, mech->scheme)) + return GSS_S_COMPLETE; + + /* + * Last chance attempt to obtain session key for imported exported partial + * contexts (which do not carry the session key at the NegoEx layer). + */ + if (mech->verify_crypto == NULL) + get_session_keys(minor, context, verify_key_flags(ctx, FALSE), mech); + + /* + * A recoverable error may cause us to be unable to verify a token from the + * other party. In this case we should send an alert. + */ + if (mech->verify_crypto == NULL) { + *send_alert_out = TRUE; + return GSS_S_COMPLETE; + } + + if (!krb5_checksum_is_keyed(context, msg->cksum_type)) { + *minor = (OM_uint32)NEGOEX_INVALID_CHECKSUM; + return GSS_S_BAD_SIG; + } + + /* + * Verify the checksum over the existing transcript and the portion of the + * input token leading up to the verify message. + */ + iov[0].flags = KRB5_CRYPTO_TYPE_DATA; + ret = krb5_storage_to_data(ctx->negoex_transcript, &iov[0].data); + if (ret) { + *minor = ret; + return GSS_S_FAILURE; + } + + iov[1].flags = KRB5_CRYPTO_TYPE_DATA; + iov[1].data.data = input_token->value; + iov[1].data.length = msg->offset_in_token; + + iov[2].flags = KRB5_CRYPTO_TYPE_CHECKSUM; + iov[2].data.data = (uint8_t *)msg->cksum; + iov[2].data.length = msg->cksum_len; + + ret = krb5_verify_checksum_iov(context, mech->verify_crypto, usage, + iov, sizeof(iov) / sizeof(iov[0]), NULL); + if (ret == 0) + mech->verified_checksum = TRUE; + else + *minor = ret; + + krb5_data_free(&iov[0].data); + + return (ret == 0) ? GSS_S_COMPLETE : GSS_S_FAILURE; +} + +static OM_uint32 +make_checksum(OM_uint32 *minor, gssspnego_ctx ctx) +{ + krb5_error_code ret; + krb5_context context = _gss_mg_krb5_context(); + krb5_data d; + krb5_keyusage usage = verify_keyusage(ctx, TRUE); + krb5_checksum cksum; + struct negoex_auth_mech *mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + OM_uint32 major; + + heim_assert(mech != NULL, "Invalid null mech when making NegoEx checksum"); + + if (mech->crypto == NULL) { + if (mech->complete) { + /* + * Last chance attempt to obtain session key for imported exported partial + * contexts (which do not carry the session key at the NegoEx layer). + */ + get_session_keys(minor, context, verify_key_flags(ctx, TRUE), mech); + if (mech->crypto == NULL) { + *minor = (OM_uint32)NEGOEX_NO_VERIFY_KEY; + return GSS_S_UNAVAILABLE; + } + } else { + return GSS_S_COMPLETE; + } + } + + ret = krb5_storage_to_data(ctx->negoex_transcript, &d); + if (ret) { + *minor = ret; + return GSS_S_FAILURE; + } + + ret = krb5_create_checksum(context, mech->crypto, + usage, 0, d.data, d.length, &cksum); + krb5_data_free(&d); + if (ret) { + *minor = ret; + return GSS_S_FAILURE; + } + + major = _gss_negoex_add_verify_message(minor, ctx, mech->scheme, + cksum.cksumtype, + cksum.checksum.data, + cksum.checksum.length); + free_Checksum(&cksum); + + if (major == GSS_S_COMPLETE) + mech->sent_checksum = TRUE; + + return major; +} + +/* + * If the other side sent a VERIFY_NO_KEY pulse alert, clear the checksum state + * on the mechanism so that we send another VERIFY message. + */ +static void +process_alerts(gssspnego_ctx ctx, + struct negoex_message *messages, + uint32_t nmessages) +{ + struct alert_message *msg; + struct negoex_auth_mech *mech; + + msg = _gss_negoex_locate_alert_message(messages, nmessages); + if (msg != NULL && msg->verify_no_key) { + mech = _gss_negoex_locate_auth_scheme(ctx, msg->scheme); + if (mech != NULL) + release_mech_crypto(mech); + } +} + +static OM_uint32 +make_output_token(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_buffer_t mech_output_token, + int send_alert, + gss_buffer_t output_token) +{ + OM_uint32 major, tmpMinor; + struct negoex_auth_mech *mech; + enum message_type type; + off_t old_transcript_len; + + output_token->length = 0; + output_token->value = NULL; + + old_transcript_len = krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR); + + /* + * If the mech is complete and we previously sent a checksum, we just + * processed the last leg and don't need to send another token. + */ + if (mech_output_token->length == 0 && + HEIM_TAILQ_FIRST(&ctx->negoex_mechs)->sent_checksum) + return GSS_S_COMPLETE; + + if (ctx->negoex_step == 1) { + if (ctx->flags.local) + major = emit_initiator_nego(minor, ctx); + else + major = emit_acceptor_nego(minor, ctx); + if (major != GSS_S_COMPLETE) + return major; + + type = ctx->flags.local ? INITIATOR_META_DATA : ACCEPTOR_META_DATA; + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) { + if (mech->metadata.length > 0) { + major = _gss_negoex_add_exchange_message(minor, ctx, + type, mech->scheme, + &mech->metadata); + if (major != GSS_S_COMPLETE) + return major; + } + } + } + + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + + if (mech_output_token->length > 0) { + type = ctx->flags.local ? AP_REQUEST : CHALLENGE; + major = _gss_negoex_add_exchange_message(minor, ctx, + type, mech->scheme, + mech_output_token); + if (major != GSS_S_COMPLETE) + return major; + } + + if (send_alert) { + major = _gss_negoex_add_verify_no_key_alert(minor, ctx, mech->scheme); + if (major != GSS_S_COMPLETE) + return major; + } + + /* Try to add a VERIFY message if we haven't already done so. */ + if (!mech->sent_checksum) { + major = make_checksum(minor, ctx); + if (major != GSS_S_COMPLETE) + return major; + } + + heim_assert(ctx->negoex_transcript != NULL, "NegoEx context uninitialized"); + + output_token->length = + krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_CUR) - old_transcript_len; + output_token->value = malloc(output_token->length); + if (output_token->value == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + krb5_storage_seek(ctx->negoex_transcript, old_transcript_len, SEEK_SET); + + if (krb5_storage_read(ctx->negoex_transcript, + output_token->value, + output_token->length) != output_token->length) { + *minor = ERANGE; + gss_release_buffer(&tmpMinor, output_token); + return GSS_S_FAILURE; + } + + krb5_storage_seek(ctx->negoex_transcript, 0, SEEK_END); + + return GSS_S_COMPLETE; +} + +OM_uint32 +_gss_negoex_init(OM_uint32 *minor, + struct gssspnego_optimistic_ctx *opt, + gssspnego_ctx ctx, + gss_cred_id_t cred, + OM_uint32 req_flags, + OM_uint32 time_req, + const gss_channel_bindings_t input_chan_bindings, + gss_const_buffer_t input_token, + gss_buffer_t output_token) +{ + OM_uint32 major, tmpMinor; + gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; + struct negoex_message *messages = NULL; + struct negoex_auth_mech *mech; + size_t nmessages = 0; + int send_alert = FALSE, mech_error = FALSE; + + output_token->length = 0; + output_token->value = NULL; + + if (ctx->negoex_step == 0 && input_token != GSS_C_NO_BUFFER && + input_token->length != 0) + return GSS_S_DEFECTIVE_TOKEN; + + major = _gss_negoex_begin(minor, ctx); + if (major != GSS_S_COMPLETE) + goto cleanup; + + ctx->negoex_step++; + + if (input_token != GSS_C_NO_BUFFER && input_token->length > 0) { + major = _gss_negoex_parse_token(minor, ctx, input_token, + &messages, &nmessages); + if (major != GSS_S_COMPLETE) + goto cleanup; + } + + process_alerts(ctx, messages, nmessages); + + if (ctx->negoex_step == 1) { + /* Choose a random conversation ID. */ + krb5_generate_random_block(ctx->negoex_conv_id, GUID_LENGTH); + + /* Query each mech for its metadata (this may prune the mech list). */ + query_meta_data(ctx, opt, cred, req_flags); + } else if (ctx->negoex_step == 2) { + /* See if the mech processed the optimistic token. */ + check_optimistic_result(ctx, messages, nmessages); + + /* Pass the acceptor metadata to each mech to prune the list. */ + exchange_meta_data(ctx, cred, req_flags, messages, nmessages); + + /* Process the ACCEPTOR_NEGO message. */ + major = process_acceptor_nego(minor, ctx, messages, nmessages); + if (major != GSS_S_COMPLETE) + goto cleanup; + } + + /* + * Process the input token and/or produce an output token. This may prune + * the mech list, but on success there will be at least one mech entry. + */ + major = mech_init(minor, opt, ctx, cred, req_flags, time_req, + input_chan_bindings, messages, nmessages, + &mech_output_token, &mech_error); + if (major != GSS_S_COMPLETE) + goto cleanup; + heim_assert(!HEIM_TAILQ_EMPTY(&ctx->negoex_mechs), + "Invalid empty NegoEx mechanism list"); + + /* + * At this point in step 2 we have performed the metadata exchange and + * chosen a mech we can use, so discard any fallback mech entries. + */ + if (ctx->negoex_step == 2) + _gss_negoex_select_auth_mech(ctx, HEIM_TAILQ_FIRST(&ctx->negoex_mechs)); + + major = verify_checksum(minor, ctx, messages, nmessages, input_token, + &send_alert); + if (major != GSS_S_COMPLETE) + goto cleanup; + + if (input_token != GSS_C_NO_BUFFER) { + if (krb5_storage_write(ctx->negoex_transcript, + input_token->value, + input_token->length) != input_token->length) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; + } + } + + major = make_output_token(minor, ctx, &mech_output_token, send_alert, + output_token); + if (major != GSS_S_COMPLETE) + goto cleanup; + + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE : + GSS_S_CONTINUE_NEEDED; + +cleanup: + free(messages); + gss_release_buffer(&tmpMinor, &mech_output_token); + _gss_negoex_end(ctx); + + if (GSS_ERROR(major)) { + if (!mech_error) { + krb5_context context = _gss_mg_krb5_context(); + const char *emsg = krb5_get_error_message(context, *minor); + + gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + major, *minor, + "NegoEx failed to initialize security context: %s", + emsg); + krb5_free_error_message(context, emsg); + } + + _gss_negoex_release_context(ctx); + } + + return major; +} + +OM_uint32 +_gss_negoex_accept(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_cred_id_t cred, + gss_const_buffer_t input_token, + const gss_channel_bindings_t input_chan_bindings, + gss_buffer_t output_token, + gss_cred_id_t *deleg_cred) +{ + OM_uint32 major, tmpMinor; + gss_buffer_desc mech_output_token = GSS_C_EMPTY_BUFFER; + struct negoex_message *messages = NULL; + struct negoex_auth_mech *mech; + size_t nmessages; + int send_alert = FALSE, mech_error = FALSE; + + output_token->length = 0; + output_token->value = NULL; + if (deleg_cred) + *deleg_cred = GSS_C_NO_CREDENTIAL; + + if (input_token == GSS_C_NO_BUFFER || input_token->length == 0) { + major = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + + major = _gss_negoex_begin(minor, ctx); + if (major != GSS_S_COMPLETE) + goto cleanup; + + ctx->negoex_step++; + + major = _gss_negoex_parse_token(minor, ctx, input_token, + &messages, &nmessages); + if (major != GSS_S_COMPLETE) + goto cleanup; + + process_alerts(ctx, messages, nmessages); + + if (ctx->negoex_step == 1) { + /* + * Read the INITIATOR_NEGO message to prune the candidate mech list. + */ + major = process_initiator_nego(minor, ctx, messages, nmessages); + if (major != GSS_S_COMPLETE) + goto cleanup; + + /* + * Pass the initiator metadata to each mech to prune the list, and + * query each mech for its acceptor metadata (which may also prune the + * list). + */ + exchange_meta_data(ctx, cred, 0, messages, nmessages); + query_meta_data(ctx, NULL, cred, 0); + + if (HEIM_TAILQ_EMPTY(&ctx->negoex_mechs)) { + *minor = (OM_uint32)NEGOEX_NO_AVAILABLE_MECHS; + major = GSS_S_FAILURE; + goto cleanup; + } + } + + /* + * Process the input token and possibly produce an output token. This may + * prune the list to a single mech. Continue on error if an output token + * is generated, so that we send the token to the initiator. + */ + major = mech_accept(minor, ctx, cred, input_chan_bindings, + messages, nmessages, &mech_output_token, + deleg_cred, &mech_error); + if (major != GSS_S_COMPLETE && mech_output_token.length == 0) + goto cleanup; + + if (major == GSS_S_COMPLETE) { + major = verify_checksum(minor, ctx, messages, nmessages, input_token, + &send_alert); + if (major != GSS_S_COMPLETE) + goto cleanup; + } + + if (krb5_storage_write(ctx->negoex_transcript, + input_token->value, + input_token->length) != input_token->length) { + major = GSS_S_FAILURE; + *minor = ENOMEM; + goto cleanup; + } + + major = make_output_token(minor, ctx, &mech_output_token, send_alert, + output_token); + if (major != GSS_S_COMPLETE) + goto cleanup; + + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + major = (mech->complete && mech->verified_checksum) ? GSS_S_COMPLETE : + GSS_S_CONTINUE_NEEDED; + +cleanup: + free(messages); + gss_release_buffer(&tmpMinor, &mech_output_token); + _gss_negoex_end(ctx); + + if (GSS_ERROR(major)) { + if (!mech_error) { + krb5_context context = _gss_mg_krb5_context(); + const char *emsg = krb5_get_error_message(context, *minor); + + gss_mg_set_error_string(GSS_SPNEGO_MECHANISM, + major, *minor, + "NegoEx failed to accept security context: %s", + emsg); + krb5_free_error_message(context, emsg); + } + + _gss_negoex_release_context(ctx); + } + + return major; +} diff --git a/lib/gssapi/spnego/negoex_err.et b/lib/gssapi/spnego/negoex_err.et new file mode 100644 index 000000000000..99a8a2ec3795 --- /dev/null +++ b/lib/gssapi/spnego/negoex_err.et @@ -0,0 +1,25 @@ +# +# NegoEx error messages +# + +id "$Id$" + +error_table ngex + +prefix NEGOEX + +error_code INVALID_MESSAGE_SIGNATURE, "Invalid NegoEx signature" +error_code INVALID_MESSAGE_TYPE, "Invalid NegoEx message type" +error_code INVALID_MESSAGE_SIZE, "Invalid NegoEx message size" +error_code INVALID_CONVERSATION_ID, "Invalid NegoEx conversation ID" +error_code AUTH_SCHEME_NOT_FOUND, "NegoEx authentication scheme not found" +error_code MISSING_NEGO_MESSAGE, "Missing NegoEx negotiate message" +error_code MISSING_AP_REQUEST_MESSAGE, "Missing NegoEx authentication protocol request message" +error_code NO_AVAILABLE_MECHS, "No mutually supported NegoEx authentication schemes" +error_code NO_VERIFY_KEY, "No NegoEx verify key" +error_code UNKNOWN_CHECKSUM_SCHEME, "Unknown NegoEx checksum scheme" +error_code INVALID_CHECKSUM, "Invalid NegoEx checksum" +error_code UNSUPPORTED_CRITICAL_EXTENSION, "Unsupported critical NegoEx extension" +error_code UNSUPPORTED_VERSION, "Unsupported NegoEx version" +error_code MESSAGE_OUT_OF_SEQUENCE, "NegoEx message out of sequence" + diff --git a/lib/gssapi/spnego/negoex_locl.h b/lib/gssapi/spnego/negoex_locl.h new file mode 100644 index 000000000000..3e0d29a31d87 --- /dev/null +++ b/lib/gssapi/spnego/negoex_locl.h @@ -0,0 +1,127 @@ +/* + * 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. + */ + +#ifndef NEGOEX_LOCL_H +#define NEGOEX_LOCL_H + +#include <negoex_err.h> + +struct gssspnego_ctx_desc; + +#define MESSAGE_SIGNATURE 0x535458454F47454EULL + +#define EXTENSION_LENGTH 12 + +#define EXTENSION_FLAG_CRITICAL 0x80000000 + +#define CHECKSUM_SCHEME_RFC3961 1 + +#define NEGOEX_KEYUSAGE_INITIATOR_CHECKSUM 23 +#define NEGOEX_KEYUSAGE_ACCEPTOR_CHECKSUM 25 + +#define CHECKSUM_HEADER_LENGTH 20 + +#define GUID_LENGTH 16 + +typedef uint8_t auth_scheme[GUID_LENGTH]; +typedef uint8_t conversation_id[GUID_LENGTH]; +#define GUID_EQ(a, b) (memcmp(a, b, GUID_LENGTH) == 0) + +#define NEGO_MESSAGE_HEADER_LENGTH 96 +#define EXCHANGE_MESSAGE_HEADER_LENGTH 64 +#define VERIFY_MESSAGE_HEADER_LENGTH 80 +#define ALERT_MESSAGE_HEADER_LENGTH 72 +#define ALERT_LENGTH 12 +#define ALERT_PULSE_LENGTH 8 + +#define ALERT_TYPE_PULSE 1 +#define ALERT_VERIFY_NO_KEY 1 + +enum message_type { + INITIATOR_NEGO = 0, /* NEGO_MESSAGE */ + ACCEPTOR_NEGO, /* NEGO_MESSAGE */ + INITIATOR_META_DATA, /* EXCHANGE_MESSAGE */ + ACCEPTOR_META_DATA, /* EXCHANGE_MESSAGE */ + CHALLENGE, /* EXCHANGE_MESSAGE */ + AP_REQUEST, /* EXCHANGE_MESSAGE */ + VERIFY, /* VERIFY_MESSAGE */ + ALERT, /* ALERT */ +}; + +struct nego_message { + uint8_t random[32]; + const uint8_t *schemes; + uint16_t nschemes; +}; + +struct exchange_message { + auth_scheme scheme; + gss_buffer_desc token; +}; + +struct verify_message { + auth_scheme scheme; + uint32_t cksum_type; + const uint8_t *cksum; + size_t cksum_len; + size_t offset_in_token; +}; + +struct alert_message { + auth_scheme scheme; + int verify_no_key; +}; + +struct negoex_message { + uint32_t type; + union { + struct nego_message n; + struct exchange_message e; + struct verify_message v; + struct alert_message a; + } u; +}; + +struct negoex_auth_mech { + HEIM_TAILQ_ENTRY(negoex_auth_mech) links; + gss_OID oid; + auth_scheme scheme; + gss_ctx_id_t mech_context; + gss_buffer_desc metadata; + krb5_crypto crypto; + krb5_crypto verify_crypto; + int complete; + int sent_checksum; + int verified_checksum; +}; + +#define NEGOEX_LOG_LEVEL 10 + +#endif /* NEGOEX_LOCL_H */ diff --git a/lib/gssapi/spnego/negoex_util.c b/lib/gssapi/spnego/negoex_util.c new file mode 100644 index 000000000000..e6c9a139248b --- /dev/null +++ b/lib/gssapi/spnego/negoex_util.c @@ -0,0 +1,1047 @@ +/* + * 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" + +/* + * SPNEGO expects to find the active mech context in ctx->negotiated_ctx_id, + * but the metadata exchange APIs force us to have one mech context per mech + * entry. To address this mismatch, move the active mech context (if we have + * one) to ctx->negotiated_ctx_id at the end of NegoEx processing. + */ +void +_gss_negoex_end(gssspnego_ctx ctx) +{ + struct negoex_auth_mech *mech; + + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + if (mech == NULL || mech->mech_context == GSS_C_NO_CONTEXT) + return; + + heim_assert(ctx->negotiated_ctx_id == GSS_C_NO_CONTEXT, + "SPNEGO/NegoEx context mismatch"); + ctx->negotiated_ctx_id = mech->mech_context; + mech->mech_context = GSS_C_NO_CONTEXT; +} + +OM_uint32 +_gss_negoex_begin(OM_uint32 *minor, gssspnego_ctx ctx) +{ + struct negoex_auth_mech *mech; + + if (ctx->negoex_transcript != NULL) { + /* + * The context is already initialized for NegoEx; undo what + * _gss_negoex_end() did, if applicable. + */ + if (ctx->negotiated_ctx_id != GSS_C_NO_CONTEXT) { + mech = HEIM_TAILQ_FIRST(&ctx->negoex_mechs); + heim_assert(mech != NULL && mech->mech_context == GSS_C_NO_CONTEXT, + "NegoEx/SPNEGO context mismatch"); + mech->mech_context = ctx->negotiated_ctx_id; + ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT; + } + return GSS_S_COMPLETE; + } + + ctx->negoex_transcript = krb5_storage_emem(); + if (ctx->negoex_transcript == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + krb5_storage_set_byteorder(ctx->negoex_transcript, + KRB5_STORAGE_BYTEORDER_LE); + + return GSS_S_COMPLETE; +} + +static void +release_all_mechs(gssspnego_ctx ctx, krb5_context context) +{ + struct negoex_auth_mech *mech, *next; + struct negoex_auth_mech *prev = NULL; + + HEIM_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) { + if (prev) + _gss_negoex_release_auth_mech(context, prev); + prev = mech; + } + if (prev) + _gss_negoex_release_auth_mech(context, mech); + + HEIM_TAILQ_INIT(&ctx->negoex_mechs); +} + +void +_gss_negoex_release_context(gssspnego_ctx ctx) +{ + krb5_context context = _gss_mg_krb5_context(); + + if (ctx->negoex_transcript != NULL) { + krb5_storage_free(ctx->negoex_transcript); + ctx->negoex_transcript = NULL; + } + + release_all_mechs(ctx, context); +} + +static int +guid_to_string(const uint8_t guid[16], char *buffer, size_t bufsiz) +{ + uint32_t data1; + uint16_t data2, data3; + + _gss_mg_decode_le_uint32(&guid[0], &data1); + _gss_mg_decode_le_uint16(&guid[4], &data2); + _gss_mg_decode_le_uint16(&guid[6], &data3); + + return snprintf(buffer, bufsiz, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + data1, data2, data3, guid[8], guid[9], guid[10], guid[11], + guid[12], guid[13], guid[14], guid[15]); +} + +void +_gss_negoex_log_auth_scheme(int initiator, + int index, + const auth_scheme scheme) +{ + char scheme_str[37]; + + guid_to_string(scheme, scheme_str, sizeof(scheme_str)); + + _gss_mg_log(NEGOEX_LOG_LEVEL, + "negoex: %s authentication scheme %d %s", + initiator ? "proposing" : "received", index, scheme_str); +} + +void +_gss_negoex_log_message(int direction, + enum message_type type, + const conversation_id conv_id, + unsigned int seqnum, + unsigned int header_len, + unsigned int msg_len) +{ + char conv_str[37]; + char *typestr; + + if (type == INITIATOR_NEGO) + typestr = "INITIATOR_NEGO"; + else if (type == ACCEPTOR_NEGO) + typestr = "ACCEPTOR_NEGO"; + else if (type == INITIATOR_META_DATA) + typestr = "INITIATOR_META_DATA"; + else if (type == ACCEPTOR_META_DATA) + typestr = "ACCEPTOR_META_DATA"; + else if (type == CHALLENGE) + typestr = "CHALLENGE"; + else if (type == AP_REQUEST) + typestr = "AP_REQUEST"; + else if (type == VERIFY) + typestr = "VERIFY"; + else if (type == ALERT) + typestr = "ALERT"; + else + typestr = "UNKNOWN"; + + guid_to_string(conv_id, conv_str, sizeof(conv_str)); + _gss_mg_log(NEGOEX_LOG_LEVEL, + "negoex: %s (%d)%s conversation %s", + direction ? "received" : "sending", + seqnum, typestr, conv_str); +} + +/* + * Check that the described vector lies within the message, and return a + * pointer to its first element. + */ +static inline const uint8_t * +vector_base(size_t offset, size_t count, size_t width, + const uint8_t *msg_base, size_t msg_len) +{ + if (offset > msg_len || count > (msg_len - offset) / width) + return NULL; + return msg_base + offset; +} + +static OM_uint32 +parse_nego_message(OM_uint32 *minor, krb5_storage *sp, + const uint8_t *msg_base, size_t msg_len, + struct nego_message *msg) +{ + krb5_error_code ret; + const uint8_t *p; + uint64_t protocol_version; + uint32_t extension_type, offset; + uint16_t count; + size_t i; + + if (krb5_storage_read(sp, msg->random, + sizeof(msg->random)) != sizeof(msg->random)) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint64(sp, &protocol_version); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (protocol_version != 0) { + *minor = (OM_uint32)NEGOEX_UNSUPPORTED_VERSION; + return GSS_S_UNAVAILABLE; + } + + ret = krb5_ret_uint32(sp, &offset); + if (ret == 0) + ret = krb5_ret_uint16(sp, &count); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + msg->schemes = vector_base(offset, count, GUID_LENGTH, msg_base, msg_len); + msg->nschemes = count; + if (msg->schemes == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint32(sp, &offset); + if (ret == 0) + ret = krb5_ret_uint16(sp, &count); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + p = vector_base(offset, count, EXTENSION_LENGTH, msg_base, msg_len); + for (i = 0; i < count; i++) { + _gss_mg_decode_le_uint32(p + i * EXTENSION_LENGTH, &extension_type); + if (extension_type & EXTENSION_FLAG_CRITICAL) { + *minor = (OM_uint32)NEGOEX_UNSUPPORTED_CRITICAL_EXTENSION; + return GSS_S_UNAVAILABLE; + } + } + + return GSS_S_COMPLETE; +} + +static OM_uint32 +parse_exchange_message(OM_uint32 *minor, krb5_storage *sp, + const uint8_t *msg_base, size_t msg_len, + struct exchange_message *msg) +{ + krb5_error_code ret; + const uint8_t *p; + uint32_t offset; + uint16_t len; + + if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) != GUID_LENGTH) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint32(sp, &offset); + if (ret == 0) + ret = krb5_ret_uint16(sp, &len); + if (ret) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + p = vector_base(offset, len, 1, msg_base, msg_len); + if (p == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + msg->token.value = (void *)p; + msg->token.length = len; + + return GSS_S_COMPLETE; +} + +static OM_uint32 +parse_verify_message(OM_uint32 *minor, krb5_storage *sp, + const uint8_t *msg_base, size_t msg_len, + size_t token_offset, struct verify_message *msg) +{ + krb5_error_code ret; + uint32_t hdrlen, cksum_scheme; + uint32_t offset, len; + + if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) == GUID_LENGTH) + ret = 0; + else + ret = NEGOEX_INVALID_MESSAGE_SIZE; + if (ret == 0) + ret = krb5_ret_uint32(sp, &hdrlen); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (hdrlen != CHECKSUM_HEADER_LENGTH) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint32(sp, &cksum_scheme); + if (ret == 0) + ret = krb5_ret_uint32(sp, &msg->cksum_type); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + if (cksum_scheme != CHECKSUM_SCHEME_RFC3961) { + *minor = (OM_uint32)NEGOEX_UNKNOWN_CHECKSUM_SCHEME; + return GSS_S_UNAVAILABLE; + } + + ret = krb5_ret_uint32(sp, &offset); + if (ret == 0) + ret = krb5_ret_uint32(sp, &len); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + msg->cksum = vector_base(offset, len, 1, msg_base, msg_len); + msg->cksum_len = len; + if (msg->cksum == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + msg->offset_in_token = token_offset; + return GSS_S_COMPLETE; +} + +static OM_uint32 +storage_from_memory(OM_uint32 *minor, + const uint8_t *data, + size_t length, + krb5_storage **sp) +{ + *sp = krb5_storage_from_readonly_mem(data, length); + if (*sp == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + krb5_storage_set_byteorder(*sp, KRB5_STORAGE_BYTEORDER_LE); + krb5_storage_set_eof_code(*sp, NEGOEX_INVALID_MESSAGE_SIZE); + + return 0; +} + +static OM_uint32 +parse_alert_message(OM_uint32 *minor, krb5_storage *sp, + const uint8_t *msg_base, size_t msg_len, + struct alert_message *msg) +{ + OM_uint32 major; + krb5_error_code ret; + const uint8_t *p; + uint32_t error_code, atype; + uint32_t alerts_offset, nalerts, value_offset, value_len; + size_t i; + krb5_storage *alerts; + + if (krb5_storage_read(sp, msg->scheme, GUID_LENGTH) == GUID_LENGTH) + ret = 0; + else + ret = NEGOEX_INVALID_MESSAGE_SIZE; + if (ret == 0) + ret = krb5_ret_uint32(sp, &error_code); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + ret = krb5_ret_uint32(sp, &alerts_offset); + if (ret == 0) + ret = krb5_ret_uint32(sp, &nalerts); + if (ret) { + *minor = ret; + return GSS_S_DEFECTIVE_TOKEN; + } + + p = vector_base(alerts_offset, nalerts, ALERT_LENGTH, msg_base, msg_len); + if (p == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + return GSS_S_DEFECTIVE_TOKEN; + } + + /* Look for a VERIFY_NO_KEY pulse alert in the alerts vector. */ + msg->verify_no_key = FALSE; + + major = storage_from_memory(minor, p, nalerts * ALERT_LENGTH, &alerts); + if (major != GSS_S_COMPLETE) + return major; + + for (i = 0; i < nalerts; i++) { + ret = krb5_ret_uint32(alerts, &atype); + if (ret == 0) + ret = krb5_ret_uint32(alerts, &value_offset); + if (ret == 0) + ret = krb5_ret_uint32(alerts, &value_len); + if (ret) { + *minor = ret; + major = GSS_S_DEFECTIVE_TOKEN; + break; + } + + p = vector_base(value_offset, value_len, 1, msg_base, msg_len); + if (p == NULL) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + major = GSS_S_DEFECTIVE_TOKEN; + break; + } + + if (atype == ALERT_TYPE_PULSE && value_len >= ALERT_PULSE_LENGTH) { + krb5_storage *pulse; + uint32_t hdrlen, reason; + + major = storage_from_memory(minor, p, value_len, &pulse); + if (major != GSS_S_COMPLETE) + break; + + ret = krb5_ret_uint32(pulse, &hdrlen); + if (ret == 0) + ret = krb5_ret_uint32(pulse, &reason); + krb5_storage_free(pulse); + if (ret) { + *minor = ret; + major = GSS_S_DEFECTIVE_TOKEN; + break; + } + + if (reason == ALERT_VERIFY_NO_KEY) + msg->verify_no_key = TRUE; + } + } + + krb5_storage_free(alerts); + + return major; +} + +static OM_uint32 +parse_message(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_const_buffer_t token, + size_t *token_offset, + struct negoex_message *msg) +{ + OM_uint32 major; + krb5_error_code ret; + krb5_storage *sp; + uint64_t signature; + uint32_t header_len, msg_len; + uint32_t type, seqnum; + conversation_id conv_id; + size_t token_remaining = token->length - *token_offset; + const uint8_t *msg_base = (uint8_t *)token->value + *token_offset; + + major = storage_from_memory(minor, msg_base, token_remaining, &sp); + if (major != GSS_S_COMPLETE) + return major; + + major = GSS_S_DEFECTIVE_TOKEN; + + ret = krb5_ret_uint64(sp, &signature); + if (ret == 0) + ret = krb5_ret_uint32(sp, &type); + if (ret == 0) + ret = krb5_ret_uint32(sp, &seqnum); + if (ret == 0) + ret = krb5_ret_uint32(sp, &header_len); + if (ret == 0) + ret = krb5_ret_uint32(sp, &msg_len); + if (ret == 0) { + if (krb5_storage_read(sp, conv_id, GUID_LENGTH) != GUID_LENGTH) + ret = NEGOEX_INVALID_MESSAGE_SIZE; + } + if (ret) { + *minor = ret; + goto cleanup; + } + + if (msg_len > token_remaining || header_len > msg_len) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + goto cleanup; + } + if (signature != MESSAGE_SIGNATURE) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIGNATURE; + goto cleanup; + } + if (seqnum != ctx->negoex_seqnum) { + *minor = (OM_uint32)NEGOEX_MESSAGE_OUT_OF_SEQUENCE; + goto cleanup; + } + if (seqnum == 0) { + memcpy(ctx->negoex_conv_id, conv_id, GUID_LENGTH); + } else if (!GUID_EQ(conv_id, ctx->negoex_conv_id)) { + *minor = (OM_uint32)NEGOEX_INVALID_CONVERSATION_ID; + goto cleanup; + } + + krb5_storage_truncate(sp, msg_len); + + msg->type = type; + if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) { + major = parse_nego_message(minor, sp, msg_base, msg_len, &msg->u.n); + } else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA || + type == CHALLENGE || type == AP_REQUEST) { + major = parse_exchange_message(minor, sp, msg_base, msg_len, + &msg->u.e); + } else if (type == VERIFY) { + major = parse_verify_message(minor, sp, msg_base, msg_len, + msg_base - (uint8_t *)token->value, + &msg->u.v); + } else if (type == ALERT) { + major = parse_alert_message(minor, sp, msg_base, msg_len, &msg->u.a); + } else { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_TYPE; + goto cleanup; + } + +cleanup: + krb5_storage_free(sp); + + if (major == GSS_S_COMPLETE) { + _gss_negoex_log_message(1, msg->type, + ctx->negoex_conv_id, ctx->negoex_seqnum, + header_len, msg_len); + ctx->negoex_seqnum++; + *token_offset += msg_len; + } + + return major; +} + +/* + * Parse token into an array of negoex_message structures. All pointer fields + * within the parsed messages are aliases into token, so the result can be + * freed with free(). An unknown protocol version, a critical extension, or an + * unknown checksum scheme will cause a parsing failure. Increment the + * sequence number in ctx for each message, and record and check the + * conversation ID in ctx as appropriate. + */ +OM_uint32 +_gss_negoex_parse_token(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_const_buffer_t token, + struct negoex_message **messages_out, + size_t *count_out) +{ + OM_uint32 major = GSS_S_DEFECTIVE_TOKEN; + size_t count = 0; + size_t token_offset = 0; + struct negoex_message *messages = NULL, *newptr; + + *messages_out = NULL; + *count_out = 0; + heim_assert(token != GSS_C_NO_BUFFER, "Invalid null NegoEx input token"); + + while (token_offset < token->length) { + newptr = realloc(messages, (count + 1) * sizeof(*newptr)); + if (newptr == NULL) { + free(messages); + *minor = ENOMEM; + return GSS_S_FAILURE; + } + messages = newptr; + + major = parse_message(minor, ctx, token, &token_offset, + &messages[count]); + if (major != GSS_S_COMPLETE) + break; + + count++; + } + + if (token_offset != token->length) { + *minor = (OM_uint32)NEGOEX_INVALID_MESSAGE_SIZE; + major = GSS_S_DEFECTIVE_TOKEN; + } + if (major != GSS_S_COMPLETE) { + free(messages); + return major; + } + + *messages_out = messages; + *count_out = count; + return GSS_S_COMPLETE; +} + +static struct negoex_message * +locate_message(struct negoex_message *messages, size_t nmessages, + enum message_type type) +{ + uint32_t i; + + for (i = 0; i < nmessages; i++) { + if (messages[i].type == type) + return &messages[i]; + } + + return NULL; +} + +struct nego_message * +_gss_negoex_locate_nego_message(struct negoex_message *messages, + size_t nmessages, + enum message_type type) +{ + struct negoex_message *msg = locate_message(messages, nmessages, type); + + return (msg == NULL) ? NULL : &msg->u.n; +} + +struct exchange_message * +_gss_negoex_locate_exchange_message(struct negoex_message *messages, + size_t nmessages, + enum message_type type) +{ + struct negoex_message *msg = locate_message(messages, nmessages, type); + + return (msg == NULL) ? NULL : &msg->u.e; +} + +struct verify_message * +_gss_negoex_locate_verify_message(struct negoex_message *messages, + size_t nmessages) +{ + struct negoex_message *msg = locate_message(messages, nmessages, VERIFY); + + return (msg == NULL) ? NULL : &msg->u.v; +} + +struct alert_message * +_gss_negoex_locate_alert_message(struct negoex_message *messages, + size_t nmessages) +{ + struct negoex_message *msg = locate_message(messages, nmessages, ALERT); + + return (msg == NULL) ? NULL : &msg->u.a; +} + +/* + * Add the encoding of a MESSAGE_HEADER structure to buf, given the number of + * bytes of the payload following the full header. Increment the sequence + * number in ctx. Set *payload_start_out to the position of the payload within + * the message. + */ +static OM_uint32 +put_message_header(OM_uint32 *minor, gssspnego_ctx ctx, + enum message_type type, uint32_t payload_len, + uint32_t *payload_start_out) +{ + krb5_error_code ret; + size_t header_len = 0; + + if (type == INITIATOR_NEGO || type == ACCEPTOR_NEGO) + header_len = NEGO_MESSAGE_HEADER_LENGTH; + else if (type == INITIATOR_META_DATA || type == ACCEPTOR_META_DATA || + type == CHALLENGE || type == AP_REQUEST) + header_len = EXCHANGE_MESSAGE_HEADER_LENGTH; + else if (type == VERIFY) + header_len = VERIFY_MESSAGE_HEADER_LENGTH; + else if (type == ALERT) + header_len = ALERT_MESSAGE_HEADER_LENGTH; + else + heim_assert(0, "Invalid NegoEx message type"); + + /* Signature */ + CHECK(ret, krb5_store_uint64(ctx->negoex_transcript, MESSAGE_SIGNATURE)); + /* MessageType */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, type)); + /* SequenceNum */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ctx->negoex_seqnum)); + /* cbHeaderLength */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, header_len)); + /* cbMessageLength */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, header_len + payload_len)); + /* ConversationId */ + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, ctx->negoex_conv_id, GUID_LENGTH)); + + _gss_negoex_log_message(0, type, + ctx->negoex_conv_id, ctx->negoex_seqnum, + header_len, + header_len + payload_len); + + ctx->negoex_seqnum++; + + *payload_start_out = header_len; + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + +OM_uint32 +_gss_negoex_add_nego_message(OM_uint32 *minor, + gssspnego_ctx ctx, + enum message_type type, + uint8_t random[32]) +{ + OM_uint32 major; + krb5_error_code ret; + struct negoex_auth_mech *mech; + uint32_t payload_start; + uint16_t nschemes; + + nschemes = 0; + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) + nschemes++; + + major = put_message_header(minor, ctx, type, + nschemes * GUID_LENGTH, &payload_start); + if (major != GSS_S_COMPLETE) + return major; + + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, random, 32)); + /* ProtocolVersion */ + CHECK(ret, krb5_store_uint64(ctx->negoex_transcript, 0)); + /* AuthSchemes vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, nschemes)); + /* Extensions vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, 0)); + /* Four bytes of padding to reach a multiple of 8 bytes. */ + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, "\0\0\0\0", 4)); + + /* Payload (auth schemes) */ + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) { + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, mech->scheme, GUID_LENGTH)); + } + + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + +OM_uint32 +_gss_negoex_add_exchange_message(OM_uint32 *minor, + gssspnego_ctx ctx, + enum message_type type, + const auth_scheme scheme, + gss_buffer_t token) +{ + OM_uint32 major; + krb5_error_code ret; + uint32_t payload_start; + + major = put_message_header(minor, ctx, type, token->length, &payload_start); + if (major != GSS_S_COMPLETE) + return major; + + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH)); + /* Exchange byte vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, token->length)); + /* Payload (token) */ + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, token->value, token->length)); + + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + +OM_uint32 +_gss_negoex_add_verify_message(OM_uint32 *minor, + gssspnego_ctx ctx, + const auth_scheme scheme, + uint32_t cksum_type, + const uint8_t *cksum, + uint32_t cksum_len) +{ + OM_uint32 major; + krb5_error_code ret; + uint32_t payload_start; + + major = put_message_header(minor, ctx, VERIFY, cksum_len, &payload_start); + if (major != GSS_S_COMPLETE) + return major; + + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, CHECKSUM_HEADER_LENGTH)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, CHECKSUM_SCHEME_RFC3961)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, cksum_type)); + /* ChecksumValue vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, cksum_len)); + /* Four bytes of padding to reach a multiple of 8 bytes. */ + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, "\0\0\0\0", 4)); + /* Payload (checksum contents) */ + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, cksum, cksum_len)); + + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + +/* + * Add an ALERT_MESSAGE containing a single ALERT_TYPE_PULSE alert with the + * reason ALERT_VERIFY_NO_KEY. + */ +OM_uint32 +_gss_negoex_add_verify_no_key_alert(OM_uint32 *minor, + gssspnego_ctx ctx, + const auth_scheme scheme) +{ + OM_uint32 major; + krb5_error_code ret; + uint32_t payload_start; + + major = put_message_header(minor, ctx, + ALERT, ALERT_LENGTH + ALERT_PULSE_LENGTH, + &payload_start); + if (major != GSS_S_COMPLETE) + return major; + + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, scheme, GUID_LENGTH)); + /* ErrorCode */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, 0)); + /* Alerts vector */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, payload_start)); + CHECK(ret, krb5_store_uint16(ctx->negoex_transcript, 1)); + /* Six bytes of padding to reach a multiple of 8 bytes. */ + CHECK(ret, krb5_store_bytes(ctx->negoex_transcript, "\0\0\0\0\0\0", 6)); + /* Payload part 1: a single ALERT element */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_TYPE_PULSE)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, + payload_start + ALERT_LENGTH)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_PULSE_LENGTH)); + /* Payload part 2: ALERT_PULSE */ + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_PULSE_LENGTH)); + CHECK(ret, krb5_store_uint32(ctx->negoex_transcript, ALERT_VERIFY_NO_KEY)); + + return GSS_S_COMPLETE; + +fail: + *minor = ret; + return GSS_S_FAILURE; +} + + +void +_gss_negoex_release_auth_mech(krb5_context context, + struct negoex_auth_mech *mech) +{ + OM_uint32 tmpmin; + + if (mech == NULL) + return; + + gss_delete_sec_context(&tmpmin, &mech->mech_context, NULL); + gss_release_oid(&tmpmin, &mech->oid); + gss_release_buffer(&tmpmin, &mech->metadata); + if (mech->crypto) + krb5_crypto_destroy(context, mech->crypto); + if (mech->verify_crypto) + krb5_crypto_destroy(context, mech->verify_crypto); + + free(mech); +} + +void +_gss_negoex_delete_auth_mech(gssspnego_ctx ctx, + struct negoex_auth_mech *mech) +{ + krb5_context context = _gss_mg_krb5_context(); + + HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links); + _gss_negoex_release_auth_mech(context, mech); +} + +/* Remove all auth mech entries except for mech from ctx->mechs. */ +void +_gss_negoex_select_auth_mech(gssspnego_ctx ctx, + struct negoex_auth_mech *mech) +{ + krb5_context context = _gss_mg_krb5_context(); + + heim_assert(mech != NULL, "Invalid null NegoEx mech"); + HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links); + release_all_mechs(ctx, context); + HEIM_TAILQ_INSERT_HEAD(&ctx->negoex_mechs, mech, links); +} + +OM_uint32 +_gss_negoex_add_auth_mech(OM_uint32 *minor, + gssspnego_ctx ctx, + gss_const_OID oid, + auth_scheme scheme) +{ + OM_uint32 major; + struct negoex_auth_mech *mech; + + mech = calloc(1, sizeof(*mech)); + if (mech == NULL) { + *minor = ENOMEM; + return GSS_S_FAILURE; + } + + major = gss_duplicate_oid(minor, (gss_OID)oid, &mech->oid); + if (major != GSS_S_COMPLETE) { + free(mech); + return major; + } + + memcpy(mech->scheme, scheme, GUID_LENGTH); + + HEIM_TAILQ_INSERT_TAIL(&ctx->negoex_mechs, mech, links); + + *minor = 0; + return GSS_S_COMPLETE; +} + +struct negoex_auth_mech * +_gss_negoex_locate_auth_scheme(gssspnego_ctx ctx, + const auth_scheme scheme) +{ + struct negoex_auth_mech *mech; + + HEIM_TAILQ_FOREACH(mech, &ctx->negoex_mechs, links) { + if (GUID_EQ(mech->scheme, scheme)) + return mech; + } + + return NULL; +} + +/* + * Prune ctx->mechs to the schemes present in schemes, and reorder them to + * match its order. + */ +void +_gss_negoex_common_auth_schemes(gssspnego_ctx ctx, + const uint8_t *schemes, + uint16_t nschemes) +{ + struct negoex_mech_list list; + struct negoex_auth_mech *mech; + uint16_t i; + krb5_context context = _gss_mg_krb5_context(); + + /* Construct a new list in the order of schemes. */ + HEIM_TAILQ_INIT(&list); + for (i = 0; i < nschemes; i++) { + mech = _gss_negoex_locate_auth_scheme(ctx, schemes + i * GUID_LENGTH); + if (mech == NULL) + continue; + HEIM_TAILQ_REMOVE(&ctx->negoex_mechs, mech, links); + HEIM_TAILQ_INSERT_TAIL(&list, mech, links); + } + + /* Release any leftover entries and replace the context list. */ + release_all_mechs(ctx, context); + HEIM_TAILQ_CONCAT(&ctx->negoex_mechs, &list, links); +} + +/* + * Prune ctx->mechs to the schemes present in schemes, but do not change + * their order. + */ +void +_gss_negoex_restrict_auth_schemes(gssspnego_ctx ctx, + const uint8_t *schemes, + uint16_t nschemes) +{ + struct negoex_auth_mech *mech, *next; + uint16_t i; + int found; + + HEIM_TAILQ_FOREACH_SAFE(mech, &ctx->negoex_mechs, links, next) { + found = FALSE; + for (i = 0; i < nschemes && !found; i++) { + if (GUID_EQ(mech->scheme, schemes + i * GUID_LENGTH)) + found = TRUE; + } + + if (!found) + _gss_negoex_delete_auth_mech(ctx, mech); + } +} + +/* + * Return the OID of the current NegoEx mechanism. + */ +struct negoex_auth_mech * +_gss_negoex_negotiated_mech(gssspnego_ctx ctx) +{ + return HEIM_TAILQ_FIRST(&ctx->negoex_mechs); +} + +/* + * Returns TRUE if mechanism can be negotiated by both NegoEx and SPNEGO + */ + +int +_gss_negoex_and_spnego_mech_p(gss_const_OID mech) +{ + OM_uint32 major, minor; + gss_OID_set attrs = GSS_C_NO_OID_SET; + int negoex_and_spnego = FALSE; + + major = gss_inquire_attrs_for_mech(&minor, mech, &attrs, NULL); + if (major == GSS_S_COMPLETE) { + gss_test_oid_set_member(&minor, GSS_C_MA_NEGOEX_AND_SPNEGO, + attrs, &negoex_and_spnego); + gss_release_oid_set(&minor, &attrs); + } + + return negoex_and_spnego; +} + +int +_gss_negoex_mech_p(gss_const_OID mech) +{ + OM_uint32 minor; + auth_scheme scheme; + + return gssspi_query_mechanism_info(&minor, mech, + scheme) == GSS_S_COMPLETE; +} + diff --git a/lib/gssapi/spnego/spnego-private.h b/lib/gssapi/spnego/spnego-private.h deleted file mode 100644 index 7486b68fc48b..000000000000 --- a/lib/gssapi/spnego/spnego-private.h +++ /dev/null @@ -1,323 +0,0 @@ -/* This is a generated file */ -#ifndef __spnego_private_h__ -#define __spnego_private_h__ - -#include <stdarg.h> - -gssapi_mech_interface -__gss_spnego_initialize (void); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_accept_sec_context ( - OM_uint32 * /*minor_status*/, - gss_ctx_id_t * /*context_handle*/, - gss_const_cred_id_t /*acceptor_cred_handle*/, - const gss_buffer_t /*input_token_buffer*/, - const gss_channel_bindings_t /*input_chan_bindings*/, - gss_name_t * /*src_name*/, - gss_OID * /*mech_type*/, - gss_buffer_t /*output_token*/, - OM_uint32 * /*ret_flags*/, - OM_uint32 * /*time_rec*/, - gss_cred_id_t *delegated_cred_handle ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_acquire_cred ( - OM_uint32 */*minor_status*/, - gss_const_name_t /*desired_name*/, - OM_uint32 /*time_req*/, - const gss_OID_set /*desired_mechs*/, - gss_cred_usage_t /*cred_usage*/, - gss_cred_id_t * /*output_cred_handle*/, - gss_OID_set * /*actual_mechs*/, - OM_uint32 * time_rec ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_alloc_sec_context ( - OM_uint32 * /*minor_status*/, - gss_ctx_id_t */*context_handle*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_canonicalize_name ( - OM_uint32 * /*minor_status*/, - gss_const_name_t /*input_name*/, - const gss_OID /*mech_type*/, - gss_name_t * output_name ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_compare_name ( - OM_uint32 */*minor_status*/, - gss_const_name_t /*name1*/, - gss_const_name_t /*name2*/, - int * name_equal ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_context_time ( - OM_uint32 */*minor_status*/, - gss_const_ctx_id_t /*context_handle*/, - OM_uint32 *time_rec ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_delete_sec_context ( - OM_uint32 */*minor_status*/, - gss_ctx_id_t */*context_handle*/, - gss_buffer_t output_token ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_display_name ( - OM_uint32 * /*minor_status*/, - gss_const_name_t /*input_name*/, - gss_buffer_t /*output_name_buffer*/, - gss_OID * output_name_type ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_duplicate_name ( - OM_uint32 * /*minor_status*/, - gss_const_name_t /*src_name*/, - gss_name_t * dest_name ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_export_cred ( - OM_uint32 */*minor_status*/, - gss_cred_id_t /*cred_handle*/, - gss_buffer_t /*value*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_export_name ( - OM_uint32 * /*minor_status*/, - gss_const_name_t /*input_name*/, - gss_buffer_t exported_name ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_export_sec_context ( - OM_uint32 * /*minor_status*/, - gss_ctx_id_t * /*context_handle*/, - gss_buffer_t interprocess_token ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_get_mic ( - OM_uint32 */*minor_status*/, - gss_const_ctx_id_t /*context_handle*/, - gss_qop_t /*qop_req*/, - const gss_buffer_t /*message_buffer*/, - gss_buffer_t message_token ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_import_cred ( - OM_uint32 */*minor_status*/, - gss_buffer_t /*value*/, - gss_cred_id_t */*cred_handle*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_import_name ( - OM_uint32 * /*minor_status*/, - const gss_buffer_t /*name_buffer*/, - const gss_OID /*name_type*/, - gss_name_t * output_name ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_import_sec_context ( - OM_uint32 * /*minor_status*/, - const gss_buffer_t /*interprocess_token*/, - gss_ctx_id_t *context_handle ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_indicate_mechtypelist ( - OM_uint32 */*minor_status*/, - gss_name_t /*target_name*/, - OM_uint32 (*/*func*/)(gss_name_t, gss_OID), - int /*includeMSCompatOID*/, - gss_const_cred_id_t /*cred_handle*/, - MechTypeList */*mechtypelist*/, - gss_OID */*preferred_mech*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_init_sec_context ( - OM_uint32 * /*minor_status*/, - gss_const_cred_id_t /*initiator_cred_handle*/, - gss_ctx_id_t * /*context_handle*/, - gss_const_name_t /*target_name*/, - const gss_OID /*mech_type*/, - OM_uint32 /*req_flags*/, - OM_uint32 /*time_req*/, - const gss_channel_bindings_t /*input_chan_bindings*/, - const gss_buffer_t /*input_token*/, - gss_OID * /*actual_mech_type*/, - gss_buffer_t /*output_token*/, - OM_uint32 * /*ret_flags*/, - OM_uint32 * time_rec ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_inquire_context ( - OM_uint32 * /*minor_status*/, - gss_const_ctx_id_t /*context_handle*/, - gss_name_t * /*src_name*/, - gss_name_t * /*targ_name*/, - OM_uint32 * /*lifetime_rec*/, - gss_OID * /*mech_type*/, - OM_uint32 * /*ctx_flags*/, - int * /*locally_initiated*/, - int * open_context ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_inquire_cred ( - OM_uint32 * /*minor_status*/, - gss_const_cred_id_t /*cred_handle*/, - gss_name_t * /*name*/, - OM_uint32 * /*lifetime*/, - gss_cred_usage_t * /*cred_usage*/, - gss_OID_set * mechanisms ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_inquire_cred_by_mech ( - OM_uint32 * /*minor_status*/, - gss_const_cred_id_t /*cred_handle*/, - const gss_OID /*mech_type*/, - gss_name_t * /*name*/, - OM_uint32 * /*initiator_lifetime*/, - OM_uint32 * /*acceptor_lifetime*/, - gss_cred_usage_t * cred_usage ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_inquire_cred_by_oid ( - OM_uint32 * /*minor_status*/, - gss_const_cred_id_t /*cred_handle*/, - const gss_OID /*desired_object*/, - gss_buffer_set_t */*data_set*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_inquire_mechs_for_name ( - OM_uint32 * /*minor_status*/, - gss_const_name_t /*input_name*/, - gss_OID_set * mech_types ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_inquire_names_for_mech ( - OM_uint32 * /*minor_status*/, - const gss_OID /*mechanism*/, - gss_OID_set * name_types ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_inquire_sec_context_by_oid ( - OM_uint32 * /*minor_status*/, - gss_const_ctx_id_t /*context_handle*/, - const gss_OID /*desired_object*/, - gss_buffer_set_t */*data_set*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_internal_delete_sec_context ( - OM_uint32 */*minor_status*/, - gss_ctx_id_t */*context_handle*/, - gss_buffer_t output_token ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_process_context_token ( - OM_uint32 */*minor_status*/, - gss_const_ctx_id_t /*context_handle*/, - const gss_buffer_t token_buffer ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_pseudo_random ( - OM_uint32 */*minor_status*/, - gss_ctx_id_t /*context_handle*/, - int /*prf_key*/, - const gss_buffer_t /*prf_in*/, - ssize_t /*desired_output_len*/, - gss_buffer_t /*prf_out*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_release_cred ( - OM_uint32 */*minor_status*/, - gss_cred_id_t */*cred_handle*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_release_name ( - OM_uint32 * /*minor_status*/, - gss_name_t * input_name ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_require_mechlist_mic ( - OM_uint32 */*minor_status*/, - gssspnego_ctx /*ctx*/, - int */*require_mic*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_set_cred_option ( - OM_uint32 */*minor_status*/, - gss_cred_id_t */*cred_handle*/, - const gss_OID /*object*/, - const gss_buffer_t /*value*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_set_sec_context_option ( - OM_uint32 * /*minor_status*/, - gss_ctx_id_t * /*context_handle*/, - const gss_OID /*desired_object*/, - const gss_buffer_t /*value*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_unwrap ( - OM_uint32 * /*minor_status*/, - gss_const_ctx_id_t /*context_handle*/, - const gss_buffer_t /*input_message_buffer*/, - gss_buffer_t /*output_message_buffer*/, - int * /*conf_state*/, - gss_qop_t * qop_state ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_unwrap_iov ( - OM_uint32 */*minor_status*/, - gss_ctx_id_t /*context_handle*/, - int */*conf_state*/, - gss_qop_t */*qop_state*/, - gss_iov_buffer_desc */*iov*/, - int /*iov_count*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_verify_mic ( - OM_uint32 * /*minor_status*/, - gss_const_ctx_id_t /*context_handle*/, - const gss_buffer_t /*message_buffer*/, - const gss_buffer_t /*token_buffer*/, - gss_qop_t * qop_state ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_wrap ( - OM_uint32 * /*minor_status*/, - gss_const_ctx_id_t /*context_handle*/, - int /*conf_req_flag*/, - gss_qop_t /*qop_req*/, - const gss_buffer_t /*input_message_buffer*/, - int * /*conf_state*/, - gss_buffer_t output_message_buffer ); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_wrap_iov ( - OM_uint32 * /*minor_status*/, - gss_ctx_id_t /*context_handle*/, - int /*conf_req_flag*/, - gss_qop_t /*qop_req*/, - int * /*conf_state*/, - gss_iov_buffer_desc */*iov*/, - int /*iov_count*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_wrap_iov_length ( - OM_uint32 * /*minor_status*/, - gss_ctx_id_t /*context_handle*/, - int /*conf_req_flag*/, - gss_qop_t /*qop_req*/, - int */*conf_state*/, - gss_iov_buffer_desc */*iov*/, - int /*iov_count*/); - -OM_uint32 GSSAPI_CALLCONV -_gss_spnego_wrap_size_limit ( - OM_uint32 * /*minor_status*/, - gss_const_ctx_id_t /*context_handle*/, - int /*conf_req_flag*/, - gss_qop_t /*qop_req*/, - OM_uint32 /*req_output_size*/, - OM_uint32 * max_input_size ); - -#endif /* __spnego_private_h__ */ diff --git a/lib/gssapi/spnego/spnego.asn1 b/lib/gssapi/spnego/spnego.asn1 index 048e86bb43d5..bd69a0512aaf 100644 --- a/lib/gssapi/spnego/spnego.asn1 +++ b/lib/gssapi/spnego/spnego.asn1 @@ -22,7 +22,7 @@ NegHints ::= SEQUENCE { hintAddress [1] OCTET STRING OPTIONAL } -NegTokenInitWin ::= SEQUENCE { +NegTokenInit2 ::= SEQUENCE { mechTypes [0] MechTypeList, reqFlags [1] ContextFlags OPTIONAL, mechToken [2] OCTET STRING OPTIONAL, @@ -37,14 +37,17 @@ NegTokenInit ::= SEQUENCE { ... } --- NB: negResult is not OPTIONAL in the new SPNEGO spec but +NegStateEnum ::= ENUMERATED { + accept-completed(0), + accept-incomplete(1), + reject(2), + request-mic(3) +} + +-- NB: negState is not OPTIONAL in the new SPNEGO spec but -- Windows clients do not always send it NegTokenResp ::= SEQUENCE { - negResult [0] ENUMERATED { - accept_completed (0), - accept_incomplete (1), - reject (2), - request-mic (3) } OPTIONAL, + negState [0] NegStateEnum OPTIONAL, supportedMech [1] MechType OPTIONAL, responseToken [2] OCTET STRING OPTIONAL, mechListMIC [3] OCTET STRING OPTIONAL, @@ -56,8 +59,8 @@ NegotiationToken ::= CHOICE { negTokenResp[1] NegTokenResp } -NegotiationTokenWin ::= CHOICE { - negTokenInit[0] NegTokenInitWin +NegotiationToken2 ::= CHOICE { + negTokenInit[0] NegTokenInit2 } END diff --git a/lib/gssapi/spnego/spnego_locl.h b/lib/gssapi/spnego/spnego_locl.h index 3e151c7c2a4c..e3434f252a3f 100644 --- a/lib/gssapi/spnego/spnego_locl.h +++ b/lib/gssapi/spnego/spnego_locl.h @@ -50,6 +50,7 @@ #include <pthread.h> #endif +#include <krb5.h> #include <gssapi.h> #include <gssapi_krb5.h> #include <gssapi_spnego.h> @@ -63,11 +64,13 @@ #endif #include <heim_threads.h> +#include <heimqueue.h> #include <asn1_err.h> #include <gssapi_mech.h> #include "spnego_asn1.h" +#include "negoex_locl.h" #include "utils.h" #include <der.h> @@ -75,37 +78,82 @@ #define ALLOC(X, N) (X) = calloc((N), sizeof(*(X))) -typedef struct { - MechTypeList initiator_mech_types; +#define CHECK(ret, x) do { (ret) = (x); if (ret) goto fail; } while (0) + +struct gssspnego_ctx_desc; +typedef struct gssspnego_ctx_desc *gssspnego_ctx; + +typedef OM_uint32 +(*gssspnego_initiator_state)(OM_uint32 * minor_status, + gss_const_cred_id_t cred, + gssspnego_ctx ctx, + gss_const_name_t name, + gss_const_OID mech_type, + 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 * ret_flags, + OM_uint32 * time_rec); + +struct gssspnego_ctx_desc { + gss_buffer_desc NegTokenInit_mech_types; gss_OID preferred_mech_type; + gss_OID selected_mech_type; gss_OID negotiated_mech_type; gss_ctx_id_t negotiated_ctx_id; OM_uint32 mech_flags; OM_uint32 mech_time_rec; gss_name_t mech_src_name; - unsigned int open : 1; - unsigned int local : 1; - unsigned int require_mic : 1; - unsigned int verified_mic : 1; - unsigned int maybe_open : 1; + struct spnego_flags { + unsigned int open : 1; + unsigned int local : 1; + unsigned int require_mic : 1; + unsigned int peer_require_mic : 1; + unsigned int sent_mic : 1; + unsigned int verified_mic : 1; + unsigned int safe_omit : 1; + unsigned int maybe_open : 1; + unsigned int seen_supported_mech : 1; + } flags; HEIMDAL_MUTEX ctx_id_mutex; gss_name_t target_name; + gssspnego_initiator_state initiator_state; - u_char oidbuf[17]; - size_t oidlen; - -} *gssspnego_ctx; - -typedef struct { - gss_OID_desc type; - gss_buffer_desc value; - gss_name_t mech; -} *spnego_name; + uint8_t negoex_step; + krb5_storage *negoex_transcript; + uint32_t negoex_seqnum; + conversation_id negoex_conv_id; + HEIM_TAILQ_HEAD(negoex_mech_list, negoex_auth_mech) negoex_mechs; +}; extern gss_OID_desc _gss_spnego_mskrb_mechanism_oid_desc; -extern gss_OID_desc _gss_spnego_krb5_mechanism_oid_desc; -#include <spnego-private.h> +struct gssspnego_optimistic_ctx { + gssspnego_ctx spnegoctx; + OM_uint32 req_flags; + gss_name_t target_name; + OM_uint32 time_req; + gss_channel_bindings_t input_chan_bindings; + /* out */ + gss_OID preferred_mech_type; + gss_OID negotiated_mech_type; + gss_buffer_desc optimistic_token; + OM_uint32 optimistic_flags, optimistic_time_rec; + gss_ctx_id_t gssctx; + int complete; + auth_scheme scheme; +}; + +#include "spnego-private.h" + +static inline int +gssspnego_ctx_complete_p(gssspnego_ctx ctx) +{ + return ctx->flags.open && + (ctx->flags.safe_omit || (ctx->flags.sent_mic && ctx->flags.verified_mic)); +} #endif /* SPNEGO_LOCL_H */ |
