current commit
261106.4 · crypto: prevent stale OpenSSL errors
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "crypto/crypto_tls.h"
#include <cstdio>
#include "async_wrap-inl.h"
#include "crypto/crypto_bio.h"
#include "crypto/crypto_client_hello.h"
#include "crypto/crypto_common.h"
#include "crypto/crypto_context.h"
#include "crypto/crypto_util.h"
#include "debug_utils-inl.h"
#include "memory_tracker-inl.h"
#include "node_buffer.h"
#include "node_errors.h"
#include "stream_base-inl.h"
#include "util-inl.h"

namespace node {

using ncrypto::BIOPointer;
using ncrypto::ClearErrorOnReturn;
using ncrypto::MarkPopErrorOnReturn;
using ncrypto::SSLPointer;
using ncrypto::SSLSessionPointer;
using ncrypto::X509Pointer;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::BackingStore;
using v8::BackingStoreInitializationMode;
using v8::Boolean;
using v8::Context;
using v8::DontDelete;
using v8::Exception;
using v8::False;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Integer;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::Null;
using v8::Number;
using v8::Object;
using v8::PropertyAttribute;
using v8::ReadOnly;
using v8::Signature;
using v8::String;
using v8::Uint32;
using v8::Value;

namespace crypto {

namespace {

void InjectOpenSSLErrorForTesting(const FunctionCallbackInfo<Value>&) {
#if NCRYPTO_USE_OPENSSL_PROVIDER
  ERR_raise(ERR_LIB_USER, ERR_R_INTERNAL_ERROR);
#elif NCRYPTO_USE_BORINGSSL
  ERR_put_error(ERR_LIB_USER, 0, ERR_R_INTERNAL_ERROR, __FILE__, __LINE__);
#else
#error "Unsupported crypto backend"
#endif
}

void HasOpenSSLErrorForTesting(const FunctionCallbackInfo<Value>& args) {
  args.GetReturnValue().Set(ERR_peek_error() != 0);
}

// Our custom implementation of the certificate verify callback
// used when establishing a TLS handshake. Because we cannot perform
// I/O quickly enough with X509_STORE_CTX_ APIs in this callback,
// we ignore preverify_ok errors here and let the handshake continue.
// In other words, this VerifyCallback is a non-op. It is imperative
// that the user user Connection::VerifyError after the `secure`
// callback has been made.
int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
  // From https://www.openssl.org/docs/man3.0/man3/SSL_verify_cb:
  //
  //   If VerifyCallback returns 1, the verification process is continued. If
  //   VerifyCallback always returns 1, the TLS/SSL handshake will not be
  //   terminated with respect to verification failures and the connection will
  //   be established. The calling process can however retrieve the error code
  //   of the last verification error using SSL_get_verify_result(3) or by
  //   maintaining its own error storage managed by VerifyCallback.
  return 1;
}

SSL_SESSION* GetSessionCallback(
    SSL* s,
    const unsigned char* key,
    int len,
    int* copy) {
  TLSWrap* w = static_cast<TLSWrap*>(SSL_get_app_data(s));
  *copy = 0;
  return w->ReleaseSession();
}

// The TLS library invokes this before version negotiation and before session
// or ticket resumption, which makes async session lookup possible. If required
// the handshake is suspended here and resumed once JS has answered.
ClientHelloResult EarlyClientHelloCallback(const ClientHelloContext& hello) {
  TLSWrap* w = static_cast<TLSWrap*>(SSL_get_app_data(hello.ssl()));
  if (!w->should_suspend_for_client_hello()) {
    return ClientHelloResult::kContinue;
  }

  auto session_id = hello.session_id();
  return w->OnEarlyClientHello(
             session_id.data(), session_id.size(), hello.has_session_ticket())
             ? ClientHelloResult::kContinue
             : ClientHelloResult::kRetry;
}

void KeylogCallback(const SSL* s, const char* line) {
  TLSWrap* w = static_cast<TLSWrap*>(SSL_get_app_data(s));
  Environment* env = w->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  const size_t size = strlen(line);
  Local<Value> line_bf = Buffer::Copy(env, line, 1 + size)
      .FromMaybe(Local<Value>());
  if (line_bf.IsEmpty()) [[unlikely]]
    return;

  char* data = Buffer::Data(line_bf);
  data[size] = '\n';
  w->MakeCallback(env->onkeylog_string(), 1, &line_bf);
}

int NewSessionCallback(SSL* s, SSL_SESSION* sess) {
  TLSWrap* w = static_cast<TLSWrap*>(SSL_get_app_data(s));
  Environment* env = w->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  if (!w->has_session_callbacks()) [[unlikely]]
    return 0;

  // Check if session is small enough to be stored
  int size = i2d_SSL_SESSION(sess, nullptr);
  if (size > SecureContext::kMaxSessionSize) [[unlikely]]
    return 0;

  // Serialize session
  Local<Object> session = Buffer::New(env, size).FromMaybe(Local<Object>());
  if (session.IsEmpty()) [[unlikely]]
    return 0;

  unsigned char* session_data =
      reinterpret_cast<unsigned char*>(Buffer::Data(session));

  CHECK_EQ(i2d_SSL_SESSION(sess, &session_data), size);

  unsigned int session_id_length;
  const unsigned char* session_id_data =
      SSL_SESSION_get_id(sess, &session_id_length);

  Local<Object> session_id = Buffer::Copy(
      env,
      reinterpret_cast<const char*>(session_id_data),
      session_id_length).FromMaybe(Local<Object>());
  if (session_id.IsEmpty()) [[unlikely]]
    return 0;

  Local<Value> argv[] = {
    session_id,
    session
  };

  // On servers, we pause the handshake until callback of 'newSession', which
  // calls NewSessionDoneCb(). On clients, there is no callback to wait for.
  if (w->is_server())
    w->set_awaiting_new_session(true);

  w->MakeCallback(env->onnewsession_string(), arraysize(argv), argv);

  return 0;
}

int SSLCertCallback(SSL* s, void* arg) {
  TLSWrap* w = static_cast<TLSWrap*>(SSL_get_app_data(s));

  if (!w->is_server() || !w->is_waiting_cert_cb())
    return 1;

  if (w->is_cert_cb_running())
    // Not an error. Suspend handshake with SSL_ERROR_WANT_X509_LOOKUP, and
    // handshake will continue after certcb is done.
    return -1;

  w->set_cert_cb_running();

  // The view points into SSL-owned memory, so copy it before deferring.
  std::string servername;
  if (auto name = SSLPointer::GetServerName(s)) servername = *name;

  w->ScheduleCertCb(std::move(servername),
                    SSL_get_tlsext_status_type(s) == TLSEXT_STATUSTYPE_ocsp);

  // Suspend handshake with SSL_ERROR_WANT_X509_LOOKUP, and handshake will
  // continue after certcb is done.
  return -1;
}

int SelectALPNCallback(
    SSL* s,
    const unsigned char** out,
    unsigned char* outlen,
    const unsigned char* in,
    unsigned int inlen,
    void* arg) {
  TLSWrap* w = static_cast<TLSWrap*>(SSL_get_app_data(s));
  if (w->get_alpn_callback_enabled()) {
    Environment* env = w->env();
    HandleScope handle_scope(env->isolate());

    Local<Value> callback_arg;
    Local<Value> callback_result;

    if (!Buffer::Copy(env, reinterpret_cast<const char*>(in), inlen)
             .ToLocal(&callback_arg)) {
      return SSL_TLSEXT_ERR_ALERT_FATAL;
    }

    if (!w->MakeCallback(env->alpn_callback_string(), 1, &callback_arg)
             .ToLocal(&callback_result)) {
      return SSL_TLSEXT_ERR_ALERT_FATAL;
    }

    if (callback_result->IsUndefined() && !callback_result->IsNumber()) {
      // If you set an ALPN callback, but you return undefined for an ALPN
      // request, you're rejecting all proposed ALPN protocols, and so we send
      // a fatal alert:
      return SSL_TLSEXT_ERR_ALERT_FATAL;
    }

    unsigned int result_int = callback_result.As<Number>()->Value();

    // The callback returns an offset into the given buffer, at which the
    // selected protocol sits in ALPN wire format: a length byte followed by
    // that many name bytes.
    SetSelectedProtocol(
        out,
        outlen,
        {reinterpret_cast<const char*>(in + result_int + 1), in[result_int]});

    return SSL_TLSEXT_ERR_OK;
  }

  auto& alpn_protos = w->get_alpn_protos();

  if (alpn_protos.empty()) return SSL_TLSEXT_ERR_NOACK;

  const std::span<const uint8_t> supported{alpn_protos.data(),
                                           alpn_protos.size()};
  const std::span<const uint8_t> offered{in, inlen};
  auto selected = SelectNextProtocol(supported, offered);

  // Previous versions of Node.js returned SSL_TLSEXT_ERR_NOACK if no protocol
  // match was found. This would neither cause a fatal alert nor would it result
  // in a useful ALPN response as part of the Server Hello message.
  // We now return SSL_TLSEXT_ERR_ALERT_FATAL in that case as per Section 3.2
  // of RFC 7301, which causes a fatal no_application_protocol alert.
  if (!selected.has_value()) return SSL_TLSEXT_ERR_ALERT_FATAL;

  SetSelectedProtocol(out, outlen, *selected);
  return SSL_TLSEXT_ERR_OK;
}

MaybeLocal<Value> GetSSLOCSPResponse(Environment* env, SSL* ssl) {
  const unsigned char* resp;
  int len = SSL_get_tlsext_status_ocsp_resp(ssl, &resp);
  if (resp == nullptr) return Null(env->isolate());

  Local<Value> ret;
  MaybeLocal<Object> maybe_buffer =
      Buffer::Copy(env, reinterpret_cast<const char*>(resp), len);

  if (!maybe_buffer.ToLocal(&ret)) return MaybeLocal<Value>();

  return ret;
}

int TLSExtStatusCallback(SSL* s, void* arg) {
  TLSWrap* w = static_cast<TLSWrap*>(SSL_get_app_data(s));
  Environment* env = w->env();
  HandleScope handle_scope(env->isolate());

  if (w->is_client()) {
    // Incoming response
    Local<Value> arg;
    if (GetSSLOCSPResponse(env, s).ToLocal(&arg))
      w->MakeCallback(env->onocspresponse_string(), 1, &arg);

    // No async acceptance is possible, so always return 1 to accept the
    // response.  The listener for 'OCSPResponse' event has no control over
    // return value, but it can .destroy() the connection if the response is not
    // acceptable.
    return 1;
  }

  // Outgoing response
  Local<ArrayBufferView> obj =
      w->ocsp_response().FromMaybe(Local<ArrayBufferView>());
  if (obj.IsEmpty()) [[unlikely]]
    return SSL_TLSEXT_ERR_NOACK;

  size_t len = obj->ByteLength();

  // OpenSSL takes control of the pointer after accepting it
  unsigned char* data = MallocOpenSSL<unsigned char>(len);
  obj->CopyContents(data, len);

  if (!SSL_set_tlsext_status_ocsp_resp(s, data, len))
    OPENSSL_free(data);

  w->ClearOcspResponse();

  return SSL_TLSEXT_ERR_OK;
}

void ConfigureSecureContext(SecureContext* sc) {
  // OCSP stapling
  sc->ctx().setStatusCallback(TLSExtStatusCallback);
  // The cert-verify callback (SPKI blocklist and the OpenSSL 4.1 CN name
  // constraint handling) is installed once per SSL_CTX in
  // SecureContext::Init, so that it also covers contexts created without a
  // TLSWrap.
}

inline bool Set(
    Environment* env,
    Local<Object> target,
    Local<String> name,
    const char* value,
    bool ignore_null = true) {
  if (value == nullptr)
    return ignore_null;
  return !target->Set(
      env->context(),
      name,
      OneByteString(env->isolate(), value))
          .IsNothing();
}

inline bool Set(Environment* env,
                Local<Object> target,
                Local<String> name,
                const std::string_view& value,
                bool ignore_null = true) {
  if (value.empty()) return ignore_null;
  return !target
              ->Set(env->context(),
                    name,
                    OneByteString(env->isolate(), value.data(), value.length()))
              .IsNothing();
}

std::string GetBIOError() {
  std::string ret;
  ERR_print_errors_cb(
      [](const char* str, size_t len, void* opaque) {
        static_cast<std::string*>(opaque)->assign(str, len);
        return 0;
      },
      static_cast<void*>(&ret));
  return ret;
}

}  // namespace

TLSWrap::TLSWrap(Environment* env,
                 Local<Object> obj,
                 Kind kind,
                 StreamBase* stream,
                 SecureContext* sc,
                 UnderlyingStreamWriteStatus under_stream_ws)
    : AsyncWrap(env, obj, AsyncWrap::PROVIDER_TLSWRAP),
      StreamBase(env),
      env_(env),
      kind_(kind),
      sc_(sc) {
  flags_.has_active_write_issued_by_prev_listener =
      under_stream_ws == UnderlyingStreamWriteStatus::kHasActive;
  MakeWeak();
  CHECK(sc_);
  ssl_ = sc_->CreateSSL();
  CHECK(ssl_);

  SetClientHelloCallback<EarlyClientHelloCallback>(sc_->ctx().get());
  sc_->SetGetSessionCallback(GetSessionCallback);
  sc_->SetNewSessionCallback(NewSessionCallback);

  StreamBase::AttachToObject(GetObject());
  stream->PushStreamListener(this);

  env_->external_memory_accounter()->Increase(env_->isolate(), kExternalSize);

  InitSSL();
  Debug(this, "Created new TLSWrap");
}

TLSWrap::~TLSWrap() {
  Destroy();
}

MaybeLocal<ArrayBufferView> TLSWrap::ocsp_response() const {
  if (ocsp_response_.IsEmpty())
    return MaybeLocal<ArrayBufferView>();
  return PersistentToLocal::Default(env()->isolate(), ocsp_response_);
}

void TLSWrap::ClearOcspResponse() {
  ocsp_response_.Reset();
}

SSL_SESSION* TLSWrap::ReleaseSession() {
  return next_sess_.release();
}

void TLSWrap::InvokeQueued(int status, const char* error_str) {
  Debug(this, "Invoking queued write callbacks (%d, %s)", status, error_str);
  if (!flags_.write_callback_scheduled) return;

  if (current_write_) {
    BaseObjectPtr<AsyncWrap> current_write = std::move(current_write_);
    current_write_.reset();
    WriteWrap* w = WriteWrap::FromObject(current_write);
    w->Done(status, error_str);
  }
}

void TLSWrap::NewSessionDoneCb() {
  Debug(this, "New session callback done");
  Cycle();
}

// N.b. TLS1.3 ClientHellos carry a fake legacy_session_id (middlebox compat),
// and so emit spurious 'resumeSession'/'newSession' events here.
bool TLSWrap::OnEarlyClientHello(const unsigned char* session_id,
                                 size_t session_id_len,
                                 bool has_ticket) {
  if (!flags_.hello_emitted) {
    flags_.hello_emitted = true;
    Debug(this, "Scheduling onclienthello");

    // The hello data is only valid inside the library callback, and JS must
    // not run while we are on its stack: a handler that synchronously wrote
    // to the socket would re-enter SSL mid-handshake. Copy what we need and
    // emit from a fresh stack instead.
    std::vector<unsigned char> id(session_id, session_id + session_id_len);
    BaseObjectPtr<TLSWrap> strong_ref{this};
    env()->SetImmediate(
        [this, strong_ref, id = std::move(id), has_ticket](Environment* env) {
          if (ssl_) EmitClientHello(id, has_ticket);
        });
  }
  return flags_.hello_answered;
}

void TLSWrap::EmitClientHello(const std::vector<unsigned char>& session_id,
                              bool has_ticket) {
  Debug(this, "Emitting onclienthello");
  Environment* env = this->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Object> hello_obj = Object::New(env->isolate());
  Local<Object> buf =
      Buffer::Copy(env,
                   reinterpret_cast<const char*>(session_id.data()),
                   session_id.size())
          .FromMaybe(Local<Object>());

  if ((buf.IsEmpty() ||
       hello_obj->Set(env->context(), env->session_id_string(), buf)
           .IsNothing()) ||
      hello_obj
          ->Set(env->context(),
                env->tls_ticket_string(),
                Boolean::New(env->isolate(), has_ticket))
          .IsNothing()) {
    // An exception is pending, so don't re-enter SSL or JS to resume.
    return;
  }

  Local<Value> argv[] = {hello_obj};
  MakeCallback(env->onclienthello_string(), arraysize(argv), argv);
}

// As with the ClientHello, JS must not run on the library's stack: 'oncertcb'
// handlers synchronously call back into the handle to resume the handshake.
void TLSWrap::ScheduleCertCb(std::string servername, bool ocsp) {
  Debug(this, "Scheduling oncertcb");
  BaseObjectPtr<TLSWrap> strong_ref{this};
  env()->SetImmediate(
      [this, strong_ref, servername = std::move(servername), ocsp](
          Environment* env) {
        if (ssl_) EmitCertCb(servername, ocsp);
      });
}

void TLSWrap::EmitCertCb(const std::string& servername, bool ocsp) {
  Debug(this, "Emitting oncertcb");
  Environment* env = this->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  Local<Object> info = Object::New(env->isolate());
  if (info->Set(env->context(),
                env->servername_string(),
                OneByteString(env->isolate(), servername))
          .IsNothing() ||
      info->Set(env->context(),
                env->ocsp_request_string(),
                Boolean::New(env->isolate(), ocsp))
          .IsNothing()) {
    return;
  }

  Local<Value> argv[] = {info};
  MakeCallback(env->oncertcb_string(), arraysize(argv), argv);
}

void TLSWrap::InitSSL() {
  // Initialize SSL – OpenSSL takes ownership of these.
  enc_in_ = NodeBIO::New(env()).release();
  enc_out_ = NodeBIO::New(env()).release();

  SSL_set_bio(ssl_.get(), enc_in_, enc_out_);

  // NOTE: This could be overridden in SetVerifyMode
  SSL_set_verify(ssl_.get(), SSL_VERIFY_NONE, VerifyCallback);

#ifdef SSL_MODE_RELEASE_BUFFERS
  SSL_set_mode(ssl_.get(), SSL_MODE_RELEASE_BUFFERS);
#endif  // SSL_MODE_RELEASE_BUFFERS

  // Set SSL_MODE_AUTO_RETRY explicitly because Cycle() doesn't currently
  // re-call ClearIn() if SSL_read() returns SSL_ERROR_WANT_READ, so data can
  // be left sitting in the incoming enc_in_ and never get processed.
  // - https://wiki.openssl.org/index.php/TLS1.3#Non-application_data_records
  SSL_set_mode(ssl_.get(), SSL_MODE_AUTO_RETRY);

#ifdef OPENSSL_IS_BORINGSSL
  // OpenSSL allows renegotiation by default, but BoringSSL disables it.
  // Configure BoringSSL to match OpenSSL's behavior.
  SSL_set_renegotiate_mode(ssl_.get(), ssl_renegotiate_freely);
#endif

  SSL_set_app_data(ssl_.get(), this);
  // Using InfoCallback isn't how we are supposed to check handshake progress:
  //   https://github.com/openssl/openssl/issues/7199#issuecomment-420915993
  //
  // Note on when this gets called on various openssl versions:
  //   https://github.com/openssl/openssl/issues/7199#issuecomment-420670544
  SSL_set_info_callback(ssl_.get(), SSLInfoCallback);

  if (is_server())
    sc_->SetSelectSNIContextCallback(SelectSNIContextCallback);

  ConfigureSecureContext(sc_.get());

  SSL_set_cert_cb(ssl_.get(), SSLCertCallback, this);

  if (is_server()) {
    SSL_set_accept_state(ssl_.get());
  } else if (is_client()) {
    // Enough space for server response (hello, cert)
    NodeBIO::FromBIO(enc_in_)->set_initial(kInitialClientBufferLength);
    SSL_set_connect_state(ssl_.get());
  } else {
    // Unexpected
    ABORT();
  }
}

void TLSWrap::Wrap(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  CHECK_EQ(args.Length(), 4);
  CHECK(args[0]->IsObject());
  CHECK(args[1]->IsObject());
  CHECK(args[2]->IsBoolean());
  CHECK(args[3]->IsBoolean());

  Local<Object> sc = args[1].As<Object>();
  Kind kind = args[2]->IsTrue() ? Kind::kServer : Kind::kClient;

  UnderlyingStreamWriteStatus under_stream_ws =
      args[3]->IsTrue() ? UnderlyingStreamWriteStatus::kHasActive
                        : UnderlyingStreamWriteStatus::kVacancy;

  StreamBase* stream = StreamBase::FromObject(args[0].As<Object>());
  CHECK_NOT_NULL(stream);

  Local<Object> obj;
  if (!env->tls_wrap_constructor_function()
           ->NewInstance(env->context())
           .ToLocal(&obj)) {
    return;
  }

  TLSWrap* res = new TLSWrap(
      env, obj, kind, stream, Unwrap<SecureContext>(sc), under_stream_ws);

  args.GetReturnValue().Set(res->object());
}

void TLSWrap::Receive(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  ArrayBufferViewContents<char> buffer(args[0]);
  const char* data = buffer.data();
  size_t len = buffer.length();
  Debug(wrap, "Receiving %zu bytes injected from JS", len);

  // Copy given buffer entirely or partiall if handle becomes closed
  while (len > 0 && wrap->IsAlive() && !wrap->IsClosing()) {
    uv_buf_t buf = wrap->OnStreamAlloc(len);
    size_t copy = buf.len > len ? len : buf.len;
    memcpy(buf.base, data, copy);
    buf.len = copy;
    wrap->OnStreamRead(copy, buf);

    data += copy;
    len -= copy;
  }
}

void TLSWrap::Start(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK(!wrap->flags_.started);
  wrap->flags_.started = true;

  // Send ClientHello handshake
  CHECK(wrap->is_client());
  // Seems odd to read when when we want to send, but SSL_read() triggers a
  // handshake if a session isn't established, and handshake will cause
  // encrypted data to become available for output.
  wrap->ClearOut();
  wrap->EncOut();
}

void TLSWrap::SSLInfoCallback(const SSL* ssl_, int where, int ret) {
  if (!(where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE)))
    return;

  // SSL_renegotiate_pending() should take `const SSL*`, but it does not.
  SSL* ssl = const_cast<SSL*>(ssl_);
  TLSWrap* c = static_cast<TLSWrap*>(SSL_get_app_data(ssl_));
  Environment* env = c->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());
  Local<Object> object = c->object();

  if (where & SSL_CB_HANDSHAKE_START) {
    Debug(c, "SSLInfoCallback(SSL_CB_HANDSHAKE_START);");
    // Start is tracked to limit number and frequency of renegotiation attempts,
    // since excessive renegotiation may be an attack.
    Local<Value> callback;

    if (object->Get(env->context(), env->onhandshakestart_string())
            .ToLocal(&callback) && callback->IsFunction()) {
      Local<Value> argv[] = { env->GetNow() };
      c->MakeCallback(callback.As<Function>(), arraysize(argv), argv);
    }
  }

  // SSL_CB_HANDSHAKE_START and SSL_CB_HANDSHAKE_DONE are called when sending
  // HelloRequest.
  // We need to check whether this is in a renegotiation state or not.
  if (where & SSL_CB_HANDSHAKE_DONE && !SSL_renegotiate_pending(ssl)) {
    Debug(c, "SSLInfoCallback(SSL_CB_HANDSHAKE_DONE);");
    CHECK(!SSL_renegotiate_pending(ssl));
    Local<Value> callback;

    c->flags_.established = true;

    if (object->Get(env->context(), env->onhandshakedone_string())
          .ToLocal(&callback) && callback->IsFunction()) {
      c->MakeCallback(callback.As<Function>(), 0, nullptr);
    }
  }
}

void TLSWrap::EncOut() {
  Debug(this, "Trying to write encrypted output");

  // Write in progress
  if (write_size_ != 0) {
    Debug(this, "Returning from EncOut(), write currently in progress");
    return;
  }

  // Wait for `newSession` callback to be invoked
  if (is_awaiting_new_session()) {
    Debug(this, "Returning from EncOut(), awaiting new session");
    return;
  }

  if (flags_.has_active_write_issued_by_prev_listener) [[unlikely]] {
    Debug(this,
          "Returning from EncOut(), "
          "has_active_write_issued_by_prev_listener_ is true");
    return;
  }

  // Split-off queue
  if (flags_.established && current_write_) {
    Debug(this, "EncOut() write is scheduled");
    flags_.write_callback_scheduled = true;
  }

  if (ssl_ == nullptr) {
    Debug(this, "Returning from EncOut(), ssl_ == nullptr");
    return;
  }

  // No encrypted output ready to write to the underlying stream.
  if (BIO_pending(enc_out_) == 0) {
    Debug(this, "No pending encrypted output");
    if (!pending_cleartext_input_ ||
        pending_cleartext_input_->ByteLength() == 0) {
      if (!flags_.in_dowrite) {
        Debug(this, "No pending cleartext input, not inside DoWrite()");
        InvokeQueued(0);
      } else {
        Debug(this, "No pending cleartext input, inside DoWrite()");
        // TODO(@sam-github, @addaleax) If in_dowrite_ is true, appdata was
        // passed to SSL_write().  If we are here, the data was not encrypted to
        // enc_out_ yet.  Calling Done() "works", but since the write is not
        // flushed, its too soon.  Just returning and letting the next EncOut()
        // call Done() passes the test suite, but without more careful analysis,
        // its not clear if it is always correct. Not calling Done() could block
        // data flow, so for now continue to call Done(), just do it in the next
        // tick.
        BaseObjectPtr<TLSWrap> strong_ref{this};
        env()->SetImmediate([this, strong_ref](Environment* env) {
          InvokeQueued(0);
        });
      }
    }
    return;
  }

  char* data[kSimultaneousBufferCount];
  size_t size[arraysize(data)];
  size_t count = arraysize(data);
  write_size_ = NodeBIO::FromBIO(enc_out_)->PeekMultiple(data, size, &count);
  CHECK(write_size_ != 0 && count != 0);

  uv_buf_t buf[arraysize(data)];
  uv_buf_t* bufs = buf;
  for (size_t i = 0; i < count; i++)
    buf[i] = uv_buf_init(data[i], size[i]);

  Debug(this, "Writing %zu buffers to the underlying stream", count);
  StreamWriteResult res = underlying_stream()->Write(bufs, count);
  if (res.err != 0) {
    InvokeQueued(res.err);
    return;
  }

  if (!res.async) {
    Debug(this, "Write finished synchronously");
    HandleScope handle_scope(env()->isolate());

    // Simulate asynchronous finishing, TLS cannot handle this at the moment.
    BaseObjectPtr<TLSWrap> strong_ref{this};
    env()->SetImmediate([this, strong_ref](Environment* env) {
      OnStreamAfterWrite(nullptr, 0);
    });
  }
}

void TLSWrap::OnStreamAfterWrite(WriteWrap* req_wrap, int status) {
  Debug(this, "OnStreamAfterWrite(status = %d)", status);

  if (flags_.has_active_write_issued_by_prev_listener) [[unlikely]] {
    Debug(this, "Notify write finish to the previous_listener_");
    CHECK_EQ(write_size_, 0);  // we must have restrained writes

    previous_listener_->OnStreamAfterWrite(req_wrap, status);
    return;
  }

  if (current_empty_write_) {
    Debug(this, "Had empty write");
    BaseObjectPtr<AsyncWrap> current_empty_write =
        std::move(current_empty_write_);
    current_empty_write_.reset();
    WriteWrap* finishing = WriteWrap::FromObject(current_empty_write);
    finishing->Done(status);
    return;
  }

  if (ssl_ == nullptr) {
    Debug(this, "ssl_ == nullptr, marking as cancelled");
    status = UV_ECANCELED;
  }

  // Handle error
  if (status) {
    if (flags_.shutdown) {
      Debug(this, "Ignoring error after shutdown");
      return;
    }

    // Notify about error
    InvokeQueued(status);
    return;
  }

  // Commit
  NodeBIO::FromBIO(enc_out_)->Read(nullptr, write_size_);

  // Ensure that the progress will be made and `InvokeQueued` will be called.
  ClearIn();

  // Try writing more data
  write_size_ = 0;
  EncOut();
}

void TLSWrap::ClearOut() {
  Debug(this, "Trying to read cleartext output");

  // No reads after EOF
  if (flags_.eof) {
    Debug(this, "Returning from ClearOut(), EOF reached");
    return;
  }

  if (ssl_ == nullptr) {
    Debug(this, "Returning from ClearOut(), ssl_ == nullptr");
    return;
  }

  MarkPopErrorOnReturn mark_pop_error_on_return;

  char out[kClearOutChunkSize];
  int length = 0;
  int read;

  // Emits out[0, length). Returns false if JS destroyed the SSL context.
  auto emit = [&]() {
    char* current = out;
    while (length > 0) {
      int avail = length;

      uv_buf_t buf = EmitAlloc(avail);
      if (static_cast<int>(buf.len) < avail)
        avail = buf.len;
      memcpy(buf.base, current, avail);
      EmitRead(avail, buf);

      // Caveat emptor: OnRead() calls into JS land which can result in
      // the SSL context object being destroyed.  We have to carefully
      // check that ssl_ != nullptr afterwards.
      if (ssl_ == nullptr) {
        Debug(this, "Returning from read loop, ssl_ == nullptr");
        return false;
      }

      length -= avail;
      current += avail;
    }
    return true;
  };

  for (;;) {
    {
      SSLLibraryCallScope ssl_library_call_scope(this);
      if (ERR_set_mark() != 0) ERR_clear_error();
      read = SSL_read(ssl_.get(), out + length, kClearOutChunkSize - length);
    }
    Debug(this, "Read %d bytes of cleartext output", read);

    if (read <= 0) break;
    length += read;

    // SSL_read() returns at most one record. If the next record is already
    // buffered, decrypt it into the same chunk instead of emitting one read
    // per record. This is limited to application data records that are too
    // long to be empty: other record types can trigger callbacks, e.g. for
    // renegotiation, that have to run after the preceding data has been
    // emitted, and SSL_read() skips over empty records to whatever follows.
    // With read-ahead disabled, enc_in_ is at a record boundary after a
    // successful read. The header need not be followed by a complete record:
    // SSL_read() can return WANT_READ, and the path below emits the batch.
    size_t pending;
    const unsigned char* next = reinterpret_cast<const unsigned char*>(
        NodeBIO::FromBIO(enc_in_)->Peek(&pending));
    if (length < kClearOutChunkSize && pending >= SSL3_RT_HEADER_LENGTH &&
        next[0] == SSL3_RT_APPLICATION_DATA &&
        (next[3] << 8 | next[4]) > SSL3_RT_MAX_ENCRYPTED_OVERHEAD) {
      continue;
    }

    if (!emit()) return;
  }

  // We need to check whether an error occurred or the connection was
  // shutdown cleanly (SSL_ERROR_ZERO_RETURN) even when read == 0.
  // See node#1642 and SSL_read(3SSL) for details. SSL_get_error must be
  // called immediately after SSL_read, without calling into JS, which may
  // change OpenSSL's error queue, modify ssl_, or even destroy ssl_
  // altogether. Data that was read before the failure is emitted once the
  // error has been captured, before it is reported.
  if (read <= 0) {
    HandleScope handle_scope(env()->isolate());
    Local<Value> error;
    int err = SSL_get_error(ssl_.get(), read);
    switch (err) {
      case SSL_ERROR_ZERO_RETURN:
        if (!emit()) return;
        if (!flags_.eof) {
          flags_.eof = true;
          EmitRead(UV_EOF);
        }
        return;

      case SSL_ERROR_SSL:
      case SSL_ERROR_SYSCALL:
        {
          unsigned long ssl_err = ERR_peek_error();  // NOLINT(runtime/int)

          Local<Context> context = env()->isolate()->GetCurrentContext();
          if (context.IsEmpty()) [[unlikely]]
            return;
          const std::string error_str = GetBIOError();
          Local<String> message = OneByteString(env()->isolate(), error_str);
          if (message.IsEmpty()) [[unlikely]]
            return;
          error = Exception::Error(message);
          if (error.IsEmpty()) [[unlikely]]
            return;
          Local<Object> obj;
          if (!error->ToObject(context).ToLocal(&obj)) [[unlikely]]
            return;

          const char* ls = ERR_lib_error_string(ssl_err);
#if NCRYPTO_USE_OPENSSL_PROVIDER
          const char* fs = nullptr;
#elif NCRYPTO_USE_BORINGSSL
          const char* fs = ERR_func_error_string(ssl_err);
#else
#error "Unsupported crypto backend"
#endif
          const char* rs = ERR_reason_error_string(ssl_err);
          if (!Set(env(), obj, env()->library_string(), ls) ||
              !Set(env(), obj, env()->function_string(), fs) ||
              !Set(env(), obj, env()->reason_string(), rs, false)) return;
          // SSL has no API to recover the error name from the number, so we
          // transform reason strings like "this error" to "ERR_SSL_THIS_ERROR",
          // which ends up being close to the original error macro name.
          std::string code(rs);
          // TODO(RaisinTen): Pass an appropriate execution policy when it is
          // implemented in our supported compilers.
          std::transform(code.begin(), code.end(), code.begin(),
                         [](char c) { return c == ' ' ? '_' : ToUpper(c); });
          if (!Set(env(), obj,
                   env()->code_string(), ("ERR_SSL_" + code).c_str())) return;
        }
        break;

      default:
        emit();
        return;
    }

    if (!emit()) {
      Debug(this, "Got SSL error (%d) but not calling onerror", err);
      return;
    }

    Debug(this, "Got SSL error (%d), calling onerror", err);
    // When TLS Alert are stored in wbio,
    // it should be flushed to socket before destroyed.
    if (BIO_pending(enc_out_) != 0)
      EncOut();

    MakeCallback(env()->onerror_string(), 1, &error);
  }
}

void TLSWrap::ClearIn() {
  Debug(this, "Trying to write cleartext input");

  if (ssl_ == nullptr) {
    Debug(this, "Returning from ClearIn(), ssl_ == nullptr");
    return;
  }

  if (!pending_cleartext_input_ ||
      pending_cleartext_input_->ByteLength() == 0) {
    Debug(this, "Returning from ClearIn(), no pending data");
    return;
  }

  std::unique_ptr<BackingStore> bs = std::move(pending_cleartext_input_);
  MarkPopErrorOnReturn mark_pop_error_on_return;

  NodeBIO::FromBIO(enc_out_)->set_allocate_tls_hint(bs->ByteLength());
  int written;
  {
    SSLLibraryCallScope ssl_library_call_scope(this);
    if (ERR_set_mark() != 0) ERR_clear_error();
    written = SSL_write(ssl_.get(), bs->Data(), bs->ByteLength());
  }
  Debug(this, "Writing %zu bytes, written = %d", bs->ByteLength(), written);
  CHECK(written == -1 || written == static_cast<int>(bs->ByteLength()));

  // All written
  if (written != -1) {
    Debug(this, "Successfully wrote all data to SSL");
    return;
  }

  // Error or partial write
  int err = SSL_get_error(ssl_.get(), written);
  if (err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL) {
    Debug(this, "Got SSL error (%d)", err);
    flags_.write_callback_scheduled = true;
    // TODO(@sam-github) Should forward an error object with
    // .code/.function/.etc, if possible.
    InvokeQueued(UV_EPROTO, GetBIOError().c_str());
    return;
  }

  Debug(this, "Pushing data back");
  // Push back the not-yet-written data. This can be skipped in the error
  // case because no further writes would succeed anyway.
  pending_cleartext_input_ = std::move(bs);
}

std::string TLSWrap::diagnostic_name() const {
  std::string name = "TLSWrap ";
  name += is_server() ? "server (" : "client (";
  name += std::to_string(static_cast<int64_t>(get_async_id())) + ")";
  return name;
}

AsyncWrap* TLSWrap::GetAsyncWrap() {
  return static_cast<AsyncWrap*>(this);
}

bool TLSWrap::IsIPCPipe() {
  return underlying_stream()->IsIPCPipe();
}

int TLSWrap::GetFD() {
  return underlying_stream()->GetFD();
}

bool TLSWrap::IsAlive() {
  return ssl_ &&
      underlying_stream() != nullptr &&
      underlying_stream()->IsAlive();
}

bool TLSWrap::IsClosing() {
  return underlying_stream()->IsClosing();
}

int TLSWrap::ReadStart() {
  Debug(this, "ReadStart()");
  if (underlying_stream() != nullptr && !flags_.eof)
    return underlying_stream()->ReadStart();
  return 0;
}

int TLSWrap::ReadStop() {
  Debug(this, "ReadStop()");
  return underlying_stream() != nullptr ? underlying_stream()->ReadStop() : 0;
}

const char* TLSWrap::Error() const {
  return error_.empty() ? nullptr : error_.c_str();
}

void TLSWrap::ClearError() {
  error_.clear();
}

// Called by StreamBase::Write() to request async write of clear text into SSL.
// TODO(@sam-github) Should there be a TLSWrap::DoTryWrite()?
int TLSWrap::DoWrite(WriteWrap* w,
                     uv_buf_t* bufs,
                     size_t count,
                     uv_stream_t* send_handle) {
  CHECK_NULL(send_handle);
  Debug(this, "DoWrite()");

  if (ssl_ == nullptr) {
    ClearError();
    error_ = "Write after DestroySSL";
    return UV_EPROTO;
  }

  size_t length = 0;
  size_t i;
  size_t nonempty_i = 0;
  size_t nonempty_count = 0;
  for (i = 0; i < count; i++) {
    length += bufs[i].len;
    if (bufs[i].len > 0) {
      nonempty_i = i;
      nonempty_count += 1;
    }
  }

  // If we got here from a call inside the OpenSSL/BoringSSL stack, we need to
  // defer reentrant write calls:
  if (in_ssl_library_call()) {
    Debug(this, "Deferring write issued from the SSL library's stack");
    CHECK(!current_write_);
    current_write_.reset(w->GetAsyncWrap());

    if (length > 0) {
      CHECK(!pending_cleartext_input_ ||
            pending_cleartext_input_->ByteLength() == 0);
      std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
          env()->isolate(),
          length,
          BackingStoreInitializationMode::kUninitialized);
      size_t offset = 0;
      for (i = 0; i < count; i++) {
        memcpy(
            static_cast<char*>(bs->Data()) + offset, bufs[i].base, bufs[i].len);
        offset += bufs[i].len;
      }
      pending_cleartext_input_ = std::move(bs);
    }

    ScheduleDeferredCycle();
    return 0;
  }

  // We want to trigger a Write() on the underlying stream to drive the stream
  // system, but don't want to encrypt empty buffers into a TLS frame, so see
  // if we can find something to Write().
  // First, call ClearOut(). It does an SSL_read(), which might cause handshake
  // or other internal messages to be encrypted. If it does, write them later
  // with EncOut().
  // If there is still no encrypted output, call Write(bufs) on the underlying
  // stream. Since the bufs are empty, it won't actually write non-TLS data
  // onto the socket, we just want the side-effects. After, make sure the
  // WriteWrap was accepted by the stream, or that we call Done() on it.
  if (length == 0) {
    Debug(this, "Empty write");
    ClearOut();
    if (BIO_pending(enc_out_) == 0) {
      Debug(this, "No pending encrypted output, writing to underlying stream");
      CHECK(!current_empty_write_);
      current_empty_write_.reset(w->GetAsyncWrap());
      StreamWriteResult res =
          underlying_stream()->Write(bufs, count, send_handle);
      if (!res.async) {
        BaseObjectPtr<TLSWrap> strong_ref{this};
        env()->SetImmediate([this, strong_ref](Environment* env) {
          OnStreamAfterWrite(WriteWrap::FromObject(current_empty_write_), 0);
        });
      }
      return 0;
    }
  }

  // Store the current write wrap
  CHECK(!current_write_);
  current_write_.reset(w->GetAsyncWrap());

  // Write encrypted data to underlying stream and call Done().
  if (length == 0) {
    EncOut();
    return 0;
  }

  std::unique_ptr<BackingStore> bs;
  MarkPopErrorOnReturn mark_pop_error_on_return;

  int written = 0;

  // It is common for zero length buffers to be written,
  // don't copy data if there there is one buffer with data
  // and one or more zero length buffers.
  // _http_outgoing.js writes a zero length buffer in
  // in OutgoingMessage.prototype.end.  If there was a large amount
  // of data supplied to end() there is no sense allocating
  // and copying it when it could just be used.

  if (nonempty_count != 1) {
    bs = ArrayBuffer::NewBackingStore(
        env()->isolate(),
        length,
        BackingStoreInitializationMode::kUninitialized);
    size_t offset = 0;
    for (i = 0; i < count; i++) {
      memcpy(static_cast<char*>(bs->Data()) + offset,
             bufs[i].base, bufs[i].len);
      offset += bufs[i].len;
    }

    NodeBIO::FromBIO(enc_out_)->set_allocate_tls_hint(length);
    {
      SSLLibraryCallScope ssl_library_call_scope(this);
      if (ERR_set_mark() != 0) ERR_clear_error();
      written = SSL_write(ssl_.get(), bs->Data(), length);
    }
  } else {
    // Only one buffer: try to write directly, only store if it fails
    uv_buf_t* buf = &bufs[nonempty_i];
    NodeBIO::FromBIO(enc_out_)->set_allocate_tls_hint(buf->len);
    {
      SSLLibraryCallScope ssl_library_call_scope(this);
      if (ERR_set_mark() != 0) ERR_clear_error();
      written = SSL_write(ssl_.get(), buf->base, buf->len);
    }

    if (written == -1) {
      bs = ArrayBuffer::NewBackingStore(
          env()->isolate(),
          length,
          BackingStoreInitializationMode::kUninitialized);
      memcpy(bs->Data(), buf->base, buf->len);
    }
  }

  CHECK(written == -1 || written == static_cast<int>(length));
  Debug(this, "Writing %zu bytes, written = %d", length, written);

  if (written == -1) {
    // If we stopped writing because of an error, it's fatal, discard the data.
    int err = SSL_get_error(ssl_.get(), written);
    if (err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL) {
      // TODO(@jasnell): What are we doing with the error?
      Debug(this, "Got SSL error (%d), returning UV_EPROTO", err);
      current_write_.reset();
      return UV_EPROTO;
    }

    Debug(this, "Saving data for later write");
    // Otherwise, save unwritten data so it can be written later by ClearIn().
    CHECK(!pending_cleartext_input_ ||
          pending_cleartext_input_->ByteLength() == 0);
    pending_cleartext_input_ = std::move(bs);
  }

  // Write any encrypted/handshake output that may be ready.
  // Guard against sync call of current_write_->Done(), its unsupported.
  flags_.in_dowrite = true;
  EncOut();
  flags_.in_dowrite = false;

  return 0;
}

uv_buf_t TLSWrap::OnStreamAlloc(size_t suggested_size) {
  CHECK_NOT_NULL(ssl_);

  size_t size = suggested_size;
  char* base = NodeBIO::FromBIO(enc_in_)->PeekWritable(&size);
  return uv_buf_init(base, size);
}

void TLSWrap::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
  Debug(this, "Read %zd bytes from underlying stream", nread);

  // Ignore everything after close_notify (rfc5246#section-7.2.1)
  if (flags_.eof) return;

  if (nread < 0)  {
    // Error should be emitted only after all data was read
    ClearOut();

    if (nread == UV_EOF) {
      // underlying stream already should have also called ReadStop on itself
      flags_.eof = true;
    }

    EmitRead(nread);
    return;
  }

  // DestroySSL() is the only thing that un-sets ssl_, but that also removes
  // this TLSWrap as a stream listener, so we should not receive OnStreamRead()
  // calls anymore.
  CHECK(ssl_);

  // Commit the amount of data actually read into the peeked/allocated buffer
  // from the underlying stream.
  NodeBIO::FromBIO(enc_in_)->Commit(nread);

  // Cycle OpenSSL's state
  Cycle();
}

ShutdownWrap* TLSWrap::CreateShutdownWrap(Local<Object> req_wrap_object) {
  return underlying_stream()->CreateShutdownWrap(req_wrap_object);
}

int TLSWrap::DoShutdown(ShutdownWrap* req_wrap) {
  Debug(this, "DoShutdown()");

  // We must not call SSL_shutdown from inside the TLS library stack, so
  // defer if required:
  if (in_ssl_library_call()) {
    Debug(this, "Deferring shutdown issued from the SSL library's stack");
    CHECK(!pending_shutdown_);
    pending_shutdown_.reset(req_wrap->GetAsyncWrap());
    flags_.shutdown = true;
    ScheduleDeferredCycle();
    return 0;
  }

  MarkPopErrorOnReturn mark_pop_error_on_return;

  if (ssl_) {
    SSLLibraryCallScope ssl_library_call_scope(this);
    if (SSL_shutdown(ssl_.get()) == 0) SSL_shutdown(ssl_.get());
  }

  flags_.shutdown = true;
  EncOut();
  return underlying_stream()->DoShutdown(req_wrap);
}

void TLSWrap::ScheduleDeferredCycle() {
  if (flags_.deferred_cycle_scheduled) return;
  flags_.deferred_cycle_scheduled = true;

  BaseObjectPtr<TLSWrap> strong_ref{this};
  env()->SetImmediate([this, strong_ref](Environment* env) {
    flags_.deferred_cycle_scheduled = false;
    if (ssl_) Cycle();
  });
}

void TLSWrap::FlushPendingShutdown() {
  if (!pending_shutdown_ || !ssl_) return;

  const bool can_send_close_notify =
      SSL_is_init_finished(ssl_.get()) && !is_awaiting_new_session();
  if (!can_send_close_notify) {
    Debug(this, "Holding deferred shutdown, cannot send close_notify yet");
    return;
  }

  BaseObjectPtr<AsyncWrap> pending = std::move(pending_shutdown_);
  ShutdownWrap* req_wrap = ShutdownWrap::FromObject(pending);
  int err = DoShutdown(req_wrap);
  if (err != 0) req_wrap->Done(err);
}

void TLSWrap::SetVerifyMode(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK_EQ(args.Length(), 2);
  CHECK(args[0]->IsBoolean());
  CHECK(args[1]->IsBoolean());
  CHECK_NOT_NULL(wrap->ssl_);

  int verify_mode;
  if (wrap->is_server()) {
    bool request_cert = args[0]->IsTrue();
    if (!request_cert) {
      // If no cert is requested, there will be none to reject as unauthorized.
      verify_mode = SSL_VERIFY_NONE;
    } else {
      bool reject_unauthorized = args[1]->IsTrue();
      verify_mode = SSL_VERIFY_PEER;
      if (reject_unauthorized)
        verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
    }
  } else {
    // Servers always send a cert if the cipher is not anonymous (anon is
    // disabled by default), so use VERIFY_NONE and check the cert after the
    // handshake has completed.
    verify_mode = SSL_VERIFY_NONE;
  }

  // Always allow a connection. We'll reject in javascript.
  SSL_set_verify(wrap->ssl_.get(), verify_mode, VerifyCallback);
}

void TLSWrap::EnableSessionCallbacks(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  CHECK_NOT_NULL(wrap->ssl_);
  wrap->enable_session_callbacks();
}

void TLSWrap::EnableKeylogCallback(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  CHECK(wrap->sc_);
  wrap->sc_->SetKeylogCallback(KeylogCallback);
}

// Check required capabilities were not excluded from the OpenSSL build:
// - OPENSSL_NO_SSL_TRACE excludes SSL_trace()
// - OPENSSL_NO_STDIO excludes BIO_new_fp()
// HAVE_SSL_TRACE is available on the internal tcp_wrap binding for the tests.
#if defined(OPENSSL_NO_SSL_TRACE) || defined(OPENSSL_NO_STDIO)
# define HAVE_SSL_TRACE 0
#else
# define HAVE_SSL_TRACE 1
#endif

void TLSWrap::EnableTrace(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

#if HAVE_SSL_TRACE
  if (wrap->ssl_) {
    wrap->bio_trace_ = BIOPointer::NewFp(stderr, BIO_NOCLOSE | BIO_FP_TEXT);
    SSL_set_msg_callback(wrap->ssl_.get(), [](int write_p, int version, int
          content_type, const void* buf, size_t len, SSL* ssl, void* arg)
        -> void {
        // BIO_write(), etc., called by SSL_trace, may error. The error should
        // be ignored, trace is a "best effort", and its usually because stderr
        // is a non-blocking pipe, and its buffer has overflowed. Leaving errors
        // on the stack that can get picked up by later SSL_ calls causes
        // unwanted failures in SSL_ calls, so keep the error stack unchanged.
        MarkPopErrorOnReturn mark_pop_error_on_return;
        SSL_trace(write_p,  version, content_type, buf, len, ssl, arg);
    });
    SSL_set_msg_callback_arg(wrap->ssl_.get(), wrap->bio_trace_.get());
  }
#endif
}

void TLSWrap::DestroySSL(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  wrap->Destroy();
  Debug(wrap, "DestroySSL() finished");
}

void TLSWrap::Destroy() {
  if (!ssl_)
    return;

  // If there is a write happening, mark it as finished.
  flags_.write_callback_scheduled = true;

  // And destroy
  InvokeQueued(UV_ECANCELED, "Canceled because of SSL destruction");

  // A shutdown held back off the SSL library's stack will never be replayed
  // now, so complete it here rather than leaving the stream waiting on it.
  if (pending_shutdown_) {
    BaseObjectPtr<AsyncWrap> pending = std::move(pending_shutdown_);
    ShutdownWrap::FromObject(pending)->Done(UV_ECANCELED);
  }

  env()->external_memory_accounter()->Decrease(env()->isolate(), kExternalSize);
  ssl_.reset();

  enc_in_ = nullptr;
  enc_out_ = nullptr;

  if (underlying_stream() != nullptr)
    underlying_stream()->RemoveStreamListener(this);

  sc_.reset();
}

void TLSWrap::EnableCertCb(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  wrap->WaitForCertCb(ResumeAfterCertCb, wrap);
}

void TLSWrap::WaitForCertCb(CertCb cb, void* arg) {
  cert_cb_ = cb;
  cert_cb_arg_ = arg;
}

void TLSWrap::ResumeAfterCertCb(void* arg) {
  TLSWrap* c = static_cast<TLSWrap*>(arg);
  Debug(c, "ResumeAfterCertCb()");
  c->Cycle();
}

void TLSWrap::EnableALPNCb(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  wrap->flags_.alpn_callback_enabled = true;

  SSL* ssl = wrap->ssl_.get();
  SSL_CTX* ssl_ctx = SSL_get_SSL_CTX(ssl);
  SSL_CTX_set_alpn_select_cb(ssl_ctx, SelectALPNCallback, nullptr);
}

void TLSWrap::GetServername(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK_NOT_NULL(wrap->ssl_);

  auto servername = wrap->ssl_.getServerName();
  if (servername.has_value()) {
    auto& sn = servername.value();
    args.GetReturnValue().Set(
        OneByteString(args.GetIsolate(), sn.data(), sn.length()));
  } else {
    args.GetReturnValue().Set(false);
  }
}

void TLSWrap::SetServername(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());

  CHECK_EQ(args.Length(), 1);
  CHECK(args[0]->IsString());
  CHECK(!wrap->flags_.started);
  CHECK(wrap->is_client());

  CHECK(wrap->ssl_);

  Utf8Value servername(args.GetIsolate(), args[0].As<String>());
  SSL_set_tlsext_host_name(wrap->ssl_.get(), *servername);
}

int TLSWrap::SelectSNIContextCallback(SSL* s, int* ad, void* arg) {
  TLSWrap* p = static_cast<TLSWrap*>(SSL_get_app_data(s));
  Environment* env = p->env();
  HandleScope handle_scope(env->isolate());
  Context::Scope context_scope(env->context());

  auto servername = SSLPointer::GetServerName(s);
  if (!servername.has_value() ||
      !Set(env, p->GetOwner(), env->servername_string(), servername.value()))
    return SSL_TLSEXT_ERR_NOACK;

  Local<Value> ctx = p->object()->Get(env->context(), env->sni_context_string())
      .FromMaybe(Local<Value>());

  if (ctx.IsEmpty() || !ctx->IsObject()) [[unlikely]]
    return SSL_TLSEXT_ERR_NOACK;

  if (!env->secure_context_constructor_template()->HasInstance(ctx)) {
    // Failure: incorrect SNI context object
    Local<Value> err = Exception::TypeError(env->sni_context_err_string());
    p->MakeCallback(env->onerror_string(), 1, &err);
    return SSL_TLSEXT_ERR_NOACK;
  }

  SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
  CHECK_NOT_NULL(sc);
  p->sni_context_ = BaseObjectPtr<SecureContext>(sc);

  ConfigureSecureContext(sc);
  CHECK_EQ(SSL_set_SSL_CTX(p->ssl_.get(), sc->ctx().get()), sc->ctx().get());
  p->SetCACerts(sc);

  return SSL_TLSEXT_ERR_OK;
}

int TLSWrap::SetCACerts(SecureContext* sc) {
  int err = SSL_set1_verify_cert_store(ssl_.get(),
                                       SSL_CTX_get_cert_store(sc->ctx().get()));
  if (err != 1)
    return err;

  STACK_OF(X509_NAME)* list =
      SSL_dup_CA_list(SSL_CTX_get_client_CA_list(sc->ctx().get()));

  // NOTE: `SSL_set_client_CA_list` takes the ownership of `list`
  SSL_set_client_CA_list(ssl_.get(), list);
  return 1;
}

#ifndef OPENSSL_NO_PSK

void TLSWrap::SetPskIdentityHint(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* p;
  ASSIGN_OR_RETURN_UNWRAP(&p, args.This());
  CHECK_NOT_NULL(p->ssl_);

  Environment* env = p->env();
  Isolate* isolate = env->isolate();

  CHECK(args[0]->IsString());
  Utf8Value hint(isolate, args[0].As<String>());

  if (!SSL_use_psk_identity_hint(p->ssl_.get(), *hint)) {
    Local<Value> err = node::ERR_TLS_PSK_SET_IDENTITY_HINT_FAILED(isolate);
    p->MakeCallback(env->onerror_string(), 1, &err);
  }
}

void TLSWrap::EnablePskCallback(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
  CHECK_NOT_NULL(wrap->ssl_);

  SSL_set_psk_server_callback(wrap->ssl_.get(), PskServerCallback);
  SSL_set_psk_client_callback(wrap->ssl_.get(), PskClientCallback);
}

unsigned int TLSWrap::PskServerCallback(
    SSL* s,
    const char* identity,
    unsigned char* psk,
    unsigned int max_psk_len) {
  TLSWrap* p = static_cast<TLSWrap*>(SSL_get_app_data(s));

  Environment* env = p->env();
  HandleScope scope(env->isolate());

  Local<String> identity_str =
      String::NewFromUtf8(env->isolate(), identity).FromMaybe(Local<String>());
  if (identity_str.IsEmpty()) [[unlikely]]
    return 0;

  // Make sure there are no utf8 replacement symbols.
  Utf8Value identity_utf8(env->isolate(), identity_str);
  if (identity_utf8 != identity) return 0;

  Local<Value> argv[] = {
    identity_str,
    Integer::NewFromUnsigned(env->isolate(), max_psk_len)
  };

  Local<Value> psk_val =
      p->MakeCallback(env->onpskexchange_symbol(), arraysize(argv), argv)
          .FromMaybe(Local<Value>());
  if (psk_val.IsEmpty() || !psk_val->IsArrayBufferView()) [[unlikely]]
    return 0;

  ArrayBufferViewContents<char> psk_buf(psk_val);

  if (psk_buf.length() > max_psk_len)
    return 0;

  memcpy(psk, psk_buf.data(), psk_buf.length());
  return psk_buf.length();
}

unsigned int TLSWrap::PskClientCallback(
    SSL* s,
    const char* hint,
    char* identity,
    unsigned int max_identity_len,
    unsigned char* psk,
    unsigned int max_psk_len) {
  TLSWrap* p = static_cast<TLSWrap*>(SSL_get_app_data(s));

  Environment* env = p->env();
  HandleScope scope(env->isolate());

  Local<Value> argv[] = {
    Null(env->isolate()),
    Integer::NewFromUnsigned(env->isolate(), max_psk_len),
    Integer::NewFromUnsigned(env->isolate(), max_identity_len)
  };

  if (hint != nullptr) {
    Local<String> local_hint =
        String::NewFromUtf8(env->isolate(), hint).FromMaybe(Local<String>());
    if (local_hint.IsEmpty()) [[unlikely]]
      return 0;

    argv[0] = local_hint;
  }

  Local<Value> ret =
      p->MakeCallback(env->onpskexchange_symbol(), arraysize(argv), argv)
          .FromMaybe(Local<Value>());
  if (ret.IsEmpty() || !ret->IsObject()) [[unlikely]]
    return 0;

  Local<Object> obj = ret.As<Object>();

  Local<Value> psk_val = obj->Get(env->context(), env->psk_string())
      .FromMaybe(Local<Value>());
  if (psk_val.IsEmpty() || !psk_val->IsArrayBufferView()) [[unlikely]]
    return 0;

  ArrayBufferViewContents<char> psk_buf(psk_val);
  if (psk_buf.length() > max_psk_len)
    return 0;

  Local<Value> identity_val = obj->Get(env->context(), env->identity_string())
      .FromMaybe(Local<Value>());
  if (identity_val.IsEmpty() || !identity_val->IsString()) [[unlikely]]
    return 0;

  Utf8Value identity_buf(env->isolate(), identity_val);

  if (identity_buf.length() > max_identity_len)
    return 0;

  memcpy(identity, *identity_buf, identity_buf.length());
  memcpy(psk, psk_buf.data(), psk_buf.length());

  return psk_buf.length();
}

#endif  // ifndef OPENSSL_NO_PSK

void TLSWrap::GetWriteQueueSize(const FunctionCallbackInfo<Value>& info) {
  TLSWrap* wrap;
  ASSIGN_OR_RETURN_UNWRAP(&wrap, info.This());

  if (!wrap->ssl_)
    return info.GetReturnValue().Set(0);

  uint32_t write_queue_size = BIO_pending(wrap->enc_out_);
  info.GetReturnValue().Set(write_queue_size);
}

void TLSWrap::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackField("ocsp_response", ocsp_response_);
  tracker->TrackField("sni_context", sni_context_);
  tracker->TrackField("error", error_);
  if (pending_cleartext_input_)
    tracker->TrackField("pending_cleartext_input", pending_cleartext_input_);
  if (enc_in_ != nullptr)
    tracker->TrackField("enc_in", NodeBIO::FromBIO(enc_in_));
  if (enc_out_ != nullptr)
    tracker->TrackField("enc_out", NodeBIO::FromBIO(enc_out_));
}

void TLSWrap::CertCbDone(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  CHECK(w->is_waiting_cert_cb() && w->flags_.cert_cb_running);

  Local<Object> object = w->object();
  Local<Value> ctx = object->Get(env->context(), env->sni_context_string())
      .FromMaybe(Local<Value>());
  if (ctx.IsEmpty()) [[unlikely]]
    return;

  Local<FunctionTemplate> cons = env->secure_context_constructor_template();
  if (cons->HasInstance(ctx)) {
    SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
    CHECK_NOT_NULL(sc);
    // Store the SNI context for later use.
    w->sni_context_ = BaseObjectPtr<SecureContext>(sc);

    if (w->ssl_.setSniContext(w->sni_context_->ctx()) && !w->SetCACerts(sc)) {
      // Not clear why sometimes we throw error, and sometimes we call
      // onerror(). Both cause .destroy(), but onerror does a bit more.
      unsigned long err = ERR_get_error();  // NOLINT(runtime/int)
      return ThrowCryptoError(env, err, "CertCbDone");
    }
    // setSniContext copies the cert via SSL_use_certificate which does not
    // carry over pre-compressed certificate data (comp_cert). If the new
    // context has certificate compression configured, set the compression
    // preferences on this connection and apply the cached compressed cert
    // data so the server can send CompressedCertificate messages.
#ifdef NODE_OPENSSL_HAS_CERT_COMP
    if (sc->HasCertCompression()) {
      SSL_set1_cert_comp_preference(
          w->ssl_.get(), sc->CertCompPrefs(), sc->CertCompPrefsLen());
      for (const auto& cc : sc->CompressedCerts()) {
        SSL_set1_compressed_cert(w->ssl_.get(),
                                 cc.algorithm,
                                 const_cast<unsigned char*>(cc.data.data()),
                                 cc.data.size(),
                                 cc.orig_length);
      }
    }
#endif
  } else if (ctx->IsObject()) {
    // Failure: incorrect SNI context object
    Local<Value> err = Exception::TypeError(env->sni_context_err_string());
    w->MakeCallback(env->onerror_string(), 1, &err);
    return;
  }

  CertCb cb;
  void* arg;

  cb = w->cert_cb_;
  arg = w->cert_cb_arg_;

  w->flags_.cert_cb_running = false;
  w->cert_cb_ = nullptr;
  w->cert_cb_arg_ = nullptr;

  cb(arg);
}

void TLSWrap::SetALPNProtocols(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Environment* env = w->env();
  if (args.Length() < 1 || !Buffer::HasInstance(args[0]))
    return env->ThrowTypeError("Must give a Buffer as first argument");

  ArrayBufferViewContents<uint8_t> protos(args[0].As<ArrayBufferView>());
  SSL* ssl = w->ssl_.get();
  if (w->is_client()) {
    CHECK_EQ(0, SSL_set_alpn_protos(ssl, protos.data(), protos.length()));
  } else {
    w->alpn_protos_ = std::vector<unsigned char>(
        protos.data(), protos.data() + protos.length());
    SSL_CTX* ssl_ctx = SSL_get_SSL_CTX(ssl);
    SSL_CTX_set_alpn_select_cb(ssl_ctx, SelectALPNCallback, nullptr);
  }
}

void TLSWrap::SetKeyCert(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Environment* env = w->env();

  if (w->is_client()) return;

  if (args.Length() < 1 || !args[0]->IsObject())
    return env->ThrowTypeError("Must give a SecureContext as first argument");

  Local<Value> ctx = args[0];
  if (ctx.IsEmpty()) [[unlikely]]
    return;

  Local<FunctionTemplate> cons = env->secure_context_constructor_template();
  if (cons->HasInstance(ctx)) {
    SecureContext* sc = Unwrap<SecureContext>(ctx.As<Object>());
    CHECK_NOT_NULL(sc);
    if (!w->ssl_.setSniContext(sc->ctx()) || !w->SetCACerts(sc)) {
      unsigned long err = ERR_get_error();  // NOLINT(runtime/int)
      return ThrowCryptoError(env, err, "SetKeyCert");
    }
  } else {
    return env->ThrowTypeError("Must give a SecureContext as first argument");
  }
}

void TLSWrap::GetPeerCertificate(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Environment* env = w->env();

  bool abbreviated = args.Length() < 1 || !args[0]->IsTrue();

  Local<Value> ret;
  if (GetPeerCert(
          env,
          w->ssl_,
          abbreviated,
          w->is_server()).ToLocal(&ret))
    args.GetReturnValue().Set(ret);
}

void TLSWrap::GetPeerX509Certificate(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Environment* env = w->env();

  X509Certificate::GetPeerCertificateFlag flag = w->is_server()
      ? X509Certificate::GetPeerCertificateFlag::SERVER
      : X509Certificate::GetPeerCertificateFlag::NONE;

  Local<Value> ret;
  if (X509Certificate::GetPeerCert(env, w->ssl_, flag).ToLocal(&ret))
    args.GetReturnValue().Set(ret);
}

void TLSWrap::HasPeerCertificate(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  bool has_peer_cert = static_cast<bool>(X509Pointer::PeerFrom(w->ssl_));
  args.GetReturnValue().Set(has_peer_cert);
}

void TLSWrap::GetCertificate(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Environment* env = w->env();

  Local<Value> ret;
  if (GetCert(env, w->ssl_).ToLocal(&ret))
    args.GetReturnValue().Set(ret);
}

void TLSWrap::GetX509Certificate(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Environment* env = w->env();
  Local<Value> ret;
  if (X509Certificate::GetCert(env, w->ssl_).ToLocal(&ret))
    args.GetReturnValue().Set(ret);
}

void TLSWrap::GetFinished(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  // We cannot just pass nullptr to SSL_get_finished()
  // because it would further be propagated to memcpy(),
  // where the standard requirements as described in ISO/IEC 9899:2011
  // sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
  // Thus, we use a dummy byte.
  char dummy[1];
  size_t len = SSL_get_finished(w->ssl_.get(), dummy, sizeof dummy);
  if (len == 0)
    return;

  auto bs = ArrayBuffer::NewBackingStore(
      env->isolate(), len, BackingStoreInitializationMode::kUninitialized);

  CHECK_EQ(bs->ByteLength(),
           SSL_get_finished(w->ssl_.get(), bs->Data(), bs->ByteLength()));

  Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
  Local<Value> buffer;
  if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return;
  args.GetReturnValue().Set(buffer);
}

void TLSWrap::GetPeerFinished(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  // We cannot just pass nullptr to SSL_get_peer_finished()
  // because it would further be propagated to memcpy(),
  // where the standard requirements as described in ISO/IEC 9899:2011
  // sections 7.21.2.1, 7.21.1.2, and 7.1.4, would be violated.
  // Thus, we use a dummy byte.
  char dummy[1];
  size_t len = SSL_get_peer_finished(w->ssl_.get(), dummy, sizeof dummy);
  if (len == 0)
    return;

  auto bs = ArrayBuffer::NewBackingStore(
      env->isolate(), len, BackingStoreInitializationMode::kUninitialized);

  CHECK_EQ(bs->ByteLength(),
           SSL_get_peer_finished(w->ssl_.get(), bs->Data(), bs->ByteLength()));

  Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
  Local<Value> buffer;
  if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return;
  args.GetReturnValue().Set(buffer);
}

void TLSWrap::GetSession(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  SSL_SESSION* sess = SSL_get_session(w->ssl_.get());
  if (sess == nullptr)
    return;

  int slen = i2d_SSL_SESSION(sess, nullptr);
  if (slen <= 0)
    return;  // Invalid or malformed session.

  auto bs = ArrayBuffer::NewBackingStore(
      env->isolate(), slen, BackingStoreInitializationMode::kUninitialized);

  unsigned char* p = static_cast<unsigned char*>(bs->Data());
  CHECK_LT(0, i2d_SSL_SESSION(sess, &p));

  Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
  Local<Value> buffer;
  if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return;
  args.GetReturnValue().Set(buffer);
}

void TLSWrap::SetSession(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);

  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  if (args.Length() < 1)
    return THROW_ERR_MISSING_ARGS(env, "Session argument is mandatory");

  THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "Session");
  ArrayBufferViewContents<unsigned char> sbuf(args[0]);
  SSLSessionPointer sess = GetTLSSession(sbuf.data(), sbuf.length());
  if (sess == nullptr)
    return;  // TODO(tniessen): figure out error handling

  if (!w->ssl_.setSession(sess))
    return env->ThrowError("SSL_set_session error");
}

void TLSWrap::IsSessionReused(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  bool yes = SSL_session_reused(w->ssl_.get());
  args.GetReturnValue().Set(yes);
}

void TLSWrap::VerifyError(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  // XXX(bnoordhuis) The UNABLE_TO_GET_ISSUER_CERT error when there is no
  // peer certificate is questionable but it's compatible with what was
  // here before.
  long x509_verify_error =  // NOLINT(runtime/int)
      w->ssl_.verifyPeerCertificate().value_or(
          X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT);

  if (x509_verify_error == X509_V_OK)
    return args.GetReturnValue().SetNull();

  Local<Value> reason;
  if (!GetValidationErrorReason(env, x509_verify_error).ToLocal(&reason)) {
    return;
  }
  if (reason->IsUndefined()) [[unlikely]]
    return;

  Local<Object> error = Exception::Error(reason.As<v8::String>())
                            ->ToObject(env->isolate()->GetCurrentContext())
                            .FromMaybe(Local<Object>());

  auto code = X509Pointer::ErrorCode(x509_verify_error);
  if (Set(env, error, env->code_string(), code))
    args.GetReturnValue().Set(error);
}

void TLSWrap::GetCipher(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  args.GetReturnValue().Set(
      GetCipherInfo(env, w->ssl_).FromMaybe(Local<Object>()));
}

void TLSWrap::LoadSession(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  // TODO(@sam-github) check arg length and types in js, and CHECK in c++
  if (args.Length() >= 1 && Buffer::HasInstance(args[0])) {
    ArrayBufferViewContents<unsigned char> sbuf(args[0]);

    const unsigned char* p = sbuf.data();
    SSL_SESSION* sess = d2i_SSL_SESSION(nullptr, &p, sbuf.length());

    // Setup next session and move hello to the BIO buffer
    w->next_sess_.reset(sess);
  }
}

void TLSWrap::GetSharedSigalgs(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  SSL* ssl = w->ssl_.get();
  int nsig = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr,
                                    nullptr);
  MaybeStackBuffer<Value, 16> ret_arr(env->isolate(), nsig);

  for (int i = 0; i < nsig; i++) {
    int hash_nid;
    int sign_nid;
    std::string sig_with_md;

    SSL_get_shared_sigalgs(ssl, i, &sign_nid, &hash_nid, nullptr, nullptr,
                           nullptr);

    switch (sign_nid) {
      case NID_rsaEncryption:
        sig_with_md = "RSA+";
        break;

      case NID_rsassaPss:
        sig_with_md = "RSA-PSS+";
        break;

      case NID_dsa:
        sig_with_md = "DSA+";
        break;

      case NID_X9_62_id_ecPublicKey:
        sig_with_md = "ECDSA+";
        break;

      case NID_ED25519:
        sig_with_md = "Ed25519+";
        break;

      case NID_ED448:
        sig_with_md = "Ed448+";
        break;
#ifndef OPENSSL_NO_GOST
      case NID_id_GostR3410_2001:
        sig_with_md = "gost2001+";
        break;

      case NID_id_GostR3410_2012_256:
        sig_with_md = "gost2012_256+";
        break;

      case NID_id_GostR3410_2012_512:
        sig_with_md = "gost2012_512+";
        break;
#endif  // !OPENSSL_NO_GOST
      default:
        const char* sn = OBJ_nid2sn(sign_nid);

        if (sn != nullptr) {
          sig_with_md = std::string(sn) + "+";
        } else {
          sig_with_md = "UNDEF+";
        }
        break;
    }

    const char* sn_hash = OBJ_nid2sn(hash_nid);
    if (sn_hash != nullptr) {
      sig_with_md += std::string(sn_hash);
    } else {
      sig_with_md += "UNDEF";
    }
    ret_arr[i] = OneByteString(env->isolate(), sig_with_md);
  }

  args.GetReturnValue().Set(ret_arr.ToArray());
}

void TLSWrap::ExportKeyingMaterial(const FunctionCallbackInfo<Value>& args) {
  CHECK(args[0]->IsInt32());
  CHECK(args[1]->IsString());

  Environment* env = Environment::GetCurrent(args);
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  uint32_t olen = args[0].As<Uint32>()->Value();
  Utf8Value label(env->isolate(), args[1]);

  auto bs = ArrayBuffer::NewBackingStore(
      env->isolate(), olen, BackingStoreInitializationMode::kUninitialized);

  ByteSource context;
  bool use_context = !args[2]->IsUndefined();
  if (use_context)
    context = ByteSource::FromBuffer(args[2]);

  if (SSL_export_keying_material(
          w->ssl_.get(),
          static_cast<unsigned char*>(bs->Data()),
          olen,
          *label,
          label.length(),
          context.data<unsigned char>(),
          context.size(),
          use_context) != 1) {
    return ThrowCryptoError(
         env,
         ERR_get_error(),
         "SSL_export_keying_material");
  }

  Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(bs));
  Local<Value> buffer;
  if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&buffer)) return;
  args.GetReturnValue().Set(buffer);
}

void TLSWrap::ClientHelloDone(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  w->flags_.hello_answered = true;
  w->Cycle();
}

void TLSWrap::Renegotiate(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  ClearErrorOnReturn clear_error_on_return;
  if (SSL_renegotiate(w->ssl_.get()) != 1)
    return ThrowCryptoError(w->env(), ERR_get_error());
}

void TLSWrap::GetTLSTicket(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Environment* env = w->env();

  SSL_SESSION* sess = SSL_get_session(w->ssl_.get());
  if (sess == nullptr)
    return;

  const unsigned char* ticket;
  size_t length;
  SSL_SESSION_get0_ticket(sess, &ticket, &length);

  if (ticket != nullptr) {
    args.GetReturnValue().Set(
        Buffer::Copy(env, reinterpret_cast<const char*>(ticket), length)
            .FromMaybe(Local<Object>()));
  }
}

void TLSWrap::NewSessionDone(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  w->flags_.awaiting_new_session = false;
  w->NewSessionDoneCb();
}

void TLSWrap::SetOCSPResponse(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Environment* env = w->env();

  if (args.Length() < 1)
    return THROW_ERR_MISSING_ARGS(env, "OCSP response argument is mandatory");

  THROW_AND_RETURN_IF_NOT_BUFFER(env, args[0], "OCSP response");

  w->ocsp_response_.Reset(args.GetIsolate(), args[0].As<ArrayBufferView>());
}

void TLSWrap::RequestOCSP(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  SSL_set_tlsext_status_type(w->ssl_.get(), TLSEXT_STATUSTYPE_ocsp);
}

void TLSWrap::GetEphemeralKeyInfo(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  Environment* env = Environment::GetCurrent(args);

  CHECK(w->ssl_);

  // tmp key is available on only client
  if (w->is_server())
    return args.GetReturnValue().SetNull();

  args.GetReturnValue().Set(GetEphemeralKey(env, w->ssl_)
      .FromMaybe(Local<Value>()));

  // TODO(@sam-github) semver-major: else return ThrowCryptoError(env,
  // ERR_get_error())
}

void TLSWrap::GetProtocol(const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  args.GetReturnValue().Set(
      OneByteString(args.GetIsolate(), SSL_get_version(w->ssl_.get())));
}

void TLSWrap::GetALPNNegotiatedProto(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  const unsigned char* alpn_proto;
  unsigned int alpn_proto_len;

  SSL_get0_alpn_selected(w->ssl_.get(), &alpn_proto, &alpn_proto_len);

  Local<Value> result;
  if (alpn_proto_len == 0) {
    result = False(env->isolate());
  } else if (alpn_proto_len == sizeof("h2") - 1 &&
             0 == memcmp(alpn_proto, "h2", sizeof("h2") - 1)) {
    result = env->h2_string();
  } else if (alpn_proto_len == sizeof("http/1.1") - 1 &&
             0 == memcmp(alpn_proto, "http/1.1", sizeof("http/1.1") - 1)) {
    result = env->http_1_1_string();
  } else {
    result = OneByteString(env->isolate(), alpn_proto, alpn_proto_len);
  }

  args.GetReturnValue().Set(result);
}

void TLSWrap::WritesIssuedByPrevListenerDone(
    const FunctionCallbackInfo<Value>& args) {
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());

  Debug(w, "WritesIssuedByPrevListenerDone is called");
  w->flags_.has_active_write_issued_by_prev_listener = false;
  w->EncOut();  // resume all of our restrained writes
}

void TLSWrap::Cycle() {
  // With no loop to extend, cycling now would re-enter the SSL library.
  if (cycle_depth_ == 0 && in_ssl_library_call()) {
    Debug(this, "Deferring cycle requested from the SSL library's stack");
    ScheduleDeferredCycle();
    return;
  }

  // Prevent recursion
  if (++cycle_depth_ > 1)
    return;

  for (; cycle_depth_ > 0; cycle_depth_--) {
    ClearIn();
    ClearOut();
    // ClearOut() could defer a write/shutdown, so we ClearIn() again now
    // to avoid needing a second pass:
    ClearIn();
    FlushPendingShutdown();
    // EncIn() doesn't exist, it happens via stream listener callbacks.
    EncOut();
  }
}

#ifdef SSL_set_max_send_fragment
void TLSWrap::SetMaxSendFragment(const FunctionCallbackInfo<Value>& args) {
  CHECK(args.Length() >= 1 && args[0]->IsNumber());
  Environment* env = Environment::GetCurrent(args);
  TLSWrap* w;
  ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
  int val;
  if (args[0]->Int32Value(env->context()).To(&val)) {
    int32_t ret = SSL_set_max_send_fragment(w->ssl_.get(), val);
    args.GetReturnValue().Set(ret);
  }
}
#endif  // SSL_set_max_send_fragment

void TLSWrap::Initialize(
    Local<Object> target,
    Local<Value> unused,
    Local<Context> context,
    void* priv) {
  Environment* env = Environment::GetCurrent(context);
  Isolate* isolate = env->isolate();

  SetMethod(context, target, "wrap", TLSWrap::Wrap);
  SetMethod(context,
            target,
            "injectOpenSSLErrorForTesting",
            InjectOpenSSLErrorForTesting);
  SetMethodNoSideEffect(
      context, target, "hasOpenSSLErrorForTesting", HasOpenSSLErrorForTesting);

  NODE_DEFINE_CONSTANT(target, HAVE_SSL_TRACE);

  Local<FunctionTemplate> t = AsyncWrap::MakeLazilyInitializedJSTemplate(env);
  Local<String> tlsWrapString =
      FIXED_ONE_BYTE_STRING(env->isolate(), "TLSWrap");
  t->SetClassName(tlsWrapString);
  t->InstanceTemplate()->SetInternalFieldCount(TLSWrap::kInternalFieldCount);

  Local<FunctionTemplate> get_write_queue_size =
      FunctionTemplate::New(env->isolate(),
                            GetWriteQueueSize,
                            Local<Value>(),
                            Signature::New(env->isolate(), t));
  t->PrototypeTemplate()->SetAccessorProperty(
      env->write_queue_size_string(),
      get_write_queue_size,
      Local<FunctionTemplate>(),
      static_cast<PropertyAttribute>(ReadOnly | DontDelete));

  t->Inherit(AsyncWrap::GetConstructorTemplate(env));

  SetProtoMethod(isolate, t, "certCbDone", CertCbDone);
  SetProtoMethod(isolate, t, "clientHelloDone", ClientHelloDone);
  SetProtoMethod(isolate, t, "destroySSL", DestroySSL);
  SetProtoMethod(isolate, t, "enableCertCb", EnableCertCb);
  SetProtoMethod(isolate, t, "enableALPNCb", EnableALPNCb);
  SetProtoMethod(isolate, t, "enableKeylogCallback", EnableKeylogCallback);
  SetProtoMethod(isolate, t, "enableSessionCallbacks", EnableSessionCallbacks);
  SetProtoMethod(isolate, t, "enableTrace", EnableTrace);
  SetProtoMethod(isolate, t, "getServername", GetServername);
  SetProtoMethod(isolate, t, "loadSession", LoadSession);
  SetProtoMethod(isolate, t, "newSessionDone", NewSessionDone);
  SetProtoMethod(isolate, t, "receive", Receive);
  SetProtoMethod(isolate, t, "renegotiate", Renegotiate);
  SetProtoMethod(isolate, t, "requestOCSP", RequestOCSP);
  SetProtoMethod(isolate, t, "setALPNProtocols", SetALPNProtocols);
  SetProtoMethod(isolate, t, "setKeyCert", SetKeyCert);
  SetProtoMethod(isolate, t, "setOCSPResponse", SetOCSPResponse);
  SetProtoMethod(isolate, t, "setServername", SetServername);
  SetProtoMethod(isolate, t, "setSession", SetSession);
  SetProtoMethod(isolate, t, "setVerifyMode", SetVerifyMode);
  SetProtoMethod(isolate, t, "start", Start);
  SetProtoMethod(isolate,
                 t,
                 "writesIssuedByPrevListenerDone",
                 WritesIssuedByPrevListenerDone);

  SetProtoMethodNoSideEffect(
      isolate, t, "exportKeyingMaterial", ExportKeyingMaterial);
  SetProtoMethodNoSideEffect(isolate, t, "isSessionReused", IsSessionReused);
  SetProtoMethodNoSideEffect(
      isolate, t, "getALPNNegotiatedProtocol", GetALPNNegotiatedProto);
  SetProtoMethodNoSideEffect(isolate, t, "getCertificate", GetCertificate);
  SetProtoMethodNoSideEffect(
      isolate, t, "getX509Certificate", GetX509Certificate);
  SetProtoMethodNoSideEffect(isolate, t, "getCipher", GetCipher);
  SetProtoMethodNoSideEffect(
      isolate, t, "getEphemeralKeyInfo", GetEphemeralKeyInfo);
  SetProtoMethodNoSideEffect(isolate, t, "getFinished", GetFinished);
  SetProtoMethodNoSideEffect(
      isolate, t, "getPeerCertificate", GetPeerCertificate);
  SetProtoMethodNoSideEffect(
      isolate, t, "getPeerX509Certificate", GetPeerX509Certificate);
  SetProtoMethodNoSideEffect(
      isolate, t, "hasPeerCertificate", HasPeerCertificate);
  SetProtoMethodNoSideEffect(isolate, t, "getPeerFinished", GetPeerFinished);
  SetProtoMethodNoSideEffect(isolate, t, "getProtocol", GetProtocol);
  SetProtoMethodNoSideEffect(isolate, t, "getSession", GetSession);
  SetProtoMethodNoSideEffect(isolate, t, "getSharedSigalgs", GetSharedSigalgs);
  SetProtoMethodNoSideEffect(isolate, t, "getTLSTicket", GetTLSTicket);
  SetProtoMethodNoSideEffect(isolate, t, "verifyError", VerifyError);

#ifdef SSL_set_max_send_fragment
  SetProtoMethod(isolate, t, "setMaxSendFragment", SetMaxSendFragment);
#endif  // SSL_set_max_send_fragment

#ifndef OPENSSL_NO_PSK
  SetProtoMethod(isolate, t, "enablePskCallback", EnablePskCallback);
  SetProtoMethod(isolate, t, "setPskIdentityHint", SetPskIdentityHint);
#endif  // !OPENSSL_NO_PSK

  StreamBase::AddMethods(env, t);

  Local<Function> fn = t->GetFunction(env->context()).ToLocalChecked();

  env->set_tls_wrap_constructor_function(fn);

  target->Set(env->context(), tlsWrapString, fn).Check();
}

void TLSWrap::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
  registry->Register(TLSWrap::Wrap);
  registry->Register(GetWriteQueueSize);
  registry->Register(InjectOpenSSLErrorForTesting);
  registry->Register(HasOpenSSLErrorForTesting);

  registry->Register(CertCbDone);
  registry->Register(ClientHelloDone);
  registry->Register(DestroySSL);
  registry->Register(EnableCertCb);
  registry->Register(EnableALPNCb);
  registry->Register(EnableKeylogCallback);
  registry->Register(EnableSessionCallbacks);
  registry->Register(EnableTrace);
  registry->Register(GetServername);
  registry->Register(LoadSession);
  registry->Register(NewSessionDone);
  registry->Register(Receive);
  registry->Register(Renegotiate);
  registry->Register(RequestOCSP);
  registry->Register(SetALPNProtocols);
  registry->Register(SetOCSPResponse);
  registry->Register(SetServername);
  registry->Register(SetSession);
  registry->Register(SetVerifyMode);
  registry->Register(Start);
  registry->Register(ExportKeyingMaterial);
  registry->Register(IsSessionReused);
  registry->Register(GetALPNNegotiatedProto);
  registry->Register(GetCertificate);
  registry->Register(GetX509Certificate);
  registry->Register(GetCipher);
  registry->Register(GetEphemeralKeyInfo);
  registry->Register(GetFinished);
  registry->Register(GetPeerCertificate);
  registry->Register(GetPeerX509Certificate);
  registry->Register(HasPeerCertificate);
  registry->Register(GetPeerFinished);
  registry->Register(GetProtocol);
  registry->Register(GetSession);
  registry->Register(GetSharedSigalgs);
  registry->Register(GetTLSTicket);
  registry->Register(VerifyError);
  registry->Register(WritesIssuedByPrevListenerDone);

#ifdef SSL_set_max_send_fragment
  registry->Register(SetMaxSendFragment);
#endif  // SSL_set_max_send_fragment

#ifndef OPENSSL_NO_PSK
  registry->Register(EnablePskCallback);
  registry->Register(SetPskIdentityHint);
#endif  // !OPENSSL_NO_PSK
}

}  // namespace crypto
}  // namespace node

NODE_BINDING_CONTEXT_AWARE_INTERNAL(tls_wrap, node::crypto::TLSWrap::Initialize)
NODE_BINDING_EXTERNAL_REFERENCE(
    tls_wrap, node::crypto::TLSWrap::RegisterExternalReferences)