krb5 commit: Only include one key in etype-info

Greg Hudson ghudson at mit.edu
Wed Jul 8 18:24:53 EDT 2015


https://github.com/krb5/krb5/commit/385cd2d07983a89892dad1606e1a41a78066c6ec
commit 385cd2d07983a89892dad1606e1a41a78066c6ec
Author: Greg Hudson <ghudson at mit.edu>
Date:   Sat Jun 6 15:45:39 2015 -0400

    Only include one key in etype-info
    
    As described in RFC 6113 section 2.1, the KDC can choose a single
    long-term key at the beginning of the preauth conversation based on
    the request enctype list.  Implement this change for the PA-ETYPE-INFO
    and PA-ETYPE-INFO2 padata included in preauth hint lists, by selecting
    the client key before checking padata, making the client keyblock
    available in the preauth rock, and unifying the etype-info handlers to
    use a single helper function for edata and AS-REP padata.
    
    ticket: 8199 (new)

 src/kdc/do_as_req.c   |   88 ++++++++++------
 src/kdc/kdc_preauth.c |  269 +++++++++----------------------------------------
 src/kdc/kdc_util.h    |    1 +
 3 files changed, 104 insertions(+), 254 deletions(-)

diff --git a/src/kdc/do_as_req.c b/src/kdc/do_as_req.c
index c62f0b3..3e18e7c 100644
--- a/src/kdc/do_as_req.c
+++ b/src/kdc/do_as_req.c
@@ -90,6 +90,46 @@ get_key_exp(krb5_db_entry *entry)
     return min(entry->expiration, entry->pw_expiration);
 }
 
+/*
+ * Find the key in client for the most preferred enctype in req_enctypes.  Fill
+ * in *kb_out with the decrypted keyblock (which the caller must free) and set
+ * *kd_out to an alias to that key data entry.  Set *kd_out to NULL and leave
+ * *kb_out zeroed if no key is found for any of the requested enctypes.
+ * kb_out->enctype may differ from the enctype of *kd_out for DES enctypes; in
+ * this case, kb_out->enctype is the requested enctype used to match the key
+ * data entry.
+ */
+static krb5_error_code
+select_client_key(krb5_context context, krb5_db_entry *client,
+                  krb5_enctype *req_enctypes, int n_req_enctypes,
+                  krb5_keyblock *kb_out, krb5_key_data **kd_out)
+{
+    krb5_error_code ret;
+    krb5_key_data *kd;
+    krb5_enctype etype;
+    int i;
+
+    memset(kb_out, 0, sizeof(*kb_out));
+    *kd_out = NULL;
+
+    for (i = 0; i < n_req_enctypes; i++) {
+        etype = req_enctypes[i];
+        if (!krb5_c_valid_enctype(etype))
+            continue;
+        if (krb5_dbe_find_enctype(context, client, etype, -1, 0, &kd) == 0) {
+            /* Decrypt the client key data and set its enctype to the request
+             * enctype (which may differ from the key data enctype for DES). */
+            ret = krb5_dbe_decrypt_key_data(context, NULL, kd, kb_out, NULL);
+            if (ret)
+                return ret;
+            kb_out->enctype = etype;
+            *kd_out = kd;
+            return 0;
+        }
+    }
+    return 0;
+}
+
 struct as_req_state {
     loop_respond_fn respond;
     void *arg;
@@ -104,6 +144,7 @@ struct as_req_state {
     krb5_db_entry *server;
     krb5_db_entry *local_tgt;
     krb5_db_entry *local_tgt_storage;
+    krb5_key_data *client_key;
     krb5_kdc_req *request;
     struct krb5_kdcpreauth_rock_st rock;
     const char *status;
@@ -131,13 +172,10 @@ static void
 finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
 {
     krb5_key_data *server_key;
-    krb5_key_data *client_key;
     krb5_keyblock *as_encrypting_key = NULL;
     krb5_data *response = NULL;
     const char *emsg = 0;
     int did_log = 0;
-    register int i;
-    krb5_enctype useenctype;
     loop_respond_fn oldrespond;
     void *oldarg;
     kdc_realm_t *kdc_active_realm = state->active_realm;
@@ -187,34 +225,6 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
         goto egress;
     }
 
-    /*
-     * Find the appropriate client key.  We search in the order specified
-     * by request keytype list.
-     */
-    client_key = NULL;
-    useenctype = 0;
-    for (i = 0; i < state->request->nktypes; i++) {
-        useenctype = state->request->ktype[i];
-        if (!krb5_c_valid_enctype(useenctype))
-            continue;
-
-        if (!krb5_dbe_find_enctype(kdc_context, state->client,
-                                   useenctype, -1, 0, &client_key))
-            break;
-    }
-
-    if (client_key != NULL) {
-        /* Decrypt the client key data entry to get the real reply key. */
-        errcode = krb5_dbe_decrypt_key_data(kdc_context, NULL, client_key,
-                                            &state->client_keyblock, NULL);
-        if (errcode) {
-            state->status = "DECRYPT_CLIENT_KEY";
-            goto egress;
-        }
-        state->client_keyblock.enctype = useenctype;
-        state->rock.client_key = client_key;
-    }
-
     /* Start assembling the response */
     state->reply.msg_type = KRB5_AS_REP;
     state->reply.client = state->enc_tkt_reply.client; /* post canonization */
@@ -327,8 +337,8 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
                                   &state->reply_encpart, 0,
                                   as_encrypting_key,
                                   &state->reply, &response);
-    if (client_key != NULL)
-        state->reply.enc_part.kvno = client_key->key_data_kvno;
+    if (state->client_key != NULL)
+        state->reply.enc_part.kvno = state->client_key->key_data_kvno;
     if (errcode) {
         state->status = "ENCODE_KDC_REP";
         goto egress;
@@ -772,6 +782,18 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
         setflag(state->client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH);
     }
 
+    errcode = select_client_key(kdc_context, state->client,
+                                state->request->ktype, state->request->nktypes,
+                                &state->client_keyblock, &state->client_key);
+    if (errcode) {
+        state->status = "DECRYPT_CLIENT_KEY";
+        goto errout;
+    }
+    if (state->client_key != NULL) {
+        state->rock.client_key = state->client_key;
+        state->rock.client_keyblock = &state->client_keyblock;
+    }
+
     /*
      * Check the preauthentication if it is there.
      */
diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c
index dd83844..0a5d8f4 100644
--- a/src/kdc/kdc_preauth.c
+++ b/src/kdc/kdc_preauth.c
@@ -107,21 +107,6 @@ get_etype_info(krb5_context context, krb5_kdc_req *request,
                krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
                krb5_kdcpreauth_edata_respond_fn respond, void *arg);
 
-static void
-get_etype_info2(krb5_context context, krb5_kdc_req *request,
-                krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
-                krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
-                krb5_kdcpreauth_edata_respond_fn respond, void *arg);
-
-static krb5_error_code
-etype_info_as_rep_helper(krb5_context context, krb5_pa_data * padata,
-                         krb5_db_entry *client,
-                         krb5_kdc_req *request, krb5_kdc_rep *reply,
-                         krb5_key_data *client_key,
-                         krb5_keyblock *encrypting_key,
-                         krb5_pa_data **send_pa,
-                         int etype_info2);
-
 static krb5_error_code
 return_etype_info(krb5_context, krb5_pa_data *padata,
                   krb5_data *req_pkt, krb5_kdc_req *request,
@@ -131,14 +116,6 @@ return_etype_info(krb5_context, krb5_pa_data *padata,
                   krb5_kdcpreauth_modreq modreq);
 
 static krb5_error_code
-return_etype_info2(krb5_context, krb5_pa_data *padata,
-                   krb5_data *req_pkt, krb5_kdc_req *request,
-                   krb5_kdc_rep *reply, krb5_keyblock *encrypting_key,
-                   krb5_pa_data **send_pa, krb5_kdcpreauth_callbacks cb,
-                   krb5_kdcpreauth_rock rock, krb5_kdcpreauth_moddata moddata,
-                   krb5_kdcpreauth_modreq modreq);
-
-static krb5_error_code
 return_pw_salt(krb5_context, krb5_pa_data *padata,
                krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply,
                krb5_keyblock *encrypting_key, krb5_pa_data **send_pa,
@@ -177,9 +154,9 @@ static preauth_system static_preauth_systems[] = {
         NULL,
         NULL,
         NULL,
-        get_etype_info2,
+        get_etype_info,
         0,
-        return_etype_info2
+        return_etype_info
     },
     {
         "pw-salt",
@@ -1288,17 +1265,6 @@ cleanup:
     return (retval);
 }
 
-static krb5_boolean
-request_contains_enctype(krb5_context context,  const krb5_kdc_req *request,
-                         krb5_enctype enctype)
-{
-    int i;
-    for (i =0; i < request->nktypes; i++)
-        if (request->ktype[i] == enctype)
-            return 1;
-    return 0;
-}
-
 static krb5_error_code
 _make_etype_info_entry(krb5_context context,
                        krb5_principal client_princ, krb5_key_data *client_key,
@@ -1353,80 +1319,27 @@ cleanup:
     return retval;
 }
 
-/* Create etype information for a client for the preauth-required hint list,
- * for either etype-info or etype-info2. */
-static void
-etype_info_helper(krb5_context context, krb5_kdc_req *request,
-                  krb5_db_entry *client, krb5_preauthtype pa_type,
-                  krb5_kdcpreauth_edata_respond_fn respond, void *arg)
+/* Create etype-info or etype-info2 padata for client_key with the given
+ * enctype, using client to compute the salt if necessary. */
+static krb5_error_code
+make_etype_info(krb5_context context, krb5_preauthtype pa_type,
+                krb5_principal client, krb5_key_data *client_key,
+                krb5_enctype enctype, krb5_pa_data **pa_out)
 {
     krb5_error_code retval;
     krb5_pa_data *pa = NULL;
     krb5_etype_info_entry **entry = NULL;
     krb5_data *scratch = NULL;
-    krb5_key_data *client_key;
-    krb5_enctype db_etype;
-    int i = 0, start = 0, seen_des = 0;
     int etype_info2 = (pa_type == KRB5_PADATA_ETYPE_INFO2);
 
-    entry = k5calloc(client->n_key_data * 2 + 1, sizeof(*entry), &retval);
+    *pa_out = NULL;
+
+    entry = k5calloc(2, sizeof(*entry), &retval);
     if (entry == NULL)
         goto cleanup;
-    entry[0] = NULL;
-
-    while (1) {
-        retval = krb5_dbe_search_enctype(context, client, &start, -1,
-                                         -1, 0, &client_key);
-        if (retval == KRB5_KDB_NO_MATCHING_KEY)
-            break;
-        if (retval)
-            goto cleanup;
-        db_etype = client_key->key_data_type[0];
-        if (db_etype == ENCTYPE_DES_CBC_MD4)
-            db_etype = ENCTYPE_DES_CBC_MD5;
-
-        if (request_contains_enctype(context, request, db_etype)) {
-            assert(etype_info2 ||
-                   !enctype_requires_etype_info_2(db_etype));
-            retval = _make_etype_info_entry(context, client->princ, client_key,
-                                            db_etype, &entry[i], etype_info2);
-            if (retval != 0)
-                goto cleanup;
-            i++;
-        }
-
-        /*
-         * If there is a des key in the kdb, try the "similar" enctypes,
-         * avoid duplicate entries.
-         */
-        if (!seen_des) {
-            switch (db_etype) {
-            case ENCTYPE_DES_CBC_MD5:
-                db_etype = ENCTYPE_DES_CBC_CRC;
-                break;
-            case ENCTYPE_DES_CBC_CRC:
-                db_etype = ENCTYPE_DES_CBC_MD5;
-                break;
-            default:
-                continue;
-
-            }
-            if (krb5_is_permitted_enctype(context, db_etype) &&
-                request_contains_enctype(context, request, db_etype)) {
-                retval = _make_etype_info_entry(context, client->princ,
-                                                client_key, db_etype,
-                                                &entry[i], etype_info2);
-                if (retval != 0)
-                    goto cleanup;
-                entry[i+1] = 0;
-                i++;
-            }
-            seen_des++;
-        }
-    }
-
-    /* If the list is empty, don't send it at all. */
-    if (i == 0)
+    retval = _make_etype_info_entry(context, client, client_key, enctype,
+                                    &entry[0], etype_info2);
+    if (retval != 0)
         goto cleanup;
 
     if (etype_info2)
@@ -1443,135 +1356,50 @@ etype_info_helper(krb5_context context, krb5_kdc_req *request,
     pa->contents = (unsigned char *)scratch->data;
     pa->length = scratch->length;
     scratch->data = NULL;
+    *pa_out = pa;
 
 cleanup:
     krb5_free_etype_info(context, entry);
     krb5_free_data(context, scratch);
-    (*respond)(arg, retval, pa);
+    return retval;
 }
 
-static void
-get_etype_info(krb5_context context, krb5_kdc_req *request,
-               krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
-               krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
-               krb5_kdcpreauth_edata_respond_fn respond, void *arg)
+/* Return true if request's enctypes indicate support for etype-info2. */
+static krb5_boolean
+requires_info2(const krb5_kdc_req *request)
 {
     int i;
 
-    for (i=0;  i < request->nktypes; i++) {
-        if (enctype_requires_etype_info_2(request->ktype[i])) {
-            /* Requestor understands etype-info2, so don't send etype-info. */
-            (*respond)(arg, KRB5KDC_ERR_PADATA_TYPE_NOSUPP, NULL);
-            return;
-        }
+    for (i = 0; i < request->nktypes; i++) {
+        if (enctype_requires_etype_info_2(request->ktype[i]))
+            return TRUE;
     }
-
-    etype_info_helper(context, request, rock->client, pa_type, respond, arg);
+    return FALSE;
 }
 
+/* Generate hint list padata for PA-ETYPE-INFO or PA-ETYPE-INFO2. */
 static void
-get_etype_info2(krb5_context context, krb5_kdc_req *request,
-                krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
-                krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
-                krb5_kdcpreauth_edata_respond_fn respond, void *arg)
-{
-    etype_info_helper(context, request, rock->client, pa_type, respond, arg);
-}
-
-static krb5_error_code
-etype_info_as_rep_helper(krb5_context context, krb5_pa_data * padata,
-                         krb5_db_entry *client,
-                         krb5_kdc_req *request, krb5_kdc_rep *reply,
-                         krb5_key_data *client_key,
-                         krb5_keyblock *encrypting_key,
-                         krb5_pa_data **send_pa,
-                         int etype_info2)
+get_etype_info(krb5_context context, krb5_kdc_req *request,
+               krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
+               krb5_kdcpreauth_moddata moddata, krb5_preauthtype pa_type,
+               krb5_kdcpreauth_edata_respond_fn respond, void *arg)
 {
-    int i;
-    krb5_error_code retval;
-    krb5_pa_data *tmp_padata;
-    krb5_etype_info_entry **entry = NULL;
-    krb5_data *scratch = NULL;
-
-    if (client_key == NULL)
-        return 0;
-
-    /*
-     * Skip PA-ETYPE-INFO completely if AS-REQ lists any "newer"
-     * enctypes.
-     */
-    if (!etype_info2) {
-        for (i = 0; i < request->nktypes; i++) {
-            if (enctype_requires_etype_info_2(request->ktype[i])) {
-                *send_pa = NULL;
-                return 0;
-            }
-        }
-    }
-
-    tmp_padata = malloc( sizeof(krb5_pa_data));
-    if (tmp_padata == NULL)
-        return ENOMEM;
-    if (etype_info2)
-        tmp_padata->pa_type = KRB5_PADATA_ETYPE_INFO2;
-    else
-        tmp_padata->pa_type = KRB5_PADATA_ETYPE_INFO;
-
-    entry = malloc(2 * sizeof(krb5_etype_info_entry *));
-    if (entry == NULL) {
-        retval = ENOMEM;
-        goto cleanup;
-    }
-    entry[0] = NULL;
-    entry[1] = NULL;
-    retval = _make_etype_info_entry(context, client->princ, client_key,
-                                    encrypting_key->enctype, entry,
-                                    etype_info2);
-    if (retval)
-        goto cleanup;
-
-    if (etype_info2)
-        retval = encode_krb5_etype_info2(entry, &scratch);
-    else
-        retval = encode_krb5_etype_info(entry, &scratch);
-
-    if (retval)
-        goto cleanup;
-    tmp_padata->contents = (krb5_octet *)scratch->data;
-    tmp_padata->length = scratch->length;
-    *send_pa = tmp_padata;
-
-    /* For cleanup - we no longer own the contents of the krb5_data
-     * only to pointer to the krb5_data
-     */
-    scratch->data = 0;
+    krb5_error_code ret;
+    krb5_pa_data *pa = NULL;
 
-cleanup:
-    if (entry)
-        krb5_free_etype_info(context, entry);
-    if (retval) {
-        if (tmp_padata)
-            free(tmp_padata);
+    if (rock->client_key == NULL) {
+        ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
+    } else if (pa_type == KRB5_PADATA_ETYPE_INFO && requires_info2(request)) {
+        ret = KRB5KDC_ERR_PADATA_TYPE_NOSUPP;
+    } else {
+        ret = make_etype_info(context, pa_type, rock->client->princ,
+                              rock->client_key, rock->client_keyblock->enctype,
+                              &pa);
     }
-    if (scratch)
-        krb5_free_data(context, scratch);
-    return retval;
+    (*respond)(arg, ret, pa);
 }
 
-static krb5_error_code
-return_etype_info2(krb5_context context, krb5_pa_data * padata,
-                   krb5_data *req_pkt, krb5_kdc_req *request,
-                   krb5_kdc_rep *reply, krb5_keyblock *encrypting_key,
-                   krb5_pa_data **send_pa, krb5_kdcpreauth_callbacks cb,
-                   krb5_kdcpreauth_rock rock, krb5_kdcpreauth_moddata moddata,
-                   krb5_kdcpreauth_modreq modreq)
-{
-    return etype_info_as_rep_helper(context, padata, rock->client, request,
-                                    reply, rock->client_key, encrypting_key,
-                                    send_pa, 1);
-}
-
-
+/* Generate AS-REP padata for PA-ETYPE-INFO or PA-ETYPE-INFO2. */
 static krb5_error_code
 return_etype_info(krb5_context context, krb5_pa_data *padata,
                   krb5_data *req_pkt, krb5_kdc_req *request,
@@ -1580,9 +1408,13 @@ return_etype_info(krb5_context context, krb5_pa_data *padata,
                   krb5_kdcpreauth_rock rock, krb5_kdcpreauth_moddata moddata,
                   krb5_kdcpreauth_modreq modreq)
 {
-    return etype_info_as_rep_helper(context, padata, rock->client, request,
-                                    reply, rock->client_key, encrypting_key,
-                                    send_pa, 0);
+    *send_pa = NULL;
+    if (rock->client_key == NULL)
+        return 0;
+    if (padata->pa_type == KRB5_PADATA_ETYPE_INFO && requires_info2(request))
+        return 0;
+    return make_etype_info(context, padata->pa_type, rock->client->princ,
+                           rock->client_key, encrypting_key->enctype, send_pa);
 }
 
 static krb5_error_code
@@ -1597,15 +1429,10 @@ return_pw_salt(krb5_context context, krb5_pa_data *in_padata,
     krb5_data *         salt = NULL;
     krb5_int16          salttype;
     krb5_key_data *     client_key = rock->client_key;
-    int i;
 
-    if (client_key == NULL)
+    if (client_key == NULL || requires_info2(request))
         return 0;
 
-    for (i = 0; i < request->nktypes; i++) {
-        if (enctype_requires_etype_info_2(request->ktype[i]))
-            return 0;
-    }
     retval = krb5_dbe_compute_salt(context, client_key, request->client,
                                    &salttype, &salt);
     if (retval)
diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h
index 0fa4fbb..97ae41f 100644
--- a/src/kdc/kdc_util.h
+++ b/src/kdc/kdc_util.h
@@ -393,6 +393,7 @@ struct krb5_kdcpreauth_rock_st {
     krb5_data *inner_body;
     krb5_db_entry *client;
     krb5_key_data *client_key;
+    krb5_keyblock *client_keyblock;
     struct kdc_request_state *rstate;
     verto_ctx *vctx;
 };


More information about the cvs-krb5 mailing list