static int decode_NegTokenResp(const unsigned char *p, size_t len, NegTokenResp * data, size_t * size) { size_t ret = 0, reallen; size_t l; int e; memset(data, 0, sizeof(*data)); reallen = 0; e = der_match_tag_and_length(p, len, ASN1_C_UNIV, CONS, UT_Sequence, &reallen, &l); FORW; { int dce_fix; if ((dce_fix = fix_dce(reallen, &len)) < 0) return ASN1_BAD_FORMAT; { size_t newlen, oldlen; e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 0, &l); if (e) (data)->negState = NULL; else { p += l; len -= l; ret += l; e = der_get_length(p, len, &newlen, &l); FORW; { int mydce_fix; oldlen = len; if ((mydce_fix = fix_dce(newlen, &len)) < 0) return ASN1_BAD_FORMAT; (data)->negState = malloc(sizeof(*(data)->negState)); if ((data)->negState == NULL) return ENOMEM; e = decode_enumerated(p, len, (data)->negState, &l); FORW; if (mydce_fix) { e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); FORW; } else len = oldlen - newlen; } } } { size_t newlen, oldlen; e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 1, &l); if (e) (data)->supportedMech = NULL; else { p += l; len -= l; ret += l; e = der_get_length(p, len, &newlen, &l); FORW; { int mydce_fix; oldlen = len; if ((mydce_fix = fix_dce(newlen, &len)) < 0) return ASN1_BAD_FORMAT; (data)->supportedMech = malloc(sizeof(*(data)->supportedMech)); if ((data)->supportedMech == NULL) return ENOMEM; e = decode_MechType(p, len, (data)->supportedMech, &l); FORW; if (mydce_fix) { e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); FORW; } else len = oldlen - newlen; } } } { size_t newlen, oldlen; e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 2, &l); if (e) (data)->responseToken = NULL; else { p += l; len -= l; ret += l; e = der_get_length(p, len, &newlen, &l); FORW; { int mydce_fix; oldlen = len; if ((mydce_fix = fix_dce(newlen, &len)) < 0) return ASN1_BAD_FORMAT; (data)->responseToken = malloc(sizeof(*(data)->responseToken)); if ((data)->responseToken == NULL) return ENOMEM; e = decode_octet_string(p, len, (data)->responseToken, &l); FORW; if (mydce_fix) { e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); FORW; } else len = oldlen - newlen; } } } { size_t newlen, oldlen; e = der_match_tag(p, len, ASN1_C_CONTEXT, CONS, 3, &l); if (e) (data)->mechListMIC = NULL; else { p += l; len -= l; ret += l; e = der_get_length(p, len, &newlen, &l); FORW; { int mydce_fix; oldlen = len; if ((mydce_fix = fix_dce(newlen, &len)) < 0) return ASN1_BAD_FORMAT; (data)->mechListMIC = malloc(sizeof(*(data)->mechListMIC)); if ((data)->mechListMIC == NULL) return ENOMEM; e = decode_octet_string(p, len, (data)->mechListMIC, &l); FORW; if (mydce_fix) { e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); FORW; } else len = oldlen - newlen; } } } if (dce_fix) { e = der_match_tag_and_length(p, len, (Der_class) 0, (Der_type) 0, 0, &reallen, &l); FORW; } } if (size) *size = ret; return 0; fail: free_NegTokenResp(data); return e; }
static OM_uint32 spnego_reply (OM_uint32 * minor_status, const gssspnego_cred cred, gss_ctx_id_t * context_handle, const gss_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 ret, minor; NegTokenResp resp; size_t len, taglen; gss_OID_desc mech; int require_mic; size_t buf_len; gss_buffer_desc mic_buf, mech_buf; gss_buffer_desc mech_output_token; gssspnego_ctx ctx; *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 = der_match_tag_and_length(input_token->value, input_token->length, ASN1_C_CONTEXT, CONS, 1, &len, &taglen); if (ret) return ret; if (len > input_token->length - taglen) return ASN1_OVERRUN; ret = decode_NegTokenResp((const unsigned char *)input_token->value+taglen, len, &resp, NULL); if (ret) { *minor_status = ENOMEM; return GSS_S_FAILURE; } if (resp.negResult == NULL || *(resp.negResult) == reject /* || resp.supportedMech == NULL */ ) { free_NegTokenResp(&resp); return GSS_S_BAD_MECH; } /* * Pick up the mechanism that the acceptor selected, only allow it * to be sent in packet. */ HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); if (resp.supportedMech) { if (ctx->oidlen) { free_NegTokenResp(&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), resp.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)) { free_NegTokenResp(&resp); HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return GSS_S_BAD_MECH; } /* 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) { gss_delete_sec_context(&minor, &ctx->negotiated_ctx_id, GSS_C_NO_BUFFER); ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT; } } else if (ctx->oidlen == 0) { free_NegTokenResp(&resp); HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return GSS_S_BAD_MECH; } /* if a token (of non zero length), or no context, pass to underlaying mech */ if ((resp.responseToken != NULL && resp.responseToken->length) || ctx->negotiated_ctx_id == GSS_C_NO_CONTEXT) { gss_buffer_desc mech_input_token; if (resp.responseToken) { mech_input_token.length = resp.responseToken->length; mech_input_token.value = resp.responseToken->data; } else { mech_input_token.length = 0; 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 != NULL) ? cred->negotiated_cred_id : GSS_C_NO_CREDENTIAL, &ctx->negotiated_ctx_id, ctx->target_name, &mech, 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)) { HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); free_NegTokenResp(&resp); gss_mg_collect_error(&mech, ret, minor); *minor_status = minor; return ret; } if (ret == GSS_S_COMPLETE) { ctx->open = 1; } } else if (*(resp.negResult) == accept_completed) { if (ctx->maybe_open) ctx->open = 1; } if (*(resp.negResult) == request_mic) { ctx->require_mic = 1; } if (ctx->open) { /* * Verify the mechListMIC if one was provided or CFX was * used and a non-preferred mechanism was selected */ if (resp.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_NegTokenResp(&resp); gss_release_buffer(&minor, &mech_output_token); return ret; } } } else { require_mic = 0; } if (require_mic) { ASN1_MALLOC_ENCODE(MechTypeList, mech_buf.value, mech_buf.length, &ctx->initiator_mech_types, &buf_len, ret); if (ret) { HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); free_NegTokenResp(&resp); gss_release_buffer(&minor, &mech_output_token); *minor_status = ret; return GSS_S_FAILURE; } if (mech_buf.length != buf_len) abort(); if (resp.mechListMIC == NULL) { HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); free(mech_buf.value); free_NegTokenResp(&resp); *minor_status = 0; return GSS_S_DEFECTIVE_TOKEN; } mic_buf.length = resp.mechListMIC->length; mic_buf.value = resp.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_NegTokenResp(&resp); return GSS_S_DEFECTIVE_TOKEN; } ctx->verified_mic = 1; } } ret = spnego_reply_internal(minor_status, ctx, require_mic ? &mech_buf : NULL, &mech_output_token, output_token); if (mech_buf.value != NULL) free(mech_buf.value); free_NegTokenResp(&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; }
/* * Send a reply. Note that we only need to send a reply if we * need to send a MIC or a mechanism token. Otherwise, we can * return an empty buffer. * * The return value of this will be returned to the API, so it * 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) { NegTokenResp resp; gss_buffer_desc mic_buf; OM_uint32 ret; gss_buffer_desc data; u_char *buf; 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; } memset(&resp, 0, sizeof(resp)); ALLOC(resp.negResult, 1); if (resp.negResult == NULL) { *minor_status = ENOMEM; return GSS_S_FAILURE; } resp.supportedMech = NULL; output_token->length = 0; output_token->value = NULL; if (mech_token->length == 0) { resp.responseToken = NULL; *(resp.negResult) = accept_completed; } else { ALLOC(resp.responseToken, 1); if (resp.responseToken == NULL) { free_NegTokenResp(&resp); *minor_status = ENOMEM; return GSS_S_FAILURE; } resp.responseToken->length = mech_token->length; resp.responseToken->data = mech_token->value; mech_token->length = 0; mech_token->value = NULL; *(resp.negResult) = accept_incomplete; } if (mech_buf != GSS_C_NO_BUFFER) { ALLOC(resp.mechListMIC, 1); if (resp.mechListMIC == NULL) { free_NegTokenResp(&resp); *minor_status = ENOMEM; return GSS_S_FAILURE; } ret = gss_get_mic(minor_status, context_handle->negotiated_ctx_id, 0, mech_buf, &mic_buf); if (ret) { free_NegTokenResp(&resp); *minor_status = ENOMEM; return GSS_S_FAILURE; } resp.mechListMIC->length = mic_buf.length; resp.mechListMIC->data = mic_buf.value; } else { resp.mechListMIC = NULL; } ret = _gss_spnego_encode_response (minor_status, &resp, &data, &buf); if (ret) { free_NegTokenResp(&resp); return ret; } output_token->value = malloc(data.length); if (output_token->value == NULL) { *minor_status = ENOMEM; ret = GSS_S_FAILURE; } else { output_token->length = data.length; memcpy(output_token->value, data.value, output_token->length); } free(buf); if (*(resp.negResult) == accept_completed) ret = GSS_S_COMPLETE; else ret = GSS_S_CONTINUE_NEEDED; free_NegTokenResp(&resp); return ret; }