Split normal kinit from s4u2 flavored kinit
[idra/samba.git] / lib / krb5_wrap / krb5_samba.c
index 10207b58bc4e47ebafc945db6db9e3cd565955cf..0a4302dd6949061c5068d6f98c6bf5579075d007 100644 (file)
@@ -1552,6 +1552,522 @@ krb5_error_code smb_krb5_get_creds(const char *server_s,
        return ret;
 }
 
+/*
+  simulate a kinit, putting the tgt in the given credentials cache.
+  Orignally by remus@snapserver.com
+
+  This version is built to use a keyblock, rather than needing the
+  original password.
+
+  The impersonate_principal is the principal if NULL, or the principal
+  to impersonate
+
+  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_keyblock_cc(krb5_context ctx, krb5_ccache cc,
+                                          krb5_principal principal,
+                                          krb5_keyblock *keyblock,
+                                          const char *target_service,
+                                          krb5_get_init_creds_opt *krb_options,
+                                          time_t *expire_time,
+                                          time_t *kdc_time)
+{
+       krb5_error_code code = 0;
+       krb5_creds my_creds;
+
+#if defined(HAVE_KRB5_GET_INIT_CREDS_KEYBLOCK)
+       code = krb5_get_init_creds_keyblock(ctx, &my_creds, principal,
+                                           keyblock, 0, target_service,
+                                           krb_options);
+#elif defined(HAVE_KRB5_GET_INIT_CREDS_KEYTAB)
+{
+#define SMB_CREDS_KEYTAB "MEMORY:tmp_smb_creds_XXXXXX"
+       char *tmp_name[sizeof(SMB_CREDS_KEYTAB)];
+       krb5_keytab_entry entry;
+       krb5_keytab keytab;
+
+       memset(entry, 0, sizeof(entry));
+       entry.principal = principal;
+       entry.key = keyblock;
+
+       memcpy(tmp_name, SMB_CREDS_KEYTAB, sizeof(SMB_CREDS_KEYTAB))
+       mktemp(tmp_name);
+       if (tmp_name[0] == 0) {
+               return KRB5_KT_BADNAME;
+       }
+       code = krb5_kt_resolve(ctx, tmp_name, &keytab);
+       if (code) {
+               return code;
+       }
+
+       code = krb5_kt_add_entry(ctx, keytab, &entry);
+       if (code) {
+               (void)krb5_kt_close(ctx, keytab);
+               goto done;
+       }
+
+       code = krb5_get_init_creds_keytab(ctx, &my_creds, principal,
+                                         keytab, 0, target_service,
+                                         krb_options);
+       (void)krb5_kt_close(ctx, keytab);
+}
+#else
+#error krb5_get_init_creds_keyblock not available!
+#endif
+       if (code) {
+               return code;
+       }
+
+       code = krb5_cc_initialize(ctx, cc, principal);
+       if (code) {
+               goto done;
+       }
+
+       code = krb5_cc_store_cred(ctx, cc, &my_creds);
+       if (code) {
+               goto done;
+       }
+
+       if (expire_time) {
+               *expire_time = (time_t) my_creds.times.endtime;
+       }
+
+       if (kdc_time) {
+               *kdc_time = (time_t) my_creds.times.starttime;
+       }
+
+       code = 0;
+done:
+       krb5_free_cred_contents(ctx, &my_creds);
+       return code;
+}
+
+krb5_error_code kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc,
+                                          krb5_principal principal,
+                                          const char *password,
+                                          const char *target_service,
+                                          krb5_get_init_creds_opt *krb_options,
+                                          time_t *expire_time,
+                                          time_t *kdc_time)
+{
+       krb5_error_code code = 0;
+       krb5_creds my_creds;
+
+       code = krb5_get_init_creds_password(ctx, &my_creds, principal,
+                                           password, NULL, NULL, 0,
+                                           target_service, krb_options);
+       if (code) {
+               return code;
+       }
+
+       code = krb5_cc_initialize(ctx, cc, principal);
+       if (code) {
+               goto done;
+       }
+
+       code = krb5_cc_store_cred(ctx, cc, &my_creds);
+       if (code) {
+               goto done;
+       }
+
+       if (expire_time) {
+               *expire_time = (time_t) my_creds.times.endtime;
+       }
+
+       if (kdc_time) {
+               *kdc_time = (time_t) my_creds.times.starttime;
+       }
+
+       code = 0;
+done:
+       krb5_free_cred_contents(ctx, &my_creds);
+       return code;
+}
+
+#ifdef SAMBA4_USES_HEIMDAL
+/*
+  simulate a kinit, putting the tgt in the given credentials cache.
+  Orignally by remus@snapserver.com
+
+  The impersonate_principal is the principal
+
+  The self_service, should be the local service (for S4U2Self if
+  impersonate_principal is given).
+
+  The target_service defaults to the krbtgt if NULL, but could be
+  kpasswd/realm or a remote service (for S4U2Proxy)
+
+*/
+krb5_error_code kerberos_kinit_s4u2_cc(krb5_context ctx,
+                                       krb5_ccache store_cc,
+                                       krb5_principal init_principal,
+                                       const char *init_password,
+                                       krb5_principal impersonate_principal,
+                                       const char *self_service,
+                                       const char *target_service,
+                                       krb5_get_init_creds_opt *krb_options,
+                                       time_t *expire_time,
+                                       time_t *kdc_time)
+{
+       krb5_error_code code = 0;
+       krb5_get_creds_opt options;
+       krb5_principal store_principal;
+       krb5_creds store_creds;
+       krb5_creds *s4u2self_creds;
+       Ticket s4u2self_ticket;
+       size_t s4u2self_ticketlen;
+       krb5_creds *s4u2proxy_creds;
+       krb5_principal self_princ;
+       bool s4u2proxy;
+       krb5_principal target_princ;
+       krb5_ccache tmp_cc;
+       const char *self_realm;
+       krb5_principal blacklist_principal = NULL;
+       krb5_principal whitelist_principal = NULL;
+
+       code = krb5_get_init_creds_password(ctx, &store_creds,
+                                           init_principal,
+                                           init_password,
+                                           NULL, NULL,
+                                           0,
+                                           impersonate_principal,
+                                           krb_options);
+       if (code != 0) {
+               return code;
+       }
+
+       store_principal = init_principal;
+
+       /*
+        * We are trying S4U2Self now:
+        *
+        * 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.
+        */
+       code = krb5_cc_new_unique(ctx, NULL, NULL, &tmp_cc);
+       if (code != 0) {
+               krb5_free_cred_contents(ctx, &store_creds);
+               return code;
+       }
+
+       code = krb5_cc_initialize(ctx, tmp_cc, store_creds.client);
+       if (code != 0) {
+               krb5_cc_destroy(ctx, tmp_cc);
+               krb5_free_cred_contents(ctx, &store_creds);
+               return code;
+       }
+
+       code = krb5_cc_store_cred(ctx, tmp_cc, &store_creds);
+       if (code != 0) {
+               krb5_free_cred_contents(ctx, &store_creds);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       /*
+        * we need to remember the client principal of our
+        * TGT and make sure the KDC does not return this
+        * in the impersonated tickets. This can happen
+        * if the KDC does not support S4U2Self and S4U2Proxy.
+        */
+       blacklist_principal = store_creds.client;
+       store_creds.client = NULL;
+       krb5_free_cred_contents(ctx, &store_creds);
+
+       /*
+        * Check if we also need S4U2Proxy or if S4U2Self is
+        * enough in order to get a ticket for the target.
+        */
+       if (target_service == NULL) {
+               s4u2proxy = false;
+       } else if (strcmp(target_service, self_service) == 0) {
+               s4u2proxy = false;
+       } else {
+               s4u2proxy = true;
+       }
+
+       /*
+        * For S4U2Self we need our own service principal,
+        * which belongs to our own realm (available on
+        * our client principal).
+        */
+       self_realm = krb5_principal_get_realm(ctx, init_principal);
+
+       code = krb5_parse_name(ctx, self_service, &self_princ);
+       if (code != 0) {
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_principal_set_realm(ctx, self_princ, self_realm);
+       if (code != 0) {
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_free_principal(ctx, self_princ);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_get_creds_opt_alloc(ctx, &options);
+       if (code != 0) {
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_free_principal(ctx, self_princ);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       if (s4u2proxy) {
+               /*
+                * If we want S4U2Proxy, we need the forwardable flag
+                * on the S4U2Self ticket.
+                */
+               krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
+       }
+
+       code = krb5_get_creds_opt_set_impersonate(ctx, options,
+                                                 impersonate_principal);
+       if (code != 0) {
+               krb5_get_creds_opt_free(ctx, options);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_free_principal(ctx, self_princ);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       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);
+       if (code != 0) {
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       if (!s4u2proxy) {
+               krb5_cc_destroy(ctx, tmp_cc);
+
+               /*
+                * Now make sure we store the impersonated principal
+                * and creds instead of the TGT related stuff
+                * in the krb5_ccache of the caller.
+                */
+               code = krb5_copy_creds_contents(ctx, s4u2self_creds,
+                                               &store_creds);
+               krb5_free_creds(ctx, s4u2self_creds);
+               if (code != 0) {
+                       return code;
+               }
+
+               /*
+                * 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;
+               goto store;
+       }
+
+       /*
+        * We are trying S4U2Proxy:
+        *
+        * We need the ticket from the S4U2Self step
+        * and our TGT in order to get the delegated ticket.
+        */
+       code = decode_Ticket((const uint8_t *)s4u2self_creds->ticket.data,
+                            s4u2self_creds->ticket.length,
+                            &s4u2self_ticket,
+                            &s4u2self_ticketlen);
+       if (code != 0) {
+               krb5_free_creds(ctx, s4u2self_creds);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       /*
+        * we need to remember the client principal of the
+        * S4U2Self stage and as it needs to match the one we
+        * will get for the S4U2Proxy stage. We need this
+        * in order to detect KDCs which does not support S4U2Proxy.
+        */
+       whitelist_principal = s4u2self_creds->client;
+       s4u2self_creds->client = NULL;
+       krb5_free_creds(ctx, s4u2self_creds);
+
+       /*
+        * For S4U2Proxy we also got a target service principal,
+        * which also belongs to our own realm (available on
+        * our client principal).
+        */
+       code = krb5_parse_name(ctx, target_service, &target_princ);
+       if (code != 0) {
+               free_Ticket(&s4u2self_ticket);
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_principal_set_realm(ctx, target_princ, self_realm);
+       if (code != 0) {
+               free_Ticket(&s4u2self_ticket);
+               krb5_free_principal(ctx, target_princ);
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_get_creds_opt_alloc(ctx, &options);
+       if (code != 0) {
+               free_Ticket(&s4u2self_ticket);
+               krb5_free_principal(ctx, target_princ);
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_FORWARDABLE);
+       krb5_get_creds_opt_set_options(ctx, options, KRB5_GC_CONSTRAINED_DELEGATION);
+
+       code = krb5_get_creds_opt_set_ticket(ctx, options, &s4u2self_ticket);
+       free_Ticket(&s4u2self_ticket);
+       if (code != 0) {
+               krb5_get_creds_opt_free(ctx, options);
+               krb5_free_principal(ctx, target_princ);
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_cc_destroy(ctx, tmp_cc);
+               return code;
+       }
+
+       code = krb5_get_creds(ctx, options, tmp_cc,
+                             target_princ, &s4u2proxy_creds);
+       krb5_get_creds_opt_free(ctx, options);
+       krb5_free_principal(ctx, target_princ);
+       krb5_cc_destroy(ctx, tmp_cc);
+       if (code != 0) {
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               return code;
+       }
+
+       /*
+        * Now make sure we store the impersonated principal
+        * and creds instead of the TGT related stuff
+        * in the krb5_ccache of the caller.
+        */
+       code = krb5_copy_creds_contents(ctx, s4u2proxy_creds,
+                                       &store_creds);
+       krb5_free_creds(ctx, s4u2proxy_creds);
+       if (code != 0) {
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               return code;
+       }
+
+       /*
+        * 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;
+
+ store:
+       if (blacklist_principal &&
+           krb5_principal_compare(ctx, store_creds.client, blacklist_principal)) {
+               char *sp = NULL;
+               char *ip = NULL;
+
+               code = krb5_unparse_name(ctx, blacklist_principal, &sp);
+               if (code != 0) {
+                       sp = NULL;
+               }
+               code = krb5_unparse_name(ctx, impersonate_principal, &ip);
+               if (code != 0) {
+                       ip = NULL;
+               }
+               DEBUG(1, ("kerberos_kinit_password_cc: "
+                         "KDC returned self principal[%s] while impersonating [%s]\n",
+                         sp?sp:"<no memory>",
+                         ip?ip:"<no memory>"));
+
+               SAFE_FREE(sp);
+               SAFE_FREE(ip);
+
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_principal(ctx, blacklist_principal);
+               krb5_free_cred_contents(ctx, &store_creds);
+               return KRB5_FWD_BAD_PRINCIPAL;
+       }
+       if (blacklist_principal) {
+               krb5_free_principal(ctx, blacklist_principal);
+       }
+
+       if (whitelist_principal &&
+           !krb5_principal_compare(ctx, store_creds.client, whitelist_principal)) {
+               char *sp = NULL;
+               char *ep = NULL;
+
+               code = krb5_unparse_name(ctx, store_creds.client, &sp);
+               if (code != 0) {
+                       sp = NULL;
+               }
+               code = krb5_unparse_name(ctx, whitelist_principal, &ep);
+               if (code != 0) {
+                       ep = NULL;
+               }
+               DEBUG(1, ("kerberos_kinit_password_cc: "
+                         "KDC returned wrong principal[%s] we expected [%s]\n",
+                         sp?sp:"<no memory>",
+                         ep?ep:"<no memory>"));
+
+               SAFE_FREE(sp);
+               SAFE_FREE(ep);
+
+               krb5_free_principal(ctx, whitelist_principal);
+               krb5_free_cred_contents(ctx, &store_creds);
+               return KRB5_FWD_BAD_PRINCIPAL;
+       }
+       if (whitelist_principal) {
+               krb5_free_principal(ctx, whitelist_principal);
+       }
+
+       code = krb5_cc_initialize(ctx, store_cc, store_principal);
+       if (code != 0) {
+               krb5_free_cred_contents(ctx, &store_creds);
+               return code;
+       }
+
+       code = krb5_cc_store_cred(ctx, store_cc, &store_creds);
+       if (code != 0) {
+               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;
+}
+#endif
+
 /*
  * smb_krb5_principal_get_realm
  *