GeezerVison is showing github.com — quic: support hostname verification · nodejs/node@b68f8ea
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/quic/quic.js +5Lines changed: 5 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -5133,6 +5133,11 @@ function processSessionOptions(options, config = kEmptyObject) { | 5133 | 5133 | // client SSL_CTX. For 'auto' and 'manual' modes, the handshake | 5134 | 5134 | // completes regardless and the result is handled in JS. | 5135 | 5135 | 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', | 5136 | 5141 | }, | 5137 | 5142 | verifyPeer, | 5138 | 5143 | qlog, | |
Collapse file Copy file name to clipboardExpand all lines: src/quic/bindingdata.h +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -169,6 +169,7 @@ class SessionManager; | 169 | 169 | V(unacknowledged_packet_threshold, "unacknowledgedPacketThreshold") \ | 170 | 170 | V(validate_address, "validateAddress") \ | 171 | 171 | V(verify_client, "verifyClient") \ | | 172 | + V(verify_hostname, "verifyHostname") \ | 172 | 173 | V(verify_peer_strict, "verifyPeerStrict") \ | 173 | 174 | V(verify_private_key, "verifyPrivateKey") \ | 174 | 175 | V(version, "version") | |
Collapse file Copy file name to clipboardExpand all lines: src/quic/session.cc +6-9Lines changed: 6 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -618,8 +618,7 @@ Maybe<Session::Options> Session::Options::From(Environment* env, | 618 | 618 | !SET(keep_alive_timeout) || !SET(max_stream_window) || !SET(max_window) || | 619 | 619 | !SET(max_payload_size) || !SET(unacknowledged_packet_threshold) || | 620 | 620 | !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)) { | 623 | 622 | return Nothing<Options>(); | 624 | 623 | } | 625 | 624 |
| @@ -2839,8 +2838,8 @@ void Session::ShutdownStream(stream_id id, QuicError error) { | 2839 | 2838 |
| 2840 | 2839 | void Session::ShutdownStreamWrite(stream_id id, QuicError error) { | 2841 | 2840 | DCHECK(!is_destroyed()); | 2842 | | - Debug(this, "Shutting down stream %" PRIi64 " write with error %s", | 2843 | | - id, error); | | 2841 | + Debug( | | 2842 | + this, "Shutting down stream %" PRIi64 " write with error %s", id, error); | 2844 | 2843 | SendPendingDataScope send_scope(this); | 2845 | 2844 | error_code code; | 2846 | 2845 | if (error.type() == QuicError::Type::APPLICATION) { | @@ -3066,17 +3065,15 @@ void Session::CheckStreamIdleTimeout(uint64_t now) { | 3066 | 3065 |
| 3067 | 3066 | uint64_t last_activity = stream->last_activity_timestamp(); | 3068 | 3067 | if (last_activity > 0 && (now - last_activity) > timeout_ns) { | 3069 | | - Debug(this, | 3070 | | - "Stream %" PRId64 " idle timeout exceeded, destroying", | 3071 | | - id); | | 3068 | + Debug(this, "Stream %" PRId64 " idle timeout exceeded, destroying", id); | 3072 | 3069 | // Notify the peer before destroying. ShutdownStream sends both | 3073 | 3070 | // STOP_SENDING and RESET_STREAM as appropriate, using the | 3074 | 3071 | // application's no-error code for non-APPLICATION errors (since | 3075 | 3072 | // these frames carry application-level error codes per RFC 9000). | 3076 | 3073 | // Without this, the peer's stream sits orphaned until the | 3077 | 3074 | // session closes. | 3078 | | - auto error = QuicError::ForTransport(NGTCP2_ERR_PROTO, | 3079 | | - "stream idle timeout"); | | 3075 | + auto error = | | 3076 | + QuicError::ForTransport(NGTCP2_ERR_PROTO, "stream idle timeout"); | 3080 | 3077 | ShutdownStream(id, error); | 3081 | 3078 | stream->Destroy(error); | 3082 | 3079 | STAT_INCREMENT(Stats, streams_idle_timed_out); | |
Collapse file Copy file name to clipboardExpand all lines: src/quic/streams.cc +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1891,6 +1891,7 @@ void Stream::EmitClose(const QuicError& error) { | 1891 | 1891 | } | 1892 | 1892 |
| 1893 | 1893 | void Stream::EmitHeaders() { | | 1894 | + STAT_RECORD_TIMESTAMP(Stats, received_at); | 1894 | 1895 | // state()->wants_headers will be set from the javascript side if the | 1895 | 1896 | // stream object has a handler for the headers event. | 1896 | 1897 | if (!env()->can_call_into_js() || !state()->wants_headers) { | |
Collapse file Copy file name to clipboardExpand all lines: src/quic/tlscontext.cc +27-6Lines changed: 27 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -261,6 +261,18 @@ bool OSSLContext::set_hostname(std::string_view hostname) const { | 261 | 261 | const_cast<char*>(name.c_str())) == 1; | 262 | 262 | } | 263 | 263 |
| | 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 | + | 264 | 276 | bool OSSLContext::set_early_data_enabled() const { | 265 | 277 | return SSL_set_quic_tls_early_data_enabled(*this, 1) == 1; | 266 | 278 | } | @@ -714,12 +726,13 @@ Maybe<TLSContext::Options> TLSContext::Options::From(Environment* env, | 714 | 726 | env, &options, params, state.name##_string()) | 715 | 727 |
| 716 | 728 | 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)) { | 723 | 736 | return Nothing<Options>(); | 724 | 737 | } | 725 | 738 |
| @@ -854,6 +867,14 @@ void TLSSession::Initialize( | 854 | 867 | return; | 855 | 868 | } | 856 | 869 |
| | 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 | + | 857 | 878 | if (maybeSessionTicket.has_value()) { | 858 | 879 | const auto& sessionTicket = *maybeSessionTicket; | 859 | 880 | uv_buf_t buf = sessionTicket.ticket(); | |
Collapse file Copy file name to clipboardExpand all lines: src/quic/tlscontext.h +8Lines changed: 8 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -51,6 +51,7 @@ class OSSLContext final { | 51 | 51 |
| 52 | 52 | bool set_alpn_protocols(std::string_view protocols) const; | 53 | 53 | bool set_hostname(std::string_view hostname) const; | | 54 | + bool set_verify_hostname(std::string_view hostname) const; | 54 | 55 | bool set_early_data_enabled() const; | 55 | 56 | bool set_transport_params(const ngtcp2_vec& tp) const; | 56 | 57 |
| @@ -215,6 +216,13 @@ class TLSContext final : public MemoryRetainer, | 215 | 216 | // by the client side. | 216 | 217 | bool verify_peer_strict = false; | 217 | 218 |
| | 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 | + | 218 | 226 | // When true (the default), the server accepts 0-RTT early data | 219 | 227 | // from clients with valid session tickets. When false, early data | 220 | 228 | // is disabled and clients must complete a full handshake before | |
|