summaryrefslogtreecommitdiff
path: root/source4/auth/kerberos
diff options
context:
space:
mode:
Diffstat (limited to 'source4/auth/kerberos')
-rw-r--r--source4/auth/kerberos/kerberos.c397
-rw-r--r--source4/auth/kerberos/kerberos.h4
-rw-r--r--source4/auth/kerberos/kerberos_pac.c4
-rw-r--r--source4/auth/kerberos/kerberos_util.c15
-rw-r--r--source4/auth/kerberos/krb5_init_context.c2
-rw-r--r--source4/auth/kerberos/wscript_build2
6 files changed, 366 insertions, 58 deletions
diff --git a/source4/auth/kerberos/kerberos.c b/source4/auth/kerberos/kerberos.c
index 0db0dd3ced..0fc9d143ab 100644
--- a/source4/auth/kerberos/kerberos.c
+++ b/source4/auth/kerberos/kerberos.c
@@ -81,86 +81,387 @@
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)
+ 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 cc,
- krb5_principal principal, const char *password,
- krb5_principal impersonate_principal, const char *target_service,
+ 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_creds my_creds;
- krb5_creds *impersonate_creds;
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 we are not impersonating, then get this ticket for the
+ 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 */
- if ((code = krb5_get_init_creds_password(ctx, &my_creds, principal, password,
- NULL, NULL,
- 0,
- impersonate_principal ? NULL : target_service,
- krb_options))) {
+ * 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;
}
- if ((code = krb5_cc_initialize(ctx, cc, principal))) {
- krb5_free_cred_contents(ctx, &my_creds);
+ 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;
}
-
- if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) {
- krb5_free_cred_contents(ctx, &my_creds);
+
+ 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;
}
-
- if (expire_time) {
- *expire_time = (time_t) my_creds.times.endtime;
+
+ 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;
}
- if (kdc_time) {
- *kdc_time = (time_t) my_creds.times.starttime;
+ /*
+ * 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;
}
- krb5_free_cred_contents(ctx, &my_creds);
-
- if (code == 0 && impersonate_principal) {
- krb5_principal target_princ;
- if ((code = krb5_get_creds_opt_alloc(ctx, &options))) {
- return code;
- }
+ /*
+ * 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);
- if ((code = krb5_get_creds_opt_set_impersonate(ctx, options, impersonate_principal))) {
- krb5_get_creds_opt_free(ctx, options);
- return code;
- }
+ 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;
+ }
- if ((code = krb5_parse_name(ctx, target_service, &target_princ))) {
- krb5_get_creds_opt_free(ctx, options);
- 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;
+ }
- if ((code = krb5_principal_set_realm(ctx, target_princ, krb5_principal_get_realm(ctx, principal)))) {
- krb5_get_creds_opt_free(ctx, options);
- krb5_free_principal(ctx, target_princ);
- 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 ((code = krb5_get_creds(ctx, options, cc, target_princ, &impersonate_creds))) {
- krb5_free_principal(ctx, target_princ);
- krb5_get_creds_opt_free(ctx, options);
+ 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_cc_store_cred(ctx, cc, impersonate_creds);
+ 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_creds(ctx, impersonate_creds);
+ 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;
}
diff --git a/source4/auth/kerberos/kerberos.h b/source4/auth/kerberos/kerberos.h
index c712569e5d..31794b8e03 100644
--- a/source4/auth/kerberos/kerberos.h
+++ b/source4/auth/kerberos/kerberos.h
@@ -97,7 +97,9 @@ krb5_error_code ads_krb5_mk_req(krb5_context context,
bool get_auth_data_from_tkt(TALLOC_CTX *mem_ctx, DATA_BLOB *auth_data, krb5_ticket *tkt);
krb5_error_code kerberos_kinit_password_cc(krb5_context ctx, krb5_ccache cc,
krb5_principal principal, const char *password,
- krb5_principal impersonate_principal, const char *target_service,
+ 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 kerberos_kinit_keyblock_cc(krb5_context ctx, krb5_ccache cc,
diff --git a/source4/auth/kerberos/kerberos_pac.c b/source4/auth/kerberos/kerberos_pac.c
index 5bc80c9a58..8ce970e486 100644
--- a/source4/auth/kerberos/kerberos_pac.c
+++ b/source4/auth/kerberos/kerberos_pac.c
@@ -448,14 +448,14 @@ NTSTATUS kerberos_pac_blob_to_user_info_dc(TALLOC_CTX *mem_ctx,
pac_blob.data, pac_blob.length,
&pac);
if (ret) {
- return map_nt_error_from_unix(ret);
+ return map_nt_error_from_unix_common(ret);
}
ret = kerberos_pac_to_user_info_dc(mem_ctx, pac, context, user_info_dc, pac_srv_sig, pac_kdc_sig);
krb5_pac_free(context, pac);
if (ret) {
- return map_nt_error_from_unix(ret);
+ return map_nt_error_from_unix_common(ret);
}
return NT_STATUS_OK;
}
diff --git a/source4/auth/kerberos/kerberos_util.c b/source4/auth/kerberos/kerberos_util.c
index 45b0b07e13..9a48e95c6d 100644
--- a/source4/auth/kerberos/kerberos_util.c
+++ b/source4/auth/kerberos/kerberos_util.c
@@ -173,7 +173,7 @@ static krb5_error_code principals_from_msg(TALLOC_CTX *parent_ctx,
return ret;
}
- /* This song-and-dance effectivly puts the principal
+ /* This song-and-dance effectively puts the principal
* into talloc, so we can't loose it. */
talloc_set_destructor(principals[i], free_principal);
i++;
@@ -262,7 +262,7 @@ static krb5_error_code salt_principal_from_msg(TALLOC_CTX *parent_ctx,
upper_realm,
"host", salt_body, NULL);
if (ret == 0) {
- /* This song-and-dance effectivly puts the principal
+ /* This song-and-dance effectively puts the principal
* into talloc, so we can't loose it. */
mem_ctx->smb_krb5_context = talloc_reference(mem_ctx, smb_krb5_context);
mem_ctx->principal = *salt_princ;
@@ -338,7 +338,9 @@ krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
const char **error_string)
{
krb5_error_code ret;
- const char *password, *target_service;
+ const char *password;
+ const char *self_service;
+ const char *target_service;
time_t kdc_time = 0;
krb5_principal princ;
krb5_principal impersonate_principal;
@@ -363,6 +365,7 @@ krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
return ret;
}
+ self_service = cli_credentials_get_self_service(credentials);
target_service = cli_credentials_get_target_service(credentials);
password = cli_credentials_get_password(credentials);
@@ -403,7 +406,9 @@ krb5_error_code principal_from_credentials(TALLOC_CTX *parent_ctx,
if (password) {
ret = kerberos_kinit_password_cc(smb_krb5_context->krb5_context, ccache,
princ, password,
- impersonate_principal, target_service,
+ impersonate_principal,
+ self_service,
+ target_service,
krb_options,
NULL, &kdc_time);
} else if (impersonate_principal) {
@@ -733,7 +738,7 @@ static krb5_error_code remove_old_entries(TALLOC_CTX *parent_ctx,
/* Release the enumeration. We are going to
* have to start this from the top again,
* because deletes during enumeration may not
- * always be consistant.
+ * always be consistent.
*
* Also, the enumeration locks a FILE: keytab
*/
diff --git a/source4/auth/kerberos/krb5_init_context.c b/source4/auth/kerberos/krb5_init_context.c
index db3a5375c9..fbcaad29d9 100644
--- a/source4/auth/kerberos/krb5_init_context.c
+++ b/source4/auth/kerberos/krb5_init_context.c
@@ -415,7 +415,7 @@ smb_krb5_init_context_basic(TALLOC_CTX *tmp_ctx,
return ret;
}
- config_file = config_path(tmp_ctx, lp_ctx, "krb5.conf");
+ config_file = lpcfg_config_path(tmp_ctx, lp_ctx, "krb5.conf");
if (!config_file) {
krb5_free_context(krb5_ctx);
return ENOMEM;
diff --git a/source4/auth/kerberos/wscript_build b/source4/auth/kerberos/wscript_build
index 586366d422..90e8560573 100644
--- a/source4/auth/kerberos/wscript_build
+++ b/source4/auth/kerberos/wscript_build
@@ -4,7 +4,7 @@ bld.SAMBA_LIBRARY('authkrb5',
source='kerberos.c kerberos_heimdal.c kerberos_pac.c gssapi_parse.c krb5_init_context.c keytab_copy.c',
autoproto='proto.h',
public_deps='krb5 ndr-krb5pac samba_socket LIBCLI_RESOLVE com_err asn1',
- deps='asn1util auth_sam_reply tevent LIBPACKET ndr ldb KRB5_WRAP',
+ deps='asn1util auth_sam_reply tevent LIBPACKET ndr ldb KRB5_WRAP errors',
private_library=True
)