Stop using SALT_TYPE_AFS_LENGTH
[krb5.git] / src / lib / krb5 / krb / preauth2.c
index f2ead9361cdd4f2e6e58635c95e7017d0aafb1f3..7c5452790218ae0128732bd4d4db72231a1fc05b 100644 (file)
@@ -1,6 +1,6 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
- * Copyright 1995, 2003, 2008 by the Massachusetts Institute of Technology.  All
+ * Copyright 1995, 2003, 2008, 2012 by the Massachusetts Institute of Technology.  All
  * Rights Reserved.
  *
  * Export of this software from the United States of America may
  */
 
 #include "k5-int.h"
-#if APPLE_PKINIT
-#include "pkinit_client.h"
-#include "pkinit_cert_store.h"
-#endif /* APPLE_PKINIT */
 #include "osconf.h"
 #include <krb5/preauth_plugin.h>
 #include "int-proto.h"
@@ -129,6 +125,8 @@ krb5_init_preauth_context(krb5_context kcontext)
     k5_plugin_register(kcontext, PLUGIN_INTERFACE_CLPREAUTH,
                        "encrypted_timestamp",
                        clpreauth_encrypted_timestamp_initvt);
+    k5_plugin_register(kcontext, PLUGIN_INTERFACE_CLPREAUTH, "sam2",
+                       clpreauth_sam2_initvt);
 
     /* Get all available clpreauth vtables. */
     if (k5_plugin_load_all(kcontext, PLUGIN_INTERFACE_CLPREAUTH, &plugins))
@@ -281,6 +279,7 @@ krb5_preauth_request_context_init(krb5_context context)
     if (context->preauth_context == NULL)
         return;
     for (i = 0; i < context->preauth_context->n_modules; i++) {
+        context->preauth_context->modules[i].use_count = 0;
         mod = &context->preauth_context->modules[i];
         if (mod->client_req_init != NULL)
             mod->client_req_init(context, mod->moddata, mod->modreq_p);
@@ -387,10 +386,58 @@ fast_armor(krb5_context context, krb5_clpreauth_rock rock)
     return rock->fast_state->armor_key;
 }
 
+static krb5_error_code
+get_as_key(krb5_context context, krb5_clpreauth_rock rock,
+           krb5_keyblock **keyblock)
+{
+    krb5_error_code ret;
+    krb5_data *salt;
+
+    if (rock->as_key->length == 0) {
+        salt = (*rock->default_salt) ? NULL : rock->salt;
+        ret = (*rock->gak_fct)(context, rock->client, *rock->etype,
+                               rock->prompter, rock->prompter_data, salt,
+                               rock->s2kparams, rock->as_key, *rock->gak_data);
+        if (ret)
+            return ret;
+    }
+    *keyblock = rock->as_key;
+    return 0;
+}
+
+static krb5_error_code
+set_as_key(krb5_context context, krb5_clpreauth_rock rock,
+           const krb5_keyblock *keyblock)
+{
+    krb5_free_keyblock_contents(context, rock->as_key);
+    return krb5_copy_keyblock_contents(context, keyblock, rock->as_key);
+}
+
+static krb5_error_code
+get_preauth_time(krb5_context context, krb5_clpreauth_rock rock,
+                 krb5_boolean allow_unauth_time, krb5_timestamp *time_out,
+                 krb5_int32 *usec_out)
+{
+    if (rock->pa_offset_state != NO_OFFSET &&
+        (allow_unauth_time || rock->pa_offset_state == AUTH_OFFSET) &&
+        (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME)) {
+        /* Use the offset we got from the preauth-required error. */
+        return k5_time_with_offset(rock->pa_offset, rock->pa_offset_usec,
+                                   time_out, usec_out);
+
+    } else {
+        /* Use the time offset from the context, or no offset. */
+        return krb5_us_timeofday(context, time_out, usec_out);
+    }
+}
+
 static struct krb5_clpreauth_callbacks_st callbacks = {
-    1,
+    2,
     get_etype,
-    fast_armor
+    fast_armor,
+    get_as_key,
+    set_as_key,
+    get_preauth_time
 };
 
 /* Tweak the request body, for now adding any enctypes which the module claims
@@ -432,12 +479,7 @@ run_preauth_plugins(krb5_context kcontext,
                     krb5_pa_data *in_padata,
                     krb5_prompter_fct prompter,
                     void *prompter_data,
-                    krb5_clpreauth_get_as_key_fn gak_fct,
-                    krb5_data *salt,
-                    krb5_data *s2kparams,
-                    void *gak_data,
                     krb5_clpreauth_rock preauth_rock,
-                    krb5_keyblock *as_key,
                     krb5_pa_data ***out_pa_list,
                     int *out_pa_list_size,
                     int *module_ret,
@@ -481,9 +523,7 @@ run_preauth_plugins(krb5_context kcontext,
                                      &callbacks, preauth_rock,
                                      request, encoded_request_body,
                                      encoded_previous_request, in_padata,
-                                     prompter, prompter_data, gak_fct,
-                                     gak_data, salt, s2kparams, as_key,
-                                     &out_pa_data);
+                                     prompter, prompter_data, &out_pa_data);
         TRACE_PREAUTH_PROCESS(kcontext, module->name, module->pa_type,
                               module->flags, ret);
         /* Make note of the module's flags and status. */
@@ -516,26 +556,103 @@ padata2data(krb5_pa_data p)
     return d;
 }
 
+/* Set etype info parameters in rock based on padata. */
 static krb5_error_code
-pa_salt(krb5_context context, krb5_kdc_req *request, krb5_pa_data *in_padata,
-        krb5_pa_data **out_padata, krb5_data *salt, krb5_data *s2kparams,
-        krb5_enctype *etype, krb5_keyblock *as_key, krb5_prompter_fct prompter,
-        void *prompter_data, krb5_gic_get_as_key_fct gak_fct, void *gak_data)
+get_etype_info(krb5_context context, krb5_pa_data **padata,
+               krb5_kdc_req *request, krb5_clpreauth_rock rock)
 {
-    krb5_data tmp;
-    krb5_error_code retval;
+    krb5_error_code ret = 0;
+    krb5_pa_data *pa;
+    krb5_data d;
+    krb5_etype_info etype_info = NULL, e;
+    krb5_etype_info_entry *entry;
+    krb5_boolean valid_found;
+    const char *p;
+    int i;
 
-    tmp = padata2data(*in_padata);
-    krb5_free_data_contents(context, salt);
-    retval = krb5int_copy_data_contents(context, &tmp, salt);
-    if (retval)
-        return retval;
+    /* Find an etype-info2 or etype-info element in padata. */
+    pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_ETYPE_INFO2);
+    if (pa != NULL) {
+        d = padata2data(*pa);
+        ret = decode_krb5_etype_info2(&d, &etype_info);
+    } else {
+        pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_ETYPE_INFO2);
+        if (pa != NULL) {
+            d = padata2data(*pa);
+            ret = decode_krb5_etype_info2(&d, &etype_info);
+        }
+    }
 
-    TRACE_PREAUTH_SALT(context, salt, in_padata->pa_type);
-    if (in_padata->pa_type == KRB5_PADATA_AFS3_SALT)
-        salt->length = SALT_TYPE_AFS_LENGTH;
+    if (etype_info != NULL) {
+        /* Search entries in order of the requests's enctype preference. */
+        entry = NULL;
+        valid_found = FALSE;
+        for (i = 0; i < request->nktypes && entry == NULL; i++) {
+            for (e = etype_info; *e != NULL && entry == NULL; e++) {
+                if ((*e)->etype == request->ktype[i])
+                    entry = *e;
+                if (krb5_c_valid_enctype((*e)->etype))
+                    valid_found = TRUE;
+            }
+        }
+        if (entry == NULL) {
+            ret = (valid_found) ? KRB5_CONFIG_ETYPE_NOSUPP :
+                KRB5_PROG_ETYPE_NOSUPP;
+            goto cleanup;
+        }
 
-    return(0);
+        /* Set rock fields based on the entry we selected. */
+        *rock->etype = entry->etype;
+        krb5_free_data_contents(context, rock->salt);
+        if (entry->length != KRB5_ETYPE_NO_SALT) {
+            *rock->salt = make_data(entry->salt, entry->length);
+            entry->salt = NULL;
+            *rock->default_salt = FALSE;
+        } else {
+            *rock->salt = empty_data();
+            *rock->default_salt = TRUE;
+        }
+        krb5_free_data_contents(context, rock->s2kparams);
+        *rock->s2kparams = entry->s2kparams;
+        entry->s2kparams = empty_data();
+        TRACE_PREAUTH_ETYPE_INFO(context, *rock->etype, rock->salt,
+                                 rock->s2kparams);
+    } else {
+        /* Look for a pw-salt or afs3-salt element. */
+        pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_PW_SALT);
+        if (pa == NULL)
+            pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_AFS3_SALT);
+        if (pa != NULL) {
+            /* Set rock->salt based on the element we found. */
+            krb5_free_data_contents(context, rock->salt);
+            d = padata2data(*pa);
+            ret = krb5int_copy_data_contents(context, &d, rock->salt);
+            if (ret)
+                goto cleanup;
+            if (pa->pa_type == KRB5_PADATA_AFS3_SALT) {
+                /* Work around a (possible) old Heimdal KDC foible. */
+                p = memchr(rock->salt->data, '@', rock->salt->length);
+                if (p != NULL)
+                    rock->salt->length = p - rock->salt->data;
+                /* Tolerate extra null in MIT KDC afs3-salt value. */
+                if (rock->salt->length > 0 &&
+                    rock->salt->data[rock->salt->length - 1] == '\0')
+                    rock->salt->length--;
+                /* Set an s2kparams value to indicate AFS string-to-key. */
+                krb5_free_data_contents(context, rock->s2kparams);
+                ret = alloc_data(rock->s2kparams, 1);
+                if (ret)
+                    goto cleanup;
+                rock->s2kparams->data[0] = '\1';
+            }
+            *rock->default_salt = FALSE;
+            TRACE_PREAUTH_SALT(context, rock->salt, pa->pa_type);
+        }
+    }
+
+cleanup:
+    krb5_free_etype_info(context, etype_info);
+    return ret;
 }
 
 static krb5_error_code
@@ -564,686 +681,6 @@ pa_fx_cookie(krb5_context context, krb5_kdc_req *request,
     return 0;
 }
 
-#if APPLE_PKINIT
-/*
- * PKINIT. One function to generate AS-REQ, one to parse AS-REP
- */
-#define  PKINIT_DEBUG    0
-#if     PKINIT_DEBUG
-#define kdcPkinitDebug(args...)       printf(args)
-#else
-#define kdcPkinitDebug(args...)
-#endif
-
-static krb5_error_code
-pa_pkinit_gen_req(krb5_context context,
-                  krb5_kdc_req *request,
-                  krb5_pa_data *in_padata,
-                  krb5_pa_data **out_padata,
-                  krb5_data *salt,
-                  krb5_data *s2kparams,
-                  krb5_enctype *etype,
-                  krb5_keyblock *as_key,
-                  krb5_prompter_fct prompter,
-                  void *prompter_data,
-                  krb5_gic_get_as_key_fct gak_fct,
-                  void *gak_data)
-{
-    krb5_error_code             krtn;
-    krb5_data                   out_data = {0, 0, NULL};
-    krb5_timestamp              kctime = 0;
-    krb5_int32                  cusec = 0;
-    krb5_ui_4                   nonce = 0;
-    krb5_checksum               cksum;
-    krb5_pkinit_signing_cert_t  client_cert;
-    krb5_data                   *der_req = NULL;
-    char                        *client_principal = NULL;
-    char                        *server_principal = NULL;
-    unsigned char               nonce_bytes[4];
-    krb5_data                   nonce_data = {0, 4, (char *)nonce_bytes};
-    int                         dex;
-
-    /*
-     * Trusted CA list and specific KC cert optionally obtained via
-     * krb5_pkinit_get_server_certs(). All are DER-encoded certs.
-     */
-    krb5_data *trusted_CAs = NULL;
-    krb5_ui_4 num_trusted_CAs;
-    krb5_data kdc_cert = {0};
-
-    kdcPkinitDebug("pa_pkinit_gen_req\n");
-
-    /* If we don't have a client cert, we're done */
-    if(request->client == NULL) {
-        kdcPkinitDebug("No request->client; aborting PKINIT\n");
-        return KRB5KDC_ERR_PREAUTH_FAILED;
-    }
-    krtn = krb5_unparse_name(context, request->client, &client_principal);
-    if(krtn) {
-        return krtn;
-    }
-    krtn = krb5_pkinit_get_client_cert(client_principal, &client_cert);
-    free(client_principal);
-    if(krtn) {
-        kdcPkinitDebug("No client cert; aborting PKINIT\n");
-        return krtn;
-    }
-
-    /* optional platform-dependent CA list and KDC cert */
-    krtn = krb5_unparse_name(context, request->server, &server_principal);
-    if(krtn) {
-        goto cleanup;
-    }
-    krtn = krb5_pkinit_get_server_certs(client_principal, server_principal,
-                                        &trusted_CAs, &num_trusted_CAs, &kdc_cert);
-    if(krtn) {
-        goto cleanup;
-    }
-
-    /* checksum of the encoded KDC-REQ-BODY */
-    krtn = encode_krb5_kdc_req_body(request, &der_req);
-    if(krtn) {
-        kdcPkinitDebug("encode_krb5_kdc_req_body returned %d\n", (int)krtn);
-        goto cleanup;
-    }
-    krtn = krb5_c_make_checksum(context, CKSUMTYPE_NIST_SHA, NULL, 0, der_req, &cksum);
-    if(krtn) {
-        goto cleanup;
-    }
-
-    krtn = krb5_us_timeofday(context, &kctime, &cusec);
-    if(krtn) {
-        goto cleanup;
-    }
-
-    /* cook up a random 4-byte nonce */
-    krtn = krb5_c_random_make_octets(context, &nonce_data);
-    if(krtn) {
-        goto cleanup;
-    }
-    for(dex=0; dex<4; dex++) {
-        nonce <<= 8;
-        nonce |= nonce_bytes[dex];
-    }
-
-    krtn = krb5int_pkinit_as_req_create(context,
-                                        kctime, cusec, nonce, &cksum,
-                                        client_cert,
-                                        trusted_CAs, num_trusted_CAs,
-                                        (kdc_cert.data ? &kdc_cert : NULL),
-                                        &out_data);
-    if(krtn) {
-        kdcPkinitDebug("error %d on pkinit_as_req_create; aborting PKINIT\n", (int)krtn);
-        goto cleanup;
-    }
-    *out_padata = (krb5_pa_data *)malloc(sizeof(krb5_pa_data));
-    if(*out_padata == NULL) {
-        krtn = ENOMEM;
-        free(out_data.data);
-        goto cleanup;
-    }
-    (*out_padata)->magic = KV5M_PA_DATA;
-    (*out_padata)->pa_type = KRB5_PADATA_PK_AS_REQ;
-    (*out_padata)->length = out_data.length;
-    (*out_padata)->contents = (krb5_octet *)out_data.data;
-    krtn = 0;
-cleanup:
-    if(client_cert) {
-        krb5_pkinit_release_cert(client_cert);
-    }
-    if(cksum.contents) {
-        free(cksum.contents);
-    }
-    if (der_req) {
-        krb5_free_data(context, der_req);
-    }
-    if(server_principal) {
-        free(server_principal);
-    }
-    /* free data mallocd by krb5_pkinit_get_server_certs() */
-    if(trusted_CAs) {
-        unsigned udex;
-        for(udex=0; udex<num_trusted_CAs; udex++) {
-            free(trusted_CAs[udex].data);
-        }
-        free(trusted_CAs);
-    }
-    if(kdc_cert.data) {
-        free(kdc_cert.data);
-    }
-    return krtn;
-
-}
-
-/* If and only if the realm is that of a Local KDC, accept
- * the KDC certificate as valid if its hash matches the
- * realm.
- */
-static krb5_boolean
-local_kdc_cert_match(krb5_context context,
-                     krb5_data *signer_cert,
-                     krb5_principal client)
-{
-    static const char lkdcprefix[] = "LKDC:SHA1.";
-    krb5_boolean match = FALSE;
-    size_t cert_hash_len;
-    char *cert_hash;
-    const char *realm_hash;
-    size_t realm_hash_len;
-
-    if (client->realm.length <= sizeof(lkdcprefix) ||
-        0 != memcmp(lkdcprefix, client->realm.data, sizeof(lkdcprefix)-1))
-        return match;
-    realm_hash = &client->realm.data[sizeof(lkdcprefix)-1];
-    realm_hash_len = client->realm.length - sizeof(lkdcprefix) + 1;
-    kdcPkinitDebug("checking realm versus certificate hash\n");
-    if (NULL != (cert_hash = krb5_pkinit_cert_hash_str(signer_cert))) {
-        kdcPkinitDebug("hash = %s\n", cert_hash);
-        cert_hash_len = strlen(cert_hash);
-        if (cert_hash_len == realm_hash_len &&
-            0 == memcmp(cert_hash, realm_hash, cert_hash_len))
-            match = TRUE;
-        free(cert_hash);
-    }
-    kdcPkinitDebug("result: %s\n", match ? "matches" : "does not match");
-    return match;
-}
-
-static krb5_error_code
-pa_pkinit_parse_rep(krb5_context context,
-                    krb5_kdc_req *request,
-                    krb5_pa_data *in_padata,
-                    krb5_pa_data **out_padata,
-                    krb5_data *salt,
-                    krb5_data *s2kparams,
-                    krb5_enctype *etype,
-                    krb5_keyblock *as_key,
-                    krb5_prompter_fct prompter,
-                    void *prompter_data,
-                    krb5_gic_get_as_key_fct gak_fct,
-                    void *gak_data)
-{
-    krb5int_cert_sig_status     sig_status = (krb5int_cert_sig_status)-999;
-    krb5_error_code             krtn;
-    krb5_data                   asRep;
-    krb5_keyblock               local_key = {0};
-    krb5_pkinit_signing_cert_t  client_cert;
-    char                        *princ_name = NULL;
-    krb5_checksum               as_req_checksum_rcd = {0};  /* received checksum */
-    krb5_checksum               as_req_checksum_gen = {0};  /* calculated checksum */
-    krb5_data                   *encoded_as_req = NULL;
-    krb5_data                   signer_cert = {0};
-
-    *out_padata = NULL;
-    kdcPkinitDebug("pa_pkinit_parse_rep\n");
-    if((in_padata == NULL) || (in_padata->length== 0)) {
-        kdcPkinitDebug("pa_pkinit_parse_rep: no in_padata\n");
-        return KRB5KDC_ERR_PREAUTH_FAILED;
-    }
-
-    /* If we don't have a client cert, we're done */
-    if(request->client == NULL) {
-        kdcPkinitDebug("No request->client; aborting PKINIT\n");
-        return KRB5KDC_ERR_PREAUTH_FAILED;
-    }
-    krtn = krb5_unparse_name(context, request->client, &princ_name);
-    if(krtn) {
-        return krtn;
-    }
-    krtn = krb5_pkinit_get_client_cert(princ_name, &client_cert);
-    free(princ_name);
-    if(krtn) {
-        kdcPkinitDebug("No client cert; aborting PKINIT\n");
-        return krtn;
-    }
-
-    memset(&local_key, 0, sizeof(local_key));
-    asRep.data = (char *)in_padata->contents;
-    asRep.length = in_padata->length;
-    krtn = krb5int_pkinit_as_rep_parse(context, &asRep, client_cert,
-                                       &local_key, &as_req_checksum_rcd, &sig_status,
-                                       &signer_cert, NULL, NULL);
-    if(krtn) {
-        kdcPkinitDebug("pkinit_as_rep_parse returned %d\n", (int)krtn);
-        return krtn;
-    }
-    switch(sig_status) {
-    case pki_cs_good:
-        break;
-    case pki_cs_unknown_root:
-        if (local_kdc_cert_match(context, &signer_cert, request->client))
-            break;
-        /* FALLTHROUGH */
-    default:
-        kdcPkinitDebug("pa_pkinit_parse_rep: bad cert/sig status %d\n",
-                       (int)sig_status);
-        krtn = KRB5KDC_ERR_PREAUTH_FAILED;
-        goto error_out;
-    }
-
-    /* calculate checksum of incoming AS-REQ using the decryption key
-     * we just got from the ReplyKeyPack */
-    krtn = encode_krb5_as_req(request, &encoded_as_req);
-    if(krtn) {
-        goto error_out;
-    }
-    krtn = krb5_c_make_checksum(context, context->kdc_req_sumtype,
-                                &local_key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
-                                encoded_as_req, &as_req_checksum_gen);
-    if(krtn) {
-        goto error_out;
-    }
-    if((as_req_checksum_gen.length != as_req_checksum_rcd.length) ||
-       memcmp(as_req_checksum_gen.contents,
-              as_req_checksum_rcd.contents,
-              as_req_checksum_gen.length)) {
-        kdcPkinitDebug("pa_pkinit_parse_rep: checksum miscompare\n");
-        krtn = KRB5KDC_ERR_PREAUTH_FAILED;
-        goto error_out;
-    }
-
-    /* We have the key; transfer to caller */
-    if (as_key->length) {
-        krb5_free_keyblock_contents(context, as_key);
-    }
-    *as_key = local_key;
-
-#if PKINIT_DEBUG
-    fprintf(stderr, "pa_pkinit_parse_rep: SUCCESS\n");
-    fprintf(stderr, "enctype %d keylen %d keydata %02x %02x %02x %02x...\n",
-            (int)as_key->enctype, (int)as_key->length,
-            as_key->contents[0], as_key->contents[1],
-            as_key->contents[2], as_key->contents[3]);
-#endif
-
-    krtn = 0;
-
-error_out:
-    if (signer_cert.data) {
-        free(signer_cert.data);
-    }
-    if(as_req_checksum_rcd.contents) {
-        free(as_req_checksum_rcd.contents);
-    }
-    if(as_req_checksum_gen.contents) {
-        free(as_req_checksum_gen.contents);
-    }
-    if(encoded_as_req) {
-        krb5_free_data(context, encoded_as_req);
-    }
-    if(krtn && (local_key.contents != NULL)) {
-        krb5_free_keyblock_contents(context, &local_key);
-    }
-    return krtn;
-}
-#endif /* APPLE_PKINIT */
-
-/* this macro expands to the int,ptr necessary for "%.*s" in an sprintf */
-
-#define SAMDATA(kdata, str, maxsize)                                    \
-    (int)((kdata.length)?                                               \
-          ((((kdata.length)<=(maxsize))?(kdata.length):strlen(str))):   \
-          strlen(str)),                                                 \
-        (kdata.length)?                                                 \
-        ((((kdata.length)<=(maxsize))?(kdata.data):(str))):(str)
-static char *
-sam_challenge_banner(krb5_int32 sam_type)
-{
-    char *label;
-
-    switch (sam_type) {
-    case PA_SAM_TYPE_ENIGMA:    /* Enigma Logic */
-        label = _("Challenge for Enigma Logic mechanism");
-        break;
-    case PA_SAM_TYPE_DIGI_PATH: /*  Digital Pathways */
-    case PA_SAM_TYPE_DIGI_PATH_HEX: /*  Digital Pathways */
-        label = _("Challenge for Digital Pathways mechanism");
-        break;
-    case PA_SAM_TYPE_ACTIVCARD_DEC: /*  Digital Pathways */
-    case PA_SAM_TYPE_ACTIVCARD_HEX: /*  Digital Pathways */
-        label = _("Challenge for Activcard mechanism");
-        break;
-    case PA_SAM_TYPE_SKEY_K0:   /*  S/key where  KDC has key 0 */
-        label = _("Challenge for Enhanced S/Key mechanism");
-        break;
-    case PA_SAM_TYPE_SKEY:      /*  Traditional S/Key */
-        label = _("Challenge for Traditional S/Key mechanism");
-        break;
-    case PA_SAM_TYPE_SECURID:   /*  Security Dynamics */
-        label = _("Challenge for Security Dynamics mechanism");
-        break;
-    case PA_SAM_TYPE_SECURID_PREDICT:   /* predictive Security Dynamics */
-        label = _("Challenge for Security Dynamics mechanism");
-        break;
-    default:
-        label = _("Challenge from authentication server");
-        break;
-    }
-
-    return(label);
-}
-
-static krb5_error_code
-pa_sam_2(krb5_context context, krb5_kdc_req *request, krb5_pa_data *in_padata,
-         krb5_pa_data **out_padata, krb5_data *salt, krb5_data *s2kparams,
-         krb5_enctype *etype, krb5_keyblock *as_key,
-         krb5_prompter_fct prompter, void *prompter_data,
-         krb5_gic_get_as_key_fct gak_fct, void *gak_data)
-{
-    krb5_error_code retval;
-    krb5_sam_challenge_2 *sc2 = NULL;
-    krb5_sam_challenge_2_body *sc2b = NULL;
-    krb5_data tmp_data;
-    krb5_data response_data;
-    char name[100], banner[100], prompt[100], response[100];
-    krb5_prompt kprompt;
-    krb5_prompt_type prompt_type;
-    krb5_data defsalt;
-    krb5_checksum **cksum;
-    krb5_data *scratch = NULL;
-    krb5_boolean valid_cksum = 0;
-    krb5_enc_sam_response_enc_2 enc_sam_response_enc_2;
-    krb5_sam_response_2 sr2;
-    size_t ciph_len;
-    krb5_pa_data *sam_padata;
-
-    if (prompter == NULL)
-        return KRB5_LIBOS_CANTREADPWD;
-
-    tmp_data.length = in_padata->length;
-    tmp_data.data = (char *)in_padata->contents;
-
-    if ((retval = decode_krb5_sam_challenge_2(&tmp_data, &sc2)))
-        return(retval);
-
-    retval = decode_krb5_sam_challenge_2_body(&sc2->sam_challenge_2_body, &sc2b);
-
-    if (retval) {
-        krb5_free_sam_challenge_2(context, sc2);
-        return(retval);
-    }
-
-    if (!sc2->sam_cksum || ! *sc2->sam_cksum) {
-        krb5_free_sam_challenge_2(context, sc2);
-        krb5_free_sam_challenge_2_body(context, sc2b);
-        return(KRB5_SAM_NO_CHECKSUM);
-    }
-
-    if (sc2b->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) {
-        krb5_free_sam_challenge_2(context, sc2);
-        krb5_free_sam_challenge_2_body(context, sc2b);
-        return(KRB5_SAM_UNSUPPORTED);
-    }
-
-    if (!krb5_c_valid_enctype(sc2b->sam_etype)) {
-        krb5_free_sam_challenge_2(context, sc2);
-        krb5_free_sam_challenge_2_body(context, sc2b);
-        return(KRB5_SAM_INVALID_ETYPE);
-    }
-
-    /* All of the above error checks are KDC-specific, that is, they     */
-    /* assume a failure in the KDC reply.  By returning anything other   */
-    /* than KRB5_KDC_UNREACH, KRB5_PREAUTH_FAILED,               */
-    /* KRB5_LIBOS_PWDINTR, or KRB5_REALM_CANT_RESOLVE, the client will   */
-    /* most likely go on to try the AS_REQ against master KDC            */
-
-    if (!(sc2b->sam_flags & KRB5_SAM_USE_SAD_AS_KEY)) {
-        /* We will need the password to obtain the key used for */
-        /* the checksum, and encryption of the sam_response.    */
-        /* Go ahead and get it now, preserving the ordering of  */
-        /* prompts for the user.                                */
-
-        retval = (gak_fct)(context, request->client,
-                           sc2b->sam_etype, prompter,
-                           prompter_data, salt, s2kparams, as_key, gak_data);
-        if (retval) {
-            krb5_free_sam_challenge_2(context, sc2);
-            krb5_free_sam_challenge_2_body(context, sc2b);
-            return(retval);
-        }
-    }
-
-    snprintf(name, sizeof(name), "%.*s",
-             SAMDATA(sc2b->sam_type_name, _("SAM Authentication"),
-                     sizeof(name) - 1));
-
-    snprintf(banner, sizeof(banner), "%.*s",
-             SAMDATA(sc2b->sam_challenge_label,
-                     sam_challenge_banner(sc2b->sam_type),
-                     sizeof(banner)-1));
-
-    snprintf(prompt, sizeof(prompt), "%s%.*s%s%.*s",
-             sc2b->sam_challenge.length?"Challenge is [":"",
-             SAMDATA(sc2b->sam_challenge, "", 20),
-             sc2b->sam_challenge.length?"], ":"",
-             SAMDATA(sc2b->sam_response_prompt, "passcode", 55));
-
-    response_data.data = response;
-    response_data.length = sizeof(response);
-    kprompt.prompt = prompt;
-    kprompt.hidden = 1;
-    kprompt.reply = &response_data;
-
-    prompt_type = KRB5_PROMPT_TYPE_PREAUTH;
-    krb5int_set_prompt_types(context, &prompt_type);
-
-    if ((retval = ((*prompter)(context, prompter_data, name,
-                               banner, 1, &kprompt)))) {
-        krb5_free_sam_challenge_2(context, sc2);
-        krb5_free_sam_challenge_2_body(context, sc2b);
-        krb5int_set_prompt_types(context, 0);
-        return(retval);
-    }
-
-    krb5int_set_prompt_types(context, (krb5_prompt_type *)NULL);
-
-    /* Generate salt used by string_to_key() */
-    if (((int) salt->length == -1) && (salt->data == NULL)) {
-        if ((retval =
-             krb5_principal2salt(context, request->client, &defsalt))) {
-            krb5_free_sam_challenge_2(context, sc2);
-            krb5_free_sam_challenge_2_body(context, sc2b);
-            return(retval);
-        }
-        salt = &defsalt;
-    } else {
-        defsalt.length = 0;
-    }
-
-    /* Get encryption key to be used for checksum and sam_response */
-    if (!(sc2b->sam_flags & KRB5_SAM_USE_SAD_AS_KEY)) {
-        /* as_key = string_to_key(password) */
-
-        if (as_key->length) {
-            krb5_free_keyblock_contents(context, as_key);
-            as_key->length = 0;
-        }
-
-        /* generate a key using the supplied password */
-        retval = krb5_c_string_to_key(context, sc2b->sam_etype,
-                                      (krb5_data *)gak_data, salt, as_key);
-
-        if (retval) {
-            krb5_free_sam_challenge_2(context, sc2);
-            krb5_free_sam_challenge_2_body(context, sc2b);
-            if (defsalt.length) free(defsalt.data);
-            return(retval);
-        }
-
-        if (!(sc2b->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD)) {
-            /* as_key = combine_key (as_key, string_to_key(SAD)) */
-            krb5_keyblock tmp_kb;
-
-            retval = krb5_c_string_to_key(context, sc2b->sam_etype,
-                                          &response_data, salt, &tmp_kb);
-
-            if (retval) {
-                krb5_free_sam_challenge_2(context, sc2);
-                krb5_free_sam_challenge_2_body(context, sc2b);
-                if (defsalt.length) free(defsalt.data);
-                return(retval);
-            }
-
-            /* This should be a call to the crypto library some day */
-            /* key types should already match the sam_etype */
-            retval = krb5int_c_combine_keys(context, as_key, &tmp_kb, as_key);
-
-            if (retval) {
-                krb5_free_sam_challenge_2(context, sc2);
-                krb5_free_sam_challenge_2_body(context, sc2b);
-                if (defsalt.length) free(defsalt.data);
-                return(retval);
-            }
-            krb5_free_keyblock_contents(context, &tmp_kb);
-        }
-
-        if (defsalt.length)
-            free(defsalt.data);
-
-    } else {
-        /* as_key = string_to_key(SAD) */
-
-        if (as_key->length) {
-            krb5_free_keyblock_contents(context, as_key);
-            as_key->length = 0;
-        }
-
-        /* generate a key using the supplied password */
-        retval = krb5_c_string_to_key(context, sc2b->sam_etype,
-                                      &response_data, salt, as_key);
-
-        if (defsalt.length)
-            free(defsalt.data);
-
-        if (retval) {
-            krb5_free_sam_challenge_2(context, sc2);
-            krb5_free_sam_challenge_2_body(context, sc2b);
-            return(retval);
-        }
-    }
-
-    /* Now we have a key, verify the checksum on the sam_challenge */
-
-    cksum = sc2->sam_cksum;
-
-    for (; *cksum; cksum++) {
-        if (!krb5_c_is_keyed_cksum((*cksum)->checksum_type))
-            continue;
-        /* Check this cksum */
-        retval = krb5_c_verify_checksum(context, as_key,
-                                        KRB5_KEYUSAGE_PA_SAM_CHALLENGE_CKSUM,
-                                        &sc2->sam_challenge_2_body,
-                                        *cksum, &valid_cksum);
-        if (retval) {
-            krb5_free_data(context, scratch);
-            krb5_free_sam_challenge_2(context, sc2);
-            krb5_free_sam_challenge_2_body(context, sc2b);
-            return(retval);
-        }
-        if (valid_cksum)
-            break;
-    }
-
-    if (!valid_cksum) {
-        krb5_free_sam_challenge_2(context, sc2);
-        krb5_free_sam_challenge_2_body(context, sc2b);
-        /*
-         * Note: We return AP_ERR_BAD_INTEGRITY so upper-level applications
-         * can interpret that as "password incorrect", which is probably
-         * the best error we can return in this situation.
-         */
-        return(KRB5KRB_AP_ERR_BAD_INTEGRITY);
-    }
-
-    /* fill in enc_sam_response_enc_2 */
-    enc_sam_response_enc_2.magic = KV5M_ENC_SAM_RESPONSE_ENC_2;
-    enc_sam_response_enc_2.sam_nonce = sc2b->sam_nonce;
-    if (sc2b->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) {
-        enc_sam_response_enc_2.sam_sad = response_data;
-    } else {
-        enc_sam_response_enc_2.sam_sad.data = NULL;
-        enc_sam_response_enc_2.sam_sad.length = 0;
-    }
-
-    /* encode and encrypt enc_sam_response_enc_2 with as_key */
-    retval = encode_krb5_enc_sam_response_enc_2(&enc_sam_response_enc_2,
-                                                &scratch);
-    if (retval) {
-        krb5_free_sam_challenge_2(context, sc2);
-        krb5_free_sam_challenge_2_body(context, sc2b);
-        return(retval);
-    }
-
-    /* Fill in sam_response_2 */
-    memset(&sr2, 0, sizeof(sr2));
-    sr2.sam_type = sc2b->sam_type;
-    sr2.sam_flags = sc2b->sam_flags;
-    sr2.sam_track_id = sc2b->sam_track_id;
-    sr2.sam_nonce = sc2b->sam_nonce;
-
-    /* Now take care of sr2.sam_enc_nonce_or_sad by encrypting encoded   */
-    /* enc_sam_response_enc_2 from above */
-
-    retval = krb5_c_encrypt_length(context, as_key->enctype, scratch->length,
-                                   &ciph_len);
-    if (retval) {
-        krb5_free_sam_challenge_2(context, sc2);
-        krb5_free_sam_challenge_2_body(context, sc2b);
-        krb5_free_data(context, scratch);
-        return(retval);
-    }
-    sr2.sam_enc_nonce_or_sad.ciphertext.length = ciph_len;
-
-    sr2.sam_enc_nonce_or_sad.ciphertext.data =
-        (char *)malloc(sr2.sam_enc_nonce_or_sad.ciphertext.length);
-
-    if (!sr2.sam_enc_nonce_or_sad.ciphertext.data) {
-        krb5_free_sam_challenge_2(context, sc2);
-        krb5_free_sam_challenge_2_body(context, sc2b);
-        krb5_free_data(context, scratch);
-        return(ENOMEM);
-    }
-
-    retval = krb5_c_encrypt(context, as_key, KRB5_KEYUSAGE_PA_SAM_RESPONSE,
-                            NULL, scratch, &sr2.sam_enc_nonce_or_sad);
-    if (retval) {
-        krb5_free_sam_challenge_2(context, sc2);
-        krb5_free_sam_challenge_2_body(context, sc2b);
-        krb5_free_data(context, scratch);
-        krb5_free_data_contents(context, &sr2.sam_enc_nonce_or_sad.ciphertext);
-        return(retval);
-    }
-    krb5_free_data(context, scratch);
-    scratch = NULL;
-
-    /* Encode the sam_response_2 */
-    retval = encode_krb5_sam_response_2(&sr2, &scratch);
-    krb5_free_sam_challenge_2(context, sc2);
-    krb5_free_sam_challenge_2_body(context, sc2b);
-    krb5_free_data_contents(context, &sr2.sam_enc_nonce_or_sad.ciphertext);
-
-    if (retval) {
-        return (retval);
-    }
-
-    /* Almost there, just need to make padata !  */
-    sam_padata = malloc(sizeof(krb5_pa_data));
-    if (sam_padata == NULL) {
-        krb5_free_data(context, scratch);
-        return(ENOMEM);
-    }
-
-    sam_padata->magic = KV5M_PA_DATA;
-    sam_padata->pa_type = KRB5_PADATA_SAM_RESPONSE_2;
-    sam_padata->length = scratch->length;
-    sam_padata->contents = (krb5_octet *) scratch->data;
-    free(scratch);
-
-    *out_padata = sam_padata;
-
-    return(0);
-}
-
 static krb5_error_code
 pa_s4u_x509_user(krb5_context context, krb5_kdc_req *request,
                  krb5_pa_data *in_padata, krb5_pa_data **out_padata,
@@ -1293,33 +730,6 @@ pa_s4u_x509_user(krb5_context context, krb5_kdc_req *request,
 
 /* FIXME - order significant? */
 static const pa_types_t pa_types[] = {
-    {
-        KRB5_PADATA_PW_SALT,
-        pa_salt,
-        PA_INFO,
-    },
-    {
-        KRB5_PADATA_AFS3_SALT,
-        pa_salt,
-        PA_INFO,
-    },
-#if APPLE_PKINIT
-    {
-        KRB5_PADATA_PK_AS_REQ,
-        pa_pkinit_gen_req,
-        PA_INFO,
-    },
-    {
-        KRB5_PADATA_PK_AS_REP,
-        pa_pkinit_parse_rep,
-        PA_REAL,
-    },
-#endif /* APPLE_PKINIT */
-    {
-        KRB5_PADATA_SAM_CHALLENGE_2,
-        pa_sam_2,
-        PA_REAL,
-    },
     {
         KRB5_PADATA_FX_COOKIE,
         pa_fx_cookie,
@@ -1350,11 +760,8 @@ krb5_do_preauth_tryagain(krb5_context kcontext,
                          krb5_pa_data **padata,
                          krb5_pa_data ***return_padata,
                          krb5_error *err_reply,
-                         krb5_data *salt, krb5_data *s2kparams,
-                         krb5_enctype *etype,
-                         krb5_keyblock *as_key,
+                         krb5_pa_data **err_padata,
                          krb5_prompter_fct prompter, void *prompter_data,
-                         krb5_gic_get_as_key_fct gak_fct, void *gak_data,
                          krb5_clpreauth_rock preauth_rock,
                          krb5_gic_opt_ext *opte)
 {
@@ -1393,11 +800,9 @@ krb5_do_preauth_tryagain(krb5_context kcontext,
                                            request,
                                            encoded_request_body,
                                            encoded_previous_request,
-                                           padata[i],
-                                           err_reply,
+                                           padata[i]->pa_type,
+                                           err_reply, err_padata,
                                            prompter, prompter_data,
-                                           gak_fct, gak_data, salt, s2kparams,
-                                           as_key,
                                            &out_padata) == 0) {
                 if (out_padata != NULL) {
                     int k;
@@ -1415,28 +820,23 @@ krb5_do_preauth_tryagain(krb5_context kcontext,
 }
 
 krb5_error_code KRB5_CALLCONV
-krb5_do_preauth(krb5_context context,
-                krb5_kdc_req *request,
+krb5_do_preauth(krb5_context context, krb5_kdc_req *request,
                 krb5_data *encoded_request_body,
                 krb5_data *encoded_previous_request,
                 krb5_pa_data **in_padata, krb5_pa_data ***out_padata,
-                krb5_data *salt, krb5_data *s2kparams,
-                krb5_enctype *etype,
-                krb5_keyblock *as_key,
                 krb5_prompter_fct prompter, void *prompter_data,
-                krb5_gic_get_as_key_fct gak_fct, void *gak_data,
-                krb5_clpreauth_rock preauth_rock, krb5_gic_opt_ext *opte)
+                krb5_clpreauth_rock rock, krb5_gic_opt_ext *opte,
+                krb5_boolean *got_real_out)
 {
     unsigned int h;
     int i, j, out_pa_list_size;
-    int seen_etype_info2 = 0;
     krb5_pa_data *out_pa = NULL, **out_pa_list = NULL;
-    krb5_data scratch;
-    krb5_etype_info etype_info = NULL;
     krb5_error_code ret;
     static const int paorder[] = { PA_INFO, PA_REAL };
     int realdone;
 
+    *got_real_out = FALSE;
+
     if (in_padata == NULL) {
         *out_padata = NULL;
         return(0);
@@ -1444,6 +844,11 @@ krb5_do_preauth(krb5_context context,
 
     TRACE_PREAUTH_INPUT(context, in_padata);
 
+    /* Scan the padata list and process etype-info or salt elements. */
+    ret = get_etype_info(context, in_padata, request, rock);
+    if (ret)
+        return ret;
+
     out_pa_list = NULL;
     out_pa_list_size = 0;
 
@@ -1452,102 +857,6 @@ krb5_do_preauth(krb5_context context,
     for (h=0; h<(sizeof(paorder)/sizeof(paorder[0])); h++) {
         realdone = 0;
         for (i=0; in_padata[i] && !realdone; i++) {
-            int k, l, etype_found, valid_etype_found;
-            /*
-             * This is really gross, but is necessary to prevent
-             * lossage when talking to a 1.0.x KDC, which returns an
-             * erroneous PA-PW-SALT when it returns a KRB-ERROR
-             * requiring additional preauth.
-             */
-            switch (in_padata[i]->pa_type) {
-            case KRB5_PADATA_ETYPE_INFO:
-            case KRB5_PADATA_ETYPE_INFO2:
-            {
-                krb5_preauthtype pa_type = in_padata[i]->pa_type;
-                if (etype_info) {
-                    if (seen_etype_info2 || pa_type != KRB5_PADATA_ETYPE_INFO2)
-                        continue;
-                    if (pa_type == KRB5_PADATA_ETYPE_INFO2) {
-                        krb5_free_etype_info( context, etype_info);
-                        etype_info = NULL;
-                    }
-                }
-
-                scratch.length = in_padata[i]->length;
-                scratch.data = (char *) in_padata[i]->contents;
-                if (pa_type == KRB5_PADATA_ETYPE_INFO2) {
-                    seen_etype_info2++;
-                    ret = decode_krb5_etype_info2(&scratch, &etype_info);
-                }
-                else ret = decode_krb5_etype_info(&scratch, &etype_info);
-                if (ret) {
-                    ret = 0; /*Ignore error and etype_info element*/
-                    if (etype_info)
-                        krb5_free_etype_info( context, etype_info);
-                    etype_info = NULL;
-                    continue;
-                }
-                if (etype_info[0] == NULL) {
-                    krb5_free_etype_info(context, etype_info);
-                    etype_info = NULL;
-                    break;
-                }
-                /*
-                 * Select first etype in our request which is also in
-                 * etype-info (preferring client request ktype order).
-                 */
-                for (etype_found = 0, valid_etype_found = 0, k = 0;
-                     !etype_found && k < request->nktypes; k++) {
-                    for (l = 0; etype_info[l]; l++) {
-                        if (etype_info[l]->etype == request->ktype[k]) {
-                            etype_found++;
-                            break;
-                        }
-                        /* check if program has support for this etype for more
-                         * precise error reporting.
-                         */
-                        if (krb5_c_valid_enctype(etype_info[l]->etype))
-                            valid_etype_found++;
-                    }
-                }
-                if (!etype_found) {
-                    if (valid_etype_found) {
-                        /* supported enctype but not requested */
-                        ret =  KRB5_CONFIG_ETYPE_NOSUPP;
-                        goto cleanup;
-                    }
-                    else {
-                        /* unsupported enctype */
-                        ret =  KRB5_PROG_ETYPE_NOSUPP;
-                        goto cleanup;
-                    }
-
-                }
-                scratch.data = (char *) etype_info[l]->salt;
-                scratch.length = etype_info[l]->length;
-                krb5_free_data_contents(context, salt);
-                if (scratch.length == KRB5_ETYPE_NO_SALT)
-                    salt->data = NULL;
-                else
-                    if ((ret = krb5int_copy_data_contents( context, &scratch, salt)) != 0)
-                        goto cleanup;
-                *etype = etype_info[l]->etype;
-                krb5_free_data_contents(context, s2kparams);
-                if ((ret = krb5int_copy_data_contents(context,
-                                                      &etype_info[l]->s2kparams,
-                                                      s2kparams)) != 0)
-                    goto cleanup;
-                TRACE_PREAUTH_ETYPE_INFO(context, *etype, salt, s2kparams);
-                break;
-            }
-            case KRB5_PADATA_PW_SALT:
-            case KRB5_PADATA_AFS3_SALT:
-                if (etype_info)
-                    continue;
-                break;
-            default:
-                ;
-            }
             /* Try the internally-provided preauth type list. */
             if (!realdone) for (j=0; pa_types[j].type >= 0; j++) {
                     if ((in_padata[i]->pa_type == pa_types[j].type) &&
@@ -1558,11 +867,13 @@ krb5_do_preauth(krb5_context context,
 #endif
                         out_pa = NULL;
 
-                        if ((ret = ((*pa_types[j].fct)(context, request,
-                                                       in_padata[i], &out_pa,
-                                                       salt, s2kparams, etype, as_key,
-                                                       prompter, prompter_data,
-                                                       gak_fct, gak_data)))) {
+                        ret = pa_types[j].fct(context, request, in_padata[i],
+                                              &out_pa, rock->salt,
+                                              rock->s2kparams, rock->etype,
+                                              rock->as_key, prompter,
+                                              prompter_data, *rock->gak_fct,
+                                              *rock->gak_data);
+                        if (ret) {
                             if (paorder[h] == PA_INFO) {
                                 TRACE_PREAUTH_INFO_FAIL(context,
                                                         in_padata[i]->pa_type,
@@ -1601,11 +912,7 @@ krb5_do_preauth(krb5_context context,
                                               in_padata[i],
                                               prompter,
                                               prompter_data,
-                                              gak_fct,
-                                              salt, s2kparams,
-                                              gak_data,
-                                              preauth_rock,
-                                              as_key,
+                                              rock,
                                               &out_pa_list,
                                               &out_pa_list_size,
                                               &module_ret,
@@ -1625,17 +932,14 @@ krb5_do_preauth(krb5_context context,
 
     TRACE_PREAUTH_OUTPUT(context, out_pa_list);
     *out_padata = out_pa_list;
-    if (etype_info)
-        krb5_free_etype_info(context, etype_info);
 
+    *got_real_out = realdone;
     return(0);
 cleanup:
     if (out_pa_list) {
         out_pa_list[out_pa_list_size++] = NULL;
         krb5_free_pa_data(context, out_pa_list);
     }
-    if (etype_info)
-        krb5_free_etype_info(context, etype_info);
     return (ret);
 }