s4:auth/kerberos: don't mix s4u2self creds with machine account creds
authorStefan Metzmacher <metze@samba.org>
Mon, 20 Jun 2011 13:27:58 +0000 (15:27 +0200)
committerStefan Metzmacher <metze@samba.org>
Wed, 22 Jun 2011 06:00:24 +0000 (08:00 +0200)
It's important that we don't store the tgt for the machine account
in the same krb5_ccache as the ticket for the impersonated principal.

We may pass it to some krb5/gssapi functions and they may use them
in the wrong way, which would grant machine account privileges to
the client.

metze

source4/auth/kerberos/kerberos.c

index 5e38d878beb95075fc5733477b4cf3ec3e6dbd99..5feb3e622118ce14a50a46d0c4caa0e2c8116730 100644 (file)
@@ -84,7 +84,7 @@
   The target_service defaults to the krbtgt if NULL, but could be kpasswd/realm or the local service (if we are doing s4u2self)
 
 */
- krb5_error_code kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc, 
+ krb5_error_code kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache store_cc,
                                            krb5_principal init_principal,
                                            const char *init_password,
                                            krb5_principal impersonate_principal,
                                            time_t *expire_time, time_t *kdc_time)
 {
        krb5_error_code code = 0;
-       krb5_creds init_creds;
        krb5_get_creds_opt options;
+       krb5_principal store_principal;
+       krb5_creds store_creds;
        const char *self_service = target_service;
 
        /* If we are not impersonating, then get this ticket for the
         * target service, otherwise a krbtgt, and get the next ticket
         * for the target */
-       if ((code = krb5_get_init_creds_password(ctx, &init_creds,
+       if ((code = krb5_get_init_creds_password(ctx, &store_creds,
                                                 init_principal,
                                                 init_password,
                                                 NULL, NULL,
                return code;
        }
 
-       if ((code = krb5_cc_initialize(ctx, cc, init_principal))) {
-               krb5_free_cred_contents(ctx, &init_creds);
-               return code;
-       }
-       
-       if ((code = krb5_cc_store_cred(ctx, cc, &init_creds))) {
-               krb5_free_cred_contents(ctx, &init_creds);
-               return code;
-       }
-       
-       if (expire_time) {
-               *expire_time = (time_t) init_creds.times.endtime;
-       }
-
-       if (kdc_time) {
-               *kdc_time = (time_t) init_creds.times.starttime;
-       }
-
-       krb5_free_cred_contents(ctx, &init_creds);
+       store_principal = init_principal;
 
        if (code == 0 && impersonate_principal) {
+               krb5_ccache tmp_cc;
                krb5_creds *s4u2self_creds;
                krb5_principal self_princ;
                const char *self_realm;
 
+               /*
+                * As we do not want to expose our TGT in the
+                * krb5_ccache, which is also holds the impersonated creds.
+                *
+                * Some low level krb5/gssapi function might use the TGT
+                * identity and let the client act as our machine account.
+                *
+                * We need to avoid that and use a temporary krb5_ccache
+                * in order to pass our TGT to the krb5_get_creds() function.
+                */
+               if ((code = krb5_cc_new_unique(ctx, NULL, NULL, &tmp_cc))) {
+                       krb5_free_cred_contents(ctx, &store_creds);
+                       return code;
+               }
+
+               if ((code = krb5_cc_initialize(ctx, tmp_cc, store_creds.client))) {
+                       krb5_cc_destroy(ctx, tmp_cc);
+                       krb5_free_cred_contents(ctx, &store_creds);
+                       return code;
+               }
+
+               if ((code = krb5_cc_store_cred(ctx, tmp_cc, &store_creds))) {
+                       krb5_cc_destroy(ctx, tmp_cc);
+                       krb5_free_cred_contents(ctx, &store_creds);
+                       return code;
+               }
+
+               krb5_free_cred_contents(ctx, &store_creds);
+
                /*
                 * For S4U2Self we need our own service principal,
                 * which belongs to our own realm (available on
                self_realm = krb5_principal_get_realm(ctx, init_principal);
 
                if ((code = krb5_parse_name(ctx, self_service, &self_princ))) {
+                       krb5_cc_destroy(ctx, tmp_cc);
                        return code;
                }
 
                if ((code = krb5_principal_set_realm(ctx, self_princ, self_realm))) {
                        krb5_free_principal(ctx, self_princ);
+                       krb5_cc_destroy(ctx, tmp_cc);
                        return code;
                }
 
                if ((code = krb5_get_creds_opt_alloc(ctx, &options))) {
                        krb5_free_principal(ctx, self_princ);
+                       krb5_cc_destroy(ctx, tmp_cc);
                        return code;
                }
 
                if ((code = krb5_get_creds_opt_set_impersonate(ctx, options, impersonate_principal))) {
                        krb5_get_creds_opt_free(ctx, options);
                        krb5_free_principal(ctx, self_princ);
+                       krb5_cc_destroy(ctx, tmp_cc);
                        return code;
                }
 
-               if ((code = krb5_get_creds(ctx, options, cc, self_princ, &s4u2self_creds))) {
+               if ((code = krb5_get_creds(ctx, options, tmp_cc, self_princ, &s4u2self_creds))) {
                        krb5_get_creds_opt_free(ctx, options);
                        krb5_free_principal(ctx, self_princ);
+                       krb5_cc_destroy(ctx, tmp_cc);
                        return code;
                }
 
                krb5_get_creds_opt_free(ctx, options);
                krb5_free_principal(ctx, self_princ);
+               krb5_cc_destroy(ctx, tmp_cc);
 
-               code = krb5_cc_store_cred(ctx, cc, s4u2self_creds);
+               /*
+                * Now make sure we store the impersonated principal
+                * and creds instead of the TGT related stuff
+                * in the krb5_ccache of the caller.
+                */
+               if ((code = krb5_copy_creds_contents(ctx, s4u2self_creds, &store_creds))) {
+                       krb5_free_creds(ctx, s4u2self_creds);
+                       return code;
+               }
                krb5_free_creds(ctx, s4u2self_creds);
 
+               /*
+                * It's important to store the principal the KDC
+                * returned, as otherwise the caller would not find
+                * the S4U2Self ticket in the krb5_ccache lookup.
+                */
+               store_principal = store_creds.client;
+       }
+
+       if ((code = krb5_cc_initialize(ctx, store_cc, store_principal))) {
+               krb5_free_cred_contents(ctx, &store_creds);
                return code;
        }
 
+       if ((code = krb5_cc_store_cred(ctx, store_cc, &store_creds))) {
+               krb5_free_cred_contents(ctx, &store_creds);
+               return code;
+       }
+
+       if (expire_time) {
+               *expire_time = (time_t) store_creds.times.endtime;
+       }
+
+       if (kdc_time) {
+               *kdc_time = (time_t) store_creds.times.starttime;
+       }
+
+       krb5_free_cred_contents(ctx, &store_creds);
+
        return 0;
 }