зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1873263 - WebTransport: Fix serverCertificateHashes Implementation r=kershaw,necko-reviewers,keeler
The current serverCertificateHashes implementation does not follow the WebTransport specification, that introduced serverCertificateHashes as a tool to replace certificate chain verification. Instead it introduced the hashes as an additional check. This patch moves the check to the Http3Session object and modifies the connection manager' hashes to prevent crossSite certificate poisoning. It is - as the WebTransport Implementation in Firefox - currently limited to http3 only. However, since the hashes live on the ConnectionEntries, it should be possible to extend this in the future. Differential Revision: https://phabricator.services.mozilla.com/D197857
This commit is contained in:
Родитель
0b3f87d615
Коммит
ca85c42741
|
@ -97,14 +97,15 @@ void WebTransportParent::Create(
|
|||
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
|
||||
"WebTransport AsyncConnect",
|
||||
[self = RefPtr{this}, uri = std::move(uri),
|
||||
dedicated = true /* aDedicated, see BUG 1915735.*/,
|
||||
nsServerCertHashes = std::move(nsServerCertHashes),
|
||||
principal = RefPtr{aPrincipal},
|
||||
flags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
|
||||
clientInfo = aClientInfo] {
|
||||
LOG(("WebTransport %p AsyncConnect", self.get()));
|
||||
if (NS_FAILED(self->mWebTransport->AsyncConnectWithClient(
|
||||
uri, std::move(nsServerCertHashes), principal, flags, self,
|
||||
clientInfo))) {
|
||||
uri, dedicated, std::move(nsServerCertHashes), principal, flags,
|
||||
self, clientInfo))) {
|
||||
LOG(("AsyncConnect failure; we should get OnSessionClosed"));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -420,6 +420,7 @@ struct HttpConnectionInfoCloneArgs
|
|||
nsCString topWindowOrigin;
|
||||
bool isHttp3;
|
||||
bool webTransport;
|
||||
uint64_t webTransportId;
|
||||
bool hasIPHintAddress;
|
||||
nsCString echConfig;
|
||||
ProxyInfoCloneArgs[] proxyInfo;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "nsSocketTransportService2.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "sslerr.h"
|
||||
#include "WebTransportCertificateVerifier.h"
|
||||
|
||||
namespace mozilla::net {
|
||||
|
||||
|
@ -2127,6 +2128,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));
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -164,6 +164,7 @@ UNIFIED_SOURCES += [
|
|||
"TlsHandshaker.cpp",
|
||||
"TLSTransportLayer.cpp",
|
||||
"TRRServiceChannel.cpp",
|
||||
"WebTransportCertificateVerifier.cpp",
|
||||
]
|
||||
|
||||
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
|
||||
|
|
|
@ -797,6 +797,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);
|
||||
|
||||
|
@ -6444,6 +6457,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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -27,7 +27,8 @@ namespace mozilla::net {
|
|||
LazyLogModule webTransportLog("nsWebTransport");
|
||||
|
||||
NS_IMPL_ISUPPORTS(WebTransportSessionProxy, WebTransportSessionEventListener,
|
||||
nsIWebTransport, nsIRedirectResultListener, nsIStreamListener,
|
||||
WebTransportConnectionSettings, nsIWebTransport,
|
||||
nsIRedirectResultListener, nsIStreamListener,
|
||||
nsIChannelEventSink, nsIInterfaceRequestor);
|
||||
|
||||
WebTransportSessionProxy::WebTransportSessionProxy()
|
||||
|
@ -63,17 +64,17 @@ WebTransportSessionProxy::~WebTransportSessionProxy() {
|
|||
//-----------------------------------------------------------------------------
|
||||
|
||||
nsresult WebTransportSessionProxy::AsyncConnect(
|
||||
nsIURI* aURI,
|
||||
nsIURI* aURI, bool aDedicated,
|
||||
const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes,
|
||||
nsIPrincipal* aPrincipal, uint32_t aSecurityFlags,
|
||||
WebTransportSessionEventListener* aListener) {
|
||||
return AsyncConnectWithClient(aURI, std::move(aServerCertHashes), aPrincipal,
|
||||
aSecurityFlags, aListener,
|
||||
return AsyncConnectWithClient(aURI, aDedicated, std::move(aServerCertHashes),
|
||||
aPrincipal, aSecurityFlags, aListener,
|
||||
Maybe<dom::ClientInfo>());
|
||||
}
|
||||
|
||||
nsresult WebTransportSessionProxy::AsyncConnectWithClient(
|
||||
nsIURI* aURI,
|
||||
nsIURI* aURI, bool aDedicated,
|
||||
const nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes,
|
||||
nsIPrincipal* aPrincipal, uint32_t aSecurityFlags,
|
||||
WebTransportSessionEventListener* aListener,
|
||||
|
@ -126,7 +127,10 @@ nsresult WebTransportSessionProxy::AsyncConnectWithClient(
|
|||
return NS_ERROR_ABORT;
|
||||
}
|
||||
|
||||
mDedicatedConnection = aDedicated;
|
||||
|
||||
if (!aServerCertHashes.IsEmpty()) {
|
||||
mServerCertHashes.Clear();
|
||||
mServerCertHashes.AppendElements(aServerCertHashes);
|
||||
}
|
||||
|
||||
|
@ -235,6 +239,18 @@ WebTransportSessionProxy::CloseSession(uint32_t status,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP WebTransportSessionProxy::GetDedicated(bool* dedicated) {
|
||||
*dedicated = mDedicatedConnection;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP WebTransportSessionProxy::GetServerCertificateHashes(
|
||||
nsTArray<RefPtr<nsIWebTransportHash>>& aServerCertHashes) {
|
||||
aServerCertHashes.Clear();
|
||||
aServerCertHashes.AppendElements(mServerCertHashes);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void WebTransportSessionProxy::CloseSessionInternalLocked() {
|
||||
MutexAutoLock lock(mMutex);
|
||||
CloseSessionInternal();
|
||||
|
@ -573,8 +589,7 @@ WebTransportSessionProxy::OnStartRequest(nsIRequest* aRequest) {
|
|||
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
|
||||
if (!httpChannel ||
|
||||
NS_FAILED(httpChannel->GetResponseStatus(&status)) ||
|
||||
!(status >= 200 && status < 300) ||
|
||||
!CheckServerCertificateIfNeeded()) {
|
||||
!(status >= 200 && status < 300)) {
|
||||
listener = mListener;
|
||||
mListener = nullptr;
|
||||
mChannel = nullptr;
|
||||
|
@ -990,57 +1005,6 @@ void WebTransportSessionProxy::CallOnSessionClosed() {
|
|||
}
|
||||
}
|
||||
|
||||
bool WebTransportSessionProxy::CheckServerCertificateIfNeeded() {
|
||||
if (mServerCertHashes.IsEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
|
||||
MOZ_ASSERT(httpChannel, "Not a http channel ?");
|
||||
nsCOMPtr<nsITransportSecurityInfo> tsi;
|
||||
httpChannel->GetSecurityInfo(getter_AddRefs(tsi));
|
||||
MOZ_ASSERT(tsi,
|
||||
"We shouln't reach this code before setting the security info.");
|
||||
nsCOMPtr<nsIX509Cert> cert;
|
||||
nsresult rv = tsi->GetServerCert(getter_AddRefs(cert));
|
||||
if (!cert || NS_WARN_IF(NS_FAILED(rv))) return true;
|
||||
nsTArray<uint8_t> certDER;
|
||||
if (NS_FAILED(cert->GetRawDER(certDER))) {
|
||||
return false;
|
||||
}
|
||||
// https://w3c.github.io/webtransport/#compute-a-certificate-hash
|
||||
nsTArray<uint8_t> certHash;
|
||||
if (NS_FAILED(Digest::DigestBuf(SEC_OID_SHA256, certDER.Elements(),
|
||||
certDER.Length(), certHash)) ||
|
||||
certHash.Length() != SHA256_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
auto verifyCertDer = [&certHash](const auto& hash) {
|
||||
return certHash.Length() == hash.Length() &&
|
||||
memcmp(certHash.Elements(), hash.Elements(), certHash.Length()) == 0;
|
||||
};
|
||||
|
||||
// https://w3c.github.io/webtransport/#verify-a-certificate-hash
|
||||
for (const auto& hash : mServerCertHashes) {
|
||||
nsCString algorithm;
|
||||
if (NS_FAILED(hash->GetAlgorithm(algorithm)) || algorithm != "sha-256") {
|
||||
continue;
|
||||
LOG(("Unexpected non-SHA-256 hash"));
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> value;
|
||||
if (NS_FAILED(hash->GetValue(value))) {
|
||||
continue;
|
||||
LOG(("Unexpected corrupted hash"));
|
||||
}
|
||||
|
||||
if (verifyCertDer(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebTransportSessionProxy::ChangeState(
|
||||
WebTransportSessionProxyState newState) {
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
|
|
@ -121,6 +121,7 @@ class WebTransportStreamCallbackWrapper;
|
|||
|
||||
class WebTransportSessionProxy final : public nsIWebTransport,
|
||||
public WebTransportSessionEventListener,
|
||||
public WebTransportConnectionSettings,
|
||||
public nsIStreamListener,
|
||||
public nsIChannelEventSink,
|
||||
public nsIRedirectResultListener,
|
||||
|
@ -129,6 +130,7 @@ class WebTransportSessionProxy final : public nsIWebTransport,
|
|||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIWEBTRANSPORT
|
||||
NS_DECL_WEBTRANSPORTSESSIONEVENTLISTENER
|
||||
NS_DECL_WEBTRANSPORTCONNECTIONSETTINGS
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
NS_DECL_NSICHANNELEVENTSINK
|
||||
|
@ -170,11 +172,9 @@ class WebTransportSessionProxy final : public nsIWebTransport,
|
|||
void OnMaxDatagramSizeInternal(uint64_t aSize);
|
||||
void OnOutgoingDatagramOutComeInternal(
|
||||
uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome);
|
||||
bool CheckServerCertificateIfNeeded();
|
||||
|
||||
nsCOMPtr<nsIChannel> mChannel;
|
||||
nsCOMPtr<nsIChannel> mRedirectChannel;
|
||||
nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes;
|
||||
nsCOMPtr<WebTransportSessionEventListener> mListener MOZ_GUARDED_BY(mMutex);
|
||||
RefPtr<Http3WebTransportSession> mWebTransportSession MOZ_GUARDED_BY(mMutex);
|
||||
uint64_t mSessionId MOZ_GUARDED_BY(mMutex) = UINT64_MAX;
|
||||
|
@ -188,6 +188,8 @@ class WebTransportSessionProxy final : public nsIWebTransport,
|
|||
nsTArray<std::function<void(nsresult)>> mPendingCreateStreamEvents
|
||||
MOZ_GUARDED_BY(mMutex);
|
||||
nsCOMPtr<nsIEventTarget> mTarget MOZ_GUARDED_BY(mMutex);
|
||||
nsTArray<RefPtr<nsIWebTransportHash>> mServerCertHashes;
|
||||
bool mDedicatedConnection; // for WebTranport
|
||||
};
|
||||
|
||||
} // namespace mozilla::net
|
||||
|
|
|
@ -38,12 +38,14 @@ interface nsIWebTransport : nsISupports {
|
|||
|
||||
// When called, perform steps in "Initialization WebTransport over HTTP".
|
||||
void asyncConnect(in nsIURI aURI,
|
||||
in boolean aDedicated,
|
||||
in Array<nsIWebTransportHash> aServerCertHashes,
|
||||
in nsIPrincipal aLoadingPrincipal,
|
||||
in unsigned long aSecurityFlags,
|
||||
in WebTransportSessionEventListener aListener);
|
||||
|
||||
void asyncConnectWithClient(in nsIURI aURI,
|
||||
in boolean aDedicated,
|
||||
in Array<nsIWebTransportHash> aServerCertHashes,
|
||||
in nsIPrincipal aLoadingPrincipal,
|
||||
in unsigned long aSecurityFlags,
|
||||
|
@ -116,6 +118,13 @@ interface WebTransportSessionEventListener : nsISupports {
|
|||
// void onStatsAvailable(in WebTransportStats aStats);
|
||||
};
|
||||
|
||||
[uuid(faad75bd-83c6-420b-9fdb-a70bd70be449)]
|
||||
interface WebTransportConnectionSettings : nsISupports {
|
||||
// WebTransport specific connection information
|
||||
readonly attribute bool dedicated;
|
||||
void getServerCertificateHashes(out Array<nsIWebTransportHash> aServerCertHashes);
|
||||
};
|
||||
|
||||
// This interface is used as a callback when creating an outgoing
|
||||
// unidirectional or bidirectional stream.
|
||||
[scriptable, uuid(c6eeff1d-599b-40a8-9157-c7a40c3d51a2)]
|
||||
|
|
Загрузка…
Ссылка в новой задаче