Skip to content

Commit 5989f4a

Browse files
jasnelladuh95
authored andcommitted
quic: support hostname verification
Signed-off-by: James M Snell <jasnell@gmail.com> PR-URL: #63483 Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent b4d30e7 commit 5989f4a

6 files changed

Lines changed: 48 additions & 15 deletions

File tree

lib/internal/quic/quic.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5133,6 +5133,11 @@ function processSessionOptions(options, config = kEmptyObject) {
51335133
// client SSL_CTX. For 'auto' and 'manual' modes, the handshake
51345134
// completes regardless and the result is handled in JS.
51355135
verifyPeerStrict: verifyPeer === 'strict',
5136+
// Enable hostname verification for 'strict' and 'auto' modes.
5137+
// SSL_set1_host tells OpenSSL to verify the server certificate's
5138+
// SAN/CN matches the servername. Without this, a valid cert for
5139+
// any domain would be accepted.
5140+
verifyHostname: verifyPeer !== 'manual',
51365141
},
51375142
verifyPeer,
51385143
qlog,

src/quic/bindingdata.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ class SessionManager;
169169
V(unacknowledged_packet_threshold, "unacknowledgedPacketThreshold") \
170170
V(validate_address, "validateAddress") \
171171
V(verify_client, "verifyClient") \
172+
V(verify_hostname, "verifyHostname") \
172173
V(verify_peer_strict, "verifyPeerStrict") \
173174
V(verify_private_key, "verifyPrivateKey") \
174175
V(version, "version")

src/quic/session.cc

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -618,8 +618,7 @@ Maybe<Session::Options> Session::Options::From(Environment* env,
618618
!SET(keep_alive_timeout) || !SET(max_stream_window) || !SET(max_window) ||
619619
!SET(max_payload_size) || !SET(unacknowledged_packet_threshold) ||
620620
!SET(cc_algorithm) || !SET(draining_period_multiplier) ||
621-
!SET(max_datagram_send_attempts) ||
622-
!SET(stream_idle_timeout)) {
621+
!SET(max_datagram_send_attempts) || !SET(stream_idle_timeout)) {
623622
return Nothing<Options>();
624623
}
625624

@@ -2831,8 +2830,8 @@ void Session::ShutdownStream(stream_id id, QuicError error) {
28312830

28322831
void Session::ShutdownStreamWrite(stream_id id, QuicError error) {
28332832
DCHECK(!is_destroyed());
2834-
Debug(this, "Shutting down stream %" PRIi64 " write with error %s",
2835-
id, error);
2833+
Debug(
2834+
this, "Shutting down stream %" PRIi64 " write with error %s", id, error);
28362835
SendPendingDataScope send_scope(this);
28372836
error_code code;
28382837
if (error.type() == QuicError::Type::APPLICATION) {
@@ -3058,17 +3057,15 @@ void Session::CheckStreamIdleTimeout(uint64_t now) {
30583057

30593058
uint64_t last_activity = stream->last_activity_timestamp();
30603059
if (last_activity > 0 && (now - last_activity) > timeout_ns) {
3061-
Debug(this,
3062-
"Stream %" PRId64 " idle timeout exceeded, destroying",
3063-
id);
3060+
Debug(this, "Stream %" PRId64 " idle timeout exceeded, destroying", id);
30643061
// Notify the peer before destroying. ShutdownStream sends both
30653062
// STOP_SENDING and RESET_STREAM as appropriate, using the
30663063
// application's no-error code for non-APPLICATION errors (since
30673064
// these frames carry application-level error codes per RFC 9000).
30683065
// Without this, the peer's stream sits orphaned until the
30693066
// session closes.
3070-
auto error = QuicError::ForTransport(NGTCP2_ERR_PROTO,
3071-
"stream idle timeout");
3067+
auto error =
3068+
QuicError::ForTransport(NGTCP2_ERR_PROTO, "stream idle timeout");
30723069
ShutdownStream(id, error);
30733070
stream->Destroy(error);
30743071
STAT_INCREMENT(Stats, streams_idle_timed_out);

src/quic/streams.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,6 +1891,7 @@ void Stream::EmitClose(const QuicError& error) {
18911891
}
18921892

18931893
void Stream::EmitHeaders() {
1894+
STAT_RECORD_TIMESTAMP(Stats, received_at);
18941895
// state()->wants_headers will be set from the javascript side if the
18951896
// stream object has a handler for the headers event.
18961897
if (!env()->can_call_into_js() || !state()->wants_headers) {

src/quic/tlscontext.cc

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,18 @@ bool OSSLContext::set_hostname(std::string_view hostname) const {
261261
const_cast<char*>(name.c_str())) == 1;
262262
}
263263

264+
bool OSSLContext::set_verify_hostname(std::string_view hostname) const {
265+
// SSL_set1_host tells OpenSSL to verify the peer certificate's
266+
// subject name (SAN/CN) matches this hostname. This is separate
267+
// from SSL_set_tlsext_host_name which only sets the SNI extension.
268+
static const char* kDefaultHostname = "localhost";
269+
if (hostname.empty()) {
270+
return SSL_set1_host(*this, kDefaultHostname) == 1;
271+
} else {
272+
return SSL_set1_host(*this, hostname.data()) == 1;
273+
}
274+
}
275+
264276
bool OSSLContext::set_early_data_enabled() const {
265277
return SSL_set_quic_tls_early_data_enabled(*this, 1) == 1;
266278
}
@@ -714,12 +726,13 @@ Maybe<TLSContext::Options> TLSContext::Options::From(Environment* env,
714726
env, &options, params, state.name##_string())
715727

716728
if (!SET(verify_client) || !SET(reject_unauthorized) ||
717-
!SET(verify_peer_strict) || !SET(enable_early_data) ||
718-
!SET(enable_tls_trace) || !SET(alpn) || !SET(servername) ||
719-
!SET(ciphers) || !SET(groups) || !SET(verify_private_key) ||
720-
!SET(keylog) || !SET(port) || !SET(authoritative) ||
721-
!SET_VECTOR(crypto::KeyObjectData, keys) || !SET_VECTOR(Store, certs) ||
722-
!SET_VECTOR(Store, ca) || !SET_VECTOR(Store, crl)) {
729+
!SET(verify_hostname) || !SET(verify_peer_strict) ||
730+
!SET(enable_early_data) || !SET(enable_tls_trace) || !SET(alpn) ||
731+
!SET(servername) || !SET(ciphers) || !SET(groups) ||
732+
!SET(verify_private_key) || !SET(keylog) || !SET(port) ||
733+
!SET(authoritative) || !SET_VECTOR(crypto::KeyObjectData, keys) ||
734+
!SET_VECTOR(Store, certs) || !SET_VECTOR(Store, ca) ||
735+
!SET_VECTOR(Store, crl)) {
723736
return Nothing<Options>();
724737
}
725738

@@ -854,6 +867,14 @@ void TLSSession::Initialize(
854867
return;
855868
}
856869

870+
if (options.verify_hostname) {
871+
if (!ossl_context_.set_verify_hostname(options.servername)) {
872+
validation_error_ = "Failed to set verify hostname";
873+
ossl_context_.reset();
874+
return;
875+
}
876+
}
877+
857878
if (maybeSessionTicket.has_value()) {
858879
const auto& sessionTicket = *maybeSessionTicket;
859880
uv_buf_t buf = sessionTicket.ticket();

src/quic/tlscontext.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class OSSLContext final {
5151

5252
bool set_alpn_protocols(std::string_view protocols) const;
5353
bool set_hostname(std::string_view hostname) const;
54+
bool set_verify_hostname(std::string_view hostname) const;
5455
bool set_early_data_enabled() const;
5556
bool set_transport_params(const ngtcp2_vec& tp) const;
5657

@@ -215,6 +216,13 @@ class TLSContext final : public MemoryRetainer,
215216
// by the client side.
216217
bool verify_peer_strict = false;
217218

219+
// When true, OpenSSL verifies that the server's certificate matches
220+
// the servername (hostname verification via SSL_set1_host). Should
221+
// be true for 'strict' and 'auto' verifyPeer modes, false for
222+
// 'manual'. Without this, a valid certificate for any domain would
223+
// be accepted. This option is only used by the client side.
224+
bool verify_hostname = false;
225+
218226
// When true (the default), the server accepts 0-RTT early data
219227
// from clients with valid session tickets. When false, early data
220228
// is disabled and clients must complete a full handshake before

0 commit comments

Comments
 (0)