Blame SOURCES/Use-KCM_OP_RETRIEVE-in-KCM-client.patch

6cfd83
From 00a2ccfeaeac7a0019a73a97cfe33063ba90c7f3 Mon Sep 17 00:00:00 2001
6cfd83
From: Greg Hudson <ghudson@mit.edu>
6cfd83
Date: Fri, 26 Mar 2021 23:38:54 -0400
6cfd83
Subject: [PATCH] Use KCM_OP_RETRIEVE in KCM client
6cfd83
6cfd83
In kcm_retrieve(), try KCM_OP_RETRIEVE.  Fall back to iteration if the
6cfd83
server doesn't implement it, or if we can an answer incompatible with
6cfd83
KRB5_TC_SUPPORTED_KTYPES.
6cfd83
6cfd83
In kcmserver.py, implement partial decoding for creds and cred tags so
6cfd83
that we can do a basic principal name match.
6cfd83
6cfd83
ticket: 8997 (new)
6cfd83
(cherry picked from commit 795ebba8c039be172ab93cd41105c73ffdba0fdb)
6cfd83
(cherry picked from commit c56d4b87de0f30a38dc61d374ad225d02d581eb3)
6cfd83
(cherry picked from commit ac0a117096324fa73afae291ed467f2ea66e279b)
6cfd83
---
6cfd83
 src/include/kcm.h            |  2 +-
6cfd83
 src/lib/krb5/ccache/cc_kcm.c | 52 +++++++++++++++++++++++++++++++++---
6cfd83
 src/tests/kcmserver.py       | 44 +++++++++++++++++++++++++++---
6cfd83
 src/tests/t_ccache.py        | 11 +++++---
6cfd83
 4 files changed, 99 insertions(+), 10 deletions(-)
6cfd83
6cfd83
diff --git a/src/include/kcm.h b/src/include/kcm.h
6cfd83
index 9b66f1cbd..85c20d345 100644
6cfd83
--- a/src/include/kcm.h
6cfd83
+++ b/src/include/kcm.h
6cfd83
@@ -87,7 +87,7 @@ typedef enum kcm_opcode {
6cfd83
     KCM_OP_INITIALIZE,          /*          (name, princ) -> ()          */
6cfd83
     KCM_OP_DESTROY,             /*                 (name) -> ()          */
6cfd83
     KCM_OP_STORE,               /*           (name, cred) -> ()          */
6cfd83
-    KCM_OP_RETRIEVE,
6cfd83
+    KCM_OP_RETRIEVE,            /* (name, flags, credtag) -> (cred)      */
6cfd83
     KCM_OP_GET_PRINCIPAL,       /*                 (name) -> (princ)     */
6cfd83
     KCM_OP_GET_CRED_UUID_LIST,  /*                 (name) -> (uuid, ...) */
6cfd83
     KCM_OP_GET_CRED_BY_UUID,    /*           (name, uuid) -> (cred)      */
6cfd83
diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c
6cfd83
index dae622feb..b600c6f15 100644
6cfd83
--- a/src/lib/krb5/ccache/cc_kcm.c
6cfd83
+++ b/src/lib/krb5/ccache/cc_kcm.c
6cfd83
@@ -826,9 +826,55 @@ static krb5_error_code KRB5_CALLCONV
6cfd83
 kcm_retrieve(krb5_context context, krb5_ccache cache, krb5_flags flags,
6cfd83
              krb5_creds *mcred, krb5_creds *cred_out)
6cfd83
 {
6cfd83
-    /* There is a KCM opcode for retrieving creds, but Heimdal's client doesn't
6cfd83
-     * use it.  It causes the KCM daemon to actually make a TGS request. */
6cfd83
-    return k5_cc_retrieve_cred_default(context, cache, flags, mcred, cred_out);
6cfd83
+    krb5_error_code ret;
6cfd83
+    struct kcmreq req = EMPTY_KCMREQ;
6cfd83
+    krb5_creds cred;
6cfd83
+    krb5_enctype *enctypes = NULL;
6cfd83
+
6cfd83
+    memset(&cred, 0, sizeof(cred));
6cfd83
+
6cfd83
+    /* Include KCM_GC_CACHED in flags to prevent Heimdal's sssd from making a
6cfd83
+     * TGS request itself. */
6cfd83
+    kcmreq_init(&req, KCM_OP_RETRIEVE, cache);
6cfd83
+    k5_buf_add_uint32_be(&req.reqbuf, map_tcflags(flags) | KCM_GC_CACHED);
6cfd83
+    k5_marshal_mcred(&req.reqbuf, mcred);
6cfd83
+    ret = cache_call(context, cache, &req;;
6cfd83
+
6cfd83
+    /* Fall back to iteration if the server does not support retrieval. */
6cfd83
+    if (ret == KRB5_FCC_INTERNAL || ret == KRB5_CC_IO) {
6cfd83
+        ret = k5_cc_retrieve_cred_default(context, cache, flags, mcred,
6cfd83
+                                          cred_out);
6cfd83
+        goto cleanup;
6cfd83
+    }
6cfd83
+    if (ret)
6cfd83
+        goto cleanup;
6cfd83
+
6cfd83
+    ret = k5_unmarshal_cred(req.reply.ptr, req.reply.len, 4, &cred);
6cfd83
+    if (ret)
6cfd83
+        goto cleanup;
6cfd83
+
6cfd83
+    /* In rare cases we might retrieve a credential with a session key this
6cfd83
+     * context can't support, in which case we must retry using iteration. */
6cfd83
+    if (flags & KRB5_TC_SUPPORTED_KTYPES) {
6cfd83
+        ret = krb5_get_tgs_ktypes(context, cred.server, &enctypes);
6cfd83
+        if (ret)
6cfd83
+            goto cleanup;
6cfd83
+        if (!k5_etypes_contains(enctypes, cred.keyblock.enctype)) {
6cfd83
+            ret = k5_cc_retrieve_cred_default(context, cache, flags, mcred,
6cfd83
+                                              cred_out);
6cfd83
+            goto cleanup;
6cfd83
+        }
6cfd83
+    }
6cfd83
+
6cfd83
+    *cred_out = cred;
6cfd83
+    memset(&cred, 0, sizeof(cred));
6cfd83
+
6cfd83
+cleanup:
6cfd83
+    kcmreq_free(&req;;
6cfd83
+    krb5_free_cred_contents(context, &cred);
6cfd83
+    free(enctypes);
6cfd83
+    /* Heimdal's KCM returns KRB5_CC_END if no cred is found. */
6cfd83
+    return (ret == KRB5_CC_END) ? KRB5_CC_NOTFOUND : map_invalid(ret);
6cfd83
 }
6cfd83
 
6cfd83
 static krb5_error_code KRB5_CALLCONV
6cfd83
diff --git a/src/tests/kcmserver.py b/src/tests/kcmserver.py
6cfd83
index 8c5e66ff1..25e6f2bbe 100644
6cfd83
--- a/src/tests/kcmserver.py
6cfd83
+++ b/src/tests/kcmserver.py
6cfd83
@@ -40,6 +40,7 @@ class KCMOpcodes(object):
6cfd83
     INITIALIZE = 4
6cfd83
     DESTROY = 5
6cfd83
     STORE = 6
6cfd83
+    RETRIEVE = 7
6cfd83
     GET_PRINCIPAL = 8
6cfd83
     GET_CRED_UUID_LIST = 9
6cfd83
     GET_CRED_BY_UUID = 10
6cfd83
@@ -54,6 +55,7 @@ class KCMOpcodes(object):
6cfd83
 
6cfd83
 
6cfd83
 class KRB5Errors(object):
6cfd83
+    KRB5_CC_NOTFOUND = -1765328243
6cfd83
     KRB5_CC_END = -1765328242
6cfd83
     KRB5_CC_NOSUPP = -1765328137
6cfd83
     KRB5_FCC_NOFILE = -1765328189
6cfd83
@@ -86,11 +88,29 @@ def get_cache(name):
6cfd83
     return cache
6cfd83
 
6cfd83
 
6cfd83
+def unpack_data(argbytes):
6cfd83
+    dlen, = struct.unpack('>L', argbytes[:4])
6cfd83
+    return argbytes[4:dlen+4], argbytes[dlen+4:]
6cfd83
+
6cfd83
+
6cfd83
 def unmarshal_name(argbytes):
6cfd83
     offset = argbytes.find(b'\0')
6cfd83
     return argbytes[0:offset], argbytes[offset+1:]
6cfd83
 
6cfd83
 
6cfd83
+def unmarshal_princ(argbytes):
6cfd83
+    # Ignore the type at argbytes[0:4].
6cfd83
+    ncomps, = struct.unpack('>L', argbytes[4:8])
6cfd83
+    realm, rest = unpack_data(argbytes[8:])
6cfd83
+    comps = []
6cfd83
+    for i in range(ncomps):
6cfd83
+        comp, rest = unpack_data(rest)
6cfd83
+        comps.append(comp)
6cfd83
+    # Asssume no quoting is needed.
6cfd83
+    princ = b'/'.join(comps) + b'@' + realm
6cfd83
+    return princ, rest
6cfd83
+
6cfd83
+
6cfd83
 def op_gen_new(argbytes):
6cfd83
     # Does not actually check for uniqueness.
6cfd83
     global next_unique
6cfd83
@@ -126,6 +146,22 @@ def op_store(argbytes):
6cfd83
     return 0, b''
6cfd83
 
6cfd83
 
6cfd83
+def op_retrieve(argbytes):
6cfd83
+    name, rest = unmarshal_name(argbytes)
6cfd83
+    # Ignore the flags at rest[0:4] and the header at rest[4:8].
6cfd83
+    # Assume there are client and server creds in the tag and match
6cfd83
+    # only against them.
6cfd83
+    cprinc, rest = unmarshal_princ(rest[8:])
6cfd83
+    sprinc, rest = unmarshal_princ(rest)
6cfd83
+    cache = get_cache(name)
6cfd83
+    for cred in (cache.creds[u] for u in cache.cred_uuids):
6cfd83
+        cred_cprinc, rest = unmarshal_princ(cred)
6cfd83
+        cred_sprinc, rest = unmarshal_princ(rest)
6cfd83
+        if cred_cprinc == cprinc and cred_sprinc == sprinc:
6cfd83
+            return 0, cred
6cfd83
+    return KRB5Errors.KRB5_CC_NOTFOUND, b''
6cfd83
+
6cfd83
+
6cfd83
 def op_get_principal(argbytes):
6cfd83
     name, rest = unmarshal_name(argbytes)
6cfd83
     cache = get_cache(name)
6cfd83
@@ -199,6 +235,7 @@ ophandlers = {
6cfd83
     KCMOpcodes.INITIALIZE : op_initialize,
6cfd83
     KCMOpcodes.DESTROY : op_destroy,
6cfd83
     KCMOpcodes.STORE : op_store,
6cfd83
+    KCMOpcodes.RETRIEVE : op_retrieve,
6cfd83
     KCMOpcodes.GET_PRINCIPAL : op_get_principal,
6cfd83
     KCMOpcodes.GET_CRED_UUID_LIST : op_get_cred_uuid_list,
6cfd83
     KCMOpcodes.GET_CRED_BY_UUID : op_get_cred_by_uuid,
6cfd83
@@ -243,10 +280,11 @@ def service_request(s):
6cfd83
     return True
6cfd83
 
6cfd83
 parser = optparse.OptionParser()
6cfd83
-parser.add_option('-c', '--credlist', action='store_true', dest='credlist',
6cfd83
-                  default=False, help='Support KCM_OP_GET_CRED_LIST')
6cfd83
+parser.add_option('-f', '--fallback', action='store_true', dest='fallback',
6cfd83
+                  default=False, help='Do not support RETRIEVE/GET_CRED_LIST')
6cfd83
 (options, args) = parser.parse_args()
6cfd83
-if not options.credlist:
6cfd83
+if options.fallback:
6cfd83
+    del ophandlers[KCMOpcodes.RETRIEVE]
6cfd83
     del ophandlers[KCMOpcodes.GET_CRED_LIST]
6cfd83
 
6cfd83
 server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
6cfd83
diff --git a/src/tests/t_ccache.py b/src/tests/t_ccache.py
6cfd83
index 90040fb7b..6ea9fb969 100755
6cfd83
--- a/src/tests/t_ccache.py
6cfd83
+++ b/src/tests/t_ccache.py
6cfd83
@@ -25,7 +25,7 @@ from k5test import *
6cfd83
 kcm_socket_path = os.path.join(os.getcwd(), 'testdir', 'kcm')
6cfd83
 conf = {'libdefaults': {'kcm_socket': kcm_socket_path,
6cfd83
                         'kcm_mach_service': '-'}}
6cfd83
-realm = K5Realm(create_host=False, krb5_conf=conf)
6cfd83
+realm = K5Realm(krb5_conf=conf)
6cfd83
 
6cfd83
 keyctl = which('keyctl')
6cfd83
 out = realm.run([klist, '-c', 'KEYRING:process:abcd'], expected_code=1)
6cfd83
@@ -71,6 +71,11 @@ def collection_test(realm, ccname):
6cfd83
     realm.kinit('alice', password('alice'))
6cfd83
     realm.run([klist], expected_msg='Default principal: alice@')
6cfd83
     realm.run([klist, '-A', '-s'])
6cfd83
+    realm.run([kvno, realm.host_princ], expected_msg = 'kvno = 1')
6cfd83
+    realm.run([kvno, realm.host_princ], expected_msg = 'kvno = 1')
6cfd83
+    out = realm.run([klist])
6cfd83
+    if out.count(realm.host_princ) != 1:
6cfd83
+        fail('Wrong number of service tickets in cache')
6cfd83
     realm.run([kdestroy])
6cfd83
     output = realm.run([klist], expected_code=1)
6cfd83
     if 'No credentials cache' not in output and 'not found' not in output:
6cfd83
@@ -126,14 +131,14 @@ def collection_test(realm, ccname):
6cfd83
 
6cfd83
 collection_test(realm, 'DIR:' + os.path.join(realm.testdir, 'cc'))
6cfd83
 
6cfd83
-# Test KCM without and with GET_CRED_LIST support.
6cfd83
+# Test KCM with and without RETRIEVE and GET_CRED_LIST support.
6cfd83
 kcmserver_path = os.path.join(srctop, 'tests', 'kcmserver.py')
6cfd83
 kcmd = realm.start_server([sys.executable, kcmserver_path, kcm_socket_path],
6cfd83
                           'starting...')
6cfd83
 collection_test(realm, 'KCM:')
6cfd83
 stop_daemon(kcmd)
6cfd83
 os.remove(kcm_socket_path)
6cfd83
-realm.start_server([sys.executable, kcmserver_path, '-c', kcm_socket_path],
6cfd83
+realm.start_server([sys.executable, kcmserver_path, '-f', kcm_socket_path],
6cfd83
                    'starting...')
6cfd83
 collection_test(realm, 'KCM:')
6cfd83