Blame SOURCES/Add-test-cases-for-preauth-fallback-behavior.patch

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