From bbbccd795940114cb18722fc6fffe2b25ce3c436 Mon Sep 17 00:00:00 2001
From: Pauli <pauli@openssl.org>
Date: Thu, 27 Jan 2022 13:33:36 +1100
Subject: [PATCH] evp enc: cache cipher key length

Instead of doing a heavy params based query every time a context is
asked for its key length, this value is cached in the context and only
queried if it could have been modified.

Fixes #17064

Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/17543)

(cherry picked from commit 70f39a487d3f7d976a01e0ee7ae98a82ceeea7a0)
Reviewed-by: Hugo Landau <hlandau@openssl.org>
---
 crypto/evp/evp_enc.c | 31 ++++++++++++++++++++++++-------
 crypto/evp/evp_lib.c | 26 ++++++++++++++++++++------
 2 files changed, 44 insertions(+), 13 deletions(-)

diff --git a/crypto/evp/evp_enc.c b/crypto/evp/evp_enc.c
index 921d24dd3d..a6468f6767 100644
--- a/crypto/evp/evp_enc.c
+++ b/crypto/evp/evp_enc.c
@@ -62,7 +62,7 @@ int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *ctx)
     ENGINE_finish(ctx->engine);
 #endif
     memset(ctx, 0, sizeof(*ctx));
-    ctx->iv_len = -1;
+    ctx->iv_len = 0;
     return 1;
 }
 
@@ -994,7 +994,7 @@ int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *c, int keylen)
     if (c->cipher->prov != NULL) {
         int ok;
         OSSL_PARAM params[2] = { OSSL_PARAM_END, OSSL_PARAM_END };
-        size_t len = keylen;
+        size_t len;
 
         if (EVP_CIPHER_CTX_get_key_length(c) == keylen)
             return 1;
@@ -1007,9 +1007,13 @@ int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *c, int keylen)
         }
 
         params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_KEYLEN, &len);
+        if (!OSSL_PARAM_set_int(params, keylen))
+            return 0;
         ok = evp_do_ciph_ctx_setparams(c->cipher, c->algctx, params);
-
-        return ok > 0 ? 1 : 0;
+        if (ok <= 0)
+            return 0;
+        c->key_len = keylen;
+        return 1;
     }
 
     /* Code below to be removed when legacy support is dropped. */
@@ -1070,6 +1074,7 @@ int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr)
     switch (type) {
     case EVP_CTRL_SET_KEY_LENGTH:
         params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_KEYLEN, &sz);
+        ctx->key_len = -1;
         break;
     case EVP_CTRL_RAND_KEY:      /* Used by DES */
         set_params = 0;
@@ -1265,11 +1270,23 @@ int EVP_CIPHER_get_params(EVP_CIPHER *cipher, OSSL_PARAM params[])
 
 int EVP_CIPHER_CTX_set_params(EVP_CIPHER_CTX *ctx, const OSSL_PARAM params[])
 {
+    int r = 0;
+    const OSSL_PARAM *p;
+
     if (ctx->cipher != NULL && ctx->cipher->set_ctx_params != NULL) {
-        ctx->iv_len = -1;
-        return ctx->cipher->set_ctx_params(ctx->algctx, params);
+        r = ctx->cipher->set_ctx_params(ctx->algctx, params);
+        if (r > 0) {
+            p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_KEYLEN);
+            if (p != NULL && !OSSL_PARAM_get_int(p, &ctx->key_len))
+                r = 0;
+        }
+        if (r > 0) {
+            p = OSSL_PARAM_locate_const(params, OSSL_CIPHER_PARAM_IVLEN);
+            if (p != NULL && !OSSL_PARAM_get_int(p, &ctx->iv_len))
+                r = 0;
+        }
     }
-    return 0;
+    return r;
 }
 
 int EVP_CIPHER_CTX_get_params(EVP_CIPHER_CTX *ctx, OSSL_PARAM params[])
diff --git a/crypto/evp/evp_lib.c b/crypto/evp/evp_lib.c
index d88066d0a2..98bb25655d 100644
--- a/crypto/evp/evp_lib.c
+++ b/crypto/evp/evp_lib.c
@@ -652,14 +652,28 @@ int EVP_CIPHER_get_key_length(const EVP_CIPHER *cipher)
 
 int EVP_CIPHER_CTX_get_key_length(const EVP_CIPHER_CTX *ctx)
 {
-    int ok;
-    size_t v = ctx->key_len;
-    OSSL_PARAM params[2] = { OSSL_PARAM_END, OSSL_PARAM_END };
+    if (ctx->key_len <= 0 && ctx->cipher->prov != NULL) {
+        int ok;
+        OSSL_PARAM params[2] = { OSSL_PARAM_END, OSSL_PARAM_END };
+        size_t len;
 
-    params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_KEYLEN, &v);
-    ok = evp_do_ciph_ctx_getparams(ctx->cipher, ctx->algctx, params);
+        params[0] = OSSL_PARAM_construct_size_t(OSSL_CIPHER_PARAM_KEYLEN, &len);
+        ok = evp_do_ciph_ctx_getparams(ctx->cipher, ctx->algctx, params);
+        if (ok <= 0)
+            return EVP_CTRL_RET_UNSUPPORTED;
 
-    return ok != 0 ? (int)v : EVP_CTRL_RET_UNSUPPORTED;
+        /*-
+         * The if branch should never be taken since EVP_MAX_KEY_LENGTH is
+         * less than INT_MAX but best to be safe.
+         *
+         * Casting away the const is annoying but required here.  We need to
+         * cache the result for performance reasons.
+         */
+        if (!OSSL_PARAM_get_int(params, &((EVP_CIPHER_CTX *)ctx)->key_len))
+            return -1;
+        ((EVP_CIPHER_CTX *)ctx)->key_len = (int)len;
+    }
+    return ctx->key_len;
 }
 
 int EVP_CIPHER_get_nid(const EVP_CIPHER *cipher)
-- 
2.33.0

