From 196be3c474881dcaf76332375c1dffbd3a9140f6 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Mon, 28 Dec 2020 15:41:46 -0500 Subject: 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']) -- cgit v1.1