#define k5_prependmsg krb5_prepend_error_message
#define k5_wrapmsg krb5_wrap_error_message
+/*
+ * Like krb5_principal_compare(), but with canonicalization of sname if
+ * fallback is enabled. This function should be avoided if multiple matches
+ * are required, since repeated canonicalization is inefficient.
+ */
+krb5_boolean
+k5_sname_compare(krb5_context context, krb5_const_principal sname,
+ krb5_const_principal princ);
+
#endif /* _KRB5_INT_H */
#endif /* DISABLE_TRACING */
+#define TRACE_CC_CACHE_MATCH(c, princ, ret) \
+ TRACE(c, "Matching {princ} in collection with result: {kerr}", \
+ princ, ret)
#define TRACE_CC_DESTROY(c, cache) \
TRACE(c, "Destroying ccache {ccache}", cache)
#define TRACE_CC_GEN_NEW(c, cache) \
krb5_flags ap_req_options = 0;
krb5_enctype negotiated_etype;
krb5_authdata_context ad_context = NULL;
- krb5_principal accprinc = NULL;
krb5_ap_req *request = NULL;
code = krb5int_accessor (&kaccess, KRB5INT_ACCESS_VERSION);
}
}
- if (!cred->default_identity) {
- if ((code = kg_acceptor_princ(context, cred->name, &accprinc))) {
- major_status = GSS_S_FAILURE;
- goto fail;
- }
- }
-
- code = krb5_rd_req_decoded(context, &auth_context, request, accprinc,
- cred->keytab, &ap_req_options, NULL);
-
- krb5_free_principal(context, accprinc);
+ code = krb5_rd_req_decoded(context, &auth_context, request,
+ cred->acceptor_mprinc, cred->keytab,
+ &ap_req_options, NULL);
if (code) {
major_status = GSS_S_FAILURE;
goto fail;
/* Try to verify that keytab contains at least one entry for name. Return 0 if
* it does, KRB5_KT_NOTFOUND if it doesn't, or another error as appropriate. */
static krb5_error_code
-check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name)
+check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name,
+ krb5_principal mprinc)
{
krb5_error_code code;
krb5_keytab_entry ent;
- krb5_principal accprinc = NULL;
char *princname;
if (name->service == NULL) {
if (kt->ops->start_seq_get == NULL)
return 0;
- /* Get the partial principal for the acceptor name. */
- code = kg_acceptor_princ(context, name, &accprinc);
- if (code)
- return code;
-
- /* Scan the keytab for host-based entries matching accprinc. */
- code = k5_kt_have_match(context, kt, accprinc);
+ /* Scan the keytab for host-based entries matching mprinc. */
+ code = k5_kt_have_match(context, kt, mprinc);
if (code == KRB5_KT_NOTFOUND) {
- if (krb5_unparse_name(context, accprinc, &princname) == 0) {
+ if (krb5_unparse_name(context, mprinc, &princname) == 0) {
k5_setmsg(context, code, _("No key table entry found matching %s"),
princname);
free(princname);
}
}
- krb5_free_principal(context, accprinc);
return code;
}
}
if (cred->name != NULL) {
+ code = kg_acceptor_princ(context, cred->name, &cred->acceptor_mprinc);
+ if (code) {
+ major = GSS_S_FAILURE;
+ goto cleanup;
+ }
+
/* Make sure we have keys matching the desired name in the keytab. */
- code = check_keytab(context, kt, cred->name);
+ code = check_keytab(context, kt, cred->name, cred->acceptor_mprinc);
if (code) {
if (code == KRB5_KT_NOTFOUND) {
k5_change_error_message_code(context, code, KG_KEYTAB_NOMATCH);
can_get_initial_creds(krb5_context context, krb5_gss_cred_id_rec *cred)
{
krb5_error_code code;
- krb5_keytab_entry entry;
if (cred->password != NULL)
return TRUE;
if (cred->name == NULL)
return !krb5_kt_have_content(context, cred->client_keytab);
- /* Check if we have a keytab key for the client principal. */
- code = krb5_kt_get_entry(context, cred->client_keytab, cred->name->princ,
- 0, 0, &entry);
- if (code) {
- krb5_clear_error_message(context);
- return FALSE;
- }
- krb5_free_keytab_entry_contents(context, &entry);
- return TRUE;
+ /*
+ * Check if we have a keytab key for the client principal. This is a bit
+ * more permissive than we really want because krb5_kt_have_match()
+ * supports wildcarding and obeys ignore_acceptor_hostname, but that should
+ * generally be harmless.
+ */
+ code = k5_kt_have_match(context, cred->client_keytab, cred->name->princ);
+ return code == 0;
}
-/* Scan cred->ccache for name, expiry time, impersonator, refresh time. */
+/* Scan cred->ccache for name, expiry time, impersonator, refresh time. If
+ * check_name is true, verify the cache name against the credential name. */
static krb5_error_code
-scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred)
+scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred,
+ krb5_boolean check_name)
{
krb5_error_code code;
krb5_ccache ccache = cred->ccache;
if (code)
return code;
- /* Credentials cache principal must match the initiator name. */
code = krb5_cc_get_principal(context, ccache, &ccache_princ);
if (code != 0)
goto cleanup;
- if (cred->name != NULL &&
- !krb5_principal_compare(context, ccache_princ, cred->name->princ)) {
- code = KG_CCACHE_NOMATCH;
- goto cleanup;
- }
- /* Save the ccache principal as the credential name if not already set. */
- if (!cred->name) {
+ if (cred->name == NULL) {
+ /* Save the ccache principal as the credential name. */
code = kg_init_name(context, ccache_princ, NULL, NULL, NULL,
KG_INIT_NAME_NO_COPY, &cred->name);
if (code)
goto cleanup;
ccache_princ = NULL;
+ } else {
+ /* Check against the desired name if needed. */
+ if (check_name) {
+ if (!k5_sname_compare(context, cred->name->princ, ccache_princ)) {
+ code = KG_CCACHE_NOMATCH;
+ goto cleanup;
+ }
+ }
+
+ /* Replace the credential name principal with the canonical client
+ * principal, retaining acceptor_mprinc if set. */
+ krb5_free_principal(context, cred->name->princ);
+ cred->name->princ = ccache_princ;
+ ccache_princ = NULL;
}
assert(cred->name->princ != NULL);
assert(cred->name != NULL && cred->ccache == NULL);
#ifdef USE_LEASH
code = get_ccache_leash(context, cred->name->princ, &cred->ccache);
- return code ? code : scan_ccache(context, cred);
+ return code ? code : scan_ccache(context, cred, TRUE);
#else
/* Check first whether we can acquire tickets, to avoid overwriting the
* extended error message from krb5_cc_cache_match. */
/* Look for an existing cache for the client principal. */
code = krb5_cc_cache_match(context, cred->name->princ, &cred->ccache);
if (code == 0)
- return scan_ccache(context, cred);
+ return scan_ccache(context, cred, FALSE);
if (code != KRB5_CC_NOTFOUND || !can_get)
return code;
krb5_clear_error_message(context);
kg_cred_set_initial_refresh(context, cred, &creds.times);
cred->have_tgt = TRUE;
cred->expire = creds.times.endtime;
+
+ /* Steal the canonical client principal name from creds and save it in the
+ * credential name, retaining acceptor_mprinc if set. */
+ krb5_free_principal(context, cred->name->princ);
+ cred->name->princ = creds.client;
+ creds.client = NULL;
+
krb5_free_cred_contents(context, &creds);
cleanup:
krb5_get_init_creds_opt_free(context, opt);
if (cred->ccache != NULL) {
/* The caller specified a ccache; check what's in it. */
- code = scan_ccache(context, cred);
+ code = scan_ccache(context, cred, TRUE);
if (code == KRB5_FCC_NOFILE) {
/* See if we can get initial creds. If the caller didn't specify
* a name, pick one from the client keytab. */
}
}
if (cred->ccache != NULL) {
- code = scan_ccache(context, cred);
+ code = scan_ccache(context, cred, FALSE);
if (code)
goto kerr;
}
code = krb5int_cc_default(context, &cred->ccache);
if (code)
goto kerr;
- code = scan_ccache(context, cred);
+ code = scan_ccache(context, cred, FALSE);
if (code == KRB5_FCC_NOFILE) {
/* Default ccache doesn't exist; fall through to client keytab. */
krb5_cc_close(context, cred->ccache);
/* name/type of credential */
gss_cred_usage_t usage;
krb5_gss_name_t name;
+ krb5_principal acceptor_mprinc;
krb5_principal impersonator;
unsigned int default_identity : 1;
unsigned int iakerb_mech : 1;
if (cred->name)
kg_release_name(context, &cred->name);
+ krb5_free_principal(context, cred->acceptor_mprinc);
krb5_free_principal(context, cred->impersonator);
if (cred->req_enctypes)
#include "cc-int.h"
#include "../krb/int-proto.h"
+#include "../os/os-proto.h"
#include <assert.h>
return 0;
}
-krb5_error_code KRB5_CALLCONV
-krb5_cc_cache_match(krb5_context context, krb5_principal client,
- krb5_ccache *cache_out)
+static krb5_error_code
+match_caches(krb5_context context, krb5_const_principal client,
+ krb5_ccache *cache_out)
{
krb5_error_code ret;
krb5_cccol_cursor cursor;
krb5_ccache cache = NULL;
krb5_principal princ;
- char *name;
krb5_boolean eq;
*cache_out = NULL;
+
ret = krb5_cccol_cursor_new(context, &cursor);
if (ret)
return ret;
krb5_cc_close(context, cache);
}
krb5_cccol_cursor_free(context, &cursor);
+
if (ret)
return ret;
- if (cache == NULL) {
- ret = krb5_unparse_name(context, client, &name);
- if (ret == 0) {
- k5_setmsg(context, KRB5_CC_NOTFOUND,
+ if (cache == NULL)
+ return KRB5_CC_NOTFOUND;
+
+ *cache_out = cache;
+ return 0;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_cc_cache_match(krb5_context context, krb5_principal client,
+ krb5_ccache *cache_out)
+{
+ krb5_error_code ret;
+ struct canonprinc iter = { client, .subst_defrealm = TRUE };
+ krb5_const_principal canonprinc = NULL;
+ krb5_ccache cache = NULL;
+ char *name;
+
+ *cache_out = NULL;
+
+ while ((ret = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
+ canonprinc != NULL) {
+ ret = match_caches(context, canonprinc, &cache);
+ if (ret != KRB5_CC_NOTFOUND)
+ break;
+ }
+ free_canonprinc(&iter);
+
+ if (ret == 0 && canonprinc == NULL) {
+ ret = KRB5_CC_NOTFOUND;
+ if (krb5_unparse_name(context, client, &name) == 0) {
+ k5_setmsg(context, ret,
_("Can't find client principal %s in cache collection"),
name);
krb5_free_unparsed_name(context, name);
}
- ret = KRB5_CC_NOTFOUND;
- } else
- *cache_out = cache;
- return ret;
+ }
+
+ TRACE_CC_CACHE_MATCH(context, client, ret);
+ if (ret)
+ return ret;
+
+ *cache_out = cache;
+ return 0;
}
/* Store the error state for code from context into errsave, but only if code
k5_size_context
k5_size_keyblock
k5_size_principal
+k5_sname_compare
k5_unmarshal_cred
k5_unmarshal_princ
k5_unwrap_cammac_svc
/* If we're not doing fallback, the input principal is canonical. */
if (context->dns_canonicalize_hostname != CANONHOST_FALLBACK ||
- iter->princ->type != KRB5_NT_SRV_HST || iter->princ->length != 2) {
+ iter->princ->type != KRB5_NT_SRV_HST || iter->princ->length != 2 ||
+ iter->princ->data[1].length == 0) {
*princ_out = (step == 1) ? iter->princ : NULL;
return 0;
}
return canonicalize_princ(context, iter, step == 2, princ_out);
}
+krb5_boolean
+k5_sname_compare(krb5_context context, krb5_const_principal sname,
+ krb5_const_principal princ)
+{
+ krb5_error_code ret;
+ struct canonprinc iter = { sname, .subst_defrealm = TRUE };
+ krb5_const_principal canonprinc = NULL;
+ krb5_boolean match = FALSE;
+
+ while ((ret = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
+ canonprinc != NULL) {
+ if (krb5_principal_compare(context, canonprinc, princ)) {
+ match = TRUE;
+ break;
+ }
+ }
+ free_canonprinc(&iter);
+ return match;
+}
+
krb5_error_code KRB5_CALLCONV
krb5_sname_to_principal(krb5_context context, const char *hostname,
const char *sname, krb5_int32 type,
; new in 1.20
krb5_marshal_credentials @472
krb5_unmarshal_credentials @473
+ k5_sname_compare @474 ; PRIVATE GSSAPI
'/Matching credential not found')
realm.run(['./t_ccselect', phost], expected_code=1,
expected_msg='Ticket expired', expected_trace=msgs)
+realm.run([kdestroy, '-A'])
+
+# Test 19: host-based initiator name
+mark('host-based initiator name')
+hsvc = 'h:svc@' + hostname
+svcprinc = 'svc/%s@%s' % (hostname, realm.realm)
+realm.addprinc(svcprinc)
+realm.extract_keytab(svcprinc, realm.client_keytab)
+# On the first run we match against the keytab while getting tickets,
+# substituting the default realm.
+msgs = ('/Can\'t find client principal svc/%s@ in' % hostname,
+ 'Getting initial credentials for svc/%s@' % hostname,
+ 'Found entries for %s in keytab' % svcprinc,
+ 'Retrieving %s from FILE:%s' % (svcprinc, realm.client_keytab),
+ 'Storing %s -> %s in' % (svcprinc, realm.krbtgt_princ),
+ 'Retrieving %s -> %s from' % (svcprinc, realm.krbtgt_princ),
+ 'authenticator for %s -> %s' % (svcprinc, realm.host_princ))
+realm.run(['./t_ccselect', phost, hsvc], expected_trace=msgs)
+# On the second run we match against the collection.
+msgs = ('Matching svc/%s@ in collection with result: 0' % hostname,
+ 'Getting credentials %s -> %s' % (svcprinc, realm.host_princ),
+ 'authenticator for %s -> %s' % (svcprinc, realm.host_princ))
+realm.run(['./t_ccselect', phost, hsvc], expected_trace=msgs)
+realm.run([kdestroy, '-A'])
+
+# Test 20: host-based initiator name with fallback
+mark('host-based fallback initiator name')
+canonname = canonicalize_hostname(hostname)
+if canonname != hostname:
+ hfsvc = 'h:fsvc@' + hostname
+ canonprinc = 'fsvc/%s@%s' % (canonname, realm.realm)
+ realm.addprinc(canonprinc)
+ realm.extract_keytab(canonprinc, realm.client_keytab)
+ msgs = ('/Can\'t find client principal fsvc/%s@ in' % hostname,
+ 'Found entries for %s in keytab' % canonprinc,
+ 'authenticator for %s -> %s' % (canonprinc, realm.host_princ))
+ realm.run(['./t_ccselect', phost, hfsvc], expected_trace=msgs)
+ msgs = ('Matching fsvc/%s@ in collection with result: 0' % hostname,
+ 'Getting credentials %s -> %s' % (canonprinc, realm.host_princ))
+ realm.run(['./t_ccselect', phost, hfsvc], expected_trace=msgs)
+ realm.run([kdestroy, '-A'])
+else:
+ skipped('GSS initiator name fallback test',
+ '%s does not canonicalize to a different name' % hostname)
success('Client keytab tests')
realm.run(['./t_credstore', '-s', 'p:' + service_cs, 'ccache', storagecache,
'keytab', servicekeytab], expected_trace=msgs)
+mark('matching')
+scc = 'FILE:' + os.path.join(realm.testdir, 'service_cache')
+realm.kinit(realm.host_princ, flags=['-k', '-c', scc])
+realm.run(['./t_credstore', '-i', 'p:' + realm.host_princ, 'ccache', scc])
+realm.run(['./t_credstore', '-i', 'h:host', 'ccache', scc])
+realm.run(['./t_credstore', '-i', 'h:host@' + hostname, 'ccache', scc])
+realm.run(['./t_credstore', '-i', 'p:wrong', 'ccache', scc],
+ expected_code=1, expected_msg='does not match desired name')
+realm.run(['./t_credstore', '-i', 'h:host@-nomatch-', 'ccache', scc],
+ expected_code=1, expected_msg='does not match desired name')
+realm.run(['./t_credstore', '-i', 'h:svc', 'ccache', scc],
+ expected_code=1, expected_msg='does not match desired name')
+
+mark('matching (fallback)')
+canonname = canonicalize_hostname(hostname)
+if canonname != hostname:
+ canonprinc = 'host/%s@%s' % (canonname, realm.realm)
+ realm.addprinc(canonprinc)
+ realm.extract_keytab(canonprinc, realm.keytab)
+ realm.kinit(canonprinc, flags=['-k', '-c', scc])
+ realm.run(['./t_credstore', '-i', 'h:host', 'ccache', scc])
+ realm.run(['./t_credstore', '-i', 'h:host@' + hostname, 'ccache', scc])
+ realm.run(['./t_credstore', '-i', 'h:host@' + canonname, 'ccache', scc])
+ realm.run(['./t_credstore', '-i', 'p:' + canonprinc, 'ccache', scc])
+ realm.run(['./t_credstore', '-i', 'p:' + realm.host_princ, 'ccache', scc],
+ expected_code=1, expected_msg='does not match desired name')
+ realm.run(['./t_credstore', '-i', 'h:host@-nomatch-', 'ccache', scc],
+ expected_code=1, expected_msg='does not match desired name')
+else:
+ skipped('fallback matching test',
+ '%s does not canonicalize to a different name' % hostname)
+
mark('rcache')
# t_credstore -r should produce a replay error normally, but not with
# rcache set to "none:".