
Normally when FAST is used with a TGS-REQ, the armor key is implicitly derived from the TGT rather than armor being explicitly present, as for AS-REQs. However, Windows allows a TGS-REQ to be explicitly armored with a computer's TGT, so that the armor key also depends on the ticket session key. This is used for compound identity, where the computer's group membership and claims are added to the PAC of the resulting ticket. Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
883 lines
22 KiB
C
883 lines
22 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;
|
|
|
|
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;
|
|
|
|
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);
|
|
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);
|
|
}
|
|
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,
|
|
krb5_data *checksum_data,
|
|
KDC_REQ *req)
|
|
{
|
|
PA_FX_FAST_REQUEST fxreq;
|
|
krb5_error_code ret;
|
|
KrbFastReq fastreq;
|
|
krb5_data data, aschecksum_data;
|
|
size_t size = 0;
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
heim_assert(checksum_data == NULL, "checksum data not NULL");
|
|
|
|
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 {
|
|
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;
|
|
if (ret)
|
|
goto out;
|
|
|
|
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);
|
|
|
|
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,
|
|
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)
|
|
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;
|
|
}
|