Files
heimdal/kdc/kerberos5.c
2023-11-28 21:34:35 -05:00

2809 lines
78 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 1997-2007 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 "kdc_locl.h"
#ifdef TIME_T_SIGNED
#if SIZEOF_TIME_T == 4
#define MAX_TIME ((time_t)INT32_MAX)
#elif SIZEOF_TIME_T == 8
#define MAX_TIME ((time_t)INT64_MAX)
#else
#error "Unexpected sizeof(time_t)"
#endif
#else
#if SIZEOF_TIME_T == 4
#define MAX_TIME ((time_t)UINT32_MAX)
#else
#define MAX_TIME ((time_t)UINT64_MAX)
#endif
#endif
#undef __attribute__
#define __attribute__(X)
void
_kdc_fix_time(time_t **t)
{
if(*t == NULL){
ALLOC(*t);
**t = MAX_TIME;
}
if(**t == 0) **t = MAX_TIME; /* fix for old clients */
}
static int
realloc_method_data(METHOD_DATA *md)
{
PA_DATA *pa;
pa = realloc(md->val, (md->len + 1) * sizeof(*md->val));
if(pa == NULL)
return ENOMEM;
md->val = pa;
md->len++;
return 0;
}
static krb5_error_code
set_salt_padata(METHOD_DATA *md, Salt *salt)
{
PA_DATA pa; /* do not free */
if (!salt)
return 0;
pa.padata_type = salt->type;
pa.padata_value = salt->salt;
return add_METHOD_DATA(md, &pa);
}
const PA_DATA*
_kdc_find_padata(const KDC_REQ *req, int *start, int type)
{
if (req->padata == NULL)
return NULL;
while((size_t)*start < req->padata->len){
(*start)++;
if(req->padata->val[*start - 1].padata_type == (unsigned)type)
return &req->padata->val[*start - 1];
}
return NULL;
}
/*
* This is a hack to allow predefined weak services, like afs to
* still use weak types
*/
krb5_boolean
_kdc_is_weak_exception(krb5_principal principal, krb5_enctype etype)
{
if (principal->name.name_string.len > 0 &&
strcmp(principal->name.name_string.val[0], "afs") == 0 &&
(etype == ETYPE_DES_CBC_CRC
|| etype == ETYPE_DES_CBC_MD4
|| etype == ETYPE_DES_CBC_MD5))
return TRUE;
return FALSE;
}
/*
* Detect if `key' is the using the the precomputed `default_salt'.
*/
static krb5_boolean
is_default_salt_p(const krb5_salt *default_salt, const Key *key)
{
if (key->salt == NULL)
return TRUE;
if (default_salt->salttype != key->salt->type)
return FALSE;
if (krb5_data_cmp(&default_salt->saltvalue, &key->salt->salt) != 0)
return FALSE;
return TRUE;
}
/*
* Detect if `key' is the using the the precomputed `default_salt'
* (for des-cbc-crc) or any salt otherwise.
*
* This is for avoiding Kerberos v4 (yes really) keys in AS-REQ as
* that salt is strange, and a buggy client will try to use the
* principal as the salt and not the returned value.
*/
static krb5_boolean
is_good_salt_p(const krb5_salt *default_salt, const Key *key)
{
if (key->key.keytype == KRB5_ENCTYPE_DES_CBC_CRC)
return is_default_salt_p(default_salt, key);
return TRUE;
}
krb5_boolean
_kdc_is_anon_request(const KDC_REQ *req)
{
const KDC_REQ_BODY *b = &req->req_body;
/*
* Versions of Heimdal from 0.9rc1 through 1.50 use bit 14 instead
* of 16 for request_anonymous, as indicated in the anonymous draft
* prior to version 11. Bit 14 is assigned to S4U2Proxy, but S4U2Proxy
* requests are only sent to the TGS and, in any case, would have an
* additional ticket present.
*/
return b->kdc_options.request_anonymous ||
(b->kdc_options.cname_in_addl_tkt && !b->additional_tickets);
}
/*
* return the first appropriate key of `princ' in `ret_key'. Look for
* all the etypes in (`etypes', `len'), stopping as soon as we find
* one, but preferring one that has default salt.
*
* XXX This function does way way too much. Split it up!
*
* XXX `etypes' and `len' are always `b->etype.val' and `b->etype.len' -- the
* etype list from the KDC-REQ-BODY, which is available here as
* `r->req->req_body', so we could just stop having it passed in.
*
* XXX Picking an enctype(s) for PA-ETYPE-INFO* is rather different than
* picking an enctype for a ticket's session key. The former is what we do
* here when `(flags & KFE_IS_PREAUTH)', the latter otherwise.
*/
krb5_error_code
_kdc_find_etype(astgs_request_t r, uint32_t flags,
krb5_enctype *etypes, unsigned len,
krb5_enctype *ret_enctype, Key **ret_key,
krb5_boolean *ret_default_salt)
{
krb5_boolean use_strongest_session_key;
krb5_boolean is_preauth = flags & KFE_IS_PREAUTH;
krb5_boolean is_tgs = flags & KFE_IS_TGS;
hdb_entry *princ;
krb5_principal request_princ;
krb5_error_code ret;
krb5_salt def_salt;
krb5_enctype enctype = ETYPE_NULL;
const krb5_enctype *p;
Key *key = NULL;
size_t i, k, m;
if (is_preauth && (flags & KFE_USE_CLIENT) &&
r->client->flags.synthetic)
return KRB5KDC_ERR_ETYPE_NOSUPP;
if ((flags & KFE_USE_CLIENT) && !r->client->flags.synthetic) {
princ = r->client;
request_princ = r->client_princ;
} else {
princ = r->server;
request_princ = r->server->principal;
}
use_strongest_session_key =
is_preauth ? r->config->preauth_use_strongest_session_key
: (is_tgs ? r->config->tgt_use_strongest_session_key :
r->config->svc_use_strongest_session_key);
/* We'll want to avoid keys with v4 salted keys in the pre-auth case... */
ret = krb5_get_pw_salt(r->context, request_princ, &def_salt);
if (ret)
return ret;
ret = KRB5KDC_ERR_ETYPE_NOSUPP;
/*
* Pick an enctype that is in the intersection of:
*
* - permitted_enctypes (local policy)
* - requested enctypes (KDC-REQ-BODY's etype list)
* - the client's long-term keys' enctypes
* OR
* the server's configured etype list
*
* There are two sub-cases:
*
* - use local enctype preference (local policy)
* - use the client's preference list
*/
if (use_strongest_session_key) {
/*
* Pick the strongest key that the KDC, target service, and
* client all support, using the local cryptosystem enctype
* list in strongest-to-weakest order to drive the search.
*
* This is not what RFC4120 says to do, but it encourages
* adoption of stronger enctypes. This doesn't play well with
* clients that have multiple Kerberos client implementations
* with different supported enctype lists sharing the same ccache.
*/
/* drive the search with local supported enctypes list */
p = krb5_kerberos_enctypes(r->context);
for (i = 0;
p[i] != ETYPE_NULL && enctype == ETYPE_NULL;
i++) {
if (krb5_enctype_valid(r->context, p[i]) != 0 &&
!_kdc_is_weak_exception(princ->principal, p[i]))
continue;
/* check that the client supports it too */
for (k = 0; k < len && enctype == ETYPE_NULL; k++) {
if (p[i] != etypes[k])
continue;
if (!is_preauth && (flags & KFE_USE_CLIENT)) {
/*
* It suffices that the client says it supports this
* enctype in its KDC-REQ-BODY's etype list, which is what
* `etypes' is here.
*/
enctype = p[i];
ret = 0;
break;
}
/* check target princ support */
key = NULL;
if (!is_preauth && !(flags & KFE_USE_CLIENT) && princ->etypes) {
/*
* Use the etypes list from the server's HDB entry instead
* of deriving it from its long-term keys. This allows an
* entry to have just one long-term key but record support
* for multiple enctypes.
*/
for (m = 0; m < princ->etypes->len; m++) {
if (p[i] == princ->etypes->val[m]) {
enctype = p[i];
ret = 0;
break;
}
}
} else {
/*
* Use the entry's long-term keys as the source of its
* supported enctypes, either because we're making
* PA-ETYPE-INFO* or because we're selecting a session key
* enctype.
*/
while (hdb_next_enctype2key(r->context, princ, NULL,
p[i], &key) == 0) {
if (key->key.keyvalue.length == 0) {
ret = KRB5KDC_ERR_NULL_KEY;
continue;
}
enctype = p[i];
ret = 0;
if (is_preauth && ret_key != NULL &&
!is_good_salt_p(&def_salt, key))
continue;
}
}
}
}
} else {
/*
* Pick the first key from the client's enctype list that is
* supported by the cryptosystem and by the given principal.
*
* RFC4120 says we SHOULD pick the first _strong_ key from the
* client's list... not the first key... If the admin disallows
* weak enctypes in krb5.conf and selects this key selection
* algorithm, then we get exactly what RFC4120 says.
*/
for(i = 0; ret != 0 && i < len; i++) {
if (krb5_enctype_valid(r->context, etypes[i]) != 0 &&
!_kdc_is_weak_exception(princ->principal, etypes[i]))
continue;
key = NULL;
while (ret != 0 &&
hdb_next_enctype2key(r->context, princ, NULL,
etypes[i], &key) == 0) {
if (key->key.keyvalue.length == 0) {
ret = KRB5KDC_ERR_NULL_KEY;
continue;
}
enctype = etypes[i];
ret = 0;
if (is_preauth && ret_key != NULL &&
!is_good_salt_p(&def_salt, key))
continue;
}
}
}
if (ret == 0 && enctype == ETYPE_NULL) {
/*
* if the service principal is one for which there is a known 1DES
* exception and no other enctype matches both the client request and
* the service key list, provide a DES-CBC-CRC key.
*/
if (ret_key == NULL &&
_kdc_is_weak_exception(princ->principal, ETYPE_DES_CBC_CRC)) {
ret = 0;
enctype = ETYPE_DES_CBC_CRC;
} else {
ret = KRB5KDC_ERR_ETYPE_NOSUPP;
}
}
if (ret == 0) {
if (ret_enctype != NULL)
*ret_enctype = enctype;
if (ret_key != NULL)
*ret_key = key;
if (ret_default_salt != NULL)
*ret_default_salt = is_default_salt_p(&def_salt, key);
}
krb5_free_salt (r->context, def_salt);
return ret;
}
krb5_error_code
_kdc_make_anonymous_principalname (PrincipalName *pn)
{
pn->name_type = KRB5_NT_WELLKNOWN;
pn->name_string.len = 2;
pn->name_string.val = calloc(2, sizeof(*pn->name_string.val));
if (pn->name_string.val == NULL)
goto failed;
pn->name_string.val[0] = strdup(KRB5_WELLKNOWN_NAME);
if (pn->name_string.val[0] == NULL)
goto failed;
pn->name_string.val[1] = strdup(KRB5_ANON_NAME);
if (pn->name_string.val[1] == NULL)
goto failed;
return 0;
failed:
free_PrincipalName(pn);
pn->name_type = KRB5_NT_UNKNOWN;
pn->name_string.len = 0;
pn->name_string.val = NULL;
return ENOMEM;
}
static void
_kdc_r_log(astgs_request_t r, int level, const char *fmt, ...)
__attribute__ ((__format__ (__printf__, 3, 4)))
{
va_list ap;
char *s;
va_start(ap, fmt);
s = kdc_log_msg_va(r->context, r->config, level, fmt, ap);
if(s) free(s);
va_end(ap);
}
void
_kdc_set_const_e_text(astgs_request_t r, const char *e_text)
{
/* We should never see this */
if (r->e_text) {
kdc_log(r->context, r->config, 1,
"trying to replace e-text \"%s\" with \"%s\"\n",
r->e_text, e_text);
return;
}
r->e_text = e_text;
kdc_log(r->context, r->config, 4, "%s", e_text);
}
void
_kdc_set_e_text(astgs_request_t r, const char *fmt, ...)
__attribute__ ((__format__ (__printf__, 2, 3)))
{
va_list ap;
char *e_text = NULL;
int vasprintf_ret;
va_start(ap, fmt);
vasprintf_ret = vasprintf(&e_text, fmt, ap);
va_end(ap);
if (vasprintf_ret < 0 || !e_text) {
/* not much else to do... */
kdc_log(r->context, r->config, 1,
"Could not set e_text: %s (out of memory)", fmt);
return;
}
/* We should never see this */
if (r->e_text) {
kdc_log(r->context, r->config, 1, "trying to replace e-text: %s\n",
e_text);
free(e_text);
return;
}
r->e_text = e_text;
r->e_text_buf = e_text;
kdc_log(r->context, r->config, 4, "%s", e_text);
}
void
_kdc_log_timestamp(astgs_request_t r, const char *type,
KerberosTime authtime, KerberosTime *starttime,
KerberosTime endtime, KerberosTime *renew_till)
{
krb5_kdc_configuration *config = r->config;
char authtime_str[100], starttime_str[100],
endtime_str[100], renewtime_str[100];
if (authtime)
kdc_audit_setkv_number((kdc_request_t)r, "auth", authtime);
if (starttime && *starttime)
kdc_audit_setkv_number((kdc_request_t)r, "start", *starttime);
if (endtime)
kdc_audit_setkv_number((kdc_request_t)r, "end", endtime);
if (renew_till && *renew_till)
kdc_audit_setkv_number((kdc_request_t)r, "renew", *renew_till);
krb5_format_time(r->context, authtime,
authtime_str, sizeof(authtime_str), TRUE);
if (starttime)
krb5_format_time(r->context, *starttime,
starttime_str, sizeof(starttime_str), TRUE);
else
strlcpy(starttime_str, "unset", sizeof(starttime_str));
krb5_format_time(r->context, endtime,
endtime_str, sizeof(endtime_str), TRUE);
if (renew_till)
krb5_format_time(r->context, *renew_till,
renewtime_str, sizeof(renewtime_str), TRUE);
else
strlcpy(renewtime_str, "unset", sizeof(renewtime_str));
kdc_log(r->context, config, 4,
"%s authtime: %s starttime: %s endtime: %s renew till: %s",
type, authtime_str, starttime_str, endtime_str, renewtime_str);
}
/*
*
*/
#ifdef PKINIT
static krb5_error_code
pa_pkinit_validate(astgs_request_t r, const PA_DATA *pa)
{
pk_client_params *pkp = NULL;
char *client_cert = NULL;
krb5_error_code ret;
ret = _kdc_pk_rd_padata(r, pa, &pkp);
if (ret || pkp == NULL) {
ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
_kdc_r_log(r, 4, "Failed to decode PKINIT PA-DATA -- %s",
r->cname);
goto out;
}
ret = _kdc_pk_check_client(r, pkp, &client_cert);
if (client_cert)
kdc_audit_addkv((kdc_request_t)r, 0, KDC_REQUEST_KV_PKINIT_CLIENT_CERT,
"%s", client_cert);
if (ret) {
_kdc_set_e_text(r, "PKINIT certificate not allowed to "
"impersonate principal");
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_CLIENT_NAME_UNAUTHORIZED);
goto out;
}
r->pa_endtime = _kdc_pk_endtime(pkp);
if (!r->client->flags.synthetic)
r->pa_max_life = _kdc_pk_max_life(pkp);
_kdc_r_log(r, 4, "PKINIT pre-authentication succeeded -- %s using %s",
r->cname, client_cert);
ret = _kdc_pk_mk_pa_reply(r, pkp);
if (ret) {
_kdc_set_e_text(r, "Failed to build PK-INIT reply");
goto out;
}
ret = _kdc_add_initial_verified_cas(r->context, r->config,
pkp, &r->et);
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_PREAUTH_SUCCEEDED);
out:
if (pkp)
_kdc_pk_free_client_param(r->context, pkp);
free(client_cert);
return ret;
}
#endif /* PKINIT */
static krb5_error_code
pa_gss_validate(astgs_request_t r, const PA_DATA *pa)
{
gss_client_params *gcp = NULL;
char *client_name = NULL;
krb5_error_code ret;
int open = 0;
ret = _kdc_gss_rd_padata(r, pa, &gcp, &open);
if (ret && gcp == NULL)
return ret;
if (open) {
ret = _kdc_gss_check_client(r, gcp, &client_name);
if (client_name)
kdc_audit_addkv((kdc_request_t)r, 0, KDC_REQUEST_KV_GSS_INITIATOR,
"%s", client_name);
if (ret) {
_kdc_set_e_text(r, "GSS-API client not allowed to "
"impersonate principal");
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_CLIENT_NAME_UNAUTHORIZED);
goto out;
}
r->pa_endtime = _kdc_gss_endtime(r, gcp);
_kdc_r_log(r, 4, "GSS pre-authentication succeeded -- %s using %s",
r->cname, client_name);
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_PREAUTH_SUCCEEDED);
ret = _kdc_gss_mk_composite_name_ad(r, gcp);
if (ret) {
_kdc_set_e_text(r, "Failed to build GSS authorization data");
goto out;
}
}
ret = _kdc_gss_mk_pa_reply(r, gcp);
if (ret) {
if (ret != KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED)
_kdc_set_e_text(r, "Failed to build GSS pre-authentication reply");
goto out;
}
ret = kdc_request_set_attribute((kdc_request_t)r,
HSTR("org.h5l.pa-gss-client-params"), gcp);
if (ret)
goto out;
out:
kdc_object_release(gcp);
free(client_name);
return ret;
}
static krb5_error_code
pa_gss_finalize_pac(astgs_request_t r)
{
gss_client_params *gcp;
gcp = kdc_request_get_attribute((kdc_request_t)r, HSTR("org.h5l.pa-gss-client-params"));
heim_assert(gcp != NULL, "invalid GSS-API client params");
return _kdc_gss_finalize_pac(r, gcp);
}
static krb5_error_code
pa_enc_chal_validate(astgs_request_t r, const PA_DATA *pa)
{
krb5_data pepper1, pepper2;
int invalidPassword = 0;
EncryptedData enc_data;
krb5_enctype aenctype;
krb5_error_code ret;
struct Key *k;
size_t size;
int i;
heim_assert(r->armor_crypto != NULL, "ENC-CHAL called for non FAST");
if (_kdc_is_anon_request(&r->req)) {
ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
kdc_log(r->context, r->config, 4, "ENC-CHALL doesn't support anon");
return ret;
}
if (r->client->flags.locked_out) {
ret = KRB5KDC_ERR_CLIENT_REVOKED;
kdc_log(r->context, r->config, 0,
"Client (%s) is locked out", r->cname);
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_CLIENT_LOCKED_OUT);
return ret;
}
ret = decode_EncryptedData(pa->padata_value.data,
pa->padata_value.length,
&enc_data,
&size);
if (ret) {
ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
_kdc_r_log(r, 4, "Failed to decode PA-DATA -- %s",
r->cname);
return ret;
}
pepper1.data = "clientchallengearmor";
pepper1.length = strlen(pepper1.data);
pepper2.data = "challengelongterm";
pepper2.length = strlen(pepper2.data);
krb5_crypto_getenctype(r->context, r->armor_crypto, &aenctype);
kdc_log(r->context, r->config, 5, "FAST armor enctype is: %d", (int)aenctype);
for (i = 0; i < r->client->keys.len; i++) {
krb5_crypto challengecrypto, longtermcrypto;
krb5_keyblock challengekey;
k = &r->client->keys.val[i];
ret = krb5_crypto_init(r->context, &k->key, 0, &longtermcrypto);
if (ret)
continue;
ret = krb5_crypto_fx_cf2(r->context, r->armor_crypto, longtermcrypto,
&pepper1, &pepper2, aenctype,
&challengekey);
if (ret) {
krb5_crypto_destroy(r->context, longtermcrypto);
continue;
}
ret = krb5_crypto_init(r->context, &challengekey, 0,
&challengecrypto);
krb5_free_keyblock_contents(r->context, &challengekey);
if (ret) {
krb5_crypto_destroy(r->context, longtermcrypto);
continue;
}
ret = _krb5_validate_pa_enc_challenge(r->context,
challengecrypto,
KRB5_KU_ENC_CHALLENGE_CLIENT,
&enc_data,
r->cname);
krb5_crypto_destroy(r->context, challengecrypto);
if (ret) {
const char *msg;
krb5_error_code ret2;
char *str = NULL;
krb5_crypto_destroy(r->context, longtermcrypto);
invalidPassword = (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY);
if (!invalidPassword) {
goto out;
}
ret2 = krb5_enctype_to_string(r->context, k->key.keytype, &str);
if (ret2)
str = NULL;
msg = krb5_get_error_message(r->context, ret);
_kdc_r_log(r, 2, "Failed to decrypt ENC-CHAL -- %s "
"(enctype %s) error %s",
r->cname, str ? str : "unknown enctype", msg);
krb5_free_error_message(r->context, msg);
free(str);
continue;
}
/*
* Found a key that the client used, lets pick that as the reply key
*/
krb5_free_keyblock_contents(r->context, &r->reply_key);
ret = krb5_copy_keyblock_contents(r->context, &k->key, &r->reply_key);
if (ret) {
krb5_crypto_destroy(r->context, longtermcrypto);
goto out;
}
krb5_free_keyblock_contents(r->context, &challengekey);
/*
* Provide KDC authentication to the client, uses a different
* challenge key (different pepper).
*/
pepper1.data = "kdcchallengearmor";
pepper1.length = strlen(pepper1.data);
ret = krb5_crypto_fx_cf2(r->context, r->armor_crypto, longtermcrypto,
&pepper1, &pepper2, aenctype,
&challengekey);
krb5_crypto_destroy(r->context, longtermcrypto);
if (ret)
goto out;
ret = krb5_crypto_init(r->context, &challengekey, 0, &challengecrypto);
krb5_free_keyblock_contents(r->context, &challengekey);
if (ret)
goto out;
ret = _krb5_make_pa_enc_challenge(r->context, challengecrypto,
KRB5_KU_ENC_CHALLENGE_KDC,
r->rep.padata);
krb5_crypto_destroy(r->context, challengecrypto);
if (ret)
goto out;
if (ret == 0)
ret = set_salt_padata(r->rep.padata, k->salt);
/*
* Success
*/
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_VALIDATED_LONG_TERM_KEY);
goto out;
}
if (invalidPassword) {
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_WRONG_LONG_TERM_KEY);
ret = KRB5KDC_ERR_PREAUTH_FAILED;
} else {
ret = KRB5KDC_ERR_ETYPE_NOSUPP;
}
out:
free_EncryptedData(&enc_data);
return ret;
}
static krb5_error_code
pa_enc_ts_validate(astgs_request_t r, const PA_DATA *pa)
{
EncryptedData enc_data;
krb5_error_code ret;
krb5_crypto crypto;
krb5_data ts_data;
PA_ENC_TS_ENC p;
size_t len;
Key *pa_key;
char *str;
if (r->armor_crypto && !r->config->enable_armored_pa_enc_timestamp) {
ret = KRB5KDC_ERR_POLICY;
kdc_log(r->context, r->config, 0,
"Armored encrypted timestamp pre-authentication is disabled");
return ret;
} else if (!r->armor_crypto && !r->config->enable_unarmored_pa_enc_timestamp) {
ret = KRB5KDC_ERR_POLICY;
kdc_log(r->context, r->config, 0,
"Unarmored encrypted timestamp pre-authentication is disabled");
return ret;
}
if (r->client->flags.locked_out) {
ret = KRB5KDC_ERR_CLIENT_REVOKED;
kdc_log(r->context, r->config, 0,
"Client (%s) is locked out", r->cname);
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_CLIENT_LOCKED_OUT);
return ret;
}
ret = decode_EncryptedData(pa->padata_value.data,
pa->padata_value.length,
&enc_data,
&len);
if (ret) {
ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
_kdc_r_log(r, 4, "Failed to decode PA-DATA -- %s",
r->cname);
goto out;
}
ret = hdb_enctype2key(r->context, r->client, NULL,
enc_data.etype, &pa_key);
if(ret){
char *estr;
_kdc_set_e_text(r, "No key matching enctype");
ret = KRB5KDC_ERR_ETYPE_NOSUPP;
if(krb5_enctype_to_string(r->context, enc_data.etype, &estr))
estr = NULL;
if(estr == NULL)
_kdc_r_log(r, 4,
"No client key matching pa-data (%d) -- %s",
enc_data.etype, r->cname);
else
_kdc_r_log(r, 4,
"No client key matching pa-data (%s) -- %s",
estr, r->cname);
free(estr);
free_EncryptedData(&enc_data);
goto out;
}
try_next_key:
ret = krb5_crypto_init(r->context, &pa_key->key, 0, &crypto);
if (ret) {
const char *msg = krb5_get_error_message(r->context, ret);
_kdc_r_log(r, 4, "krb5_crypto_init failed: %s", msg);
krb5_free_error_message(r->context, msg);
free_EncryptedData(&enc_data);
goto out;
}
ret = krb5_decrypt_EncryptedData (r->context,
crypto,
KRB5_KU_PA_ENC_TIMESTAMP,
&enc_data,
&ts_data);
krb5_crypto_destroy(r->context, crypto);
/*
* Since the user might have several keys with the same
* enctype but with different salting, we need to try all
* the keys with the same enctype.
*/
if(ret){
krb5_error_code ret2;
const char *msg = krb5_get_error_message(r->context, ret);
ret2 = krb5_enctype_to_string(r->context,
pa_key->key.keytype, &str);
if (ret2)
str = NULL;
_kdc_r_log(r, 2, "Failed to decrypt PA-DATA -- %s "
"(enctype %s) error %s",
r->cname, str ? str : "unknown enctype", msg);
krb5_xfree(str);
krb5_free_error_message(r->context, msg);
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_PA_ETYPE,
pa_key->key.keytype);
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_WRONG_LONG_TERM_KEY);
if(hdb_next_enctype2key(r->context, r->client, NULL,
enc_data.etype, &pa_key) == 0)
goto try_next_key;
free_EncryptedData(&enc_data);
ret = KRB5KDC_ERR_PREAUTH_FAILED;
goto out;
}
free_EncryptedData(&enc_data);
ret = decode_PA_ENC_TS_ENC(ts_data.data,
ts_data.length,
&p,
&len);
krb5_data_free(&ts_data);
if(ret){
ret = KRB5KDC_ERR_PREAUTH_FAILED;
_kdc_r_log(r, 4, "Failed to decode PA-ENC-TS-ENC -- %s",
r->cname);
goto out;
}
if (labs(kdc_time - p.patimestamp) > r->context->max_skew) {
char client_time[100];
krb5_format_time(r->context, p.patimestamp,
client_time, sizeof(client_time), TRUE);
ret = KRB5KRB_AP_ERR_SKEW;
_kdc_r_log(r, 4, "Too large time skew, "
"client time %s is out by %u > %u seconds -- %s",
client_time,
(unsigned)labs(kdc_time - p.patimestamp),
r->context->max_skew,
r->cname);
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_CLIENT_TIME_SKEW);
/*
* The following is needed to make windows clients to
* retry using the timestamp in the error message, if
* there is a e_text, they become unhappy.
*/
r->e_text = NULL;
free_PA_ENC_TS_ENC(&p);
goto out;
}
free_PA_ENC_TS_ENC(&p);
ret = set_salt_padata(r->rep.padata, pa_key->salt);
if (ret == 0)
ret = krb5_copy_keyblock_contents(r->context, &pa_key->key, &r->reply_key);
if (ret)
return ret;
ret = krb5_enctype_to_string(r->context, pa_key->key.keytype, &str);
if (ret)
str = NULL;
_kdc_r_log(r, 4, "ENC-TS Pre-authentication succeeded -- %s using %s",
r->cname, str ? str : "unknown enctype");
krb5_xfree(str);
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_PA_ETYPE,
pa_key->key.keytype);
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_VALIDATED_LONG_TERM_KEY);
ret = 0;
out:
return ret;
}
struct kdc_patypes {
int type;
const char *name;
unsigned int flags;
#define PA_ANNOUNCE 1
#define PA_REQ_FAST 2 /* only use inside fast */
#define PA_SYNTHETIC_OK 4
#define PA_REPLACE_REPLY_KEY 8 /* PA mech replaces reply key */
#define PA_USES_LONG_TERM_KEY 16 /* PA mech uses client's long-term key */
krb5_error_code (*validate)(astgs_request_t, const PA_DATA *pa);
krb5_error_code (*finalize_pac)(astgs_request_t r);
void (*cleanup)(astgs_request_t r);
};
static const struct kdc_patypes pat[] = {
#ifdef PKINIT
{
KRB5_PADATA_PK_AS_REQ, "PK-INIT(ietf)",
PA_ANNOUNCE | PA_SYNTHETIC_OK | PA_REPLACE_REPLY_KEY,
pa_pkinit_validate, NULL, NULL
},
{
KRB5_PADATA_PK_AS_REQ_WIN, "PK-INIT(win2k)", PA_ANNOUNCE | PA_REPLACE_REPLY_KEY,
pa_pkinit_validate, NULL, NULL
},
{
KRB5_PADATA_PKINIT_KX, "Anonymous PK-INIT", PA_ANNOUNCE,
NULL, NULL, NULL
},
#else
{ KRB5_PADATA_PK_AS_REQ, "PK-INIT(ietf)", 0, NULL , NULL, NULL },
{ KRB5_PADATA_PK_AS_REQ_WIN, "PK-INIT(win2k)", 0, NULL, NULL, NULL },
{ KRB5_PADATA_PKINIT_KX, "Anonymous PK-INIT", 0, NULL, NULL, NULL },
#endif
{ KRB5_PADATA_PA_PK_OCSP_RESPONSE , "OCSP", 0, NULL, NULL, NULL },
{
KRB5_PADATA_ENC_TIMESTAMP , "ENC-TS",
PA_ANNOUNCE | PA_USES_LONG_TERM_KEY,
pa_enc_ts_validate, NULL, NULL
},
{
KRB5_PADATA_ENCRYPTED_CHALLENGE , "ENC-CHAL",
PA_ANNOUNCE | PA_USES_LONG_TERM_KEY | PA_REQ_FAST,
pa_enc_chal_validate, NULL, NULL
},
{ KRB5_PADATA_REQ_ENC_PA_REP , "REQ-ENC-PA-REP", 0, NULL, NULL, NULL },
{ KRB5_PADATA_FX_FAST, "FX-FAST", PA_ANNOUNCE, NULL, NULL, NULL },
{ KRB5_PADATA_FX_ERROR, "FX-ERROR", 0, NULL, NULL, NULL },
{ KRB5_PADATA_FX_COOKIE, "FX-COOKIE", 0, NULL, NULL, NULL },
{
KRB5_PADATA_GSS , "GSS",
PA_ANNOUNCE | PA_SYNTHETIC_OK | PA_REPLACE_REPLY_KEY,
pa_gss_validate, pa_gss_finalize_pac, NULL
},
};
static void
log_patypes(astgs_request_t r, METHOD_DATA *padata)
{
krb5_kdc_configuration *config = r->config;
struct rk_strpool *p = NULL;
char *str;
size_t n, m;
for (n = 0; n < padata->len; n++) {
for (m = 0; m < sizeof(pat) / sizeof(pat[0]); m++) {
if (padata->val[n].padata_type == pat[m].type) {
p = rk_strpoolprintf(p, "%s", pat[m].name);
break;
}
}
if (m == sizeof(pat) / sizeof(pat[0]))
p = rk_strpoolprintf(p, "%d", padata->val[n].padata_type);
if (p && n + 1 < padata->len)
p = rk_strpoolprintf(p, ", ");
if (p == NULL) {
kdc_log(r->context, config, 1, "out of memory");
return;
}
}
if (p == NULL)
p = rk_strpoolprintf(p, "none");
str = rk_strpoolcollect(p);
kdc_log(r->context, config, 4, "Client sent patypes: %s", str);
kdc_audit_addkv((kdc_request_t)r, KDC_AUDIT_EATWHITE,
"client-pa", "%s", str);
free(str);
}
static krb5_boolean
pa_used_flag_isset(astgs_request_t r, unsigned int flag)
{
if (r->pa_used == NULL)
return FALSE;
return (r->pa_used->flags & flag) == flag;
}
/*
*
*/
krb5_error_code
_kdc_encode_reply(krb5_context context,
krb5_kdc_configuration *config,
astgs_request_t r, uint32_t nonce,
krb5_enctype etype,
int skvno, const EncryptionKey *skey,
int ckvno,
int rk_is_subkey,
krb5_data *reply)
{
unsigned char *buf;
size_t buf_size;
size_t len = 0;
krb5_error_code ret;
krb5_crypto crypto;
KDC_REP *rep = &r->rep;
EncTicketPart *et = &r->et;
EncKDCRepPart *ek = &r->ek;
heim_assert(rep->padata != NULL, "reply padata uninitialized");
ASN1_MALLOC_ENCODE(EncTicketPart, buf, buf_size, et, &len, ret);
if(ret) {
const char *msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 4, "Failed to encode ticket: %s", msg);
krb5_free_error_message(context, msg);
return ret;
}
if(buf_size != len)
krb5_abortx(context, "Internal error in ASN.1 encoder");
ret = krb5_crypto_init(context, skey, etype, &crypto);
if (ret) {
const char *msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 4, "krb5_crypto_init failed: %s", msg);
krb5_free_error_message(context, msg);
free(buf);
return ret;
}
ret = krb5_encrypt_EncryptedData(context,
crypto,
KRB5_KU_TICKET,
buf,
len,
skvno,
&rep->ticket.enc_part);
free(buf);
krb5_crypto_destroy(context, crypto);
if(ret) {
const char *msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 4, "Failed to encrypt data: %s", msg);
krb5_free_error_message(context, msg);
return ret;
}
if (r && r->armor_crypto) {
KrbFastFinished finished;
krb5_data data;
kdc_log(context, config, 4, "FAST armor protection");
memset(&finished, 0, sizeof(finished));
krb5_data_zero(&data);
finished.timestamp = kdc_time;
finished.usec = 0;
finished.crealm = et->crealm;
finished.cname = et->cname;
ASN1_MALLOC_ENCODE(Ticket, data.data, data.length,
&rep->ticket, &len, ret);
if (ret)
return ret;
if (data.length != len)
krb5_abortx(context, "internal asn.1 error");
ret = krb5_create_checksum(context, r->armor_crypto,
KRB5_KU_FAST_FINISHED, 0,
data.data, data.length,
&finished.ticket_checksum);
krb5_data_free(&data);
if (ret)
return ret;
ret = _kdc_fast_mk_response(context, r->armor_crypto,
rep->padata, &r->strengthen_key, &finished,
nonce, &data);
free_Checksum(&finished.ticket_checksum);
if (ret)
return ret;
free_METHOD_DATA(r->rep.padata);
ret = krb5_padata_add(context, rep->padata,
KRB5_PADATA_FX_FAST,
data.data, data.length);
if (ret)
return ret;
/*
* Hide client name for privacy reasons
*/
if (r->fast.flags.requested_hidden_names) {
Realm anon_realm = KRB5_ANON_REALM;
free_Realm(&rep->crealm);
ret = copy_Realm(&anon_realm, &rep->crealm);
if (ret == 0) {
free_PrincipalName(&rep->cname);
ret = _kdc_make_anonymous_principalname(&rep->cname);
}
if (ret)
return ret;
}
}
if (rep->padata->len == 0) {
free_METHOD_DATA(rep->padata);
free(rep->padata);
rep->padata = NULL;
}
if(rep->msg_type == krb_as_rep && !config->encode_as_rep_as_tgs_rep)
ASN1_MALLOC_ENCODE(EncASRepPart, buf, buf_size, ek, &len, ret);
else
ASN1_MALLOC_ENCODE(EncTGSRepPart, buf, buf_size, ek, &len, ret);
if(ret) {
const char *msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 4, "Failed to encode KDC-REP: %s", msg);
krb5_free_error_message(context, msg);
return ret;
}
if(buf_size != len) {
free(buf);
kdc_log(context, config, 4, "Internal error in ASN.1 encoder");
_kdc_set_e_text(r, "KDC internal error");
return KRB5KRB_ERR_GENERIC;
}
ret = krb5_crypto_init(context, &r->reply_key, 0, &crypto);
if (ret) {
const char *msg = krb5_get_error_message(context, ret);
free(buf);
kdc_log(context, config, 4, "krb5_crypto_init failed: %s", msg);
krb5_free_error_message(context, msg);
return ret;
}
if(rep->msg_type == krb_as_rep) {
ret = krb5_encrypt_EncryptedData(context,
crypto,
KRB5_KU_AS_REP_ENC_PART,
buf,
len,
ckvno,
&rep->enc_part);
free(buf);
if (ret == 0)
ASN1_MALLOC_ENCODE(AS_REP, buf, buf_size, rep, &len, ret);
} else {
ret = krb5_encrypt_EncryptedData(context,
crypto,
rk_is_subkey ?
KRB5_KU_TGS_REP_ENC_PART_SUB_KEY :
KRB5_KU_TGS_REP_ENC_PART_SESSION,
buf,
len,
ckvno,
&rep->enc_part);
free(buf);
if (ret == 0)
ASN1_MALLOC_ENCODE(TGS_REP, buf, buf_size, rep, &len, ret);
}
krb5_crypto_destroy(context, crypto);
if(ret) {
const char *msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 4, "Failed to encode KDC-REP: %s", msg);
krb5_free_error_message(context, msg);
return ret;
}
if(buf_size != len) {
free(buf);
kdc_log(context, config, 4, "Internal error in ASN.1 encoder");
_kdc_set_e_text(r, "KDC internal error");
return KRB5KRB_ERR_GENERIC;
}
reply->data = buf;
reply->length = buf_size;
return 0;
}
/*
*
*/
static krb5_error_code
get_pa_etype_info(krb5_context context,
krb5_kdc_configuration *config,
METHOD_DATA *md, Key *ckey,
krb5_boolean include_salt)
{
krb5_error_code ret = 0;
ETYPE_INFO_ENTRY eie; /* do not free this one */
ETYPE_INFO ei;
PA_DATA pa;
size_t len;
/*
* Code moved here from what used to be make_etype_info_entry() because
* using the ASN.1 compiler-generated SEQUENCE OF add functions makes that
* old function's body and this one's small and clean.
*
* The following comment blocks were there:
*
* According to `the specs', we can't send a salt if we have AFS3 salted
* key, but that requires that you *know* what cell you are using (e.g by
* assuming that the cell is the same as the realm in lower case)
*
* We shouldn't sent salttype since it is incompatible with the
* specification and it breaks windows clients. The afs salting problem
* is solved by using KRB5-PADATA-AFS3-SALT implemented in Heimdal 0.7 and
* later.
*
* We return no salt type at all, as that should indicate the default salt
* type and make everybody happy. some systems (like w2k) dislike being
* told the salt type here.
*/
pa.padata_type = KRB5_PADATA_ETYPE_INFO;
pa.padata_value.data = NULL;
pa.padata_value.length = 0;
ei.len = 0;
ei.val = NULL;
eie.etype = ckey->key.keytype;
eie.salttype = NULL;
eie.salt = NULL;
if (include_salt && ckey->salt)
eie.salt = &ckey->salt->salt;
ret = add_ETYPE_INFO(&ei, &eie);
if (ret == 0)
ASN1_MALLOC_ENCODE(ETYPE_INFO, pa.padata_value.data, pa.padata_value.length,
&ei, &len, ret);
if (ret == 0)
add_METHOD_DATA(md, &pa);
free_ETYPE_INFO(&ei);
free_PA_DATA(&pa);
return ret;
}
/*
*
*/
extern const int _krb5_AES_SHA1_string_to_default_iterator;
extern const int _krb5_AES_SHA2_string_to_default_iterator;
static krb5_error_code
make_s2kparams(int value, size_t len, krb5_data **ps2kparams)
{
krb5_data *s2kparams;
krb5_error_code ret;
ALLOC(s2kparams);
if (s2kparams == NULL)
return ENOMEM;
ret = krb5_data_alloc(s2kparams, len);
if (ret) {
free(s2kparams);
return ret;
}
_krb5_put_int(s2kparams->data, value, len);
*ps2kparams = s2kparams;
return 0;
}
static krb5_error_code
make_etype_info2_entry(ETYPE_INFO2_ENTRY *ent,
Key *key,
krb5_boolean include_salt)
{
krb5_error_code ret;
ent->etype = key->key.keytype;
if (key->salt && include_salt) {
ALLOC(ent->salt);
if (ent->salt == NULL)
return ENOMEM;
*ent->salt = malloc(key->salt->salt.length + 1);
if (*ent->salt == NULL) {
free(ent->salt);
ent->salt = NULL;
return ENOMEM;
}
memcpy(*ent->salt, key->salt->salt.data, key->salt->salt.length);
(*ent->salt)[key->salt->salt.length] = '\0';
} else
ent->salt = NULL;
ent->s2kparams = NULL;
switch (key->key.keytype) {
case ETYPE_AES128_CTS_HMAC_SHA1_96:
case ETYPE_AES256_CTS_HMAC_SHA1_96:
ret = make_s2kparams(_krb5_AES_SHA1_string_to_default_iterator,
4, &ent->s2kparams);
break;
case KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128:
case KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384_192:
ret = make_s2kparams(_krb5_AES_SHA2_string_to_default_iterator,
4, &ent->s2kparams);
break;
case ETYPE_DES_CBC_CRC:
case ETYPE_DES_CBC_MD4:
case ETYPE_DES_CBC_MD5:
/* Check if this was a AFS3 salted key */
if(key->salt && key->salt->type == hdb_afs3_salt)
ret = make_s2kparams(1, 1, &ent->s2kparams);
else
ret = 0;
break;
default:
ret = 0;
break;
}
return ret;
}
/*
* Return an ETYPE-INFO2. Enctypes are storted the same way as in the
* database (client supported enctypes first, then the unsupported
* enctypes).
*/
static krb5_error_code
get_pa_etype_info2(krb5_context context,
krb5_kdc_configuration *config,
METHOD_DATA *md, Key *ckey,
krb5_boolean include_salt)
{
krb5_error_code ret = 0;
ETYPE_INFO2 pa;
unsigned char *buf;
size_t len;
pa.len = 1;
pa.val = calloc(1, sizeof(pa.val[0]));
if(pa.val == NULL)
return ENOMEM;
ret = make_etype_info2_entry(&pa.val[0], ckey, include_salt);
if (ret) {
free_ETYPE_INFO2(&pa);
return ret;
}
ASN1_MALLOC_ENCODE(ETYPE_INFO2, buf, len, &pa, &len, ret);
free_ETYPE_INFO2(&pa);
if(ret)
return ret;
ret = realloc_method_data(md);
if(ret) {
free(buf);
return ret;
}
md->val[md->len - 1].padata_type = KRB5_PADATA_ETYPE_INFO2;
md->val[md->len - 1].padata_value.length = len;
md->val[md->len - 1].padata_value.data = buf;
return 0;
}
/*
* Return 0 if the client has only older enctypes, this is for
* determining if the server should send ETYPE_INFO2 or not.
*/
static int
newer_enctype_present(krb5_context context,
struct KDC_REQ_BODY_etype *etype_list)
{
size_t i;
for (i = 0; i < etype_list->len; i++) {
if (!krb5_is_enctype_old(context, etype_list->val[i]))
return 1;
}
return 0;
}
static krb5_error_code
get_pa_etype_info_both(krb5_context context,
krb5_kdc_configuration *config,
struct KDC_REQ_BODY_etype *etype_list,
METHOD_DATA *md, Key *ckey,
krb5_boolean include_salt)
{
krb5_error_code ret;
/*
* Windows 2019 (and earlier versions) always sends the salt
* and Samba has testsuites that check this behaviour, so a
* Samba AD DC will set this flag to match the AS-REP packet
* more closely.
*/
if (config->force_include_pa_etype_salt)
include_salt = TRUE;
/*
* RFC4120 requires:
* When the AS server is to include pre-authentication data in a
* KRB-ERROR or in an AS-REP, it MUST use PA-ETYPE-INFO2, not
* PA-ETYPE-INFO, if the etype field of the client's AS-REQ lists
* at least one "newer" encryption type. Otherwise (when the etype
* field of the client's AS-REQ does not list any "newer" encryption
* types), it MUST send both PA-ETYPE-INFO2 and PA-ETYPE-INFO (both
* with an entry for each enctype). A "newer" enctype is any enctype
* first officially specified concurrently with or subsequent to the
* issue of this RFC. The enctypes DES, 3DES, or RC4 and any defined
* in [RFC1510] are not "newer" enctypes.
*
* It goes on to state:
* The preferred ordering of the "hint" pre-authentication data that
* affect client key selection is: ETYPE-INFO2, followed by ETYPE-INFO,
* followed by PW-SALT. As noted in Section 3.1.3, a KDC MUST NOT send
* ETYPE-INFO or PW-SALT when the client's AS-REQ includes at least one
* "newer" etype.
*/
ret = get_pa_etype_info2(context, config, md, ckey, include_salt);
if (ret)
return ret;
if (!newer_enctype_present(context, etype_list))
ret = get_pa_etype_info(context, config, md, ckey, include_salt);
return ret;
}
/*
*
*/
void
_log_astgs_req(astgs_request_t r, krb5_enctype setype)
{
const KDC_REQ_BODY *b = &r->req.req_body;
krb5_enctype cetype = r->reply_key.keytype;
krb5_error_code ret;
struct rk_strpool *p;
struct rk_strpool *s = NULL;
char *str;
char *cet;
char *set;
size_t i;
/*
* we are collecting ``p'' and ``s''. The former is a textual
* representation of the enctypes as strings which will be used
* for debugging. The latter is a terse comma separated list of
* the %d's of the enctypes to emit into our audit trail to
* conserve space in the logs.
*/
p = rk_strpoolprintf(NULL, "%s", "Client supported enctypes: ");
for (i = 0; i < b->etype.len; i++) {
ret = krb5_enctype_to_string(r->context, b->etype.val[i], &str);
if (ret == 0) {
p = rk_strpoolprintf(p, "%s", str);
free(str);
} else
p = rk_strpoolprintf(p, "%d", b->etype.val[i]);
if (p == NULL) {
rk_strpoolfree(s);
_kdc_r_log(r, 4, "out of memory");
return;
}
s = rk_strpoolprintf(s, "%d", b->etype.val[i]);
if (i + 1 < b->etype.len) {
p = rk_strpoolprintf(p, ", ");
s = rk_strpoolprintf(s, ",");
}
}
if (p == NULL)
p = rk_strpoolprintf(p, "no encryption types");
str = rk_strpoolcollect(s);
if (str)
kdc_audit_addkv((kdc_request_t)r, KDC_AUDIT_EATWHITE, "etypes", "%s",
str);
free(str);
ret = krb5_enctype_to_string(r->context, cetype, &cet);
if(ret == 0) {
ret = krb5_enctype_to_string(r->context, setype, &set);
if (ret == 0) {
p = rk_strpoolprintf(p, ", using %s/%s", cet, set);
free(set);
}
free(cet);
}
if (ret != 0)
p = rk_strpoolprintf(p, ", using enctypes %d/%d",
cetype, setype);
str = rk_strpoolcollect(p);
if (str)
_kdc_r_log(r, 4, "%s", str);
free(str);
kdc_audit_addkv((kdc_request_t)r, 0, "etype", "%d/%d", cetype, setype);
{
char fixedstr[128];
int result;
result = unparse_flags(KDCOptions2int(b->kdc_options), asn1_KDCOptions_units(),
fixedstr, sizeof(fixedstr));
if (result > 0) {
_kdc_r_log(r, 4, "Requested flags: %s", fixedstr);
kdc_audit_addkv((kdc_request_t)r, KDC_AUDIT_EATWHITE,
"flags", "%s", fixedstr);
}
}
}
/*
* verify the flags on `client' and `server', returning 0
* if they are OK and generating an error messages and returning
* and error code otherwise.
*/
KDC_LIB_FUNCTION krb5_error_code KDC_LIB_CALL
kdc_check_flags(astgs_request_t r,
krb5_boolean is_as_req,
hdb_entry *client,
hdb_entry *server)
{
if (client != NULL) {
/* check client */
if (client->flags.locked_out) {
kdc_audit_addreason((kdc_request_t)r, "Client is locked out");
return KRB5KDC_ERR_CLIENT_REVOKED;
}
if (client->flags.invalid) {
kdc_audit_addreason((kdc_request_t)r,
"Client has invalid bit set");
return KRB5KDC_ERR_POLICY;
}
if (!client->flags.client) {
kdc_audit_addreason((kdc_request_t)r,
"Principal may not act as client");
return KRB5KDC_ERR_POLICY;
}
if (client->valid_start && *client->valid_start > kdc_time) {
char starttime_str[100];
krb5_format_time(r->context, *client->valid_start,
starttime_str, sizeof(starttime_str), TRUE);
kdc_audit_addreason((kdc_request_t)r, "Client not yet valid "
"until %s", starttime_str);
return KRB5KDC_ERR_CLIENT_NOTYET;
}
if (client->valid_end && *client->valid_end < kdc_time) {
char endtime_str[100];
krb5_format_time(r->context, *client->valid_end,
endtime_str, sizeof(endtime_str), TRUE);
kdc_audit_addreason((kdc_request_t)r, "Client expired at %s",
endtime_str);
return KRB5KDC_ERR_NAME_EXP;
}
if (client->flags.require_pwchange &&
(server == NULL || !server->flags.change_pw))
return KRB5KDC_ERR_KEY_EXPIRED;
if (client->pw_end && *client->pw_end < kdc_time
&& (server == NULL || !server->flags.change_pw)) {
char pwend_str[100];
krb5_format_time(r->context, *client->pw_end,
pwend_str, sizeof(pwend_str), TRUE);
kdc_audit_addreason((kdc_request_t)r, "Client's key has expired "
"at %s", pwend_str);
return KRB5KDC_ERR_KEY_EXPIRED;
}
}
/* check server */
if (server != NULL) {
if (server->flags.locked_out) {
kdc_audit_addreason((kdc_request_t)r, "Server locked out");
return KRB5KDC_ERR_SERVICE_REVOKED;
}
if (server->flags.invalid) {
kdc_audit_addreason((kdc_request_t)r,
"Server has invalid flag set");
return KRB5KDC_ERR_POLICY;
}
if (!server->flags.server) {
kdc_audit_addreason((kdc_request_t)r,
"Principal may not act as server");
return KRB5KDC_ERR_POLICY;
}
if (!is_as_req && server->flags.initial) {
kdc_audit_addreason((kdc_request_t)r,
"AS-REQ is required for server");
return KRB5KDC_ERR_POLICY;
}
if (server->valid_start && *server->valid_start > kdc_time) {
char starttime_str[100];
krb5_format_time(r->context, *server->valid_start,
starttime_str, sizeof(starttime_str), TRUE);
kdc_audit_addreason((kdc_request_t)r, "Server not yet valid "
"until %s", starttime_str);
return KRB5KDC_ERR_SERVICE_NOTYET;
}
if (server->valid_end && *server->valid_end < kdc_time) {
char endtime_str[100];
krb5_format_time(r->context, *server->valid_end,
endtime_str, sizeof(endtime_str), TRUE);
kdc_audit_addreason((kdc_request_t)r, "Server expired at %s",
endtime_str);
return KRB5KDC_ERR_SERVICE_EXP;
}
if (server->pw_end && *server->pw_end < kdc_time) {
char pwend_str[100];
krb5_format_time(r->context, *server->pw_end,
pwend_str, sizeof(pwend_str), TRUE);
kdc_audit_addreason((kdc_request_t)r, "Server's key has expired "
"at %s", pwend_str);
return KRB5KDC_ERR_KEY_EXPIRED;
}
}
return 0;
}
/*
* Return TRUE if `from' is part of `addresses' taking into consideration
* the configuration variables that tells us how strict we should be about
* these checks
*/
krb5_boolean
_kdc_check_addresses(astgs_request_t r, HostAddresses *addresses,
const struct sockaddr *from)
{
krb5_kdc_configuration *config = r->config;
krb5_error_code ret;
krb5_address addr;
krb5_boolean result;
krb5_boolean only_netbios = TRUE;
size_t i;
if (!config->check_ticket_addresses && !config->warn_ticket_addresses)
return TRUE;
/*
* Fields of HostAddresses type are always OPTIONAL and should be non-
* empty, but we check for empty just in case as our compiler doesn't
* support size constraints on SEQUENCE OF.
*/
if (addresses == NULL || addresses->len == 0)
return config->allow_null_ticket_addresses;
for (i = 0; i < addresses->len; ++i) {
if (addresses->val[i].addr_type != KRB5_ADDRESS_NETBIOS) {
only_netbios = FALSE;
}
}
/* Windows sends it's netbios name, which I can only assume is
* used for the 'allowed workstations' check. This is painful,
* but we still want to check IP addresses if they happen to be
* present.
*/
if(only_netbios)
return config->allow_null_ticket_addresses;
ret = krb5_sockaddr2address (r->context, from, &addr);
if(ret)
return FALSE;
result = krb5_address_search(r->context, &addr, addresses);
krb5_free_address (r->context, &addr);
return result;
}
/*
*
*/
krb5_error_code
_kdc_check_anon_policy(astgs_request_t r)
{
if (!r->config->allow_anonymous) {
kdc_audit_addreason((kdc_request_t)r,
"Anonymous tickets denied by local policy");
return KRB5KDC_ERR_POLICY;
}
return 0;
}
/*
* Determine whether the client requested a PAC be included
* or excluded explictly, or whether it doesn't care.
*/
static uint64_t
get_pac_attributes(krb5_context context, KDC_REQ *req)
{
krb5_error_code ret;
PA_PAC_REQUEST pacreq;
const PA_DATA *pa;
int i = 0;
uint32_t pac_attributes;
pa = _kdc_find_padata(req, &i, KRB5_PADATA_PA_PAC_REQUEST);
if (pa == NULL)
return KRB5_PAC_WAS_GIVEN_IMPLICITLY;
ret = decode_PA_PAC_REQUEST(pa->padata_value.data,
pa->padata_value.length,
&pacreq,
NULL);
if (ret)
return KRB5_PAC_WAS_GIVEN_IMPLICITLY;
pac_attributes = pacreq.include_pac ? KRB5_PAC_WAS_REQUESTED : 0;
free_PA_PAC_REQUEST(&pacreq);
return pac_attributes;
}
/*
*
*/
static krb5_error_code
generate_pac(astgs_request_t r, const Key *skey, const Key *tkey,
krb5_boolean is_tgs)
{
krb5_error_code ret;
krb5_data data;
uint16_t rodc_id;
krb5_principal client;
krb5_const_principal canon_princ = NULL;
r->pac_attributes = get_pac_attributes(r->context, &r->req);
kdc_audit_setkv_number((kdc_request_t)r, "pac_attributes",
r->pac_attributes);
if (!_kdc_include_pac_p(r))
return 0;
/*
* When a PA mech does not use the client's long-term key, the PAC
* may include the client's long-term key (encrypted in the reply key)
* for use by other shared secret authentication protocols, e.g. NTLM.
* Validate a PA mech was actually used before doing this.
*/
ret = _kdc_pac_generate(r,
r->client,
r->server,
r->pa_used && !pa_used_flag_isset(r, PA_USES_LONG_TERM_KEY)
? &r->reply_key : NULL,
r->pac_attributes,
&r->pac);
if (ret) {
_kdc_r_log(r, 4, "PAC generation failed for -- %s",
r->cname);
return ret;
}
if (r->pac == NULL)
return 0;
rodc_id = r->server->kvno >> 16;
/* libkrb5 expects ticket and PAC client names to match */
ret = _krb5_principalname2krb5_principal(r->context, &client,
r->et.cname, r->et.crealm);
if (ret)
return ret;
/*
* Include the canonical name of the principal in the authorization
* data, if the realms match (if they don't, then the KDC could
* impersonate any realm. Windows always canonicalizes the realm,
* but Heimdal permits aliases between realms.)
*/
if (krb5_realm_compare(r->context, client, r->canon_client_princ)) {
char *cpn = NULL;
canon_princ = r->canon_client_princ;
(void) krb5_unparse_name(r->context, canon_princ, &cpn);
kdc_audit_addkv((kdc_request_t)r, 0, "canon_client_name", "%s",
cpn ? cpn : "<unknown>");
krb5_xfree(cpn);
}
if (r->pa_used && r->pa_used->finalize_pac) {
ret = r->pa_used->finalize_pac(r);
if (ret)
return ret;
}
ret = _krb5_pac_sign(r->context,
r->pac,
r->et.authtime,
client,
&skey->key, /* Server key */
&tkey->key, /* TGS key */
rodc_id,
NULL, /* UPN */
canon_princ,
is_tgs ? &r->pac_attributes : NULL,
&data);
krb5_free_principal(r->context, client);
krb5_pac_free(r->context, r->pac);
r->pac = NULL;
if (ret) {
_kdc_r_log(r, 4, "PAC signing failed for -- %s",
r->cname);
return ret;
}
ret = _kdc_tkt_insert_pac(r->context, &r->et, &data);
krb5_data_free(&data);
return ret;
}
/*
*
*/
krb5_boolean
_kdc_is_anonymous(krb5_context context, krb5_const_principal principal)
{
return krb5_principal_is_anonymous(context, principal, KRB5_ANON_MATCH_ANY);
}
/*
* Returns TRUE if principal is the unauthenticated anonymous identity,
* i.e. WELLKNOWN/ANONYMOUS@WELLKNOWN:ANONYMOUS. Unfortunately due to
* backwards compatibility logic in krb5_principal_is_anonymous() we
* have to use our own implementation.
*/
krb5_boolean
_kdc_is_anonymous_pkinit(krb5_context context, krb5_const_principal principal)
{
return _kdc_is_anonymous(context, principal) &&
strcmp(principal->realm, KRB5_ANON_REALM) == 0;
}
static int
require_preauth_p(astgs_request_t r)
{
return r->config->require_preauth
|| r->client->flags.require_preauth
|| r->server->flags.require_preauth;
}
/*
*
*/
static krb5_error_code
add_enc_pa_rep(astgs_request_t r)
{
krb5_error_code ret;
krb5_crypto crypto;
Checksum checksum;
krb5_data cdata;
size_t len;
ret = krb5_crypto_init(r->context, &r->reply_key, 0, &crypto);
if (ret)
return ret;
ret = krb5_create_checksum(r->context, crypto,
KRB5_KU_AS_REQ, 0,
r->request.data, r->request.length,
&checksum);
krb5_crypto_destroy(r->context, crypto);
if (ret)
return ret;
ASN1_MALLOC_ENCODE(Checksum, cdata.data, cdata.length,
&checksum, &len, ret);
free_Checksum(&checksum);
if (ret)
return ret;
heim_assert(cdata.length == len, "ASN.1 internal error");
if (r->ek.encrypted_pa_data == NULL) {
ALLOC(r->ek.encrypted_pa_data);
if (r->ek.encrypted_pa_data == NULL)
return ENOMEM;
}
ret = krb5_padata_add(r->context, r->ek.encrypted_pa_data,
KRB5_PADATA_REQ_ENC_PA_REP, cdata.data, cdata.length);
if (ret)
return ret;
if (!r->config->enable_fast)
return 0;
return krb5_padata_add(r->context, r->ek.encrypted_pa_data,
KRB5_PADATA_FX_FAST, NULL, 0);
}
/*
* Add an authorization data element indicating that a synthetic
* principal was used, so that the TGS does not accidentally
* synthesize a non-synthetic principal that has since been deleted.
*/
static krb5_error_code
add_synthetic_princ_ad(astgs_request_t r)
{
krb5_data data;
krb5_data_zero(&data);
return _kdc_tkt_add_if_relevant_ad(r->context, &r->et,
KRB5_AUTHDATA_SYNTHETIC_PRINC_USED,
&data);
}
static krb5_error_code
get_local_tgs(krb5_context context,
krb5_kdc_configuration *config,
krb5_const_realm realm,
HDB **krbtgtdb,
hdb_entry **krbtgt)
{
krb5_error_code ret;
krb5_principal tgs_name;
*krbtgtdb = NULL;
*krbtgt = NULL;
ret = krb5_make_principal(context,
&tgs_name,
realm,
KRB5_TGS_NAME,
realm,
NULL);
if (ret == 0)
ret = _kdc_db_fetch(context, config, tgs_name,
HDB_F_GET_KRBTGT, NULL, krbtgtdb, krbtgt);
krb5_free_principal(context, tgs_name);
return ret;
}
/*
*
*/
krb5_error_code
_kdc_as_rep(astgs_request_t r)
{
krb5_kdc_configuration *config = r->config;
KDC_REQ *req = &r->req;
const char *from = r->from;
KDC_REQ_BODY *b = NULL;
KDC_REP *rep = &r->rep;
KDCOptions f;
krb5_enctype setype;
krb5_error_code ret = 0;
Key *skey;
int found_pa = 0;
int i, flags = HDB_F_FOR_AS_REQ;
const PA_DATA *pa;
krb5_boolean is_tgs;
const char *msg;
Key *krbtgt_key;
memset(rep, 0, sizeof(*rep));
ALLOC(rep->padata);
if (rep->padata == NULL) {
ret = ENOMEM;
krb5_set_error_message(r->context, ret, N_("malloc: out of memory", ""));
goto out;
}
/*
* Look for FAST armor and unwrap
*/
ret = _kdc_fast_unwrap_request(r, NULL, NULL);
if (ret) {
_kdc_r_log(r, 1, "FAST unwrap request from %s failed: %d", from, ret);
goto out;
}
b = &req->req_body;
f = b->kdc_options;
if (f.canonicalize)
flags |= HDB_F_CANON;
if (b->sname == NULL) {
ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
_kdc_set_e_text(r, "No server in request");
goto out;
}
ret = _krb5_principalname2krb5_principal(r->context, &r->server_princ,
*(b->sname), b->realm);
if (!ret)
ret = krb5_unparse_name(r->context, r->server_princ, &r->sname);
if (ret) {
kdc_log(r->context, config, 2,
"AS_REQ malformed server name from %s", from);
goto out;
}
if (b->cname == NULL) {
ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
_kdc_set_e_text(r, "No client in request");
goto out;
}
ret = _krb5_principalname2krb5_principal(r->context, &r->client_princ,
*(b->cname), b->realm);
if (!ret)
ret = krb5_unparse_name(r->context, r->client_princ, &r->cname);
if (ret) {
kdc_log(r->context, config, 2,
"AS-REQ malformed client name from %s", from);
goto out;
}
kdc_log(r->context, config, 4, "AS-REQ %s from %s for %s",
r->cname, r->from, r->sname);
is_tgs = krb5_principal_is_krbtgt(r->context, r->server_princ);
if (_kdc_is_anonymous(r->context, r->client_princ) &&
!_kdc_is_anon_request(req)) {
kdc_log(r->context, config, 2, "Anonymous client w/o anonymous flag");
ret = KRB5KDC_ERR_BADOPTION;
goto out;
}
ret = _kdc_db_fetch(r->context, config, r->client_princ,
HDB_F_GET_CLIENT | HDB_F_SYNTHETIC_OK | flags, NULL,
&r->clientdb, &r->client);
switch (ret) {
case 0: /* Success */
break;
case HDB_ERR_NOT_FOUND_HERE:
kdc_log(r->context, config, 5, "client %s does not have secrets at this KDC, need to proxy",
r->cname);
goto out;
case HDB_ERR_WRONG_REALM: {
char *fixed_client_name = NULL;
ret = krb5_unparse_name(r->context, r->client->principal,
&fixed_client_name);
if (ret) {
goto out;
}
kdc_log(r->context, config, 4, "WRONG_REALM - %s -> %s",
r->cname, fixed_client_name);
free(fixed_client_name);
r->e_text = NULL;
ret = _kdc_fast_mk_error(r, r->rep.padata, r->armor_crypto,
&req->req_body,
r->error_code = KRB5_KDC_ERR_WRONG_REALM,
r->client->principal, r->server_princ,
NULL, NULL, r->reply);
goto out;
}
default:
{
msg = krb5_get_error_message(r->context, ret);
kdc_log(r->context, config, 4, "UNKNOWN -- %s: %s", r->cname, msg);
krb5_free_error_message(r->context, msg);
ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_CLIENT_UNKNOWN);
goto out;
}
}
ret = _kdc_db_fetch(r->context, config, r->server_princ,
HDB_F_GET_SERVER | HDB_F_DELAY_NEW_KEYS |
flags | (is_tgs ? HDB_F_GET_KRBTGT : 0),
NULL, &r->serverdb, &r->server);
switch (ret) {
case 0: /* Success */
break;
case HDB_ERR_NOT_FOUND_HERE:
kdc_log(r->context, config, 5, "target %s does not have secrets at this KDC, need to proxy",
r->sname);
goto out;
default:
msg = krb5_get_error_message(r->context, ret);
kdc_log(r->context, config, 4, "UNKNOWN -- %s: %s", r->sname, msg);
krb5_free_error_message(r->context, msg);
ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
goto out;
}
/*
* Select an enctype for the to-be-issued ticket's session key using the
* intersection of the client's requested enctypes and the server's (like a
* root krbtgt, but not necessarily) etypes from its HDB entry.
*/
ret = _kdc_find_etype(r, (is_tgs ? KFE_IS_TGS:0) | KFE_USE_CLIENT,
b->etype.val, b->etype.len,
&r->sessionetype, NULL, NULL);
if (ret) {
kdc_log(r->context, config, 4,
"Client (%s) from %s has no common enctypes with KDC "
"to use for the session key",
r->cname, from);
goto out;
}
/*
* Pre-auth processing
*/
if(req->padata){
unsigned int n;
log_patypes(r, req->padata);
/* Check if preauth matching */
for (n = 0; !found_pa && n < sizeof(pat) / sizeof(pat[0]); n++) {
if (pat[n].validate == NULL)
continue;
if (r->armor_crypto == NULL && (pat[n].flags & PA_REQ_FAST))
continue;
kdc_log(r->context, config, 5,
"Looking for %s pa-data -- %s", pat[n].name, r->cname);
i = 0;
pa = _kdc_find_padata(req, &i, pat[n].type);
if (pa) {
if (r->client->flags.synthetic &&
!(pat[n].flags & PA_SYNTHETIC_OK)) {
kdc_log(r->context, config, 4, "UNKNOWN -- %s", r->cname);
ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
goto out;
}
kdc_audit_addkv((kdc_request_t)r, KDC_AUDIT_VIS, "pa", "%s",
pat[n].name);
ret = pat[n].validate(r, pa);
if (ret != 0) {
krb5_error_code ret2;
Key *ckey = NULL;
krb5_boolean default_salt;
if (ret != KRB5_KDC_ERR_MORE_PREAUTH_DATA_REQUIRED &&
!kdc_audit_getkv((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT))
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_PREAUTH_FAILED);
/*
* If there is a client key, send ETYPE_INFO{,2}
*/
ret2 = _kdc_find_etype(r, KFE_IS_PREAUTH|KFE_USE_CLIENT,
b->etype.val, b->etype.len,
NULL, &ckey, &default_salt);
if (ret2 == 0) {
ret2 = get_pa_etype_info_both(r->context, config, &b->etype,
r->rep.padata, ckey, !default_salt);
if (ret2 != 0)
ret = ret2;
}
goto out;
}
if (!kdc_audit_getkv((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT))
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_PREAUTH_SUCCEEDED);
kdc_log(r->context, config, 4,
"%s pre-authentication succeeded -- %s",
pat[n].name, r->cname);
found_pa = 1;
r->pa_used = &pat[n];
r->et.flags.pre_authent = 1;
}
}
}
if (found_pa == 0) {
Key *ckey = NULL;
size_t n;
krb5_boolean default_salt;
if (r->client->flags.synthetic) {
kdc_log(r->context, config, 4, "UNKNOWN -- %s", r->cname);
ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
goto out;
}
for (n = 0; n < sizeof(pat) / sizeof(pat[0]); n++) {
if ((pat[n].flags & PA_ANNOUNCE) == 0)
continue;
if (!r->armor_crypto && (pat[n].flags & PA_REQ_FAST))
continue;
if (pat[n].type == KRB5_PADATA_PKINIT_KX && !r->config->allow_anonymous)
continue;
if (pat[n].type == KRB5_PADATA_ENC_TIMESTAMP) {
if (r->armor_crypto && !r->config->enable_armored_pa_enc_timestamp)
continue;
if (!r->armor_crypto && !r->config->enable_unarmored_pa_enc_timestamp)
continue;
}
if (pat[n].type == KRB5_PADATA_FX_FAST && !r->config->enable_fast)
continue;
if (pat[n].type == KRB5_PADATA_GSS && !r->config->enable_gss_preauth)
continue;
ret = krb5_padata_add(r->context, r->rep.padata,
pat[n].type, NULL, 0);
if (ret)
goto out;
}
/*
* If there is a client key, send ETYPE_INFO{,2}
*/
ret = _kdc_find_etype(r, KFE_IS_PREAUTH|KFE_USE_CLIENT,
b->etype.val, b->etype.len,
NULL, &ckey, &default_salt);
if (ret == 0) {
ret = get_pa_etype_info_both(r->context, config, &b->etype,
r->rep.padata, ckey, !default_salt);
if (ret)
goto out;
}
/*
* send requre preauth is its required or anon is requested,
* anon is today only allowed via preauth mechanisms.
*/
if (require_preauth_p(r) || _kdc_is_anon_request(&r->req)) {
ret = KRB5KDC_ERR_PREAUTH_REQUIRED;
_kdc_set_e_text(r, "Need to use PA-ENC-TIMESTAMP/PA-PK-AS-REQ");
goto out;
}
if (ckey == NULL) {
ret = KRB5KDC_ERR_CLIENT_NOTYET;
_kdc_set_e_text(r, "Doesn't have a client key available");
goto out;
}
krb5_free_keyblock_contents(r->context, &r->reply_key);
ret = krb5_copy_keyblock_contents(r->context, &ckey->key, &r->reply_key);
if (ret)
goto out;
}
r->canon_client_princ = r->client->principal;
/*
* Verify flags after the user been required to prove its identity
* with in a preauth mech.
*/
ret = _kdc_check_access(r);
if(ret)
goto out;
if (_kdc_is_anon_request(&r->req)) {
ret = _kdc_check_anon_policy(r);
if (ret) {
_kdc_set_e_text(r, "Anonymous ticket requests are disabled");
goto out;
}
r->et.flags.anonymous = 1;
}
kdc_audit_setkv_number((kdc_request_t)r, KDC_REQUEST_KV_AUTH_EVENT,
KDC_AUTH_EVENT_CLIENT_AUTHORIZED);
/*
* Select the best encryption type for the KDC with out regard to
* the client since the client never needs to read that data.
*/
ret = _kdc_get_preferred_key(r->context, config,
r->server, r->sname,
&setype, &skey);
if(ret)
goto out;
/* If server is not krbtgt, fetch local krbtgt key for signing authdata */
if (is_tgs) {
krbtgt_key = skey;
} else {
ret = get_local_tgs(r->context, config, r->server_princ->realm,
&r->krbtgtdb, &r->krbtgt);
if (ret)
goto out;
ret = _kdc_get_preferred_key(r->context, config, r->krbtgt,
r->server_princ->realm,
NULL, &krbtgt_key);
if (ret)
goto out;
}
if(f.renew || f.validate || f.proxy || f.forwarded || f.enc_tkt_in_skey) {
ret = KRB5KDC_ERR_BADOPTION;
_kdc_set_e_text(r, "Bad KDC options");
goto out;
}
/*
* Build reply
*/
rep->pvno = 5;
rep->msg_type = krb_as_rep;
if (!config->historical_anon_realm &&
_kdc_is_anonymous(r->context, r->client_princ)) {
Realm anon_realm = KRB5_ANON_REALM;
ret = copy_Realm(&anon_realm, &rep->crealm);
} else if (f.canonicalize || r->client->flags.force_canonicalize)
ret = copy_Realm(&r->canon_client_princ->realm, &rep->crealm);
else
ret = copy_Realm(&r->client_princ->realm, &rep->crealm);
if (ret)
goto out;
if (r->et.flags.anonymous)
ret = _kdc_make_anonymous_principalname(&rep->cname);
else if (f.canonicalize || r->client->flags.force_canonicalize)
ret = _krb5_principal2principalname(&rep->cname, r->canon_client_princ);
else
ret = _krb5_principal2principalname(&rep->cname, r->client_princ);
if (ret)
goto out;
rep->ticket.tkt_vno = 5;
if (f.canonicalize || r->server->flags.force_canonicalize)
ret = copy_Realm(&r->server->principal->realm, &rep->ticket.realm);
else
ret = copy_Realm(&r->server_princ->realm, &rep->ticket.realm);
if (ret)
goto out;
if (f.canonicalize || r->server->flags.force_canonicalize)
_krb5_principal2principalname(&rep->ticket.sname,
r->server->principal);
else
_krb5_principal2principalname(&rep->ticket.sname,
r->server_princ);
/* java 1.6 expects the name to be the same type, lets allow that
* uncomplicated name-types. */
#define CNT(sp,t) (((sp)->sname->name_type) == KRB5_NT_##t)
if (CNT(b, UNKNOWN) || CNT(b, PRINCIPAL) || CNT(b, SRV_INST) || CNT(b, SRV_HST) || CNT(b, SRV_XHST))
rep->ticket.sname.name_type = b->sname->name_type;
#undef CNT
r->et.flags.initial = 1;
if(r->client->flags.forwardable && r->server->flags.forwardable)
r->et.flags.forwardable = f.forwardable;
if(r->client->flags.proxiable && r->server->flags.proxiable)
r->et.flags.proxiable = f.proxiable;
else if (f.proxiable) {
_kdc_set_e_text(r, "Ticket may not be proxiable");
ret = KRB5KDC_ERR_POLICY;
goto out;
}
if(r->client->flags.postdate && r->server->flags.postdate)
r->et.flags.may_postdate = f.allow_postdate;
else if (f.allow_postdate){
_kdc_set_e_text(r, "Ticket may not be postdateable");
ret = KRB5KDC_ERR_POLICY;
goto out;
}
if (b->addresses)
kdc_audit_addaddrs((kdc_request_t)r, b->addresses, "reqaddrs");
/* check for valid set of addresses */
if (!_kdc_check_addresses(r, b->addresses, r->addr)) {
if (r->config->warn_ticket_addresses) {
kdc_audit_setkv_bool((kdc_request_t)r, "wrongaddr", TRUE);
} else {
_kdc_set_e_text(r, "Request from wrong address");
ret = KRB5KRB_AP_ERR_BADADDR;
goto out;
}
}
ret = copy_PrincipalName(&rep->cname, &r->et.cname);
if (ret)
goto out;
ret = copy_Realm(&rep->crealm, &r->et.crealm);
if (ret)
goto out;
{
time_t start;
time_t t;
start = r->et.authtime = kdc_time;
if(f.postdated && req->req_body.from){
ALLOC(r->et.starttime);
start = *r->et.starttime = *req->req_body.from;
r->et.flags.invalid = 1;
r->et.flags.postdated = 1; /* XXX ??? */
}
_kdc_fix_time(&b->till);
t = *b->till;
/* be careful not to overflow */
/*
* Pre-auth can override r->client->max_life if configured.
*
* See pre-auth methods, specifically PKINIT, which can get or derive
* this from the client's certificate.
*/
if (r->pa_max_life > 0)
t = rk_time_add(start, min(rk_time_sub(t, start), r->pa_max_life));
else if (r->client->max_life && *r->client->max_life)
t = rk_time_add(start, min(rk_time_sub(t, start),
*r->client->max_life));
if (r->server->max_life && *r->server->max_life)
t = rk_time_add(start, min(rk_time_sub(t, start),
*r->server->max_life));
/* Pre-auth can bound endtime as well */
if (r->pa_endtime > 0)
t = rk_time_add(start, min(rk_time_sub(t, start), r->pa_endtime));
#if 0
t = min(t, rk_time_add(start, realm->max_life));
#endif
r->et.endtime = t;
if(f.renewable_ok && r->et.endtime < *b->till){
f.renewable = 1;
if(b->rtime == NULL){
ALLOC(b->rtime);
*b->rtime = 0;
}
if(*b->rtime < *b->till)
*b->rtime = *b->till;
}
if(f.renewable && b->rtime){
t = *b->rtime;
if(t == 0)
t = MAX_TIME;
if(r->client->max_renew && *r->client->max_renew)
t = rk_time_add(start, min(rk_time_sub(t, start),
*r->client->max_renew));
if(r->server->max_renew && *r->server->max_renew)
t = rk_time_add(start, min(rk_time_sub(t, start),
*r->server->max_renew));
#if 0
t = min(t, rk_time_add(start, realm->max_renew));
#endif
ALLOC(r->et.renew_till);
*r->et.renew_till = t;
r->et.flags.renewable = 1;
}
}
if(b->addresses){
ALLOC(r->et.caddr);
copy_HostAddresses(b->addresses, r->et.caddr);
}
r->et.transited.tr_type = domain_X500_Compress;
krb5_data_zero(&r->et.transited.contents);
/* The MIT ASN.1 library (obviously) doesn't tell lengths encoded
* as 0 and as 0x80 (meaning indefinite length) apart, and is thus
* incapable of correctly decoding SEQUENCE OF's of zero length.
*
* To fix this, always send at least one no-op last_req
*
* If there's a pw_end or valid_end we will use that,
* otherwise just a dummy lr.
*/
r->ek.last_req.val = malloc(2 * sizeof(*r->ek.last_req.val));
if (r->ek.last_req.val == NULL) {
ret = ENOMEM;
goto out;
}
r->ek.last_req.len = 0;
if (r->client->pw_end
&& (config->kdc_warn_pwexpire == 0
|| kdc_time + config->kdc_warn_pwexpire >= *r->client->pw_end)) {
r->ek.last_req.val[r->ek.last_req.len].lr_type = LR_PW_EXPTIME;
r->ek.last_req.val[r->ek.last_req.len].lr_value = *r->client->pw_end;
++r->ek.last_req.len;
}
if (r->client->valid_end) {
r->ek.last_req.val[r->ek.last_req.len].lr_type = LR_ACCT_EXPTIME;
r->ek.last_req.val[r->ek.last_req.len].lr_value = *r->client->valid_end;
++r->ek.last_req.len;
}
if (r->ek.last_req.len == 0) {
r->ek.last_req.val[r->ek.last_req.len].lr_type = LR_NONE;
r->ek.last_req.val[r->ek.last_req.len].lr_value = 0;
++r->ek.last_req.len;
}
r->ek.nonce = b->nonce;
if (r->client->valid_end || r->client->pw_end) {
ALLOC(r->ek.key_expiration);
if (r->client->valid_end) {
if (r->client->pw_end)
*r->ek.key_expiration = min(*r->client->valid_end,
*r->client->pw_end);
else
*r->ek.key_expiration = *r->client->valid_end;
} else
*r->ek.key_expiration = *r->client->pw_end;
} else
r->ek.key_expiration = NULL;
r->ek.flags = r->et.flags;
r->ek.authtime = r->et.authtime;
if (r->et.starttime) {
ALLOC(r->ek.starttime);
*r->ek.starttime = *r->et.starttime;
}
r->ek.endtime = r->et.endtime;
if (r->et.renew_till) {
ALLOC(r->ek.renew_till);
*r->ek.renew_till = *r->et.renew_till;
}
ret = copy_Realm(&rep->ticket.realm, &r->ek.srealm);
if (ret)
goto out;
ret = copy_PrincipalName(&rep->ticket.sname, &r->ek.sname);
if (ret)
goto out;
if(r->et.caddr){
ALLOC(r->ek.caddr);
copy_HostAddresses(r->et.caddr, r->ek.caddr);
}
/*
* Check session and reply keys
*/
if (r->session_key.keytype == ETYPE_NULL) {
ret = krb5_generate_random_keyblock(r->context, r->sessionetype, &r->session_key);
if (ret)
goto out;
}
if (r->reply_key.keytype == ETYPE_NULL) {
_kdc_set_e_text(r, "Client has no reply key");
ret = KRB5KDC_ERR_CLIENT_NOTYET;
goto out;
}
ret = copy_EncryptionKey(&r->session_key, &r->et.key);
if (ret)
goto out;
ret = copy_EncryptionKey(&r->session_key, &r->ek.key);
if (ret)
goto out;
/* Add the PAC */
if (!r->et.flags.anonymous) {
ret = generate_pac(r, skey, krbtgt_key, is_tgs);
if (ret)
goto out;
}
if (r->client->flags.synthetic) {
ret = add_synthetic_princ_ad(r);
if (ret)
goto out;
}
_kdc_log_timestamp(r, "AS-REQ", r->et.authtime,
r->et.starttime, r->et.endtime,
r->et.renew_till);
_log_astgs_req(r, setype);
/*
* We always say we support FAST/enc-pa-rep
*/
r->et.flags.enc_pa_rep = r->ek.flags.enc_pa_rep = 1;
/*
* update reply-key with strengthen-key
*/
ret = _kdc_fast_strengthen_reply_key(r);
if (ret)
goto out;
/*
* Add REQ_ENC_PA_REP if client supports it
*/
i = 0;
pa = _kdc_find_padata(req, &i, KRB5_PADATA_REQ_ENC_PA_REP);
if (pa) {
ret = add_enc_pa_rep(r);
if (ret) {
msg = krb5_get_error_message(r->context, ret);
_kdc_r_log(r, 4, "add_enc_pa_rep failed: %s: %d", msg, ret);
krb5_free_error_message(r->context, msg);
goto out;
}
}
/*
* Last chance for plugins to update reply
*/
ret = _kdc_finalize_reply(r);
if (ret)
goto out;
/*
* Don't send kvno from client entry if the pre-authentication
* mechanism replaced the reply key.
*/
ret = _kdc_encode_reply(r->context, config,
r, req->req_body.nonce, setype,
r->server->kvno, &skey->key,
pa_used_flag_isset(r, PA_REPLACE_REPLY_KEY) ? 0 : r->client->kvno,
0, r->reply);
if (ret)
goto out;
/*
* Check if message is too large
*/
if (r->datagram_reply && r->reply->length > config->max_datagram_reply_length) {
krb5_data_free(r->reply);
ret = KRB5KRB_ERR_RESPONSE_TOO_BIG;
_kdc_set_e_text(r, "Reply packet too large");
}
out:
if (ret) {
/* Overwrite error_code only if we have an actual error. */
r->error_code = ret;
}
{
krb5_error_code ret2 = _kdc_audit_request(r);
if (ret2) {
krb5_data_free(r->reply);
ret = ret2;
}
}
/*
* In case of a non proxy error, build an error message.
*/
if (ret != 0 && ret != HDB_ERR_NOT_FOUND_HERE && r->reply->length == 0)
ret = _kdc_fast_mk_error(r,
r->rep.padata,
r->armor_crypto,
&req->req_body,
r->error_code ? r->error_code : ret,
r->client_princ,
r->server_princ,
NULL, NULL,
r->reply);
if (r->pa_used && r->pa_used->cleanup)
r->pa_used->cleanup(r);
free_AS_REP(&r->rep);
free_EncTicketPart(&r->et);
free_EncKDCRepPart(&r->ek);
_kdc_free_fast_state(&r->fast);
if (r->client_princ) {
krb5_free_principal(r->context, r->client_princ);
r->client_princ = NULL;
}
if (r->server_princ){
krb5_free_principal(r->context, r->server_princ);
r->server_princ = NULL;
}
if (r->client)
_kdc_free_ent(r->context, r->clientdb, r->client);
if (r->server)
_kdc_free_ent(r->context, r->serverdb, r->server);
if (r->krbtgt)
_kdc_free_ent(r->context, r->krbtgtdb, r->krbtgt);
if (r->armor_crypto) {
krb5_crypto_destroy(r->context, r->armor_crypto);
r->armor_crypto = NULL;
}
if (r->armor_ticket)
krb5_free_ticket(r->context, r->armor_ticket);
if (r->armor_server)
_kdc_free_ent(r->context, r->armor_serverdb, r->armor_server);
krb5_free_keyblock_contents(r->context, &r->reply_key);
krb5_free_keyblock_contents(r->context, &r->session_key);
krb5_free_keyblock_contents(r->context, &r->strengthen_key);
krb5_pac_free(r->context, r->pac);
return ret;
}