From 0287558838de79313e38026d2f0905ffc987d0b8 Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Fri, 24 Dec 2021 13:49:55 +1100 Subject: [PATCH] kdc: move Services for User implementation out of krb5tgs.c Move the Services for User (SFU/S4U) implementation -- protocol transition and constrained delegation -- into its own compilation unit, with an interface that only takes an astgs_request_t, so it can be easily factored out into a plugin module in the future. This refactoring is also careful to update all client names in the request structure after the SFU/S4U validation has successfully completed. --- kdc/Makefile.am | 1 + kdc/NTMakefile | 2 + kdc/kdc.h | 11 +- kdc/kerberos5.c | 2 + kdc/krb5tgs.c | 561 +++++++--------------------------------------- kdc/mssfu.c | 575 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 670 insertions(+), 482 deletions(-) create mode 100644 kdc/mssfu.c diff --git a/kdc/Makefile.am b/kdc/Makefile.am index f3beba87f..544229708 100644 --- a/kdc/Makefile.am +++ b/kdc/Makefile.am @@ -125,6 +125,7 @@ libkdc_la_SOURCES = \ krb5tgs.c \ pkinit.c \ pkinit-ec.c \ + mssfu.c \ log.c \ misc.c \ kx509.c \ diff --git a/kdc/NTMakefile b/kdc/NTMakefile index 2dd65c721..b0855e3be 100644 --- a/kdc/NTMakefile +++ b/kdc/NTMakefile @@ -103,6 +103,7 @@ LIBKDC_OBJS=\ $(OBJ)\krb5tgs.obj \ $(OBJ)\pkinit.obj \ $(OBJ)\pkinit-ec.obj \ + $(OBJ)\mssfu.obj \ $(OBJ)\log.obj \ $(OBJ)\misc.obj \ $(OBJ)\kx509.obj \ @@ -144,6 +145,7 @@ libkdc_la_SOURCES = \ krb5tgs.c \ pkinit.c \ pkinit-ec.c \ + mssfu.c \ log.c \ misc.c \ kx509.c \ diff --git a/kdc/kdc.h b/kdc/kdc.h index 6d39e4c38..9a3df2c23 100644 --- a/kdc/kdc.h +++ b/kdc/kdc.h @@ -131,20 +131,29 @@ typedef struct krb5_kdc_configuration { #define ASTGS_REQUEST_DESC_COMMON_ELEMENTS \ HEIM_SVC_REQUEST_DESC_COMMON_ELEMENTS; \ \ + /* AS-REQ or TGS-REQ */ \ KDC_REQ req; \ \ + /* AS-REP or TGS-REP */ \ KDC_REP rep; \ EncTicketPart et; \ EncKDCRepPart ek; \ \ - /* princ requested by client (AS) or canon princ (TGT) */ \ + /* client principal (AS) or TGT/S4U principal (TGS) */ \ krb5_principal client_princ; \ hdb_entry_ex *client; \ HDB *clientdb; \ + krb5_principal canon_client_princ; \ \ + /* server principal */ \ krb5_principal server_princ; \ hdb_entry_ex *server; \ \ + /* presented ticket in TGS-REQ (unused by AS) */ \ + krb5_principal *krbtgt_princ; \ + hdb_entry_ex *krbtgt; \ + krb5_ticket *ticket; \ + \ krb5_keyblock reply_key; \ \ krb5_pac pac; \ diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index ae80ec63f..0c57e3821 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -2343,6 +2343,8 @@ _kdc_as_rep(astgs_request_t r) goto out; } + r->canon_client_princ = r->client->entry.principal; + /* * Verify flags after the user been required to prove its identity * with in a preauth mech. diff --git a/kdc/krb5tgs.c b/kdc/krb5tgs.c index 24c0a1206..ce76485d5 100644 --- a/kdc/krb5tgs.c +++ b/kdc/krb5tgs.c @@ -342,63 +342,6 @@ check_tgs_flags(astgs_request_t r, KDC_REQ_BODY *b, return 0; } -/* - * Determine if constrained delegation is allowed from this client to this server - */ - -static krb5_error_code -check_constrained_delegation(krb5_context context, - krb5_kdc_configuration *config, - HDB *clientdb, - hdb_entry_ex *client, - hdb_entry_ex *server, - krb5_const_principal target) -{ - const HDB_Ext_Constrained_delegation_acl *acl; - krb5_error_code ret; - size_t i; - - /* - * constrained_delegation (S4U2Proxy) only works within - * the same realm. We use the already canonicalized version - * of the principals here, while "target" is the principal - * provided by the client. - */ - if(!krb5_realm_compare(context, client->entry.principal, server->entry.principal)) { - ret = KRB5KDC_ERR_BADOPTION; - kdc_log(context, config, 4, - "Bad request for constrained delegation"); - return ret; - } - - if (clientdb->hdb_check_constrained_delegation) { - ret = clientdb->hdb_check_constrained_delegation(context, clientdb, client, target); - if (ret == 0) - return 0; - } else { - /* if client delegates to itself, that ok */ - if (krb5_principal_compare(context, client->entry.principal, server->entry.principal) == TRUE) - return 0; - - ret = hdb_entry_get_ConstrainedDelegACL(&client->entry, &acl); - if (ret) { - krb5_clear_error_message(context); - return ret; - } - - if (acl) { - for (i = 0; i < acl->len; i++) { - if (krb5_principal_compare(context, target, &acl->val[i]) == TRUE) - return 0; - } - } - ret = KRB5KDC_ERR_BADOPTION; - } - kdc_log(context, config, 4, - "Bad request for constrained delegation"); - return ret; -} - /* * Determine if s4u2self is allowed from this client to this server * @@ -412,13 +355,13 @@ check_constrained_delegation(krb5_context context, * alias of client, then it's safe. */ -static krb5_error_code -check_client_matches_target_service(krb5_context context, - krb5_kdc_configuration *config, - HDB *clientdb, - hdb_entry_ex *client, - hdb_entry_ex *target_server, - krb5_const_principal target_server_principal) +krb5_error_code +_kdc_check_client_matches_target_service(krb5_context context, + krb5_kdc_configuration *config, + HDB *clientdb, + hdb_entry_ex *client, + hdb_entry_ex *target_server, + krb5_const_principal target_server_principal) { krb5_error_code ret; @@ -587,7 +530,6 @@ fix_transited_encoding(krb5_context context, static krb5_error_code tgs_make_reply(astgs_request_t r, - krb5_principal tgt_name, const EncTicketPart *tgt, const EncryptionKey *serverkey, const EncryptionKey *krbtgtkey, @@ -611,6 +553,8 @@ tgs_make_reply(astgs_request_t r, krb5_error_code ret; int is_weak = 0; + heim_assert(r->client_princ != NULL, "invalid client name passed to tgs_make_reply"); + rep->pvno = 5; rep->msg_type = krb_tgs_rep; @@ -620,7 +564,7 @@ tgs_make_reply(astgs_request_t r, ALLOC(et->starttime); *et->starttime = kdc_time; - ret = check_tgs_flags(r, b, tgt_name, tgt, et); + ret = check_tgs_flags(r, b, r->client_princ, tgt, et); if(ret) goto out; @@ -661,7 +605,7 @@ tgs_make_reply(astgs_request_t r, if (ret) goto out; _krb5_principal2principalname(&rep->ticket.sname, server_principal); - ret = copy_Realm(&tgt_name->realm, &rep->crealm); + ret = copy_Realm(&r->client_princ->realm, &rep->crealm); if (ret) goto out; @@ -674,7 +618,7 @@ tgs_make_reply(astgs_request_t r, if (et->flags.anonymous && !tgt->flags.anonymous) _kdc_make_anonymous_principalname(&rep->cname); else - ret = copy_PrincipalName(&tgt_name->name, &rep->cname); + ret = copy_PrincipalName(&r->client_princ->name, &rep->cname); if (ret) goto out; rep->ticket.tkt_vno = 5; @@ -791,10 +735,10 @@ tgs_make_reply(astgs_request_t r, is_weak = 1; } - if (r->client_princ) { + if (r->canon_client_princ) { char *cpn; - krb5_unparse_name(r->context, r->client_princ, &cpn); + krb5_unparse_name(r->context, r->canon_client_princ, &cpn); _kdc_audit_addkv((kdc_request_t)r, 0, "canon_client_name", "%s", cpn ? cpn : ""); krb5_xfree(cpn); @@ -819,8 +763,8 @@ tgs_make_reply(astgs_request_t r, krb5_boolean is_tgs = krb5_principal_is_krbtgt(r->context, server->entry.principal); - ret = _krb5_kdc_pac_sign_ticket(r->context, r->pac, tgt_name, serverkey, - krbtgtkey, rodc_id, NULL, r->client_princ, + ret = _krb5_kdc_pac_sign_ticket(r->context, r->pac, r->client_princ, serverkey, + krbtgtkey, rodc_id, NULL, r->canon_client_princ, add_ticket_sig, et, is_tgs ? &r->pac_attributes : NULL); if (ret) @@ -962,9 +906,7 @@ validate_fast_ad(astgs_request_t r, krb5_authdata *auth_data) static krb5_error_code tgs_parse_request(astgs_request_t r, const PA_DATA *tgs_req, - hdb_entry_ex **krbtgt, krb5_enctype *krbtgt_etype, - krb5_ticket **ticket, const char *from, const struct sockaddr *from_addr, time_t **csec, @@ -1016,7 +958,7 @@ tgs_parse_request(astgs_request_t r, krbtgt_kvno = ap_req.ticket.enc_part.kvno ? *ap_req.ticket.enc_part.kvno : 0; ret = _kdc_db_fetch(r->context, config, princ, HDB_F_GET_KRBTGT, - &krbtgt_kvno, NULL, krbtgt); + &krbtgt_kvno, NULL, &r->krbtgt); if (ret == HDB_ERR_NOT_FOUND_HERE) { /* XXX Factor out this unparsing of the same princ all over */ @@ -1074,12 +1016,12 @@ tgs_parse_request(astgs_request_t r, goto out; } - krbtgt_kvno_try = krbtgt_kvno ? krbtgt_kvno : (*krbtgt)->entry.kvno; + krbtgt_kvno_try = krbtgt_kvno ? krbtgt_kvno : r->krbtgt->entry.kvno; *krbtgt_etype = ap_req.ticket.enc_part.etype; next_kvno: - krbtgt_keys = hdb_kvno2keys(r->context, &(*krbtgt)->entry, krbtgt_kvno_try); - ret = hdb_enctype2key(r->context, &(*krbtgt)->entry, krbtgt_keys, + krbtgt_keys = hdb_kvno2keys(r->context, &r->krbtgt->entry, krbtgt_kvno_try); + ret = hdb_enctype2key(r->context, &r->krbtgt->entry, krbtgt_keys, ap_req.ticket.enc_part.etype, &tkey); if (ret && krbtgt_kvno == 0 && kvno_search_tries > 0) { kvno_search_tries--; @@ -1113,12 +1055,12 @@ next_kvno: &tkey->key, verify_ap_req_flags, &ap_req_options, - ticket, + &r->ticket, KRB5_KU_TGS_REQ_AUTH); - if (*ticket && (*ticket)->ticket.caddr) - _kdc_audit_addaddrs((kdc_request_t)r, (*ticket)->ticket.caddr, "tixaddrs"); + if (r->ticket && r->ticket->ticket.caddr) + _kdc_audit_addaddrs((kdc_request_t)r, r->ticket->ticket.caddr, "tixaddrs"); if (r->config->warn_ticket_addresses && ret == KRB5KRB_AP_ERR_BADADDR && - *ticket != NULL) { + r->ticket != NULL) { _kdc_audit_setkv_bool((kdc_request_t)r, "wrongaddr", TRUE); ret = 0; } @@ -1165,8 +1107,7 @@ next_kvno: } } - ret = tgs_check_authenticator(r->context, config, ac, b, - &(*ticket)->ticket.key); + ret = tgs_check_authenticator(r->context, config, ac, b, &r->ticket->ticket.key); if (ret) { krb5_auth_con_free(r->context, ac); goto out; @@ -1251,7 +1192,7 @@ next_kvno: } } - ret = validate_fast_ad(r, (*ticket)->ticket.authorization_data); + ret = validate_fast_ad(r, r->ticket->ticket.authorization_data); if (ret) goto out; @@ -1260,7 +1201,7 @@ next_kvno: * Check for FAST request */ - ret = _kdc_fast_unwrap_request(r, *ticket, ac); + ret = _kdc_fast_unwrap_request(r, r->ticket, ac); if (ret) goto out; @@ -1405,29 +1346,26 @@ _kdc_db_fetch_client(krb5_context context, static krb5_error_code tgs_build_reply(astgs_request_t priv, - hdb_entry_ex *krbtgt, krb5_enctype krbtgt_etype, - krb5_ticket *ticket, AuthorizationData **auth_data, const struct sockaddr *from_addr) { krb5_context context = priv->context; krb5_kdc_configuration *config = priv->config; - KDC_REQ *req = &priv->req; KDC_REQ_BODY *b = &priv->req.req_body; const char *from = priv->from; krb5_error_code ret, ret2; - krb5_principal cp = NULL, sp = NULL, rsp = NULL, tp = NULL, dp = NULL; + krb5_principal cp = NULL, sp = NULL, rsp = NULL; krb5_principal krbtgt_out_principal = NULL; krb5_principal user2user_princ = NULL; - char *spn = NULL, *cpn = NULL, *tpn = NULL, *dpn = NULL, *krbtgt_out_n = NULL; + char *spn = NULL, *cpn = NULL, *krbtgt_out_n = NULL; char *user2user_name = NULL; - hdb_entry_ex *server = NULL, *client = NULL, *s4u2self_impersonated_client = NULL; + hdb_entry_ex *server = NULL, *client = NULL; hdb_entry_ex *user2user_krbtgt = NULL; - HDB *clientdb, *s4u2self_impersonated_clientdb; + HDB *clientdb; HDB *serverdb = NULL; krb5_realm ref_realm = NULL; - EncTicketPart *tgt = &ticket->ticket; + EncTicketPart *tgt = &priv->ticket->ticket; const EncryptionKey *ekey; krb5_keyblock sessionkey; krb5_kvno kvno; @@ -1435,9 +1373,9 @@ tgs_build_reply(astgs_request_t priv, uint16_t rodc_id; krb5_boolean add_ticket_sig = FALSE; const char *tgt_realm = /* Realm of TGT issuer */ - krb5_principal_get_realm(context, krbtgt->entry.principal); + krb5_principal_get_realm(context, priv->krbtgt->entry.principal); const char *our_realm = /* Realm of this KDC */ - krb5_principal_get_comp_string(context, krbtgt->entry.principal, 1); + krb5_principal_get_comp_string(context, priv->krbtgt->entry.principal, 1); char **capath = NULL; size_t num_capath = 0; @@ -1650,6 +1588,8 @@ server_lookup: else rsp = sp; + priv->server_princ = sp; + /* * Now refetch the primary krbtgt, and get the current kvno (the * sign check may have been on an old kvno, and the server may @@ -1680,7 +1620,7 @@ server_lookup: HDB_F_GET_KRBTGT, NULL, NULL, &krbtgt_out); if (ret) { char *ktpn = NULL; - ret = krb5_unparse_name(context, krbtgt->entry.principal, &ktpn); + ret = krb5_unparse_name(context, priv->krbtgt->entry.principal, &ktpn); kdc_log(context, config, 4, "No such principal %s (needed for authz-data signature keys) " "while processing TGS-REQ for service %s with krbtg %s", @@ -1706,6 +1646,7 @@ server_lookup: size_t i; hdb_entry_ex *user2user_client = NULL; krb5_boolean user2user_kdc_issued = FALSE; + char *tpn; if(b->additional_tickets == NULL || b->additional_tickets->len == 0){ @@ -1745,6 +1686,7 @@ server_lookup: ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; _kdc_audit_addreason((kdc_request_t)priv, "User-to-user service principal (TGS) unknown"); + krb5_xfree(tpn); goto out; } ret = hdb_enctype2key(context, &user2user_krbtgt->entry, NULL, @@ -1753,12 +1695,14 @@ server_lookup: ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ _kdc_audit_addreason((kdc_request_t)priv, "User-to-user enctype not supported"); + krb5_xfree(tpn); goto out; } ret = krb5_decrypt_ticket(context, t, &uukey->key, &adtkt, 0); if(ret) { _kdc_audit_addreason((kdc_request_t)priv, "User-to-user TGT decrypt failure"); + krb5_xfree(tpn); goto out; } @@ -1766,8 +1710,10 @@ server_lookup: if (ret) { _kdc_audit_addreason((kdc_request_t)priv, "User-to-user TGT expired or invalid"); + krb5_xfree(tpn); goto out; } + krb5_xfree(tpn); /* Fetch the name from the TGT. */ ret = _krb5_principalname2krb5_principal(context, &user2user_princ, @@ -1816,12 +1762,12 @@ server_lookup: * Also check that the account is the same one specified in the * request. */ - ret = check_client_matches_target_service(context, - config, - serverdb, - server, - user2user_client, - user2user_princ); + ret = _kdc_check_client_matches_target_service(context, + config, + serverdb, + server, + user2user_client, + user2user_princ); if (ret) { _kdc_free_ent(context, user2user_client); goto out; @@ -1945,7 +1891,7 @@ server_lookup: goto out; } - if (_kdc_synthetic_princ_used_p(context, ticket)) + if (_kdc_synthetic_princ_used_p(context, priv->ticket)) flags |= HDB_F_SYNTHETIC_OK; ret = _kdc_db_fetch_client(context, config, flags, cp, cpn, our_realm, @@ -1954,12 +1900,14 @@ server_lookup: goto out; flags &= ~HDB_F_SYNTHETIC_OK; priv->client = client; + priv->clientdb = clientdb; - 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, + ret = _kdc_check_pac(context, config, cp, NULL, + priv->client, priv->server, + priv->krbtgt, priv->krbtgt, &priv->ticket_key->key, &priv->ticket_key->key, tgt, - &kdc_issued, &priv->pac, &priv->client_princ, &priv->pac_attributes); + &kdc_issued, &priv->pac, &priv->canon_client_princ, + &priv->pac_attributes); if (ret) { const char *msg = krb5_get_error_message(context, ret); _kdc_audit_addreason((kdc_request_t)priv, "PAC check failed"); @@ -1975,351 +1923,15 @@ server_lookup: */ /* by default the tgt principal matches the client principal */ - tp = cp; - tpn = cpn; - - if (client) { - const PA_DATA *sdata; - int i = 0; - - sdata = _kdc_find_padata(req, &i, KRB5_PADATA_FOR_USER); - if (sdata) { - krb5_crypto crypto; - krb5_data datack; - PA_S4U2Self self; - const char *str; - - ret = decode_PA_S4U2Self(sdata->padata_value.data, - sdata->padata_value.length, - &self, NULL); - if (ret) { - _kdc_audit_addreason((kdc_request_t)priv, - "Failed to decode PA-S4U2Self"); - kdc_log(context, config, 4, "Failed to decode PA-S4U2Self"); - goto out; - } - - if (!krb5_checksum_is_keyed(context, self.cksum.cksumtype)) { - free_PA_S4U2Self(&self); - _kdc_audit_addreason((kdc_request_t)priv, - "PA-S4U2Self with unkeyed checksum"); - kdc_log(context, config, 4, "Reject PA-S4U2Self with unkeyed checksum"); - ret = KRB5KRB_AP_ERR_INAPP_CKSUM; - goto out; - } - - ret = _krb5_s4u2self_to_checksumdata(context, &self, &datack); - if (ret) - goto out; - - ret = krb5_crypto_init(context, &tgt->key, 0, &crypto); - if (ret) { - const char *msg = krb5_get_error_message(context, ret); - free_PA_S4U2Self(&self); - krb5_data_free(&datack); - kdc_log(context, config, 4, "krb5_crypto_init failed: %s", msg); - krb5_free_error_message(context, msg); - goto out; - } - - /* Allow HMAC_MD5 checksum with any key type */ - if (self.cksum.cksumtype == CKSUMTYPE_HMAC_MD5) { - struct krb5_crypto_iov iov; - unsigned char csdata[16]; - Checksum cs; - - cs.checksum.length = sizeof(csdata); - cs.checksum.data = &csdata; - - iov.data.data = datack.data; - iov.data.length = datack.length; - iov.flags = KRB5_CRYPTO_TYPE_DATA; - - ret = _krb5_HMAC_MD5_checksum(context, NULL, &crypto->key, - KRB5_KU_OTHER_CKSUM, &iov, 1, - &cs); - if (ret == 0 && - krb5_data_ct_cmp(&cs.checksum, &self.cksum.checksum) != 0) - ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; - } - else { - ret = _kdc_verify_checksum(context, - crypto, - KRB5_KU_OTHER_CKSUM, - &datack, - &self.cksum); - } - krb5_data_free(&datack); - krb5_crypto_destroy(context, crypto); - if (ret) { - const char *msg = krb5_get_error_message(context, ret); - free_PA_S4U2Self(&self); - _kdc_audit_addreason((kdc_request_t)priv, - "S4U2Self checksum failed"); - kdc_log(context, config, 4, - "krb5_verify_checksum failed for S4U2Self: %s", msg); - krb5_free_error_message(context, msg); - goto out; - } - - ret = _krb5_principalname2krb5_principal(context, - &tp, - self.name, - self.realm); - free_PA_S4U2Self(&self); - if (ret) - goto out; - - ret = krb5_unparse_name(context, tp, &tpn); - if (ret) - goto out; - - /* - * Note no HDB_F_SYNTHETIC_OK -- impersonating non-existent clients - * is probably not desirable! - */ - ret = _kdc_db_fetch(context, config, tp, HDB_F_GET_CLIENT | flags, - NULL, &s4u2self_impersonated_clientdb, - &s4u2self_impersonated_client); - if (ret) { - const char *msg; - - /* - * If the client belongs to the same realm as our krbtgt, it - * should exist in the local database. - * - */ - - if (ret == HDB_ERR_NOENTRY) - ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; - msg = krb5_get_error_message(context, ret); - _kdc_audit_addreason((kdc_request_t)priv, - "S4U2Self principal to impersonate not found"); - kdc_log(context, config, 2, - "S4U2Self principal to impersonate %s not found in database: %s", - tpn, msg); - krb5_free_error_message(context, msg); - goto out; - } - - /* Ignore require_pwchange and pw_end attributes (as Windows does), - * since S4U2Self is not password authentication. */ - s4u2self_impersonated_client->entry.flags.require_pwchange = FALSE; - free(s4u2self_impersonated_client->entry.pw_end); - s4u2self_impersonated_client->entry.pw_end = NULL; - - ret = kdc_check_flags(priv, FALSE, s4u2self_impersonated_client, priv->server); - if (ret) - goto out; /* kdc_check_flags() calls _kdc_audit_addreason() */ - - /* If we were about to put a PAC into the ticket, we better fix it to be the right PAC */ - krb5_pac_free(context, priv->pac); - priv->pac = NULL; - - ret = _kdc_pac_generate(context, - s4u2self_impersonated_client, - server, - NULL, - KRB5_PAC_WAS_GIVEN_IMPLICITLY, - &priv->pac); - if (ret) { - kdc_log(context, config, 4, "PAC generation failed for -- %s", tpn); - goto out; - } - - /* - * Check that service doing the impersonating is - * requesting a ticket to it-self. - */ - ret = check_client_matches_target_service(context, - config, - clientdb, - client, - server, - sp); - if (ret) { - kdc_log(context, config, 4, "S4U2Self: %s is not allowed " - "to impersonate to service " - "(tried for user %s to service %s)", - cpn, tpn, spn); - goto out; - } - - /* - * If the service isn't trusted for authentication to - * delegation or if the impersonate client is disallowed - * forwardable, remove the forwardable flag. - */ - - if (client->entry.flags.trusted_for_delegation && - s4u2self_impersonated_client->entry.flags.forwardable) { - str = "[forwardable]"; - } else { - b->kdc_options.forwardable = 0; - str = ""; - } - kdc_log(context, config, 4, "s4u2self %s impersonating %s to " - "service %s %s", cpn, tpn, spn, str); - } - } + priv->client_princ = cp; /* - * Constrained delegation + * Services for User: protocol transition and constrained delegation */ - if (client != NULL - && b->additional_tickets != NULL - && b->additional_tickets->len != 0 - && b->kdc_options.cname_in_addl_tkt - && b->kdc_options.enc_tkt_in_skey == 0) - { - hdb_entry_ex *adclient = NULL; - krb5_boolean ad_kdc_issued = FALSE; - Key *clientkey; - Ticket *t; - - /* - * We require that the service's krbtgt has a PAC. - */ - if (priv->pac == NULL) { - ret = KRB5KDC_ERR_BADOPTION; - _kdc_audit_addreason((kdc_request_t)priv, "Missing PAC"); - kdc_log(context, config, 4, - "Constrained delegation without PAC, %s/%s", - cpn, spn); - goto out; - } - - krb5_pac_free(context, priv->pac); - priv->pac = NULL; - - krb5_free_principal(context, priv->client_princ); - priv->client_princ = NULL; - - t = &b->additional_tickets->val[0]; - - ret = hdb_enctype2key(context, &client->entry, - hdb_kvno2keys(context, &client->entry, - t->enc_part.kvno ? * t->enc_part.kvno : 0), - t->enc_part.etype, &clientkey); - if(ret){ - ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ - goto out; - } - - ret = krb5_decrypt_ticket(context, t, &clientkey->key, &adtkt, 0); - if (ret) { - _kdc_audit_addreason((kdc_request_t)priv, - "Failed to decrypt constrained delegation ticket"); - kdc_log(context, config, 4, - "failed to decrypt ticket for " - "constrained delegation from %s to %s ", cpn, spn); - goto out; - } - - ret = _krb5_principalname2krb5_principal(context, - &tp, - adtkt.cname, - adtkt.crealm); - if (ret) - goto out; - - ret = krb5_unparse_name(context, tp, &tpn); - if (ret) - goto out; - - _kdc_audit_addkv((kdc_request_t)priv, 0, "impersonatee", "%s", tpn); - - ret = _krb5_principalname2krb5_principal(context, - &dp, - t->sname, - t->realm); - if (ret) - goto out; - - ret = krb5_unparse_name(context, dp, &dpn); - if (ret) - goto out; - - /* check that ticket is valid */ - if (adtkt.flags.forwardable == 0) { - _kdc_audit_addreason((kdc_request_t)priv, - "Missing forwardable flag on ticket for constrained delegation"); - kdc_log(context, config, 4, - "Missing forwardable flag on ticket for " - "constrained delegation from %s (%s) as %s to %s ", - cpn, dpn, tpn, spn); - ret = KRB5KDC_ERR_BADOPTION; - goto out; - } - - ret = check_constrained_delegation(context, config, clientdb, - client, server, sp); - if (ret) { - _kdc_audit_addreason((kdc_request_t)priv, - "Constrained delegation not allowed"); - kdc_log(context, config, 4, - "constrained delegation from %s (%s) as %s to %s not allowed", - cpn, dpn, tpn, spn); - goto out; - } - - ret = _kdc_verify_flags(context, config, &adtkt, tpn); - if (ret) { - _kdc_audit_addreason((kdc_request_t)priv, - "Constrained delegation ticket expired or invalid"); - goto out; - } - - /* Try lookup the delegated client in DB */ - ret = _kdc_db_fetch_client(context, config, flags, tp, tpn, our_realm, - NULL, &adclient); - if (ret) - goto out; - - if (adclient != NULL) { - ret = kdc_check_flags(priv, FALSE, adclient, priv->server); - if (ret) { - _kdc_free_ent(context, adclient); - goto out; - } - } - - /* - * TODO: pass in t->sname and t->realm and build - * 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, &priv->pac, &priv->client_princ, &priv->pac_attributes); - if (adclient) - _kdc_free_ent(context, adclient); - if (ret) { - const char *msg = krb5_get_error_message(context, ret); - _kdc_audit_addreason((kdc_request_t)priv, - "Constrained delegation ticket PAC check failed"); - kdc_log(context, config, 4, - "Verify delegated PAC failed to %s for client" - "%s (%s) as %s from %s with %s", - spn, cpn, dpn, tpn, from, msg); - krb5_free_error_message(context, msg); - goto out; - } - - if (priv->pac == NULL || !ad_kdc_issued) { - ret = KRB5KDC_ERR_BADOPTION; - kdc_log(context, config, 4, - "Ticket not signed with PAC; service %s failed for " - "for delegation to %s for client %s (%s) from %s; (%s).", - spn, tpn, dpn, cpn, from, priv->pac ? "Ticket unsigned" : "No PAC"); - _kdc_audit_addreason((kdc_request_t)priv, - "Constrained delegation ticket not signed"); - goto out; - } - - kdc_log(context, config, 4, "constrained delegation for %s " - "from %s (%s) to %s", tpn, cpn, dpn, spn); - } + ret = _kdc_validate_services_for_user(priv); + if (ret) + goto out; /* * Check flags @@ -2331,8 +1943,8 @@ server_lookup: if((b->kdc_options.validate || b->kdc_options.renew) && !krb5_principal_compare(context, - krbtgt->entry.principal, - server->entry.principal)){ + priv->krbtgt->entry.principal, + priv->server->entry.principal)){ _kdc_audit_addreason((kdc_request_t)priv, "Inconsistent request"); kdc_log(context, config, 4, "Inconsistent request."); ret = KRB5KDC_ERR_SERVER_NOMATCH; @@ -2427,7 +2039,6 @@ server_lookup: */ ret = tgs_make_reply(priv, - tp, tgt, ekey, &tkey_sign->key, @@ -2444,29 +2055,18 @@ server_lookup: out: free(user2user_name); - if (tpn != cpn) - free(tpn); - free(dpn); free(krbtgt_out_n); _krb5_free_capath(context, capath); krb5_free_keyblock_contents(context, &sessionkey); if(krbtgt_out) _kdc_free_ent(context, krbtgt_out); - if(server) - _kdc_free_ent(context, server); - if(client) - _kdc_free_ent(context, client); - if(s4u2self_impersonated_client) - _kdc_free_ent(context, s4u2self_impersonated_client); if(user2user_krbtgt) _kdc_free_ent(context, user2user_krbtgt); krb5_free_principal(context, user2user_princ); - if (tp && tp != cp) - krb5_free_principal(context, tp); - krb5_free_principal(context, cp); - krb5_free_principal(context, dp); + if (priv->client_princ != cp) + krb5_free_principal(context, cp); /* else caller frees */ krb5_free_principal(context, sp); krb5_free_principal(context, krbtgt_out_principal); free(ref_realm); @@ -2495,9 +2095,6 @@ _kdc_tgs_rep(astgs_request_t r) krb5_error_code ret; int i = 0; const PA_DATA *tgs_req, *pa; - - hdb_entry_ex *krbtgt = NULL; - krb5_ticket *ticket = NULL; krb5_enctype krbtgt_etype = ETYPE_NULL; time_t *csec = NULL; @@ -2530,9 +2127,7 @@ _kdc_tgs_rep(astgs_request_t r) goto out; } ret = tgs_parse_request(r, tgs_req, - &krbtgt, &krbtgt_etype, - &ticket, from, from_addr, &csec, &cusec, &auth_data); @@ -2558,9 +2153,7 @@ _kdc_tgs_rep(astgs_request_t r) } ret = tgs_build_reply(r, - krbtgt, krbtgt_etype, - ticket, &auth_data, from_addr); if (ret) { @@ -2589,8 +2182,8 @@ out: r->armor_crypto, &req->req_body, r->ret, - ticket != NULL ? ticket->client : NULL, - ticket != NULL ? ticket->server : NULL, + r->ticket != NULL ? r->ticket->client : NULL, + r->ticket != NULL ? r->ticket->server : NULL, csec, cusec, data); free_METHOD_DATA(&error_method); @@ -2613,9 +2206,9 @@ out: } free_EncryptionKey(&r->et.key); - if (r->client_princ) { - krb5_free_principal(r->context, r->client_princ); - r->client_princ = NULL; + if (r->canon_client_princ) { + krb5_free_principal(r->context, r->canon_client_princ); + r->canon_client_princ = NULL; } if (r->armor_crypto) { krb5_crypto_destroy(r->context, r->armor_crypto); @@ -2628,10 +2221,16 @@ out: krb5_free_keyblock_contents(r->context, &r->reply_key); krb5_free_keyblock_contents(r->context, &r->strengthen_key); - if (ticket) - krb5_free_ticket(r->context, ticket); - if(krbtgt) - _kdc_free_ent(r->context, krbtgt); + if (r->ticket) + krb5_free_ticket(r->context, r->ticket); + if (r->krbtgt) + _kdc_free_ent(r->context, r->krbtgt); + + if (r->client) + _kdc_free_ent(r->context, r->client); + krb5_free_principal(r->context, r->client_princ); + if (r->server) + _kdc_free_ent(r->context, r->server); _kdc_free_fast_state(&r->fast); krb5_pac_free(r->context, r->pac); diff --git a/kdc/mssfu.c b/kdc/mssfu.c new file mode 100644 index 000000000..2f97b86d8 --- /dev/null +++ b/kdc/mssfu.c @@ -0,0 +1,575 @@ +/* + * Copyright (c) 1997-2008 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" + +/* + * [MS-SFU] Kerberos Protocol Extensions: + * Service for User (S4U2Self) and Constrained Delegation Protocol (S4U2Proxy) + * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-sfu/ + */ + +/* + * Determine if constrained delegation is allowed from this client to this server + */ + +static krb5_error_code +check_constrained_delegation(krb5_context context, + krb5_kdc_configuration *config, + HDB *clientdb, + hdb_entry_ex *client, + hdb_entry_ex *server, + krb5_const_principal target) +{ + const HDB_Ext_Constrained_delegation_acl *acl; + krb5_error_code ret; + size_t i; + + /* + * constrained delegation (S4U2Proxy) only works within + * the same realm. We use the already canonicalized version + * of the principals here, while "target" is the principal + * provided by the client. + */ + if (!krb5_realm_compare(context, client->entry.principal, server->entry.principal)) { + ret = KRB5KDC_ERR_BADOPTION; + kdc_log(context, config, 4, + "Bad request for constrained delegation"); + return ret; + } + + if (clientdb->hdb_check_constrained_delegation) { + ret = clientdb->hdb_check_constrained_delegation(context, clientdb, client, target); + if (ret == 0) + return 0; + } else { + /* if client delegates to itself, that ok */ + if (krb5_principal_compare(context, client->entry.principal, server->entry.principal) == TRUE) + return 0; + + ret = hdb_entry_get_ConstrainedDelegACL(&client->entry, &acl); + if (ret) { + krb5_clear_error_message(context); + return ret; + } + + if (acl) { + for (i = 0; i < acl->len; i++) { + if (krb5_principal_compare(context, target, &acl->val[i]) == TRUE) + return 0; + } + } + ret = KRB5KDC_ERR_BADOPTION; + } + kdc_log(context, config, 4, + "Bad request for constrained delegation"); + return ret; +} + +static void +update_client_names(astgs_request_t r, + char **s4ucname, + krb5_principal *s4u_client_name, + hdb_entry_ex **s4u_client, + krb5_principal *s4u_canon_client_name, + krb5_pac *s4u_pac) +{ + krb5_xfree(r->cname); + r->cname = *s4ucname; + *s4ucname = NULL; + + r->client_princ = *s4u_client_name; + *s4u_client_name = NULL; + + _kdc_free_ent(r->context, r->client); + r->client = *s4u_client; + *s4u_client = NULL; + + krb5_free_principal(r->context, r->canon_client_princ); + r->canon_client_princ = *s4u_canon_client_name; + *s4u_canon_client_name = NULL; + + krb5_pac_free(r->context, r->pac); + r->pac = *s4u_pac; + *s4u_pac = NULL; +} + +/* + * Validate a protocol transition (S4U2Self) request. If present and + * successfully validated then the client in the request structure + * will be replaced with the impersonated client. + */ + +static krb5_error_code +validate_protocol_transition(astgs_request_t r) +{ + krb5_error_code ret; + KDC_REQ_BODY *b = &r->req.req_body; + EncTicketPart *ticket = &r->ticket->ticket; + hdb_entry_ex *s4u_client = NULL; + HDB *s4u_clientdb; + int flags = HDB_F_FOR_TGS_REQ; + krb5_principal s4u_client_name = NULL, s4u_canon_client_name = NULL; + krb5_pac s4u_pac = NULL; + const PA_DATA *sdata; + char *s4ucname = NULL; + int i = 0; + krb5_crypto crypto; + krb5_data datack; + PA_S4U2Self self; + const char *str; + + if (r->client == NULL) + return 0; + + sdata = _kdc_find_padata(&r->req, &i, KRB5_PADATA_FOR_USER); + if (sdata == NULL) + return 0; + + memset(&self, 0, sizeof(self)); + + if (b->kdc_options.canonicalize) + flags |= HDB_F_CANON; + + ret = decode_PA_S4U2Self(sdata->padata_value.data, + sdata->padata_value.length, + &self, NULL); + if (ret) { + _kdc_audit_addreason((kdc_request_t)r, + "Failed to decode PA-S4U2Self"); + kdc_log(r->context, r->config, 4, "Failed to decode PA-S4U2Self"); + goto out; + } + + if (!krb5_checksum_is_keyed(r->context, self.cksum.cksumtype)) { + _kdc_audit_addreason((kdc_request_t)r, + "PA-S4U2Self with unkeyed checksum"); + kdc_log(r->context, r->config, 4, "Reject PA-S4U2Self with unkeyed checksum"); + ret = KRB5KRB_AP_ERR_INAPP_CKSUM; + goto out; + } + + ret = _krb5_s4u2self_to_checksumdata(r->context, &self, &datack); + if (ret) + goto out; + + ret = krb5_crypto_init(r->context, &ticket->key, 0, &crypto); + if (ret) { + const char *msg = krb5_get_error_message(r->context, ret); + krb5_data_free(&datack); + kdc_log(r->context, r->config, 4, "krb5_crypto_init failed: %s", msg); + krb5_free_error_message(r->context, msg); + goto out; + } + + /* Allow HMAC_MD5 checksum with any key type */ + if (self.cksum.cksumtype == CKSUMTYPE_HMAC_MD5) { + struct krb5_crypto_iov iov; + unsigned char csdata[16]; + Checksum cs; + + cs.checksum.length = sizeof(csdata); + cs.checksum.data = &csdata; + + iov.data.data = datack.data; + iov.data.length = datack.length; + iov.flags = KRB5_CRYPTO_TYPE_DATA; + + ret = _krb5_HMAC_MD5_checksum(r->context, NULL, &crypto->key, + KRB5_KU_OTHER_CKSUM, &iov, 1, + &cs); + if (ret == 0 && + krb5_data_ct_cmp(&cs.checksum, &self.cksum.checksum) != 0) + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + } else { + ret = _kdc_verify_checksum(r->context, + crypto, + KRB5_KU_OTHER_CKSUM, + &datack, + &self.cksum); + } + krb5_data_free(&datack); + krb5_crypto_destroy(r->context, crypto); + if (ret) { + const char *msg = krb5_get_error_message(r->context, ret); + _kdc_audit_addreason((kdc_request_t)r, + "S4U2Self checksum failed"); + kdc_log(r->context, r->config, 4, + "krb5_verify_checksum failed for S4U2Self: %s", msg); + krb5_free_error_message(r->context, msg); + goto out; + } + + ret = _krb5_principalname2krb5_principal(r->context, + &s4u_client_name, + self.name, + self.realm); + if (ret) + goto out; + + ret = krb5_unparse_name(r->context, s4u_client_name, &s4ucname); + if (ret) + goto out; + + /* + * Note no HDB_F_SYNTHETIC_OK -- impersonating non-existent clients + * is probably not desirable! + */ + ret = _kdc_db_fetch(r->context, r->config, s4u_client_name, + HDB_F_GET_CLIENT | flags, NULL, + &s4u_clientdb, &s4u_client); + if (ret) { + const char *msg; + + /* + * If the client belongs to the same realm as our krbtgt, it + * should exist in the local database. + * + */ + if (ret == HDB_ERR_NOENTRY) + ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + msg = krb5_get_error_message(r->context, ret); + _kdc_audit_addreason((kdc_request_t)r, + "S4U2Self principal to impersonate not found"); + kdc_log(r->context, r->config, 2, + "S4U2Self principal to impersonate %s not found in database: %s", + s4ucname, msg); + krb5_free_error_message(r->context, msg); + goto out; + } + + /* + * Ignore require_pwchange and pw_end attributes (as Windows does), + * since S4U2Self is not password authentication. + */ + s4u_client->entry.flags.require_pwchange = FALSE; + free(s4u_client->entry.pw_end); + s4u_client->entry.pw_end = NULL; + + ret = kdc_check_flags(r, FALSE, s4u_client, r->server); + if (ret) + goto out; /* kdc_check_flags() calls _kdc_audit_addreason() */ + + ret = _kdc_pac_generate(r->context, + s4u_client, + r->server, + NULL, + KRB5_PAC_WAS_GIVEN_IMPLICITLY, + &s4u_pac); + if (ret) { + kdc_log(r->context, r->config, 4, "PAC generation failed for -- %s", s4ucname); + goto out; + } + + /* + * Check that service doing the impersonating is + * requesting a ticket to it-self. + */ + ret = _kdc_check_client_matches_target_service(r->context, + r->config, + r->clientdb, + r->client, + r->server, + r->server_princ); + if (ret) { + kdc_log(r->context, r->config, 4, "S4U2Self: %s is not allowed " + "to impersonate to service " + "(tried for user %s to service %s)", + r->cname, s4ucname, r->sname); + goto out; + } + + ret = krb5_copy_principal(r->context, s4u_client->entry.principal, + &s4u_canon_client_name); + if (ret) + goto out; + + /* + * If the service isn't trusted for authentication to + * delegation or if the impersonate client is disallowed + * forwardable, remove the forwardable flag. + */ + if (r->client->entry.flags.trusted_for_delegation && + s4u_client->entry.flags.forwardable) { + str = "[forwardable]"; + } else { + b->kdc_options.forwardable = 0; + str = ""; + } + kdc_log(r->context, r->config, 4, "s4u2self %s impersonating %s to " + "service %s %s", r->cname, s4ucname, r->sname, str); + + /* + * Replace all client information in the request with the + * impersonated client. (The audit entry containing the original + * client name will have been created before this point.) + */ + update_client_names(r, &s4ucname, &s4u_client_name, &s4u_client, + &s4u_canon_client_name, &s4u_pac); + +out: + if (s4u_client) + _kdc_free_ent(r->context, s4u_client); + krb5_free_principal(r->context, s4u_client_name); + krb5_xfree(s4ucname); + krb5_free_principal(r->context, s4u_canon_client_name); + krb5_pac_free(r->context, s4u_pac); + + free_PA_S4U2Self(&self); + + return ret; +} + +/* + * Validate a constrained delegation (S4U2Proxy) request. If present + * and successfully validated then the client in the request structure + * will be replaced with the client from the evidence ticket. + */ + +static krb5_error_code +validate_constrained_delegation(astgs_request_t r) +{ + krb5_error_code ret; + KDC_REQ_BODY *b = &r->req.req_body; + int flags = HDB_F_FOR_TGS_REQ; + krb5_principal s4u_client_name = NULL, s4u_server_name = NULL; + krb5_principal s4u_canon_client_name = NULL; + krb5_pac s4u_pac = NULL; + uint64_t s4u_pac_attributes; + char *s4ucname = NULL, *s4usname = NULL; + EncTicketPart evidence_tkt; + hdb_entry_ex *s4u_client = NULL; + krb5_boolean ad_kdc_issued = FALSE; + Key *clientkey; + Ticket *t; + krb5_const_realm local_realm; + + if (r->client == NULL + || b->additional_tickets == NULL + || b->additional_tickets->len == 0 + || b->kdc_options.cname_in_addl_tkt == 0 + || b->kdc_options.enc_tkt_in_skey) + return 0; + + memset(&evidence_tkt, 0, sizeof(evidence_tkt)); + local_realm = + krb5_principal_get_comp_string(r->context, r->krbtgt->entry.principal, 1); + + /* + * We require that the service's TGT has a PAC; this will have been + * validated prior to this function being called. + */ + if (r->pac == NULL) { + ret = KRB5KDC_ERR_BADOPTION; + _kdc_audit_addreason((kdc_request_t)r, "Missing PAC"); + kdc_log(r->context, r->config, 4, + "Constrained delegation without PAC, %s/%s", + r->cname, r->sname); + goto out; + } + + t = &b->additional_tickets->val[0]; + + ret = hdb_enctype2key(r->context, &r->client->entry, + hdb_kvno2keys(r->context, &r->client->entry, + t->enc_part.kvno ? * t->enc_part.kvno : 0), + t->enc_part.etype, &clientkey); + if (ret) { + ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ + goto out; + } + + ret = krb5_decrypt_ticket(r->context, t, &clientkey->key, &evidence_tkt, 0); + if (ret) { + _kdc_audit_addreason((kdc_request_t)r, + "Failed to decrypt constrained delegation ticket"); + kdc_log(r->context, r->config, 4, + "failed to decrypt ticket for " + "constrained delegation from %s to %s ", r->cname, r->sname); + goto out; + } + + ret = _krb5_principalname2krb5_principal(r->context, + &s4u_client_name, + evidence_tkt.cname, + evidence_tkt.crealm); + if (ret) + goto out; + + ret = krb5_unparse_name(r->context, s4u_client_name, &s4ucname); + if (ret) + goto out; + + _kdc_audit_addkv((kdc_request_t)r, 0, "impersonatee", "%s", s4ucname); + + ret = _krb5_principalname2krb5_principal(r->context, + &s4u_server_name, + t->sname, + t->realm); + if (ret) + goto out; + + ret = krb5_unparse_name(r->context, s4u_server_name, &s4usname); + if (ret) + goto out; + + /* check that ticket is valid */ + if (evidence_tkt.flags.forwardable == 0) { + _kdc_audit_addreason((kdc_request_t)r, + "Missing forwardable flag on ticket for constrained delegation"); + kdc_log(r->context, r->config, 4, + "Missing forwardable flag on ticket for " + "constrained delegation from %s (%s) as %s to %s ", + r->cname, s4usname, s4ucname, r->sname); + ret = KRB5KDC_ERR_BADOPTION; + goto out; + } + + ret = check_constrained_delegation(r->context, r->config, r->clientdb, + r->client, r->server, r->server_princ); + if (ret) { + _kdc_audit_addreason((kdc_request_t)r, + "Constrained delegation not allowed"); + kdc_log(r->context, r->config, 4, + "constrained delegation from %s (%s) as %s to %s not allowed", + r->cname, s4usname, s4ucname, r->sname); + goto out; + } + + ret = _kdc_verify_flags(r->context, r->config, &evidence_tkt, s4ucname); + if (ret) { + _kdc_audit_addreason((kdc_request_t)r, + "Constrained delegation ticket expired or invalid"); + goto out; + } + + /* Try lookup the delegated client in DB */ + ret = _kdc_db_fetch_client(r->context, r->config, flags, + s4u_client_name, s4ucname, local_realm, + NULL, &s4u_client); + if (ret) + goto out; + + if (s4u_client != NULL) { + ret = kdc_check_flags(r, FALSE, s4u_client, r->server); + if (ret) + goto out; + } + + /* + * TODO: pass in t->sname and t->realm and build + * a S4U_DELEGATION_INFO blob to the PAC. + */ + ret = _kdc_check_pac(r->context, r->config, s4u_client_name, s4u_server_name, + s4u_client, r->server, r->krbtgt, r->client, + &clientkey->key, &r->ticket_key->key, &evidence_tkt, + &ad_kdc_issued, &s4u_pac, + &s4u_canon_client_name, &s4u_pac_attributes); + if (ret) { + const char *msg = krb5_get_error_message(r->context, ret); + _kdc_audit_addreason((kdc_request_t)r, + "Constrained delegation ticket PAC check failed"); + kdc_log(r->context, r->config, 4, + "Verify delegated PAC failed to %s for client" + "%s (%s) as %s from %s with %s", + r->sname, r->cname, s4usname, s4ucname, r->from, msg); + krb5_free_error_message(r->context, msg); + goto out; + } + + if (s4u_pac == NULL || !ad_kdc_issued) { + ret = KRB5KDC_ERR_BADOPTION; + kdc_log(r->context, r->config, 4, + "Ticket not signed with PAC; service %s failed for " + "for delegation to %s for client %s (%s) from %s; (%s).", + r->sname, s4ucname, s4usname, r->cname, r->from, + s4u_pac ? "Ticket unsigned" : "No PAC"); + _kdc_audit_addreason((kdc_request_t)r, + "Constrained delegation ticket not signed"); + goto out; + } + + /* + * If the evidence ticket PAC didn't include PAC_UPN_DNS_INFO with + * the canonical client name, but the user is local to our KDC, we + * can insert the canonical client name ourselves. + */ + if (s4u_canon_client_name == NULL && s4u_client != NULL) { + ret = krb5_copy_principal(r->context, s4u_client->entry.principal, + &s4u_canon_client_name); + if (ret) + goto out; + } + + kdc_log(r->context, r->config, 4, "constrained delegation for %s " + "from %s (%s) to %s", s4ucname, r->cname, s4usname, r->sname); + + /* + * Replace all client information in the request with the + * impersonated client. (The audit entry containing the original + * client name will have been created before this point.) + */ + update_client_names(r, &s4ucname, &s4u_client_name, &s4u_client, + &s4u_canon_client_name, &s4u_pac); + r->pac_attributes = s4u_pac_attributes; + +out: + if (s4u_client) + _kdc_free_ent(r->context, s4u_client); + krb5_free_principal(r->context, s4u_client_name); + krb5_xfree(s4ucname); + krb5_free_principal(r->context, s4u_server_name); + krb5_xfree(s4usname); + krb5_free_principal(r->context, s4u_canon_client_name); + krb5_pac_free(r->context, s4u_pac); + + free_EncTicketPart(&evidence_tkt); + + return ret; +} + +/* + * + */ + +krb5_error_code +_kdc_validate_services_for_user(astgs_request_t r) +{ + krb5_error_code ret; + + ret = validate_protocol_transition(r); + if (ret == 0) + ret = validate_constrained_delegation(r); + + return ret; +}