/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* 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/. */ // HttpLog.h should generally be included first #include "HttpLog.h" // Log on level :5, instead of default :4. #undef LOG #define LOG(args) LOG5(args) #undef LOG_ENABLED #define LOG_ENABLED() LOG5_ENABLED() #include "nsHttpConnectionInfo.h" #include "mozilla/net/DNS.h" #include "mozilla/net/NeckoChannelParams.h" #include "nsComponentManagerUtils.h" #include "nsICryptoHash.h" #include "nsIDNSByTypeRecord.h" #include "nsIProtocolProxyService.h" #include "nsHttpHandler.h" #include "nsNetCID.h" #include "nsProxyInfo.h" #include "prnetdb.h" static nsresult SHA256(const char* aPlainText, nsAutoCString& aResult) { nsresult rv; nsCOMPtr hasher = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); if (NS_FAILED(rv)) { LOG(("nsHttpDigestAuth: no crypto hash!\n")); return rv; } rv = hasher->Init(nsICryptoHash::SHA256); NS_ENSURE_SUCCESS(rv, rv); rv = hasher->Update((unsigned char*)aPlainText, strlen(aPlainText)); NS_ENSURE_SUCCESS(rv, rv); return hasher->Finish(false, aResult); } namespace mozilla { namespace net { nsHttpConnectionInfo::nsHttpConnectionInfo( const nsACString& originHost, int32_t originPort, const nsACString& npnToken, const nsACString& username, nsProxyInfo* proxyInfo, const OriginAttributes& originAttributes, bool endToEndSSL, bool aIsHttp3, bool aWebTransport) : mRoutedPort(443), mLessThanTls13(false) { Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes, endToEndSSL, aIsHttp3, aWebTransport); } nsHttpConnectionInfo::nsHttpConnectionInfo( const nsACString& originHost, int32_t originPort, const nsACString& npnToken, const nsACString& username, nsProxyInfo* proxyInfo, const OriginAttributes& originAttributes, const nsACString& routedHost, int32_t routedPort, bool aIsHttp3, bool aWebTransport) : mLessThanTls13(false) { mEndToEndSSL = true; // so DefaultPort() works mRoutedPort = routedPort == -1 ? DefaultPort() : routedPort; if (!originHost.Equals(routedHost) || (originPort != routedPort) || aIsHttp3) { mRoutedHost = routedHost; } Init(originHost, originPort, npnToken, username, proxyInfo, originAttributes, true, aIsHttp3, aWebTransport); } void nsHttpConnectionInfo::Init(const nsACString& host, int32_t port, const nsACString& npnToken, const nsACString& username, nsProxyInfo* proxyInfo, const OriginAttributes& originAttributes, bool e2eSSL, bool aIsHttp3, bool aWebTransport) { LOG(("Init nsHttpConnectionInfo @%p\n", this)); MOZ_RELEASE_ASSERT(!aWebTransport || aIsHttp3); mUsername = username; mProxyInfo = proxyInfo; mEndToEndSSL = e2eSSL; mUsingConnect = false; mNPNToken = npnToken; mIsHttp3 = aIsHttp3; mWebTransport = aWebTransport; mOriginAttributes = originAttributes; mTlsFlags = 0x0; mIsTrrServiceChannel = false; mTRRMode = nsIRequest::TRR_DEFAULT_MODE; mIPv4Disabled = false; mIPv6Disabled = false; mHasIPHintAddress = false; mUsingHttpsProxy = (proxyInfo && proxyInfo->IsHTTPS()); mUsingHttpProxy = mUsingHttpsProxy || (proxyInfo && proxyInfo->IsHTTP()); if (mUsingHttpProxy) { mUsingConnect = mEndToEndSSL; // SSL always uses CONNECT uint32_t resolveFlags = 0; if (NS_SUCCEEDED(mProxyInfo->GetResolveFlags(&resolveFlags)) && resolveFlags & nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL) { mUsingConnect = true; } } SetOriginServer(host, port); } void nsHttpConnectionInfo::BuildHashKey() { // // build hash key: // // the hash key uniquely identifies the connection type. two connections // are "equal" if they end up talking the same protocol to the same server // and are both used for anonymous or non-anonymous connection only; // anonymity of the connection is setup later from nsHttpChannel::AsyncOpen // where we know we use anonymous connection (LOAD_ANONYMOUS load flag) // const char* keyHost; int32_t keyPort; if (mUsingHttpProxy && !mUsingConnect) { keyHost = ProxyHost(); keyPort = ProxyPort(); } else { keyHost = Origin(); keyPort = OriginPort(); } // The hashkey has 9 fields followed by host connection info // byte 0 is P/T/. {P,T} for Plaintext/TLS Proxy over HTTP // byte 1 is S/. S is for end to end ssl such as https:// uris // byte 2 is A/. A is for an anonymous channel (no cookies, etc..) // byte 3 is P/. P is for a private browising channel // byte 4 is I/. I is for insecure scheme on TLS for http:// uris // byte 5 is X/. X is for disallow_spdy flag // byte 6 is C/. C is for be Conservative // byte 7 is B/. B is for allowing client certs on an anonymous channel // byte 8 is F/. F is for indicating a fallback connection // byte 9 is W/. W is for indicating a webTransport // Note: when adding/removing fields from this list which do not have // corresponding data fields on the object itself, you may also need to // modify RebuildHashKey. const auto keyTemplate = std::string(UnderlyingIndex(HashKeyIndex::End), '.') + std::string("[tlsflags0x00000000]"); mHashKey.Assign(keyTemplate.c_str()); mHashKey.Append(keyHost); mHashKey.Append(':'); mHashKey.AppendInt(keyPort); if (!mUsername.IsEmpty()) { mHashKey.Append('['); mHashKey.Append(mUsername); mHashKey.Append(']'); } if (mUsingHttpsProxy) { SetHashCharAt('T', HashKeyIndex::Proxy); } else if (mUsingHttpProxy) { SetHashCharAt('P', HashKeyIndex::Proxy); } if (mEndToEndSSL) { SetHashCharAt('S', HashKeyIndex::EndToEndSSL); } if (mWebTransport) { SetHashCharAt('W', HashKeyIndex::WebTransport); } // NOTE: for transparent proxies (e.g., SOCKS) we need to encode the proxy // info in the hash key (this ensures that we will continue to speak the // right protocol even if our proxy preferences change). // // NOTE: for SSL tunnels add the proxy information to the cache key. // We cannot use the proxy as the host parameter (as we do for non SSL) // because this is a single host tunnel, but we need to include the proxy // information so that a change in proxy config will mean this connection // is not reused // NOTE: Adding the username and the password provides a means to isolate // keep-alive to the URL bar domain as well: If the username is the URL bar // domain, keep-alive connections are not reused by resources bound to // different URL bar domains as the respective hash keys are not matching. if ((!mUsingHttpProxy && ProxyHost()) || (mUsingHttpProxy && mUsingConnect)) { mHashKey.AppendLiteral(" ("); mHashKey.Append(ProxyType()); mHashKey.Append(':'); mHashKey.Append(ProxyHost()); mHashKey.Append(':'); mHashKey.AppendInt(ProxyPort()); mHashKey.Append(')'); mHashKey.Append('['); mHashKey.Append(ProxyUsername()); mHashKey.Append(':'); const char* password = ProxyPassword(); if (strlen(password) > 0) { nsAutoCString digestedPassword; nsresult rv = SHA256(password, digestedPassword); if (rv == NS_OK) { mHashKey.Append(digestedPassword); } } mHashKey.Append(']'); } if (!mRoutedHost.IsEmpty()) { mHashKey.AppendLiteral(" '); } if (!mNPNToken.IsEmpty()) { mHashKey.AppendLiteral(" {NPN-TOKEN "); mHashKey.Append(mNPNToken); mHashKey.AppendLiteral("}"); } if (GetTRRMode() != nsIRequest::TRR_DEFAULT_MODE) { // When connecting with another TRR mode, we enforce a separate connection // hashkey so that we also can trigger a fresh DNS resolver that then // doesn't use TRR as the previous connection might have. mHashKey.AppendLiteral("[TRR:"); mHashKey.AppendInt(GetTRRMode()); mHashKey.AppendLiteral("]"); } if (GetIPv4Disabled()) { mHashKey.AppendLiteral("[!v4]"); } if (GetIPv6Disabled()) { mHashKey.AppendLiteral("[!v6]"); } if (mProxyInfo) { const nsCString& connectionIsolationKey = mProxyInfo->ConnectionIsolationKey(); if (!connectionIsolationKey.IsEmpty()) { mHashKey.AppendLiteral("{CIK "); mHashKey.Append(connectionIsolationKey); mHashKey.AppendLiteral("}"); } if (mProxyInfo->Flags() & nsIProxyInfo::TRANSPARENT_PROXY_RESOLVES_HOST) { mHashKey.AppendLiteral("{TPRH}"); } } if (mWebTransportId) { mHashKey.AppendLiteral("{wId"); mHashKey.AppendInt(mWebTransportId, 16); mHashKey.AppendLiteral("}"); } nsAutoCString originAttributes; mOriginAttributes.CreateSuffix(originAttributes); mHashKey.Append(originAttributes); } void nsHttpConnectionInfo::RebuildHashKey() { // Create copies of all properties stored in our hash key. bool isAnonymous = GetAnonymous(); bool isPrivate = GetPrivate(); bool isInsecureScheme = GetInsecureScheme(); bool isNoSpdy = GetNoSpdy(); bool isBeConservative = GetBeConservative(); bool isAnonymousAllowClientCert = GetAnonymousAllowClientCert(); bool isFallback = GetFallbackConnection(); BuildHashKey(); // Restore all of those properties. SetAnonymous(isAnonymous); SetPrivate(isPrivate); SetInsecureScheme(isInsecureScheme); SetNoSpdy(isNoSpdy); SetBeConservative(isBeConservative); SetAnonymousAllowClientCert(isAnonymousAllowClientCert); SetFallbackConnection(isFallback); } void nsHttpConnectionInfo::SetOriginServer(const nsACString& host, int32_t port) { mOrigin = host; mOriginPort = port == -1 ? DefaultPort() : port; // Use BuildHashKey() since this can only be called when constructing an // nsHttpConnectionInfo object. MOZ_DIAGNOSTIC_ASSERT(mHashKey.IsEmpty()); BuildHashKey(); } // Note that this function needs to be synced with // nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs to make sure // nsHttpConnectionInfo can be serialized/deserialized. already_AddRefed nsHttpConnectionInfo::Clone() const { RefPtr clone; if (mRoutedHost.IsEmpty()) { clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername, mProxyInfo, mOriginAttributes, mEndToEndSSL, mIsHttp3, mWebTransport); } else { MOZ_ASSERT(mEndToEndSSL); clone = new nsHttpConnectionInfo(mOrigin, mOriginPort, mNPNToken, mUsername, mProxyInfo, mOriginAttributes, mRoutedHost, mRoutedPort, mIsHttp3, mWebTransport); } // Make sure the anonymous, insecure-scheme, and private flags are transferred clone->SetAnonymous(GetAnonymous()); clone->SetPrivate(GetPrivate()); clone->SetInsecureScheme(GetInsecureScheme()); clone->SetNoSpdy(GetNoSpdy()); clone->SetBeConservative(GetBeConservative()); clone->SetAnonymousAllowClientCert(GetAnonymousAllowClientCert()); clone->SetFallbackConnection(GetFallbackConnection()); clone->SetTlsFlags(GetTlsFlags()); clone->SetIsTrrServiceChannel(GetIsTrrServiceChannel()); clone->SetTRRMode(GetTRRMode()); clone->SetIPv4Disabled(GetIPv4Disabled()); clone->SetIPv6Disabled(GetIPv6Disabled()); clone->SetHasIPHintAddress(HasIPHintAddress()); clone->SetEchConfig(GetEchConfig()); clone->SetWebTransportId(GetWebTransportId()); MOZ_ASSERT(clone->Equals(this)); return clone.forget(); } already_AddRefed nsHttpConnectionInfo::CloneAndAdoptHTTPSSVCRecord( nsISVCBRecord* aRecord) const { MOZ_ASSERT(aRecord); // Get the domain name of this HTTPS RR. This name will be assigned to // mRoutedHost in the new connection info. nsAutoCString name; aRecord->GetName(name); // Try to get the port and Alpn. If this record has SvcParamKeyPort defined, // the new port will be used as mRoutedPort. Maybe port = aRecord->GetPort(); Maybe> alpn = aRecord->GetAlpn(); // Let the new conn info learn h3 will be used. bool isHttp3 = alpn ? mozilla::net::IsHttp3(std::get<1>(*alpn)) : false; LOG(("HTTPSSVC: use new routed host (%s) and new npnToken (%s)", name.get(), alpn ? std::get<0>(*alpn).get() : "None")); RefPtr clone; if (name.IsEmpty()) { clone = new nsHttpConnectionInfo( mOrigin, mOriginPort, alpn ? std::get<0>(*alpn) : EmptyCString(), mUsername, mProxyInfo, mOriginAttributes, mEndToEndSSL, isHttp3); } else { MOZ_ASSERT(mEndToEndSSL); clone = new nsHttpConnectionInfo( mOrigin, mOriginPort, alpn ? std::get<0>(*alpn) : EmptyCString(), mUsername, mProxyInfo, mOriginAttributes, name, port ? *port : mOriginPort, isHttp3, mWebTransport); } // Make sure the anonymous, insecure-scheme, and private flags are transferred clone->SetAnonymous(GetAnonymous()); clone->SetPrivate(GetPrivate()); clone->SetInsecureScheme(GetInsecureScheme()); clone->SetNoSpdy(GetNoSpdy()); clone->SetBeConservative(GetBeConservative()); clone->SetAnonymousAllowClientCert(GetAnonymousAllowClientCert()); clone->SetFallbackConnection(GetFallbackConnection()); clone->SetTlsFlags(GetTlsFlags()); clone->SetIsTrrServiceChannel(GetIsTrrServiceChannel()); clone->SetTRRMode(GetTRRMode()); clone->SetIPv4Disabled(GetIPv4Disabled()); clone->SetIPv6Disabled(GetIPv6Disabled()); bool hasIPHint = false; Unused << aRecord->GetHasIPHintAddress(&hasIPHint); if (hasIPHint) { clone->SetHasIPHintAddress(hasIPHint); } nsAutoCString echConfig; Unused << aRecord->GetEchConfig(echConfig); clone->SetEchConfig(echConfig); return clone.forget(); } /* static */ void nsHttpConnectionInfo::SerializeHttpConnectionInfo( nsHttpConnectionInfo* aInfo, HttpConnectionInfoCloneArgs& aArgs) { aArgs.host() = aInfo->GetOrigin(); aArgs.port() = aInfo->OriginPort(); aArgs.npnToken() = aInfo->GetNPNToken(); aArgs.username() = aInfo->GetUsername(); aArgs.originAttributes() = aInfo->GetOriginAttributes(); aArgs.endToEndSSL() = aInfo->EndToEndSSL(); aArgs.routedHost() = aInfo->GetRoutedHost(); aArgs.routedPort() = aInfo->RoutedPort(); aArgs.anonymous() = aInfo->GetAnonymous(); aArgs.aPrivate() = aInfo->GetPrivate(); aArgs.insecureScheme() = aInfo->GetInsecureScheme(); aArgs.noSpdy() = aInfo->GetNoSpdy(); aArgs.beConservative() = aInfo->GetBeConservative(); aArgs.anonymousAllowClientCert() = aInfo->GetAnonymousAllowClientCert(); aArgs.tlsFlags() = aInfo->GetTlsFlags(); aArgs.isTrrServiceChannel() = aInfo->GetTRRMode(); aArgs.trrMode() = aInfo->GetTRRMode(); aArgs.isIPv4Disabled() = aInfo->GetIPv4Disabled(); aArgs.isIPv6Disabled() = aInfo->GetIPv6Disabled(); aArgs.isHttp3() = aInfo->IsHttp3(); aArgs.hasIPHintAddress() = aInfo->HasIPHintAddress(); aArgs.echConfig() = aInfo->GetEchConfig(); aArgs.webTransport() = aInfo->GetWebTransport(); aArgs.webTransportId() = aInfo->GetWebTransportId(); if (!aInfo->ProxyInfo()) { return; } nsTArray proxyInfoArray; nsProxyInfo::SerializeProxyInfo(aInfo->ProxyInfo(), proxyInfoArray); aArgs.proxyInfo() = std::move(proxyInfoArray); } // This function needs to be synced with nsHttpConnectionInfo::Clone. /* static */ already_AddRefed nsHttpConnectionInfo::DeserializeHttpConnectionInfoCloneArgs( const HttpConnectionInfoCloneArgs& aInfoArgs) { nsProxyInfo* pi = nsProxyInfo::DeserializeProxyInfo(aInfoArgs.proxyInfo()); RefPtr cinfo; if (aInfoArgs.routedHost().IsEmpty()) { cinfo = new nsHttpConnectionInfo( aInfoArgs.host(), aInfoArgs.port(), aInfoArgs.npnToken(), aInfoArgs.username(), pi, aInfoArgs.originAttributes(), aInfoArgs.endToEndSSL(), aInfoArgs.isHttp3(), aInfoArgs.webTransport()); } else { MOZ_ASSERT(aInfoArgs.endToEndSSL()); cinfo = new nsHttpConnectionInfo( aInfoArgs.host(), aInfoArgs.port(), aInfoArgs.npnToken(), aInfoArgs.username(), pi, aInfoArgs.originAttributes(), 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()); cinfo->SetPrivate(aInfoArgs.aPrivate()); cinfo->SetInsecureScheme(aInfoArgs.insecureScheme()); cinfo->SetNoSpdy(aInfoArgs.noSpdy()); cinfo->SetBeConservative(aInfoArgs.beConservative()); cinfo->SetAnonymousAllowClientCert(aInfoArgs.anonymousAllowClientCert()); cinfo->SetFallbackConnection(aInfoArgs.fallbackConnection()); cinfo->SetTlsFlags(aInfoArgs.tlsFlags()); cinfo->SetIsTrrServiceChannel(aInfoArgs.isTrrServiceChannel()); cinfo->SetTRRMode(static_cast(aInfoArgs.trrMode())); cinfo->SetIPv4Disabled(aInfoArgs.isIPv4Disabled()); cinfo->SetIPv6Disabled(aInfoArgs.isIPv6Disabled()); cinfo->SetHasIPHintAddress(aInfoArgs.hasIPHintAddress()); cinfo->SetEchConfig(aInfoArgs.echConfig()); return cinfo.forget(); } void nsHttpConnectionInfo::CloneAsDirectRoute(nsHttpConnectionInfo** outCI) { // Explicitly use an empty npnToken when |mIsHttp3| is true, since we want to // create a non-http3 connection info. RefPtr clone = new nsHttpConnectionInfo( mOrigin, mOriginPort, (mRoutedHost.IsEmpty() && !mIsHttp3) ? mNPNToken : ""_ns, mUsername, mProxyInfo, mOriginAttributes, mEndToEndSSL, false, mWebTransport); // Make sure the anonymous, insecure-scheme, and private flags are transferred clone->SetAnonymous(GetAnonymous()); clone->SetPrivate(GetPrivate()); clone->SetInsecureScheme(GetInsecureScheme()); clone->SetNoSpdy(GetNoSpdy()); clone->SetBeConservative(GetBeConservative()); clone->SetAnonymousAllowClientCert(GetAnonymousAllowClientCert()); clone->SetFallbackConnection(GetFallbackConnection()); clone->SetTlsFlags(GetTlsFlags()); clone->SetIsTrrServiceChannel(GetIsTrrServiceChannel()); clone->SetTRRMode(GetTRRMode()); clone->SetIPv4Disabled(GetIPv4Disabled()); clone->SetIPv6Disabled(GetIPv6Disabled()); clone->SetHasIPHintAddress(HasIPHintAddress()); clone->SetEchConfig(GetEchConfig()); clone.forget(outCI); } nsresult nsHttpConnectionInfo::CreateWildCard(nsHttpConnectionInfo** outParam) { // T???mozilla.org:443 (https:proxy.ducksong.com:3128) [specifc form] // TS??*:0 (https:proxy.ducksong.com:3128) [wildcard form] if (!mUsingHttpsProxy) { MOZ_ASSERT(false); return NS_ERROR_NOT_IMPLEMENTED; } RefPtr clone; clone = new nsHttpConnectionInfo("*"_ns, 0, mNPNToken, mUsername, mProxyInfo, mOriginAttributes, true, mIsHttp3, mWebTransport); // Make sure the anonymous and private flags are transferred! clone->SetAnonymous(GetAnonymous()); clone->SetPrivate(GetPrivate()); clone.forget(outParam); return NS_OK; } void nsHttpConnectionInfo::SetTRRMode(nsIRequest::TRRMode aTRRMode) { if (mTRRMode != aTRRMode) { mTRRMode = aTRRMode; RebuildHashKey(); } } void nsHttpConnectionInfo::SetIPv4Disabled(bool aNoIPv4) { if (mIPv4Disabled != aNoIPv4) { mIPv4Disabled = aNoIPv4; RebuildHashKey(); } } void nsHttpConnectionInfo::SetIPv6Disabled(bool aNoIPv6) { if (mIPv6Disabled != aNoIPv6) { mIPv6Disabled = aNoIPv6; RebuildHashKey(); } } void nsHttpConnectionInfo::SetWebTransport(bool aWebTransport) { if (mWebTransport != aWebTransport) { mWebTransport = aWebTransport; RebuildHashKey(); } } 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; const uint32_t tlsFlagsIndex = UnderlyingIndex(HashKeyIndex::End) + strlen("[tlsflags0x"); mHashKey.Replace(tlsFlagsIndex, tlsFlagsLength, nsPrintfCString("%08x", mTlsFlags)); } bool nsHttpConnectionInfo::UsingProxy() { if (!mProxyInfo) return false; return !mProxyInfo->IsDirect(); } bool nsHttpConnectionInfo::HostIsLocalIPLiteral() const { NetAddr netAddr; // If the host/proxy host is not an IP address literal, return false. nsAutoCString host(ProxyHost() ? ProxyHost() : Origin()); if (NS_FAILED(netAddr.InitFromString(host))) { return false; } return netAddr.IsIPAddrLocal(); } } // namespace net } // namespace mozilla