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

f0ceb1
From 68c182525ba6d8289fcc58536373c51f9d20f07e Mon Sep 17 00:00:00 2001
3f476a
From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= <jstanek@redhat.com>
f0ceb1
Date: Wed, 7 Jul 2021 13:37:47 +0200
3f476a
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
f0ceb1
index c9de7d8a19..31e8276e97 100644
3f476a
--- a/src/node_crypto.cc
3f476a
+++ b/src/node_crypto.cc
f0ceb1
@@ -541,6 +541,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() {
f0ceb1
@@ -554,7 +559,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;
f0ceb1
@@ -609,38 +618,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());
f0ceb1
@@ -670,8 +664,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
f0ceb1
@@ -1216,6 +1216,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;
f0ceb1
@@ -1267,6 +1271,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;
f0ceb1
@@ -1277,7 +1340,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
 
f0ceb1
@@ -1290,7 +1358,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
 
f0ceb1
@@ -1301,7 +1374,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
 
f0ceb1
@@ -1313,11 +1390,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());
f0ceb1
@@ -6873,8 +6953,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
-- 
f0ceb1
2.31.1
3f476a