diff options
| author | Sam Hartman <hartmans@mit.edu> | 2009-01-03 23:19:42 +0000 |
|---|---|---|
| committer | Sam Hartman <hartmans@mit.edu> | 2009-01-03 23:19:42 +0000 |
| commit | 0ba5ccd7bb3ea15e44a87f84ca6feed8890f657d (patch) | |
| tree | 2049c9c2cb135fe36b14c0a171711259258d18ec /src/kdc/kdc_util.c | |
| parent | ff0a6514c9f4230938c29922d69cbd4e83691adf (diff) | |
| download | krb5-0ba5ccd7bb3ea15e44a87f84ca6feed8890f657d.tar.gz krb5-0ba5ccd7bb3ea15e44a87f84ca6feed8890f657d.tar.xz krb5-0ba5ccd7bb3ea15e44a87f84ca6feed8890f657d.zip | |
Merge mskrb-integ onto trunk
The mskrb-integ branch includes support for the following projects:
Projects/Aliases
* Projects/PAC and principal APIs
* Projects/AEAD encryption API
* Projects/GSSAPI DCE
* Projects/RFC 3244
In addition, it includes support for enctype negotiation, and a variety of GSS-API extensions.
In the KDC it includes support for protocol transition, constrained delegation
and a new authorization data interface.
The old authorization data interface is also supported.
This commit merges the mskrb-integ branch on to the trunk.
Additional review and testing is required.
Merge commit 'mskrb-integ' into trunk
ticket: new
status: open
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@21690 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src/kdc/kdc_util.c')
| -rw-r--r-- | src/kdc/kdc_util.c | 732 |
1 files changed, 671 insertions, 61 deletions
diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c index 4068ec622..a3628cf91 100644 --- a/src/kdc/kdc_util.c +++ b/src/kdc/kdc_util.c @@ -26,6 +26,33 @@ * * Utility functions for the KDC implementation. */ +/* + * Copyright (c) 2006-2008, Novell, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * 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. + * * The copyright holder's name is not used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 "k5-int.h" #include "kdc_util.h" @@ -135,19 +162,22 @@ concat_authorization_data(krb5_authdata **first, krb5_authdata **second, } krb5_boolean -realm_compare(krb5_principal princ1, krb5_principal princ2) +realm_compare(krb5_const_principal princ1, krb5_const_principal princ2) { - krb5_data *realm1 = krb5_princ_realm(kdc_context, princ1); - krb5_data *realm2 = krb5_princ_realm(kdc_context, princ2); + return krb5_realm_compare(kdc_context, princ1, princ2); +} - return data_eq(*realm1, *realm2); +krb5_boolean +is_local_principal(krb5_const_principal princ1) +{ + return krb5_realm_compare(kdc_context, princ1, tgs_server); } /* * Returns TRUE if the kerberos principal is the name of a Kerberos ticket * service. */ -krb5_boolean krb5_is_tgs_principal(krb5_principal principal) +krb5_boolean krb5_is_tgs_principal(krb5_const_principal principal) { if ((krb5_princ_size(kdc_context, principal) > 0) && data_eq_string (*krb5_princ_component(kdc_context, principal, 0), @@ -186,12 +216,29 @@ comp_cksum(krb5_context kcontext, krb5_data *source, krb5_ticket *ticket, return(0); } +krb5_pa_data * +find_pa_data(krb5_pa_data **padata, krb5_preauthtype pa_type) +{ + krb5_pa_data **tmppa; + + if (padata == NULL) + return NULL; + + for (tmppa = padata; *tmppa != NULL; tmppa++) { + if ((*tmppa)->pa_type == pa_type) + break; + } + + return *tmppa; +} + krb5_error_code kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, krb5_data *pkt, krb5_ticket **ticket, + krb5_db_entry *krbtgt, int *nprincs, krb5_keyblock **subkey) { - krb5_pa_data ** tmppa; + krb5_pa_data * tmppa; krb5_ap_req * apreq; krb5_error_code retval; krb5_data scratch1; @@ -200,23 +247,20 @@ kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, krb5_auth_context auth_context = NULL; krb5_authenticator * authenticator = NULL; krb5_checksum * his_cksum = NULL; -/* krb5_keyblock * key = NULL;*/ -/* krb5_kvno kvno = 0;*/ + krb5_keyblock * key = NULL; + krb5_kvno kvno = 0; - if (!request->padata) - return KRB5KDC_ERR_PADATA_TYPE_NOSUPP; - for (tmppa = request->padata; *tmppa; tmppa++) { - if ((*tmppa)->pa_type == KRB5_PADATA_AP_REQ) - break; - } - if (!*tmppa) /* cannot find any AP_REQ */ + *nprincs = 0; + + tmppa = find_pa_data(request->padata, KRB5_PADATA_AP_REQ); + if (!tmppa) return KRB5KDC_ERR_PADATA_TYPE_NOSUPP; - scratch1.length = (*tmppa)->length; - scratch1.data = (char *)(*tmppa)->contents; + scratch1.length = tmppa->length; + scratch1.data = (char *)tmppa->contents; if ((retval = decode_krb5_ap_req(&scratch1, &apreq))) return retval; - + if (isflagset(apreq->ap_options, AP_OPTS_USE_SESSION_KEY) || isflagset(apreq->ap_options, AP_OPTS_MUTUAL_REQUIRED)) { krb5_klog_syslog(LOG_INFO, "TGS_REQ: SESSION KEY or MUTUAL"); @@ -234,9 +278,7 @@ kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, we set a flag here for checking below. */ - if (!data_eq(*krb5_princ_realm(kdc_context, apreq->ticket->server), - *krb5_princ_realm(kdc_context, tgs_server))) - foreign_server = TRUE; + foreign_server = !is_local_principal(apreq->ticket->server); if ((retval = krb5_auth_con_init(kdc_context, &auth_context))) goto cleanup; @@ -250,21 +292,15 @@ kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, goto cleanup_auth_context; #endif -/* - if ((retval = kdc_get_server_key(apreq->ticket, &key, &kvno))) + if ((retval = kdc_get_server_key(apreq->ticket, 0, krbtgt, nprincs, &key, &kvno))) goto cleanup_auth_context; -*/ - /* - * XXX This is currently wrong but to fix it will require making a - * new keytab for groveling over the kdb. + * We do not use the KDB keytab because other parts of the TGS need the TGT key. */ -/* retval = krb5_auth_con_setuseruserkey(kdc_context, auth_context, key); krb5_free_keyblock(kdc_context, key); if (retval) goto cleanup_auth_context; -*/ if ((retval = krb5_rd_req_decoded_anyflag(kdc_context, &auth_context, apreq, apreq->ticket->server, @@ -322,11 +358,8 @@ kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, } /* make sure the client is of proper lineage (see above) */ - if (foreign_server) { - krb5_data *tkt_realm = krb5_princ_realm(kdc_context, - (*ticket)->enc_part2->client); - krb5_data *tgs_realm = krb5_princ_realm(kdc_context, tgs_server); - if (data_eq(*tkt_realm, *tgs_realm)) { + if (foreign_server && !find_pa_data(request->padata, KRB5_PADATA_FOR_USER)) { + if (is_local_principal((*ticket)->enc_part2->client)) { /* someone in a foreign realm claiming to be local */ krb5_klog_syslog(LOG_INFO, "PROCESS_TGS: failed lineage check"); retval = KRB5KDC_ERR_POLICY; @@ -374,39 +407,40 @@ cleanup: * much else. -- tlyu */ krb5_error_code -kdc_get_server_key(krb5_ticket *ticket, krb5_keyblock **key, krb5_kvno *kvno) +kdc_get_server_key(krb5_ticket *ticket, unsigned int flags, + krb5_db_entry *server, + int *nprincs, krb5_keyblock **key, krb5_kvno *kvno) { krb5_error_code retval; - krb5_db_entry server; krb5_boolean more; - int nprincs; krb5_key_data * server_key; - nprincs = 1; + *nprincs = 1; - if ((retval = get_principal(kdc_context, ticket->server, - &server, &nprincs, - &more))) { + retval = krb5_db_get_principal_ext(kdc_context, + ticket->server, + flags, + server, + nprincs, + &more); + if (retval) { return(retval); } if (more) { - krb5_db_free_principal(kdc_context, &server, nprincs); return(KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE); - } else if (nprincs != 1) { + } else if (*nprincs != 1) { char *sname; - krb5_db_free_principal(kdc_context, &server, nprincs); if (!krb5_unparse_name(kdc_context, ticket->server, &sname)) { - limit_string(sname); krb5_klog_syslog(LOG_ERR,"TGS_REQ: UNKNOWN SERVER: server='%s'", sname); free(sname); } return(KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN); } - retval = krb5_dbe_find_enctype(kdc_context, &server, + retval = krb5_dbe_find_enctype(kdc_context, server, ticket->enc_part.enctype, -1, - ticket->enc_part.kvno, &server_key); + (krb5_int32)ticket->enc_part.kvno, &server_key); if (retval) goto errout; if (!server_key) { @@ -418,14 +452,9 @@ kdc_get_server_key(krb5_ticket *ticket, krb5_keyblock **key, krb5_kvno *kvno) retval = krb5_dbekd_decrypt_key_data(kdc_context, &master_keyblock, server_key, *key, NULL); - if (retval) { - free(*key); - *key = NULL; - } } else retval = ENOMEM; errout: - krb5_db_free_principal(kdc_context, &server, nprincs); return retval; } @@ -711,7 +740,7 @@ add_to_transited(krb5_data *tgt_trans, krb5_data *new_trans, /* Note that the second test here is an unsigned comparison, so the first half (or a cast) is also required. */ - assert(nlst < 0 || nlst < sizeof(next)); + assert(nlst < 0 || nlst < (int)sizeof(next)); if ((nlst < 0 || next[nlst] != '.') && (next[0] != '/') && (pl = subrealm(exp, realm))) { @@ -900,7 +929,21 @@ validate_as_request(register krb5_kdc_req *request, krb5_db_entry client, *status = "POSTDATE NOT ALLOWED"; return(KDC_ERR_CANNOT_POSTDATE); } - + + /* + * A Windows KDC will return KDC_ERR_PREAUTH_REQUIRED instead of + * KDC_ERR_POLICY in the following case: + * + * - KDC_OPT_FORWARDABLE is set in KDCOptions but local + * policy has KRB5_KDB_DISALLOW_FORWARDABLE set for the + * client, and; + * - KRB5_KDB_REQUIRES_PRE_AUTH is set for the client but + * preauthentication data is absent in the request. + * + * Hence, this check most be done after the check for preauth + * data, and is now performed by validate_forwardable(). + */ +#if 0 /* Client and server must allow forwardable tickets */ if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE) && (isflagset(client.attributes, KRB5_KDB_DISALLOW_FORWARDABLE) || @@ -908,6 +951,7 @@ validate_as_request(register krb5_kdc_req *request, krb5_db_entry client, *status = "FORWARDABLE NOT ALLOWED"; return(KDC_ERR_POLICY); } +#endif /* Client and server must allow renewable tickets */ if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE) && @@ -928,7 +972,7 @@ validate_as_request(register krb5_kdc_req *request, krb5_db_entry client, /* Check to see if client is locked out */ if (isflagset(client.attributes, KRB5_KDB_DISALLOW_ALL_TIX)) { *status = "CLIENT LOCKED OUT"; - return(KDC_ERR_C_PRINCIPAL_UNKNOWN); + return(KDC_ERR_CLIENT_REVOKED); } /* Check to see if server is locked out */ @@ -940,13 +984,13 @@ validate_as_request(register krb5_kdc_req *request, krb5_db_entry client, /* Check to see if server is allowed to be a service */ if (isflagset(server.attributes, KRB5_KDB_DISALLOW_SVR)) { *status = "SERVICE NOT ALLOWED"; - return(KDC_ERR_S_PRINCIPAL_UNKNOWN); + return(KDC_ERR_MUST_USE_USER2USER); } /* * Check against local policy */ - errcode = against_local_policy_as(request, server, client, + errcode = against_local_policy_as(request, client, server, kdc_time, status); if (errcode) return errcode; @@ -954,6 +998,21 @@ validate_as_request(register krb5_kdc_req *request, krb5_db_entry client, return 0; } +int +validate_forwardable(krb5_kdc_req *request, krb5_db_entry client, + krb5_db_entry server, krb5_timestamp kdc_time, + const char **status) +{ + *status = NULL; + if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE) && + (isflagset(client.attributes, KRB5_KDB_DISALLOW_FORWARDABLE) || + isflagset(server.attributes, KRB5_KDB_DISALLOW_FORWARDABLE))) { + *status = "FORWARDABLE NOT ALLOWED"; + return(KDC_ERR_POLICY); + } else + return 0; +} + #define ASN1_ID_CLASS (0xc0) #define ASN1_ID_TYPE (0x20) #define ASN1_ID_TAG (0x1f) @@ -1061,7 +1120,7 @@ fetch_asn1_field(unsigned char *astream, unsigned int level, lastlevel = tag; if (levels == level) { /* in our context-dependent class, is this the one we're looking for ? */ - if (tag == field) { + if (tag == (int)field) { /* return length and data */ astream++; savelen = *astream; @@ -1108,8 +1167,7 @@ fetch_asn1_field(unsigned char *astream, unsigned int level, KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED | \ KDC_OPT_RENEWABLE | KDC_OPT_RENEWABLE_OK | \ KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_RENEW | \ - KDC_OPT_VALIDATE) - + KDC_OPT_VALIDATE | KDC_OPT_CANONICALIZE | KDC_OPT_CNAME_IN_ADDL_TKT) #define NO_TGT_OPTION (KDC_OPT_FORWARDED | KDC_OPT_PROXY | KDC_OPT_RENEW | \ KDC_OPT_VALIDATE) @@ -1277,7 +1335,7 @@ validate_tgs_request(register krb5_kdc_req *request, krb5_db_entry server, /* Server must be allowed to be a service */ if (isflagset(server.attributes, KRB5_KDB_DISALLOW_SVR)) { *status = "SERVER NOT ALLOWED"; - return(KDC_ERR_S_PRINCIPAL_UNKNOWN); + return(KDC_ERR_MUST_USE_USER2USER); } /* Check the hot list */ @@ -1323,6 +1381,14 @@ validate_tgs_request(register krb5_kdc_req *request, krb5_db_entry server, } st_idx++; } + if (isflagset(request->kdc_options, KDC_OPT_CNAME_IN_ADDL_TKT)) { + if (!request->second_ticket || + !request->second_ticket[st_idx]) { + *status = "NO_2ND_TKT"; + return(KDC_ERR_BADOPTION); + } + st_idx++; + } /* Check for hardware preauthentication */ if (isflagset(server.attributes, KRB5_KDB_REQUIRES_HW_AUTH) && @@ -1603,6 +1669,549 @@ get_principal (krb5_context kcontext, more); } + +krb5_error_code +sign_db_authdata (krb5_context context, + unsigned int flags, + krb5_const_principal client_princ, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_db_entry *krbtgt, + krb5_keyblock *client_key, + krb5_keyblock *server_key, + krb5_timestamp authtime, + krb5_authdata **tgs_authdata, + krb5_authdata ***ret_authdata, + krb5_db_entry *ad_entry, + int *ad_nprincs) +{ + krb5_error_code code; + kdb_sign_auth_data_req req; + kdb_sign_auth_data_rep rep; + krb5_data req_data; + krb5_data rep_data; + + *ret_authdata = NULL; + if (ad_entry != NULL) { + assert(ad_nprincs != NULL); + memset(ad_entry, 0, sizeof(*ad_entry)); + *ad_nprincs = 0; + } + + memset(&req, 0, sizeof(req)); + memset(&rep, 0, sizeof(rep)); + + req.flags = flags; + req.client_princ = client_princ; + req.client = client; + req.server = server; + req.krbtgt = krbtgt; + req.client_key = client_key; + req.server_key = server_key; + req.authtime = authtime; + req.auth_data = tgs_authdata; + + rep.entry = ad_entry; + rep.nprincs = 0; + + req_data.data = (void *)&req; + req_data.length = sizeof(req); + + rep_data.data = (void *)&rep; + rep_data.length = sizeof(rep); + + code = krb5_db_invoke(context, + KRB5_KDB_METHOD_SIGN_AUTH_DATA, + &req_data, + &rep_data); + + *ret_authdata = rep.auth_data; + *ad_nprincs = rep.nprincs; + + return code; +} + +static krb5_error_code +verify_s4u2self_checksum(krb5_context context, + krb5_keyblock *key, + krb5_pa_for_user *req) +{ + krb5_error_code code; + int i; + krb5_int32 name_type; + char *p; + krb5_data data; + krb5_boolean valid = FALSE; + + if (!krb5_c_is_keyed_cksum(req->cksum.checksum_type)) { + return KRB5KRB_AP_ERR_INAPP_CKSUM; + } + + /* + * Checksum is over name type and string components of + * client principal name and auth_package. + */ + data.length = 4; + for (i = 0; i < krb5_princ_size(context, req->user); i++) { + data.length += krb5_princ_component(context, req->user, i)->length; + } + data.length += krb5_princ_realm(context, req->user)->length; + data.length += req->auth_package.length; + + p = data.data = malloc(data.length); + if (data.data == NULL) { + return ENOMEM; + } + + name_type = krb5_princ_type(context, req->user); + p[0] = (name_type >> 0 ) & 0xFF; + p[1] = (name_type >> 8 ) & 0xFF; + p[2] = (name_type >> 16) & 0xFF; + p[3] = (name_type >> 24) & 0xFF; + p += 4; + + for (i = 0; i < krb5_princ_size(context, req->user); i++) { + memcpy(p, krb5_princ_component(context, req->user, i)->data, + krb5_princ_component(context, req->user, i)->length); + p += krb5_princ_component(context, req->user, i)->length; + } + + memcpy(p, krb5_princ_realm(context, req->user)->data, + krb5_princ_realm(context, req->user)->length); + p += krb5_princ_realm(context, req->user)->length; + + memcpy(p, req->auth_package.data, req->auth_package.length); + p += req->auth_package.length; + + code = krb5_c_verify_checksum(context, + key, + KRB5_KEYUSAGE_APP_DATA_CKSUM, + &data, + &req->cksum, + &valid); + + if (code == 0 && valid == FALSE) + code = KRB5KRB_AP_ERR_BAD_INTEGRITY; + + free(data.data); + + return code; +} + +/* + * Protocol transition validation code based on AS-REQ + * validation code + */ +static int +validate_s4u2self_request(krb5_kdc_req *request, + const krb5_db_entry *client, + krb5_timestamp kdc_time, + const char **status) +{ + int errcode; + krb5_db_entry server = { 0 }; + + /* The client's password must not be expired, unless the server is + a KRB5_KDC_PWCHANGE_SERVICE. */ + if (client->pw_expiration && client->pw_expiration < kdc_time) { + *status = "CLIENT KEY EXPIRED"; + return KDC_ERR_KEY_EXP; + } + + /* The client must not be expired */ + if (client->expiration && client->expiration < kdc_time) { + *status = "CLIENT EXPIRED"; + return KDC_ERR_NAME_EXP; + } + + /* + * If the client requires password changing, then return an + * error; S4U2Self cannot be used to change a password. + */ + if (isflagset(client->attributes, KRB5_KDB_REQUIRES_PWCHANGE)) { + *status = "REQUIRED PWCHANGE"; + return KDC_ERR_KEY_EXP; + } + + /* Check to see if client is locked out */ + if (isflagset(client->attributes, KRB5_KDB_DISALLOW_ALL_TIX)) { + *status = "CLIENT LOCKED OUT"; + return KDC_ERR_C_PRINCIPAL_UNKNOWN; + } + + /* + * Check against local policy + */ + errcode = against_local_policy_as(request, *client, server, + kdc_time, status); + if (errcode) + return errcode; + + return 0; +} + +/* + * Protocol transition (S4U2Self) + */ +krb5_error_code +kdc_process_s4u2self_req(krb5_context context, + krb5_kdc_req *request, + krb5_const_principal client_princ, + const krb5_db_entry *server, + krb5_keyblock *subkey, + krb5_timestamp kdc_time, + krb5_pa_for_user **for_user, + krb5_db_entry *princ, + int *nprincs, + const char **status) +{ + krb5_error_code code; + krb5_pa_data **pa_data; + krb5_data req_data; + krb5_boolean more; + + *nprincs = 0; + memset(princ, 0, sizeof(*princ)); + + if (request->padata == NULL) { + return 0; + } + + for (pa_data = request->padata; *pa_data != NULL; pa_data++) { + if ((*pa_data)->pa_type == KRB5_PADATA_FOR_USER) + break; + } + if (*pa_data == NULL) { + return 0; + } + +#if 0 + /* + * Ignore request if the server principal is a TGS, not so much + * to avoid unconstrained tickets being issued (as that would + * require knowing the TGS key anyway) but so that we do not + * block the server referral path. + */ + if (krb5_is_tgs_principal(server->princ)) { + return 0; + } +#endif + + *status = "PROCESS_S4U2SELF_REQUEST"; + + req_data.length = (*pa_data)->length; + req_data.data = (char *)(*pa_data)->contents; + + code = decode_krb5_pa_for_user(&req_data, for_user); + if (code) { + return code; + } + + if (krb5_princ_type(context, (*for_user)->user) != + KRB5_NT_ENTERPRISE_PRINCIPAL) { + *status = "INVALID_S4U2SELF_REQUEST"; + return KRB5KDC_ERR_POLICY; + } + + code = verify_s4u2self_checksum(context, subkey, *for_user); + if (code) { + *status = "INVALID_S4U2SELF_CHECKSUM"; + krb5_free_pa_for_user(kdc_context, *for_user); + *for_user = NULL; + return code; + } + if (!krb5_principal_compare_flags(context, request->server, client_princ, + KRB5_PRINCIPAL_COMPARE_ENTERPRISE)) { + *status = "INVALID_S4U2SELF_REQUEST"; + return KRB5KDC_ERR_POLICY; + } + + /* + * Protocol transition is mutually exclusive with renew/forward/etc + * as well as user-to-user and constrained delegation. + * + * We can assert from this check that the header ticket was a TGT, as + * that is validated previously in validate_tgs_request(). + */ + if (request->kdc_options & (NO_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_CNAME_IN_ADDL_TKT)) { + return KRB5KDC_ERR_BADOPTION; + } + + /* + * Do not attempt to lookup principals in foreign realms. + */ + if (is_local_principal((*for_user)->user)) { + *nprincs = 1; + code = krb5_db_get_principal_ext(kdc_context, + (*for_user)->user, + KRB5_KDB_FLAG_INCLUDE_PAC, + princ, nprincs, &more); + if (code) { + *status = "LOOKING_UP_S4U2SELF_PRINCIPAL"; + *nprincs = 0; + return code; /* caller can free for_user */ + } + + if (more) { + *status = "NON_UNIQUE_S4U2SELF_PRINCIPAL"; + return KRB5KDC_ERR_PRINCIPAL_NOT_UNIQUE; + } else if (*nprincs != 1) { + *status = "UNKNOWN_S4U2SELF_PRINCIPAL"; + return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + } + + code = validate_s4u2self_request(request, princ, kdc_time, status); + if (code) { + return code; + } + } + + *status = NULL; + + return 0; +} + +static krb5_boolean +check_constrained_delegation_acl(krb5_context context, + krb5_tl_data *tl_data, + krb5_const_principal spn) +{ + krb5_principal acl; + krb5_boolean ret; + + assert(tl_data->tl_data_contents[tl_data->tl_data_length] == '\0'); + + if (krb5_parse_name_flags(context, + (char *)tl_data->tl_data_contents, + KRB5_PRINCIPAL_PARSE_NO_REALM, + &acl) != 0) + return FALSE; + + ret = krb5_principal_compare_flags(context, acl, spn, KRB5_PRINCIPAL_COMPARE_IGNORE_REALM); + + krb5_free_principal(context, acl); + + return ret; +} + +static krb5_error_code +check_allowed_to_delegate_to(krb5_context context, + const krb5_db_entry *server, + krb5_const_principal proxy) +{ + krb5_tl_data *tl_data; + krb5_boolean allowed = FALSE; + + /* Can't get a TGT (otherwise it would be unconstrained delegation) */ + if (krb5_is_tgs_principal(proxy)) { + return KRB5KDC_ERR_POLICY; + } + + /* Must be in same realm -- ACLs are non-qualified SPNs */ + if (!krb5_realm_compare(kdc_context, server->princ, proxy)) { + return KRB5_IN_TKT_REALM_MISMATCH; /* XXX */ + } + + for (tl_data = server->tl_data; tl_data != NULL; tl_data = tl_data->tl_data_next) { + if (tl_data->tl_data_type == KRB5_TL_CONSTRAINED_DELEGATION_ACL) { + if (check_constrained_delegation_acl(context, tl_data, proxy)) { + allowed = TRUE; + break; + } + } + } + + if (allowed == FALSE) { + return KRB5KDC_ERR_POLICY; + } + + return 0; +} + +krb5_error_code +kdc_process_s4u2proxy_req(krb5_context context, + krb5_kdc_req *request, + const krb5_enc_tkt_part *t2enc, + const krb5_db_entry *server, + krb5_const_principal server_princ, + krb5_const_principal proxy_princ, + const char **status) +{ + krb5_error_code errcode; + + /* + * Constrained delegation is mutually exclusive with renew/forward/etc. + * We can assert from this check that the header ticket was a TGT, as + * that is validated previously in validate_tgs_request(). + */ + if (request->kdc_options & (NO_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY)) { + return KRB5KDC_ERR_BADOPTION; + } + + /* Ensure that evidence ticket server matches TGT client */ + if (!krb5_principal_compare(kdc_context, + server->princ, /* after canon */ + server_princ)) { + return KRB5KDC_ERR_SERVER_NOMATCH; + } + + if (!isflagset(t2enc->flags, TKT_FLG_FORWARDABLE)) { + *status = "EVIDENCE_TKT_NOT_FORWARDABLE"; + return KRB5_TKT_NOT_FORWARDABLE; + } + + /* Backend policy check */ + errcode = check_allowed_to_delegate_to(kdc_context, + server, proxy_princ); + if (errcode) { + *status = "NOT_ALLOWED_TO_DELEGATE"; + return errcode; + } + + return 0; +} + +krb5_error_code +kdc_check_transited_list(krb5_context context, + const krb5_data *trans, + const krb5_data *realm1, + const krb5_data *realm2) +{ + krb5_error_code code; + kdb_check_transited_realms_req req; + krb5_data req_data; + krb5_data rep_data; + + /* First check using krb5.conf */ + code = krb5_check_transited_list(kdc_context, trans, realm1, realm2); + if (code) + return code; + + memset(&req, 0, sizeof(req)); + + req.tr_contents = trans; + req.client_realm = realm1; + req.server_realm = realm2; + + req_data.data = (void *)&req; + req_data.length = sizeof(req); + + rep_data.data = NULL; + rep_data.length = 0; + + code = krb5_db_invoke(context, + KRB5_KDB_METHOD_CHECK_TRANSITED_REALMS, + &req_data, + &rep_data); + if (code == KRB5_KDB_DBTYPE_NOSUP) { + code = 0; + } + + assert(rep_data.length == 0); + + return code; +} + +krb5_error_code +audit_as_request(krb5_kdc_req *request, + krb5_db_entry *client, + krb5_db_entry *server, + krb5_timestamp authtime, + krb5_error_code errcode) +{ + krb5_error_code code; + kdb_audit_as_req req; + krb5_data req_data; + krb5_data rep_data; + + memset(&req, 0, sizeof(req)); + + req.request = request; + req.client = client; + req.server = server; + req.authtime = authtime; + req.error_code = errcode; + + req_data.data = (void *)&req; + req_data.length = sizeof(req); + + rep_data.data = NULL; + rep_data.length = 0; + + code = krb5_db_invoke(kdc_context, + KRB5_KDB_METHOD_AUDIT_AS, + &req_data, + &rep_data); + if (code == KRB5_KDB_DBTYPE_NOSUP) { + return 0; + } + + assert(rep_data.length == 0); + + return code; +} + +krb5_error_code +audit_tgs_request(krb5_kdc_req *request, + krb5_const_principal client, + krb5_db_entry *server, + krb5_timestamp authtime, + krb5_error_code errcode) +{ + krb5_error_code code; + kdb_audit_tgs_req req; + krb5_data req_data; + krb5_data rep_data; + + memset(&req, 0, sizeof(req)); + + req.request = request; + req.client = client; + req.server = server; + req.authtime = authtime; + req.error_code = errcode; + + req_data.data = (void *)&req; + req_data.length = sizeof(req); + + rep_data.data = NULL; + rep_data.length = 0; + + code = krb5_db_invoke(kdc_context, + KRB5_KDB_METHOD_AUDIT_TGS, + &req_data, + &rep_data); + if (code == KRB5_KDB_DBTYPE_NOSUP) { + return 0; + } + + assert(rep_data.length == 0); + + return code; +} + +krb5_error_code +validate_transit_path(krb5_context context, + krb5_const_principal client, + krb5_db_entry *server, + krb5_db_entry *krbtgt) +{ + /* Incoming */ + if (isflagset(server->attributes, KRB5_KDB_TRUST_NON_TRANSITIVE)) { + return KRB5KDC_ERR_PATH_NOT_ACCEPTED; + } + + /* Outgoing */ + if (isflagset(krbtgt->attributes, KRB5_KDB_TRUST_NON_TRANSITIVE) && + (!krb5_principal_compare(context, server->princ, krbtgt->princ) || + !krb5_realm_compare(context, client, krbtgt->princ))) { + return KRB5KDC_ERR_PATH_NOT_ACCEPTED; + } + + return 0; +} + + /* Main logging routines for ticket requests. There are a few simple cases -- unparseable requests mainly -- @@ -1722,3 +2331,4 @@ log_tgs_alt_tgt(krb5_principal p) } /* OpenSolaris: audit_krb5kdc_tgs_req_alt_tgt(...) */ } + |
