@ 261106.1
current
#include "dtls_context.h"
#include "dtls_session.h"

#if HAVE_OPENSSL && HAVE_DTLS

#include <base_object-inl.h>
#include <crypto/crypto_bio.h>
#include <crypto/crypto_client_hello.h>
#include <crypto/crypto_context.h>
#include <crypto/crypto_tls_certificates.h>
#include <crypto/crypto_util.h>
#include <env-inl.h>
#include <memory_tracker-inl.h>
#include <node_errors.h>
#include <node_sockaddr-inl.h>
#include <util-inl.h>
#include <uv.h>

#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/srtp.h>
#include <openssl/ssl.h>

#include <cstring>

namespace node {

using v8::Array;
using v8::ArrayBufferView;
using v8::Context;
using v8::Exception;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::HandleScope;
using v8::Int32;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::TryCatch;
using v8::Undefined;
using v8::Value;

namespace dtls {

namespace {
// The cookie secret is 32 bytes (256 bits), generated once per context and
// not rotated. Rotation would need the previous secret kept alive to validate
// cookies already in flight, which is the job the time window below already
// does: a cookie stops verifying once its window passes, whatever the secret.
// A fresh secret is generated on every context, so a restart invalidates
// outstanding cookies too.
constexpr size_t kCookieSecretLen = 32;
// Cookies are bound to a coarse time window so they expire. A cookie is
// accepted for the window it was minted in and the immediately preceding one,
// giving ~30-60s of validity -- ample for the cookie exchange while bounding
// how long a captured cookie can be replayed.
constexpr uint64_t kCookieWindowNs = 30ull * 1000 * 1000 * 1000;
}  // namespace

DTLSContext::DTLSContext(Environment* env,
                         Local<Object> wrap,
                         ncrypto::SSLCtxPointer ctx,
                         bool is_server)
    : BaseObject(env, wrap),
      ctx_(std::move(ctx)),
      is_server_(is_server),
      cookie_secret_(kCookieSecretLen) {
  MakeWeak();

  // Generate random cookie secret for HMAC-based cookie generation.
  CHECK_EQ(RAND_bytes(cookie_secret_.data(), kCookieSecretLen), 1);

  // Cookie generate/verify callbacks are registered on the SSL_CTX so they
  // are inherited by all SSL objects created from it. However, we do NOT set
  // SSL_OP_COOKIE_EXCHANGE on the context -- DTLSv1_listen() sets this option
  // automatically on the per-SSL object when it runs (see d1_lib.c:804 in
  // OpenSSL). This is important: if SSL_OP_COOKIE_EXCHANGE were set on the
  // context, any SSL created from it would attempt a fresh cookie exchange,
  // which is wrong for session SSLs that have already completed cookie
  // verification via DTLSv1_listen().
  SSL_CTX_set_cookie_generate_cb(ctx_.get(), CookieGenerateCallback);
  SSL_CTX_set_cookie_verify_cb(ctx_.get(), CookieVerifyCallback);

  // Keylog is a per-SSL_CTX setting, so register it once here rather than
  // re-registering it from every session constructor. The callback is inert
  // unless the session it resolves to has a keylog listener, so installing it
  // unconditionally costs nothing and no secret reaches the JS heap uninvited.
  SSL_CTX_set_keylog_callback(ctx_.get(), DTLSSession::SSLKeylogCallback);

  // Store pointer to this context in the SSL_CTX app data for callbacks.
  SSL_CTX_set_app_data(ctx_.get(), this);
}

void DTLSContext::MemoryInfo(MemoryTracker* tracker) const {
  tracker->TrackFieldWithSize("cookie_secret", cookie_secret_.size());
  tracker->TrackFieldWithSize("alpn_protos", alpn_protos_.size());
  size_t psk_size = 0;
  for (const auto& [identity, key] : psk_identities_) {
    psk_size += identity.size() + key.size();
  }
  tracker->TrackFieldWithSize("psk_identities", psk_size);
  tracker->TrackFieldWithSize("psk_client_key", psk_client_key_.size());
  // Strong references to JavaScript functions held by a weak object. A
  // callback that closes over the server, which is the usual way to write
  // one, is a cycle through C++ that a heap snapshot cannot otherwise show.
  tracker->TrackField("psk_callback", psk_callback_);
  tracker->TrackField("sni_callback", sni_callback_);
  tracker->TrackField("sni_contexts", sni_contexts_);
}

// SSL ex_data slot holding the DTLSContext an SSL was created from, for the
// PSK callbacks, which unlike the ALPN and SNI ones have no argument of their
// own.
//
// On the SSL and not the SSL_CTX. SSL_set_SSL_CTX(), which is how SNI selects
// an identity, reassigns ssl->ctx but does not re-copy the PSK callbacks --
// those were installed on the SSL by SSL_new() and stay. Looking the context
// up through SSL_get_SSL_CTX() therefore found whichever context SNI had
// switched to, which is not the one whose PSK configuration is in force and
// is usually not configured for PSK at all.
static int PSKContextIndex() {
  static const int index =
      SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
  return index;
}

void DTLSContext::BindToSSL(SSL* ssl) {
  SSL_set_ex_data(ssl, PSKContextIndex(), this);
}

DTLSContext* DTLSContext::FromSSL(SSL* ssl) {
  return static_cast<DTLSContext*>(SSL_get_ex_data(ssl, PSKContextIndex()));
}

bool DTLSContext::HasInstance(Environment* env, Local<Value> value) {
  return GetConstructorTemplate(env)->HasInstance(value);
}

Local<FunctionTemplate> DTLSContext::GetConstructorTemplate(Environment* env) {
  auto tmpl = env->dtls_context_constructor_template();
  if (tmpl.IsEmpty()) {
    Isolate* isolate = env->isolate();
    tmpl = NewFunctionTemplate(isolate, New);
    tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "DTLSContext"));
    tmpl->InstanceTemplate()->SetInternalFieldCount(
        BaseObject::kInternalFieldCount);

    SetProtoMethod(isolate, tmpl, "setCert", SetCert);
    SetProtoMethod(isolate, tmpl, "setKey", SetKey);
    SetProtoMethod(isolate, tmpl, "addCACert", AddCACert);
    SetProtoMethod(isolate, tmpl, "setCiphers", SetCiphers);
    SetProtoMethod(isolate, tmpl, "setALPN", SetALPN);
    SetProtoMethod(isolate, tmpl, "setSRTP", SetSRTP);
    SetProtoMethod(isolate, tmpl, "setVerifyMode", SetVerifyMode);
    SetProtoMethod(isolate, tmpl, "loadDefaultCAs", LoadDefaultCAs);
    SetProtoMethod(isolate, tmpl, "setECDHCurve", SetECDHCurve);
    SetProtoMethod(isolate, tmpl, "setSessionIdContext", SetSessionIdContext);
    SetProtoMethod(isolate, tmpl, "setSNIContexts", SetSNIContexts);
    SetProtoMethod(isolate, tmpl, "setTicketKeys", SetTicketKeys);
    SetProtoMethod(isolate, tmpl, "setPSK", SetPSK);

    env->set_dtls_context_constructor_template(tmpl);
  }
  return tmpl;
}

void DTLSContext::InitPerContext(Local<Object> target,
                                 Local<Context> context,
                                 Environment* env) {
  SetConstructorFunction(context,
                         target,
                         "DTLSContext",
                         GetConstructorTemplate(env),
                         SetConstructorFunctionFlag::NONE);
}

void DTLSContext::RegisterExternalReferences(
    ExternalReferenceRegistry* registry) {
  registry->Register(New);
  registry->Register(SetCert);
  registry->Register(SetKey);
  registry->Register(AddCACert);
  registry->Register(SetCiphers);
  registry->Register(SetALPN);
  registry->Register(SetSRTP);
  registry->Register(SetVerifyMode);
  registry->Register(LoadDefaultCAs);
  registry->Register(SetECDHCurve);
  registry->Register(SetSessionIdContext);
  registry->Register(SetSNIContexts);
  registry->Register(SetTicketKeys);
  registry->Register(SetPSK);
}

// new DTLSContext(isServer)
void DTLSContext::New(const FunctionCallbackInfo<Value>& args) {
  Environment* env = Environment::GetCurrent(args);
  CHECK(args.IsConstructCall());

  bool is_server = args[0]->IsTrue();

  const SSL_METHOD* method;
  if (is_server) {
    method = DTLS_server_method();
  } else {
    method = DTLS_client_method();
  }

  ncrypto::SSLCtxPointer ctx(SSL_CTX_new(method));
  if (!ctx) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                             "Failed to create DTLS SSL_CTX");
  }

  // DTLS builds its SSL_CTX here instead of through SecureContext, so the
  // chain verification hook shared with TLS has to be installed explicitly.
  crypto::InstallCertVerifyCallback(ctx.get());

  // Default to DTLS 1.2 only. DTLS 1.0 (based on TLS 1.1) is deprecated
  // by RFC 8996 and lacks AEAD cipher suites.
  //
  // Checked because the whole point is to refuse DTLS 1.0. An OpenSSL built
  // without DTLS 1.2 would fail these and, unchecked, leave a context whose
  // floor is whatever the build allows -- the deprecated version this is
  // meant to exclude.
  if (SSL_CTX_set_min_proto_version(ctx.get(), DTLS1_2_VERSION) != 1 ||
      SSL_CTX_set_max_proto_version(ctx.get(), DTLS1_2_VERSION) != 1) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(
        env, "Failed to restrict the DTLS version to 1.2");
  }

  // Disable OpenSSL's MTU querying (we manage MTU manually).
  SSL_CTX_set_options(ctx.get(), SSL_OP_NO_QUERY_MTU);

  // Name the workarounds rather than taking SSL_OP_ALL. Its membership is not
  // stable -- it has held SSL_OP_LEGACY_SERVER_CONNECT in the past and does
  // not now -- so using the macro means silently inheriting whatever a future
  // OpenSSL decides belongs in it.
  //
  // All four of its current members are TLS-specific and inert under DTLS 1.2,
  // which is the only version this supports. They are kept so that behaviour
  // is unchanged from taking SSL_OP_ALL, not because any is known to be
  // needed:
  //
  //   DONT_INSERT_EMPTY_FRAGMENTS  the 1/n-1 split for CBC in TLS 1.0; DTLS
  //                                1.2 has explicit IVs and does not do it
  //   TLSEXT_PADDING               pads ClientHello for an F5 bug
  //   CRYPTOPRO_TLSEXT_BUG         GOST client workaround
  //   SAFARI_ECDHE_ECDSA_BUG       Safari on OS X 10.8
  SSL_CTX_set_options(ctx.get(),
                      SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS |
                          SSL_OP_TLSEXT_PADDING | SSL_OP_CRYPTOPRO_TLSEXT_BUG |
                          SSL_OP_SAFARI_ECDHE_ECDSA_BUG);

  if (is_server) {
    // NOTE: SSL_OP_COOKIE_EXCHANGE must not be set here, on the context. It
    // would then be inherited by every SSL the context creates, including the
    // session SSLs built by CreateFromSSL() for peers that have *already*
    // completed the cookie exchange, which would leave them waiting for a
    // second one. DTLSv1_listen() sets the option on the individual SSL it is
    // given (d1_lib.c:804), which is the correct scope.

    // Enable session caching for session resumption.
    //
    // SSL_SESS_CACHE_NO_INTERNAL, matching node:tls and the client branch
    // below. The previous mode enabled OpenSSL's internal cache and then set
    // NO_AUTO_CLEAR, which is only coherent as node:tls uses it -- alongside
    // NO_INTERNAL, where there is no internal cache for the auto-clear to
    // walk. Enabled-plus-NO_AUTO_CLEAR instead meant every accepted session
    // was retained, with its master secret, for the 7200 second default
    // timeout and past it.
    SSL_CTX_set_session_cache_mode(
        ctx.get(), SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL);
  } else {
    // Client session caching for resumption.
    SSL_CTX_set_session_cache_mode(
        ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
  }

  // NOTE: We do NOT call SSL_CTX_set_default_verify_paths() here.
  // CA loading is handled in JS: if the user provides custom CAs, only
  // those are loaded (via addCACert). Otherwise, system default CAs are
  // loaded via loadDefaultCAs(). This matches Node.js TLS behavior.

  new DTLSContext(env, args.This(), std::move(ctx), is_server);
}

void DTLSContext::SetCert(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  if (!args[0]->IsString()) {
    return THROW_ERR_INVALID_ARG_TYPE(env, "cert must be a string (PEM)");
  }

  Utf8Value cert_pem(env->isolate(), args[0]);

  auto bio = crypto::NodeBIO::NewFixed(*cert_pem, cert_pem.length());
  if (!bio) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to create BIO");
  }

  ncrypto::X509Pointer cert;
  ncrypto::X509Pointer issuer;
  if (crypto::SSL_CTX_use_certificate_chain(
          ctx->ctx_.get(), std::move(bio), &cert, &issuer) != 1) {
    return crypto::ThrowCryptoError(env, ERR_get_error(), "PEM_read_bio_X509");
  }
}

void DTLSContext::SetKey(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  if (!args[0]->IsString()) {
    return THROW_ERR_INVALID_ARG_TYPE(env, "key must be a string (PEM)");
  }

  Utf8Value key_pem(env->isolate(), args[0]);

  auto bio = crypto::NodeBIO::NewFixed(*key_pem, key_pem.length());
  if (!bio) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to create BIO");
  }

  // Optional passphrase for an encrypted key. Absent is not the same as
  // empty: UsePrivateKey() substitutes an empty ByteSource when given
  // nullptr, and OpenSSL reports a decryption failure either way.
  crypto::ByteSource passphrase;
  if (args[1]->IsString()) {
    passphrase = crypto::ByteSource::FromString(env, args[1].As<String>());
  }

  switch (crypto::UsePrivateKey(ctx->ctx_.get(), bio, &passphrase)) {
    case crypto::PrivateKeyResult::kSuccess:
      break;
    case crypto::PrivateKeyResult::kParseError:
      return crypto::ThrowCryptoError(
          env, ERR_get_error(), "PEM_read_bio_PrivateKey");
    case crypto::PrivateKeyResult::kApplyError:
      return crypto::ThrowCryptoError(
          env, ERR_get_error(), "SSL_CTX_use_PrivateKey");
  }

  // Verify that the private key matches the certificate.
  if (SSL_CTX_check_private_key(ctx->ctx_.get()) != 1) {
    return crypto::ThrowCryptoError(
        env, ERR_get_error(), "Private key does not match certificate");
  }
}

void DTLSContext::AddCACert(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  if (!args[0]->IsString()) {
    return THROW_ERR_INVALID_ARG_TYPE(env, "ca must be a string (PEM)");
  }

  Utf8Value ca_pem(env->isolate(), args[0]);

  auto bio = crypto::NodeBIO::NewFixed(*ca_pem, ca_pem.length());
  if (!bio) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to create BIO");
  }

  ncrypto::ClearErrorOnReturn clear_error_on_return;
  if (crypto::AddCACertificates(env, ctx->ctx_.get(), bio) == 0) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(
        env, "No CA certificates found in PEM data");
  }
}

void DTLSContext::SetCiphers(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  if (!args[0]->IsString()) {
    return THROW_ERR_INVALID_ARG_TYPE(env, "ciphers must be a string");
  }

  Utf8Value ciphers(env->isolate(), args[0]);
  if (SSL_CTX_set_cipher_list(ctx->ctx_.get(), *ciphers) != 1) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
                                             "SSL_CTX_set_cipher_list failed");
  }
}

void DTLSContext::SetALPN(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  if (!Buffer::HasInstance(args[0])) {
    return THROW_ERR_INVALID_ARG_TYPE(env, "alpnProtocols must be a Buffer");
  }

  const uint8_t* data = reinterpret_cast<const uint8_t*>(Buffer::Data(args[0]));
  size_t len = Buffer::Length(args[0]);

  if (ctx->is_server_) {
    // Server: store protocols for the selection callback.
    ctx->alpn_protos_.assign(data, data + len);
    SSL_CTX_set_alpn_select_cb(ctx->ctx_.get(), ALPNSelectCallback, ctx);
  } else {
    // Client: advertise protocols to the server. Returns 0 on success.
    if (SSL_CTX_set_alpn_protos(ctx->ctx_.get(), data, len) != 0) {
      return THROW_ERR_CRYPTO_OPERATION_FAILED(
          env, "SSL_CTX_set_alpn_protos failed");
    }
  }
}

void DTLSContext::SetSRTP(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  if (!args[0]->IsString()) {
    return THROW_ERR_INVALID_ARG_TYPE(env, "srtpProfiles must be a string");
  }

  Utf8Value profiles(env->isolate(), args[0]);
  if (SSL_CTX_set_tlsext_use_srtp(ctx->ctx_.get(), *profiles) != 0) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(
        env, "SSL_CTX_set_tlsext_use_srtp failed");
  }
}

namespace {
// Installed only where the application has taken responsibility for the
// authorization decision itself: a server that asked for a client certificate
// but disabled rejection. Returning 1 unconditionally keeps the handshake
// going; the verification result is still recorded and remains reachable
// through SSL_get_verify_result(), which is what session.authorized reports.
//
// Everywhere else the callback is left null so OpenSSL enforces, and a peer
// that fails verification receives a proper alert.
int AllowUnauthorizedCallback(int preverify_ok, X509_STORE_CTX* ctx) {
  return 1;
}
}  // namespace

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

  CHECK(args[0]->IsInt32());
  CHECK(args[1]->IsBoolean());

  int mode = args[0].As<Int32>()->Value();
  bool defer_to_application = args[1]->IsTrue();

  SSL_CTX_set_verify(
      ctx->ctx_.get(),
      mode,
      defer_to_application ? AllowUnauthorizedCallback : nullptr);
}

void DTLSContext::LoadDefaultCAs(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());

  // This populates the verification store only, not the client-CA list sent
  // in a CertificateRequest. That is deliberate. The bundled root store holds
  // on the order of 150 certificates, and advertising all of their
  // distinguished names would produce a CertificateRequest of tens of
  // kilobytes -- which, over a datagram transport, then has to be fragmented
  // across many packets, any of which can be lost. node:tls does the same:
  // SSL_CTX_add_client_CA() is called for explicitly supplied chains and PFX
  // bundles, never from UseDefaultRootCertStore().
  crypto::UseDefaultRootCertStore(ctx->env(), ctx->ctx_.get());
}

// Scopes cached sessions to this server. OpenSSL refuses to resume a session
// whose id context differs from the one on the accepting SSL, which is what
// stops a session established under one configuration being resumed under
// another. It matters most when client certificates are in use.
void DTLSContext::SetSessionIdContext(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  CHECK(args[0]->IsString());
  Utf8Value sid_ctx(env->isolate(), args[0]);

  if (!SSL_CTX_set_session_id_context(
          ctx->ctx_.get(),
          reinterpret_cast<const unsigned char*>(*sid_ctx),
          sid_ctx.length())) {
    return THROW_ERR_CRYPTO_OPERATION_FAILED(
        env, "Failed to set session id context");
  }
}

// The wildcard key: the identity used when no host name matches.
constexpr const char* kSNIWildcard = "*";

// Ask JavaScript which identity to serve. Synchronous, like the PSK
// callbacks and for the same reason: suspending a DTLS handshake to await an
// answer would mean driving SSL_ERROR_WANT_X509_LOOKUP back through Cycle(),
// and a datagram peer is retransmitting while it waits.
DTLSContext* DTLSContext::SelectSNIContextFromCallback(SSL* ssl,
                                                       const char* servername) {
  HandleScope scope(env()->isolate());
  Context::Scope context_scope(env()->context());

  // Called rather than MakeCallback'd: this runs inside SSL_do_handshake(),
  // and MakeCallback drains the tick queue as its scope exits, which would
  // run user code in the middle of OpenSSL's state machine. See
  // PSKServerCallback.
  TryCatch try_catch(env()->isolate());

  // A client that sent no SNI extension still reaches here, and the callback
  // is told so rather than being given an empty string it cannot distinguish
  // from one.
  Local<Value> name = Undefined(env()->isolate());
  if (servername != nullptr) {
    Local<Value> name_str;
    if (!ToV8Value(env()->context(), servername).ToLocal(&name_str)) {
      ReportCallbackError(ssl, &try_catch);
      return nullptr;
    }
    // Reject a name that did not survive the round trip through UTF-8, so a
    // peer cannot offer bytes that reach JavaScript as replacement characters
    // and match a host it was never given.
    Utf8Value round_trip(env()->isolate(), name_str);
    if (round_trip != servername) return nullptr;
    name = name_str;
  }

  Local<Value> argv[] = {name};
  Local<Value> ret;
  if (!sni_callback_.Get(env()->isolate())
           ->Call(env()->context(), object(), 1, argv)
           .ToLocal(&ret)) {
    ReportCallbackError(ssl, &try_catch);
    return nullptr;
  }

  // Declining is not an error: it means this name is not served.
  if (!HasInstance(env(), ret)) return nullptr;

  DTLSContext* chosen;
  ASSIGN_OR_RETURN_UNWRAP(&chosen, ret.As<Object>(), nullptr);

  // Hold it. A context the callback built has no other owner once it
  // returns, and the handshake is not finished with it: SSL_set_SSL_CTX()
  // reassigns ssl->ctx, so a later callback resolving its configuration
  // through SSL_get_SSL_CTX() lands on this context and not on the one the
  // endpoint holds. Verified by pointer -- the PSK callback on a session
  // whose name was chosen here reports the chosen context, not the base.
  DTLSSession* session = static_cast<DTLSSession*>(SSL_get_app_data(ssl));
  if (session != nullptr) session->SetSNIContext(chosen);

  return chosen;
}

int DTLSContext::SNISelectCallback(SSL* ssl, int* ad, void* arg) {
  auto* ctx = static_cast<DTLSContext*>(arg);

  const char* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);

  auto it = servername != nullptr ? ctx->sni_contexts_.find(servername)
                                  : ctx->sni_contexts_.end();
  if (it == ctx->sni_contexts_.end()) {
    it = ctx->sni_contexts_.find(kSNIWildcard);
  }

  DTLSContext* chosen =
      it != ctx->sni_contexts_.end() ? it->second.get() : nullptr;

  // The map answers first, so a configuration using only the map still runs
  // no JavaScript inside the handshake. The callback is reached only when it
  // has nothing to say.
  if (chosen == nullptr && !ctx->sni_callback_.IsEmpty()) {
    chosen = ctx->SelectSNIContextFromCallback(ssl, servername);
  }

  if (chosen == nullptr) {
    // Configuring sni without a "*" entry, or a callback that declines, is a
    // statement that only the named hosts are served, so refuse anything
    // else rather than falling back to the endpoint's own certificate.
    *ad = SSL_AD_UNRECOGNIZED_NAME;
    return SSL_TLSEXT_ERR_ALERT_FATAL;
  }

  SSL_CTX* selected = chosen->ssl_ctx();
  if (SSL_set_SSL_CTX(ssl, selected) != selected) {
    *ad = SSL_AD_INTERNAL_ERROR;
    return SSL_TLSEXT_ERR_ALERT_FATAL;
  }

  // Nothing further is needed. node:tls follows SSL_set_SSL_CTX() with
  // SSL_set1_verify_cert_store() and a duplicated client-CA list, which
  // suggests the switch leaves verification behind; it does not.
  // SSL_set_SSL_CTX() reassigns ssl->ctx (ssl_lib.c), and both the
  // verification store and the client-CA list are read through it, so they
  // move with the certificate. Adding the calls anyway changed no observed
  // behaviour: an SNI identity trusting only ca2 rejects a ca1 client
  // either way.
  //
  // Verify mode is deliberately not touched. requestCert and
  // rejectUnauthorized belong to the endpoint rather than the identity and
  // are configured once on its own context.
  return SSL_TLSEXT_ERR_OK;
}

void DTLSContext::SetSNIContexts(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  CHECK(args[0]->IsArray());
  Local<Array> entries = args[0].As<Array>();

  // Built to one side and swapped in, so a failure part way through cannot
  // leave the context serving a half-populated map.
  std::unordered_map<std::string, BaseObjectWeakPtr<DTLSContext>> next;

  uint32_t length = entries->Length();
  for (uint32_t i = 0; i < length; i += 2) {
    Local<Value> host;
    Local<Value> value;
    if (!entries->Get(env->context(), i).ToLocal(&host) ||
        !entries->Get(env->context(), i + 1).ToLocal(&value)) {
      return;
    }

    CHECK(host->IsString());
    CHECK(DTLSContext::HasInstance(env, value));

    DTLSContext* entry;
    ASSIGN_OR_RETURN_UNWRAP(&entry, value.As<Object>());

    Utf8Value hostname(env->isolate(), host);
    next[*hostname] = BaseObjectWeakPtr<DTLSContext>(entry);
  }

  ctx->sni_contexts_ = std::move(next);

  // Assigned either way. Only ever setting it meant a context reconfigured
  // with a map and no callback kept the callback it was given before, which
  // for SNI is a fail-open: a map with no '*' entry is meant to refuse an
  // unmatched name, and a leftover callback answers it instead.
  if (args[1]->IsFunction()) {
    ctx->sni_callback_.Reset(env->isolate(), args[1].As<Function>());
  } else {
    ctx->sni_callback_.Reset();
  }

  if (ctx->sni_contexts_.empty() && ctx->sni_callback_.IsEmpty()) {
    SSL_CTX_set_tlsext_servername_callback(ctx->ctx_.get(), nullptr);
    SSL_CTX_set_tlsext_servername_arg(ctx->ctx_.get(), nullptr);
    return;
  }

  SSL_CTX_set_tlsext_servername_callback(ctx->ctx_.get(), SNISelectCallback);
  SSL_CTX_set_tlsext_servername_arg(ctx->ctx_.get(), ctx);
}

// Hand an exception thrown by one of the handshake callbacks to the session
// it belongs to, so it fails that handshake instead of reaching the process
// as an uncaughtException. The exception must not still be pending when
// control returns to OpenSSL, which goes on to build an alert and unwind
// through Cycle()'s error path.
void DTLSContext::ReportPSKError(SSL* ssl, std::string_view message) {
  DTLSSession* session = static_cast<DTLSSession*>(SSL_get_app_data(ssl));
  if (session == nullptr) return;

  Environment* env = session->env();
  HandleScope scope(env->isolate());
  Local<Value> str;
  if (!ToV8Value(env->context(), message).ToLocal(&str)) return;
  session->SetPendingError(Exception::TypeError(str.As<String>()));
}

void DTLSContext::ReportCallbackError(SSL* ssl, TryCatch* try_catch) {
  if (!try_catch->HasCaught() || try_catch->HasTerminated()) return;

  DTLSSession* session = static_cast<DTLSSession*>(SSL_get_app_data(ssl));
  Local<Value> exception = try_catch->Exception();

  // Clear before reporting: EmitCallback runs JavaScript, which cannot be
  // entered with an exception still pending.
  try_catch->Reset();

  if (session != nullptr && !exception.IsEmpty()) {
    session->SetPendingError(exception);
  }
}

unsigned int DTLSContext::PSKServerCallback(SSL* ssl,
                                            const char* identity,
                                            unsigned char* psk,
                                            unsigned int max_psk_len) {
  DTLSContext* ctx = FromSSL(ssl);
  if (ctx == nullptr || identity == nullptr) return 0;

  // The map first, so a configuration that uses only the map never runs
  // JavaScript inside the handshake.
  auto it = ctx->psk_identities_.find(identity);
  if (it != ctx->psk_identities_.end()) {
    if (it->second.size() > max_psk_len) return 0;
    memcpy(psk, it->second.data(), it->second.size());
    return static_cast<unsigned int>(it->second.size());
  }

  if (ctx->psk_callback_.IsEmpty()) return 0;

  Environment* env = ctx->env();
  HandleScope scope(env->isolate());
  Context::Scope context_scope(env->context());

  // Called rather than MakeCallback'd, deliberately. MakeCallback drains the
  // tick queue as its scope exits, and this runs inside SSL_do_handshake():
  // measured, a nextTick scheduled by the callback runs before OpenSSL's
  // state machine has finished the transition it is in the middle of. The
  // module's other MakeCallback uses all fire at safe points in Cycle(),
  // after SSL_do_handshake() has returned. node:tls accepts that risk here;
  // this does not, having a reentrancy guard that exists because of it.
  //
  // The cost is that exceptions are ours to handle: a pending one would
  // otherwise be carried back into OpenSSL and surface as an
  // uncaughtException, killing the process over one bad callback.
  TryCatch try_catch(env->isolate());

  Local<Value> identity_str;
  if (!ToV8Value(env->context(), identity).ToLocal(&identity_str)) {
    ctx->ReportCallbackError(ssl, &try_catch);
    return 0;
  }

  // Reject an identity that did not survive the round trip through UTF-8:
  // otherwise a peer could offer bytes that reach JavaScript as replacement
  // characters and match an identity it was never given. node:tls does the
  // same check.
  Utf8Value round_trip(env->isolate(), identity_str);
  if (round_trip != identity) return 0;

  Local<Value> argv[] = {identity_str};
  Local<Value> ret;
  if (!ctx->psk_callback_.Get(env->isolate())
           ->Call(env->context(), ctx->object(), 1, argv)
           .ToLocal(&ret)) {
    ctx->ReportCallbackError(ssl, &try_catch);
    return 0;
  }

  if (!ret->IsArrayBufferView()) return 0;
  ArrayBufferViewContents<unsigned char> key(ret.As<ArrayBufferView>());
  if (key.length() > max_psk_len) return 0;  // measurement: allow empty

  memcpy(psk, key.data(), key.length());
  return static_cast<unsigned int>(key.length());
}

unsigned int DTLSContext::PSKClientCallback(SSL* ssl,
                                            const char* hint,
                                            char* identity,
                                            unsigned int max_identity_len,
                                            unsigned char* psk,
                                            unsigned int max_psk_len) {
  DTLSContext* ctx = FromSSL(ssl);
  if (ctx == nullptr) return 0;

  std::string use_identity = ctx->psk_client_identity_;
  std::vector<unsigned char> use_key = ctx->psk_client_key_;

  if (!ctx->psk_callback_.IsEmpty()) {
    Environment* env = ctx->env();
    HandleScope scope(env->isolate());
    Context::Scope context_scope(env->context());
    // See PSKServerCallback for why this is Call() and not MakeCallback().
    TryCatch try_catch(env->isolate());

    // The hint is optional and frequently absent (RFC 4279 section 5.2).
    Local<Value> hint_value = Undefined(env->isolate());
    if (hint != nullptr) {
      if (!ToV8Value(env->context(), hint).ToLocal(&hint_value)) {
        ctx->ReportCallbackError(ssl, &try_catch);
        return 0;
      }
    }

    Local<Value> argv[] = {hint_value};
    Local<Value> ret;
    if (!ctx->psk_callback_.Get(env->isolate())
             ->Call(env->context(), ctx->object(), 1, argv)
             .ToLocal(&ret)) {
      ctx->ReportCallbackError(ssl, &try_catch);
      return 0;
    }
    if (!ret->IsObject()) {
      ReportPSKError(ssl, "The psk callback must return an object");
      return 0;
    }

    // Reading these can throw: the object is whatever the callback returned,
    // so a getter or a Proxy trap runs here. Report it rather than returning
    // with it caught but unreported, which loses the error the caller threw
    // and leaves it to unwind into OpenSSL when the TryCatch is destroyed.
    Local<Object> obj = ret.As<Object>();
    Local<Value> id_val;
    Local<Value> key_val;
    if (!obj->Get(env->context(),
                  FIXED_ONE_BYTE_STRING(env->isolate(), "identity"))
             .ToLocal(&id_val) ||
        !obj->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "key"))
             .ToLocal(&key_val)) {
      ctx->ReportCallbackError(ssl, &try_catch);
      return 0;
    }
    if (!id_val->IsString() || !key_val->IsArrayBufferView()) {
      ReportPSKError(ssl,
                     "The psk callback must return an object with a string "
                     "'identity' and a TypedArray or DataView 'key'");
      return 0;
    }

    Utf8Value id(env->isolate(), id_val);
    ArrayBufferViewContents<unsigned char> key(key_val.As<ArrayBufferView>());
    use_identity.assign(*id, id.length());
    use_key.assign(key.data(), key.data() + key.length());
  }

  // Returning 0 is how OpenSSL is told there is no PSK, so each of these
  // would otherwise reach the caller as a handshake that failed for no stated
  // reason. An empty identity or key is the one case that really is "no PSK".
  if (use_identity.empty() || use_key.empty()) return 0;
  // The identity is written as a C string, so it needs room for the
  // terminator OpenSSL expects.
  if (use_identity.size() + 1 > max_identity_len) {
    ReportPSKError(ssl, "The psk identity is too long for this handshake");
    return 0;
  }
  if (use_key.size() > max_psk_len) {
    ReportPSKError(ssl, "The psk key is too long for this handshake");
    return 0;
  }

  memcpy(identity, use_identity.c_str(), use_identity.size() + 1);
  memcpy(psk, use_key.data(), use_key.size());
  return static_cast<unsigned int>(use_key.size());
}

void DTLSContext::SetTicketKeys(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());

  CHECK(args[0]->IsArrayBufferView());
  ArrayBufferViewContents<unsigned char> buf(args[0].As<ArrayBufferView>());

  // Key name, HMAC key and AES key concatenated. node:tls installs its own
  // callback and defines a 48-byte layout of its own; this uses OpenSSL's
  // native keys instead, which are longer -- a 32-byte HMAC key and AES-256
  // rather than 16 and AES-128 -- and need no callback. The length is asked
  // for rather than hardcoded, since it comes from a private OpenSSL header.
  long expected =  // NOLINT(runtime/int) -- SSL_CTX_ctrl returns long
      SSL_CTX_set_tlsext_ticket_keys(ctx->ctx_.get(), nullptr, 0);

  if (static_cast<long>(buf.length()) != expected) {  // NOLINT(runtime/int)
    return THROW_ERR_INVALID_ARG_VALUE(
        ctx->env(), "options.ticketKeys must be exactly %ld bytes", expected);
  }

  if (SSL_CTX_set_tlsext_ticket_keys(ctx->ctx_.get(),
                                     const_cast<unsigned char*>(buf.data()),
                                     buf.length()) != 1) {
    THROW_ERR_CRYPTO_OPERATION_FAILED(ctx->env(),
                                      "Failed to set session ticket keys");
  }
}

// setPSK(entries, hint, clientIdentity, clientKey, callback)
//
// |entries| is a flattened [identity, key, ...] array for a server; the
// client fields carry the single identity a client presents. Any of them may
// be absent: a server may rely entirely on the callback, and a client may
// rely on it too.
void DTLSContext::SetPSK(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  if (args[0]->IsArray()) {
    Local<Array> entries = args[0].As<Array>();
    std::unordered_map<std::string, std::vector<unsigned char>> next;
    uint32_t length = entries->Length();
    for (uint32_t i = 0; i < length; i += 2) {
      Local<Value> identity;
      Local<Value> key;
      if (!entries->Get(env->context(), i).ToLocal(&identity) ||
          !entries->Get(env->context(), i + 1).ToLocal(&key)) {
        return;
      }
      CHECK(identity->IsString());
      CHECK(key->IsArrayBufferView());
      Utf8Value id(env->isolate(), identity);
      ArrayBufferViewContents<unsigned char> buf(key.As<ArrayBufferView>());
      next[std::string(*id, id.length())] =
          std::vector<unsigned char>(buf.data(), buf.data() + buf.length());
    }
    ctx->psk_identities_ = std::move(next);
  }

  if (args[1]->IsString()) {
    Utf8Value hint(env->isolate(), args[1]);
    ctx->psk_identity_hint_.assign(*hint, hint.length());
  }

  if (args[2]->IsString()) {
    Utf8Value identity(env->isolate(), args[2]);
    ctx->psk_client_identity_.assign(*identity, identity.length());
  }

  if (args[3]->IsArrayBufferView()) {
    ArrayBufferViewContents<unsigned char> key(args[3].As<ArrayBufferView>());
    ctx->psk_client_key_.assign(key.data(), key.data() + key.length());
  }

  if (args[4]->IsFunction()) {
    ctx->psk_callback_.Reset(env->isolate(), args[4].As<Function>());
  } else {
    ctx->psk_callback_.Reset();
  }

  if (ctx->is_server_) {
    SSL_CTX_set_psk_server_callback(ctx->ctx_.get(), PSKServerCallback);
    if (!ctx->psk_identity_hint_.empty() &&
        SSL_CTX_use_psk_identity_hint(ctx->ctx_.get(),
                                      ctx->psk_identity_hint_.c_str()) != 1) {
      THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to set PSK identity hint");
    }
  } else {
    SSL_CTX_set_psk_client_callback(ctx->ctx_.get(), PSKClientCallback);
  }
}

void DTLSContext::SetECDHCurve(const FunctionCallbackInfo<Value>& args) {
  DTLSContext* ctx;
  ASSIGN_OR_RETURN_UNWRAP(&ctx, args.This());
  Environment* env = ctx->env();

  CHECK(args[0]->IsString());
  Utf8Value curve(env->isolate(), args[0]);

  // "auto" means use OpenSSL's default curve selection.
  if (strcmp(*curve, "auto") != 0) {
    if (!SSL_CTX_set1_curves_list(ctx->ctx_.get(), *curve)) {
      return THROW_ERR_CRYPTO_OPERATION_FAILED(env, "Failed to set ECDH curve");
    }
  }
}

// 1 byte family tag + 2 port + 16 address + 4 scope id.
constexpr size_t kMaxCanonicalAddrLen = 23;

// Writes the parts of an address that identify a peer -- family, port,
// address, and for IPv6 the scope id -- into `out`, returning the number of
// bytes written, or 0 for an address we do not understand.
//
// Deliberately not a memcpy of the sockaddr. That would fold in sin_zero and
// sin6_flowinfo: padding the kernel need not zero, and a QoS label that may
// differ between two datagrams from the same host. Either would change the
// cookie for an unchanged peer and fail the handshake. scope id is included
// because it does identify a link-local peer.
size_t CanonicalizeAddress(const sockaddr* sa, unsigned char* out) {
  switch (sa->sa_family) {
    case AF_INET: {
      const sockaddr_in* v4 = reinterpret_cast<const sockaddr_in*>(sa);
      out[0] = 4;
      memcpy(out + 1, &v4->sin_port, 2);
      memcpy(out + 3, &v4->sin_addr, 4);
      return 7;
    }
    case AF_INET6: {
      const sockaddr_in6* v6 = reinterpret_cast<const sockaddr_in6*>(sa);
      out[0] = 6;
      memcpy(out + 1, &v6->sin6_port, 2);
      memcpy(out + 3, &v6->sin6_addr, 16);
      memcpy(out + 19, &v6->sin6_scope_id, 4);
      return 23;
    }
    default:
      return 0;
  }
}

// HMAC-SHA256 cookie derived from the peer's address and a coarse time window
// so cookies expire (see kCookieWindowNs).
//
// The cookie is not bound to the ClientHello, which RFC 6347 section 4.2.1
// recommends, so within one window a cookie issued to an address is accepted
// for any handshake attempt from that address. The cookie's purpose --
// proving the peer can receive at the address it claims, so the handshake
// cannot be used to amplify traffic at a third party -- is unaffected by
// that. Binding it via SSL_get_client_random() was tried and does not work:
// the random is not populated consistently across the generate and verify
// callbacks during DTLSv1_listen(), and every handshake fails with a
// handshake_failure alert. Doing it would mean lifting the 32-byte random out
// of the raw ClientHello, which CouldBeClientHello() already walks, and
// stashing it next to current_cookie_peer_. During DTLSv1_listen() the peer
// address comes from DTLSContext::current_cookie_peer_ (set synchronously
// before the call); during the session handshake it comes from the
// DTLSSession stored in SSL app_data.
bool DTLSContext::ComputeCookie(SSL* ssl,
                                uint64_t window,
                                unsigned char* out,
                                unsigned int* out_len) {
  SSL_CTX* ctx = SSL_get_SSL_CTX(ssl);
  DTLSContext* dtls_ctx = static_cast<DTLSContext*>(SSL_CTX_get_app_data(ctx));
  CHECK_NOT_NULL(dtls_ctx);

  // Message = canonical peer address followed by the 8-byte window counter.
  unsigned char msg[kMaxCanonicalAddrLen + sizeof(uint64_t)];
  size_t addr_len = 0;

  void* app_data = SSL_get_app_data(ssl);
  if (app_data != nullptr) {
    // Session handshake path.
    const sockaddr* sa =
        static_cast<DTLSSession*>(app_data)->remote_address().data();
    addr_len = CanonicalizeAddress(sa, msg);
  } else {
    // DTLSv1_listen path -- use the peer address stored on the context.
    const sockaddr* sa = dtls_ctx->current_cookie_peer_.data();
    addr_len = CanonicalizeAddress(sa, msg);
  }

  // Fail closed rather than deriving a cookie from an address we could not
  // make sense of.
  if (addr_len == 0) return false;

  // Append the window counter in a fixed byte order.
  for (size_t i = 0; i < sizeof(uint64_t); i++) {
    msg[addr_len + i] = static_cast<unsigned char>((window >> (8 * i)) & 0xff);
  }

  unsigned char* result = HMAC(EVP_sha256(),
                               dtls_ctx->cookie_secret_.data(),
                               dtls_ctx->cookie_secret_.size(),
                               msg,
                               addr_len + sizeof(uint64_t),
                               out,
                               out_len);
  return result != nullptr;
}

int DTLSContext::CookieGenerateCallback(SSL* ssl,
                                        unsigned char* cookie,
                                        unsigned int* cookie_len) {
  const uint64_t window = uv_hrtime() / kCookieWindowNs;
  return ComputeCookie(ssl, window, cookie, cookie_len) ? 1 : 0;
}

int DTLSContext::CookieVerifyCallback(SSL* ssl,
                                      const unsigned char* cookie,
                                      unsigned int cookie_len) {
  const uint64_t window = uv_hrtime() / kCookieWindowNs;

  // Accept a cookie minted in the current window or the immediately preceding
  // one, so a handshake that straddles a window boundary still succeeds.
  unsigned char expected[EVP_MAX_MD_SIZE];
  unsigned int expected_len = 0;
  for (int i = 0; i < 2; i++) {
    if (i == 1 && window == 0) break;
    if (ComputeCookie(ssl, window - i, expected, &expected_len) &&
        cookie_len == expected_len &&
        CRYPTO_memcmp(cookie, expected, expected_len) == 0) {
      return 1;
    }
  }
  return 0;
}

int DTLSContext::ALPNSelectCallback(SSL* ssl,
                                    const unsigned char** out,
                                    unsigned char* outlen,
                                    const unsigned char* in,
                                    unsigned int inlen,
                                    void* arg) {
  DTLSContext* ctx = static_cast<DTLSContext*>(arg);

  // This server does not do ALPN. Decline the extension and let the handshake
  // continue: the client offering protocols to a server that has none
  // configured is not an error. OpenSSL only calls this at all when the client
  // sent the extension, so a client that offered nothing never reaches here.
  if (ctx->alpn_protos_.empty()) {
    return SSL_TLSEXT_ERR_NOACK;
  }

  auto selected = crypto::SelectNextProtocol(
      {ctx->alpn_protos_.data(), ctx->alpn_protos_.size()}, {in, inlen});

  if (!selected.has_value()) {
    return SSL_TLSEXT_ERR_ALERT_FATAL;
  }

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

}  // namespace dtls
}  // namespace node

#endif  // HAVE_OPENSSL && HAVE_DTLS