From d1384be39be11423bd05b88ec444733361fc1467 Mon Sep 17 00:00:00 2001 From: Sam Hartman Date: Tue, 31 Mar 2009 17:00:41 +0000 Subject: [PATCH] Implement FAST from draft-ietf-krb-wg-preauth-framework Merge fast branch at 22146 onto trunk Implement the kerberos pre-authentication framework FAST feature per Projects/FAST on the wiki. ticket: 6436 Target_Version: 1.7 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@22149 dc483132-0cff-0310-8789-dd5450dbe970 --- src/Makefile.in | 1 + src/configure.in | 2 +- src/include/k5-int-pkinit.h | 5 + src/include/k5-int.h | 96 +++- src/include/krb5/krb5.hin | 33 +- src/include/krb5/preauth_plugin.h | 29 +- src/kdc/Makefile.in | 2 + src/kdc/dispatch.c | 3 +- src/kdc/do_as_req.c | 66 ++- src/kdc/do_tgs_req.c | 40 +- src/kdc/fast_util.c | 398 ++++++++++++++ src/kdc/kdc_preauth.c | 48 +- src/kdc/kdc_util.c | 34 +- src/kdc/kdc_util.h | 36 +- src/lib/krb5/asn.1/asn1_k_decode.c | 51 ++ src/lib/krb5/asn.1/asn1_k_decode.h | 12 + src/lib/krb5/asn.1/asn1_k_encode.c | 88 +++ src/lib/krb5/asn.1/krb5_decode.c | 93 +++- src/lib/krb5/error_tables/krb5_err.et | 3 +- src/lib/krb5/error_tables/kv5m_err.et | 4 +- src/lib/krb5/krb/Makefile.in | 3 + src/lib/krb5/krb/fast.c | 499 ++++++++++++++++++ src/lib/krb5/krb/fast.h | 77 +++ src/lib/krb5/krb/get_in_tkt.c | 65 ++- src/lib/krb5/krb/gic_opt.c | 20 + src/lib/krb5/krb/kfree.c | 62 +++ src/lib/krb5/krb/preauth2.c | 39 +- src/lib/krb5/libkrb5.exports | 11 + src/lib/krb5/os/accessor.c | 7 + .../preauth/encrypted_challenge/Makefile.in | 41 ++ src/plugins/preauth/encrypted_challenge/deps | 0 .../encrypted_challenge.exports | 2 + .../encrypted_challenge_main.c | 409 ++++++++++++++ src/plugins/preauth/fast_factor.h | 53 ++ 34 files changed, 2272 insertions(+), 60 deletions(-) create mode 100644 src/kdc/fast_util.c create mode 100644 src/lib/krb5/krb/fast.c create mode 100644 src/lib/krb5/krb/fast.h create mode 100644 src/plugins/preauth/encrypted_challenge/Makefile.in create mode 100644 src/plugins/preauth/encrypted_challenge/deps create mode 100644 src/plugins/preauth/encrypted_challenge/encrypted_challenge.exports create mode 100644 src/plugins/preauth/encrypted_challenge/encrypted_challenge_main.c create mode 100644 src/plugins/preauth/fast_factor.h diff --git a/src/Makefile.in b/src/Makefile.in index 16349b8f4..dc80d2fe4 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -12,6 +12,7 @@ mydir=. SUBDIRS=util include lib kdc kadmin @ldap_plugin_dir@ slave clients \ plugins/kdb/db2 \ plugins/preauth/pkinit \ + plugins/preauth/encrypted_challenge \ appl tests \ config-files gen-manpages BUILDTOP=$(REL)$(C) diff --git a/src/configure.in b/src/configure.in index ef5af2dd1..b0d362489 100644 --- a/src/configure.in +++ b/src/configure.in @@ -1080,7 +1080,7 @@ dnl ccapi ccapi/lib ccapi/lib/unix ccapi/server ccapi/server/unix ccapi/test plugins/kdb/db2/libdb2/mpool plugins/kdb/db2/libdb2/recno plugins/kdb/db2/libdb2/test - plugins/preauth/cksum_body + plugins/preauth/cksum_body plugins/preauth/encrypted_challenge plugins/preauth/wpse plugins/authdata/greet diff --git a/src/include/k5-int-pkinit.h b/src/include/k5-int-pkinit.h index e75c8031f..2fb5f8719 100644 --- a/src/include/k5-int-pkinit.h +++ b/src/include/k5-int-pkinit.h @@ -101,6 +101,9 @@ typedef struct _krb5_trusted_ca { } krb5_trusted_ca; /* typed data */ +/* The FAST error handling logic currently assumes that this structure and krb5_pa_data * can be safely cast to each other + * if this structure changes, that code needs to be updated to copy. + */ typedef struct _krb5_typed_data { krb5_magic magic; krb5_int32 type; @@ -267,4 +270,6 @@ krb5_error_code decode_krb5_td_trusted_certifiers krb5_error_code decode_krb5_td_dh_parameters (const krb5_data *, krb5_algorithm_identifier ***); +void krb5_free_typed_data(krb5_context, krb5_typed_data **); + #endif /* _KRB5_INT_PKINIT_H */ diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 33ccb1690..db3976057 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -910,9 +910,11 @@ error(MIT_DES_KEYSIZE does not equal KRB5_MIT_DES_KEYSIZE) * requested information. It is opaque to the plugin code and can be * expanded in the future as new types of requests are defined which * may require other things to be passed through. */ + struct krb5int_fast_request_state; typedef struct _krb5_preauth_client_rock { krb5_magic magic; - krb5_kdc_rep *as_reply; + krb5_enctype *etype; + struct krb5int_fast_request_state *fast_state; } krb5_preauth_client_rock; /* This structure lets us keep track of all of the modules which are loaded, @@ -963,6 +965,48 @@ typedef struct _krb5_pa_for_user { krb5_data auth_package; } krb5_pa_for_user; +enum { + KRB5_FAST_ARMOR_AP_REQUEST = 0x1 +}; + +typedef struct _krb5_fast_armor { + krb5_int32 armor_type; + krb5_data armor_value; +} krb5_fast_armor; +typedef struct _krb5_fast_armored_req { + krb5_magic magic; + krb5_fast_armor *armor; + krb5_checksum req_checksum; + krb5_enc_data enc_part; +} krb5_fast_armored_req; + +typedef struct _krb5_fast_req { + krb5_magic magic; + krb5_int32 fast_options; + /* padata from req_body is used*/ + krb5_kdc_req *req_body; +} krb5_fast_req; + +/* Bits 0-15 are critical in fast options.*/ +#define UNSUPPORTED_CRITICAL_FAST_OPTIONS 0x00ff +#define KRB5_FAST_OPTION_HIDE_CLIENT_NAMES 0x01 + +typedef struct _krb5_fast_finished { + krb5_timestamp timestamp; + krb5_int32 usec; + krb5_principal client; + krb5_checksum ticket_checksum; +} krb5_fast_finished; + +typedef struct _krb5_fast_response { + krb5_magic magic; + krb5_pa_data **padata; + krb5_keyblock *rep_key; + krb5_fast_finished *finished; + krb5_int32 nonce; +} krb5_fast_response; + + typedef krb5_error_code (*krb5_preauth_obtain_proc) (krb5_context, krb5_pa_data *, @@ -1036,6 +1080,10 @@ krb5_error_code krb5_process_padata krb5_creds *, krb5_int32 *); +krb5_pa_data * krb5int_find_pa_data +(krb5_context, krb5_pa_data * const *, krb5_preauthtype); +/* Does not return a copy; original padata sequence responsible for freeing*/ + void krb5_free_etype_info (krb5_context, krb5_etype_info); @@ -1088,6 +1136,7 @@ void krb5_free_etype_info typedef struct _krb5_gic_opt_private { int num_preauth_data; krb5_gic_opt_pa_data *preauth_data; + char * fast_ccache_name; } krb5_gic_opt_private; /* @@ -1254,6 +1303,16 @@ void KRB5_CALLCONV krb5_free_pa_pac_req void KRB5_CALLCONV krb5_free_etype_list (krb5_context, krb5_etype_list * ); +void KRB5_CALLCONV krb5_free_fast_armor +(krb5_context, krb5_fast_armor *); +void KRB5_CALLCONV krb5_free_fast_armored_req +(krb5_context, krb5_fast_armored_req *); +void KRB5_CALLCONV krb5_free_fast_req(krb5_context, krb5_fast_req *); +void KRB5_CALLCONV krb5_free_fast_finished +(krb5_context, krb5_fast_finished *); +void KRB5_CALLCONV krb5_free_fast_response +(krb5_context, krb5_fast_response *); + /* #include "krb5/wordsize.h" -- comes in through base-defs.h. */ #include "com_err.h" #include "k5-plugin.h" @@ -1563,6 +1622,16 @@ krb5_error_code encode_krb5_pa_pac_req krb5_error_code encode_krb5_etype_list (const krb5_etype_list * , krb5_data **); +krb5_error_code encode_krb5_pa_fx_fast_request +(const krb5_fast_armored_req *, krb5_data **); +krb5_error_code encode_krb5_fast_req +(const krb5_fast_req *, krb5_data **); +krb5_error_code encode_krb5_pa_fx_fast_reply +(const krb5_enc_data *, krb5_data **); + +krb5_error_code encode_krb5_fast_response +(const krb5_fast_response *, krb5_data **); + /************************************************************************* * End of prototypes for krb5_encode.c *************************************************************************/ @@ -1722,6 +1791,19 @@ krb5_error_code decode_krb5_pa_pac_req krb5_error_code decode_krb5_etype_list (const krb5_data *, krb5_etype_list **); +krb5_error_code decode_krb5_pa_fx_fast_request +(const krb5_data *, krb5_fast_armored_req **); + +krb5_error_code decode_krb5_fast_req +(const krb5_data *, krb5_fast_req **); + + +krb5_error_code decode_krb5_pa_fx_fast_reply +(const krb5_data *, krb5_enc_data **); + +krb5_error_code decode_krb5_fast_response +(const krb5_data *, krb5_fast_response **); + struct _krb5_key_data; /* kdb.h */ struct ldap_seqof_key_data { @@ -1951,7 +2033,7 @@ void krb5int_free_srv_dns_data(struct srv_dns_entry *); /* To keep happy libraries which are (for now) accessing internal stuff */ /* Make sure to increment by one when changing the struct */ -#define KRB5INT_ACCESS_STRUCT_VERSION 13 +#define KRB5INT_ACCESS_STRUCT_VERSION 14 #ifndef ANAME_SZ struct ktext; /* from krb.h, for krb524 support */ @@ -2005,6 +2087,16 @@ typedef struct _krb5int_access { krb5_error_code (*asn1_ldap_decode_sequence_of_keys) (krb5_data *in, ldap_seqof_key_data **); + /* Used for encrypted challenge fast factor*/ + krb5_error_code (*encode_enc_data)(const krb5_enc_data *, krb5_data **); + krb5_error_code (*decode_enc_data)(const krb5_data *, krb5_enc_data **); + void (*free_enc_data)(krb5_context, krb5_enc_data *); + krb5_error_code (*encode_enc_ts)(const krb5_pa_enc_ts *, krb5_data **); + krb5_error_code (*decode_enc_ts)(const krb5_data *, krb5_pa_enc_ts **); + void (*free_enc_ts)(krb5_context, krb5_pa_enc_ts *); + krb5_error_code (*encrypt_helper) + (krb5_context, const krb5_keyblock *, krb5_keyusage, const krb5_data *, + krb5_enc_data *); /* * pkinit asn.1 encode/decode functions diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index 69fb038ce..bf8c29c59 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -631,7 +631,15 @@ krb5_error_code KRB5_CALLCONV /* Defined in KDC referrals draft */ #define KRB5_KEYUSAGE_PA_REFERRAL 26 /* XXX note conflict with above */ - +/* define in draft-ietf-krb-wg-preauth-framework*/ +#define KRB5_KEYUSAGE_FAST_REQ_CHKSUM 50 +#define KRB5_KEYUSAGE_FAST_ENC 51 +#define KRB5_KEYUSAGE_FAST_REP 52 +#define KRB5_KEYUSAGE_FAST_FINISHED 53 +#define KRB5_KEYUSAGE_ENC_CHALLENGE_CLIENT 54 +#define KRB5_KEYUSAGE_ENC_CHALLENGE_KDC 55 + +#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 @@ -982,6 +990,11 @@ krb5_error_code KRB5_CALLCONV krb5_verify_checksum #define KRB5_PADATA_PAC_REQUEST 128 /* include Windows PAC */ #define KRB5_PADATA_FOR_USER 129 /* username protocol transition request */ #define KRB5_PADATA_S4U_X509_USER 130 /* certificate protocol transition request */ +#define KRB5_PADATA_FX_COOKIE 133 +#define KRB5_PADATA_FX_FAST 136 +#define KRB5_PADATA_FX_ERROR 137 +#define KRB5_PADATA_ENCRYPTED_CHALLENGE 138 + #define KRB5_SAM_USE_SAD_AS_KEY 0x80000000 #define KRB5_SAM_SEND_ENCRYPTED_SAD 0x40000000 #define KRB5_SAM_MUST_PK_ENCRYPT_SAD 0x20000000 /* currently must be zero */ @@ -1005,7 +1018,7 @@ krb5_error_code KRB5_CALLCONV krb5_verify_checksum #define KRB5_AUTHDATA_SESAME 65 #define KRB5_AUTHDATA_WIN2K_PAC 128 #define KRB5_AUTHDATA_ETYPE_NEGOTIATION 129 /* RFC 4537 */ - +#define KRB5_AUTHDATA_FX_ARMOR 71 /* password change constants */ #define KRB5_KPASSWD_SUCCESS 0 @@ -1142,6 +1155,13 @@ typedef struct _krb5_kdc_req { krb5_authdata **unenc_authdata; /* unencrypted auth data, if available */ krb5_ticket **second_ticket;/* second ticket array; OPTIONAL */ + /* the following field is added in March 2009; it is a hack so + * that FAST state can be carried to pre-authentication plugins. + * A new plugin interface may be a better long-term approach. It + * is believed to be safe to extend this structure because it is + * not found in any public APIs. + */ + void * kdc_state; } krb5_kdc_req; typedef struct _krb5_enc_kdc_rep_part { @@ -2393,6 +2413,15 @@ krb5_get_init_creds_opt_set_pa const char *attr, const char *value); +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.*/ krb5_error_code KRB5_CALLCONV krb5_get_init_creds_password (krb5_context context, diff --git a/src/include/krb5/preauth_plugin.h b/src/include/krb5/preauth_plugin.h index 242956076..e11913e3f 100644 --- a/src/include/krb5/preauth_plugin.h +++ b/src/include/krb5/preauth_plugin.h @@ -123,11 +123,22 @@ typedef krb5_error_code * information to enable it to process a request. */ enum krb5plugin_preauth_client_request_type { - /* The returned krb5_data item holds the enctype used to encrypt the - * encrypted portion of the AS_REP packet. */ + /* The returned krb5_data item holds the enctype expected to be used to encrypt the + * encrypted portion of the AS_REP packet. When handling a + * PREAUTH_REQUIRED error, this typically comes from etype-info2. + * When handling an AS reply, it is initialized from the AS reply itself.*/ krb5plugin_preauth_client_get_etype = 1, /* Free the data returned from krb5plugin_preauth_client_req_get_etype */ - krb5plugin_preauth_client_free_etype = 2 + krb5plugin_preauth_client_free_etype = 2, + /* The returned krb5_data contains the FAST armor key in a + * krb5_keyblock. Returns success with a NULL data item in the + * krb5_data if the client library supports FAST but is not using it.*/ + krb5plugin_preauth_client_fast_armor = 3, + /* Frees return from KRB5PLUGIN_PREAUTH_CLIENT_FAST_ARMOR. It is + * acceptable to set data to NULL and free the keyblock using + * krb5_free_keyblock; in that case, this frees the krb5_data + * only.*/ +krb5plugin_preauth_client_free_fast_armor = 4, }; typedef krb5_error_code (*preauth_get_client_data_proc)(krb5_context, @@ -326,8 +337,16 @@ enum krb5plugin_preauth_entry_request_type { * implementation, there's a good chance that the result will not match * what the client sent, so don't go creating any fatal errors if it * doesn't match up. */ - krb5plugin_preauth_request_body = 4 -}; + krb5plugin_preauth_request_body = 4, + /* The returned krb5_data contains a krb5_keyblock with the FAST + armor key. The data member is NULL if this method is not part + of a FAST tunnel */ + krb5plugin_preauth_fast_armor = 5, + /* Frees a fast armor key; it is acceptable to set data to NULL + and free the keyblock using krb5_free_keyblock; in that case, + this function simply frees the data*/ + krb5plugin_preauth_free_fast_armor = 6, + }; typedef krb5_error_code (*preauth_get_entry_data_proc)(krb5_context, diff --git a/src/kdc/Makefile.in b/src/kdc/Makefile.in index 83e5f82d9..0d37fe1a1 100644 --- a/src/kdc/Makefile.in +++ b/src/kdc/Makefile.in @@ -24,6 +24,7 @@ SRCS= \ $(srcdir)/dispatch.c \ $(srcdir)/do_as_req.c \ $(srcdir)/do_tgs_req.c \ + $(srcdir)/fast_util.c \ $(srcdir)/kdc_util.c \ $(srcdir)/kdc_preauth.c \ $(srcdir)/main.c \ @@ -38,6 +39,7 @@ OBJS= \ dispatch.o \ do_as_req.o \ do_tgs_req.o \ + fast_util.o \ kdc_util.o \ kdc_preauth.o \ main.o \ diff --git a/src/kdc/dispatch.c b/src/kdc/dispatch.c index 77415af82..36786457f 100644 --- a/src/kdc/dispatch.c +++ b/src/kdc/dispatch.c @@ -92,11 +92,12 @@ dispatch(krb5_data *pkt, const krb5_fulladdr *from, krb5_data **response) /* * setup_server_realm() sets up the global realm-specific data * pointer. + * process_as_req frees the request if it is called */ if (!(retval = setup_server_realm(as_req->server))) { retval = process_as_req(as_req, pkt, from, response); } - krb5_free_kdc_req(kdc_context, as_req); + else krb5_free_kdc_req(kdc_context, as_req); } } else diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c index 8db39ac4f..7b590f4e0 100644 --- a/src/kdc/do_as_req.c +++ b/src/kdc/do_as_req.c @@ -82,7 +82,7 @@ #endif #endif /* APPLE_PKINIT */ -static krb5_error_code prepare_error_as (krb5_kdc_req *, int, krb5_data *, +static krb5_error_code prepare_error_as (struct kdc_request_state *, krb5_kdc_req *, int, krb5_data *, krb5_principal, krb5_data **, const char *); @@ -117,6 +117,9 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, int did_log = 0; const char *emsg = 0; krb5_keylist_node *tmp_mkey_list; + struct kdc_request_state *state = NULL; + krb5_data encoded_req_body; + #if APPLE_PKINIT asReqDebug("process_as_req top realm %s name %s\n", @@ -133,6 +136,22 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, session_key.contents = 0; enc_tkt_reply.authorization_data = NULL; + errcode = kdc_make_rstate(&state); + if (errcode != 0) { + status = "constructing state"; + goto errout; + } + if (fetch_asn1_field((unsigned char *) req_pkt->data, + 1, 4, &encoded_req_body) != 0) { + errcode = ASN1_BAD_ID; + status = "Finding req_body"; +} + errcode = kdc_find_fast(&request, &encoded_req_body, NULL /*TGS key*/, NULL, state); + if (errcode) { + status = "error decoding FAST"; + goto errout; + } + request->kdc_state = state; if (!request->client) { status = "NULL_CLIENT"; errcode = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; @@ -548,6 +567,7 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, goto errout; } + errcode = handle_authdata(kdc_context, c_flags, &client, @@ -572,6 +592,11 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, goto errout; } ticket_reply.enc_part.kvno = server_key->key_data_kvno; + errcode = kdc_fast_response_handle_padata(state, request, &reply); + if (errcode) { + status = "fast response handling"; + goto errout; + } /* now encode/encrypt the response */ @@ -629,7 +654,7 @@ egress: if (errcode < 0 || errcode > 128) errcode = KRB_ERR_GENERIC; - errcode = prepare_error_as(request, errcode, &e_data, + errcode = prepare_error_as(state, request, errcode, &e_data, c_nprincs ? client.princ : NULL, response, status); status = 0; @@ -679,18 +704,24 @@ egress: } krb5_free_data_contents(kdc_context, &e_data); + kdc_free_rstate(state); + request->kdc_state = NULL; + krb5_free_kdc_req(kdc_context, request); assert(did_log != 0); return errcode; } static krb5_error_code -prepare_error_as (krb5_kdc_req *request, int error, krb5_data *e_data, +prepare_error_as (struct kdc_request_state *rstate, krb5_kdc_req *request, int error, krb5_data *e_data, krb5_principal canon_client, krb5_data **response, const char *status) { krb5_error errpkt; krb5_error_code retval; krb5_data *scratch; + krb5_pa_data **pa = NULL; + krb5_typed_data **td = NULL; + size_t size; errpkt.ctime = request->nonce; errpkt.cusec = 0; @@ -719,13 +750,38 @@ prepare_error_as (krb5_kdc_req *request, int error, krb5_data *e_data, errpkt.e_data.length = 0; errpkt.e_data.data = NULL; } - + /*We need to try and produce a padata sequence for FAST*/ + retval = decode_krb5_padata_sequence(e_data, &pa); + if (retval != 0) { + retval = decode_krb5_typed_data(e_data, &td); + if (retval == 0) { + for (size =0; td[size]; size++); + pa = calloc(size+1, sizeof(*pa)); + if (pa == NULL) + retval = ENOMEM; + else for (size = 0; td[size]; size++) { + krb5_pa_data *pad = malloc(sizeof(krb5_pa_data *)); + if (pad == NULL) { + retval = ENOMEM; + break; + } + pad->pa_type = td[size]->type; + pad->contents = td[size]->data; + pad->length = td[size]->length; + pa[size] = pad; + } + krb5_free_typed_data(kdc_context, td); + } + } + retval = kdc_fast_handle_error(kdc_context, rstate, + request, pa, &errpkt); + if (retval == 0) retval = krb5_mk_error(kdc_context, &errpkt, scratch); free(errpkt.text.data); if (retval) free(scratch); else *response = scratch; - + krb5_free_pa_data(kdc_context, pa); return retval; } diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c index 33ba0cd24..5af773046 100644 --- a/src/kdc/do_tgs_req.c +++ b/src/kdc/do_tgs_req.c @@ -76,7 +76,7 @@ find_alternate_tgs(krb5_kdc_req *,krb5_db_entry *, krb5_boolean *,int *); static krb5_error_code -prepare_error_tgs(krb5_kdc_req *,krb5_ticket *,int, +prepare_error_tgs(struct kdc_request_state *, krb5_kdc_req *,krb5_ticket *,int, krb5_principal,krb5_data **,const char *); static krb5_int32 @@ -125,6 +125,9 @@ process_tgs_req(krb5_data *pkt, const krb5_fulladdr *from, krb5_data *tgs_1 =NULL, *server_1 = NULL; krb5_principal krbtgt_princ; krb5_kvno ticket_kvno = 0; + struct kdc_request_state *state = NULL; + krb5_pa_data *pa_tgs_req; /*points into request*/ + krb5_data scratch; session_key.contents = NULL; @@ -140,7 +143,7 @@ process_tgs_req(krb5_data *pkt, const krb5_fulladdr *from, return retval; } errcode = kdc_process_tgs_req(request, from, pkt, &header_ticket, - &krbtgt, &k_nprincs, &subkey); + &krbtgt, &k_nprincs, &subkey, &pa_tgs_req); if (header_ticket && header_ticket->enc_part2 && (errcode2 = krb5_unparse_name(kdc_context, header_ticket->enc_part2->client, @@ -161,7 +164,15 @@ process_tgs_req(krb5_data *pkt, const krb5_fulladdr *from, status="UNEXPECTED NULL in header_ticket"; goto cleanup; } - + scratch.length = pa_tgs_req->length; + scratch.data = (char *) pa_tgs_req->contents; + errcode = kdc_find_fast(&request, &scratch, subkey, header_ticket->enc_part2->session, state); + if (errcode !=0) { + status = "kdc_find_fast"; + goto cleanup; + } + + /* * Pointer to the encrypted part of the header ticket, which may be * replaced to point to the encrypted part of the evidence ticket @@ -862,7 +873,12 @@ tgt_again: reply.enc_part.enctype = subkey ? subkey->enctype : header_ticket->enc_part2->session->enctype; - errcode = krb5_encode_kdc_rep(kdc_context, KRB5_TGS_REP, &reply_encpart, + errcode = kdc_fast_response_handle_padata(state, request, &reply); + if (errcode !=0 ) { + status = "Preparing FAST padata"; + goto cleanup; + } + errcode = krb5_encode_kdc_rep(kdc_context, KRB5_TGS_REP, &reply_encpart, subkey ? 1 : 0, subkey ? subkey : header_ticket->enc_part2->session, @@ -903,7 +919,7 @@ cleanup: if (errcode < 0 || errcode > 128) errcode = KRB_ERR_GENERIC; - retval = prepare_error_tgs(request, header_ticket, errcode, + retval = prepare_error_tgs(state, request, header_ticket, errcode, nprincs ? server.princ : NULL, response, status); if (got_err) { @@ -916,6 +932,8 @@ cleanup: krb5_free_ticket(kdc_context, header_ticket); if (request != NULL) krb5_free_kdc_req(kdc_context, request); + if (state) + kdc_free_rstate(state); if (cname != NULL) free(cname); if (sname != NULL) @@ -943,7 +961,8 @@ cleanup: } static krb5_error_code -prepare_error_tgs (krb5_kdc_req *request, krb5_ticket *ticket, int error, +prepare_error_tgs (struct kdc_request_state *state, + krb5_kdc_req *request, krb5_ticket *ticket, int error, krb5_principal canon_server, krb5_data **response, const char *status) { @@ -966,14 +985,19 @@ prepare_error_tgs (krb5_kdc_req *request, krb5_ticket *ticket, int error, errpkt.text.length = strlen(status) + 1; if (!(errpkt.text.data = strdup(status))) return ENOMEM; - + if (!(scratch = (krb5_data *)malloc(sizeof(*scratch)))) { free(errpkt.text.data); return ENOMEM; } errpkt.e_data.length = 0; errpkt.e_data.data = NULL; - + retval = kdc_fast_handle_error(kdc_context, state, request, NULL, &errpkt); + if (retval) { + free(scratch); + free(errpkt.text.data); + return retval; + } retval = krb5_mk_error(kdc_context, &errpkt, scratch); free(errpkt.text.data); if (retval) diff --git a/src/kdc/fast_util.c b/src/kdc/fast_util.c new file mode 100644 index 000000000..10d1d3eb6 --- /dev/null +++ b/src/kdc/fast_util.c @@ -0,0 +1,398 @@ +/* + * kdc/fast_util.c + * + * Copyright (C) 2009 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * + * + */ + +#include + +#include "kdc_util.h" +#include "extern.h" + + +/* + * This function will find the fast and cookie padata and if fast is + * successfully processed, will throw away (and free) the outer + * request and update the pointer to point to the inner request. The + * checksummed_data points to the data that is in the + * armored_fast_request checksum; either the pa-tgs-req or the + * kdc-req-body. + */ + +static krb5_error_code armor_ap_request +(struct kdc_request_state *state, krb5_fast_armor *armor) +{ + krb5_error_code retval = 0; + krb5_auth_context authcontext = NULL; + krb5_ticket *ticket = NULL; + krb5_keyblock *subkey = NULL; + + assert(armor->armor_type = KRB5_FAST_ARMOR_AP_REQUEST); + krb5_clear_error_message(kdc_context); + retval = krb5_auth_con_init(kdc_context, &authcontext); + if (retval == 0) + retval = krb5_auth_con_setflags(kdc_context, authcontext, 0); /*disable replay cache*/ + retval = krb5_rd_req(kdc_context, &authcontext, + &armor->armor_value, NULL /*server*/, + kdc_active_realm->realm_keytab, NULL, &ticket); + if (retval !=0) { + const char * errmsg = krb5_get_error_message(kdc_context, retval); + krb5_set_error_message(kdc_context, retval, + "%s while handling ap-request armor", errmsg); + krb5_free_error_message(kdc_context, errmsg); + } + if (retval == 0) { + if (!krb5_principal_compare_any_realm(kdc_context, + tgs_server, + ticket->server)) { + krb5_set_error_message(kdc_context, KRB5KDC_ERR_SERVER_NOMATCH, + "ap-request armor for something other than the local TGS"); + retval = KRB5KDC_ERR_SERVER_NOMATCH; + } + } + if (retval ==0) { + retval = krb5_auth_con_getrecvsubkey(kdc_context, authcontext, &subkey); + if (retval !=0 || subkey == NULL) { + krb5_set_error_message(kdc_context, KRB5KDC_ERR_POLICY, + "ap-request armor without subkey"); + retval = KRB5KDC_ERR_POLICY; + } + } + if (retval==0) + retval = krb5_c_fx_cf2_simple(kdc_context, + subkey, "subkeyarmor", + ticket->enc_part2->session, "ticketarmor", + &state->armor_key); + if (ticket) + krb5_free_ticket(kdc_context, ticket); + if (subkey) + krb5_free_keyblock(kdc_context, subkey); + if (authcontext) + krb5_auth_con_free(kdc_context, authcontext); + return retval; +} + +static krb5_error_code encrypt_fast_reply +(struct kdc_request_state *state, const krb5_fast_response *response, + krb5_data **fx_fast_reply) +{ + krb5_error_code retval = 0; + krb5_enc_data encrypted_reply; + krb5_data *encoded_response = NULL; + assert(state->armor_key); + retval = encode_krb5_fast_response(response, &encoded_response); + if (retval== 0) + retval = krb5_encrypt_helper(kdc_context, state->armor_key, + KRB5_KEYUSAGE_FAST_REP, + encoded_response, &encrypted_reply); + if (encoded_response) + krb5_free_data(kdc_context, encoded_response); + encoded_response = NULL; + if (retval == 0) { + retval = encode_krb5_pa_fx_fast_reply(&encrypted_reply, + fx_fast_reply); + krb5_free_data_contents(kdc_context, &encrypted_reply.ciphertext); + } + return retval; +} + + +krb5_error_code kdc_find_fast +(krb5_kdc_req **requestptr, krb5_data *checksummed_data, + krb5_keyblock *tgs_subkey, + krb5_keyblock *tgs_session, + struct kdc_request_state *state) +{ + krb5_error_code retval = 0; + krb5_pa_data *fast_padata, *cookie_padata; + krb5_data scratch; + krb5_fast_req * fast_req = NULL; + krb5_kdc_req *request = *requestptr; + krb5_fast_armored_req *fast_armored_req = NULL; + krb5_boolean cksum_valid; + + scratch.data = NULL; + krb5_clear_error_message(kdc_context); + fast_padata = find_pa_data(request->padata, + KRB5_PADATA_FX_FAST); + if (fast_padata != NULL){ + scratch.length = fast_padata->length; + scratch.data = (char *) fast_padata->contents; + retval = decode_krb5_pa_fx_fast_request(&scratch, &fast_armored_req); + if (retval == 0 &&fast_armored_req->armor) { + switch (fast_armored_req->armor->armor_type) { + case KRB5_FAST_ARMOR_AP_REQUEST: + retval = armor_ap_request(state, fast_armored_req->armor); + break; + default: + krb5_set_error_message(kdc_context, KRB5KDC_ERR_PREAUTH_FAILED, + "Unknow FAST armor type %d", + fast_armored_req->armor->armor_type); + retval = KRB5KDC_ERR_PREAUTH_FAILED; + } + } + if (retval == 0 && !state->armor_key) { + if (tgs_subkey) + retval = krb5_c_fx_cf2_simple(kdc_context, + tgs_subkey, "subkeyarmor", + tgs_session, "ticketarmor", + &state->armor_key); + else { + krb5_set_error_message(kdc_context, KRB5KDC_ERR_PREAUTH_FAILED, + "No armor key but FAST armored request present"); + retval = KRB5KDC_ERR_PREAUTH_FAILED; + } + } + if (retval == 0) { + krb5_data plaintext; + plaintext.length = fast_armored_req->enc_part.ciphertext.length; + plaintext.data = malloc(plaintext.length); + if (plaintext.data == NULL) + retval = ENOMEM; + retval = krb5_c_decrypt(kdc_context, + state->armor_key, + KRB5_KEYUSAGE_FAST_ENC, NULL, + &fast_armored_req->enc_part, + &plaintext); + if (retval == 0) + retval = decode_krb5_fast_req(&plaintext, &fast_req); + if (plaintext.data) + free(plaintext.data); + } + if (retval == 0) + retval = krb5_c_verify_checksum(kdc_context, state->armor_key, + KRB5_KEYUSAGE_FAST_REQ_CHKSUM, + checksummed_data, &fast_armored_req->req_checksum, + &cksum_valid); + if (retval == 0 && !cksum_valid) { + retval = KRB5KRB_AP_ERR_MODIFIED; + krb5_set_error_message(kdc_context, KRB5KRB_AP_ERR_MODIFIED, + "FAST req_checksum invalid; request modified"); + } + if (retval == 0) { + if ((fast_req->fast_options & UNSUPPORTED_CRITICAL_FAST_OPTIONS) !=0) + retval = KRB5KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTION; + } + if (retval == 0) + cookie_padata = find_pa_data(fast_req->req_body->padata, KRB5_PADATA_FX_COOKIE); + if (retval == 0) { + state->fast_options = fast_req->fast_options; + if (request->kdc_state == state) + request->kdc_state = NULL; + krb5_free_kdc_req( kdc_context, request); + *requestptr = fast_req->req_body; + fast_req->req_body = NULL; + + } + } + else cookie_padata = find_pa_data(request->padata, KRB5_PADATA_FX_COOKIE); + if (retval == 0 && cookie_padata != NULL) { + krb5_pa_data *new_padata = malloc(sizeof (krb5_pa_data)); + if (new_padata != NULL) { + retval = ENOMEM; + } else { + new_padata->pa_type = KRB5_PADATA_FX_COOKIE; + new_padata->length = cookie_padata->length; + new_padata->contents = malloc(new_padata->length); + if (new_padata->contents == NULL) { + retval = ENOMEM; + free(new_padata); + } else { + memcpy(new_padata->contents, cookie_padata->contents, new_padata->length); + state->cookie = new_padata; + } + } + } + if (fast_req) + krb5_free_fast_req( kdc_context, fast_req); + if (fast_armored_req) + krb5_free_fast_armored_req(kdc_context, fast_armored_req); + return retval; +} + + +krb5_error_code kdc_make_rstate(struct kdc_request_state **out) +{ + struct kdc_request_state *state = malloc( sizeof(struct kdc_request_state)); + if (state == NULL) + return ENOMEM; + memset( state, 0, sizeof(struct kdc_request_state)); + *out = state; + return 0; +} + +void kdc_free_rstate +(struct kdc_request_state *s) +{ + if (s == NULL) + return; + if (s->armor_key) + krb5_free_keyblock(kdc_context, s->armor_key); + if (s->reply_key) + krb5_free_keyblock(kdc_context, s->reply_key); + if (s->cookie) { + free(s->cookie->contents); + free(s->cookie); + } + free(s); +} + +krb5_error_code kdc_fast_response_handle_padata +(struct kdc_request_state *state, + krb5_kdc_req *request, + krb5_kdc_rep *rep) +{ + krb5_error_code retval = 0; + krb5_fast_finished finish; + krb5_fast_response fast_response; + krb5_data *encoded_ticket = NULL; + krb5_data *encrypted_reply = NULL; + krb5_pa_data *pa = NULL, **pa_array; + krb5_cksumtype cksumtype = CKSUMTYPE_RSA_MD5; + krb5_pa_data *empty_padata[] = {NULL}; + + if (!state->armor_key) + return 0; + memset(&finish, 0, sizeof(finish)); + fast_response.padata = rep->padata; + if (fast_response.padata == NULL) + fast_response.padata = &empty_padata[0]; + fast_response.rep_key = state->reply_key; + fast_response.nonce = request->nonce; + fast_response.finished = &finish; + finish.client = rep->client; + pa_array = calloc(3, sizeof(*pa_array)); + if (pa_array == NULL) + retval = ENOMEM; + pa = calloc(1, sizeof(krb5_pa_data)); + if (retval == 0 && pa == NULL) + retval = ENOMEM; + if (retval == 0) + retval = krb5_us_timeofday(kdc_context, &finish.timestamp, &finish.usec); + if (retval == 0) + retval = encode_krb5_ticket(rep->ticket, &encoded_ticket); + if (retval == 0) + retval = krb5int_c_mandatory_cksumtype(kdc_context, state->armor_key->enctype, &cksumtype); + if (retval == 0) + retval = krb5_c_make_checksum(kdc_context, cksumtype, + state->armor_key, KRB5_KEYUSAGE_FAST_FINISHED, + encoded_ticket, &finish.ticket_checksum); + if (retval == 0) + retval = encrypt_fast_reply(state, &fast_response, &encrypted_reply); + if (retval == 0) { + pa[0].pa_type = KRB5_PADATA_FX_FAST; + pa[0].length = encrypted_reply->length; + pa[0].contents = (unsigned char *) encrypted_reply->data; + pa_array[0] = &pa[0]; + rep->padata = pa_array; + pa_array = NULL; + encrypted_reply = NULL; + pa = NULL; + } + if (pa) + free(pa); + if (encrypted_reply) + krb5_free_data(kdc_context, encrypted_reply); + if (encoded_ticket) + krb5_free_data(kdc_context, encoded_ticket); + if (finish.ticket_checksum.contents) + krb5_free_checksum_contents(kdc_context, &finish.ticket_checksum); + return retval; +} + + +/* + * We assume the caller is responsible for passing us an in_padata + * sufficient to include in a FAST error. In the FAST case we will + * throw away the e_data in the error (if any); in the non-FAST case + * we will not use the in_padata. + */ +krb5_error_code kdc_fast_handle_error +(krb5_context context, struct kdc_request_state *state, + krb5_kdc_req *request, + krb5_pa_data **in_padata, krb5_error *err) +{ + krb5_error_code retval = 0; + krb5_fast_response resp; + krb5_error fx_error; + krb5_data *encoded_fx_error = NULL, *encrypted_reply = NULL; + krb5_pa_data pa[2]; + krb5_pa_data *outer_pa[3]; + krb5_pa_data **inner_pa = NULL; + size_t size = 0; + krb5_data *encoded_e_data = NULL; + + memset(outer_pa, 0, sizeof(outer_pa)); + if (!state->armor_key) + return 0; + fx_error = *err; + fx_error.e_data.data = NULL; + fx_error.e_data.length = 0; + for (size = 0; in_padata&&in_padata[size]; size++); + size +=3; + inner_pa = calloc(size, sizeof(krb5_pa_data *)); + if (inner_pa == NULL) + retval = ENOMEM; + if (retval == 0) + for (size=0; in_padata&&in_padata[size]; size++) + inner_pa[size] = in_padata[size]; + if (retval == 0) + retval = encode_krb5_error(&fx_error, &encoded_fx_error); + if (retval == 0) { + pa[0].pa_type = KRB5_PADATA_FX_ERROR; + pa[0].length = encoded_fx_error->length; + pa[0].contents = (unsigned char *) encoded_fx_error->data; + inner_pa[size++] = &pa[0]; + resp.padata = inner_pa; + resp.nonce = request->nonce; + resp.rep_key = NULL; + resp.finished = NULL; + } + if (retval == 0) + retval = encrypt_fast_reply(state, &resp, &encrypted_reply); + if (inner_pa) + free(inner_pa); /*contained storage from caller and our stack*/ + if (retval == 0) { + pa[0].pa_type = KRB5_PADATA_FX_FAST; + pa[0].length = encrypted_reply->length; + pa[0].contents = (unsigned char *) encrypted_reply->data; + outer_pa[0] = &pa[0]; + } + retval = encode_krb5_padata_sequence(outer_pa, &encoded_e_data); + if (retval == 0) { + /*process_as holds onto a pointer to the original e_data and frees it*/ + err->e_data = *encoded_e_data; + free(encoded_e_data); /*contents belong to err*/ + encoded_e_data = NULL; + } + if (encoded_e_data) + krb5_free_data(kdc_context, encoded_e_data); + if (encrypted_reply) + krb5_free_data(kdc_context, encrypted_reply); + if (encoded_fx_error) + krb5_free_data(kdc_context, encoded_fx_error); + return retval; +} diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c index a80110abd..cf269753d 100644 --- a/src/kdc/kdc_preauth.c +++ b/src/kdc/kdc_preauth.c @@ -133,6 +133,12 @@ static krb5_error_code verify_enc_timestamp krb5_data **e_data, krb5_authdata ***authz_data); +static krb5_error_code get_enc_ts + (krb5_context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + preauth_get_entry_data_proc get_entry_data, + void *pa_system_context, + krb5_pa_data *data); static krb5_error_code get_etype_info (krb5_context, krb5_kdc_req *request, krb5_db_entry *client, krb5_db_entry *server, @@ -279,7 +285,7 @@ static krb5_preauth_systems static_preauth_systems[] = { NULL, NULL, NULL, - 0, + get_enc_ts, verify_enc_timestamp, 0 }, @@ -668,6 +674,7 @@ get_entry_data(krb5_context context, krb5_keyblock *keys, *mkey_ptr; krb5_key_data *entry_key; krb5_error_code error; + struct kdc_request_state *state = request->kdc_state; switch (type) { case krb5plugin_preauth_entry_request_certificate: @@ -752,6 +759,30 @@ get_entry_data(krb5_context context, } return ASN1_PARSE_ERROR; break; + case krb5plugin_preauth_fast_armor: + ret = calloc(1, sizeof(krb5_data)); + if (ret == NULL) + return ENOMEM; + if (state->armor_key == NULL) { + *result = ret; + return 0; + } + error = krb5_copy_keyblock(context, state->armor_key, &keys); + if (error == 0) { + ret->data = (char *) keys; + ret->length = sizeof(krb5_keyblock); + *result = ret; + return 0; + } + free(ret); + return error; + case krb5plugin_preauth_free_fast_armor: + if ((*result)->data) { + keys = (krb5_keyblock *) (*result)->data; + krb5_free_keyblock(context, keys); + } + free(*result); + return 0; default: break; } @@ -1340,7 +1371,20 @@ request_contains_enctype (krb5_context context, const krb5_kdc_req *request, return 0; } - +static krb5_error_code get_enc_ts + (krb5_context context, krb5_kdc_req *request, + krb5_db_entry *client, krb5_db_entry *server, + preauth_get_entry_data_proc get_entry_data_proc, + void *pa_system_context, + krb5_pa_data *data) +{ + struct kdc_request_state *state = request->kdc_state; + if (state->armor_key) + return ENOENT; + return 0; +} + + static krb5_error_code verify_enc_timestamp(krb5_context context, krb5_db_entry *client, krb5_data *req_pkt, diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c index 4b1e31c78..08d84db68 100644 --- a/src/kdc/kdc_util.c +++ b/src/kdc/kdc_util.c @@ -217,28 +217,20 @@ comp_cksum(krb5_context kcontext, krb5_data *source, krb5_ticket *ticket, krb5_pa_data * find_pa_data(krb5_pa_data **padata, krb5_preauthtype pa_type) { - krb5_pa_data **tmppa; - - if (padata == NULL) - return NULL; - - for (tmppa = padata; *tmppa != NULL; tmppa++) { - if ((*tmppa)->pa_type == pa_type) - break; - } - - return *tmppa; +return krb5int_find_pa_data(kdc_context, padata, pa_type); } krb5_error_code kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, krb5_data *pkt, krb5_ticket **ticket, krb5_db_entry *krbtgt, int *nprincs, - krb5_keyblock **subkey) + krb5_keyblock **subkey, + krb5_pa_data **pa_tgs_req) { krb5_pa_data * tmppa; krb5_ap_req * apreq; krb5_error_code retval; + krb5_authdata **authdata = NULL; krb5_data scratch1; krb5_data * scratch = NULL; krb5_boolean foreign_server = FALSE; @@ -350,6 +342,22 @@ kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, &authenticator))) goto cleanup_auth_context; + retval = krb5int_find_authdata(kdc_context, + (*ticket)->enc_part2->authorization_data, + authenticator->authorization_data, + KRB5_AUTHDATA_FX_ARMOR, &authdata); + if (retval != 0) + goto cleanup_auth_context; + if (authdata&& authdata[0]) { + krb5_set_error_message(kdc_context, KRB5KDC_ERR_POLICY, + "ticket valid only as FAST armor"); + retval = KRB5KDC_ERR_POLICY; + krb5_free_authdata(kdc_context, authdata); + goto cleanup_auth_context; + } + krb5_free_authdata(kdc_context, authdata); + + /* Check for a checksum */ if (!(his_cksum = authenticator->checksum)) { retval = KRB5KRB_AP_ERR_INAPP_CKSUM; @@ -383,6 +391,8 @@ kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, } } + if (retval == 0) + *pa_tgs_req = tmppa; cleanup_authenticator: krb5_free_authenticator(kdc_context, authenticator); diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h index e7bbf8113..90de8d39b 100644 --- a/src/kdc/kdc_util.h +++ b/src/kdc/kdc_util.h @@ -66,7 +66,7 @@ krb5_error_code kdc_process_tgs_req krb5_ticket **, krb5_db_entry *krbtgt, int *nprincs, - krb5_keyblock **); + krb5_keyblock **, krb5_pa_data **pa_tgs_req); krb5_error_code kdc_get_server_key (krb5_ticket *, unsigned int, krb5_boolean match_enctype, @@ -298,6 +298,40 @@ log_tgs_req(const krb5_fulladdr *from, const char *status, krb5_error_code errcode, const char *emsg); void log_tgs_alt_tgt(krb5_principal p); +/*Request state*/ + +struct kdc_request_state { + krb5_keyblock *armor_key; + krb5_keyblock *reply_key; /*When replaced by FAST*/ + krb5_pa_data *cookie; + krb5_int32 fast_options; + krb5_int32 fast_internal_flags; +}; +krb5_error_code kdc_make_rstate(struct kdc_request_state **out); +void kdc_free_rstate +(struct kdc_request_state *s); + +/* FAST*/ +enum krb5_fast_kdc_flags { + KRB5_FAST_REPLY_KEY_USED = 0x1, + KRB5_FAST_REPLY_KEY_REPLACED = 0x02, +}; + +krb5_error_code kdc_find_fast +(krb5_kdc_req **requestptr, krb5_data *checksummed_data, + krb5_keyblock *tgs_subkey, krb5_keyblock *tgs_session, + struct kdc_request_state *state); + +krb5_error_code kdc_fast_response_handle_padata +(struct kdc_request_state *state, + krb5_kdc_req *request, + krb5_kdc_rep *rep); +krb5_error_code kdc_fast_handle_error +(krb5_context context, struct kdc_request_state *state, + krb5_kdc_req *request, + krb5_pa_data **in_padata, krb5_error *err); + + #define isflagset(flagfield, flag) (flagfield & (flag)) diff --git a/src/lib/krb5/asn.1/asn1_k_decode.c b/src/lib/krb5/asn.1/asn1_k_decode.c index 25cc4cc62..c6857fe60 100644 --- a/src/lib/krb5/asn.1/asn1_k_decode.c +++ b/src/lib/krb5/asn.1/asn1_k_decode.c @@ -1625,6 +1625,57 @@ error_out: return retval; } +asn1_error_code asn1_decode_fast_armor +(asn1buf *buf, krb5_fast_armor *val) +{ + setup(); + val->armor_value.data = NULL; + {begin_structure(); + get_field(val->armor_type, 0, asn1_decode_int32); + get_lenfield(val->armor_value.length, val->armor_value.data, + 1, asn1_decode_charstring); + end_structure(); + } + return 0; + error_out: + krb5_free_data_contents( NULL, &val->armor_value); + return retval; +} + +asn1_error_code asn1_decode_fast_armor_ptr +(asn1buf *buf, krb5_fast_armor **valptr) +{ + decode_ptr(krb5_fast_armor *, asn1_decode_fast_armor); +} + +asn1_error_code asn1_decode_fast_finished +(asn1buf *buf, krb5_fast_finished *val) +{ + setup(); + val->client = NULL; + val->ticket_checksum.contents = NULL; + {begin_structure(); + get_field(val->timestamp, 0, asn1_decode_kerberos_time); + get_field(val->usec, 1, asn1_decode_int32); + alloc_field(val->client); + get_field(val->client, 2, asn1_decode_realm); + get_field(val->client, 3, asn1_decode_principal_name); + get_field(val->ticket_checksum, 4, asn1_decode_checksum); + end_structure(); + } + return 0; + error_out: + krb5_free_principal(NULL, val->client); + krb5_free_checksum_contents( NULL, &val->ticket_checksum); + return retval; +} +asn1_error_code asn1_decode_fast_finished_ptr +(asn1buf *buf, krb5_fast_finished **valptr) +{ + decode_ptr( krb5_fast_finished *, asn1_decode_fast_finished); +} + + #ifndef DISABLE_PKINIT /* PKINIT */ diff --git a/src/lib/krb5/asn.1/asn1_k_decode.h b/src/lib/krb5/asn.1/asn1_k_decode.h index f258f65c4..7444443ba 100644 --- a/src/lib/krb5/asn.1/asn1_k_decode.h +++ b/src/lib/krb5/asn.1/asn1_k_decode.h @@ -266,4 +266,16 @@ asn1_error_code asn1_decode_pa_for_user asn1_error_code asn1_decode_pa_pac_req (asn1buf *buf, krb5_pa_pac_req *val); +asn1_error_code asn1_decode_fast_armor +(asn1buf *buf, krb5_fast_armor *val); + +asn1_error_code asn1_decode_fast_armor_ptr +(asn1buf *buf, krb5_fast_armor **val); + +asn1_error_code asn1_decode_fast_finished +(asn1buf *buf, krb5_fast_finished *val); + +asn1_error_code asn1_decode_fast_finished_ptr +(asn1buf *buf, krb5_fast_finished **val); + #endif diff --git a/src/lib/krb5/asn.1/asn1_k_encode.c b/src/lib/krb5/asn.1/asn1_k_encode.c index 4790b57f1..53ce7fee1 100644 --- a/src/lib/krb5/asn.1/asn1_k_encode.c +++ b/src/lib/krb5/asn.1/asn1_k_encode.c @@ -338,6 +338,8 @@ asn1_encode_kdc_req_body(asn1buf *buf, const krb5_kdc_req *val, DEFFNXTYPE(kdc_req_body, krb5_kdc_req, asn1_encode_kdc_req_body); /* end ugly hack */ +DEFPTRTYPE(ptr_kdc_req_body,kdc_req_body); + static const struct field_info transited_fields[] = { FIELDOF_NORM(krb5_transited, octet, tr_type, 0), FIELDOF_NORM(krb5_transited, ostring_data, tr_contents, 1), @@ -1177,6 +1179,88 @@ DEFSEQTYPE(pa_pac_request, krb5_pa_pac_req, pa_pac_request_fields, 0); DEFFIELDTYPE(etype_list, krb5_etype_list, FIELDOF_SEQOF_INT32(krb5_etype_list, int32_ptr, etypes, length, -1)); +/* draft-ietf-krb-wg-preauth-framework-09 */ +static const struct field_info fast_armor_fields[] = { + FIELDOF_NORM(krb5_fast_armor, int32, armor_type, 0), + FIELDOF_NORM( krb5_fast_armor, ostring_data, armor_value, 1), +}; + +DEFSEQTYPE( fast_armor, krb5_fast_armor, fast_armor_fields, 0); +DEFPTRTYPE( ptr_fast_armor, fast_armor); + +static const struct field_info fast_armored_req_fields[] = { + FIELDOF_OPT( krb5_fast_armored_req, ptr_fast_armor, armor, 0, 0), + FIELDOF_NORM( krb5_fast_armored_req, checksum, req_checksum, 1), + FIELDOF_NORM( krb5_fast_armored_req, encrypted_data, enc_part, 2), +}; + +static unsigned int fast_armored_req_optional (const void *p) { + const krb5_fast_armored_req *val = p; + unsigned int optional = 0; + if (val->armor) + optional |= (1u)<<0; + return optional; +} + +DEFSEQTYPE( fast_armored_req, krb5_fast_armored_req, fast_armored_req_fields, fast_armored_req_optional); +DEFFIELDTYPE( pa_fx_fast_request, krb5_fast_armored_req, + FIELDOF_ENCODEAS( krb5_fast_armored_req, fast_armored_req, 0)); + +DEFFIELDTYPE(fast_req_padata, krb5_kdc_req, + FIELDOF_NORM(krb5_kdc_req, ptr_seqof_pa_data, padata, -1)); +DEFPTRTYPE(ptr_fast_req_padata, fast_req_padata); + +static const struct field_info fast_req_fields[] = { + FIELDOF_NORM(krb5_fast_req, int32, fast_options, 0), + FIELDOF_NORM( krb5_fast_req, ptr_fast_req_padata, req_body, 1), + FIELDOF_NORM( krb5_fast_req, ptr_kdc_req_body, req_body, 2), +}; + +DEFSEQTYPE(fast_req, krb5_fast_req, fast_req_fields, 0); + + +static const struct field_info fast_finished_fields[] = { + FIELDOF_NORM( krb5_fast_finished, kerberos_time, timestamp, 0), + FIELDOF_NORM( krb5_fast_finished, int32, usec, 1), + FIELDOF_NORM( krb5_fast_finished, realm_of_principal, client, 2), + FIELDOF_NORM(krb5_fast_finished, principal, client, 3), + FIELDOF_NORM( krb5_fast_finished, checksum, ticket_checksum, 4), +}; + +DEFSEQTYPE( fast_finished, krb5_fast_finished, fast_finished_fields, 0); + +DEFPTRTYPE( ptr_fast_finished, fast_finished); + +static const struct field_info fast_response_fields[] = { + FIELDOF_NORM(krb5_fast_response, ptr_seqof_pa_data, padata, 0), + FIELDOF_OPT( krb5_fast_response, ptr_encryption_key, rep_key, 1, 1), + FIELDOF_OPT( krb5_fast_response, ptr_fast_finished, finished, 2, 2), + FIELDOF_NORM(krb5_fast_response, int32, nonce, 3), +}; + +static unsigned int fast_response_optional (const void *p) +{ + unsigned int optional = 0; + const krb5_fast_response *val = p; + if (val->rep_key) + optional |= (1u <<1); + if (val->finished) + optional |= (1u<<2); + return optional; +} +DEFSEQTYPE( fast_response, krb5_fast_response, fast_response_fields, fast_response_optional); + +static const struct field_info fast_rep_fields[] = { + FIELDOF_ENCODEAS(krb5_enc_data, encrypted_data, 0), +}; +DEFSEQTYPE(fast_rep, krb5_enc_data, fast_rep_fields, 0); + +DEFFIELDTYPE(pa_fx_fast_reply, krb5_enc_data, + FIELDOF_ENCODEAS(krb5_enc_data, fast_rep, 0)); + + + + /* Exported complete encoders -- these produce a krb5_data with the encoding in the correct byte order. */ @@ -1243,6 +1327,10 @@ MAKE_FULL_ENCODER(encode_krb5_pa_svr_referral_data, pa_svr_referral_data); MAKE_FULL_ENCODER(encode_krb5_pa_server_referral_data, pa_server_referral_data); MAKE_FULL_ENCODER(encode_krb5_etype_list, etype_list); +MAKE_FULL_ENCODER(encode_krb5_pa_fx_fast_request, pa_fx_fast_request); +MAKE_FULL_ENCODER( encode_krb5_fast_req, fast_req); +MAKE_FULL_ENCODER( encode_krb5_pa_fx_fast_reply, pa_fx_fast_reply); +MAKE_FULL_ENCODER(encode_krb5_fast_response, fast_response); diff --git a/src/lib/krb5/asn.1/krb5_decode.c b/src/lib/krb5/asn.1/krb5_decode.c index b118c453c..32d60915d 100644 --- a/src/lib/krb5/asn.1/krb5_decode.c +++ b/src/lib/krb5/asn.1/krb5_decode.c @@ -94,9 +94,9 @@ if ((var) == NULL) clean_return(ENOMEM) /* process a structure *******************************************/ /* decode an explicit tag and place the number in tagnum */ -#define next_tag() \ +#define next_tag_from_buf(buf) \ { taginfo t2; \ - retval = asn1_get_tag_2(&subbuf, &t2); \ + retval = asn1_get_tag_2(&(buf), &t2); \ if (retval) clean_return(retval); \ asn1class = t2.asn1class; \ construction = t2.construction; \ @@ -104,6 +104,8 @@ if ((var) == NULL) clean_return(ENOMEM) indef = t2.indef; \ taglen = t2.length; \ } +#define next_tag() next_tag_from_buf(subbuf) + static asn1_error_code asn1_get_eoc_tag (asn1buf *buf) @@ -518,6 +520,7 @@ decode_krb5_as_req(const krb5_data *code, krb5_kdc_req **repptr) clear_field(rep,authorization_data.ciphertext.data); clear_field(rep,unenc_authdata); clear_field(rep,second_ticket); + clear_field(rep, kdc_state); check_apptag(10); retval = asn1_decode_kdc_req(&buf,rep); @@ -545,6 +548,7 @@ decode_krb5_tgs_req(const krb5_data *code, krb5_kdc_req **repptr) clear_field(rep,authorization_data.ciphertext.data); clear_field(rep,unenc_authdata); clear_field(rep,second_ticket); + clear_field(rep, kdc_state); check_apptag(12); retval = asn1_decode_kdc_req(&buf,rep); @@ -1080,6 +1084,91 @@ decode_krb5_etype_list(const krb5_data *code, krb5_etype_list **repptr) cleanup(free); } +krb5_error_code decode_krb5_pa_fx_fast_request +(const krb5_data *code, krb5_fast_armored_req **repptr) +{ + setup(krb5_fast_armored_req *); + alloc_field(rep); + clear_field(rep, armor); + { + int indef; + unsigned int taglen; + next_tag_from_buf(buf); + if (tagnum != 0) + clean_return(ASN1_BAD_ID); + } + {begin_structure(); + opt_field(rep->armor, 0, asn1_decode_fast_armor_ptr); + get_field(rep->req_checksum, 1, asn1_decode_checksum); + get_field(rep->enc_part, 2, asn1_decode_encrypted_data); + end_structure();} + rep->magic = KV5M_FAST_ARMORED_REQ; + cleanup(free); +} + +krb5_error_code decode_krb5_fast_req +(const krb5_data *code, krb5_fast_req **repptr) +{ + setup(krb5_fast_req *); + alloc_field(rep); + alloc_field(rep->req_body); + clear_field(rep, req_body->padata); + {begin_structure(); + get_field(rep->fast_options, 0, asn1_decode_int32); + opt_field(rep->req_body->padata, 1, asn1_decode_sequence_of_pa_data); + get_field(*(rep->req_body), 2, asn1_decode_kdc_req_body); + end_structure(); } + rep->magic = KV5M_FAST_REQ; + cleanup_manual(); + error_out: + if (rep) { + if (rep->req_body) + krb5_free_kdc_req(0, rep->req_body); + free(rep); + } + return retval; +} + +krb5_error_code decode_krb5_fast_response +(const krb5_data *code, krb5_fast_response **repptr) +{ + setup(krb5_fast_response *); + + alloc_field(rep); + clear_field(rep, finished); + clear_field(rep, padata); + clear_field(rep,rep_key); + {begin_structure(); + get_field(rep->padata, 0, asn1_decode_sequence_of_pa_data); + opt_field(rep->rep_key, 1, asn1_decode_encryption_key_ptr); + opt_field(rep->finished, 2, asn1_decode_fast_finished_ptr); + get_field(rep->nonce, 3, asn1_decode_int32); + end_structure(); } + rep->magic = KV5M_FAST_RESPONSE; + cleanup(free); +} + +krb5_error_code decode_krb5_pa_fx_fast_reply +(const krb5_data *code, krb5_enc_data **repptr) +{ + setup(krb5_enc_data *); + alloc_field(rep); + { + int indef; + unsigned int taglen; + next_tag_from_buf(buf); + if (tagnum != 0) + clean_return(ASN1_BAD_ID); + } + {begin_structure(); + get_field(*rep, 0, asn1_decode_encrypted_data); + end_structure(); + } + + cleanup(free); +} + + #ifndef DISABLE_PKINIT krb5_error_code decode_krb5_pa_pk_as_req(const krb5_data *code, krb5_pa_pk_as_req **repptr) diff --git a/src/lib/krb5/error_tables/krb5_err.et b/src/lib/krb5/error_tables/krb5_err.et index 71db24ee6..5698f1e4a 100644 --- a/src/lib/krb5/error_tables/krb5_err.et +++ b/src/lib/krb5/error_tables/krb5_err.et @@ -134,7 +134,7 @@ error_code KRB5PLACEHOLD_89, "KRB5 error code 89" error_code KRB5PLACEHOLD_90, "KRB5 error code 90" error_code KRB5PLACEHOLD_91, "KRB5 error code 91" error_code KRB5PLACEHOLD_92, "KRB5 error code 92" -error_code KRB5PLACEHOLD_93, "KRB5 error code 93" +error_code KRB5KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTION, "An unsupported critical FAST option was requested" error_code KRB5PLACEHOLD_94, "KRB5 error code 94" error_code KRB5PLACEHOLD_95, "KRB5 error code 95" error_code KRB5PLACEHOLD_96, "KRB5 error code 96" @@ -347,4 +347,5 @@ error_code KRB5_PLUGIN_NO_HANDLE, "Supplied data not handled by this plugin" error_code KRB5_PLUGIN_OP_NOTSUPP, "Plugin does not support the operaton" error_code KRB5_ERR_INVALID_UTF8, "Invalid UTF-8 string" +error_code KRB5_ERR_FAST_REQUIRED, "FAST protected pre-authentication required but not supported by KDC" end diff --git a/src/lib/krb5/error_tables/kv5m_err.et b/src/lib/krb5/error_tables/kv5m_err.et index 1b79de252..d68398cf9 100644 --- a/src/lib/krb5/error_tables/kv5m_err.et +++ b/src/lib/krb5/error_tables/kv5m_err.et @@ -86,5 +86,7 @@ error_code KV5M_PREDICTED_SAM_RESPONSE, "Bad magic number for krb5_predicted_sam error_code KV5M_PASSWD_PHRASE_ELEMENT, "Bad magic number for passwd_phrase_element" error_code KV5M_GSS_OID, "Bad magic number for GSSAPI OID" error_code KV5M_GSS_QUEUE, "Bad magic number for GSSAPI QUEUE" - +error_code KV5M_FAST_ARMORED_REQ, "Bad magic number for fast armored request" +error_code KV5M_FAST_REQ, "Bad magic number for FAST request" +error_code KV5M_FAST_RESPONSE, "Bad magic number for FAST response" end diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index 23b27e527..acc18f6f4 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -40,6 +40,7 @@ STLIBOBJS= \ enc_helper.o \ encode_kdc.o \ encrypt_tk.o \ + fast.o \ free_rtree.o \ fwd_tgt.o \ gc_frm_kdc.o \ @@ -127,6 +128,7 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \ $(OUTPRE)enc_helper.$(OBJEXT) \ $(OUTPRE)encode_kdc.$(OBJEXT) \ $(OUTPRE)encrypt_tk.$(OBJEXT) \ + $(OUTPRE)fast.$(OBJEXT) \ $(OUTPRE)free_rtree.$(OBJEXT) \ $(OUTPRE)fwd_tgt.$(OBJEXT) \ $(OUTPRE)gc_frm_kdc.$(OBJEXT) \ @@ -215,6 +217,7 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/enc_helper.c \ $(srcdir)/encode_kdc.c \ $(srcdir)/encrypt_tk.c \ + $(srcdir)/fast.c \ $(srcdir)/free_rtree.c \ $(srcdir)/fwd_tgt.c \ $(srcdir)/gc_frm_kdc.c \ diff --git a/src/lib/krb5/krb/fast.c b/src/lib/krb5/krb/fast.c new file mode 100644 index 000000000..e9f54be78 --- /dev/null +++ b/src/lib/krb5/krb/fast.c @@ -0,0 +1,499 @@ +/* + * lib/krb5/krb/fast.c + * + * Copyright (C) 2009 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * + * + */ + +#include + +/* + * It is possible to support sending a request that includes both a + * FAST and normal version. This would complicate the + * pre-authentication logic significantly. You would need to maintain + * two contexts, one for FAST and one for normal use. In adition, you + * would need to manage the security issues surrounding downgrades. + * However trying FAST at all requires an armor key. Generally in + * obtaining the armor key, the client learns enough to know that FAST + * is supported. If not, the client can see FAST in the + * preauth_required error's padata and retry with FAST. So, this + * implementation does not support FAST+normal. + * + * We store the outer version of the request to use . The caller + * stores the inner version. We handle the encoding of the request + * body (and request) and provide encoded request bodies for the + * caller to use as these may be used for checksums. In the AS case + * we also evaluate whether to continue a conversation as one of the + * important questions there is the presence of a cookie. + */ +#include "fast.h" +#include "int-proto.h" + + +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_error_code retval = 0; + krb5_creds creds, *out_creds = NULL; + krb5_auth_context authcontext = NULL; + krb5_data encoded_authenticator; + krb5_fast_armor *armor = NULL; + 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); + if (retval == 0) + retval = krb5_get_credentials(context, 0, ccache, &creds, &out_creds); + if (retval == 0) + retval = krb5_mk_req_extended(context, &authcontext, AP_OPTS_USE_SUBKEY, NULL /*data*/, + out_creds, &encoded_authenticator); + if (retval == 0) + retval = krb5_auth_con_getsendsubkey(context, authcontext, &subkey); + if (retval == 0) + retval = krb5_c_fx_cf2_simple(context, subkey, "subkeyarmor", + &out_creds->keyblock, "ticketarmor", &armor_key); + if (retval == 0) { + armor = calloc(1, sizeof(krb5_fast_armor)); + if (armor == NULL) + retval = ENOMEM; + } + if (retval == 0) { + armor->armor_type = KRB5_FAST_ARMOR_AP_REQUEST; + armor->armor_value = encoded_authenticator; + encoded_authenticator.data = NULL; + encoded_authenticator.length = 0; + state->armor = armor; + armor = NULL; + state->armor_key = armor_key; + armor_key = NULL; + } + krb5_free_keyblock(context, armor_key); + krb5_free_keyblock(context, subkey); + if (out_creds) + krb5_free_creds(context, out_creds); + krb5_free_cred_contents(context, &creds); + if (encoded_authenticator.data) + krb5_free_data_contents(context, &encoded_authenticator); + krb5_auth_con_free(context, authcontext); + return retval; +} + +krb5_error_code +krb5int_fast_prep_req_body(krb5_context context, struct krb5int_fast_request_state *state, + krb5_kdc_req *request, krb5_data **encoded_request_body) +{ + krb5_error_code retval = 0; + krb5_data *local_encoded_request_body = NULL; + assert(state != NULL); + *encoded_request_body = NULL; + if (state->armor_key == NULL) { + return encode_krb5_kdc_req_body(request, encoded_request_body); + } + state->fast_outer_request = *request; + state->fast_outer_request.padata = NULL; + if (retval == 0) + retval = encode_krb5_kdc_req_body(&state->fast_outer_request, + &local_encoded_request_body); + if (retval == 0) { + *encoded_request_body = local_encoded_request_body; + local_encoded_request_body = NULL; + } + if (local_encoded_request_body != NULL) + krb5_free_data(context, local_encoded_request_body); + return retval; +} + +krb5_error_code krb5int_fast_as_armor +(krb5_context context, struct krb5int_fast_request_state *state, + krb5_gic_opt_ext *opte, + krb5_kdc_req *request) +{ + krb5_error_code retval = 0; + krb5_ccache ccache = NULL; + krb5_clear_error_message(context); + if (opte->opt_private->fast_ccache_name) { + retval = krb5_cc_resolve(context, opte->opt_private->fast_ccache_name, + &ccache); + if (retval==0) + retval = fast_armor_ap_request(context, state, ccache, + krb5_princ_realm(context, request->server)); + if (retval != 0) { + const char * errmsg; + errmsg = krb5_get_error_message(context, retval); + if (errmsg) { + krb5_set_error_message(context, retval, "%s constructing AP-REQ armor", errmsg); + krb5_free_error_message(context, errmsg); + } + } + } + if (ccache) + krb5_cc_close(context, ccache); + return retval; +} + + +krb5_error_code +krb5int_fast_prep_req (krb5_context context, struct krb5int_fast_request_state *state, + krb5_kdc_req *request, + const krb5_data *to_be_checksummed, kdc_req_encoder_proc encoder, + krb5_data **encoded_request) +{ + krb5_error_code retval = 0; + krb5_pa_data *pa_array[2]; + krb5_pa_data pa[2]; + krb5_fast_req fast_req; + krb5_fast_armored_req *armored_req = NULL; + krb5_data *encoded_fast_req = NULL; + krb5_data *encoded_armored_req = NULL; + krb5_data *local_encoded_result = NULL; + krb5_cksumtype cksumtype; + krb5_data random_data; + char random_buf[4]; + + + assert(state != NULL); + assert(state->fast_outer_request.padata == NULL); + memset(pa_array, 0, sizeof pa_array); + if (state->armor_key == NULL) { + return encoder(request, encoded_request); + } +/* Fill in a fresh random nonce for each inner request*/ + random_data.length = 4; + random_data.data = (char *)random_buf; + retval = krb5_c_random_make_octets(context, &random_data); + if (retval == 0) { + request->nonce = 0x7fffffff & load_32_n(random_buf); + state->nonce = request->nonce; + } + fast_req.req_body = request; + if (fast_req.req_body->padata == NULL) { + fast_req.req_body->padata = calloc(1, sizeof(krb5_pa_data *)); + if (fast_req.req_body->padata == NULL) + retval = ENOMEM; + } + fast_req.fast_options = state->fast_options; + if (retval == 0) + retval = encode_krb5_fast_req(&fast_req, &encoded_fast_req); + if (retval == 0) { + armored_req = calloc(1, sizeof(krb5_fast_armored_req)); + if (armored_req == NULL) + retval = ENOMEM; + } + if (retval == 0) + armored_req->armor = state->armor; + if (retval == 0) + retval = krb5int_c_mandatory_cksumtype(context, state->armor_key->enctype, + &cksumtype); + if (retval ==0) + retval = krb5_c_make_checksum(context, cksumtype, state->armor_key, + KRB5_KEYUSAGE_FAST_REQ_CHKSUM, to_be_checksummed, + &armored_req->req_checksum); + if (retval == 0) + retval = krb5_encrypt_helper(context, state->armor_key, + KRB5_KEYUSAGE_FAST_ENC, encoded_fast_req, + &armored_req->enc_part); + if (retval == 0) + retval = encode_krb5_pa_fx_fast_request(armored_req, &encoded_armored_req); + if (retval==0) { + pa[0].pa_type = KRB5_PADATA_FX_FAST; + pa[0].contents = (unsigned char *) encoded_armored_req->data; + pa[0].length = encoded_armored_req->length; + pa_array[0] = &pa[0]; + } + state->fast_outer_request.padata = pa_array; + if(retval == 0) + retval = encoder(&state->fast_outer_request, &local_encoded_result); + if (retval == 0) { + *encoded_request = local_encoded_result; + local_encoded_result = NULL; + } + if (encoded_armored_req) + krb5_free_data(context, encoded_armored_req); + if (armored_req) { + armored_req->armor = NULL; /*owned by state*/ + krb5_free_fast_armored_req(context, armored_req); + } + if (encoded_fast_req) + krb5_free_data(context, encoded_fast_req); + if (local_encoded_result) + krb5_free_data(context, local_encoded_result); + state->fast_outer_request.padata = NULL; + return retval; +} + +static krb5_error_code decrypt_fast_reply +(krb5_context context, struct krb5int_fast_request_state *state, + krb5_pa_data **in_padata, + krb5_fast_response **response) +{ + krb5_error_code retval = 0; + krb5_data scratch; + krb5_enc_data *encrypted_response = NULL; + krb5_pa_data *fx_reply = NULL; + krb5_fast_response *local_resp = NULL; + assert(state != NULL); + assert(state->armor_key); + fx_reply = krb5int_find_pa_data(context, in_padata, KRB5_PADATA_FX_FAST); + if (fx_reply == NULL) + retval = KRB5_ERR_FAST_REQUIRED; + if (retval == 0) { + scratch.data = (char *) fx_reply->contents; + scratch.length = fx_reply->length; + retval = decode_krb5_pa_fx_fast_reply(&scratch, &encrypted_response); + } + scratch.data = NULL; + if (retval == 0) { + scratch.data = malloc(encrypted_response->ciphertext.length); + if (scratch.data == NULL) + retval = ENOMEM; + scratch.length = encrypted_response->ciphertext.length; + } + if (retval == 0) + retval = krb5_c_decrypt(context, state->armor_key, + KRB5_KEYUSAGE_FAST_REP, NULL, + encrypted_response, &scratch); + if (retval != 0) { + const char * errmsg; + errmsg = krb5_get_error_message(context, retval); + krb5_set_error_message(context, retval, "%s while decrypting FAST reply", errmsg); + krb5_free_error_message(context, errmsg); + } + if (retval == 0) + retval = decode_krb5_fast_response(&scratch, &local_resp); + if (retval == 0) { + if (local_resp->nonce != state->nonce) { + retval = KRB5_KDCREP_MODIFIED; + krb5_set_error_message(context, retval, "nonce modified in FAST response: KDC response modified"); + } + } + if (retval == 0) { + *response = local_resp; + local_resp = NULL; + } + if (scratch.data) + free(scratch.data); + if (encrypted_response) + krb5_free_enc_data(context, encrypted_response); + return retval; +} + +/* + * FAST separates two concepts: the set of padata we're using to + * decide what pre-auth mechanisms to use and the set of padata we're + * making available to mechanisms in order for them to respond to an + * error. The plugin interface in March 2009 does not permit + * separating these concepts for the plugins. This function makes + * both available for future revisions to the plugin interface. It + * also re-encodes the padata from the current error as a encoded + * typed-data and puts that in the e_data field. That will allow + * existing plugins with the old interface to find the error data. + * The output parameter out_padata contains the padata from the error + * whenever padata is available (all the time with fast). + */ +krb5_error_code +krb5int_fast_process_error(krb5_context context, struct krb5int_fast_request_state *state, + krb5_error **err_replyptr , krb5_pa_data ***out_padata, + krb5_boolean *retry) +{ + krb5_error_code retval = 0; + krb5_error *err_reply = *err_replyptr; + *out_padata = NULL; + *retry = 0; + if (state->armor_key) { + krb5_pa_data *fx_error_pa; + krb5_pa_data **result = NULL; + krb5_data scratch, *encoded_td = NULL; + krb5_error *fx_error = NULL; + krb5_fast_response *fast_response = NULL; + retval = decode_krb5_padata_sequence(&err_reply->e_data, &result); + if (retval == 0) + retval = decrypt_fast_reply(context, state, result, &fast_response); + if (retval) { + /*This can happen if the KDC does not understand FAST. We + * don't expect that, but treating it as the fatal error + * indicated by the KDC seems reasonable. + */ + *retry = 0; + krb5_free_pa_data(context, result); + return 0; + } + krb5_free_pa_data(context, result); + result = NULL; + if (retval == 0) { + fx_error_pa = krb5int_find_pa_data(context, fast_response->padata, KRB5_PADATA_FX_ERROR); + if (fx_error_pa == NULL) { + krb5_set_error_message(context, KRB5KDC_ERR_PREAUTH_FAILED, "Expecting FX_ERROR pa-data inside FAST container"); + retval = KRB5KDC_ERR_PREAUTH_FAILED; + } + } + if (retval == 0) { + scratch.data = (char *) fx_error_pa->contents; + scratch.length = fx_error_pa->length; + retval = decode_krb5_error(&scratch, &fx_error); + } + /* + * krb5_pa_data and krb5_typed_data are safe to cast between: + * they have the same type fields in the same order. + * (krb5_preauthtype is a krb5_int32). If krb5_typed_data is + * ever changed then this will need to be a copy not a cast. + */ + if (retval == 0) + retval = encode_krb5_typed_data( (krb5_typed_data **) fast_response->padata, + &encoded_td); + if (retval == 0) { + fx_error->e_data = *encoded_td; + free(encoded_td); /*contents owned by fx_error*/ + encoded_td = NULL; + krb5_free_error(context, err_reply); + *err_replyptr = fx_error; + fx_error = NULL; + *out_padata = fast_response->padata; + fast_response->padata = NULL; + /* + * If there is more than the fx_error padata, then we want + * to retry the error + */ + *retry = (*out_padata)[1] != NULL; + } + if (fx_error) + krb5_free_error(context, fx_error); + krb5_free_fast_response(context, fast_response); + } else { /*not FAST*/ + *retry = (err_reply->e_data.length > 0); + if ((err_reply->error == KDC_ERR_PREAUTH_REQUIRED + ||err_reply->error == KDC_ERR_PREAUTH_FAILED) && err_reply->e_data.length) { + krb5_pa_data **result = NULL; + retval = decode_krb5_padata_sequence(&err_reply->e_data, &result); + if (retval == 0) + if (retval == 0) { + *out_padata = result; + + return 0; + } + krb5_free_pa_data(context, result); + krb5_set_error_message(context, retval, + "Error decoding padata in error reply"); + return retval; + } + } + return retval; +} + + +krb5_error_code krb5int_fast_process_response +(krb5_context context, struct krb5int_fast_request_state *state, + krb5_kdc_rep *resp, + krb5_keyblock **as_key) +{ + krb5_error_code retval = 0; + krb5_fast_response *fast_response = NULL; + krb5_data *encoded_ticket = NULL; + krb5_boolean cksum_valid; + krb5_clear_error_message(context); + *as_key = NULL; + if (state->armor_key == 0) + return 0; + retval = decrypt_fast_reply(context, state, resp->padata, + &fast_response); + if (retval == 0) { + if (fast_response->finished == 0) { + retval = KRB5_KDCREP_MODIFIED; + krb5_set_error_message(context, retval, "FAST response missing finish message in KDC reply"); + } + } + if (retval == 0) + retval = encode_krb5_ticket(resp->ticket, &encoded_ticket); + if (retval == 0) + retval = krb5_c_verify_checksum(context, state->armor_key, + KRB5_KEYUSAGE_FAST_FINISHED, + encoded_ticket, + &fast_response->finished->ticket_checksum, + &cksum_valid); + if (retval == 0 && cksum_valid == 0) { + retval = KRB5_KDCREP_MODIFIED; + krb5_set_error_message(context, retval, "ticket modified in KDC reply"); + } + if (retval == 0) { + krb5_free_principal(context, resp->client); + resp->client = fast_response->finished->client; + fast_response->finished->client = NULL; + *as_key = fast_response->rep_key; + fast_response->rep_key = NULL; + krb5_free_pa_data(context, resp->padata); + resp->padata = fast_response->padata; + fast_response->padata = NULL; + } + if (fast_response) + krb5_free_fast_response(context, fast_response); + if (encoded_ticket) + krb5_free_data(context, encoded_ticket); + return retval; +} +krb5_error_code +krb5int_fast_make_state( krb5_context context, struct krb5int_fast_request_state **state) +{ + krb5_error_code retval = 0; + struct krb5int_fast_request_state *local_state ; + local_state = malloc(sizeof *local_state); + if (local_state == NULL) + return ENOMEM; + memset(local_state, 0, sizeof(*local_state)); + *state = local_state; + return 0; +} + +void +krb5int_fast_free_state( krb5_context context, struct krb5int_fast_request_state *state) +{ + /*We are responsible for none of the store in the fast_outer_req*/ + krb5_free_keyblock(context, state->armor_key); + krb5_free_fast_armor(context, state->armor); + if (state->cookie) { + free(state->cookie->contents); + free(state->cookie); + state->cookie = NULL; + } + free(state); +} + +krb5_pa_data * krb5int_find_pa_data +(krb5_context context, krb5_pa_data *const *padata, krb5_preauthtype pa_type) +{ + krb5_pa_data * const *tmppa; + + if (padata == NULL) + return NULL; + + for (tmppa = padata; *tmppa != NULL; tmppa++) { + if ((*tmppa)->pa_type == pa_type) + break; + } + + return *tmppa; +} + diff --git a/src/lib/krb5/krb/fast.h b/src/lib/krb5/krb/fast.h new file mode 100644 index 000000000..e21df6504 --- /dev/null +++ b/src/lib/krb5/krb/fast.h @@ -0,0 +1,77 @@ +/* + * lib/krb5/krb/fast.h + * + * Copyright (C) 2009 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * + * <<< Description >>> + */ +#ifndef KRB_FAST_H + +#define KRB_FAST_H + +#include + +struct krb5int_fast_request_state { + krb5_kdc_req fast_outer_request; + krb5_keyblock *armor_key; /*non-null means fast is in use*/ + krb5_fast_armor *armor; + krb5_ui_4 fast_state_flags; + krb5_ui_4 fast_options; + krb5_pa_data *cookie; + krb5_int32 nonce; +}; + +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); + +typedef krb5_error_code(*kdc_req_encoder_proc) (const krb5_kdc_req *, krb5_data **); + +krb5_error_code +krb5int_fast_prep_req (krb5_context context, struct krb5int_fast_request_state *state, + krb5_kdc_req *request, + const krb5_data *to_be_checksummed, kdc_req_encoder_proc encoder, + krb5_data **encoded_request); +krb5_error_code +krb5int_fast_process_error(krb5_context context, struct krb5int_fast_request_state *state, + krb5_error **err_replyptr , krb5_pa_data ***out_padata, + krb5_boolean *retry); + +krb5_error_code krb5int_fast_process_response +(krb5_context context, struct krb5int_fast_request_state *state, + krb5_kdc_rep *resp, + krb5_keyblock **as_key); + +krb5_error_code +krb5int_fast_make_state( krb5_context context, struct krb5int_fast_request_state **state); + +void +krb5int_fast_free_state( krb5_context , struct krb5int_fast_request_state *state); +krb5_error_code krb5int_fast_as_armor +(krb5_context context, struct krb5int_fast_request_state *state, + krb5_gic_opt_ext *opte, + krb5_kdc_req *request); + + +#endif diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index f82e2a81e..30a38cdae 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -32,6 +32,7 @@ #include "k5-int.h" #include "int-proto.h" #include "os-proto.h" +#include "fast.h" #if APPLE_PKINIT #define IN_TKT_DEBUG 0 @@ -967,6 +968,7 @@ krb5_get_init_creds(krb5_context context, krb5_data salt; krb5_data s2kparams; krb5_keyblock as_key; + krb5_keyblock *fast_as_key = NULL; krb5_error *err_reply; krb5_kdc_rep *local_as_reply; krb5_timestamp time_now; @@ -974,6 +976,10 @@ krb5_get_init_creds(krb5_context context, krb5_preauth_client_rock get_data_rock; int canon_flag = 0; krb5_principal_data referred_client; + krb5_boolean retry = 0; + struct krb5int_fast_request_state *fast_state = NULL; + krb5_pa_data **out_padata = NULL; + /* initialize everything which will be freed at cleanup */ @@ -988,7 +994,7 @@ krb5_get_init_creds(krb5_context context, preauth_to_use = NULL; kdc_padata = NULL; as_key.length = 0; - salt.length = 0; + salt.length = 0; salt.data = NULL; local_as_reply = 0; @@ -1002,6 +1008,9 @@ krb5_get_init_creds(krb5_context context, referred_client = *client; referred_client.realm.data = NULL; referred_client.realm.length = 0; + ret = krb5int_fast_make_state(context, &fast_state); + if (ret) + goto cleanup; /* * Set up the basic request structure @@ -1231,15 +1240,20 @@ krb5_get_init_creds(krb5_context context, /* XXX Yuck. Old version. */ request.nonce = (krb5_int32) time_now; } + ret = krb5int_fast_as_armor(context, fast_state, options, &request); + if (ret != 0) + goto cleanup; /* give the preauth plugins a chance to prep the request body */ krb5_preauth_prepare_request(context, options, &request); - ret = encode_krb5_kdc_req_body(&request, &encoded_request_body); + ret = krb5int_fast_prep_req_body(context, fast_state, + &request, &encoded_request_body); if (ret) goto cleanup; get_data_rock.magic = CLIENT_ROCK_MAGIC; - get_data_rock.as_reply = NULL; - + get_data_rock.etype = &etype; + get_data_rock.fast_state = fast_state; + /* now, loop processing preauth data and talking to the kdc */ for (loopcount = 0; loopcount < MAX_IN_TKT_LOOPS; loopcount++) { if (request.padata) { @@ -1258,6 +1272,10 @@ krb5_get_init_creds(krb5_context context, gak_fct, gak_data, &get_data_rock, options))) goto cleanup; + if (out_padata) { + krb5_free_pa_data(context, out_padata); + out_padata = NULL; + } } else { if (preauth_to_use != NULL) { /* @@ -1293,7 +1311,9 @@ krb5_get_init_creds(krb5_context context, krb5_free_data(context, encoded_previous_request); encoded_previous_request = NULL; } - ret = encode_krb5_as_req(&request, &encoded_previous_request); + ret = krb5int_fast_prep_req(context, fast_state, + &request, encoded_request_body, + encode_krb5_as_req, &encoded_previous_request); if (ret) goto cleanup; @@ -1305,15 +1325,19 @@ krb5_get_init_creds(krb5_context context, goto cleanup; if (err_reply) { - if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED && - err_reply->e_data.length > 0) { + ret = krb5int_fast_process_error(context, fast_state, &err_reply, + &out_padata, &retry); + if (ret !=0) + goto cleanup; + if ((err_reply->error == KDC_ERR_PREAUTH_REQUIRED ||err_reply->error == KDC_ERR_PREAUTH_FAILED) +&& retry) { /* reset the list of preauth types to try */ if (preauth_to_use) { krb5_free_pa_data(context, preauth_to_use); preauth_to_use = NULL; } - ret = decode_krb5_padata_sequence(&err_reply->e_data, - &preauth_to_use); + preauth_to_use = out_padata; + out_padata = NULL; krb5_free_error(context, err_reply); err_reply = NULL; if (ret) @@ -1345,7 +1369,7 @@ krb5_get_init_creds(krb5_context context, goto cleanup; request.client = &referred_client; } else { - if (err_reply->e_data.length > 0) { + if (retry) { /* continue to next iteration */ } else { /* error + no hints = give up */ @@ -1374,10 +1398,14 @@ krb5_get_init_creds(krb5_context context, /* process any preauth data in the as_reply */ krb5_clear_preauth_context_use_counts(context); + ret = krb5int_fast_process_response(context, fast_state, + local_as_reply, &fast_as_key); + if (ret) + goto cleanup; if ((ret = sort_krb5_padata_sequence(context, &request.server->realm, local_as_reply->padata))) goto cleanup; - get_data_rock.as_reply = local_as_reply; + etype = local_as_reply->enc_part.enctype; if ((ret = krb5_do_preauth(context, &request, encoded_request_body, encoded_previous_request, @@ -1419,8 +1447,14 @@ krb5_get_init_creds(krb5_context context, it. If decrypting the as_rep fails, or if there isn't an as_key at all yet, then use the gak_fct to get one, and try again. */ - - if (as_key.length) + if (fast_as_key) { + if (as_key.length) + krb5_free_keyblock_contents(context, &as_key); + as_key = *fast_as_key; + free(fast_as_key); + fast_as_key = NULL; + } + if (as_key.length) ret = decrypt_as_reply(context, NULL, local_as_reply, NULL, NULL, &as_key, krb5_kdc_rep_decrypt_proc, NULL); @@ -1477,6 +1511,11 @@ cleanup: } } krb5_preauth_request_context_fini(context); + krb5_free_keyblock(context, fast_as_key); + if (fast_state) + krb5int_fast_free_state(context, fast_state); + if (out_padata) + krb5_free_pa_data(context, out_padata); if (encoded_previous_request != NULL) { krb5_free_data(context, encoded_previous_request); encoded_previous_request = NULL; diff --git a/src/lib/krb5/krb/gic_opt.c b/src/lib/krb5/krb/gic_opt.c index 348637ca3..72203f0e7 100644 --- a/src/lib/krb5/krb/gic_opt.c +++ b/src/lib/krb5/krb/gic_opt.c @@ -146,6 +146,8 @@ krb5int_gic_opte_private_free(krb5_context context, krb5_gic_opt_ext *opte) /* Free up any private stuff */ if (opte->opt_private->preauth_data != NULL) free_gic_opt_ext_preauth_data(context, opte); + if (opte->opt_private->fast_ccache_name) + free(opte->opt_private->fast_ccache_name); free(opte->opt_private); opte->opt_private = NULL; return 0; @@ -465,3 +467,21 @@ krb5_get_init_creds_opt_free_pa(krb5_context context, } free(preauth_data); } +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 *ccache_name) +{ + 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_ccache_name"); + if (retval) + return retval; + if (opte->opt_private->fast_ccache_name) { + free(opte->opt_private->fast_ccache_name); + } + opte->opt_private->fast_ccache_name = strdup(ccache_name); + if (opte->opt_private->fast_ccache_name == NULL) + retval = ENOMEM; + return retval; +} diff --git a/src/lib/krb5/krb/kfree.c b/src/lib/krb5/krb/kfree.c index e0e71746d..d17d46bc7 100644 --- a/src/lib/krb5/krb/kfree.c +++ b/src/lib/krb5/krb/kfree.c @@ -54,6 +54,7 @@ */ #include "k5-int.h" +#include void KRB5_CALLCONV krb5_free_address(krb5_context context, krb5_address *val) @@ -344,6 +345,7 @@ krb5_free_kdc_req(krb5_context context, krb5_kdc_req *val) { if (val == NULL) return; + assert( val->kdc_state == NULL); krb5_free_pa_data(context, val->padata); krb5_free_principal(context, val->client); krb5_free_principal(context, val->server); @@ -795,3 +797,63 @@ krb5_free_etype_list(krb5_context context, free(etypes); } } +void krb5_free_fast_req(krb5_context context, krb5_fast_req *val) +{ + if (val == NULL) + return; + krb5_free_kdc_req(context, val->req_body); + free(val); +} + +void krb5_free_fast_armor(krb5_context context, krb5_fast_armor *val) +{ + if (val == NULL) + return; + krb5_free_data_contents(context, &val->armor_value); + free(val); +} + +void krb5_free_fast_response(krb5_context context, krb5_fast_response *val) +{ + if (!val) + return; + krb5_free_pa_data(context, val->padata); + krb5_free_fast_finished(context, val->finished); + free(val); +} + +void krb5_free_fast_finished +(krb5_context context, krb5_fast_finished *val) +{ + if (!val) + return; + krb5_free_principal(context, val->client); + krb5_free_checksum_contents(context, &val->ticket_checksum); + free(val); +} + +void krb5_free_typed_data(krb5_context context, krb5_typed_data **in) +{ + int i = 0; + if (in == NULL) return; + while (in[i] != NULL) { + if (in[i]->data != NULL) + free(in[i]->data); + free(in[i]); + i++; + } + free(in); +} + +void krb5_free_fast_armored_req(krb5_context context, + krb5_fast_armored_req *val) +{ + if (val == NULL) + return; + if (val->armor) + krb5_free_fast_armor(context, val->armor); + krb5_free_data_contents(context, &val->enc_part.ciphertext); + if (val->req_checksum.contents) + krb5_free_checksum_contents(context, &val->req_checksum); + free(val); +} diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c index ccb4af234..4c7dd5e60 100644 --- a/src/lib/krb5/krb/preauth2.c +++ b/src/lib/krb5/krb/preauth2.c @@ -37,6 +37,7 @@ #include "osconf.h" #include #include "int-proto.h" +#include "fast.h" #if !defined(_WIN32) #include @@ -419,6 +420,7 @@ client_data_proc(krb5_context kcontext, krb5_data **retdata) { krb5_data *ret; + krb5_error_code retval; char *data; if (rock->magic != CLIENT_ROCK_MAGIC) @@ -430,8 +432,6 @@ client_data_proc(krb5_context kcontext, case krb5plugin_preauth_client_get_etype: { krb5_enctype *eptr; - if (rock->as_reply == NULL) - return ENOENT; ret = malloc(sizeof(krb5_data)); if (ret == NULL) return ENOMEM; @@ -443,7 +443,7 @@ client_data_proc(krb5_context kcontext, ret->data = data; ret->length = sizeof(krb5_enctype); eptr = (krb5_enctype *)data; - *eptr = rock->as_reply->enc_part.enctype; + *eptr = *rock->etype; *retdata = ret; return 0; } @@ -457,7 +457,38 @@ client_data_proc(krb5_context kcontext, free(ret); return 0; break; - default: + case krb5plugin_preauth_client_fast_armor: { + krb5_keyblock *key = NULL; + ret = calloc(1, sizeof(krb5_data)); + if (ret == NULL) + return ENOMEM; + retval = 0; + if (rock->fast_state->armor_key) + retval = krb5_copy_keyblock(kcontext, rock->fast_state->armor_key, + &key); + if (retval == 0) { + ret->data = (char *) key; + ret->length = key?sizeof(krb5_keyblock):0; + key = NULL; + } + if (retval == 0) { + *retdata = ret; + ret = NULL; + } + if (ret) + free(ret); + return retval; + } + case krb5plugin_preauth_client_free_fast_armor: + ret = *retdata; + if (ret) { + if (ret->data) + krb5_free_keyblock(kcontext, (krb5_keyblock *) ret->data); + free(ret); + *retdata = NULL; + } + return 0; + default: return EINVAL; } } diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index c41d2e811..f30b57114 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -19,6 +19,8 @@ decode_krb5_encryption_key decode_krb5_error decode_krb5_etype_info decode_krb5_etype_info2 +decode_krb5_fast_req +decode_krb5_pa_fx_fast_request decode_krb5_kdc_req_body decode_krb5_pa_enc_ts decode_krb5_pa_for_user @@ -36,6 +38,7 @@ decode_krb5_setpw_req decode_krb5_tgs_rep decode_krb5_tgs_req decode_krb5_ticket +decode_krb5_typed_data encode_krb5_alt_method encode_krb5_ap_rep encode_krb5_ap_rep_enc_part @@ -56,6 +59,8 @@ encode_krb5_encryption_key encode_krb5_error encode_krb5_etype_info encode_krb5_etype_info2 +encode_krb5_fast_response +encode_krb5_pa_fx_fast_reply encode_krb5_kdc_req_body encode_krb5_pa_enc_ts encode_krb5_pa_for_user @@ -226,6 +231,8 @@ krb5_free_enc_tkt_part krb5_free_error krb5_free_error_message krb5_free_etype_info +krb5_free_fast_armored_req +krb5_free_fast_req krb5_free_host_realm krb5_free_kdc_rep krb5_free_kdc_req @@ -266,6 +273,7 @@ krb5_free_tgt_creds krb5_free_ticket krb5_free_tickets krb5_free_tkt_authent +krb5_free_typed_data krb5_free_unparsed_name krb5_fwd_tgt_creds krb5_gen_portaddr @@ -300,6 +308,7 @@ krb5_get_init_creds_opt_set_address_list 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_forwardable krb5_get_init_creds_opt_set_pa krb5_get_init_creds_opt_set_preauth_list @@ -516,6 +525,8 @@ krb5int_cc_default krb5int_cleanup_library krb5int_cm_call_select krb5int_copy_data_contents_add0 +krb5int_find_authdata +krb5int_find_pa_data krb5int_foreach_localaddr krb5int_free_addrlist krb5int_init_context_kdc diff --git a/src/lib/krb5/os/accessor.c b/src/lib/krb5/os/accessor.c index 1bf171a6f..bb66568db 100644 --- a/src/lib/krb5/os/accessor.c +++ b/src/lib/krb5/os/accessor.c @@ -132,6 +132,13 @@ krb5int_accessor(krb5int_access *internals, krb5_int32 version) S (encode_krb5_sam_response_2, encode_krb5_sam_response_2), S (encode_krb5_enc_sam_response_enc_2, encode_krb5_enc_sam_response_enc_2), + S (encode_enc_ts, encode_krb5_pa_enc_ts), + S (decode_enc_ts, decode_krb5_pa_enc_ts), + S (encode_enc_data, encode_krb5_enc_data), + S(decode_enc_data, decode_krb5_enc_data), + S(free_enc_ts, krb5_free_pa_enc_ts), + S(free_enc_data, krb5_free_enc_data), + S(encrypt_helper, krb5_encrypt_helper), #if DESIGNATED_INITIALIZERS }; diff --git a/src/plugins/preauth/encrypted_challenge/Makefile.in b/src/plugins/preauth/encrypted_challenge/Makefile.in new file mode 100644 index 000000000..8f6a8a640 --- /dev/null +++ b/src/plugins/preauth/encrypted_challenge/Makefile.in @@ -0,0 +1,41 @@ +thisconfigdir=../../.. +myfulldir=plugins/preauth/encrypted_challenge +mydir=plugins/preauth/encrypted_challenge +BUILDTOP=$(REL)..$(S)..$(S).. +KRB5_RUN_ENV = @KRB5_RUN_ENV@ +KRB5_CONFIG_SETUP = KRB5_CONFIG=$(SRCTOP)/config-files/krb5.conf ; export KRB5_CONFIG ; +PROG_LIBPATH=-L$(TOPLIBD) +PROG_RPATH=$(KRB5_LIBDIR) +MODULE_INSTALL_DIR = $(KRB5_PA_MODULE_DIR) +DEFS=@DEFS@ + +LOCALINCLUDES = -I../../../include/krb5 -I. + +LIBBASE=encrypted_challenge +LIBMAJOR=0 +LIBMINOR=0 +SO_EXT=.so +RELDIR=../plugins/preauth/encrypted_challenge +# Depends on libk5crypto and libkrb5 +SHLIB_EXPDEPS = \ + $(TOPLIBD)/libk5crypto$(SHLIBEXT) \ + $(TOPLIBD)/libkrb5$(SHLIBEXT) +SHLIB_EXPLIBS= -lkrb5 -lcom_err -lk5crypto $(SUPPORT_LIB) $(LIBS) + +SHLIB_DIRS=-L$(TOPLIBD) +SHLIB_RDIRS=$(KRB5_LIBDIR) +STOBJLISTS=OBJS.ST +STLIBOBJS=encrypted_challenge_main.o + +SRCS= $(srcdir)/encrypted_challenge_main.c + +all-unix:: $(LIBBASE)$(SO_EXT) +install-unix:: install-libs +clean-unix:: clean-libs clean-libobjs + +clean:: + $(RM) lib$(LIBBASE)$(SO_EXT) + +@libnover_frag@ +@libobj_frag@ + diff --git a/src/plugins/preauth/encrypted_challenge/deps b/src/plugins/preauth/encrypted_challenge/deps new file mode 100644 index 000000000..e69de29bb diff --git a/src/plugins/preauth/encrypted_challenge/encrypted_challenge.exports b/src/plugins/preauth/encrypted_challenge/encrypted_challenge.exports new file mode 100644 index 000000000..98e96c399 --- /dev/null +++ b/src/plugins/preauth/encrypted_challenge/encrypted_challenge.exports @@ -0,0 +1,2 @@ +preauthentication_client_1 +preauthentication_server_1 diff --git a/src/plugins/preauth/encrypted_challenge/encrypted_challenge_main.c b/src/plugins/preauth/encrypted_challenge/encrypted_challenge_main.c new file mode 100644 index 000000000..3fad7ccd3 --- /dev/null +++ b/src/plugins/preauth/encrypted_challenge/encrypted_challenge_main.c @@ -0,0 +1,409 @@ +/* + * plugins/preauth/encrypted_challenge/encrypted_challenge.c + * + * Copyright (C) 2009 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * + * + * Implement EncryptedChallenge fast factor from draft-ietf-krb-wg-preauth-framework + */ + +#include +#include "../fast_factor.h" + +#include + +static int preauth_flags +(krb5_context context, krb5_preauthtype pa_type) +{ + return PA_REAL; +} + +static krb5_error_code process_preauth +(krb5_context context, + void *plugin_context, + void *request_context, + krb5_get_init_creds_opt *opt, + preauth_get_client_data_proc get_data_proc, + struct _krb5_preauth_client_rock *rock, + krb5_kdc_req *request, + krb5_data *encoded_request_body, + krb5_data *encoded_previous_request, + krb5_pa_data *padata, + krb5_prompter_fct prompter, + void *prompter_data, + preauth_get_as_key_proc gak_fct, + void *gak_data, + krb5_data *salt, + krb5_data *s2kparams, + krb5_keyblock *as_key, + krb5_pa_data ***out_padata) +{ + krb5_error_code retval = 0; + krb5_enctype enctype = 0; + krb5_keyblock *challenge_key = NULL, *armor_key = NULL; + krb5_data *etype_data = NULL; + krb5int_access kaccess; + + if (krb5int_accessor(&kaccess, KRB5INT_ACCESS_VERSION) != 0) + return 0; + retval = fast_get_armor_key(context, get_data_proc, rock, &armor_key); + if (retval || armor_key == NULL) + return 0; + retval = get_data_proc(context, rock, krb5plugin_preauth_client_get_etype, &etype_data); + if (retval == 0) { + enctype = *((krb5_enctype *)etype_data->data); + if (as_key->length == 0 ||as_key->enctype != enctype) + retval = gak_fct(context, request->client, + enctype, prompter, prompter_data, + salt, s2kparams, + as_key, gak_data); + } + if (padata->length) { + krb5_enc_data *enc = NULL; + krb5_data scratch; + scratch.length = padata->length; + scratch.data = (char *) padata->contents; + if (retval == 0) + retval = krb5_c_fx_cf2_simple(context,armor_key, "kdcchallengearmor", + as_key, "challengelongterm", &challenge_key); + if (retval == 0) + retval =kaccess.decode_enc_data(&scratch, &enc); + scratch.data = NULL; + if (retval == 0) { + scratch.data = malloc(enc->ciphertext.length); + scratch.length = enc->ciphertext.length; + if (scratch.data == NULL) + retval = ENOMEM; + } + if (retval == 0) + retval = krb5_c_decrypt(context, challenge_key, + KRB5_KEYUSAGE_ENC_CHALLENGE_KDC, NULL, + enc, &scratch); +/*Per draft 11 of the preauth framework, the client MAY but + * is not required to actually check the timestamp from the KDC other than + * to confirm it decrypts. This code does not perform that check. + */ + if (scratch.data) + krb5_free_data_contents(context, &scratch); + if (retval == 0) + fast_set_kdc_verified(context, get_data_proc, rock); + if (enc) + kaccess.free_enc_data(context, enc); + } else { /*No padata; we send*/ + krb5_enc_data enc; + krb5_pa_data *pa = NULL; + krb5_pa_data **pa_array = NULL; + krb5_data *encoded_ts = NULL; + krb5_pa_enc_ts ts; + if (retval == 0) + retval = krb5_us_timeofday(context, &ts.patimestamp, &ts.pausec); + if (retval == 0) + retval = kaccess.encode_enc_ts(&ts, &encoded_ts); + if (retval == 0) + retval = krb5_c_fx_cf2_simple(context, + armor_key, "clientchallengearmor", + as_key, "challengelongterm", + &challenge_key); + if (retval == 0) + retval = kaccess.encrypt_helper(context, challenge_key, + KRB5_KEYUSAGE_ENC_CHALLENGE_CLIENT, + encoded_ts, &enc); + if (encoded_ts) + krb5_free_data(context, encoded_ts); + encoded_ts = NULL; + if (retval == 0) { + retval = kaccess.encode_enc_data(&enc, &encoded_ts); + krb5_free_data_contents(context, &enc.ciphertext); + } + if (retval == 0) { + pa = calloc(1, sizeof(krb5_pa_data)); + if (pa == NULL) + retval = ENOMEM; + } + if (retval == 0) { + pa_array = calloc(2, sizeof(krb5_pa_data *)); + if (pa_array == NULL) + retval = ENOMEM; + } + if (retval == 0) { + pa->length = encoded_ts->length; + pa->contents = (unsigned char *) encoded_ts->data; + pa->pa_type = KRB5_PADATA_ENCRYPTED_CHALLENGE; + free(encoded_ts); + encoded_ts = NULL; + pa_array[0] = pa; + pa = NULL; + *out_padata = pa_array; + pa_array = NULL; + } + if (pa) + free(pa); + if (encoded_ts) + krb5_free_data(context, encoded_ts); + if (pa_array) + free(pa_array); + } + if (challenge_key) + krb5_free_keyblock(context, challenge_key); + if (armor_key) + krb5_free_keyblock(context, armor_key); + if (etype_data != NULL) + get_data_proc(context, rock, krb5plugin_preauth_client_free_etype, + &etype_data); + return retval; +} + + + + +static krb5_error_code kdc_include_padata +(krb5_context context, + krb5_kdc_req *request, + struct _krb5_db_entry_new *client, + struct _krb5_db_entry_new *server, + preauth_get_entry_data_proc get_entry_proc, + void *pa_module_context, + krb5_pa_data *data) +{ + krb5_error_code retval = 0; + krb5_keyblock *armor_key = NULL; + retval = fast_kdc_get_armor_key(context, get_entry_proc, request, client, &armor_key); + if (retval) + return retval; + if (armor_key == 0) + return ENOENT; + krb5_free_keyblock(context, armor_key); + return 0; +} + +static krb5_error_code kdc_verify_preauth +(krb5_context context, + struct _krb5_db_entry_new *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_enc_tkt_part *enc_tkt_reply, + krb5_pa_data *data, + preauth_get_entry_data_proc get_entry_proc, + void *pa_module_context, + void **pa_request_context, + krb5_data **e_data, + krb5_authdata ***authz_data) +{ + krb5_error_code retval = 0; + krb5_timestamp now; + krb5_enc_data *enc = NULL; + krb5_data scratch, plain; + krb5_keyblock *armor_key = NULL; + krb5_pa_enc_ts *ts = NULL; + krb5int_access kaccess; + krb5_keyblock *client_keys = NULL; + krb5_data *client_data = NULL; + krb5_keyblock *challenge_key = NULL; + int i; + + plain.data = NULL; + if (krb5int_accessor(&kaccess, KRB5INT_ACCESS_VERSION) != 0) + return 0; + + retval = fast_kdc_get_armor_key(context, get_entry_proc, request, client, &armor_key); + if (retval == 0 &&armor_key == NULL) { + retval = ENOENT; + krb5_set_error_message(context, ENOENT, "Encrypted Challenge used outside of FAST tunnel"); + } + scratch.data = (char *) data->contents; + scratch.length = data->length; + if (retval == 0) + retval = kaccess.decode_enc_data(&scratch, &enc); + if (retval == 0) { + plain.data = malloc(enc->ciphertext.length); + plain.length = enc->ciphertext.length; + if (plain.data == NULL) + retval = ENOMEM; + } + if (retval == 0) + retval = get_entry_proc(context, request, client, + krb5plugin_preauth_keys, &client_data); + if (retval == 0) { + client_keys = (krb5_keyblock *) client_data->data; + for (i = 0; client_keys[i].enctype&& (retval == 0); i++ ) { + retval = krb5_c_fx_cf2_simple(context, + armor_key, "clientchallengearmor", + &client_keys[i], "challengelongterm", + &challenge_key); + if (retval == 0) + retval = krb5_c_decrypt(context, challenge_key, + KRB5_KEYUSAGE_ENC_CHALLENGE_CLIENT, + NULL, enc, &plain); + if (challenge_key) + krb5_free_keyblock(context, challenge_key); + challenge_key = NULL; + if (retval == 0) + break; + /*We failed to decrypt. Try next key*/ + retval = 0; + krb5_free_keyblock_contents(context, &client_keys[i]); + } + if (client_keys[i].enctype == 0) { + retval = KRB5KDC_ERR_PREAUTH_FAILED; + krb5_set_error_message(context, retval, "Incorrect password in encrypted challenge"); + } else { /*not run out of keys*/ + int j; + assert (retval == 0); + for (j = i+1; client_keys[j].enctype; j++) + krb5_free_keyblock_contents(context, &client_keys[j]); + } + + } + if (retval == 0) + retval = kaccess.decode_enc_ts(&plain, &ts); + if (retval == 0) + retval = krb5_timeofday(context, &now); + if (retval == 0) { + if (labs(now-ts->patimestamp) < context->clockskew) { + enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH; +/*If this fails, we won't generate a reply to the client. That may + * cause the client to fail, but at this point the KDC has considered + this a success, so the return value is ignored. */ + fast_kdc_replace_reply_key(context, get_entry_proc, request); + krb5_c_fx_cf2_simple(context, armor_key, "kdcchallengearmor", + &client_keys[i], "challengelongterm", + (krb5_keyblock **) pa_request_context); + } else { /*skew*/ + retval = KRB5KRB_AP_ERR_SKEW; + } + } + if (client_keys) { + if (client_keys[i].enctype) + krb5_free_keyblock_contents(context, &client_keys[i]); + krb5_free_data(context, client_data); + } + if (armor_key) + krb5_free_keyblock(context, armor_key); + if (challenge_key) + krb5_free_keyblock(context, challenge_key); + if (plain.data) + free(plain.data); + if (enc) + kaccess.free_enc_data(context, enc); + if (ts) + kaccess.free_enc_ts(context, ts); + return retval; +} + +static krb5_error_code kdc_return_preauth +(krb5_context context, + krb5_pa_data * padata, + struct _krb5_db_entry_new *client, + krb5_data *req_pkt, + krb5_kdc_req *request, + krb5_kdc_rep *reply, + struct _krb5_key_data *client_keys, + krb5_keyblock *encrypting_key, + krb5_pa_data **send_pa, + preauth_get_entry_data_proc get_entry_proc, + void *pa_module_context, + void **pa_request_context) +{ + krb5_error_code retval = 0; + krb5_keyblock *challenge_key = *pa_request_context; + krb5_pa_enc_ts ts; + krb5_data *plain = NULL; + krb5_enc_data enc; + krb5_data *encoded = NULL; + krb5_pa_data *pa = NULL; + krb5int_access kaccess; + + if (krb5int_accessor(&kaccess, KRB5INT_ACCESS_VERSION) != 0) + return 0; + if (challenge_key == NULL) + return 0; + * pa_request_context = NULL; /*this function will free the + * challenge key*/ + retval = krb5_us_timeofday(context, &ts.patimestamp, &ts.pausec); + if (retval == 0) + retval = kaccess.encode_enc_ts(&ts, &plain); + if (retval == 0) + retval = kaccess.encrypt_helper(context, challenge_key, + KRB5_KEYUSAGE_ENC_CHALLENGE_KDC, + plain, &enc); + if (retval == 0) + retval = kaccess.encode_enc_data(&enc, &encoded); + if (retval == 0) { + pa = calloc(1, sizeof(krb5_pa_data)); + if (pa == NULL) + retval = ENOMEM; + } + if (retval == 0) { + pa->pa_type = KRB5_PADATA_ENCRYPTED_CHALLENGE; + pa->contents = (unsigned char *) encoded->data; + pa->length = encoded->length; + encoded->data = NULL; + *send_pa = pa; + pa = NULL; + } + if (challenge_key) + krb5_free_keyblock(context, challenge_key); + if (encoded) + krb5_free_data(context, encoded); + if (plain) + krb5_free_data(context, plain); + if (enc.ciphertext.data) + krb5_free_data_contents(context, &enc.ciphertext); + return retval; +} + +static int kdc_preauth_flags +(krb5_context context, krb5_preauthtype patype) +{ + return 0; +} + +krb5_preauthtype supported_pa_types[] = { + KRB5_PADATA_ENCRYPTED_CHALLENGE, 0}; + +struct krb5plugin_preauth_server_ftable_v1 preauthentication_server_1 = { + "Encrypted challenge", + &supported_pa_types[0], +NULL, +NULL, +kdc_preauth_flags, + kdc_include_padata, + kdc_verify_preauth, + kdc_return_preauth, +NULL +}; + +struct krb5plugin_preauth_client_ftable_v1 preauthentication_client_1 = { + "Encrypted Challenge", /* name */ + &supported_pa_types[0], /* pa_type_list */ + NULL, /* enctype_list */ + NULL, /* plugin init function */ + NULL, /* plugin fini function */ + preauth_flags, /* get flags function */ + NULL, /* request init function */ + NULL, /* request fini function */ + process_preauth, /* process function */ + NULL, /* try_again function */ +NULL /* get init creds opt function */ +}; diff --git a/src/plugins/preauth/fast_factor.h b/src/plugins/preauth/fast_factor.h new file mode 100644 index 000000000..4059b2876 --- /dev/null +++ b/src/plugins/preauth/fast_factor.h @@ -0,0 +1,53 @@ +/*Returns success with a null armor_key if FAST is available but not in use. +Returns failure if the client library does not support FAST +*/ +static krb5_error_code fast_get_armor_key +(krb5_context context, preauth_get_client_data_proc get_data, + struct _krb5_preauth_client_rock *rock, + krb5_keyblock **armor_key) +{ + krb5_error_code retval = 0; + krb5_data *data; + retval = get_data(context, rock, krb5plugin_preauth_client_fast_armor, &data); + if (retval == 0) { + *armor_key = (krb5_keyblock *) data->data; + data->data = NULL; + get_data(context, rock, krb5plugin_preauth_client_free_fast_armor, + &data); + } + return retval; +} + +static krb5_error_code fast_kdc_get_armor_key +(krb5_context context, preauth_get_entry_data_proc get_entry, + krb5_kdc_req *request,struct _krb5_db_entry_new *client, + krb5_keyblock **armor_key) +{ + krb5_error_code retval; + krb5_data *data; + retval = get_entry(context, request, client, krb5plugin_preauth_fast_armor, + &data); + if (retval == 0) { + *armor_key = (krb5_keyblock *) data->data; + data->data = NULL; + get_entry(context, request, client, + krb5plugin_preauth_free_fast_armor, &data); + } + return retval; + } + + + + static krb5_error_code fast_kdc_replace_reply_key + (krb5_context context, preauth_get_entry_data_proc get_data, + krb5_kdc_req *request) + { + return 0; + } + +static krb5_error_code fast_set_kdc_verified +(krb5_context context, preauth_get_client_data_proc get_data, + struct _krb5_preauth_client_rock *rock) +{ + return 0; +} -- 2.26.2