/* $NetBSD: accept_sec_context.c,v 1.2.22.1 2023/08/11 13:39:57 martin Exp $ */ /* * Copyright (c) 1997 - 2006 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * 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 the Institute 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 THE INSTITUTE 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 INSTITUTE 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 "gsskrb5_locl.h" HEIMDAL_MUTEX gssapi_keytab_mutex = HEIMDAL_MUTEX_INITIALIZER; krb5_keytab _gsskrb5_keytab; static krb5_error_code validate_keytab(krb5_context context, const char *name, krb5_keytab *id) { krb5_error_code ret; ret = krb5_kt_resolve(context, name, id); if (ret) return ret; ret = krb5_kt_have_content(context, *id); if (ret) { krb5_kt_close(context, *id); *id = NULL; } return ret; } OM_uint32 _gsskrb5_register_acceptor_identity(OM_uint32 *min_stat, const char *identity) { krb5_context context; krb5_error_code ret; *min_stat = 0; ret = _gsskrb5_init(&context); if(ret) return GSS_S_FAILURE; HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex); if(_gsskrb5_keytab != NULL) { krb5_kt_close(context, _gsskrb5_keytab); _gsskrb5_keytab = NULL; } if (identity == NULL) { ret = krb5_kt_default(context, &_gsskrb5_keytab); } else { /* * First check if we can the keytab as is and if it has content... */ ret = validate_keytab(context, identity, &_gsskrb5_keytab); /* * if it doesn't, lets prepend FILE: and try again */ if (ret) { char *p = NULL; ret = asprintf(&p, "FILE:%s", identity); if(ret < 0 || p == NULL) { HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex); return GSS_S_FAILURE; } ret = validate_keytab(context, p, &_gsskrb5_keytab); free(p); } } HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex); if(ret) { *min_stat = ret; return GSS_S_FAILURE; } return GSS_S_COMPLETE; } void _gsskrb5i_is_cfx(krb5_context context, gsskrb5_ctx ctx, int acceptor) { krb5_keyblock *key; if (acceptor) { if (ctx->auth_context->local_subkey) key = ctx->auth_context->local_subkey; else key = ctx->auth_context->remote_subkey; } else { if (ctx->auth_context->remote_subkey) key = ctx->auth_context->remote_subkey; else key = ctx->auth_context->local_subkey; } if (key == NULL) key = ctx->auth_context->keyblock; if (key == NULL) return; switch (key->keytype) { case ETYPE_DES_CBC_CRC: case ETYPE_DES_CBC_MD4: case ETYPE_DES_CBC_MD5: case ETYPE_DES3_CBC_MD5: case ETYPE_OLD_DES3_CBC_SHA1: case ETYPE_DES3_CBC_SHA1: case ETYPE_ARCFOUR_HMAC_MD5: case ETYPE_ARCFOUR_HMAC_MD5_56: break; default : ctx->more_flags |= IS_CFX; if ((acceptor && ctx->auth_context->local_subkey) || (!acceptor && ctx->auth_context->remote_subkey)) ctx->more_flags |= ACCEPTOR_SUBKEY; break; } if (ctx->crypto) krb5_crypto_destroy(context, ctx->crypto); /* XXX We really shouldn't ignore this; will come back to this */ (void) krb5_crypto_init(context, key, 0, &ctx->crypto); } static OM_uint32 gsskrb5_accept_delegated_token (OM_uint32 * minor_status, gsskrb5_ctx ctx, krb5_context context, gss_cred_id_t * delegated_cred_handle ) { krb5_ccache ccache = NULL; krb5_error_code kret; int32_t ac_flags, ret = GSS_S_COMPLETE; *minor_status = 0; /* XXX Create a new delegated_cred_handle? */ if (delegated_cred_handle == NULL) { ret = GSS_S_COMPLETE; goto out; } *delegated_cred_handle = NULL; kret = krb5_cc_new_unique (context, krb5_cc_type_memory, NULL, &ccache); if (kret) { ctx->flags &= ~GSS_C_DELEG_FLAG; goto out; } kret = krb5_cc_initialize(context, ccache, ctx->source); if (kret) { ctx->flags &= ~GSS_C_DELEG_FLAG; goto out; } krb5_auth_con_removeflags(context, ctx->auth_context, KRB5_AUTH_CONTEXT_DO_TIME, &ac_flags); kret = krb5_rd_cred2(context, ctx->auth_context, ccache, &ctx->fwd_data); krb5_auth_con_setflags(context, ctx->auth_context, ac_flags); if (kret) { ctx->flags &= ~GSS_C_DELEG_FLAG; ret = GSS_S_FAILURE; *minor_status = kret; goto out; } if (delegated_cred_handle) { gsskrb5_cred handle; ret = _gsskrb5_krb5_import_cred(minor_status, ccache, NULL, NULL, delegated_cred_handle); if (ret != GSS_S_COMPLETE) goto out; handle = (gsskrb5_cred) *delegated_cred_handle; handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE; krb5_cc_close(context, ccache); ccache = NULL; } out: if (ccache) { /* Don't destroy the default cred cache */ if (delegated_cred_handle == NULL) krb5_cc_close(context, ccache); else krb5_cc_destroy(context, ccache); } return ret; } static OM_uint32 gsskrb5_acceptor_ready(OM_uint32 * minor_status, gsskrb5_ctx ctx, krb5_context context, gss_cred_id_t *delegated_cred_handle) { OM_uint32 ret; int32_t seq_number; int is_cfx = 0; krb5_auth_con_getremoteseqnumber (context, ctx->auth_context, &seq_number); _gsskrb5i_is_cfx(context, ctx, 1); is_cfx = (ctx->more_flags & IS_CFX); ret = _gssapi_msg_order_create(minor_status, &ctx->order, _gssapi_msg_order_f(ctx->flags), seq_number, 0, is_cfx); if (ret) return ret; /* * If requested, set local sequence num to remote sequence if this * isn't a mutual authentication context */ if (!(ctx->flags & GSS_C_MUTUAL_FLAG) && _gssapi_msg_order_f(ctx->flags)) { krb5_auth_con_setlocalseqnumber(context, ctx->auth_context, seq_number); } /* * We should handle the delegation ticket, in case it's there */ if (ctx->fwd_data.length > 0 && (ctx->flags & GSS_C_DELEG_FLAG)) { ret = gsskrb5_accept_delegated_token(minor_status, ctx, context, delegated_cred_handle); if (ret != GSS_S_COMPLETE) return ret; } else { /* Well, looks like it wasn't there after all */ ctx->flags &= ~GSS_C_DELEG_FLAG; } ctx->state = ACCEPTOR_READY; ctx->more_flags |= OPEN; return GSS_S_COMPLETE; } static OM_uint32 send_error_token(OM_uint32 *minor_status, krb5_context context, krb5_error_code kret, krb5_principal server, krb5_data *indata, gss_buffer_t output_token) { krb5_principal ap_req_server = NULL; krb5_error_code ret; krb5_data outbuf; /* this e_data value encodes KERB_AP_ERR_TYPE_SKEW_RECOVERY which tells windows to try again with the corrected timestamp. See [MS-KILE] 2.2.1 KERB-ERROR-DATA */ krb5_data e_data = { 7, rk_UNCONST("\x30\x05\xa1\x03\x02\x01\x02") }; /* build server from request if the acceptor had not selected one */ if (server == NULL) { AP_REQ ap_req; ret = krb5_decode_ap_req(context, indata, &ap_req); if (ret) { *minor_status = ret; return GSS_S_FAILURE; } ret = _krb5_principalname2krb5_principal(context, &ap_req_server, ap_req.ticket.sname, ap_req.ticket.realm); free_AP_REQ(&ap_req); if (ret) { *minor_status = ret; return GSS_S_FAILURE; } server = ap_req_server; } ret = krb5_mk_error(context, kret, NULL, &e_data, NULL, server, NULL, NULL, &outbuf); if (ap_req_server) krb5_free_principal(context, ap_req_server); if (ret) { *minor_status = ret; return GSS_S_FAILURE; } ret = _gsskrb5_encapsulate(minor_status, &outbuf, output_token, "\x03\x00", GSS_KRB5_MECHANISM); krb5_data_free (&outbuf); if (ret) return ret; *minor_status = 0; return GSS_S_CONTINUE_NEEDED; } static OM_uint32 gsskrb5_acceptor_start(OM_uint32 * minor_status, gsskrb5_ctx ctx, krb5_context context, 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) { krb5_error_code kret; OM_uint32 ret = GSS_S_COMPLETE; krb5_data indata; krb5_flags ap_options; krb5_keytab keytab = NULL; int is_cfx = 0; int close_kt = 0; const gsskrb5_cred acceptor_cred = (gsskrb5_cred)acceptor_cred_handle; /* * We may, or may not, have an escapsulation. */ ret = _gsskrb5_decapsulate (minor_status, input_token_buffer, &indata, "\x01\x00", GSS_KRB5_MECHANISM); if (ret) { /* Assume that there is no OID wrapping. */ indata.length = input_token_buffer->length; indata.data = input_token_buffer->value; } /* * We need to get our keytab */ if (acceptor_cred == NULL) { HEIMDAL_MUTEX_lock(&gssapi_keytab_mutex); if (_gsskrb5_keytab != NULL) { char *name = NULL; kret = krb5_kt_get_full_name(context, _gsskrb5_keytab, &name); if (kret == 0) { kret = krb5_kt_resolve(context, name, &keytab); krb5_xfree(name); } if (kret == 0) close_kt = 1; else keytab = NULL; } HEIMDAL_MUTEX_unlock(&gssapi_keytab_mutex); } else if (acceptor_cred->keytab != NULL) { keytab = acceptor_cred->keytab; } /* * We need to check the ticket and create the AP-REP packet */ { krb5_rd_req_in_ctx in = NULL; krb5_rd_req_out_ctx out = NULL; krb5_principal server = NULL; if (acceptor_cred) server = acceptor_cred->principal; kret = krb5_rd_req_in_ctx_alloc(context, &in); if (kret == 0) kret = krb5_rd_req_in_set_keytab(context, in, keytab); if (kret) { if (in) krb5_rd_req_in_ctx_free(context, in); if (close_kt) krb5_kt_close(context, keytab); *minor_status = kret; return GSS_S_FAILURE; } kret = krb5_rd_req_ctx(context, &ctx->auth_context, &indata, server, in, &out); krb5_rd_req_in_ctx_free(context, in); if (close_kt) krb5_kt_close(context, keytab); if (kret == KRB5KRB_AP_ERR_SKEW || kret == KRB5KRB_AP_ERR_TKT_NYV) { /* * No reply in non-MUTUAL mode, but we don't know that its * non-MUTUAL mode yet, thats inside the 8003 checksum, so * lets only send the error token on clock skew, that * limit when send error token for non-MUTUAL. */ free_Authenticator(ctx->auth_context->authenticator); return send_error_token(minor_status, context, kret, server, &indata, output_token); } else if (kret) { *minor_status = kret; return GSS_S_FAILURE; } /* * we need to remember some data on the context_handle. */ kret = krb5_rd_req_out_get_ap_req_options(context, out, &ap_options); if (kret == 0) kret = krb5_rd_req_out_get_ticket(context, out, &ctx->ticket); if (kret == 0) kret = krb5_rd_req_out_get_keyblock(context, out, &ctx->service_keyblock); ctx->endtime = ctx->ticket->ticket.endtime; krb5_rd_req_out_ctx_free(context, out); if (kret) { ret = GSS_S_FAILURE; *minor_status = kret; return ret; } } /* * We need to copy the principal names to the context and the * calling layer. */ kret = krb5_copy_principal(context, ctx->ticket->client, &ctx->source); if (kret) { ret = GSS_S_FAILURE; *minor_status = kret; return ret; } kret = krb5_copy_principal(context, ctx->ticket->server, &ctx->target); if (kret) { ret = GSS_S_FAILURE; *minor_status = kret; return ret; } /* * We need to setup some compat stuff, this assumes that * context_handle->target is already set. */ ret = _gss_DES3_get_mic_compat(minor_status, ctx, context); if (ret) return ret; if (src_name != NULL) { kret = krb5_copy_principal (context, ctx->ticket->client, (gsskrb5_name*)src_name); if (kret) { ret = GSS_S_FAILURE; *minor_status = kret; return ret; } } /* * We need to get the flags out of the 8003 checksum. */ { krb5_authenticator authenticator; kret = krb5_auth_con_getauthenticator(context, ctx->auth_context, &authenticator); if(kret) { ret = GSS_S_FAILURE; *minor_status = kret; return ret; } if (authenticator->cksum != NULL && authenticator->cksum->cksumtype == CKSUMTYPE_GSSAPI) { ret = _gsskrb5_verify_8003_checksum(minor_status, input_chan_bindings, authenticator->cksum, &ctx->flags, &ctx->fwd_data); if (ret) { krb5_free_authenticator(context, &authenticator); return ret; } } else { if (authenticator->cksum != NULL) { krb5_crypto crypto; kret = krb5_crypto_init(context, ctx->auth_context->keyblock, 0, &crypto); if (kret) { krb5_free_authenticator(context, &authenticator); ret = GSS_S_FAILURE; *minor_status = kret; return ret; } /* * Windows accepts Samba3's use of a kerberos, rather than * GSSAPI checksum here */ kret = krb5_verify_checksum(context, crypto, KRB5_KU_AP_REQ_AUTH_CKSUM, NULL, 0, authenticator->cksum); krb5_crypto_destroy(context, crypto); if (kret) { krb5_free_authenticator(context, &authenticator); ret = GSS_S_BAD_SIG; *minor_status = kret; return ret; } } /* * If there is no checksum or a kerberos checksum (which Windows * and Samba accept), we use the ap_options to guess the mutual * flag. */ ctx->flags = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG; if (ap_options & AP_OPTS_MUTUAL_REQUIRED) ctx->flags |= GSS_C_MUTUAL_FLAG; } krb5_free_authenticator(context, &authenticator); } if(ctx->flags & GSS_C_MUTUAL_FLAG) { krb5_data outbuf; int use_subkey = 0; _gsskrb5i_is_cfx(context, ctx, 1); is_cfx = (ctx->more_flags & IS_CFX); if (is_cfx || (ap_options & AP_OPTS_USE_SUBKEY)) { use_subkey = 1; } else { krb5_keyblock *rkey; /* * If there is a initiator subkey, copy that to acceptor * subkey to match Windows behavior */ kret = krb5_auth_con_getremotesubkey(context, ctx->auth_context, &rkey); if (kret == 0) { kret = krb5_auth_con_setlocalsubkey(context, ctx->auth_context, rkey); if (kret == 0) use_subkey = 1; } krb5_free_keyblock(context, rkey); } if (use_subkey) { ctx->more_flags |= ACCEPTOR_SUBKEY; krb5_auth_con_addflags(context, ctx->auth_context, KRB5_AUTH_CONTEXT_USE_SUBKEY, NULL); } kret = krb5_mk_rep(context, ctx->auth_context, &outbuf); if (kret) { *minor_status = kret; return GSS_S_FAILURE; } if (IS_DCE_STYLE(ctx)) { output_token->length = outbuf.length; output_token->value = outbuf.data; } else { ret = _gsskrb5_encapsulate(minor_status, &outbuf, output_token, "\x02\x00", GSS_KRB5_MECHANISM); krb5_data_free (&outbuf); if (ret) return ret; } } ctx->flags |= GSS_C_TRANS_FLAG; /* Remember the flags */ ctx->endtime = ctx->ticket->ticket.endtime; ctx->more_flags |= OPEN; if (mech_type) *mech_type = GSS_KRB5_MECHANISM; if (time_rec) { ret = _gsskrb5_lifetime_left(minor_status, context, ctx->endtime, time_rec); if (ret) { return ret; } } /* * When GSS_C_DCE_STYLE is in use, we need ask for a AP-REP from * the client. */ if (IS_DCE_STYLE(ctx)) { /* * Return flags to caller, but we haven't processed * delgations yet */ if (ret_flags) *ret_flags = (ctx->flags & ~GSS_C_DELEG_FLAG); ctx->state = ACCEPTOR_WAIT_FOR_DCESTYLE; return GSS_S_CONTINUE_NEEDED; } ret = gsskrb5_acceptor_ready(minor_status, ctx, context, delegated_cred_handle); if (ret_flags) *ret_flags = ctx->flags; return ret; } static OM_uint32 acceptor_wait_for_dcestyle(OM_uint32 * minor_status, gsskrb5_ctx ctx, krb5_context context, 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 ret; krb5_error_code kret; krb5_data inbuf; int32_t r_seq_number, l_seq_number; /* * We know it's GSS_C_DCE_STYLE so we don't need to decapsulate the AP_REP */ inbuf.length = input_token_buffer->length; inbuf.data = input_token_buffer->value; /* * We need to remeber the old remote seq_number, then check if the * client has replied with our local seq_number, and then reset * the remote seq_number to the old value */ { kret = krb5_auth_con_getlocalseqnumber(context, ctx->auth_context, &l_seq_number); if (kret) { *minor_status = kret; return GSS_S_FAILURE; } kret = krb5_auth_con_getremoteseqnumber(context, ctx->auth_context, &r_seq_number); if (kret) { *minor_status = kret; return GSS_S_FAILURE; } kret = krb5_auth_con_setremoteseqnumber(context, ctx->auth_context, l_seq_number); if (kret) { *minor_status = kret; return GSS_S_FAILURE; } } /* * We need to verify the AP_REP, but we need to flag that this is * DCE_STYLE, so don't check the timestamps this time, but put the * flag DO_TIME back afterward. */ { krb5_ap_rep_enc_part *repl; int32_t auth_flags; krb5_auth_con_removeflags(context, ctx->auth_context, KRB5_AUTH_CONTEXT_DO_TIME, &auth_flags); kret = krb5_rd_rep(context, ctx->auth_context, &inbuf, &repl); if (kret) { *minor_status = kret; return GSS_S_FAILURE; } krb5_free_ap_rep_enc_part(context, repl); krb5_auth_con_setflags(context, ctx->auth_context, auth_flags); } /* We need to check the liftime */ { OM_uint32 lifetime_rec; ret = _gsskrb5_lifetime_left(minor_status, context, ctx->endtime, &lifetime_rec); if (ret) { return ret; } if (lifetime_rec == 0) { return GSS_S_CONTEXT_EXPIRED; } if (time_rec) *time_rec = lifetime_rec; } /* We need to give the caller the flags which are in use */ if (ret_flags) *ret_flags = ctx->flags; if (src_name) { kret = krb5_copy_principal(context, ctx->source, (gsskrb5_name*)src_name); if (kret) { *minor_status = kret; return GSS_S_FAILURE; } } /* * After the krb5_rd_rep() the remote and local seq_number should * be the same, because the client just replies the seq_number * from our AP-REP in its AP-REP, but then the client uses the * seq_number from its AP-REQ for GSS_wrap() */ { int32_t tmp_r_seq_number, tmp_l_seq_number; kret = krb5_auth_con_getremoteseqnumber(context, ctx->auth_context, &tmp_r_seq_number); if (kret) { *minor_status = kret; return GSS_S_FAILURE; } kret = krb5_auth_con_getlocalseqnumber(context, ctx->auth_context, &tmp_l_seq_number); if (kret) { *minor_status = kret; return GSS_S_FAILURE; } /* * Here we check if the client has responsed with our local seq_number, */ if (tmp_r_seq_number != tmp_l_seq_number) { return GSS_S_UNSEQ_TOKEN; } } /* * We need to reset the remote seq_number, because the client will use, * the old one for the GSS_wrap() calls */ { kret = krb5_auth_con_setremoteseqnumber(context, ctx->auth_context, r_seq_number); if (kret) { *minor_status = kret; return GSS_S_FAILURE; } } return gsskrb5_acceptor_ready(minor_status, ctx, context, delegated_cred_handle); } OM_uint32 GSSAPI_CALLCONV _gsskrb5_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) { krb5_context context; OM_uint32 ret; gsskrb5_ctx ctx; GSSAPI_KRB5_INIT(&context); output_token->length = 0; output_token->value = NULL; if (src_name != NULL) *src_name = NULL; if (mech_type) *mech_type = GSS_KRB5_MECHANISM; if (*context_handle == GSS_C_NO_CONTEXT) { ret = _gsskrb5_create_ctx(minor_status, context_handle, context, input_chan_bindings, ACCEPTOR_START); if (ret) return ret; } ctx = (gsskrb5_ctx)*context_handle; /* * TODO: check the channel_bindings * (above just sets them to krb5 layer) */ HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); switch (ctx->state) { case ACCEPTOR_START: ret = gsskrb5_acceptor_start(minor_status, ctx, context, acceptor_cred_handle, input_token_buffer, input_chan_bindings, src_name, mech_type, output_token, ret_flags, time_rec, delegated_cred_handle); break; case ACCEPTOR_WAIT_FOR_DCESTYLE: ret = acceptor_wait_for_dcestyle(minor_status, ctx, context, acceptor_cred_handle, input_token_buffer, input_chan_bindings, src_name, mech_type, output_token, ret_flags, time_rec, delegated_cred_handle); break; case ACCEPTOR_READY: /* * If we get there, the caller have called * gss_accept_sec_context() one time too many. */ ret = GSS_S_BAD_STATUS; break; default: /* TODO: is this correct here? --metze */ ret = GSS_S_BAD_STATUS; break; } HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); if (GSS_ERROR(ret)) { OM_uint32 min2; _gsskrb5_delete_sec_context(&min2, context_handle, GSS_C_NO_BUFFER); } return ret; }