diff options
Diffstat (limited to 'third_party/heimdal/lib/krb5/init_creds_pw.c')
-rw-r--r-- | third_party/heimdal/lib/krb5/init_creds_pw.c | 4052 |
1 files changed, 4052 insertions, 0 deletions
diff --git a/third_party/heimdal/lib/krb5/init_creds_pw.c b/third_party/heimdal/lib/krb5/init_creds_pw.c new file mode 100644 index 0000000..4790c7e --- /dev/null +++ b/third_party/heimdal/lib/krb5/init_creds_pw.c @@ -0,0 +1,4052 @@ +/* + * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Portions Copyright (c) 2009 - 2010 Apple Inc. All rights reserved. + * Portions 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: + * + * 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 "krb5_locl.h" + +#include <heimbasepriv.h> + +struct pa_info_data { + krb5_enctype etype; + krb5_salt salt; + krb5_data *s2kparams; +}; + +struct krb5_gss_init_ctx_data { + krb5_gssic_step step; + krb5_gssic_finish finish; + krb5_gssic_release_cred release_cred; + krb5_gssic_delete_sec_context delete_sec_context; + + const struct gss_OID_desc_struct *mech; + struct gss_cred_id_t_desc_struct *cred; + + struct { + unsigned int release_cred : 1; + } flags; +}; + +struct krb5_get_init_creds_ctx { + KDCOptions flags; + krb5_creds cred; + const krb5_addresses *addrs; + krb5_enctype *etypes; + krb5_preauthtype *pre_auth_types; + char *in_tkt_service; + unsigned nonce; + unsigned pk_nonce; + + krb5_data req_buffer; + AS_REQ as_req; + int pa_counter; + + /* password and keytab_data is freed on completion */ + char *password; + krb5_keytab_key_proc_args *keytab_data; + + krb5_pointer *keyseed; + krb5_s2k_proc keyproc; + + krb5_get_init_creds_tristate req_pac; + + krb5_pk_init_ctx pk_init_ctx; + krb5_gss_init_ctx gss_init_ctx; + int ic_flags; + + char *kdc_hostname; + char *sitename; + + struct { + unsigned int change_password:1; + unsigned int change_password_prompt:1; + unsigned int allow_enc_pa_rep:1; + unsigned int allow_save_as_reply_key:1; + } runflags; + + struct pa_info_data paid; + + METHOD_DATA md; + KRB_ERROR error; + EncKDCRepPart enc_part; + + krb5_prompter_fct prompter; + void *prompter_data; + int warned_user; + + struct pa_info_data *ppaid; + + struct krb5_fast_state fast_state; + krb5_enctype as_enctype; + krb5_keyblock *as_reply_key; + + /* current and available pa mechansm in this exchange */ + struct pa_auth_mech *pa_mech; + heim_array_t available_pa_mechs; + const char *pa_used; + + struct { + struct timeval run_time; + } stats; +}; + +static void +free_paid(krb5_context context, struct pa_info_data *ppaid) +{ + krb5_free_salt(context, ppaid->salt); + if (ppaid->s2kparams) + krb5_free_data(context, ppaid->s2kparams); + memset(ppaid, 0, sizeof(*ppaid)); +} + +static krb5_error_code KRB5_CALLCONV +default_s2k_func(krb5_context context, krb5_enctype type, + krb5_const_pointer keyseed, + krb5_salt salt, krb5_data *s2kparms, + krb5_keyblock **key) +{ + krb5_error_code ret; + krb5_data password; + krb5_data opaque; + + if (_krb5_have_debug(context, 5)) { + char *str = NULL; + ret = krb5_enctype_to_string(context, type, &str); + if (ret) + return ret; + + _krb5_debug(context, 5, "krb5_get_init_creds: using default_s2k_func: %s (%d)", str, (int)type); + free(str); + } + + password.data = rk_UNCONST(keyseed); + password.length = keyseed ? strlen(keyseed) : 0; + if (s2kparms) + opaque = *s2kparms; + else + krb5_data_zero(&opaque); + + *key = malloc(sizeof(**key)); + if (*key == NULL) + return krb5_enomem(context); + ret = krb5_string_to_key_data_salt_opaque(context, type, password, + salt, opaque, *key); + if (ret) { + free(*key); + *key = NULL; + } + return ret; +} + +static void +free_gss_init_ctx(krb5_context context, krb5_gss_init_ctx gssic) +{ + if (gssic == NULL) + return; + + if (gssic->flags.release_cred) + gssic->release_cred(context, gssic, gssic->cred); + free(gssic); +} + +static void +free_init_creds_ctx(krb5_context context, krb5_init_creds_context ctx) +{ + if (ctx->etypes) + free(ctx->etypes); + if (ctx->pre_auth_types) + free (ctx->pre_auth_types); + if (ctx->in_tkt_service) + free(ctx->in_tkt_service); + if (ctx->keytab_data) + free(ctx->keytab_data); + if (ctx->password) { + size_t len; + len = strlen(ctx->password); + memset_s(ctx->password, len, 0, len); + free(ctx->password); + } + free_gss_init_ctx(context, ctx->gss_init_ctx); + /* + * FAST state + */ + _krb5_fast_free(context, &ctx->fast_state); + if (ctx->as_reply_key) + krb5_free_keyblock(context, ctx->as_reply_key); + + krb5_data_free(&ctx->req_buffer); + krb5_free_cred_contents(context, &ctx->cred); + free_METHOD_DATA(&ctx->md); + free_EncKDCRepPart(&ctx->enc_part); + free_KRB_ERROR(&ctx->error); + free_AS_REQ(&ctx->as_req); + + heim_release(ctx->available_pa_mechs); + heim_release(ctx->pa_mech); + ctx->pa_mech = NULL; + free(ctx->kdc_hostname); + free(ctx->sitename); + free_paid(context, &ctx->paid); + memset_s(ctx, sizeof(*ctx), 0, sizeof(*ctx)); +} + +static krb5_deltat +get_config_time (krb5_context context, + const char *realm, + const char *name, + int def) +{ + krb5_deltat ret; + + ret = krb5_config_get_time (context, NULL, + "realms", + realm, + name, + NULL); + if (ret >= 0) + return ret; + ret = krb5_config_get_time (context, NULL, + "libdefaults", + name, + NULL); + if (ret >= 0) + return ret; + return def; +} + +static krb5_error_code +init_cred (krb5_context context, + krb5_creds *cred, + krb5_principal client, + krb5_deltat start_time, + krb5_get_init_creds_opt *options) +{ + krb5_error_code ret; + krb5_deltat tmp; + krb5_timestamp now; + + krb5_timeofday (context, &now); + + memset (cred, 0, sizeof(*cred)); + + if (client) + ret = krb5_copy_principal(context, client, &cred->client); + else + ret = krb5_get_default_principal(context, &cred->client); + if (ret) + goto out; + + if (start_time) + cred->times.starttime = now + start_time; + + if (options->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE) + tmp = options->tkt_life; + else + tmp = KRB5_TKT_LIFETIME_DEFAULT; + cred->times.endtime = now + tmp; + + if ((options->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE)) { + if (options->renew_life > 0) + tmp = options->renew_life; + else + tmp = KRB5_TKT_RENEW_LIFETIME_DEFAULT; + cred->times.renew_till = now + tmp; + } + + return 0; + +out: + krb5_free_cred_contents (context, cred); + return ret; +} + +/* + * Print a message (str) to the user about the expiration in `lr' + */ + +static void +report_expiration (krb5_context context, + krb5_prompter_fct prompter, + krb5_data *data, + const char *str, + time_t now) +{ + char *p = NULL; + + if (asprintf(&p, "%s%s", str, ctime(&now)) < 0 || p == NULL) + return; + (*prompter)(context, data, NULL, p, 0, NULL); + free(p); +} + +/* + * Check the context, and in the case there is a expiration warning, + * use the prompter to print the warning. + * + * @param context A Kerberos 5 context. + * @param options An GIC options structure + * @param ctx The krb5_init_creds_context check for expiration. + */ + +krb5_error_code +krb5_process_last_request(krb5_context context, + krb5_get_init_creds_opt *options, + krb5_init_creds_context ctx) +{ + LastReq *lr; + size_t i; + + /* + * First check if there is a API consumer. + */ + + lr = &ctx->enc_part.last_req; + + if (options && options->opt_private && options->opt_private->lr.func) { + krb5_last_req_entry **lre; + + lre = calloc(lr->len + 1, sizeof(*lre)); + if (lre == NULL) + return krb5_enomem(context); + + for (i = 0; i < lr->len; i++) { + lre[i] = calloc(1, sizeof(*lre[i])); + if (lre[i] == NULL) + break; + lre[i]->lr_type = lr->val[i].lr_type; + lre[i]->value = lr->val[i].lr_value; + } + + (*options->opt_private->lr.func)(context, lre, + options->opt_private->lr.ctx); + + for (i = 0; i < lr->len; i++) + free(lre[i]); + free(lre); + } + + return krb5_init_creds_warn_user(context, ctx); +} + +/** + * Warn the user using prompter in the krb5_init_creds_context about + * possible password and account expiration. + * + * @param context a Kerberos 5 context. + * @param ctx a krb5_init_creds_context context. + * + * @return 0 for success, or an Kerberos 5 error code, see krb5_get_error_message(). + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_warn_user(krb5_context context, + krb5_init_creds_context ctx) +{ + krb5_timestamp sec; + krb5_const_realm realm; + krb5_enctype weak_enctype = KRB5_ENCTYPE_NULL; + LastReq *lr; + unsigned i; + time_t t; + + if (ctx->prompter == NULL) + return 0; + + if (ctx->warned_user) + return 0; + + ctx->warned_user = 1; + + krb5_timeofday (context, &sec); + + realm = krb5_principal_get_realm (context, ctx->cred.client); + lr = &ctx->enc_part.last_req; + + t = sec + get_config_time (context, + realm, + "warn_pwexpire", + 7 * 24 * 60 * 60); + + for (i = 0; i < lr->len; ++i) { + if (lr->val[i].lr_value <= t) { + switch (lr->val[i].lr_type) { + case LR_PW_EXPTIME : + report_expiration(context, ctx->prompter, + ctx->prompter_data, + "Your password will expire at ", + lr->val[i].lr_value); + break; + case LR_ACCT_EXPTIME : + report_expiration(context, ctx->prompter, + ctx->prompter_data, + "Your account will expire at ", + lr->val[i].lr_value); + break; + default: + break; + } + } + } + + if (krb5_is_enctype_weak(context, ctx->as_enctype)) + weak_enctype = ctx->as_enctype; + else if (krb5_is_enctype_weak(context, ctx->cred.session.keytype)) + weak_enctype = ctx->cred.session.keytype; + + if (ctx->prompter && weak_enctype != KRB5_ENCTYPE_NULL) { + int suppress = krb5_config_get_bool_default(context, NULL, false, + "libdefaults", + "suppress_weak_enctype", NULL); + if (!suppress) { + char *str = NULL, *p = NULL; + int aret; + + (void) krb5_enctype_to_string(context, weak_enctype, &str); + aret = asprintf(&p, "Encryption type %s(%d) used for authentication is weak and will be deprecated", + str ? str : "unknown", weak_enctype); + if (aret >= 0 && p) { + (*ctx->prompter)(context, ctx->prompter_data, NULL, p, 0, NULL); + free(p); + } + free(str); + } + } + + return 0; +} + +static const krb5_addresses no_addrs = { 0, NULL }; + +static krb5_error_code +get_init_creds_common(krb5_context context, + krb5_principal client, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_deltat start_time, + krb5_get_init_creds_opt *options, + krb5_init_creds_context ctx) +{ + krb5_get_init_creds_opt *default_opt = NULL; + krb5_error_code ret; + krb5_enctype *etypes; + krb5_preauthtype *pre_auth_types; + + memset(ctx, 0, sizeof(*ctx)); + + if (options == NULL) { + const char *realm = krb5_principal_get_realm(context, client); + + ret = krb5_get_init_creds_opt_alloc(context, &default_opt); + if (ret) + return ret; + options = default_opt; + krb5_get_init_creds_opt_set_default_flags(context, NULL, realm, options); + } + + if (options->opt_private) { + if (options->opt_private->password) { + ret = krb5_init_creds_set_password(context, ctx, + options->opt_private->password); + if (ret) + goto out; + } + + ctx->keyproc = options->opt_private->key_proc; + ctx->req_pac = options->opt_private->req_pac; + ctx->pk_init_ctx = options->opt_private->pk_init_ctx; + ctx->ic_flags = options->opt_private->flags; + } else + ctx->req_pac = KRB5_INIT_CREDS_TRISTATE_UNSET; + + if (ctx->keyproc == NULL) + ctx->keyproc = default_s2k_func; + + if (ctx->ic_flags & KRB5_INIT_CREDS_CANONICALIZE) + ctx->flags.canonicalize = 1; + if (krb5_principal_get_type(context, client) == KRB5_NT_ENTERPRISE_PRINCIPAL) + ctx->flags.canonicalize = 1; + + ctx->pre_auth_types = NULL; + ctx->addrs = NULL; + ctx->etypes = NULL; + ctx->pre_auth_types = NULL; + + ret = init_cred(context, &ctx->cred, client, start_time, options); + if (ret) + goto out; + + ret = krb5_init_creds_set_service(context, ctx, NULL); + if (ret) + goto out; + + if (options->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE) + ctx->flags.forwardable = options->forwardable; + + if (options->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE) + ctx->flags.proxiable = options->proxiable; + + if (start_time) + ctx->flags.postdated = 1; + if (ctx->cred.times.renew_till) + ctx->flags.renewable = 1; + if (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST) { + ctx->addrs = options->address_list; + } else if (options->opt_private) { + switch (options->opt_private->addressless) { + case KRB5_INIT_CREDS_TRISTATE_UNSET: +#if KRB5_ADDRESSLESS_DEFAULT == TRUE + ctx->addrs = &no_addrs; +#else + ctx->addrs = NULL; +#endif + break; + case KRB5_INIT_CREDS_TRISTATE_FALSE: + ctx->addrs = NULL; + break; + case KRB5_INIT_CREDS_TRISTATE_TRUE: + ctx->addrs = &no_addrs; + break; + } + } + if (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST) { + if (ctx->etypes) + free(ctx->etypes); + + etypes = malloc((options->etype_list_length + 1) + * sizeof(krb5_enctype)); + if (etypes == NULL) { + ret = krb5_enomem(context); + goto out; + } + memcpy (etypes, options->etype_list, + options->etype_list_length * sizeof(krb5_enctype)); + etypes[options->etype_list_length] = ETYPE_NULL; + ctx->etypes = etypes; + } + if (options->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST) { + pre_auth_types = malloc((options->preauth_list_length + 1) + * sizeof(krb5_preauthtype)); + if (pre_auth_types == NULL) { + ret = krb5_enomem(context); + goto out; + } + memcpy (pre_auth_types, options->preauth_list, + options->preauth_list_length * sizeof(krb5_preauthtype)); + pre_auth_types[options->preauth_list_length] = KRB5_PADATA_NONE; + ctx->pre_auth_types = pre_auth_types; + } + if (options->flags & KRB5_GET_INIT_CREDS_OPT_ANONYMOUS) + ctx->flags.request_anonymous = options->anonymous; + + ctx->prompter = prompter; + ctx->prompter_data = prompter_data; + + if ((options->flags & KRB5_GET_INIT_CREDS_OPT_CHANGE_PASSWORD_PROMPT) && + !options->change_password_prompt) + ctx->runflags.change_password_prompt = 0; + else + ctx->runflags.change_password_prompt = ctx->prompter != NULL; + + if (options->opt_private->fast_armor_ccache_name) { + /* Open the caller-supplied FAST ccache and set the caller flags */ + ret = krb5_cc_resolve(context, options->opt_private->fast_armor_ccache_name, + &ctx->fast_state.armor_ccache); + if (ret) + goto out; + } + + ctx->fast_state.flags = options->opt_private->fast_flags; + + /* + * If FAST is required with a real credential cache, then the KDC + * will be verified. This allows the + * krb5_get_init_creds_opt_set_fast API to work like MIT without + * exposing KRB5_FAST_KDC_VERIFIED to callers + */ + if (ctx->fast_state.flags & KRB5_FAST_REQUIRED) + ctx->fast_state.flags |= KRB5_FAST_KDC_VERIFIED; + + out: + if (default_opt) + krb5_get_init_creds_opt_free(context, default_opt); + return ret; +} + +static krb5_error_code +change_password (krb5_context context, + krb5_principal client, + const char *password, + char *newpw, + size_t newpw_sz, + krb5_prompter_fct prompter, + void *data, + krb5_get_init_creds_opt *old_options) +{ + krb5_prompt prompts[2]; + krb5_error_code ret; + krb5_creds cpw_cred; + char buf1[BUFSIZ], buf2[BUFSIZ]; + krb5_data password_data[2]; + int result_code; + krb5_data result_code_string; + krb5_data result_string; + char *p; + krb5_get_init_creds_opt *options; + + heim_assert(prompter != NULL, "unexpected NULL prompter"); + + memset (&cpw_cred, 0, sizeof(cpw_cred)); + + ret = krb5_get_init_creds_opt_alloc(context, &options); + if (ret) + return ret; + krb5_get_init_creds_opt_set_tkt_life (options, 60); + krb5_get_init_creds_opt_set_forwardable (options, FALSE); + krb5_get_init_creds_opt_set_proxiable (options, FALSE); + if (old_options && + (old_options->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) + krb5_get_init_creds_opt_set_preauth_list(options, + old_options->preauth_list, + old_options->preauth_list_length); + if (old_options && + (old_options->flags & KRB5_GET_INIT_CREDS_OPT_CHANGE_PASSWORD_PROMPT)) + krb5_get_init_creds_opt_set_change_password_prompt(options, + old_options->change_password_prompt); + + krb5_data_zero (&result_code_string); + krb5_data_zero (&result_string); + + ret = krb5_get_init_creds_password (context, + &cpw_cred, + client, + password, + prompter, + data, + 0, + "kadmin/changepw", + options); + krb5_get_init_creds_opt_free(context, options); + if (ret) + goto out; + + for(;;) { + password_data[0].data = buf1; + password_data[0].length = sizeof(buf1); + + prompts[0].hidden = 1; + prompts[0].prompt = "New password: "; + prompts[0].reply = &password_data[0]; + prompts[0].type = KRB5_PROMPT_TYPE_NEW_PASSWORD; + + password_data[1].data = buf2; + password_data[1].length = sizeof(buf2); + + prompts[1].hidden = 1; + prompts[1].prompt = "Repeat new password: "; + prompts[1].reply = &password_data[1]; + prompts[1].type = KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN; + + ret = (*prompter) (context, data, NULL, "Changing password", + 2, prompts); + if (ret) { + memset (buf1, 0, sizeof(buf1)); + memset (buf2, 0, sizeof(buf2)); + goto out; + } + + if (strcmp (buf1, buf2) == 0) + break; + memset (buf1, 0, sizeof(buf1)); + memset (buf2, 0, sizeof(buf2)); + } + + ret = krb5_set_password (context, + &cpw_cred, + buf1, + client, + &result_code, + &result_code_string, + &result_string); + if (ret) + goto out; + + if (asprintf(&p, "%s: %.*s\n", + result_code ? "Error" : "Success", + (int)result_string.length, + result_string.length > 0 ? (char*)result_string.data : "") < 0) + { + ret = krb5_enomem(context); + goto out; + } + + /* return the result */ + (*prompter) (context, data, NULL, p, 0, NULL); + + if (result_code == 0) { + strlcpy (newpw, buf1, newpw_sz); + ret = 0; + } else { + krb5_set_error_message(context, ret = KRB5_CHPW_FAIL, + N_("failed changing password: %s", ""), p); + } + free (p); + +out: + memset_s(buf1, sizeof(buf1), 0, sizeof(buf1)); + memset_s(buf2, sizeof(buf2), 0, sizeof(buf2)); + krb5_data_free (&result_string); + krb5_data_free (&result_code_string); + krb5_free_cred_contents (context, &cpw_cred); + return ret; +} + + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_keyblock_key_proc (krb5_context context, + krb5_keytype type, + krb5_data *salt, + krb5_const_pointer keyseed, + krb5_keyblock **key) +{ + return krb5_copy_keyblock (context, keyseed, key); +} + +/* + * + */ + +static krb5_error_code +init_as_req (krb5_context context, + KDCOptions opts, + const krb5_creds *creds, + const krb5_addresses *addrs, + const krb5_enctype *etypes, + AS_REQ *a) +{ + krb5_error_code ret; + + memset(a, 0, sizeof(*a)); + + a->pvno = 5; + a->msg_type = krb_as_req; + a->req_body.kdc_options = opts; + a->req_body.cname = calloc(1, sizeof(*a->req_body.cname)); + if (a->req_body.cname == NULL) { + ret = krb5_enomem(context); + goto fail; + } + a->req_body.sname = calloc(1, sizeof(*a->req_body.sname)); + if (a->req_body.sname == NULL) { + ret = krb5_enomem(context); + goto fail; + } + + ret = _krb5_principal2principalname (a->req_body.cname, creds->client); + if (ret) + goto fail; + ret = copy_Realm(&creds->client->realm, &a->req_body.realm); + if (ret) + goto fail; + + ret = _krb5_principal2principalname (a->req_body.sname, creds->server); + if (ret) + goto fail; + + if(creds->times.starttime) { + a->req_body.from = malloc(sizeof(*a->req_body.from)); + if (a->req_body.from == NULL) { + ret = krb5_enomem(context); + goto fail; + } + *a->req_body.from = creds->times.starttime; + } + if(creds->times.endtime){ + if ((ALLOC(a->req_body.till, 1)) != NULL) + *a->req_body.till = creds->times.endtime; + else { + ret = krb5_enomem(context); + goto fail; + } + } + if(creds->times.renew_till){ + a->req_body.rtime = malloc(sizeof(*a->req_body.rtime)); + if (a->req_body.rtime == NULL) { + ret = krb5_enomem(context); + goto fail; + } + *a->req_body.rtime = creds->times.renew_till; + } + a->req_body.nonce = 0; + ret = _krb5_init_etype(context, + KRB5_PDU_AS_REQUEST, + &a->req_body.etype.len, + &a->req_body.etype.val, + etypes); + if (ret) + goto fail; + + /* + * This means no addresses + */ + + if (addrs && addrs->len == 0) { + a->req_body.addresses = NULL; + } else { + a->req_body.addresses = malloc(sizeof(*a->req_body.addresses)); + if (a->req_body.addresses == NULL) { + ret = krb5_enomem(context); + goto fail; + } + + if (addrs) + ret = krb5_copy_addresses(context, addrs, a->req_body.addresses); + else { + ret = krb5_get_all_client_addrs (context, a->req_body.addresses); + if(ret == 0 && a->req_body.addresses->len == 0) { + free(a->req_body.addresses); + a->req_body.addresses = NULL; + } + } + if (ret) + goto fail; + } + + a->req_body.enc_authorization_data = NULL; + a->req_body.additional_tickets = NULL; + + a->padata = NULL; + + return 0; + fail: + free_AS_REQ(a); + memset_s(a, sizeof(*a), 0, sizeof(*a)); + return ret; +} + + +static krb5_error_code +set_paid(struct pa_info_data *paid, krb5_context context, + krb5_enctype etype, + krb5_salttype salttype, void *salt_string, size_t salt_len, + krb5_data *s2kparams) +{ + paid->etype = etype; + paid->salt.salttype = salttype; + paid->salt.saltvalue.data = malloc(salt_len + 1); + if (paid->salt.saltvalue.data == NULL) { + krb5_clear_error_message(context); + return krb5_enomem(context); + } + memcpy(paid->salt.saltvalue.data, salt_string, salt_len); + ((char *)paid->salt.saltvalue.data)[salt_len] = '\0'; + paid->salt.saltvalue.length = salt_len; + if (s2kparams) { + krb5_error_code ret; + + ret = krb5_copy_data(context, s2kparams, &paid->s2kparams); + if (ret) { + krb5_clear_error_message(context); + krb5_free_salt(context, paid->salt); + return ret; + } + } else + paid->s2kparams = NULL; + + return 0; +} + +static struct pa_info_data * +pa_etype_info2(krb5_context context, + const krb5_principal client, + const AS_REQ *asreq, + struct pa_info_data *paid, + heim_octet_string *data) +{ + krb5_error_code ret; + ETYPE_INFO2 e; + size_t sz; + size_t i, j; + + memset(&e, 0, sizeof(e)); + ret = decode_ETYPE_INFO2(data->data, data->length, &e, &sz); + if (ret) + goto out; + if (e.len == 0) + goto out; + for (j = 0; j < asreq->req_body.etype.len; j++) { + for (i = 0; i < e.len; i++) { + + if (krb5_enctype_valid(context, e.val[i].etype) != 0) + continue; + + if (asreq->req_body.etype.val[j] == e.val[i].etype) { + krb5_salt salt; + if (e.val[i].salt == NULL) + ret = krb5_get_pw_salt(context, client, &salt); + else { + salt.saltvalue.data = *e.val[i].salt; + salt.saltvalue.length = strlen(*e.val[i].salt); + ret = 0; + } + if (ret == 0) + ret = set_paid(paid, context, e.val[i].etype, + KRB5_PW_SALT, + salt.saltvalue.data, + salt.saltvalue.length, + e.val[i].s2kparams); + if (e.val[i].salt == NULL) + krb5_free_salt(context, salt); + if (ret == 0) { + free_ETYPE_INFO2(&e); + return paid; + } + } + } + } + out: + free_ETYPE_INFO2(&e); + return NULL; +} + +static struct pa_info_data * +pa_etype_info(krb5_context context, + const krb5_principal client, + const AS_REQ *asreq, + struct pa_info_data *paid, + heim_octet_string *data) +{ + krb5_error_code ret; + ETYPE_INFO e; + size_t sz; + size_t i, j; + + memset(&e, 0, sizeof(e)); + ret = decode_ETYPE_INFO(data->data, data->length, &e, &sz); + if (ret) + goto out; + if (e.len == 0) + goto out; + for (j = 0; j < asreq->req_body.etype.len; j++) { + for (i = 0; i < e.len; i++) { + + if (krb5_enctype_valid(context, e.val[i].etype) != 0) + continue; + + if (asreq->req_body.etype.val[j] == e.val[i].etype) { + krb5_salt salt; + salt.salttype = KRB5_PW_SALT; + if (e.val[i].salt == NULL) + ret = krb5_get_pw_salt(context, client, &salt); + else { + salt.saltvalue = *e.val[i].salt; + ret = 0; + } + if (e.val[i].salttype) + salt.salttype = *e.val[i].salttype; + if (ret == 0) { + ret = set_paid(paid, context, e.val[i].etype, + salt.salttype, + salt.saltvalue.data, + salt.saltvalue.length, + NULL); + if (e.val[i].salt == NULL) + krb5_free_salt(context, salt); + } + if (ret == 0) { + free_ETYPE_INFO(&e); + return paid; + } + } + } + } + out: + free_ETYPE_INFO(&e); + return NULL; +} + +static struct pa_info_data * +pa_pw_or_afs3_salt(krb5_context context, + const krb5_principal client, + const AS_REQ *asreq, + struct pa_info_data *paid, + heim_octet_string *data) +{ + krb5_error_code ret; + if (paid->etype == KRB5_ENCTYPE_NULL) + return NULL; + if (krb5_enctype_valid(context, paid->etype) != 0) + return NULL; + + ret = set_paid(paid, context, + paid->etype, + paid->salt.salttype, + data->data, + data->length, + NULL); + if (ret) + return NULL; + return paid; +} + + +static krb5_error_code +make_pa_enc_timestamp(krb5_context context, METHOD_DATA *md, + krb5_enctype etype, krb5_keyblock *key) +{ + PA_ENC_TS_ENC p; + unsigned char *buf; + size_t buf_size; + size_t len = 0; + EncryptedData encdata; + krb5_error_code ret; + int32_t usec; + int usec2; + krb5_crypto crypto; + + krb5_us_timeofday (context, &p.patimestamp, &usec); + usec2 = usec; + p.pausec = &usec2; + + ASN1_MALLOC_ENCODE(PA_ENC_TS_ENC, buf, buf_size, &p, &len, ret); + if (ret) + return ret; + if(buf_size != len) + krb5_abortx(context, "internal error in ASN.1 encoder"); + + ret = krb5_crypto_init(context, key, 0, &crypto); + if (ret) { + free(buf); + return ret; + } + ret = krb5_encrypt_EncryptedData(context, + crypto, + KRB5_KU_PA_ENC_TIMESTAMP, + buf, + len, + 0, + &encdata); + free(buf); + krb5_crypto_destroy(context, crypto); + if (ret) + return ret; + + ASN1_MALLOC_ENCODE(EncryptedData, buf, buf_size, &encdata, &len, ret); + free_EncryptedData(&encdata); + if (ret) + return ret; + if(buf_size != len) + krb5_abortx(context, "internal error in ASN.1 encoder"); + + ret = krb5_padata_add(context, md, KRB5_PADATA_ENC_TIMESTAMP, buf, len); + if (ret) + free(buf); + return ret; +} + +static krb5_error_code +add_enc_ts_padata(krb5_context context, + METHOD_DATA *md, + krb5_principal client, + krb5_s2k_proc keyproc, + krb5_const_pointer keyseed, + krb5_enctype *enctypes, + unsigned netypes, + krb5_salt *salt, + krb5_data *s2kparams) +{ + krb5_error_code ret; + krb5_salt salt2; + krb5_enctype *ep; + size_t i; + + memset(&salt2, 0, sizeof(salt2)); + + if(salt == NULL) { + /* default to standard salt */ + ret = krb5_get_pw_salt (context, client, &salt2); + if (ret) + return ret; + salt = &salt2; + } + if (!enctypes) { + enctypes = context->etypes; + netypes = 0; + for (ep = enctypes; *ep != ETYPE_NULL; ep++) + netypes++; + } + + for (i = 0; i < netypes; ++i) { + krb5_keyblock *key; + + _krb5_debug(context, 5, "krb5_get_init_creds: using ENC-TS with enctype %d", enctypes[i]); + + ret = (*keyproc)(context, enctypes[i], keyseed, + *salt, s2kparams, &key); + if (ret) + continue; + ret = make_pa_enc_timestamp (context, md, enctypes[i], key); + krb5_free_keyblock (context, key); + if (ret) + return ret; + } + if(salt == &salt2) + krb5_free_salt(context, salt2); + return 0; +} + +static krb5_error_code +pa_data_to_md_ts_enc(krb5_context context, + const AS_REQ *a, + const krb5_principal client, + krb5_init_creds_context ctx, + struct pa_info_data *ppaid, + METHOD_DATA *md) +{ + if (ctx->keyproc == NULL || ctx->keyseed == NULL) + return 0; + + if (ppaid) { + add_enc_ts_padata(context, md, client, + ctx->keyproc, ctx->keyseed, + &ppaid->etype, 1, + &ppaid->salt, ppaid->s2kparams); + } else { + krb5_salt salt; + + _krb5_debug(context, 5, "krb5_get_init_creds: pa-info not found, guessing salt"); + + /* make a v5 salted pa-data */ + add_enc_ts_padata(context, md, client, + ctx->keyproc, ctx->keyseed, + a->req_body.etype.val, a->req_body.etype.len, + NULL, NULL); + + /* make a v4 salted pa-data */ + salt.salttype = KRB5_PW_SALT; + krb5_data_zero(&salt.saltvalue); + add_enc_ts_padata(context, md, client, + ctx->keyproc, ctx->keyseed, + a->req_body.etype.val, a->req_body.etype.len, + &salt, NULL); + } + return 0; +} + +static krb5_error_code +pa_data_to_key_plain(krb5_context context, + const krb5_principal client, + krb5_init_creds_context ctx, + krb5_salt salt, + krb5_data *s2kparams, + krb5_enctype etype, + krb5_keyblock **key) +{ + krb5_error_code ret; + + ret = (*ctx->keyproc)(context, etype, ctx->keyseed, + salt, s2kparams, key); + return ret; +} + +struct pkinit_context { + unsigned int win2k : 1; + unsigned int used_pkinit : 1; +}; + + +static krb5_error_code +pa_data_to_md_pkinit(krb5_context context, + const AS_REQ *a, + const krb5_principal client, + int win2k, + krb5_init_creds_context ctx, + METHOD_DATA *md) +{ + if (ctx->pk_init_ctx == NULL) + return 0; +#ifdef PKINIT + return _krb5_pk_mk_padata(context, + ctx->pk_init_ctx, + ctx->ic_flags, + win2k, + &a->req_body, + ctx->pk_nonce, + md); +#else + krb5_set_error_message(context, EINVAL, + N_("no support for PKINIT compiled in", "")); + return EINVAL; +#endif +} + +static krb5_error_code +pkinit_configure_ietf(krb5_context context, krb5_init_creds_context ctx, void *pa_ctx) +{ + struct pkinit_context *pkinit_ctx = pa_ctx; + + pkinit_ctx->win2k = 0; + + if (ctx->pk_init_ctx == NULL) + return HEIM_ERR_PA_CANT_CONTINUE; + + return 0; +} + +static krb5_error_code +pkinit_configure_win(krb5_context context, krb5_init_creds_context ctx, void *pa_ctx) +{ + struct pkinit_context *pkinit_ctx = pa_ctx; + + pkinit_ctx->win2k = 1; + pkinit_ctx->used_pkinit = 0; + + if (ctx->pk_init_ctx == NULL) + return HEIM_ERR_PA_CANT_CONTINUE; + + return 0; +} + +static krb5_error_code +pkinit_step(krb5_context context, krb5_init_creds_context ctx, void *pa_ctx, PA_DATA *pa, const AS_REQ *a, + const AS_REP *rep, METHOD_DATA *in_md, METHOD_DATA *out_md) +{ + krb5_error_code ret = HEIM_ERR_PA_CANT_CONTINUE; + struct pkinit_context *pkinit_ctx = pa_ctx; + + if (rep == NULL) { + if (pkinit_ctx->used_pkinit) { + krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP, + "Already tried PKINIT(%s), looping", + pkinit_ctx->win2k ? "win2k" : "ietf"); + } else { + ret = pa_data_to_md_pkinit(context, a, ctx->cred.client, + (pkinit_ctx->win2k != 0), + ctx, out_md); + if (ret == 0) + ret = HEIM_ERR_PA_CONTINUE_NEEDED; + + pkinit_ctx->used_pkinit = 1; + } + } else if (pa) { + ret = _krb5_pk_rd_pa_reply(context, + a->req_body.realm, + ctx->pk_init_ctx, + rep->enc_part.etype, + ctx->pk_nonce, + &ctx->req_buffer, + pa, + &ctx->fast_state.reply_key); + if (ret == 0) + ctx->runflags.allow_save_as_reply_key = 1; + } + + return ret; +} + +static void +pkinit_release(void *pa_ctx) +{ +} + +/* + * GSS-API pre-authentication support + */ + +struct pa_gss_context { + struct gss_ctx_id_t_desc_struct *context_handle; + int open; +}; + +static krb5_error_code +pa_gss_configure(krb5_context context, + krb5_init_creds_context ctx, + void *pa_ctx) +{ + krb5_gss_init_ctx gssic = ctx->gss_init_ctx; + struct pa_gss_context *pa_gss_ctx = pa_ctx; + + if (gssic == NULL) + return HEIM_ERR_PA_CANT_CONTINUE; + + pa_gss_ctx->context_handle = NULL; + pa_gss_ctx->open = 0; + + return 0; +} + +static krb5_error_code +pa_data_to_md_gss(krb5_context context, + const AS_REQ *a, + const krb5_creds *creds, + krb5_init_creds_context ctx, + struct pa_gss_context *pa_gss_ctx, + PA_DATA *pa, + METHOD_DATA *out_md) +{ + krb5_error_code ret; + krb5_gss_init_ctx gssic = ctx->gss_init_ctx; + krb5_data req_body; + krb5_data *input_token, output_token; + size_t len = 0; + + krb5_data_zero(&req_body); + krb5_data_zero(&output_token); + + input_token = pa ? &pa->padata_value : NULL; + + if ((input_token == NULL || input_token->length == 0) && + pa_gss_ctx->context_handle) { + krb5_set_error_message(context, HEIM_ERR_PA_CANT_CONTINUE, + "Missing GSS preauthentication data from KDC"); + return HEIM_ERR_PA_CANT_CONTINUE; + } + + ASN1_MALLOC_ENCODE(KDC_REQ_BODY, req_body.data, req_body.length, + &ctx->as_req.req_body, &len, ret); + if (ret) + goto out; + heim_assert(req_body.length == len, "ASN.1 internal error"); + + ret = gssic->step(context, gssic, creds, &pa_gss_ctx->context_handle, + ctx->flags, &req_body, + input_token, &output_token); + + /* + * If FAST authenticated the KDC (which will be the case unless anonymous + * PKINIT was used without KDC certificate validation) then we can relax + * the mutual authentication requirement. + */ + if (ret == KRB5_MUTUAL_FAILED && + (ctx->fast_state.flags & KRB5_FAST_EXPECTED) && + (ctx->fast_state.flags & KRB5_FAST_KDC_VERIFIED)) + ret = 0; + if (ret == 0) { + /* + * Always require a strengthen key if FAST was used, to avoid a MITM + * attack that could result in unintended privilege escalation should + * the KDC add positive authorization data from the armor ticket. + */ + if ((ctx->fast_state.flags & KRB5_FAST_EXPECTED) && + ctx->fast_state.strengthen_key == NULL) { + krb5_set_error_message(context, HEIM_ERR_PA_CANT_CONTINUE, + "FAST GSS pre-authentication without strengthen key"); + ret = KRB5_KDCREP_MODIFIED; + goto out; + } + + pa_gss_ctx->open = 1; + } + + if (output_token.length) { + ret = krb5_padata_add(context, out_md, KRB5_PADATA_GSS, + output_token.data, output_token.length); + if (ret) + goto out; + + krb5_data_zero(&output_token); + } + +out: + krb5_data_free(&output_token); + krb5_data_free(&req_body); + + return ret; +} + +static krb5_error_code +pa_gss_step(krb5_context context, + krb5_init_creds_context ctx, + void *pa_ctx, + PA_DATA *pa, + const AS_REQ *a, + const AS_REP *rep, + METHOD_DATA *in_md, + METHOD_DATA *out_md) +{ + krb5_error_code ret; + krb5_principal cname; + krb5_gss_init_ctx gssic = ctx->gss_init_ctx; + struct pa_gss_context *pa_gss_ctx = pa_ctx; + + heim_assert(gssic != NULL, "invalid context passed to pa_gss_step"); + + if (!pa_gss_ctx->open) { + ret = pa_data_to_md_gss(context, a, &ctx->cred, ctx, + pa_gss_ctx, pa, out_md); + if (ret == HEIM_ERR_PA_CONTINUE_NEEDED && rep) { + krb5_set_error_message(context, KRB5_PREAUTH_FAILED, + "KDC sent AS-REP before GSS " + "pre-authentication completed"); + ret = KRB5_KDCREP_MODIFIED; + } else if (ret == 0 && rep == NULL) { + ret = HEIM_ERR_PA_CONTINUE_NEEDED; /* odd number of legs */ + } + if (ret) + return ret; + } else if (pa && pa->padata_value.length) { + krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP, + "Already completed GSS pre-authentication"); + return KRB5_GET_IN_TKT_LOOP; + } else if (rep == NULL) { + krb5_set_error_message(context, KRB5_PREAUTH_FAILED, + "Completed GSS pre-authentication before KDC"); + return KRB5_PREAUTH_FAILED; + } + + heim_assert(pa_gss_ctx->open, + "GSS pre-authentication incomplete"); + + ret = gssic->finish(context, gssic, &ctx->cred, + pa_gss_ctx->context_handle, ctx->nonce, + rep->enc_part.etype, &cname, + &ctx->fast_state.reply_key); + if (ret) + return ret; + + { + char *from = NULL; + char *to = NULL; + + if (krb5_unparse_name(context, ctx->cred.client, &from) == 0) { + if (krb5_unparse_name(context, cname, &to) == 0) { + _krb5_debug(context, 1, "pa_gss_step: %s as %s", + from, to); + krb5_xfree(to); + } + krb5_xfree(from); + } + } + + if (krb5_principal_is_federated(context, ctx->cred.client)) { + /* + * The well-known federated name will be replaced with the cname + * in the AS-REP, but save the locally mapped initiator name in the + * cred for logging. + */ + krb5_free_principal(context, ctx->cred.client); + ctx->cred.client = cname; + + ctx->ic_flags |= KRB5_INIT_CREDS_NO_C_CANON_CHECK; + } else { + krb5_free_principal(context, cname); + } + + ctx->runflags.allow_save_as_reply_key = 1; + + gssic->delete_sec_context(context, gssic, pa_gss_ctx->context_handle); + pa_gss_ctx->context_handle = NULL; + pa_gss_ctx->open = 0; + + return 0; +} + +static krb5_error_code +pa_gss_restart(krb5_context context, + krb5_init_creds_context ctx, + void *pa_ctx) +{ + krb5_gss_init_ctx gssic = ctx->gss_init_ctx; + struct pa_gss_context *pa_gss_ctx = pa_ctx; + + if (gssic == NULL) + return HEIM_ERR_PA_CANT_CONTINUE; + + gssic->delete_sec_context(context, gssic, pa_gss_ctx->context_handle); + pa_gss_ctx->context_handle = NULL; + pa_gss_ctx->open = 0; + + return 0; +} + +static void +pa_gss_release(void *pa_ctx) +{ +} + +krb5_error_code +_krb5_make_pa_enc_challenge(krb5_context context, + krb5_crypto crypto, + krb5_key_usage usage, + METHOD_DATA *md) +{ + PA_ENC_TS_ENC p; + unsigned char *buf; + size_t buf_size; + size_t len = 0; + EncryptedData encdata; + krb5_error_code ret; + int32_t usec; + int usec2; + + krb5_us_timeofday (context, &p.patimestamp, &usec); + usec2 = usec; + p.pausec = &usec2; + + ASN1_MALLOC_ENCODE(PA_ENC_TS_ENC, buf, buf_size, &p, &len, ret); + if (ret) + return ret; + if(buf_size != len) + krb5_abortx(context, "internal error in ASN.1 encoder"); + + ret = krb5_encrypt_EncryptedData(context, + crypto, + usage, + buf, + len, + 0, + &encdata); + free(buf); + if (ret) + return ret; + + ASN1_MALLOC_ENCODE(EncryptedData, buf, buf_size, &encdata, &len, ret); + free_EncryptedData(&encdata); + if (ret) + return ret; + if(buf_size != len) + krb5_abortx(context, "internal error in ASN.1 encoder"); + + ret = krb5_padata_add(context, md, KRB5_PADATA_ENCRYPTED_CHALLENGE, buf, len); + if (ret) + free(buf); + return ret; +} + +krb5_error_code +_krb5_validate_pa_enc_challenge(krb5_context context, + krb5_crypto crypto, + krb5_key_usage usage, + EncryptedData *enc_data, + const char *peer_name) +{ + krb5_error_code ret; + krb5_data ts_data; + PA_ENC_TS_ENC p; + time_t timestamp; + int32_t usec; + size_t size; + + ret = krb5_decrypt_EncryptedData(context, crypto, usage, enc_data, &ts_data); + if (ret) + return ret; + + ret = decode_PA_ENC_TS_ENC(ts_data.data, + ts_data.length, + &p, + &size); + krb5_data_free(&ts_data); + if(ret){ + ret = KRB5KDC_ERR_PREAUTH_FAILED; + _krb5_debug(context, 5, "Failed to decode PA-ENC-TS_ENC -- %s", peer_name); + goto out; + } + + krb5_us_timeofday(context, ×tamp, &usec); + + if (krb5_time_abs(timestamp, p.patimestamp) > context->max_skew) { + char client_time[100]; + + krb5_format_time(context, p.patimestamp, + client_time, sizeof(client_time), TRUE); + + ret = KRB5KRB_AP_ERR_SKEW; + _krb5_debug(context, 0, "Too large time skew, " + "client time %s is out by %u > %d seconds -- %s", + client_time, + (unsigned)krb5_time_abs(timestamp, p.patimestamp), + (int)context->max_skew, + peer_name); + } else { + ret = 0; + } + + out: + free_PA_ENC_TS_ENC(&p); + + return ret; +} + + +static struct pa_info_data * +process_pa_info(krb5_context, const krb5_principal, const AS_REQ *, struct pa_info_data *, METHOD_DATA *); + + +static krb5_error_code +enc_chal_step(krb5_context context, krb5_init_creds_context ctx, void *pa_ctx, PA_DATA *pa, const AS_REQ *a, + const AS_REP *rep, METHOD_DATA *in_md, METHOD_DATA *out_md) +{ + struct pa_info_data paid, *ppaid; + krb5_keyblock challengekey; + krb5_data pepper1, pepper2; + krb5_crypto crypto = NULL; + krb5_enctype aenctype; + krb5_error_code ret; + + memset(&paid, 0, sizeof(paid)); + + if (rep == NULL) + paid.etype = KRB5_ENCTYPE_NULL; + else + paid.etype = rep->enc_part.etype; + ppaid = process_pa_info(context, ctx->cred.client, a, &paid, in_md); + + /* + * If we don't have ppaid, it's because the KDC has not sent any + * salt info. Let's do the first roundtrip so the KDC has a chance + * to send some. + */ + if (ppaid == NULL) { + _krb5_debug(context, 5, "no ppaid found"); + return HEIM_ERR_PA_CONTINUE_NEEDED; + } + if (ppaid->etype == KRB5_ENCTYPE_NULL) { + return HEIM_ERR_PA_CANT_CONTINUE; + } + + if (ctx->fast_state.reply_key) + krb5_free_keyblock(context, ctx->fast_state.reply_key); + + ret = pa_data_to_key_plain(context, ctx->cred.client, ctx, + ppaid->salt, ppaid->s2kparams, ppaid->etype, + &ctx->fast_state.reply_key); + free_paid(context, &paid); + if (ret) { + _krb5_debug(context, 5, "enc-chal: failed to build key"); + return ret; + } + + ret = krb5_crypto_init(context, ctx->fast_state.reply_key, 0, &crypto); + if (ret) + return ret; + + krb5_crypto_getenctype(context, ctx->fast_state.armor_crypto, &aenctype); + + pepper1.data = rep ? "kdcchallengearmor" : "clientchallengearmor"; + pepper1.length = strlen(pepper1.data); + pepper2.data = "challengelongterm"; + pepper2.length = strlen(pepper2.data); + + ret = krb5_crypto_fx_cf2(context, ctx->fast_state.armor_crypto, crypto, + &pepper1, &pepper2, aenctype, + &challengekey); + krb5_crypto_destroy(context, crypto); + if (ret) + return ret; + + ret = krb5_crypto_init(context, &challengekey, 0, &crypto); + krb5_free_keyblock_contents(context, &challengekey); + if (ret) + return ret; + + if (rep) { + EncryptedData enc_data; + size_t size; + + _krb5_debug(context, 5, "ENC_CHAL rep key"); + + if (ctx->fast_state.strengthen_key == NULL) { + krb5_crypto_destroy(context, crypto); + _krb5_debug(context, 5, "ENC_CHAL w/o strengthen_key"); + return KRB5_KDCREP_MODIFIED; + } + + if (pa == NULL) { + krb5_crypto_destroy(context, crypto); + _krb5_debug(context, 0, "KDC response missing"); + return HEIM_ERR_PA_CANT_CONTINUE; + } + + ret = decode_EncryptedData(pa->padata_value.data, + pa->padata_value.length, + &enc_data, + &size); + if (ret) { + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + _krb5_debug(context, 5, "Failed to decode ENC_CHAL KDC reply"); + return ret; + } + + ret = _krb5_validate_pa_enc_challenge(context, crypto, + KRB5_KU_ENC_CHALLENGE_KDC, + &enc_data, + "KDC"); + free_EncryptedData(&enc_data); + krb5_crypto_destroy(context, crypto); + + return ret; + + } else { + + ret = _krb5_make_pa_enc_challenge(context, crypto, + KRB5_KU_ENC_CHALLENGE_CLIENT, + out_md); + krb5_crypto_destroy(context, crypto); + if (ret) { + _krb5_debug(context, 5, "enc-chal: failed build enc challenge"); + return ret; + } + + return HEIM_ERR_PA_CONTINUE_NEEDED; + } +} + +struct enc_ts_context { + int used_pa_types; +#define USED_ENC_TS_GUESS 4 +#define USED_ENC_TS_INFO 8 +#define USED_ENC_TS_RENEG 16 + krb5_principal user; +}; + +static krb5_error_code +enc_ts_restart(krb5_context context, krb5_init_creds_context ctx, void *pa_ctx) +{ + struct enc_ts_context *pactx = (struct enc_ts_context *)pa_ctx; + pactx->used_pa_types = 0; + krb5_free_principal(context, pactx->user); + pactx->user = NULL; + return 0; +} + +static krb5_error_code +enc_ts_step(krb5_context context, krb5_init_creds_context ctx, void *pa_ctx, PA_DATA *pa, + const AS_REQ *a, + const AS_REP *rep, + METHOD_DATA *in_md, METHOD_DATA *out_md) +{ + struct enc_ts_context *pactx = (struct enc_ts_context *)pa_ctx; + struct pa_info_data paid, *ppaid; + krb5_error_code ret; + const char *state; + unsigned flag; + + /* + * Keep track of the user we used so that we can restart + * authentication when we get referrals. + */ + + if (pactx->user && !krb5_principal_compare(context, pactx->user, ctx->cred.client)) { + pactx->used_pa_types = 0; + krb5_free_principal(context, pactx->user); + pactx->user = NULL; + } + + if (pactx->user == NULL) { + ret = krb5_copy_principal(context, ctx->cred.client, &pactx->user); + if (ret) + return ret; + } + + memset(&paid, 0, sizeof(paid)); + + if (rep == NULL) + paid.etype = KRB5_ENCTYPE_NULL; + else + paid.etype = rep->enc_part.etype; + + ppaid = process_pa_info(context, ctx->cred.client, a, &paid, in_md); + + if (rep) { + /* + * Some KDC's don't send salt info in the reply when there is + * success pre-auth happened before, so use cached copy (or + * even better, if there is just one pre-auth, save reply-key). + */ + if (ppaid == NULL && ctx->paid.etype != KRB5_ENCTYPE_NULL) { + ppaid = &ctx->paid; + + } else if (ppaid == NULL) { + _krb5_debug(context, 0, "no paid when building key, build a default salt structure ?"); + return HEIM_ERR_PA_CANT_CONTINUE; + } + + ret = pa_data_to_key_plain(context, ctx->cred.client, ctx, + ppaid->salt, ppaid->s2kparams, rep->enc_part.etype, + &ctx->fast_state.reply_key); + free_paid(context, &paid); + return ret; + } + + /* + * If we don't have ppaid, it's because the KDC has not sent any + * salt info. Let's do the first roundtrip so the KDC has a chance + * to send some. + * + * Don't bother guessing, it sounds like a good idea until you run + * into KDCs that are doing failed auth counting based on the + * ENC_TS tries. + * + * Stashing the salt for the next run is a different issue and + * could be considered in the future. + */ + + if (ppaid == NULL) { + _krb5_debug(context, 5, + "TS-ENC: waiting for KDC to set pw-salt/etype_info{,2}"); + return HEIM_ERR_PA_CONTINUE_NEEDED; + } + if (ppaid->etype == KRB5_ENCTYPE_NULL) { + free_paid(context, &paid); + _krb5_debug(context, 5, + "TS-ENC: kdc proposes enctype NULL ?"); + return HEIM_ERR_PA_CANT_CONTINUE; + } + + /* + * We have to allow the KDC to re-negotiate the PA-TS data + * once, this is since a windows read only + * KDC that doesn't have the keys simply guesses what the + * master is supposed to support. The case where this + * breaks is when the RO-KDC is a newer version than the RW-KDC + * and the RO-KDC announced a enctype that the older doesn't + * support. + */ + if (pactx->used_pa_types & USED_ENC_TS_INFO) { + flag = USED_ENC_TS_RENEG; + state = "reneg"; + } else { + flag = USED_ENC_TS_INFO; + state = "info"; + } + + if (pactx->used_pa_types & flag) { + free_paid(context, &paid); + krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP, + "Already tried ENC-TS-%s, looping", state); + return KRB5_GET_IN_TKT_LOOP; + } + + pactx->used_pa_types |= flag; + + free_paid(context, &ctx->paid); + ctx->paid = *ppaid; + + ret = pa_data_to_md_ts_enc(context, a, ctx->cred.client, ctx, ppaid, out_md); + if (ret) + return ret; + + return HEIM_ERR_PA_CONTINUE_NEEDED; +} + +static void +enc_ts_release(void *pa_ctx) +{ + struct enc_ts_context *pactx = (struct enc_ts_context *)pa_ctx; + + if (pactx->user) + krb5_free_principal(NULL, pactx->user); +} + +static krb5_error_code +pa_pac_step(krb5_context context, krb5_init_creds_context ctx, void *pa_ctx, PA_DATA *pa, const AS_REQ *a, + const AS_REP *rep, METHOD_DATA *in_md, METHOD_DATA *out_md) +{ + size_t len = 0, length; + krb5_error_code ret; + PA_PAC_REQUEST req; + void *buf; + + switch (ctx->req_pac) { + case KRB5_INIT_CREDS_TRISTATE_UNSET: + return 0; /* don't bother */ + case KRB5_INIT_CREDS_TRISTATE_TRUE: + req.include_pac = 1; + break; + case KRB5_INIT_CREDS_TRISTATE_FALSE: + req.include_pac = 0; + } + + ASN1_MALLOC_ENCODE(PA_PAC_REQUEST, buf, length, + &req, &len, ret); + if (ret) + return ret; + heim_assert(len == length, "internal error in ASN.1 encoder"); + + ret = krb5_padata_add(context, out_md, KRB5_PADATA_PA_PAC_REQUEST, buf, len); + if (ret) + free(buf); + + return 0; +} + +static krb5_error_code +pa_enc_pa_rep_step(krb5_context context, krb5_init_creds_context ctx, void *pa_ctx, PA_DATA *pa, const AS_REQ *a, + const AS_REP *rep, METHOD_DATA *in_md, METHOD_DATA *out_md) +{ + if (ctx->runflags.allow_enc_pa_rep) + return krb5_padata_add(context, out_md, KRB5_PADATA_REQ_ENC_PA_REP, NULL, 0); + + return 0; +} + +static krb5_error_code +pa_fx_cookie_step(krb5_context context, + krb5_init_creds_context ctx, + void *pa_ctx, + PA_DATA *pa, + const AS_REQ *a, + const AS_REP *rep, + METHOD_DATA *in_md, + METHOD_DATA *out_md) +{ + krb5_error_code ret; + void *cookie; + PA_DATA *pad; + int idx = 0; + + pad = krb5_find_padata(in_md->val, in_md->len, KRB5_PADATA_FX_COOKIE, &idx); + if (pad == NULL) { + /* + * RFC 6113 5.4.3: PA-FX-COOKIE MUST be included if the KDC + * expects at least one more message from the client. + */ + if (ctx->error.error_code == KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED) + return KRB5_PREAUTH_FAILED; + else + return 0; + } + + cookie = malloc(pad->padata_value.length); + if (cookie == NULL) + return krb5_enomem(context); + + memcpy(cookie, pad->padata_value.data, pad->padata_value.length); + + ret = krb5_padata_add(context, out_md, KRB5_PADATA_FX_COOKIE, + cookie, pad->padata_value.length); + if (ret) + free(cookie); + else + _krb5_debug(context, 5, "Mirrored FX-COOKIE to KDC"); + + return ret; +} + +typedef struct pa_info_data *(*pa_salt_info_f)(krb5_context, const krb5_principal, const AS_REQ *, struct pa_info_data *, heim_octet_string *); +typedef krb5_error_code (*pa_configure_f)(krb5_context, krb5_init_creds_context, void *); +typedef krb5_error_code (*pa_restart_f)(krb5_context, krb5_init_creds_context, void *); +typedef krb5_error_code (*pa_step_f)(krb5_context, krb5_init_creds_context, void *, PA_DATA *, const AS_REQ *, const AS_REP *, METHOD_DATA *, METHOD_DATA *); +typedef void (*pa_release_f)(void *); + +static const struct patype { + int type; + const char *name; + int flags; +#define PA_F_ANNOUNCE 1 +#define PA_F_CONFIG 2 +#define PA_F_FAST 4 /* available inside FAST */ +#define PA_F_NOT_FAST 8 /* only available without FAST */ + size_t pa_ctx_size; + pa_salt_info_f salt_info; + /** + * Return 0 if the PA-mechanism is available and optionally set pa_ctx pointer to non-NULL. + */ + pa_configure_f configure; + /** + * Return 0 if the PA-mechanism can be restarted (time skew, referrals, etc) + */ + pa_restart_f restart; + /** + * Return 0 when complete, HEIM_ERR_PA_CONTINUE_NEEDED if more steps are required + */ + pa_step_f step; + pa_release_f release; +} patypes[] = { + { + KRB5_PADATA_PK_AS_REP, + "PKINIT(IETF)", + PA_F_FAST | PA_F_NOT_FAST, + sizeof(struct pkinit_context), + NULL, + pkinit_configure_ietf, + NULL, + pkinit_step, + pkinit_release + }, + { + KRB5_PADATA_PK_AS_REP_19, + "PKINIT(win)", + PA_F_FAST | PA_F_NOT_FAST, + sizeof(struct pkinit_context), + NULL, + pkinit_configure_win, + NULL, + pkinit_step, + pkinit_release + }, + { + KRB5_PADATA_GSS, + "GSS", + PA_F_FAST | PA_F_NOT_FAST, + sizeof(struct pa_gss_context), + NULL, + pa_gss_configure, + pa_gss_restart, + pa_gss_step, + pa_gss_release + }, + { + KRB5_PADATA_ENCRYPTED_CHALLENGE, + "ENCRYPTED_CHALLENGE", + PA_F_FAST, + 0, + NULL, + NULL, + NULL, + enc_chal_step, + NULL + }, + { + KRB5_PADATA_ENC_TIMESTAMP, + "ENCRYPTED_TIMESTAMP", + PA_F_NOT_FAST, + sizeof(struct enc_ts_context), + NULL, + NULL, + enc_ts_restart, + enc_ts_step, + enc_ts_release + }, + { + KRB5_PADATA_PA_PAC_REQUEST, + "PA_PAC_REQUEST", + PA_F_CONFIG, + 0, + NULL, + NULL, + NULL, + pa_pac_step, + NULL + }, + { + KRB5_PADATA_REQ_ENC_PA_REP, + "REQ-ENC-PA-REP", + PA_F_CONFIG, + 0, + NULL, + NULL, + NULL, + pa_enc_pa_rep_step, + NULL + }, + { + KRB5_PADATA_FX_COOKIE, + "FX-COOKIE", + PA_F_CONFIG, + 0, + NULL, + NULL, + NULL, + pa_fx_cookie_step, + NULL + }, +#define patype_salt(n, f) { KRB5_PADATA_##n, #n, 0, 0, f, NULL, NULL, NULL, NULL } + patype_salt(ETYPE_INFO2, pa_etype_info2), + patype_salt(ETYPE_INFO, pa_etype_info), + patype_salt(PW_SALT, pa_pw_or_afs3_salt), + patype_salt(AFS3_SALT, pa_pw_or_afs3_salt), +#undef patype_salt + /* below are just for pretty printing */ +#define patype_info(n) { KRB5_PADATA_##n, #n, 0, 0, NULL, NULL, NULL, NULL, NULL } + patype_info(AUTHENTICATION_SET), + patype_info(AUTH_SET_SELECTED), + patype_info(FX_FAST), + patype_info(FX_ERROR), + patype_info(PKINIT_KX), + patype_info(PK_AS_REQ) +#undef patype_info +}; + +static const char * +get_pa_type_name(int type) +{ + size_t n; + for (n = 0; n < sizeof(patypes)/sizeof(patypes[0]); n++) + if (type == patypes[n].type) + return patypes[n].name; + return "unknown"; +} + +/* + * + */ + +struct pa_auth_mech { + const struct patype *patype; + struct pa_auth_mech *next; /* when doing authentication sets */ + char pactx[1]; +}; + +/* + * + */ + +static struct pa_info_data * +process_pa_info(krb5_context context, + const krb5_principal client, + const AS_REQ *asreq, + struct pa_info_data *paid, + METHOD_DATA *md) +{ + struct pa_info_data *p = NULL; + PA_DATA *pa; + size_t i; + + if (md == NULL) + return NULL; + + for (i = 0; p == NULL && i < sizeof(patypes)/sizeof(patypes[0]); i++) { + int idx = 0; + + if (patypes[i].salt_info == NULL) + continue; + + pa = krb5_find_padata(md->val, md->len, patypes[i].type, &idx); + if (pa == NULL) + continue; + + paid->salt.salttype = (krb5_salttype)patypes[i].type; + p = patypes[i].salt_info(context, client, asreq, paid, &pa->padata_value); + } + return p; +} + +static krb5_error_code +pa_announce(krb5_context context, + int types, + krb5_init_creds_context ctx, + METHOD_DATA *in_md, + METHOD_DATA *out_md) +{ + krb5_error_code ret = 0; + size_t n; + + for (n = 0; ret == 0 && n < sizeof(patypes)/sizeof(patypes[0]); n++) { + if ((patypes[n].flags & types) == 0) + continue; + + if (patypes[n].step) + patypes[n].step(context, ctx, NULL, NULL, NULL, NULL, in_md, out_md); + else + ret = krb5_padata_add(context, out_md, patypes[n].type, NULL, 0); + } + return ret; +} + + +static void HEIM_CALLCONV +mech_dealloc(void *ctx) +{ + struct pa_auth_mech *pa_mech = ctx; + if (pa_mech->patype->release) + pa_mech->patype->release((void *)&pa_mech->pactx[0]); +} + +static const struct heim_type_data pa_auth_mech_object = { + HEIM_TID_PA_AUTH_MECH, + "heim-pa-mech-context", + NULL, + mech_dealloc, + NULL, + NULL, + NULL, + NULL +}; + +static struct pa_auth_mech * +pa_mech_create(krb5_context context, krb5_init_creds_context ctx, int pa_type) +{ + struct pa_auth_mech *pa_mech; + const struct patype *patype = NULL; + size_t n; + + for (n = 0; patype == NULL && n < sizeof(patypes)/sizeof(patypes[0]); n++) { + if (patypes[n].type == pa_type) + patype = &patypes[n]; + } + if (patype == NULL) + return NULL; + + pa_mech = _heim_alloc_object(&pa_auth_mech_object, sizeof(*pa_mech) - 1 + patype->pa_ctx_size); + if (pa_mech == NULL) + return NULL; + + pa_mech->patype = patype; + + if (pa_mech->patype->configure) { + krb5_error_code ret; + + ret = pa_mech->patype->configure(context, ctx, &pa_mech->pactx[0]); + if (ret) { + heim_release(pa_mech); + return NULL; + } + } + + _krb5_debug(context, 5, "Adding PA mech: %s", patype->name); + + return pa_mech; +} + +static void +pa_mech_add(krb5_context context, krb5_init_creds_context ctx, int pa_type) +{ + struct pa_auth_mech *mech; + + mech = pa_mech_create(context, ctx, pa_type); + if (mech) { + heim_array_append_value(ctx->available_pa_mechs, mech); + heim_release(mech); + } +} + +static krb5_error_code +pa_configure(krb5_context context, + krb5_init_creds_context ctx, + METHOD_DATA *in_md) +{ + ctx->available_pa_mechs = heim_array_create(); + + if (ctx->gss_init_ctx) { + pa_mech_add(context, ctx, KRB5_PADATA_GSS); + } else if (ctx->pk_init_ctx) { + pa_mech_add(context, ctx, KRB5_PADATA_PK_AS_REP); + pa_mech_add(context, ctx, KRB5_PADATA_PK_AS_REP_19); + } else if (ctx->keyproc || ctx->keyseed || ctx->prompter) { + pa_mech_add(context, ctx, KRB5_PADATA_ENCRYPTED_CHALLENGE); + pa_mech_add(context, ctx, KRB5_PADATA_ENC_TIMESTAMP); + } + /* XXX setup context based on KDC reply */ + + return 0; +} + +static krb5_error_code +pa_restart(krb5_context context, + krb5_init_creds_context ctx) +{ + krb5_error_code ret = HEIM_ERR_PA_CANT_CONTINUE; + + if (ctx->pa_mech && ctx->pa_mech->patype->restart) + ret = ctx->pa_mech->patype->restart(context, ctx, (void *)&ctx->pa_mech->pactx[0]); + + return ret; +} + + +static krb5_error_code +pa_step(krb5_context context, + krb5_init_creds_context ctx, + const AS_REQ *a, + const AS_REP *rep, + METHOD_DATA *in_md, + METHOD_DATA *out_md) +{ + krb5_error_code ret; + PA_DATA *pa = NULL; + int idx; + + next: + do { + if (ctx->pa_mech == NULL) { + size_t len = heim_array_get_length(ctx->available_pa_mechs); + if (len == 0) { + _krb5_debug(context, 0, "no more available_pa_mechs to try"); + return HEIM_ERR_NO_MORE_PA_MECHS; + } + + ctx->pa_mech = heim_array_copy_value(ctx->available_pa_mechs, 0); + heim_array_delete_value(ctx->available_pa_mechs, 0); + } + + if (ctx->fast_state.armor_crypto) { + if ((ctx->pa_mech->patype->flags & PA_F_FAST) == 0) { + _krb5_debug(context, 0, "pa-mech %s dropped under FAST (not supported)", + ctx->pa_mech->patype->name); + heim_release(ctx->pa_mech); + ctx->pa_mech = NULL; + continue; + } + } else { + if ((ctx->pa_mech->patype->flags & PA_F_NOT_FAST) == 0) { + _krb5_debug(context, 0, "dropped pa-mech %s since not running under FAST", + ctx->pa_mech->patype->name); + heim_release(ctx->pa_mech); + ctx->pa_mech = NULL; + continue; + } + } + + _krb5_debug(context, 0, "pa-mech trying: %s, searching for %d", + ctx->pa_mech->patype->name, ctx->pa_mech->patype->type); + + idx = 0; + if (in_md) + pa = krb5_find_padata(in_md->val, in_md->len, ctx->pa_mech->patype->type, &idx); + else + pa = NULL; + + } while (ctx->pa_mech == NULL); + + _krb5_debug(context, 5, "Stepping pa-mech: %s", ctx->pa_mech->patype->name); + + ret = ctx->pa_mech->patype->step(context, ctx, (void *)&ctx->pa_mech->pactx[0], pa, a, rep, in_md, out_md); + _krb5_debug(context, 10, "PA type %s returned %d", ctx->pa_mech->patype->name, ret); + if (ret == 0) { + struct pa_auth_mech *next_pa = ctx->pa_mech->next; + + if (next_pa) { + _krb5_debug(context, 5, "Next PA type in set is: %s", + next_pa->patype->name); + ret = HEIM_ERR_PA_CONTINUE_NEEDED; + } else if (rep == NULL) { + _krb5_debug(context, 5, "PA %s done, but no ticket in sight!!!", + ctx->pa_mech->patype->name); + ret = HEIM_ERR_PA_CANT_CONTINUE; + } else { + ctx->pa_used = ctx->pa_mech->patype->name; + } + + heim_retain(next_pa); + heim_release(ctx->pa_mech); + ctx->pa_mech = next_pa; + } + + if (ret == HEIM_ERR_PA_CANT_CONTINUE) { + if (ctx->pa_mech) { + _krb5_debug(context, 5, "Dropping PA type %s", ctx->pa_mech->patype->name); + heim_release(ctx->pa_mech); + ctx->pa_mech = NULL; + } + goto next; + } else if (ret == HEIM_ERR_PA_CONTINUE_NEEDED) { + _krb5_debug(context, 5, "Continue needed for %s", ctx->pa_mech->patype->name); + } else if (ret != 0) { + _krb5_debug(context, 5, "Other error from mech %s: %d", ctx->pa_mech->patype->name, ret); + heim_release(ctx->pa_mech); + ctx->pa_mech = NULL; + } + + return ret; +} + +static void +log_kdc_pa_types(krb5_context context, METHOD_DATA *in_md) +{ + if (_krb5_have_debug(context, 5)) { + unsigned i; + _krb5_debug(context, 5, "KDC sent %d patypes", in_md->len); + for (i = 0; i < in_md->len; i++) + _krb5_debug(context, 5, "KDC sent PA-DATA type: %d (%s)", + in_md->val[i].padata_type, + get_pa_type_name(in_md->val[i].padata_type)); + } +} + +/* + * Assumes caller always will free `out_md', even on error. + */ + +static krb5_error_code +process_pa_data_to_md(krb5_context context, + const krb5_creds *creds, + const AS_REQ *a, + krb5_init_creds_context ctx, + METHOD_DATA *in_md, + METHOD_DATA **out_md) +{ + krb5_error_code ret; + + ALLOC(*out_md, 1); + if (*out_md == NULL) { + return krb5_enomem(context); + } + (*out_md)->len = 0; + (*out_md)->val = NULL; + + log_kdc_pa_types(context, in_md); + + ret = pa_step(context, ctx, a, NULL, in_md, *out_md); + if (ret == HEIM_ERR_PA_CONTINUE_NEEDED) { + _krb5_debug(context, 0, "pamech need more stepping"); + } else if (ret == 0) { + _krb5_debug(context, 0, "pamech done step"); + } else { + return ret; + } + + /* + * Send announcement (what we support) and configuration (user + * introduced behavior change) + */ + ret = pa_announce(context, PA_F_ANNOUNCE|PA_F_CONFIG, ctx, in_md, *out_md); + + /* + * + */ + + if ((*out_md)->len == 0) { + free(*out_md); + *out_md = NULL; + } + + return ret; +} + +static krb5_error_code +process_pa_data_to_key(krb5_context context, + krb5_init_creds_context ctx, + krb5_creds *creds, + AS_REQ *a, + AS_REP *rep, + krb5_keyblock **key) +{ + struct pa_info_data paid, *ppaid = NULL; + krb5_error_code ret; + krb5_enctype etype = rep->enc_part.etype; + + memset(&paid, 0, sizeof(paid)); + + if (rep->padata) + log_kdc_pa_types(context, rep->padata); + + if (rep->padata) { + paid.etype = etype; + ppaid = process_pa_info(context, creds->client, a, &paid, + rep->padata); + } + if (ppaid == NULL) { + if (ctx->paid.etype == KRB5_ENCTYPE_NULL) { + ctx->paid.etype = etype; + ctx->paid.s2kparams = NULL; + ret = krb5_get_pw_salt (context, creds->client, &ctx->paid.salt); + if (ret) + return ret; + } + } + + ret = pa_step(context, ctx, a, rep, rep->padata, NULL); + if (ret == HEIM_ERR_PA_CONTINUE_NEEDED) { + _krb5_debug(context, 0, "In final stretch and pa require more stepping ?"); + return ret; + } else if (ret == 0) { + _krb5_debug(context, 0, "final pamech done step"); + goto out; + } else { + return ret; + } + out: + free_paid(context, &paid); + return ret; +} + +/* + * + */ + +static krb5_error_code +capture_lkdc_domain(krb5_context context, + krb5_init_creds_context ctx) +{ + size_t len; + + len = strlen(_krb5_wellknown_lkdc); + + if (ctx->kdc_hostname != NULL || + strncmp(ctx->cred.client->realm, _krb5_wellknown_lkdc, len) != 0 || + ctx->cred.client->realm[len] != ':') + return 0; + + ctx->kdc_hostname = strdup(&ctx->cred.client->realm[len + 1]); + + _krb5_debug(context, 5, "krb5_get_init_creds: setting LKDC hostname to: %s", + ctx->kdc_hostname); + return 0; +} + +/** + * Start a new context to get a new initial credential. + * + * @param context A Kerberos 5 context. + * @param client The Kerberos principal to get the credential for, if + * NULL is given, the default principal is used as determined by + * krb5_get_default_principal(). + * @param prompter + * @param prompter_data + * @param start_time the time the ticket should start to be valid or 0 for now. + * @param options a options structure, can be NULL for default options. + * @param rctx A new allocated free with krb5_init_creds_free(). + * + * @return 0 for success or an Kerberos 5 error code, see krb5_get_error_message(). + * + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_init(krb5_context context, + krb5_principal client, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_deltat start_time, + krb5_get_init_creds_opt *options, + krb5_init_creds_context *rctx) +{ + krb5_init_creds_context ctx; + krb5_error_code ret; + + *rctx = NULL; + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) + return krb5_enomem(context); + + ret = get_init_creds_common(context, client, prompter, prompter_data, + start_time, options, ctx); + if (ret) { + free(ctx); + return ret; + } + + /* Set a new nonce. */ + /* FIXME should generate a new nonce for each AS-REQ */ + krb5_generate_random_block (&ctx->nonce, sizeof(ctx->nonce)); + ctx->nonce &= 0x7fffffff; + /* XXX these just need to be the same when using Windows PK-INIT */ + ctx->pk_nonce = ctx->nonce; + + ctx->prompter = prompter; + ctx->prompter_data = prompter_data; + + /* pick up hostname from LKDC realm name */ + ret = capture_lkdc_domain(context, ctx); + if (ret) { + free_init_creds_ctx(context, ctx); + return ret; + } + + ctx->runflags.allow_enc_pa_rep = 1; + + ctx->fast_state.flags |= KRB5_FAST_AS_REQ; + + *rctx = ctx; + + return ret; +} + +/** + * Set the KDC hostname for the initial request, it will not be + * considered in referrals to another KDC. + * + * @param context a Kerberos 5 context. + * @param ctx a krb5_init_creds_context context. + * @param hostname the hostname for the KDC of realm + * + * @return 0 for success, or an Kerberos 5 error code, see krb5_get_error_message(). + * @ingroup krb5_credential + */ + +krb5_error_code KRB5_LIB_FUNCTION +krb5_init_creds_set_kdc_hostname(krb5_context context, + krb5_init_creds_context ctx, + const char *hostname) +{ + if (ctx->kdc_hostname) + free(ctx->kdc_hostname); + ctx->kdc_hostname = strdup(hostname); + if (ctx->kdc_hostname == NULL) + return krb5_enomem(context); + return 0; +} + +/** + * Set the sitename for the request + * + */ + +krb5_error_code KRB5_LIB_FUNCTION +krb5_init_creds_set_sitename(krb5_context context, + krb5_init_creds_context ctx, + const char *sitename) +{ + if (ctx->sitename) + free(ctx->sitename); + ctx->sitename = strdup(sitename); + if (ctx->sitename == NULL) + return krb5_enomem(context); + return 0; +} + +/** + * Sets the service that the is requested. This call is only neede for + * special initial tickets, by default the a krbtgt is fetched in the default realm. + * + * @param context a Kerberos 5 context. + * @param ctx a krb5_init_creds_context context. + * @param service the service given as a string, for example + * "kadmind/admin". If NULL, the default krbtgt in the clients + * realm is set. + * + * @return 0 for success, or an Kerberos 5 error code, see krb5_get_error_message(). + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_set_service(krb5_context context, + krb5_init_creds_context ctx, + const char *service) +{ + krb5_const_realm client_realm; + krb5_principal principal; + krb5_error_code ret; + + client_realm = krb5_principal_get_realm (context, ctx->cred.client); + + if (service) { + ret = krb5_parse_name (context, service, &principal); + if (ret) + return ret; + ret = krb5_principal_set_realm (context, principal, client_realm); + if (ret) { + krb5_free_principal(context, principal); + return ret; + } + } else { + ret = krb5_make_principal(context, &principal, + client_realm, KRB5_TGS_NAME, client_realm, + NULL); + if (ret) + return ret; + } + + /* + * This is for Windows RODC that are picky about what name type + * the server principal have, and the really strange part is that + * they are picky about the AS-REQ name type and not the TGS-REQ + * later. Oh well. + */ + + if (krb5_principal_is_krbtgt(context, principal)) + krb5_principal_set_type(context, principal, KRB5_NT_SRV_INST); + + krb5_free_principal(context, ctx->cred.server); + ctx->cred.server = principal; + + return 0; +} + +/** + * Sets the password that will use for the request. + * + * @param context a Kerberos 5 context. + * @param ctx ctx krb5_init_creds_context context. + * @param password the password to use. + * + * @return 0 for success, or an Kerberos 5 error code, see krb5_get_error_message(). + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_set_password(krb5_context context, + krb5_init_creds_context ctx, + const char *password) +{ + if (ctx->password) { + size_t len; + len = strlen(ctx->password); + memset_s(ctx->password, len, 0, len); + free(ctx->password); + } + if (password) { + ctx->password = strdup(password); + if (ctx->password == NULL) + return krb5_enomem(context); + ctx->keyseed = (void *) ctx->password; + } else { + ctx->keyseed = NULL; + ctx->password = NULL; + } + + return 0; +} + +static krb5_error_code KRB5_CALLCONV +keytab_key_proc(krb5_context context, krb5_enctype enctype, + krb5_const_pointer keyseed, + krb5_salt salt, krb5_data *s2kparms, + krb5_keyblock **key) +{ + krb5_keytab_key_proc_args *args = rk_UNCONST(keyseed); + krb5_keytab keytab = args->keytab; + krb5_principal principal = args->principal; + krb5_error_code ret; + krb5_keytab real_keytab = NULL; + krb5_keytab_entry entry; + + if (keytab == NULL) { + ret = krb5_kt_default(context, &real_keytab); + if (ret) + return ret; + keytab = real_keytab; + } + + ret = krb5_kt_get_entry (context, keytab, principal, 0, enctype, &entry); + if (ret == 0) { + ret = krb5_copy_keyblock(context, &entry.keyblock, key); + krb5_kt_free_entry(context, &entry); + } + + krb5_kt_close(context, real_keytab); + return ret; +} + + +/** + * Set the keytab to use for authentication. + * + * @param context a Kerberos 5 context. + * @param ctx ctx krb5_init_creds_context context. + * @param keytab the keytab to read the key from. + * + * @return 0 for success, or an Kerberos 5 error code, see krb5_get_error_message(). + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_set_keytab(krb5_context context, + krb5_init_creds_context ctx, + krb5_keytab keytab) +{ + krb5_keytab_key_proc_args *a; + krb5_keytab_entry entry; + krb5_kt_cursor cursor; + krb5_enctype *etypes = NULL; + krb5_error_code ret; + size_t netypes = 0; + int kvno = 0, found = 0; + unsigned n; + + a = malloc(sizeof(*a)); + if (a == NULL) + return krb5_enomem(context); + + a->principal = ctx->cred.client; + a->keytab = keytab; + + ctx->keytab_data = a; + ctx->keyseed = (void *)a; + ctx->keyproc = keytab_key_proc; + + /* + * We need to tell the KDC what enctypes we support for this keytab, + * especially if the keytab is really a password based entry, then the + * KDC might have more enctypes in the database then what we have + * in the keytab. + */ + + ret = krb5_kt_start_seq_get(context, keytab, &cursor); + if(ret) + goto out; + + while(krb5_kt_next_entry(context, keytab, &entry, &cursor) == 0){ + void *ptr; + + if (!krb5_principal_compare(context, entry.principal, ctx->cred.client)) + goto next; + + found = 1; + + /* check if we have this kvno already */ + if (entry.vno > kvno) { + /* remove old list of etype */ + if (etypes) + free(etypes); + etypes = NULL; + netypes = 0; + kvno = entry.vno; + } else if (entry.vno != kvno) + goto next; + + /* check if enctype is supported */ + if (krb5_enctype_valid(context, entry.keyblock.keytype) != 0) + goto next; + + /* + * If user already provided a enctype list, use that as an + * additonal filter. + */ + if (ctx->etypes) { + for (n = 0; ctx->etypes[n] != KRB5_ENCTYPE_NULL; n++) { + if (ctx->etypes[n] == entry.keyblock.keytype) + break; + } + if (ctx->etypes[n] == KRB5_ENCTYPE_NULL) + goto next; + } + + /* add enctype to supported list */ + ptr = realloc(etypes, sizeof(etypes[0]) * (netypes + 2)); + if (ptr == NULL) { + free(etypes); + ret = krb5_enomem(context); + goto out; + } + + etypes = ptr; + etypes[netypes] = entry.keyblock.keytype; + etypes[netypes + 1] = ETYPE_NULL; + netypes++; + next: + krb5_kt_free_entry(context, &entry); + } + krb5_kt_end_seq_get(context, keytab, &cursor); + + if (etypes) { + if (ctx->etypes) + free(ctx->etypes); + ctx->etypes = etypes; + } + + out: + if (!found) { + if (ret == 0) + ret = KRB5_KT_NOTFOUND; + _krb5_kt_principal_not_found(context, ret, keytab, ctx->cred.client, 0, 0); + } + + return ret; +} + +static krb5_error_code KRB5_CALLCONV +keyblock_key_proc(krb5_context context, krb5_enctype enctype, + krb5_const_pointer keyseed, + krb5_salt salt, krb5_data *s2kparms, + krb5_keyblock **key) +{ + return krb5_copy_keyblock (context, keyseed, key); +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_set_keyblock(krb5_context context, + krb5_init_creds_context ctx, + krb5_keyblock *keyblock) +{ + ctx->keyseed = (void *)keyblock; + ctx->keyproc = keyblock_key_proc; + + return 0; +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_set_fast_ccache(krb5_context context, + krb5_init_creds_context ctx, + krb5_ccache fast_ccache) +{ + ctx->fast_state.armor_ccache = fast_ccache; + ctx->fast_state.flags |= KRB5_FAST_REQUIRED; + ctx->fast_state.flags |= KRB5_FAST_KDC_VERIFIED; + return 0; +} + +static krb5_error_code +validate_pkinit_fx(krb5_context context, + krb5_init_creds_context ctx, + AS_REP *rep, + krb5_keyblock *ticket_sessionkey) +{ + PA_DATA *pa = NULL; + int idx = 0; + + if (rep->padata) + pa = krb5_find_padata(rep->padata->val, rep->padata->len, KRB5_PADATA_PKINIT_KX, &idx); + + if (pa == NULL) { + if (ctx->flags.request_anonymous && ctx->pk_init_ctx) { + /* XXX handle the case where pkinit is not used */ + krb5_set_error_message(context, KRB5_KDCREP_MODIFIED, + N_("Requested anonymous with PKINIT and KDC didn't set PKINIT_KX", "")); + return KRB5_KDCREP_MODIFIED; + } + + return 0; + } + + heim_assert(ctx->fast_state.reply_key != NULL, "must have a reply key at this stage"); + + return _krb5_pk_kx_confirm(context, + ctx->pk_init_ctx, + ctx->fast_state.reply_key, + ticket_sessionkey, + pa); +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_set_fast_ap_armor_service(krb5_context context, + krb5_init_creds_context ctx, + krb5_const_principal armor_service) +{ + krb5_error_code ret; + + if (ctx->fast_state.armor_service) + krb5_free_principal(context, ctx->fast_state.armor_service); + if (armor_service) { + ret = krb5_copy_principal(context, armor_service, &ctx->fast_state.armor_service); + if (ret) + return ret; + } else { + ctx->fast_state.armor_service = NULL; + } + ctx->fast_state.flags |= KRB5_FAST_AP_ARMOR_SERVICE; + return 0; +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_set_fast_anon_pkinit(krb5_context context, + krb5_init_creds_context ctx) +{ + if (ctx->fast_state.armor_ccache) + return EINVAL; + + ctx->fast_state.flags |= KRB5_FAST_REQUIRED; + ctx->fast_state.flags |= KRB5_FAST_ANON_PKINIT_ARMOR; + return 0; +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +_krb5_init_creds_set_fast_anon_pkinit_optimistic(krb5_context context, + krb5_init_creds_context ctx) +{ + if (ctx->fast_state.armor_ccache) + return EINVAL; + + ctx->fast_state.flags |= KRB5_FAST_REQUIRED; + ctx->fast_state.flags |= KRB5_FAST_ANON_PKINIT_ARMOR; + ctx->fast_state.flags |= KRB5_FAST_OPTIMISTIC; + return 0; +} + +static size_t +available_padata_count(METHOD_DATA *md) +{ + size_t i, count = 0; + + for (i = 0; i < md->len; i++) { + PA_DATA *pa = &md->val[i]; + + if (pa->padata_type == KRB5_PADATA_FX_COOKIE || + pa->padata_type == KRB5_PADATA_FX_ERROR) + continue; + + count++; + } + + return count; +} + +static krb5_error_code +init_creds_step(krb5_context context, + krb5_init_creds_context ctx, + const krb5_data *in, + krb5_data *out, + krb5_realm *out_realm, + unsigned int *flags) +{ + struct timeval start_time, end_time; + krb5_data checksum_data; + krb5_error_code ret; + size_t len = 0; + size_t size; + AS_REQ req2; + + gettimeofday(&start_time, NULL); + + krb5_data_zero(out); + *out_realm = NULL; + krb5_data_zero(&checksum_data); + + if (ctx->as_req.req_body.cname == NULL) { + ret = init_as_req(context, ctx->flags, &ctx->cred, + ctx->addrs, ctx->etypes, &ctx->as_req); + if (ret) + return ret; + if (ctx->fast_state.flags & KRB5_FAST_REQUIRED) + ; + else if (ctx->fast_state.flags & KRB5_FAST_AP_ARMOR_SERVICE) + /* Check with armor service if there is FAST */; + else + ctx->fast_state.flags |= KRB5_FAST_DISABLED; + + + /* XXX should happen after we get back reply from KDC */ + pa_configure(context, ctx, NULL); + } + +#define MAX_PA_COUNTER 15 + if (ctx->pa_counter > MAX_PA_COUNTER) { + krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP, + N_("Looping %d times while getting " + "initial credentials", ""), + ctx->pa_counter); + return KRB5_GET_IN_TKT_LOOP; + } + ctx->pa_counter++; + + _krb5_debug(context, 5, "krb5_get_init_creds: loop %d", ctx->pa_counter); + + /* Lets process the input packet */ + if (in && in->length) { + krb5_kdc_rep rep; + + memset(&rep, 0, sizeof(rep)); + + _krb5_debug(context, 5, "krb5_get_init_creds: processing input"); + + ret = decode_AS_REP(in->data, in->length, &rep.kdc_rep, &size); + if (ret == 0) { + unsigned eflags = EXTRACT_TICKET_AS_REQ | EXTRACT_TICKET_TIMESYNC; + krb5_data data; + + /* + * Unwrap AS-REP + */ + ASN1_MALLOC_ENCODE(Ticket, data.data, data.length, + &rep.kdc_rep.ticket, &size, ret); + if (ret) + goto out; + heim_assert(data.length == size, "ASN.1 internal error"); + + ret = _krb5_fast_unwrap_kdc_rep(context, ctx->nonce, &data, + &ctx->fast_state, &rep.kdc_rep); + krb5_data_free(&data); + if (ret) + goto out; + + /* + * Now check and extract the ticket + */ + + if (ctx->flags.canonicalize) { + eflags |= EXTRACT_TICKET_ALLOW_SERVER_MISMATCH; + eflags |= EXTRACT_TICKET_MATCH_REALM; + } + if (ctx->ic_flags & KRB5_INIT_CREDS_NO_C_CANON_CHECK) + eflags |= EXTRACT_TICKET_ALLOW_CNAME_MISMATCH; + if (ctx->flags.request_anonymous) + eflags |= EXTRACT_TICKET_MATCH_ANON; + + ret = process_pa_data_to_key(context, ctx, &ctx->cred, + &ctx->as_req, &rep.kdc_rep, + &ctx->fast_state.reply_key); + if (ret) { + free_AS_REP(&rep.kdc_rep); + goto out; + } + + if (ctx->fast_state.strengthen_key) { + krb5_keyblock result; + + _krb5_debug(context, 5, "krb5_get_init_creds: FAST strengthen_key"); + + ret = _krb5_fast_cf2(context, + ctx->fast_state.strengthen_key, + "strengthenkey", + ctx->fast_state.reply_key, + "replykey", + &result, + NULL); + if (ret) { + free_AS_REP(&rep.kdc_rep); + goto out; + } + + ctx->runflags.allow_save_as_reply_key = 1; + + krb5_free_keyblock_contents(context, ctx->fast_state.reply_key); + *ctx->fast_state.reply_key = result; + } + + _krb5_debug(context, 5, "krb5_get_init_creds: extracting ticket"); + + ret = _krb5_extract_ticket(context, + &rep, + &ctx->cred, + ctx->fast_state.reply_key, + NULL, + KRB5_KU_AS_REP_ENC_PART, + NULL, + ctx->nonce, + eflags, + &ctx->req_buffer, + NULL, + NULL); + + if (ret == 0) + ret = copy_EncKDCRepPart(&rep.enc_part, &ctx->enc_part); + if (ret == 0) + ret = validate_pkinit_fx(context, ctx, &rep.kdc_rep, &ctx->cred.session); + + ctx->as_enctype = ctx->fast_state.reply_key->keytype; + + if (ctx->runflags.allow_save_as_reply_key) { + ctx->as_reply_key = ctx->fast_state.reply_key; + ctx->fast_state.reply_key = NULL; + } else { + krb5_free_keyblock(context, ctx->fast_state.reply_key); + ctx->fast_state.reply_key = NULL; + } + ctx->ic_flags |= KRB5_INIT_CREDS_DONE; + *flags = 0; + + free_AS_REP(&rep.kdc_rep); + free_EncASRepPart(&rep.enc_part); + + gettimeofday(&end_time, NULL); + timevalsub(&end_time, &start_time); + timevaladd(&ctx->stats.run_time, &end_time); + + _krb5_debug(context, 1, "krb5_get_init_creds: wc: %lld.%06ld", + (long long)ctx->stats.run_time.tv_sec, + (long)ctx->stats.run_time.tv_usec); + return ret; + + } else { + /* let's try to parse it as a KRB-ERROR */ + + _krb5_debug(context, 5, "krb5_get_init_creds: got an KRB-ERROR from KDC"); + + free_KRB_ERROR(&ctx->error); + + ret = krb5_rd_error(context, in, &ctx->error); + if(ret && in->length && ((char*)in->data)[0] == 4) + ret = KRB5KRB_AP_ERR_V4_REPLY; + if (ret) { + _krb5_debug(context, 5, "krb5_get_init_creds: failed to read error"); + goto out; + } + + /* + * Unwrap method-data, if there is any, + * fast_unwrap_error() below might replace it with a + * wrapped version if we are using FAST. + */ + + free_METHOD_DATA(&ctx->md); + memset(&ctx->md, 0, sizeof(ctx->md)); + + if (ctx->error.e_data) { + KERB_ERROR_DATA kerb_error_data; + krb5_error_code ret2; + + memset(&kerb_error_data, 0, sizeof(kerb_error_data)); + + /* First try to decode the e-data as KERB-ERROR-DATA. */ + ret2 = decode_KERB_ERROR_DATA(ctx->error.e_data->data, + ctx->error.e_data->length, + &kerb_error_data, + &len); + if (ret2) { + /* That failed, so try to decode it as METHOD-DATA. */ + ret2 = decode_METHOD_DATA(ctx->error.e_data->data, + ctx->error.e_data->length, + &ctx->md, + NULL); + if (ret2) { + /* + * Just ignore any error, the error will be pushed + * out from krb5_error_from_rd_error() if there + * was one. + */ + _krb5_debug(context, 5, N_("Failed to decode METHOD-DATA", "")); + } + } else if (len != ctx->error.e_data->length) { + /* Trailing data — just ignore the error. */ + free_KERB_ERROR_DATA(&kerb_error_data); + } else { + /* OK. */ + free_KERB_ERROR_DATA(&kerb_error_data); + } + } + + /* + * Unwrap KRB-ERROR, we are always calling this so that + * FAST can tell us if your peer KDC suddenly dropped FAST + * wrapping and its really an attacker's packet (or a bug + * in the KDC). + */ + ret = _krb5_fast_unwrap_error(context, ctx->nonce, &ctx->fast_state, + &ctx->md, &ctx->error); + if (ret) + goto out; + + /* + * + */ + + ret = krb5_error_from_rd_error(context, &ctx->error, &ctx->cred); + + /* log the failure */ + if (_krb5_have_debug(context, 5)) { + const char *str = krb5_get_error_message(context, ret); + _krb5_debug(context, 5, "krb5_get_init_creds: KRB-ERROR %d/%s", ret, str); + krb5_free_error_message(context, str); + } + + /* + * Handle special error codes + */ + + if (ret == KRB5KDC_ERR_PREAUTH_REQUIRED + || ret == KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED + || ret == KRB5KDC_ERR_ETYPE_NOSUPP) + { + /* + * If no preauth was set and KDC requires it, give it one + * more try. + * + * If the KDC returned KRB5KDC_ERR_ETYPE_NOSUPP, just loop + * one more time since that might mean we are dealing with + * a Windows KDC that is confused about what enctypes are + * available. + */ + + if (available_padata_count(&ctx->md) == 0) { + krb5_set_error_message(context, ret, + N_("Preauth required but no preauth " + "options sent by KDC", "")); + goto out; + } + } else if (ret == KRB5KRB_AP_ERR_SKEW && context->kdc_sec_offset == 0) { + /* + * Try adapt to timeskrew when we are using pre-auth, and + * if there was a time skew, try again. + */ + krb5_set_real_time(context, ctx->error.stime, -1); + if (context->kdc_sec_offset) + ret = 0; + + _krb5_debug(context, 10, "init_creds: err skew updating kdc offset to %d", + context->kdc_sec_offset); + if (ret) + goto out; + + pa_restart(context, ctx); + + } else if (ret == KRB5_KDC_ERR_WRONG_REALM && ctx->flags.canonicalize) { + /* client referral to a new realm */ + char *ref_realm; + + if (ctx->error.crealm == NULL) { + krb5_set_error_message(context, ret, + N_("Got a client referral, not but no realm", "")); + goto out; + } + ref_realm = *ctx->error.crealm; + + _krb5_debug(context, 5, "krb5_get_init_creds: referral to realm %s", + ref_realm); + + /* + * If its a krbtgt, lets update the requested krbtgt too + */ + if (krb5_principal_is_krbtgt(context, ctx->cred.server)) { + + free(ctx->cred.server->name.name_string.val[1]); + ctx->cred.server->name.name_string.val[1] = strdup(ref_realm); + if (ctx->cred.server->name.name_string.val[1] == NULL) { + ret = krb5_enomem(context); + goto out; + } + + free_PrincipalName(ctx->as_req.req_body.sname); + ret = _krb5_principal2principalname(ctx->as_req.req_body.sname, ctx->cred.server); + if (ret) + goto out; + } + + free(ctx->as_req.req_body.realm); + ret = copy_Realm(&ref_realm, &ctx->as_req.req_body.realm); + if (ret) + goto out; + + ret = krb5_principal_set_realm(context, + ctx->cred.client, + *ctx->error.crealm); + if (ret) + goto out; + + ret = krb5_unparse_name(context, ctx->cred.client, &ref_realm); + if (ret == 0) { + _krb5_debug(context, 5, "krb5_get_init_creds: got referral to %s", ref_realm); + krb5_xfree(ref_realm); + } + + pa_restart(context, ctx); + + } else if (ret == KRB5KDC_ERR_KEY_EXP && ctx->runflags.change_password == 0 && + ctx->runflags.change_password_prompt) { + char buf2[1024]; + + ctx->runflags.change_password = 1; + + ctx->prompter(context, ctx->prompter_data, NULL, N_("Password has expired", ""), 0, NULL); + + /* try to avoid recursion */ + if (ctx->in_tkt_service != NULL && strcmp(ctx->in_tkt_service, "kadmin/changepw") == 0) + goto out; + + /* don't include prompter in runtime */ + gettimeofday(&end_time, NULL); + timevalsub(&end_time, &start_time); + timevaladd(&ctx->stats.run_time, &end_time); + + ret = change_password(context, + ctx->cred.client, + ctx->password, + buf2, + sizeof(buf2), + ctx->prompter, + ctx->prompter_data, + NULL); + if (ret) + goto out; + + gettimeofday(&start_time, NULL); + + krb5_init_creds_set_password(context, ctx, buf2); + + pa_restart(context, ctx); + + } else if (ret == KRB5KDC_ERR_PREAUTH_FAILED) { + + /* + * Old MIT KDC can't handle KRB5_PADATA_REQ_ENC_PA_REP, + * so drop it and try again. But only try that for MIT + * Kerberos servers by keying of no METHOD-DATA. + */ + if (ctx->runflags.allow_enc_pa_rep) { + if (ctx->md.len != 0) { + _krb5_debug(context, 10, "Server sent PA data with KRB-ERROR, " + "so not a pre 1.7 MIT KDC and won't retry w/o ENC-PA-REQ"); + goto out; + } + _krb5_debug(context, 10, "Disabling allow_enc_pa_rep and trying again"); + ctx->runflags.allow_enc_pa_rep = 0; + goto retry; + } + + if (ctx->fast_state.flags & KRB5_FAST_DISABLED) { + _krb5_debug(context, 10, "FAST disabled and got preauth failed"); + goto out; + } + + retry: + pa_restart(context, ctx); + + } else if (ctx->fast_state.flags & KRB5_FAST_OPTIMISTIC) { + _krb5_debug(context, 10, + "Some other error %d failed with optimistic FAST, trying w/o FAST", ret); + + ctx->fast_state.flags &= ~KRB5_FAST_OPTIMISTIC; + ctx->fast_state.flags &= ~KRB5_FAST_REQUIRED; + ctx->fast_state.flags &= ~KRB5_FAST_ANON_PKINIT_ARMOR; + ctx->fast_state.flags |= KRB5_FAST_DISABLED; + pa_restart(context, ctx); + } else { + /* some other error code from the KDC, lets' return it to the user */ + goto out; + } + } + } + + if (ctx->as_req.padata) { + free_METHOD_DATA(ctx->as_req.padata); + free(ctx->as_req.padata); + ctx->as_req.padata = NULL; + } + + ret = _krb5_fast_create_armor(context, &ctx->fast_state, + ctx->cred.client->realm); + if (ret) + goto out; + + /* Set a new nonce. */ + ctx->as_req.req_body.nonce = ctx->nonce; + + + /* + * Step and announce PA-DATA + */ + + ret = process_pa_data_to_md(context, &ctx->cred, &ctx->as_req, ctx, + &ctx->md, &ctx->as_req.padata); + if (ret) + goto out; + + + /* + * Wrap with FAST + */ + ret = copy_AS_REQ(&ctx->as_req, &req2); + if (ret) + goto out; + + ret = _krb5_fast_wrap_req(context, + &ctx->fast_state, + &req2); + + krb5_data_free(&checksum_data); + if (ret) { + free_AS_REQ(&req2); + goto out; + } + + krb5_data_free(&ctx->req_buffer); + + ASN1_MALLOC_ENCODE(AS_REQ, + ctx->req_buffer.data, ctx->req_buffer.length, + &req2, &len, ret); + free_AS_REQ(&req2); + if (ret) + goto out; + if(len != ctx->req_buffer.length) + krb5_abortx(context, "internal error in ASN.1 encoder"); + + ret = krb5_data_copy(out, + ctx->req_buffer.data, + ctx->req_buffer.length); + if (ret) + goto out; + + *out_realm = strdup(ctx->cred.client->realm); + if (*out_realm == NULL) { + krb5_data_free(out); + ret = ENOMEM; + goto out; + } + + *flags = KRB5_INIT_CREDS_STEP_FLAG_CONTINUE; + + gettimeofday(&end_time, NULL); + timevalsub(&end_time, &start_time); + timevaladd(&ctx->stats.run_time, &end_time); + + return 0; + out: + return ret; +} + +/** + * The core loop if krb5_get_init_creds() function family. Create the + * packets and have the caller send them off to the KDC. + * + * If the caller want all work been done for them, use + * krb5_init_creds_get() instead. + * + * @param context a Kerberos 5 context. + * @param ctx ctx krb5_init_creds_context context. + * @param in input data from KDC, first round it should be reset by krb5_data_zero(). + * @param out reply to KDC. The caller needs to call krb5_data_free() + * @param out_realm the destination realm for 'out', free with krb5_xfree() + * @param flags status of the round, if + * KRB5_INIT_CREDS_STEP_FLAG_CONTINUE is set, continue one more round. + * + * @return 0 for success, or an Kerberos 5 error code, see + * krb5_get_error_message(). + * + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_step(krb5_context context, + krb5_init_creds_context ctx, + const krb5_data *in, + krb5_data *out, + krb5_realm *out_realm, + unsigned int *flags) +{ + krb5_error_code ret; + krb5_data empty; + + krb5_data_zero(&empty); + krb5_data_zero(out); + *out_realm = NULL; + + if ((ctx->fast_state.flags & KRB5_FAST_ANON_PKINIT_ARMOR) && + ctx->fast_state.armor_ccache == NULL) { + ret = _krb5_fast_anon_pkinit_step(context, ctx, &ctx->fast_state, + in, out, out_realm, flags); + if (ret && (ctx->fast_state.flags & KRB5_FAST_OPTIMISTIC)) { + _krb5_debug(context, 5, "Preauth failed with optimistic " + "FAST, trying w/o FAST"); + ctx->fast_state.flags &= ~KRB5_FAST_OPTIMISTIC; + ctx->fast_state.flags &= ~KRB5_FAST_REQUIRED; + ctx->fast_state.flags &= ~KRB5_FAST_ANON_PKINIT_ARMOR; + } else if (ret || + (*flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE)) + return ret; + + in = ∅ + } + + return init_creds_step(context, ctx, in, out, out_realm, flags); +} + +/** + * Extract the newly acquired credentials from krb5_init_creds_context + * context. + * + * @param context A Kerberos 5 context. + * @param ctx + * @param cred credentials, free with krb5_free_cred_contents(). + * + * @return 0 for sucess or An Kerberos error code, see krb5_get_error_message(). + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_get_creds(krb5_context context, + krb5_init_creds_context ctx, + krb5_creds *cred) +{ + return krb5_copy_creds_contents(context, &ctx->cred, cred); +} + +/** + * Extract the as-reply key from the context. + * + * Only allowed when the as-reply-key is not directly derived from the + * password like PK-INIT, GSS, FAST hardened key, etc. + * + * @param context A Kerberos 5 context. + * @param ctx ctx krb5_init_creds_context context. + * @param as_reply_key keyblock, free with krb5_free_keyblock_contents(). + * + * @return 0 for sucess or An Kerberos error code, see krb5_get_error_message(). + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_get_as_reply_key(krb5_context context, + krb5_init_creds_context ctx, + krb5_keyblock *as_reply_key) +{ + if (ctx->as_reply_key == NULL) + return KRB5KDC_ERR_PREAUTH_REQUIRED; + return krb5_copy_keyblock_contents(context, ctx->as_reply_key, as_reply_key); +} + +KRB5_LIB_FUNCTION krb5_timestamp KRB5_LIB_CALL +_krb5_init_creds_get_cred_starttime(krb5_context context, krb5_init_creds_context ctx) +{ + return ctx->cred.times.starttime; +} + +KRB5_LIB_FUNCTION krb5_timestamp KRB5_LIB_CALL +_krb5_init_creds_get_cred_endtime(krb5_context context, krb5_init_creds_context ctx) +{ + return ctx->cred.times.endtime; +} + +KRB5_LIB_FUNCTION krb5_principal KRB5_LIB_CALL +_krb5_init_creds_get_cred_client(krb5_context context, krb5_init_creds_context ctx) +{ + return ctx->cred.client; +} + +/** + * Get the last error from the transaction. + * + * @return Returns 0 or an error code + * + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_get_error(krb5_context context, + krb5_init_creds_context ctx, + KRB_ERROR *error) +{ + krb5_error_code ret; + + ret = copy_KRB_ERROR(&ctx->error, error); + if (ret) + krb5_enomem(context); + + return ret; +} + +/** + * Store config + * + * @param context A Kerberos 5 context. + * @param ctx The krb5_init_creds_context to free. + * @param id store + * + * @return Returns 0 or an error code + * + * @ingroup krb5_credential + */ + +krb5_error_code KRB5_LIB_FUNCTION +krb5_init_creds_store_config(krb5_context context, + krb5_init_creds_context ctx, + krb5_ccache id) +{ + krb5_error_code ret; + + if (ctx->kdc_hostname) { + krb5_data data; + data.length = strlen(ctx->kdc_hostname); + data.data = ctx->kdc_hostname; + + ret = krb5_cc_set_config(context, id, NULL, "lkdc-hostname", &data); + if (ret) + return ret; + } + if (ctx->sitename) { + krb5_data data; + data.length = strlen(ctx->sitename); + data.data = ctx->sitename; + + ret = krb5_cc_set_config(context, id, NULL, "sitename", &data); + if (ret) + return ret; + } + + return 0; +} + +/** + * + * @ingroup krb5_credential + */ + +krb5_error_code +krb5_init_creds_store(krb5_context context, + krb5_init_creds_context ctx, + krb5_ccache id) +{ + krb5_error_code ret; + + if (ctx->cred.client == NULL) { + ret = KRB5KDC_ERR_PREAUTH_REQUIRED; + krb5_set_error_message(context, ret, "init creds not completed yet"); + return ret; + } + + ret = krb5_cc_initialize(context, id, ctx->cred.client); + if (ret) + return ret; + + ret = krb5_cc_store_cred(context, id, &ctx->cred); + if (ret) + return ret; + + if (ctx->cred.flags.b.enc_pa_rep) { + krb5_data data = { 3, rk_UNCONST("yes") }; + ret = krb5_cc_set_config(context, id, ctx->cred.server, + "fast_avail", &data); + if (ret && ret != KRB5_CC_NOSUPP) + return ret; + } + + return 0; +} + +/** + * Free the krb5_init_creds_context allocated by krb5_init_creds_init(). + * + * @param context A Kerberos 5 context. + * @param ctx The krb5_init_creds_context to free. + * + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +krb5_init_creds_free(krb5_context context, + krb5_init_creds_context ctx) +{ + free_init_creds_ctx(context, ctx); + free(ctx); +} + +/** + * Get new credentials as setup by the krb5_init_creds_context. + * + * @param context A Kerberos 5 context. + * @param ctx The krb5_init_creds_context to process. + * + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_init_creds_get(krb5_context context, krb5_init_creds_context ctx) +{ + krb5_sendto_ctx stctx = NULL; + krb5_error_code ret; + krb5_data in, out; + unsigned int flags = 0; + + krb5_data_zero(&in); + krb5_data_zero(&out); + + ret = krb5_sendto_ctx_alloc(context, &stctx); + if (ret) + goto out; + krb5_sendto_ctx_set_func(stctx, _krb5_kdc_retry, NULL); + + if (ctx->kdc_hostname) + krb5_sendto_set_hostname(context, stctx, ctx->kdc_hostname); + if (ctx->sitename) + krb5_sendto_set_sitename(context, stctx, ctx->sitename); + + while (1) { + struct timeval nstart, nend; + krb5_realm realm = NULL; + + flags = 0; + ret = krb5_init_creds_step(context, ctx, &in, &out, &realm, &flags); + krb5_data_free(&in); + if (ret) + goto out; + + if ((flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE) == 0) + break; + + gettimeofday(&nstart, NULL); + + ret = krb5_sendto_context (context, stctx, &out, realm, &in); + krb5_data_free(&out); + free(realm); + if (ret) + goto out; + + gettimeofday(&nend, NULL); + timevalsub(&nend, &nstart); + timevaladd(&ctx->stats.run_time, &nend); + } + + out: + if (stctx) + krb5_sendto_ctx_free(context, stctx); + + return ret; +} + +/** + * Get new credentials using password. + * + * @ingroup krb5_credential + */ + + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_get_init_creds_password(krb5_context context, + krb5_creds *creds, + krb5_principal client, + const char *password, + krb5_prompter_fct prompter, + void *data, + krb5_deltat start_time, + const char *in_tkt_service, + krb5_get_init_creds_opt *options) +{ + krb5_init_creds_context ctx; + char buf[BUFSIZ], buf2[BUFSIZ]; + krb5_error_code ret; + int chpw = 0; + + again: + ret = krb5_init_creds_init(context, client, prompter, data, start_time, options, &ctx); + if (ret) + goto out; + + ret = krb5_init_creds_set_service(context, ctx, in_tkt_service); + if (ret) + goto out; + + if (prompter != NULL && ctx->password == NULL && password == NULL) { + krb5_prompt prompt; + krb5_data password_data; + char *p, *q = NULL; + int aret; + + ret = krb5_unparse_name(context, client, &p); + if (ret) + goto out; + + aret = asprintf(&q, "%s's Password: ", p); + free (p); + if (aret == -1 || q == NULL) { + ret = krb5_enomem(context); + goto out; + } + prompt.prompt = q; + password_data.data = buf; + password_data.length = sizeof(buf); + prompt.hidden = 1; + prompt.reply = &password_data; + prompt.type = KRB5_PROMPT_TYPE_PASSWORD; + + ret = (*prompter) (context, data, NULL, NULL, 1, &prompt); + free (q); + if (ret) { + memset_s(buf, sizeof(buf), 0, sizeof(buf)); + ret = KRB5_LIBOS_PWDINTR; + krb5_clear_error_message (context); + goto out; + } + password = password_data.data; + } + + if (password) { + ret = krb5_init_creds_set_password(context, ctx, password); + if (ret) + goto out; + } + + ret = krb5_init_creds_get(context, ctx); + + if (ret == 0) + krb5_process_last_request(context, options, ctx); + + + if (ret == KRB5KDC_ERR_KEY_EXPIRED && chpw == 0) { + /* try to avoid recursion */ + if (in_tkt_service != NULL && strcmp(in_tkt_service, "kadmin/changepw") == 0) + goto out; + + /* don't try to change password if no prompter or prompting disabled */ + if (!ctx->runflags.change_password_prompt) + goto out; + + ret = change_password (context, + client, + ctx->password, + buf2, + sizeof(buf2), + prompter, + data, + options); + if (ret) + goto out; + password = buf2; + chpw = 1; + krb5_init_creds_free(context, ctx); + goto again; + } + + out: + if (ret == 0) + krb5_init_creds_get_creds(context, ctx, creds); + + if (ctx) + krb5_init_creds_free(context, ctx); + + memset_s(buf, sizeof(buf), 0, sizeof(buf)); + memset_s(buf2, sizeof(buf), 0, sizeof(buf2)); + return ret; +} + +/** + * Get new credentials using keyblock. + * + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_get_init_creds_keyblock(krb5_context context, + krb5_creds *creds, + krb5_principal client, + krb5_keyblock *keyblock, + krb5_deltat start_time, + const char *in_tkt_service, + krb5_get_init_creds_opt *options) +{ + krb5_init_creds_context ctx; + krb5_error_code ret; + + memset(creds, 0, sizeof(*creds)); + + ret = krb5_init_creds_init(context, client, NULL, NULL, start_time, options, &ctx); + if (ret) + goto out; + + ret = krb5_init_creds_set_service(context, ctx, in_tkt_service); + if (ret) + goto out; + + ret = krb5_init_creds_set_keyblock(context, ctx, keyblock); + if (ret) + goto out; + + ret = krb5_init_creds_get(context, ctx); + + if (ret == 0) + krb5_process_last_request(context, options, ctx); + + out: + if (ret == 0) + krb5_init_creds_get_creds(context, ctx, creds); + + if (ctx) + krb5_init_creds_free(context, ctx); + + return ret; +} + +/** + * Get new credentials using keytab. + * + * @ingroup krb5_credential + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +krb5_get_init_creds_keytab(krb5_context context, + krb5_creds *creds, + krb5_principal client, + krb5_keytab keytab, + krb5_deltat start_time, + const char *in_tkt_service, + krb5_get_init_creds_opt *options) +{ + krb5_init_creds_context ctx; + krb5_keytab_entry ktent; + krb5_error_code ret; + + memset(&ktent, 0, sizeof(ktent)); + memset(creds, 0, sizeof(*creds)); + + if (strcmp(client->realm, "") == 0) { + /* + * Referral realm. We have a keytab, so pick a realm by + * matching in the keytab. + */ + ret = krb5_kt_get_entry(context, keytab, client, 0, 0, &ktent); + if (ret == 0) + client = ktent.principal; + } + + ret = krb5_init_creds_init(context, client, NULL, NULL, start_time, options, &ctx); + if (ret) + goto out; + + ret = krb5_init_creds_set_service(context, ctx, in_tkt_service); + if (ret) + goto out; + + ret = krb5_init_creds_set_keytab(context, ctx, keytab); + if (ret) + goto out; + + ret = krb5_init_creds_get(context, ctx); + if (ret == 0) + krb5_process_last_request(context, options, ctx); + + out: + krb5_kt_free_entry(context, &ktent); + if (ret == 0) + krb5_init_creds_get_creds(context, ctx, creds); + + if (ctx) + krb5_init_creds_free(context, ctx); + + return ret; +} + +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +_krb5_init_creds_set_gss_mechanism(krb5_context context, + krb5_gss_init_ctx gssic, + const struct gss_OID_desc_struct *gss_mech) +{ + gssic->mech = gss_mech; /* OIDs are interned, so no copy required */ +} + +KRB5_LIB_FUNCTION const struct gss_OID_desc_struct * KRB5_LIB_CALL +_krb5_init_creds_get_gss_mechanism(krb5_context context, + krb5_gss_init_ctx gssic) +{ + return gssic->mech; +} + +KRB5_LIB_FUNCTION void KRB5_LIB_CALL +_krb5_init_creds_set_gss_cred(krb5_context context, + krb5_gss_init_ctx gssic, + struct gss_cred_id_t_desc_struct *gss_cred) +{ + if (gssic->cred != gss_cred && gssic->flags.release_cred) + gssic->release_cred(context, gssic, gssic->cred); + + gssic->cred = gss_cred; + gssic->flags.release_cred = 1; +} + +KRB5_LIB_FUNCTION const struct gss_cred_id_t_desc_struct * KRB5_LIB_CALL +_krb5_init_creds_get_gss_cred(krb5_context context, + krb5_gss_init_ctx gssic) +{ + return gssic->cred; +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +_krb5_init_creds_init_gss(krb5_context context, + krb5_init_creds_context ctx, + krb5_gssic_step step, + krb5_gssic_finish finish, + krb5_gssic_release_cred release_cred, + krb5_gssic_delete_sec_context delete_sec_context, + const struct gss_cred_id_t_desc_struct *gss_cred, + const struct gss_OID_desc_struct *gss_mech, + unsigned int flags) +{ + krb5_gss_init_ctx gssic; + + gssic = calloc(1, sizeof(*gssic)); + if (gssic == NULL) + return krb5_enomem(context); + + if (ctx->gss_init_ctx) + free_gss_init_ctx(context, ctx->gss_init_ctx); + ctx->gss_init_ctx = gssic; + + gssic->cred = (struct gss_cred_id_t_desc_struct *)gss_cred; + gssic->mech = gss_mech; + if (flags & KRB5_GSS_IC_FLAG_RELEASE_CRED) + gssic->flags.release_cred = 1; + + gssic->step = step; + gssic->finish = finish; + gssic->release_cred = release_cred; + gssic->delete_sec_context = delete_sec_context; + + return 0; +} |