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