diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index cc0199a622ae..62f0e72215d8 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -13287,6 +13287,18 @@ mirror: always rust: true +# Whether to send a Xyber768 key share in HTTP/3 TLS handshakes. +# Has no effect unless security.tls.enable_kyber is true. +- name: network.http.http3.enable_kyber + type: RelaxedAtomicBool +#ifdef ANDROID + value: false +#else + value: @IS_NIGHTLY_BUILD@ +#endif + mirror: always + rust: true + # When true, a http request will be upgraded to https when HTTPS RR is # available. - name: network.dns.upgrade_with_https_rr @@ -15052,6 +15064,7 @@ value: @IS_NIGHTLY_BUILD@ #endif mirror: always + rust: true - name: security.ssl.treat_unsafe_negotiation_as_broken type: RelaxedAtomicBool diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp index 80ee3267fa8a..53734ade9625 100644 --- a/netwerk/protocol/http/Http3Session.cpp +++ b/netwerk/protocol/http/Http3Session.cpp @@ -91,7 +91,7 @@ static nsresult RawBytesToNetAddr(uint16_t aFamily, const uint8_t* aRemoteAddr, nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo, nsINetAddr* aSelfAddr, nsINetAddr* aPeerAddr, - HttpConnectionUDP* udpConn, uint32_t controlFlags, + HttpConnectionUDP* udpConn, uint32_t aProviderFlags, nsIInterfaceRequestor* callbacks) { LOG3(("Http3Session::Init %p", this)); @@ -108,7 +108,7 @@ nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo, mSocketControl = new QuicSocketControl( httpsProxy ? aConnInfo->ProxyInfo()->Host() : aConnInfo->GetOrigin(), httpsProxy ? aConnInfo->ProxyInfo()->Port() : aConnInfo->OriginPort(), - controlFlags, this); + aProviderFlags, this); NetAddr selfAddr; MOZ_ALWAYS_SUCCEEDS(aSelfAddr->GetNetAddr(&selfAddr)); @@ -142,7 +142,7 @@ nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo, StaticPrefs::network_http_http3_max_stream_data(), StaticPrefs::network_http_http3_version_negotiation_enabled(), mConnInfo->GetWebTransport(), gHttpHandler->Http3QlogDir(), datagramSize, - StaticPrefs::network_http_http3_max_accumlated_time_ms(), + StaticPrefs::network_http_http3_max_accumlated_time_ms(), aProviderFlags, getter_AddRefs(mHttp3Connection)); if (NS_FAILED(rv)) { return rv; diff --git a/netwerk/protocol/http/Http3Session.h b/netwerk/protocol/http/Http3Session.h index c72f24959ec3..9be9278a33f4 100644 --- a/netwerk/protocol/http/Http3Session.h +++ b/netwerk/protocol/http/Http3Session.h @@ -130,7 +130,7 @@ class Http3Session final : public nsAHttpTransaction, public nsAHttpConnection { Http3Session(); nsresult Init(const nsHttpConnectionInfo* aConnInfo, nsINetAddr* selfAddr, nsINetAddr* peerAddr, HttpConnectionUDP* udpConn, - uint32_t controlFlags, nsIInterfaceRequestor* callbacks); + uint32_t aProviderFlags, nsIInterfaceRequestor* callbacks); bool IsConnected() const { return mState == CONNECTED; } bool CanSendData() const { diff --git a/netwerk/protocol/http/HttpConnectionUDP.cpp b/netwerk/protocol/http/HttpConnectionUDP.cpp index b0e39a6d564b..0c33d147f06d 100644 --- a/netwerk/protocol/http/HttpConnectionUDP.cpp +++ b/netwerk/protocol/http/HttpConnectionUDP.cpp @@ -24,6 +24,7 @@ #include "nsHttpHandler.h" #include "Http3Session.h" #include "nsComponentManagerUtils.h" +#include "nsIHttpChannelInternal.h" #include "nsISocketProvider.h" #include "nsNetAddr.h" #include "nsINetAddr.h" @@ -129,26 +130,31 @@ nsresult HttpConnectionUDP::Init(nsHttpConnectionInfo* info, return rv; } - uint32_t controlFlags = 0; + uint32_t providerFlags = 0; if (caps & NS_HTTP_LOAD_ANONYMOUS) { - controlFlags |= nsISocketProvider::ANONYMOUS_CONNECT; + providerFlags |= nsISocketProvider::ANONYMOUS_CONNECT; } if (mConnInfo->GetPrivate()) { - controlFlags |= nsISocketProvider::NO_PERMANENT_STORAGE; + providerFlags |= nsISocketProvider::NO_PERMANENT_STORAGE; } if (((caps & NS_HTTP_BE_CONSERVATIVE) || mConnInfo->GetBeConservative()) && gHttpHandler->ConnMgr()->BeConservativeIfProxied( mConnInfo->ProxyInfo())) { - controlFlags |= nsISocketProvider::BE_CONSERVATIVE; + providerFlags |= nsISocketProvider::BE_CONSERVATIVE; + } + if ((caps & NS_HTTP_IS_RETRY) || + (mConnInfo->GetTlsFlags() & + nsIHttpChannelInternal::TLS_FLAG_CONFIGURE_AS_RETRY)) { + providerFlags |= nsISocketProvider::IS_RETRY; } if (mResolvedByTRR) { - controlFlags |= nsISocketProvider::USED_PRIVATE_DNS; + providerFlags |= nsISocketProvider::USED_PRIVATE_DNS; } mPeerAddr = new nsNetAddr(&peerAddr); mHttp3Session = new Http3Session(); - rv = mHttp3Session->Init(mConnInfo, mSelfAddr, mPeerAddr, this, controlFlags, + rv = mHttp3Session->Init(mConnInfo, mSelfAddr, mPeerAddr, this, providerFlags, callbacks); if (NS_FAILED(rv)) { LOG( diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl index f1ebeb7c9451..bd8848038239 100644 --- a/netwerk/protocol/http/nsIHttpChannelInternal.idl +++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl @@ -315,9 +315,10 @@ interface nsIHttpChannelInternal : nsISupports /** * An opaque flags for non-standard behavior of the TLS system. - * It is unlikely this will need to be set outside of telemetry studies - * relating to the TLS implementation. + * It is unlikely this will need to be set outside of tests or telemetry + * studies relating to the TLS implementation. */ + const unsigned long TLS_FLAG_CONFIGURE_AS_RETRY = (1 << 16); [must_use] attribute unsigned long tlsFlags; [must_use] readonly attribute PRTime lastModifiedTime; diff --git a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h index 63fbe389f3f2..2c5929fdc789 100644 --- a/netwerk/socket/neqo_glue/NeqoHttp3Conn.h +++ b/netwerk/socket/neqo_glue/NeqoHttp3Conn.h @@ -19,12 +19,13 @@ class NeqoHttp3Conn final { uint64_t aMaxData, uint64_t aMaxStreamData, bool aVersionNegotiation, bool aWebTransport, const nsACString& aQlogDir, uint32_t aDatagramSize, - uint32_t aMaxAccumulatedTime, NeqoHttp3Conn** aConn) { + uint32_t aMaxAccumulatedTime, uint32_t aProviderFlags, + NeqoHttp3Conn** aConn) { return neqo_http3conn_new( &aOrigin, &aAlpn, &aLocalAddr, &aRemoteAddr, aMaxTableSize, aMaxBlockedStreams, aMaxData, aMaxStreamData, aVersionNegotiation, aWebTransport, &aQlogDir, aDatagramSize, aMaxAccumulatedTime, - (const mozilla::net::NeqoHttp3Conn**)aConn); + aProviderFlags, (const mozilla::net::NeqoHttp3Conn**)aConn); } void Close(uint64_t aError) { neqo_http3conn_close(this, aError); } diff --git a/netwerk/socket/neqo_glue/src/lib.rs b/netwerk/socket/neqo_glue/src/lib.rs index 9bc07b055b7e..9d1fa68ed283 100644 --- a/netwerk/socket/neqo_glue/src/lib.rs +++ b/netwerk/socket/neqo_glue/src/lib.rs @@ -39,7 +39,7 @@ use thin_vec::ThinVec; use uuid::Uuid; #[cfg(windows)] use winapi::shared::ws2def::{AF_INET, AF_INET6}; -use xpcom::{AtomicRefcnt, RefCounted, RefPtr}; +use xpcom::{interfaces::nsISocketProvider, AtomicRefcnt, RefCounted, RefPtr}; #[repr(C)] pub struct NeqoHttp3Conn { @@ -119,6 +119,7 @@ impl NeqoHttp3Conn { qlog_dir: &nsACString, webtransport_datagram_size: u32, max_accumlated_time_ms: u32, + provider_flags: u32, ) -> Result, nsresult> { // Nss init. init().map_err(|_| NS_ERROR_UNEXPECTED)?; @@ -194,6 +195,24 @@ impl NeqoHttp3Conn { return Err(NS_ERROR_INVALID_ARG); }; + if static_prefs::pref!("security.tls.enable_kyber") + && static_prefs::pref!("network.http.http3.enable_kyber") + && (provider_flags & nsISocketProvider::IS_RETRY) == 0 + && (provider_flags & nsISocketProvider::BE_CONSERVATIVE) == 0 + { + // These operations are infallible when conn.state == State::Init. + let _ = conn.set_groups(&[ + neqo_crypto::TLS_GRP_KEM_XYBER768D00, + neqo_crypto::TLS_GRP_EC_X25519, + neqo_crypto::TLS_GRP_EC_SECP256R1, + neqo_crypto::TLS_GRP_EC_SECP384R1, + neqo_crypto::TLS_GRP_EC_SECP521R1, + ]); + // This ensures that we send key shares for Xyber768D00, X25519, and P-256, + // so that servers are less likely to use HelloRetryRequest. + let _ = conn.send_additional_key_shares(2); + } + let mut conn = Http3Client::new_with_conn(conn, http3_settings); if !qlog_dir.is_empty() { @@ -280,6 +299,7 @@ pub extern "C" fn neqo_http3conn_new( qlog_dir: &nsACString, webtransport_datagram_size: u32, max_accumlated_time_ms: u32, + provider_flags: u32, result: &mut *const NeqoHttp3Conn, ) -> nsresult { *result = ptr::null_mut(); @@ -298,6 +318,7 @@ pub extern "C" fn neqo_http3conn_new( qlog_dir, webtransport_datagram_size, max_accumlated_time_ms, + provider_flags, ) { Ok(http3_conn) => { http3_conn.forget(result); diff --git a/netwerk/test/unit/test_http3_kyber.js b/netwerk/test/unit/test_http3_kyber.js new file mode 100644 index 000000000000..4b3f1cbc50ea --- /dev/null +++ b/netwerk/test/unit/test_http3_kyber.js @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +registerCleanupFunction(async () => { + Services.prefs.clearUserPref("security.tls.enable_kyber"); + Services.prefs.clearUserPref("network.http.http3.enable_kyber"); + http3_clear_prefs(); +}); + +add_task(async function setup() { + Services.prefs.setBoolPref("security.tls.enable_kyber", true); + Services.prefs.setBoolPref("network.http.http3.enable_kyber", true); + await http3_setup_tests("h3"); +}); + +let Http3Listener = function () {}; + +Http3Listener.prototype = { + expectedKeaGroup: undefined, + + onStartRequest: function testOnStartRequest(request) { + Assert.equal(request.status, Cr.NS_OK); + Assert.equal(request.responseStatus, 200); + + Assert.equal(request.securityInfo.keaGroupName, this.expectedKeaGroup); + }, + + onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) { + read_stream(stream, cnt); + }, + + onStopRequest: function testOnStopRequest(request) { + let httpVersion = ""; + try { + httpVersion = request.protocolVersion; + } catch (e) {} + Assert.equal(httpVersion, "h3"); + + this.finish(); + }, +}; + +function chanPromise(chan, listener) { + return new Promise(resolve => { + function finish(result) { + resolve(result); + } + listener.finish = finish; + chan.asyncOpen(listener); + }); +} + +function makeChan(uri) { + let chan = NetUtil.newChannel({ + uri, + loadUsingSystemPrincipal: true, + }).QueryInterface(Ci.nsIHttpChannel); + chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; + return chan; +} + +add_task(async function test_kyber_success() { + let listener = new Http3Listener(); + listener.expectedKeaGroup = "xyber768d00"; + let chan = makeChan("https://foo.example.com"); + await chanPromise(chan, listener); +}); + +add_task(async function test_no_kyber_on_retry() { + Services.obs.notifyObservers(null, "net:cancel-all-connections"); + + let listener = new Http3Listener(); + listener.expectedKeaGroup = "x25519"; + let chan = makeChan("https://foo.example.com"); + chan.QueryInterface(Ci.nsIHttpChannelInternal).tlsFlags = + Ci.nsIHttpChannelInternal.TLS_FLAG_CONFIGURE_AS_RETRY; + await chanPromise(chan, listener); +}); diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml index ed605d1efbc5..cdbf863b528c 100644 --- a/netwerk/test/unit/xpcshell.toml +++ b/netwerk/test/unit/xpcshell.toml @@ -703,6 +703,12 @@ skip-if = [ ] run-sequentially = "node server exceptions dont replay well" +["test_http3_kyber.js"] +skip-if = [ + "os == 'win'", + "os == 'android'", +] + ["test_http3_large_post.js"] skip-if = [ "os == 'win'", @@ -718,6 +724,7 @@ disabled = "bug 1771744 - telemetry probe expired" # os == 'android' # socketprocess_networking + ["test_http3_perf.js"] skip-if = [ "os == 'android'",