/src/node/src/quic/tlscontext.cc
Line | Count | Source |
1 | | #if HAVE_OPENSSL |
2 | | #include "guard.h" |
3 | | #ifndef OPENSSL_NO_QUIC |
4 | | #include <async_wrap-inl.h> |
5 | | #include <base_object-inl.h> |
6 | | #include <crypto/crypto_util.h> |
7 | | #include <debug_utils-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_ossl.h> |
13 | | #include <node_sockaddr-inl.h> |
14 | | #include <openssl/ssl.h> |
15 | | #include <v8.h> |
16 | | #include "bindingdata.h" |
17 | | #include "defs.h" |
18 | | #include "session.h" |
19 | | #include "tlscontext.h" |
20 | | #include "transportparams.h" |
21 | | |
22 | | namespace node { |
23 | | |
24 | | using ncrypto::BIOPointer; |
25 | | using ncrypto::ClearErrorOnReturn; |
26 | | using ncrypto::MarkPopErrorOnReturn; |
27 | | using ncrypto::SSLCtxPointer; |
28 | | using ncrypto::SSLPointer; |
29 | | using ncrypto::SSLSessionPointer; |
30 | | using ncrypto::X509Pointer; |
31 | | using v8::ArrayBuffer; |
32 | | using v8::Just; |
33 | | using v8::Local; |
34 | | using v8::Maybe; |
35 | | using v8::MaybeLocal; |
36 | | using v8::Nothing; |
37 | | using v8::Object; |
38 | | using v8::Undefined; |
39 | | using v8::Value; |
40 | | |
41 | | namespace quic { |
42 | | |
43 | | // ============================================================================ |
44 | | |
45 | | namespace { |
46 | | // Performance optimization recommended by ngtcp2. Need to investigate why |
47 | | // this causes some tests to fail. |
48 | | // auto _ = []() { |
49 | | // if (ngtcp2_crypto_ossl_init() != 0) { |
50 | | // assert(0); |
51 | | // abort(); |
52 | | // } |
53 | | |
54 | | // return 0; |
55 | | // }(); |
56 | | |
57 | | // Temporarily wraps an SSL pointer but does not take ownership. |
58 | | // Use by a few of the TLSSession methods that need access to the SSL* |
59 | | // pointer held by the OSSLContext but cannot take ownership of it. |
60 | | class SSLPointerRef final { |
61 | | public: |
62 | 0 | inline SSLPointerRef(SSL* ssl) : temp_(ssl) { CHECK(temp_); } |
63 | 0 | inline ~SSLPointerRef() { release(); } |
64 | | DISALLOW_COPY_AND_MOVE(SSLPointerRef) |
65 | 0 | inline operator const SSLPointer&() const { return temp_; } |
66 | 0 | inline const SSLPointer* operator->() const { return &temp_; } |
67 | 0 | inline const SSLPointer& operator*() const { return temp_; } |
68 | 0 | inline void release() { temp_.release(); } |
69 | | |
70 | | private: |
71 | | SSLPointer temp_; |
72 | | }; |
73 | | |
74 | 0 | void EnableTrace(Environment* env, BIOPointer* bio, SSL* ssl) { |
75 | | #if HAVE_SSL_TRACE |
76 | | static bool warn_trace_tls = true; |
77 | | if (warn_trace_tls) { |
78 | | warn_trace_tls = false; |
79 | | ProcessEmitWarning(env, |
80 | | "Enabling --trace-tls can expose sensitive data in " |
81 | | "the resulting log"); |
82 | | } |
83 | | if (!*bio) { |
84 | | bio->reset(BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT)); |
85 | | SSL_set_msg_callback( |
86 | | ssl, |
87 | | [](int write_p, |
88 | | int version, |
89 | | int content_type, |
90 | | const void* buf, |
91 | | size_t len, |
92 | | SSL* ssl, |
93 | | void* arg) -> void { |
94 | | MarkPopErrorOnReturn mark_pop_error_on_return; |
95 | | SSL_trace(write_p, version, content_type, buf, len, ssl, arg); |
96 | | }); |
97 | | SSL_set_msg_callback_arg(ssl, bio->get()); |
98 | | } |
99 | | #endif |
100 | 0 | } |
101 | | |
102 | | template <typename T, typename Opt, std::vector<T> Opt::*member> |
103 | | bool SetOption(Environment* env, |
104 | | Opt* options, |
105 | | const Local<Object>& object, |
106 | 0 | const Local<v8::String>& name) { |
107 | 0 | Local<Value> value; |
108 | 0 | if (!object->Get(env->context(), name).ToLocal(&value)) return false; |
109 | | |
110 | 0 | if (value->IsUndefined()) return true; |
111 | | |
112 | | // The value can be either a single item or an array of items. |
113 | | |
114 | 0 | if (value->IsArray()) { |
115 | 0 | auto context = env->context(); |
116 | 0 | auto values = value.As<v8::Array>(); |
117 | 0 | uint32_t count = values->Length(); |
118 | 0 | for (uint32_t n = 0; n < count; n++) { |
119 | 0 | Local<Value> item; |
120 | 0 | if (!values->Get(context, n).ToLocal(&item)) { |
121 | 0 | return false; |
122 | 0 | } |
123 | 0 | if constexpr (std::is_same<T, crypto::KeyObjectData>::value) { |
124 | 0 | if (crypto::KeyObjectHandle::HasInstance(env, item)) { |
125 | 0 | crypto::KeyObjectHandle* handle; |
126 | 0 | ASSIGN_OR_RETURN_UNWRAP(&handle, item, false); |
127 | 0 | (options->*member).push_back(handle->Data().addRef()); |
128 | 0 | } else { |
129 | 0 | Utf8Value namestr(env->isolate(), name); |
130 | 0 | THROW_ERR_INVALID_ARG_TYPE( |
131 | 0 | env, "%s value must be a key object", namestr); |
132 | 0 | return false; |
133 | 0 | } |
134 | 0 | } else if constexpr (std::is_same<T, Store>::value) { |
135 | 0 | if (item->IsArrayBufferView()) { |
136 | 0 | Store store; |
137 | 0 | if (!Store::From(item.As<v8::ArrayBufferView>()).To(&store)) { |
138 | 0 | return false; |
139 | 0 | } |
140 | 0 | (options->*member).push_back(std::move(store)); |
141 | 0 | } else if (item->IsArrayBuffer()) { |
142 | 0 | Store store; |
143 | 0 | if (!Store::From(item.As<ArrayBuffer>()).To(&store)) { |
144 | 0 | return false; |
145 | 0 | } |
146 | 0 | (options->*member).push_back(std::move(store)); |
147 | 0 | } else { |
148 | 0 | Utf8Value namestr(env->isolate(), name); |
149 | 0 | THROW_ERR_INVALID_ARG_TYPE( |
150 | 0 | env, |
151 | 0 | "%s value must be an array buffer or array buffer view", |
152 | 0 | *namestr); |
153 | 0 | return false; |
154 | 0 | } |
155 | 0 | } |
156 | 0 | } |
157 | 0 | } else { |
158 | 0 | if constexpr (std::is_same<T, crypto::KeyObjectData>::value) { |
159 | 0 | if (crypto::KeyObjectHandle::HasInstance(env, value)) { |
160 | 0 | crypto::KeyObjectHandle* handle; |
161 | 0 | ASSIGN_OR_RETURN_UNWRAP(&handle, value, false); |
162 | 0 | (options->*member).push_back(handle->Data().addRef()); |
163 | 0 | } else { |
164 | 0 | Utf8Value namestr(env->isolate(), name); |
165 | 0 | THROW_ERR_INVALID_ARG_TYPE( |
166 | 0 | env, "%s value must be a key object", namestr); |
167 | 0 | return false; |
168 | 0 | } |
169 | 0 | } else if constexpr (std::is_same<T, Store>::value) { |
170 | 0 | if (value->IsArrayBufferView()) { |
171 | 0 | Store store; |
172 | 0 | if (!Store::From(value.As<v8::ArrayBufferView>()).To(&store)) { |
173 | 0 | return false; |
174 | 0 | } |
175 | 0 | (options->*member).push_back(std::move(store)); |
176 | 0 | } else if (value->IsArrayBuffer()) { |
177 | 0 | Store store; |
178 | 0 | if (!Store::From(value.As<ArrayBuffer>()).To(&store)) { |
179 | 0 | return false; |
180 | 0 | } |
181 | 0 | (options->*member).push_back(std::move(store)); |
182 | 0 | } else { |
183 | 0 | Utf8Value namestr(env->isolate(), name); |
184 | 0 | THROW_ERR_INVALID_ARG_TYPE( |
185 | 0 | env, |
186 | 0 | "%s value must be an array buffer or array buffer view", |
187 | 0 | *namestr); |
188 | 0 | return false; |
189 | 0 | } |
190 | 0 | } |
191 | 0 | } |
192 | 0 | return true; |
193 | 0 | } Unexecuted instantiation: tlscontext.cc:_ZN4node4quic12_GLOBAL__N_19SetOptionINS_6crypto13KeyObjectDataENS0_10TLSContext7OptionsETnMT0_NSt3__16vectorIT_NS8_9allocatorISA_EEEEXadL_ZNS6_4keysEEEEEbPNS_11EnvironmentEPS7_RKN2v85LocalINSI_6ObjectEEERKNSJ_INSI_6StringEEE Unexecuted instantiation: tlscontext.cc:_ZN4node4quic12_GLOBAL__N_19SetOptionINS0_5StoreENS0_10TLSContext7OptionsETnMT0_NSt3__16vectorIT_NS7_9allocatorIS9_EEEEXadL_ZNS5_5certsEEEEEbPNS_11EnvironmentEPS6_RKN2v85LocalINSH_6ObjectEEERKNSI_INSH_6StringEEE Unexecuted instantiation: tlscontext.cc:_ZN4node4quic12_GLOBAL__N_19SetOptionINS0_5StoreENS0_10TLSContext7OptionsETnMT0_NSt3__16vectorIT_NS7_9allocatorIS9_EEEEXadL_ZNS5_2caEEEEEbPNS_11EnvironmentEPS6_RKN2v85LocalINSH_6ObjectEEERKNSI_INSH_6StringEEE Unexecuted instantiation: tlscontext.cc:_ZN4node4quic12_GLOBAL__N_19SetOptionINS0_5StoreENS0_10TLSContext7OptionsETnMT0_NSt3__16vectorIT_NS7_9allocatorIS9_EEEEXadL_ZNS5_3crlEEEEEbPNS_11EnvironmentEPS6_RKN2v85LocalINSH_6ObjectEEERKNSI_INSH_6StringEEE |
194 | | } // namespace |
195 | | |
196 | 0 | OSSLContext::OSSLContext() { |
197 | 0 | CHECK_EQ(ngtcp2_crypto_ossl_ctx_new(&ctx_, nullptr), 0); |
198 | 0 | } |
199 | | |
200 | 0 | OSSLContext::~OSSLContext() { |
201 | 0 | reset(); |
202 | 0 | } |
203 | | |
204 | 0 | void OSSLContext::reset() { |
205 | 0 | if (ctx_) { |
206 | 0 | SSL_set_app_data(*this, nullptr); |
207 | 0 | ngtcp2_conn_set_tls_native_handle(connection_, nullptr); |
208 | 0 | ngtcp2_crypto_ossl_ctx_del(ctx_); |
209 | 0 | ctx_ = nullptr; |
210 | 0 | connection_ = nullptr; |
211 | 0 | } |
212 | 0 | } |
213 | | |
214 | 0 | OSSLContext::operator SSL*() const { |
215 | 0 | return ngtcp2_crypto_ossl_ctx_get_ssl(ctx_); |
216 | 0 | } |
217 | | |
218 | 0 | OSSLContext::operator ngtcp2_crypto_ossl_ctx*() const { |
219 | 0 | return ctx_; |
220 | 0 | } |
221 | | |
222 | | void OSSLContext::Initialize(SSL* ssl, |
223 | | ngtcp2_crypto_conn_ref* ref, |
224 | | ngtcp2_conn* connection, |
225 | 0 | SSL_CTX* ssl_ctx) { |
226 | 0 | CHECK(ssl); |
227 | 0 | ngtcp2_crypto_ossl_ctx_set_ssl(ctx_, ssl); |
228 | 0 | SSL_set_app_data(*this, ref); |
229 | | // TODO(@jasnell): Later when BoringSSL is also supported, the native |
230 | | // handle will be different. The ngtcp2_crypto_ossl.h impl requires |
231 | | // that the native handle be set to the ngtcp2_crypto_ossl_ctx. So |
232 | | // this will need to be updated to support both cases. |
233 | 0 | ngtcp2_conn_set_tls_native_handle(connection, ctx_); |
234 | 0 | connection_ = connection; |
235 | 0 | } |
236 | | |
237 | 0 | std::string OSSLContext::get_cipher_name() const { |
238 | 0 | return SSL_get_cipher_name(*this); |
239 | 0 | } |
240 | | |
241 | 0 | std::string OSSLContext::get_selected_alpn() const { |
242 | 0 | const unsigned char* alpn = nullptr; |
243 | 0 | unsigned int len; |
244 | 0 | SSL_get0_alpn_selected(*this, &alpn, &len); |
245 | 0 | return std::string(alpn, alpn + len); |
246 | 0 | } |
247 | | |
248 | 0 | std::string_view OSSLContext::get_negotiated_group() const { |
249 | 0 | auto name = SSL_get0_group_name(*this); |
250 | 0 | if (name == nullptr) return ""; |
251 | 0 | return name; |
252 | 0 | } |
253 | | |
254 | 0 | bool OSSLContext::set_alpn_protocols(std::string_view protocols) const { |
255 | 0 | return SSL_set_alpn_protos( |
256 | 0 | *this, |
257 | 0 | reinterpret_cast<const unsigned char*>(protocols.data()), |
258 | 0 | protocols.size()) == 0; |
259 | 0 | } |
260 | | |
261 | 0 | bool OSSLContext::set_hostname(std::string_view hostname) const { |
262 | 0 | if (!hostname.empty()) { |
263 | 0 | SSL_set_tlsext_host_name(*this, hostname.data()); |
264 | 0 | } else { |
265 | 0 | SSL_set_tlsext_host_name(*this, "localhost"); |
266 | 0 | } |
267 | 0 | return true; |
268 | 0 | } |
269 | | |
270 | 0 | bool OSSLContext::set_early_data_enabled() const { |
271 | 0 | return SSL_set_quic_tls_early_data_enabled(*this, 1) == 1; |
272 | 0 | } |
273 | | |
274 | 0 | bool OSSLContext::set_transport_params(const ngtcp2_vec& tp) const { |
275 | 0 | return SSL_set_quic_tls_transport_params(*this, tp.base, tp.len) == 1; |
276 | 0 | } |
277 | | |
278 | 0 | bool OSSLContext::get_early_data_accepted() const { |
279 | 0 | return SSL_get_early_data_status(*this) == SSL_EARLY_DATA_ACCEPTED; |
280 | 0 | } |
281 | | |
282 | 0 | bool OSSLContext::ConfigureServer() const { |
283 | 0 | if (ngtcp2_crypto_ossl_configure_server_session(*this) != 0) return false; |
284 | 0 | SSL_set_accept_state(*this); |
285 | 0 | return set_early_data_enabled(); |
286 | 0 | } |
287 | | |
288 | 0 | bool OSSLContext::ConfigureClient() const { |
289 | 0 | if (ngtcp2_crypto_ossl_configure_client_session(*this) != 0) return false; |
290 | 0 | SSL_set_connect_state(*this); |
291 | 0 | return true; |
292 | 0 | } |
293 | | |
294 | | // ============================================================================ |
295 | | |
296 | 0 | std::shared_ptr<TLSContext> TLSContext::CreateClient(const Options& options) { |
297 | 0 | return std::make_shared<TLSContext>(Side::CLIENT, options); |
298 | 0 | } |
299 | | |
300 | 0 | std::shared_ptr<TLSContext> TLSContext::CreateServer(const Options& options) { |
301 | 0 | return std::make_shared<TLSContext>(Side::SERVER, options); |
302 | 0 | } |
303 | | |
304 | | TLSContext::TLSContext(Side side, const Options& options) |
305 | 0 | : side_(side), options_(options), ctx_(Initialize()) {} |
306 | | |
307 | 0 | TLSContext::operator SSL_CTX*() const { |
308 | 0 | DCHECK(ctx_); |
309 | 0 | return ctx_.get(); |
310 | 0 | } |
311 | | |
312 | | int TLSContext::OnSelectAlpn(SSL* ssl, |
313 | | const unsigned char** out, |
314 | | unsigned char* outlen, |
315 | | const unsigned char* in, |
316 | | unsigned int inlen, |
317 | 0 | void* arg) { |
318 | 0 | static constexpr size_t kMaxAlpnLen = 255; |
319 | 0 | auto& session = TLSSession::From(ssl); |
320 | |
|
321 | 0 | const auto& requested = session.context().options().protocol; |
322 | 0 | if (requested.length() > kMaxAlpnLen) return SSL_TLSEXT_ERR_NOACK; |
323 | | |
324 | | // The Session supports exactly one ALPN identifier. If that does not match |
325 | | // any of the ALPN identifiers provided in the client request, then we fail |
326 | | // here. Note that this will not fail the TLS handshake, so we have to check |
327 | | // later if the ALPN matches the expected identifier or not. |
328 | | // |
329 | | // TODO(@jasnell): We might eventually want to support the ability to |
330 | | // negotiate multiple possible ALPN's on a single endpoint/session but for |
331 | | // now, we only support one. |
332 | 0 | if (SSL_select_next_proto( |
333 | 0 | const_cast<unsigned char**>(out), |
334 | 0 | outlen, |
335 | 0 | reinterpret_cast<const unsigned char*>(requested.data()), |
336 | 0 | requested.length(), |
337 | 0 | in, |
338 | 0 | inlen) == OPENSSL_NPN_NO_OVERLAP) { |
339 | 0 | Debug(&session.session(), "ALPN negotiation failed"); |
340 | 0 | return SSL_TLSEXT_ERR_NOACK; |
341 | 0 | } |
342 | | |
343 | 0 | Debug(&session.session(), "ALPN negotiation succeeded"); |
344 | 0 | return SSL_TLSEXT_ERR_OK; |
345 | 0 | } |
346 | | |
347 | 0 | int TLSContext::OnNewSession(SSL* ssl, SSL_SESSION* sess) { |
348 | 0 | auto& session = TLSSession::From(ssl).session(); |
349 | | |
350 | | // If there is nothing listening for the session ticket, do not bother. |
351 | 0 | if (session.wants_session_ticket()) { |
352 | 0 | Debug(&session, "Preparing TLS session resumption ticket"); |
353 | | |
354 | | // Pre-fight to see how much space we need to allocate for the session |
355 | | // ticket. |
356 | 0 | size_t size = i2d_SSL_SESSION(sess, nullptr); |
357 | | |
358 | | // If size is 0, the size is greater than our max, or there is not |
359 | | // enough memory to allocate the backing store, then we ignore it |
360 | | // and continue without emitting the sessionticket event. |
361 | 0 | if (size > 0 && size <= crypto::SecureContext::kMaxSessionSize) { |
362 | 0 | JS_TRY_ALLOCATE_BACKING_OR_RETURN(session.env(), ticket, size, 0); |
363 | 0 | auto data = reinterpret_cast<unsigned char*>(ticket->Data()); |
364 | 0 | if (i2d_SSL_SESSION(sess, &data) > 0) { |
365 | 0 | session.EmitSessionTicket(Store(std::move(ticket), size)); |
366 | 0 | } |
367 | 0 | } |
368 | 0 | } |
369 | | |
370 | 0 | return 0; |
371 | 0 | } |
372 | | |
373 | 0 | void TLSContext::OnKeylog(const SSL* ssl, const char* line) { |
374 | 0 | TLSSession::From(ssl).session().EmitKeylog(line); |
375 | 0 | } |
376 | | |
377 | | int TLSContext::OnVerifyClientCertificate(int preverify_ok, |
378 | 0 | X509_STORE_CTX* ctx) { |
379 | | // TODO(@jasnell): Implement the logic to verify the client certificate |
380 | 0 | return 1; |
381 | 0 | } |
382 | | |
383 | | std::unique_ptr<TLSSession> TLSContext::NewSession( |
384 | 0 | Session* session, const std::optional<SessionTicket>& maybeSessionTicket) { |
385 | | // Passing a session ticket only makes sense with a client session. |
386 | 0 | CHECK_IMPLIES(session->is_server(), !maybeSessionTicket.has_value()); |
387 | 0 | return std::make_unique<TLSSession>( |
388 | 0 | session, shared_from_this(), maybeSessionTicket); |
389 | 0 | } |
390 | | |
391 | 0 | SSLCtxPointer TLSContext::Initialize() { |
392 | 0 | SSLCtxPointer ctx; |
393 | 0 | switch (side_) { |
394 | 0 | case Side::SERVER: { |
395 | 0 | static constexpr unsigned char kSidCtx[] = "Node.js QUIC Server"; |
396 | 0 | ctx = SSLCtxPointer::NewServer(); |
397 | 0 | if (!ctx) [[unlikely]] { |
398 | 0 | validation_error_ = "Failed to create SSL_CTX for server"; |
399 | 0 | return {}; |
400 | 0 | } |
401 | | |
402 | 0 | if (SSL_CTX_set_max_early_data(ctx.get(), UINT32_MAX) != 1) { |
403 | 0 | validation_error_ = "Failed to set max early data"; |
404 | 0 | return {}; |
405 | 0 | } |
406 | 0 | SSL_CTX_set_options(ctx.get(), |
407 | 0 | (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | |
408 | 0 | SSL_OP_SINGLE_ECDH_USE | |
409 | 0 | SSL_OP_CIPHER_SERVER_PREFERENCE | |
410 | 0 | SSL_OP_NO_ANTI_REPLAY); |
411 | |
|
412 | 0 | SSL_CTX_set_mode(ctx.get(), SSL_MODE_RELEASE_BUFFERS); |
413 | 0 | SSL_CTX_set_alpn_select_cb(ctx.get(), OnSelectAlpn, this); |
414 | |
|
415 | 0 | if (SSL_CTX_set_session_id_context( |
416 | 0 | ctx.get(), kSidCtx, sizeof(kSidCtx) - 1) != 1) { |
417 | 0 | validation_error_ = "Failed to set session ID context"; |
418 | 0 | return {}; |
419 | 0 | } |
420 | | |
421 | 0 | if (options_.verify_client) [[likely]] { |
422 | 0 | SSL_CTX_set_verify(ctx.get(), |
423 | 0 | SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | |
424 | 0 | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, |
425 | 0 | OnVerifyClientCertificate); |
426 | 0 | } |
427 | | |
428 | | // TODO(@jasnell): There's a bug int the GenerateCallback flow somewhere. |
429 | | // Need to update in order to support session tickets. |
430 | | // CHECK_EQ(SSL_CTX_set_session_ticket_cb(ctx.get(), |
431 | | // SessionTicket::GenerateCallback, |
432 | | // SessionTicket::DecryptedCallback, |
433 | | // nullptr), |
434 | | // 1); |
435 | 0 | break; |
436 | 0 | } |
437 | 0 | case Side::CLIENT: { |
438 | 0 | ctx = SSLCtxPointer::NewClient(); |
439 | |
|
440 | 0 | SSL_CTX_set_session_cache_mode( |
441 | 0 | ctx.get(), SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL); |
442 | 0 | SSL_CTX_sess_set_new_cb(ctx.get(), OnNewSession); |
443 | 0 | break; |
444 | 0 | } |
445 | 0 | } |
446 | | |
447 | 0 | SSL_CTX_set_default_verify_paths(ctx.get()); |
448 | 0 | SSL_CTX_set_keylog_callback(ctx.get(), OnKeylog); |
449 | |
|
450 | 0 | if (SSL_CTX_set_ciphersuites(ctx.get(), options_.ciphers.c_str()) != 1) { |
451 | 0 | validation_error_ = "Invalid cipher suite"; |
452 | 0 | return SSLCtxPointer(); |
453 | 0 | } |
454 | | |
455 | 0 | if (SSL_CTX_set1_groups_list(ctx.get(), options_.groups.c_str()) != 1) { |
456 | 0 | validation_error_ = "Invalid cipher groups"; |
457 | 0 | return SSLCtxPointer(); |
458 | 0 | } |
459 | | |
460 | 0 | { |
461 | 0 | ClearErrorOnReturn clear_error_on_return; |
462 | 0 | if (options_.ca.empty()) { |
463 | 0 | auto store = crypto::GetOrCreateRootCertStore(); |
464 | 0 | X509_STORE_up_ref(store); |
465 | 0 | SSL_CTX_set_cert_store(ctx.get(), store); |
466 | 0 | } else { |
467 | 0 | for (const auto& ca : options_.ca) { |
468 | 0 | uv_buf_t buf = ca; |
469 | 0 | if (buf.len == 0) { |
470 | 0 | auto store = crypto::GetOrCreateRootCertStore(); |
471 | 0 | X509_STORE_up_ref(store); |
472 | 0 | SSL_CTX_set_cert_store(ctx.get(), store); |
473 | 0 | } else { |
474 | 0 | BIOPointer bio = crypto::NodeBIO::NewFixed(buf.base, buf.len); |
475 | 0 | CHECK(bio); |
476 | 0 | X509_STORE* cert_store = SSL_CTX_get_cert_store(ctx.get()); |
477 | 0 | while ( |
478 | 0 | auto x509 = X509Pointer(PEM_read_bio_X509_AUX( |
479 | 0 | bio.get(), nullptr, crypto::NoPasswordCallback, nullptr))) { |
480 | 0 | if (cert_store == crypto::GetOrCreateRootCertStore()) { |
481 | 0 | cert_store = crypto::NewRootCertStore(); |
482 | 0 | SSL_CTX_set_cert_store(ctx.get(), cert_store); |
483 | 0 | } |
484 | 0 | CHECK_EQ(1, X509_STORE_add_cert(cert_store, x509.get())); |
485 | 0 | CHECK_EQ(1, SSL_CTX_add_client_CA(ctx.get(), x509.get())); |
486 | 0 | } |
487 | 0 | } |
488 | 0 | } |
489 | 0 | } |
490 | 0 | } |
491 | | |
492 | 0 | { |
493 | 0 | ClearErrorOnReturn clear_error_on_return; |
494 | 0 | for (const auto& cert : options_.certs) { |
495 | 0 | uv_buf_t buf = cert; |
496 | 0 | if (buf.len > 0) { |
497 | 0 | BIOPointer bio = crypto::NodeBIO::NewFixed(buf.base, buf.len); |
498 | 0 | CHECK(bio); |
499 | 0 | cert_.reset(); |
500 | 0 | issuer_.reset(); |
501 | 0 | if (crypto::SSL_CTX_use_certificate_chain( |
502 | 0 | ctx.get(), std::move(bio), &cert_, &issuer_) == 0) { |
503 | 0 | validation_error_ = "Invalid certificate"; |
504 | 0 | return SSLCtxPointer(); |
505 | 0 | } |
506 | 0 | } |
507 | 0 | } |
508 | 0 | } |
509 | | |
510 | 0 | { |
511 | 0 | ClearErrorOnReturn clear_error_on_return; |
512 | 0 | for (const auto& key : options_.keys) { |
513 | 0 | if (key.GetKeyType() != crypto::KeyType::kKeyTypePrivate) { |
514 | 0 | validation_error_ = "Invalid key"; |
515 | 0 | return SSLCtxPointer(); |
516 | 0 | } |
517 | 0 | if (!SSL_CTX_use_PrivateKey(ctx.get(), key.GetAsymmetricKey().get())) { |
518 | 0 | validation_error_ = "Invalid key"; |
519 | 0 | return SSLCtxPointer(); |
520 | 0 | } |
521 | 0 | } |
522 | 0 | } |
523 | | |
524 | 0 | { |
525 | 0 | ClearErrorOnReturn clear_error_on_return; |
526 | 0 | for (const auto& crl : options_.crl) { |
527 | 0 | uv_buf_t buf = crl; |
528 | 0 | BIOPointer bio = crypto::NodeBIO::NewFixed(buf.base, buf.len); |
529 | 0 | DeleteFnPtr<X509_CRL, X509_CRL_free> crlptr(PEM_read_bio_X509_CRL( |
530 | 0 | bio.get(), nullptr, crypto::NoPasswordCallback, nullptr)); |
531 | |
|
532 | 0 | if (!crlptr) { |
533 | 0 | validation_error_ = "Invalid CRL"; |
534 | 0 | return SSLCtxPointer(); |
535 | 0 | } |
536 | | |
537 | 0 | X509_STORE* cert_store = SSL_CTX_get_cert_store(ctx.get()); |
538 | 0 | if (cert_store == crypto::GetOrCreateRootCertStore()) { |
539 | 0 | cert_store = crypto::NewRootCertStore(); |
540 | 0 | SSL_CTX_set_cert_store(ctx.get(), cert_store); |
541 | 0 | } |
542 | |
|
543 | 0 | CHECK_EQ(1, X509_STORE_add_crl(cert_store, crlptr.get())); |
544 | 0 | CHECK_EQ( |
545 | 0 | 1, |
546 | 0 | X509_STORE_set_flags( |
547 | 0 | cert_store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL)); |
548 | 0 | } |
549 | 0 | } |
550 | | |
551 | 0 | { |
552 | 0 | ClearErrorOnReturn clear_error_on_return; |
553 | 0 | if (options_.verify_private_key && |
554 | 0 | SSL_CTX_check_private_key(ctx.get()) != 1) { |
555 | 0 | validation_error_ = "Invalid private key"; |
556 | 0 | return SSLCtxPointer(); |
557 | 0 | } |
558 | 0 | } |
559 | | |
560 | 0 | return ctx; |
561 | 0 | } |
562 | | |
563 | 0 | void TLSContext::MemoryInfo(MemoryTracker* tracker) const { |
564 | 0 | tracker->TrackField("options", options_); |
565 | 0 | } |
566 | | |
567 | | Maybe<TLSContext::Options> TLSContext::Options::From(Environment* env, |
568 | 0 | Local<Value> value) { |
569 | 0 | if (value.IsEmpty()) { |
570 | 0 | return Nothing<Options>(); |
571 | 0 | } |
572 | | |
573 | 0 | Options options; |
574 | 0 | auto& state = BindingData::Get(env); |
575 | |
|
576 | 0 | if (value->IsUndefined()) { |
577 | 0 | return Just(kDefault); |
578 | 0 | } |
579 | | |
580 | 0 | if (!value->IsObject()) { |
581 | 0 | THROW_ERR_INVALID_ARG_TYPE(env, "tls options must be an object"); |
582 | 0 | return Nothing<Options>(); |
583 | 0 | } |
584 | | |
585 | 0 | auto params = value.As<Object>(); |
586 | |
|
587 | 0 | #define SET_VECTOR(Type, name) \ |
588 | 0 | SetOption<Type, TLSContext::Options, &TLSContext::Options::name>( \ |
589 | 0 | env, &options, params, state.name##_string()) |
590 | |
|
591 | 0 | #define SET(name) \ |
592 | 0 | SetOption<TLSContext::Options, &TLSContext::Options::name>( \ |
593 | 0 | env, &options, params, state.name##_string()) |
594 | |
|
595 | 0 | if (!SET(verify_client) || !SET(enable_tls_trace) || !SET(protocol) || |
596 | 0 | !SET(servername) || !SET(ciphers) || !SET(groups) || |
597 | 0 | !SET(verify_private_key) || !SET(keylog) || |
598 | 0 | !SET_VECTOR(crypto::KeyObjectData, keys) || !SET_VECTOR(Store, certs) || |
599 | 0 | !SET_VECTOR(Store, ca) || !SET_VECTOR(Store, crl)) { |
600 | 0 | return Nothing<Options>(); |
601 | 0 | } |
602 | | |
603 | 0 | return Just<Options>(options); |
604 | 0 | } |
605 | | |
606 | 0 | std::string TLSContext::Options::ToString() const { |
607 | 0 | DebugIndentScope indent; |
608 | 0 | auto prefix = indent.Prefix(); |
609 | 0 | std::string res("{"); |
610 | 0 | res += prefix + "protocol: " + protocol; |
611 | 0 | res += prefix + "servername: " + servername; |
612 | 0 | res += |
613 | 0 | prefix + "keylog: " + (keylog ? std::string("yes") : std::string("no")); |
614 | 0 | res += prefix + "verify client: " + |
615 | 0 | (verify_client ? std::string("yes") : std::string("no")); |
616 | 0 | res += prefix + "enable_tls_trace: " + |
617 | 0 | (enable_tls_trace ? std::string("yes") : std::string("no")); |
618 | 0 | res += prefix + "verify private key: " + |
619 | 0 | (verify_private_key ? std::string("yes") : std::string("no")); |
620 | 0 | res += prefix + "ciphers: " + ciphers; |
621 | 0 | res += prefix + "groups: " + groups; |
622 | 0 | res += prefix + "keys: " + std::to_string(keys.size()); |
623 | 0 | res += prefix + "certs: " + std::to_string(certs.size()); |
624 | 0 | res += prefix + "ca: " + std::to_string(ca.size()); |
625 | 0 | res += prefix + "crl: " + std::to_string(crl.size()); |
626 | 0 | res += indent.Close(); |
627 | 0 | return res; |
628 | 0 | } |
629 | | |
630 | 0 | void TLSContext::Options::MemoryInfo(MemoryTracker* tracker) const { |
631 | 0 | tracker->TrackField("keys", keys); |
632 | 0 | tracker->TrackField("certs", certs); |
633 | 0 | tracker->TrackField("ca", ca); |
634 | 0 | tracker->TrackField("crl", crl); |
635 | 0 | } |
636 | | |
637 | | const TLSContext::Options TLSContext::Options::kDefault = {}; |
638 | | |
639 | | // ============================================================================ |
640 | | |
641 | 0 | const TLSSession& TLSSession::From(const SSL* ssl) { |
642 | 0 | auto ref = static_cast<ngtcp2_crypto_conn_ref*>(SSL_get_app_data(ssl)); |
643 | 0 | CHECK_NOT_NULL(ref); |
644 | 0 | return *static_cast<TLSSession*>(ref->user_data); |
645 | 0 | } |
646 | | |
647 | | TLSSession::TLSSession(Session* session, |
648 | | std::shared_ptr<TLSContext> context, |
649 | | const std::optional<SessionTicket>& maybeSessionTicket) |
650 | 0 | : ref_({connection, this}), |
651 | 0 | context_(std::move(context)), |
652 | 0 | session_(session) { |
653 | 0 | Debug(session_, "Created new TLS session for %s", session->config().dcid); |
654 | 0 | Initialize(maybeSessionTicket); |
655 | 0 | if (!ossl_context_) [[unlikely]] { |
656 | 0 | Debug(session_, |
657 | 0 | "Failed to initialize TLS session: %s", |
658 | 0 | validation_error_.empty() ? "unknown error" : validation_error_); |
659 | 0 | } |
660 | 0 | } |
661 | | |
662 | 0 | TLSSession::operator SSL*() const { |
663 | 0 | return ossl_context_; |
664 | 0 | } |
665 | | |
666 | 0 | bool TLSSession::early_data_was_accepted() const { |
667 | 0 | CHECK_NE(ngtcp2_conn_get_handshake_completed(*session_), 0); |
668 | 0 | return ossl_context_.get_early_data_accepted(); |
669 | 0 | } |
670 | | |
671 | | void TLSSession::Initialize( |
672 | 0 | const std::optional<SessionTicket>& maybeSessionTicket) { |
673 | 0 | auto& ctx = context(); |
674 | 0 | auto& options = ctx.options(); |
675 | 0 | auto ssl = SSLPointer::New(ctx); |
676 | 0 | if (!ssl) [[unlikely]] { |
677 | 0 | validation_error_ = "Failed to create SSL session"; |
678 | 0 | ossl_context_.reset(); |
679 | 0 | return; |
680 | 0 | } |
681 | | |
682 | | // Enable tracing if the `--trace-tls` command line flag is used. |
683 | 0 | if (session_->env()->options()->trace_tls || options.enable_tls_trace) |
684 | 0 | [[unlikely]] { |
685 | 0 | EnableTrace(session_->env(), &bio_trace_, ssl); |
686 | 0 | } |
687 | |
|
688 | 0 | ossl_context_.Initialize(ssl.release(), &ref_, session(), ctx); |
689 | |
|
690 | 0 | switch (ctx.side()) { |
691 | 0 | case Side::SERVER: { |
692 | 0 | if (!ossl_context_.ConfigureServer()) [[unlikely]] { |
693 | 0 | validation_error_ = "Failed to configure server session"; |
694 | 0 | ossl_context_.reset(); |
695 | 0 | return; |
696 | 0 | } |
697 | 0 | break; |
698 | 0 | } |
699 | 0 | case Side::CLIENT: { |
700 | 0 | if (!ossl_context_.ConfigureClient()) [[unlikely]] { |
701 | 0 | validation_error_ = "Failed to configure client session"; |
702 | 0 | ossl_context_.reset(); |
703 | 0 | return; |
704 | 0 | }; |
705 | |
|
706 | 0 | if (!ossl_context_.set_alpn_protocols(options.protocol)) { |
707 | 0 | validation_error_ = "Failed to set ALPN protocols"; |
708 | 0 | ossl_context_.reset(); |
709 | 0 | return; |
710 | 0 | } |
711 | | |
712 | 0 | if (!ossl_context_.set_hostname(options.servername)) { |
713 | 0 | validation_error_ = "Failed to set server name"; |
714 | 0 | ossl_context_.reset(); |
715 | 0 | return; |
716 | 0 | } |
717 | | |
718 | 0 | if (maybeSessionTicket.has_value()) { |
719 | 0 | auto sessionTicket = maybeSessionTicket.value(); |
720 | 0 | uv_buf_t buf = sessionTicket.ticket(); |
721 | 0 | SSLSessionPointer ticket = crypto::GetTLSSession( |
722 | 0 | reinterpret_cast<unsigned char*>(buf.base), buf.len); |
723 | | |
724 | | // The early data will just be ignored if it's invalid. |
725 | 0 | if (ssl.setSession(ticket) && |
726 | 0 | SSL_SESSION_get_max_early_data(ticket.get()) != 0) { |
727 | 0 | ngtcp2_vec rtp = sessionTicket.transport_params(); |
728 | 0 | if (ngtcp2_conn_decode_and_set_0rtt_transport_params( |
729 | 0 | *session_, rtp.base, rtp.len) == 0) { |
730 | 0 | if (!ossl_context_.set_early_data_enabled()) { |
731 | 0 | validation_error_ = "Failed to enable early data"; |
732 | 0 | ossl_context_.reset(); |
733 | 0 | return; |
734 | 0 | } |
735 | 0 | session_->SetStreamOpenAllowed(); |
736 | 0 | } |
737 | 0 | } |
738 | 0 | } |
739 | | |
740 | 0 | break; |
741 | 0 | } |
742 | 0 | } |
743 | | |
744 | 0 | TransportParams tp(ngtcp2_conn_get_local_transport_params(*session_)); |
745 | 0 | Store store = tp.Encode(session_->env()); |
746 | 0 | if (store && store.length() > 0) { |
747 | 0 | if (!ossl_context_.set_transport_params(store)) { |
748 | 0 | validation_error_ = "Failed to set transport parameters"; |
749 | 0 | ossl_context_.reset(); |
750 | 0 | return; |
751 | 0 | } |
752 | 0 | } |
753 | 0 | } |
754 | | |
755 | | std::optional<TLSSession::PeerIdentityValidationError> |
756 | 0 | TLSSession::VerifyPeerIdentity(Environment* env) { |
757 | | // We are just temporarily wrapping the ssl, not taking ownership. |
758 | 0 | SSLPointerRef ssl(ossl_context_); |
759 | 0 | int err = ssl->verifyPeerCertificate().value_or(X509_V_ERR_UNSPECIFIED); |
760 | 0 | if (err == X509_V_OK) return std::nullopt; |
761 | 0 | Local<Value> reason; |
762 | 0 | Local<Value> code; |
763 | 0 | if (!crypto::GetValidationErrorReason(env, err).ToLocal(&reason) || |
764 | 0 | !crypto::GetValidationErrorCode(env, err).ToLocal(&code)) { |
765 | | // Getting the validation error details failed. We'll return a value but |
766 | | // the fields will be empty. |
767 | 0 | return PeerIdentityValidationError{}; |
768 | 0 | } |
769 | 0 | return PeerIdentityValidationError{reason, code}; |
770 | 0 | } |
771 | | |
772 | 0 | MaybeLocal<Object> TLSSession::cert(Environment* env) const { |
773 | 0 | SSLPointerRef ssl(ossl_context_); |
774 | 0 | return crypto::X509Certificate::GetCert(env, ssl); |
775 | 0 | } |
776 | | |
777 | 0 | MaybeLocal<Object> TLSSession::peer_cert(Environment* env) const { |
778 | | // We are just temporarily wrapping the ssl, not taking ownership. |
779 | 0 | SSLPointerRef ssl(ossl_context_); |
780 | 0 | crypto::X509Certificate::GetPeerCertificateFlag flag = |
781 | 0 | context_->side() == Side::SERVER |
782 | 0 | ? crypto::X509Certificate::GetPeerCertificateFlag::SERVER |
783 | 0 | : crypto::X509Certificate::GetPeerCertificateFlag::NONE; |
784 | 0 | return crypto::X509Certificate::GetPeerCert(env, ssl, flag); |
785 | 0 | } |
786 | | |
787 | 0 | MaybeLocal<Object> TLSSession::ephemeral_key(Environment* env) const { |
788 | | // We are just temporarily wrapping the ssl, not taking ownership. |
789 | 0 | SSLPointerRef ssl(ossl_context_); |
790 | 0 | return crypto::GetEphemeralKey(env, ssl); |
791 | 0 | } |
792 | | |
793 | 0 | MaybeLocal<Value> TLSSession::cipher_name(Environment* env) const { |
794 | 0 | CHECK(ossl_context_); |
795 | 0 | auto name = ossl_context_.get_cipher_name(); |
796 | 0 | return OneByteString(env->isolate(), name); |
797 | 0 | } |
798 | | |
799 | 0 | MaybeLocal<Value> TLSSession::cipher_version(Environment* env) const { |
800 | 0 | SSLPointerRef ssl(ossl_context_); |
801 | 0 | auto version = ssl->getCipherVersion(); |
802 | 0 | if (!version.has_value()) return Undefined(env->isolate()); |
803 | 0 | return OneByteString(env->isolate(), version.value()); |
804 | 0 | } |
805 | | |
806 | 0 | const std::string_view TLSSession::servername() const { |
807 | 0 | SSLPointerRef ssl(ossl_context_); |
808 | 0 | return ssl->getServerName().value_or(std::string_view()); |
809 | 0 | } |
810 | | |
811 | 0 | const std::string TLSSession::protocol() const { |
812 | 0 | CHECK(ossl_context_); |
813 | 0 | return ossl_context_.get_selected_alpn(); |
814 | 0 | } |
815 | | |
816 | 0 | bool TLSSession::InitiateKeyUpdate() { |
817 | 0 | if (in_key_update_) return false; |
818 | 0 | auto leave = OnScopeLeave([this] { in_key_update_ = false; }); |
819 | 0 | in_key_update_ = true; |
820 | |
|
821 | 0 | Debug(session_, "Initiating key update"); |
822 | 0 | return ngtcp2_conn_initiate_key_update(*session_, uv_hrtime()) == 0; |
823 | 0 | } |
824 | | |
825 | 0 | ngtcp2_conn* TLSSession::connection(ngtcp2_crypto_conn_ref* ref) { |
826 | 0 | CHECK_NOT_NULL(ref->user_data); |
827 | 0 | return static_cast<TLSSession*>(ref->user_data)->session(); |
828 | 0 | } |
829 | | |
830 | 0 | void TLSSession::MemoryInfo(MemoryTracker* tracker) const { |
831 | 0 | tracker->TrackField("context", context_); |
832 | 0 | } |
833 | | |
834 | | } // namespace quic |
835 | | } // namespace node |
836 | | #endif // OPENSSL_NO_QUIC |
837 | | #endif // HAVE_OPENSSL |