diff options
author | Greg Hudson <ghudson@mit.edu> | 2020-10-05 12:01:32 -0400 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2020-10-29 14:18:41 -0400 |
commit | 3b163eed1cf1f55dd4a7bc6d6fffc34f55695b00 (patch) | |
tree | 1bbe6b44e6e78c7c973950fd123f2681e8269221 /src | |
parent | 6dbf0b755bb1d8254c881cf21717441cc2e880b8 (diff) | |
download | krb5-3b163eed1cf1f55dd4a7bc6d6fffc34f55695b00.zip krb5-3b163eed1cf1f55dd4a7bc6d6fffc34f55695b00.tar.gz krb5-3b163eed1cf1f55dd4a7bc6d6fffc34f55695b00.tar.bz2 |
Move more KDC checks to validate_tgs_request()
Move the following validity checks:
* the INVALID ticket flag check from kdc_process_tgs_req()
* the lineage check from process_tgs_req()
* the user-to-user second ticket client check from process_tgs_req()
* all S4U2Self validity checks from kdc_process_s4u2self_req()
* S4U2Proxy validity checks (but not KDB authorization checks) from
kdc_process_s4u2proxy_req()
In process_tgs_req(), call validate_tgs_request() after
kdc_process_s4u2self_req() and decrypt_2ndtkt() so that their outputs
can be used as validation inputs. Add stkt and is_crossrealm locals
for convenience, and remove st_idx.
There are some minor behavior changes:
* For invalid S4U2Self request options, the status string is changed
from "INVALID AS OPTIONS" to "INVALID S4U2SELF OPTIONS".
* For a header ticket with the INVALID flag, the reply code is changed
to KRB_AP_ERR_TKT_NYV (as specified in RFC 4120) and the status
string to "TICKET NOT VALID".
* For a lineage check failure, the explicit KDC log is removed, and
the status string is changed to "INVALID LINEAGE".
* For a user-to-user second ticket client mismatch, the explicit audit
call is removed, and the log message does not include the second
ticket client.
* e_data returned from the KDB check_policy_as() method will be
included in the error for S4U2Self requests.
ticket: 8953 (new)
Diffstat (limited to 'src')
-rw-r--r-- | src/kdc/do_tgs_req.c | 137 | ||||
-rw-r--r-- | src/kdc/kdc_util.c | 141 | ||||
-rw-r--r-- | src/kdc/kdc_util.h | 17 | ||||
-rw-r--r-- | src/kdc/tgs_policy.c | 225 |
4 files changed, 275 insertions, 245 deletions
diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c index 7d41dc8..6d244ff 100644 --- a/src/kdc/do_tgs_req.c +++ b/src/kdc/do_tgs_req.c @@ -78,8 +78,8 @@ prepare_error_tgs(struct kdc_request_state *, krb5_kdc_req *,krb5_ticket *,int, krb5_principal,krb5_data **,const char *, krb5_pa_data **); static krb5_error_code -decrypt_2ndtkt(kdc_realm_t *, krb5_kdc_req *, krb5_flags, krb5_db_entry **, - krb5_keyblock **, const char **); +decrypt_2ndtkt(kdc_realm_t *, krb5_kdc_req *, krb5_flags, const krb5_ticket **, + krb5_db_entry **, krb5_keyblock **, const char **); static krb5_error_code gen_session_key(kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *, @@ -112,7 +112,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, krb5_kdc_rep reply; krb5_enc_kdc_rep_part reply_encpart; krb5_ticket ticket_reply, *header_ticket = 0; - int st_idx = 0; + const krb5_ticket *stkt = NULL; krb5_enc_tkt_part enc_tkt_reply; int newtransited = 0; krb5_error_code retval = 0; @@ -133,7 +133,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, krb5_pa_s4u_x509_user *s4u_x509_user = NULL; /* protocol transition request */ krb5_authdata **kdc_issued_auth_data = NULL; /* auth data issued by KDC */ unsigned int c_flags = 0, s_flags = 0; /* client/server KDB flags */ - krb5_boolean is_referral; + krb5_boolean is_referral, is_crossrealm; const char *emsg = NULL; krb5_kvno ticket_kvno = 0; struct kdc_request_state *state = NULL; @@ -258,48 +258,38 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, if ((errcode = krb5_timeofday(kdc_context, &kdc_time))) goto cleanup; - if ((retval = validate_tgs_request(kdc_active_realm, - request, server, header_ticket, - kdc_time, &status, &e_data))) { - if (retval == KDC_ERR_POLICY || retval == KDC_ERR_BADOPTION) - au_state->violation = PROT_CONSTRAINT; - errcode = retval + ERROR_TABLE_BASE_krb5; - goto cleanup; - } - - if (!data_eq(header_server->princ->realm, sprinc->realm)) + is_crossrealm = !data_eq(header_server->princ->realm, sprinc->realm); + if (is_crossrealm) setflag(c_flags, KRB5_KDB_FLAG_CROSS_REALM); if (is_referral) setflag(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL); /* Check for protocol transition */ - errcode = kdc_process_s4u2self_req(kdc_active_realm, - request, - header_enc_tkt->client, - c_flags, - server, - subkey, - header_enc_tkt->session, - kdc_time, - &s4u_x509_user, - &client, - &status); + errcode = kdc_process_s4u2self_req(kdc_active_realm, request, server, + subkey, header_enc_tkt->session, + &s4u_x509_user, &client, &status); if (s4u_x509_user != NULL || errcode != 0) { if (s4u_x509_user != NULL) au_state->s4u2self_user = s4u_x509_user->user_id.user; - if (errcode == KDC_ERR_POLICY || errcode == KDC_ERR_BADOPTION) - au_state->violation = PROT_CONSTRAINT; au_state->status = status; kau_s4u2self(kdc_context, errcode ? FALSE : TRUE, au_state); au_state->s4u2self_user = NULL; } - /* Aside from cross-realm S4U2Self requests, do not accept header tickets - * for local users issued by foreign realms. */ - if (s4u_x509_user == NULL && data_eq(cprinc->realm, sprinc->realm) && - isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM)) { - krb5_klog_syslog(LOG_INFO, _("PROCESS_TGS: failed lineage check")); - retval = KRB5KDC_ERR_POLICY; + /* For user-to-user and S4U2Proxy requests, decrypt the second ticket. */ + errcode = decrypt_2ndtkt(kdc_active_realm, request, c_flags, + &stkt, &stkt_server, &stkt_server_key, &status); + if (errcode) + goto cleanup; + + retval = validate_tgs_request(kdc_active_realm, request, server, + header_ticket, stkt, stkt_server, kdc_time, + s4u_x509_user, client, is_crossrealm, + is_referral, &status, &e_data); + if (retval) { + if (retval == KDC_ERR_POLICY || retval == KDC_ERR_BADOPTION) + au_state->violation = PROT_CONSTRAINT; + errcode = retval + ERROR_TABLE_BASE_krb5; goto cleanup; } @@ -332,20 +322,14 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, if (s4u_x509_user != NULL) setflag(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION); - /* Deal with user-to-user and constrained delegation */ - errcode = decrypt_2ndtkt(kdc_active_realm, request, c_flags, - &stkt_server, &stkt_server_key, &status); - if (errcode) - goto cleanup; - if (isflagset(request->kdc_options, KDC_OPT_CNAME_IN_ADDL_TKT)) { /* Do constrained delegation protocol and authorization checks. */ setflag(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION); errcode = kdc_process_s4u2proxy_req(kdc_active_realm, c_flags, request, - request->second_ticket[st_idx]->enc_part2, - local_tgt, &local_tgt_key, - stkt_server, stkt_server_key, + stkt->enc_part2, local_tgt, + &local_tgt_key, stkt_server, + stkt_server_key, header_ticket->enc_part2->client, server, request->server, ad_info, &stkt_ad_info, @@ -356,8 +340,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, else if (errcode) au_state->violation = LOCAL_POLICY; au_state->status = status; - retval = kau_make_tkt_id(kdc_context, request->second_ticket[st_idx], - &au_state->evid_tkt_id); + retval = kau_make_tkt_id(kdc_context, stkt, &au_state->evid_tkt_id); if (retval) { errcode = retval; goto cleanup; @@ -390,7 +373,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, */ if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) { - subject_tkt = request->second_ticket[st_idx]->enc_part2; + subject_tkt = stkt->enc_part2; subject_server = stkt_server; subject_key = stkt_server_key; } else { @@ -522,14 +505,12 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, } else if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) { /* kdc_process_s4u2proxy_req() only allows cross-realm requests if * stkt_authdata_client is set. */ - altcprinc = isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM) ? - stkt_authdata_client : subject_tkt->client; + altcprinc = is_crossrealm ? stkt_authdata_client : subject_tkt->client; } else { altcprinc = NULL; } if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) { - krb5_enc_tkt_part *t2enc = request->second_ticket[st_idx]->enc_part2; - encrypting_key = t2enc->session; + encrypting_key = stkt->enc_part2->session; } else { errcode = get_first_current_key(kdc_context, server, &server_keyblock); if (errcode) { @@ -579,7 +560,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, * implicitly part of the transited list and should not be explicitly * listed). */ - if (!isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM) || + if (!is_crossrealm || krb5_realm_compare(kdc_context, header_ticket->server, enc_tkt_reply.client)) { /* tgt issued by local realm or issued by realm of client */ @@ -643,32 +624,12 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt, ticket_reply.enc_part2 = &enc_tkt_reply; - /* - * If we are doing user-to-user authentication, then make sure - * that the client for the second ticket matches the request - * server, and then encrypt the ticket using the session key of - * the second ticket. - */ + /* If we are doing user-to-user authentication, encrypt the ticket using + * the session key of the second ticket. */ if (isflagset(request->kdc_options, KDC_OPT_ENC_TKT_IN_SKEY)) { - /* - * Make sure the client for the second ticket matches - * requested server. - */ - krb5_enc_tkt_part *t2enc = request->second_ticket[st_idx]->enc_part2; - krb5_principal client2 = t2enc->client; - if (!is_client_db_alias(kdc_context, server, client2)) { - altcprinc = client2; - errcode = KRB5KDC_ERR_SERVER_NOMATCH; - status = "2ND_TKT_MISMATCH"; - au_state->status = status; - kau_u2u(kdc_context, FALSE, au_state); - goto cleanup; - } - ticket_kvno = 0; - ticket_reply.enc_part.enctype = t2enc->session->enctype; + ticket_reply.enc_part.enctype = stkt->enc_part2->session->enctype; kau_u2u(kdc_context, TRUE, au_state); - st_idx++; } else { ticket_kvno = current_kvno(server); } @@ -917,38 +878,52 @@ prepare_error_tgs (struct kdc_request_state *state, /* KDC options that require a second ticket */ #define STKT_OPTIONS (KDC_OPT_CNAME_IN_ADDL_TKT | KDC_OPT_ENC_TKT_IN_SKEY) /* - * Get the key for the second ticket, if any, and decrypt it. + * If req is a second-ticket request and a second ticket is present, decrypt + * it. Set *stkt_out to an alias to the ticket with populated enc_part2. Set + * *server_out to the server DB entry and *key_out to the ticket decryption + * key. */ static krb5_error_code decrypt_2ndtkt(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req, - krb5_flags flags, krb5_db_entry **server_out, - krb5_keyblock **key_out, const char **status) + krb5_flags flags, const krb5_ticket **stkt_out, + krb5_db_entry **server_out, krb5_keyblock **key_out, + const char **status) { krb5_error_code retval; krb5_db_entry *server = NULL; + krb5_keyblock *key = NULL; krb5_kvno kvno; krb5_ticket *stkt; - if (!(req->kdc_options & STKT_OPTIONS)) + *stkt_out = NULL; + *server_out = NULL; + *key_out = NULL; + + if (!(req->kdc_options & STKT_OPTIONS) || req->second_ticket == NULL || + req->second_ticket[0] == NULL) return 0; stkt = req->second_ticket[0]; - retval = kdc_get_server_key(kdc_context, stkt, flags, TRUE, &server, - key_out, &kvno); + retval = kdc_get_server_key(kdc_context, stkt, flags, TRUE, + &server, &key, &kvno); if (retval != 0) { *status = "2ND_TKT_SERVER"; goto cleanup; } - retval = krb5_decrypt_tkt_part(kdc_context, *key_out, - req->second_ticket[0]); + retval = krb5_decrypt_tkt_part(kdc_context, key, stkt); if (retval != 0) { *status = "2ND_TKT_DECRYPT"; goto cleanup; } + *stkt_out = stkt; *server_out = server; + *key_out = key; server = NULL; + key = NULL; + cleanup: krb5_db_free_principal(kdc_context, server); + krb5_free_keyblock(kdc_context, key); return retval; } diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c index dc5fe09..60f30c4 100644 --- a/src/kdc/kdc_util.c +++ b/src/kdc/kdc_util.c @@ -200,13 +200,6 @@ kdc_process_tgs_req(kdc_realm_t *kdc_active_realm, if (retval) goto cleanup_auth_context; - /* "invalid flag" tickets can must be used to validate */ - if (isflagset(ticket->enc_part2->flags, TKT_FLG_INVALID) && - !isflagset(request->kdc_options, KDC_OPT_VALIDATE)) { - retval = KRB5KRB_AP_ERR_TKT_INVALID; - goto cleanup_auth_context; - } - if ((retval = krb5_auth_con_getrecvsubkey(kdc_context, auth_context, subkey))) goto cleanup_auth_context; @@ -581,9 +574,6 @@ check_anon(kdc_realm_t *kdc_active_realm, * Returns a Kerberos protocol error number, which is _not_ the same * as a com_err error number! */ -#define AS_INVALID_OPTIONS (KDC_OPT_FORWARDED | KDC_OPT_PROXY | \ - KDC_OPT_VALIDATE | KDC_OPT_RENEW | \ - KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_CNAME_IN_ADDL_TKT) int validate_as_request(kdc_realm_t *kdc_active_realm, krb5_kdc_req *request, krb5_db_entry *client, @@ -1408,17 +1398,16 @@ is_client_db_alias(krb5_context context, const krb5_db_entry *entry, } /* - * Protocol transition (S4U2Self) + * If S4U2Self padata is present in request, verify the checksum and set + * *s4u_x509_user to the S4U2Self request. If the requested client realm is + * local, look up the client and set *princ_ptr to its DB entry. */ krb5_error_code kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm, krb5_kdc_req *request, - krb5_const_principal client_princ, - unsigned int c_flags, const krb5_db_entry *server, krb5_keyblock *tgs_subkey, krb5_keyblock *tgs_session, - krb5_timestamp kdc_time, krb5_pa_s4u_x509_user **s4u_x509_user, krb5_db_entry **princ_ptr, const char **status) @@ -1458,62 +1447,7 @@ kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm, } id = &(*s4u_x509_user)->user_id; - /* If the server is local, check that the request is for self. */ - if (!isflagset(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL) && - !is_client_db_alias(kdc_context, server, client_princ)) { - *status = "INVALID_S4U2SELF_REQUEST_SERVER_MISMATCH"; - return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */ - } - - /* - * Protocol transition is mutually exclusive with renew/forward/etc - * as well as user-to-user and constrained delegation. This check - * is also made in validate_as_request(). - * - * 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 & AS_INVALID_OPTIONS) { - *status = "INVALID AS OPTIONS"; - return KRB5KDC_ERR_BADOPTION; - } - - /* - * Valid S4U2Self requests can occur in the following combinations: - * - * (1) local TGT, local user, local server - * (2) cross TGT, local user, issuing referral - * (3) cross TGT, non-local user, issuing referral - * (4) cross TGT, non-local user, local server - * - * The first case is for a single-realm S4U2Self scenario; the second, - * third, and fourth cases are for the initial, intermediate (if any), and - * final cross-realm requests in a multi-realm scenario. - */ - - if (!isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM) && - isflagset(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL)) { - /* The requesting server appears to no longer exist, and we found - * a referral instead. Treat this as a server lookup failure. */ - *status = "LOOKING_UP_SERVER"; - return KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; - } - - /* - * Do not attempt to lookup principals in foreign realms. - */ if (data_eq(server->princ->realm, id->user->realm)) { - krb5_db_entry no_server; - krb5_pa_data **e_data = NULL; - - if (isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM) && - !isflagset(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL)) { - /* A local server should not need a cross-realm TGT to impersonate - * a local principal. */ - *status = "NOT_CROSS_REALM_REQUEST"; - return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */ - } - if (id->subject_cert.length != 0) { code = krb5_db_get_s4u_x509_principal(kdc_context, &id->subject_cert, id->user, @@ -1536,43 +1470,23 @@ kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm, return code; /* caller can free for_user */ } - memset(&no_server, 0, sizeof(no_server)); - /* Ignore password expiration and needchange attributes (as Windows * does), since S4U2Self is not password authentication. */ princ->pw_expiration = 0; clear(princ->attributes, KRB5_KDB_REQUIRES_PWCHANGE); - code = validate_as_request(kdc_active_realm, request, princ, - &no_server, kdc_time, status, &e_data); - if (code) { - krb5_db_free_principal(kdc_context, princ); - krb5_free_pa_data(kdc_context, e_data); - return code; - } - *princ_ptr = princ; - } else if (!isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM)) { - /* - * The server is asking to impersonate a principal from another realm, - * using a local TGT. It should instead ask that principal's realm and - * follow referrals back to us. - */ - *status = "S4U2SELF_CLIENT_NOT_OURS"; - return KRB5KDC_ERR_POLICY; /* match Windows error */ - } else if (id->user->length == 0) { - /* - * Only a KDC in the client realm can handle a certificate-only - * S4U2Self request. Other KDCs require a principal name and ignore - * the subject-certificate field. - */ - *status = "INVALID_XREALM_S4U2SELF_REQUEST"; - return KRB5KDC_ERR_POLICY; /* match Windows error */ } return 0; } +/* + * Determine if an S4U2Proxy request is authorized. Set **stkt_ad_info to the + * KDB authdata handle for the second ticket if the KDB module supplied one. + * Set *stkt_authdata_client to the subject client name if the KDB module + * supplied one; it must do so for a cross-realm request to be authorized. + */ krb5_error_code kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm, unsigned int flags, krb5_kdc_req *request, @@ -1591,22 +1505,6 @@ kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm, unsigned int flags, krb5_boolean support_rbcd; krb5_principal client_princ = t2enc->client; - /* - * 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 & (NON_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY)) { - *status = "INVALID_S4U2PROXY_OPTIONS"; - return KRB5KDC_ERR_BADOPTION; - } - - /* Can't get a TGT (otherwise it would be unconstrained delegation). */ - if (krb5_is_tgs_principal(proxy_princ)) { - *status = "NOT_ALLOWED_TO_DELEGATE"; - return KRB5KDC_ERR_POLICY; - } - /* Check if the client supports resource-based constrained delegation. */ errcode = kdc_get_pa_pac_rbcd(kdc_context, request->padata, &support_rbcd); if (errcode) @@ -1628,23 +1526,9 @@ kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm, unsigned int flags, if (errcode != 0 || ad_info == NULL) support_rbcd = FALSE; - /* Ensure that either the evidence ticket server or the client matches the - * TGT client. */ + /* For an RBCD final request, the KDB module must be able to recover the + * reply ticket client name from the evidence ticket authorization data. */ if (isflagset(flags, KRB5_KDB_FLAG_CROSS_REALM)) { - /* - * Check that the proxy server is local, that the second ticket is a - * cross-realm TGT for us, and that the second ticket client matches - * the header ticket client. - */ - if (isflagset(flags, KRB5_KDB_FLAG_ISSUING_REFERRAL) || - !is_cross_tgs_principal(server->princ) || - !data_eq(server->princ->data[1], proxy->princ->realm) || - !krb5_principal_compare(kdc_context, client_princ, server_princ)) { - *status = "XREALM_EVIDENCE_TICKET_MISMATCH"; - return KRB5KDC_ERR_BADOPTION; - } - /* The KDB module must be able to recover the reply ticket client name - * from the evidence ticket authorization data. */ if (*stkt_authdata_client == NULL || (*stkt_authdata_client)->realm.length == 0) { *status = "UNSUPPORTED_S4U2PROXY_REQUEST"; @@ -1652,9 +1536,6 @@ kdc_process_s4u2proxy_req(kdc_realm_t *kdc_active_realm, unsigned int flags, } client_princ = *stkt_authdata_client; - } else if (!is_client_db_alias(kdc_context, server, server_princ)) { - *status = "EVIDENCE_TICKET_MISMATCH"; - return KRB5KDC_ERR_SERVER_NOMATCH; } /* If both are in the same realm, try allowed_to_delegate first. */ diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h index 4c5e427..f2d179c 100644 --- a/src/kdc/kdc_util.h +++ b/src/kdc/kdc_util.h @@ -84,9 +84,14 @@ validate_as_request (kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *, const char **, krb5_pa_data ***); int -validate_tgs_request (kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *, - krb5_ticket *, krb5_timestamp, - const char **, krb5_pa_data ***); +validate_tgs_request(kdc_realm_t *kdc_active_realm, + krb5_kdc_req *request, krb5_db_entry *server, + krb5_ticket *ticket, const krb5_ticket *stkt, + krb5_db_entry *stkt_server, krb5_timestamp kdc_time, + krb5_pa_s4u_x509_user *s4u_x509_user, + krb5_db_entry *s4u2self_client, + krb5_boolean is_crossrealm, krb5_boolean is_referral, + const char **status, krb5_pa_data ***e_data); krb5_flags get_ticket_flags(krb5_flags reqflags, krb5_db_entry *client, @@ -262,12 +267,9 @@ return_enc_padata(krb5_context context, krb5_error_code kdc_process_s4u2self_req (kdc_realm_t *kdc_active_realm, krb5_kdc_req *request, - krb5_const_principal client_princ, - unsigned int c_flags, const krb5_db_entry *server, krb5_keyblock *tgs_subkey, krb5_keyblock *tgs_session, - krb5_timestamp kdc_time, krb5_pa_s4u_x509_user **s4u2self_req, krb5_db_entry **princ_ptr, const char **status); @@ -462,6 +464,9 @@ struct krb5_kdcpreauth_rock_st { /* TGS-REQ options which are not compatible with referrals */ #define NO_REFERRAL_OPTION (NON_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY) +/* Options incompatible with AS and S4U2Self requests */ +#define AS_INVALID_OPTIONS (NO_REFERRAL_OPTION | KDC_OPT_CNAME_IN_ADDL_TKT) + /* * Mask of KDC options that request the corresponding ticket flag with * the same number. Some of these are invalid for AS-REQs, but diff --git a/src/kdc/tgs_policy.c b/src/kdc/tgs_policy.c index a5a00f0..ae6c763 100644 --- a/src/kdc/tgs_policy.c +++ b/src/kdc/tgs_policy.c @@ -94,6 +94,13 @@ check_tgs_opts(krb5_kdc_req *req, krb5_ticket *tkt, const char **status) } } } + + if (isflagset(tkt->enc_part2->flags, TKT_FLG_INVALID) && + !isflagset(req->kdc_options, KDC_OPT_VALIDATE)) { + *status = "TICKET NOT VALID"; + return KRB_AP_ERR_TKT_NYV; + } + return 0; } @@ -237,40 +244,180 @@ check_tgs_times(krb5_kdc_req *req, krb5_ticket_times *times, return 0; } +/* Check for local user tickets issued by foreign realms. This check is + * skipped for S4U2Self requests. */ static int -check_tgs_s4u2proxy(kdc_realm_t *kdc_active_realm, - krb5_kdc_req *req, const char **status) +check_tgs_lineage(krb5_db_entry *server, krb5_ticket *tkt, + krb5_boolean is_crossrealm, const char **status) { - if (req->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) { - /* Check that second ticket is in request. */ - if (!req->second_ticket || !req->second_ticket[0]) { - *status = "NO_2ND_TKT"; - return KDC_ERR_BADOPTION; - } + if (is_crossrealm && data_eq(tkt->enc_part2->client->realm, + server->princ->realm)) { + *status = "INVALID LINEAGE"; + return KDC_ERR_POLICY; } return 0; } static int +check_tgs_s4u2self(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req, + krb5_db_entry *server, krb5_ticket *tkt, + krb5_timestamp kdc_time, + krb5_pa_s4u_x509_user *s4u_x509_user, krb5_db_entry *client, + krb5_boolean is_crossrealm, krb5_boolean is_referral, + const char **status, krb5_pa_data ***e_data) +{ + krb5_db_entry empty_server = { 0 }; + + /* If the server is local, check that the request is for self. */ + if (!is_referral && + !is_client_db_alias(kdc_context, server, tkt->enc_part2->client)) { + *status = "INVALID_S4U2SELF_REQUEST_SERVER_MISMATCH"; + return KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */ + } + + /* S4U2Self requests must use options valid for AS requests. */ + if (req->kdc_options & AS_INVALID_OPTIONS) { + *status = "INVALID S4U2SELF OPTIONS"; + return KDC_ERR_BADOPTION; + } + + /* + * Valid S4U2Self requests can occur in the following combinations: + * + * (1) local TGT, local user, local server + * (2) cross TGT, local user, issuing referral + * (3) cross TGT, non-local user, issuing referral + * (4) cross TGT, non-local user, local server + * + * The first case is for a single-realm S4U2Self scenario; the second, + * third, and fourth cases are for the initial, intermediate (if any), and + * final cross-realm requests in a multi-realm scenario. + */ + + if (!is_crossrealm && is_referral) { + /* This could happen if the requesting server no longer exists, and we + * found a referral instead. Treat this as a server lookup failure. */ + *status = "LOOKING_UP_SERVER"; + return KDC_ERR_S_PRINCIPAL_UNKNOWN; + } + if (client != NULL && is_crossrealm && !is_referral) { + /* A local server should not need a cross-realm TGT to impersonate + * a local principal. */ + *status = "NOT_CROSS_REALM_REQUEST"; + return KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */ + } + if (client == NULL && !is_crossrealm) { + /* + * The server is asking to impersonate a principal from another realm, + * using a local TGT. It should instead ask that principal's realm and + * follow referrals back to us. + */ + *status = "S4U2SELF_CLIENT_NOT_OURS"; + return KDC_ERR_POLICY; /* match Windows error */ + } + if (client == NULL && s4u_x509_user->user_id.user->length == 0) { + /* + * Only a KDC in the client realm can handle a certificate-only + * S4U2Self request. Other KDCs require a principal name and ignore + * the subject-certificate field. + */ + *status = "INVALID_XREALM_S4U2SELF_REQUEST"; + return KDC_ERR_POLICY; /* match Windows error */ + } + + if (client != NULL) { + /* Validate the client policy. Use an empty server principal to bypass + * server policy checks. */ + return validate_as_request(kdc_active_realm, req, client, + &empty_server, kdc_time, status, e_data); + } + + return 0; +} + +static int +check_tgs_s4u2proxy(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req, + krb5_db_entry *server, krb5_ticket *tkt, + const krb5_ticket *stkt, krb5_db_entry *stkt_server, + krb5_boolean is_crossrealm, krb5_boolean is_referral, + const char **status) +{ + /* A second ticket must be present in the request. */ + if (stkt == NULL) { + *status = "NO_2ND_TKT"; + return KDC_ERR_BADOPTION; + } + + /* Constrained delegation is mutually exclusive with renew/forward/etc. + * (and therefore requires the header ticket to be a TGT). */ + if (req->kdc_options & (NON_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY)) { + *status = "INVALID_S4U2PROXY_OPTIONS"; + return KDC_ERR_BADOPTION; + } + + /* Can't get a TGT (otherwise it would be unconstrained delegation). */ + if (krb5_is_tgs_principal(req->server)) { + *status = "NOT_ALLOWED_TO_DELEGATE"; + return KDC_ERR_POLICY; + } + + /* + * An S4U2Proxy request must be an initial request to the impersonator's + * realm (possibly for a target resource in the same realm), or a final + * cross-realm RBCD request to the resource realm. Intermediate + * referral-chasing requests do not use the CNAME-IN-ADDL-TKT flag. + */ + + /* For an initial or same-realm request, the second ticket server and + * header ticket client must be the same principal. */ + if (!is_crossrealm && !is_client_db_alias(kdc_context, stkt_server, + tkt->enc_part2->client)) { + *status = "EVIDENCE_TICKET_MISMATCH"; + return KDC_ERR_SERVER_NOMATCH; + } + + /* + * For a cross-realm request, the second ticket must be a referral TGT to + * our realm with the impersonator as client. (Unlike the header ticket, + * the second ticket contains authdata for the subject client.) The target + * server must also be local, so we must not be issuing a referral. + */ + if (is_crossrealm && + (is_referral || !is_cross_tgs_principal(stkt_server->princ) || + !data_eq(stkt_server->princ->data[1], server->princ->realm) || + !krb5_principal_compare(kdc_context, stkt->enc_part2->client, + tkt->enc_part2->client))) { + *status = "XREALM_EVIDENCE_TICKET_MISMATCH"; + return KDC_ERR_BADOPTION; + } + + return 0; +} + +static int check_tgs_u2u(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req, - krb5_const_principal server_princ, const char **status) + const krb5_ticket *stkt, krb5_db_entry *server, + const char **status) { - krb5_const_principal second_server_princ; + /* A second ticket must be present in the request. */ + if (stkt == NULL) { + *status = "NO_2ND_TKT"; + return KDC_ERR_BADOPTION; + } - if (req->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) { - /* Check that second ticket is in request. */ - if (!req->second_ticket || !req->second_ticket[0]) { - *status = "NO_2ND_TKT"; - return KDC_ERR_BADOPTION; - } - /* Check that second ticket is a TGT to the server realm. */ - second_server_princ = req->second_ticket[0]->server; - if (!is_local_tgs_principal(second_server_princ) || - !data_eq(second_server_princ->data[1], server_princ->realm)) { - *status = "2ND_TKT_NOT_TGS"; - return KDC_ERR_POLICY; - } + /* The second ticket must be a TGT to the server realm. */ + if (!is_local_tgs_principal(stkt->server) || + !data_eq(stkt->server->data[1], server->princ->realm)) { + *status = "2ND_TKT_NOT_TGS"; + return KDC_ERR_POLICY; } + + /* The second ticket client must match the requested server. */ + if (!is_client_db_alias(kdc_context, server, stkt->enc_part2->client)) { + *status = "2ND_TKT_MISMATCH"; + return KDC_ERR_SERVER_NOMATCH; + } + return 0; } @@ -321,7 +468,11 @@ check_tgs_tgt(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req, int validate_tgs_request(kdc_realm_t *kdc_active_realm, krb5_kdc_req *request, krb5_db_entry *server, - krb5_ticket *ticket, krb5_timestamp kdc_time, + krb5_ticket *ticket, const krb5_ticket *stkt, + krb5_db_entry *stkt_server, krb5_timestamp kdc_time, + krb5_pa_s4u_x509_user *s4u_x509_user, + krb5_db_entry *s4u2self_client, + krb5_boolean is_crossrealm, krb5_boolean is_referral, const char **status, krb5_pa_data ***e_data) { int errcode; @@ -355,13 +506,31 @@ validate_tgs_request(kdc_realm_t *kdc_active_realm, return(KRB_AP_ERR_REPEAT); } - errcode = check_tgs_u2u(kdc_active_realm, request, server->princ, status); + if (s4u_x509_user != NULL) { + errcode = check_tgs_s4u2self(kdc_active_realm, request, server, ticket, + kdc_time, s4u_x509_user, s4u2self_client, + is_crossrealm, is_referral, + status, e_data); + } else { + errcode = check_tgs_lineage(server, ticket, is_crossrealm, status); + } if (errcode != 0) return errcode; - errcode = check_tgs_s4u2proxy(kdc_active_realm, request, status); - if (errcode != 0) - return errcode; + if (request->kdc_options & KDC_OPT_ENC_TKT_IN_SKEY) { + errcode = check_tgs_u2u(kdc_active_realm, request, stkt, server, + status); + if (errcode != 0) + return errcode; + } + + if (request->kdc_options & KDC_OPT_CNAME_IN_ADDL_TKT) { + errcode = check_tgs_s4u2proxy(kdc_active_realm, request, server, + ticket, stkt, stkt_server, is_crossrealm, + is_referral, status); + if (errcode != 0) + return errcode; + } if (check_anon(kdc_active_realm, ticket->enc_part2->client, request->server) != 0) { |