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