diff options
Diffstat (limited to 'src/lib/gssapi/krb5/iakerb.c')
| -rw-r--r-- | src/lib/gssapi/krb5/iakerb.c | 1329 |
1 files changed, 1329 insertions, 0 deletions
diff --git a/src/lib/gssapi/krb5/iakerb.c b/src/lib/gssapi/krb5/iakerb.c new file mode 100644 index 000000000000..2dc4d0c1a4d5 --- /dev/null +++ b/src/lib/gssapi/krb5/iakerb.c @@ -0,0 +1,1329 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* + * Copyright 2009 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ +#include "k5-int.h" +#include "gssapiP_krb5.h" + +/* + * IAKERB implementation + */ + +extern int gssint_get_der_length(unsigned char **, OM_uint32, unsigned int*); + +enum iakerb_state { + IAKERB_AS_REQ, /* acquiring ticket with initial creds */ + IAKERB_TGS_REQ, /* acquiring ticket with TGT */ + IAKERB_AP_REQ /* hand-off to normal GSS AP-REQ exchange */ +}; + +struct _iakerb_ctx_id_rec { + krb5_magic magic; /* KG_IAKERB_CONTEXT */ + krb5_context k5c; + gss_cred_id_t defcred; /* Initiator only */ + enum iakerb_state state; /* Initiator only */ + krb5_init_creds_context icc; /* Initiator only */ + krb5_tkt_creds_context tcc; /* Initiator only */ + gss_ctx_id_t gssc; + krb5_data conv; /* conversation for checksumming */ + unsigned int count; /* number of round trips */ + int initiate; + int established; + krb5_get_init_creds_opt *gic_opts; +}; + +#define IAKERB_MAX_HOPS ( 16 /* MAX_IN_TKT_LOOPS */ + KRB5_REFERRAL_MAXHOPS ) + +typedef struct _iakerb_ctx_id_rec iakerb_ctx_id_rec; +typedef iakerb_ctx_id_rec *iakerb_ctx_id_t; + +/* + * Release an IAKERB context + */ +static void +iakerb_release_context(iakerb_ctx_id_t ctx) +{ + OM_uint32 tmp; + + if (ctx == NULL) + return; + + krb5_gss_release_cred(&tmp, &ctx->defcred); + krb5_init_creds_free(ctx->k5c, ctx->icc); + krb5_tkt_creds_free(ctx->k5c, ctx->tcc); + krb5_gss_delete_sec_context(&tmp, &ctx->gssc, NULL); + krb5_free_data_contents(ctx->k5c, &ctx->conv); + krb5_get_init_creds_opt_free(ctx->k5c, ctx->gic_opts); + krb5_free_context(ctx->k5c); + free(ctx); +} + +/* + * Create a IAKERB-FINISHED structure containing a checksum of + * the entire IAKERB exchange. + */ +krb5_error_code +iakerb_make_finished(krb5_context context, + krb5_key key, + const krb5_data *conv, + krb5_data **finished) +{ + krb5_error_code code; + krb5_iakerb_finished iaf; + + *finished = NULL; + + memset(&iaf, 0, sizeof(iaf)); + + if (key == NULL) + return KRB5KDC_ERR_NULL_KEY; + + code = krb5_k_make_checksum(context, 0, key, KRB5_KEYUSAGE_IAKERB_FINISHED, + conv, &iaf.checksum); + if (code != 0) + return code; + + code = encode_krb5_iakerb_finished(&iaf, finished); + + krb5_free_checksum_contents(context, &iaf.checksum); + + return code; +} + +/* + * Verify a IAKERB-FINISHED structure submitted by the initiator + */ +krb5_error_code +iakerb_verify_finished(krb5_context context, + krb5_key key, + const krb5_data *conv, + const krb5_data *finished) +{ + krb5_error_code code; + krb5_iakerb_finished *iaf; + krb5_boolean valid = FALSE; + + if (key == NULL) + return KRB5KDC_ERR_NULL_KEY; + + code = decode_krb5_iakerb_finished(finished, &iaf); + if (code != 0) + return code; + + code = krb5_k_verify_checksum(context, key, KRB5_KEYUSAGE_IAKERB_FINISHED, + conv, &iaf->checksum, &valid); + if (code == 0 && valid == FALSE) + code = KRB5KRB_AP_ERR_BAD_INTEGRITY; + + krb5_free_iakerb_finished(context, iaf); + + return code; +} + +/* + * Save a token for future checksumming. + */ +static krb5_error_code +iakerb_save_token(iakerb_ctx_id_t ctx, const gss_buffer_t token) +{ + char *p; + + p = realloc(ctx->conv.data, ctx->conv.length + token->length); + if (p == NULL) + return ENOMEM; + + memcpy(p + ctx->conv.length, token->value, token->length); + ctx->conv.data = p; + ctx->conv.length += token->length; + + return 0; +} + +/* + * Parse a token into IAKERB-HEADER and KRB-KDC-REQ/REP + */ +static krb5_error_code +iakerb_parse_token(iakerb_ctx_id_t ctx, + int initialContextToken, + const gss_buffer_t token, + krb5_data *realm, + krb5_data **cookie, + krb5_data *request) +{ + krb5_error_code code; + krb5_iakerb_header *iah = NULL; + unsigned int bodysize, lenlen; + int length; + unsigned char *ptr; + int flags = 0; + krb5_data data; + + if (token == GSS_C_NO_BUFFER || token->length == 0) { + code = KRB5_BAD_MSIZE; + goto cleanup; + } + + if (initialContextToken) + flags |= G_VFY_TOKEN_HDR_WRAPPER_REQUIRED; + + ptr = token->value; + + code = g_verify_token_header(gss_mech_iakerb, + &bodysize, &ptr, + IAKERB_TOK_PROXY, + token->length, flags); + if (code != 0) + goto cleanup; + + data.data = (char *)ptr; + + if (bodysize-- == 0 || *ptr++ != 0x30 /* SEQUENCE */) { + code = ASN1_BAD_ID; + goto cleanup; + } + + length = gssint_get_der_length(&ptr, bodysize, &lenlen); + if (length < 0 || bodysize - lenlen < (unsigned int)length) { + code = KRB5_BAD_MSIZE; + goto cleanup; + } + data.length = 1 /* SEQUENCE */ + lenlen + length; + + ptr += length; + bodysize -= (lenlen + length); + + code = decode_krb5_iakerb_header(&data, &iah); + if (code != 0) + goto cleanup; + + if (realm != NULL) { + *realm = iah->target_realm; + iah->target_realm.data = NULL; + } + + if (cookie != NULL) { + *cookie = iah->cookie; + iah->cookie = NULL; + } + + request->data = (char *)ptr; + request->length = bodysize; + + assert(request->data + request->length == + (char *)token->value + token->length); + +cleanup: + krb5_free_iakerb_header(ctx->k5c, iah); + + return code; +} + +/* + * Create a token from IAKERB-HEADER and KRB-KDC-REQ/REP + */ +static krb5_error_code +iakerb_make_token(iakerb_ctx_id_t ctx, + krb5_data *realm, + krb5_data *cookie, + krb5_data *request, + int initialContextToken, + gss_buffer_t token) +{ + krb5_error_code code; + krb5_iakerb_header iah; + krb5_data *data = NULL; + char *p; + unsigned int tokenSize; + unsigned char *q; + + token->value = NULL; + token->length = 0; + + /* + * Assemble the IAKERB-HEADER from the realm and cookie + */ + memset(&iah, 0, sizeof(iah)); + iah.target_realm = *realm; + iah.cookie = cookie; + + code = encode_krb5_iakerb_header(&iah, &data); + if (code != 0) + goto cleanup; + + /* + * Concatenate Kerberos request. + */ + p = realloc(data->data, data->length + request->length); + if (p == NULL) { + code = ENOMEM; + goto cleanup; + } + data->data = p; + + if (request->length > 0) + memcpy(data->data + data->length, request->data, request->length); + data->length += request->length; + + if (initialContextToken) + tokenSize = g_token_size(gss_mech_iakerb, data->length); + else + tokenSize = 2 + data->length; + + token->value = q = gssalloc_malloc(tokenSize); + if (q == NULL) { + code = ENOMEM; + goto cleanup; + } + token->length = tokenSize; + + if (initialContextToken) { + g_make_token_header(gss_mech_iakerb, data->length, &q, + IAKERB_TOK_PROXY); + } else { + store_16_be(IAKERB_TOK_PROXY, q); + q += 2; + } + memcpy(q, data->data, data->length); + q += data->length; + + assert(q == (unsigned char *)token->value + token->length); + +cleanup: + krb5_free_data(ctx->k5c, data); + + return code; +} + +/* + * Parse the IAKERB token in input_token and send the contained KDC + * request to the KDC for the realm. + * + * Wrap the KDC reply in output_token. + */ +static krb5_error_code +iakerb_acceptor_step(iakerb_ctx_id_t ctx, + int initialContextToken, + const gss_buffer_t input_token, + gss_buffer_t output_token) +{ + krb5_error_code code; + krb5_data request = empty_data(), reply = empty_data(); + krb5_data realm = empty_data(); + OM_uint32 tmp; + int tcp_only, use_master; + krb5_ui_4 kdc_code; + + output_token->length = 0; + output_token->value = NULL; + + if (ctx->count >= IAKERB_MAX_HOPS) { + code = KRB5_KDC_UNREACH; + goto cleanup; + } + + code = iakerb_parse_token(ctx, initialContextToken, input_token, &realm, + NULL, &request); + if (code != 0) + goto cleanup; + + if (realm.length == 0 || request.length == 0) { + code = KRB5_BAD_MSIZE; + goto cleanup; + } + + code = iakerb_save_token(ctx, input_token); + if (code != 0) + goto cleanup; + + for (tcp_only = 0; tcp_only <= 1; tcp_only++) { + use_master = 0; + code = krb5_sendto_kdc(ctx->k5c, &request, &realm, + &reply, &use_master, tcp_only); + if (code == 0 && krb5_is_krb_error(&reply)) { + krb5_error *error; + + code = decode_krb5_error(&reply, &error); + if (code != 0) + goto cleanup; + kdc_code = error->error; + krb5_free_error(ctx->k5c, error); + if (kdc_code == KRB_ERR_RESPONSE_TOO_BIG) { + krb5_free_data_contents(ctx->k5c, &reply); + reply = empty_data(); + continue; + } + } + break; + } + + if (code == KRB5_KDC_UNREACH || code == KRB5_REALM_UNKNOWN) { + krb5_error error; + + memset(&error, 0, sizeof(error)); + if (code == KRB5_KDC_UNREACH) + error.error = KRB_AP_ERR_IAKERB_KDC_NO_RESPONSE; + else if (code == KRB5_REALM_UNKNOWN) + error.error = KRB_AP_ERR_IAKERB_KDC_NOT_FOUND; + + code = krb5_mk_error(ctx->k5c, &error, &reply); + if (code != 0) + goto cleanup; + } else if (code != 0) + goto cleanup; + + code = iakerb_make_token(ctx, &realm, NULL, &reply, 0, output_token); + if (code != 0) + goto cleanup; + + code = iakerb_save_token(ctx, output_token); + if (code != 0) + goto cleanup; + + ctx->count++; + +cleanup: + if (code != 0) + gss_release_buffer(&tmp, output_token); + /* request is a pointer into input_token, no need to free */ + krb5_free_data_contents(ctx->k5c, &realm); + krb5_free_data_contents(ctx->k5c, &reply); + + return code; +} + +/* + * Initialise the krb5_init_creds context for the IAKERB context + */ +static krb5_error_code +iakerb_init_creds_ctx(iakerb_ctx_id_t ctx, + krb5_gss_cred_id_t cred, + OM_uint32 time_req) +{ + krb5_error_code code; + + if (cred->iakerb_mech == 0) { + code = EINVAL; + goto cleanup; + } + + assert(cred->name != NULL); + assert(cred->name->princ != NULL); + + code = krb5_get_init_creds_opt_alloc(ctx->k5c, &ctx->gic_opts); + if (code != 0) + goto cleanup; + + if (time_req != 0 && time_req != GSS_C_INDEFINITE) + krb5_get_init_creds_opt_set_tkt_life(ctx->gic_opts, time_req); + + code = krb5_get_init_creds_opt_set_out_ccache(ctx->k5c, ctx->gic_opts, + cred->ccache); + if (code != 0) + goto cleanup; + + code = krb5_init_creds_init(ctx->k5c, + cred->name->princ, + NULL, /* prompter */ + NULL, /* data */ + 0, /* start_time */ + ctx->gic_opts, + &ctx->icc); + if (code != 0) + goto cleanup; + + if (cred->password != NULL) { + code = krb5_init_creds_set_password(ctx->k5c, ctx->icc, + cred->password); + } else if (cred->client_keytab != NULL) { + code = krb5_init_creds_set_keytab(ctx->k5c, ctx->icc, + cred->client_keytab); + } else { + code = KRB5_KT_NOTFOUND; + } + if (code != 0) + goto cleanup; + +cleanup: + return code; +} + +/* + * Initialise the krb5_tkt_creds context for the IAKERB context + */ +static krb5_error_code +iakerb_tkt_creds_ctx(iakerb_ctx_id_t ctx, + krb5_gss_cred_id_t cred, + krb5_gss_name_t name, + OM_uint32 time_req) + +{ + krb5_error_code code; + krb5_creds creds; + krb5_timestamp now; + + assert(cred->name != NULL); + assert(cred->name->princ != NULL); + + memset(&creds, 0, sizeof(creds)); + + creds.client = cred->name->princ; + creds.server = name->princ; + + if (time_req != 0 && time_req != GSS_C_INDEFINITE) { + code = krb5_timeofday(ctx->k5c, &now); + if (code != 0) + goto cleanup; + + creds.times.endtime = now + time_req; + } + + if (cred->name->ad_context != NULL) { + code = krb5_authdata_export_authdata(ctx->k5c, + cred->name->ad_context, + AD_USAGE_TGS_REQ, + &creds.authdata); + if (code != 0) + goto cleanup; + } + + code = krb5_tkt_creds_init(ctx->k5c, cred->ccache, &creds, 0, &ctx->tcc); + if (code != 0) + goto cleanup; + +cleanup: + krb5_free_authdata(ctx->k5c, creds.authdata); + + return code; +} + +/* + * Parse the IAKERB token in input_token and process the KDC + * response. + * + * Emit the next KDC request, if any, in output_token. + */ +static krb5_error_code +iakerb_initiator_step(iakerb_ctx_id_t ctx, + krb5_gss_cred_id_t cred, + krb5_gss_name_t name, + OM_uint32 time_req, + const gss_buffer_t input_token, + gss_buffer_t output_token) +{ + krb5_error_code code = 0; + krb5_data in = empty_data(), out = empty_data(), realm = empty_data(); + krb5_data *cookie = NULL; + OM_uint32 tmp; + unsigned int flags = 0; + krb5_ticket_times times; + + output_token->length = 0; + output_token->value = NULL; + + if (input_token != GSS_C_NO_BUFFER) { + code = iakerb_parse_token(ctx, 0, input_token, NULL, &cookie, &in); + if (code != 0) + goto cleanup; + + code = iakerb_save_token(ctx, input_token); + if (code != 0) + goto cleanup; + } + + switch (ctx->state) { + case IAKERB_AS_REQ: + if (ctx->icc == NULL) { + code = iakerb_init_creds_ctx(ctx, cred, time_req); + if (code != 0) + goto cleanup; + } + + code = krb5_init_creds_step(ctx->k5c, ctx->icc, &in, &out, &realm, + &flags); + if (code != 0) { + if (cred->have_tgt) { + /* We were trying to refresh; keep going with current creds. */ + ctx->state = IAKERB_TGS_REQ; + krb5_clear_error_message(ctx->k5c); + } else { + goto cleanup; + } + } else if (!(flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE)) { + krb5_init_creds_get_times(ctx->k5c, ctx->icc, ×); + kg_cred_set_initial_refresh(ctx->k5c, cred, ×); + cred->expire = times.endtime; + + krb5_init_creds_free(ctx->k5c, ctx->icc); + ctx->icc = NULL; + + ctx->state = IAKERB_TGS_REQ; + } else + break; + in = empty_data(); + /* Done with AS request; fall through to TGS request. */ + case IAKERB_TGS_REQ: + if (ctx->tcc == NULL) { + code = iakerb_tkt_creds_ctx(ctx, cred, name, time_req); + if (code != 0) + goto cleanup; + } + + code = krb5_tkt_creds_step(ctx->k5c, ctx->tcc, &in, &out, &realm, + &flags); + if (code != 0) + goto cleanup; + if (!(flags & KRB5_TKT_CREDS_STEP_FLAG_CONTINUE)) { + krb5_tkt_creds_get_times(ctx->k5c, ctx->tcc, ×); + cred->expire = times.endtime; + + krb5_tkt_creds_free(ctx->k5c, ctx->tcc); + ctx->tcc = NULL; + + ctx->state = IAKERB_AP_REQ; + } else + break; + /* Done with TGS request; fall through to AP request. */ + case IAKERB_AP_REQ: + break; + } + + if (out.length != 0) { + assert(ctx->state != IAKERB_AP_REQ); + + code = iakerb_make_token(ctx, &realm, cookie, &out, + (input_token == GSS_C_NO_BUFFER), + output_token); + if (code != 0) + goto cleanup; + + /* Save the token for generating a future checksum */ + code = iakerb_save_token(ctx, output_token); + if (code != 0) + goto cleanup; + + ctx->count++; + } + +cleanup: + if (code != 0) + gss_release_buffer(&tmp, output_token); + krb5_free_data(ctx->k5c, cookie); + krb5_free_data_contents(ctx->k5c, &out); + krb5_free_data_contents(ctx->k5c, &realm); + + return code; +} + +/* + * Determine the starting IAKERB state for a context. If we already + * have a ticket, we may not need to do IAKERB at all. + */ +static krb5_error_code +iakerb_get_initial_state(iakerb_ctx_id_t ctx, + krb5_gss_cred_id_t cred, + krb5_gss_name_t target, + OM_uint32 time_req, + enum iakerb_state *state) +{ + krb5_creds in_creds, *out_creds = NULL; + krb5_error_code code; + + memset(&in_creds, 0, sizeof(in_creds)); + + in_creds.client = cred->name->princ; + in_creds.server = target->princ; + + if (cred->name->ad_context != NULL) { + code = krb5_authdata_export_authdata(ctx->k5c, + cred->name->ad_context, + AD_USAGE_TGS_REQ, + &in_creds.authdata); + if (code != 0) + goto cleanup; + } + + if (time_req != 0 && time_req != GSS_C_INDEFINITE) { + krb5_timestamp now; + + code = krb5_timeofday(ctx->k5c, &now); + if (code != 0) + goto cleanup; + + in_creds.times.endtime = now + time_req; + } + + /* Make an AS request if we have no creds or it's time to refresh them. */ + if (cred->expire == 0 || kg_cred_time_to_refresh(ctx->k5c, cred)) { + *state = IAKERB_AS_REQ; + code = 0; + goto cleanup; + } + + code = krb5_get_credentials(ctx->k5c, KRB5_GC_CACHED, cred->ccache, + &in_creds, &out_creds); + if (code == KRB5_CC_NOTFOUND || code == KRB5_CC_NOT_KTYPE) { + *state = cred->have_tgt ? IAKERB_TGS_REQ : IAKERB_AS_REQ; + code = 0; + } else if (code == 0) { + *state = IAKERB_AP_REQ; + krb5_free_creds(ctx->k5c, out_creds); + } + +cleanup: + krb5_free_authdata(ctx->k5c, in_creds.authdata); + + return code; +} + +/* + * Allocate and initialise an IAKERB context + */ +static krb5_error_code +iakerb_alloc_context(iakerb_ctx_id_t *pctx, int initiate) +{ + iakerb_ctx_id_t ctx; + krb5_error_code code; + + *pctx = NULL; + + ctx = k5alloc(sizeof(*ctx), &code); + if (ctx == NULL) + goto cleanup; + ctx->defcred = GSS_C_NO_CREDENTIAL; + ctx->magic = KG_IAKERB_CONTEXT; + ctx->state = IAKERB_AS_REQ; + ctx->count = 0; + ctx->initiate = initiate; + ctx->established = 0; + + code = krb5_gss_init_context(&ctx->k5c); + if (code != 0) + goto cleanup; + + *pctx = ctx; + +cleanup: + if (code != 0) + iakerb_release_context(ctx); + + return code; +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_delete_sec_context(OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + gss_buffer_t output_token) +{ + iakerb_ctx_id_t iakerb_ctx = (iakerb_ctx_id_t)*context_handle; + + if (output_token != GSS_C_NO_BUFFER) { + output_token->length = 0; + output_token->value = NULL; + } + + *minor_status = 0; + *context_handle = GSS_C_NO_CONTEXT; + iakerb_release_context(iakerb_ctx); + + return GSS_S_COMPLETE; +} + +static krb5_boolean +iakerb_is_iakerb_token(const gss_buffer_t token) +{ + krb5_error_code code; + unsigned int bodysize = token->length; + unsigned char *ptr = token->value; + + code = g_verify_token_header(gss_mech_iakerb, + &bodysize, &ptr, + IAKERB_TOK_PROXY, + token->length, 0); + + return (code == 0); +} + +static void +iakerb_make_exts(iakerb_ctx_id_t ctx, krb5_gss_ctx_ext_rec *exts) +{ + memset(exts, 0, sizeof(*exts)); + + if (ctx->conv.length != 0) + exts->iakerb.conv = &ctx->conv; +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_accept_sec_context(OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + gss_cred_id_t verifier_cred_handle, + gss_buffer_t input_token, + 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 major_status = GSS_S_FAILURE; + OM_uint32 code; + iakerb_ctx_id_t ctx; + int initialContextToken = (*context_handle == GSS_C_NO_CONTEXT); + + if (initialContextToken) { + code = iakerb_alloc_context(&ctx, 0); + if (code != 0) + goto cleanup; + + } else + ctx = (iakerb_ctx_id_t)*context_handle; + + if (iakerb_is_iakerb_token(input_token)) { + if (ctx->gssc != GSS_C_NO_CONTEXT) { + /* We shouldn't get an IAKERB token now. */ + code = G_WRONG_TOKID; + major_status = GSS_S_DEFECTIVE_TOKEN; + goto cleanup; + } + code = iakerb_acceptor_step(ctx, initialContextToken, + input_token, output_token); + if (code == (OM_uint32)KRB5_BAD_MSIZE) + major_status = GSS_S_DEFECTIVE_TOKEN; + if (code != 0) + goto cleanup; + if (initialContextToken) { + *context_handle = (gss_ctx_id_t)ctx; + ctx = NULL; + } + if (src_name != NULL) + *src_name = GSS_C_NO_NAME; + if (mech_type != NULL) + *mech_type = (gss_OID)gss_mech_iakerb; + if (ret_flags != NULL) + *ret_flags = 0; + if (time_rec != NULL) + *time_rec = 0; + if (delegated_cred_handle != NULL) + *delegated_cred_handle = GSS_C_NO_CREDENTIAL; + major_status = GSS_S_CONTINUE_NEEDED; + } else { + krb5_gss_ctx_ext_rec exts; + + iakerb_make_exts(ctx, &exts); + + major_status = krb5_gss_accept_sec_context_ext(&code, + &ctx->gssc, + verifier_cred_handle, + input_token, + input_chan_bindings, + src_name, + NULL, + output_token, + ret_flags, + time_rec, + delegated_cred_handle, + &exts); + if (major_status == GSS_S_COMPLETE) + ctx->established = 1; + if (mech_type != NULL) + *mech_type = (gss_OID)gss_mech_krb5; + } + +cleanup: + if (initialContextToken && GSS_ERROR(major_status)) { + iakerb_release_context(ctx); + *context_handle = GSS_C_NO_CONTEXT; + } + + *minor_status = code; + return major_status; +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_init_sec_context(OM_uint32 *minor_status, + gss_cred_id_t claimant_cred_handle, + gss_ctx_id_t *context_handle, + gss_name_t target_name, + gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + gss_channel_bindings_t input_chan_bindings, + 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 major_status = GSS_S_FAILURE; + krb5_error_code code; + iakerb_ctx_id_t ctx; + krb5_gss_cred_id_t kcred; + krb5_gss_name_t kname; + krb5_boolean cred_locked = FALSE; + int initialContextToken = (*context_handle == GSS_C_NO_CONTEXT); + + if (initialContextToken) { + code = iakerb_alloc_context(&ctx, 1); + if (code != 0) { + *minor_status = code; + goto cleanup; + } + if (claimant_cred_handle == GSS_C_NO_CREDENTIAL) { + major_status = iakerb_gss_acquire_cred(minor_status, NULL, + GSS_C_INDEFINITE, + GSS_C_NULL_OID_SET, + GSS_C_INITIATE, + &ctx->defcred, NULL, NULL); + if (GSS_ERROR(major_status)) + goto cleanup; + claimant_cred_handle = ctx->defcred; + } + } else { + ctx = (iakerb_ctx_id_t)*context_handle; + if (claimant_cred_handle == GSS_C_NO_CREDENTIAL) + claimant_cred_handle = ctx->defcred; + } + + kname = (krb5_gss_name_t)target_name; + + major_status = kg_cred_resolve(minor_status, ctx->k5c, + claimant_cred_handle, target_name); + if (GSS_ERROR(major_status)) + goto cleanup; + cred_locked = TRUE; + kcred = (krb5_gss_cred_id_t)claimant_cred_handle; + + major_status = GSS_S_FAILURE; + + if (initialContextToken) { + code = iakerb_get_initial_state(ctx, kcred, kname, time_req, + &ctx->state); + if (code != 0) { + *minor_status = code; + goto cleanup; + } + *context_handle = (gss_ctx_id_t)ctx; + } + + if (ctx->state != IAKERB_AP_REQ) { + /* We need to do IAKERB. */ + code = iakerb_initiator_step(ctx, + kcred, + kname, + time_req, + input_token, + output_token); + if (code == KRB5_BAD_MSIZE) + major_status = GSS_S_DEFECTIVE_TOKEN; + if (code != 0) { + *minor_status = code; + goto cleanup; + } + } + + if (ctx->state == IAKERB_AP_REQ) { + krb5_gss_ctx_ext_rec exts; + + if (cred_locked) { + k5_mutex_unlock(&kcred->lock); + cred_locked = FALSE; + } + + iakerb_make_exts(ctx, &exts); + + if (ctx->gssc == GSS_C_NO_CONTEXT) + input_token = GSS_C_NO_BUFFER; + + /* IAKERB is finished, or we skipped to Kerberos directly. */ + major_status = krb5_gss_init_sec_context_ext(minor_status, + (gss_cred_id_t) kcred, + &ctx->gssc, + target_name, + (gss_OID)gss_mech_iakerb, + req_flags, + time_req, + input_chan_bindings, + input_token, + NULL, + output_token, + ret_flags, + time_rec, + &exts); + if (major_status == GSS_S_COMPLETE) + ctx->established = 1; + if (actual_mech_type != NULL) + *actual_mech_type = (gss_OID)gss_mech_krb5; + } else { + if (actual_mech_type != NULL) + *actual_mech_type = (gss_OID)gss_mech_iakerb; + if (ret_flags != NULL) + *ret_flags = 0; + if (time_rec != NULL) + *time_rec = 0; + major_status = GSS_S_CONTINUE_NEEDED; + } + +cleanup: + if (cred_locked) + k5_mutex_unlock(&kcred->lock); + if (initialContextToken && GSS_ERROR(major_status)) { + iakerb_release_context(ctx); + *context_handle = GSS_C_NO_CONTEXT; + } + + return major_status; +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_unwrap(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_buffer_t input_message_buffer, + gss_buffer_t output_message_buffer, int *conf_state, + gss_qop_t *qop_state) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_unwrap(minor_status, ctx->gssc, input_message_buffer, + output_message_buffer, conf_state, qop_state); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_wrap(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + int conf_req_flag, gss_qop_t qop_req, + gss_buffer_t input_message_buffer, int *conf_state, + gss_buffer_t output_message_buffer) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_wrap(minor_status, ctx->gssc, conf_req_flag, qop_req, + input_message_buffer, conf_state, + output_message_buffer); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_process_context_token(OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_buffer_t token_buffer) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_DEFECTIVE_TOKEN; + + return krb5_gss_process_context_token(minor_status, ctx->gssc, + token_buffer); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_context_time(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + OM_uint32 *time_rec) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_context_time(minor_status, ctx->gssc, time_rec); +} + +#ifndef LEAN_CLIENT + +OM_uint32 KRB5_CALLCONV +iakerb_gss_export_sec_context(OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + gss_buffer_t interprocess_token) +{ + OM_uint32 maj; + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)*context_handle; + + /* We don't currently support exporting partially established contexts. */ + if (!ctx->established) + return GSS_S_UNAVAILABLE; + + maj = krb5_gss_export_sec_context(minor_status, &ctx->gssc, + interprocess_token); + if (ctx->gssc == GSS_C_NO_CONTEXT) { + iakerb_release_context(ctx); + *context_handle = GSS_C_NO_CONTEXT; + } + return maj; +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_import_sec_context(OM_uint32 *minor_status, + gss_buffer_t interprocess_token, + gss_ctx_id_t *context_handle) +{ + OM_uint32 maj, tmpmin; + krb5_error_code code; + gss_ctx_id_t gssc; + krb5_gss_ctx_id_t kctx; + iakerb_ctx_id_t ctx; + + maj = krb5_gss_import_sec_context(minor_status, interprocess_token, &gssc); + if (maj != GSS_S_COMPLETE) + return maj; + kctx = (krb5_gss_ctx_id_t)gssc; + + if (!kctx->established) { + /* We don't currently support importing partially established + * contexts. */ + krb5_gss_delete_sec_context(&tmpmin, &gssc, GSS_C_NO_BUFFER); + return GSS_S_FAILURE; + } + + code = iakerb_alloc_context(&ctx, kctx->initiate); + if (code != 0) { + krb5_gss_delete_sec_context(&tmpmin, &gssc, GSS_C_NO_BUFFER); + *minor_status = code; + return GSS_S_FAILURE; + } + + ctx->gssc = gssc; + ctx->established = 1; + *context_handle = (gss_ctx_id_t)ctx; + return GSS_S_COMPLETE; +} +#endif /* LEAN_CLIENT */ + +OM_uint32 KRB5_CALLCONV +iakerb_gss_inquire_context(OM_uint32 *minor_status, + gss_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 *initiate, int *opened) +{ + OM_uint32 ret; + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (src_name != NULL) + *src_name = GSS_C_NO_NAME; + if (targ_name != NULL) + *targ_name = GSS_C_NO_NAME; + if (lifetime_rec != NULL) + *lifetime_rec = 0; + if (mech_type != NULL) + *mech_type = (gss_OID)gss_mech_iakerb; + if (ctx_flags != NULL) + *ctx_flags = 0; + if (initiate != NULL) + *initiate = ctx->initiate; + if (opened != NULL) + *opened = ctx->established; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_COMPLETE; + + ret = krb5_gss_inquire_context(minor_status, ctx->gssc, src_name, + targ_name, lifetime_rec, mech_type, + ctx_flags, initiate, opened); + + if (!ctx->established) { + /* Report IAKERB as the mech OID until the context is established. */ + if (mech_type != NULL) + *mech_type = (gss_OID)gss_mech_iakerb; + + /* We don't support exporting partially-established contexts. */ + if (ctx_flags != NULL) + *ctx_flags &= ~GSS_C_TRANS_FLAG; + } + + return ret; +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_wrap_size_limit(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, int conf_req_flag, + gss_qop_t qop_req, OM_uint32 req_output_size, + OM_uint32 *max_input_size) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_wrap_size_limit(minor_status, ctx->gssc, conf_req_flag, + qop_req, req_output_size, max_input_size); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_get_mic(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_qop_t qop_req, gss_buffer_t message_buffer, + gss_buffer_t message_token) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_get_mic(minor_status, ctx->gssc, qop_req, message_buffer, + message_token); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_verify_mic(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_buffer_t msg_buffer, gss_buffer_t token_buffer, + gss_qop_t *qop_state) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_verify_mic(minor_status, ctx->gssc, msg_buffer, + token_buffer, qop_state); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_inquire_sec_context_by_oid(OM_uint32 *minor_status, + const gss_ctx_id_t context_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_UNAVAILABLE; + + return krb5_gss_inquire_sec_context_by_oid(minor_status, ctx->gssc, + desired_object, data_set); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_set_sec_context_option(OM_uint32 *minor_status, + gss_ctx_id_t *context_handle, + const gss_OID desired_object, + const gss_buffer_t value) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)*context_handle; + + if (ctx == NULL || ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_UNAVAILABLE; + + return krb5_gss_set_sec_context_option(minor_status, &ctx->gssc, + desired_object, value); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_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) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_wrap_iov(minor_status, ctx->gssc, conf_req_flag, qop_req, + conf_state, iov, iov_count); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_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) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_unwrap_iov(minor_status, ctx->gssc, conf_state, qop_state, + iov, iov_count); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_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) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_wrap_iov_length(minor_status, ctx->gssc, conf_req_flag, + qop_req, conf_state, iov, iov_count); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_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) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_pseudo_random(minor_status, ctx->gssc, prf_key, prf_in, + desired_output_len, prf_out); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_get_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_qop_t qop_req, gss_iov_buffer_desc *iov, + int iov_count) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_get_mic_iov(minor_status, ctx->gssc, qop_req, iov, + iov_count); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_verify_mic_iov(OM_uint32 *minor_status, gss_ctx_id_t context_handle, + gss_qop_t *qop_state, gss_iov_buffer_desc *iov, + int iov_count) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_verify_mic_iov(minor_status, ctx->gssc, qop_state, iov, + iov_count); +} + +OM_uint32 KRB5_CALLCONV +iakerb_gss_get_mic_iov_length(OM_uint32 *minor_status, + gss_ctx_id_t context_handle, gss_qop_t qop_req, + gss_iov_buffer_desc *iov, int iov_count) +{ + iakerb_ctx_id_t ctx = (iakerb_ctx_id_t)context_handle; + + if (ctx->gssc == GSS_C_NO_CONTEXT) + return GSS_S_NO_CONTEXT; + + return krb5_gss_get_mic_iov_length(minor_status, ctx->gssc, qop_req, iov, + iov_count); +} |
