]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Improve S4U2Self realm identification internals 890/head
authorGreg Hudson <ghudson@mit.edu>
Wed, 2 Jan 2019 21:54:28 +0000 (16:54 -0500)
committerGreg Hudson <ghudson@mit.edu>
Tue, 5 Mar 2019 16:45:21 +0000 (11:45 -0500)
Realm identification for S4U2Self requests ([MS-SFU] 3.1.5.1.1.1) uses
the AS code path with some differences: we might want to include a
subject certificate in pa-data, we want to stop as soon as we get a
reply indicating which realm the client is in, and we want to
communicate that realm to the caller.  The current method of making
these changes is fragile--it uses an optimistic preauth type but does
not actually pre-authenticate, and it assumes that the AS code will
terminate with a predictable error if there is no prompter and a
trivial GAK function.

Instead, add fields to krb5_get_init_creds_context for realm
identification, and support them in the AS state machine, making sure
never to invoke preauth modules.  Add a new library-internal function
k5_identify_realm() to set up an appropriate context, run the state
machine, and copy out the client principal of the last request on
success.

src/include/k5-trace.h
src/lib/krb5/krb/get_in_tkt.c
src/lib/krb5/krb/init_creds_ctx.h
src/lib/krb5/krb/int-proto.h
src/lib/krb5/krb/preauth2.c
src/lib/krb5/krb/s4u_creds.c
src/tests/gssapi/t_s4u.py

index f3ed6a45d1f01ecc7cfcea43e21e71b62b7cb415..d08e5ece470aba88d58d46bcfcce771e107b6c69 100644 (file)
@@ -227,6 +227,8 @@ void krb5int_trace(krb5_context context, const char *fmt, ...);
 #define TRACE_INIT_CREDS_GAK(c, salt, s2kparams)                    \
     TRACE(c, "Getting AS key, salt \"{data}\", params \"{data}\"",  \
           salt, s2kparams)
+#define TRACE_INIT_CREDS_IDENTIFIED_REALM(c, realm)                     \
+    TRACE(c, "Identified realm of client principal as {data}", realm)
 #define TRACE_INIT_CREDS_KEYTAB_LOOKUP(c, etypes)               \
     TRACE(c, "Looked up etypes in keytab: {etypes}", etypes)
 #define TRACE_INIT_CREDS_KEYTAB_LOOKUP_FAILED(c, code)          \
index 79dede2c6704ae6adae342edd77a2f9618c85fa2..1e53d7ac087517e4d4235cd2f372c948622ef427 100644 (file)
@@ -1424,6 +1424,13 @@ init_creds_step_request(krb5_context context,
     if (code)
         goto cleanup;
 
+    if (ctx->subject_cert != NULL) {
+        code = add_padata(&ctx->request->padata, KRB5_PADATA_S4U_X509_USER,
+                          ctx->subject_cert->data, ctx->subject_cert->length);
+        if (code)
+            return code;
+    }
+
     code = maybe_add_pac_request(context, ctx);
     if (code)
         goto cleanup;
@@ -1566,6 +1573,12 @@ init_creds_step_reply(krb5_context context,
              * FAST upgrade. */
             ctx->restarted = FALSE;
             code = restart_init_creds_loop(context, ctx, FALSE);
+        } else if (ctx->identify_realm &&
+                   (reply_code == KDC_ERR_PREAUTH_REQUIRED ||
+                    reply_code == KDC_ERR_KEY_EXP)) {
+            /* The client exists in this realm; we can stop. */
+            ctx->complete = TRUE;
+            goto cleanup;
         } else if (reply_code == KDC_ERR_PREAUTH_REQUIRED && retry) {
             note_req_timestamp(context, ctx, ctx->err_reply->stime,
                                ctx->err_reply->susec);
@@ -1625,6 +1638,12 @@ init_creds_step_reply(krb5_context context,
     if (code != 0)
         goto cleanup;
 
+    if (ctx->identify_realm) {
+        /* Just getting a reply means the client exists in this realm. */
+        ctx->complete = TRUE;
+        goto cleanup;
+    }
+
     code = sort_krb5_padata_sequence(context, &ctx->request->client->realm,
                                      ctx->reply->padata);
     if (code != 0)
@@ -1849,6 +1868,46 @@ cleanup:
     return code;
 }
 
+krb5_error_code
+k5_identify_realm(krb5_context context, krb5_principal client,
+                  const krb5_data *subject_cert, krb5_principal *client_out)
+{
+    krb5_error_code ret;
+    krb5_get_init_creds_opt *opts = NULL;
+    krb5_init_creds_context ctx = NULL;
+    int use_master = 0;
+
+    *client_out = NULL;
+
+    ret = krb5_get_init_creds_opt_alloc(context, &opts);
+    if (ret)
+        goto cleanup;
+    krb5_get_init_creds_opt_set_tkt_life(opts, 15);
+    krb5_get_init_creds_opt_set_renew_life(opts, 0);
+    krb5_get_init_creds_opt_set_forwardable(opts, 0);
+    krb5_get_init_creds_opt_set_proxiable(opts, 0);
+    krb5_get_init_creds_opt_set_canonicalize(opts, 1);
+
+    ret = krb5_init_creds_init(context, client, NULL, NULL, 0, opts, &ctx);
+    if (ret)
+        goto cleanup;
+
+    ctx->identify_realm = TRUE;
+    ctx->subject_cert = subject_cert;
+
+    ret = k5_init_creds_get(context, ctx, &use_master);
+    if (ret)
+        goto cleanup;
+
+    TRACE_INIT_CREDS_IDENTIFIED_REALM(context, &ctx->request->client->realm);
+    ret = krb5_copy_principal(context, ctx->request->client, client_out);
+
+cleanup:
+    krb5_get_init_creds_opt_free(context, opts);
+    krb5_init_creds_free(context, ctx);
+    return ret;
+}
+
 krb5_error_code
 k5_populate_gic_opt(krb5_context context, krb5_get_init_creds_opt **out,
                     krb5_flags options, krb5_address *const *addrs,
index 7a6219b1c49c6dd12432f85fe46b0dd341405b60..5bd67a1d86b8782322dab526a8b7c2032d14cfc3 100644 (file)
@@ -20,6 +20,8 @@ struct gak_password {
 struct _krb5_init_creds_context {
     krb5_get_init_creds_opt *opt;
     krb5_get_init_creds_opt opt_storage;
+    krb5_boolean identify_realm;
+    const krb5_data *subject_cert;
     char *in_tkt_service;
     krb5_prompter_fct prompter;
     void *prompter_data;
index 4464a1344ea3667b1987df993348a18a00599832..3e80241b3cc4ddd77c87353f17ad878b13191d4a 100644 (file)
@@ -291,6 +291,17 @@ k5_get_init_creds(krb5_context context, krb5_creds *creds,
                   get_as_key_fn gak, void *gak_data, int *master,
                   krb5_kdc_rep **as_reply);
 
+/*
+ * Make AS requests with the canonicalize flag set, stopping when we get a
+ * message indicating which realm the client principal is in.  Set *client_out
+ * to a copy of client with the canonical realm.  If subject_cert is non-null,
+ * include PA_S4U_X509_USER pa-data with the subject certificate each request.
+ * (See [MS-SFU] 3.1.5.1.1.1 and 3.1.5.1.1.2.)
+ */
+krb5_error_code
+k5_identify_realm(krb5_context context, krb5_principal client,
+                  const krb5_data *subject_cert, krb5_principal *client_out);
+
 krb5_error_code
 k5_populate_gic_opt(krb5_context context, krb5_get_init_creds_opt **opt,
                     krb5_flags options, krb5_address *const *addrs,
index a73568cba5972982b111a2b1209a550f84188333..ffca476c2bfedeeb41826388f726cb50199e7c68 100644 (file)
@@ -879,49 +879,6 @@ error:
     return ENOMEM;
 }
 
-static krb5_error_code
-add_s4u_x509_user_padata(krb5_context context, krb5_s4u_userid *userid,
-                         krb5_principal client, krb5_pa_data ***out_pa_list,
-                         int *out_pa_list_size)
-{
-    krb5_pa_data *s4u_padata;
-    krb5_error_code code;
-    krb5_principal client_copy;
-
-    if (userid == NULL)
-        return EINVAL;
-    code = krb5_copy_principal(context, client, &client_copy);
-    if (code != 0)
-        return code;
-    krb5_free_principal(context, userid->user);
-    userid->user = client_copy;
-
-    if (userid->subject_cert.length != 0) {
-        s4u_padata = malloc(sizeof(*s4u_padata));
-        if (s4u_padata == NULL)
-            return ENOMEM;
-
-        s4u_padata->magic = KV5M_PA_DATA;
-        s4u_padata->pa_type = KRB5_PADATA_S4U_X509_USER;
-        s4u_padata->contents = k5memdup(userid->subject_cert.data,
-                                        userid->subject_cert.length, &code);
-        if (s4u_padata->contents == NULL) {
-            free(s4u_padata);
-            return code;
-        }
-        s4u_padata->length = userid->subject_cert.length;
-
-        code = grow_pa_list(out_pa_list, out_pa_list_size, &s4u_padata, 1);
-        if (code) {
-            free(s4u_padata->contents);
-            free(s4u_padata);
-            return code;
-        }
-    }
-
-    return 0;
-}
-
 /*
  * If the module for pa_type can adjust its AS_REQ data using the contents of
  * err and err_padata, return 0 with *padata_out set to a padata list for the
@@ -1017,7 +974,8 @@ k5_preauth(krb5_context context, krb5_init_creds_context ctx,
     *padata_out = NULL;
     *pa_type_out = KRB5_PADATA_NONE;
 
-    if (in_padata == NULL)
+    /* We should never invoke preauth modules when identifying the realm. */
+    if (in_padata == NULL || ctx->identify_realm)
         return 0;
 
     TRACE_PREAUTH_INPUT(context, in_padata);
@@ -1032,16 +990,6 @@ k5_preauth(krb5_context context, krb5_init_creds_context ctx,
     if (ret)
         goto error;
 
-    if (krb5int_find_pa_data(context, in_padata,
-                             KRB5_PADATA_S4U_X509_USER) != NULL) {
-        /* Fulfill a private contract with krb5_get_credentials_for_user. */
-        ret = add_s4u_x509_user_padata(context, ctx->gak_data,
-                                       ctx->request->client,
-                                       &out_pa_list, &out_pa_list_size);
-        if (ret)
-            goto error;
-    }
-
     /* If we can't initialize the preauth context, stop with what we have. */
     k5_init_preauth_context(context);
     if (context->preauth_context == NULL) {
index 614ed41908f2a3c059c3a0511f1ffa4919b5cd8e..e72a64a81f1b14f32822d17b680dbeccba6edd51 100644 (file)
  * itself on behalf of an arbitrary principal.
  */
 
-static krb5_error_code
-krb5_get_as_key_noop(
-    krb5_context context,
-    krb5_principal client,
-    krb5_enctype etype,
-    krb5_prompter_fct prompter,
-    void *prompter_data,
-    krb5_data *salt,
-    krb5_data *params,
-    krb5_keyblock *as_key,
-    void *gak_data,
-    k5_response_items *ritems)
-{
-    /* force a hard error, we don't actually have the key */
-    return KRB5_PREAUTH_FAILED;
-}
-
 static krb5_error_code
 s4u_identify_user(krb5_context context,
                   krb5_creds *in_creds,
                   krb5_data *subject_cert,
                   krb5_principal *canon_user)
 {
-    krb5_error_code code;
-    krb5_preauthtype ptypes[1] = { KRB5_PADATA_S4U_X509_USER };
-    krb5_creds creds;
-    int use_master = 0;
-    krb5_get_init_creds_opt *opts = NULL;
     krb5_principal_data client;
-    krb5_s4u_userid userid;
 
     *canon_user = NULL;
 
@@ -85,22 +62,6 @@ s4u_identify_user(krb5_context context,
                                    canon_user);
     }
 
-    memset(&creds, 0, sizeof(creds));
-
-    memset(&userid, 0, sizeof(userid));
-    if (subject_cert != NULL)
-        userid.subject_cert = *subject_cert;
-
-    code = krb5_get_init_creds_opt_alloc(context, &opts);
-    if (code != 0)
-        goto cleanup;
-    krb5_get_init_creds_opt_set_tkt_life(opts, 15);
-    krb5_get_init_creds_opt_set_renew_life(opts, 0);
-    krb5_get_init_creds_opt_set_forwardable(opts, 0);
-    krb5_get_init_creds_opt_set_proxiable(opts, 0);
-    krb5_get_init_creds_opt_set_canonicalize(opts, 1);
-    krb5_get_init_creds_opt_set_preauth_list(opts, ptypes, 1);
-
     if (in_creds->client != NULL) {
         client = *in_creds->client;
         client.realm = in_creds->server->realm;
@@ -113,23 +74,7 @@ s4u_identify_user(krb5_context context,
         client.type = KRB5_NT_ENTERPRISE_PRINCIPAL;
     }
 
-    code = k5_get_init_creds(context, &creds, &client, NULL, NULL, 0, NULL,
-                             opts, krb5_get_as_key_noop, &userid, &use_master,
-                             NULL);
-    if (!code || code == KRB5_PREAUTH_FAILED || code == KRB5KDC_ERR_KEY_EXP) {
-        *canon_user = userid.user;
-        userid.user = NULL;
-        code = 0;
-    }
-
-cleanup:
-    krb5_free_cred_contents(context, &creds);
-    if (opts != NULL)
-        krb5_get_init_creds_opt_free(context, opts);
-    if (userid.user != NULL)
-        krb5_free_principal(context, userid.user);
-
-    return code;
+    return k5_identify_realm(context, &client, subject_cert, canon_user);
 }
 
 static krb5_error_code
index 164fec862e196711bb5f5b59cd18acf5bc398b61..7aff9afca8e01eced10a4092c96e80f4315c0b52 100755 (executable)
@@ -198,14 +198,12 @@ r1.run(['./t_s4u', 'p:' + r2.user_princ, '-', r1.keytab], env=no_default,
 # that we start at the server realm.
 mark('cross-realm S4U2Self with enterprise name')
 msgs = ('Getting initial credentials for enterprise\\@abc@SREALM',
-        'Processing preauth types: PA-FOR-X509-USER (130)',
         'Sending unauthenticated request',
         '/Realm not local to KDC',
         'Following referral to realm UREALM',
-        'Processing preauth types: PA-FOR-X509-USER (130)',
         'Sending unauthenticated request',
         '/Additional pre-authentication required',
-        '/Generic preauthentication failure',
+        'Identified realm of client principal as UREALM',
         'Getting credentials enterprise\\@abc@UREALM -> user@SREALM',
         'TGS reply is for enterprise\@abc@UREALM -> user@SREALM')
 r1.run(['./t_s4u', 'e:enterprise@abc@NOREALM', '-', r1.keytab],