X-Git-Url: http://git.samba.org/idra/samba.git/?p=idra%2Fsamba.git;a=blobdiff_plain;f=lib%2Fkrb5_wrap%2Fkrb5_samba.c;h=0a4302dd6949061c5068d6f98c6bf5579075d007;hp=4e555b285331086e515ac564fb906c09a338187e;hb=9a4446a8ca66f08ef834a1488fa0e6a17a33228a;hpb=08c733d75fd83fd5e32ced9712d41dd595e0f182;ds=sidebyside diff --git a/lib/krb5_wrap/krb5_samba.c b/lib/krb5_wrap/krb5_samba.c index 4e555b28533..0a4302dd694 100644 --- a/lib/krb5_wrap/krb5_samba.c +++ b/lib/krb5_wrap/krb5_samba.c @@ -39,6 +39,7 @@ #define GSSAPI_BNDLENGTH 16 /* Bind Length (rfc-1964 pg.3) */ #define GSSAPI_CHECKSUM_SIZE (4+GSSAPI_BNDLENGTH+4) /* Length of bind length, bind field, flags field. */ +#define GSS_C_DELEG_FLAG 1 /* MIT krb5 1.7beta3 (in Ubuntu Karmic) is missing the prototype, but still has the symbol */ @@ -1551,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:"", + 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 + /* * smb_krb5_principal_get_realm *