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