diff options
author | Greg Hudson <ghudson@mit.edu> | 2014-10-02 12:40:25 -0400 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2015-06-15 12:47:08 -0400 |
commit | 1c3c40454f18f2165b959e6ecd856d5ddbbcb4c2 (patch) | |
tree | 4d7fdd38ed31e42b5b1d32fdfb054c71e52cf604 | |
parent | b9820f5b3bfe1347565a39b6f8dce97828e8a2a3 (diff) | |
download | krb5-1c3c40454f18f2165b959e6ecd856d5ddbbcb4c2.zip krb5-1c3c40454f18f2165b959e6ecd856d5ddbbcb4c2.tar.gz krb5-1c3c40454f18f2165b959e6ecd856d5ddbbcb4c2.tar.bz2 |
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.
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | src/tests/Makefile.in | 16 | ||||
-rw-r--r-- | src/tests/adata.c | 296 | ||||
-rw-r--r-- | src/tests/t_authdata.py | 90 |
4 files changed, 397 insertions, 6 deletions
@@ -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') |