diff --git a/Add-APIs-for-marshalling-credentials.patch b/Add-APIs-for-marshalling-credentials.patch new file mode 100644 index 0000000..39b3455 --- /dev/null +++ b/Add-APIs-for-marshalling-credentials.patch @@ -0,0 +1,220 @@ +From fd3ffdf173173e08abfe9ba78922f63723541c54 Mon Sep 17 00:00:00 2001 +From: Robbie Harwood +Date: Thu, 14 Jan 2021 18:13:09 -0500 +Subject: [PATCH] Add APIs for marshalling credentials + +Faciliate KCM daemon implementations by providing functions to +deserialize and reserialize credentials in the FILE v4 format. + +[ghudson@mit.edu: minor editorial changes] + +ticket: 8980 (new) +(cherry picked from commit 18ea3bd2fca55b789b7de9c663624bc11d348fa6) +--- + doc/appdev/refs/api/index.rst | 2 ++ + src/include/krb5/krb5.hin | 36 ++++++++++++++++++++++ + src/lib/krb5/ccache/ccmarshal.c | 53 +++++++++++++++++++++++++++++++++ + src/lib/krb5/ccache/t_marshal.c | 15 +++++++++- + src/lib/krb5/libkrb5.exports | 2 ++ + src/lib/krb5_32.def | 4 +++ + 6 files changed, 111 insertions(+), 1 deletion(-) + +diff --git a/doc/appdev/refs/api/index.rst b/doc/appdev/refs/api/index.rst +index 727d9b492..9e03fd386 100644 +--- a/doc/appdev/refs/api/index.rst ++++ b/doc/appdev/refs/api/index.rst +@@ -232,6 +232,7 @@ Rarely used public interfaces + krb5_kt_remove_entry.rst + krb5_kt_start_seq_get.rst + krb5_make_authdata_kdc_issued.rst ++ krb5_marshal_credentials.rst + krb5_merge_authdata.rst + krb5_mk_1cred.rst + krb5_mk_error.rst +@@ -285,6 +286,7 @@ Rarely used public interfaces + krb5_tkt_creds_get_times.rst + krb5_tkt_creds_init.rst + krb5_tkt_creds_step.rst ++ krb5_unmarshal_credentials.rst + krb5_verify_init_creds.rst + krb5_verify_init_creds_opt_init.rst + krb5_verify_init_creds_opt_set_ap_req_nofail.rst +diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin +index 63e67a2ba..c26dde535 100644 +--- a/src/include/krb5/krb5.hin ++++ b/src/include/krb5/krb5.hin +@@ -3125,6 +3125,42 @@ krb5_get_credentials(krb5_context context, krb5_flags options, + krb5_ccache ccache, krb5_creds *in_creds, + krb5_creds **out_creds); + ++/** ++ * Serialize a @c krb5_creds object. ++ * ++ * @param [in] context Library context ++ * @param [in] creds The credentials object to serialize ++ * @param [out] data_out The serialized credentials ++ * ++ * Serialize @a creds in the format used by the FILE ccache format (vesion 4) ++ * and KCM ccache protocol. ++ * ++ * Use krb5_free_data() to free @a data_out when it is no longer needed. ++ * ++ * @retval 0 Success; otherwise - Kerberos error codes ++ */ ++krb5_error_code KRB5_CALLCONV ++krb5_marshal_credentials(krb5_context context, krb5_creds *in_creds, ++ krb5_data **data_out); ++ ++/** ++ * Deserialize a @c krb5_creds object. ++ * ++ * @param [in] context Library context ++ * @param [in] data The serialized credentials ++ * @param [out] creds_out The resulting creds object ++ * ++ * Deserialize @a data to credentials in the format used by the FILE ccache ++ * format (vesion 4) and KCM ccache protocol. ++ * ++ * Use krb5_free_creds() to free @a creds_out when it is no longer needed. ++ * ++ * @retval 0 Success; otherwise - Kerberos error codes ++ */ ++krb5_error_code KRB5_CALLCONV ++krb5_unmarshal_credentials(krb5_context context, const krb5_data *data, ++ krb5_creds **creds_out); ++ + /** @deprecated Replaced by krb5_get_validated_creds. */ + krb5_error_code KRB5_CALLCONV + krb5_get_credentials_validate(krb5_context context, krb5_flags options, +diff --git a/src/lib/krb5/ccache/ccmarshal.c b/src/lib/krb5/ccache/ccmarshal.c +index ae634ccab..ab284e721 100644 +--- a/src/lib/krb5/ccache/ccmarshal.c ++++ b/src/lib/krb5/ccache/ccmarshal.c +@@ -515,3 +515,56 @@ k5_marshal_mcred(struct k5buf *buf, krb5_creds *mcred) + if (mcred->second_ticket.length > 0) + put_data(buf, version, &mcred->second_ticket); + } ++ ++krb5_error_code KRB5_CALLCONV ++krb5_marshal_credentials(krb5_context context, krb5_creds *in_creds, ++ krb5_data **data_out) ++{ ++ krb5_error_code ret; ++ krb5_data *data; ++ struct k5buf buf; ++ ++ *data_out = NULL; ++ ++ data = k5alloc(sizeof(krb5_data), &ret); ++ if (ret) ++ return ret; ++ ++ k5_buf_init_dynamic(&buf); ++ k5_marshal_cred(&buf, 4, in_creds); ++ ++ ret = k5_buf_status(&buf); ++ if (ret) { ++ free(data); ++ return ret; ++ } ++ ++ /* Steal payload from buf. */ ++ *data = make_data(buf.data, buf.len); ++ *data_out = data; ++ return 0; ++} ++ ++krb5_error_code KRB5_CALLCONV ++krb5_unmarshal_credentials(krb5_context context, const krb5_data *data, ++ krb5_creds **creds_out) ++{ ++ krb5_error_code ret; ++ krb5_creds *creds; ++ ++ *creds_out = NULL; ++ ++ creds = k5alloc(sizeof(krb5_creds), &ret); ++ if (ret) ++ return ret; ++ ++ ret = k5_unmarshal_cred((unsigned char *)data->data, data->length, 4, ++ creds); ++ if (ret) { ++ free(creds); ++ return ret; ++ } ++ ++ *creds_out = creds; ++ return 0; ++} +diff --git a/src/lib/krb5/ccache/t_marshal.c b/src/lib/krb5/ccache/t_marshal.c +index bd0284afa..96e0931a2 100644 +--- a/src/lib/krb5/ccache/t_marshal.c ++++ b/src/lib/krb5/ccache/t_marshal.c +@@ -268,13 +268,14 @@ main(int argc, char **argv) + krb5_context context; + krb5_ccache cache; + krb5_principal princ; +- krb5_creds cred1, cred2; ++ krb5_creds cred1, cred2, *alloc_cred; + krb5_cc_cursor cursor; + const char *filename; + char *ccname, filebuf[256]; + int version, fd; + const struct test *t; + struct k5buf buf; ++ krb5_data ser_data, *alloc_data; + + if (argc != 2) + abort(); +@@ -285,6 +286,18 @@ main(int argc, char **argv) + if (krb5_init_context(&context) != 0) + abort(); + ++ /* Test public functions for unmarshalling and marshalling. */ ++ ser_data = make_data((char *)tests[3].cred1, tests[3].cred1len); ++ if (krb5_unmarshal_credentials(context, &ser_data, &alloc_cred) != 0) ++ abort(); ++ verify_cred1(alloc_cred); ++ if (krb5_marshal_credentials(context, alloc_cred, &alloc_data) != 0) ++ abort(); ++ assert(alloc_data->length == tests[3].cred1len); ++ assert(memcmp(tests[3].cred1, alloc_data->data, alloc_data->length) == 0); ++ krb5_free_data(context, alloc_data); ++ krb5_free_creds(context, alloc_cred); ++ + for (version = FIRST_VERSION; version <= 4; version++) { + t = &tests[version - 1]; + +diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports +index 72652f2ce..9de0fcdb3 100644 +--- a/src/lib/krb5/libkrb5.exports ++++ b/src/lib/krb5/libkrb5.exports +@@ -489,6 +489,7 @@ krb5_lock_file + krb5_make_authdata_kdc_issued + krb5_make_full_ipaddr + krb5_make_fulladdr ++krb5_marshal_credentials + krb5_mcc_ops + krb5_merge_authdata + krb5_mk_1cred +@@ -591,6 +592,7 @@ krb5_timeofday + krb5_timestamp_to_sfstring + krb5_timestamp_to_string + krb5_unlock_file ++krb5_unmarshal_credentials + krb5_unpack_full_ipaddr + krb5_unparse_name + krb5_unparse_name_ext +diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def +index 4953907aa..60b8dd311 100644 +--- a/src/lib/krb5_32.def ++++ b/src/lib/krb5_32.def +@@ -503,3 +503,7 @@ EXPORTS + ; new in 1.19 + k5_cc_store_primary_cred @470 ; PRIVATE + k5_kt_have_match @471 ; PRIVATE GSSAPI ++ ++; new in 1.20 ++ krb5_marshal_credentials @472 ++ krb5_unmarshal_credentials @473 diff --git a/Add-hostname-canonicalization-helper-to-k5test.py.patch b/Add-hostname-canonicalization-helper-to-k5test.py.patch new file mode 100644 index 0000000..93d3963 --- /dev/null +++ b/Add-hostname-canonicalization-helper-to-k5test.py.patch @@ -0,0 +1,84 @@ +From 3204462c480484845513f2d7f323e367efde62cd Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Fri, 15 Jan 2021 14:43:34 -0500 +Subject: [PATCH] Add hostname canonicalization helper to k5test.py + +To facilitate fallback tests, add a canonicalize_hostname() function +to k5test.py which works similarly to krb5_expand_hostname(). Use it +in t_gssapi.py for the recently-added acceptor name fallback test. + +(cherry picked from commit 225fffe4e912772acea3a01d45bafb60bfb80948) +--- + src/tests/gssapi/t_gssapi.py | 11 +++-------- + src/util/k5test.py | 22 ++++++++++++++++++++++ + 2 files changed, 25 insertions(+), 8 deletions(-) + +diff --git a/src/tests/gssapi/t_gssapi.py b/src/tests/gssapi/t_gssapi.py +index 1af6f31c2..e22cec427 100755 +--- a/src/tests/gssapi/t_gssapi.py ++++ b/src/tests/gssapi/t_gssapi.py +@@ -8,7 +8,7 @@ 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'}}) ++realm = K5Realm() + + # Test gss_add_cred(). + realm.run(['./t_add_cred']) +@@ -62,13 +62,8 @@ realm.run(['./t_accname', 'p:host/-nomatch-', + 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 ++# against a keytab entry. ++canonname = canonicalize_hostname(hostname) + if canonname != hostname: + os.rename(realm.keytab, realm.keytab + '.save') + canonprinc = 'host/' + canonname +diff --git a/src/util/k5test.py b/src/util/k5test.py +index 789b0f4b9..251d11a9d 100644 +--- a/src/util/k5test.py ++++ b/src/util/k5test.py +@@ -155,6 +155,10 @@ Scripts may use the following functions and variables: + * password(name): Return a weakly random password based on name. The + password will be consistent across calls with the same name. + ++* canonicalize_hostname(name, rdns=True): Return the DNS ++ canonicalization of name, optionally using reverse DNS. On error, ++ return name converted to lowercase. ++ + * stop_daemon(proc): Stop a daemon process started with + realm.start_server() or realm.start_in_inetd(). Only necessary if + the port needs to be reused; daemon processes will be stopped +@@ -458,6 +462,24 @@ def password(name): + return name + str(os.getpid()) + + ++def canonicalize_hostname(name, rdns=True): ++ """Canonicalize name using DNS, optionally with reverse DNS.""" ++ try: ++ ai = socket.getaddrinfo(name, None, 0, 0, 0, socket.AI_CANONNAME) ++ except socket.gaierror as e: ++ return name.lower() ++ (family, socktype, proto, canonname, sockaddr) = ai[0] ++ ++ if not rdns: ++ return canonname.lower() ++ ++ try: ++ rname = socket.getnameinfo(sockaddr, socket.NI_NAMEREQD) ++ except socket.gaierror: ++ return canonname.lower() ++ return rname[0].lower() ++ ++ + # Exit handler which ensures processes are cleaned up and, on failure, + # prints messages to help developers debug the problem. + def _onexit(): diff --git a/Support-host-based-GSS-initiator-names.patch b/Support-host-based-GSS-initiator-names.patch new file mode 100644 index 0000000..f04609f --- /dev/null +++ b/Support-host-based-GSS-initiator-names.patch @@ -0,0 +1,578 @@ +From 067e3a509442d81d1a31dd4bcbcc190f55369cc9 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Fri, 15 Jan 2021 13:51:34 -0500 +Subject: [PATCH] Support host-based GSS initiator names + +When checking if we can get initial credentials in the GSS krb5 mech, +use krb5_kt_have_match() to support fallback iteration. When scanning +the ccache or getting initial credentials, rewrite cred->name->princ +to the canonical client name. When a name check is necessary (such as +when the caller specifies both a name and ccache), use a new internal +API k5_sname_compare() to support fallback iteration. Add fallback +iteration to krb5_cc_cache_match() to allow host-based names to be +canonicalized against the cache collection. + +Create and store the matching principal for acceptor names in +acquire_accept_cred() so that it isn't affected by changes in +cred->name->princ during acquire_init_cred(). + +ticket: 8978 (new) +(cherry picked from commit c374ab40dd059a5938ffc0440d87457ac5da3a46) +--- + src/include/k5-int.h | 9 +++ + src/include/k5-trace.h | 3 + + src/lib/gssapi/krb5/accept_sec_context.c | 15 +--- + src/lib/gssapi/krb5/acquire_cred.c | 89 ++++++++++++++---------- + src/lib/gssapi/krb5/gssapiP_krb5.h | 1 + + src/lib/gssapi/krb5/rel_cred.c | 1 + + src/lib/krb5/ccache/cccursor.c | 57 +++++++++++---- + src/lib/krb5/libkrb5.exports | 1 + + src/lib/krb5/os/sn2princ.c | 23 +++++- + src/lib/krb5_32.def | 1 + + src/tests/gssapi/t_client_keytab.py | 44 ++++++++++++ + src/tests/gssapi/t_credstore.py | 32 +++++++++ + 12 files changed, 214 insertions(+), 62 deletions(-) + +diff --git a/src/include/k5-int.h b/src/include/k5-int.h +index efb523689..46f2ce2d3 100644 +--- a/src/include/k5-int.h ++++ b/src/include/k5-int.h +@@ -2411,4 +2411,13 @@ void k5_change_error_message_code(krb5_context ctx, krb5_error_code oldcode, + #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 */ +diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h +index b3e039dc8..79b5a7a85 100644 +--- a/src/include/k5-trace.h ++++ b/src/include/k5-trace.h +@@ -105,6 +105,9 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); + + #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) \ +diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c +index fcf2c2152..a1d7e0d96 100644 +--- a/src/lib/gssapi/krb5/accept_sec_context.c ++++ b/src/lib/gssapi/krb5/accept_sec_context.c +@@ -683,7 +683,6 @@ kg_accept_krb5(minor_status, context_handle, + 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); +@@ -849,17 +848,9 @@ kg_accept_krb5(minor_status, context_handle, + } + } + +- 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; +diff --git a/src/lib/gssapi/krb5/acquire_cred.c b/src/lib/gssapi/krb5/acquire_cred.c +index 632ee7def..e226a0269 100644 +--- a/src/lib/gssapi/krb5/acquire_cred.c ++++ b/src/lib/gssapi/krb5/acquire_cred.c +@@ -123,11 +123,11 @@ gss_krb5int_register_acceptor_identity(OM_uint32 *minor_status, + /* 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) { +@@ -141,21 +141,15 @@ check_keytab(krb5_context context, krb5_keytab kt, krb5_gss_name_t name) + 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; + } + +@@ -202,8 +196,14 @@ acquire_accept_cred(krb5_context context, OM_uint32 *minor_status, + } + + 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); +@@ -324,7 +324,6 @@ static krb5_boolean + 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; +@@ -336,20 +335,21 @@ can_get_initial_creds(krb5_context context, krb5_gss_cred_id_rec *cred) + 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; +@@ -365,23 +365,31 @@ scan_ccache(krb5_context context, krb5_gss_cred_id_rec *cred) + 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); +@@ -447,7 +455,7 @@ get_cache_for_name(krb5_context context, krb5_gss_cred_id_rec *cred) + 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. */ +@@ -456,7 +464,7 @@ get_cache_for_name(krb5_context context, krb5_gss_cred_id_rec *cred) + /* 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); +@@ -633,6 +641,13 @@ get_initial_cred(krb5_context context, const struct verify_params *verify, + 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); +@@ -721,7 +736,7 @@ acquire_init_cred(krb5_context context, OM_uint32 *minor_status, + + 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. */ +@@ -984,7 +999,7 @@ kg_cred_resolve(OM_uint32 *minor_status, krb5_context context, + } + } + if (cred->ccache != NULL) { +- code = scan_ccache(context, cred); ++ code = scan_ccache(context, cred, FALSE); + if (code) + goto kerr; + } +@@ -996,7 +1011,7 @@ kg_cred_resolve(OM_uint32 *minor_status, krb5_context context, + 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); +diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h +index 3bacdcd35..fd7abbd77 100644 +--- a/src/lib/gssapi/krb5/gssapiP_krb5.h ++++ b/src/lib/gssapi/krb5/gssapiP_krb5.h +@@ -175,6 +175,7 @@ typedef struct _krb5_gss_cred_id_rec { + /* 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; +diff --git a/src/lib/gssapi/krb5/rel_cred.c b/src/lib/gssapi/krb5/rel_cred.c +index a9515daf7..0da6c1b95 100644 +--- a/src/lib/gssapi/krb5/rel_cred.c ++++ b/src/lib/gssapi/krb5/rel_cred.c +@@ -72,6 +72,7 @@ krb5_gss_release_cred(minor_status, cred_handle) + 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) +diff --git a/src/lib/krb5/ccache/cccursor.c b/src/lib/krb5/ccache/cccursor.c +index 8f5872116..760216d05 100644 +--- a/src/lib/krb5/ccache/cccursor.c ++++ b/src/lib/krb5/ccache/cccursor.c +@@ -30,6 +30,7 @@ + + #include "cc-int.h" + #include "../krb/int-proto.h" ++#include "../os/os-proto.h" + + #include + +@@ -141,18 +142,18 @@ krb5_cccol_cursor_free(krb5_context context, + 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; +@@ -169,20 +170,52 @@ krb5_cc_cache_match(krb5_context context, krb5_principal client, + 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 +diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports +index 9de0fcdb3..25141dfc5 100644 +--- a/src/lib/krb5/libkrb5.exports ++++ b/src/lib/krb5/libkrb5.exports +@@ -181,6 +181,7 @@ k5_size_authdata_context + k5_size_context + k5_size_keyblock + k5_size_principal ++k5_sname_compare + k5_unmarshal_cred + k5_unmarshal_princ + k5_unwrap_cammac_svc +diff --git a/src/lib/krb5/os/sn2princ.c b/src/lib/krb5/os/sn2princ.c +index 8b7214189..c99b7da17 100644 +--- a/src/lib/krb5/os/sn2princ.c ++++ b/src/lib/krb5/os/sn2princ.c +@@ -277,7 +277,8 @@ k5_canonprinc(krb5_context context, struct canonprinc *iter, + + /* 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; + } +@@ -288,6 +289,26 @@ k5_canonprinc(krb5_context context, struct canonprinc *iter, + 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, +diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def +index 60b8dd311..cf690dbe4 100644 +--- a/src/lib/krb5_32.def ++++ b/src/lib/krb5_32.def +@@ -507,3 +507,4 @@ EXPORTS + ; new in 1.20 + krb5_marshal_credentials @472 + krb5_unmarshal_credentials @473 ++ k5_sname_compare @474 ; PRIVATE GSSAPI +diff --git a/src/tests/gssapi/t_client_keytab.py b/src/tests/gssapi/t_client_keytab.py +index 7847b3ecd..9a61d53b8 100755 +--- a/src/tests/gssapi/t_client_keytab.py ++++ b/src/tests/gssapi/t_client_keytab.py +@@ -141,5 +141,49 @@ msgs = ('Getting initial credentials for user/admin@KRBTEST.COM', + '/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') +diff --git a/src/tests/gssapi/t_credstore.py b/src/tests/gssapi/t_credstore.py +index c11975bf5..9be57bb82 100644 +--- a/src/tests/gssapi/t_credstore.py ++++ b/src/tests/gssapi/t_credstore.py +@@ -15,6 +15,38 @@ msgs = ('Storing %s -> %s in %s' % (service_cs, realm.krbtgt_princ, + 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:". diff --git a/krb5.spec b/krb5.spec index 62b763c..8994c9c 100644 --- a/krb5.spec +++ b/krb5.spec @@ -19,7 +19,7 @@ Summary: The Kerberos network authentication system Name: krb5 Version: 1.19 -Release: %{?zdpd}1%{?dist}.2 +Release: %{?zdpd}5%{?dist} # rharwood has trust path to signing key and verifies on check-in Source0: https://web.mit.edu/kerberos/dist/krb5/%{version}/krb5-%{version}%{?dashpre}.tar.gz @@ -47,6 +47,9 @@ Patch4: downstream-fix-debuginfo-with-y.tab.c.patch Patch5: downstream-Remove-3des-support.patch Patch6: downstream-Use-backported-version-of-OpenSSL-3-KDF-i.patch Patch7: downstream-FIPS-with-PRNG-and-RADIUS-and-MD4.patch +Patch8: Add-APIs-for-marshalling-credentials.patch +Patch9: Add-hostname-canonicalization-helper-to-k5test.py.patch +Patch10: Support-host-based-GSS-initiator-names.patch License: MIT URL: https://web.mit.edu/kerberos/www/ @@ -114,6 +117,7 @@ Kerberos, you need to install this package. %package server Summary: The KDC and related programs for Kerberos 5 Requires: %{name}-libs%{?_isa} = %{version}-%{release} +Requires: %{name}-pkinit%{?_isa} = %{version}-%{release} Requires(post): systemd-units Requires(preun): systemd-units Requires(postun): systemd-units @@ -150,6 +154,7 @@ realm, you need to install this package. %package workstation Summary: Kerberos 5 programs for use on workstations Requires: %{name}-libs%{?_isa} = %{version}-%{release} +Requires: %{name}-pkinit%{?_isa} = %{version}-%{release} Requires: libkadm5%{?_isa} = %{version}-%{release} %description workstation @@ -608,7 +613,19 @@ exit 0 %{_libdir}/libkadm5srv_mit.so.* %changelog -* Wed Jan 27 2021 Robbie Harwood - 1.19.0.beta2.1.2 +* Thu Jan 28 2021 Robbie Harwood - 1.19-5 +- Support host-based GSS initiator names + +* Thu Jan 28 2021 Robbie Harwood - 1.19-0.beta2.4 +- Require krb5-pkinit from krb5-{server,workstation} + +* Thu Jan 28 2021 Robbie Harwood - 1.19-0.beta2.3 +- Fix up weird mass rebuild versioning + +* Thu Jan 28 2021 Robbie Harwood - 1.19-0.beta2.2.2 +- Add APIs for marshalling credentials + +* Wed Jan 27 2021 Robbie Harwood - 1.19-0.beta2.1.2 - Cope with new autotools behavior wrt runstatedir * Tue Jan 26 2021 Fedora Release Engineering - 1.19-0.beta2.1.1