From d223dacd673396251e6201d94ba29b5eaa3b1987 Mon Sep 17 00:00:00 2001 From: Robbie Harwood Date: Thu, 10 Jun 2021 15:23:28 -0400 Subject: [PATCH] Backport KCM retrieval fixes Resolves: #1956403 --- ...P_GET_CRED_LIST-for-faster-iteration.patch | 359 ++++++++++++++++++ ...CM-flag-transmission-for-remove_cred.patch | 104 +++++ Fix-KCM-retrieval-support-for-sssd.patch | 63 +++ ...teration-fallback-work-with-sssd-kcm.patch | 27 ++ Use-KCM_OP_RETRIEVE-in-KCM-client.patch | 236 ++++++++++++ krb5.spec | 11 +- 6 files changed, 799 insertions(+), 1 deletion(-) create mode 100644 Add-KCM_OP_GET_CRED_LIST-for-faster-iteration.patch create mode 100644 Fix-KCM-flag-transmission-for-remove_cred.patch create mode 100644 Fix-KCM-retrieval-support-for-sssd.patch create mode 100644 Make-KCM-iteration-fallback-work-with-sssd-kcm.patch create mode 100644 Use-KCM_OP_RETRIEVE-in-KCM-client.patch diff --git a/Add-KCM_OP_GET_CRED_LIST-for-faster-iteration.patch b/Add-KCM_OP_GET_CRED_LIST-for-faster-iteration.patch new file mode 100644 index 0000000..84c55dc --- /dev/null +++ b/Add-KCM_OP_GET_CRED_LIST-for-faster-iteration.patch @@ -0,0 +1,359 @@ +From baf60dbdeceb3cad35cad7d9930782f94b6c8221 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavel=20B=C5=99ezina?= +Date: Thu, 11 Feb 2021 15:33:10 +0100 +Subject: [PATCH] Add KCM_OP_GET_CRED_LIST for faster iteration + +For large caches, one IPC operation per credential dominates the cost +of iteration. Instead transfer the whole list of credentials to the +client in one IPC operation. + +Add optional support for the new opcode to the test KCM server to +allow testing of the main and fallback code paths. + +[ghudson@mit.edu: fixed memory leaks and potential memory errors; +adjusted code style and comments; rewrote commit message; added +kcmserver.py support and tests] + +ticket: 8990 (new) +(cherry picked from commit 81bdb47d8ded390263d8ee48f71d5c312b4f1736) +(cherry picked from commit a0ee8b02e56c65e5dcd569caed0e151cef004ef4) +--- + src/include/kcm.h | 12 ++- + src/lib/krb5/ccache/cc_kcm.c | 144 ++++++++++++++++++++++++++++++++--- + src/tests/kcmserver.py | 28 ++++++- + src/tests/t_ccache.py | 10 ++- + 4 files changed, 175 insertions(+), 19 deletions(-) + +diff --git a/src/include/kcm.h b/src/include/kcm.h +index 5ea1447cd..e4140c3a0 100644 +--- a/src/include/kcm.h ++++ b/src/include/kcm.h +@@ -51,9 +51,9 @@ + * + * All replies begin with a 32-bit big-endian reply code. + * +- * Parameters are appended to the request or reply with no delimiters. Flags +- * and time offsets are stored as 32-bit big-endian integers. Names are +- * marshalled as zero-terminated strings. Principals and credentials are ++ * Parameters are appended to the request or reply with no delimiters. Flags, ++ * time offsets, and lengths are stored as 32-bit big-endian integers. Names ++ * are marshalled as zero-terminated strings. Principals and credentials are + * marshalled in the v4 FILE ccache format. UUIDs are 16 bytes. UUID lists + * are not delimited, so nothing can come after them. + */ +@@ -89,7 +89,11 @@ typedef enum kcm_opcode { + KCM_OP_HAVE_NTLM_CRED, + KCM_OP_DEL_NTLM_CRED, + KCM_OP_DO_NTLM_AUTH, +- KCM_OP_GET_NTLM_USER_LIST ++ KCM_OP_GET_NTLM_USER_LIST, ++ ++ /* MIT extensions */ ++ KCM_OP_MIT_EXTENSION_BASE = 13000, ++ KCM_OP_GET_CRED_LIST, /* (name) -> (count, count*{len, cred}) */ + } kcm_opcode; + + #endif /* KCM_H */ +diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c +index 9093f894d..772928e4d 100644 +--- a/src/lib/krb5/ccache/cc_kcm.c ++++ b/src/lib/krb5/ccache/cc_kcm.c +@@ -61,6 +61,17 @@ struct uuid_list { + size_t pos; + }; + ++struct cred_list { ++ krb5_creds *creds; ++ size_t count; ++ size_t pos; ++}; ++ ++struct kcm_cursor { ++ struct uuid_list *uuids; ++ struct cred_list *creds; ++}; ++ + struct kcmio { + SOCKET fd; + #ifdef __APPLE__ +@@ -489,6 +500,69 @@ free_uuid_list(struct uuid_list *uuids) + free(uuids); + } + ++static void ++free_cred_list(struct cred_list *list) ++{ ++ size_t i; ++ ++ if (list == NULL) ++ return; ++ ++ /* Creds are transferred to the caller as list->pos is incremented, so we ++ * can start freeing there. */ ++ for (i = list->pos; i < list->count; i++) ++ krb5_free_cred_contents(NULL, &list->creds[i]); ++ free(list->creds); ++ free(list); ++} ++ ++/* Fetch a cred list from req->reply. */ ++static krb5_error_code ++kcmreq_get_cred_list(struct kcmreq *req, struct cred_list **creds_out) ++{ ++ struct cred_list *list; ++ const unsigned char *data; ++ krb5_error_code ret = 0; ++ size_t count, len, i; ++ ++ *creds_out = NULL; ++ ++ /* Check a rough bound on the count to prevent very large allocations. */ ++ count = k5_input_get_uint32_be(&req->reply); ++ if (count > req->reply.len / 4) ++ return KRB5_KCM_MALFORMED_REPLY; ++ ++ list = malloc(sizeof(*list)); ++ if (list == NULL) ++ return ENOMEM; ++ ++ list->creds = NULL; ++ list->count = count; ++ list->pos = 0; ++ list->creds = k5calloc(count, sizeof(*list->creds), &ret); ++ if (list->creds == NULL) { ++ free(list); ++ return ret; ++ } ++ ++ for (i = 0; i < count; i++) { ++ len = k5_input_get_uint32_be(&req->reply); ++ data = k5_input_get_bytes(&req->reply, len); ++ if (data == NULL) ++ break; ++ ret = k5_unmarshal_cred(data, len, 4, &list->creds[i]); ++ if (ret) ++ break; ++ } ++ if (i < count) { ++ free_cred_list(list); ++ return (ret == ENOMEM) ? ENOMEM : KRB5_KCM_MALFORMED_REPLY; ++ } ++ ++ *creds_out = list; ++ return 0; ++} ++ + static void + kcmreq_free(struct kcmreq *req) + { +@@ -753,33 +827,53 @@ kcm_start_seq_get(krb5_context context, krb5_ccache cache, + { + krb5_error_code ret; + struct kcmreq req = EMPTY_KCMREQ; +- struct uuid_list *uuids; ++ struct uuid_list *uuids = NULL; ++ struct cred_list *creds = NULL; ++ struct kcm_cursor *cursor; + + *cursor_out = NULL; + + get_kdc_offset(context, cache); + +- kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache); ++ kcmreq_init(&req, KCM_OP_GET_CRED_LIST, cache); + ret = cache_call(context, cache, &req); +- if (ret) ++ if (ret == 0) { ++ /* GET_CRED_LIST is available. */ ++ ret = kcmreq_get_cred_list(&req, &creds); ++ if (ret) ++ goto cleanup; ++ } else if (ret == KRB5_FCC_INTERNAL) { ++ /* Fall back to GET_CRED_UUID_LIST. */ ++ kcmreq_free(&req); ++ kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache); ++ ret = cache_call(context, cache, &req); ++ if (ret) ++ goto cleanup; ++ ret = kcmreq_get_uuid_list(&req, &uuids); ++ if (ret) ++ goto cleanup; ++ } else { + goto cleanup; +- ret = kcmreq_get_uuid_list(&req, &uuids); +- if (ret) ++ } ++ ++ cursor = k5alloc(sizeof(*cursor), &ret); ++ if (cursor == NULL) + goto cleanup; +- *cursor_out = (krb5_cc_cursor)uuids; ++ cursor->uuids = uuids; ++ cursor->creds = creds; ++ *cursor_out = (krb5_cc_cursor)cursor; + + cleanup: + kcmreq_free(&req); + return ret; + } + +-static krb5_error_code KRB5_CALLCONV +-kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor, +- krb5_creds *cred_out) ++static krb5_error_code ++next_cred_by_uuid(krb5_context context, krb5_ccache cache, ++ struct uuid_list *uuids, krb5_creds *cred_out) + { + krb5_error_code ret; + struct kcmreq req; +- struct uuid_list *uuids = (struct uuid_list *)*cursor; + + memset(cred_out, 0, sizeof(*cred_out)); + +@@ -797,11 +891,39 @@ kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor, + return map_invalid(ret); + } + ++static krb5_error_code KRB5_CALLCONV ++kcm_next_cred(krb5_context context, krb5_ccache cache, krb5_cc_cursor *cursor, ++ krb5_creds *cred_out) ++{ ++ struct kcm_cursor *c = (struct kcm_cursor *)*cursor; ++ struct cred_list *list; ++ ++ if (c->uuids != NULL) ++ return next_cred_by_uuid(context, cache, c->uuids, cred_out); ++ ++ list = c->creds; ++ if (list->pos >= list->count) ++ return KRB5_CC_END; ++ ++ /* Transfer memory ownership of one cred to the caller. */ ++ *cred_out = list->creds[list->pos]; ++ memset(&list->creds[list->pos], 0, sizeof(*list->creds)); ++ list->pos++; ++ ++ return 0; ++} ++ + static krb5_error_code KRB5_CALLCONV + kcm_end_seq_get(krb5_context context, krb5_ccache cache, + krb5_cc_cursor *cursor) + { +- free_uuid_list((struct uuid_list *)*cursor); ++ struct kcm_cursor *c = *cursor; ++ ++ if (c == NULL) ++ return 0; ++ free_uuid_list(c->uuids); ++ free_cred_list(c->creds); ++ free(c); + *cursor = NULL; + return 0; + } +diff --git a/src/tests/kcmserver.py b/src/tests/kcmserver.py +index 57432e5a7..8c5e66ff1 100644 +--- a/src/tests/kcmserver.py ++++ b/src/tests/kcmserver.py +@@ -23,6 +23,7 @@ + # traceback.print_exception(etype, value, tb, file=f) + # sys.excepthook = ehook + ++import optparse + import select + import socket + import struct +@@ -49,12 +50,14 @@ class KCMOpcodes(object): + SET_DEFAULT_CACHE = 21 + GET_KDC_OFFSET = 22 + SET_KDC_OFFSET = 23 ++ GET_CRED_LIST = 13001 + + + class KRB5Errors(object): + KRB5_CC_END = -1765328242 + KRB5_CC_NOSUPP = -1765328137 + KRB5_FCC_NOFILE = -1765328189 ++ KRB5_FCC_INTERNAL = -1765328188 + + + def make_uuid(): +@@ -183,6 +186,14 @@ def op_set_kdc_offset(argbytes): + return 0, b'' + + ++def op_get_cred_list(argbytes): ++ name, rest = unmarshal_name(argbytes) ++ cache = get_cache(name) ++ creds = [cache.creds[u] for u in cache.cred_uuids] ++ return 0, (struct.pack('>L', len(creds)) + ++ b''.join(struct.pack('>L', len(c)) + c for c in creds)) ++ ++ + ophandlers = { + KCMOpcodes.GEN_NEW : op_gen_new, + KCMOpcodes.INITIALIZE : op_initialize, +@@ -197,7 +208,8 @@ ophandlers = { + KCMOpcodes.GET_DEFAULT_CACHE : op_get_default_cache, + KCMOpcodes.SET_DEFAULT_CACHE : op_set_default_cache, + KCMOpcodes.GET_KDC_OFFSET : op_get_kdc_offset, +- KCMOpcodes.SET_KDC_OFFSET : op_set_kdc_offset ++ KCMOpcodes.SET_KDC_OFFSET : op_set_kdc_offset, ++ KCMOpcodes.GET_CRED_LIST : op_get_cred_list + } + + # Read and respond to a request from the socket s. +@@ -215,7 +227,11 @@ def service_request(s): + + majver, minver, op = struct.unpack('>BBH', req[:4]) + argbytes = req[4:] +- code, payload = ophandlers[op](argbytes) ++ ++ if op in ophandlers: ++ code, payload = ophandlers[op](argbytes) ++ else: ++ code, payload = KRB5Errors.KRB5_FCC_INTERNAL, b'' + + # The KCM response is the code (4 bytes) and the response payload. + # The Heimdal IPC response is the length of the KCM response (4 +@@ -226,9 +242,15 @@ def service_request(s): + s.sendall(hipc_response) + return True + ++parser = optparse.OptionParser() ++parser.add_option('-c', '--credlist', action='store_true', dest='credlist', ++ default=False, help='Support KCM_OP_GET_CRED_LIST') ++(options, args) = parser.parse_args() ++if not options.credlist: ++ del ophandlers[KCMOpcodes.GET_CRED_LIST] + + server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +-server.bind(sys.argv[1]) ++server.bind(args[0]) + server.listen(5) + select_input = [server,] + sys.stderr.write('starting...\n') +diff --git a/src/tests/t_ccache.py b/src/tests/t_ccache.py +index 66804afa5..90040fb7b 100755 +--- a/src/tests/t_ccache.py ++++ b/src/tests/t_ccache.py +@@ -125,10 +125,18 @@ def collection_test(realm, ccname): + + + collection_test(realm, 'DIR:' + os.path.join(realm.testdir, 'cc')) ++ ++# Test KCM without and with GET_CRED_LIST support. + kcmserver_path = os.path.join(srctop, 'tests', 'kcmserver.py') +-realm.start_server([sys.executable, kcmserver_path, kcm_socket_path], ++kcmd = realm.start_server([sys.executable, kcmserver_path, kcm_socket_path], ++ 'starting...') ++collection_test(realm, 'KCM:') ++stop_daemon(kcmd) ++os.remove(kcm_socket_path) ++realm.start_server([sys.executable, kcmserver_path, '-c', kcm_socket_path], + 'starting...') + collection_test(realm, 'KCM:') ++ + if test_keyring: + def cleanup_keyring(anchor, name): + out = realm.run(['keyctl', 'list', anchor]) diff --git a/Fix-KCM-flag-transmission-for-remove_cred.patch b/Fix-KCM-flag-transmission-for-remove_cred.patch new file mode 100644 index 0000000..8961c2e --- /dev/null +++ b/Fix-KCM-flag-transmission-for-remove_cred.patch @@ -0,0 +1,104 @@ +From ddbb295dee2adcc6cec26944974420bba188f191 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Mon, 29 Mar 2021 14:32:56 -0400 +Subject: [PATCH] Fix KCM flag transmission for remove_cred + +MIT krb5 uses low bits for KRB5_TC flags, while Heimdal uses high bits +so that the same flag word can also hold KRB5_GC flags. Add a mapping +function and send the Heimdal flag values when performing a +remove_cred operation. + +ticket: 8995 +(cherry picked from commit 11a82cf424f9c905bb73680c64524f087090d4ef) +(cherry picked from commit 04f0de4420508161ce439f262f2761ff51a07ab0) +--- + src/include/kcm.h | 19 +++++++++++++++++++ + src/lib/krb5/ccache/cc_kcm.c | 36 +++++++++++++++++++++++++++++++++++- + 2 files changed, 54 insertions(+), 1 deletion(-) + +diff --git a/src/include/kcm.h b/src/include/kcm.h +index e4140c3a0..9b66f1cbd 100644 +--- a/src/include/kcm.h ++++ b/src/include/kcm.h +@@ -56,8 +56,27 @@ + * are marshalled as zero-terminated strings. Principals and credentials are + * marshalled in the v4 FILE ccache format. UUIDs are 16 bytes. UUID lists + * are not delimited, so nothing can come after them. ++ * ++ * Flag words must use Heimdal flag values, which are not the same as MIT krb5 ++ * values for KRB5_GC and KRB5_TC constants. The same flag word may contain ++ * both kinds of flags in Heimdal, but not in MIT krb5. Defines for the ++ * applicable Heimdal flag values are given below using KCM_GC and KCM_TC ++ * prefixes. + */ + ++#define KCM_GC_CACHED (1U << 0) ++ ++#define KCM_TC_DONT_MATCH_REALM (1U << 31) ++#define KCM_TC_MATCH_KEYTYPE (1U << 30) ++#define KCM_TC_MATCH_SRV_NAMEONLY (1U << 29) ++#define KCM_TC_MATCH_FLAGS_EXACT (1U << 28) ++#define KCM_TC_MATCH_FLAGS (1U << 27) ++#define KCM_TC_MATCH_TIMES_EXACT (1U << 26) ++#define KCM_TC_MATCH_TIMES (1U << 25) ++#define KCM_TC_MATCH_AUTHDATA (1U << 24) ++#define KCM_TC_MATCH_2ND_TKT (1U << 23) ++#define KCM_TC_MATCH_IS_SKEY (1U << 22) ++ + /* Opcodes without comments are currently unused in the MIT client + * implementation. */ + typedef enum kcm_opcode { +diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c +index 772928e4d..1f81a2190 100644 +--- a/src/lib/krb5/ccache/cc_kcm.c ++++ b/src/lib/krb5/ccache/cc_kcm.c +@@ -110,6 +110,40 @@ map_invalid(krb5_error_code code) + KRB5_KCM_MALFORMED_REPLY : code; + } + ++/* ++ * Map an MIT krb5 KRB5_TC flag word to the equivalent Heimdal flag word. Note ++ * that there is no MIT krb5 equivalent for Heimdal's KRB5_TC_DONT_MATCH_REALM ++ * (which is like KRB5_TC_MATCH_SRV_NAMEONLY but also applies to the client ++ * principal) and no Heimdal equivalent for MIT krb5's KRB5_TC_SUPPORTED_KTYPES ++ * (which matches against enctypes from the krb5_context rather than the ++ * matching cred). ++ */ ++static inline krb5_flags ++map_tcflags(krb5_flags mitflags) ++{ ++ krb5_flags heimflags = 0; ++ ++ if (mitflags & KRB5_TC_MATCH_TIMES) ++ heimflags |= KCM_TC_MATCH_TIMES; ++ if (mitflags & KRB5_TC_MATCH_IS_SKEY) ++ heimflags |= KCM_TC_MATCH_IS_SKEY; ++ if (mitflags & KRB5_TC_MATCH_FLAGS) ++ heimflags |= KCM_TC_MATCH_FLAGS; ++ if (mitflags & KRB5_TC_MATCH_TIMES_EXACT) ++ heimflags |= KCM_TC_MATCH_TIMES_EXACT; ++ if (mitflags & KRB5_TC_MATCH_FLAGS_EXACT) ++ heimflags |= KCM_TC_MATCH_FLAGS_EXACT; ++ if (mitflags & KRB5_TC_MATCH_AUTHDATA) ++ heimflags |= KCM_TC_MATCH_AUTHDATA; ++ if (mitflags & KRB5_TC_MATCH_SRV_NAMEONLY) ++ heimflags |= KCM_TC_MATCH_SRV_NAMEONLY; ++ if (mitflags & KRB5_TC_MATCH_2ND_TKT) ++ heimflags |= KCM_TC_MATCH_2ND_TKT; ++ if (mitflags & KRB5_TC_MATCH_KTYPE) ++ heimflags |= KCM_TC_MATCH_KEYTYPE; ++ return heimflags; ++} ++ + /* Begin a request for the given opcode. If cache is non-null, supply the + * cache name as a request parameter. */ + static void +@@ -936,7 +970,7 @@ kcm_remove_cred(krb5_context context, krb5_ccache cache, krb5_flags flags, + struct kcmreq req; + + kcmreq_init(&req, KCM_OP_REMOVE_CRED, cache); +- k5_buf_add_uint32_be(&req.reqbuf, flags); ++ k5_buf_add_uint32_be(&req.reqbuf, map_tcflags(flags)); + k5_marshal_mcred(&req.reqbuf, mcred); + ret = cache_call(context, cache, &req); + kcmreq_free(&req); diff --git a/Fix-KCM-retrieval-support-for-sssd.patch b/Fix-KCM-retrieval-support-for-sssd.patch new file mode 100644 index 0000000..560795f --- /dev/null +++ b/Fix-KCM-retrieval-support-for-sssd.patch @@ -0,0 +1,63 @@ +From 6a00fd149edd017ece894566771e2e9d4ba089f4 Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Tue, 11 May 2021 14:04:07 -0400 +Subject: [PATCH] Fix KCM retrieval support for sssd + +Commit 795ebba8c039be172ab93cd41105c73ffdba0fdb added a retrieval +handler using KCM_OP_RETRIEVE, falling back on the same error codes as +the previous KCM_OP_GET_CRED_LIST support. But sssd (as of 2.4) +returns KRB5_CC_NOSUPP instead of KRB5_CC_IO if it recognizes an +opcode but does not implement it. Add a helper function to recognize +all known unsupported-opcode error codes, and use it in kcm_retrieve() +and kcm_start_seq_get(). + +ticket: 8997 +(cherry picked from commit da103e36e13f3c846bcddbe38dd518a21e5260a0) +(cherry picked from commit a5b2cff51808cd86fe8195e7ac074ecd25c3344d) +--- + src/lib/krb5/ccache/cc_kcm.c | 18 ++++++++++++++++-- + 1 file changed, 16 insertions(+), 2 deletions(-) + +diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c +index 23fcf13ea..18505cd3d 100644 +--- a/src/lib/krb5/ccache/cc_kcm.c ++++ b/src/lib/krb5/ccache/cc_kcm.c +@@ -144,6 +144,20 @@ map_tcflags(krb5_flags mitflags) + return heimflags; + } + ++/* ++ * Return true if code could indicate an unsupported operation. Heimdal's KCM ++ * returns KRB5_FCC_INTERNAL. sssd's KCM daemon (as of sssd 2.4) returns ++ * KRB5_CC_NO_SUPP if it recognizes the operation but does not implement it, ++ * and KRB5_CC_IO if it doesn't recognize the operation (which is unfortunate ++ * since it could also indicate a communication failure). ++ */ ++static krb5_boolean ++unsupported_op_error(krb5_error_code code) ++{ ++ return code == KRB5_FCC_INTERNAL || code == KRB5_CC_IO || ++ code == KRB5_CC_NOSUPP; ++} ++ + /* Begin a request for the given opcode. If cache is non-null, supply the + * cache name as a request parameter. */ + static void +@@ -841,7 +855,7 @@ kcm_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags, + ret = cache_call(context, cache, &req); + + /* Fall back to iteration if the server does not support retrieval. */ +- if (ret == KRB5_FCC_INTERNAL || ret == KRB5_CC_IO) { ++ if (unsupported_op_error(ret)) { + ret = k5_cc_retrieve_cred_default(context, cache, flags, mcred, + cred_out); + goto cleanup; +@@ -922,7 +936,7 @@ kcm_start_seq_get(krb5_context context, krb5_ccache cache, + ret = kcmreq_get_cred_list(&req, &creds); + if (ret) + goto cleanup; +- } else if (ret == KRB5_FCC_INTERNAL || ret == KRB5_CC_IO) { ++ } else if (unsupported_op_error(ret)) { + /* Fall back to GET_CRED_UUID_LIST. */ + kcmreq_free(&req); + kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache); diff --git a/Make-KCM-iteration-fallback-work-with-sssd-kcm.patch b/Make-KCM-iteration-fallback-work-with-sssd-kcm.patch new file mode 100644 index 0000000..b822b98 --- /dev/null +++ b/Make-KCM-iteration-fallback-work-with-sssd-kcm.patch @@ -0,0 +1,27 @@ +From f7702c5b11bdd186d03fed32568c9a252d049d44 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavel=20B=C5=99ezina?= +Date: Tue, 30 Mar 2021 14:35:28 +0200 +Subject: [PATCH] Make KCM iteration fallback work with sssd-kcm + +sssd-kcm returns KRB5_CC_IO if the operation code is not known. + +ticket: 8990 +(cherry picked from commit 06afae820a44c1dc96ad88a0b16c3e50bc938b2a) +(cherry picked from commit 2dbca7e14c945d6394e0e05f285a068dcd541295) +--- + src/lib/krb5/ccache/cc_kcm.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c +index 1f81a2190..46705f1da 100644 +--- a/src/lib/krb5/ccache/cc_kcm.c ++++ b/src/lib/krb5/ccache/cc_kcm.c +@@ -876,7 +876,7 @@ kcm_start_seq_get(krb5_context context, krb5_ccache cache, + ret = kcmreq_get_cred_list(&req, &creds); + if (ret) + goto cleanup; +- } else if (ret == KRB5_FCC_INTERNAL) { ++ } else if (ret == KRB5_FCC_INTERNAL || ret == KRB5_CC_IO) { + /* Fall back to GET_CRED_UUID_LIST. */ + kcmreq_free(&req); + kcmreq_init(&req, KCM_OP_GET_CRED_UUID_LIST, cache); diff --git a/Use-KCM_OP_RETRIEVE-in-KCM-client.patch b/Use-KCM_OP_RETRIEVE-in-KCM-client.patch new file mode 100644 index 0000000..5e502d3 --- /dev/null +++ b/Use-KCM_OP_RETRIEVE-in-KCM-client.patch @@ -0,0 +1,236 @@ +From ac0a117096324fa73afae291ed467f2ea66e279b Mon Sep 17 00:00:00 2001 +From: Greg Hudson +Date: Fri, 26 Mar 2021 23:38:54 -0400 +Subject: [PATCH] Use KCM_OP_RETRIEVE in KCM client + +In kcm_retrieve(), try KCM_OP_RETRIEVE. Fall back to iteration if the +server doesn't implement it, or if we can an answer incompatible with +KRB5_TC_SUPPORTED_KTYPES. + +In kcmserver.py, implement partial decoding for creds and cred tags so +that we can do a basic principal name match. + +ticket: 8997 (new) +(cherry picked from commit 795ebba8c039be172ab93cd41105c73ffdba0fdb) +(cherry picked from commit c56d4b87de0f30a38dc61d374ad225d02d581eb3) +--- + src/include/kcm.h | 2 +- + src/lib/krb5/ccache/cc_kcm.c | 52 +++++++++++++++++++++++++++++++++--- + src/tests/kcmserver.py | 44 +++++++++++++++++++++++++++--- + src/tests/t_ccache.py | 11 +++++--- + 4 files changed, 99 insertions(+), 10 deletions(-) + +diff --git a/src/include/kcm.h b/src/include/kcm.h +index 9b66f1cbd..85c20d345 100644 +--- a/src/include/kcm.h ++++ b/src/include/kcm.h +@@ -87,7 +87,7 @@ typedef enum kcm_opcode { + KCM_OP_INITIALIZE, /* (name, princ) -> () */ + KCM_OP_DESTROY, /* (name) -> () */ + KCM_OP_STORE, /* (name, cred) -> () */ +- KCM_OP_RETRIEVE, ++ KCM_OP_RETRIEVE, /* (name, flags, credtag) -> (cred) */ + KCM_OP_GET_PRINCIPAL, /* (name) -> (princ) */ + KCM_OP_GET_CRED_UUID_LIST, /* (name) -> (uuid, ...) */ + KCM_OP_GET_CRED_BY_UUID, /* (name, uuid) -> (cred) */ +diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c +index 46705f1da..23fcf13ea 100644 +--- a/src/lib/krb5/ccache/cc_kcm.c ++++ b/src/lib/krb5/ccache/cc_kcm.c +@@ -826,9 +826,55 @@ static krb5_error_code KRB5_CALLCONV + kcm_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags, + krb5_creds *mcred, krb5_creds *cred_out) + { +- /* There is a KCM opcode for retrieving creds, but Heimdal's client doesn't +- * use it. It causes the KCM daemon to actually make a TGS request. */ +- return k5_cc_retrieve_cred_default(context, cache, flags, mcred, cred_out); ++ krb5_error_code ret; ++ struct kcmreq req = EMPTY_KCMREQ; ++ krb5_creds cred; ++ krb5_enctype *enctypes = NULL; ++ ++ memset(&cred, 0, sizeof(cred)); ++ ++ /* Include KCM_GC_CACHED in flags to prevent Heimdal's sssd from making a ++ * TGS request itself. */ ++ kcmreq_init(&req, KCM_OP_RETRIEVE, cache); ++ k5_buf_add_uint32_be(&req.reqbuf, map_tcflags(flags) | KCM_GC_CACHED); ++ k5_marshal_mcred(&req.reqbuf, mcred); ++ ret = cache_call(context, cache, &req); ++ ++ /* Fall back to iteration if the server does not support retrieval. */ ++ if (ret == KRB5_FCC_INTERNAL || ret == KRB5_CC_IO) { ++ ret = k5_cc_retrieve_cred_default(context, cache, flags, mcred, ++ cred_out); ++ goto cleanup; ++ } ++ if (ret) ++ goto cleanup; ++ ++ ret = k5_unmarshal_cred(req.reply.ptr, req.reply.len, 4, &cred); ++ if (ret) ++ goto cleanup; ++ ++ /* In rare cases we might retrieve a credential with a session key this ++ * context can't support, in which case we must retry using iteration. */ ++ if (flags & KRB5_TC_SUPPORTED_KTYPES) { ++ ret = krb5_get_tgs_ktypes(context, cred.server, &enctypes); ++ if (ret) ++ goto cleanup; ++ if (!k5_etypes_contains(enctypes, cred.keyblock.enctype)) { ++ ret = k5_cc_retrieve_cred_default(context, cache, flags, mcred, ++ cred_out); ++ goto cleanup; ++ } ++ } ++ ++ *cred_out = cred; ++ memset(&cred, 0, sizeof(cred)); ++ ++cleanup: ++ kcmreq_free(&req); ++ krb5_free_cred_contents(context, &cred); ++ free(enctypes); ++ /* Heimdal's KCM returns KRB5_CC_END if no cred is found. */ ++ return (ret == KRB5_CC_END) ? KRB5_CC_NOTFOUND : map_invalid(ret); + } + + static krb5_error_code KRB5_CALLCONV +diff --git a/src/tests/kcmserver.py b/src/tests/kcmserver.py +index 8c5e66ff1..25e6f2bbe 100644 +--- a/src/tests/kcmserver.py ++++ b/src/tests/kcmserver.py +@@ -40,6 +40,7 @@ class KCMOpcodes(object): + INITIALIZE = 4 + DESTROY = 5 + STORE = 6 ++ RETRIEVE = 7 + GET_PRINCIPAL = 8 + GET_CRED_UUID_LIST = 9 + GET_CRED_BY_UUID = 10 +@@ -54,6 +55,7 @@ class KCMOpcodes(object): + + + class KRB5Errors(object): ++ KRB5_CC_NOTFOUND = -1765328243 + KRB5_CC_END = -1765328242 + KRB5_CC_NOSUPP = -1765328137 + KRB5_FCC_NOFILE = -1765328189 +@@ -86,11 +88,29 @@ def get_cache(name): + return cache + + ++def unpack_data(argbytes): ++ dlen, = struct.unpack('>L', argbytes[:4]) ++ return argbytes[4:dlen+4], argbytes[dlen+4:] ++ ++ + def unmarshal_name(argbytes): + offset = argbytes.find(b'\0') + return argbytes[0:offset], argbytes[offset+1:] + + ++def unmarshal_princ(argbytes): ++ # Ignore the type at argbytes[0:4]. ++ ncomps, = struct.unpack('>L', argbytes[4:8]) ++ realm, rest = unpack_data(argbytes[8:]) ++ comps = [] ++ for i in range(ncomps): ++ comp, rest = unpack_data(rest) ++ comps.append(comp) ++ # Asssume no quoting is needed. ++ princ = b'/'.join(comps) + b'@' + realm ++ return princ, rest ++ ++ + def op_gen_new(argbytes): + # Does not actually check for uniqueness. + global next_unique +@@ -126,6 +146,22 @@ def op_store(argbytes): + return 0, b'' + + ++def op_retrieve(argbytes): ++ name, rest = unmarshal_name(argbytes) ++ # Ignore the flags at rest[0:4] and the header at rest[4:8]. ++ # Assume there are client and server creds in the tag and match ++ # only against them. ++ cprinc, rest = unmarshal_princ(rest[8:]) ++ sprinc, rest = unmarshal_princ(rest) ++ cache = get_cache(name) ++ for cred in (cache.creds[u] for u in cache.cred_uuids): ++ cred_cprinc, rest = unmarshal_princ(cred) ++ cred_sprinc, rest = unmarshal_princ(rest) ++ if cred_cprinc == cprinc and cred_sprinc == sprinc: ++ return 0, cred ++ return KRB5Errors.KRB5_CC_NOTFOUND, b'' ++ ++ + def op_get_principal(argbytes): + name, rest = unmarshal_name(argbytes) + cache = get_cache(name) +@@ -199,6 +235,7 @@ ophandlers = { + KCMOpcodes.INITIALIZE : op_initialize, + KCMOpcodes.DESTROY : op_destroy, + KCMOpcodes.STORE : op_store, ++ KCMOpcodes.RETRIEVE : op_retrieve, + KCMOpcodes.GET_PRINCIPAL : op_get_principal, + KCMOpcodes.GET_CRED_UUID_LIST : op_get_cred_uuid_list, + KCMOpcodes.GET_CRED_BY_UUID : op_get_cred_by_uuid, +@@ -243,10 +280,11 @@ def service_request(s): + return True + + parser = optparse.OptionParser() +-parser.add_option('-c', '--credlist', action='store_true', dest='credlist', +- default=False, help='Support KCM_OP_GET_CRED_LIST') ++parser.add_option('-f', '--fallback', action='store_true', dest='fallback', ++ default=False, help='Do not support RETRIEVE/GET_CRED_LIST') + (options, args) = parser.parse_args() +-if not options.credlist: ++if options.fallback: ++ del ophandlers[KCMOpcodes.RETRIEVE] + del ophandlers[KCMOpcodes.GET_CRED_LIST] + + server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +diff --git a/src/tests/t_ccache.py b/src/tests/t_ccache.py +index 90040fb7b..6ea9fb969 100755 +--- a/src/tests/t_ccache.py ++++ b/src/tests/t_ccache.py +@@ -25,7 +25,7 @@ from k5test import * + kcm_socket_path = os.path.join(os.getcwd(), 'testdir', 'kcm') + conf = {'libdefaults': {'kcm_socket': kcm_socket_path, + 'kcm_mach_service': '-'}} +-realm = K5Realm(create_host=False, krb5_conf=conf) ++realm = K5Realm(krb5_conf=conf) + + keyctl = which('keyctl') + out = realm.run([klist, '-c', 'KEYRING:process:abcd'], expected_code=1) +@@ -71,6 +71,11 @@ def collection_test(realm, ccname): + realm.kinit('alice', password('alice')) + realm.run([klist], expected_msg='Default principal: alice@') + realm.run([klist, '-A', '-s']) ++ realm.run([kvno, realm.host_princ], expected_msg = 'kvno = 1') ++ realm.run([kvno, realm.host_princ], expected_msg = 'kvno = 1') ++ out = realm.run([klist]) ++ if out.count(realm.host_princ) != 1: ++ fail('Wrong number of service tickets in cache') + realm.run([kdestroy]) + output = realm.run([klist], expected_code=1) + if 'No credentials cache' not in output and 'not found' not in output: +@@ -126,14 +131,14 @@ def collection_test(realm, ccname): + + collection_test(realm, 'DIR:' + os.path.join(realm.testdir, 'cc')) + +-# Test KCM without and with GET_CRED_LIST support. ++# Test KCM with and without RETRIEVE and GET_CRED_LIST support. + kcmserver_path = os.path.join(srctop, 'tests', 'kcmserver.py') + kcmd = realm.start_server([sys.executable, kcmserver_path, kcm_socket_path], + 'starting...') + collection_test(realm, 'KCM:') + stop_daemon(kcmd) + os.remove(kcm_socket_path) +-realm.start_server([sys.executable, kcmserver_path, '-c', kcm_socket_path], ++realm.start_server([sys.executable, kcmserver_path, '-f', kcm_socket_path], + 'starting...') + collection_test(realm, 'KCM:') + diff --git a/krb5.spec b/krb5.spec index 730b803..bdce4b6 100644 --- a/krb5.spec +++ b/krb5.spec @@ -42,7 +42,7 @@ Summary: The Kerberos network authentication system Name: krb5 Version: 1.19.1 -Release: %{?zdpd}5%{?dist} +Release: %{?zdpd}6%{?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 @@ -79,6 +79,11 @@ Patch14: Use-OpenSSL-s-SSKDF-in-PKINIT-when-available.patch Patch15: Use-OpenSSL-s-KBKDF-and-KRB5KDF-for-deriving-long-te.patch Patch16: Handle-OpenSSL-3-s-providers.patch Patch17: Handle-SSL_read-changed-behavior-on-server-hangup.patch +Patch18: Add-KCM_OP_GET_CRED_LIST-for-faster-iteration.patch +Patch19: Fix-KCM-flag-transmission-for-remove_cred.patch +Patch20: Make-KCM-iteration-fallback-work-with-sssd-kcm.patch +Patch21: Use-KCM_OP_RETRIEVE-in-KCM-client.patch +Patch22: Fix-KCM-retrieval-support-for-sssd.patch License: MIT URL: https://web.mit.edu/kerberos/www/ @@ -640,6 +645,10 @@ exit 0 %{_libdir}/libkadm5srv_mit.so.* %changelog +* Thu Jun 10 2021 Robbie Harwood - 1.19.1-6 +- Backport KCM retrieval fixes +- Resolves: #1956403 + * Thu May 20 2021 Robbie Harwood - 1.19.1-5 - Fix DES3 mention in KDFs - Resolves: #1955873