krb5 commit: Add secure cookie support

Greg Hudson ghudson at mit.edu
Wed Aug 26 13:29:46 EDT 2015


https://github.com/krb5/krb5/commit/4e15c03b54464b661c6578f78de3bd348163fc07
commit 4e15c03b54464b661c6578f78de3bd348163fc07
Author: Greg Hudson <ghudson at mit.edu>
Date:   Wed Aug 12 11:58:17 2015 -0400

    Add secure cookie support
    
    Remove the existing support for creating trivial cookies.  Add new
    functions to fast_util.c for reading and generating secure cookies.
    Add new kdcpreauth callbacks "get_cookie" and "set_cookie" to allow
    preauth mechs to retrieve and set cookie values.
    
    Based on a patch by Nathaniel McCallum.
    
    ticket: 8233 (new)

 doc/appdev/refs/macros/index.rst     |    1 +
 src/include/krb5/kdcpreauth_plugin.h |   21 ++
 src/include/krb5/krb5.hin            |    2 +
 src/kdc/do_as_req.c                  |   40 +++-
 src/kdc/fast_util.c                  |  363 +++++++++++++++++++++++++++-------
 src/kdc/kdc_preauth.c                |   24 ++-
 src/kdc/kdc_util.h                   |   20 ++-
 src/kdc/reqstate.h                   |    3 +-
 8 files changed, 382 insertions(+), 92 deletions(-)

diff --git a/doc/appdev/refs/macros/index.rst b/doc/appdev/refs/macros/index.rst
index 7b07122..2271e90 100644
--- a/doc/appdev/refs/macros/index.rst
+++ b/doc/appdev/refs/macros/index.rst
@@ -177,6 +177,7 @@ Public
    KRB5_KEYUSAGE_KRB_ERROR_CKSUM.rst
    KRB5_KEYUSAGE_KRB_PRIV_ENCPART.rst
    KRB5_KEYUSAGE_KRB_SAFE_CKSUM.rst
+   KRB5_KEYUSAGE_PA_FX_COOKIE.rst
    KRB5_KEYUSAGE_PA_OTP_REQUEST.rst
    KRB5_KEYUSAGE_PA_PKINIT_KX.rst
    KRB5_KEYUSAGE_PA_S4U_X509_USER_REPLY.rst
diff --git a/src/include/krb5/kdcpreauth_plugin.h b/src/include/krb5/kdcpreauth_plugin.h
index 356004f..f455eff 100644
--- a/src/include/krb5/kdcpreauth_plugin.h
+++ b/src/include/krb5/kdcpreauth_plugin.h
@@ -198,6 +198,27 @@ typedef struct krb5_kdcpreauth_callbacks_st {
                                           krb5_kdcpreauth_rock rock,
                                           const char *indicator);
 
+    /*
+     * Read a data value for pa_type from the request cookie, placing it in
+     * *out.  The value placed there is an alias and must not be freed.
+     * Returns true if a value for pa_type was retrieved, false if not.
+     */
+    krb5_boolean (*get_cookie)(krb5_context context, krb5_kdcpreauth_rock rock,
+                               krb5_preauthtype pa_type, krb5_data *out);
+
+    /*
+     * Set a data value for pa_type to be sent in a secure cookie in the next
+     * error response.  If pa_type is already present, the value is ignored.
+     * If the preauth mechanism has different preauth types for requests and
+     * responses, use the request type.  Secure cookies are encrypted in a key
+     * known only to the KDCs, but can be replayed within a short time window
+     * for requests using the same client principal.
+     */
+    krb5_error_code (*set_cookie)(krb5_context context,
+                                  krb5_kdcpreauth_rock rock,
+                                  krb5_preauthtype pa_type,
+                                  const krb5_data *data);
+
     /* End of version 3 kdcpreauth callbacks. */
 
 } *krb5_kdcpreauth_callbacks;
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index 55fa88e..0d19a65 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -1007,6 +1007,8 @@ krb5_c_keyed_checksum_types(krb5_context context, krb5_enctype enctype,
 #define KRB5_KEYUSAGE_ENC_CHALLENGE_KDC 55
 #define KRB5_KEYUSAGE_AS_REQ 56
 #define KRB5_KEYUSAGE_CAMMAC 64
+
+#define KRB5_KEYUSAGE_PA_FX_COOKIE 513  /**< Used for encrypted FAST cookies */
 /** @} */ /* end of KRB5_KEYUSAGE group */
 
 /**
diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c
index 64e849d..3a3ce8b 100644
--- a/src/kdc/do_as_req.c
+++ b/src/kdc/do_as_req.c
@@ -75,7 +75,7 @@
 #include "extern.h"
 
 static krb5_error_code
-prepare_error_as(struct kdc_request_state *, krb5_kdc_req *,
+prepare_error_as(struct kdc_request_state *, krb5_kdc_req *, krb5_db_entry *,
                  int, krb5_pa_data **, krb5_boolean, krb5_principal,
                  krb5_data **, const char *);
 
@@ -397,8 +397,8 @@ egress:
                 errcode = KRB_ERR_GENERIC;
 
             errcode = prepare_error_as(state->rstate, state->request,
-                                       errcode, state->e_data,
-                                       state->typed_e_data,
+                                       state->local_tgt, errcode,
+                                       state->e_data, state->typed_e_data,
                                        ((state->client != NULL) ?
                                         state->client->princ : NULL),
                                        &response, state->status);
@@ -805,6 +805,13 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
         state->rock.client_keyblock = &state->client_keyblock;
     }
 
+    errcode = kdc_fast_read_cookie(kdc_context, state->rstate, state->request,
+                                   state->local_tgt);
+    if (errcode) {
+        state->status = "READ_COOKIE";
+        goto errout;
+    }
+
     /*
      * Check the preauthentication if it is there.
      */
@@ -822,15 +829,30 @@ errout:
 }
 
 static krb5_error_code
-prepare_error_as (struct kdc_request_state *rstate, krb5_kdc_req *request,
-                  int error, krb5_pa_data **e_data, krb5_boolean typed_e_data,
-                  krb5_principal canon_client, krb5_data **response,
-                  const char *status)
+prepare_error_as(struct kdc_request_state *rstate, krb5_kdc_req *request,
+                 krb5_db_entry *local_tgt, int error, krb5_pa_data **e_data_in,
+                 krb5_boolean typed_e_data, krb5_principal canon_client,
+                 krb5_data **response, const char *status)
 {
     krb5_error errpkt;
     krb5_error_code retval;
     krb5_data *scratch = NULL, *e_data_asn1 = NULL, *fast_edata = NULL;
+    krb5_pa_data **e_data = NULL, *cookie = NULL;
     kdc_realm_t *kdc_active_realm = rstate->realm_data;
+    size_t count;
+
+    if (e_data_in != NULL) {
+        /* Add a PA-FX-COOKIE to e_data_in.  e_data is a shallow copy
+         * containing aliases. */
+        for (count = 0; e_data_in[count] != NULL; count++);
+        e_data = calloc(count + 2, sizeof(*e_data));
+        if (e_data == NULL)
+            return ENOMEM;
+        memcpy(e_data, e_data_in, count * sizeof(*e_data));
+        retval = kdc_fast_make_cookie(kdc_context, rstate, local_tgt,
+                                      request->client, &cookie);
+        e_data[count] = cookie;
+    }
 
     errpkt.ctime = request->nonce;
     errpkt.cusec = 0;
@@ -878,5 +900,9 @@ cleanup:
     krb5_free_data(kdc_context, fast_edata);
     krb5_free_data(kdc_context, e_data_asn1);
     free(scratch);
+    free(e_data);
+    if (cookie != NULL)
+        free(cookie->contents);
+    free(cookie);
     return retval;
 }
diff --git a/src/kdc/fast_util.c b/src/kdc/fast_util.c
index 20b7fef..f76ad37 100644
--- a/src/kdc/fast_util.c
+++ b/src/kdc/fast_util.c
@@ -1,7 +1,7 @@
 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /* kdc/fast_util.c */
 /*
- * Copyright (C) 2009 by the Massachusetts Institute of Technology.
+ * Copyright (C) 2009, 2015 by the Massachusetts Institute of Technology.
  * All rights reserved.
  *
  * Export of this software from the United States of America may
@@ -29,15 +29,8 @@
 #include "kdc_util.h"
 #include "extern.h"
 
-
-/*
- * This function will find the fast and cookie padata and if fast is
- * successfully processed, will throw away (and free) the outer
- * request and update the pointer to point to the inner request.  The
- * checksummed_data points to the data that is in the
- * armored_fast_request checksum; either the pa-tgs-req or the
- * kdc-req-body.
- */
+/* Let cookies be valid for ten minutes. */
+#define COOKIE_LIFETIME 600
 
 static krb5_error_code armor_ap_request
 (struct kdc_request_state *state, krb5_fast_armor *armor)
@@ -123,6 +116,12 @@ encrypt_fast_reply(struct kdc_request_state *state,
 }
 
 
+/*
+ * This function will find the FAST padata and, if FAST is successfully
+ * processed, will free the outer request and update the pointer to point to
+ * the inner request.  checksummed_data points to the data that is in the
+ * armored_fast_request checksum; either the pa-tgs-req or the kdc-req-body.
+ */
 krb5_error_code
 kdc_find_fast(krb5_kdc_req **requestptr,
               krb5_data *checksummed_data,
@@ -132,7 +131,7 @@ kdc_find_fast(krb5_kdc_req **requestptr,
               krb5_data **inner_body_out)
 {
     krb5_error_code retval = 0;
-    krb5_pa_data *fast_padata, *cookie_padata = NULL;
+    krb5_pa_data *fast_padata;
     krb5_data scratch, *inner_body = NULL;
     krb5_fast_req * fast_req = NULL;
     krb5_kdc_req *request = *requestptr;
@@ -229,10 +228,6 @@ kdc_find_fast(krb5_kdc_req **requestptr,
             if ((fast_req->fast_options & UNSUPPORTED_CRITICAL_FAST_OPTIONS) != 0)
                 retval = KRB5KDC_ERR_UNKNOWN_CRITICAL_FAST_OPTION;
         }
-        if (retval == 0)
-            cookie_padata = krb5int_find_pa_data(kdc_context,
-                                                 fast_req->req_body->padata,
-                                                 KRB5_PADATA_FX_COOKIE);
         if (retval == 0) {
             state->fast_options = fast_req->fast_options;
             fast_req->req_body->msg_type = request->msg_type;
@@ -241,26 +236,6 @@ kdc_find_fast(krb5_kdc_req **requestptr,
             fast_req->req_body = NULL;
         }
     }
-    else {
-        cookie_padata = krb5int_find_pa_data(kdc_context,
-                                             request->padata,
-                                             KRB5_PADATA_FX_COOKIE);
-    }
-    if (retval == 0 && cookie_padata != NULL) {
-        krb5_pa_data *new_padata = malloc(sizeof (krb5_pa_data));
-        if (new_padata == NULL) {
-            retval = ENOMEM;
-        } else {
-            new_padata->pa_type = KRB5_PADATA_FX_COOKIE;
-            new_padata->length = cookie_padata->length;
-            new_padata->contents =
-                k5memdup(cookie_padata->contents, new_padata->length, &retval);
-            if (new_padata->contents == NULL)
-                free(new_padata);
-            else
-                state->cookie = new_padata;
-        }
-    }
     if (retval == 0 && inner_body_out != NULL) {
         *inner_body_out = inner_body;
         inner_body = NULL;
@@ -295,10 +270,8 @@ kdc_free_rstate (struct kdc_request_state *s)
         krb5_free_keyblock(kdc_context, s->armor_key);
     if (s->strengthen_key)
         krb5_free_keyblock(kdc_context, s->strengthen_key);
-    if (s->cookie) {
-        free(s->cookie->contents);
-        free(s->cookie);
-    }
+    krb5_free_pa_data(NULL, s->in_cookie_padata);
+    krb5_free_pa_data(NULL, s->out_cookie_padata);
     free(s);
 }
 
@@ -403,7 +376,7 @@ kdc_fast_handle_error(krb5_context context,
     krb5_error fx_error;
     krb5_data *encoded_fx_error = NULL, *encrypted_reply = NULL;
     krb5_pa_data pa[1];
-    krb5_pa_data *outer_pa[3], *cookie = NULL;
+    krb5_pa_data *outer_pa[3];
     krb5_pa_data **inner_pa = NULL;
     size_t size = 0;
     kdc_realm_t *kdc_active_realm = state->realm_data;
@@ -416,8 +389,7 @@ kdc_fast_handle_error(krb5_context context,
     fx_error.e_data.data = NULL;
     fx_error.e_data.length = 0;
     for (size = 0; in_padata&&in_padata[size]; size++);
-    size +=3;
-    inner_pa = calloc(size, sizeof(krb5_pa_data *));
+    inner_pa = calloc(size + 2, sizeof(krb5_pa_data *));
     if (inner_pa == NULL)
         retval = ENOMEM;
     if (retval == 0)
@@ -430,12 +402,7 @@ kdc_fast_handle_error(krb5_context context,
         pa[0].length = encoded_fx_error->length;
         pa[0].contents = (unsigned char *) encoded_fx_error->data;
         inner_pa[size++] = &pa[0];
-        if (krb5int_find_pa_data(kdc_context,
-                                 inner_pa, KRB5_PADATA_FX_COOKIE) == NULL)
-            retval = kdc_preauth_get_cookie(state, &cookie);
     }
-    if (cookie != NULL)
-        inner_pa[size++] = cookie;
     if (retval == 0) {
         resp.padata = inner_pa;
         resp.nonce = request->nonce;
@@ -446,11 +413,6 @@ kdc_fast_handle_error(krb5_context context,
         retval = encrypt_fast_reply(state, &resp, &encrypted_reply);
     if (inner_pa)
         free(inner_pa); /*contained storage from caller and our stack*/
-    if (cookie) {
-        free(cookie->contents);
-        free(cookie);
-        cookie = NULL;
-    }
     if (retval == 0) {
         pa[0].pa_type = KRB5_PADATA_FX_FAST;
         pa[0].length = encrypted_reply->length;
@@ -483,38 +445,289 @@ kdc_fast_handle_reply_key(struct kdc_request_state *state,
     return retval;
 }
 
+krb5_boolean
+kdc_fast_hide_client(struct kdc_request_state *state)
+{
+    return (state->fast_options & KRB5_FAST_OPTION_HIDE_CLIENT_NAMES) != 0;
+}
 
-krb5_error_code
-kdc_preauth_get_cookie(struct kdc_request_state *state,
-                       krb5_pa_data **cookie)
+/* Allocate a pa-data entry with an uninitialized buffer of size len. */
+static krb5_error_code
+alloc_padata(krb5_preauthtype pa_type, size_t len, krb5_pa_data **out)
 {
-    char *contents;
-    krb5_pa_data *pa = NULL;
-
-    /* In our current implementation, the only purpose served by
-     * returning a cookie is to indicate that a conversation should
-     * continue on error.  Thus, the cookie can have a constant
-     * string.  If cookies are used for real, versioning so that KDCs
-     * can be upgraded, keying, expiration and many other issues need
-     * to be considered.
-     */
-    contents = strdup("MIT");
-    if (contents == NULL)
+    krb5_pa_data *pa;
+    uint8_t *buf;
+
+    *out = NULL;
+    buf = malloc(len);
+    if (buf == NULL)
         return ENOMEM;
-    pa = calloc(1, sizeof(krb5_pa_data));
+    pa = malloc(sizeof(*pa));
     if (pa == NULL) {
-        free(contents);
+        free(buf);
         return ENOMEM;
     }
-    pa->pa_type = KRB5_PADATA_FX_COOKIE;
-    pa->length = strlen(contents);
-    pa->contents = (unsigned char *) contents;
-    *cookie = pa;
+    pa->magic = KV5M_PA_DATA;
+    pa->pa_type = pa_type;
+    pa->length = len;
+    pa->contents = buf;
+    *out = pa;
     return 0;
 }
 
+/* Create a pa-data entry with the specified type and contents. */
+static krb5_error_code
+make_padata(krb5_preauthtype pa_type, const void *contents, size_t len,
+            krb5_pa_data **out)
+{
+    if (alloc_padata(pa_type, len, out) != 0)
+        return ENOMEM;
+    memcpy((*out)->contents, contents, len);
+    return 0;
+}
+
+/*
+ * Construct the secure cookie encryption key for the given local-realm TGT
+ * entry, kvno, and client principal.  The cookie key is derived from the first
+ * TGT key for the given kvno, using the concatenation of "COOKIE" and the
+ * unparsed client principal name as input.  If kvno is 0, the highest current
+ * kvno of the TGT is used.  If kvno_out is not null, *kvno_out is set to the
+ * kvno used.
+ */
+static krb5_error_code
+get_cookie_key(krb5_context context, krb5_db_entry *tgt, krb5_kvno kvno,
+               krb5_const_principal client_princ, krb5_keyblock **key_out,
+               krb5_kvno *kvno_out)
+{
+    krb5_error_code ret;
+    krb5_key_data *kd;
+    krb5_keyblock kb;
+    krb5_data d;
+    krb5_int32 start = 0;
+    char *princstr = NULL, *derive_input = NULL;
+
+    *key_out = NULL;
+    memset(&kb, 0, sizeof(kb));
+
+    /* Find the first krbtgt key with the specified kvno. */
+    ret = krb5_dbe_search_enctype(context, tgt, &start, -1, -1, kvno, &kd);
+    if (ret)
+        goto cleanup;
+
+    /* Decrypt the key. */
+    ret = krb5_dbe_decrypt_key_data(context, NULL, kd, &kb, NULL);
+    if (ret)
+        goto cleanup;
+
+    /* Construct the input string and derive the cookie key. */
+    ret = krb5_unparse_name(context, client_princ, &princstr);
+    if (ret)
+        goto cleanup;
+    if (asprintf(&derive_input, "COOKIE%s", princstr) < 0) {
+        ret = ENOMEM;
+        goto cleanup;
+    }
+    d = string2data(derive_input);
+    ret = krb5_c_derive_prfplus(context, &kb, &d, ENCTYPE_NULL, key_out);
+
+    if (kvno_out != NULL)
+        *kvno_out = kd->key_data_kvno;
+
+cleanup:
+    krb5_free_keyblock_contents(context, &kb);
+    krb5_free_unparsed_name(context, princstr);
+    free(derive_input);
+    return ret;
+}
+
+/* Return true if there is any overlap between padata types in cpadata
+ * (from the cookie) and rpadata (from the request). */
+static krb5_boolean
+is_relevant(krb5_pa_data *const *cpadata, krb5_pa_data *const *rpadata)
+{
+    krb5_pa_data *const *p;
+
+    for (p = cpadata; p != NULL && *p != NULL; p++) {
+        if (krb5int_find_pa_data(NULL, rpadata, (*p)->pa_type) != NULL)
+            return TRUE;
+    }
+    return FALSE;
+}
+
+/*
+ * Locate and decode the FAST cookie in req, storing its contents in state for
+ * later access by preauth modules.  If the cookie is expired, return
+ * KRB5KDC_ERR_PREAUTH_EXPIRED if its contents are relevant to req, and ignore
+ * it if they aren't.
+ */
+krb5_error_code
+kdc_fast_read_cookie(krb5_context context, struct kdc_request_state *state,
+                     krb5_kdc_req *req, krb5_db_entry *local_tgt)
+{
+    krb5_error_code ret;
+    krb5_secure_cookie *cookie = NULL;
+    krb5_timestamp now;
+    krb5_keyblock *key = NULL;
+    krb5_enc_data enc;
+    krb5_pa_data *pa;
+    krb5_kvno kvno;
+    krb5_data plain = empty_data();
+
+    pa = krb5int_find_pa_data(context, req->padata, KRB5_PADATA_FX_COOKIE);
+    if (pa == NULL)
+        return 0;
+
+    /* If it's not an MIT version 1 cookie, ignore it.  It may be an empty
+     * "MIT" cookie or a cookie generated by a different KDC implementation. */
+    if (pa->length <= 8 || memcmp(pa->contents, "MIT1", 4) != 0)
+        return 0;
+
+    /* Extract the kvno and generate the corresponding cookie key. */
+    kvno = load_32_be(pa->contents + 4);
+    ret = get_cookie_key(context, local_tgt, kvno, req->client, &key, NULL);
+    if (ret)
+        goto cleanup;
+
+    /* Decrypt and decode the cookie. */
+    memset(&enc, 0, sizeof(enc));
+    enc.enctype = key->enctype;
+    enc.ciphertext = make_data(pa->contents + 8, pa->length - 8);
+    ret = alloc_data(&plain, pa->length - 8);
+    if (ret)
+        goto cleanup;
+    ret = krb5_c_decrypt(context, key, KRB5_KEYUSAGE_PA_FX_COOKIE, NULL, &enc,
+                         &plain);
+    if (ret)
+        goto cleanup;
+    ret = decode_krb5_secure_cookie(&plain, &cookie);
+    if (ret)
+        goto cleanup;
+
+    /* Check if the cookie is expired. */
+    ret = krb5_timeofday(context, &now);
+    if (ret)
+        goto cleanup;
+    if (now - COOKIE_LIFETIME > cookie->time) {
+        /* Don't accept the cookie contents.  Only return an error if the
+         * cookie is relevant to the request. */
+        if (is_relevant(cookie->data, req->padata))
+            ret = KRB5KDC_ERR_PREAUTH_EXPIRED;
+        goto cleanup;
+    }
+
+    /* Steal the pa-data list pointer from the cookie and store it in state. */
+    state->in_cookie_padata = cookie->data;
+    cookie->data = NULL;
+
+cleanup:
+    krb5_free_data_contents(context, &plain);
+    krb5_free_keyblock(context, key);
+    k5_free_secure_cookie(context, cookie);
+    return 0;
+}
+
+/* If state contains a cookie value for pa_type, set *out to the corresponding
+ * data and return true.  Otherwise set *out to empty and return false. */
 krb5_boolean
-kdc_fast_hide_client(struct kdc_request_state *state)
+kdc_fast_search_cookie(struct kdc_request_state *state,
+                       krb5_preauthtype pa_type, krb5_data *out)
 {
-    return (state->fast_options & KRB5_FAST_OPTION_HIDE_CLIENT_NAMES) != 0;
+    krb5_pa_data *pa;
+
+    pa = krb5int_find_pa_data(NULL, state->in_cookie_padata, pa_type);
+    if (pa == NULL) {
+        *out = empty_data();
+        return FALSE;
+    } else {
+        *out = make_data(pa->contents, pa->length);
+        return TRUE;
+    }
+}
+
+/* Set a cookie value in state for data, to be included in the outgoing
+ * cookie.  Duplicate values are ignored. */
+krb5_error_code
+kdc_fast_set_cookie(struct kdc_request_state *state, krb5_preauthtype pa_type,
+                    const krb5_data *data)
+{
+    krb5_pa_data **list = state->out_cookie_padata;
+    size_t count;
+
+    for (count = 0; list != NULL && list[count] != NULL; count++) {
+        if (list[count]->pa_type == pa_type)
+            return 0;
+    }
+
+    list = realloc(list, (count + 2) * sizeof(*list));
+    if (list == NULL)
+        return ENOMEM;
+    state->out_cookie_padata = list;
+    list[count] = list[count + 1] = NULL;
+    return make_padata(pa_type, data->data, data->length, &list[count]);
+}
+
+/* Construct a cookie pa-data item using the cookie values from state, or a
+ * trivial "MIT" cookie if no values are set. */
+krb5_error_code
+kdc_fast_make_cookie(krb5_context context, struct kdc_request_state *state,
+                     krb5_db_entry *local_tgt,
+                     krb5_const_principal client_princ,
+                     krb5_pa_data **cookie_out)
+{
+    krb5_error_code ret;
+    krb5_secure_cookie cookie;
+    krb5_pa_data **contents = state->out_cookie_padata, *pa;
+    krb5_keyblock *key = NULL;
+    krb5_timestamp now;
+    krb5_enc_data enc;
+    krb5_data *der_cookie = NULL;
+    krb5_kvno kvno;
+    size_t ctlen;
+
+    *cookie_out = NULL;
+    memset(&enc, 0, sizeof(enc));
+
+    /* Make a trivial cookie if there are no contents to marshal or we don't
+     * have a TGT entry to encrypt them. */
+    if (contents == NULL || *contents == NULL || local_tgt == NULL)
+        return make_padata(KRB5_PADATA_FX_COOKIE, "MIT", 3, cookie_out);
+
+    ret = get_cookie_key(context, local_tgt, 0, client_princ, &key, &kvno);
+    if (ret)
+        goto cleanup;
+
+    /* Encode the cookie. */
+    ret = krb5_timeofday(context, &now);
+    if (ret)
+        goto cleanup;
+    cookie.time = now;
+    cookie.data = contents;
+    ret = encode_krb5_secure_cookie(&cookie, &der_cookie);
+    if (ret)
+        goto cleanup;
+
+    /* Encrypt the cookie in key. */
+    ret = krb5_c_encrypt_length(context, key->enctype, der_cookie->length,
+                                &ctlen);
+    if (ret)
+        goto cleanup;
+    ret = alloc_data(&enc.ciphertext, ctlen);
+    if (ret)
+        goto cleanup;
+    ret = krb5_c_encrypt(context, key, KRB5_KEYUSAGE_PA_FX_COOKIE, NULL,
+                         der_cookie, &enc);
+    if (ret)
+        goto cleanup;
+
+    /* Construct the cookie pa-data entry. */
+    ret = alloc_padata(KRB5_PADATA_FX_COOKIE, 8 + enc.ciphertext.length, &pa);
+    memcpy(pa->contents, "MIT1", 4);
+    store_32_be(kvno, pa->contents + 4);
+    memcpy(pa->contents + 8, enc.ciphertext.data, enc.ciphertext.length);
+    *cookie_out = pa;
+
+cleanup:
+    krb5_free_data(context, der_cookie);
+    krb5_free_data_contents(context, &enc.ciphertext);
+    return ret;
 }
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c
index b8d6e43..bbb4ed2 100644
--- a/src/kdc/kdc_preauth.c
+++ b/src/kdc/kdc_preauth.c
@@ -549,6 +549,20 @@ add_auth_indicator(krb5_context context, krb5_kdcpreauth_rock rock,
     return authind_add(context, indicator, rock->auth_indicators);
 }
 
+static krb5_boolean
+get_cookie(krb5_context context, krb5_kdcpreauth_rock rock,
+           krb5_preauthtype pa_type, krb5_data *out)
+{
+    return kdc_fast_search_cookie(rock->rstate, pa_type, out);
+}
+
+static krb5_error_code
+set_cookie(krb5_context context, krb5_kdcpreauth_rock rock,
+           krb5_preauthtype pa_type, const krb5_data *data)
+{
+    return kdc_fast_set_cookie(rock->rstate, pa_type, data);
+}
+
 static struct krb5_kdcpreauth_callbacks_st callbacks = {
     3,
     max_time_skew,
@@ -562,7 +576,9 @@ static struct krb5_kdcpreauth_callbacks_st callbacks = {
     event_context,
     have_client_keys,
     client_keyblock,
-    add_auth_indicator
+    add_auth_indicator,
+    get_cookie,
+    set_cookie
 };
 
 static krb5_error_code
@@ -752,9 +768,6 @@ hint_list_finish(struct hint_state *state, krb5_error_code code)
                              _("%spreauth required but hint list is empty"),
                              state->hw_only ? "hw" : "");
         }
-        /* If we fail to get the cookie it is probably still reasonable to
-         * continue with the response. */
-        kdc_preauth_get_cookie(state->rock->rstate, state->pa_cur);
 
         *state->e_data_out = state->pa_data;
         state->pa_data = NULL;
@@ -840,8 +853,7 @@ get_preauth_hint_list(krb5_kdc_req *request, krb5_kdcpreauth_rock rock,
     state->realm = rock->rstate->realm_data;
     state->e_data_out = e_data_out;
 
-    /* Allocate two extra entries for the cookie and the terminator. */
-    state->pa_data = calloc(n_preauth_systems + 2, sizeof(krb5_pa_data *));
+    state->pa_data = calloc(n_preauth_systems + 1, sizeof(krb5_pa_data *));
     if (!state->pa_data) {
         free(state);
         (*respond)(arg);
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index 0f49ca0..137952d 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -409,9 +409,6 @@ krb5_error_code kdc_fast_handle_reply_key(struct kdc_request_state *state,
                                           krb5_keyblock **out_key);
 
 
-krb5_error_code kdc_preauth_get_cookie(struct kdc_request_state *state,
-                                       krb5_pa_data **cookie);
-
 krb5_boolean
 kdc_fast_hide_client(struct kdc_request_state *state);
 
@@ -421,6 +418,23 @@ kdc_handle_protected_negotiation( krb5_context context,
                                   const krb5_keyblock *reply_key,
                                   krb5_pa_data ***out_enc_padata);
 
+krb5_error_code
+kdc_fast_read_cookie(krb5_context context, struct kdc_request_state *state,
+                     krb5_kdc_req *req, krb5_db_entry *local_tgt);
+
+krb5_boolean kdc_fast_search_cookie(struct kdc_request_state *state,
+                                    krb5_preauthtype pa_type, krb5_data *out);
+
+krb5_error_code kdc_fast_set_cookie(struct kdc_request_state *state,
+                                    krb5_preauthtype pa_type,
+                                    const krb5_data *data);
+
+krb5_error_code
+kdc_fast_make_cookie(krb5_context context, struct kdc_request_state *state,
+                     krb5_db_entry *local_tgt,
+                     krb5_const_principal client_princ,
+                     krb5_pa_data **cookie_out);
+
 /* Information handle for kdcpreauth callbacks.  All pointers are aliases. */
 struct krb5_kdcpreauth_rock_st {
     krb5_kdc_req *request;
diff --git a/src/kdc/reqstate.h b/src/kdc/reqstate.h
index 58dd616..38c399d 100644
--- a/src/kdc/reqstate.h
+++ b/src/kdc/reqstate.h
@@ -40,7 +40,8 @@
 struct kdc_request_state {
     krb5_keyblock *armor_key;
     krb5_keyblock *strengthen_key;
-    krb5_pa_data *cookie;
+    krb5_pa_data **in_cookie_padata;
+    krb5_pa_data **out_cookie_padata;
     krb5_int32 fast_options;
     krb5_int32 fast_internal_flags;
     kdc_realm_t *realm_data;


More information about the cvs-krb5 mailing list