krb5 commit [krb5-1.19]: Implement fallback for GSS acceptor names

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/196be3c474881dcaf76332375c1dffbd3a9140f6
commit 196be3c474881dcaf76332375c1dffbd3a9140f6
Author: Greg Hudson <[email protected]>
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().
    
    (cherry picked from commit 7e0a2a7a3a76205ebd7192f06a99f23bad8dc5bd)
    
    ticket: 8971
    version_fixed: 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 edad910..cf52425 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -2126,6 +2126,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 2ffdf95..72652f2 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'])
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.