krb5 commit: Add KDC support for X.509 S4U2Self requests
Greg Hudson
ghudson at mit.edu
Wed Mar 13 19:39:14 EDT 2019
https://github.com/krb5/krb5/commit/0fbfffbef2c266fedac557e00108b944e31e8d50
commit 0fbfffbef2c266fedac557e00108b944e31e8d50
Author: Isaac Boukris <iboukris at gmail.com>
Date: Thu Jan 17 00:23:25 2019 +0200
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 at 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)
src/include/kdb.h | 31 ++++++++++++++++++++++++++++---
src/kdc/do_as_req.c | 23 +++++++++++++++++++++--
src/kdc/kdc_util.c | 34 +++++++++++++++++++++++++++-------
src/lib/kdb/kdb5.c | 32 ++++++++++++++++++++++++++++++++
src/lib/kdb/libkdb5.exports | 1 +
5 files changed, 109 insertions(+), 12 deletions(-)
diff --git a/src/include/kdb.h b/src/include/kdb.h
index 9812a35..92f8458 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 8a96c12..b49df6a 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 0dcc0c3..1c77cc1 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 da53322..0c25bf2 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 5df596a..f9f602e 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
More information about the cvs-krb5
mailing list