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

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