Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
greghudson committed Apr 5, 2021
1 parent 06afae8 commit 795ebba
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 10 deletions.
2 changes: 1 addition & 1 deletion src/include/kcm.h
Expand Up @@ -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) */
Expand Down
52 changes: 49 additions & 3 deletions src/lib/krb5/ccache/cc_kcm.c
Expand Up @@ -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
Expand Down
44 changes: 41 additions & 3 deletions src/tests/kcmserver.py
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -199,6 +235,7 @@ def op_get_cred_list(argbytes):
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,
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 8 additions & 3 deletions src/tests/t_ccache.py
Expand Up @@ -25,7 +25,7 @@
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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:')

Expand Down

0 comments on commit 795ebba

Please sign in to comment.