diff --git a/src/node_quic_crypto.cc b/src/node_quic_crypto.cc index 0602ee53ab..b2b4089d14 100644 --- a/src/node_quic_crypto.cc +++ b/src/node_quic_crypto.cc @@ -823,7 +823,7 @@ int Client_Hello_CB( } } -int ALPN_Select_Proto_CB( +int AlpnSelection( SSL* ssl, const unsigned char** out, unsigned char* outlen, @@ -831,31 +831,23 @@ int ALPN_Select_Proto_CB( unsigned int inlen, void* arg) { QuicSession* session = static_cast(SSL_get_app_data(ssl)); - const uint8_t* alpn; - size_t alpnlen; - uint32_t version = session->GetNegotiatedVersion(); - - switch (version) { - case NGTCP2_PROTO_VER: - alpn = reinterpret_cast(session->GetALPN().c_str()); - alpnlen = session->GetALPN().length(); - break; - default: - // Unexpected QUIC protocol version - return SSL_TLSEXT_ERR_NOACK; - } - for (auto p = in, end = in + inlen; p + alpnlen < end; p += *p + 1) { - if (std::equal(alpn, alpn + alpnlen, p)) { - *out = p + 1; - *outlen = *p; - return SSL_TLSEXT_ERR_OK; - } + unsigned char* tmp; + + // The QuicServerSession supports exactly one ALPN identifier. If that does + // not match any of the ALPN identifiers provided in the client request, + // then we fail here. Note that this will not fail the TLS handshake, so + // we have to check later if the ALPN matches the expected identifier or not. + if (SSL_select_next_proto( + &tmp, + outlen, + reinterpret_cast(session->GetALPN().c_str()), + session->GetALPN().length(), + in, + inlen) == OPENSSL_NPN_NO_OVERLAP) { + return SSL_TLSEXT_ERR_NOACK; } - - *out = alpn + 1; - *outlen = alpn[0]; - + *out = tmp; return SSL_TLSEXT_ERR_OK; } @@ -1016,7 +1008,7 @@ void InitializeSecureContext( SSL_CTX_set_options(**sc, ssl_server_opts); SSL_CTX_set_mode(**sc, SSL_MODE_RELEASE_BUFFERS); 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_alpn_select_cb(**sc, AlpnSelection, nullptr); SSL_CTX_set_client_hello_cb(**sc, Client_Hello_CB, nullptr); break; case NGTCP2_CRYPTO_SIDE_CLIENT: diff --git a/src/node_quic_session.cc b/src/node_quic_session.cc index f6d20b29dd..522fd738b4 100644 --- a/src/node_quic_session.cc +++ b/src/node_quic_session.cc @@ -592,6 +592,12 @@ void QuicSession::HandleError() { // need to do at this point is let the javascript side know. void QuicSession::HandshakeCompleted() { Debug(this, "Handshake is completed"); + + // TODO(@jasnell): We should determine if the ALPN + // protocol identifier was selected by this point. + // If no protocol identifier was selected, then we + // should stop here with an error. + session_stats_.handshake_completed_at = uv_hrtime(); HandleScope scope(env()->isolate()); @@ -1673,6 +1679,7 @@ void QuicSession::MemoryInfo(MemoryTracker* tracker) const { // QuicServerSession QuicServerSession::InitialPacketResult QuicServerSession::Accept( ngtcp2_pkt_hd* hd, + uint32_t version, const uint8_t* data, ssize_t nread) { // The initial packet is too short and not a valid QUIC packet. @@ -1685,6 +1692,16 @@ QuicServerSession::InitialPacketResult QuicServerSession::Accept( case 1: return PACKET_VERSION; } + + // Currently, we only understand one version of the QUIC + // protocol, but that could change in the future. If it + // does change, the following check needs to be updated + // to check against a range of possible versions. + // See NGTCP2_PROTO_VER and NGTCP2_PROTO_VER_MAX in the + // ngtcp2.h header file for more details. + if (version != NGTCP2_PROTO_VER) + return PACKET_VERSION; + return PACKET_OK; } diff --git a/src/node_quic_session.h b/src/node_quic_session.h index 3d31e37865..502b1cffc0 100644 --- a/src/node_quic_session.h +++ b/src/node_quic_session.h @@ -1000,6 +1000,7 @@ class QuicServerSession : public QuicSession { static InitialPacketResult Accept( ngtcp2_pkt_hd* hd, + uint32_t version, const uint8_t* data, ssize_t nread); diff --git a/src/node_quic_socket.cc b/src/node_quic_socket.cc index eb090dbf8e..2de8748e31 100644 --- a/src/node_quic_socket.cc +++ b/src/node_quic_socket.cc @@ -655,7 +655,7 @@ BaseObjectPtr QuicSocket::AcceptInitialPacket( // Perform some initial checks on the packet to see if it is an // acceptable initial packet with the right QUIC version. - switch (QuicServerSession::Accept(&hd, data, nread)) { + switch (QuicServerSession::Accept(&hd, version, data, nread)) { case QuicServerSession::InitialPacketResult::PACKET_VERSION: SendVersionNegotiation(version, dcid, scid, addr); // Fall through