167778
From 6909a4e3aa5c41cfd896b91cc8f9560481dddfd1 Mon Sep 17 00:00:00 2001
167778
From: Greg Hudson <ghudson@mit.edu>
167778
Date: Fri, 20 Jan 2017 12:44:12 -0500
167778
Subject: [PATCH] Add test cases for preauth fallback behavior
167778
167778
Add options to icred for performing optimistic preauth and setting
167778
preauth options, and for choosing between the normal and stepwise
167778
interfaces.  Add options to the test preauth module to allow induced
167778
failures at several points in processing, factoring out some padata
167778
manipulation functions into a new file to avoid repeating too much
167778
code.  Add test cases to t_preauth.py using the new facilities to
167778
exercise and verify several preauth fallback scenarios.  Amend the
167778
tryagain test case in t_pkinit.py to look for more trace log messages.
167778
167778
ticket: 8537
167778
(cherry picked from commit 748beda1e36d76bed8b06b272ecb72988eede94b)
167778
[rharwood@redhat.com: more expected_trace]
167778
---
167778
 src/plugins/preauth/test/Makefile.in |   4 +-
167778
 src/plugins/preauth/test/cltest.c    |  86 ++++++++++-----
167778
 src/plugins/preauth/test/common.c    |  61 +++++++++++
167778
 src/plugins/preauth/test/common.h    |  41 +++++++
167778
 src/plugins/preauth/test/deps        |  14 ++-
167778
 src/plugins/preauth/test/kdctest.c   |  96 ++++++++++------
167778
 src/tests/icred.c                    |  69 +++++++++---
167778
 src/tests/t_general.py               |   1 +
167778
 src/tests/t_pkinit.py                |  12 +-
167778
 src/tests/t_preauth.py               | 158 ++++++++++++++++++++++++++-
167778
 10 files changed, 452 insertions(+), 90 deletions(-)
167778
 create mode 100644 src/plugins/preauth/test/common.c
167778
 create mode 100644 src/plugins/preauth/test/common.h
167778
167778
diff --git a/src/plugins/preauth/test/Makefile.in b/src/plugins/preauth/test/Makefile.in
167778
index ac3cb8155..77321b60f 100644
167778
--- a/src/plugins/preauth/test/Makefile.in
167778
+++ b/src/plugins/preauth/test/Makefile.in
167778
@@ -9,9 +9,9 @@ RELDIR=../plugins/preauth/test
167778
 SHLIB_EXPDEPS=$(KRB5_BASE_DEPLIBS)
167778
 SHLIB_EXPLIBS=$(KRB5_BASE_LIBS)
167778
 
167778
-STLIBOBJS=cltest.o kdctest.o
167778
+STLIBOBJS=cltest.o kdctest.o common.o
167778
 
167778
-SRCS= $(srcdir)/cltest.c $(srcdir)/kdctest.c
167778
+SRCS= $(srcdir)/cltest.c $(srcdir)/kdctest.c $(srcdir)/common.c
167778
 
167778
 all-unix: all-liblinks
167778
 install-unix: install-libs
167778
diff --git a/src/plugins/preauth/test/cltest.c b/src/plugins/preauth/test/cltest.c
167778
index 4c31e1c0f..f5f7c5aba 100644
167778
--- a/src/plugins/preauth/test/cltest.c
167778
+++ b/src/plugins/preauth/test/cltest.c
167778
@@ -1,7 +1,7 @@
167778
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
167778
 /* plugins/preauth/test/cltest.c - Test clpreauth module */
167778
 /*
167778
- * Copyright (C) 2015 by the Massachusetts Institute of Technology.
167778
+ * Copyright (C) 2015, 2017 by the Massachusetts Institute of Technology.
167778
  * All rights reserved.
167778
  *
167778
  * Redistribution and use in source and binary forms, with or without
167778
@@ -32,7 +32,7 @@
167778
 
167778
 /*
167778
  * This module is used to test preauth interface features.  At this time, the
167778
- * clpreauth module does two things:
167778
+ * clpreauth module does the following:
167778
  *
167778
  * - It decrypts a message from the initial KDC pa-data using the reply key and
167778
  *   prints it to stdout.  (The unencrypted message "no key" can also be
167778
@@ -45,17 +45,27 @@
167778
  *   it to the server, instructing the kdcpreauth module to assert one or more
167778
  *   space-separated authentication indicators.  (This string is sent on both
167778
  *   round trips if a second round trip is requested.)
167778
+ *
167778
+ * - If a KDC_ERR_ENCTYPE_NOSUPP error with e-data is received, it prints the
167778
+ *   accompanying error padata and sends a follow-up request containing
167778
+ *   "tryagain".
167778
+ *
167778
+ * - If the "fail_optimistic", "fail_2rt", or "fail_tryagain" gic options are
167778
+ *   set, it fails with a recognizable error string at the requested point in
167778
+ *   processing.
167778
  */
167778
 
167778
 #include "k5-int.h"
167778
 #include <krb5/clpreauth_plugin.h>
167778
-
167778
-#define TEST_PA_TYPE -123
167778
+#include "common.h"
167778
 
167778
 static krb5_preauthtype pa_types[] = { TEST_PA_TYPE, 0 };
167778
 
167778
 struct client_state {
167778
     char *indicators;
167778
+    krb5_boolean fail_optimistic;
167778
+    krb5_boolean fail_2rt;
167778
+    krb5_boolean fail_tryagain;
167778
 };
167778
 
167778
 struct client_request_state {
167778
@@ -70,6 +80,7 @@ test_init(krb5_context context, krb5_clpreauth_moddata *moddata_out)
167778
     st = malloc(sizeof(*st));
167778
     assert(st != NULL);
167778
     st->indicators = NULL;
167778
+    st->fail_optimistic = st->fail_2rt = st->fail_tryagain = FALSE;
167778
     *moddata_out = (krb5_clpreauth_moddata)st;
167778
     return 0;
167778
 }
167778
@@ -114,7 +125,6 @@ test_process(krb5_context context, krb5_clpreauth_moddata moddata,
167778
     struct client_state *st = (struct client_state *)moddata;
167778
     struct client_request_state *reqst = (struct client_request_state *)modreq;
167778
     krb5_error_code ret;
167778
-    krb5_pa_data **list, *pa;
167778
     krb5_keyblock *k;
167778
     krb5_enc_data enc;
167778
     krb5_data plain;
167778
@@ -123,20 +133,18 @@ test_process(krb5_context context, krb5_clpreauth_moddata moddata,
167778
     if (pa_data->length == 0) {
167778
         /* This is an optimistic preauth test.  Send a recognizable padata
167778
          * value so the KDC knows not to expect a cookie. */
167778
-        list = k5calloc(2, sizeof(*list), &ret;;
167778
-        assert(!ret);
167778
-        pa = k5alloc(sizeof(*pa), &ret;;
167778
-        assert(!ret);
167778
-        pa->pa_type = TEST_PA_TYPE;
167778
-        pa->contents = (uint8_t *)strdup("optimistic");
167778
-        assert(pa->contents != NULL);
167778
-        pa->length = 10;
167778
-        list[0] = pa;
167778
-        list[1] = NULL;
167778
-        *out_pa_data = list;
167778
+        if (st->fail_optimistic) {
167778
+            k5_setmsg(context, KRB5_PREAUTH_FAILED, "induced optimistic fail");
167778
+            return KRB5_PREAUTH_FAILED;
167778
+        }
167778
+        *out_pa_data = make_pa_list("optimistic", 10);
167778
         return 0;
167778
     } else if (reqst->second_round_trip) {
167778
         printf("2rt: %.*s\n", pa_data->length, pa_data->contents);
167778
+        if (st->fail_2rt) {
167778
+            k5_setmsg(context, KRB5_PREAUTH_FAILED, "induced 2rt fail");
167778
+            return KRB5_PREAUTH_FAILED;
167778
+        }
167778
     } else if (pa_data->length == 6 &&
167778
                memcmp(pa_data->contents, "no key", 6) == 0) {
167778
         printf("no key\n");
167778
@@ -157,17 +165,34 @@ test_process(krb5_context context, krb5_clpreauth_moddata moddata,
167778
     reqst->second_round_trip = TRUE;
167778
 
167778
     indstr = (st->indicators != NULL) ? st->indicators : "";
167778
-    list = k5calloc(2, sizeof(*list), &ret;;
167778
-    assert(!ret);
167778
-    pa = k5alloc(sizeof(*pa), &ret;;
167778
-    assert(!ret);
167778
-    pa->pa_type = TEST_PA_TYPE;
167778
-    pa->contents = (uint8_t *)strdup(indstr);
167778
-    assert(pa->contents != NULL);
167778
-    pa->length = strlen(indstr);
167778
-    list[0] = pa;
167778
-    list[1] = NULL;
167778
-    *out_pa_data = list;
167778
+    *out_pa_data = make_pa_list(indstr, strlen(indstr));
167778
+    return 0;
167778
+}
167778
+
167778
+static krb5_error_code
167778
+test_tryagain(krb5_context context, krb5_clpreauth_moddata moddata,
167778
+              krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
167778
+              krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
167778
+              krb5_kdc_req *request, krb5_data *enc_req, krb5_data *enc_prev,
167778
+              krb5_preauthtype pa_type, krb5_error *error,
167778
+              krb5_pa_data **padata, krb5_prompter_fct prompter,
167778
+              void *prompter_data, krb5_pa_data ***padata_out)
167778
+{
167778
+    struct client_state *st = (struct client_state *)moddata;
167778
+    int i;
167778
+
167778
+    *padata_out = NULL;
167778
+    if (st->fail_tryagain) {
167778
+        k5_setmsg(context, KRB5_PREAUTH_FAILED, "induced tryagain fail");
167778
+        return KRB5_PREAUTH_FAILED;
167778
+    }
167778
+    if (error->error != KDC_ERR_ENCTYPE_NOSUPP)
167778
+        return KRB5_PREAUTH_FAILED;
167778
+    for (i = 0; padata[i] != NULL; i++) {
167778
+        if (padata[i]->pa_type == TEST_PA_TYPE)
167778
+            printf("tryagain: %.*s\n", padata[i]->length, padata[i]->contents);
167778
+    }
167778
+    *padata_out = make_pa_list("tryagain", 8);
167778
     return 0;
167778
 }
167778
 
167778
@@ -181,6 +206,12 @@ test_gic_opt(krb5_context kcontext, krb5_clpreauth_moddata moddata,
167778
         free(st->indicators);
167778
         st->indicators = strdup(value);
167778
         assert(st->indicators != NULL);
167778
+    } else if (strcmp(attr, "fail_optimistic") == 0) {
167778
+        st->fail_optimistic = TRUE;
167778
+    } else if (strcmp(attr, "fail_2rt") == 0) {
167778
+        st->fail_2rt = TRUE;
167778
+    } else if (strcmp(attr, "fail_tryagain") == 0) {
167778
+        st->fail_tryagain = TRUE;
167778
     }
167778
     return 0;
167778
 }
167778
@@ -205,6 +236,7 @@ clpreauth_test_initvt(krb5_context context, int maj_ver,
167778
     vt->request_init = test_request_init;
167778
     vt->request_fini = test_request_fini;
167778
     vt->process = test_process;
167778
+    vt->tryagain = test_tryagain;
167778
     vt->gic_opts = test_gic_opt;
167778
     return 0;
167778
 }
167778
diff --git a/src/plugins/preauth/test/common.c b/src/plugins/preauth/test/common.c
167778
new file mode 100644
167778
index 000000000..4d1f49dfa
167778
--- /dev/null
167778
+++ b/src/plugins/preauth/test/common.c
167778
@@ -0,0 +1,61 @@
167778
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
167778
+/* plugins/preauth/test/common.c - common functions for test preauth module */
167778
+/*
167778
+ * Copyright (C) 2017 by the Massachusetts Institute of Technology.
167778
+ * All rights reserved.
167778
+ *
167778
+ * Redistribution and use in source and binary forms, with or without
167778
+ * modification, are permitted provided that the following conditions
167778
+ * are met:
167778
+ *
167778
+ * * Redistributions of source code must retain the above copyright
167778
+ *   notice, this list of conditions and the following disclaimer.
167778
+ *
167778
+ * * Redistributions in binary form must reproduce the above copyright
167778
+ *   notice, this list of conditions and the following disclaimer in
167778
+ *   the documentation and/or other materials provided with the
167778
+ *   distribution.
167778
+ *
167778
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
167778
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
167778
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
167778
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
167778
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
167778
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
167778
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
167778
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
167778
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
167778
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
167778
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
167778
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
167778
+ */
167778
+
167778
+#include "k5-int.h"
167778
+#include "common.h"
167778
+
167778
+krb5_pa_data *
167778
+make_pa(const char *contents, size_t len)
167778
+{
167778
+    krb5_error_code ret;
167778
+    krb5_pa_data *pa;
167778
+
167778
+    pa = calloc(1, sizeof(*pa));
167778
+    assert(pa != NULL);
167778
+    pa->pa_type = TEST_PA_TYPE;
167778
+    pa->contents = k5memdup(contents, len, &ret;;
167778
+    assert(!ret);
167778
+    pa->length = len;
167778
+    return pa;
167778
+}
167778
+
167778
+/* Make a one-element padata list of type TEST_PA_TYPE. */
167778
+krb5_pa_data **
167778
+make_pa_list(const char *contents, size_t len)
167778
+{
167778
+    krb5_pa_data **list;
167778
+
167778
+    list = calloc(2, sizeof(*list));
167778
+    assert(list != NULL);
167778
+    list[0] = make_pa(contents, len);
167778
+    return list;
167778
+}
167778
diff --git a/src/plugins/preauth/test/common.h b/src/plugins/preauth/test/common.h
167778
new file mode 100644
167778
index 000000000..b748e0874
167778
--- /dev/null
167778
+++ b/src/plugins/preauth/test/common.h
167778
@@ -0,0 +1,41 @@
167778
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
167778
+/* plugins/preauth/test/common.h - Declarations for test preauth module */
167778
+/*
167778
+ * Copyright (C) 2017 by the Massachusetts Institute of Technology.
167778
+ * All rights reserved.
167778
+ *
167778
+ * Redistribution and use in source and binary forms, with or without
167778
+ * modification, are permitted provided that the following conditions
167778
+ * are met:
167778
+ *
167778
+ * * Redistributions of source code must retain the above copyright
167778
+ *   notice, this list of conditions and the following disclaimer.
167778
+ *
167778
+ * * Redistributions in binary form must reproduce the above copyright
167778
+ *   notice, this list of conditions and the following disclaimer in
167778
+ *   the documentation and/or other materials provided with the
167778
+ *   distribution.
167778
+ *
167778
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
167778
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
167778
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
167778
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
167778
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
167778
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
167778
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
167778
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
167778
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
167778
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
167778
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
167778
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
167778
+ */
167778
+
167778
+#ifndef COMMON_H
167778
+#define COMMON_H
167778
+
167778
+#define TEST_PA_TYPE -123
167778
+
167778
+krb5_pa_data *make_pa(const char *contents, size_t len);
167778
+krb5_pa_data **make_pa_list(const char *contents, size_t len);
167778
+
167778
+#endif /* COMMON_H */
167778
diff --git a/src/plugins/preauth/test/deps b/src/plugins/preauth/test/deps
167778
index b48f00032..b1429e9e1 100644
167778
--- a/src/plugins/preauth/test/deps
167778
+++ b/src/plugins/preauth/test/deps
167778
@@ -11,7 +11,7 @@ cltest.so cltest.po $(OUTPRE)cltest.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
167778
   $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
167778
   $(top_srcdir)/include/krb5/clpreauth_plugin.h $(top_srcdir)/include/krb5/plugin.h \
167778
   $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
167778
-  cltest.c
167778
+  cltest.c common.h
167778
 kdctest.so kdctest.po $(OUTPRE)kdctest.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
167778
   $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
167778
   $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
167778
@@ -22,4 +22,14 @@ kdctest.so kdctest.po $(OUTPRE)kdctest.$(OBJEXT): $(BUILDTOP)/include/autoconf.h
167778
   $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
167778
   $(top_srcdir)/include/krb5/kdcpreauth_plugin.h $(top_srcdir)/include/krb5/plugin.h \
167778
   $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
167778
-  kdctest.c
167778
+  common.h kdctest.c
167778
+common.so common.po $(OUTPRE)common.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
167778
+  $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
167778
+  $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
167778
+  $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h \
167778
+  $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h \
167778
+  $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h \
167778
+  $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h \
167778
+  $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
167778
+  $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
167778
+  $(top_srcdir)/include/socket-utils.h common.c common.h
167778
diff --git a/src/plugins/preauth/test/kdctest.c b/src/plugins/preauth/test/kdctest.c
167778
index 026dc680d..66b77969a 100644
167778
--- a/src/plugins/preauth/test/kdctest.c
167778
+++ b/src/plugins/preauth/test/kdctest.c
167778
@@ -1,7 +1,7 @@
167778
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
167778
 /* plugins/preauth/test/kdctest.c - Test kdcpreauth module */
167778
 /*
167778
- * Copyright (C) 2015 by the Massachusetts Institute of Technology.
167778
+ * Copyright (C) 2015, 2017 by the Massachusetts Institute of Technology.
167778
  * All rights reserved.
167778
  *
167778
  * Redistribution and use in source and binary forms, with or without
167778
@@ -40,10 +40,20 @@
167778
  *   key; the encrypted message "no attr" is sent if there is no string
167778
  *   attribute.)  It also sets a cookie containing "method-data".
167778
  *
167778
- * - It retrieves the "2rt" attribute from the client principal.  If set, the
167778
- *   verify method sends the client a KDC_ERR_MORE_PREAUTH_DATA_REQUIRED error
167778
- *   with the contents of the 2rt attribute as pa-data, and sets a cookie
167778
- *   containing "more".
167778
+ * - If the "err" attribute is set on the client principal, the verify method
167778
+ *   returns an KDC_ERR_ETYPE_NOSUPP error on the first try, with the contents
167778
+ *   of the err attribute as pa-data.  If the client tries again with the
167778
+ *   padata value "tryagain", the verify method preuthenticates successfully
167778
+ *   with no additional processing.
167778
+ *
167778
+ * - If the "failopt" attribute is set on the client principal, the verify
167778
+ *   method returns KDC_ERR_PREAUTH_FAILED on optimistic preauth attempts.
167778
+ *
167778
+ * - If the "2rt" attribute is set on client principal, the verify method sends
167778
+ *   the client a KDC_ERR_MORE_PREAUTH_DATA_REQUIRED error with the contents of
167778
+ *   the 2rt attribute as pa-data, and sets a cookie containing "more".  If the
167778
+ *   "fail2rt" attribute is set on the client principal, the client's second
167778
+ *   try results in a KDC_ERR_PREAUTH_FAILED error.
167778
  *
167778
  * - It receives a space-separated list from the clpreauth module and asserts
167778
  *   each string as an authentication indicator.  It always succeeds in
167778
@@ -52,6 +62,7 @@
167778
 
167778
 #include "k5-int.h"
167778
 #include <krb5/kdcpreauth_plugin.h>
167778
+#include "common.h"
167778
 
167778
 #define TEST_PA_TYPE -123
167778
 
167778
@@ -73,11 +84,6 @@ test_edata(krb5_context context, krb5_kdc_req *req,
167778
 
167778
     ret = cb->get_string(context, rock, "teststring", &attr);
167778
     assert(!ret);
167778
-    pa = k5alloc(sizeof(*pa), &ret;;
167778
-    assert(!ret);
167778
-    if (pa == NULL)
167778
-        abort();
167778
-    pa->pa_type = TEST_PA_TYPE;
167778
     if (k != NULL) {
167778
         d = string2data((attr != NULL) ? attr : "no attr");
167778
         ret = krb5_c_encrypt_length(context, k->enctype, d.length, &enclen);
167778
@@ -86,12 +92,10 @@ test_edata(krb5_context context, krb5_kdc_req *req,
167778
         assert(!ret);
167778
         ret = krb5_c_encrypt(context, k, 1024, NULL, &d, &enc;;
167778
         assert(!ret);
167778
-        pa->contents = (uint8_t *)enc.ciphertext.data;
167778
-        pa->length = enc.ciphertext.length;
167778
+        pa = make_pa(enc.ciphertext.data, enc.ciphertext.length);
167778
+        free(enc.ciphertext.data);
167778
     } else {
167778
-        pa->contents = (uint8_t *)strdup("no key");
167778
-        assert(pa->contents != NULL);
167778
-        pa->length = 6;
167778
+        pa = make_pa("no key", 6);
167778
     }
167778
 
167778
     /* Exercise setting a cookie information from the edata method. */
167778
@@ -111,12 +115,19 @@ test_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
167778
             krb5_kdcpreauth_verify_respond_fn respond, void *arg)
167778
 {
167778
     krb5_error_code ret;
167778
-    krb5_boolean second_round_trip = FALSE;
167778
-    krb5_pa_data **list;
167778
+    krb5_boolean second_round_trip = FALSE, optimistic = FALSE;
167778
+    krb5_pa_data **list = NULL;
167778
     krb5_data cookie_data, d;
167778
-    char *str, *ind, *attr, *toksave = NULL;
167778
+    char *str, *ind, *toksave = NULL;
167778
+    char *attr_err, *attr_2rt, *attr_fail2rt, *attr_failopt;
167778
 
167778
-    ret = cb->get_string(context, rock, "2rt", &attr);
167778
+    ret = cb->get_string(context, rock, "err", &attr_err);
167778
+    assert(!ret);
167778
+    ret = cb->get_string(context, rock, "2rt", &attr_2rt);
167778
+    assert(!ret);
167778
+    ret = cb->get_string(context, rock, "fail2rt", &attr_fail2rt);
167778
+    assert(!ret);
167778
+    ret = cb->get_string(context, rock, "failopt", &attr_failopt);
167778
     assert(!ret);
167778
 
167778
     /* Check the incoming cookie value. */
167778
@@ -124,13 +135,36 @@ test_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
167778
         /* Make sure we are seeing optimistic preauth and not a lost cookie. */
167778
         d = make_data(data->contents, data->length);
167778
         assert(data_eq_string(d, "optimistic"));
167778
+        optimistic = TRUE;
167778
     } else if (data_eq_string(cookie_data, "more")) {
167778
         second_round_trip = TRUE;
167778
     } else {
167778
-        assert(data_eq_string(cookie_data, "method-data"));
167778
+        assert(data_eq_string(cookie_data, "method-data") ||
167778
+               data_eq_string(cookie_data, "err"));
167778
     }
167778
 
167778
-    if (attr == NULL || second_round_trip) {
167778
+    if (attr_err != NULL) {
167778
+        d = make_data(data->contents, data->length);
167778
+        if (data_eq_string(d, "tryagain")) {
167778
+            /* Authenticate successfully. */
167778
+            enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
167778
+        } else {
167778
+            d = string2data("err");
167778
+            ret = cb->set_cookie(context, rock, TEST_PA_TYPE, &d);
167778
+            assert(!ret);
167778
+            ret = KRB5KDC_ERR_ETYPE_NOSUPP;
167778
+            list = make_pa_list(attr_err, strlen(attr_err));
167778
+        }
167778
+    } else if (attr_2rt != NULL && !second_round_trip) {
167778
+        d = string2data("more");
167778
+        ret = cb->set_cookie(context, rock, TEST_PA_TYPE, &d);
167778
+        assert(!ret);
167778
+        ret = KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED;
167778
+        list = make_pa_list(attr_2rt, strlen(attr_2rt));
167778
+    } else if ((attr_fail2rt != NULL && second_round_trip) ||
167778
+               (attr_failopt != NULL && optimistic)) {
167778
+        ret = KRB5KDC_ERR_PREAUTH_FAILED;
167778
+    } else {
167778
         /* Parse and assert the indicators. */
167778
         str = k5memdup0(data->contents, data->length, &ret;;
167778
         if (ret)
167778
@@ -142,21 +176,13 @@ test_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
167778
         }
167778
         free(str);
167778
         enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
167778
-        cb->free_string(context, rock, attr);
167778
-        (*respond)(arg, 0, NULL, NULL, NULL);
167778
-    } else {
167778
-        d = string2data("more");
167778
-        ret = cb->set_cookie(context, rock, TEST_PA_TYPE, &d);
167778
-        list = k5calloc(2, sizeof(*list), &ret;;
167778
-        assert(!ret);
167778
-        list[0] = k5alloc(sizeof(*list[0]), &ret;;
167778
-        assert(!ret);
167778
-        list[0]->pa_type = TEST_PA_TYPE;
167778
-        list[0]->contents = (uint8_t *)attr;
167778
-        list[0]->length = strlen(attr);
167778
-        (*respond)(arg, KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED, NULL, list,
167778
-                   NULL);
167778
     }
167778
+
167778
+    cb->free_string(context, rock, attr_err);
167778
+    cb->free_string(context, rock, attr_2rt);
167778
+    cb->free_string(context, rock, attr_fail2rt);
167778
+    cb->free_string(context, rock, attr_failopt);
167778
+    (*respond)(arg, ret, NULL, list, NULL);
167778
 }
167778
 
167778
 static krb5_error_code
167778
diff --git a/src/tests/icred.c b/src/tests/icred.c
167778
index 071f91c80..55f929cd7 100644
167778
--- a/src/tests/icred.c
167778
+++ b/src/tests/icred.c
167778
@@ -35,8 +35,8 @@
167778
  * it is very simplistic, but it can be extended as needed.
167778
  */
167778
 
167778
+#include "k5-platform.h"
167778
 #include <krb5.h>
167778
-#include <stdio.h>
167778
 
167778
 static krb5_context ctx;
167778
 
167778
@@ -59,29 +59,64 @@ main(int argc, char **argv)
167778
     const char *princstr, *password;
167778
     krb5_principal client;
167778
     krb5_init_creds_context icc;
167778
+    krb5_get_init_creds_opt *opt;
167778
     krb5_creds creds;
167778
-
167778
-    if (argc != 3) {
167778
-        fprintf(stderr, "Usage: icred princname password\n");
167778
-        exit(1);
167778
-    }
167778
-    princstr = argv[1];
167778
-    password = argv[2];
167778
+    krb5_boolean stepwise = FALSE;
167778
+    krb5_preauthtype ptypes[64];
167778
+    int c, nptypes = 0;
167778
+    char *val;
167778
 
167778
     check(krb5_init_context(&ctx));
167778
+    check(krb5_get_init_creds_opt_alloc(ctx, &opt));
167778
+
167778
+    while ((c = getopt(argc, argv, "so:X:")) != -1) {
167778
+        switch (c) {
167778
+        case 's':
167778
+            stepwise = TRUE;
167778
+            break;
167778
+        case 'o':
167778
+            assert(nptypes < 64);
167778
+            ptypes[nptypes++] = atoi(optarg);
167778
+            break;
167778
+        case 'X':
167778
+            val = strchr(optarg, '=');
167778
+            if (val != NULL)
167778
+                *val++ = '\0';
167778
+            else
167778
+                val = "yes";
167778
+            check(krb5_get_init_creds_opt_set_pa(ctx, opt, optarg, val));
167778
+            break;
167778
+        default:
167778
+            abort();
167778
+        }
167778
+    }
167778
+
167778
+    argc -= optind;
167778
+    argv += optind;
167778
+    if (argc != 2)
167778
+        abort();
167778
+    princstr = argv[0];
167778
+    password = argv[1];
167778
+
167778
     check(krb5_parse_name(ctx, princstr, &client));
167778
 
167778
-    /* Try once with the traditional interface. */
167778
-    check(krb5_get_init_creds_password(ctx, &creds, client, password, NULL,
167778
-                                       NULL, 0, NULL, NULL));
167778
-    krb5_free_cred_contents(ctx, &creds);
167778
+    if (nptypes > 0)
167778
+        krb5_get_init_creds_opt_set_preauth_list(opt, ptypes, nptypes);
167778
 
167778
-    /* Try again with the step interface. */
167778
-    check(krb5_init_creds_init(ctx, client, NULL, NULL, 0, NULL, &icc));
167778
-    check(krb5_init_creds_set_password(ctx, icc, password));
167778
-    check(krb5_init_creds_get(ctx, icc));
167778
-    krb5_init_creds_free(ctx, icc);
167778
+    if (stepwise) {
167778
+        /* Use the stepwise interface. */
167778
+        check(krb5_init_creds_init(ctx, client, NULL, NULL, 0, NULL, &icc));
167778
+        check(krb5_init_creds_set_password(ctx, icc, password));
167778
+        check(krb5_init_creds_get(ctx, icc));
167778
+        krb5_init_creds_free(ctx, icc);
167778
+    } else {
167778
+        /* Use the traditional one-shot interface. */
167778
+        check(krb5_get_init_creds_password(ctx, &creds, client, password, NULL,
167778
+                                           NULL, 0, NULL, opt));
167778
+        krb5_free_cred_contents(ctx, &creds);
167778
+    }
167778
 
167778
+    krb5_get_init_creds_opt_free(ctx, opt);
167778
     krb5_free_principal(ctx, client);
167778
     krb5_free_context(ctx);
167778
     return 0;
167778
diff --git a/src/tests/t_general.py b/src/tests/t_general.py
167778
index 6d523fe45..b16cffa37 100755
167778
--- a/src/tests/t_general.py
167778
+++ b/src/tests/t_general.py
167778
@@ -30,6 +30,7 @@ conf={'plugins': {'pwqual': {'disable': 'empty'}}}
167778
 realm = K5Realm(create_user=False, create_host=False, krb5_conf=conf)
167778
 realm.run([kadminl, 'addprinc', '-pw', '', 'user'])
167778
 realm.run(['./icred', 'user', ''])
167778
+realm.run(['./icred', '-s', 'user', ''])
167778
 realm.stop()
167778
 
167778
 realm = K5Realm(create_host=False)
167778
diff --git a/src/tests/t_pkinit.py b/src/tests/t_pkinit.py
167778
index 38424932b..c25475096 100755
167778
--- a/src/tests/t_pkinit.py
167778
+++ b/src/tests/t_pkinit.py
167778
@@ -176,14 +176,20 @@ realm.klist(realm.user_princ)
167778
 
167778
 # Test a DH parameter renegotiation by temporarily setting a 4096-bit
167778
 # minimum on the KDC.  (Preauth type 16 is PKINIT PA_PK_AS_REQ;
167778
-# 133 is FAST PA-FX-COOKIE.)
167778
+# 109 is PKINIT TD_DH_PARAMETERS; 133 is FAST PA-FX-COOKIE.)
167778
 minbits_kdc_conf = {'realms': {'$realm': {'pkinit_dh_min_bits': '4096'}}}
167778
 minbits_env = realm.special_env('restrict', True, kdc_conf=minbits_kdc_conf)
167778
 realm.stop_kdc()
167778
 realm.start_kdc(env=minbits_env)
167778
-expected_trace = ('Key parameters not accepted',
167778
-                  'Preauth tryagain input types',
167778
+expected_trace = ('Sending unauthenticated request',
167778
+                  '/Additional pre-authentication required',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Preauth module pkinit (16) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, 16',
167778
+                  '/Key parameters not accepted',
167778
+                  'Preauth tryagain input types (16): 109, 133',
167778
                   'trying again with KDC-provided parameters',
167778
+                  'Preauth module pkinit (16) tryagain returned: 0/Success',
167778
                   'Followup preauth for next request: 16, 133')
167778
 realm.kinit(realm.user_princ,
167778
             flags=['-X', 'X509_user_identity=%s' % file_identity],
167778
diff --git a/src/tests/t_preauth.py b/src/tests/t_preauth.py
167778
index 9b6da5a96..7d4d299dc 100644
167778
--- a/src/tests/t_preauth.py
167778
+++ b/src/tests/t_preauth.py
167778
@@ -18,11 +18,161 @@ out = realm.run([kinit, 'nokeyuser'], input=password('user')+'\n',
167778
 if 'no key' not in out:
167778
     fail('Expected "no key" message not in kinit output')
167778
 
167778
-# Exercise KDC_ERR_MORE_PREAUTH_DATA_REQUIRED and secure cookies.
167778
+# Preauth type -123 is the test preauth module type; 133 is FAST
167778
+# PA-FX-COOKIE; 2 is encrypted timestamp.
167778
+
167778
+# Test normal preauth flow.
167778
+expected_trace = ('Sending unauthenticated request',
167778
+                  '/Additional pre-authentication required',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  'Decrypted AS reply')
167778
+realm.run(['./icred', realm.user_princ, password('user')],
167778
+          expected_msg='testval', expected_trace=expected_trace)
167778
+
167778
+# Test successful optimistic preauth.
167778
+expected_trace = ('Attempting optimistic preauth',
167778
+                  'Processing preauth types: -123',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: -123',
167778
+                  'Decrypted AS reply')
167778
+realm.run(['./icred', '-o', '-123', realm.user_princ, password('user')],
167778
+          expected_trace=expected_trace)
167778
+
167778
+# Test optimistic preauth failing on client, followed by successful
167778
+# preauth using the same module.
167778
+expected_trace = ('Attempting optimistic preauth',
167778
+                  'Processing preauth types: -123',
167778
+                  '/induced optimistic fail',
167778
+                  'Sending unauthenticated request',
167778
+                  '/Additional pre-authentication required',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  'Decrypted AS reply')
167778
+realm.run(['./icred', '-o', '-123', '-X', 'fail_optimistic', realm.user_princ,
167778
+           password('user')], expected_msg='testval',
167778
+          expected_trace=expected_trace)
167778
+
167778
+# Test optimistic preauth failing on KDC, followed by successful preauth
167778
+# using the same module.
167778
+realm.run([kadminl, 'setstr', realm.user_princ, 'failopt', 'yes'])
167778
+expected_trace = ('Attempting optimistic preauth',
167778
+                  'Processing preauth types: -123',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: -123',
167778
+                  '/Preauthentication failed',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  'Decrypted AS reply')
167778
+realm.run(['./icred', '-o', '-123', realm.user_princ, password('user')],
167778
+          expected_msg='testval', expected_trace=expected_trace)
167778
+realm.run([kadminl, 'delstr', realm.user_princ, 'failopt'])
167778
+
167778
+# Test KDC_ERR_MORE_PREAUTH_DATA_REQUIRED and secure cookies.
167778
 realm.run([kadminl, 'setstr', realm.user_princ, '2rt', 'secondtrip'])
167778
-out = realm.run([kinit, realm.user_princ], input=password('user')+'\n')
167778
-if '2rt: secondtrip' not in out:
167778
-    fail('multi round-trip cookie test')
167778
+expected_trace = ('Sending unauthenticated request',
167778
+                  '/Additional pre-authentication required',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  '/More preauthentication data is required',
167778
+                  'Continuing preauth mech -123',
167778
+                  'Processing preauth types: -123, 133',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  'Decrypted AS reply')
167778
+realm.run(['./icred', realm.user_princ, password('user')],
167778
+          expected_msg='2rt: secondtrip', expected_trace=expected_trace)
167778
+
167778
+# Test client-side failure after KDC_ERR_MORE_PREAUTH_DATA_REQUIRED,
167778
+# falling back to encrypted timestamp.
167778
+expected_trace = ('Sending unauthenticated request',
167778
+                  '/Additional pre-authentication required',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  '/More preauthentication data is required',
167778
+                  'Continuing preauth mech -123',
167778
+                  'Processing preauth types: -123, 133',
167778
+                  '/induced 2rt fail',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Encrypted timestamp (for ',
167778
+                  'module encrypted_timestamp (2) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, 2',
167778
+                  'Decrypted AS reply')
167778
+realm.run(['./icred', '-X', 'fail_2rt', realm.user_princ, password('user')],
167778
+          expected_msg='2rt: secondtrip', expected_trace=expected_trace)
167778
+
167778
+# Test KDC-side failure after KDC_ERR_MORE_PREAUTH_DATA_REQUIRED,
167778
+# falling back to encrypted timestamp.
167778
+realm.run([kadminl, 'setstr', realm.user_princ, 'fail2rt', 'yes'])
167778
+expected_trace = ('Sending unauthenticated request',
167778
+                  '/Additional pre-authentication required',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  '/More preauthentication data is required',
167778
+                  'Continuing preauth mech -123',
167778
+                  'Processing preauth types: -123, 133',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  '/Preauthentication failed',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Encrypted timestamp (for ',
167778
+                  'module encrypted_timestamp (2) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, 2',
167778
+                  'Decrypted AS reply')
167778
+realm.run(['./icred', realm.user_princ, password('user')],
167778
+          expected_msg='2rt: secondtrip', expected_trace=expected_trace)
167778
+realm.run([kadminl, 'delstr', realm.user_princ, 'fail2rt'])
167778
+
167778
+# Test tryagain flow by inducing a KDC_ERR_ENCTYPE_NOSUPP error on the KDC.
167778
+realm.run([kadminl, 'setstr', realm.user_princ, 'err', 'testagain'])
167778
+expected_trace = ('Sending unauthenticated request',
167778
+                  '/Additional pre-authentication required',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  '/KDC has no support for encryption type',
167778
+                  'Recovering from KDC error 14 using preauth mech -123',
167778
+                  'Preauth tryagain input types (-123): -123, 133',
167778
+                  'Preauth module test (-123) tryagain returned: 0/Success',
167778
+                  'Followup preauth for next request: -123, 133',
167778
+                  'Decrypted AS reply')
167778
+realm.run(['./icred', realm.user_princ, password('user')],
167778
+          expected_msg='tryagain: testagain', expected_trace=expected_trace)
167778
+
167778
+# Test a client-side tryagain failure, falling back to encrypted
167778
+# timestamp.
167778
+expected_trace = ('Sending unauthenticated request',
167778
+                  '/Additional pre-authentication required',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Preauth module test (-123) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, -123',
167778
+                  '/KDC has no support for encryption type',
167778
+                  'Recovering from KDC error 14 using preauth mech -123',
167778
+                  'Preauth tryagain input types (-123): -123, 133',
167778
+                  '/induced tryagain fail',
167778
+                  'Preauthenticating using KDC method data',
167778
+                  'Processing preauth types:',
167778
+                  'Encrypted timestamp (for ',
167778
+                  'module encrypted_timestamp (2) (real) returned: 0/Success',
167778
+                  'Produced preauth for next request: 133, 2',
167778
+                  'Decrypted AS reply')
167778
+realm.run(['./icred', '-X', 'fail_tryagain', realm.user_princ,
167778
+           password('user')], expected_trace=expected_trace)
167778
 
167778
 # Test that multiple stepwise initial creds operations can be
167778
 # performed with the same krb5_context, with proper tracking of