]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Add replace_reply_key kdcpreauth callback
authorGreg Hudson <ghudson@mit.edu>
Thu, 13 Jan 2022 17:58:32 +0000 (12:58 -0500)
committerGreg Hudson <ghudson@mit.edu>
Thu, 27 Jan 2022 06:52:24 +0000 (01:52 -0500)
Provide an explicit way for kdcpreauth modules to replace the reply
key, and internally track when the reply key is fully replaced (as
opposed to strengthened by replacing it with a derivative of the
client long-term key).  Use this facility in the FAST OTP, PKINIT, and
SPAKE kdcpreauth modules.

ticket: 9049 (new)

src/include/krb5/kdcpreauth_plugin.h
src/kdc/do_as_req.c
src/kdc/kdc_preauth.c
src/kdc/kdc_util.h
src/plugins/preauth/otp/main.c
src/plugins/preauth/pkinit/pkinit_srv.c
src/plugins/preauth/spake/spake_kdc.c

index b0daae16be15cfdf81fee445e04d04d2c9443e1a..f40e368cc1d1a3988f2c5f9c620e9e5b185e557f 100644 (file)
@@ -182,12 +182,12 @@ typedef struct krb5_kdcpreauth_callbacks_st {
     /* End of version 2 kdcpreauth callbacks. */
 
     /*
-     * Get the decrypted client long-term key chosen according to the request
-     * enctype list, or NULL if no matching key was found.  The returned
-     * pointer is an alias and should not be freed.  If invoked from
-     * return_padata, the result will be the same as the encrypting_key
-     * parameter if it is not NULL, and will therefore reflect the modified
-     * reply key if a return_padata handler has replaced the reply key.
+     * Get the current reply key.  Initially the reply key is the decrypted
+     * client long-term key chosen according to the request enctype list, or
+     * NULL if no matching key was found.  The value may be changed by the
+     * replace_reply_key callback or a return_padata method modifying
+     * encrypting_key.  The returned pointer is an alias and should not be
+     * freed.
      */
     const krb5_keyblock *(*client_keyblock)(krb5_context context,
                                             krb5_kdcpreauth_rock rock);
@@ -257,6 +257,20 @@ typedef struct krb5_kdcpreauth_callbacks_st {
 
     /* End of version 5 kdcpreauth callbacks. */
 
+    /*
+     * Replace the reply key with key.  If is_strengthen is true, key must be a
+     * derivative of the client long-term key.  This callback may be invoked
+     * from the verify or return_padata methods.  If it is invoked from the
+     * verify method, the new key will appear as the encrypting_key input to
+     * return_padata.
+     */
+    krb5_error_code (*replace_reply_key)(krb5_context context,
+                                         krb5_kdcpreauth_rock rock,
+                                         const krb5_keyblock *key,
+                                         krb5_boolean is_strengthen);
+
+    /* End of version 6 kdcpreauth callbacks. */
+
 } *krb5_kdcpreauth_callbacks;
 
 /* Optional: preauth plugin initialization function. */
@@ -350,7 +364,8 @@ typedef void
 /*
  * Optional: generate preauthentication response data to send to the client as
  * part of the AS-REP.  If it needs to override the key which is used to
- * encrypt the response, it can do so.
+ * encrypt the response, it can do so by modifying encrypting_key, but it is
+ * preferrable to use the replace_reply_key callback.
  */
 typedef krb5_error_code
 (*krb5_kdcpreauth_return_fn)(krb5_context context,
index 5e966de8f70e76daa6c49cd793535ad1f1cf8186..34723fa34368e2009557caadc575b06b8929944d 100644 (file)
@@ -743,10 +743,9 @@ process_as_req(krb5_kdc_req *request, krb5_data *req_pkt,
         state->status = "DECRYPT_CLIENT_KEY";
         goto errout;
     }
-    if (state->client_key != NULL) {
+    if (state->client_key != NULL)
         state->rock.client_key = state->client_key;
-        state->rock.client_keyblock = &state->client_keyblock;
-    }
+    state->rock.client_keyblock = &state->client_keyblock;
 
     errcode = kdc_fast_read_cookie(kdc_context, state->rstate, state->request,
                                    state->local_tgt, &state->local_tgt_key);
index 8d832743d24bfb45256ee59992f7d3c3c121a0b0..e1323907e339be41ea160c91b048e6639c24f9a7 100644 (file)
@@ -449,6 +449,8 @@ have_client_keys(krb5_context context, krb5_kdcpreauth_rock rock)
 static const krb5_keyblock *
 client_keyblock(krb5_context context, krb5_kdcpreauth_rock rock)
 {
+    if (rock->client_keyblock->enctype == ENCTYPE_NULL)
+        return NULL;
     return rock->client_keyblock;
 }
 
@@ -562,8 +564,23 @@ cleanup:
     return valid ? 0 : KRB5KDC_ERR_PREAUTH_EXPIRED;
 }
 
+static krb5_error_code
+replace_reply_key(krb5_context context, krb5_kdcpreauth_rock rock,
+                  const krb5_keyblock *key, krb5_boolean is_strengthen)
+{
+    krb5_keyblock copy;
+
+    if (krb5_copy_keyblock_contents(context, key, &copy) != 0)
+        return ENOMEM;
+    krb5_free_keyblock_contents(context, rock->client_keyblock);
+    *rock->client_keyblock = copy;
+    if (!is_strengthen)
+        rock->replaced_reply_key = TRUE;
+    return 0;
+}
+
 static struct krb5_kdcpreauth_callbacks_st callbacks = {
-    5,
+    6,
     max_time_skew,
     client_keys,
     free_keys,
@@ -581,7 +598,8 @@ static struct krb5_kdcpreauth_callbacks_st callbacks = {
     match_client,
     client_name,
     send_freshness_token,
-    check_freshness_token
+    check_freshness_token,
+    replace_reply_key
 };
 
 static krb5_error_code
index 45683ece5ffdd6e4be18fb672a9c94714ea7117b..ded2912fb0b568f0531875aa7a9dfc7aeeda39f0 100644 (file)
@@ -447,6 +447,7 @@ struct krb5_kdcpreauth_rock_st {
     verto_ctx *vctx;
     krb5_data ***auth_indicators;
     krb5_boolean send_freshness_token;
+    krb5_boolean replaced_reply_key;
 };
 
 #define isflagset(flagfield, flag) (flagfield & (flag))
index a1b6816824053eb9861092a2e2763e2ba5ade754..119714f994e9acad8c9f18a1982f70b2c9d25083 100644 (file)
@@ -158,19 +158,36 @@ on_response(void *data, krb5_error_code retval, otp_response response,
             char *const *indicators)
 {
     struct request_state rs = *(struct request_state *)data;
+    krb5_context context = rs.context;
+    krb5_keyblock *armor_key;
     char *const *ind;
 
     free(data);
 
     if (retval == 0 && response != otp_response_success)
         retval = KRB5_PREAUTH_FAILED;
+    if (retval)
+        goto done;
 
-    if (retval == 0)
-        rs.enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
+    rs.enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
+    armor_key = rs.preauth_cb->fast_armor(context, rs.rock);
+    if (armor_key == NULL) {
+        retval = ENOENT;
+        goto done;
+    }
 
-    for (ind = indicators; ind != NULL && *ind != NULL && retval == 0; ind++)
-        retval = rs.preauth_cb->add_auth_indicator(rs.context, rs.rock, *ind);
+    retval = rs.preauth_cb->replace_reply_key(context, rs.rock, armor_key,
+                                              FALSE);
+    if (retval)
+        goto done;
 
+    for (ind = indicators; ind != NULL && *ind != NULL; ind++) {
+        retval = rs.preauth_cb->add_auth_indicator(context, rs.rock, *ind);
+        if (retval)
+            goto done;
+    }
+
+done:
     rs.respond(rs.arg, retval, NULL, NULL, NULL);
 }
 
@@ -343,31 +360,6 @@ error:
     (*respond)(arg, retval, NULL, NULL, NULL);
 }
 
-static krb5_error_code
-otp_return_padata(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_out, krb5_kdcpreauth_callbacks cb,
-                  krb5_kdcpreauth_rock rock, krb5_kdcpreauth_moddata moddata,
-                  krb5_kdcpreauth_modreq modreq)
-{
-    krb5_keyblock *armor_key = NULL;
-
-    if (padata->length == 0)
-        return 0;
-
-    /* Get the armor key. */
-    armor_key = cb->fast_armor(context, rock);
-    if (!armor_key) {
-      com_err("otp", ENOENT, "No armor key found when returning padata");
-      return ENOENT;
-    }
-
-    /* Replace the reply key with the FAST armor key. */
-    krb5_free_keyblock_contents(context, encrypting_key);
-    return krb5_copy_keyblock_contents(context, armor_key, encrypting_key);
-}
-
 krb5_error_code
 kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
                       krb5_plugin_vtable vtable);
@@ -389,7 +381,6 @@ kdcpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
     vt->flags = otp_flags;
     vt->edata = otp_edata;
     vt->verify = otp_verify;
-    vt->return_padata = otp_return_padata;
 
     com_err("otp", 0, "Loaded");
 
index 81e9656537cd96dd442017fafedf31b3a19bfb22..1147a8fc2d7a106d665302fd2c2a2cf58edb2be6 100644 (file)
@@ -763,6 +763,7 @@ pkinit_server_return_padata(krb5_context context,
 
     unsigned char *dh_pubkey = NULL, *server_key = NULL;
     unsigned int server_key_len = 0, dh_pubkey_len = 0;
+    krb5_keyblock reply_key = { 0 };
 
     krb5_kdc_dh_key_info dhkey_info;
     krb5_data *encoded_dhkey_info = NULL;
@@ -800,12 +801,6 @@ pkinit_server_return_padata(krb5_context context,
     TRACE_PKINIT_SERVER_RETURN_PADATA(context);
     reqctx = (pkinit_kdc_req_context)modreq;
 
-    if (encrypting_key->contents) {
-        free(encrypting_key->contents);
-        encrypting_key->length = 0;
-        encrypting_key->contents = NULL;
-    }
-
     for(i = 0; i < request->nktypes; i++) {
         enctype = request->ktype[i];
         if (!krb5_c_valid_enctype(enctype))
@@ -883,19 +878,19 @@ pkinit_server_return_padata(krb5_context context,
     } else {
         pkiDebug("received RSA key delivery AS REQ\n");
 
-        retval = krb5_c_make_random_key(context, enctype, encrypting_key);
-        if (retval) {
-            pkiDebug("unable to make a session key\n");
-            goto cleanup;
-        }
-
         init_krb5_reply_key_pack(&key_pack);
         if (key_pack == NULL) {
             retval = ENOMEM;
             goto cleanup;
         }
 
-        retval = krb5_c_make_checksum(context, 0, encrypting_key,
+        retval = krb5_c_make_random_key(context, enctype, &key_pack->replyKey);
+        if (retval) {
+            pkiDebug("unable to make a session key\n");
+            goto cleanup;
+        }
+
+        retval = krb5_c_make_checksum(context, 0, &key_pack->replyKey,
                                       KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM,
                                       req_pkt, &key_pack->asChecksum);
         if (retval) {
@@ -908,13 +903,10 @@ pkinit_server_return_padata(krb5_context context,
         pkiDebug("checksum size = %d\n", key_pack->asChecksum.length);
         print_buffer(key_pack->asChecksum.contents,
                      key_pack->asChecksum.length);
-        pkiDebug("encrypting key (%d)\n", encrypting_key->length);
-        print_buffer(encrypting_key->contents, encrypting_key->length);
+        pkiDebug("encrypting key (%d)\n", key_pack->replyKey.length);
+        print_buffer(key_pack->replyKey.contents, key_pack->replyKey.length);
 #endif
 
-        krb5_copy_keyblock_contents(context, encrypting_key,
-                                    &key_pack->replyKey);
-
         retval = k5int_encode_krb5_reply_key_pack(key_pack,
                                                   &encoded_key_pack);
         if (retval) {
@@ -944,6 +936,11 @@ pkinit_server_return_padata(krb5_context context,
         print_buffer_bin(rep->u.encKeyPack.data, rep->u.encKeyPack.length,
                          "/tmp/kdc_enc_key_pack");
 #endif
+
+        retval = cb->replace_reply_key(context, rock, &key_pack->replyKey,
+                                       FALSE);
+        if (retval)
+            goto cleanup;
     }
 
     if (rep->choice == choice_pa_pk_as_rep_dhInfo &&
@@ -989,7 +986,7 @@ pkinit_server_return_padata(krb5_context context,
                                             rep->u.dh_Info.kdfID,
                                             request->client, request->server,
                                             enctype, req_pkt, out_data,
-                                            encrypting_key);
+                                            &reply_key);
             if (retval) {
                 pkiDebug("pkinit_alg_agility_kdf failed: %s\n",
                          error_message(retval));
@@ -999,13 +996,16 @@ pkinit_server_return_padata(krb5_context context,
             /* Otherwise, use the older octetstring2key() function */
         } else {
             retval = pkinit_octetstring2key(context, enctype, server_key,
-                                            server_key_len, encrypting_key);
+                                            server_key_len, &reply_key);
             if (retval) {
                 pkiDebug("pkinit_octetstring2key failed: %s\n",
                          error_message(retval));
                 goto cleanup;
             }
         }
+        retval = cb->replace_reply_key(context, rock, &reply_key, FALSE);
+        if (retval)
+            goto cleanup;
     }
 
     *send_pa = malloc(sizeof(krb5_pa_data));
@@ -1034,6 +1034,7 @@ cleanup:
     free_krb5_pa_pk_as_req(&reqp);
     free_krb5_pa_pk_as_rep(&rep);
     free_krb5_reply_key_pack(&key_pack);
+    krb5_free_keyblock_contents(context, &reply_key);
 
     if (retval)
         pkiDebug("pkinit_verify_padata failure");
index 88c964ce1dad581543bdc602e825173a8ef7f382..95cdb55159ea84b08a485278d25d5b7c084ec0e8 100644 (file)
@@ -458,6 +458,10 @@ verify_response(krb5_context context, groupstate *gstate,
 
     ret = derive_key(context, gstate, group, ikey, &wbytes, &spakeresult,
                      &thash, der_req, 0, &reply_key);
+    if (ret)
+        goto cleanup;
+
+    ret = cb->replace_reply_key(context, rock, reply_key, TRUE);
 
 cleanup:
     zapfree(wbytes.data, wbytes.length);
@@ -466,7 +470,7 @@ cleanup:
     krb5_free_data_contents(context, &thash);
     krb5_free_keyblock(context, k1);
     k5_free_spake_factor(context, factor);
-    (*respond)(arg, ret, (krb5_kdcpreauth_modreq)reply_key, NULL, NULL);
+    (*respond)(arg, ret, NULL, NULL, NULL);
 }
 
 /*
@@ -533,23 +537,6 @@ spake_verify(krb5_context context, krb5_data *req_pkt, krb5_kdc_req *request,
     k5_free_pa_spake(context, pa_spake);
 }
 
-/* If a key was set in the per-request module data, replace the reply key.  Do
- * not generate any pa-data to include with the KDC reply. */
-static krb5_error_code
-spake_return(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_out,
-             krb5_kdcpreauth_callbacks cb, krb5_kdcpreauth_rock rock,
-             krb5_kdcpreauth_moddata moddata, krb5_kdcpreauth_modreq modreq)
-{
-    krb5_keyblock *reply_key = (krb5_keyblock *)modreq;
-
-    if (reply_key == NULL)
-        return 0;
-    krb5_free_keyblock_contents(context, encrypting_key);
-    return krb5_copy_keyblock_contents(context, reply_key, encrypting_key);
-}
-
 /* Release a per-request module data object. */
 static void
 spake_free_modreq(krb5_context context, krb5_kdcpreauth_moddata moddata,
@@ -578,7 +565,6 @@ kdcpreauth_spake_initvt(krb5_context context, int maj_ver, int min_ver,
     vt->fini = spake_fini;
     vt->edata = spake_edata;
     vt->verify = spake_verify;
-    vt->return_padata = spake_return;
     vt->free_modreq = spake_free_modreq;
     return 0;
 }