From 9d6466855749f308f7ee810da6586995107516e9 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 5 Jun 2019 13:45:39 -0700 Subject: [PATCH] quic: complete implementation of OCSPRequest/OCSPResponse PR-URL: https://github.com/nodejs/quic/pull/31 --- lib/internal/quic/core.js | 132 ++++++++++++++------ lib/internal/quic/util.js | 9 ++ src/env.h | 1 + src/node_crypto.cc | 9 -- src/node_crypto.h | 8 ++ src/node_quic.cc | 10 ++ src/node_quic_session.cc | 151 +++++++++++++++++------ src/node_quic_session.h | 23 ++-- test/parallel/test-quic-client-server.js | 30 +++-- 9 files changed, 276 insertions(+), 97 deletions(-) diff --git a/lib/internal/quic/core.js b/lib/internal/quic/core.js index 219f4d55e1..0abf6a62d0 100644 --- a/lib/internal/quic/core.js +++ b/lib/internal/quic/core.js @@ -10,6 +10,7 @@ const { assertCrypto(); const { Error } = primordials; +const { isArrayBufferView } = require('internal/util/types'); const { getAllowUnauthorized, getSocketType, @@ -222,20 +223,72 @@ function onSessionClose(code, family) { this[owner_symbol].destroy(); } +// This callback is invoked at the start of the TLS handshake to provide +// some basic information about the ALPN, SNI, and Ciphers that are +// being requested. It is only called if the 'clientHello' event is +// listened for. function onSessionClientHello(alpn, servername, ciphers, callback) { callback = callback.bind(this); - function cb(...args) { - callback(...args); - } - this[owner_symbol][kClientHello](alpn, servername, ciphers, cb); + this[owner_symbol][kClientHello]( + alpn, + servername, + ciphers, + (err, ...args) => { + if (err) { + this[owner_symbol].destroy(err); + return; + } + try { + callback(...args); + } catch (err) { + this[owner_symbol].destroy(err); + } + }); } -function onSessionCert(servername, ocsp, callback) { +// This callback is only ever invoked for QuicServerSession instances, +// and is used to trigger OCSP request processing when needed. The +// user callback must invoke the callback function in order for the +// TLS handshake to continue. +function onSessionCert(servername, callback) { callback = callback.bind(this); - function cb(...args) { - callback(...args); - } - this[owner_symbol][kCert](servername, ocsp, cb); + this[owner_symbol][kCert](servername, (err, context, ocspResponse) => { + if (err) { + this[owner_symbol].destroy(err); + return; + } + if (context != null && !context.context) { + this[owner_symbol].destroy( + new ERR_INVALID_ARG_TYPE( + 'context', + 'SecureContext', + context)); + } + if (ocspResponse != null) { + if (typeof ocspResponse === 'string') + ocspResponse = Buffer.from(ocspResponse); + if (!isArrayBufferView(ocspResponse)) { + this[owner_symbol].destroy( + new ERR_INVALID_ARG_TYPE( + 'ocspResponse', + ['string', 'Buffer', 'TypedArray', 'DataView'], + ocspResponse)); + } + } + try { + callback(context, ocspResponse); + } catch (err) { + this[owner_symbol].destroy(err); + } + }); +} + +// This callback is only ever invoked for QuicClientSession instances, +// and is used to deliver the OCSP response as provided by the server. +// If the requestOCSP configuration option is false, this will never +// be called. +function onSessionStatus(response) { + this[owner_symbol][kCert](response); } function onSessionHandshake( @@ -334,6 +387,7 @@ setCallbacks({ onSessionExtend, onSessionHandshake, onSessionKeylog, + onSessionStatus, onSessionTicket, onStreamReady, onStreamClose, @@ -454,7 +508,7 @@ function onNewListener(event) { case 'clientHello': this[kHandle].state[IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED] = 1; break; - case 'cert': + case 'OCSPRequest': this[kHandle].state[IDX_QUIC_SESSION_STATE_CERT_ENABLED] = 1; break; } @@ -471,7 +525,7 @@ function onRemoveListener(event) { case 'clientHello': this[kHandle].state[IDX_QUIC_SESSION_STATE_CLIENT_HELLO_ENABLED] = 0; break; - case 'cert': + case 'OCSPRequest': this[kHandle].state[IDX_QUIC_SESSION_STATE_CERT_ENABLED] = 0; break; } @@ -1222,18 +1276,11 @@ class QuicServerSession extends QuicSession { } [kClientHello](alpn, servername, ciphers, callback) { - const { serverSecureContext } = this.socket; - if (!serverSecureContext) - callback(null, null); - const { context } = serverSecureContext; - this.emit( 'clientHello', alpn, servername, ciphers, - context.getCertificate(), - context.getIssuer(), callback.bind(this[kHandle])); } @@ -1241,15 +1288,16 @@ class QuicServerSession extends QuicSession { process.nextTick(emit.bind(this, 'ready')); } - [kCert](servername, ocsp, callback) { - if (!ocsp) { - callback(null); - return; - } + [kCert](servername, callback) { + const { serverSecureContext } = this.socket; + if (!serverSecureContext) + callback(null, null); + const { context } = serverSecureContext; this.emit( - 'cert', + 'OCSPRequest', servername, - ocsp, + context.getCertificate(), + context.getIssuer(), callback.bind(this[kHandle])); } @@ -1284,16 +1332,17 @@ function setSocketAfterBind(socket, callback) { class QuicClientSession extends QuicSession { #alpn = undefined; + #dcid = undefined; #handleReady = false; #ipv6Only = undefined; #minDHSize = undefined; #port = undefined; + #remoteTransportParams = undefined; + #requestOCSP = undefined; #secureContext = undefined; + #sessionTicket = undefined; #socketReady = false; #transportParams = undefined; - #sessionTicket = undefined; - #remoteTransportParams = undefined; - #dcid = undefined; #preferredAddressPolicy; constructor(socket, options) { @@ -1311,26 +1360,28 @@ class QuicClientSession extends QuicSession { port, preferredAddressPolicy, remoteTransportParams, + requestOCSP, servername, sessionTicket, } = validateQuicClientSessionOptions(options); super(socket, servername); this.#alpn = alpn; - this.#transportParams = - validateTransportParams( - options, - maxCidLen, - minCidLen); + this.#dcid = dcid; this.#ipv6Only = ipv6Only; this.#minDHSize = minDHSize; this.#port = port || 0; + this.#preferredAddressPolicy = preferredAddressPolicy; + this.#remoteTransportParams = remoteTransportParams; + this.#requestOCSP = requestOCSP; this.#secureContext = createSecureContext(sc_options, - initSecureContextClient); + initSecureContextClient); this.#sessionTicket = sessionTicket; - this.#remoteTransportParams = remoteTransportParams; - this.#dcid = dcid; - this.#preferredAddressPolicy = preferredAddressPolicy; + this.#transportParams = + validateTransportParams( + options, + maxCidLen, + minCidLen); } [kHandshakePost]() { @@ -1371,7 +1422,8 @@ class QuicClientSession extends QuicSession { this.#sessionTicket, this.#dcid, this.#preferredAddressPolicy, - this.#alpn); + this.#alpn, + this.#requestOCSP); // We no longer need these, unset them so // memory can be garbage collected. this.#remoteTransportParams = undefined; @@ -1407,6 +1459,10 @@ class QuicClientSession extends QuicSession { this[kMaybeReady](); } + [kCert](response) { + this.emit('OCSPResponse', response); + } + [kMaybeReady]() { if (this.#socketReady && this.#handleReady) process.nextTick(emit.bind(this, 'ready')); diff --git a/lib/internal/quic/util.js b/lib/internal/quic/util.js index 2b53f61644..95e676c050 100644 --- a/lib/internal/quic/util.js +++ b/lib/internal/quic/util.js @@ -212,6 +212,7 @@ function validateQuicClientSessionOptions(options) { port = 0, preferredAddressPolicy = 'ignore', remoteTransportParams, + requestOCSP = false, servername = address, sessionTicket, } = { ...options }; @@ -301,6 +302,13 @@ function validateQuicClientSessionOptions(options) { preferredAddressPolicy); } + if (typeof requestOCSP !== 'boolean') { + throw new ERR_INVALID_ARG_TYPE( + 'options.requestOCSP', + 'boolean', + requestOCSP); + } + return { address, alpn, @@ -315,6 +323,7 @@ function validateQuicClientSessionOptions(options) { QUIC_PREFERRED_ADDRESS_ACCEPT : QUIC_PREFERRED_ADDRESS_IGNORE, remoteTransportParams, + requestOCSP, servername, sessionTicket, }; diff --git a/src/env.h b/src/env.h index c44b8dd2bf..31b121278d 100644 --- a/src/env.h +++ b/src/env.h @@ -462,6 +462,7 @@ constexpr size_t kFsStatsBufferLength = V(quic_on_session_extend_function, v8::Function) \ V(quic_on_session_handshake_function, v8::Function) \ V(quic_on_session_keylog_function, v8::Function) \ + V(quic_on_session_status_function, v8::Function) \ V(quic_on_session_ticket_function, v8::Function) \ V(quic_on_session_path_validation_function, v8::Function) \ V(quic_on_stream_ready_function, v8::Function) \ diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 5604f298f3..7d997414f8 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -458,15 +458,6 @@ bool EntropySource(unsigned char* buffer, size_t length) { return RAND_bytes(buffer, length) != -1; } - -template -static T* MallocOpenSSL(size_t count) { - void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T))); - CHECK_IMPLIES(mem == nullptr, count == 0); - return static_cast(mem); -} - - void SecureContext::Initialize(Environment* env, Local target) { Local t = env->NewFunctionTemplate(New); t->InstanceTemplate()->SetInternalFieldCount(1); diff --git a/src/node_crypto.h b/src/node_crypto.h index 78d98367e3..cf24cf984c 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -852,6 +852,14 @@ v8::Local GetLastIssuedCert( SSL* ssl, v8::Local issuer_chain, Environment* const env); + +template +inline T* MallocOpenSSL(size_t count) { + void* mem = OPENSSL_malloc(MultiplyWithOverflowCheck(count, sizeof(T))); + CHECK_IMPLIES(mem == nullptr, count == 0); + return static_cast(mem); +} + } // namespace crypto } // namespace node diff --git a/src/node_quic.cc b/src/node_quic.cc index 2adc43a1eb..9bdcc84ecc 100755 --- a/src/node_quic.cc +++ b/src/node_quic.cc @@ -55,6 +55,7 @@ void QuicSetCallbacks(const FunctionCallbackInfo& args) { SETFUNCTION("onSessionHandshake", session_handshake); SETFUNCTION("onSessionKeylog", session_keylog); SETFUNCTION("onSessionPathValidation", session_path_validation); + SETFUNCTION("onSessionStatus", session_status); SETFUNCTION("onSessionTicket", session_ticket); SETFUNCTION("onStreamReady", stream_ready); SETFUNCTION("onStreamClose", stream_close); @@ -159,6 +160,11 @@ int Client_Transport_Params_Add_CB( return 1; } +int TLS_Status_Callback(SSL* ssl, void* arg) { + QuicSession* session = static_cast(SSL_get_app_data(ssl)); + return session->OnTLSStatus(); +} + int Server_Transport_Params_Add_CB( SSL* ssl, unsigned int ext_type, @@ -290,6 +296,8 @@ void QuicInitSecureContext(const FunctionCallbackInfo& args) { SSL_CTX_set_max_early_data(**sc, std::numeric_limits::max()); SSL_CTX_set_alpn_select_cb(**sc, ALPN_Select_Proto_CB, nullptr); SSL_CTX_set_client_hello_cb(**sc, Client_Hello_CB, nullptr); + SSL_CTX_set_tlsext_status_cb(**sc, TLS_Status_Callback); + SSL_CTX_set_tlsext_status_arg(**sc, nullptr); CHECK_EQ( SSL_CTX_add_custom_ext( **sc, @@ -320,6 +328,8 @@ void QuicInitSecureContextClient(const FunctionCallbackInfo& args) { SSL_CTX_set_mode(**sc, SSL_MODE_QUIC_HACK); SSL_CTX_clear_options(**sc, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); SSL_CTX_set_default_verify_paths(**sc); + SSL_CTX_set_tlsext_status_cb(**sc, TLS_Status_Callback); + SSL_CTX_set_tlsext_status_arg(**sc, nullptr); CHECK_EQ(SSL_CTX_add_custom_ext( **sc, diff --git a/src/node_quic_session.cc b/src/node_quic_session.cc index c06f815b58..bf4d25439d 100644 --- a/src/node_quic_session.cc +++ b/src/node_quic_session.cc @@ -2166,41 +2166,63 @@ int QuicServerSession::HandleError() { } namespace { +// This callback is invoked by user code after completing handling +// of the 'OCSPRequest' event. The callback is invoked with two +// possible arguments, both of which are optional +// 1. A replacement SecureContext +// 2. An OCSP response void OnServerCertCB(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); QuicServerSession* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - // TODO(@jasnell): Args[0] is possibly an error? - Local cons = env->secure_context_constructor_template(); crypto::SecureContext* context = nullptr; - if (args[1]->IsObject() && cons->HasInstance(args[1])) - ASSIGN_OR_RETURN_UNWRAP(&context, args[1].As()); - session->OnCertDone(context); + if (args[0]->IsObject() && cons->HasInstance(args[0])) + ASSIGN_OR_RETURN_UNWRAP(&context, args[0].As()); + session->OnCertDone(context, args[1]); } } // namespace -void QuicServerSession::OnCertDone(crypto::SecureContext* context) { +// The OnCertDone function is called by the OnServerCertCB +// function when usercode is done handling the OCSPRequest event. +void QuicServerSession::OnCertDone( + crypto::SecureContext* context, + Local ocsp_response) { + Debug(this, "OCSPRequest completed. Context Provided? %s, OCSP Provided? %s", + context != nullptr ? "Yes" : "No", + ocsp_response->IsArrayBufferView() ? "Yes" : "No"); // Continue the TLS handshake when this function exits // otherwise it will stall and fail. TLSHandshakeScope handshake_scope(this, &cert_cb_running_); // Disable the callback at this point so we don't loop continuously state_[IDX_QUIC_SESSION_STATE_CERT_ENABLED] = 0; - if (context == nullptr) - return; - - int err = UseSNIContext(ssl(), context); - if (!err) { - unsigned long err = ERR_get_error(); // NOLINT(runtime/int) - if (!err) - return env()->ThrowError("CertCbDone"); // TODO(@jasnell): revisit - return crypto::ThrowCryptoError(env(), err); + if (context != nullptr) { + int err = UseSNIContext(ssl(), context); + if (!err) { + unsigned long err = ERR_get_error(); // NOLINT(runtime/int) + if (!err) + return env()->ThrowError("CertCbDone"); // TODO(@jasnell): revisit + return crypto::ThrowCryptoError(env(), err); + } } + + if (ocsp_response->IsArrayBufferView()) + ocsp_response_.Reset(env()->isolate(), ocsp_response.As()); } +// The OnCert callback provides an opportunity to prompt the server to +// perform on OCSP request on behalf of the client (when the client +// requests it). If there is a listener for the 'OCSPRequest' event +// on the JavaScript side, the IDX_QUIC_SESSION_STATE_CERT_ENABLED +// session state slot will equal 1, which will cause the callback to +// be invoked. The callback will be given a reference to a JavaScript +// function that must be called in order for the TLS handshake to +// continue. int QuicServerSession::OnCert() { + Debug(this, "Is there an OCSPRequest handler registered? %s", + state_[IDX_QUIC_SESSION_STATE_CERT_ENABLED] == 0 ? "No" : "Yes"); if (LIKELY(state_[IDX_QUIC_SESSION_STATE_CERT_ENABLED] == 0)) return 1; @@ -2213,13 +2235,21 @@ int QuicServerSession::OnCert() { HandleScope handle_scope(env()->isolate()); Context::Scope context_scope(env()->context()); - cert_cb_running_ = true; Local servername_str; - const char* servername = SSL_get_servername(ssl(), TLSEXT_NAMETYPE_host_name); const bool ocsp = (SSL_get_tlsext_status_type(ssl()) == TLSEXT_STATUSTYPE_ocsp); + Debug(this, "Is the client requesting OCSP? %s", ocsp ? "Yes" : "No"); + + // If status type is not ocsp, there's nothing further to do here. + // Save ourselves the callback into JavaScript and continue the + // handshake. + if (!ocsp) + return 1; + + const char* servername = SSL_get_servername(ssl(), TLSEXT_NAMETYPE_host_name); + cert_cb_running_ = true; Local argv[] = { servername == nullptr ? String::Empty(env()->isolate()) : @@ -2227,7 +2257,6 @@ int QuicServerSession::OnCert() { env()->isolate(), servername, strlen(servername)), - Boolean::New(env()->isolate(), ocsp), Function::New( env()->context(), OnServerCertCB, @@ -2241,20 +2270,35 @@ int QuicServerSession::OnCert() { return cert_cb_running_ ? -1 : 1; } -void QuicClientSession::OnCertDone(crypto::SecureContext* context) { - // Continue the TLS handshake when this function exits - // otherwise it will stall and fail. - TLSHandshakeScope handshake_scope(this, &cert_cb_running_); - // Disable the callback at this point so we don't loop continuously - state_[IDX_QUIC_SESSION_STATE_CERT_ENABLED] = 0; - if (context == nullptr) - return; - // TODO(@jasnell): Do something with the new context... -} +// When the client has requested OSCP, this function will be called to provide +// the OSCP response. The OnCert() callback should have already been called +// by this point if any data is to be provided. If it hasn't, and ocsp_response_ +// is empty, no OCSP response will be sent. +int QuicServerSession::OnTLSStatus() { + Debug(this, "Asking for OCSP status to send. Is there a response? %s", + ocsp_response_.IsEmpty() ? "No" : "Yes"); -int QuicClientSession::OnCert() { - // TODO(@jasnell): Implement - return 1; + if (ocsp_response_.IsEmpty()) + return SSL_TLSEXT_ERR_NOACK; + + HandleScope scope(env()->isolate()); + + Local obj = + PersistentToLocal::Default( + env()->isolate(), + ocsp_response_); + size_t len = obj->ByteLength(); + + unsigned char* data = crypto::MallocOpenSSL(len); + obj->CopyContents(data, len); + + Debug(this, "The OCSP Response is %d bytes in length.", len); + + if (!SSL_set_tlsext_status_ocsp_resp(ssl(), data, len)) + OPENSSL_free(data); + ocsp_response_.Reset(); + + return SSL_TLSEXT_ERR_OK; } int QuicServerSession::OnKey( @@ -2702,7 +2746,8 @@ std::shared_ptr QuicClientSession::New( Local session_ticket, Local dcid, int select_preferred_address_policy, - const std::string& alpn) { + const std::string& alpn, + bool request_ocsp) { std::shared_ptr session; Local obj; if (!socket->env() @@ -2724,7 +2769,8 @@ std::shared_ptr QuicClientSession::New( session_ticket, dcid, select_preferred_address_policy, - alpn); + alpn, + request_ocsp); session->AddToSocket(socket); session->Start(); @@ -2744,7 +2790,8 @@ QuicClientSession::QuicClientSession( Local session_ticket, Local dcid, int select_preferred_address_policy, - const std::string& alpn) : + const std::string& alpn, + bool request_ocsp) : QuicSession( socket, wrap, @@ -2753,7 +2800,8 @@ QuicClientSession::QuicClientSession( alpn), resumption_(false), hostname_(hostname), - select_preferred_address_policy_(select_preferred_address_policy) { + select_preferred_address_policy_(select_preferred_address_policy), + request_ocsp_(request_ocsp) { // TODO(@jasnell): Init may fail. Need to handle the error conditions Init(addr, version, early_transport_params, session_ticket, dcid); } @@ -2956,6 +3004,7 @@ int QuicClientSession::SetSession(SSL_SESSION* session) { void QuicClientSession::InitTLS_Post() { SSL_set_connect_state(ssl()); + Debug(this, "Using %s as the ALPN protocol.", GetALPN().c_str() + 1); const uint8_t* alpn = reinterpret_cast(GetALPN().c_str()); size_t alpnlen = GetALPN().length(); SSL_set_alpn_protos(ssl(), alpn, alpnlen); @@ -2963,10 +3012,39 @@ void QuicClientSession::InitTLS_Post() { // If the hostname is an IP address and we have no additional // information, use localhost. if (SocketAddress::numeric_host(hostname_)) { + Debug(this, "Using localhost as fallback hostname."); SSL_set_tlsext_host_name(ssl(), "localhost"); } else { SSL_set_tlsext_host_name(ssl(), hostname_); } + + // Are we going to request OCSP status? + if (request_ocsp_) { + Debug(this, "Request OCSP status from the server."); + SSL_set_tlsext_status_type(ssl(), TLSEXT_STATUSTYPE_ocsp); + } +} + +// During TLS handshake, if the client has requested OCSP status, this +// function will be invoked when the response has been received from +// the server. +int QuicClientSession::OnTLSStatus() { + HandleScope scope(env()->isolate()); + Context::Scope context_scope(env()->context()); + + const unsigned char* resp; + int len = SSL_get_tlsext_status_ocsp_resp(ssl(), &resp); + Debug(this, "An OCSP Response of %d bytes has been received.", len); + Local arg; + if (resp == nullptr) + arg = Undefined(env()->isolate()); + else { + arg = Buffer::Copy(env(), reinterpret_cast(resp), len) + .ToLocalChecked(); + } + + MakeCallback(env()->quic_on_session_status_function(), 1, &arg); + return 1; } int QuicClientSession::OnKey( @@ -3553,7 +3631,8 @@ void NewQuicClientSession(const FunctionCallbackInfo& args) { args[8], args[9], select_preferred_address_policy, - alpn); + alpn, + args[12]->IsTrue()); // request_oscp session->SendPendingData(); diff --git a/src/node_quic_session.h b/src/node_quic_session.h index 9aae490abb..1b9ec9cb72 100644 --- a/src/node_quic_session.h +++ b/src/node_quic_session.h @@ -228,6 +228,10 @@ class QuicSession : public AsyncWrap, virtual bool IsServer() const { return false; } virtual int OnClientHello() { return 0; } virtual void OnClientHelloDone() {} + virtual int OnCert() { return 1; } + virtual void OnCertDone( + crypto::SecureContext* context, + v8::Local ocsp_response) {} // These must be implemented by QuicSession types virtual void AddToSocket(QuicSocket* socket) = 0; @@ -237,8 +241,6 @@ class QuicSession : public AsyncWrap, size_t datalen) = 0; virtual int HandleError() = 0; virtual bool MaybeTimeout() = 0; - virtual int OnCert() = 0; - virtual void OnCertDone(crypto::SecureContext* context) = 0; virtual void OnIdleTimeout() = 0; virtual int OnKey( int name, @@ -257,6 +259,7 @@ class QuicSession : public AsyncWrap, virtual int TLSHandshake_Complete() = 0; virtual int TLSHandshake_Initial() = 0; virtual int TLSRead() = 0; + virtual int OnTLSStatus() = 0; static void SetupTokenContext( CryptoContext* context); @@ -848,11 +851,14 @@ class QuicServerSession : public QuicSession { bool IsServer() const override { return true; } int OnClientHello() override; void OnClientHelloDone() override; + int OnTLSStatus() override; bool MaybeTimeout() override; int OnCert() override; - void OnCertDone(crypto::SecureContext* context) override; + void OnCertDone( + crypto::SecureContext* context, + v8::Local ocsp_response) override; const ngtcp2_cid* rcid() const; ngtcp2_cid* pscid(); @@ -917,6 +923,7 @@ class QuicServerSession : public QuicSession { bool request_cert_; MallocedBuffer conn_closebuf_; + v8::Global ocsp_response_; const ngtcp2_conn_callbacks callbacks_ = { nullptr, @@ -973,7 +980,8 @@ class QuicClientSession : public QuicSession { v8::Local dcid, int select_preferred_address_policy = QUIC_PREFERRED_ADDRESS_IGNORE, - const std::string& alpn = NGTCP2_ALPN_H3); + const std::string& alpn = NGTCP2_ALPN_H3, + bool request_ocsp = false); QuicClientSession( QuicSocket* socket, @@ -987,14 +995,14 @@ class QuicClientSession : public QuicSession { v8::Local session_ticket, v8::Local dcid, int select_preferred_address_policy, - const std::string& alpn); + const std::string& alpn, + bool request_ocsp); void AddToSocket(QuicSocket* socket) override; bool MaybeTimeout() override; - int OnCert() override; - void OnCertDone(crypto::SecureContext* context) override; + int OnTLSStatus() override; int SetSocket( QuicSocket* socket, @@ -1071,6 +1079,7 @@ class QuicClientSession : public QuicSession { MaybeStackBuffer transportParams_; int select_preferred_address_policy_; + bool request_ocsp_; const ngtcp2_conn_callbacks callbacks_ = { OnClientInitial, diff --git a/test/parallel/test-quic-client-server.js b/test/parallel/test-quic-client-server.js index 3cf4f0248a..93fe1a4941 100644 --- a/test/parallel/test-quic-client-server.js +++ b/test/parallel/test-quic-client-server.js @@ -69,18 +69,28 @@ server.on('session', common.mustCall((session) => { debug('QuicServerSession Created'); session.on('clientHello', common.mustCall( - (alpn, servername, ciphers, cert, issuer, cb) => { + (alpn, servername, ciphers, cb) => { assert.strictEqual(alpn, kALPN); assert.strictEqual(servername, kServerName); + assert.strictEqual(ciphers.length, 4); cb(); })); - // session.on('cert', common.mustCall( - // (servername, ocsp, cb) => { - // assert.strictEqual(servername, kServerName); - // assert.strictEqual(ocsp, false); - // cb(); - // })); + session.on('OCSPRequest', common.mustCall( + (servername, cert, issuer, cb) => { + debug('QuicServerSession received a OCSP request'); + assert.strictEqual(servername, kServerName); + if (cert) + assert(cert instanceof Buffer); + if (issuer) + assert(issuer instanceof Buffer); + // The callback can be invoked asynchronously + // TODO(@jasnell): Using setImmediate here causes the test + // to fail, but it shouldn't. Investigate why. + process.nextTick(() => { + cb(null, null, Buffer.from('hello')); + }); + })); session.on('keylog', common.mustCall((line) => { assert(kKeylogs.shift().test(line)); @@ -153,12 +163,18 @@ server.on('ready', common.mustCall(() => { address: 'localhost', port: server.address.port, servername: kServerName, + requestOCSP: true, }); client.on('close', () => debug('Client closing')); assert.strictEqual(req.servername, kServerName); + req.on('OCSPResponse', common.mustCall((response) => { + debug(`QuicClientSession OCSP response: "${response.toString()}"`); + assert.strictEqual(response.toString(), 'hello'); + })); + req.on('sessionTicket', common.mustCall((id, ticket, params) => { debug('Session ticket received'); assert(id instanceof Buffer);