• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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