/* Reply part of gss_krb5_init_sec_context. Assumes that context_handle is valid, and has krb5 specific structure, and that output_token is valid and cleared. */ static OM_uint32 init_reply (OM_uint32 * minor_status, const gss_cred_id_t initiator_cred_handle, 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) { gss_ctx_id_t ctx = *context_handle; _gss_krb5_ctx_t k5 = ctx->krb5; char *data; size_t datalen; int rc; if (!gss_decapsulate_token (input_token, GSS_KRB5, &data, &datalen)) return GSS_S_DEFECTIVE_TOKEN; if (datalen < TOK_LEN) return GSS_S_DEFECTIVE_TOKEN; if (memcmp (data, TOK_AP_REP, TOK_LEN) != 0) return GSS_S_DEFECTIVE_TOKEN; rc = shishi_ap_rep_der_set (k5->ap, data + TOK_LEN, datalen - TOK_LEN); if (rc != SHISHI_OK) return GSS_S_DEFECTIVE_TOKEN; rc = shishi_ap_rep_verify (k5->ap); if (rc != SHISHI_OK) return GSS_S_DEFECTIVE_TOKEN; rc = shishi_encapreppart_seqnumber_get (k5->sh, shishi_ap_encapreppart (k5->ap), &k5->acceptseqnr); if (rc != SHISHI_OK) { /* A strict 1964 implementation would return GSS_S_DEFECTIVE_TOKEN here. gssapi-cfx permit absent sequence number, though. */ k5->acceptseqnr = 0; } return GSS_S_COMPLETE; }
/* Copy token to output buffer. On first round trip, strip context token header and add channel binding data. For later round trips, just copy the buffer. Return GSASL_OK on success or an error code. */ static int token2output (Gsasl_session * sctx, _gsasl_gs2_client_state * state, const gss_buffer_t token, char **output, size_t * output_len) { OM_uint32 maj_stat, min_stat; gss_buffer_desc bufdesc; if (state->step == 1) { state->step++; maj_stat = gss_decapsulate_token (token, state->mech_oid, &bufdesc); if (GSS_ERROR (maj_stat)) return GSASL_GSSAPI_ENCAPSULATE_TOKEN_ERROR; *output_len = state->cb.application_data.length + bufdesc.length; *output = malloc (*output_len); if (!*output) { gss_release_buffer (&min_stat, &bufdesc); return GSASL_MALLOC_ERROR; } memcpy (*output, state->cb.application_data.value, state->cb.application_data.length); memcpy (*output + state->cb.application_data.length, bufdesc.value, bufdesc.length); maj_stat = gss_release_buffer (&min_stat, &bufdesc); if (GSS_ERROR (maj_stat)) return GSASL_GSSAPI_RELEASE_BUFFER_ERROR; } else { *output_len = token->length; *output = malloc (*output_len); if (!*output) return GSASL_MALLOC_ERROR; memcpy (*output, token->value, token->length); } return GSASL_OK; }
static OM_uint32 GSSAPI_CALLCONV acceptor_start (OM_uint32 * minor_status, gss_ctx_id_t * context_handle, const gss_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, junk; NegotiationToken nt; size_t nt_len; NegTokenInit *ni; int i; 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; 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); ret = _gss_spnego_alloc_sec_context(minor_status, context_handle); if (ret != GSS_S_COMPLETE) return ret; ctx = (gssspnego_ctx)*context_handle; /* * The GSS-API encapsulation is only present on the initial * context token (negTokenInit). */ ret = gss_decapsulate_token (input_token_buffer, GSS_SPNEGO_MECHANISM, &data); if (ret) return ret; ret = decode_NegotiationToken(data.value, data.length, &nt, &nt_len); gss_release_buffer(minor_status, &data); if (ret) { *minor_status = ret; return GSS_S_DEFECTIVE_TOKEN; } if (nt.element != choice_NegotiationToken_negTokenInit) { *minor_status = 0; return GSS_S_DEFECTIVE_TOKEN; } ni = &nt.u.negTokenInit; if (ni->mechTypes.len < 1) { free_NegotiationToken(&nt); *minor_status = 0; return GSS_S_DEFECTIVE_TOKEN; } HEIMDAL_MUTEX_lock(&ctx->ctx_id_mutex); 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; } /* * First we try the opportunistic token if we have support for it, * don't try to verify we have credential for the token, * gss_accept_sec_context() will (hopefully) tell us that. * If that failes, */ ret = select_mech(minor_status, &ni->mechTypes.val[0], 0, &preferred_mech_type); if (ret == 0 && 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); 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; ret = acceptor_complete(minor_status, ctx, &get_mic, &mech_buf, mech_input_token, &mech_output_token, ni->mechListMIC, output_token); if (ret != GSS_S_COMPLETE) goto out; first_ok = 1; } else { gss_mg_collect_error(preferred_mech_type, ret, *minor_status); } } /* * If opportunistic token failed, lets try the other mechs. */ if (!first_ok && ni->mechToken != NULL) { preferred_mech_type = GSS_C_NO_OID; /* Call glue layer to find first mech we support */ for (i = 1; i < ni->mechTypes.len; ++i) { ret = select_mech(minor_status, &ni->mechTypes.val[i], 1, &preferred_mech_type); if (ret == 0) break; } if (preferred_mech_type == GSS_C_NO_OID) { HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); free_NegotiationToken(&nt); return ret; } ctx->preferred_mech_type = preferred_mech_type; } /* * The initial token always have a response */ ret = send_accept (minor_status, ctx, &mech_output_token, 1, get_mic ? &mech_buf : NULL, output_token); if (ret) goto out; out: 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 (mech_type != NULL) *mech_type = ctx->negotiated_mech_type; if (ret_flags != NULL) *ret_flags = ctx->mech_flags; if (time_rec != NULL) *time_rec = ctx->mech_time_rec; if (ret == GSS_S_COMPLETE || ret == GSS_S_CONTINUE_NEEDED) { HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex); return ret; } _gss_spnego_internal_delete_sec_context(&junk, context_handle, GSS_C_NO_BUFFER); return ret; }
/* Allows a remotely initiated security context between the application and a remote peer to be established, using krb5. Assumes context_handle is valid. */ OM_uint32 gss_krb5_accept_sec_context (OM_uint32 * minor_status, gss_ctx_id_t * context_handle, const gss_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) { gss_buffer_desc data; char *in; size_t inlen; gss_ctx_id_t cx; _gss_krb5_ctx_t cxk5; _gss_krb5_cred_t crk5; int rc; if (minor_status) *minor_status = 0; if (ret_flags) *ret_flags = 0; if (!acceptor_cred_handle) /* XXX support GSS_C_NO_CREDENTIAL: acquire_cred() default server */ return GSS_S_NO_CRED; if (*context_handle) return GSS_S_FAILURE; crk5 = acceptor_cred_handle->krb5; cx = calloc (sizeof (*cx), 1); if (!cx) { if (minor_status) *minor_status = ENOMEM; return GSS_S_FAILURE; } cxk5 = calloc (sizeof (*cxk5), 1); if (!cxk5) { free (cx); if (minor_status) *minor_status = ENOMEM; return GSS_S_FAILURE; } cx->mech = GSS_KRB5; cx->krb5 = cxk5; /* XXX cx->peer?? */ *context_handle = cx; cxk5->sh = crk5->sh; cxk5->key = crk5->key; cxk5->acceptor = 1; rc = shishi_ap (cxk5->sh, &cxk5->ap); if (rc != SHISHI_OK) return GSS_S_FAILURE; rc = gss_decapsulate_token (input_token_buffer, GSS_KRB5, &in, &inlen); if (!rc) return GSS_S_BAD_MIC; if (inlen < TOK_LEN) return GSS_S_BAD_MIC; if (memcmp (in, TOK_AP_REQ, TOK_LEN) != 0) return GSS_S_BAD_MIC; rc = shishi_ap_req_der_set (cxk5->ap, in + TOK_LEN, inlen - TOK_LEN); if (rc != SHISHI_OK) return GSS_S_FAILURE; rc = shishi_ap_req_process (cxk5->ap, crk5->key); if (rc != SHISHI_OK) { if (minor_status) *minor_status = GSS_KRB5_S_G_VALIDATE_FAILED; return GSS_S_FAILURE; } rc = shishi_authenticator_seqnumber_get (cxk5->sh, shishi_ap_authenticator (cxk5->ap), &cxk5->initseqnr); if (rc != SHISHI_OK) return GSS_S_FAILURE; rc = _gss_krb5_checksum_parse (minor_status, context_handle, input_chan_bindings); if (rc != GSS_S_COMPLETE) return GSS_S_FAILURE; cxk5->tkt = shishi_ap_tkt (cxk5->ap); cxk5->key = shishi_ap_key (cxk5->ap); if (shishi_apreq_mutual_required_p (crk5->sh, shishi_ap_req (cxk5->ap))) { Shishi_asn1 aprep; rc = shishi_ap_rep_asn1 (cxk5->ap, &aprep); if (rc != SHISHI_OK) { printf ("Error creating AP-REP: %s\n", shishi_strerror (rc)); return GSS_S_FAILURE; } rc = shishi_encapreppart_seqnumber_get (cxk5->sh, shishi_ap_encapreppart (cxk5-> ap), &cxk5->acceptseqnr); if (rc != SHISHI_OK) { /* A strict 1964 implementation would return GSS_S_DEFECTIVE_TOKEN here. gssapi-cfx permit absent sequence number, though. */ cxk5->acceptseqnr = 0; } { char *der; size_t len; rc = shishi_asn1_to_der (crk5->sh, aprep, &der, &len); if (rc != SHISHI_OK) { printf ("Error der encoding aprep: %s\n", shishi_strerror (rc)); return GSS_S_FAILURE; } data.value = der; data.length = len; } rc = gss_encapsulate_token_prefix (&data, TOK_AP_REP, TOK_LEN, GSS_KRB5, output_token); if (!rc) return GSS_S_FAILURE; if (ret_flags) *ret_flags = GSS_C_MUTUAL_FLAG; } else { output_token->value = NULL; output_token->length = 0; } if (src_name) { gss_name_t p; p = malloc (sizeof (*p)); if (!p) { if (minor_status) *minor_status = ENOMEM; return GSS_S_FAILURE; } rc = shishi_encticketpart_client (cxk5->sh, shishi_tkt_encticketpart (cxk5->tkt), &p->value, &p->length); if (rc != SHISHI_OK) return GSS_S_FAILURE; p->type = GSS_KRB5_NT_PRINCIPAL_NAME; *src_name = p; } /* PROT_READY is not mentioned in 1964/gssapi-cfx but we support it anyway. */ if (ret_flags) *ret_flags |= GSS_C_PROT_READY_FLAG; if (minor_status) *minor_status = 0; return GSS_S_COMPLETE; }
static int test_scram(const char *test_name, const char *user, const char *password) { gss_name_t cname, target = GSS_C_NO_NAME; OM_uint32 maj_stat, min_stat; gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; gss_buffer_desc cn, input, output, output2; int ret; heim_scram *scram = NULL; heim_scram_data in, out; gss_auth_identity_desc identity; memset(&identity, 0, sizeof(identity)); identity.username = rk_UNCONST(user); identity.realm = ""; identity.password = rk_UNCONST(password); cn.value = rk_UNCONST(user); cn.length = strlen(user); maj_stat = gss_import_name(&min_stat, &cn, GSS_C_NT_USER_NAME, &cname); if (maj_stat) errx(1, "gss_import_name: %d", (int)maj_stat); maj_stat = gss_acquire_cred_ex_f(NULL, cname, 0, GSS_C_INDEFINITE, GSS_SCRAM_MECHANISM, GSS_C_INITIATE, &identity, NULL, ac_complete); if (maj_stat) errx(1, "gss_acquire_cred_ex_f: %d", (int)maj_stat); if (client_cred == GSS_C_NO_CREDENTIAL) errx(1, "gss_acquire_cred_ex_f"); cn.value = rk_UNCONST("host@localhost"); cn.length = strlen((char *)cn.value); maj_stat = gss_import_name(&min_stat, &cn, GSS_C_NT_HOSTBASED_SERVICE, &target); if (maj_stat) errx(1, "gss_import_name: %d", (int)maj_stat); maj_stat = gss_init_sec_context(&min_stat, client_cred, &ctx, target, GSS_SCRAM_MECHANISM, 0, 0, NULL, GSS_C_NO_BUFFER, NULL, &output, NULL, NULL); if (maj_stat != GSS_S_CONTINUE_NEEDED) errx(1, "accept_sec_context %s %s", test_name, gssapi_err(maj_stat, min_stat, GSS_C_NO_OID)); if (output.length == 0) errx(1, "output.length == 0"); maj_stat = gss_decapsulate_token(&output, GSS_SCRAM_MECHANISM, &output2); if (maj_stat) errx(1, "decapsulate token"); in.length = output2.length; in.data = output2.value; ret = heim_scram_server1(&in, NULL, HEIM_SCRAM_DIGEST_SHA1, &server_proc, NULL, &scram, &out); if (ret) errx(1, "heim_scram_server1"); gss_release_buffer(&min_stat, &output); input.length = out.length; input.value = out.data; maj_stat = gss_init_sec_context(&min_stat, client_cred, &ctx, target, GSS_SCRAM_MECHANISM, 0, 0, NULL, &input, NULL, &output, NULL, NULL); if (maj_stat != GSS_S_CONTINUE_NEEDED) { warnx("accept_sec_context v1 2 %s", gssapi_err(maj_stat, min_stat, GSS_C_NO_OID)); return 1; } in.length = output.length; in.data = output.value; ret = heim_scram_server2(&in, scram, &out); if (ret) errx(1, "heim_scram_server2"); gss_release_buffer(&min_stat, &output); input.length = out.length; input.value = out.data; maj_stat = gss_init_sec_context(&min_stat, client_cred, &ctx, target, GSS_SCRAM_MECHANISM, 0, 0, NULL, &input, NULL, &output, NULL, NULL); if (maj_stat != GSS_S_COMPLETE) { warnx("accept_sec_context v1 2 %s", gssapi_err(maj_stat, min_stat, GSS_C_NO_OID)); return 1; } heim_scram_free(scram); //gss_destroy_cred(NULL, &client_cred); printf("done: %s\n", test_name); return 0; }