krb5 commit: Fix gss_krb5_set_allowable_enctypes for acceptor
Greg Hudson
ghudson at MIT.EDU
Mon Aug 12 11:48:48 EDT 2013
https://github.com/krb5/krb5/commit/2e956074b228ff4df3b7462037ab69e4e88ffffe
commit 2e956074b228ff4df3b7462037ab69e4e88ffffe
Author: Greg Hudson <ghudson at mit.edu>
Date: Mon Aug 5 23:47:52 2013 -0400
Fix gss_krb5_set_allowable_enctypes for acceptor
The acceptor implementation of gss_krb5_set_allowable_enctypes (added
in 1.9.1) is intended to restrict the acceptor subkey negotiated by
krb5_rd_req(). It uses the same approach as the initiator, calling
krb5_set_default_tgs_enctypes on the context. This has the unwanted
side effect of restricting the encryption key of the ticket, because
krb5_decrypt_tkt_part has checked krb5_is_permitted_enctype on the
ticket encryption key since 1.8.
Instead, use krb5_auth_con_setpermetypes on the auth context. This
list is only used for session key enctype negotiation. Also add
automated tests to verify that gss_krb5_set_allowable_enctypes works
as desired.
ticket: 7688 (new)
target_version: 1.11.4
tags: pullup
src/lib/gssapi/krb5/accept_sec_context.c | 4 +-
src/tests/gssapi/Makefile.in | 3 +
src/tests/gssapi/t_enctypes.c | 229 ++++++++++++++++++++++++++++++
src/tests/gssapi/t_enctypes.py | 149 +++++++++++++++++++
4 files changed, 383 insertions(+), 2 deletions(-)
diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c
index 42ac122..82bd013 100644
--- a/src/lib/gssapi/krb5/accept_sec_context.c
+++ b/src/lib/gssapi/krb5/accept_sec_context.c
@@ -625,8 +625,8 @@ kg_accept_krb5(minor_status, context_handle,
/* Limit the encryption types negotiated (if requested). */
if (cred->req_enctypes) {
- if ((code = krb5_set_default_tgs_enctypes(context,
- cred->req_enctypes))) {
+ if ((code = krb5_auth_con_setpermetypes(context, auth_context,
+ cred->req_enctypes))) {
major_status = GSS_S_FAILURE;
goto fail;
}
diff --git a/src/tests/gssapi/Makefile.in b/src/tests/gssapi/Makefile.in
index c53bda5..da6f534 100644
--- a/src/tests/gssapi/Makefile.in
+++ b/src/tests/gssapi/Makefile.in
@@ -32,6 +32,7 @@ check-pytests:: ccinit ccrefresh t_accname t_ccselect t_credstore \
$(RUNPYTEST) $(srcdir)/t_gssapi.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_ccselect.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_client_keytab.py $(PYTESTFLAGS)
+ $(RUNPYTEST) $(srcdir)/t_enctypes.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_export_cred.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_s4u.py $(PYTESTFLAGS)
@@ -45,6 +46,8 @@ t_ccselect: t_ccselect.o $(COMMON_DEPS)
$(CC_LINK) -o $@ t_ccselect.o $(COMMON_LIBS)
t_credstore: t_credstore.o $(COMMON_DEPS)
$(CC_LINK) -o $@ t_credstore.o $(COMMON_LIBS)
+t_enctypes: t_enctypes.o $(COMMON_DEPS)
+ $(CC_LINK) -o $@ t_enctypes.o $(COMMON_LIBS)
t_export_cred: t_export_cred.o $(COMMON_DEPS)
$(CC_LINK) -o $@ t_export_cred.o $(COMMON_LIBS)
t_export_name: t_export_name.o $(COMMON_DEPS)
diff --git a/src/tests/gssapi/t_enctypes.c b/src/tests/gssapi/t_enctypes.c
new file mode 100644
index 0000000..c1e02fa
--- /dev/null
+++ b/src/tests/gssapi/t_enctypes.c
@@ -0,0 +1,229 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* tests/gssapi/t_enctypes.c - gss_krb5_set_allowable_enctypes test */
+/*
+ * Copyright (C) 2013 by the Massachusetts Institute of Technology.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include "k5-int.h"
+#include "common.h"
+
+/*
+ * This test program performs a gss_init_sec_context/gss_accept_sec_context
+ * exchange with the krb5 mech, the default initiator name, a specified
+ * principal name as target name, and the default acceptor name. Before the
+ * exchange, gss_set_allowable_enctypes is called for the initiator and the
+ * acceptor cred if requested. If the exchange is successful, the resulting
+ * contexts are exported with gss_krb5_export_lucid_sec_context, checked for
+ * mismatches, and the GSS protocol and keys are displayed. Exits with status
+ * 0 if all operations are successful, or 1 if not.
+ *
+ * Usage: ./t_enctypes [-i initenctypes] [-a accenctypes] targetname
+ */
+
+static void
+usage()
+{
+ errout("Usage: t_enctypes [-i initenctypes] [-a accenctypes] "
+ "targetname");
+}
+
+/* Error out if ikey is not the same as akey. */
+static void
+check_key_match(gss_krb5_lucid_key_t *ikey, gss_krb5_lucid_key_t *akey)
+{
+ if (ikey->type != akey->type || ikey->length != akey->length ||
+ memcmp(ikey->data, akey->data, ikey->length) != 0)
+ errout("Initiator and acceptor keys do not match");
+}
+
+/* Display the name of enctype. */
+static void
+display_enctype(krb5_enctype enctype)
+{
+ char ename[128];
+
+ if (krb5_enctype_to_name(enctype, FALSE, ename, sizeof(ename)) == 0)
+ fputs(ename, stdout);
+ else
+ fputs("(unknown)", stdout);
+}
+
+int
+main(int argc, char *argv[])
+{
+ krb5_error_code ret;
+ krb5_context kctx = NULL;
+ krb5_enctype *ienc = NULL, *aenc = NULL, zero = 0;
+ OM_uint32 minor, major, flags;
+ gss_name_t tname;
+ gss_cred_id_t icred = GSS_C_NO_CREDENTIAL, acred = GSS_C_NO_CREDENTIAL;
+ gss_ctx_id_t ictx = GSS_C_NO_CONTEXT, actx = GSS_C_NO_CONTEXT;
+ gss_buffer_desc itok, atok, tmp;
+ gss_krb5_lucid_context_v1_t *ilucid, *alucid;
+ gss_krb5_rfc1964_keydata_t *i1964, *a1964;
+ gss_krb5_cfx_keydata_t *icfx, *acfx;
+ size_t count;
+ void *lptr;
+ int c;
+
+ ret = krb5_init_context(&kctx);
+ check_k5err(kctx, "krb5_init_context", ret);
+
+ /* Parse arguments. */
+ while ((c = getopt(argc, argv, "i:a:")) != -1) {
+ switch (c) {
+ case 'i':
+ ret = krb5int_parse_enctype_list(kctx, "", optarg, &zero, &ienc);
+ check_k5err(kctx, "krb5_parse_enctype_list(initiator)", ret);
+ break;
+ case 'a':
+ ret = krb5int_parse_enctype_list(kctx, "", optarg, &zero, &aenc);
+ check_k5err(kctx, "krb5_parse_enctype_list(acceptor)", ret);
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ usage();
+ tname = import_name(*argv);
+
+ if (ienc != NULL) {
+ major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+ &mechset_krb5, GSS_C_INITIATE, &icred, NULL,
+ NULL);
+ check_gsserr("gss_acquire_cred(initiator)", major, minor);
+
+ for (count = 0; ienc[count]; count++);
+ major = gss_krb5_set_allowable_enctypes(&minor, icred, count, ienc);
+ check_gsserr("gss_krb5_set_allowable_enctypes(init)", major, minor);
+ }
+ if (aenc != NULL) {
+ major = gss_acquire_cred(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE,
+ &mechset_krb5, GSS_C_ACCEPT, &acred, NULL,
+ NULL);
+ check_gsserr("gss_acquire_cred(acceptor)", major, minor);
+
+ for (count = 0; aenc[count]; count++);
+ major = gss_krb5_set_allowable_enctypes(&minor, acred, count, aenc);
+ check_gsserr("gss_krb5_set_allowable_enctypes(acc)", major, minor);
+ }
+
+ /* Create initiator context and get the first token. */
+ itok.value = NULL;
+ itok.length = 0;
+ flags = GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_MUTUAL_FLAG;
+ major = gss_init_sec_context(&minor, icred, &ictx, tname, &mech_krb5,
+ flags, GSS_C_INDEFINITE,
+ GSS_C_NO_CHANNEL_BINDINGS, GSS_C_NO_BUFFER,
+ NULL, &itok, NULL, NULL);
+ check_gsserr("gss_init_sec_context(1)", major, minor);
+ if (major != GSS_S_CONTINUE_NEEDED)
+ errout("gss_init_sec_context(1) unexpected complete");
+
+ /* Pass the initiator token to gss_accept_sec_context. */
+ atok.value = NULL;
+ atok.length = 0;
+ major = gss_accept_sec_context(&minor, &actx, acred, &itok,
+ GSS_C_NO_CHANNEL_BINDINGS, NULL, NULL,
+ &atok, NULL, NULL, NULL);
+ check_gsserr("gss_accept_sec_context", major, minor);
+ if (major != GSS_S_COMPLETE)
+ errout("gss_accept_sec_context unexpected continue");
+
+ /* Pass the return token to gss_init_sec_context again. */
+ tmp.value = NULL;
+ tmp.length = 0;
+ major = gss_init_sec_context(&minor, icred, &ictx, tname, &mech_krb5,
+ flags, GSS_C_INDEFINITE,
+ GSS_C_NO_CHANNEL_BINDINGS, &atok, NULL, &tmp,
+ NULL, NULL);
+ check_gsserr("gss_init_sec_context(2)", major, minor);
+ if (major != GSS_S_COMPLETE)
+ errout("gss_init_sec_context(2) unexpected continue");
+
+ /* Export to lucid contexts. */
+ major = gss_krb5_export_lucid_sec_context(&minor, &ictx, 1, &lptr);
+ check_gsserr("gss_export_lucid_sec_context(initiator)", major, minor);
+ ilucid = lptr;
+ major = gss_krb5_export_lucid_sec_context(&minor, &actx, 1, &lptr);
+ check_gsserr("gss_export_lucid_sec_context(acceptor)", major, minor);
+ alucid = lptr;
+
+ /* Grab the session keys and make sure they match. */
+ if (ilucid->protocol != alucid->protocol)
+ errout("Initiator/acceptor protocol mismatch");
+ if (ilucid->protocol) {
+ icfx = &ilucid->cfx_kd;
+ acfx = &alucid->cfx_kd;
+ if (icfx->have_acceptor_subkey != acfx->have_acceptor_subkey)
+ errout("Initiator/acceptor have_acceptor_subkey mismatch");
+ check_key_match(&icfx->ctx_key, &acfx->ctx_key);
+ if (icfx->have_acceptor_subkey)
+ check_key_match(&icfx->acceptor_subkey, &acfx->acceptor_subkey);
+ fputs("cfx ", stdout);
+ display_enctype(icfx->ctx_key.type);
+ if (icfx->have_acceptor_subkey) {
+ fputs(" ", stdout);
+ display_enctype(icfx->acceptor_subkey.type);
+ }
+ fputs("\n", stdout);
+ } else {
+ i1964 = &ilucid->rfc1964_kd;
+ a1964 = &alucid->rfc1964_kd;
+ if (i1964->sign_alg != a1964->sign_alg ||
+ i1964->seal_alg != a1964->seal_alg)
+ errout("Initiator/acceptor sign or seal alg mismatch");
+ check_key_match(&i1964->ctx_key, &a1964->ctx_key);
+ fputs("rfc1964 ", stdout);
+ display_enctype(i1964->ctx_key.type);
+ fputs("\n", stdout);
+ }
+
+ krb5_free_context(kctx);
+ free(ienc);
+ free(aenc);
+ (void)gss_release_name(&minor, &tname);
+ (void)gss_release_cred(&minor, &icred);
+ (void)gss_release_cred(&minor, &acred);
+ (void)gss_delete_sec_context(&minor, &ictx, NULL);
+ (void)gss_delete_sec_context(&minor, &actx, NULL);
+ (void)gss_release_buffer(&minor, &itok);
+ (void)gss_release_buffer(&minor, &atok);
+ (void)gss_release_buffer(&minor, &tmp);
+ (void)gss_krb5_free_lucid_sec_context(&minor, ilucid);
+ (void)gss_krb5_free_lucid_sec_context(&minor, alucid);
+ return 0;
+}
diff --git a/src/tests/gssapi/t_enctypes.py b/src/tests/gssapi/t_enctypes.py
new file mode 100644
index 0000000..d7577bf
--- /dev/null
+++ b/src/tests/gssapi/t_enctypes.py
@@ -0,0 +1,149 @@
+#!/usr/bin/python
+from k5test import *
+
+# Define some convenience abbreviations for enctypes we will see in
+# test program output. For background, aes256 and aes128 are "CFX
+# enctypes", meaning that they imply support for RFC 4121, while des3
+# and rc4 are not. DES3 keys will appear as 'des3-cbc-raw' in
+# t_enctypes output because that's how GSSAPI does raw triple-DES
+# encryption without the RFC3961 framing.
+aes256 = 'aes256-cts-hmac-sha1-96'
+aes128 = 'aes128-cts-hmac-sha1-96'
+des3 = 'des3-cbc-sha1'
+des3raw = 'des3-cbc-raw'
+rc4 = 'arcfour-hmac'
+
+# These tests make assumptions about the default enctype lists, so set
+# them explicitly rather than relying on the library defaults.
+enctypes='aes des3 rc4'
+supp='aes256-cts:normal aes128-cts:normal des3-cbc-sha1:normal rc4-hmac:normal'
+conf = {'libdefaults': {
+ 'default_tgs_enctypes': enctypes,
+ 'default_tkt_enctypes': enctypes,
+ 'permitted_enctypes': enctypes},
+ 'realms': {'$realm': {'supported_enctypes': supp}}}
+realm = K5Realm(krb5_conf=conf)
+shutil.copyfile(realm.ccache, os.path.join(realm.testdir, 'save'))
+
+# Return an argument list for running t_enctypes with optional initiator
+# and acceptor enctype lists.
+def cmdline(ienc, aenc):
+ iflags = ienc and ['-i', ienc] or []
+ aflags = aenc and ['-a', aenc] or []
+ return ['./t_enctypes'] + iflags + aflags + ['p:' + realm.host_princ]
+
+
+# Run t_enctypes with optional initiator and acceptor enctype lists,
+# and check that it succeeds with the expected output. Also check
+# that the ticket we got has the expected encryption key and session
+# key.
+def test(msg, ienc, aenc, tktenc='', tktsession='', proto='', isubkey='',
+ asubkey=None):
+ shutil.copyfile(os.path.join(realm.testdir, 'save'), realm.ccache)
+ # Run the test program and check its output.
+ out = realm.run(cmdline(ienc, aenc)).split()
+ if out[0] != proto or out[1] != isubkey:
+ fail(msg)
+ if asubkey is not None and (len(out) < 3 or out[2] != asubkey):
+ fail(msg)
+ lines = realm.run([klist, '-e']).splitlines()
+ for ind, line in enumerate(lines):
+ if realm.host_princ in line:
+ if lines[ind + 1].strip() != ('Etype (skey, tkt): %s, %s' %
+ (tktsession, tktenc)):
+ fail(msg)
+ break
+
+# Run t_enctypes with optional initiator and acceptor enctype lists,
+# and check that it fails with the expected error message.
+def test_err(msg, ienc, aenc, expected_err):
+ shutil.copyfile(os.path.join(realm.testdir, 'save'), realm.ccache)
+ out = realm.run(cmdline(ienc, aenc), expected_code=1)
+ if expected_err not in out:
+ fail(msg)
+
+
+# By default, all of the key enctypes should be aes256.
+test('noargs', None, None,
+ tktenc=aes256, tktsession=aes256,
+ proto='cfx', isubkey=aes256, asubkey=aes256)
+
+# When the initiator constrains the permitted session enctypes to
+# aes128, the ticket encryption key should remain aes256. The client
+# initiator will not send an RFC 4537 upgrade list because it sees no
+# other permitted enctypes, so the acceptor subkey will not be
+# upgraded from aes128.
+test('init aes128', 'aes128-cts', None,
+ tktenc=aes256, tktsession=aes128,
+ proto='cfx', isubkey=aes128, asubkey=aes128)
+
+# If the initiator and acceptor both constrain the permitted session
+# enctypes to aes128, we should see the same keys as above. This
+# tests that the acceptor does not mistakenly contrain the ticket
+# encryption key.
+test('both aes128', 'aes128-cts', 'aes128-cts',
+ tktenc=aes256, tktsession=aes128,
+ proto='cfx', isubkey=aes128, asubkey=aes128)
+
+# If only the acceptor constrains the permitted session enctypes to
+# aes128, subkey negotiation fails because the acceptor considers the
+# aes256 session key to be non-permitted.
+test_err('acc aes128', None, 'aes128-cts', 'Encryption type not permitted')
+
+# If the initiator constrains the permitted session enctypes to des3,
+# no acceptor subkey will be generated because we can't upgrade to a
+# CFX enctype.
+test('init des3', 'des3', None,
+ tktenc=aes256, tktsession=des3,
+ proto='rfc1964', isubkey=des3raw, asubkey=None)
+
+# Force the ticket session key to be rc4, so we can test some subkey
+# upgrade cases. The ticket encryption key remains aes256.
+realm.run_kadminl('setstr %s session_enctypes rc4' % realm.host_princ)
+
+# With no arguments, the initiator should send an upgrade list of
+# [aes256 aes128 des3] and the acceptor should upgrade to an aes256
+# subkey.
+test('upgrade noargs', None, None,
+ tktenc=aes256, tktsession=rc4,
+ proto='cfx', isubkey=rc4, asubkey=aes256)
+
+# If the initiator won't permit rc4 as a session key, it won't be able
+# to get a ticket.
+test_err('upgrade init aes', 'aes', None, 'no support for encryption type')
+
+# If the initiator permits rc4 but prefers aes128, it will send an
+# upgrade list of [aes128] and the acceptor will upgrade to aes128.
+test('upgrade init aes128+rc4', 'aes128-cts rc4', None,
+ tktenc=aes256, tktsession=rc4,
+ proto='cfx', isubkey=rc4, asubkey=aes128)
+
+# If the initiator permits rc4 but prefers des3, it will send an
+# upgrade list of [des3], but the acceptor won't generate a subkey
+# because des3 isn't a CFX enctype.
+test('upgrade init des3+rc4', 'des3 rc4', None,
+ tktenc=aes256, tktsession=rc4,
+ proto='rfc1964', isubkey=rc4, asubkey=None)
+
+# If the acceptor permits only aes128, subkey negotiation will fail
+# because the ticket session key and initiator subkey are
+# non-permitted. (This is unfortunate if the acceptor's restriction
+# is only for the sake of the kernel, since we could upgrade to an
+# aes128 subkey, but it's the current semantics.)
+test_err('upgrade acc aes128', None, 'aes128-cts',
+ 'Encryption type ArcFour with HMAC/md5 not permitted')
+
+# If the acceptor permits rc4 but prefers aes128, it will negotiate an
+# upgrade to aes128.
+test('upgrade acc aes128 rc4', None, 'aes128-cts rc4',
+ tktenc=aes256, tktsession=rc4,
+ proto='cfx', isubkey=rc4, asubkey=aes128)
+
+# In this test, the initiator and acceptor each prefer an AES enctype
+# to rc4, but they can't agree on which one, so no subkey is
+# generated.
+test('upgrade mismatch', 'aes128-cts rc4', 'aes256-cts rc4',
+ tktenc=aes256, tktsession=rc4,
+ proto='rfc1964', isubkey=rc4, asubkey=None)
+
+success('gss_krb5_set_allowable_enctypes tests')
More information about the cvs-krb5
mailing list