krb5 commit: Add KDC authdata tests

Greg Hudson ghudson at mit.edu
Mon Jun 15 12:56:02 EDT 2015


https://github.com/krb5/krb5/commit/1c3c40454f18f2165b959e6ecd856d5ddbbcb4c2
commit 1c3c40454f18f2165b959e6ecd856d5ddbbcb4c2
Author: Greg Hudson <ghudson at mit.edu>
Date:   Thu Oct 2 12:40:25 2014 -0400

    Add KDC authdata tests
    
    Add a new test script t_authdata.py and a C harness adata.c to test
    KDC authdata handling logic.  KDB module authdata is not currently
    tested.

 .gitignore              |    1 +
 src/tests/Makefile.in   |   16 ++-
 src/tests/adata.c       |  296 +++++++++++++++++++++++++++++++++++++++++++++++
 src/tests/t_authdata.py |   90 ++++++++++++++
 4 files changed, 397 insertions(+), 6 deletions(-)

diff --git a/.gitignore b/.gitignore
index 72eaf7f..e86fea8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -249,6 +249,7 @@ testlog
 /src/slave/kpropd
 /src/slave/kproplog
 
+/src/tests/adata
 /src/tests/gcred
 /src/tests/hist
 /src/tests/hrealm
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 6fdbbef..121c1e1 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -5,10 +5,10 @@ SUBDIRS = resolve asn.1 create hammer verify gssapi dejagnu shlib \
 
 RUN_SETUP = @KRB5_RUN_ENV@ KRB5_KDC_PROFILE=kdc.conf KRB5_CONFIG=krb5.conf
 
-OBJS= gcred.o hist.o hrealm.o kdbtest.o plugorder.o t_init_creds.o \
+OBJS= adata.o gcred.o hist.o hrealm.o kdbtest.o plugorder.o t_init_creds.o \
 	t_localauth.o rdreq.o responder.o s2p.o
-EXTRADEPSRCS= gcred.c hist.c hrealm.c kdbtest.c plugorder.c t_init_creds.c \
-	t_localauth.c rdreq.o responder.c s2p.c
+EXTRADEPSRCS= adata.c gcred.c hist.c hrealm.c kdbtest.c plugorder.c \
+	t_init_creds.c t_localauth.c rdreq.o responder.c s2p.c
 
 TEST_DB = ./testdb
 TEST_REALM = FOO.TEST.REALM
@@ -20,6 +20,9 @@ TEST_PREFIX = "foo bar"
 KADMIN_OPTS= -d $(TEST_DB) -r $(TEST_REALM) -P $(TEST_MKEY)
 KTEST_OPTS= $(KADMIN_OPTS) -p $(TEST_PREFIX) -n $(TEST_NUM) -D $(TEST_DEPTH)
 
+adata: adata.o $(KRB5_BASE_DEPLIBS)
+	$(CC_LINK) -o $@ adata.o $(KRB5_BASE_LIBS)
+
 gcred: gcred.o $(KRB5_BASE_DEPLIBS)
 	$(CC_LINK) -o $@ gcred.o $(KRB5_BASE_LIBS)
 
@@ -97,8 +100,8 @@ kdb_check: kdc.conf krb5.conf
 	$(RUN_SETUP) $(VALGRIND) ../kadmin/dbutil/kdb5_util $(KADMIN_OPTS) destroy -f
 	$(RM) $(TEST_DB)* stash_file
 
-check-pytests:: gcred hist hrealm kdbtest plugorder rdreq responder s2p
-check-pytests:: t_init_creds t_localauth unlockiter
+check-pytests:: adata gcred hist hrealm kdbtest plugorder rdreq responder
+check-pytests:: s2p t_init_creds t_localauth unlockiter
 	$(RUNPYTEST) $(srcdir)/t_general.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_dump.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_iprop.py $(PYTESTFLAGS)
@@ -142,9 +145,10 @@ check-pytests:: t_init_creds t_localauth unlockiter
 	$(RUNPYTEST) $(srcdir)/t_proxy.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_unlockiter.py $(PYTESTFLAGS)
 	$(RUNPYTEST) $(srcdir)/t_errmsg.py $(PYTESTFLAGS)
+	$(RUNPYTEST) $(srcdir)/t_authdata.py $(PYTESTFLAGS)
 
 clean::
-	$(RM) gcred hist hrealm kdbtest plugorder rdreq responder s2p
+	$(RM) adata gcred hist hrealm kdbtest plugorder rdreq responder s2p
 	$(RM) t_init_creds t_localauth krb5.conf kdc.conf
 	$(RM) -rf kdc_realm/sandbox ldap
 	$(RM) au.log
diff --git a/src/tests/adata.c b/src/tests/adata.c
new file mode 100644
index 0000000..ec63044
--- /dev/null
+++ b/src/tests/adata.c
@@ -0,0 +1,296 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* tests/adata.c - Test harness for KDC authorization data */
+/*
+ * Copyright (C) 2014 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.
+ */
+
+/*
+ * Usage: ./adata [-c ccname] [-p clientprinc] serviceprinc
+ *            [ad-type ad-contents ...]
+ *
+ * This program acquires credentials for the specified service principal, using
+ * the specified or default ccache, possibly including requested authdata.  The
+ * resulting ticket is decrypted using the default keytab, and the authdata in
+ * the ticket are displayed to stdout.
+ *
+ * In the requested authdata types, the type may be prefixed with '?' for an
+ * AD-IF-RELEVANT container, '!' for an AD-MANDATORY-FOR-KDC container, or '^'
+ * for an AD-KDC-ISSUED container checksummed with a random AES256 key.
+ * Multiple prefixes may be specified for nested container.
+ *
+ * In the output, authdata containers will be flattened and displayed with the
+ * above prefixes, with AD-KDC-ISSUED containers verified using the ticket
+ * session key.  Nested containers only display the prefix for the innermost
+ * container.
+ */
+
+#include <k5-int.h>
+#include <ctype.h>
+
+static krb5_context ctx;
+
+static void display_authdata_list(krb5_authdata **list, krb5_keyblock *skey,
+                                  krb5_keyblock *tktkey, char prefix_byte);
+
+static void
+check(krb5_error_code code)
+{
+    const char *errmsg;
+
+    if (code) {
+        errmsg = krb5_get_error_message(ctx, code);
+        fprintf(stderr, "%s\n", errmsg);
+        krb5_free_error_message(ctx, errmsg);
+        exit(1);
+    }
+}
+
+static krb5_authdatatype
+get_type_for_prefix(int prefix_byte)
+{
+    if (prefix_byte == '?')
+        return KRB5_AUTHDATA_IF_RELEVANT;
+    if (prefix_byte == '!')
+        return KRB5_AUTHDATA_MANDATORY_FOR_KDC;
+    if (prefix_byte == '^')
+        return KRB5_AUTHDATA_KDC_ISSUED;
+    abort();
+}
+
+static int
+get_prefix_byte(krb5_authdata *ad)
+{
+    if (ad->ad_type == KRB5_AUTHDATA_IF_RELEVANT)
+        return '?';
+    if (ad->ad_type == KRB5_AUTHDATA_MANDATORY_FOR_KDC)
+        return '!';
+    if (ad->ad_type == KRB5_AUTHDATA_KDC_ISSUED)
+        return '^';
+    abort();
+}
+
+/* Construct a container of type ad_type for the single authdata element
+ * content.  For KDC-ISSUED containers, use a random checksum key. */
+static krb5_authdata *
+make_container(krb5_authdatatype ad_type, krb5_authdata *content)
+{
+    krb5_authdata *list[2], **enclist, *ad;
+    krb5_keyblock kb;
+
+    list[0] = content;
+    list[1] = NULL;
+
+    if (ad_type == KRB5_AUTHDATA_KDC_ISSUED) {
+        check(krb5_c_make_random_key(ctx, ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+                                     &kb));
+        check(krb5_make_authdata_kdc_issued(ctx, &kb, NULL, list, &enclist));
+        krb5_free_keyblock_contents(ctx, &kb);
+    } else {
+        check(krb5_encode_authdata_container(ctx, ad_type, list, &enclist));
+    }
+
+    /* Grab the first element from the encoded list and free the array. */
+    ad = enclist[0];
+    free(enclist);
+    return ad;
+}
+
+/* Parse typestr and contents into an authdata element. */
+static krb5_authdata *
+make_authdata(const char *typestr, const char *contents)
+{
+    krb5_authdata *inner_ad, *ad;
+
+    if (*typestr == '?' || *typestr == '!' || *typestr == '^') {
+        inner_ad = make_authdata(typestr + 1, contents);
+        return make_container(get_type_for_prefix(*typestr), inner_ad);
+    }
+
+    ad = malloc(sizeof(*ad));
+    assert(ad != NULL);
+    ad->magic = KV5M_AUTHDATA;
+    ad->ad_type = atoi(typestr);
+    ad->length = strlen(contents);
+    ad->contents = (unsigned char *)strdup(contents);
+    assert(ad->contents != NULL);
+    return ad;
+}
+
+static krb5_authdata **
+get_container_contents(krb5_authdata *ad, krb5_keyblock *skey,
+                       krb5_keyblock *tktkey)
+{
+    krb5_authdata **inner_ad;
+
+    if (ad->ad_type == KRB5_AUTHDATA_KDC_ISSUED)
+        check(krb5_verify_authdata_kdc_issued(ctx, skey, ad, NULL, &inner_ad));
+    else
+        check(krb5_decode_authdata_container(ctx, ad->ad_type, ad, &inner_ad));
+    return inner_ad;
+}
+
+/* Display ad as either a hex dump or ASCII text. */
+static void
+display_binary_or_ascii(krb5_authdata *ad)
+{
+    krb5_boolean binary = FALSE;
+    unsigned char *p;
+
+    for (p = ad->contents; p < ad->contents + ad->length; p++) {
+        if (!isascii(*p) || !isprint(*p))
+            binary = TRUE;
+    }
+    if (binary) {
+        for (p = ad->contents; p < ad->contents + ad->length; p++)
+            printf("%02X", *p);
+    } else {
+        printf("%.*s", (int)ad->length, ad->contents);
+    }
+}
+
+/* Display the contents of an authdata element, prefixed by prefix_byte.  skey
+ * must be the ticket session key. */
+static void
+display_authdata(krb5_authdata *ad, krb5_keyblock *skey, krb5_keyblock *tktkey,
+                 int prefix_byte)
+{
+    krb5_authdata **inner_ad;
+
+    if (ad->ad_type == KRB5_AUTHDATA_IF_RELEVANT ||
+        ad->ad_type == KRB5_AUTHDATA_MANDATORY_FOR_KDC ||
+        ad->ad_type == KRB5_AUTHDATA_KDC_ISSUED) {
+        /* Decode and display the contents. */
+        inner_ad = get_container_contents(ad, skey, tktkey);
+        display_authdata_list(inner_ad, skey, tktkey, get_prefix_byte(ad));
+        krb5_free_authdata(ctx, inner_ad);
+        return;
+    }
+
+    printf("%c", prefix_byte);
+    printf("%d: ", (int)ad->ad_type);
+    display_binary_or_ascii(ad);
+    printf("\n");
+}
+
+static void
+display_authdata_list(krb5_authdata **list, krb5_keyblock *skey,
+                      krb5_keyblock *tktkey, char prefix_byte)
+{
+    if (list == NULL)
+        return;
+    for (; *list != NULL; list++)
+        display_authdata(*list, skey, tktkey, prefix_byte);
+}
+
+int
+main(int argc, char **argv)
+{
+    const char *ccname = NULL, *clientname = NULL;
+    krb5_principal client, server;
+    krb5_ccache ccache;
+    krb5_keytab keytab;
+    krb5_creds in_creds, *creds;
+    krb5_ticket *ticket;
+    krb5_authdata **req_authdata = NULL, *ad;
+    krb5_keytab_entry ktent;
+    size_t count;
+    int c;
+
+    check(krb5_init_context(&ctx));
+
+    while ((c = getopt(argc, argv, "+c:p:")) != -1) {
+        switch (c) {
+        case 'c':
+            ccname = optarg;
+            break;
+        case 'p':
+            clientname = optarg;
+            break;
+        default:
+            abort();
+        }
+    }
+    argv += optind;
+    /* Parse arguments. */
+    assert(*argv != NULL);
+    check(krb5_parse_name(ctx, *argv++, &server));
+
+    count = 0;
+    for (; argv[0] != NULL && argv[1] != NULL; argv += 2) {
+        ad = make_authdata(argv[0], argv[1]);
+        req_authdata = realloc(req_authdata,
+                               (count + 2) * sizeof(*req_authdata));
+        assert(req_authdata != NULL);
+        req_authdata[count++] = ad;
+        req_authdata[count] = NULL;
+    }
+    assert(*argv == NULL);
+
+    if (ccname != NULL)
+        check(krb5_cc_resolve(ctx, ccname, &ccache));
+    else
+        check(krb5_cc_default(ctx, &ccache));
+
+    if (clientname != NULL)
+        check(krb5_parse_name(ctx, clientname, &client));
+    else
+        check(krb5_cc_get_principal(ctx, ccache, &client));
+
+    memset(&in_creds, 0, sizeof(in_creds));
+    in_creds.client = client;
+    in_creds.server = server;
+    in_creds.authdata = req_authdata;
+
+    check(krb5_get_credentials(ctx, KRB5_GC_NO_STORE, ccache, &in_creds,
+                               &creds));
+
+    check(krb5_decode_ticket(&creds->ticket, &ticket));
+    check(krb5_kt_default(ctx, &keytab));
+    check(krb5_kt_get_entry(ctx, keytab, server, ticket->enc_part.kvno,
+                            ticket->enc_part.enctype, &ktent));
+    check(krb5_decrypt_tkt_part(ctx, &ktent.key, ticket));
+
+    display_authdata_list(ticket->enc_part2->authorization_data,
+                          ticket->enc_part2->session, &ktent.key, ' ');
+
+    while (count > 0) {
+        free(req_authdata[--count]->contents);
+        free(req_authdata[count]);
+    }
+    free(req_authdata);
+    krb5_free_keytab_entry_contents(ctx, &ktent);
+    krb5_free_creds(ctx, creds);
+    krb5_free_ticket(ctx, ticket);
+    krb5_free_principal(ctx, client);
+    krb5_free_principal(ctx, server);
+    krb5_cc_close(ctx, ccache);
+    krb5_kt_close(ctx, keytab);
+    krb5_free_context(ctx);
+    return 0;
+}
diff --git a/src/tests/t_authdata.py b/src/tests/t_authdata.py
new file mode 100644
index 0000000..0b8aaa6
--- /dev/null
+++ b/src/tests/t_authdata.py
@@ -0,0 +1,90 @@
+#!/usr/bin/python
+from k5test import *
+
+# Load the sample KDC authdata module.
+greet_path = os.path.join(buildtop, 'plugins', 'authdata', 'greet_server',
+                          'greet_server.so')
+conf = {'plugins': {'kdcauthdata': {'module': 'greet:' + greet_path}}}
+realm = K5Realm(krb5_conf=conf)
+
+# With no requested authdata, we expect to see SIGNTICKET (512) in an
+# if-relevant container and the greet authdata in a kdc-issued
+# container.
+out = realm.run(['./adata', realm.host_princ])
+if '?512: ' not in out or '^-42: Hello' not in out:
+    fail('expected authdata not seen for basic request')
+
+# Requested authdata is copied into the ticket, with KDC-only types
+# filtered out.  (128 is win2k-pac, which should be filtered.)
+out = realm.run(['./adata', realm.host_princ, '-5', 'test1', '?-6', 'test2',
+                 '128', 'fakepac', '?128', 'ifrelfakepac',
+                 '^-8', 'fakekdcissued', '?^-8', 'ifrelfakekdcissued'])
+if ' -5: test1' not in out or '?-6: test2' not in out:
+    fail('expected authdata not seen for request with authdata')
+if 'fake' in out:
+    fail('KDC-only authdata not filtered for request with authdata')
+
+out = realm.run(['./adata', realm.host_princ, '!-1', 'mandatoryforkdc'],
+                expected_code=1)
+if 'KDC policy rejects request' not in out:
+    fail('Wrong error seen for mandatory-for-kdc failure')
+
+# The no_auth_data_required server flag should suppress SIGNTICKET,
+# but not module or request authdata.
+realm.run([kadminl, 'ank', '-randkey', '+no_auth_data_required', 'noauth'])
+realm.extract_keytab('noauth', realm.keytab)
+out = realm.run(['./adata', 'noauth', '-2', 'test'])
+if '^-42: Hello' not in out or ' -2: test' not in out:
+    fail('expected authdata not seen for no_auth_data_required request')
+if '512: ' in out:
+    fail('SIGNTICKET authdata seen for no_auth_data_required request')
+
+# Cross-realm TGT requests should also suppress SIGNTICKET, but not
+# module or request authdata.
+realm.addprinc('krbtgt/XREALM')
+realm.extract_keytab('krbtgt/XREALM', realm.keytab)
+out = realm.run(['./adata', 'krbtgt/XREALM', '-3', 'test'])
+if '^-42: Hello' not in out or ' -3: test' not in out:
+    fail('expected authdata not seen for cross-realm TGT request')
+if '512: ' in out:
+    fail('SIGNTICKET authdata seen in cross-realm TGT')
+
+realm.stop()
+
+if not os.path.exists(os.path.join(plugins, 'preauth', 'pkinit.so')):
+    skipped('anonymous ticket authdata tests', 'PKINIT not built')
+else:
+    # Set up a realm with PKINIT support and get anonymous tickets.
+    certs = os.path.join(srctop, 'tests', 'dejagnu', 'pkinit-certs')
+    ca_pem = os.path.join(certs, 'ca.pem')
+    kdc_pem = os.path.join(certs, 'kdc.pem')
+    privkey_pem = os.path.join(certs, 'privkey.pem')
+    pkinit_conf = {'realms': {'$realm': {
+                'pkinit_anchors': 'FILE:%s' % ca_pem,
+                'pkinit_identity': 'FILE:%s,%s' % (kdc_pem, privkey_pem)}}}
+    conf.update(pkinit_conf)
+    realm = K5Realm(krb5_conf=conf, get_creds=False)
+    realm.addprinc('WELLKNOWN/ANONYMOUS')
+    realm.kinit('@%s' % realm.realm, flags=['-n'])
+
+    # SIGNTICKET and module authdata should be suppressed for
+    # anonymous tickets, but not request authdata.
+    out = realm.run(['./adata', realm.host_princ, '-4', 'test'])
+    if ' -4: test' not in out:
+        fail('expected authdata not seen for anonymous request')
+    if '512: ' in out or '-42: ' in out:
+        fail('SIGNTICKET or greet authdata seen for anonymous request')
+
+# KDB authdata is not tested here; we would need a test KDB module to
+# generate authdata, and also some additions to the test harness.  The
+# current rules we would want to test are:
+#
+# * The no_auth_data_required server flag suppresses KDB authdata in
+#   TGS requests.
+# * KDB authdata is also suppressed in TGS requests if the TGT
+#   contains no authdata and the request is not cross-realm or S4U.
+# * For AS requests, KDB authdata is suppressed if negative
+#   KRB5_PADATA_PAC_REQUEST padata is present in the request.
+# * KDB authdata is suppressed for anonymous tickets.
+
+success('Authorization data tests')


More information about the cvs-krb5 mailing list