diff options
Diffstat (limited to 'netwerk/protocol/http')
27 files changed, 706 insertions, 123 deletions
diff --git a/netwerk/protocol/http/AlternateServices.cpp b/netwerk/protocol/http/AlternateServices.cpp index a386206b6d..e0571f02ce 100644 --- a/netwerk/protocol/http/AlternateServices.cpp +++ b/netwerk/protocol/http/AlternateServices.cpp @@ -7,6 +7,7 @@ #include "HttpLog.h" #include "AlternateServices.h" +#include <algorithm> #include "LoadInfo.h" #include "mozilla/Atomics.h" #include "mozilla/StaticPrefs_network.h" @@ -87,16 +88,15 @@ void AltSvcMapping::ProcessHeader( ParsedHeaderValueListList parsedAltSvc(buf); int32_t numEntriesInHeader = parsedAltSvc.mValues.Length(); - // Only use one http3 version. - bool http3Found = false; - + nsTArray<RefPtr<AltSvcMapping>> h3Mappings; + nsTArray<RefPtr<AltSvcMapping>> otherMappings; for (uint32_t index = 0; index < parsedAltSvc.mValues.Length(); ++index) { uint32_t maxage = 86400; // default nsAutoCString hostname; nsAutoCString npnToken; int32_t portno = originPort; bool clearEntry = false; - bool isHttp3 = false; + SupportedAlpnRank alpnRank = SupportedAlpnRank::NOT_SUPPORTED; for (uint32_t pairIndex = 0; pairIndex < parsedAltSvc.mValues[index].mValues.Length(); @@ -116,7 +116,7 @@ void AltSvcMapping::ProcessHeader( // h2=[hostname]:443 or h3-xx=[hostname]:port // XX is current version we support and it is define in nsHttp.h. - isHttp3 = gHttpHandler->IsHttp3VersionSupported(currentName); + alpnRank = IsAlpnSupported(currentName); npnToken = currentName; int32_t colonIndex = currentValue.FindChar(':'); @@ -153,12 +153,7 @@ void AltSvcMapping::ProcessHeader( // update nsCString length nsUnescape(npnToken.BeginWriting()); npnToken.SetLength(strlen(npnToken.BeginReading())); - - if (http3Found && isHttp3) { - LOG(("Alt Svc ignore multiple Http3 options (%s)", npnToken.get())); - continue; - } - + bool isHttp3 = net::IsHttp3(alpnRank); SpdyInformation* spdyInfo = gHttpHandler->SpdyInfo(); if (!(npnToken.Equals(spdyInfo->VersionString) && StaticPrefs::network_http_http2_enabled()) && @@ -169,16 +164,13 @@ void AltSvcMapping::ProcessHeader( continue; } - if (isHttp3) { - http3Found = true; - } - - RefPtr<AltSvcMapping> mapping = - new AltSvcMapping(gHttpHandler->AltServiceCache()->GetStoragePtr(), - gHttpHandler->AltServiceCache()->StorageEpoch(), - originScheme, originHost, originPort, username, - privateBrowsing, NowInSeconds() + maxage, hostname, - portno, npnToken, originAttributes, isHttp3); + LOG(("AltSvcMapping created npnToken=%s", npnToken.get())); + RefPtr<AltSvcMapping> mapping = new AltSvcMapping( + gHttpHandler->AltServiceCache()->GetStoragePtr(), + gHttpHandler->AltServiceCache()->StorageEpoch(), originScheme, + originHost, originPort, username, privateBrowsing, + NowInSeconds() + maxage, hostname, portno, npnToken, originAttributes, + isHttp3, alpnRank); if (mapping->TTL() <= 0) { LOG(("Alt Svc invalid map")); mapping = nullptr; @@ -186,15 +178,39 @@ void AltSvcMapping::ProcessHeader( // as that would have happened if we had accepted the parameters. gHttpHandler->AltServiceCache()->ClearHostMapping(originHost, originPort, originAttributes); - } else if (!aDontValidate) { - gHttpHandler->UpdateAltServiceMapping(mapping, proxyInfo, callbacks, caps, - originAttributes); + } else { + if (isHttp3) { + h3Mappings.AppendElement(std::move(mapping)); + } else { + otherMappings.AppendElement(std::move(mapping)); + } + } + } + + auto doUpdateAltSvcMapping = [&](AltSvcMapping* aMapping) { + if (!aDontValidate) { + gHttpHandler->UpdateAltServiceMapping(aMapping, proxyInfo, callbacks, + caps, originAttributes); } else { gHttpHandler->UpdateAltServiceMappingWithoutValidation( - mapping, proxyInfo, callbacks, caps, originAttributes); + aMapping, proxyInfo, callbacks, caps, originAttributes); } + }; + + if (!h3Mappings.IsEmpty()) { + // Select the HTTP/3 (h3) AltSvcMapping with the highest ALPN rank from + // h3Mappings. + RefPtr<AltSvcMapping> latestH3Mapping = *std::max_element( + h3Mappings.begin(), h3Mappings.end(), + [](const RefPtr<AltSvcMapping>& a, const RefPtr<AltSvcMapping>& b) { + return a->AlpnRank() < b->AlpnRank(); + }); + doUpdateAltSvcMapping(latestH3Mapping); } + std::for_each(otherMappings.begin(), otherMappings.end(), + doUpdateAltSvcMapping); + if (numEntriesInHeader) { // Ignore headers that were just "alt-svc: clear" Telemetry::Accumulate(Telemetry::HTTP_ALTSVC_ENTRIES_PER_HEADER, numEntriesInHeader); @@ -209,7 +225,7 @@ AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch, const nsACString& alternateHost, int32_t alternatePort, const nsACString& npnToken, const OriginAttributes& originAttributes, - bool aIsHttp3) + bool aIsHttp3, SupportedAlpnRank aRank) : mStorage(storage), mStorageEpoch(epoch), mAlternateHost(alternateHost), @@ -221,7 +237,8 @@ AltSvcMapping::AltSvcMapping(nsIDataStorage* storage, int32_t epoch, mExpiresAt(expiresAt), mNPNToken(npnToken), mOriginAttributes(originAttributes), - mIsHttp3(aIsHttp3) { + mIsHttp3(aIsHttp3), + mAlpnRank(aRank) { MOZ_ASSERT(NS_IsMainThread()); if (NS_FAILED(SchemeIsHTTPS(originScheme, mHttps))) { diff --git a/netwerk/protocol/http/AlternateServices.h b/netwerk/protocol/http/AlternateServices.h index 4de5a281c0..02b45f7bee 100644 --- a/netwerk/protocol/http/AlternateServices.h +++ b/netwerk/protocol/http/AlternateServices.h @@ -23,6 +23,7 @@ https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06 #ifndef mozilla_net_AlternateServices_h #define mozilla_net_AlternateServices_h +#include "nsHttp.h" #include "nsRefPtrHashtable.h" #include "nsString.h" #include "nsIDataStorage.h" @@ -53,7 +54,8 @@ class AltSvcMapping { bool privateBrowsing, uint32_t expiresAt, const nsACString& alternateHost, int32_t alternatePort, const nsACString& npnToken, - const OriginAttributes& originAttributes, bool aIsHttp3); + const OriginAttributes& originAttributes, bool aIsHttp3, + SupportedAlpnRank aRank); public: AltSvcMapping(nsIDataStorage* storage, int32_t storageEpoch, @@ -103,6 +105,7 @@ class AltSvcMapping { bool IsHttp3() { return mIsHttp3; } const nsCString& NPNToken() const { return mNPNToken; } + SupportedAlpnRank AlpnRank() const { return mAlpnRank; } private: virtual ~AltSvcMapping() = default; @@ -138,6 +141,7 @@ class AltSvcMapping { bool mSyncOnlyOnSuccess{false}; bool mIsHttp3{false}; + SupportedAlpnRank mAlpnRank{SupportedAlpnRank::NOT_SUPPORTED}; }; class AltSvcOverride : public nsIInterfaceRequestor, diff --git a/netwerk/protocol/http/CacheControlParser.cpp b/netwerk/protocol/http/CacheControlParser.cpp index 79cdef5439..4166ab783d 100644 --- a/netwerk/protocol/http/CacheControlParser.cpp +++ b/netwerk/protocol/http/CacheControlParser.cpp @@ -69,6 +69,7 @@ void CacheControlParser::Directive() { bool CacheControlParser::SecondsValue(uint32_t* seconds, uint32_t defaultVal) { SkipWhites(); if (!CheckChar('=')) { + IgnoreDirective(); *seconds = defaultVal; return !!defaultVal; } @@ -76,7 +77,9 @@ bool CacheControlParser::SecondsValue(uint32_t* seconds, uint32_t defaultVal) { SkipWhites(); if (!ReadInteger(seconds)) { NS_WARNING("Unexpected value in Cache-control header value"); - return false; + IgnoreDirective(); + *seconds = defaultVal; + return !!defaultVal; } return true; diff --git a/netwerk/protocol/http/ConnectionEntry.cpp b/netwerk/protocol/http/ConnectionEntry.cpp index 473f0ab9fc..7b59011cd3 100644 --- a/netwerk/protocol/http/ConnectionEntry.cpp +++ b/netwerk/protocol/http/ConnectionEntry.cpp @@ -1098,5 +1098,16 @@ void ConnectionEntry::SetRetryDifferentIPFamilyForHttp3(uint16_t aIPFamily) { MOZ_DIAGNOSTIC_ASSERT(mPreferIPv4 ^ mPreferIPv6); } +void ConnectionEntry::SetServerCertHashes( + nsTArray<RefPtr<nsIWebTransportHash>>&& aHashes) { + mServerCertHashes = std::move(aHashes); +} + +const nsTArray<RefPtr<nsIWebTransportHash>>& +ConnectionEntry::GetServerCertHashes() { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + return mServerCertHashes; +} + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/ConnectionEntry.h b/netwerk/protocol/http/ConnectionEntry.h index 811c5481de..8ccc126503 100644 --- a/netwerk/protocol/http/ConnectionEntry.h +++ b/netwerk/protocol/http/ConnectionEntry.h @@ -200,6 +200,10 @@ class ConnectionEntry { bool AllowToRetryDifferentIPFamilyForHttp3(nsresult aError); void SetRetryDifferentIPFamilyForHttp3(uint16_t aIPFamily); + void SetServerCertHashes(nsTArray<RefPtr<nsIWebTransportHash>>&& aHashes); + + const nsTArray<RefPtr<nsIWebTransportHash>>& GetServerCertHashes(); + private: void InsertIntoIdleConnections_internal(nsHttpConnection* conn); void RemoveFromIdleConnectionsIndex(size_t inx); @@ -219,6 +223,9 @@ class ConnectionEntry { nsTArray<RefPtr<DnsAndConnectSocket>> mDnsAndConnectSockets; // dns resolution and half open connections + // If serverCertificateHashes are used, these are stored here + nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes; + PendingTransactionQueue mPendingQ; ~ConnectionEntry(); diff --git a/netwerk/protocol/http/Http2Session.cpp b/netwerk/protocol/http/Http2Session.cpp index e969d60c4d..d5793b2147 100644 --- a/netwerk/protocol/http/Http2Session.cpp +++ b/netwerk/protocol/http/Http2Session.cpp @@ -223,7 +223,7 @@ void Http2Session::ShutdownStream(Http2StreamBase* aStream, nsresult aReason) { CloseStream(aStream, NS_ERROR_NET_INADEQUATE_SECURITY); } else if (!mCleanShutdown && (mGoAwayReason != NO_HTTP_ERROR)) { CloseStream(aStream, NS_ERROR_NET_HTTP2_SENT_GOAWAY); - } else if (!mCleanShutdown && SecurityErrorThatMayNeedRestart(aReason)) { + } else if (!mCleanShutdown && PossibleZeroRTTRetryError(aReason)) { CloseStream(aStream, aReason); } else { CloseStream(aStream, NS_ERROR_ABORT); @@ -1419,6 +1419,13 @@ nsresult Http2Session::RecvHeaders(Http2Session* self) { return self->SessionError(PROTOCOL_ERROR); } + uint32_t frameSize = self->mInputFrameDataSize - paddingControlBytes - + priorityLen - paddingLength; + if (self->mAggregatedHeaderSize + frameSize > + StaticPrefs::network_http_max_response_header_size()) { + LOG(("Http2Session %p header exceeds the limit\n", self)); + return self->SessionError(PROTOCOL_ERROR); + } if (!self->mInputFrameDataStream) { // Cannot find stream. We can continue the session, but we need to // uncompress the header block to maintain the correct compression context @@ -1435,8 +1442,7 @@ nsresult Http2Session::RecvHeaders(Http2Session* self) { self->mDecompressBuffer.Append( &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + priorityLen], - self->mInputFrameDataSize - paddingControlBytes - priorityLen - - paddingLength); + frameSize); if (self->mInputFrameFlags & kFlag_END_HEADERS) { rv = self->UncompressAndDiscard(false); @@ -1466,21 +1472,16 @@ nsresult Http2Session::RecvHeaders(Http2Session* self) { self->mDecompressBuffer.Append( &self->mInputFrameBuffer[kFrameHeaderBytes + paddingControlBytes + priorityLen], - self->mInputFrameDataSize - paddingControlBytes - priorityLen - - paddingLength); + frameSize); self->mInputFrameDataStream->UpdateTransportReadEvents( self->mInputFrameDataSize); self->mLastDataReadEpoch = self->mLastReadEpoch; if (!isContinuation) { - self->mAggregatedHeaderSize = self->mInputFrameDataSize - - paddingControlBytes - priorityLen - - paddingLength; + self->mAggregatedHeaderSize = frameSize; } else { - self->mAggregatedHeaderSize += self->mInputFrameDataSize - - paddingControlBytes - priorityLen - - paddingLength; + self->mAggregatedHeaderSize += frameSize; } if (!endHeadersFlag) { // more are coming - don't process yet diff --git a/netwerk/protocol/http/Http3Session.cpp b/netwerk/protocol/http/Http3Session.cpp index 0369da94ac..80ee3267fa 100644 --- a/netwerk/protocol/http/Http3Session.cpp +++ b/netwerk/protocol/http/Http3Session.cpp @@ -29,6 +29,7 @@ #include "nsSocketTransportService2.h" #include "nsThreadUtils.h" #include "sslerr.h" +#include "WebTransportCertificateVerifier.h" namespace mozilla::net { @@ -153,7 +154,20 @@ nsresult Http3Session::Init(const nsHttpConnectionInfo* aConnInfo, SessionCacheInfo info; udpConn->ChangeConnectionState(ConnectionState::TLS_HANDSHAKING); - if (StaticPrefs::network_http_http3_enable_0rtt() && + auto hasServCertHashes = [&]() -> bool { + if (!mConnInfo->GetWebTransport()) { + return false; + } + const nsTArray<RefPtr<nsIWebTransportHash>>* servCertHashes = + gHttpHandler->ConnMgr()->GetServerCertHashes(mConnInfo); + return servCertHashes && !servCertHashes->IsEmpty(); + }; + + // In WebTransport, when servCertHashes is specified, it indicates that the + // connection to the WebTransport server should authenticate using the + // expected certificate hash. Therefore, 0RTT should be disabled in this + // context to ensure the certificate hash is checked. + if (StaticPrefs::network_http_http3_enable_0rtt() && !hasServCertHashes() && NS_SUCCEEDED(SSLTokensCache::Get(peerId, token, info))) { LOG(("Found a resumption token in the cache.")); mHttp3Connection->SetResumptionToken(token); @@ -1514,7 +1528,7 @@ nsresult Http3Session::SendData(nsIUDPSocket* socket) { while (CanSendData() && (stream = mReadyForWrite.PopFront())) { LOG(("Http3Session::SendData call ReadSegments from stream=%p [this=%p]", stream.get(), this)); - + stream->SetInTxQueue(false); rv = stream->ReadSegments(); // on stream error we return earlier to let the error be handled. @@ -1557,7 +1571,15 @@ nsresult Http3Session::SendData(nsIUDPSocket* socket) { void Http3Session::StreamReadyToWrite(Http3StreamBase* aStream) { MOZ_ASSERT(aStream); + // Http3Session::StreamReadyToWrite can be called multiple times when we get + // duplicate DataWrite events from neqo at the same time. In this case, we + // only want to insert the stream in `mReadyForWrite` once. + if (aStream->IsInTxQueue()) { + return; + } + mReadyForWrite.Push(aStream); + aStream->SetInTxQueue(true); if (CanSendData() && mConnection) { Unused << mConnection->ResumeSend(); } @@ -2119,6 +2141,32 @@ void Http3Session::CallCertVerification(Maybe<nsCString> aEchPublicName) { return; } + if (mConnInfo->GetWebTransport()) { + // if our connection is webtransport, we might do a verification + // based on serverCertificatedHashes + const nsTArray<RefPtr<nsIWebTransportHash>>* servCertHashes = + gHttpHandler->ConnMgr()->GetServerCertHashes(mConnInfo); + if (servCertHashes && !servCertHashes->IsEmpty() && + certInfo.certs.Length() >= 1) { + // ok, we verify based on serverCertificateHashes + mozilla::pkix::Result rv = AuthCertificateWithServerCertificateHashes( + certInfo.certs[0], *servCertHashes); + if (rv != mozilla::pkix::Result::Success) { + // ok we failed, report it back + LOG( + ("Http3Session::CallCertVerification [this=%p] " + "AuthCertificateWithServerCertificateHashes failed", + this)); + mHttp3Connection->PeerAuthenticated(SSL_ERROR_BAD_CERTIFICATE); + mError = psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERTIFICATE); + return; + } + // ok, we succeded + Authenticated(0); + return; + } + } + Maybe<nsTArray<nsTArray<uint8_t>>> stapledOCSPResponse; if (certInfo.stapled_ocsp_responses_present) { stapledOCSPResponse.emplace(std::move(certInfo.stapled_ocsp_responses)); diff --git a/netwerk/protocol/http/Http3StreamBase.h b/netwerk/protocol/http/Http3StreamBase.h index 9ca85de98e..d658741384 100644 --- a/netwerk/protocol/http/Http3StreamBase.h +++ b/netwerk/protocol/http/Http3StreamBase.h @@ -52,6 +52,9 @@ class Http3StreamBase : public SupportsWeakPtr, public ARefBase { virtual bool RecvdReset() const { return mResetRecv; } virtual void SetRecvdReset() { mResetRecv = true; } + void SetInTxQueue(bool aValue) { mInTxQueue = aValue; } + bool IsInTxQueue() const { return mInTxQueue; } + protected: ~Http3StreamBase(); @@ -63,6 +66,7 @@ class Http3StreamBase : public SupportsWeakPtr, public ARefBase { bool mQueued{false}; bool mFin{false}; bool mResetRecv{false}; + bool mInTxQueue{false}; }; } // namespace mozilla::net diff --git a/netwerk/protocol/http/Http3WebTransportStream.cpp b/netwerk/protocol/http/Http3WebTransportStream.cpp index c76d8ef798..01a467030f 100644 --- a/netwerk/protocol/http/Http3WebTransportStream.cpp +++ b/netwerk/protocol/http/Http3WebTransportStream.cpp @@ -527,6 +527,10 @@ nsresult Http3WebTransportStream::WriteSegments() { if (rv == NS_BASE_STREAM_WOULD_BLOCK) { rv = NS_OK; } + if (rv == NS_BASE_STREAM_CLOSED) { + mReceiveStreamPipeOut->Close(); + rv = NS_OK; + } again = false; } else if (NS_FAILED(mSocketInCondition)) { if (mSocketInCondition != NS_BASE_STREAM_WOULD_BLOCK) { diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index 59242f6933..9008d758fc 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -3927,7 +3927,6 @@ HttpBaseChannel::GetRequestMode(RequestMode* aMode) { NS_IMETHODIMP HttpBaseChannel::SetRequestMode(RequestMode aMode) { - MOZ_ASSERT(aMode != RequestMode::EndGuard_); mRequestMode = aMode; return NS_OK; } @@ -4438,6 +4437,9 @@ void HttpBaseChannel::DoNotifyListener() { // as not-pending. StoreIsPending(false); + // notify "http-on-before-stop-request" observers + gHttpHandler->OnBeforeStopRequest(this); + if (mListener && !LoadOnStopRequestCalled()) { nsCOMPtr<nsIStreamListener> listener = mListener; StoreOnStopRequestCalled(true); @@ -4445,7 +4447,7 @@ void HttpBaseChannel::DoNotifyListener() { } StoreOnStopRequestCalled(true); - // notify "http-on-stop-connect" observers + // notify "http-on-stop-request" observers gHttpHandler->OnStopRequest(this); // This channel has finished its job, potentially release any tail-blocked diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp index 393d0aa37d..c7009f34d3 100644 --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -1160,25 +1160,45 @@ void HttpChannelChild::CollectOMTTelemetry() { key, static_cast<LABELS_HTTP_CHILD_OMT_STATS>(mOMTResult)); } +// We want to inspect all upgradable mixed content loads +// (i.e., loads point to HTTP from an HTTPS page), for +// resources that stem from audio, video and img elements. +// Of those, we want to measure which succceed and which fail. +// Some double negatives, but we check the following:exempt loads that +// 1) Request was upgraded as mixed passive content +// 2) Request _could_ have been upgraded as mixed passive content if the pref +// had been set and Request wasn't upgraded by any other means (URL isn't https) void HttpChannelChild::CollectMixedContentTelemetry() { MOZ_ASSERT(NS_IsMainThread()); - nsContentPolicyType internalLoadType; - mLoadInfo->GetInternalContentPolicyType(&internalLoadType); - bool statusIsSuccess = NS_SUCCEEDED(mStatus); + bool wasUpgraded = mLoadInfo->GetBrowserDidUpgradeInsecureRequests(); + if (!wasUpgraded) { + // If this wasn't upgraded, let's check if it _could_ have been upgraded as + // passive mixed content and that it wasn't upgraded with any other method + if (!mURI->SchemeIs("https") && + !mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) { + return; + } + } + + // UseCounters require a document. RefPtr<Document> doc; mLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); if (!doc) { return; } - if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE || - internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD) { - if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) { + + nsContentPolicyType internalLoadType; + mLoadInfo->GetInternalContentPolicyType(&internalLoadType); + bool statusIsSuccess = NS_SUCCEEDED(mStatus); + + if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_IMAGE) { + if (wasUpgraded) { doc->SetUseCounter( statusIsSuccess ? eUseCounter_custom_MixedContentUpgradedImageSuccess : eUseCounter_custom_MixedContentUpgradedImageFailure); - } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) { + } else { doc->SetUseCounter( statusIsSuccess ? eUseCounter_custom_MixedContentNotUpgradedImageSuccess @@ -1187,12 +1207,12 @@ void HttpChannelChild::CollectMixedContentTelemetry() { return; } if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_VIDEO) { - if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) { + if (wasUpgraded) { doc->SetUseCounter( statusIsSuccess ? eUseCounter_custom_MixedContentUpgradedVideoSuccess : eUseCounter_custom_MixedContentUpgradedVideoFailure); - } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) { + } else { doc->SetUseCounter( statusIsSuccess ? eUseCounter_custom_MixedContentNotUpgradedVideoSuccess @@ -1201,12 +1221,12 @@ void HttpChannelChild::CollectMixedContentTelemetry() { return; } if (internalLoadType == nsIContentPolicy::TYPE_INTERNAL_AUDIO) { - if (mLoadInfo->GetBrowserUpgradeInsecureRequests()) { + if (wasUpgraded) { doc->SetUseCounter( statusIsSuccess ? eUseCounter_custom_MixedContentUpgradedAudioSuccess : eUseCounter_custom_MixedContentUpgradedAudioFailure); - } else if (mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) { + } else { doc->SetUseCounter( statusIsSuccess ? eUseCounter_custom_MixedContentNotUpgradedAudioSuccess @@ -1262,6 +1282,9 @@ void HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest, MOZ_ASSERT(!LoadOnStopRequestCalled(), "We should not call OnStopRequest twice"); + // notify "http-on-before-stop-request" observers + gHttpHandler->OnBeforeStopRequest(this); + if (mListener) { nsCOMPtr<nsIStreamListener> listener(mListener); StoreOnStopRequestCalled(true); @@ -1280,7 +1303,7 @@ void HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest, return; } - // notify "http-on-stop-connect" observers + // notify "http-on-stop-request" observers gHttpHandler->OnStopRequest(this); ReleaseListeners(); @@ -1471,6 +1494,9 @@ void HttpChannelChild::ContinueDoNotifyListener() { // as not-pending. StoreIsPending(false); + // notify "http-on-before-stop-request" observers + gHttpHandler->OnBeforeStopRequest(this); + if (mListener && !LoadOnStopRequestCalled()) { nsCOMPtr<nsIStreamListener> listener = mListener; StoreOnStopRequestCalled(true); @@ -3083,30 +3109,19 @@ HttpChannelChild::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) { void HttpChannelChild::TrySendDeletingChannel() { AUTO_PROFILER_LABEL("HttpChannelChild::TrySendDeletingChannel", NETWORK); + MOZ_ASSERT(NS_IsMainThread()); + if (!mDeletingChannelSent.compareExchange(false, true)) { // SendDeletingChannel is already sent. return; } - if (NS_IsMainThread()) { - if (NS_WARN_IF(!CanSend())) { - // IPC actor is destroyed already, do not send more messages. - return; - } - - Unused << PHttpChannelChild::SendDeletingChannel(); + if (NS_WARN_IF(!CanSend())) { + // IPC actor is destroyed already, do not send more messages. return; } - nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget(); - MOZ_ASSERT(neckoTarget); - - DebugOnly<nsresult> rv = neckoTarget->Dispatch( - NewNonOwningRunnableMethod( - "net::HttpChannelChild::TrySendDeletingChannel", this, - &HttpChannelChild::TrySendDeletingChannel), - NS_DISPATCH_NORMAL); - MOZ_ASSERT(NS_SUCCEEDED(rv)); + Unused << PHttpChannelChild::SendDeletingChannel(); } nsresult HttpChannelChild::AsyncCallImpl( diff --git a/netwerk/protocol/http/WebTransportCertificateVerifier.cpp b/netwerk/protocol/http/WebTransportCertificateVerifier.cpp new file mode 100644 index 0000000000..cc778640a1 --- /dev/null +++ b/netwerk/protocol/http/WebTransportCertificateVerifier.cpp @@ -0,0 +1,280 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "WebTransportCertificateVerifier.h" +#include "ScopedNSSTypes.h" +#include "nss/mozpkix/pkixutil.h" +#include "nss/mozpkix/pkixcheck.h" +#include "hasht.h" + +namespace mozilla::psm { + +class ServerCertHashesTrustDomain : public mozilla::pkix::TrustDomain { + public: + ServerCertHashesTrustDomain() = default; + + virtual mozilla::pkix::Result FindIssuer( + mozilla::pkix::Input encodedIssuerName, IssuerChecker& checker, + mozilla::pkix::Time time) override; + + virtual mozilla::pkix::Result GetCertTrust( + mozilla::pkix::EndEntityOrCA endEntityOrCA, + const mozilla::pkix::CertPolicyId& policy, + mozilla::pkix::Input candidateCertDER, + /*out*/ mozilla::pkix::TrustLevel& trustLevel) override; + + virtual mozilla::pkix::Result CheckSignatureDigestAlgorithm( + mozilla::pkix::DigestAlgorithm digestAlg, + mozilla::pkix::EndEntityOrCA endEntityOrCA, + mozilla::pkix::Time notBefore) override; + + virtual mozilla::pkix::Result CheckRSAPublicKeyModulusSizeInBits( + mozilla::pkix::EndEntityOrCA endEntityOrCA, + unsigned int modulusSizeInBits) override; + + virtual mozilla::pkix::Result VerifyRSAPKCS1SignedData( + mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, + mozilla::pkix::Input signature, + mozilla::pkix::Input subjectPublicKeyInfo) override; + + virtual mozilla::pkix::Result VerifyRSAPSSSignedData( + mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, + mozilla::pkix::Input signature, + mozilla::pkix::Input subjectPublicKeyInfo) override; + + virtual mozilla::pkix::Result CheckECDSACurveIsAcceptable( + mozilla::pkix::EndEntityOrCA endEntityOrCA, + mozilla::pkix::NamedCurve curve) override; + + virtual mozilla::pkix::Result VerifyECDSASignedData( + mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, + mozilla::pkix::Input signature, + mozilla::pkix::Input subjectPublicKeyInfo) override; + + virtual mozilla::pkix::Result DigestBuf( + mozilla::pkix::Input item, mozilla::pkix::DigestAlgorithm digestAlg, + /*out*/ uint8_t* digestBuf, size_t digestBufLen) override; + + virtual mozilla::pkix::Result CheckValidityIsAcceptable( + mozilla::pkix::Time notBefore, mozilla::pkix::Time notAfter, + mozilla::pkix::EndEntityOrCA endEntityOrCA, + mozilla::pkix::KeyPurposeId keyPurpose) override; + + virtual mozilla::pkix::Result NetscapeStepUpMatchesServerAuth( + mozilla::pkix::Time notBefore, + /*out*/ bool& matches) override; + + virtual mozilla::pkix::Result CheckRevocation( + mozilla::pkix::EndEntityOrCA endEntityOrCA, + const mozilla::pkix::CertID& certID, mozilla::pkix::Time time, + mozilla::pkix::Duration validityDuration, + /*optional*/ const mozilla::pkix::Input* stapledOCSPResponse, + /*optional*/ const mozilla::pkix::Input* aiaExtension, + /*optional*/ const mozilla::pkix::Input* sctExtension) override; + + virtual mozilla::pkix::Result IsChainValid( + const mozilla::pkix::DERArray& certChain, mozilla::pkix::Time time, + const mozilla::pkix::CertPolicyId& requiredPolicy) override; + + virtual void NoteAuxiliaryExtension( + mozilla::pkix::AuxiliaryExtension extension, + mozilla::pkix::Input extensionData) override; +}; + +mozilla::pkix::Result ServerCertHashesTrustDomain::FindIssuer( + mozilla::pkix::Input encodedIssuerName, IssuerChecker& checker, + mozilla::pkix::Time time) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result ServerCertHashesTrustDomain::GetCertTrust( + mozilla::pkix::EndEntityOrCA endEntityOrCA, + const mozilla::pkix::CertPolicyId& policy, + mozilla::pkix::Input candidateCertDER, + /*out*/ mozilla::pkix::TrustLevel& trustLevel) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result +ServerCertHashesTrustDomain::CheckSignatureDigestAlgorithm( + mozilla::pkix::DigestAlgorithm digestAlg, + mozilla::pkix::EndEntityOrCA endEntityOrCA, mozilla::pkix::Time notBefore) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result +ServerCertHashesTrustDomain::CheckRSAPublicKeyModulusSizeInBits( + mozilla::pkix::EndEntityOrCA endEntityOrCA, + unsigned int modulusSizeInBits) { + return mozilla::pkix::Result:: + ERROR_UNSUPPORTED_KEYALG; // RSA is not supported for + // serverCertificateHashes, + // Chromium does only support it for an intermediate period due to spec + // change, we do not support it. +} + +mozilla::pkix::Result ServerCertHashesTrustDomain::VerifyRSAPKCS1SignedData( + mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, + mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result ServerCertHashesTrustDomain::VerifyRSAPSSSignedData( + mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, + mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result ServerCertHashesTrustDomain::CheckECDSACurveIsAcceptable( + mozilla::pkix::EndEntityOrCA endEntityOrCA, + mozilla::pkix::NamedCurve curve) { + return mozilla::pkix::Result::Success; +} + +mozilla::pkix::Result ServerCertHashesTrustDomain::VerifyECDSASignedData( + mozilla::pkix::Input data, mozilla::pkix::DigestAlgorithm digestAlgorithm, + mozilla::pkix::Input signature, mozilla::pkix::Input subjectPublicKeyInfo) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result ServerCertHashesTrustDomain::DigestBuf( + mozilla::pkix::Input item, mozilla::pkix::DigestAlgorithm digestAlg, + /*out*/ uint8_t* digestBuf, size_t digestBufLen) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result ServerCertHashesTrustDomain::CheckValidityIsAcceptable( + mozilla::pkix::Time notBefore, mozilla::pkix::Time notAfter, + mozilla::pkix::EndEntityOrCA endEntityOrCA, + mozilla::pkix::KeyPurposeId keyPurpose) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result +ServerCertHashesTrustDomain::NetscapeStepUpMatchesServerAuth( + mozilla::pkix::Time notBefore, + /*out*/ bool& matches) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result ServerCertHashesTrustDomain::CheckRevocation( + mozilla::pkix::EndEntityOrCA endEntityOrCA, + const mozilla::pkix::CertID& certID, mozilla::pkix::Time time, + mozilla::pkix::Duration validityDuration, + /*optional*/ const mozilla::pkix::Input* stapledOCSPResponse, + /*optional*/ const mozilla::pkix::Input* aiaExtension, + /*optional*/ const mozilla::pkix::Input* sctExtension) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +mozilla::pkix::Result ServerCertHashesTrustDomain::IsChainValid( + const mozilla::pkix::DERArray& certChain, mozilla::pkix::Time time, + const mozilla::pkix::CertPolicyId& requiredPolicy) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); + + return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; +} + +void ServerCertHashesTrustDomain::NoteAuxiliaryExtension( + mozilla::pkix::AuxiliaryExtension extension, + mozilla::pkix::Input extensionData) { + MOZ_ASSERT_UNREACHABLE("not expecting this to be called"); +} + +} // namespace mozilla::psm + +namespace mozilla::net { +// Does certificate verificate as required for serverCertificateHashes +// This function is currently only used for Quic, but may be used later also for +// http/2 +mozilla::pkix::Result AuthCertificateWithServerCertificateHashes( + nsTArray<uint8_t>& peerCert, + const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes) { + using namespace mozilla::pkix; + Input certDER; + mozilla::pkix::Result rv = + certDER.Init(peerCert.Elements(), peerCert.Length()); + if (rv != Success) { + return rv; + } + BackCert cert(certDER, EndEntityOrCA::MustBeEndEntity, nullptr); + rv = cert.Init(); + if (rv != Success) { + return rv; + } + + Time notBefore(Time::uninitialized); + Time notAfter(Time::uninitialized); + rv = ParseValidity(cert.GetValidity(), ¬Before, ¬After); + if (rv != Success) { + return rv; + } + // now we check that validity is not greater than 14 days + Duration certDuration(notBefore, notAfter); + if (certDuration > Duration(60 * 60 * 24 * 14)) { + return mozilla::pkix::Result::ERROR_VALIDITY_TOO_LONG; + } + Time now = Now(); + // and if the certificate is actually valid? + rv = CheckValidity(now, notBefore, notAfter); + if (rv != Success) { + return rv; + } + + mozilla::psm::ServerCertHashesTrustDomain trustDomain; + rv = CheckSubjectPublicKeyInfo( + cert.GetSubjectPublicKeyInfo(), trustDomain, + EndEntityOrCA::MustBeEndEntity /* should be ignored*/); + if (rv != Success) { + return rv; + } + + // ok now the final check, calculate the hash and compare it: + // https://w3c.github.io/webtransport/#compute-a-certificate-hash + nsTArray<uint8_t> certHash; + if (NS_FAILED(Digest::DigestBuf(SEC_OID_SHA256, peerCert, certHash)) || + certHash.Length() != SHA256_LENGTH) { + return mozilla::pkix::Result::ERROR_INVALID_ALGORITHM; + } + + // https://w3c.github.io/webtransport/#verify-a-certificate-hash + for (const auto& hash : aServerCertHashes) { + nsCString algorithm; + if (NS_FAILED(hash->GetAlgorithm(algorithm)) || algorithm != "sha-256") { + continue; + } + + nsTArray<uint8_t> value; + if (NS_FAILED(hash->GetValue(value))) { + continue; + } + + if (certHash == value) { + return Success; + } + } + return mozilla::pkix::Result::ERROR_UNTRUSTED_CERT; +} +} // namespace mozilla::net diff --git a/netwerk/protocol/http/WebTransportCertificateVerifier.h b/netwerk/protocol/http/WebTransportCertificateVerifier.h new file mode 100644 index 0000000000..2045a95a89 --- /dev/null +++ b/netwerk/protocol/http/WebTransportCertificateVerifier.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef mozilla_net_WebTransportCertificateVerifier_h +#define mozilla_net_WebTransportCertificateVerifier_h + +#include "nsTArray.h" +#include "nsIWebTransport.h" +#include "nss/mozpkix/pkixtypes.h" + +namespace mozilla::net { +// This is a special version for serverCertificateHashes introduced with +// WebTransport +mozilla::pkix::Result AuthCertificateWithServerCertificateHashes( + nsTArray<uint8_t>& peerCert, + const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes); + +} // namespace mozilla::net + +#endif // mozilla_net_WebTransportCertificateVerifier_h diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build index db6b2a1a68..b090a56d88 100644 --- a/netwerk/protocol/http/moz.build +++ b/netwerk/protocol/http/moz.build @@ -164,6 +164,7 @@ UNIFIED_SOURCES += [ "TlsHandshaker.cpp", "TLSTransportLayer.cpp", "TRRServiceChannel.cpp", + "WebTransportCertificateVerifier.cpp", ] if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": diff --git a/netwerk/protocol/http/nsHttp.cpp b/netwerk/protocol/http/nsHttp.cpp index 768ad91729..5425c3c7b9 100644 --- a/netwerk/protocol/http/nsHttp.cpp +++ b/netwerk/protocol/http/nsHttp.cpp @@ -1017,13 +1017,15 @@ SupportedAlpnRank IsAlpnSupported(const nsACString& aAlpn) { return SupportedAlpnRank::NOT_SUPPORTED; } -// On some security error when 0RTT is used we want to restart transactions -// without 0RTT. Some firewalls do not behave well with 0RTT and cause this -// errors. -bool SecurityErrorThatMayNeedRestart(nsresult aReason) { +// NSS Errors which *may* have been triggered by the use of 0-RTT in the +// presence of badly behaving middleboxes. We may re-attempt the connection +// without early data. +bool PossibleZeroRTTRetryError(nsresult aReason) { return (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) || - (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_MAC_ALERT)); + (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_MAC_ALERT)) || + (aReason == + psm::GetXPCOMFromNSSError(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT)); } nsresult MakeOriginURL(const nsACString& origin, nsCOMPtr<nsIURI>& url) { diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h index d0bd4cfe67..fc20351c9d 100644 --- a/netwerk/protocol/http/nsHttp.h +++ b/netwerk/protocol/http/nsHttp.h @@ -499,8 +499,6 @@ static inline bool AllowedErrorForHTTPSRRFallback(nsresult aError) { aError == NS_ERROR_UNKNOWN_HOST || aError == NS_ERROR_NET_TIMEOUT; } -bool SecurityErrorThatMayNeedRestart(nsresult aReason); - [[nodiscard]] nsresult MakeOriginURL(const nsACString& origin, nsCOMPtr<nsIURI>& url); @@ -521,6 +519,8 @@ uint64_t WebTransportErrorToHttp3Error(uint8_t aErrorCode); uint8_t Http3ErrorToWebTransportError(uint64_t aErrorCode); +bool PossibleZeroRTTRetryError(nsresult aReason); + } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index b3806f761b..96e61e4fb8 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -9,11 +9,10 @@ #include <inttypes.h> -#include "DocumentChannelParent.h" #include "mozilla/ScopeExit.h" #include "mozilla/Sprintf.h" #include "mozilla/dom/nsCSPContext.h" -#include "mozilla/dom/nsCSPService.h" +#include "mozilla/glean/GleanMetrics.h" #include "mozilla/StoragePrincipalHelper.h" #include "nsContentSecurityUtils.h" @@ -29,7 +28,6 @@ #include "nsIEffectiveTLDService.h" #include "nsIHttpHeaderVisitor.h" #include "nsINetworkInterceptController.h" -#include "nsINSSErrorsService.h" #include "nsIStringBundle.h" #include "nsIStreamListenerTee.h" #include "nsISeekableStream.h" @@ -40,7 +38,6 @@ #include "nsMimeTypes.h" #include "nsNetCID.h" #include "nsNetUtil.h" -#include "nsIURL.h" #include "nsIStreamTransportService.h" #include "prnetdb.h" #include "nsEscape.h" @@ -63,11 +60,8 @@ #include "mozilla/BasePrincipal.h" #include "mozilla/DebugOnly.h" #include "mozilla/PerfStats.h" -#include "mozilla/Preferences.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/Components.h" -#include "mozilla/StaticPrefs_browser.h" -#include "mozilla/StaticPrefs_fission.h" #include "mozilla/StaticPrefs_network.h" #include "mozilla/StaticPrefs_privacy.h" #include "mozilla/StaticPrefs_security.h" @@ -93,15 +87,12 @@ #include "nsIStreamConverterService.h" #include "nsISiteSecurityService.h" #include "nsString.h" -#include "CacheObserver.h" #include "mozilla/dom/PerformanceStorage.h" #include "mozilla/dom/ReferrerInfo.h" #include "mozilla/Telemetry.h" #include "AlternateServices.h" #include "NetworkMarker.h" #include "nsIHttpPushListener.h" -#include "nsIX509Cert.h" -#include "ScopedNSSTypes.h" #include "nsIDNSRecord.h" #include "mozilla/dom/Document.h" #include "nsICompressConvStats.h" @@ -116,14 +107,11 @@ #include "CacheStorageService.h" #include "HttpChannelParent.h" #include "HttpTransactionParent.h" -#include "ParentChannelListener.h" #include "ThirdPartyUtil.h" #include "InterceptedHttpChannel.h" #include "../../cache2/CacheFileUtils.h" -#include "nsIMultiplexInputStream.h" #include "nsINetworkLinkService.h" #include "mozilla/ContentBlockingAllowList.h" -#include "mozilla/dom/Promise.h" #include "mozilla/dom/ServiceWorkerUtils.h" #include "mozilla/dom/nsHTTPSOnlyStreamListener.h" #include "mozilla/dom/nsHTTPSOnlyUtils.h" @@ -134,7 +122,6 @@ #include "mozilla/net/UrlClassifierFeatureFactory.h" #include "HttpTrafficAnalyzer.h" #include "mozilla/net/SocketProcessParent.h" -#include "js/Conversions.h" #include "mozilla/dom/SecFetch.h" #include "mozilla/net/TRRService.h" #include "nsUnknownDecoder.h" @@ -560,7 +547,8 @@ nsresult nsHttpChannel::OnBeforeConnect() { } rv = sss->IsSecureURI(mURI, originAttributes, &isSecureURI); NS_ENSURE_SUCCESS(rv, rv); - // Save that on the loadInfo so it can later be consumed by SecurityInfo.jsm + // Save that on the loadInfo so it can later be consumed by + // SecurityInfo.sys.mjs mLoadInfo->SetHstsStatus(isSecureURI); RefPtr<mozilla::dom::BrowsingContext> bc; @@ -797,6 +785,19 @@ nsresult nsHttpChannel::ContinueOnBeforeConnect(bool aShouldUpgrade, mConnectionInfo->SetAnonymousAllowClientCert( (mLoadFlags & LOAD_ANONYMOUS_ALLOW_CLIENT_CERT) != 0); + if (mWebTransportSessionEventListener) { + nsTArray<RefPtr<nsIWebTransportHash>> aServerCertHashes; + nsresult rv; + nsCOMPtr<WebTransportConnectionSettings> wtconSettings = + do_QueryInterface(mWebTransportSessionEventListener, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + wtconSettings->GetServerCertificateHashes(aServerCertHashes); + gHttpHandler->ConnMgr()->StoreServerCertHashes( + mConnectionInfo, gHttpHandler->IsHttp2Excluded(mConnectionInfo), + !Http3Allowed(), std::move(aServerCertHashes)); + } + // notify "http-on-before-connect" observers gHttpHandler->OnBeforeConnect(this); @@ -2277,8 +2278,10 @@ nsresult nsHttpChannel::ContinueProcessResponse1() { // Given a successful connection, process any STS or PKP data that's // relevant. - DebugOnly<nsresult> rv = ProcessSecurityHeaders(); - MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load."); + nsresult rv = ProcessSecurityHeaders(); + if (NS_FAILED(rv)) { + NS_WARNING("ProcessSTSHeader failed, continuing load."); + } if ((httpStatus < 500) && (httpStatus != 421)) { ProcessAltService(); @@ -2400,7 +2403,6 @@ nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) { rv = ProcessNormal(); } break; - case 300: case 301: case 302: case 307: @@ -6443,6 +6445,16 @@ nsresult nsHttpChannel::BeginConnect() { connInfo = new nsHttpConnectionInfo(host, port, "h3"_ns, mUsername, proxyInfo, originAttributes, isHttps, true, true); + bool dedicated = true; + nsresult rv; + nsCOMPtr<WebTransportConnectionSettings> wtconSettings = + do_QueryInterface(mWebTransportSessionEventListener, &rv); + NS_ENSURE_SUCCESS(rv, rv); + wtconSettings->GetDedicated(&dedicated); + if (dedicated) { + connInfo->SetWebTransportId( + gHttpHandler->ConnMgr()->GenerateNewWebTransportId()); + } } else { connInfo = new nsHttpConnectionInfo(host, port, ""_ns, mUsername, proxyInfo, originAttributes, isHttps); @@ -6954,7 +6966,54 @@ nsHttpChannel::OnProxyAvailable(nsICancelable* request, nsIChannel* channel, // request was canceled. We just failover to DIRECT when proxy resolution // fails (failure can mean that the PAC URL could not be loaded). - if (NS_SUCCEEDED(status)) mProxyInfo = pi; + if (NS_SUCCEEDED(status)) { + mProxyInfo = pi; + + if (mProxyInfo) { + nsAutoCStringN<8> type; + mProxyInfo->GetType(type); + uint32_t flags = 0; + mProxyInfo->GetFlags(&flags); + + if (type.EqualsLiteral("socks")) { + if (flags & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) { + glean::networking::proxy_info_type + .EnumGet(glean::networking::ProxyInfoTypeLabel::eSocks5h) + .Add(1); + } else { + glean::networking::proxy_info_type + .EnumGet(glean::networking::ProxyInfoTypeLabel::eSocks5) + .Add(1); + } + } else if (type.EqualsLiteral("socks4")) { + if (flags & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) { + glean::networking::proxy_info_type + .EnumGet(glean::networking::ProxyInfoTypeLabel::eSocks4a) + .Add(1); + } else { + glean::networking::proxy_info_type + .EnumGet(glean::networking::ProxyInfoTypeLabel::eSocks4) + .Add(1); + } + } else if (type.EqualsLiteral("http")) { + glean::networking::proxy_info_type + .EnumGet(glean::networking::ProxyInfoTypeLabel::eHttp) + .Add(1); + } else if (type.EqualsLiteral("https")) { + glean::networking::proxy_info_type + .EnumGet(glean::networking::ProxyInfoTypeLabel::eHttps) + .Add(1); + } else if (type.EqualsLiteral("direct")) { + glean::networking::proxy_info_type + .EnumGet(glean::networking::ProxyInfoTypeLabel::eDirect) + .Add(1); + } else { + glean::networking::proxy_info_type + .EnumGet(glean::networking::ProxyInfoTypeLabel::eUnknown) + .Add(1); + } + } + } if (!gHttpHandler->Active()) { LOG( @@ -8203,6 +8262,10 @@ nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet, } mAuthRetryPending = false; } + + // notify "http-on-before-stop-request" observers + gHttpHandler->OnBeforeStopRequest(this); + if (mListener) { LOG(("nsHttpChannel %p calling OnStopRequest\n", this)); MOZ_ASSERT(LoadOnStartRequestCalled(), @@ -8219,7 +8282,7 @@ nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet, mRedirectChannel = nullptr; - // notify "http-on-stop-connect" observers + // notify "http-on-stop-request" observers gHttpHandler->OnStopRequest(this); RemoveAsNonTailRequest(); @@ -9342,8 +9405,8 @@ nsresult nsHttpChannel::OnPush(uint32_t aPushedStreamId, const nsACString& aUrl, // static bool nsHttpChannel::IsRedirectStatus(uint32_t status) { // 305 disabled as a security measure (see bug 187996). - return status == 300 || status == 301 || status == 302 || status == 303 || - status == 307 || status == 308; + return status == 301 || status == 302 || status == 303 || status == 307 || + status == 308; } void nsHttpChannel::SetCouldBeSynthesized() { diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index 023c06f3f3..cd41cd65bc 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -742,7 +742,7 @@ void nsHttpConnection::Close(nsresult reason, bool aIsShutdown) { gHttpHandler->ClearHostMapping(mConnInfo); } if (mTlsHandshaker->EarlyDataWasAvailable() && - SecurityErrorThatMayNeedRestart(reason)) { + PossibleZeroRTTRetryError(reason)) { gHttpHandler->Exclude0RttTcp(mConnInfo); } diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.cpp b/netwerk/protocol/http/nsHttpConnectionInfo.cpp index e91128d85a..21c3e75b54 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp +++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp @@ -258,6 +258,12 @@ void nsHttpConnectionInfo::BuildHashKey() { } } + if (mWebTransportId) { + mHashKey.AppendLiteral("{wId"); + mHashKey.AppendInt(mWebTransportId, 16); + mHashKey.AppendLiteral("}"); + } + nsAutoCString originAttributes; mOriginAttributes.CreateSuffix(originAttributes); mHashKey.Append(originAttributes); @@ -326,6 +332,7 @@ already_AddRefed<nsHttpConnectionInfo> nsHttpConnectionInfo::Clone() const { clone->SetIPv6Disabled(GetIPv6Disabled()); clone->SetHasIPHintAddress(HasIPHintAddress()); clone->SetEchConfig(GetEchConfig()); + clone->SetWebTransportId(GetWebTransportId()); MOZ_ASSERT(clone->Equals(this)); return clone.forget(); @@ -418,6 +425,7 @@ void nsHttpConnectionInfo::SerializeHttpConnectionInfo( aArgs.hasIPHintAddress() = aInfo->HasIPHintAddress(); aArgs.echConfig() = aInfo->GetEchConfig(); aArgs.webTransport() = aInfo->GetWebTransport(); + aArgs.webTransportId() = aInfo->GetWebTransportId(); if (!aInfo->ProxyInfo()) { return; @@ -448,6 +456,8 @@ nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs( aInfoArgs.routedHost(), aInfoArgs.routedPort(), aInfoArgs.isHttp3(), aInfoArgs.webTransport()); } + // Transfer Webtransport ids + cinfo->SetWebTransportId(aInfoArgs.webTransportId()); // Make sure the anonymous, insecure-scheme, and private flags are transferred cinfo->SetAnonymous(aInfoArgs.anonymous()); @@ -542,6 +552,13 @@ void nsHttpConnectionInfo::SetWebTransport(bool aWebTransport) { } } +void nsHttpConnectionInfo::SetWebTransportId(uint64_t id) { + if (mWebTransportId != id) { + mWebTransportId = id; + RebuildHashKey(); + } +} + void nsHttpConnectionInfo::SetTlsFlags(uint32_t aTlsFlags) { mTlsFlags = aTlsFlags; const uint32_t tlsFlagsLength = 8; diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h index 892d795b10..3757b87be7 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.h +++ b/netwerk/protocol/http/nsHttpConnectionInfo.h @@ -222,6 +222,9 @@ class nsHttpConnectionInfo final : public ARefBase { void SetWebTransport(bool aWebTransport); bool GetWebTransport() const { return mWebTransport; } + void SetWebTransportId(uint64_t id); + uint32_t GetWebTransportId() const { return mWebTransportId; }; + const nsCString& GetNPNToken() { return mNPNToken; } const nsCString& GetUsername() { return mUsername; } @@ -307,6 +310,9 @@ class nsHttpConnectionInfo final : public ARefBase { bool mHasIPHintAddress = false; nsCString mEchConfig; + uint64_t mWebTransportId = 0; // current dedicated Id only used for + // Webtransport, zero means not dedicated + // for RefPtr NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsHttpConnectionInfo, override) }; diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index 2e937d0f2a..c7c385a42a 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -3821,6 +3821,62 @@ void nsHttpConnectionMgr::DecrementNumIdleConns() { ConditionallyStopPruneDeadConnectionsTimer(); } +// A structure used to marshall objects necessary for ServerCertificateHashaes +class nsStoreServerCertHashesData : public ARefBase { + public: + nsStoreServerCertHashesData( + nsHttpConnectionInfo* aConnInfo, bool aNoSpdy, bool aNoHttp3, + nsTArray<RefPtr<nsIWebTransportHash>>&& aServerCertHashes) + : mConnInfo(aConnInfo), + mNoSpdy(aNoSpdy), + mNoHttp3(aNoHttp3), + mServerCertHashes(std::move(aServerCertHashes)) {} + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsStoreServerCertHashesData, override) + + RefPtr<nsHttpConnectionInfo> mConnInfo; + bool mNoSpdy; + bool mNoHttp3; + nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes; + + private: + virtual ~nsStoreServerCertHashesData() = default; +}; + +// The connection manager needs to know the hashes used for a WebTransport +// connection authenticated with serverCertHashes +nsresult nsHttpConnectionMgr::StoreServerCertHashes( + nsHttpConnectionInfo* aConnInfo, bool aNoSpdy, bool aNoHttp3, + nsTArray<RefPtr<nsIWebTransportHash>>&& aServerCertHashes) { + RefPtr<nsHttpConnectionInfo> ci = aConnInfo->Clone(); + RefPtr<nsStoreServerCertHashesData> data = new nsStoreServerCertHashesData( + ci, aNoSpdy, aNoHttp3, std::move(aServerCertHashes)); + return PostEvent(&nsHttpConnectionMgr::OnMsgStoreServerCertHashes, 0, data); +} + +void nsHttpConnectionMgr::OnMsgStoreServerCertHashes(int32_t, ARefBase* param) { + MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + + nsStoreServerCertHashesData* data = + static_cast<nsStoreServerCertHashesData*>(param); + + bool isWildcard; + ConnectionEntry* connEnt = GetOrCreateConnectionEntry( + data->mConnInfo, true, data->mNoSpdy, data->mNoHttp3, &isWildcard); + MOZ_ASSERT(!isWildcard, "No webtransport with wildcard"); + connEnt->SetServerCertHashes(std::move(data->mServerCertHashes)); +} + +const nsTArray<RefPtr<nsIWebTransportHash>>* +nsHttpConnectionMgr::GetServerCertHashes(nsHttpConnectionInfo* aConnInfo) { + ConnectionEntry* connEnt = mCT.GetWeak(aConnInfo->HashKey()); + if (!connEnt) { + MOZ_ASSERT(0); + return nullptr; + } + return &connEnt->GetServerCertHashes(); +} + void nsHttpConnectionMgr::CheckTransInPendingQueue(nsHttpTransaction* aTrans) { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED // We only do this check on socket thread. When this function is called on diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h index 2cf4ab7568..3be6b01689 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -60,6 +60,12 @@ class nsHttpConnectionMgr final : public HttpConnectionMgrShell, [[nodiscard]] nsresult CancelTransactions(nsHttpConnectionInfo*, nsresult code); + // The connection manager needs to know the hashes used for a WebTransport + // connection authenticated with serverCertHashes + nsresult StoreServerCertHashes( + nsHttpConnectionInfo* aConnInfo, bool aNoSpdy, bool aNoHttp3, + nsTArray<RefPtr<nsIWebTransportHash>>&& aServerCertHashes); + //------------------------------------------------------------------------- // NOTE: functions below may be called only on the socket thread. //------------------------------------------------------------------------- @@ -143,6 +149,11 @@ class nsHttpConnectionMgr final : public HttpConnectionMgrShell, void NewIdleConnectionAdded(uint32_t timeToLive); void DecrementNumIdleConns(); + const nsTArray<RefPtr<nsIWebTransportHash>>* GetServerCertHashes( + nsHttpConnectionInfo* aConnInfo); + + uint64_t GenerateNewWebTransportId() { return mMaxWebTransportId++; } + private: virtual ~nsHttpConnectionMgr(); @@ -334,6 +345,7 @@ class nsHttpConnectionMgr final : public HttpConnectionMgrShell, void OnMsgPruneNoTraffic(int32_t, ARefBase*); void OnMsgUpdateCurrentBrowserId(int32_t, ARefBase*); void OnMsgClearConnectionHistory(int32_t, ARefBase*); + void OnMsgStoreServerCertHashes(int32_t, ARefBase*); // Total number of active connections in all of the ConnectionEntry objects // that are accessed from mCT connection table. @@ -462,6 +474,10 @@ class nsHttpConnectionMgr final : public HttpConnectionMgrShell, void NotifyConnectionOfBrowserIdChange(uint64_t previousId); void CheckTransInPendingQueue(nsHttpTransaction* aTrans); + + // Used for generating unique IDSs for dedicated connections, currently used + // by WebTransport + Atomic<uint64_t> mMaxWebTransportId{1}; }; } // namespace mozilla::net diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index dcc5307984..4861b45466 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -1397,13 +1397,6 @@ void nsHttpHandler::PrefsChanged(const char* pref) { } } - if (PREF_CHANGED(HTTP_PREF("max_response_header_size"))) { - rv = Preferences::GetInt(HTTP_PREF("max_response_header_size"), &val); - if (NS_SUCCEEDED(rv)) { - mMaxHttpResponseHeaderSize = val; - } - } - if (PREF_CHANGED(HTTP_PREF("throttle.enable"))) { rv = Preferences::GetBool(HTTP_PREF("throttle.enable"), &mThrottleEnabled); if (NS_SUCCEEDED(rv) && mConnMgr) { diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index cf745347ee..2562d0c833 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -375,7 +375,12 @@ class nsHttpHandler final : public nsIHttpProtocolHandler, NotifyObservers(chan, NS_DOCUMENT_ON_MODIFY_REQUEST_TOPIC); } - // Called by the channel before writing a request + // Called by the channel before calling onStopRequest + void OnBeforeStopRequest(nsIHttpChannel* chan) { + NotifyObservers(chan, NS_HTTP_ON_BEFORE_STOP_REQUEST_TOPIC); + } + + // Called by the channel after calling onStopRequest void OnStopRequest(nsIHttpChannel* chan) { NotifyObservers(chan, NS_HTTP_ON_STOP_REQUEST_TOPIC); } @@ -447,10 +452,6 @@ class nsHttpHandler final : public nsIHttpProtocolHandler, return (uint16_t)mHttp3MaxBlockedStreams; } - uint32_t MaxHttpResponseHeaderSize() const { - return mMaxHttpResponseHeaderSize; - } - const nsCString& Http3QlogDir(); float FocusedWindowTransactionRatio() const { @@ -730,9 +731,6 @@ class nsHttpHandler final : public nsIHttpProtocolHandler, nsCString mHttp3QlogDir; - // The max size (in bytes) for received Http response header. - uint32_t mMaxHttpResponseHeaderSize{393216}; - // The ratio for dispatching transactions from the focused window. float mFocusedWindowTransactionRatio{0.9f}; diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 580ea35841..558de8e6ea 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -217,6 +217,13 @@ nsresult nsHttpTransaction::Init( LOG1(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps)); + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + LOG( + ("nsHttpTransaction aborting init because of app" + "shutdown")); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + MOZ_ASSERT(cinfo); MOZ_ASSERT(requestHead); MOZ_ASSERT(target); @@ -1334,7 +1341,7 @@ bool nsHttpTransaction::ShouldRestartOn0RttError(nsresult reason) { "mEarlyDataWasAvailable=%d error=%" PRIx32 "]\n", this, mEarlyDataWasAvailable, static_cast<uint32_t>(reason))); return StaticPrefs::network_http_early_data_disable_on_error() && - mEarlyDataWasAvailable && SecurityErrorThatMayNeedRestart(reason); + mEarlyDataWasAvailable && PossibleZeroRTTRetryError(reason); } static void MaybeRemoveSSLToken(nsITransportSecurityInfo* aSecurityInfo) { @@ -1501,7 +1508,7 @@ void nsHttpTransaction::Close(nsresult reason) { if (reason == psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) || - reason == psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT) || + PossibleZeroRTTRetryError(reason) || (!mReceivedData && ((mRequestHead && mRequestHead->IsSafeMethod()) || !reallySentData || connReused)) || shouldRestartTransactionForHTTPSRR) { @@ -1542,9 +1549,8 @@ void nsHttpTransaction::Close(nsresult reason) { } else if (reason == psm::GetXPCOMFromNSSError( SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA)) { SetRestartReason(TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA); - } else if (reason == - psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) { - SetRestartReason(TRANSACTION_RESTART_PROTOCOL_VERSION_ALERT); + } else if (PossibleZeroRTTRetryError(reason)) { + SetRestartReason(TRANSACTION_RESTART_POSSIBLE_0RTT_ERROR); } // if restarting fails, then we must proceed to close the pipe, // which will notify the channel that the transaction failed. @@ -2520,7 +2526,7 @@ nsresult nsHttpTransaction::ProcessData(char* buf, uint32_t count, mCurrentHttpResponseHeaderSize += bytesConsumed; if (mCurrentHttpResponseHeaderSize > - gHttpHandler->MaxHttpResponseHeaderSize()) { + StaticPrefs::network_http_max_response_header_size()) { LOG(("nsHttpTransaction %p The response header exceeds the limit.\n", this)); return NS_ERROR_FILE_TOO_BIG; diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h index 354fa2c4d2..5256de4cf2 100644 --- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -299,6 +299,7 @@ class nsHttpTransaction final : public nsAHttpTransaction, TRANSACTION_RESTART_HTTP3_FAST_FALLBACK, TRANSACTION_RESTART_OTHERS, TRANSACTION_RESTART_PROTOCOL_VERSION_ALERT, + TRANSACTION_RESTART_POSSIBLE_0RTT_ERROR }; void SetRestartReason(TRANSACTION_RESTART_REASON aReason); diff --git a/netwerk/protocol/http/nsIHttpProtocolHandler.idl b/netwerk/protocol/http/nsIHttpProtocolHandler.idl index d6a6f8a0c1..03d1d6dfe5 100644 --- a/netwerk/protocol/http/nsIHttpProtocolHandler.idl +++ b/netwerk/protocol/http/nsIHttpProtocolHandler.idl @@ -206,6 +206,12 @@ interface nsIHttpProtocolHandler : nsIProxiedProtocolHandler #define NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC "http-on-examine-cached-response" /** + * This topic is notified for every http channel before calling + * OnStopRequest on its listener. + */ +#define NS_HTTP_ON_BEFORE_STOP_REQUEST_TOPIC "http-on-before-stop-request" + +/** * This topic is notified for every http channel right after it called * OnStopRequest on its listener, regardless whether it was finished * successfully, failed or has been canceled. |