diff --git a/kdc/fast.c b/kdc/fast.c index 5d2a97a6d..751b0376f 100644 --- a/kdc/fast.c +++ b/kdc/fast.c @@ -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); diff --git a/kdc/kdc_locl.h b/kdc/kdc_locl.h index cc57d0b71..c1c58faba 100644 --- a/kdc/kdc_locl.h +++ b/kdc/kdc_locl.h @@ -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; diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index 07a7988d8..fc324f11f 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -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 : ""); + 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); diff --git a/kdc/krb5tgs.c b/kdc/krb5tgs.c index 2f2e3bb9b..1a8644583 100644 --- a/kdc/krb5tgs.c +++ b/kdc/krb5tgs.c @@ -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 : ""); + 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; diff --git a/lib/krb5/libkrb5-exports.def.in b/lib/krb5/libkrb5-exports.def.in index 5cc5fe97c..7957a1191 100644 --- a/lib/krb5/libkrb5-exports.def.in +++ b/lib/krb5/libkrb5-exports.def.in @@ -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 diff --git a/lib/krb5/pac.c b/lib/krb5/pac.c index 130e9a098..2c230617b 100644 --- a/lib/krb5/pac.c +++ b/lib/krb5/pac.c @@ -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; } diff --git a/lib/krb5/store.c b/lib/krb5/store.c index e3e4aa8b8..6a287bdf9 100644 --- a/lib/krb5/store.c +++ b/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; +} diff --git a/lib/krb5/version-script.map b/lib/krb5/version-script.map index 0245dffba..bc97098d0 100644 --- a/lib/krb5/version-script.map +++ b/lib/krb5/version-script.map @@ -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;