1 #if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
2
3 #include "tlscontext.h"
4 #include "bindingdata.h"
5 #include "defs.h"
6 #include "transportparams.h"
7 #include <base_object-inl.h>
8 #include <env-inl.h>
9 #include <memory_tracker-inl.h>
10 #include <ngtcp2/ngtcp2.h>
11 #include <ngtcp2/ngtcp2_crypto.h>
12 #include <ngtcp2/ngtcp2_crypto_openssl.h>
13 #include <openssl/ssl.h>
14 #include <v8.h>
15
16 namespace node {
17
18 using v8::ArrayBuffer;
19 using v8::BackingStore;
20 using v8::Just;
21 using v8::Local;
22 using v8::Maybe;
23 using v8::MaybeLocal;
24 using v8::Nothing;
25 using v8::Object;
26 using v8::Value;
27
28 namespace quic {
29
30 // TODO(@jasnell): This session class is just a placeholder.
31 // The real session impl will be added in a separate commit.
32 class Session {
33 public:
operator ngtcp2_conn*()34 operator ngtcp2_conn*() { return nullptr; }
EmitKeylog(const char * line) const35 void EmitKeylog(const char* line) const {}
EmitSessionTicket(Store && store)36 void EmitSessionTicket(Store&& store) {}
SetStreamOpenAllowed()37 void SetStreamOpenAllowed() {}
is_destroyed() const38 bool is_destroyed() const { return false; }
wants_session_ticket() const39 bool wants_session_ticket() const { return false; }
40 };
41
42 namespace {
43 constexpr size_t kMaxAlpnLen = 255;
44
AllowEarlyDataCallback(SSL * ssl,void * arg)45 int AllowEarlyDataCallback(SSL* ssl, void* arg) {
46 // Currently, we always allow early data. Later we might make
47 // it configurable.
48 return 1;
49 }
50
NewSessionCallback(SSL * ssl,SSL_SESSION * session)51 int NewSessionCallback(SSL* ssl, SSL_SESSION* session) {
52 // We use this event to trigger generation of the SessionTicket
53 // if the user has requested to receive it.
54 return TLSContext::From(ssl).OnNewSession(session);
55 }
56
KeylogCallback(const SSL * ssl,const char * line)57 void KeylogCallback(const SSL* ssl, const char* line) {
58 TLSContext::From(ssl).Keylog(line);
59 }
60
AlpnSelectionCallback(SSL * ssl,const unsigned char ** out,unsigned char * outlen,const unsigned char * in,unsigned int inlen,void * arg)61 int AlpnSelectionCallback(SSL* ssl,
62 const unsigned char** out,
63 unsigned char* outlen,
64 const unsigned char* in,
65 unsigned int inlen,
66 void* arg) {
67 auto& context = TLSContext::From(ssl);
68
69 auto requested = context.options().alpn;
70 if (requested.length() > kMaxAlpnLen) return SSL_TLSEXT_ERR_NOACK;
71
72 // The Session supports exactly one ALPN identifier. If that does not match
73 // any of the ALPN identifiers provided in the client request, then we fail
74 // here. Note that this will not fail the TLS handshake, so we have to check
75 // later if the ALPN matches the expected identifier or not.
76 //
77 // We might eventually want to support the ability to negotiate multiple
78 // possible ALPN's on a single endpoint/session but for now, we only support
79 // one.
80 if (SSL_select_next_proto(
81 const_cast<unsigned char**>(out),
82 outlen,
83 reinterpret_cast<const unsigned char*>(requested.c_str()),
84 requested.length(),
85 in,
86 inlen) == OPENSSL_NPN_NO_OVERLAP) {
87 return SSL_TLSEXT_ERR_NOACK;
88 }
89
90 return SSL_TLSEXT_ERR_OK;
91 }
92
InitializeSecureContext(Side side,Environment * env,const TLSContext::Options & options)93 BaseObjectPtr<crypto::SecureContext> InitializeSecureContext(
94 Side side, Environment* env, const TLSContext::Options& options) {
95 auto context = crypto::SecureContext::Create(env);
96
97 auto& ctx = context->ctx();
98
99 switch (side) {
100 case Side::SERVER: {
101 ctx.reset(SSL_CTX_new(TLS_server_method()));
102 SSL_CTX_set_app_data(ctx.get(), context);
103
104 if (ngtcp2_crypto_openssl_configure_server_context(ctx.get()) != 0) {
105 return BaseObjectPtr<crypto::SecureContext>();
106 }
107
108 SSL_CTX_set_max_early_data(ctx.get(), UINT32_MAX);
109 SSL_CTX_set_allow_early_data_cb(
110 ctx.get(), AllowEarlyDataCallback, nullptr);
111 SSL_CTX_set_options(ctx.get(),
112 (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) |
113 SSL_OP_SINGLE_ECDH_USE |
114 SSL_OP_CIPHER_SERVER_PREFERENCE |
115 SSL_OP_NO_ANTI_REPLAY);
116 SSL_CTX_set_mode(ctx.get(), SSL_MODE_RELEASE_BUFFERS);
117 SSL_CTX_set_alpn_select_cb(ctx.get(), AlpnSelectionCallback, nullptr);
118 SSL_CTX_set_session_ticket_cb(ctx.get(),
119 SessionTicket::GenerateCallback,
120 SessionTicket::DecryptedCallback,
121 nullptr);
122
123 const unsigned char* sid_ctx = reinterpret_cast<const unsigned char*>(
124 options.session_id_ctx.c_str());
125 SSL_CTX_set_session_id_context(
126 ctx.get(), sid_ctx, options.session_id_ctx.length());
127
128 break;
129 }
130 case Side::CLIENT: {
131 ctx.reset(SSL_CTX_new(TLS_client_method()));
132 SSL_CTX_set_app_data(ctx.get(), context);
133
134 if (ngtcp2_crypto_openssl_configure_client_context(ctx.get()) != 0) {
135 return BaseObjectPtr<crypto::SecureContext>();
136 }
137
138 SSL_CTX_set_session_cache_mode(
139 ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);
140 SSL_CTX_sess_set_new_cb(ctx.get(), NewSessionCallback);
141 break;
142 }
143 default:
144 UNREACHABLE();
145 }
146
147 SSL_CTX_set_default_verify_paths(ctx.get());
148
149 if (options.keylog) SSL_CTX_set_keylog_callback(ctx.get(), KeylogCallback);
150
151 if (SSL_CTX_set_ciphersuites(ctx.get(), options.ciphers.c_str()) != 1) {
152 return BaseObjectPtr<crypto::SecureContext>();
153 }
154
155 if (SSL_CTX_set1_groups_list(ctx.get(), options.groups.c_str()) != 1) {
156 return BaseObjectPtr<crypto::SecureContext>();
157 }
158
159 // Handle CA certificates...
160
161 const auto addCACert = [&](uv_buf_t ca) {
162 crypto::ClearErrorOnReturn clear_error_on_return;
163 crypto::BIOPointer bio = crypto::NodeBIO::NewFixed(ca.base, ca.len);
164 if (!bio) return false;
165 context->SetCACert(bio);
166 return true;
167 };
168
169 const auto addRootCerts = [&] {
170 crypto::ClearErrorOnReturn clear_error_on_return;
171 context->SetRootCerts();
172 };
173
174 if (!options.ca.empty()) {
175 for (auto& ca : options.ca) {
176 if (!addCACert(ca)) {
177 return BaseObjectPtr<crypto::SecureContext>();
178 }
179 }
180 } else {
181 addRootCerts();
182 }
183
184 // Handle Certs
185
186 const auto addCert = [&](uv_buf_t cert) {
187 crypto::ClearErrorOnReturn clear_error_on_return;
188 crypto::BIOPointer bio = crypto::NodeBIO::NewFixed(cert.base, cert.len);
189 if (!bio) return Just(false);
190 auto ret = context->AddCert(env, std::move(bio));
191 return ret;
192 };
193
194 for (auto& cert : options.certs) {
195 if (!addCert(cert).IsJust()) {
196 return BaseObjectPtr<crypto::SecureContext>();
197 }
198 }
199
200 // Handle keys
201
202 const auto addKey = [&](auto& key) {
203 crypto::ClearErrorOnReturn clear_error_on_return;
204 return context->UseKey(env, key);
205 // TODO(@jasnell): Maybe SSL_CTX_check_private_key also?
206 };
207
208 for (auto& key : options.keys) {
209 if (!addKey(key).IsJust()) {
210 return BaseObjectPtr<crypto::SecureContext>();
211 }
212 }
213
214 // Handle CRL
215
216 const auto addCRL = [&](uv_buf_t crl) {
217 crypto::ClearErrorOnReturn clear_error_on_return;
218 crypto::BIOPointer bio = crypto::NodeBIO::NewFixed(crl.base, crl.len);
219 if (!bio) return Just(false);
220 return context->SetCRL(env, bio);
221 };
222
223 for (auto& crl : options.crl) {
224 if (!addCRL(crl).IsJust()) {
225 return BaseObjectPtr<crypto::SecureContext>();
226 }
227 }
228
229 // TODO(@jasnell): Possibly handle other bits. Such a pfx, client cert engine,
230 // and session timeout.
231 return BaseObjectPtr<crypto::SecureContext>(context);
232 }
233
EnableTrace(Environment * env,crypto::BIOPointer * bio,SSL * ssl)234 void EnableTrace(Environment* env, crypto::BIOPointer* bio, SSL* ssl) {
235 #if HAVE_SSL_TRACE
236 static bool warn_trace_tls = true;
237 if (warn_trace_tls) {
238 warn_trace_tls = false;
239 ProcessEmitWarning(env,
240 "Enabling --trace-tls can expose sensitive data in "
241 "the resulting log");
242 }
243 if (!*bio) {
244 bio->reset(BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT));
245 SSL_set_msg_callback(
246 ssl,
247 [](int write_p,
248 int version,
249 int content_type,
250 const void* buf,
251 size_t len,
252 SSL* ssl,
253 void* arg) -> void {
254 crypto::MarkPopErrorOnReturn mark_pop_error_on_return;
255 SSL_trace(write_p, version, content_type, buf, len, ssl, arg);
256 });
257 SSL_set_msg_callback_arg(ssl, bio->get());
258 }
259 #endif
260 }
261
262 template <typename T, typename Opt, std::vector<T> Opt::*member>
SetOption(Environment * env,Opt * options,const v8::Local<v8::Object> & object,const v8::Local<v8::String> & name)263 bool SetOption(Environment* env,
264 Opt* options,
265 const v8::Local<v8::Object>& object,
266 const v8::Local<v8::String>& name) {
267 v8::Local<v8::Value> value;
268 if (!object->Get(env->context(), name).ToLocal(&value)) return false;
269
270 // The value can be either a single item or an array of items.
271
272 if (value->IsArray()) {
273 auto context = env->context();
274 auto values = value.As<v8::Array>();
275 uint32_t count = values->Length();
276 for (uint32_t n = 0; n < count; n++) {
277 v8::Local<v8::Value> item;
278 if (!values->Get(context, n).ToLocal(&item)) {
279 return false;
280 }
281 if constexpr (std::is_same<T, std::shared_ptr<crypto::KeyObjectData>>::
282 value) {
283 if (crypto::KeyObjectHandle::HasInstance(env, item)) {
284 crypto::KeyObjectHandle* handle;
285 ASSIGN_OR_RETURN_UNWRAP(&handle, item, false);
286 (options->*member).push_back(handle->Data());
287 } else {
288 return false;
289 }
290 } else if constexpr (std::is_same<T, Store>::value) {
291 if (item->IsArrayBufferView()) {
292 (options->*member).emplace_back(item.As<v8::ArrayBufferView>());
293 } else if (item->IsArrayBuffer()) {
294 (options->*member).emplace_back(item.As<v8::ArrayBuffer>());
295 } else {
296 return false;
297 }
298 }
299 }
300 } else {
301 if constexpr (std::is_same<T,
302 std::shared_ptr<crypto::KeyObjectData>>::value) {
303 if (crypto::KeyObjectHandle::HasInstance(env, value)) {
304 crypto::KeyObjectHandle* handle;
305 ASSIGN_OR_RETURN_UNWRAP(&handle, value, false);
306 (options->*member).push_back(handle->Data());
307 } else {
308 return false;
309 }
310 } else if constexpr (std::is_same<T, Store>::value) {
311 if (value->IsArrayBufferView()) {
312 (options->*member).emplace_back(value.As<v8::ArrayBufferView>());
313 } else if (value->IsArrayBuffer()) {
314 (options->*member).emplace_back(value.As<v8::ArrayBuffer>());
315 } else {
316 return false;
317 }
318 }
319 }
320 return true;
321 }
322 } // namespace
323
side() const324 Side TLSContext::side() const {
325 return side_;
326 }
327
options() const328 const TLSContext::Options& TLSContext::options() const {
329 return options_;
330 }
331
From(const SSL * ssl)332 inline const TLSContext& TLSContext::From(const SSL* ssl) {
333 auto ref = static_cast<ngtcp2_crypto_conn_ref*>(SSL_get_app_data(ssl));
334 TLSContext* context = ContainerOf(&TLSContext::conn_ref_, ref);
335 return *context;
336 }
337
From(SSL * ssl)338 inline TLSContext& TLSContext::From(SSL* ssl) {
339 auto ref = static_cast<ngtcp2_crypto_conn_ref*>(SSL_get_app_data(ssl));
340 TLSContext* context = ContainerOf(&TLSContext::conn_ref_, ref);
341 return *context;
342 }
343
TLSContext(Environment * env,Side side,Session * session,const Options & options)344 TLSContext::TLSContext(Environment* env,
345 Side side,
346 Session* session,
347 const Options& options)
348 : conn_ref_({getConnection, this}),
349 side_(side),
350 env_(env),
351 session_(session),
352 options_(options),
353 secure_context_(InitializeSecureContext(side, env, options)) {
354 CHECK(secure_context_);
355 ssl_.reset(SSL_new(secure_context_->ctx().get()));
356 CHECK(ssl_ && SSL_is_quic(ssl_.get()));
357
358 SSL_set_app_data(ssl_.get(), &conn_ref_);
359 SSL_set_verify(ssl_.get(), SSL_VERIFY_NONE, crypto::VerifyCallback);
360
361 // Enable tracing if the `--trace-tls` command line flag is used.
362 if (UNLIKELY(env->options()->trace_tls || options.enable_tls_trace))
363 EnableTrace(env, &bio_trace_, ssl_.get());
364
365 switch (side) {
366 case Side::CLIENT: {
367 SSL_set_connect_state(ssl_.get());
368 CHECK_EQ(0,
369 SSL_set_alpn_protos(ssl_.get(),
370 reinterpret_cast<const unsigned char*>(
371 options_.alpn.c_str()),
372 options_.alpn.length()));
373 CHECK_EQ(0,
374 SSL_set_tlsext_host_name(ssl_.get(), options_.hostname.c_str()));
375 break;
376 }
377 case Side::SERVER: {
378 SSL_set_accept_state(ssl_.get());
379 if (options.request_peer_certificate) {
380 int verify_mode = SSL_VERIFY_PEER;
381 if (options.reject_unauthorized)
382 verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
383 SSL_set_verify(ssl_.get(), verify_mode, crypto::VerifyCallback);
384 }
385 break;
386 }
387 default:
388 UNREACHABLE();
389 }
390 }
391
Start()392 void TLSContext::Start() {
393 ngtcp2_conn_set_tls_native_handle(*session_, ssl_.get());
394
395 TransportParams tp(TransportParams::Type::ENCRYPTED_EXTENSIONS,
396 ngtcp2_conn_get_local_transport_params(*session_));
397 Store store = tp.Encode(env_);
398 if (store && store.length() > 0) {
399 ngtcp2_vec vec = store;
400 SSL_set_quic_transport_params(ssl_.get(), vec.base, vec.len);
401 }
402 }
403
Keylog(const char * line) const404 void TLSContext::Keylog(const char* line) const {
405 session_->EmitKeylog(line);
406 }
407
Receive(ngtcp2_crypto_level crypto_level,uint64_t offset,const ngtcp2_vec & vec)408 int TLSContext::Receive(ngtcp2_crypto_level crypto_level,
409 uint64_t offset,
410 const ngtcp2_vec& vec) {
411 // ngtcp2 provides an implementation of this in
412 // ngtcp2_crypto_recv_crypto_data_cb but given that we are using the
413 // implementation specific error codes below, we can't use it.
414
415 if (UNLIKELY(session_->is_destroyed())) return NGTCP2_ERR_CALLBACK_FAILURE;
416
417 // Internally, this passes the handshake data off to openssl for processing.
418 // The handshake may or may not complete.
419 int ret = ngtcp2_crypto_read_write_crypto_data(
420 *session_, crypto_level, vec.base, vec.len);
421
422 switch (ret) {
423 case 0:
424 // Fall-through
425
426 // In either of following cases, the handshake is being paused waiting for
427 // user code to take action (for instance OCSP requests or client hello
428 // modification)
429 case NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_X509_LOOKUP:
430 [[fallthrough]];
431 case NGTCP2_CRYPTO_OPENSSL_ERR_TLS_WANT_CLIENT_HELLO_CB:
432 return 0;
433 }
434 return ret;
435 }
436
OnNewSession(SSL_SESSION * session)437 int TLSContext::OnNewSession(SSL_SESSION* session) {
438 // Used to generate and emit a SessionTicket for TLS session resumption.
439
440 // If there is nothing listening for the session ticket, don't both emitting.
441 if (!session_->wants_session_ticket()) return 0;
442
443 // Pre-fight to see how much space we need to allocate for the session ticket.
444 size_t size = i2d_SSL_SESSION(session, nullptr);
445
446 if (size > 0 && size < crypto::SecureContext::kMaxSessionSize) {
447 // Generate the actual ticket. If this fails, we'll simply carry on without
448 // emitting the ticket.
449 std::shared_ptr<BackingStore> ticket =
450 ArrayBuffer::NewBackingStore(env_->isolate(), size);
451 unsigned char* data = reinterpret_cast<unsigned char*>(ticket->Data());
452 if (i2d_SSL_SESSION(session, &data) <= 0) return 0;
453 session_->EmitSessionTicket(Store(std::move(ticket), size));
454 }
455 // If size == 0, there's no session ticket data to emit. Let's ignore it
456 // and continue without emitting the sessionticket event.
457
458 return 0;
459 }
460
InitiateKeyUpdate()461 bool TLSContext::InitiateKeyUpdate() {
462 if (session_->is_destroyed() || in_key_update_) return false;
463 auto leave = OnScopeLeave([this] { in_key_update_ = false; });
464 in_key_update_ = true;
465
466 return ngtcp2_conn_initiate_key_update(*session_, uv_hrtime()) == 0;
467 }
468
VerifyPeerIdentity()469 int TLSContext::VerifyPeerIdentity() {
470 return crypto::VerifyPeerCertificate(ssl_);
471 }
472
MaybeSetEarlySession(const SessionTicket & sessionTicket)473 void TLSContext::MaybeSetEarlySession(const SessionTicket& sessionTicket) {
474 TransportParams rtp(TransportParams::Type::ENCRYPTED_EXTENSIONS,
475 sessionTicket.transport_params());
476
477 // Ignore invalid remote transport parameters.
478 if (!rtp) return;
479
480 uv_buf_t buf = sessionTicket.ticket();
481 crypto::SSLSessionPointer ticket = crypto::GetTLSSession(
482 reinterpret_cast<unsigned char*>(buf.base), buf.len);
483
484 // Silently ignore invalid TLS session
485 if (!ticket || !SSL_SESSION_get_max_early_data(ticket.get())) return;
486
487 // The early data will just be ignored if it's invalid.
488 if (crypto::SetTLSSession(ssl_, ticket)) {
489 ngtcp2_conn_set_early_remote_transport_params(*session_, rtp);
490 session_->SetStreamOpenAllowed();
491 }
492 }
493
MemoryInfo(MemoryTracker * tracker) const494 void TLSContext::MemoryInfo(MemoryTracker* tracker) const {
495 tracker->TrackField("options", options_);
496 tracker->TrackField("secure_context", secure_context_);
497 }
498
cert(Environment * env) const499 MaybeLocal<Object> TLSContext::cert(Environment* env) const {
500 return crypto::X509Certificate::GetCert(env, ssl_);
501 }
502
peer_cert(Environment * env) const503 MaybeLocal<Object> TLSContext::peer_cert(Environment* env) const {
504 crypto::X509Certificate::GetPeerCertificateFlag flag =
505 side_ == Side::SERVER
506 ? crypto::X509Certificate::GetPeerCertificateFlag::SERVER
507 : crypto::X509Certificate::GetPeerCertificateFlag::NONE;
508 return crypto::X509Certificate::GetPeerCert(env, ssl_, flag);
509 }
510
cipher_name(Environment * env) const511 MaybeLocal<Value> TLSContext::cipher_name(Environment* env) const {
512 return crypto::GetCurrentCipherName(env, ssl_);
513 }
514
cipher_version(Environment * env) const515 MaybeLocal<Value> TLSContext::cipher_version(Environment* env) const {
516 return crypto::GetCurrentCipherVersion(env, ssl_);
517 }
518
ephemeral_key(Environment * env) const519 MaybeLocal<Object> TLSContext::ephemeral_key(Environment* env) const {
520 return crypto::GetEphemeralKey(env, ssl_);
521 }
522
servername() const523 const std::string_view TLSContext::servername() const {
524 const char* servername = crypto::GetServerName(ssl_.get());
525 return servername != nullptr ? std::string_view(servername)
526 : std::string_view();
527 }
528
alpn() const529 const std::string_view TLSContext::alpn() const {
530 const unsigned char* alpn_buf = nullptr;
531 unsigned int alpnlen;
532 SSL_get0_alpn_selected(ssl_.get(), &alpn_buf, &alpnlen);
533 return alpnlen ? std::string_view(reinterpret_cast<const char*>(alpn_buf),
534 alpnlen)
535 : std::string_view();
536 }
537
early_data_was_accepted() const538 bool TLSContext::early_data_was_accepted() const {
539 return (early_data_ &&
540 SSL_get_early_data_status(ssl_.get()) == SSL_EARLY_DATA_ACCEPTED);
541 }
542
MemoryInfo(MemoryTracker * tracker) const543 void TLSContext::Options::MemoryInfo(MemoryTracker* tracker) const {
544 tracker->TrackField("keys", keys);
545 tracker->TrackField("certs", certs);
546 tracker->TrackField("ca", ca);
547 tracker->TrackField("crl", crl);
548 }
549
getConnection(ngtcp2_crypto_conn_ref * ref)550 ngtcp2_conn* TLSContext::getConnection(ngtcp2_crypto_conn_ref* ref) {
551 TLSContext* context = ContainerOf(&TLSContext::conn_ref_, ref);
552 return *context->session_;
553 }
554
From(Environment * env,Local<Value> value)555 Maybe<const TLSContext::Options> TLSContext::Options::From(Environment* env,
556 Local<Value> value) {
557 if (value.IsEmpty() || !value->IsObject()) {
558 return Nothing<const Options>();
559 }
560
561 auto& state = BindingData::Get(env);
562 auto params = value.As<Object>();
563 Options options;
564
565 #define SET_VECTOR(Type, name) \
566 SetOption<Type, TLSContext::Options, &TLSContext::Options::name>( \
567 env, &options, params, state.name##_string())
568
569 #define SET(name) \
570 SetOption<TLSContext::Options, &TLSContext::Options::name>( \
571 env, &options, params, state.name##_string())
572
573 if (!SET(keylog) || !SET(reject_unauthorized) || !SET(enable_tls_trace) ||
574 !SET(request_peer_certificate) || !SET(verify_hostname_identity) ||
575 !SET(alpn) || !SET(hostname) || !SET(session_id_ctx) || !SET(ciphers) ||
576 !SET(groups) ||
577 !SET_VECTOR(std::shared_ptr<crypto::KeyObjectData>, keys) ||
578 !SET_VECTOR(Store, certs) || !SET_VECTOR(Store, ca) ||
579 !SET_VECTOR(Store, crl)) {
580 return Nothing<const Options>();
581 }
582
583 return Just<const Options>(options);
584 }
585
586 } // namespace quic
587 } // namespace node
588
589 #endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
590