/* Unix SMB/CIFS implementation. kerberos utility library Copyright (C) Andrew Tridgell 2001 Copyright (C) Remus Koos 2001 Copyright (C) Nalin Dahyabhai 2004. Copyright (C) Jeremy Allison 2004. Copyright (C) Andrew Bartlett 2004-2005 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "system/kerberos.h" #include "auth/kerberos/kerberos.h" #ifdef HAVE_KRB5 /* 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 ((code = krb5_get_init_creds_keyblock(ctx, &my_creds, principal, keyblock, 0, target_service, krb_options))) { return code; } if ((code = krb5_cc_initialize(ctx, cc, principal))) { krb5_free_cred_contents(ctx, &my_creds); return code; } if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) { krb5_free_cred_contents(ctx, &my_creds); return code; } if (expire_time) { *expire_time = (time_t) my_creds.times.endtime; } if (kdc_time) { *kdc_time = (time_t) my_creds.times.starttime; } krb5_free_cred_contents(ctx, &my_creds); return 0; } /* simulate a kinit, putting the tgt in the given credentials cache. Orignally by remus@snapserver.com The impersonate_principal is the principal if NULL, or the principal to impersonate 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_password_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; if (impersonate_principal && self_service == NULL) { return EINVAL; } /* * If we are not impersonating, then get this ticket for the * target service, otherwise a krbtgt, and get the next ticket * for the target */ code = krb5_get_init_creds_password(ctx, &store_creds, init_principal, init_password, NULL, NULL, 0, impersonate_principal ? NULL : target_service, krb_options); if (code != 0) { return code; } store_principal = init_principal; if (impersonate_principal == NULL) { goto store; } /* * 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:"", ip?ip:"")); 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:"", ep?ep:"")); 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