kdc: add canonical principal name to authz data
Use the UPN_DNS_INFO buffer of the PAC to include the canonical principal name. Arguably we should use AD-LOGIN-ALIAS as defined in RFC6806, but we may not always know all the principal's aliases, and this approach allows us to share application service logic with Windows.
This commit is contained in:
@@ -848,7 +848,7 @@ _kdc_fast_check_armor_pac(astgs_request_t r)
|
||||
armor_client, r->armor_server,
|
||||
r->armor_server, r->armor_server,
|
||||
&r->armor_key->key, &r->armor_key->key,
|
||||
&r->armor_ticket->ticket, &ad_kdc_issued, &mspac);
|
||||
&r->armor_ticket->ticket, &ad_kdc_issued, &mspac, NULL);
|
||||
if (ret) {
|
||||
const char *msg = krb5_get_error_message(r->context, ret);
|
||||
|
||||
|
@@ -84,8 +84,10 @@ struct astgs_request_desc {
|
||||
krb5_keyblock session_key;
|
||||
|
||||
/* state */
|
||||
krb5_principal client_princ;
|
||||
hdb_entry_ex *client;
|
||||
krb5_principal client_princ; /* AS: principal requested by client
|
||||
* TGS: opt. canon principal from TGT PAC */
|
||||
hdb_entry_ex *client; /* AS: client entry
|
||||
* TGS: opt. client entry, if local to KDC */
|
||||
HDB *clientdb;
|
||||
|
||||
krb5_principal server_princ;
|
||||
|
@@ -1826,6 +1826,7 @@ generate_pac(astgs_request_t r, const Key *skey, const Key *tkey)
|
||||
uint16_t rodc_id;
|
||||
krb5_principal client;
|
||||
krb5_boolean client_sent_pac_req, pac_request;
|
||||
krb5_const_principal canon_princ = NULL;
|
||||
|
||||
client_sent_pac_req =
|
||||
(check_pa_pac_request(r->context, &r->req, &pac_request) == 0);
|
||||
@@ -1860,11 +1861,30 @@ generate_pac(astgs_request_t r, const Key *skey, const Key *tkey)
|
||||
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->client->entry.principal)) {
|
||||
char *cpn = NULL;
|
||||
|
||||
canon_princ = r->client->entry.principal;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ret = _krb5_pac_sign(r->context, p, r->et.authtime,
|
||||
client,
|
||||
&skey->key, /* Server key */
|
||||
&tkey->key, /* TGS key */
|
||||
rodc_id,
|
||||
NULL, /* UPN */
|
||||
canon_princ,
|
||||
&data);
|
||||
krb5_free_principal(r->context, client);
|
||||
krb5_pac_free(r->context, p);
|
||||
|
@@ -88,7 +88,8 @@ _kdc_check_pac(krb5_context context,
|
||||
const EncryptionKey *krbtgt_check_key,
|
||||
EncTicketPart *tkt,
|
||||
krb5_boolean *kdc_issued,
|
||||
krb5_pac *ppac)
|
||||
krb5_pac *ppac,
|
||||
krb5_principal *pac_canon_name)
|
||||
{
|
||||
krb5_pac pac = NULL;
|
||||
krb5_error_code ret;
|
||||
@@ -96,6 +97,8 @@ _kdc_check_pac(krb5_context context,
|
||||
|
||||
*kdc_issued = FALSE;
|
||||
*ppac = NULL;
|
||||
if (pac_canon_name)
|
||||
*pac_canon_name = NULL;
|
||||
|
||||
ret = _krb5_kdc_pac_ticket_parse(context, tkt, &signedticket, &pac);
|
||||
if (ret)
|
||||
@@ -118,7 +121,15 @@ _kdc_check_pac(krb5_context context,
|
||||
/* Verify the KDC signatures. */
|
||||
ret = _kdc_pac_verify(context, client_principal, delegated_proxy_principal,
|
||||
client, server, krbtgt, &pac);
|
||||
if (ret == KRB5_PLUGIN_NO_HANDLE) {
|
||||
if (ret == 0) {
|
||||
if (pac_canon_name) {
|
||||
ret = _krb5_pac_get_canon_principal(context, pac, pac_canon_name);
|
||||
if (ret && ret != ENOENT) {
|
||||
krb5_pac_free(context, pac);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} else if (ret == KRB5_PLUGIN_NO_HANDLE) {
|
||||
/*
|
||||
* We can't verify the KDC signatures if the ticket was issued by
|
||||
* another realm's KDC.
|
||||
@@ -132,12 +143,21 @@ _kdc_check_pac(krb5_context context,
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (pac_canon_name) {
|
||||
ret = _krb5_pac_get_canon_principal(context, pac, pac_canon_name);
|
||||
if (ret && ret != ENOENT) {
|
||||
krb5_pac_free(context, pac);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Discard the PAC if the plugin didn't handle it */
|
||||
krb5_pac_free(context, pac);
|
||||
ret = krb5_pac_init(context, &pac);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (ret) {
|
||||
} else {
|
||||
krb5_pac_free(context, pac);
|
||||
return ret;
|
||||
}
|
||||
@@ -787,10 +807,19 @@ tgs_make_reply(astgs_request_t r,
|
||||
* is implementation dependent.
|
||||
*/
|
||||
if (mspac && !et.flags.anonymous) {
|
||||
if (r->client_princ) {
|
||||
char *cpn;
|
||||
|
||||
krb5_unparse_name(r->context, r->client_princ, &cpn);
|
||||
_kdc_audit_addkv((kdc_request_t)r, 0, "canon_client_name", "%s",
|
||||
cpn ? cpn : "<unknown>");
|
||||
krb5_xfree(cpn);
|
||||
}
|
||||
|
||||
/* The PAC should be the last change to the ticket. */
|
||||
ret = _krb5_kdc_pac_sign_ticket(r->context, mspac, tgt_name, serverkey,
|
||||
krbtgtkey, rodc_id, add_ticket_sig, &et);
|
||||
krbtgtkey, rodc_id, add_ticket_sig, &et,
|
||||
NULL, r->client_princ);
|
||||
if (ret)
|
||||
goto out;
|
||||
}
|
||||
@@ -1812,7 +1841,8 @@ server_lookup:
|
||||
/* Verify the PAC of the TGT. */
|
||||
ret = _kdc_check_pac(context, config, user2user_princ, NULL,
|
||||
user2user_client, user2user_krbtgt, user2user_krbtgt, user2user_krbtgt,
|
||||
&uukey->key, &priv->ticket_key->key, &adtkt, &user2user_kdc_issued, &user2user_pac);
|
||||
&uukey->key, &priv->ticket_key->key, &adtkt,
|
||||
&user2user_kdc_issued, &user2user_pac, NULL);
|
||||
_kdc_free_ent(context, user2user_client);
|
||||
if (ret) {
|
||||
const char *msg = krb5_get_error_message(context, ret);
|
||||
@@ -1936,8 +1966,11 @@ server_lookup:
|
||||
flags &= ~HDB_F_SYNTHETIC_OK;
|
||||
priv->client = client;
|
||||
|
||||
heim_assert(priv->client_princ == NULL, "client_princ should be NULL for TGS");
|
||||
|
||||
ret = _kdc_check_pac(context, config, cp, NULL, client, server, krbtgt, krbtgt,
|
||||
&priv->ticket_key->key, &priv->ticket_key->key, tgt, &kdc_issued, &mspac);
|
||||
&priv->ticket_key->key, &priv->ticket_key->key, tgt,
|
||||
&kdc_issued, &mspac, &priv->client_princ);
|
||||
if (ret) {
|
||||
const char *msg = krb5_get_error_message(context, ret);
|
||||
_kdc_audit_addreason((kdc_request_t)priv, "PAC check failed");
|
||||
@@ -2171,6 +2204,9 @@ server_lookup:
|
||||
krb5_pac_free(context, mspac);
|
||||
mspac = NULL;
|
||||
|
||||
krb5_free_principal(context, priv->client_princ);
|
||||
priv->client_princ = NULL;
|
||||
|
||||
t = &b->additional_tickets->val[0];
|
||||
|
||||
ret = hdb_enctype2key(context, &client->entry,
|
||||
@@ -2265,7 +2301,8 @@ server_lookup:
|
||||
* a S4U_DELEGATION_INFO blob to the PAC.
|
||||
*/
|
||||
ret = _kdc_check_pac(context, config, tp, dp, adclient, server, krbtgt, client,
|
||||
&clientkey->key, &priv->ticket_key->key, &adtkt, &ad_kdc_issued, &mspac);
|
||||
&clientkey->key, &priv->ticket_key->key, &adtkt,
|
||||
&ad_kdc_issued, &mspac, &priv->client_princ);
|
||||
if (adclient)
|
||||
_kdc_free_ent(context, adclient);
|
||||
if (ret) {
|
||||
@@ -2567,6 +2604,10 @@ out:
|
||||
free(csec);
|
||||
free(cusec);
|
||||
|
||||
if (r->client_princ) {
|
||||
krb5_free_principal(r->context, r->client_princ);
|
||||
r->client_princ = NULL;
|
||||
}
|
||||
if (r->armor_crypto) {
|
||||
krb5_crypto_destroy(r->context, r->armor_crypto);
|
||||
r->armor_crypto = NULL;
|
||||
|
@@ -843,6 +843,7 @@ EXPORTS
|
||||
_krb5_expand_path_tokens ;!
|
||||
_krb5_make_pa_enc_challenge
|
||||
_krb5_validate_pa_enc_challenge
|
||||
_krb5_store_utf8_as_ucs2le_at_offset
|
||||
|
||||
; kinit helper
|
||||
krb5_get_init_creds_opt_set_pkinit_user_certs
|
||||
|
@@ -671,18 +671,23 @@ parse_upn_dns_info(krb5_context context,
|
||||
CHECK(ret, krb5_ret_uint16(sp, &sam_name_offset), out);
|
||||
CHECK(ret, krb5_ret_uint16(sp, &sid_length), out);
|
||||
CHECK(ret, krb5_ret_uint16(sp, &sid_offset), out);
|
||||
} else {
|
||||
sam_name_offset = 0;
|
||||
sid_offset = 0;
|
||||
}
|
||||
|
||||
CHECK(ret, _krb5_ret_utf8_from_ucs2le_at_offset(sp, upn_offset,
|
||||
upn_length, &upn), out);
|
||||
if (upn_offset) {
|
||||
CHECK(ret, _krb5_ret_utf8_from_ucs2le_at_offset(sp, upn_offset,
|
||||
upn_length, &upn), out);
|
||||
}
|
||||
CHECK(ret, _krb5_ret_utf8_from_ucs2le_at_offset(sp, dns_domain_name_offset,
|
||||
dns_domain_name_length, &dns_domain_name), out);
|
||||
if (*flags & PAC_EXTRA_LOGON_INFO_FLAGS_HAS_SAM_NAME_AND_SID) {
|
||||
if ((*flags & PAC_EXTRA_LOGON_INFO_FLAGS_HAS_SAM_NAME_AND_SID) && sam_name_offset) {
|
||||
CHECK(ret, _krb5_ret_utf8_from_ucs2le_at_offset(sp, sam_name_offset,
|
||||
sam_name_length, &sam_name), out);
|
||||
}
|
||||
|
||||
if (upn_length) {
|
||||
if (upn_offset) {
|
||||
ret = krb5_parse_name_flags(context,
|
||||
upn,
|
||||
KRB5_PRINCIPAL_PARSE_ENTERPRISE |
|
||||
@@ -696,7 +701,7 @@ parse_upn_dns_info(krb5_context context,
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (*flags & PAC_EXTRA_LOGON_INFO_FLAGS_HAS_SAM_NAME_AND_SID) {
|
||||
if (sam_name_offset) {
|
||||
ret = krb5_parse_name_flags(context,
|
||||
sam_name,
|
||||
KRB5_PRINCIPAL_PARSE_NO_REALM |
|
||||
@@ -708,16 +713,18 @@ parse_upn_dns_info(krb5_context context,
|
||||
ret = krb5_principal_set_realm(context, *sam_name_princ, dns_domain_name);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
CHECK(ret, _krb5_ret_data_at_offset(sp, sid_offset, sid_length, sid), out);
|
||||
}
|
||||
|
||||
if (sid_offset)
|
||||
CHECK(ret, _krb5_ret_data_at_offset(sp, sid_offset, sid_length, sid), out);
|
||||
|
||||
out:
|
||||
free(upn);
|
||||
free(dns_domain_name);
|
||||
free(sam_name);
|
||||
|
||||
krb5_storage_free(sp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -762,8 +769,7 @@ build_upn_dns_info(krb5_context context,
|
||||
|
||||
if (canon_princ) {
|
||||
ret = krb5_unparse_name_flags(context, canon_princ,
|
||||
KRB5_PRINCIPAL_UNPARSE_NO_REALM |
|
||||
KRB5_PRINCIPAL_UNPARSE_DISPLAY,
|
||||
KRB5_PRINCIPAL_UNPARSE_NO_REALM,
|
||||
&canon_princ_name);
|
||||
if (ret)
|
||||
goto out;
|
||||
@@ -805,6 +811,7 @@ out:
|
||||
|
||||
krb5_xfree(canon_princ_name);
|
||||
krb5_xfree(upn_princ_name);
|
||||
krb5_storage_free(sp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
108
lib/krb5/store.c
108
lib/krb5/store.c
@@ -1864,7 +1864,7 @@ cleanup:
|
||||
|
||||
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
|
||||
_krb5_ret_utf8_from_ucs2le_at_offset(krb5_storage *sp,
|
||||
size_t offset,
|
||||
off_t offset,
|
||||
size_t length,
|
||||
char **utf8)
|
||||
{
|
||||
@@ -1919,3 +1919,109 @@ out:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
|
||||
_krb5_store_data_at_offset(krb5_storage *sp,
|
||||
size_t offset,
|
||||
const krb5_data *data)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
krb5_ssize_t nbytes;
|
||||
off_t pos;
|
||||
|
||||
if (offset == (off_t)-1) {
|
||||
if (data == NULL || data->data == NULL) {
|
||||
offset = 0;
|
||||
} else {
|
||||
pos = sp->seek(sp, 0, SEEK_CUR);
|
||||
offset = sp->seek(sp, 0, SEEK_END);
|
||||
sp->seek(sp, pos, SEEK_SET);
|
||||
|
||||
if (offset == (off_t)-1)
|
||||
return HEIM_ERR_NOT_SEEKABLE;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset > 0xFFFF)
|
||||
return ERANGE;
|
||||
else if ((offset != 0) != (data && data->data))
|
||||
return EINVAL;
|
||||
else if (data && data->length > 0xFFFF)
|
||||
return ERANGE;
|
||||
|
||||
ret = krb5_store_uint16(sp, data ? (uint16_t)data->length : 0);
|
||||
if (ret == 0)
|
||||
ret = krb5_store_uint16(sp, (uint16_t)offset);
|
||||
if (ret == 0 && offset) {
|
||||
pos = sp->seek(sp, 0, SEEK_CUR);
|
||||
sp->seek(sp, offset, SEEK_SET);
|
||||
nbytes = krb5_storage_write(sp, data->data, data->length);
|
||||
if ((size_t)nbytes != data->length)
|
||||
ret = sp->eof_code;
|
||||
sp->seek(sp, pos, SEEK_SET);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
|
||||
_krb5_store_utf8_as_ucs2le_at_offset(krb5_storage *sp,
|
||||
off_t offset,
|
||||
const char *utf8)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
size_t ucs2_len, ucs2le_size;
|
||||
uint16_t *ucs2, *ucs2le;
|
||||
unsigned int flags;
|
||||
|
||||
if (utf8) {
|
||||
ret = wind_utf8ucs2_length(utf8, &ucs2_len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ucs2 = malloc(sizeof(ucs2[0]) * ucs2_len);
|
||||
if (ucs2 == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
ret = wind_utf8ucs2(utf8, ucs2, &ucs2_len);
|
||||
if (ret) {
|
||||
free(ucs2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ucs2le_size = (ucs2_len + 1) * 2;
|
||||
ucs2le = malloc(ucs2le_size);
|
||||
if (ucs2le == NULL) {
|
||||
free(ucs2);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
flags = WIND_RW_LE;
|
||||
ret = wind_ucs2write(ucs2, ucs2_len, &flags, ucs2le, &ucs2le_size);
|
||||
free(ucs2);
|
||||
if (ret) {
|
||||
free(ucs2le);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ucs2le_size = ucs2_len * 2;
|
||||
} else {
|
||||
ucs2le = NULL;
|
||||
ucs2le_size = 0;
|
||||
offset = 0;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
{
|
||||
krb5_data data;
|
||||
|
||||
data.data = ucs2le;
|
||||
data.length = ucs2le_size;
|
||||
|
||||
ret = _krb5_store_data_at_offset(sp, offset, &data);
|
||||
}
|
||||
|
||||
free(ucs2le);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@@ -833,6 +833,7 @@ HEIMDAL_KRB5_2.0 {
|
||||
_krb5_crypto_set_flags;
|
||||
_krb5_make_pa_enc_challenge;
|
||||
_krb5_validate_pa_enc_challenge;
|
||||
_krb5_store_utf8_as_ucs2le_at_offset;
|
||||
|
||||
# kinit helper
|
||||
krb5_get_init_creds_opt_set_pkinit_user_certs;
|
||||
|
Reference in New Issue
Block a user