967 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			967 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2011 Kungliga Tekniska Högskolan
 | |
|  * (Royal Institute of Technology, Stockholm, Sweden).
 | |
|  * All rights reserved.
 | |
|  *
 | |
|  * Redistribution and use in source and binary forms, with or without
 | |
|  * modification, are permitted provided that the following conditions
 | |
|  * are met:
 | |
|  *
 | |
|  * 1. Redistributions of source code must retain the above copyright
 | |
|  *    notice, this list of conditions and the following disclaimer.
 | |
|  *
 | |
|  * 2. Redistributions in binary form must reproduce the above copyright
 | |
|  *    notice, this list of conditions and the following disclaimer in the
 | |
|  *    documentation and/or other materials provided with the distribution.
 | |
|  *
 | |
|  * 3. Neither the name of the Institute nor the names of its contributors
 | |
|  *    may be used to endorse or promote products derived from this software
 | |
|  *    without specific prior written permission.
 | |
|  *
 | |
|  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 | |
|  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | |
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | |
|  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 | |
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 | |
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 | |
|  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 | |
|  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | |
|  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 | |
|  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 | |
|  * SUCH DAMAGE.
 | |
|  */
 | |
| 
 | |
| #include "krb5_locl.h"
 | |
| #ifndef WIN32
 | |
| #include <heim-ipc.h>
 | |
| #endif
 | |
| 
 | |
| KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
 | |
| _krb5_fast_cf2(krb5_context context,
 | |
| 	       krb5_keyblock *key1,
 | |
| 	       const char *pepper1,
 | |
| 	       krb5_keyblock *key2,
 | |
| 	       const char *pepper2,
 | |
| 	       krb5_keyblock *armorkey,
 | |
| 	       krb5_crypto *armor_crypto)
 | |
| {
 | |
|     krb5_crypto crypto1, crypto2;
 | |
|     krb5_data pa1, pa2;
 | |
|     krb5_error_code ret;
 | |
| 
 | |
|     ret = krb5_crypto_init(context, key1, 0, &crypto1);
 | |
|     if (ret)
 | |
| 	return ret;
 | |
| 
 | |
|     ret = krb5_crypto_init(context, key2, 0, &crypto2);
 | |
|     if (ret) {
 | |
| 	krb5_crypto_destroy(context, crypto1);
 | |
| 	return ret;
 | |
|     }
 | |
| 
 | |
|     pa1.data = rk_UNCONST(pepper1);
 | |
|     pa1.length = strlen(pepper1);
 | |
|     pa2.data = rk_UNCONST(pepper2);
 | |
|     pa2.length = strlen(pepper2);
 | |
| 
 | |
|     ret = krb5_crypto_fx_cf2(context, crypto1, crypto2, &pa1, &pa2,
 | |
| 			     key1->keytype, armorkey);
 | |
|     krb5_crypto_destroy(context, crypto1);
 | |
|     krb5_crypto_destroy(context, crypto2);
 | |
|     if (ret)
 | |
| 	return ret;
 | |
| 
 | |
|     if (armor_crypto) {
 | |
| 	ret = krb5_crypto_init(context, armorkey, 0, armor_crypto);
 | |
| 	if (ret)
 | |
| 	    krb5_free_keyblock_contents(context, armorkey);
 | |
|     }
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
 | |
| _krb5_fast_armor_key(krb5_context context,
 | |
| 		     krb5_keyblock *subkey,
 | |
| 		     krb5_keyblock *sessionkey,
 | |
| 		     krb5_keyblock *armorkey,
 | |
| 		     krb5_crypto *armor_crypto)
 | |
| {
 | |
|     return _krb5_fast_cf2(context,
 | |
| 			  subkey,
 | |
| 			  "subkeyarmor",
 | |
| 			  sessionkey,
 | |
| 			  "ticketarmor",
 | |
| 			  armorkey,
 | |
| 			  armor_crypto);
 | |
| }
 | |
| 
 | |
| KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
 | |
| _krb5_fast_explicit_armor_key(krb5_context context,
 | |
| 			      krb5_keyblock *armorkey,
 | |
| 			      krb5_keyblock *subkey,
 | |
| 			      krb5_keyblock *explicit_armorkey,
 | |
| 			      krb5_crypto *explicit_armor_crypto)
 | |
| {
 | |
|     return _krb5_fast_cf2(context,
 | |
| 			  armorkey,
 | |
| 			  "explicitarmor",
 | |
| 			  subkey,
 | |
| 			  "tgsarmor",
 | |
| 			  explicit_armorkey,
 | |
| 			  explicit_armor_crypto);
 | |
| }
 | |
| 
 | |
| static krb5_error_code
 | |
| check_fast(krb5_context context, struct krb5_fast_state *state)
 | |
| {
 | |
|     if (state && (state->flags & KRB5_FAST_EXPECTED)) {
 | |
| 	krb5_set_error_message(context, KRB5KRB_AP_ERR_MODIFIED,
 | |
| 			       "Expected FAST, but no FAST "
 | |
| 			       "was in the response from the KDC");
 | |
| 	return KRB5KRB_AP_ERR_MODIFIED;
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static krb5_error_code
 | |
| make_local_fast_ap_fxarmor(krb5_context context,
 | |
| 			   krb5_ccache armor_ccache,
 | |
| 			   krb5_const_realm realm,
 | |
| 			   krb5_data *armor_value,
 | |
| 			   krb5_keyblock *armor_key,
 | |
| 			   krb5_crypto *armor_crypto)
 | |
| {
 | |
|     krb5_auth_context auth_context = NULL;
 | |
|     krb5_creds cred, *credp = NULL;
 | |
|     krb5_error_code ret;
 | |
|     krb5_data empty;
 | |
|     krb5_const_realm tgs_realm;
 | |
| 
 | |
|     if (armor_ccache == NULL) {
 | |
| 	krb5_set_error_message(context, EINVAL,
 | |
| 			       "Armor credential cache required");
 | |
| 	return EINVAL;
 | |
|     }
 | |
| 
 | |
|     krb5_data_zero(&empty);
 | |
|     memset(&cred, 0, sizeof(cred));
 | |
| 
 | |
|     ret = krb5_auth_con_init (context, &auth_context);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     ret = krb5_cc_get_principal(context, armor_ccache, &cred.client);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     /*
 | |
|      * Make sure we don't ask for a krbtgt/WELLKNOWN:ANONYMOUS
 | |
|      */
 | |
|     if (krb5_principal_is_anonymous(context, cred.client,
 | |
| 				    KRB5_ANON_MATCH_UNAUTHENTICATED))
 | |
| 	tgs_realm = realm;
 | |
|     else
 | |
| 	tgs_realm = cred.client->realm;
 | |
| 
 | |
|     ret = krb5_make_principal(context, &cred.server,
 | |
| 			      tgs_realm,
 | |
| 			      KRB5_TGS_NAME,
 | |
| 			      tgs_realm,
 | |
| 			      NULL);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     ret = krb5_get_credentials(context, 0, armor_ccache, &cred, &credp);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     ret = krb5_auth_con_add_AuthorizationData(context, auth_context,
 | |
| 					      KRB5_AUTHDATA_FX_FAST_ARMOR,
 | |
| 					      &empty);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     ret = krb5_mk_req_extended(context,
 | |
| 			       &auth_context,
 | |
| 			       AP_OPTS_USE_SUBKEY,
 | |
| 			       NULL,
 | |
| 			       credp,
 | |
| 			       armor_value);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     ret = _krb5_fast_armor_key(context,
 | |
| 			       auth_context->local_subkey,
 | |
| 			       auth_context->keyblock,
 | |
| 			       armor_key,
 | |
| 			       armor_crypto);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|  out:
 | |
|     if (auth_context)
 | |
| 	krb5_auth_con_free(context, auth_context);
 | |
|     if (credp)
 | |
| 	krb5_free_creds(context, credp);
 | |
|     krb5_free_principal(context, cred.server);
 | |
|     krb5_free_principal(context, cred.client);
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| #ifndef WIN32
 | |
| static heim_base_once_t armor_service_once = HEIM_BASE_ONCE_INIT;
 | |
| static heim_ipc armor_service = NULL;
 | |
| 
 | |
| static void
 | |
| fast_armor_init_ipc(void *ctx)
 | |
| {
 | |
|     heim_ipc *ipc = ctx;
 | |
|     heim_ipc_init_context("ANY:org.h5l.armor-service", ipc);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| static krb5_error_code
 | |
| make_fast_ap_fxarmor(krb5_context context,
 | |
| 		     struct krb5_fast_state *state,
 | |
| 		     krb5_const_realm realm,
 | |
| 		     KrbFastArmor **armor)
 | |
| {
 | |
|     KrbFastArmor *fxarmor = NULL;
 | |
|     krb5_error_code ret;
 | |
| 
 | |
|     *armor = NULL;
 | |
| 
 | |
|     ALLOC(fxarmor, 1);
 | |
|     if (fxarmor == NULL) {
 | |
| 	ret = ENOMEM;
 | |
| 	goto out;
 | |
|     }
 | |
| 
 | |
|     if (state->flags & KRB5_FAST_AP_ARMOR_SERVICE) {
 | |
| #ifdef WIN32
 | |
| 	krb5_set_error_message(context, ENOTSUP, "Fast armor IPC service not supportted yet on Windows");
 | |
| 	ret = ENOTSUP;
 | |
| 	goto out;
 | |
| #else
 | |
| 	KERB_ARMOR_SERVICE_REPLY msg;
 | |
| 	krb5_data request, reply;
 | |
| 
 | |
| 	heim_base_once_f(&armor_service_once, &armor_service, fast_armor_init_ipc);
 | |
| 	if (armor_service == NULL) {
 | |
| 	    krb5_set_error_message(context, ENOENT, "Failed to open fast armor service");
 | |
| 	    ret = ENOENT;
 | |
| 	    goto out;
 | |
| 	}
 | |
| 
 | |
| 	krb5_data_zero(&reply);
 | |
| 
 | |
| 	request.data = rk_UNCONST(realm);
 | |
| 	request.length = strlen(realm);
 | |
| 
 | |
| 	ret = heim_ipc_call(armor_service, &request, &reply, NULL);
 | |
| 	if (ret) {
 | |
| 	    krb5_set_error_message(context, ret, "Failed to get armor service credential");
 | |
| 	    goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = decode_KERB_ARMOR_SERVICE_REPLY(reply.data, reply.length, &msg, NULL);
 | |
| 	krb5_data_free(&reply);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
| 	ret = copy_KrbFastArmor(fxarmor, &msg.armor);
 | |
| 	if (ret) {
 | |
| 	    free_KERB_ARMOR_SERVICE_REPLY(&msg);
 | |
| 	    goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = krb5_copy_keyblock_contents(context, &msg.armor_key, &state->armor_key);
 | |
| 	free_KERB_ARMOR_SERVICE_REPLY(&msg);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
| 	ret = krb5_crypto_init(context, &state->armor_key, 0, &state->armor_crypto);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| #endif /* WIN32 */
 | |
|     } else {
 | |
| 	fxarmor->armor_type = 1;
 | |
| 
 | |
| 	ret = make_local_fast_ap_fxarmor(context,
 | |
| 					 state->armor_ccache,
 | |
| 					 realm,
 | |
| 					 &fxarmor->armor_value,
 | |
| 					 &state->armor_key,
 | |
| 					 &state->armor_crypto);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     *armor = fxarmor;
 | |
|     fxarmor = NULL;
 | |
| 
 | |
|  out:
 | |
|     if (fxarmor) {
 | |
| 	free_KrbFastArmor(fxarmor);
 | |
| 	free(fxarmor);
 | |
|     }
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static krb5_error_code
 | |
| unwrap_fast_rep(krb5_context context,
 | |
| 		struct krb5_fast_state *state,
 | |
| 		PA_DATA *pa,
 | |
| 		KrbFastResponse *fastrep)
 | |
| {
 | |
|     PA_FX_FAST_REPLY fxfastrep;
 | |
|     krb5_error_code ret;
 | |
| 
 | |
|     memset(&fxfastrep, 0, sizeof(fxfastrep));
 | |
| 
 | |
|     ret = decode_PA_FX_FAST_REPLY(pa->padata_value.data,
 | |
| 				  pa->padata_value.length,
 | |
| 				  &fxfastrep, NULL);
 | |
|     if (ret)
 | |
| 	return ret;
 | |
| 
 | |
|     if (fxfastrep.element == choice_PA_FX_FAST_REPLY_armored_data) {
 | |
| 	krb5_data data;
 | |
| 
 | |
| 	ret = krb5_decrypt_EncryptedData(context,
 | |
| 					 state->armor_crypto,
 | |
| 					 KRB5_KU_FAST_REP,
 | |
| 					 &fxfastrep.u.armored_data.enc_fast_rep,
 | |
| 					 &data);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
| 	ret = decode_KrbFastResponse(data.data, data.length, fastrep, NULL);
 | |
| 	krb5_data_free(&data);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
|     } else {
 | |
| 	ret = KRB5KDC_ERR_PREAUTH_FAILED;
 | |
| 	goto out;
 | |
|     }
 | |
| 
 | |
|  out:
 | |
|     free_PA_FX_FAST_REPLY(&fxfastrep);
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static krb5_error_code
 | |
| set_anon_principal(krb5_context context, PrincipalName **p)
 | |
| {
 | |
| 
 | |
|     ALLOC((*p), 1);
 | |
|     if (*p == NULL)
 | |
| 	goto fail;
 | |
| 
 | |
|     (*p)->name_type = KRB5_NT_PRINCIPAL;
 | |
| 
 | |
|     ALLOC_SEQ(&(*p)->name_string, 2);
 | |
|     if ((*p)->name_string.val == NULL)
 | |
| 	goto fail;
 | |
| 
 | |
|     (*p)->name_string.val[0] = strdup(KRB5_WELLKNOWN_NAME);
 | |
|     if ((*p)->name_string.val[0] == NULL)
 | |
| 	goto fail;
 | |
| 
 | |
|     (*p)->name_string.val[1] = strdup(KRB5_ANON_NAME);
 | |
|     if ((*p)->name_string.val[1] == NULL)
 | |
| 	goto fail;
 | |
| 
 | |
|     return 0;
 | |
|  fail:
 | |
|     if (*p) {
 | |
| 	if ((*p)->name_string.val) {
 | |
| 	    free((*p)->name_string.val[0]);
 | |
| 	    free((*p)->name_string.val[1]);
 | |
| 	    free((*p)->name_string.val);
 | |
| 	}
 | |
| 	free(*p);
 | |
|     }
 | |
| 
 | |
|     return krb5_enomem(context);
 | |
| }
 | |
| 
 | |
| krb5_error_code
 | |
| _krb5_fast_create_armor(krb5_context context,
 | |
| 			struct krb5_fast_state *state,
 | |
| 			const char *realm)
 | |
| {
 | |
|     krb5_error_code ret;
 | |
| 
 | |
|     if (state->armor_crypto == NULL) {
 | |
| 	if (state->armor_ccache || state->armor_ac || (state->flags & KRB5_FAST_AP_ARMOR_SERVICE)) {
 | |
| 	    /*
 | |
| 	     * Instead of keeping state in FX_COOKIE in the KDC, we
 | |
| 	     * rebuild a new armor key for every request, because this
 | |
| 	     * is what the MIT KDC expect and RFC6113 is vage about
 | |
| 	     * what the behavior should be.
 | |
| 	     */
 | |
| 	    state->type = choice_PA_FX_FAST_REQUEST_armored_data;
 | |
| 	} else {
 | |
| 	    return check_fast(context, state);
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     if (state->type == choice_PA_FX_FAST_REQUEST_armored_data) {
 | |
| 	if (state->armor_crypto) {
 | |
| 	    krb5_crypto_destroy(context, state->armor_crypto);
 | |
| 	    state->armor_crypto = NULL;
 | |
| 	}
 | |
| 	if (state->strengthen_key) {
 | |
| 	    krb5_free_keyblock(context, state->strengthen_key);
 | |
| 	    state->strengthen_key = NULL;
 | |
| 	}
 | |
| 	krb5_free_keyblock_contents(context, &state->armor_key);
 | |
| 
 | |
| 	/*
 | |
| 	 * If we have a armor auth context, its because the caller
 | |
| 	 * wants us to do an implicit FAST armor (TGS-REQ).
 | |
| 	 */
 | |
| 	if (state->armor_ac) {
 | |
| 	    heim_assert((state->flags & KRB5_FAST_AS_REQ) == 0, "FAST AS with AC");
 | |
| 
 | |
| 	    ret = _krb5_fast_armor_key(context,
 | |
| 				       state->armor_ac->local_subkey,
 | |
| 				       state->armor_ac->keyblock,
 | |
| 				       &state->armor_key,
 | |
| 				       &state->armor_crypto);
 | |
| 	    if (ret)
 | |
| 		goto out;
 | |
| 	} else {
 | |
| 	    heim_assert((state->flags & KRB5_FAST_AS_REQ) != 0, "FAST TGS without AC");
 | |
| 
 | |
| 	    if (state->armor_data) {
 | |
| 		free_KrbFastArmor(state->armor_data);
 | |
| 		free(state->armor_data);
 | |
|                 state->armor_data = NULL;
 | |
| 	    }
 | |
| 	    ret = make_fast_ap_fxarmor(context, state, realm,
 | |
| 				       &state->armor_data);
 | |
| 	    if (ret)
 | |
| 		goto out;
 | |
| 	}
 | |
|     } else {
 | |
| 	heim_abort("unknown state type: %d", (int)state->type);
 | |
|     }
 | |
|  out:
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| krb5_error_code
 | |
| _krb5_fast_wrap_req(krb5_context context,
 | |
| 		    struct krb5_fast_state *state,
 | |
| 		    KDC_REQ *req)
 | |
| {
 | |
|     PA_FX_FAST_REQUEST fxreq;
 | |
|     krb5_error_code ret;
 | |
|     KrbFastReq fastreq;
 | |
|     krb5_data data, aschecksum_data, tgschecksum_data;
 | |
|     const krb5_data *checksum_data = NULL;
 | |
|     size_t size = 0;
 | |
|     krb5_boolean readd_padata_to_outer = FALSE;
 | |
| 
 | |
|     if (state->flags & KRB5_FAST_DISABLED) {
 | |
| 	_krb5_debug(context, 10, "fast disabled, not doing any fast wrapping");
 | |
| 	return 0;
 | |
|     }
 | |
| 
 | |
|     memset(&fxreq, 0, sizeof(fxreq));
 | |
|     memset(&fastreq, 0, sizeof(fastreq));
 | |
|     krb5_data_zero(&data);
 | |
|     krb5_data_zero(&aschecksum_data);
 | |
|     krb5_data_zero(&tgschecksum_data);
 | |
| 
 | |
|     if (state->armor_crypto == NULL)
 | |
| 	return check_fast(context, state);
 | |
| 
 | |
|     state->flags |= KRB5_FAST_EXPECTED;
 | |
| 
 | |
|     fastreq.fast_options.hide_client_names = 1;
 | |
| 
 | |
|     ret = copy_KDC_REQ_BODY(&req->req_body, &fastreq.req_body);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     /*
 | |
|      * In the case of a AS-REQ, remove all account names. Want to this
 | |
|      * for TGS-REQ too, but due to layering this is tricky.
 | |
|      *
 | |
|      * 1. TGS-REQ need checksum of REQ-BODY
 | |
|      * 2. FAST needs checksum of TGS-REQ, so, FAST needs to happen after TGS-REQ
 | |
|      * 3. FAST privacy mangaling needs to happen before TGS-REQ does the checksum in 1.
 | |
|      *
 | |
|      * So lets not modify the bits for now for TGS-REQ
 | |
|      */
 | |
|     if (state->flags & KRB5_FAST_AS_REQ) {
 | |
| 	free_KDC_REQ_BODY(&req->req_body);
 | |
| 
 | |
| 	req->req_body.realm = strdup(KRB5_ANON_REALM);
 | |
| 	if (req->req_body.realm == NULL) {
 | |
| 	    ret = krb5_enomem(context);
 | |
| 	    goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = set_anon_principal(context, &req->req_body.cname);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
| 	ALLOC(req->req_body.till, 1);
 | |
| 	*req->req_body.till = 0;
 | |
| 
 | |
| 	ASN1_MALLOC_ENCODE(KDC_REQ_BODY,
 | |
| 			   aschecksum_data.data,
 | |
| 			   aschecksum_data.length,
 | |
| 			   &req->req_body,
 | |
| 			   &size, ret);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 	heim_assert(aschecksum_data.length == size, "ASN.1 internal error");
 | |
| 
 | |
| 	checksum_data = &aschecksum_data;
 | |
| 
 | |
| 	if (req->padata) {
 | |
| 	    ret = copy_METHOD_DATA(req->padata, &fastreq.padata);
 | |
| 	    free_METHOD_DATA(req->padata);
 | |
| 	    if (ret)
 | |
| 		goto out;
 | |
| 	}
 | |
|     } else {
 | |
| 	const PA_DATA *tgs_req_ptr = NULL;
 | |
| 	int tgs_req_idx = 0;
 | |
| 	size_t i;
 | |
| 
 | |
| 	heim_assert(req->padata != NULL, "req->padata is NULL");
 | |
| 
 | |
| 	tgs_req_ptr = krb5_find_padata(req->padata->val,
 | |
| 				       req->padata->len,
 | |
| 				       KRB5_PADATA_TGS_REQ,
 | |
| 				       &tgs_req_idx);
 | |
| 	heim_assert(tgs_req_ptr != NULL, "KRB5_PADATA_TGS_REQ not found");
 | |
| 	heim_assert(tgs_req_idx == 0, "KRB5_PADATA_TGS_REQ not first");
 | |
| 
 | |
| 	tgschecksum_data.data = tgs_req_ptr->padata_value.data;
 | |
| 	tgschecksum_data.length = tgs_req_ptr->padata_value.length;
 | |
| 	checksum_data = &tgschecksum_data;
 | |
| 
 | |
| 	/*
 | |
| 	 * Now copy all remaining once to
 | |
| 	 * the fastreq.padata and clear
 | |
| 	 * them in the outer req first,
 | |
| 	 * and remember to readd them later.
 | |
| 	 */
 | |
| 	readd_padata_to_outer = TRUE;
 | |
| 
 | |
| 	for (i = 1; i < req->padata->len; i++) {
 | |
| 	    PA_DATA *val = &req->padata->val[i];
 | |
| 
 | |
| 	    ret = krb5_padata_add(context,
 | |
| 				  &fastreq.padata,
 | |
| 				  val->padata_type,
 | |
| 				  val->padata_value.data,
 | |
| 				  val->padata_value.length);
 | |
| 	    if (ret) {
 | |
| 		krb5_set_error_message(context, ret,
 | |
| 				       N_("malloc: out of memory", ""));
 | |
| 		goto out;
 | |
| 	    }
 | |
| 	    val->padata_value.data = NULL;
 | |
| 	    val->padata_value.length = 0;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Only TGS-REQ remaining
 | |
| 	 */
 | |
| 	req->padata->len = 1;
 | |
|     }
 | |
| 
 | |
|     if (req->padata == NULL) {
 | |
| 	ALLOC(req->padata, 1);
 | |
| 	if (req->padata == NULL) {
 | |
| 	    ret = krb5_enomem(context);
 | |
| 	    goto out;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     ASN1_MALLOC_ENCODE(KrbFastReq, data.data, data.length, &fastreq, &size, ret);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
|     heim_assert(data.length == size, "ASN.1 internal error");
 | |
| 
 | |
|     fxreq.element = state->type;
 | |
| 
 | |
|     if (state->type == choice_PA_FX_FAST_REQUEST_armored_data) {
 | |
| 	fxreq.u.armored_data.armor = state->armor_data;
 | |
| 	state->armor_data = NULL;
 | |
| 
 | |
| 	heim_assert(state->armor_crypto != NULL,
 | |
| 		    "FAST armor key missing when FAST started");
 | |
| 
 | |
| 	ret = krb5_create_checksum(context, state->armor_crypto,
 | |
| 				   KRB5_KU_FAST_REQ_CHKSUM, 0,
 | |
| 				   checksum_data->data,
 | |
| 				   checksum_data->length,
 | |
| 				   &fxreq.u.armored_data.req_checksum);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
| 	ret = krb5_encrypt_EncryptedData(context, state->armor_crypto,
 | |
| 					 KRB5_KU_FAST_ENC,
 | |
| 					 data.data,
 | |
| 					 data.length,
 | |
| 					 0,
 | |
| 					 &fxreq.u.armored_data.enc_fast_req);
 | |
| 	krb5_data_free(&data);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
|     } else {
 | |
| 	krb5_data_free(&data);
 | |
| 	heim_assert(false, "unknown FAST type, internal error");
 | |
|     }
 | |
| 
 | |
|     ASN1_MALLOC_ENCODE(PA_FX_FAST_REQUEST, data.data, data.length, &fxreq, &size, ret);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
|     heim_assert(data.length == size, "ASN.1 internal error");
 | |
| 
 | |
| 
 | |
|     ret = krb5_padata_add(context, req->padata, KRB5_PADATA_FX_FAST, data.data, data.length);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
|     krb5_data_zero(&data);
 | |
| 
 | |
|     if (readd_padata_to_outer) {
 | |
| 	size_t i;
 | |
| 
 | |
| 	for (i = 0; i < fastreq.padata.len; i++) {
 | |
| 	    PA_DATA *val = &fastreq.padata.val[i];
 | |
| 
 | |
| 	    ret = krb5_padata_add(context,
 | |
| 				  req->padata,
 | |
| 				  val->padata_type,
 | |
| 				  val->padata_value.data,
 | |
| 				  val->padata_value.length);
 | |
| 	    if (ret) {
 | |
| 		krb5_set_error_message(context, ret,
 | |
| 				       N_("malloc: out of memory", ""));
 | |
| 		goto out;
 | |
| 	    }
 | |
| 	    val->padata_value.data = NULL;
 | |
| 	    val->padata_value.length = 0;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|  out:
 | |
|     free_KrbFastReq(&fastreq);
 | |
|     free_PA_FX_FAST_REQUEST(&fxreq);
 | |
|     krb5_data_free(&data);
 | |
|     krb5_data_free(&aschecksum_data);
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| krb5_error_code
 | |
| _krb5_fast_unwrap_error(krb5_context context,
 | |
| 			int32_t nonce,
 | |
| 			struct krb5_fast_state *state,
 | |
| 			METHOD_DATA *md,
 | |
| 			KRB_ERROR *error)
 | |
| {
 | |
|     KrbFastResponse fastrep;
 | |
|     krb5_error_code ret;
 | |
|     PA_DATA *pa;
 | |
|     int idx;
 | |
| 
 | |
|     if (state->armor_crypto == NULL)
 | |
| 	return check_fast(context, state);
 | |
| 
 | |
|     memset(&fastrep, 0, sizeof(fastrep));
 | |
| 
 | |
|     if (error->error_code != KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED)
 | |
| 	_krb5_debug(context, 10, "using FAST without FAST outer error code");
 | |
| 
 | |
|     idx = 0;
 | |
|     pa = krb5_find_padata(md->val, md->len, KRB5_PADATA_FX_FAST, &idx);
 | |
|     if (pa == NULL) {
 | |
| 	ret = KRB5_KDCREP_MODIFIED;
 | |
| 	krb5_set_error_message(context, ret,
 | |
| 			       N_("FAST fast response is missing FX-FAST", ""));
 | |
| 	goto out;
 | |
|     }
 | |
| 
 | |
|     ret = unwrap_fast_rep(context, state, pa, &fastrep);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     if (fastrep.strengthen_key || nonce != (int32_t)fastrep.nonce) {
 | |
| 	ret = KRB5KDC_ERR_PREAUTH_FAILED;
 | |
| 	goto out;
 | |
|     }
 | |
| 
 | |
|     idx = 0;
 | |
|     pa = krb5_find_padata(fastrep.padata.val, fastrep.padata.len, KRB5_PADATA_FX_ERROR, &idx);
 | |
|     if (pa == NULL) {
 | |
| 	ret = KRB5_KDCREP_MODIFIED;
 | |
| 	krb5_set_error_message(context, ret, N_("No wrapped error", ""));
 | |
| 	goto out;
 | |
|     }
 | |
| 
 | |
|     free_KRB_ERROR(error);
 | |
| 
 | |
|     ret = krb5_rd_error(context, &pa->padata_value, error);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     if (error->e_data)
 | |
| 	_krb5_debug(context, 10, "FAST wrapped KBB_ERROR contained e_data: %d",
 | |
| 		     (int)error->e_data->length);
 | |
| 
 | |
|     free_METHOD_DATA(md);
 | |
|     md->val = fastrep.padata.val;
 | |
|     md->len = fastrep.padata.len;
 | |
| 
 | |
|     fastrep.padata.val = NULL;
 | |
|     fastrep.padata.len = 0;
 | |
| 
 | |
|  out:
 | |
|     free_KrbFastResponse(&fastrep);
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| krb5_error_code
 | |
| _krb5_fast_unwrap_kdc_rep(krb5_context context, int32_t nonce,
 | |
| 			  krb5_data *chksumdata,
 | |
| 			  struct krb5_fast_state *state, AS_REP *rep)
 | |
| {
 | |
|     KrbFastResponse fastrep;
 | |
|     krb5_error_code ret;
 | |
|     PA_DATA *pa = NULL;
 | |
|     int idx = 0;
 | |
| 
 | |
|     if (state == NULL || state->armor_crypto == NULL || rep->padata == NULL)
 | |
| 	return check_fast(context, state);
 | |
| 
 | |
|     /* find PA_FX_FAST_REPLY */
 | |
| 
 | |
|     pa = krb5_find_padata(rep->padata->val, rep->padata->len,
 | |
| 			  KRB5_PADATA_FX_FAST, &idx);
 | |
|     if (pa == NULL)
 | |
| 	return check_fast(context, state);
 | |
| 
 | |
|     memset(&fastrep, 0, sizeof(fastrep));
 | |
| 
 | |
|     ret = unwrap_fast_rep(context, state, pa, &fastrep);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     free_METHOD_DATA(rep->padata);
 | |
|     ret = copy_METHOD_DATA(&fastrep.padata, rep->padata);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     if (fastrep.strengthen_key) {
 | |
| 	if (state->strengthen_key)
 | |
| 	    krb5_free_keyblock(context, state->strengthen_key);
 | |
| 
 | |
| 	ret = krb5_copy_keyblock(context, fastrep.strengthen_key, &state->strengthen_key);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
|     }
 | |
| 
 | |
|     if (nonce != (int32_t)fastrep.nonce) {
 | |
| 	ret = KRB5KDC_ERR_PREAUTH_FAILED;
 | |
| 	goto out;
 | |
|     }
 | |
|     if (fastrep.finished) {
 | |
| 	PrincipalName cname;
 | |
| 	krb5_realm crealm = NULL;
 | |
| 
 | |
| 	if (chksumdata == NULL) {
 | |
| 	    ret = KRB5KDC_ERR_PREAUTH_FAILED;
 | |
| 	    goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = krb5_verify_checksum(context, state->armor_crypto,
 | |
| 				   KRB5_KU_FAST_FINISHED,
 | |
| 				   chksumdata->data, chksumdata->length,
 | |
| 				   &fastrep.finished->ticket_checksum);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
| 	/* update */
 | |
| 	ret = copy_Realm(&fastrep.finished->crealm, &crealm);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 	free_Realm(&rep->crealm);
 | |
| 	rep->crealm = crealm;
 | |
| 
 | |
| 	ret = copy_PrincipalName(&fastrep.finished->cname, &cname);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 	free_PrincipalName(&rep->cname);
 | |
| 	rep->cname = cname;
 | |
|     } else if (chksumdata) {
 | |
| 	/* expected fastrep.finish but didn't get it */
 | |
| 	ret = KRB5KDC_ERR_PREAUTH_FAILED;
 | |
|     }
 | |
| 
 | |
|  out:
 | |
|     free_KrbFastResponse(&fastrep);
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| void
 | |
| _krb5_fast_free(krb5_context context, struct krb5_fast_state *state)
 | |
| {
 | |
|     if (state->armor_ccache) {
 | |
| 	if (state->flags & KRB5_FAST_ANON_PKINIT_ARMOR)
 | |
| 	    krb5_cc_destroy(context, state->armor_ccache);
 | |
| 	else
 | |
| 	    krb5_cc_close(context, state->armor_ccache);
 | |
|     }
 | |
|     if (state->armor_service)
 | |
| 	krb5_free_principal(context, state->armor_service);
 | |
|     if (state->armor_crypto)
 | |
| 	krb5_crypto_destroy(context, state->armor_crypto);
 | |
|     if (state->strengthen_key)
 | |
| 	krb5_free_keyblock(context, state->strengthen_key);
 | |
|     krb5_free_keyblock_contents(context, &state->armor_key);
 | |
|     if (state->armor_data) {
 | |
| 	free_KrbFastArmor(state->armor_data);
 | |
| 	free(state->armor_data);
 | |
|     }
 | |
| 
 | |
|     if (state->anon_pkinit_ctx)
 | |
| 	krb5_init_creds_free(context, state->anon_pkinit_ctx);
 | |
|     if (state->anon_pkinit_opt)
 | |
| 	krb5_get_init_creds_opt_free(context, state->anon_pkinit_opt);
 | |
| 
 | |
|     memset(state, 0, sizeof(*state));
 | |
| }
 | |
| 
 | |
| krb5_error_code
 | |
| _krb5_fast_anon_pkinit_step(krb5_context context,
 | |
| 			    krb5_init_creds_context ctx,
 | |
| 			    struct krb5_fast_state *state,
 | |
| 			    const krb5_data *in,
 | |
| 			    krb5_data *out,
 | |
| 			    krb5_realm *out_realm,
 | |
| 			    unsigned int *flags)
 | |
| {
 | |
|     krb5_error_code ret;
 | |
|     krb5_const_realm realm = _krb5_init_creds_get_cred_client(context, ctx)->realm;
 | |
|     krb5_init_creds_context anon_pk_ctx;
 | |
|     krb5_principal principal = NULL, anon_pk_client;
 | |
|     krb5_ccache ccache = NULL;
 | |
|     krb5_creds cred;
 | |
|     krb5_data data = { 3, rk_UNCONST("yes") };
 | |
| 
 | |
|     krb5_data_zero(out);
 | |
|     *out_realm = NULL;
 | |
| 
 | |
|     memset(&cred, 0, sizeof(cred));
 | |
| 
 | |
|     if (state->anon_pkinit_opt == NULL) {
 | |
| 	ret = krb5_get_init_creds_opt_alloc(context, &state->anon_pkinit_opt);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
| 	krb5_get_init_creds_opt_set_tkt_life(state->anon_pkinit_opt, 60);
 | |
| 	krb5_get_init_creds_opt_set_anonymous(state->anon_pkinit_opt, TRUE);
 | |
| 
 | |
| 	ret = krb5_make_principal(context, &principal, realm,
 | |
| 				  KRB5_WELLKNOWN_NAME, KRB5_ANON_NAME, NULL);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
| 	ret = krb5_get_init_creds_opt_set_pkinit(context,
 | |
| 						 state->anon_pkinit_opt,
 | |
| 						 principal,
 | |
| 						 NULL, NULL, NULL, NULL,
 | |
| 						 KRB5_GIC_OPT_PKINIT_ANONYMOUS |
 | |
| 						 KRB5_GIC_OPT_PKINIT_NO_KDC_ANCHOR,
 | |
| 						 NULL, NULL, NULL);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
| 
 | |
| 	ret = krb5_init_creds_init(context, principal, NULL, NULL,
 | |
| 				   _krb5_init_creds_get_cred_starttime(context, ctx),
 | |
| 				   state->anon_pkinit_opt,
 | |
| 				   &state->anon_pkinit_ctx);
 | |
| 	if (ret)
 | |
| 	    goto out;
 | |
|     }
 | |
| 
 | |
|     anon_pk_ctx = state->anon_pkinit_ctx;
 | |
| 
 | |
|     ret = krb5_init_creds_step(context, anon_pk_ctx, in, out, out_realm, flags);
 | |
|     if (ret ||
 | |
| 	(*flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE))
 | |
| 	goto out;
 | |
| 
 | |
|     ret = krb5_process_last_request(context, state->anon_pkinit_opt, anon_pk_ctx);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     ret = krb5_cc_new_unique(context, "MEMORY", NULL, &ccache);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     ret = krb5_init_creds_get_creds(context, anon_pk_ctx, &cred);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     if (!cred.flags.b.enc_pa_rep) {
 | |
| 	ret = KRB5KDC_ERR_BADOPTION; /* KDC does not support FAST */
 | |
| 	goto out;
 | |
|     }
 | |
| 
 | |
|     anon_pk_client = _krb5_init_creds_get_cred_client(context, anon_pk_ctx);
 | |
| 
 | |
|     ret = krb5_cc_initialize(context, ccache, anon_pk_client);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     ret = krb5_cc_store_cred(context, ccache, &cred);
 | |
|     if (ret)
 | |
| 	goto out;
 | |
| 
 | |
|     ret = krb5_cc_set_config(context, ccache, cred.server,
 | |
| 			     "fast_avail", &data);
 | |
|     if (ret && ret != KRB5_CC_NOSUPP)
 | |
| 	return ret;
 | |
| 
 | |
|     if (_krb5_pk_is_kdc_verified(context, state->anon_pkinit_opt))
 | |
| 	state->flags |= KRB5_FAST_KDC_VERIFIED;
 | |
|     else
 | |
| 	state->flags &= ~(KRB5_FAST_KDC_VERIFIED);
 | |
| 
 | |
|     state->armor_ccache = ccache;
 | |
|     ccache = NULL;
 | |
| 
 | |
|     krb5_init_creds_free(context, state->anon_pkinit_ctx);
 | |
|     state->anon_pkinit_ctx = NULL;
 | |
| 
 | |
|     krb5_get_init_creds_opt_free(context, state->anon_pkinit_opt);
 | |
|     state->anon_pkinit_opt = NULL;
 | |
| 
 | |
| out:
 | |
|     krb5_free_principal(context, principal);
 | |
|     krb5_free_cred_contents(context, &cred);
 | |
|     if (ccache)
 | |
| 	krb5_cc_destroy(context, ccache);
 | |
| 
 | |
|     return ret;
 | |
| }
 | 
