From acd672680307a3f1bad828e6573fa34bd7779203 Mon Sep 17 00:00:00 2001 From: Sam Hartman Date: Mon, 14 Dec 2009 18:28:16 +0000 Subject: [PATCH] fast negotiation projec Merge branches/fast-negotiate into trunk. This implements http://k5wiki.kerberos.org/wiki/Projects/Fast_negotiation Additional changes: * krb5_c_make_checksum with checksum type 0 uses mandatory checksum for given key enctype Conflicts: src/lib/crypto/krb/make_checksum.c ticket: 6595 Tags: enhancement git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@23465 dc483132-0cff-0310-8789-dd5450dbe970 --- src/clients/kinit/kinit.c | 28 +-- src/clients/klist/klist.c | 4 +- src/include/k5-int.h | 7 + src/include/krb5/krb5.hin | 61 +++++- src/kdc/do_as_req.c | 14 +- src/kdc/do_tgs_req.c | 34 ++- src/kdc/fast_util.c | 2 +- src/kdc/kdc_preauth.c | 31 ++- src/kdc/kdc_util.c | 61 ++++++ src/kdc/kdc_util.h | 13 +- src/lib/crypto/krb/make_checksum.c | 8 + src/lib/krb5/asn.1/asn1_k_decode.c | 1 + src/lib/krb5/asn.1/asn1_k_encode.c | 2 + src/lib/krb5/asn.1/krb5_decode.c | 10 + src/lib/krb5/ccache/ccapi/stdcc.c | 7 +- src/lib/krb5/ccache/ccfns.c | 159 ++++++++++++++ src/lib/krb5/krb/fast.c | 94 +++++++- src/lib/krb5/krb/fast.h | 14 ++ src/lib/krb5/krb/get_in_tkt.c | 334 ++++++++++++++++++++--------- src/lib/krb5/krb/gic_opt.c | 58 +++++ src/lib/krb5/krb/init_creds_ctx.h | 4 +- src/lib/krb5/libkrb5.exports | 7 + 22 files changed, 790 insertions(+), 163 deletions(-) diff --git a/src/clients/kinit/kinit.c b/src/clients/kinit/kinit.c index 96bb9cdcc..fdfae8828 100644 --- a/src/clients/kinit/kinit.c +++ b/src/clients/kinit/kinit.c @@ -629,6 +629,9 @@ k5_kinit(opts, k5) goto cleanup; } } + code = krb5_get_init_creds_opt_set_out_ccache(k5->ctx, options, k5->cc); + if (code) + goto cleanup; switch (opts->action) { case INIT_PW: @@ -678,20 +681,21 @@ k5_kinit(opts, k5) goto cleanup; } - code = krb5_cc_initialize(k5->ctx, k5->cc, - opts->canonicalize ? my_creds.client : k5->me); - if (code) { - com_err(progname, code, "when initializing cache %s", - opts->k5_cache_name?opts->k5_cache_name:""); - goto cleanup; - } + if ((opts->action != INIT_PW) && (opts->action != INIT_KT)) { + code = krb5_cc_initialize(k5->ctx, k5->cc, opts->canonicalize ? + my_creds.client : k5->me); + if (code) { + com_err(progname, code, "when initializing cache %s", + opts->k5_cache_name?opts->k5_cache_name:""); + goto cleanup; + } - code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds); - if (code) { - com_err(progname, code, "while storing credentials"); - goto cleanup; + code = krb5_cc_store_cred(k5->ctx, k5->cc, &my_creds); + if (code) { + com_err(progname, code, "while storing credentials"); + goto cleanup; + } } - notix = 0; cleanup: diff --git a/src/clients/klist/klist.c b/src/clients/klist/klist.c index 1a6309eb1..df4dbd533 100644 --- a/src/clients/klist/klist.c +++ b/src/clients/klist/klist.c @@ -284,7 +284,7 @@ void do_keytab(name) if (show_keys) { printf(" (0x"); { - int i; + unsigned int i; for (i = 0; i < entry.key.length; i++) printf("%02x", entry.key.contents[i]); } @@ -382,6 +382,8 @@ void do_ccache(name) exit(1); } while (!(code = krb5_cc_next_cred(kcontext, cache, &cur, &creds))) { + if (krb5_is_config_principal(kcontext, creds.server)) + continue; if (status_only) { if (exit_status && creds.server->length == 2 && strcmp(creds.server->realm.data, princ->realm.data) == 0 && diff --git a/src/include/k5-int.h b/src/include/k5-int.h index e0637e69d..f1b48c0af 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -257,6 +257,7 @@ typedef INT64_TYPE krb5_int64; #define KRB5_CONF_V4_INSTANCE_CONVERT "v4_instance_convert" #define KRB5_CONF_V4_REALM "v4_realm" #define KRB5_CONF_ASTERISK "*" +#define KRB5_CONF_FAST_AVAIL "fast_avail" /* Error codes used in KRB_ERROR protocol messages. Return values of library routines are based on a different error table @@ -1129,6 +1130,8 @@ typedef struct _krb5_gic_opt_private { int num_preauth_data; krb5_gic_opt_pa_data *preauth_data; char * fast_ccache_name; + krb5_ccache out_ccache; + krb5_flags fast_flags; } krb5_gic_opt_private; /* @@ -1623,6 +1626,8 @@ encode_krb5_enc_priv_part(const krb5_priv_enc_part *rep, krb5_data **code); krb5_error_code encode_krb5_cred(const krb5_cred *rep, krb5_data **code); +krb5_error_code +encode_krb5_checksum(const krb5_checksum *, krb5_data **); krb5_error_code encode_krb5_enc_cred_part(const krb5_cred_enc_part *rep, krb5_data **code); @@ -1851,6 +1856,8 @@ decode_krb5_priv(const krb5_data *output, krb5_priv **rep); krb5_error_code decode_krb5_enc_priv_part(const krb5_data *output, krb5_priv_enc_part **rep); +krb5_error_code +decode_krb5_checksum(const krb5_data *, krb5_checksum **); krb5_error_code decode_krb5_cred(const krb5_data *output, krb5_cred **rep); diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index 675917052..9e5c3853f 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -636,8 +636,8 @@ krb5_c_keyed_checksum_types(krb5_context context, krb5_enctype enctype, #define KRB5_KEYUSAGE_FAST_FINISHED 53 #define KRB5_KEYUSAGE_ENC_CHALLENGE_CLIENT 54 #define KRB5_KEYUSAGE_ENC_CHALLENGE_KDC 55 +#define KRB5_KEYUSAGE_AS_REQ 56 -#define KRB5_KEYUSAGE_FAST_REP 52 krb5_boolean KRB5_CALLCONV krb5_c_valid_enctype(krb5_enctype ktype); krb5_boolean KRB5_CALLCONV krb5_c_valid_cksumtype(krb5_cksumtype ctype); krb5_boolean KRB5_CALLCONV krb5_c_is_coll_proof_cksum(krb5_cksumtype ctype); @@ -930,7 +930,7 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype, #define TKT_FLG_TRANSIT_POLICY_CHECKED 0x00080000 #define TKT_FLG_OK_AS_DELEGATE 0x00040000 #define TKT_FLG_ANONYMOUS 0x00020000 -/* #define TKT_FLG_RESERVED 0x00010000 */ +#define TKT_FLG_ENC_PA_REP 0x00010000 /* #define TKT_FLG_RESERVED 0x00008000 */ /* #define TKT_FLG_RESERVED 0x00004000 */ /* #define TKT_FLG_RESERVED 0x00002000 */ @@ -1033,6 +1033,7 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype, #define KRB5_PADATA_FX_FAST 136 #define KRB5_PADATA_FX_ERROR 137 #define KRB5_PADATA_ENCRYPTED_CHALLENGE 138 +#define KRB5_ENCPADATA_REQ_ENC_PA_REP 149 #define KRB5_SAM_USE_SAD_AS_KEY 0x80000000 #define KRB5_SAM_SEND_ENCRYPTED_SAD 0x40000000 @@ -1835,6 +1836,20 @@ krb5_cc_default(krb5_context, krb5_ccache *); krb5_error_code KRB5_CALLCONV krb5_cc_copy_creds(krb5_context context, krb5_ccache incc, krb5_ccache outcc); +krb5_error_code KRB5_CALLCONV +krb5_cc_get_config(krb5_context, krb5_ccache, + krb5_const_principal, + const char *, krb5_data *); + +krb5_error_code KRB5_CALLCONV +krb5_cc_set_config(krb5_context, krb5_ccache, + krb5_const_principal, + const char *, krb5_data *); + +krb5_boolean KRB5_CALLCONV +krb5_is_config_principal(krb5_context, + krb5_const_principal); + /* krb5_free.c */ void KRB5_CALLCONV krb5_free_principal(krb5_context, krb5_principal ); void KRB5_CALLCONV krb5_free_authenticator(krb5_context, @@ -2255,17 +2270,47 @@ krb5_get_init_creds_opt_set_pa(krb5_context context, krb5_get_init_creds_opt *opt, const char *attr, const char *value); +/** + * This API sets a ccache name that will contain some TGT on calls to + * t_init_creds functions. If set, this ccache will be used for FAST + * (draft-ietf-krb-wg-preauth-framework) to protect the AS-REQ from observation + * and active attack. If the fast_ccache_name is set, then FAST may be + * required by the client library. In this and future versions, FAST will be + * used if available; krb5_get_init_creds_opt_set_fast_flags() may be used to + * require that the request fail is FAST is unavailable. In MIT Kerberos 1.7 + * setting the fast ccache at all required that FAST be present or the request + * would fail. + */ krb5_error_code KRB5_CALLCONV krb5_get_init_creds_opt_set_fast_ccache_name(krb5_context context, krb5_get_init_creds_opt *opt, const char *fast_ccache_name); -/* This API sets a ccache name that will contain some TGT on - calls to get_init_creds functions. If set, this ccache will - be used for FAST (draft-ietf-krb-wg-preauth-framework) to - protect the AS-REQ from observation and active attack. If - the fast_ccache_name is set, then FAST may be required by the - client library. In this version FAST is required.*/ +/** + * Set a ccache where resulting credentials will be stored. If set, then the + * krb5_get_init_creds family of APIs will write out credentials to the given + * ccache. Setting an output ccache is desirable both because it simplifies + * calling code and because it permits the krb5_get_init_creds APIs to write + * out configuration information about the realm to the ccache. + */ +krb5_error_code KRB5_CALLCONV +krb5_get_init_creds_opt_set_out_ccache(krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_ccache ccache); + +krb5_error_code KRB5_CALLCONV +krb5_get_init_creds_opt_set_fast_flags(krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_flags flags); + +krb5_error_code KRB5_CALLCONV +krb5_get_init_creds_opt_get_fast_flags(krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_flags *out_flags); + +/* Fast flags*/ +#define KRB5_FAST_REQUIRED 1l<<0 /*!< Require KDC to support FAST*/ + krb5_error_code KRB5_CALLCONV krb5_get_init_creds_password(krb5_context context, krb5_creds *creds, krb5_principal client, char *password, diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c index 45ae49633..23f1ddcb8 100644 --- a/src/kdc/do_as_req.c +++ b/src/kdc/do_as_req.c @@ -310,6 +310,7 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, enc_tkt_reply.times.authtime = authtime; setflag(enc_tkt_reply.flags, TKT_FLG_INITIAL); + setflag(enc_tkt_reply.flags, TKT_FLG_ENC_PA_REP); /* * It should be noted that local policy may affect the @@ -556,12 +557,6 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, reply.client->realm.data, reply.client->data->data); #endif /* APPLE_PKINIT */ - errcode = return_svr_referral_data(kdc_context, - &server, &reply_encpart); - if (errcode) { - status = "KDC_RETURN_ENC_PADATA"; - goto errout; - } errcode = handle_authdata(kdc_context, @@ -607,6 +602,13 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, status = "generating reply key"; goto errout; } + errcode = return_enc_padata(kdc_context, req_pkt, request, + as_encrypting_key, &server, &reply_encpart); + if (errcode) { + status = "KDC_RETURN_ENC_PADATA"; + goto errout; + } + errcode = krb5_encode_kdc_rep(kdc_context, KRB5_AS_REP, &reply_encpart, 0, as_encrypting_key, &reply, response); reply.enc_part.kvno = client_key->key_data_kvno; diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c index 778a3e87c..75d413250 100644 --- a/src/kdc/do_tgs_req.c +++ b/src/kdc/do_tgs_req.c @@ -454,6 +454,7 @@ tgt_again: */ if (!(header_enc_tkt->times.starttime)) header_enc_tkt->times.starttime = authtime; + setflag(enc_tkt_reply.flags, TKT_FLG_ENC_PA_REP); /* don't use new addresses unless forwarded, see below */ @@ -755,14 +756,6 @@ tgt_again: goto cleanup; } - if (is_referral && isflagset(s_flags, KRB5_KDB_FLAG_CANONICALIZE)) { - errcode = return_svr_referral_data(kdc_context, - &server, &reply_encpart); - if (errcode) { - status = "KDC_RETURN_ENC_PADATA"; - goto cleanup; - } - } /* * Only add the realm of the presented tgt to the transited list if @@ -954,6 +947,31 @@ tgt_again: status = "generating reply key"; goto cleanup; } + if (is_referral && isflagset(s_flags, KRB5_KDB_FLAG_CANONICALIZE)) { + int idx = 0; + + errcode = return_enc_padata(kdc_context, pkt, request, + reply_key, &server, &reply_encpart); + if (errcode) { + status = "KDC_RETURN_ENC_PADATA"; + goto cleanup; + } + /* Not referral. */ + reply_encpart.enc_padata = calloc(3, sizeof(krb5_pa_data *)); + if (reply_encpart.enc_padata == NULL) { + errcode = ENOMEM; + status = "Allocating enc_padata"; + goto cleanup; + } + errcode = kdc_handle_protected_negotiation(pkt, request, reply_key, + reply_encpart.enc_padata, + &idx); + if (errcode != 0) { + status = "protected negotiation"; + goto cleanup; + } + } + errcode = krb5_encode_kdc_rep(kdc_context, KRB5_TGS_REP, &reply_encpart, subkey ? 1 : 0, reply_key, diff --git a/src/kdc/fast_util.c b/src/kdc/fast_util.c index a47c512d4..e6503cf8a 100644 --- a/src/kdc/fast_util.c +++ b/src/kdc/fast_util.c @@ -128,7 +128,7 @@ kdc_find_fast(krb5_kdc_req **requestptr, struct kdc_request_state *state) { krb5_error_code retval = 0; - krb5_pa_data *fast_padata, *cookie_padata; + krb5_pa_data *fast_padata, *cookie_padata = NULL; krb5_data scratch; krb5_fast_req * fast_req = NULL; krb5_kdc_req *request = *requestptr; diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c index 05b109b4e..2262c8956 100644 --- a/src/kdc/kdc_preauth.c +++ b/src/kdc/kdc_preauth.c @@ -3064,27 +3064,29 @@ include_pac_p(krb5_context context, krb5_kdc_req *request) } krb5_error_code -return_svr_referral_data(krb5_context context, - krb5_db_entry *server, - krb5_enc_kdc_rep_part *reply_encpart) +return_enc_padata(krb5_context context, krb5_data *req_pkt, + krb5_kdc_req *request, krb5_keyblock *reply_key, + krb5_db_entry *server, krb5_enc_kdc_rep_part *reply_encpart) { krb5_error_code code; krb5_tl_data tl_data; krb5_pa_data *pa_data; + int idx = 0; - /* This should be initialized and only used for Win2K compat */ + /* This should be initialized and only used for Win2K compat and other + * specific standardized uses such as FAST negotiation. */ assert(reply_encpart->enc_padata == NULL); - + reply_encpart->enc_padata = calloc(4, sizeof(krb5_pa_data *)); + if (reply_encpart->enc_padata == NULL) + return ENOMEM; tl_data.tl_data_type = KRB5_TL_SVR_REFERRAL_DATA; - code = krb5_dbe_lookup_tl_data(context, server, &tl_data); if (code || tl_data.tl_data_length == 0) - return 0; /* no server referrals to return */ + goto negotiate; /* no server referrals to return */ pa_data = (krb5_pa_data *)malloc(sizeof(*pa_data)); if (pa_data == NULL) return ENOMEM; - pa_data->magic = KV5M_PA_DATA; pa_data->pa_type = KRB5_PADATA_SVR_REFERRAL_INFO; pa_data->length = tl_data.tl_data_length; @@ -3095,17 +3097,12 @@ return_svr_referral_data(krb5_context context, } memcpy(pa_data->contents, tl_data.tl_data_contents, tl_data.tl_data_length); - reply_encpart->enc_padata = (krb5_pa_data **)calloc(2, sizeof(krb5_pa_data *)); - if (reply_encpart->enc_padata == NULL) { - free(pa_data->contents); - free(pa_data); - return ENOMEM; - } - reply_encpart->enc_padata[0] = pa_data; + reply_encpart->enc_padata[idx++] = pa_data; reply_encpart->enc_padata[1] = NULL; - - return 0; +negotiate: + return kdc_handle_protected_negotiation(req_pkt, request, reply_key, + reply_encpart->enc_padata, &idx); } #if 0 diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c index 39c6be600..475265e71 100644 --- a/src/kdc/kdc_util.c +++ b/src/kdc/kdc_util.c @@ -2651,3 +2651,64 @@ kdc_get_ticket_endtime(krb5_context context, *out_endtime = starttime + life; } + +/** + * Handle protected negotiation of FAST using enc_padata + * - If ENCPADATA_REQ_ENC_PA_REP is present, then: + * - Return ENCPADATA_REQ_ENC_PA_REP with checksum of AS-REQ from client + * - Include PADATA_FX_FAST in the enc_padata to indicate FAST + * @pre @c out_enc_padata has space for at least two more padata + * @param index in/out index into @c out_enc_padata for next item + */ +krb5_error_code +kdc_handle_protected_negotiation(krb5_data *req_pkt, krb5_kdc_req *request, + const krb5_keyblock *reply_key, + krb5_pa_data **out_enc_padata, int *idx) +{ + krb5_error_code retval = 0; + krb5_checksum checksum; + krb5_data *out = NULL; + krb5_pa_data *pa; + assert(out_enc_padata != NULL); + pa = krb5int_find_pa_data(kdc_context, request->padata, + KRB5_ENCPADATA_REQ_ENC_PA_REP); + if (pa == NULL) + return 0; + checksum.contents = NULL; + pa = malloc(sizeof(krb5_pa_data)); + if (pa == NULL) + return ENOMEM; + pa->magic = KV5M_PA_DATA; + pa->pa_type = KRB5_ENCPADATA_REQ_ENC_PA_REP; + retval = krb5_c_make_checksum(kdc_context,0, reply_key, + KRB5_KEYUSAGE_AS_REQ, req_pkt, &checksum); + if (retval != 0) + goto cleanup; + retval = encode_krb5_checksum(&checksum, &out); + if (retval != 0) + goto cleanup; + pa->contents = (krb5_octet *) out->data; + pa->length = out->length; + out_enc_padata[(*idx)++] = pa; + pa = NULL; + out->data = NULL; + pa = malloc(sizeof(krb5_pa_data)); + if (pa == NULL) { + retval = ENOMEM; + goto cleanup; + } + pa->magic = KV5M_PA_DATA; + pa->pa_type = KRB5_PADATA_FX_FAST; + pa->length = 0; + pa->contents = NULL; + out_enc_padata[(*idx)++] = pa; + pa = NULL; +cleanup: + if (checksum.contents) + krb5_free_checksum_contents(kdc_context, &checksum); + if (out != NULL) + krb5_free_data(kdc_context, out); + if (pa != NULL) + free(pa); + return retval; +} diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h index a2347208c..353bbfc5d 100644 --- a/src/kdc/kdc_util.h +++ b/src/kdc/kdc_util.h @@ -250,9 +250,11 @@ krb5_boolean include_pac_p(krb5_context context, krb5_kdc_req *request); krb5_error_code -return_svr_referral_data (krb5_context context, - krb5_db_entry *server, - krb5_enc_kdc_rep_part *reply_encpart); +return_enc_padata(krb5_context context, + krb5_data *req_pkt, krb5_kdc_req *request, + krb5_keyblock *reply_key, + krb5_db_entry *server, + krb5_enc_kdc_rep_part *reply_encpart); krb5_error_code sign_db_authdata (krb5_context context, @@ -392,7 +394,10 @@ krb5_error_code kdc_fast_handle_reply_key(struct kdc_request_state *state, krb5_error_code kdc_preauth_get_cookie(struct kdc_request_state *state, krb5_pa_data **cookie); - +krb5_error_code +kdc_handle_protected_negotiation( krb5_data *req_pkt, krb5_kdc_req *request, + const krb5_keyblock *reply_key, + krb5_pa_data **out_enc_padata, int *idx); diff --git a/src/lib/crypto/krb/make_checksum.c b/src/lib/crypto/krb/make_checksum.c index aa4545780..d0dc62237 100644 --- a/src/lib/crypto/krb/make_checksum.c +++ b/src/lib/crypto/krb/make_checksum.c @@ -30,6 +30,8 @@ #include "etypes.h" #include "dk.h" +/* A 0 checksum type means use the mandatory checksum. */ + krb5_error_code KRB5_CALLCONV krb5_k_make_checksum(krb5_context context, krb5_cksumtype cksumtype, krb5_key key, krb5_keyusage usage, @@ -41,6 +43,12 @@ krb5_k_make_checksum(krb5_context context, krb5_cksumtype cksumtype, krb5_octet *trunc; krb5_error_code ret; + if (cksumtype == 0) { + ret = krb5int_c_mandatory_cksumtype(context, key->keyblock.enctype, + &cksumtype); + if (ret != 0) + return ret; + } ctp = find_cksumtype(cksumtype); if (ctp == NULL) return KRB5_BAD_ENCTYPE; diff --git a/src/lib/krb5/asn.1/asn1_k_decode.c b/src/lib/krb5/asn.1/asn1_k_decode.c index 0257a8b0c..60d9455a1 100644 --- a/src/lib/krb5/asn.1/asn1_k_decode.c +++ b/src/lib/krb5/asn.1/asn1_k_decode.c @@ -658,6 +658,7 @@ asn1_decode_enc_kdc_rep_part(asn1buf *buf, krb5_enc_kdc_rep_part *val) val->last_req = NULL; val->server = NULL; val->caddrs = NULL; + val->enc_padata = NULL; { begin_structure(); get_field(val->session,0,asn1_decode_encryption_key_ptr); get_field(val->last_req,1,asn1_decode_last_req); diff --git a/src/lib/krb5/asn.1/asn1_k_encode.c b/src/lib/krb5/asn.1/asn1_k_encode.c index 1cd6c8c24..a35f561e6 100644 --- a/src/lib/krb5/asn.1/asn1_k_encode.c +++ b/src/lib/krb5/asn.1/asn1_k_encode.c @@ -1424,6 +1424,8 @@ MAKE_FULL_ENCODER(encode_krb5_safe_with_body, krb5_safe_with_body); MAKE_FULL_ENCODER(encode_krb5_priv, krb5_priv); MAKE_FULL_ENCODER(encode_krb5_enc_priv_part, priv_enc_part); +MAKE_FULL_ENCODER(encode_krb5_checksum, checksum); + MAKE_FULL_ENCODER(encode_krb5_cred, krb5_cred); MAKE_FULL_ENCODER(encode_krb5_enc_cred_part, enc_cred_part); MAKE_FULL_ENCODER(encode_krb5_error, krb5_error); diff --git a/src/lib/krb5/asn.1/krb5_decode.c b/src/lib/krb5/asn.1/krb5_decode.c index e255551f5..542a626da 100644 --- a/src/lib/krb5/asn.1/krb5_decode.c +++ b/src/lib/krb5/asn.1/krb5_decode.c @@ -702,6 +702,16 @@ error_out: return retval; } +krb5_error_code +decode_krb5_checksum(const krb5_data *code, krb5_checksum **repptr) +{ + setup_buf_only(krb5_checksum *); + alloc_field(rep); + retval = asn1_decode_checksum(&buf, rep); + if (retval) clean_return(retval); + cleanup(free); +} + krb5_error_code decode_krb5_cred(const krb5_data *code, krb5_cred **repptr) { diff --git a/src/lib/krb5/ccache/ccapi/stdcc.c b/src/lib/krb5/ccache/ccapi/stdcc.c index 33fb97c76..de2bc9d32 100644 --- a/src/lib/krb5/ccache/ccapi/stdcc.c +++ b/src/lib/krb5/ccache/ccapi/stdcc.c @@ -805,7 +805,7 @@ krb5_stdccv3_get_flags (krb5_context context, krb5_error_code KRB5_CALLCONV krb5_stdccv3_remove (krb5_context context, krb5_ccache id, - krb5_flags flags, + krb5_flags whichfields, krb5_creds *in_creds) { krb5_error_code err = 0; @@ -836,7 +836,10 @@ krb5_stdccv3_remove (krb5_context context, credentials->data, &creds); if (!err) { - found = krb5_creds_compare (context, in_creds, &creds); + found = krb5int_cc_creds_match_request(context, + whichfields, + in_creds, + &creds); krb5_free_cred_contents (context, &creds); } diff --git a/src/lib/krb5/ccache/ccfns.c b/src/lib/krb5/ccache/ccfns.c index e12dd563f..f08bfb98d 100644 --- a/src/lib/krb5/ccache/ccfns.c +++ b/src/lib/krb5/ccache/ccfns.c @@ -191,3 +191,162 @@ krb5_cc_unlock (krb5_context context, krb5_ccache ccache) { return ccache->ops->unlock(context, ccache); } + +static const char conf_realm[] = "X-CACHECONF:"; +static const char conf_name[] = "krb5_ccache_conf_data"; + +static krb5_error_code +build_conf_principals (krb5_context context, krb5_ccache id, + krb5_const_principal principal, + const char *name, krb5_creds *cred) +{ + krb5_principal client; + krb5_error_code ret; + char *pname = NULL; + + memset(cred, 0, sizeof(*cred)); + + ret = krb5_cc_get_principal(context, id, &client); + if (ret) + return ret; + + if (principal) { + ret = krb5_unparse_name(context, principal, &pname); + if (ret) + return ret; + } + + ret = krb5_build_principal(context, &cred->server, + sizeof(conf_realm) - 1, conf_realm, + conf_name, name, pname, (char *)NULL); + free(pname); + if (ret) { + krb5_free_principal(context, client); + return ret; + } + ret = krb5_copy_principal(context, client, &cred->client); + krb5_free_principal(context, client); + return ret; +} + +/*! + * \param context a Keberos context + * \param principal principal to check if it a configuration principal + * + * \brief Return TRUE (non zero) if the principal is a configuration + * principal (generated part of krb5_cc_set_config()). Returns + * FALSE (zero) if not a configuration principal. + * + */ + +krb5_boolean KRB5_CALLCONV +krb5_is_config_principal (krb5_context context, + krb5_const_principal principal) +{ + const krb5_data *realm; + + realm = krb5_princ_realm(context, principal); + + if (realm->length != sizeof(conf_realm) - 1 || + memcmp(realm->data, conf_realm, sizeof(conf_realm) - 1) != 0) + return FALSE; + + if (principal->length == 0 || + principal->data[0].length != (sizeof(conf_name) - 1) || + memcmp(principal->data[0].data, conf_name, sizeof(conf_name) - 1) != 0) + return FALSE; + + return TRUE; +} + +/*! + * \param context a Keberos context + * \param id the credential cache to store the data for + * \param principal configuration for a specific principal, if + * NULL, global for the whole cache. + * \param key name under which the configuraion is stored. + * \param data data to store + * + * \brief Store some configuration for the credential cache in the + * cache. Existing configuration under the same key is + * over-written. + * + */ + +krb5_error_code KRB5_CALLCONV +krb5_cc_set_config (krb5_context context, krb5_ccache id, + krb5_const_principal principal, + const char *key, krb5_data *data) +{ + krb5_error_code ret; + krb5_creds cred; + memset(&cred, 0, sizeof(cred)); + + ret = build_conf_principals(context, id, principal, key, &cred); + if (ret) + goto out; + + ret = krb5_cc_remove_cred(context, id, 0, &cred); + if (ret && ret != KRB5_CC_NOTFOUND && ret != KRB5_CC_NOSUPP) + goto out; + + cred.ticket.data = malloc(data->length); + if (cred.ticket.data == NULL) { + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + return ENOMEM; + } + cred.ticket.length = data->length; + memcpy(cred.ticket.data, data->data, data->length); + + ret = krb5_cc_store_cred(context, id, &cred); + +out: + krb5_free_cred_contents(context, &cred); + return ret; +} + +/*! + * \param context a Keberos context + * \param id the credential cache to store the data for + * \param principal configuration for a specific principal, if + * NULL, global for the whole cache. + * \param key name under which the configuraion is stored. + * \param data data to fetched, free with krb5_data_free() + * + * \brief Get some configuration for the credential cache in the cache. + */ + + +krb5_error_code KRB5_CALLCONV +krb5_cc_get_config (krb5_context context, krb5_ccache id, + krb5_const_principal principal, + const char *key, krb5_data *data) +{ + krb5_creds mcred, cred; + krb5_error_code ret; + + memset(&cred, 0, sizeof(cred)); + memset(data, 0, sizeof(*data)); + + ret = build_conf_principals(context, id, principal, key, &mcred); + if (ret) + goto out; + + ret = krb5_cc_retrieve_cred(context, id, 0, &mcred, &cred); + if (ret) + goto out; + + data->data = malloc(cred.ticket.length); + if (data->data == NULL) { + ret = ENOMEM; + krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); + goto out; + } + data->length = cred.ticket.length; + memcpy(data->data, cred.ticket.data, data->length); + +out: + krb5_free_cred_contents(context, &cred); + krb5_free_cred_contents(context, &mcred); + return ret; +} diff --git a/src/lib/krb5/krb/fast.c b/src/lib/krb5/krb/fast.c index 884a4ed82..279165124 100644 --- a/src/lib/krb5/krb/fast.c +++ b/src/lib/krb5/krb/fast.c @@ -56,7 +56,7 @@ static krb5_error_code fast_armor_ap_request(krb5_context context, struct krb5int_fast_request_state *state, - krb5_ccache ccache, krb5_data *target_realm) + krb5_ccache ccache, krb5_principal target_principal) { krb5_error_code retval = 0; krb5_creds creds, *out_creds = NULL; @@ -66,9 +66,8 @@ fast_armor_ap_request(krb5_context context, krb5_keyblock *subkey = NULL, *armor_key = NULL; encoded_authenticator.data = NULL; memset(&creds, 0, sizeof(creds)); - retval = krb5_tgtname(context, target_realm, target_realm, &creds.server); - if (retval ==0) - retval = krb5_cc_get_principal(context, ccache, &creds.client); + creds.server = target_principal; + retval = krb5_cc_get_principal(context, ccache, &creds.client); if (retval == 0) retval = krb5_get_credentials(context, 0, ccache, &creds, &out_creds); if (retval == 0) @@ -98,6 +97,8 @@ fast_armor_ap_request(krb5_context context, krb5_free_keyblock(context, subkey); if (out_creds) krb5_free_creds(context, out_creds); + /* target_principal is owned by caller. */ + creds.server = NULL; krb5_free_cred_contents(context, &creds); if (encoded_authenticator.data) krb5_free_data_contents(context, &encoded_authenticator); @@ -138,13 +139,34 @@ krb5int_fast_as_armor(krb5_context context, { krb5_error_code retval = 0; krb5_ccache ccache = NULL; + krb5_principal target_principal = NULL; + krb5_data *target_realm; krb5_clear_error_message(context); + target_realm = krb5_princ_realm(context, request->server); if (opte->opt_private->fast_ccache_name) { + state->fast_state_flags |= KRB5INT_FAST_ARMOR_AVAIL; retval = krb5_cc_resolve(context, opte->opt_private->fast_ccache_name, &ccache); - if (retval==0) + if (retval == 0) { + retval = krb5_tgtname(context, target_realm, target_realm, + &target_principal); + } + if (retval == 0) { + krb5_data config_data; + config_data.data = NULL; + retval = krb5_cc_get_config(context, ccache, target_principal, + KRB5_CONF_FAST_AVAIL, &config_data); + if ((retval == 0) && config_data.data ) + state->fast_state_flags |= KRB5INT_FAST_DO_FAST; + krb5_free_data_contents(context, &config_data); + retval = 0; + } + if (opte->opt_private->fast_flags& KRB5_FAST_REQUIRED) + state->fast_state_flags |= KRB5INT_FAST_DO_FAST; + if (retval == 0 && (state->fast_state_flags & KRB5INT_FAST_DO_FAST)) { retval = fast_armor_ap_request(context, state, ccache, - krb5_princ_realm(context, request->server)); + target_principal); + } if (retval != 0) { const char * errmsg; errmsg = krb5_get_error_message(context, retval); @@ -156,6 +178,8 @@ krb5int_fast_as_armor(krb5_context context, } if (ccache) krb5_cc_close(context, ccache); + if (target_principal) + krb5_free_principal(context, target_principal); return retval; } @@ -528,3 +552,61 @@ krb5int_find_pa_data(krb5_context context, krb5_pa_data *const *padata, return *tmppa; } + + +krb5_error_code +krb5int_fast_verify_nego(krb5_context context, + struct krb5int_fast_request_state *state, + krb5_kdc_rep *rep, krb5_data *request, + krb5_keyblock *decrypting_key, + krb5_boolean *fast_avail) +{ + krb5_error_code retval = 0; + krb5_checksum *checksum = NULL; + krb5_pa_data *pa; + krb5_data scratch; + krb5_boolean valid; + + if (rep->enc_part2->flags& TKT_FLG_ENC_PA_REP) { + pa = krb5int_find_pa_data(context, rep->enc_part2->enc_padata, + KRB5_ENCPADATA_REQ_ENC_PA_REP); + if (pa == NULL) + retval = KRB5_KDCREP_MODIFIED; + else { + scratch.data = (char *) pa->contents; + scratch.length = pa->length; + } + if (retval == 0) + retval = decode_krb5_checksum(&scratch, &checksum); + if (retval == 0) + retval = krb5_c_verify_checksum(context, decrypting_key, + KRB5_KEYUSAGE_AS_REQ, + request, checksum, &valid); + if (retval == 0 &&valid == 0) + retval = KRB5_KDCREP_MODIFIED; + if (retval == 0) { + pa = krb5int_find_pa_data(context, rep->enc_part2->enc_padata, + KRB5_PADATA_FX_FAST); + *fast_avail = (pa != NULL); + } + } + if (checksum) + krb5_free_checksum(context, checksum); + return retval; +} + +krb5_boolean +krb5int_upgrade_to_fast_p(krb5_context context, + struct krb5int_fast_request_state *state, + krb5_pa_data **padata) +{ + if (state->armor_key != NULL) + return 0; /*already using FAST*/ + if (!(state->fast_state_flags & KRB5INT_FAST_ARMOR_AVAIL)) + return 0; + if (krb5int_find_pa_data(context, padata, KRB5_PADATA_FX_FAST) != NULL) { + state->fast_state_flags |= KRB5INT_FAST_DO_FAST; + return 1; + } + return 0; +} diff --git a/src/lib/krb5/krb/fast.h b/src/lib/krb5/krb/fast.h index 443f3e196..66dc98523 100644 --- a/src/lib/krb5/krb/fast.h +++ b/src/lib/krb5/krb/fast.h @@ -42,6 +42,9 @@ struct krb5int_fast_request_state { krb5_int32 nonce; }; +#define KRB5INT_FAST_DO_FAST (1l<<0) /* Perform FAST */ +#define KRB5INT_FAST_ARMOR_AVAIL (1l<<1) + krb5_error_code krb5int_fast_prep_req_body(krb5_context context, struct krb5int_fast_request_state *state, krb5_kdc_req *request, krb5_data **encoded_req_body); @@ -79,5 +82,16 @@ krb5_error_code krb5int_fast_reply_key(krb5_context context, krb5_keyblock *output_key); +krb5_error_code +krb5int_fast_verify_nego(krb5_context context, + struct krb5int_fast_request_state *state, + krb5_kdc_rep *rep, krb5_data *request, + krb5_keyblock *decrypting_key, + krb5_boolean *fast_avail); + +krb5_boolean +krb5int_upgrade_to_fast_p(krb5_context context, + struct krb5int_fast_request_state *state, + krb5_pa_data **padata); #endif diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index 472801588..b13c9a94c 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -545,6 +545,30 @@ tgt_is_local_realm(krb5_creds *tgt) && data_eq(tgt->server->realm, tgt->client->realm)); } +static krb5_error_code +request_enc_pa_rep(krb5_pa_data ***padptr) +{ + size_t size = 0; + krb5_pa_data **pad = *padptr; + krb5_pa_data *pa= NULL; + if (pad) + for (size=0; pad[size]; size++); + pad = realloc(pad, sizeof(*pad)*(size+2)); + + if (pad == NULL) + return ENOMEM; + pad[size+1] = NULL; + pa = malloc(sizeof(krb5_pa_data)); + if (pa == NULL) + return ENOMEM; + pa->contents = NULL; + pa->length = 0; + pa->pa_type = KRB5_ENCPADATA_REQ_ENC_PA_REP; + pad[size] = pa; + *padptr = pad; + return 0; +} + krb5_error_code KRB5_CALLCONV krb5_get_in_tkt(krb5_context context, krb5_flags options, @@ -1234,6 +1258,110 @@ cleanup: return code; } +/** + * Throw away any state related to specific realm either at the beginning of a + * request, or when a realm changes, or when we start to use FAST after + * assuming we would not do so. + * + * @param padata padata from an error if an error from the realm we now expect + * to talk to caused the restart. Used to infer negotiation characteristics + * such as whether FAST is used. + */ +static krb5_error_code +restart_init_creds_loop(krb5_context context, krb5_init_creds_context ctx, + krb5_pa_data **padata) +{ + krb5_error_code code = 0; + unsigned char random_buf[4]; + krb5_data random_data; + if (ctx->preauth_to_use) { + krb5_free_pa_data(context, ctx->preauth_to_use); + ctx->preauth_to_use = NULL; + } + + if (ctx->fast_state) { + krb5int_fast_free_state(context, ctx->fast_state); + ctx->fast_state = NULL; + } + code = krb5int_fast_make_state(context, &ctx->fast_state); + if (code != 0) + goto cleanup; + ctx->get_data_rock.fast_state = ctx->fast_state; + krb5_preauth_request_context_init(context); + if (ctx->encoded_request_body) { + krb5_free_data(context, ctx->encoded_request_body); + ctx->encoded_request_body = NULL; + } + if (ctx->opte && + (ctx->opte->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) { + if ((code = make_preauth_list(context, ctx->opte->preauth_list, + ctx->opte->preauth_list_length, + &ctx->preauth_to_use))) + goto cleanup; + } + + /* Set the request nonce. */ + random_data.length = 4; + random_data.data = (char *)random_buf; + code = krb5_c_random_make_octets(context, &random_data); + if (code !=0) + goto cleanup; + /* + * See RT ticket 3196 at MIT. If we set the high bit, we may have + * compatibility problems with Heimdal, because we (incorrectly) encode + * this value as signed. + */ + ctx->request->nonce = 0x7fffffff & load_32_n(random_buf); + krb5_free_principal(context, ctx->request->server); + ctx->request->server = NULL; + + code = build_in_tkt_name(context, ctx->in_tkt_service, + ctx->request->client, + &ctx->request->server); + if (code != 0) + goto cleanup; + + code = krb5_timeofday(context, &ctx->request_time); + if (code != 0) + goto cleanup; + + code = krb5int_fast_as_armor(context, ctx->fast_state, + ctx->opte, ctx->request); + if (code != 0) + goto cleanup; + if (krb5int_upgrade_to_fast_p(context, ctx->fast_state, padata)) { + code = krb5int_fast_as_armor(context, ctx->fast_state, + ctx->opte, ctx->request); + if (code != 0) + goto cleanup; + } + /* give the preauth plugins a chance to prep the request body */ + krb5_preauth_prepare_request(context, ctx->opte, ctx->request); + + ctx->request->from = krb5int_addint32(ctx->request_time, + ctx->start_time); + ctx->request->till = krb5int_addint32(ctx->request->from, + ctx->tkt_life); + + if (ctx->renew_life > 0) { + ctx->request->rtime = + krb5int_addint32(ctx->request->from, ctx->renew_life); + if (ctx->request->rtime < ctx->request->till) { + /* don't ask for a smaller renewable time than the lifetime */ + ctx->request->rtime = ctx->request->till; + } + ctx->request->kdc_options &= ~(KDC_OPT_RENEWABLE_OK); + } else + ctx->request->rtime = 0; + code = krb5int_fast_prep_req_body(context, ctx->fast_state, + ctx->request, + &ctx->encoded_request_body); + if (code != 0) + goto cleanup; +cleanup: + return code; +} + krb5_error_code KRB5_CALLCONV krb5_init_creds_init(krb5_context context, krb5_principal client, @@ -1256,7 +1384,7 @@ krb5_init_creds_init(krb5_context context, ctx->request = k5alloc(sizeof(krb5_kdc_req), &code); if (code != 0) goto cleanup; - + ctx->enc_pa_rep_permitted = 1; code = krb5_copy_principal(context, client, &ctx->request->client); if (code != 0) goto cleanup; @@ -1282,13 +1410,8 @@ krb5_init_creds_init(krb5_context context, opte = ctx->opte; - code = krb5int_fast_make_state(context, &ctx->fast_state); - if (code != 0) - goto cleanup; - ctx->get_data_rock.magic = CLIENT_ROCK_MAGIC; ctx->get_data_rock.etype = &ctx->etype; - ctx->get_data_rock.fast_state = ctx->fast_state; /* Initialise request parameters as per krb5_get_init_creds() */ ctx->request->kdc_options = context->kdc_default_options; @@ -1388,8 +1511,8 @@ krb5_init_creds_init(krb5_context context, if (code != 0) goto cleanup; } else if (krb5_libdefault_boolean(context, &ctx->request->client->realm, - KRB5_CONF_NOADDRESSES, &tmp) != 0 - || tmp) { + KRB5_CONF_NOADDRESSES, &tmp) != 0 + || tmp) { ctx->request->addresses = NULL; } else { code = krb5_os_localaddr(context, &ctx->request->addresses); @@ -1397,18 +1520,6 @@ krb5_init_creds_init(krb5_context context, goto cleanup; } - /* initial preauth state */ - krb5_preauth_request_context_init(context); - - if (opte->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST) { - code = make_preauth_list(context, - opte->preauth_list, - opte->preauth_list_length, - &ctx->preauth_to_use); - if (code != 0) - goto cleanup; - } - if (opte->flags & KRB5_GET_INIT_CREDS_OPT_SALT) { code = krb5int_copy_data_contents(context, opte->salt, &ctx->salt); if (code != 0) @@ -1418,33 +1529,7 @@ krb5_init_creds_init(krb5_context context, ctx->salt.data = NULL; } - /* nonce */ - { - unsigned char random_buf[4]; - krb5_data random_data; - - random_data.length = sizeof(random_buf); - random_data.data = (char *)random_buf; - - /* - * See RT ticket 3196 at MIT. If we set the high bit, we - * may have compatibility problems with Heimdal, because - * we (incorrectly) encode this value as signed. - */ - if (krb5_c_random_make_octets(context, &random_data) == 0) - ctx->request->nonce = 0x7fffffff & load_32_n(random_buf); - else { - krb5_timestamp now; - - code = krb5_timeofday(context, &now); - if (code != 0) - goto cleanup; - - ctx->request->nonce = (krb5_int32)now; - } - } - - ctx->loopcount = 0; + code = restart_init_creds_loop(context, ctx, NULL); *pctx = ctx; @@ -1471,7 +1556,8 @@ krb5_init_creds_set_service(krb5_context context, free(ctx->in_tkt_service); ctx->in_tkt_service = s; - return 0; + krb5_preauth_request_context_fini(context); + return restart_init_creds_loop(context, ctx, NULL); } static krb5_error_code @@ -1552,49 +1638,7 @@ init_creds_step_request(krb5_context context, { krb5_error_code code; - krb5_free_principal(context, ctx->request->server); - ctx->request->server = NULL; - - code = build_in_tkt_name(context, ctx->in_tkt_service, - ctx->request->client, - &ctx->request->server); - if (code != 0) - goto cleanup; - - if (ctx->loopcount == 0) { - code = krb5_timeofday(context, &ctx->request_time); - if (code != 0) - goto cleanup; - - code = krb5int_fast_as_armor(context, ctx->fast_state, - ctx->opte, ctx->request); - if (code != 0) - goto cleanup; - - /* give the preauth plugins a chance to prep the request body */ - krb5_preauth_prepare_request(context, ctx->opte, ctx->request); - code = krb5int_fast_prep_req_body(context, ctx->fast_state, - ctx->request, - &ctx->encoded_request_body); - if (code != 0) - goto cleanup; - - ctx->request->from = krb5int_addint32(ctx->request_time, - ctx->start_time); - ctx->request->till = krb5int_addint32(ctx->request->from, - ctx->tkt_life); - - if (ctx->renew_life > 0) { - ctx->request->rtime = - krb5int_addint32(ctx->request->from, ctx->renew_life); - if (ctx->request->rtime < ctx->request->till) { - /* don't ask for a smaller renewable time than the lifetime */ - ctx->request->rtime = ctx->request->till; - } - ctx->request->kdc_options &= ~(KDC_OPT_RENEWABLE_OK); - } else - ctx->request->rtime = 0; - } else if (ctx->loopcount >= MAX_IN_TKT_LOOPS) { + if (ctx->loopcount >= MAX_IN_TKT_LOOPS) { code = KRB5_GET_IN_TKT_LOOP; goto cleanup; } @@ -1657,7 +1701,12 @@ init_creds_step_request(krb5_context context, krb5_free_data(context, ctx->encoded_previous_request); ctx->encoded_previous_request = NULL; } - + if (ctx->request->padata) + ctx->sent_nontrivial_preauth = 1; + if (ctx->enc_pa_rep_permitted) + code = request_enc_pa_rep(&ctx->request->padata); + if (code) + goto cleanup; code = krb5int_fast_prep_req(context, ctx->fast_state, ctx->request, ctx->encoded_request_body, encode_krb5_as_req, @@ -1675,6 +1724,53 @@ cleanup: return code; } +/* + * The control flow is complicated. In order to switch from non-FAST mode to + * FAST mode, we need to reset our pre-authentication state. FAST negotiation + * attempts to make sure we rarely have to do this. When FAST negotiation is + * working, we record whether FAST is available when we obtain an armor ticket; + * if so, we start out with FAST enabled . There are two complicated + * situations. + * + * First, if we get a PREAUTH_REQUIRED error including PADATA_FX_FAST back from + * a KDC in a case where we were not expecting to use FAST, and we have an + * armor ticket available, then we want to use FAST. That involves clearing + * out the pre-auth state, reinitializing the plugins and trying again with an + * armor key. + * + * Secondly, using the negotiation can cause problems with some older KDCs. + * Negotiation involves including a special padata item. Some KDCs, including + * MIT prior to 1.7, will return PREAUTH_FAILED rather than PREAUTH_REQUIRED in + * pre-authentication is required and unknown padata are included in the + * request. To make matters worse, these KDCs typically do not include a list + * of padata in PREAUTH_FAILED errors. So, if we get PREAUTH_FAILED and we + * generated no pre-authentication other than the negotiation then we want to + * retry without negotiation. In this case it is probably also desirable to + * retry with the preauth plugin state cleared. + * + * In all these cases we should not start over more than once. Control flow is + * managed by several variables. + * + * sent_nontrivial_preauth: if true, we sent preauth other than negotiation; + * no restart on PREAUTH_FAILED + * + * KRB5INT_FAST_ARMOR_AVAIL: fast_state_flag if desired we could generate + * armor; if not set, then we can't use FAST even if the KDC wants to. + * + * have_restarted: true if we've already restarted + */ +static krb5_boolean +negotiation_requests_restart(krb5_context context, krb5_init_creds_context ctx, + krb5_pa_data **padata) +{ + if (!ctx->have_restarted && + (krb5int_upgrade_to_fast_p(context, ctx->fast_state, padata) || + (ctx->err_reply->error == KDC_ERR_PREAUTH_FAILED && + !ctx->sent_nontrivial_preauth))) + return 1; + return 0; +} + static krb5_error_code init_creds_step_reply(krb5_context context, krb5_init_creds_context ctx, @@ -1687,6 +1783,7 @@ init_creds_step_reply(krb5_context context, int canon_flag = 0; krb5_keyblock *strengthen_key = NULL; krb5_keyblock encrypting_key; + krb5_boolean fast_avail; encrypting_key.length = 0; encrypting_key.contents = NULL; @@ -1705,8 +1802,16 @@ init_creds_step_reply(krb5_context context, &ctx->err_reply, &padata, &retry); if (code != 0) goto cleanup; - - if (ctx->err_reply->error == KDC_ERR_PREAUTH_REQUIRED && retry) { + if (negotiation_requests_restart(context, ctx, padata)) { + ctx->have_restarted = 1; + krb5_preauth_request_context_fini(context); + if ((ctx->fast_state->fast_state_flags & KRB5INT_FAST_DO_FAST) ==0) + ctx->enc_pa_rep_permitted = 0; + code = restart_init_creds_loop(context, ctx, padata); + krb5_free_error(context, ctx->err_reply); + ctx->err_reply = NULL; + } else if (ctx->err_reply->error == KDC_ERR_PREAUTH_REQUIRED && + retry) { /* reset the list of preauth types to try */ krb5_free_pa_data(context, ctx->preauth_to_use); ctx->preauth_to_use = padata; @@ -1732,13 +1837,20 @@ init_creds_step_reply(krb5_context context, /* this will trigger a new call to krb5_do_preauth() */ krb5_free_error(context, ctx->err_reply); ctx->err_reply = NULL; + krb5_preauth_request_context_fini(context); + /* Permit another negotiation based restart. */ + ctx->have_restarted = 0; + ctx->sent_nontrivial_preauth = 0; + code = restart_init_creds_loop(context, ctx, NULL); + if (code != 0) + goto cleanup; } else { if (retry) { code = 0; } else { /* error + no hints = give up */ code = (krb5_error_code)ctx->err_reply->error + - ERROR_TABLE_BASE_krb5; + ERROR_TABLE_BASE_krb5; } } @@ -1812,7 +1924,7 @@ init_creds_step_reply(krb5_context context, again. */ if (ctx->as_key.length) { code = krb5int_fast_reply_key(context, strengthen_key, &ctx->as_key, - &encrypting_key); + &encrypting_key); if (code != 0) goto cleanup; code = decrypt_as_reply(context, NULL, ctx->reply, NULL, NULL, @@ -1843,6 +1955,11 @@ init_creds_step_reply(krb5_context context, goto cleanup; } + code = krb5int_fast_verify_nego(context, ctx->fast_state, + ctx->reply, ctx->encoded_previous_request, + &encrypting_key, &fast_avail); + if (code) + goto cleanup; code = verify_as_reply(context, ctx->request_time, ctx->request, ctx->reply); if (code != 0) @@ -1852,6 +1969,30 @@ init_creds_step_reply(krb5_context context, ctx->reply, &ctx->cred, NULL); if (code != 0) goto cleanup; + if (ctx->opte && ctx->opte->opt_private->out_ccache) { + krb5_ccache out_ccache = ctx->opte->opt_private->out_ccache; + krb5_data config_data; + code = krb5_cc_initialize(context, out_ccache, ctx->cred.client); + if (code != 0) + goto cc_cleanup; + code = krb5_cc_store_cred(context, out_ccache, &ctx->cred); + if (code != 0) + goto cc_cleanup; + if (fast_avail) { + config_data.data = "yes"; + config_data.length = strlen(config_data.data); + code = krb5_cc_set_config(context, out_ccache, ctx->cred.server, + KRB5_CONF_FAST_AVAIL, &config_data); + } + cc_cleanup: + if (code !=0) { + const char *msg; + msg = krb5_get_error_message(context, code); + krb5_set_error_message(context, code, + "%s while storing credentials", msg); + krb5_free_error_message(context, msg); + } + } krb5_preauth_request_context_fini(context); @@ -2002,4 +2143,3 @@ cleanup: return code; } - diff --git a/src/lib/krb5/krb/gic_opt.c b/src/lib/krb5/krb/gic_opt.c index bff45392f..f4cfd9220 100644 --- a/src/lib/krb5/krb/gic_opt.c +++ b/src/lib/krb5/krb/gic_opt.c @@ -149,6 +149,8 @@ krb5int_gic_opte_private_free(krb5_context context, krb5_gic_opt_ext *opte) free_gic_opt_ext_preauth_data(context, opte); if (opte->opt_private->fast_ccache_name) free(opte->opt_private->fast_ccache_name); + if (opte->opt_private->out_ccache) + krb5_cc_close(context, opte->opt_private->out_ccache); free(opte->opt_private); opte->opt_private = NULL; return 0; @@ -486,3 +488,59 @@ krb5_error_code KRB5_CALLCONV krb5_get_init_creds_opt_set_fast_ccache_name retval = ENOMEM; return retval; } + +krb5_error_code KRB5_CALLCONV +krb5_get_init_creds_opt_set_out_ccache(krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_ccache ccache) +{ + krb5_error_code retval = 0; + krb5_gic_opt_ext *opte; + + retval = krb5int_gic_opt_to_opte(context, opt, &opte, 0, + "krb5_get_init_creds_opt_set_out_ccache"); + if (retval) + return retval; + if (opte->opt_private->out_ccache) { + krb5_cc_close(context, opte->opt_private->out_ccache); + opte->opt_private->out_ccache = NULL; + } + retval = krb5_cc_resolve(context, krb5_cc_get_name(context, ccache), + &opte->opt_private->out_ccache); + return retval; +} + +krb5_error_code KRB5_CALLCONV +krb5_get_init_creds_opt_set_fast_flags(krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_flags flags) +{ + krb5_error_code retval = 0; + krb5_gic_opt_ext *opte; + + retval = krb5int_gic_opt_to_opte(context, opt, &opte, 0, + "krb5_get_init_creds_opt_set_fast_flags"); + if (retval) + return retval; + opte->opt_private->fast_flags = flags; + return retval; +} + +krb5_error_code KRB5_CALLCONV +krb5_get_init_creds_opt_get_fast_flags(krb5_context context, + krb5_get_init_creds_opt *opt, + krb5_flags *out_flags) +{ + krb5_error_code retval = 0; + krb5_gic_opt_ext *opte; + if (out_flags == NULL) + return EINVAL; + *out_flags = 0; + retval = krb5int_gic_opt_to_opte(context, opt, &opte, 0, + "krb5_get_init_creds_opt_get_fast_flags"); + if (retval) + return retval; + *out_flags = opte->opt_private->fast_flags; + return retval; +} + diff --git a/src/lib/krb5/krb/init_creds_ctx.h b/src/lib/krb5/krb/init_creds_ctx.h index b830684f2..1d41a44f4 100644 --- a/src/lib/krb5/krb/init_creds_ctx.h +++ b/src/lib/krb5/krb/init_creds_ctx.h @@ -30,6 +30,9 @@ struct _krb5_init_creds_context { krb5_keyblock as_key; krb5_enctype etype; krb5_preauth_client_rock get_data_rock; + krb5_boolean enc_pa_rep_permitted; + krb5_boolean have_restarted; + krb5_boolean sent_nontrivial_preauth; }; #define KRB5_INIT_CREDS_STEP_FLAG_COMPLETE 0x1 @@ -46,4 +49,3 @@ krb5_get_as_key_password(krb5_context context, void *gak_data); #endif /* !KRB5_INIT_CREDS_CONTEXT */ - diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index 3de9915c3..15a887b2f 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -53,6 +53,7 @@ encode_krb5_as_rep encode_krb5_as_req encode_krb5_authdata encode_krb5_authenticator +encode_krb5_checksum encode_krb5_cred encode_krb5_enc_cred_part encode_krb5_enc_data @@ -171,6 +172,7 @@ krb5_cc_dfl_ops krb5_cc_end_seq_get krb5_cc_file_ops krb5_cc_gen_new +krb5_cc_get_config krb5_cc_get_name krb5_cc_get_principal krb5_cc_get_type @@ -182,6 +184,7 @@ krb5_cc_remove_cred krb5_cc_resolve krb5_cc_retrieve_cred krb5_cc_retrieve_cred_default +krb5_cc_set_config krb5_cc_set_default_name krb5_cc_set_flags krb5_cc_start_seq_get @@ -330,6 +333,7 @@ krb5_get_init_creds_keytab krb5_get_init_creds_opt_alloc krb5_get_init_creds_opt_free krb5_get_init_creds_opt_free_pa +krb5_get_init_creds_opt_get_fast_flags krb5_get_init_creds_opt_get_pa krb5_get_init_creds_opt_init krb5_get_init_creds_opt_set_address_list @@ -337,7 +341,9 @@ krb5_get_init_creds_opt_set_canonicalize krb5_get_init_creds_opt_set_change_password_prompt krb5_get_init_creds_opt_set_etype_list krb5_get_init_creds_opt_set_fast_ccache_name +krb5_get_init_creds_opt_set_fast_flags krb5_get_init_creds_opt_set_forwardable +krb5_get_init_creds_opt_set_out_ccache krb5_get_init_creds_opt_set_pa krb5_get_init_creds_opt_set_preauth_list krb5_get_init_creds_opt_set_proxiable @@ -371,6 +377,7 @@ krb5_init_creds_store_creds krb5_init_keyblock krb5_init_secure_context krb5_internalize_opaque +krb5_is_config_principal krb5_is_permitted_enctype krb5_is_referral_realm krb5_is_thread_safe -- 2.26.2