From: Isaac Boukris Date: Wed, 16 Jan 2019 22:23:25 +0000 (+0200) Subject: Add KDC support for X.509 S4U2Self requests X-Git-Tag: krb5-1.18-beta1~174 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0fbfffbef2c266fedac557e00108b944e31e8d50;p=thirdparty%2Fkrb5.git Add KDC support for X.509 S4U2Self requests Add a KDB function krb5_db_get_s4u_x509_principal() and an associated method in the DAL, bumping the minor version and cleaning up a leftover comment in the table from major version 6. When processing an AS-REQ, look up the client principal by certificate if the request contains a non-empty PA-S4U-X509-USER value. When processing an S4U2Self TGS-REQ, allow requests with certificates, and look up the client principal by certificate if one is presented. [ghudson@mit.edu: factored out lookup_client() in AS code; rewrote commit message and some comments; adjusted flow control changes in kdc_process_s4u_x509_user()] ticket: 8781 (new) --- diff --git a/src/include/kdb.h b/src/include/kdb.h index 9812a35e68..92f8458abe 100644 --- a/src/include/kdb.h +++ b/src/include/kdb.h @@ -707,6 +707,12 @@ krb5_error_code krb5_db_check_allowed_to_delegate(krb5_context kcontext, const krb5_db_entry *server, krb5_const_principal proxy); +krb5_error_code krb5_db_get_s4u_x509_principal(krb5_context kcontext, + const krb5_data *client_cert, + krb5_const_principal in_princ, + unsigned int flags, + krb5_db_entry **entry); + /** * Sort an array of @a krb5_key_data keys in descending order by their kvno. * Key data order within a kvno is preserved. @@ -1389,8 +1395,6 @@ typedef struct _kdb_vftabl { const krb5_db_entry *server, krb5_const_principal proxy); - /* End of minor version 0. */ - /* * Optional: Free the e_data pointer of a database entry. If this method * is not implemented, the e_data pointer in principal entries will be @@ -1398,7 +1402,28 @@ typedef struct _kdb_vftabl { */ void (*free_principal_e_data)(krb5_context kcontext, krb5_octet *e_data); - /* End of minor version 1 for major version 6. */ + /* End of minor version 0. */ + + /* + * Optional: get a principal entry for S4U2Self based on X509 certificate. + * + * If flags include KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY, princ->realm + * indicates the request realm, but the data components should be ignored. + * The module can return an out-of-realm client referral as it would for + * get_principal(). + * + * If flags does not include KRB5_KDB_FLAG_CLIENT_REFERRALS_ONLY, princ is + * from PA-S4U-X509-USER. If it contains data components (and not just a + * realm), the module should verify that it is the same as the lookup + * result for client_cert. The module should not return a referral. + */ + krb5_error_code (*get_s4u_x509_principal)(krb5_context kcontext, + const krb5_data *client_cert, + krb5_const_principal princ, + unsigned int flags, + krb5_db_entry **entry_out); + + /* End of minor version 1 for major version 7. */ } kdb_vftabl; #endif /* !defined(_WIN32) */ diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c index 8a96c12a9a..b49df6a748 100644 --- a/src/kdc/do_as_req.c +++ b/src/kdc/do_as_req.c @@ -130,6 +130,25 @@ select_client_key(krb5_context context, krb5_db_entry *client, return 0; } +static krb5_error_code +lookup_client(krb5_context context, krb5_kdc_req *req, unsigned int flags, + krb5_db_entry **entry_out) +{ + krb5_pa_data *pa; + krb5_data cert; + + *entry_out = NULL; + pa = krb5int_find_pa_data(context, req->padata, KRB5_PADATA_S4U_X509_USER); + if (pa != NULL && pa->length != 0 && + req->client->type == KRB5_NT_X500_PRINCIPAL) { + cert = make_data(pa->contents, pa->length); + return krb5_db_get_s4u_x509_principal(context, &cert, req->client, + flags, entry_out); + } else { + return krb5_db_get_principal(context, req->client, flags, entry_out); + } +} + struct as_req_state { loop_respond_fn respond; void *arg; @@ -597,8 +616,8 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt, if (include_pac_p(kdc_context, state->request)) { setflag(state->c_flags, KRB5_KDB_FLAG_INCLUDE_PAC); } - errcode = krb5_db_get_principal(kdc_context, state->request->client, - state->c_flags, &state->client); + errcode = lookup_client(kdc_context, state->request, state->c_flags, + &state->client); if (errcode == KRB5_KDB_CANTLOCK_DB) errcode = KRB5KDC_ERR_SVC_UNAVAILABLE; if (errcode == KRB5_KDB_NOENTRY) { diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c index 0dcc0c3354..1c77cc1dbd 100644 --- a/src/kdc/kdc_util.c +++ b/src/kdc/kdc_util.c @@ -1364,8 +1364,8 @@ kdc_process_s4u_x509_user(krb5_context context, return code; } - if (krb5_princ_size(context, (*s4u_x509_user)->user_id.user) == 0 || - (*s4u_x509_user)->user_id.subject_cert.length != 0) { + if (krb5_princ_size(context, (*s4u_x509_user)->user_id.user) == 0 && + (*s4u_x509_user)->user_id.subject_cert.length == 0) { *status = "INVALID_S4U2SELF_REQUEST"; krb5_free_pa_s4u_x509_user(context, *s4u_x509_user); *s4u_x509_user = NULL; @@ -1487,6 +1487,7 @@ kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm, krb5_pa_data *pa_data; int flags; krb5_db_entry *princ; + krb5_s4u_userid *id; *princ_ptr = NULL; @@ -1516,6 +1517,7 @@ kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm, } else return 0; } + id = &(*s4u_x509_user)->user_id; /* * We need to compare the client name in the TGT with the requested @@ -1601,8 +1603,7 @@ kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm, /* * Do not attempt to lookup principals in foreign realms. */ - if (is_local_principal(kdc_active_realm, - (*s4u_x509_user)->user_id.user)) { + if (is_local_principal(kdc_active_realm, id->user)) { krb5_db_entry no_server; krb5_pa_data **e_data = NULL; @@ -1613,9 +1614,20 @@ kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm, return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error */ } - code = krb5_db_get_principal(kdc_context, - (*s4u_x509_user)->user_id.user, - KRB5_KDB_FLAG_INCLUDE_PAC, &princ); + if (id->subject_cert.length != 0) { + code = krb5_db_get_s4u_x509_principal(kdc_context, + &id->subject_cert, id->user, + KRB5_KDB_FLAG_INCLUDE_PAC, + &princ); + if (code == 0 && id->user->length == 0) { + krb5_free_principal(kdc_context, id->user); + code = krb5_copy_principal(kdc_context, princ->princ, + &id->user); + } + } else { + code = krb5_db_get_principal(kdc_context, id->user, + KRB5_KDB_FLAG_INCLUDE_PAC, &princ); + } if (code == KRB5_KDB_NOENTRY) { *status = "UNKNOWN_S4U2SELF_PRINCIPAL"; return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; @@ -1648,6 +1660,14 @@ kdc_process_s4u2self_req(kdc_realm_t *kdc_active_realm, */ *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; diff --git a/src/lib/kdb/kdb5.c b/src/lib/kdb/kdb5.c index da5332217f..0c25bf2c40 100644 --- a/src/lib/kdb/kdb5.c +++ b/src/lib/kdb/kdb5.c @@ -324,6 +324,12 @@ copy_vtable(const kdb_vftabl *in, kdb_vftabl *out) out->check_allowed_to_delegate = in->check_allowed_to_delegate; out->free_principal_e_data = in->free_principal_e_data; + /* Copy fields for minor version 1 (major version 7). */ + assert(KRB5_KDB_DAL_MAJOR_VERSION == 7); + out->get_s4u_x509_principal = NULL; + if (in->min_ver >= 1) + out->get_s4u_x509_principal = in->get_s4u_x509_principal; + /* Set defaults for optional fields. */ if (out->fetch_master_key == NULL) out->fetch_master_key = krb5_db_def_fetch_mkey; @@ -2717,6 +2723,32 @@ krb5_db_check_allowed_to_delegate(krb5_context kcontext, return v->check_allowed_to_delegate(kcontext, client, server, proxy); } +krb5_error_code +krb5_db_get_s4u_x509_principal(krb5_context kcontext, + const krb5_data *client_cert, + krb5_const_principal in_princ, + unsigned int flags, krb5_db_entry **entry) +{ + krb5_error_code ret; + kdb_vftabl *v; + + ret = get_vftabl(kcontext, &v); + if (ret) + return ret; + if (v->get_s4u_x509_principal == NULL) + return KRB5_PLUGIN_OP_NOTSUPP; + ret = v->get_s4u_x509_principal(kcontext, client_cert, in_princ, flags, + entry); + if (ret) + return ret; + + /* Sort the keys in the db entry, same as get_principal(). */ + if ((*entry)->key_data != NULL) + krb5_dbe_sort_key_data((*entry)->key_data, (*entry)->n_key_data); + + return 0; +} + void krb5_dbe_sort_key_data(krb5_key_data *key_data, size_t key_data_length) { diff --git a/src/lib/kdb/libkdb5.exports b/src/lib/kdb/libkdb5.exports index 5df596aa47..f9f602eab9 100644 --- a/src/lib/kdb/libkdb5.exports +++ b/src/lib/kdb/libkdb5.exports @@ -5,6 +5,7 @@ krb5_db_alloc krb5_db_free krb5_db_audit_as_req krb5_db_check_allowed_to_delegate +krb5_db_get_s4u_x509_principal krb5_db_check_policy_as krb5_db_check_policy_tgs krb5_db_check_transited_realms