Blame SOURCES/0003-Backport-necessary-OpenSSL-features.patch

effb77
From 683dd65f2b3dd67e64ef4d4aaadf390d08b481aa Mon Sep 17 00:00:00 2001
3f476a
From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= <jstanek@redhat.com>
effb77
Date: Wed, 13 Jul 2022 14:27:40 +0200
effb77
Subject: [PATCH] Backport necessary OpenSSL features
3f476a
MIME-Version: 1.0
3f476a
Content-Type: text/plain; charset=UTF-8
3f476a
Content-Transfer-Encoding: 8bit
3f476a
3f476a
- Remove uses of SSL_CTX_{get,set}_{min,max}_proto_version()
3f476a
3f476a
  These functions are not provided by OpenSSL v1.0,
3f476a
  and unfortunately cannot be fully re-implemented using available APIs.
3f476a
3f476a
- Implement SSL_{get,set}_{min,max}_proto_version on NodeJS level
3f476a
3f476a
  The shim layer cannot provide implementation for the above functions,
3f476a
  as there is no place to store the current boundary information.
3f476a
  Setting them can be simulated by using SSL_CTX options,
3f476a
  but getting them is tricky and starts to fail once the boundaries meet.
3f476a
3f476a
  Since Node wraps the secure context in it's own structure,
3f476a
  that structure can be extended to record the last set boundaries,
3f476a
  and then set the appropriate options on the OpenSSL structure.
3f476a
3f476a
  This patch is a first draft of this implementation.
3f476a
  The boundaries are stored directly as private members of
3f476a
  `node::crypto::SecureContext`.
3f476a
  In order to actually apply them, a new private `_SyncMinMaxProto()`
3f476a
  method has to be called.
3f476a
  This will modify the option mask of the associated SSL_CTX structure
3f476a
  to match the recorded boundaries.
3f476a
  Use with care!
3f476a
3f476a
- Provide locking mechanism for legacy OpenSSL
3f476a
3f476a
  This commit is probably a good candidate for inclusion in the shim,
3f476a
  although probably as some sort of extras – it is not trivial!
3f476a
f0ceb1
- Setup ECDHE curve negotiation
f0ceb1
f0ceb1
  - SSL_OP_SINGLE_ECDH_USE is no-op and default in OpenSSL 1.1,
f0ceb1
    but should be specified in OpenSSL 1.0
f0ceb1
  - SSL_CTX_set_ecdh_auto() is presumably the same case;
f0ceb1
    does not even exist in OpenSSL 1.1
f0ceb1
f0ceb1
  Without this setup, ECDHE curve negotiation is broken: rhbz#1910749
f0ceb1
3f476a
Signed-off-by: Jan Staněk <jstanek@redhat.com>
3f476a
---
f0ceb1
 src/node_crypto.cc | 196 +++++++++++++++++++++++++++++++++++++++------
3f476a
 src/node_crypto.h  |  14 ++++
f0ceb1
 2 files changed, 184 insertions(+), 26 deletions(-)
3f476a
3f476a
diff --git a/src/node_crypto.cc b/src/node_crypto.cc
effb77
index de8b26930b..975a148fc8 100644
3f476a
--- a/src/node_crypto.cc
3f476a
+++ b/src/node_crypto.cc
effb77
@@ -557,6 +557,11 @@ inline void SecureContext::Reset() {
3f476a
   ctx_.reset();
3f476a
   cert_.reset();
3f476a
   issuer_.reset();
3f476a
+
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+  _min_proto_version = 0;
3f476a
+  _max_proto_version = 0;
3f476a
+#endif // OPENSSL_IS_LEGACY
3f476a
 }
3f476a
 
3f476a
 SecureContext::~SecureContext() {
effb77
@@ -570,7 +575,11 @@ void SecureContext::New(const FunctionCallbackInfo<Value>& args) {
3f476a
 
3f476a
 // A maxVersion of 0 means "any", but OpenSSL may support TLS versions that
3f476a
 // Node.js doesn't, so pin the max to what we do support.
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+const int MAX_SUPPORTED_VERSION = TLS1_2_VERSION;
3f476a
+#else // OPENSSL_IS_LEGACY
3f476a
 const int MAX_SUPPORTED_VERSION = TLS1_3_VERSION;
3f476a
+#endif // OPENSSL_IS_LEGACY
3f476a
 
3f476a
 void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
3f476a
   SecureContext* sc;
effb77
@@ -625,38 +634,23 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
3f476a
       max_version = MAX_SUPPORTED_VERSION;
3f476a
       method = TLS_client_method();
3f476a
     } else if (sslmethod == "TLSv1_method") {
3f476a
-      min_version = TLS1_VERSION;
3f476a
-      max_version = TLS1_VERSION;
3f476a
+      method = TLSv1_method();
3f476a
     } else if (sslmethod == "TLSv1_server_method") {
3f476a
-      min_version = TLS1_VERSION;
3f476a
-      max_version = TLS1_VERSION;
3f476a
-      method = TLS_server_method();
3f476a
+      method = TLSv1_server_method();
3f476a
     } else if (sslmethod == "TLSv1_client_method") {
3f476a
-      min_version = TLS1_VERSION;
3f476a
-      max_version = TLS1_VERSION;
3f476a
-      method = TLS_client_method();
3f476a
+      method = TLSv1_client_method();
3f476a
     } else if (sslmethod == "TLSv1_1_method") {
3f476a
-      min_version = TLS1_1_VERSION;
3f476a
-      max_version = TLS1_1_VERSION;
3f476a
+      method = TLSv1_1_method();
3f476a
     } else if (sslmethod == "TLSv1_1_server_method") {
3f476a
-      min_version = TLS1_1_VERSION;
3f476a
-      max_version = TLS1_1_VERSION;
3f476a
-      method = TLS_server_method();
3f476a
+      method = TLSv1_1_server_method();
3f476a
     } else if (sslmethod == "TLSv1_1_client_method") {
3f476a
-      min_version = TLS1_1_VERSION;
3f476a
-      max_version = TLS1_1_VERSION;
3f476a
-      method = TLS_client_method();
3f476a
+      method = TLSv1_1_client_method();
3f476a
     } else if (sslmethod == "TLSv1_2_method") {
3f476a
-      min_version = TLS1_2_VERSION;
3f476a
-      max_version = TLS1_2_VERSION;
3f476a
+      method = TLSv1_2_method();
3f476a
     } else if (sslmethod == "TLSv1_2_server_method") {
3f476a
-      min_version = TLS1_2_VERSION;
3f476a
-      max_version = TLS1_2_VERSION;
3f476a
-      method = TLS_server_method();
3f476a
+      method = TLSv1_2_server_method();
3f476a
     } else if (sslmethod == "TLSv1_2_client_method") {
3f476a
-      min_version = TLS1_2_VERSION;
3f476a
-      max_version = TLS1_2_VERSION;
3f476a
-      method = TLS_client_method();
3f476a
+      method = TLSv1_2_client_method();
3f476a
     } else {
3f476a
       const std::string msg("Unknown method: ");
3f476a
       THROW_ERR_TLS_INVALID_PROTOCOL_METHOD(env, (msg + * sslmethod).c_str());
effb77
@@ -686,8 +680,14 @@ void SecureContext::Init(const FunctionCallbackInfo<Value>& args) {
3f476a
                                  SSL_SESS_CACHE_NO_INTERNAL |
3f476a
                                  SSL_SESS_CACHE_NO_AUTO_CLEAR);
3f476a
 
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+  sc->_min_proto_version = min_version;
3f476a
+  sc->_max_proto_version = max_version;
3f476a
+  sc->_SyncMinMaxProto();
3f476a
+#else
3f476a
   SSL_CTX_set_min_proto_version(sc->ctx_.get(), min_version);
3f476a
   SSL_CTX_set_max_proto_version(sc->ctx_.get(), max_version);
3f476a
+#endif // !OPENSSL_IS_LEGACY
3f476a
 
3f476a
   // OpenSSL 1.1.0 changed the ticket key size, but the OpenSSL 1.0.x size was
3f476a
   // exposed in the public API. To retain compatibility, install a callback
effb77
@@ -1232,6 +1232,10 @@ void SecureContext::SetECDHCurve(const FunctionCallbackInfo<Value>& args) {
f0ceb1
   THROW_AND_RETURN_IF_NOT_STRING(env, args[0], "ECDH curve name");
f0ceb1
 
f0ceb1
   node::Utf8Value curve(env->isolate(), args[0]);
f0ceb1
+#if OPENSSL_IS_LEGACY
f0ceb1
+  SSL_CTX_set_options(sc->ctx_.get(), SSL_OP_SINGLE_ECDH_USE);
f0ceb1
+  SSL_CTX_set_ecdh_auto(sc->ctx_.get(), 1);
f0ceb1
+#endif
f0ceb1
 
f0ceb1
   if (strcmp(*curve, "auto") == 0)
f0ceb1
     return;
effb77
@@ -1283,6 +1287,65 @@ void SecureContext::SetDHParam(const FunctionCallbackInfo<Value>& args) {
3f476a
     return env->ThrowTypeError("Error setting temp DH parameter");
3f476a
 }
3f476a
 
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+int SecureContext::_SyncMinMaxProto() {
3f476a
+  // Node explicitly disables SSLv2 and v3 – do not intefere with that
3f476a
+  static const auto ALL_SUPPORTED_VERSIONS =
3f476a
+    SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2;
3f476a
+
3f476a
+  auto mask = long(0);
3f476a
+
3f476a
+  // Setup minimum version
3f476a
+  switch (_min_proto_version) {
3f476a
+    default: return 0; // unsupported version
3f476a
+    case TLS1_3_VERSION:
3f476a
+      mask |= SSL_OP_NO_TLSv1_2;
3f476a
+      [[fallthrough]];
3f476a
+    case TLS1_2_VERSION:
3f476a
+      mask |= SSL_OP_NO_TLSv1_1;
3f476a
+      [[fallthrough]];
3f476a
+    case TLS1_1_VERSION:
3f476a
+      mask |= SSL_OP_NO_TLSv1;
3f476a
+      [[fallthrough]];
3f476a
+    case TLS1_VERSION:
3f476a
+      mask |= SSL_OP_NO_SSLv3;
3f476a
+      [[fallthrough]];
3f476a
+    case SSL3_VERSION:
3f476a
+      mask |= SSL_OP_NO_SSLv2;
3f476a
+      [[fallthrough]];
3f476a
+    case 0: // any supported version
3f476a
+      break;
3f476a
+  }
3f476a
+
3f476a
+  // Setup maximum version
3f476a
+  switch (_max_proto_version) {
3f476a
+    default: return 0; // unsupported version
3f476a
+    case SSL3_VERSION:
3f476a
+      mask |= SSL_OP_NO_TLSv1;
3f476a
+      [[fallthrough]];
3f476a
+    case TLS1_VERSION:
3f476a
+      mask |= SSL_OP_NO_TLSv1_1;
3f476a
+      [[fallthrough]];
3f476a
+    case TLS1_1_VERSION:
3f476a
+      mask |= SSL_OP_NO_TLSv1_2;
3f476a
+      [[fallthrough]];
3f476a
+    case TLS1_2_VERSION:
3f476a
+      // no-op, legacy OpenSSL does not know about TLSv1.3
3f476a
+      [[fallthrough]];
3f476a
+    case TLS1_3_VERSION:
3f476a
+      [[fallthrough]];
3f476a
+    case 0: // any version
3f476a
+      break;
3f476a
+  }
3f476a
+
3f476a
+  // Reset current context mask
3f476a
+  auto *raw_context = ctx_.get();
3f476a
+  SSL_CTX_clear_options(raw_context, ALL_SUPPORTED_VERSIONS);
3f476a
+  SSL_CTX_set_options(raw_context, mask);
3f476a
+
3f476a
+  return 1;
3f476a
+}
3f476a
+#endif // OPENSSL_IS_LEGACY
3f476a
 
3f476a
 void SecureContext::SetMinProto(const FunctionCallbackInfo<Value>& args) {
3f476a
   SecureContext* sc;
effb77
@@ -1293,7 +1356,12 @@ void SecureContext::SetMinProto(const FunctionCallbackInfo<Value>& args) {
3f476a
 
3f476a
   int version = args[0].As<Int32>()->Value();
3f476a
 
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+  sc->_min_proto_version = version;
3f476a
+  CHECK(sc->_SyncMinMaxProto());
3f476a
+#else  // OPENSSL_IS_LEGACY
3f476a
   CHECK(SSL_CTX_set_min_proto_version(sc->ctx_.get(), version));
3f476a
+#endif  // OPENSSL_IS_LEGACY
3f476a
 }
3f476a
 
3f476a
 
effb77
@@ -1306,7 +1374,12 @@ void SecureContext::SetMaxProto(const FunctionCallbackInfo<Value>& args) {
3f476a
 
3f476a
   int version = args[0].As<Int32>()->Value();
3f476a
 
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+  sc->_max_proto_version = version;
3f476a
+  CHECK(sc->_SyncMinMaxProto());
3f476a
+#else // OPENSSL_IS_LEGACY
3f476a
   CHECK(SSL_CTX_set_max_proto_version(sc->ctx_.get(), version));
3f476a
+#endif // OPENSSL_IS_LEGACY
3f476a
 }
3f476a
 
3f476a
 
effb77
@@ -1317,7 +1390,11 @@ void SecureContext::GetMinProto(const FunctionCallbackInfo<Value>& args) {
3f476a
   CHECK_EQ(args.Length(), 0);
3f476a
 
3f476a
   long version =  // NOLINT(runtime/int)
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+    sc->_min_proto_version;
3f476a
+#else // OPENSSL_IS_LEGACY
3f476a
     SSL_CTX_get_min_proto_version(sc->ctx_.get());
3f476a
+#endif // OPENSSL_IS_LEGACY
3f476a
   args.GetReturnValue().Set(static_cast<uint32_t>(version));
3f476a
 }
3f476a
 
effb77
@@ -1329,11 +1406,14 @@ void SecureContext::GetMaxProto(const FunctionCallbackInfo<Value>& args) {
3f476a
   CHECK_EQ(args.Length(), 0);
3f476a
 
3f476a
   long version =  // NOLINT(runtime/int)
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+    sc->_max_proto_version;
3f476a
+#else // OPENSSL_IS_LEGACY
3f476a
     SSL_CTX_get_max_proto_version(sc->ctx_.get());
3f476a
+#endif // OPENSSL_IS_LEGACY
3f476a
   args.GetReturnValue().Set(static_cast<uint32_t>(version));
3f476a
 }
3f476a
 
3f476a
-
3f476a
 void SecureContext::SetOptions(const FunctionCallbackInfo<Value>& args) {
3f476a
   SecureContext* sc;
3f476a
   ASSIGN_OR_RETURN_UNWRAP(&sc, args.Holder());
effb77
@@ -6883,8 +6963,72 @@ void TimingSafeEqual(const FunctionCallbackInfo<Value>& args) {
3f476a
       CRYPTO_memcmp(buf1.data(), buf2.data(), buf1.length()) == 0);
3f476a
 }
3f476a
 
3f476a
+
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+// Array of mutexes provided for OpenSSL internal use
3f476a
+static std::unique_ptr<Mutex[]> openssl_lock_array = nullptr;
3f476a
+
3f476a
+// OpenSSL callback – lock/unlock a mutex
3f476a
+static void openssl_lock_callback(
3f476a
+    int mode, int which, const char *file [[maybe_unused]], int line [[maybe_unused]])
3f476a
+{
3f476a
+  // Exactly one of CRYPTO_LOCK or CRYPTO_UNLOCK is set
3f476a
+  CHECK(!(mode & CRYPTO_LOCK) ^ !(mode & CRYPTO_UNLOCK));
3f476a
+  // Exactly one of CRYPTO_READ or CRYPTO_WRITE is set
3f476a
+  CHECK(!(mode & CRYPTO_READ) ^ !(mode & CRYPTO_WRITE));
3f476a
+  // FIXME: No easy way to make a array bounds check
3f476a
+
3f476a
+  auto mtx = &openssl_lock_array[which];
3f476a
+  if (mode & CRYPTO_LOCK) {
3f476a
+    mtx->Lock();
3f476a
+  } else {
3f476a
+    mtx->Unlock();
3f476a
+  }
3f476a
+}
3f476a
+
3f476a
+// OpenSSL callback - retrieve current thread ID
3f476a
+static void openssl_threadid_callback(CRYPTO_THREADID *thread_id) {
3f476a
+  static_assert(sizeof (uv_thread_t) <= sizeof (void *), "uv_thread_t does not fit in a pointer");
3f476a
+  CRYPTO_THREADID_set_pointer(thread_id, reinterpret_cast<void *>(uv_thread_self()));
3f476a
+}
3f476a
+
3f476a
+// Initialize (reset) the provided locking mechanism
3f476a
+static void openssl_reset_locking() {
3f476a
+  openssl_lock_array = std::make_unique<Mutex[]>(CRYPTO_num_locks());
3f476a
+
3f476a
+  CRYPTO_THREADID_set_callback(openssl_threadid_callback);
3f476a
+  CRYPTO_set_locking_callback(openssl_lock_callback);
3f476a
+}
3f476a
+
3f476a
+#endif // OPENSSL_IS_LEGACY
3f476a
+
3f476a
 void InitCryptoOnce() {
3f476a
-#ifndef OPENSSL_IS_BORINGSSL
3f476a
+#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_IS_LEGACY
3f476a
+  SSL_load_error_strings();
3f476a
+  OPENSSL_no_config();
3f476a
+
3f476a
+  if (!per_process::cli_options->openssl_config.empty()) {
3f476a
+    OPENSSL_load_builtin_modules();
3f476a
+#ifndef OPENSSL_NO_ENGINE
3f476a
+    ENGINE_load_builtin_engines();
3f476a
+#endif // OPENSSL_NO_ENGINE
3f476a
+    ERR_clear_error();
3f476a
+
3f476a
+    const char *conf_path = per_process::cli_options->openssl_config.c_str();
3f476a
+    CONF_modules_load_file(conf_path, nullptr, CONF_MFLAGS_DEFAULT_SECTION);
3f476a
+    auto err = ERR_get_error();
3f476a
+    if (err != 0) {
3f476a
+      fprintf(stderr, "openssl config failed: %s\n", ERR_error_string(err, nullptr));
3f476a
+      CHECK_NE(err, 0);
3f476a
+    }
3f476a
+  }
3f476a
+
3f476a
+  // Initialize OpenSSL library
3f476a
+  SSL_library_init();
3f476a
+  OpenSSL_add_all_algorithms();
3f476a
+  // Provide mutex array for OpenSSL
3f476a
+  openssl_reset_locking();
3f476a
+#else
3f476a
   OPENSSL_INIT_SETTINGS* settings = OPENSSL_INIT_new();
3f476a
 
3f476a
   // --openssl-config=...
3f476a
diff --git a/src/node_crypto.h b/src/node_crypto.h
3f476a
index dbc46fbec8..d27125042b 100644
3f476a
--- a/src/node_crypto.h
3f476a
+++ b/src/node_crypto.h
3f476a
@@ -183,6 +183,20 @@ class SecureContext final : public BaseObject {
3f476a
 
3f476a
   SecureContext(Environment* env, v8::Local<v8::Object> wrap);
3f476a
   void Reset();
3f476a
+
3f476a
+#if OPENSSL_IS_LEGACY
3f476a
+ private:
3f476a
+  // Legacy OpenSSL does not store min-max allowed versions of SSL/TLS;
3f476a
+  // instead it relies on flags and enabling/disabling specific versions.
3f476a
+  //
3f476a
+  // In order to provide enough information for *getting* the allowed
3f476a
+  // versions last set, record the boundaries on our secure context.
3f476a
+  int _min_proto_version = 0;
3f476a
+  int _max_proto_version = 0;
3f476a
+
3f476a
+  /** Sync _{min,max}_proto_version to wrapped context. */
3f476a
+  int _SyncMinMaxProto();
3f476a
+#endif // OPENSSL_IS_LEGACY
3f476a
 };
3f476a
 
3f476a
 // SSLWrap implicitly depends on the inheriting class' handle having an
3f476a
-- 
effb77
2.36.1
3f476a