#include "k5-int.h"
#include "int-proto.h"
#include "os-proto.h"
#define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
static krb5_error_code
s4u_identify_user(krb5_context context,
krb5_creds *in_creds,
krb5_data *subject_cert,
krb5_principal *canon_user)
{
krb5_principal_data client;
krb5_data empty_name = empty_data();
*canon_user = NULL;
if (in_creds->client == NULL && subject_cert == NULL) {
return EINVAL;
}
if (in_creds->client != NULL &&
in_creds->client->type != KRB5_NT_ENTERPRISE_PRINCIPAL) {
int anonymous;
anonymous = krb5_principal_compare(context, in_creds->client,
krb5_anonymous_principal());
return krb5_copy_principal(context,
anonymous ? in_creds->server
: in_creds->client,
canon_user);
}
if (in_creds->client != NULL) {
client = *in_creds->client;
client.realm = in_creds->server->realm;
return k5_identify_realm(context, &client, NULL, canon_user);
}
client.magic = KV5M_PRINCIPAL;
client.realm = in_creds->server->realm;
client.data = &empty_name;
client.length = 1;
client.type = KRB5_NT_X500_PRINCIPAL;
return k5_identify_realm(context, &client, subject_cert, canon_user);
}
static krb5_error_code
make_pa_for_user_checksum(krb5_context context,
krb5_keyblock *key,
krb5_pa_for_user *req,
krb5_checksum *cksum)
{
krb5_error_code code;
int i;
char *p;
krb5_data data;
data.length = 4;
for (i = 0; i < req->user->length; i++)
data.length += req->user->data[i].length;
data.length += req->user->realm.length;
data.length += req->auth_package.length;
p = data.data = malloc(data.length);
if (data.data == NULL)
return ENOMEM;
p[0] = (req->user->type >> 0) & 0xFF;
p[1] = (req->user->type >> 8) & 0xFF;
p[2] = (req->user->type >> 16) & 0xFF;
p[3] = (req->user->type >> 24) & 0xFF;
p += 4;
for (i = 0; i < req->user->length; i++) {
if (req->user->data[i].length > 0)
memcpy(p, req->user->data[i].data, req->user->data[i].length);
p += req->user->data[i].length;
}
if (req->user->realm.length > 0)
memcpy(p, req->user->realm.data, req->user->realm.length);
p += req->user->realm.length;
if (req->auth_package.length > 0)
memcpy(p, req->auth_package.data, req->auth_package.length);
code = krb5_c_make_checksum(context, CKSUMTYPE_HMAC_MD5_ARCFOUR, key,
KRB5_KEYUSAGE_APP_DATA_CKSUM, &data,
cksum);
free(data.data);
return code;
}
static krb5_error_code
build_pa_for_user(krb5_context context,
krb5_creds *tgt,
krb5_s4u_userid *userid,
krb5_pa_data **out_padata)
{
krb5_error_code code;
krb5_pa_data *padata;
krb5_pa_for_user for_user;
krb5_data *for_user_data = NULL;
char package[] = "Kerberos";
if (userid->user == NULL)
return EINVAL;
memset(&for_user, 0, sizeof(for_user));
for_user.user = userid->user;
for_user.auth_package.data = package;
for_user.auth_package.length = sizeof(package) - 1;
code = make_pa_for_user_checksum(context, &tgt->keyblock,
&for_user, &for_user.cksum);
if (code != 0)
goto cleanup;
code = encode_krb5_pa_for_user(&for_user, &for_user_data);
if (code != 0)
goto cleanup;
padata = malloc(sizeof(*padata));
if (padata == NULL) {
code = ENOMEM;
goto cleanup;
}
padata->magic = KV5M_PA_DATA;
padata->pa_type = KRB5_PADATA_FOR_USER;
padata->length = for_user_data->length;
padata->contents = (krb5_octet *)for_user_data->data;
free(for_user_data);
for_user_data = NULL;
*out_padata = padata;
cleanup:
if (for_user.cksum.contents != NULL)
krb5_free_checksum_contents(context, &for_user.cksum);
krb5_free_data(context, for_user_data);
return code;
}
static krb5_error_code
build_pa_s4u_x509_user(krb5_context context,
krb5_keyblock *subkey,
krb5_kdc_req *tgsreq,
void *gcvt_data)
{
krb5_error_code code;
krb5_pa_s4u_x509_user *s4u_user = (krb5_pa_s4u_x509_user *)gcvt_data;
krb5_data *data = NULL;
krb5_cksumtype cksumtype;
size_t i;
assert(s4u_user->cksum.contents == NULL);
s4u_user->user_id.nonce = tgsreq->nonce;
code = encode_krb5_s4u_userid(&s4u_user->user_id, &data);
if (code != 0)
goto cleanup;
if (subkey->enctype == ENCTYPE_ARCFOUR_HMAC ||
subkey->enctype == ENCTYPE_ARCFOUR_HMAC_EXP) {
cksumtype = CKSUMTYPE_RSA_MD4;
} else {
code = krb5int_c_mandatory_cksumtype(context, subkey->enctype,
&cksumtype);
}
if (code != 0)
goto cleanup;
code = krb5_c_make_checksum(context, cksumtype, subkey,
KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST, data,
&s4u_user->cksum);
if (code != 0)
goto cleanup;
krb5_free_data(context, data);
data = NULL;
code = encode_krb5_pa_s4u_x509_user(s4u_user, &data);
if (code != 0)
goto cleanup;
assert(tgsreq->padata != NULL);
for (i = 0; tgsreq->padata[i] != NULL; i++) {
if (tgsreq->padata[i]->pa_type == KRB5_PADATA_S4U_X509_USER)
break;
}
assert(tgsreq->padata[i] != NULL);
free(tgsreq->padata[i]->contents);
tgsreq->padata[i]->length = data->length;
tgsreq->padata[i]->contents = (krb5_octet *)data->data;
free(data);
data = NULL;
cleanup:
if (code != 0 && s4u_user->cksum.contents != NULL) {
krb5_free_checksum_contents(context, &s4u_user->cksum);
s4u_user->cksum.contents = NULL;
}
krb5_free_data(context, data);
return code;
}
static krb5_error_code
verify_s4u2self_reply(krb5_context context,
krb5_keyblock *subkey,
krb5_pa_s4u_x509_user *req_s4u_user,
krb5_pa_data **rep_padata,
krb5_pa_data **enc_padata,
krb5_boolean update_req_user)
{
krb5_error_code code;
krb5_pa_data *rep_s4u_padata, *enc_s4u_padata;
krb5_pa_s4u_x509_user *rep_s4u_user = NULL;
krb5_data data, *datap = NULL;
krb5_keyusage usage;
krb5_boolean valid;
krb5_boolean not_newer;
assert(req_s4u_user != NULL);
switch (subkey->enctype) {
case ENCTYPE_DES3_CBC_SHA1:
case ENCTYPE_DES3_CBC_RAW:
case ENCTYPE_ARCFOUR_HMAC:
case ENCTYPE_ARCFOUR_HMAC_EXP :
not_newer = TRUE;
break;
default:
not_newer = FALSE;
break;
}
enc_s4u_padata = krb5int_find_pa_data(context,
enc_padata,
KRB5_PADATA_S4U_X509_USER);
rep_s4u_padata = krb5int_find_pa_data(context,
rep_padata,
KRB5_PADATA_S4U_X509_USER);
if (rep_s4u_padata == NULL)
return (enc_s4u_padata != NULL) ? KRB5_KDCREP_MODIFIED : 0;
data.length = rep_s4u_padata->length;
data.data = (char *)rep_s4u_padata->contents;
code = decode_krb5_pa_s4u_x509_user(&data, &rep_s4u_user);
if (code != 0)
goto cleanup;
if (rep_s4u_user->user_id.nonce != req_s4u_user->user_id.nonce) {
code = KRB5_KDCREP_MODIFIED;
goto cleanup;
}
code = encode_krb5_s4u_userid(&rep_s4u_user->user_id, &datap);
if (code != 0)
goto cleanup;
if (rep_s4u_user->user_id.options & KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE)
usage = KRB5_KEYUSAGE_PA_S4U_X509_USER_REPLY;
else
usage = KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST;
code = krb5_c_verify_checksum(context, subkey, usage, datap,
&rep_s4u_user->cksum, &valid);
if (code != 0)
goto cleanup;
if (valid == FALSE) {
code = KRB5_KDCREP_MODIFIED;
goto cleanup;
}
if (rep_s4u_user->user_id.user == NULL ||
rep_s4u_user->user_id.user->length == 0) {
code = KRB5_KDCREP_MODIFIED;
goto cleanup;
}
if (update_req_user) {
krb5_free_principal(context, req_s4u_user->user_id.user);
code = krb5_copy_principal(context, rep_s4u_user->user_id.user,
&req_s4u_user->user_id.user);
if (code != 0)
goto cleanup;
} else if (!krb5_principal_compare(context, rep_s4u_user->user_id.user,
req_s4u_user->user_id.user)) {
code = KRB5_KDCREP_MODIFIED;
goto cleanup;
}
if (not_newer) {
if (enc_s4u_padata == NULL) {
if (rep_s4u_user->user_id.options &
KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE) {
code = KRB5_KDCREP_MODIFIED;
goto cleanup;
}
} else {
if (enc_s4u_padata->length !=
req_s4u_user->cksum.length + rep_s4u_user->cksum.length) {
code = KRB5_KDCREP_MODIFIED;
goto cleanup;
}
if (memcmp(enc_s4u_padata->contents,
req_s4u_user->cksum.contents,
req_s4u_user->cksum.length) ||
memcmp(&enc_s4u_padata->contents[req_s4u_user->cksum.length],
rep_s4u_user->cksum.contents,
rep_s4u_user->cksum.length)) {
code = KRB5_KDCREP_MODIFIED;
goto cleanup;
}
}
} else if (!krb5_c_is_keyed_cksum(rep_s4u_user->cksum.checksum_type)) {
code = KRB5KRB_AP_ERR_INAPP_CKSUM;
goto cleanup;
}
cleanup:
krb5_free_pa_s4u_x509_user(context, rep_s4u_user);
krb5_free_data(context, datap);
return code;
}
static krb5_error_code
convert_to_enterprise(krb5_context context, krb5_principal princ,
krb5_principal *eprinc_out)
{
krb5_error_code code;
char *str;
*eprinc_out = NULL;
code = krb5_unparse_name(context, princ, &str);
if (code != 0)
return code;
code = krb5_parse_name_flags(context, str,
KRB5_PRINCIPAL_PARSE_ENTERPRISE |
KRB5_PRINCIPAL_PARSE_IGNORE_REALM,
eprinc_out);
krb5_free_unparsed_name(context, str);
return code;
}
static krb5_error_code
krb5_get_self_cred_from_kdc(krb5_context context,
krb5_flags options,
krb5_ccache ccache,
krb5_creds *in_creds,
krb5_data *subject_cert,
krb5_data *user_realm,
krb5_creds **out_creds)
{
krb5_error_code code;
krb5_principal tgs = NULL, eprinc = NULL;
krb5_principal_data sprinc;
krb5_creds tgtq, s4u_creds, *tgt = NULL, *tgtptr;
krb5_creds *referral_tgts[KRB5_REFERRAL_MAXHOPS];
krb5_pa_s4u_x509_user s4u_user;
int referral_count = 0, i;
krb5_flags kdcopt;
memset(&tgtq, 0, sizeof(tgtq));
memset(referral_tgts, 0, sizeof(referral_tgts));
*out_creds = NULL;
memset(&s4u_user, 0, sizeof(s4u_user));
if (in_creds->client != NULL && in_creds->client->length > 0) {
if (in_creds->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
code = krb5_build_principal_ext(context,
&s4u_user.user_id.user,
user_realm->length,
user_realm->data,
in_creds->client->data[0].length,
in_creds->client->data[0].data,
0);
if (code != 0)
goto cleanup;
s4u_user.user_id.user->type = KRB5_NT_ENTERPRISE_PRINCIPAL;
} else {
code = krb5_copy_principal(context,
in_creds->client,
&s4u_user.user_id.user);
if (code != 0)
goto cleanup;
}
} else {
code = krb5_build_principal_ext(context, &s4u_user.user_id.user,
user_realm->length, user_realm->data,
0);
if (code != 0)
goto cleanup;
}
if (subject_cert != NULL)
s4u_user.user_id.subject_cert = *subject_cert;
s4u_user.user_id.options = KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE;
code = krb5int_tgtname(context, user_realm, &in_creds->server->realm,
&tgs);
if (code != 0)
goto cleanup;
tgtq.client = in_creds->server;
tgtq.server = tgs;
code = krb5_get_credentials(context, options, ccache, &tgtq, &tgt);
if (code != 0)
goto cleanup;
tgtptr = tgt;
code = convert_to_enterprise(context, in_creds->server, &eprinc);
if (code != 0)
goto cleanup;
s4u_creds = *in_creds;
s4u_creds.client = in_creds->server;
kdcopt = 0;
if (options & KRB5_GC_CANONICALIZE)
kdcopt |= KDC_OPT_CANONICALIZE;
if (options & KRB5_GC_FORWARDABLE)
kdcopt |= KDC_OPT_FORWARDABLE;
if (options & KRB5_GC_NO_TRANSIT_CHECK)
kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
for (referral_count = 0;
referral_count < KRB5_REFERRAL_MAXHOPS;
referral_count++)
{
krb5_pa_data **in_padata = NULL;
krb5_pa_data **out_padata = NULL;
krb5_pa_data **enc_padata = NULL;
krb5_keyblock *subkey = NULL;
in_padata = k5calloc(3, sizeof(krb5_pa_data *), &code);
if (in_padata == NULL)
goto cleanup;
in_padata[0] = k5alloc(sizeof(krb5_pa_data), &code);
if (in_padata[0] == NULL) {
krb5_free_pa_data(context, in_padata);
goto cleanup;
}
in_padata[0]->magic = KV5M_PA_DATA;
in_padata[0]->pa_type = KRB5_PADATA_S4U_X509_USER;
in_padata[0]->length = 0;
in_padata[0]->contents = NULL;
if (s4u_user.user_id.user != NULL && s4u_user.user_id.user->length) {
code = build_pa_for_user(context, tgtptr, &s4u_user.user_id,
&in_padata[1]);
if (code == KRB5_CRYPTO_INTERNAL)
code = 0;
if (code != 0) {
krb5_free_pa_data(context, in_padata);
goto cleanup;
}
}
if (data_eq(tgtptr->server->data[1], in_creds->server->realm)) {
s4u_creds.server = in_creds->server;
} else {
sprinc = *eprinc;
sprinc.realm = tgtptr->server->data[1];
s4u_creds.server = &sprinc;
}
code = krb5_get_cred_via_tkt_ext(context, tgtptr,
KDC_OPT_CANONICALIZE |
FLAGS2OPTS(tgtptr->ticket_flags) |
kdcopt,
tgtptr->addresses,
in_padata, &s4u_creds,
build_pa_s4u_x509_user, &s4u_user,
&out_padata, &enc_padata,
out_creds, &subkey);
if (code != 0) {
krb5_free_checksum_contents(context, &s4u_user.cksum);
krb5_free_pa_data(context, in_padata);
goto cleanup;
}
code = verify_s4u2self_reply(context, subkey, &s4u_user, out_padata,
enc_padata, referral_count == 0);
krb5_free_checksum_contents(context, &s4u_user.cksum);
krb5_free_pa_data(context, in_padata);
krb5_free_pa_data(context, out_padata);
krb5_free_pa_data(context, enc_padata);
krb5_free_keyblock(context, subkey);
if (code != 0)
goto cleanup;
s4u_creds.authdata = NULL;
s4u_user.user_id.subject_cert = empty_data();
if (krb5_principal_compare_any_realm(context, in_creds->server,
(*out_creds)->server)) {
if (!krb5_principal_compare(context, (*out_creds)->client,
s4u_user.user_id.user))
code = KRB5_KDCREP_MODIFIED;
goto cleanup;
} else if (IS_TGS_PRINC((*out_creds)->server)) {
krb5_data *r1 = &tgtptr->server->data[1];
krb5_data *r2 = &(*out_creds)->server->data[1];
if (data_eq(*r1, *r2)) {
krb5_free_creds(context, *out_creds);
*out_creds = NULL;
code = KRB5_ERR_HOST_REALM_UNKNOWN;
break;
}
for (i = 0; i < referral_count; i++) {
if (krb5_principal_compare(context,
(*out_creds)->server,
referral_tgts[i]->server)) {
code = KRB5_KDC_UNREACH;
goto cleanup;
}
}
tgtptr = *out_creds;
referral_tgts[referral_count] = *out_creds;
*out_creds = NULL;
} else {
krb5_free_creds(context, *out_creds);
*out_creds = NULL;
code = KRB5KRB_AP_WRONG_PRINC;
break;
}
}
cleanup:
for (i = 0; i < KRB5_REFERRAL_MAXHOPS; i++) {
if (referral_tgts[i] != NULL)
krb5_free_creds(context, referral_tgts[i]);
}
krb5_free_principal(context, tgs);
krb5_free_principal(context, eprinc);
krb5_free_creds(context, tgt);
krb5_free_principal(context, s4u_user.user_id.user);
krb5_free_checksum_contents(context, &s4u_user.cksum);
return code;
}
krb5_error_code KRB5_CALLCONV
krb5_get_credentials_for_user(krb5_context context, krb5_flags options,
krb5_ccache ccache, krb5_creds *in_creds,
krb5_data *subject_cert,
krb5_creds **out_creds)
{
krb5_error_code code;
krb5_principal realm = NULL;
*out_creds = NULL;
if (options & KRB5_GC_CONSTRAINED_DELEGATION) {
code = EINVAL;
goto cleanup;
}
if (in_creds->client != NULL) {
code = krb5_get_credentials(context, options | KRB5_GC_CACHED,
ccache, in_creds, out_creds);
if ((code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) ||
(options & KRB5_GC_CACHED))
goto cleanup;
} else if (options & KRB5_GC_CACHED) {
code = KRB5_CC_NOTFOUND;
goto cleanup;
}
code = s4u_identify_user(context, in_creds, subject_cert, &realm);
if (code != 0)
goto cleanup;
if (in_creds->client != NULL &&
in_creds->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
krb5_creds mcreds = *in_creds;
mcreds.client = realm;
code = krb5_get_credentials(context, options | KRB5_GC_CACHED,
ccache, &mcreds, out_creds);
if (code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE)
goto cleanup;
}
code = krb5_get_self_cred_from_kdc(context, options, ccache, in_creds,
subject_cert, &realm->realm, out_creds);
if (code != 0)
goto cleanup;
assert(*out_creds != NULL);
if (in_creds->client == NULL ||
!krb5_principal_compare(context, in_creds->client,
(*out_creds)->client)) {
krb5_creds *old_creds;
krb5_creds mcreds = *in_creds;
mcreds.client = (*out_creds)->client;
code = krb5_get_credentials(context, options | KRB5_GC_CACHED, ccache,
&mcreds, &old_creds);
if (code == 0) {
krb5_free_creds(context, *out_creds);
*out_creds = old_creds;
options |= KRB5_GC_NO_STORE;
} else if (code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) {
goto cleanup;
}
}
code = krb5_copy_authdata(context, in_creds->authdata,
&(*out_creds)->authdata);
if (code)
goto cleanup;
if ((options & KRB5_GC_NO_STORE) == 0) {
code = krb5_cc_store_cred(context, ccache, *out_creds);
if (code != 0)
goto cleanup;
}
cleanup:
if (code != 0 && *out_creds != NULL) {
krb5_free_creds(context, *out_creds);
*out_creds = NULL;
}
krb5_free_principal(context, realm);
return code;
}
static krb5_error_code
check_rbcd_support(krb5_context context, krb5_pa_data **padata)
{
krb5_error_code code;
krb5_pa_data *pa;
krb5_pa_pac_options *pac_options;
krb5_data der_pac_options;
pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_PAC_OPTIONS);
if (pa == NULL)
return KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
der_pac_options = make_data(pa->contents, pa->length);
code = decode_krb5_pa_pac_options(&der_pac_options, &pac_options);
if (code)
return code;
if (!(pac_options->options & KRB5_PA_PAC_OPTIONS_RBCD))
code = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
free(pac_options);
return code;
}
static krb5_error_code
add_rbcd_padata(krb5_context context, krb5_pa_data ***in_padata)
{
krb5_error_code code;
krb5_pa_pac_options pac_options;
krb5_data *der_pac_options = NULL;
memset(&pac_options, 0, sizeof(pac_options));
pac_options.options |= KRB5_PA_PAC_OPTIONS_RBCD;
code = encode_krb5_pa_pac_options(&pac_options, &der_pac_options);
if (code)
return code;
code = k5_add_pa_data_from_data(in_padata, KRB5_PADATA_PAC_OPTIONS,
der_pac_options);
krb5_free_data(context, der_pac_options);
return code;
}
static krb5_error_code
get_client_tgt(krb5_context context, krb5_flags options, krb5_ccache ccache,
krb5_principal client, krb5_creds **tgt_out)
{
krb5_error_code code;
krb5_principal tgs;
krb5_creds mcreds;
*tgt_out = NULL;
code = krb5int_tgtname(context, &client->realm, &client->realm, &tgs);
if (code)
return code;
memset(&mcreds, 0, sizeof(mcreds));
mcreds.client = client;
mcreds.server = tgs;
code = krb5_get_credentials(context, options, ccache, &mcreds, tgt_out);
krb5_free_principal(context, tgs);
return code;
}
static krb5_error_code
normalize_server_princ(krb5_context context, const krb5_data *realm,
krb5_principal req_server, krb5_principal *out_server)
{
krb5_error_code code;
krb5_principal server;
*out_server = NULL;
code = krb5_copy_principal(context, req_server, &server);
if (code)
return code;
if (krb5_is_referral_realm(&server->realm)) {
krb5_free_data_contents(context, &server->realm);
code = krb5int_copy_data_contents(context, realm, &server->realm);
if (code) {
krb5_free_principal(context, server);
return code;
}
}
*out_server = server;
return 0;
}
static krb5_error_code
check_referral_path(krb5_context context, krb5_principal server,
krb5_creds **referral_list, int referral_count)
{
int i;
for (i = 0; i < referral_count; i++) {
if (krb5_principal_compare(context, server, referral_list[i]->server))
return KRB5_KDC_UNREACH;
}
return 0;
}
static krb5_error_code
chase_referrals(krb5_context context, krb5_creds *in_creds, krb5_flags kdcopt,
krb5_creds **tgt_inout, krb5_creds **creds_out)
{
krb5_error_code code;
krb5_creds *referral_tgts[KRB5_REFERRAL_MAXHOPS] = { NULL };
krb5_creds mcreds, *tgt, *tkt = NULL;
krb5_principal_data server;
int referral_count = 0, i;
tgt = *tgt_inout;
*tgt_inout = NULL;
*creds_out = NULL;
mcreds = *in_creds;
server = *in_creds->server;
mcreds.server = &server;
for (referral_count = 0; referral_count < KRB5_REFERRAL_MAXHOPS;
referral_count++) {
code = krb5_get_cred_via_tkt(context, tgt, kdcopt, tgt->addresses,
&mcreds, &tkt);
if (code)
goto cleanup;
if (krb5_principal_compare_any_realm(context, mcreds.server,
tkt->server)) {
*creds_out = tkt;
*tgt_inout = tgt;
tkt = tgt = NULL;
goto cleanup;
}
if (!IS_TGS_PRINC(tkt->server)) {
code = KRB5KRB_AP_WRONG_PRINC;
goto cleanup;
}
if (data_eq(tgt->server->data[1], tkt->server->data[1])) {
code = KRB5_ERR_HOST_REALM_UNKNOWN;
goto cleanup;
}
code = check_referral_path(context, tkt->server, referral_tgts,
referral_count);
if (code)
goto cleanup;
referral_tgts[referral_count] = tgt;
tgt = tkt;
tkt = NULL;
server.realm = tgt->server->data[1];
}
code = KRB5_KDCREP_MODIFIED;
cleanup:
for (i = 0; i < KRB5_REFERRAL_MAXHOPS; i++)
krb5_free_creds(context, referral_tgts[i]);
krb5_free_creds(context, tkt);
krb5_free_creds(context, tgt);
return code;
}
static krb5_error_code
get_tgt_to_target_realm(krb5_context context, krb5_creds *in_creds,
krb5_flags req_kdcopt, krb5_creds **tgt_inout)
{
krb5_error_code code;
krb5_flags kdcopt;
krb5_creds mcreds, *out;
mcreds = *in_creds;
mcreds.second_ticket = empty_data();
kdcopt = FLAGS2OPTS((*tgt_inout)->ticket_flags) | req_kdcopt;
code = chase_referrals(context, &mcreds, kdcopt, tgt_inout, &out);
krb5_free_creds(context, out);
return code;
}
static krb5_error_code
get_target_realm_proxy_tgt(krb5_context context, const krb5_data *realm,
krb5_flags req_kdcopt, krb5_creds **tgt_inout)
{
krb5_error_code code;
krb5_creds mcreds, *out;
krb5_principal tgs;
krb5_flags flags;
if (data_eq(*realm, (*tgt_inout)->server->data[1]))
return 0;
code = krb5int_tgtname(context, realm, &(*tgt_inout)->server->data[1],
&tgs);
if (code)
return code;
memset(&mcreds, 0, sizeof(mcreds));
mcreds.client = (*tgt_inout)->client;
mcreds.server = tgs;
flags = req_kdcopt | FLAGS2OPTS((*tgt_inout)->ticket_flags);
code = chase_referrals(context, &mcreds, flags, tgt_inout, &out);
krb5_free_principal(context, tgs);
if (code)
return code;
krb5_free_creds(context, *tgt_inout);
*tgt_inout = out;
return 0;
}
static krb5_error_code
get_proxy_cred_from_kdc(krb5_context context, krb5_flags options,
krb5_ccache ccache, krb5_creds *in_creds,
krb5_creds **out_creds)
{
krb5_error_code code;
krb5_flags flags, req_kdcopt = 0;
krb5_principal server = NULL;
krb5_pa_data **in_padata = NULL;
krb5_pa_data **enc_padata = NULL;
krb5_creds mcreds, *tgt = NULL, *tkt = NULL;
*out_creds = NULL;
if (in_creds->second_ticket.length == 0 ||
(options & KRB5_GC_CONSTRAINED_DELEGATION) == 0)
return EINVAL;
options &= ~KRB5_GC_CONSTRAINED_DELEGATION;
code = get_client_tgt(context, options, ccache, in_creds->client, &tgt);
if (code)
goto cleanup;
code = normalize_server_princ(context, &in_creds->client->realm,
in_creds->server, &server);
if (code)
goto cleanup;
code = add_rbcd_padata(context, &in_padata);
if (code)
goto cleanup;
if (options & KRB5_GC_CANONICALIZE)
req_kdcopt |= KDC_OPT_CANONICALIZE;
if (options & KRB5_GC_FORWARDABLE)
req_kdcopt |= KDC_OPT_FORWARDABLE;
if (options & KRB5_GC_NO_TRANSIT_CHECK)
req_kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
mcreds = *in_creds;
mcreds.server = server;
flags = req_kdcopt | FLAGS2OPTS(tgt->ticket_flags) |
KDC_OPT_CNAME_IN_ADDL_TKT | KDC_OPT_CANONICALIZE;
code = krb5_get_cred_via_tkt_ext(context, tgt, flags, tgt->addresses,
in_padata, &mcreds, NULL, NULL, NULL,
&enc_padata, &tkt, NULL);
if (code == KRB5KRB_AP_ERR_BAD_INTEGRITY &&
!krb5_realm_compare(context, in_creds->client, server)) {
k5_setmsg(context, code, _("Realm specified but S4U2Proxy must use "
"referral realm"));
}
if (code)
goto cleanup;
if (!krb5_principal_compare_any_realm(context, server, tkt->server)) {
if (!IS_TGS_PRINC(tkt->server)) {
code = KRB5KRB_AP_WRONG_PRINC;
goto cleanup;
}
mcreds.authdata = NULL;
code = check_rbcd_support(context, enc_padata);
if (code)
goto cleanup;
krb5_free_pa_data(context, enc_padata);
enc_padata = NULL;
code = get_tgt_to_target_realm(context, &mcreds, req_kdcopt, &tgt);
if (code)
goto cleanup;
code = get_target_realm_proxy_tgt(context, &tgt->server->data[1],
req_kdcopt, &tkt);
if (code)
goto cleanup;
krb5_free_data_contents(context, &server->realm);
code = krb5int_copy_data_contents(context, &tgt->server->data[1],
&server->realm);
if (code)
goto cleanup;
mcreds.second_ticket = tkt->ticket;
tkt->ticket = empty_data();
krb5_free_creds(context, tkt);
tkt = NULL;
flags = req_kdcopt | FLAGS2OPTS(tgt->ticket_flags) |
KDC_OPT_CNAME_IN_ADDL_TKT | KDC_OPT_CANONICALIZE;
code = krb5_get_cred_via_tkt_ext(context, tgt, flags, tgt->addresses,
in_padata, &mcreds, NULL, NULL, NULL,
&enc_padata, &tkt, NULL);
free(mcreds.second_ticket.data);
if (code)
goto cleanup;
code = check_rbcd_support(context, enc_padata);
if (code)
goto cleanup;
if (!krb5_principal_compare(context, server, tkt->server)) {
code = KRB5KRB_AP_WRONG_PRINC;
goto cleanup;
}
krb5_free_data_contents(context, &tkt->second_ticket);
code = krb5int_copy_data_contents(context, &in_creds->second_ticket,
&tkt->second_ticket);
if (code)
goto cleanup;
}
code = krb5_copy_authdata(context, in_creds->authdata, &tkt->authdata);
if (code)
goto cleanup;
*out_creds = tkt;
tkt = NULL;
cleanup:
krb5_free_creds(context, tgt);
krb5_free_creds(context, tkt);
krb5_free_principal(context, server);
krb5_free_pa_data(context, in_padata);
krb5_free_pa_data(context, enc_padata);
return code;
}
krb5_error_code
k5_get_proxy_cred_from_kdc(krb5_context context, krb5_flags options,
krb5_ccache ccache, krb5_creds *in_creds,
krb5_creds **out_creds)
{
krb5_error_code code;
krb5_const_principal canonprinc;
krb5_creds copy, *creds;
struct canonprinc iter = { in_creds->server, .no_hostrealm = TRUE };
*out_creds = NULL;
code = k5_get_cached_cred(context, options, ccache, in_creds, out_creds);
if ((code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) ||
options & KRB5_GC_CACHED)
return code;
copy = *in_creds;
while ((code = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
canonprinc != NULL) {
copy.server = (krb5_principal)canonprinc;
code = get_proxy_cred_from_kdc(context, options, ccache, ©,
&creds);
if (code != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
break;
}
if (!code && canonprinc == NULL)
code = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN;
free_canonprinc(&iter);
if (code)
return code;
krb5_free_principal(context, creds->server);
creds->server = NULL;
code = krb5_copy_principal(context, in_creds->server, &creds->server);
if (code) {
krb5_free_creds(context, creds);
return code;
}
if (!(options & KRB5_GC_NO_STORE))
(void)krb5_cc_store_cred(context, ccache, creds);
*out_creds = creds;
return 0;
}
krb5_error_code KRB5_CALLCONV
krb5_get_credentials_for_proxy(krb5_context context,
krb5_flags options,
krb5_ccache ccache,
krb5_creds *in_creds,
krb5_ticket *evidence_tkt,
krb5_creds **out_creds)
{
krb5_error_code code;
krb5_data *evidence_tkt_data = NULL;
krb5_creds s4u_creds;
*out_creds = NULL;
if (in_creds == NULL || in_creds->client == NULL || evidence_tkt == NULL) {
code = EINVAL;
goto cleanup;
}
if (evidence_tkt->enc_part2 != NULL &&
!krb5_principal_compare(context, evidence_tkt->enc_part2->client,
in_creds->client)) {
code = EINVAL;
goto cleanup;
}
code = encode_krb5_ticket(evidence_tkt, &evidence_tkt_data);
if (code != 0)
goto cleanup;
s4u_creds = *in_creds;
s4u_creds.client = evidence_tkt->server;
s4u_creds.second_ticket = *evidence_tkt_data;
code = k5_get_proxy_cred_from_kdc(context,
options | KRB5_GC_CONSTRAINED_DELEGATION,
ccache, &s4u_creds, out_creds);
if (code != 0)
goto cleanup;
if (!krb5_principal_compare(context, in_creds->client,
(*out_creds)->client)) {
code = KRB5_KDCREP_MODIFIED;
goto cleanup;
}
cleanup:
if (*out_creds != NULL && code != 0) {
krb5_free_creds(context, *out_creds);
*out_creds = NULL;
}
if (evidence_tkt_data != NULL)
krb5_free_data(context, evidence_tkt_data);
return code;
}