krb5 commit: Implement fallback for GSS acceptor names
Greg Hudson
ghudson at mit.edu
Thu Jan 7 16:02:58 EST 2021
https://github.com/krb5/krb5/commit/7e0a2a7a3a76205ebd7192f06a99f23bad8dc5bd
commit 7e0a2a7a3a76205ebd7192f06a99f23bad8dc5bd
Author: Greg Hudson <ghudson at mit.edu>
Date: Mon Dec 28 15:41:46 2020 -0500
Implement fallback for GSS acceptor names
Commit 3fcc365a6f049730b3f47168f7112c03997c5c0b added fallback support
to krb5_rd_req(), but acquiring acceptor creds for a host-based name
could still fail within check_keytab() in the krb5 mech.
Add an internal libkrb5 API k5_kt_have_match() to check for a matching
keytab entry with canonicalization, and use it in check_keytab(). Add
a library-internal function k5_sname_wildcard_host() to share logic
between rd_req and k5_kt_have_match().
ticket: 8971 (new)
tags: pullup
target_version: 1.19
src/include/k5-int.h | 3 ++
src/lib/gssapi/krb5/acquire_cred.c | 18 +-----------
src/lib/krb5/keytab/ktfns.c | 49 ++++++++++++++++++++++++++++++++++++
src/lib/krb5/krb/int-proto.h | 5 +++
src/lib/krb5/krb/rd_req_dec.c | 6 +---
src/lib/krb5/krb/sname_match.c | 13 +++++++++
src/lib/krb5/libkrb5.exports | 1 +
src/lib/krb5_32.def | 1 +
src/tests/gssapi/t_gssapi.py | 24 +++++++++++++++++-
9 files changed, 99 insertions(+), 21 deletions(-)
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index cfbb639..ab0a565 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -2122,6 +2122,9 @@ krb5_error_code KRB5_CALLCONV krb5_kt_register(krb5_context,
krb5_error_code k5_kt_get_principal(krb5_context context, krb5_keytab keytab,
krb5_principal *princ_out);
+krb5_error_code k5_kt_have_match(krb5_context context, krb5_keytab keytab,
+ krb5_principal mprinc);
+
krb5_error_code krb5_principal2salt_norealm(krb5_context, krb5_const_principal,
krb5_data *);
diff --git a/src/lib/gssapi/krb5/acquire_cred.c b/src/lib/gssapi/krb5/acquire_cred.c
index 91a22dd..632ee7d 100644
--- a/src/lib/gssapi/krb5/acquire_cred.c
+++ b/src/lib/gssapi/krb5/acquire_cred.c
@@ -127,9 +127,7 @@ check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name)
{
krb5_error_code code;
krb5_keytab_entry ent;
- krb5_kt_cursor cursor;
krb5_principal accprinc = NULL;
- krb5_boolean match;
char *princname;
if (name->service == NULL) {
@@ -149,26 +147,14 @@ check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name)
return code;
/* Scan the keytab for host-based entries matching accprinc. */
- code = krb5_kt_start_seq_get(context, kt, &cursor);
- if (code)
- goto cleanup;
- while ((code = krb5_kt_next_entry(context, kt, &ent, &cursor)) == 0) {
- match = krb5_sname_match(context, accprinc, ent.principal);
- (void)krb5_free_keytab_entry_contents(context, &ent);
- if (match)
- break;
- }
- (void)krb5_kt_end_seq_get(context, kt, &cursor);
- if (code == KRB5_KT_END) {
- code = KRB5_KT_NOTFOUND;
+ code = k5_kt_have_match(context, kt, accprinc);
+ if (code == KRB5_KT_NOTFOUND) {
if (krb5_unparse_name(context, accprinc, &princname) == 0) {
k5_setmsg(context, code, _("No key table entry found matching %s"),
princname);
free(princname);
}
}
-
-cleanup:
krb5_free_principal(context, accprinc);
return code;
}
diff --git a/src/lib/krb5/keytab/ktfns.c b/src/lib/krb5/keytab/ktfns.c
index 7945253..d6658b3 100644
--- a/src/lib/krb5/keytab/ktfns.c
+++ b/src/lib/krb5/keytab/ktfns.c
@@ -31,6 +31,8 @@
#ifndef LEAN_CLIENT
#include "k5-int.h"
+#include "../krb/int-proto.h"
+#include "../os/os-proto.h"
const char * KRB5_CALLCONV
krb5_kt_get_type (krb5_context context, krb5_keytab keytab)
@@ -129,6 +131,53 @@ no_entries:
return KRB5_KT_NOTFOUND;
}
+static krb5_error_code
+match_entries(krb5_context context, krb5_keytab keytab,
+ krb5_const_principal mprinc)
+{
+ krb5_error_code ret;
+ krb5_keytab_entry ent;
+ krb5_kt_cursor cursor;
+ krb5_boolean match;
+
+ /* Scan the keytab for host-based entries matching accprinc. */
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret)
+ return ret;
+ while ((ret = krb5_kt_next_entry(context, keytab, &ent, &cursor)) == 0) {
+ match = krb5_sname_match(context, mprinc, ent.principal);
+ (void)krb5_free_keytab_entry_contents(context, &ent);
+ if (match)
+ break;
+ }
+ (void)krb5_kt_end_seq_get(context, keytab, &cursor);
+ if (ret && ret != KRB5_KT_END)
+ return ret;
+ return match ? 0 : KRB5_KT_NOTFOUND;
+}
+
+krb5_error_code
+k5_kt_have_match(krb5_context context, krb5_keytab keytab,
+ krb5_principal mprinc)
+{
+ krb5_error_code ret;
+ struct canonprinc iter = { mprinc, .no_hostrealm = TRUE };
+ krb5_const_principal canonprinc = NULL;
+
+ /* Don't try to canonicalize if we're going to ignore hostnames. */
+ if (k5_sname_wildcard_host(context, mprinc))
+ return match_entries(context, keytab, mprinc);
+
+ while ((ret = k5_canonprinc(context, &iter, &canonprinc)) == 0 &&
+ canonprinc != NULL) {
+ ret = match_entries(context, keytab, canonprinc);
+ if (ret != KRB5_KT_NOTFOUND)
+ break;
+ }
+ free_canonprinc(&iter);
+ return (ret == 0 && canonprinc == NULL) ? KRB5_KT_NOTFOUND : ret;
+}
+
/*
* In a couple of places we need to get a principal name from a keytab: when
* verifying credentials against a keytab, and when querying the name of a
diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h
index f2a2a3c..2fde08d 100644
--- a/src/lib/krb5/krb/int-proto.h
+++ b/src/lib/krb5/krb/int-proto.h
@@ -386,4 +386,9 @@ k5_get_proxy_cred_from_kdc(krb5_context context, krb5_flags options,
krb5_ccache ccache, krb5_creds *in_creds,
krb5_creds **out_creds);
+/* Return true if mprinc will match any hostname in a host-based principal name
+ * (possibly due to ignore_acceptor_hostname) with krb5_sname_match(). */
+krb5_boolean
+k5_sname_wildcard_host(krb5_context context, krb5_const_principal mprinc);
+
#endif /* KRB5_INT_FUNC_PROTO__ */
diff --git a/src/lib/krb5/krb/rd_req_dec.c b/src/lib/krb5/krb/rd_req_dec.c
index 013ca90..f37a360 100644
--- a/src/lib/krb5/krb/rd_req_dec.c
+++ b/src/lib/krb5/krb/rd_req_dec.c
@@ -451,10 +451,8 @@ decrypt_ticket(krb5_context context, const krb5_ap_req *req,
struct canonprinc iter = { server, .no_hostrealm = TRUE };
krb5_const_principal canonprinc;
- /* Don't try to canonicalize if we're going to ignore the hostname, or if
- * server is null or has a wildcard hostname. */
- if (context->ignore_acceptor_hostname || server == NULL ||
- (server->length == 2 && server->data[1].length == 0))
+ /* Don't try to canonicalize if we're going to ignore hostnames. */
+ if (k5_sname_wildcard_host(context, server))
return decrypt_try_server(context, req, server, keytab, keyblock_out);
/* Try each canonicalization candidate for server. If they all fail,
diff --git a/src/lib/krb5/krb/sname_match.c b/src/lib/krb5/krb/sname_match.c
index 9520dfc..55a4b7d 100644
--- a/src/lib/krb5/krb/sname_match.c
+++ b/src/lib/krb5/krb/sname_match.c
@@ -25,6 +25,7 @@
*/
#include "k5-int.h"
+#include "int-proto.h"
krb5_boolean KRB5_CALLCONV
krb5_sname_match(krb5_context context, krb5_const_principal matching,
@@ -55,3 +56,15 @@ krb5_sname_match(krb5_context context, krb5_const_principal matching,
/* All elements match. */
return TRUE;
}
+
+krb5_boolean
+k5_sname_wildcard_host(krb5_context context, krb5_const_principal mprinc)
+{
+ if (mprinc == NULL)
+ return TRUE;
+
+ if (mprinc->type != KRB5_NT_SRV_HST || mprinc->length != 2)
+ return FALSE;
+
+ return context->ignore_acceptor_hostname || mprinc->data[1].length == 0;
+}
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index 05fbf98..32cef6a 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -159,6 +159,7 @@ k5_internalize_keyblock
k5_internalize_principal
k5_is_string_numeric
k5_kt_get_principal
+k5_kt_have_match
k5_localauth_free_context
k5_locate_kdc
k5_marshal_cred
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
index de5823c..4953907 100644
--- a/src/lib/krb5_32.def
+++ b/src/lib/krb5_32.def
@@ -502,3 +502,4 @@ EXPORTS
; new in 1.19
k5_cc_store_primary_cred @470 ; PRIVATE
+ k5_kt_have_match @471 ; PRIVATE GSSAPI
diff --git a/src/tests/gssapi/t_gssapi.py b/src/tests/gssapi/t_gssapi.py
index ff2e248..1af6f31 100755
--- a/src/tests/gssapi/t_gssapi.py
+++ b/src/tests/gssapi/t_gssapi.py
@@ -8,8 +8,9 @@ for realm in multipass_realms():
realm.run(['./t_iov', '-s', 'p:' + realm.host_princ])
realm.run(['./t_pcontok', 'p:' + realm.host_princ])
+realm = K5Realm(krb5_conf={'libdefaults': {'rdns': 'false'}})
+
# Test gss_add_cred().
-realm = K5Realm()
realm.run(['./t_add_cred'])
### Test acceptor name behavior.
@@ -60,6 +61,27 @@ realm.run(['./t_accname', 'p:host/-nomatch-',
'h:host@%s' % socket.gethostname()], expected_code=1,
expected_msg=' not found in keytab')
+# If possible, test with an acceptor name requiring fallback to match
+# against a keytab entry. Forward-canonicalize the hostname, relying
+# on the rdns=false realm setting.
+try:
+ ai = socket.getaddrinfo(hostname, None, 0, 0, 0, socket.AI_CANONNAME)
+ (family, socktype, proto, canonname, sockaddr) = ai[0]
+except socket.gaierror:
+ canonname = hostname
+if canonname != hostname:
+ os.rename(realm.keytab, realm.keytab + '.save')
+ canonprinc = 'host/' + canonname
+ realm.run([kadminl, 'addprinc', '-randkey', canonprinc])
+ realm.extract_keytab(canonprinc, realm.keytab)
+ # Use the canonical name for the initiator's target name, since
+ # host/hostname exists in the KDB (but not the keytab).
+ realm.run(['./t_accname', 'h:host@' + canonname, 'h:host@' + hostname])
+ os.rename(realm.keytab + '.save', realm.keytab)
+else:
+ skipped('GSS acceptor name fallback test',
+ '%s does not canonicalize to a different name' % hostname)
+
# Test krb5_gss_import_cred.
realm.run(['./t_imp_cred', 'p:service1/barack'])
realm.run(['./t_imp_cred', 'p:service1/barack', 'service1/barack'])
More information about the cvs-krb5
mailing list