diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /third_party/heimdal/lib/krb5/fast.c | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/heimdal/lib/krb5/fast.c')
-rw-r--r-- | third_party/heimdal/lib/krb5/fast.c | 962 |
1 files changed, 962 insertions, 0 deletions
diff --git a/third_party/heimdal/lib/krb5/fast.c b/third_party/heimdal/lib/krb5/fast.c new file mode 100644 index 0000000..8389354 --- /dev/null +++ b/third_party/heimdal/lib/krb5/fast.c @@ -0,0 +1,962 @@ +/* + * 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)); + + 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, + krb5_data *in, + krb5_data *out, + krb5_krbhst_info *hostinfo, + 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") }; + + 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) + return ret; + + 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, hostinfo, 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; + + *flags |= KRB5_INIT_CREDS_STEP_FLAG_CONTINUE; + +out: + krb5_free_principal(context, principal); + krb5_free_cred_contents(context, &cred); + if (ccache) + krb5_cc_destroy(context, ccache); + + return ret; +} |