Convert SAM-2 preauth mechanism to a module
authorGreg Hudson <ghudson@mit.edu>
Thu, 26 Apr 2012 21:47:02 +0000 (21:47 +0000)
committerGreg Hudson <ghudson@mit.edu>
Thu, 26 Apr 2012 21:47:02 +0000 (21:47 +0000)
Move the last real preauth mechanism, SAM-2, from the pa_types table
to a built-in module.

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@25831 dc483132-0cff-0310-8789-dd5450dbe970

src/lib/krb5/krb/Makefile.in
src/lib/krb5/krb/int-proto.h
src/lib/krb5/krb/preauth2.c
src/lib/krb5/krb/preauth_sam2.c [new file with mode: 0644]

index fe55c24ee8d0f4dc011b1fc76826f1743885fc52..933edd09aaa0009003de9e63ce68d38947ef53b8 100644 (file)
@@ -79,6 +79,7 @@ STLIBOBJS= \
        preauth2.o      \
        preauth_ec.o    \
        preauth_encts.o \
+       preauth_sam2.o  \
        gic_opt_set_pa.o        \
        princ_comp.o    \
        privsafe.o      \
index 899579f75677f60c07c4fea799c75c879461110c..3b93532af23ce6aed2f6364bd100159024500c4e 100644 (file)
@@ -63,6 +63,10 @@ krb5_error_code
 clpreauth_encrypted_timestamp_initvt(krb5_context context, int maj_ver,
                                      int min_ver, krb5_plugin_vtable vtable);
 
+krb5_error_code
+clpreauth_sam2_initvt(krb5_context context, int maj_ver, int min_ver,
+                      krb5_plugin_vtable vtable);
+
 krb5_error_code
 krb5int_construct_matching_creds(krb5_context context, krb5_flags options,
                                  krb5_creds *in_creds, krb5_creds *mcreds,
index 81c508d53d019fc343c1914c9e8c65182952a1df..25fa581d1cd51162c706f1805cfa9bd215d317fc 100644 (file)
@@ -125,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))
@@ -600,372 +602,6 @@ pa_fx_cookie(krb5_context context, krb5_kdc_req *request,
     return 0;
 }
 
-/* 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,
@@ -1025,11 +661,6 @@ static const pa_types_t pa_types[] = {
         pa_salt,
         PA_INFO,
     },
-    {
-        KRB5_PADATA_SAM_CHALLENGE_2,
-        pa_sam_2,
-        PA_REAL,
-    },
     {
         KRB5_PADATA_FX_COOKIE,
         pa_fx_cookie,
diff --git a/src/lib/krb5/krb/preauth_sam2.c b/src/lib/krb5/krb/preauth_sam2.c
new file mode 100644 (file)
index 0000000..fd8028a
--- /dev/null
@@ -0,0 +1,435 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/krb/preauth_sam2.c - SAM-2 clpreauth module */
+/*
+ * 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
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ *
+ */
+
+#include <k5-int.h>
+#include <krb5/preauth_plugin.h>
+#include "int-proto.h"
+
+static int
+sam2_flags(krb5_context context, krb5_preauthtype pa_type)
+{
+    return PA_REAL;
+}
+
+/* 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
+sam2_process(krb5_context context, krb5_clpreauth_moddata moddata,
+            krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
+            krb5_clpreauth_callbacks cb, krb5_clpreauth_rock rock,
+            krb5_kdc_req *request, krb5_data *encoded_request_body,
+            krb5_data *encoded_previous_request, krb5_pa_data *padata,
+            krb5_prompter_fct prompter, void *prompter_data,
+            krb5_pa_data ***out_padata)
+{
+    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, *salt;
+    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 = padata->length;
+    tmp_data.data = (char *)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 = (*rock->gak_fct)(context, request->client, sc2b->sam_etype,
+                                 prompter, prompter_data, rock->salt,
+                                 rock->s2kparams, rock->as_key,
+                                 *rock->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() */
+    salt = rock->salt;
+    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 (rock->as_key->length) {
+            krb5_free_keyblock_contents(context, rock->as_key);
+            rock->as_key->length = 0;
+        }
+
+        /* generate a key using the supplied password */
+        retval = krb5_c_string_to_key(context, sc2b->sam_etype,
+                                     (krb5_data *)*rock->gak_data, salt,
+                                     rock->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, rock->as_key, &tmp_kb,
+                                           rock->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 (rock->as_key->length) {
+            krb5_free_keyblock_contents(context, rock->as_key);
+            rock->as_key->length = 0;
+        }
+
+        /* generate a key using the supplied password */
+        retval = krb5_c_string_to_key(context, sc2b->sam_etype,
+                                      &response_data, salt, rock->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, rock->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, rock->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, rock->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(2 * sizeof(*sam_padata));
+    if (sam_padata == NULL) {
+        krb5_free_data(context, scratch);
+        return(ENOMEM);
+    }
+    sam_padata[0] = malloc(sizeof(krb5_pa_data));
+    if (sam_padata[0] == NULL) {
+        krb5_free_data(context, scratch);
+       free(sam_padata);
+        return(ENOMEM);
+    }
+
+    sam_padata[0]->magic = KV5M_PA_DATA;
+    sam_padata[0]->pa_type = KRB5_PADATA_SAM_RESPONSE_2;
+    sam_padata[0]->length = scratch->length;
+    sam_padata[0]->contents = (krb5_octet *) scratch->data;
+    free(scratch);
+    sam_padata[1] = NULL;
+
+    *out_padata = sam_padata;
+
+    return(0);
+}
+
+static krb5_preauthtype sam2_pa_types[] = {
+    KRB5_PADATA_SAM_CHALLENGE_2, 0};
+
+krb5_error_code
+clpreauth_sam2_initvt(krb5_context context, int maj_ver, int min_ver,
+                     krb5_plugin_vtable vtable)
+{
+    krb5_clpreauth_vtable vt;
+
+    if (maj_ver != 1)
+        return KRB5_PLUGIN_VER_NOTSUPP;
+    vt = (krb5_clpreauth_vtable)vtable;
+    vt->name = "sam2";
+    vt->pa_type_list = sam2_pa_types;
+    vt->flags = sam2_flags;
+    vt->process = sam2_process;
+    return 0;
+}