This is an automated email from the ASF dual-hosted git repository.

JosiahWI pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new ec93038b7e Support OpenSSL 3.0 APIs for Diffie-Hellman (#13349)
ec93038b7e is described below

commit ec93038b7e98d7881f111cb5fc45e71529f73e0a
Author: JosiahWI <[email protected]>
AuthorDate: Tue Jul 28 19:50:34 2026 -0500

    Support OpenSSL 3.0 APIs for Diffie-Hellman (#13349)
    
    * Add unit tests for SSL Diffie-Hellman key configuration
    
    * Support OpenSSL 3.0 APIs for Diffie-Hellman
    
    * Move DH keygen to SSLKeyUtils.{h,cc}
---
 src/iocore/net/CMakeLists.txt                 |   4 +
 src/iocore/net/P_SSLUtils.h                   |  26 ++++
 src/iocore/net/SSLKeyUtils.cc                 | 190 ++++++++++++++++++++++++++
 src/iocore/net/SSLKeyUtils.h                  |  42 ++++++
 src/iocore/net/SSLUtils.cc                    |  76 ++---------
 src/iocore/net/unit_tests/test_SSLDHParams.cc | 186 +++++++++++++++++++++++++
 6 files changed, 457 insertions(+), 67 deletions(-)

diff --git a/src/iocore/net/CMakeLists.txt b/src/iocore/net/CMakeLists.txt
index 901a830228..d1d5875ab4 100644
--- a/src/iocore/net/CMakeLists.txt
+++ b/src/iocore/net/CMakeLists.txt
@@ -52,6 +52,7 @@ add_library(
   SSLSessionCache.cc
   SSLSessionTicket.cc
   SSLUtils.cc
+  SSLKeyUtils.cc
   OCSPStapling.cc
   TLSBasicSupport.cc
   TLSEventSupport.cc
@@ -147,6 +148,9 @@ if(BUILD_TESTING)
     unit_tests/unit_test_main.cc
     unit_tests/benchmark_TLSCertCompression.cc
   )
+  if(SSLLIB_IS_OPENSSL3)
+    target_sources(test_net PRIVATE unit_tests/test_SSLDHParams.cc)
+  endif()
   # Use link groups to solve circular dependency
   set(LINK_GROUP_LIBS
       ts::logging
diff --git a/src/iocore/net/P_SSLUtils.h b/src/iocore/net/P_SSLUtils.h
index 8e2cbf5788..af5728e486 100644
--- a/src/iocore/net/P_SSLUtils.h
+++ b/src/iocore/net/P_SSLUtils.h
@@ -25,6 +25,10 @@
 #include "iocore/net/SSLTypes.h"
 #include "tscore/Diags.h"
 
+#ifdef OPENSSL_IS_OPENSSL3
+#include <openssl/decoder.h>
+#include <openssl/evp.h>
+#endif
 #define OPENSSL_THREAD_DEFINES
 #if __has_include(<openssl/opensslconf.h>)
 #include <openssl/opensslconf.h>
@@ -110,6 +114,24 @@ namespace detail
     }
   };
 
+#ifdef OPENSSL_IS_OPENSSL3
+  struct PKEYCTXDeleter {
+    void
+    operator()(EVP_PKEY_CTX *pctx)
+    {
+      EVP_PKEY_CTX_free(pctx);
+    }
+  };
+
+  struct DecoderCTXDeleter {
+    void
+    operator()(OSSL_DECODER_CTX *dctx)
+    {
+      OSSL_DECODER_CTX_free(dctx);
+    }
+  };
+#endif
+
 } // namespace detail
 } // namespace ssl
 
@@ -134,3 +156,7 @@ private:
 
 using scoped_X509 = std::unique_ptr<X509, ssl::detail::X509Deleter>;
 using scoped_BIO  = std::unique_ptr<BIO, ssl::detail::BIODeleter>;
+#ifdef OPENSSL_IS_OPENSSL3
+using scoped_PKEY_CTX    = std::unique_ptr<EVP_PKEY_CTX, 
ssl::detail::PKEYCTXDeleter>;
+using scoped_Decoder_CTX = std::unique_ptr<OSSL_DECODER_CTX, 
ssl::detail::DecoderCTXDeleter>;
+#endif
diff --git a/src/iocore/net/SSLKeyUtils.cc b/src/iocore/net/SSLKeyUtils.cc
new file mode 100644
index 0000000000..4bf14c5f47
--- /dev/null
+++ b/src/iocore/net/SSLKeyUtils.cc
@@ -0,0 +1,190 @@
+/** @file
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "SSLKeyUtils.h"
+#include "P_SSLUtils.h"
+
+#include <tscore/Diags.h>
+#ifdef OPENSSL_IS_OPENSSL3
+#include <tscore/ink_assert.h>
+#else
+#include <tscore/ink_config.h>
+#endif
+
+#ifdef OPENSSL_IS_OPENSSL3
+#include <openssl/evp.h>
+#include <openssl/decoder.h>
+#include <openssl/params.h>
+#include <openssl/ssl.h>
+#else
+#include <openssl/bio.h>
+#include <openssl/bn.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#endif
+
+#ifdef OPENSSL_IS_OPENSSL3
+
+EVP_PKEY *
+gen_dh_2048_256_pkey()
+{
+  scoped_PKEY_CTX pctx{EVP_PKEY_CTX_new_from_name(NULL, "DH", NULL)};
+  if (!pctx) {
+    Error("failed to create OpenSSL pkey context");
+    return nullptr;
+  }
+
+  if (EVP_PKEY_keygen_init(pctx.get()) <= 0) {
+    Error("failed to initialize OpenSSL keygen");
+    return nullptr;
+  }
+
+  char             prime_group[]{"dh_2048_256"};
+  OSSL_PARAM const params[]{OSSL_PARAM_utf8_string("group", prime_group, 0), 
OSSL_PARAM_END};
+
+  if (!EVP_PKEY_CTX_set_params(pctx.get(), params)) {
+    Error("SSL dhparams source returned invalid parameters");
+    return nullptr;
+  }
+
+  EVP_PKEY *pkey{};
+  EVP_PKEY_generate(pctx.get(), &pkey);
+
+  return pkey;
+}
+
+EVP_PKEY *
+load_dhparams_file(char const *dhparams_file)
+{
+  EVP_PKEY          *pkey{};
+  scoped_Decoder_CTX dctx{OSSL_DECODER_CTX_new_for_pkey(&pkey, "PEM", NULL, 
"DH", OSSL_KEYMGMT_SELECT_ALL_PARAMETERS, NULL, NULL)};
+  if (!dctx) {
+    Error("failed to create OpenSSL decoder context");
+    return nullptr;
+  }
+
+  ink_assert(OSSL_DECODER_CTX_get_num_decoders(dctx.get()) > 0);
+  scoped_BIO bio{BIO_new_file(dhparams_file, "r")};
+  if (!bio) {
+    Error("failed to open parameters file");
+    return nullptr;
+  }
+  if (!OSSL_DECODER_from_bio(dctx.get(), bio.get())) {
+    Error("SSL dhparams source returned invalid parameters");
+    return nullptr;
+  }
+
+  return pkey;
+}
+
+bool
+set_ctx_dh(SSL_CTX *ctx, dh_key_t *pkey)
+{
+  bool result{SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE) && 
SSL_CTX_set0_tmp_dh_pkey(ctx, pkey)};
+  if (!result) {
+    EVP_PKEY_free(pkey);
+  }
+  return result;
+}
+
+#else
+
+DH *
+load_dhparams_file(char const *dhparams_file)
+{
+  scoped_BIO bio(BIO_new_file(dhparams_file, "r"));
+  DH        *dh{PEM_read_bio_DHparams(bio.get(), nullptr, nullptr, nullptr)};
+  if (!dh) {
+    Error("SSL dhparams source returned invalid parameters");
+    return nullptr;
+  }
+
+  return dh;
+}
+
+#if TS_USE_GET_DH_2048_256
+DH *
+gen_dh_2048_256_pkey()
+{
+  return DH_get_2048_256();
+}
+#else
+DH *
+gen_dh_2048_256_pkey()
+{
+  /* Build 2048-bit MODP Group with 256-bit Prime Order Subgroup from RFC 5114 
*/
+  static const unsigned char dh2048_p[] = {
+    0x87, 0xA8, 0xE6, 0x1D, 0xB4, 0xB6, 0x66, 0x3C, 0xFF, 0xBB, 0xD1, 0x9C, 
0x65, 0x19, 0x59, 0x99, 0x8C, 0xEE, 0xF6, 0x08,
+    0x66, 0x0D, 0xD0, 0xF2, 0x5D, 0x2C, 0xEE, 0xD4, 0x43, 0x5E, 0x3B, 0x00, 
0xE0, 0x0D, 0xF8, 0xF1, 0xD6, 0x19, 0x57, 0xD4,
+    0xFA, 0xF7, 0xDF, 0x45, 0x61, 0xB2, 0xAA, 0x30, 0x16, 0xC3, 0xD9, 0x11, 
0x34, 0x09, 0x6F, 0xAA, 0x3B, 0xF4, 0x29, 0x6D,
+    0x83, 0x0E, 0x9A, 0x7C, 0x20, 0x9E, 0x0C, 0x64, 0x97, 0x51, 0x7A, 0xBD, 
0x5A, 0x8A, 0x9D, 0x30, 0x6B, 0xCF, 0x67, 0xED,
+    0x91, 0xF9, 0xE6, 0x72, 0x5B, 0x47, 0x58, 0xC0, 0x22, 0xE0, 0xB1, 0xEF, 
0x42, 0x75, 0xBF, 0x7B, 0x6C, 0x5B, 0xFC, 0x11,
+    0xD4, 0x5F, 0x90, 0x88, 0xB9, 0x41, 0xF5, 0x4E, 0xB1, 0xE5, 0x9B, 0xB8, 
0xBC, 0x39, 0xA0, 0xBF, 0x12, 0x30, 0x7F, 0x5C,
+    0x4F, 0xDB, 0x70, 0xC5, 0x81, 0xB2, 0x3F, 0x76, 0xB6, 0x3A, 0xCA, 0xE1, 
0xCA, 0xA6, 0xB7, 0x90, 0x2D, 0x52, 0x52, 0x67,
+    0x35, 0x48, 0x8A, 0x0E, 0xF1, 0x3C, 0x6D, 0x9A, 0x51, 0xBF, 0xA4, 0xAB, 
0x3A, 0xD8, 0x34, 0x77, 0x96, 0x52, 0x4D, 0x8E,
+    0xF6, 0xA1, 0x67, 0xB5, 0xA4, 0x18, 0x25, 0xD9, 0x67, 0xE1, 0x44, 0xE5, 
0x14, 0x05, 0x64, 0x25, 0x1C, 0xCA, 0xCB, 0x83,
+    0xE6, 0xB4, 0x86, 0xF6, 0xB3, 0xCA, 0x3F, 0x79, 0x71, 0x50, 0x60, 0x26, 
0xC0, 0xB8, 0x57, 0xF6, 0x89, 0x96, 0x28, 0x56,
+    0xDE, 0xD4, 0x01, 0x0A, 0xBD, 0x0B, 0xE6, 0x21, 0xC3, 0xA3, 0x96, 0x0A, 
0x54, 0xE7, 0x10, 0xC3, 0x75, 0xF2, 0x63, 0x75,
+    0xD7, 0x01, 0x41, 0x03, 0xA4, 0xB5, 0x43, 0x30, 0xC1, 0x98, 0xAF, 0x12, 
0x61, 0x16, 0xD2, 0x27, 0x6E, 0x11, 0x71, 0x5F,
+    0x69, 0x38, 0x77, 0xFA, 0xD7, 0xEF, 0x09, 0xCA, 0xDB, 0x09, 0x4A, 0xE9, 
0x1E, 0x1A, 0x15, 0x97};
+  static const unsigned char dh2048_g[] = {
+    0x3F, 0xB3, 0x2C, 0x9B, 0x73, 0x13, 0x4D, 0x0B, 0x2E, 0x77, 0x50, 0x66, 
0x60, 0xED, 0xBD, 0x48, 0x4C, 0xA7, 0xB1, 0x8F,
+    0x21, 0xEF, 0x20, 0x54, 0x07, 0xF4, 0x79, 0x3A, 0x1A, 0x0B, 0xA1, 0x25, 
0x10, 0xDB, 0xC1, 0x50, 0x77, 0xBE, 0x46, 0x3F,
+    0xFF, 0x4F, 0xED, 0x4A, 0xAC, 0x0B, 0xB5, 0x55, 0xBE, 0x3A, 0x6C, 0x1B, 
0x0C, 0x6B, 0x47, 0xB1, 0xBC, 0x37, 0x73, 0xBF,
+    0x7E, 0x8C, 0x6F, 0x62, 0x90, 0x12, 0x28, 0xF8, 0xC2, 0x8C, 0xBB, 0x18, 
0xA5, 0x5A, 0xE3, 0x13, 0x41, 0x00, 0x0A, 0x65,
+    0x01, 0x96, 0xF9, 0x31, 0xC7, 0x7A, 0x57, 0xF2, 0xDD, 0xF4, 0x63, 0xE5, 
0xE9, 0xEC, 0x14, 0x4B, 0x77, 0x7D, 0xE6, 0x2A,
+    0xAA, 0xB8, 0xA8, 0x62, 0x8A, 0xC3, 0x76, 0xD2, 0x82, 0xD6, 0xED, 0x38, 
0x64, 0xE6, 0x79, 0x82, 0x42, 0x8E, 0xBC, 0x83,
+    0x1D, 0x14, 0x34, 0x8F, 0x6F, 0x2F, 0x91, 0x93, 0xB5, 0x04, 0x5A, 0xF2, 
0x76, 0x71, 0x64, 0xE1, 0xDF, 0xC9, 0x67, 0xC1,
+    0xFB, 0x3F, 0x2E, 0x55, 0xA4, 0xBD, 0x1B, 0xFF, 0xE8, 0x3B, 0x9C, 0x80, 
0xD0, 0x52, 0xB9, 0x85, 0xD1, 0x82, 0xEA, 0x0A,
+    0xDB, 0x2A, 0x3B, 0x73, 0x13, 0xD3, 0xFE, 0x14, 0xC8, 0x48, 0x4B, 0x1E, 
0x05, 0x25, 0x88, 0xB9, 0xB7, 0xD2, 0xBB, 0xD2,
+    0xDF, 0x01, 0x61, 0x99, 0xEC, 0xD0, 0x6E, 0x15, 0x57, 0xCD, 0x09, 0x15, 
0xB3, 0x35, 0x3B, 0xBB, 0x64, 0xE0, 0xEC, 0x37,
+    0x7F, 0xD0, 0x28, 0x37, 0x0D, 0xF9, 0x2B, 0x52, 0xC7, 0x89, 0x14, 0x28, 
0xCD, 0xC6, 0x7E, 0xB6, 0x18, 0x4B, 0x52, 0x3D,
+    0x1D, 0xB2, 0x46, 0xC3, 0x2F, 0x63, 0x07, 0x84, 0x90, 0xF0, 0x0E, 0xF8, 
0xD6, 0x47, 0xD1, 0x48, 0xD4, 0x79, 0x54, 0x51,
+    0x5E, 0x23, 0x27, 0xCF, 0xEF, 0x98, 0xC5, 0x82, 0x66, 0x4B, 0x4C, 0x0F, 
0x6C, 0xC4, 0x16, 0x59};
+  DH     *dh;
+  BIGNUM *p;
+  BIGNUM *g;
+
+  if ((dh = DH_new()) == nullptr) {
+    return nullptr;
+  }
+  p = BN_bin2bn(dh2048_p, sizeof(dh2048_p), nullptr);
+  g = BN_bin2bn(dh2048_g, sizeof(dh2048_g), nullptr);
+  if (p == nullptr || g == nullptr) {
+    DH_free(dh);
+    BN_free(p);
+    BN_free(g);
+    return nullptr;
+  }
+  DH_set0_pqg(dh, p, nullptr, g);
+  return (dh);
+}
+#endif // TS_USE_GET_DH_2048_256
+
+bool
+set_ctx_dh(SSL_CTX *ctx, dh_key_t *pkey)
+{
+  bool result{SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE) && 
SSL_CTX_set_tmp_dh(ctx, pkey)};
+  DH_free(pkey);
+  return result;
+}
+
+#endif // OPENSSL_IS_OPENSSL3
diff --git a/src/iocore/net/SSLKeyUtils.h b/src/iocore/net/SSLKeyUtils.h
new file mode 100644
index 0000000000..64bea23e9e
--- /dev/null
+++ b/src/iocore/net/SSLKeyUtils.h
@@ -0,0 +1,42 @@
+/** @file
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#if OPENSSL_IS_OPENSSL3
+#include <openssl/evp.h>
+#else
+#include <openssl/dh.h>
+#endif
+#include <openssl/ssl.h>
+
+#ifdef OPENSSL_IS_OPENSSL3
+using dh_key_t = EVP_PKEY;
+#else
+using dh_key_t = DH;
+#endif
+
+// Both gen_dh_2048_256_pkey and load_dhparams_file return owning pointers.
+dh_key_t *gen_dh_2048_256_pkey();
+dh_key_t *load_dhparams_file(char const *dhparams_file);
+
+// Takes ownership of pkey.
+bool set_ctx_dh(SSL_CTX *ctx, dh_key_t *pkey);
diff --git a/src/iocore/net/SSLUtils.cc b/src/iocore/net/SSLUtils.cc
index e09df82dfe..9c865bbbf8 100644
--- a/src/iocore/net/SSLUtils.cc
+++ b/src/iocore/net/SSLUtils.cc
@@ -26,6 +26,7 @@
 #include "P_SSLConfig.h"
 #include "P_SSLNetVConnection.h"
 #include "P_TLSKeyLogger.h"
+#include "SSLKeyUtils.h"
 #include "SSLStats.h"
 #include "SSLSessionCache.h"
 #include "SSLSessionTicket.h"
@@ -42,6 +43,7 @@
 #include "tscore/ink_config.h"
 #include "tscore/SimpleTokenizer.h"
 #include "tscore/Layout.h"
+#include "tscore/ink_assert.h"
 #include "tscore/ink_cap.h"
 #include "tscore/ink_mutex.h"
 #include "tscore/Filenames.h"
@@ -52,8 +54,10 @@
 #include "swoc/Errata.h"
 #include <openssl/asn1.h>
 #include <openssl/bio.h>
-#include <openssl/bn.h>
 #include <openssl/conf.h>
+#ifdef OPENSSL_IS_OPENSSL3
+#include <openssl/evp.h>
+#endif
 #include <openssl/dh.h>
 #include <openssl/ec.h>
 #if HAVE_ENGINE_LOAD_DYNAMIC
@@ -383,59 +387,6 @@ ssl_alpn_select_callback(SSL *ssl, const unsigned char 
**out, unsigned char *out
   return SSL_TLSEXT_ERR_NOACK;
 }
 
-#if TS_USE_GET_DH_2048_256 == 0
-/* Build 2048-bit MODP Group with 256-bit Prime Order Subgroup from RFC 5114 */
-static DH *
-DH_get_2048_256()
-{
-  static const unsigned char dh2048_p[] = {
-    0x87, 0xA8, 0xE6, 0x1D, 0xB4, 0xB6, 0x66, 0x3C, 0xFF, 0xBB, 0xD1, 0x9C, 
0x65, 0x19, 0x59, 0x99, 0x8C, 0xEE, 0xF6, 0x08,
-    0x66, 0x0D, 0xD0, 0xF2, 0x5D, 0x2C, 0xEE, 0xD4, 0x43, 0x5E, 0x3B, 0x00, 
0xE0, 0x0D, 0xF8, 0xF1, 0xD6, 0x19, 0x57, 0xD4,
-    0xFA, 0xF7, 0xDF, 0x45, 0x61, 0xB2, 0xAA, 0x30, 0x16, 0xC3, 0xD9, 0x11, 
0x34, 0x09, 0x6F, 0xAA, 0x3B, 0xF4, 0x29, 0x6D,
-    0x83, 0x0E, 0x9A, 0x7C, 0x20, 0x9E, 0x0C, 0x64, 0x97, 0x51, 0x7A, 0xBD, 
0x5A, 0x8A, 0x9D, 0x30, 0x6B, 0xCF, 0x67, 0xED,
-    0x91, 0xF9, 0xE6, 0x72, 0x5B, 0x47, 0x58, 0xC0, 0x22, 0xE0, 0xB1, 0xEF, 
0x42, 0x75, 0xBF, 0x7B, 0x6C, 0x5B, 0xFC, 0x11,
-    0xD4, 0x5F, 0x90, 0x88, 0xB9, 0x41, 0xF5, 0x4E, 0xB1, 0xE5, 0x9B, 0xB8, 
0xBC, 0x39, 0xA0, 0xBF, 0x12, 0x30, 0x7F, 0x5C,
-    0x4F, 0xDB, 0x70, 0xC5, 0x81, 0xB2, 0x3F, 0x76, 0xB6, 0x3A, 0xCA, 0xE1, 
0xCA, 0xA6, 0xB7, 0x90, 0x2D, 0x52, 0x52, 0x67,
-    0x35, 0x48, 0x8A, 0x0E, 0xF1, 0x3C, 0x6D, 0x9A, 0x51, 0xBF, 0xA4, 0xAB, 
0x3A, 0xD8, 0x34, 0x77, 0x96, 0x52, 0x4D, 0x8E,
-    0xF6, 0xA1, 0x67, 0xB5, 0xA4, 0x18, 0x25, 0xD9, 0x67, 0xE1, 0x44, 0xE5, 
0x14, 0x05, 0x64, 0x25, 0x1C, 0xCA, 0xCB, 0x83,
-    0xE6, 0xB4, 0x86, 0xF6, 0xB3, 0xCA, 0x3F, 0x79, 0x71, 0x50, 0x60, 0x26, 
0xC0, 0xB8, 0x57, 0xF6, 0x89, 0x96, 0x28, 0x56,
-    0xDE, 0xD4, 0x01, 0x0A, 0xBD, 0x0B, 0xE6, 0x21, 0xC3, 0xA3, 0x96, 0x0A, 
0x54, 0xE7, 0x10, 0xC3, 0x75, 0xF2, 0x63, 0x75,
-    0xD7, 0x01, 0x41, 0x03, 0xA4, 0xB5, 0x43, 0x30, 0xC1, 0x98, 0xAF, 0x12, 
0x61, 0x16, 0xD2, 0x27, 0x6E, 0x11, 0x71, 0x5F,
-    0x69, 0x38, 0x77, 0xFA, 0xD7, 0xEF, 0x09, 0xCA, 0xDB, 0x09, 0x4A, 0xE9, 
0x1E, 0x1A, 0x15, 0x97};
-  static const unsigned char dh2048_g[] = {
-    0x3F, 0xB3, 0x2C, 0x9B, 0x73, 0x13, 0x4D, 0x0B, 0x2E, 0x77, 0x50, 0x66, 
0x60, 0xED, 0xBD, 0x48, 0x4C, 0xA7, 0xB1, 0x8F,
-    0x21, 0xEF, 0x20, 0x54, 0x07, 0xF4, 0x79, 0x3A, 0x1A, 0x0B, 0xA1, 0x25, 
0x10, 0xDB, 0xC1, 0x50, 0x77, 0xBE, 0x46, 0x3F,
-    0xFF, 0x4F, 0xED, 0x4A, 0xAC, 0x0B, 0xB5, 0x55, 0xBE, 0x3A, 0x6C, 0x1B, 
0x0C, 0x6B, 0x47, 0xB1, 0xBC, 0x37, 0x73, 0xBF,
-    0x7E, 0x8C, 0x6F, 0x62, 0x90, 0x12, 0x28, 0xF8, 0xC2, 0x8C, 0xBB, 0x18, 
0xA5, 0x5A, 0xE3, 0x13, 0x41, 0x00, 0x0A, 0x65,
-    0x01, 0x96, 0xF9, 0x31, 0xC7, 0x7A, 0x57, 0xF2, 0xDD, 0xF4, 0x63, 0xE5, 
0xE9, 0xEC, 0x14, 0x4B, 0x77, 0x7D, 0xE6, 0x2A,
-    0xAA, 0xB8, 0xA8, 0x62, 0x8A, 0xC3, 0x76, 0xD2, 0x82, 0xD6, 0xED, 0x38, 
0x64, 0xE6, 0x79, 0x82, 0x42, 0x8E, 0xBC, 0x83,
-    0x1D, 0x14, 0x34, 0x8F, 0x6F, 0x2F, 0x91, 0x93, 0xB5, 0x04, 0x5A, 0xF2, 
0x76, 0x71, 0x64, 0xE1, 0xDF, 0xC9, 0x67, 0xC1,
-    0xFB, 0x3F, 0x2E, 0x55, 0xA4, 0xBD, 0x1B, 0xFF, 0xE8, 0x3B, 0x9C, 0x80, 
0xD0, 0x52, 0xB9, 0x85, 0xD1, 0x82, 0xEA, 0x0A,
-    0xDB, 0x2A, 0x3B, 0x73, 0x13, 0xD3, 0xFE, 0x14, 0xC8, 0x48, 0x4B, 0x1E, 
0x05, 0x25, 0x88, 0xB9, 0xB7, 0xD2, 0xBB, 0xD2,
-    0xDF, 0x01, 0x61, 0x99, 0xEC, 0xD0, 0x6E, 0x15, 0x57, 0xCD, 0x09, 0x15, 
0xB3, 0x35, 0x3B, 0xBB, 0x64, 0xE0, 0xEC, 0x37,
-    0x7F, 0xD0, 0x28, 0x37, 0x0D, 0xF9, 0x2B, 0x52, 0xC7, 0x89, 0x14, 0x28, 
0xCD, 0xC6, 0x7E, 0xB6, 0x18, 0x4B, 0x52, 0x3D,
-    0x1D, 0xB2, 0x46, 0xC3, 0x2F, 0x63, 0x07, 0x84, 0x90, 0xF0, 0x0E, 0xF8, 
0xD6, 0x47, 0xD1, 0x48, 0xD4, 0x79, 0x54, 0x51,
-    0x5E, 0x23, 0x27, 0xCF, 0xEF, 0x98, 0xC5, 0x82, 0x66, 0x4B, 0x4C, 0x0F, 
0x6C, 0xC4, 0x16, 0x59};
-  DH     *dh;
-  BIGNUM *p;
-  BIGNUM *g;
-
-  if ((dh = DH_new()) == nullptr) {
-    return nullptr;
-  }
-  p = BN_bin2bn(dh2048_p, sizeof(dh2048_p), nullptr);
-  g = BN_bin2bn(dh2048_g, sizeof(dh2048_g), nullptr);
-  if (p == nullptr || g == nullptr) {
-    DH_free(dh);
-    BN_free(p);
-    BN_free(g);
-    return nullptr;
-  }
-  DH_set0_pqg(dh, p, nullptr, g);
-  return (dh);
-}
-#endif
-
 bool
 SSLMultiCertConfigLoader::_enable_cert_compression(SSL_CTX *ctx)
 {
@@ -490,28 +441,19 @@ 
SSLMultiCertConfigLoader::_enable_early_data([[maybe_unused]] SSL_CTX *ctx)
 static SSL_CTX *
 ssl_context_enable_dhe(const char *dhparams_file, SSL_CTX *ctx)
 {
-  DH *server_dh;
+  dh_key_t *pkey{};
 
   if (dhparams_file) {
-    scoped_BIO bio(BIO_new_file(dhparams_file, "r"));
-    server_dh = PEM_read_bio_DHparams(bio.get(), nullptr, nullptr, nullptr);
+    pkey = load_dhparams_file(dhparams_file);
   } else {
-    server_dh = DH_get_2048_256();
-  }
-
-  if (!server_dh) {
-    Error("SSL dhparams source returned invalid parameters");
-    return nullptr;
+    pkey = gen_dh_2048_256_pkey();
   }
 
-  if (!SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE) || 
!SSL_CTX_set_tmp_dh(ctx, server_dh)) {
-    DH_free(server_dh);
+  if (!pkey || !set_ctx_dh(ctx, pkey)) {
     Error("failed to configure SSL DH");
     return nullptr;
   }
 
-  DH_free(server_dh);
-
   return ctx;
 }
 
diff --git a/src/iocore/net/unit_tests/test_SSLDHParams.cc 
b/src/iocore/net/unit_tests/test_SSLDHParams.cc
new file mode 100644
index 0000000000..6a32a7458b
--- /dev/null
+++ b/src/iocore/net/unit_tests/test_SSLDHParams.cc
@@ -0,0 +1,186 @@
+/** @file
+
+  Catch based unit tests for the DH-parameter handling behavior of
+  SSLMultiCertConfigLoader::init_server_ssl_ctx, which is the inknet
+  public boundary that transitively invokes ssl_context_enable_dhe
+  and (when a file is configured) load_dhparams_file.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include <iocore/net/SSLMultiCertConfigLoader.h>
+#include "../P_SSLCertLookup.h"
+#include "../P_SSLConfig.h"
+#include "../P_SSLUtils.h"
+
+#include <tscore/ink_memory.h>
+#include <tscore/ink_platform.h>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <openssl/bio.h>
+#include <openssl/core_names.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+
+#include <cstdio>
+#include <string>
+
+namespace
+{
+
+std::string
+make_valid_dh_pem()
+{
+  EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(nullptr, "DH", nullptr);
+  REQUIRE(pctx != nullptr);
+  REQUIRE(EVP_PKEY_paramgen_init(pctx) > 0);
+  char             prime_group[]{"dh_2048_256"};
+  OSSL_PARAM const params[2] = {
+    OSSL_PARAM_construct_utf8_string("group", prime_group, 0),
+    OSSL_PARAM_construct_end(),
+  };
+  REQUIRE(EVP_PKEY_CTX_set_params(pctx, params) > 0);
+  EVP_PKEY *pkey = nullptr;
+  REQUIRE(EVP_PKEY_generate(pctx, &pkey) > 0);
+
+  BIO *bio = BIO_new(BIO_s_mem());
+  REQUIRE(PEM_write_bio_Parameters(bio, pkey) == 1);
+  BUF_MEM *bm = nullptr;
+  BIO_get_mem_ptr(bio, &bm);
+  std::string out{bm->data, bm->length};
+  BIO_free(bio);
+  EVP_PKEY_free(pkey);
+  EVP_PKEY_CTX_free(pctx);
+  return out;
+}
+
+std::string
+make_rsa_pem()
+{
+  EVP_PKEY *pkey = EVP_RSA_gen(2048);
+  REQUIRE(pkey != nullptr);
+  BIO *bio = BIO_new(BIO_s_mem());
+  REQUIRE(PEM_write_bio_PrivateKey(bio, pkey, nullptr, nullptr, 0, nullptr, 
nullptr) == 1);
+  BUF_MEM *bm = nullptr;
+  BIO_get_mem_ptr(bio, &bm);
+  std::string out{bm->data, bm->length};
+  BIO_free(bio);
+  EVP_PKEY_free(pkey);
+  return out;
+}
+
+class TempFile
+{
+public:
+  explicit TempFile(std::string const &contents)
+  {
+    char tmpl[] = "/tmp/ats_dhparams_XXXXXX";
+    int  fd     = mkstemp(tmpl);
+    REQUIRE(fd != -1);
+    this->path = tmpl;
+    if (!contents.empty()) {
+      REQUIRE(write(fd, contents.data(), contents.size()) == 
static_cast<ssize_t>(contents.size()));
+    }
+    close(fd);
+  }
+  TempFile(TempFile const &)            = delete;
+  TempFile(TempFile &&)                 = delete;
+  TempFile &operator=(TempFile const &) = delete;
+  TempFile &operator=(TempFile &&)      = delete;
+  ~TempFile() { unlink(this->path.c_str()); }
+
+  char const *
+  get_path() const
+  {
+    return this->path.c_str();
+  }
+
+private:
+  std::string path;
+};
+
+// Drives ssl_context_enable_dhe via init_server_ssl_ctx, holding every
+// non-DHE input fixed and varying only dhparamsFile. An empty CertLoadData
+// selects the "default generated ctx" branch which still traverses
+// ssl_context_enable_dhe but skips cert/key loading entirely, so a non-empty
+// returned vector with a non-null SSL_CTX is observable iff DHE configuration
+// succeeded.
+bool
+init_with_dhparams(char const *dhparams_file)
+{
+  SSLConfigParams params;
+  params.dhparamsFile = dhparams_file ? ats_strdup(dhparams_file) : nullptr;
+
+  SSLMultiCertConfigLoader               loader{&params};
+  SSLMultiCertConfigLoader::CertLoadData data;
+  auto                                   contexts = 
loader.init_server_ssl_ctx(data, nullptr);
+
+  bool ok = !contexts.empty() && contexts.front().ctx != nullptr;
+  for (auto const &lc : contexts) {
+    SSL_CTX_free(lc.ctx);
+  }
+  return ok;
+}
+
+} // namespace
+
+TEST_CASE("ssl_context_enable_dhe: nullptr dhparams file falls back to 
built-in DH parameters")
+{
+  CHECK(init_with_dhparams(nullptr));
+}
+
+TEST_CASE("ssl_context_enable_dhe: valid dh_2048_256 DH PEM file is accepted")
+{
+  TempFile dh{make_valid_dh_pem()};
+  CHECK(init_with_dhparams(dh.get_path()));
+}
+
+TEST_CASE("ssl_context_enable_dhe: nonexistent dhparams path is rejected")
+{
+  CHECK_FALSE(init_with_dhparams("/tmp/ats_dhparams_does_not_exist_zzz_xyz"));
+}
+
+TEST_CASE("ssl_context_enable_dhe: empty dhparams file is rejected")
+{
+  TempFile empty{""};
+  CHECK_FALSE(init_with_dhparams(empty.get_path()));
+}
+
+TEST_CASE("ssl_context_enable_dhe: non-PEM garbage in dhparams file is 
rejected")
+{
+  TempFile garbage{"this is definitely not a PEM-encoded DH parameter 
block\n"};
+  CHECK_FALSE(init_with_dhparams(garbage.get_path()));
+}
+
+TEST_CASE("ssl_context_enable_dhe: PEM of wrong key type (RSA) is rejected by 
DH-only decoder")
+{
+  TempFile rsa{make_rsa_pem()};
+  CHECK_FALSE(init_with_dhparams(rsa.get_path()));
+}
+
+TEST_CASE("ssl_context_enable_dhe: truncated DH PEM (missing END marker) is 
rejected")
+{
+  std::string pem = make_valid_dh_pem();
+  auto        end = pem.find("-----END");
+  REQUIRE(end != std::string::npos);
+  TempFile truncated{pem.substr(0, end)};
+  CHECK_FALSE(init_with_dhparams(truncated.get_path()));
+}

Reply via email to