mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00

Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: https://github.com/nodejs/node/pull/56328 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
656 lines
22 KiB
C++
656 lines
22 KiB
C++
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|
|
|
|
#include "tlscontext.h"
|
|
#include <async_wrap-inl.h>
|
|
#include <base_object-inl.h>
|
|
#include <crypto/crypto_util.h>
|
|
#include <debug_utils-inl.h>
|
|
#include <env-inl.h>
|
|
#include <memory_tracker-inl.h>
|
|
#include <ngtcp2/ngtcp2.h>
|
|
#include <ngtcp2/ngtcp2_crypto.h>
|
|
#include <ngtcp2/ngtcp2_crypto_quictls.h>
|
|
#include <node_sockaddr-inl.h>
|
|
#include <openssl/ssl.h>
|
|
#include <v8.h>
|
|
#include "bindingdata.h"
|
|
#include "defs.h"
|
|
#include "session.h"
|
|
#include "transportparams.h"
|
|
|
|
namespace node {
|
|
|
|
using v8::ArrayBuffer;
|
|
using v8::Just;
|
|
using v8::Local;
|
|
using v8::Maybe;
|
|
using v8::MaybeLocal;
|
|
using v8::Nothing;
|
|
using v8::Object;
|
|
using v8::Value;
|
|
|
|
namespace quic {
|
|
|
|
// ============================================================================
|
|
|
|
namespace {
|
|
// TODO(@jasnell): One time initialization. ngtcp2 says this is optional but
|
|
// highly recommended to deal with some perf regression. Unfortunately doing
|
|
// this breaks some existing tests and we need to understand the potential
|
|
// impact of calling this.
|
|
// auto _ = []() {
|
|
// CHECK_EQ(ngtcp2_crypto_quictls_init(), 0);
|
|
// return 0;
|
|
// }();
|
|
|
|
void EnableTrace(Environment* env, crypto::BIOPointer* bio, SSL* ssl) {
|
|
#if HAVE_SSL_TRACE
|
|
static bool warn_trace_tls = true;
|
|
if (warn_trace_tls) {
|
|
warn_trace_tls = false;
|
|
ProcessEmitWarning(env,
|
|
"Enabling --trace-tls can expose sensitive data in "
|
|
"the resulting log");
|
|
}
|
|
if (!*bio) {
|
|
bio->reset(BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT));
|
|
SSL_set_msg_callback(
|
|
ssl,
|
|
[](int write_p,
|
|
int version,
|
|
int content_type,
|
|
const void* buf,
|
|
size_t len,
|
|
SSL* ssl,
|
|
void* arg) -> void {
|
|
crypto::MarkPopErrorOnReturn mark_pop_error_on_return;
|
|
SSL_trace(write_p, version, content_type, buf, len, ssl, arg);
|
|
});
|
|
SSL_set_msg_callback_arg(ssl, bio->get());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
template <typename T, typename Opt, std::vector<T> Opt::*member>
|
|
bool SetOption(Environment* env,
|
|
Opt* options,
|
|
const v8::Local<v8::Object>& object,
|
|
const v8::Local<v8::String>& name) {
|
|
v8::Local<v8::Value> value;
|
|
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
|
|
|
|
if (value->IsUndefined()) return true;
|
|
|
|
// The value can be either a single item or an array of items.
|
|
|
|
if (value->IsArray()) {
|
|
auto context = env->context();
|
|
auto values = value.As<v8::Array>();
|
|
uint32_t count = values->Length();
|
|
for (uint32_t n = 0; n < count; n++) {
|
|
v8::Local<v8::Value> item;
|
|
if (!values->Get(context, n).ToLocal(&item)) {
|
|
return false;
|
|
}
|
|
if constexpr (std::is_same<T, crypto::KeyObjectData>::value) {
|
|
if (crypto::KeyObjectHandle::HasInstance(env, item)) {
|
|
crypto::KeyObjectHandle* handle;
|
|
ASSIGN_OR_RETURN_UNWRAP(&handle, item, false);
|
|
(options->*member).push_back(handle->Data().addRef());
|
|
} else {
|
|
Utf8Value namestr(env->isolate(), name);
|
|
THROW_ERR_INVALID_ARG_TYPE(
|
|
env, "%s value must be a key object", *namestr);
|
|
return false;
|
|
}
|
|
} else if constexpr (std::is_same<T, Store>::value) {
|
|
if (item->IsArrayBufferView()) {
|
|
(options->*member).emplace_back(item.As<v8::ArrayBufferView>());
|
|
} else if (item->IsArrayBuffer()) {
|
|
(options->*member).emplace_back(item.As<v8::ArrayBuffer>());
|
|
} else {
|
|
Utf8Value namestr(env->isolate(), name);
|
|
THROW_ERR_INVALID_ARG_TYPE(
|
|
env, "%s value must be an array buffer", *namestr);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if constexpr (std::is_same<T, crypto::KeyObjectData>::value) {
|
|
if (crypto::KeyObjectHandle::HasInstance(env, value)) {
|
|
crypto::KeyObjectHandle* handle;
|
|
ASSIGN_OR_RETURN_UNWRAP(&handle, value, false);
|
|
(options->*member).push_back(handle->Data().addRef());
|
|
} else {
|
|
Utf8Value namestr(env->isolate(), name);
|
|
THROW_ERR_INVALID_ARG_TYPE(
|
|
env, "%s value must be a key object", *namestr);
|
|
return false;
|
|
}
|
|
} else if constexpr (std::is_same<T, Store>::value) {
|
|
if (value->IsArrayBufferView()) {
|
|
(options->*member).emplace_back(value.As<v8::ArrayBufferView>());
|
|
} else if (value->IsArrayBuffer()) {
|
|
(options->*member).emplace_back(value.As<v8::ArrayBuffer>());
|
|
} else {
|
|
Utf8Value namestr(env->isolate(), name);
|
|
THROW_ERR_INVALID_ARG_TYPE(
|
|
env, "%s value must be an array buffer", *namestr);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
std::shared_ptr<TLSContext> TLSContext::CreateClient(const Options& options) {
|
|
return std::make_shared<TLSContext>(Side::CLIENT, options);
|
|
}
|
|
|
|
std::shared_ptr<TLSContext> TLSContext::CreateServer(const Options& options) {
|
|
return std::make_shared<TLSContext>(Side::SERVER, options);
|
|
}
|
|
|
|
TLSContext::TLSContext(Side side, const Options& options)
|
|
: side_(side), options_(options), ctx_(Initialize()) {}
|
|
|
|
TLSContext::operator SSL_CTX*() const {
|
|
DCHECK(ctx_);
|
|
return ctx_.get();
|
|
}
|
|
|
|
int TLSContext::OnSelectAlpn(SSL* ssl,
|
|
const unsigned char** out,
|
|
unsigned char* outlen,
|
|
const unsigned char* in,
|
|
unsigned int inlen,
|
|
void* arg) {
|
|
static constexpr size_t kMaxAlpnLen = 255;
|
|
auto& session = TLSSession::From(ssl);
|
|
|
|
const auto& requested = session.context().options().protocol;
|
|
if (requested.length() > kMaxAlpnLen) return SSL_TLSEXT_ERR_NOACK;
|
|
|
|
// The Session supports exactly one ALPN identifier. If that does not match
|
|
// any of the ALPN identifiers provided in the client request, then we fail
|
|
// here. Note that this will not fail the TLS handshake, so we have to check
|
|
// later if the ALPN matches the expected identifier or not.
|
|
//
|
|
// We might eventually want to support the ability to negotiate multiple
|
|
// possible ALPN's on a single endpoint/session but for now, we only support
|
|
// one.
|
|
if (SSL_select_next_proto(
|
|
const_cast<unsigned char**>(out),
|
|
outlen,
|
|
reinterpret_cast<const unsigned char*>(requested.data()),
|
|
requested.length(),
|
|
in,
|
|
inlen) == OPENSSL_NPN_NO_OVERLAP) {
|
|
Debug(&session.session(), "ALPN negotiation failed");
|
|
return SSL_TLSEXT_ERR_NOACK;
|
|
}
|
|
|
|
Debug(&session.session(), "ALPN negotiation succeeded");
|
|
return SSL_TLSEXT_ERR_OK;
|
|
}
|
|
|
|
int TLSContext::OnNewSession(SSL* ssl, SSL_SESSION* sess) {
|
|
auto& session = TLSSession::From(ssl).session();
|
|
|
|
// If there is nothing listening for the session ticket, do not bother.
|
|
if (session.wants_session_ticket()) {
|
|
Debug(&session, "Preparing TLS session resumption ticket");
|
|
|
|
// Pre-fight to see how much space we need to allocate for the session
|
|
// ticket.
|
|
size_t size = i2d_SSL_SESSION(sess, nullptr);
|
|
|
|
// If size is 0 or the size is greater than our max, let's ignore it
|
|
// and continue without emitting the sessionticket event.
|
|
if (size > 0 && size <= crypto::SecureContext::kMaxSessionSize) {
|
|
auto ticket =
|
|
ArrayBuffer::NewBackingStore(session.env()->isolate(), size);
|
|
auto data = reinterpret_cast<unsigned char*>(ticket->Data());
|
|
if (i2d_SSL_SESSION(sess, &data) > 0) {
|
|
session.EmitSessionTicket(Store(std::move(ticket), size));
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void TLSContext::OnKeylog(const SSL* ssl, const char* line) {
|
|
TLSSession::From(ssl).session().EmitKeylog(line);
|
|
}
|
|
|
|
int TLSContext::OnVerifyClientCertificate(int preverify_ok,
|
|
X509_STORE_CTX* ctx) {
|
|
// TODO(@jasnell): Implement the logic to verify the client certificate
|
|
return 1;
|
|
}
|
|
|
|
std::unique_ptr<TLSSession> TLSContext::NewSession(
|
|
Session* session, const std::optional<SessionTicket>& maybeSessionTicket) {
|
|
// Passing a session ticket only makes sense with a client session.
|
|
CHECK_IMPLIES(session->is_server(), !maybeSessionTicket.has_value());
|
|
return std::make_unique<TLSSession>(
|
|
session, shared_from_this(), maybeSessionTicket);
|
|
}
|
|
|
|
crypto::SSLCtxPointer TLSContext::Initialize() {
|
|
crypto::SSLCtxPointer ctx;
|
|
switch (side_) {
|
|
case Side::SERVER: {
|
|
static constexpr unsigned char kSidCtx[] = "Node.js QUIC Server";
|
|
ctx.reset(SSL_CTX_new(TLS_server_method()));
|
|
CHECK_EQ(ngtcp2_crypto_quictls_configure_server_context(ctx.get()), 0);
|
|
CHECK_EQ(SSL_CTX_set_max_early_data(ctx.get(), UINT32_MAX), 1);
|
|
SSL_CTX_set_options(ctx.get(),
|
|
(SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
|
|
SSL_OP_SINGLE_ECDH_USE |
|
|
SSL_OP_CIPHER_SERVER_PREFERENCE |
|
|
SSL_OP_NO_ANTI_REPLAY);
|
|
SSL_CTX_set_mode(ctx.get(), SSL_MODE_RELEASE_BUFFERS);
|
|
SSL_CTX_set_alpn_select_cb(ctx.get(), OnSelectAlpn, this);
|
|
CHECK_EQ(SSL_CTX_set_session_id_context(
|
|
ctx.get(), kSidCtx, sizeof(kSidCtx) - 1),
|
|
1);
|
|
|
|
if (options_.verify_client) {
|
|
SSL_CTX_set_verify(ctx.get(),
|
|
SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE |
|
|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
OnVerifyClientCertificate);
|
|
}
|
|
|
|
// TODO(@jasnell): There's a bug int the GenerateCallback flow somewhere.
|
|
// Need to update in order to support session tickets.
|
|
// CHECK_EQ(SSL_CTX_set_session_ticket_cb(ctx.get(),
|
|
// SessionTicket::GenerateCallback,
|
|
// SessionTicket::DecryptedCallback,
|
|
// nullptr),
|
|
// 1);
|
|
break;
|
|
}
|
|
case Side::CLIENT: {
|
|
ctx.reset(SSL_CTX_new(TLS_client_method()));
|
|
CHECK_EQ(ngtcp2_crypto_quictls_configure_client_context(ctx.get()), 0);
|
|
|
|
SSL_CTX_set_session_cache_mode(
|
|
ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
|
|
SSL_CTX_sess_set_new_cb(ctx.get(), OnNewSession);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SSL_CTX_set_default_verify_paths(ctx.get());
|
|
SSL_CTX_set_keylog_callback(ctx.get(), OnKeylog);
|
|
|
|
if (SSL_CTX_set_ciphersuites(ctx.get(), options_.ciphers.c_str()) != 1) {
|
|
validation_error_ = "Invalid cipher suite";
|
|
return crypto::SSLCtxPointer();
|
|
}
|
|
|
|
if (SSL_CTX_set1_groups_list(ctx.get(), options_.groups.c_str()) != 1) {
|
|
validation_error_ = "Invalid cipher groups";
|
|
return crypto::SSLCtxPointer();
|
|
}
|
|
|
|
{
|
|
crypto::ClearErrorOnReturn clear_error_on_return;
|
|
if (options_.ca.empty()) {
|
|
auto store = crypto::GetOrCreateRootCertStore();
|
|
X509_STORE_up_ref(store);
|
|
SSL_CTX_set_cert_store(ctx.get(), store);
|
|
} else {
|
|
for (const auto& ca : options_.ca) {
|
|
uv_buf_t buf = ca;
|
|
if (buf.len == 0) {
|
|
auto store = crypto::GetOrCreateRootCertStore();
|
|
X509_STORE_up_ref(store);
|
|
SSL_CTX_set_cert_store(ctx.get(), store);
|
|
} else {
|
|
crypto::BIOPointer bio = crypto::NodeBIO::NewFixed(buf.base, buf.len);
|
|
CHECK(bio);
|
|
X509_STORE* cert_store = SSL_CTX_get_cert_store(ctx.get());
|
|
while (crypto::X509Pointer x509 = crypto::X509Pointer(
|
|
PEM_read_bio_X509_AUX(bio.get(),
|
|
nullptr,
|
|
crypto::NoPasswordCallback,
|
|
nullptr))) {
|
|
if (cert_store == crypto::GetOrCreateRootCertStore()) {
|
|
cert_store = crypto::NewRootCertStore();
|
|
SSL_CTX_set_cert_store(ctx.get(), cert_store);
|
|
}
|
|
CHECK_EQ(1, X509_STORE_add_cert(cert_store, x509.get()));
|
|
CHECK_EQ(1, SSL_CTX_add_client_CA(ctx.get(), x509.get()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
crypto::ClearErrorOnReturn clear_error_on_return;
|
|
for (const auto& cert : options_.certs) {
|
|
uv_buf_t buf = cert;
|
|
if (buf.len > 0) {
|
|
crypto::BIOPointer bio = crypto::NodeBIO::NewFixed(buf.base, buf.len);
|
|
CHECK(bio);
|
|
cert_.reset();
|
|
issuer_.reset();
|
|
if (crypto::SSL_CTX_use_certificate_chain(
|
|
ctx.get(), std::move(bio), &cert_, &issuer_) == 0) {
|
|
validation_error_ = "Invalid certificate";
|
|
return crypto::SSLCtxPointer();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
crypto::ClearErrorOnReturn clear_error_on_return;
|
|
for (const auto& key : options_.keys) {
|
|
if (key.GetKeyType() != crypto::KeyType::kKeyTypePrivate) {
|
|
validation_error_ = "Invalid key";
|
|
return crypto::SSLCtxPointer();
|
|
}
|
|
if (!SSL_CTX_use_PrivateKey(ctx.get(), key.GetAsymmetricKey().get())) {
|
|
validation_error_ = "Invalid key";
|
|
return crypto::SSLCtxPointer();
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
crypto::ClearErrorOnReturn clear_error_on_return;
|
|
for (const auto& crl : options_.crl) {
|
|
uv_buf_t buf = crl;
|
|
crypto::BIOPointer bio = crypto::NodeBIO::NewFixed(buf.base, buf.len);
|
|
DeleteFnPtr<X509_CRL, X509_CRL_free> crlptr(PEM_read_bio_X509_CRL(
|
|
bio.get(), nullptr, crypto::NoPasswordCallback, nullptr));
|
|
|
|
if (!crlptr) {
|
|
validation_error_ = "Invalid CRL";
|
|
return crypto::SSLCtxPointer();
|
|
}
|
|
|
|
X509_STORE* cert_store = SSL_CTX_get_cert_store(ctx.get());
|
|
if (cert_store == crypto::GetOrCreateRootCertStore()) {
|
|
cert_store = crypto::NewRootCertStore();
|
|
SSL_CTX_set_cert_store(ctx.get(), cert_store);
|
|
}
|
|
|
|
CHECK_EQ(1, X509_STORE_add_crl(cert_store, crlptr.get()));
|
|
CHECK_EQ(
|
|
1,
|
|
X509_STORE_set_flags(
|
|
cert_store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL));
|
|
}
|
|
}
|
|
|
|
{
|
|
crypto::ClearErrorOnReturn clear_error_on_return;
|
|
if (options_.verify_private_key &&
|
|
SSL_CTX_check_private_key(ctx.get()) != 1) {
|
|
validation_error_ = "Invalid private key";
|
|
return crypto::SSLCtxPointer();
|
|
}
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
|
|
void TLSContext::MemoryInfo(MemoryTracker* tracker) const {
|
|
tracker->TrackField("options", options_);
|
|
}
|
|
|
|
Maybe<TLSContext::Options> TLSContext::Options::From(Environment* env,
|
|
Local<Value> value) {
|
|
if (value.IsEmpty()) {
|
|
return Nothing<Options>();
|
|
}
|
|
|
|
Options options;
|
|
auto& state = BindingData::Get(env);
|
|
|
|
if (value->IsUndefined()) {
|
|
return Just(TLSContext::Options::kDefault);
|
|
}
|
|
|
|
if (!value->IsObject()) {
|
|
THROW_ERR_INVALID_ARG_TYPE(env, "tls options must be an object");
|
|
return Nothing<Options>();
|
|
}
|
|
|
|
auto params = value.As<Object>();
|
|
|
|
#define SET_VECTOR(Type, name) \
|
|
SetOption<Type, TLSContext::Options, &TLSContext::Options::name>( \
|
|
env, &options, params, state.name##_string())
|
|
|
|
#define SET(name) \
|
|
SetOption<TLSContext::Options, &TLSContext::Options::name>( \
|
|
env, &options, params, state.name##_string())
|
|
|
|
if (!SET(verify_client) || !SET(enable_tls_trace) || !SET(protocol) ||
|
|
!SET(servername) || !SET(ciphers) || !SET(groups) ||
|
|
!SET(verify_private_key) || !SET(keylog) ||
|
|
!SET_VECTOR(crypto::KeyObjectData, keys) || !SET_VECTOR(Store, certs) ||
|
|
!SET_VECTOR(Store, ca) || !SET_VECTOR(Store, crl)) {
|
|
return Nothing<Options>();
|
|
}
|
|
|
|
return Just<Options>(options);
|
|
}
|
|
|
|
std::string TLSContext::Options::ToString() const {
|
|
DebugIndentScope indent;
|
|
auto prefix = indent.Prefix();
|
|
std::string res("{");
|
|
res += prefix + "protocol: " + protocol;
|
|
res += prefix + "servername: " + servername;
|
|
res +=
|
|
prefix + "keylog: " + (keylog ? std::string("yes") : std::string("no"));
|
|
res += prefix + "verify client: " +
|
|
(verify_client ? std::string("yes") : std::string("no"));
|
|
res += prefix + "enable_tls_trace: " +
|
|
(enable_tls_trace ? std::string("yes") : std::string("no"));
|
|
res += prefix + "verify private key: " +
|
|
(verify_private_key ? std::string("yes") : std::string("no"));
|
|
res += prefix + "ciphers: " + ciphers;
|
|
res += prefix + "groups: " + groups;
|
|
res += prefix + "keys: " + std::to_string(keys.size());
|
|
res += prefix + "certs: " + std::to_string(certs.size());
|
|
res += prefix + "ca: " + std::to_string(ca.size());
|
|
res += prefix + "crl: " + std::to_string(crl.size());
|
|
res += indent.Close();
|
|
return res;
|
|
}
|
|
|
|
void TLSContext::Options::MemoryInfo(MemoryTracker* tracker) const {
|
|
tracker->TrackField("keys", keys);
|
|
tracker->TrackField("certs", certs);
|
|
tracker->TrackField("ca", ca);
|
|
tracker->TrackField("crl", crl);
|
|
}
|
|
|
|
const TLSContext::Options TLSContext::Options::kDefault = {};
|
|
|
|
// ============================================================================
|
|
|
|
const TLSSession& TLSSession::From(const SSL* ssl) {
|
|
auto ref = static_cast<ngtcp2_crypto_conn_ref*>(SSL_get_app_data(ssl));
|
|
CHECK_NOT_NULL(ref);
|
|
return *static_cast<TLSSession*>(ref->user_data);
|
|
}
|
|
|
|
TLSSession::TLSSession(Session* session,
|
|
std::shared_ptr<TLSContext> context,
|
|
const std::optional<SessionTicket>& maybeSessionTicket)
|
|
: ref_({connection, this}),
|
|
context_(std::move(context)),
|
|
session_(session),
|
|
ssl_(Initialize(maybeSessionTicket)) {
|
|
Debug(session_, "Created new TLS session for %s", session->config().dcid);
|
|
}
|
|
|
|
TLSSession::~TLSSession() {
|
|
if (ssl_) {
|
|
SSL_set_app_data(ssl_.get(), nullptr);
|
|
}
|
|
}
|
|
|
|
TLSSession::operator SSL*() const {
|
|
CHECK(ssl_);
|
|
return ssl_.get();
|
|
}
|
|
|
|
bool TLSSession::early_data_was_accepted() const {
|
|
CHECK_NE(ngtcp2_conn_get_handshake_completed(*session_), 0);
|
|
return SSL_get_early_data_status(*this) == SSL_EARLY_DATA_ACCEPTED;
|
|
}
|
|
|
|
crypto::SSLPointer TLSSession::Initialize(
|
|
const std::optional<SessionTicket>& maybeSessionTicket) {
|
|
auto& ctx = context();
|
|
auto& options = ctx.options();
|
|
crypto::SSLPointer ssl(SSL_new(ctx));
|
|
SSL_set_app_data(ssl.get(), &ref_);
|
|
ngtcp2_conn_set_tls_native_handle(*session_, ssl.get());
|
|
|
|
// Enable tracing if the `--trace-tls` command line flag is used.
|
|
if (session_->env()->options()->trace_tls || options.enable_tls_trace)
|
|
[[unlikely]] {
|
|
EnableTrace(session_->env(), &bio_trace_, *this);
|
|
}
|
|
|
|
switch (ctx.side()) {
|
|
case Side::SERVER: {
|
|
SSL_set_accept_state(ssl.get());
|
|
SSL_set_quic_early_data_enabled(ssl.get(), 1);
|
|
break;
|
|
}
|
|
case Side::CLIENT: {
|
|
SSL_set_connect_state(ssl.get());
|
|
if (SSL_set_alpn_protos(
|
|
ssl.get(),
|
|
reinterpret_cast<const unsigned char*>(options.protocol.data()),
|
|
options.protocol.size()) != 0) {
|
|
validation_error_ = "Invalid ALPN";
|
|
return crypto::SSLPointer();
|
|
}
|
|
|
|
if (!options.servername.empty()) {
|
|
SSL_set_tlsext_host_name(ssl.get(), options.servername.data());
|
|
} else {
|
|
SSL_set_tlsext_host_name(ssl.get(), "localhost");
|
|
}
|
|
|
|
if (maybeSessionTicket.has_value()) {
|
|
auto sessionTicket = maybeSessionTicket.value();
|
|
uv_buf_t buf = sessionTicket.ticket();
|
|
crypto::SSLSessionPointer ticket = crypto::GetTLSSession(
|
|
reinterpret_cast<unsigned char*>(buf.base), buf.len);
|
|
|
|
// The early data will just be ignored if it's invalid.
|
|
if (crypto::SetTLSSession(ssl, ticket) &&
|
|
SSL_SESSION_get_max_early_data(ticket.get()) != 0) {
|
|
ngtcp2_vec rtp = sessionTicket.transport_params();
|
|
if (ngtcp2_conn_decode_and_set_0rtt_transport_params(
|
|
*session_, rtp.base, rtp.len) == 0) {
|
|
SSL_set_quic_early_data_enabled(ssl.get(), 1);
|
|
session_->SetStreamOpenAllowed();
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
TransportParams tp(ngtcp2_conn_get_local_transport_params(*session_));
|
|
Store store = tp.Encode(session_->env());
|
|
if (store && store.length() > 0) {
|
|
ngtcp2_vec vec = store;
|
|
SSL_set_quic_transport_params(ssl.get(), vec.base, vec.len);
|
|
}
|
|
|
|
return ssl;
|
|
}
|
|
|
|
std::optional<TLSSession::PeerIdentityValidationError>
|
|
TLSSession::VerifyPeerIdentity(Environment* env) {
|
|
int err = crypto::VerifyPeerCertificate(ssl_);
|
|
if (err == X509_V_OK) return std::nullopt;
|
|
Local<Value> reason;
|
|
Local<Value> code;
|
|
if (!crypto::GetValidationErrorReason(env, err).ToLocal(&reason) ||
|
|
!crypto::GetValidationErrorCode(env, err).ToLocal(&code)) {
|
|
// Getting the validation error details failed. We'll return a value but
|
|
// the fields will be empty.
|
|
return PeerIdentityValidationError{};
|
|
}
|
|
return PeerIdentityValidationError{reason, code};
|
|
}
|
|
|
|
MaybeLocal<Object> TLSSession::cert(Environment* env) const {
|
|
return crypto::X509Certificate::GetCert(env, ssl_);
|
|
}
|
|
|
|
MaybeLocal<Object> TLSSession::peer_cert(Environment* env) const {
|
|
crypto::X509Certificate::GetPeerCertificateFlag flag =
|
|
context_->side() == Side::SERVER
|
|
? crypto::X509Certificate::GetPeerCertificateFlag::SERVER
|
|
: crypto::X509Certificate::GetPeerCertificateFlag::NONE;
|
|
return crypto::X509Certificate::GetPeerCert(env, ssl_, flag);
|
|
}
|
|
|
|
MaybeLocal<Object> TLSSession::ephemeral_key(Environment* env) const {
|
|
return crypto::GetEphemeralKey(env, ssl_);
|
|
}
|
|
|
|
MaybeLocal<Value> TLSSession::cipher_name(Environment* env) const {
|
|
return crypto::GetCurrentCipherName(env, ssl_);
|
|
}
|
|
|
|
MaybeLocal<Value> TLSSession::cipher_version(Environment* env) const {
|
|
return crypto::GetCurrentCipherVersion(env, ssl_);
|
|
}
|
|
|
|
const std::string_view TLSSession::servername() const {
|
|
const char* servername = crypto::GetServerName(ssl_.get());
|
|
return servername != nullptr ? std::string_view(servername)
|
|
: std::string_view();
|
|
}
|
|
|
|
const std::string_view TLSSession::protocol() const {
|
|
const unsigned char* alpn_buf = nullptr;
|
|
unsigned int alpnlen;
|
|
SSL_get0_alpn_selected(ssl_.get(), &alpn_buf, &alpnlen);
|
|
return alpnlen ? std::string_view(reinterpret_cast<const char*>(alpn_buf),
|
|
alpnlen)
|
|
: std::string_view();
|
|
}
|
|
|
|
bool TLSSession::InitiateKeyUpdate() {
|
|
if (in_key_update_) return false;
|
|
auto leave = OnScopeLeave([this] { in_key_update_ = false; });
|
|
in_key_update_ = true;
|
|
|
|
Debug(session_, "Initiating key update");
|
|
return ngtcp2_conn_initiate_key_update(*session_, uv_hrtime()) == 0;
|
|
}
|
|
|
|
ngtcp2_conn* TLSSession::connection(ngtcp2_crypto_conn_ref* ref) {
|
|
CHECK_NOT_NULL(ref->user_data);
|
|
return static_cast<TLSSession*>(ref->user_data)->session();
|
|
}
|
|
|
|
} // namespace quic
|
|
} // namespace node
|
|
|
|
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
|