Bug 1896625 - Support creating TLS tunnel for WebrtcTCPSocket, a=dmeehan

Differential Revision: https://phabricator.services.mozilla.com/D217025
This commit is contained in:
Kershaw Chang 2024-07-22 13:09:51 +00:00
parent 7119cc28b9
commit 8d1f864c40
14 changed files with 128 additions and 33 deletions

View file

@ -4907,8 +4907,9 @@ std::unique_ptr<NrSocketProxyConfig> PeerConnectionImpl::GetProxyConfig()
TabId id = browserChild->GetTabId();
nsCOMPtr<nsILoadInfo> loadInfo =
new net::LoadInfo(doc->NodePrincipal(), doc->NodePrincipal(), doc, 0,
nsIContentPolicy::TYPE_INVALID);
new net::LoadInfo(doc->NodePrincipal(), doc->NodePrincipal(), doc,
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA);
net::LoadInfoArgs loadInfoArgs;
MOZ_ALWAYS_SUCCEEDS(

View file

@ -19,6 +19,7 @@
#include "nsString.h"
#include "mozilla/dom/ContentProcessManager.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/StaticPrefs_media.h"
#include "nsISocketTransportService.h"
#include "nsICancelable.h"
#include "nsSocketTransportService2.h"
@ -420,14 +421,14 @@ nsresult WebrtcTCPSocket::OpenWithHttpProxy() {
// introduce new behavior. can't follow redirects on connect anyway.
nsCOMPtr<nsIChannel> localChannel;
rv = ioService->NewChannelFromURIWithProxyFlags(
mURI, nullptr,
// Proxy flags are overridden by SetConnectOnly()
0, loadInfo->LoadingNode(), loadInfo->GetLoadingPrincipal(),
mURI, mURI,
nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
loadInfo->LoadingNode(), loadInfo->GetLoadingPrincipal(),
loadInfo->TriggeringPrincipal(),
nsILoadInfo::SEC_COOKIES_OMIT |
// We need this flag to allow loads from any origin since this channel
// is being used to CONNECT to an HTTP proxy.
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
// We need this flag to allow loads from any origin since this channel
// is being used to CONNECT to an HTTP proxy.
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA,
getter_AddRefs(localChannel));
if (NS_FAILED(rv)) {
@ -448,6 +449,9 @@ nsresult WebrtcTCPSocket::OpenWithHttpProxy() {
return NS_ERROR_FAILURE;
}
rv = localChannel->SetLoadInfo(loadInfo);
NS_ENSURE_SUCCESS(rv, rv);
httpChannel->SetNotificationCallbacks(this);
// don't block webrtc proxy setup with other requests
@ -465,7 +469,8 @@ nsresult WebrtcTCPSocket::OpenWithHttpProxy() {
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = httpChannel->SetConnectOnly();
rv = httpChannel->SetConnectOnly(
mozilla::StaticPrefs::media_webrtc_tls_tunnel_for_all_proxy());
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}

View file

@ -11350,6 +11350,11 @@
#endif
mirror: always
- name: media.webrtc.tls_tunnel_for_all_proxy
type: bool
value: true
mirror: always
# If true, then we require explicit approval from the embedding app (ex. Fenix)
# on GeckoView to know if we can allow audible, inaudible media or both kinds
# of media to autoplay.

View file

@ -3757,7 +3757,7 @@ HttpBaseChannel::GetOnlyConnect(bool* aOnlyConnect) {
}
NS_IMETHODIMP
HttpBaseChannel::SetConnectOnly() {
HttpBaseChannel::SetConnectOnly(bool aTlsTunnel) {
ENSURE_CALLED_BEFORE_CONNECT();
if (!mUpgradeProtocolCallback) {
@ -3765,6 +3765,9 @@ HttpBaseChannel::SetConnectOnly() {
}
mCaps |= NS_HTTP_CONNECT_ONLY;
if (aTlsTunnel) {
mCaps |= NS_HTTP_TLS_TUNNEL;
}
mProxyResolveFlags = nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL;
return SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_ANONYMOUS |

View file

@ -273,7 +273,7 @@ class HttpBaseChannel : public nsHashPropertyBag,
NS_IMETHOD GetRemoteAddress(nsACString& addr) override;
NS_IMETHOD GetRemotePort(int32_t* port) override;
NS_IMETHOD GetOnlyConnect(bool* aOnlyConnect) override;
NS_IMETHOD SetConnectOnly() override;
NS_IMETHOD SetConnectOnly(bool aTlsTunnel) override;
NS_IMETHOD GetAllowSpdy(bool* aAllowSpdy) override;
NS_IMETHOD SetAllowSpdy(bool aAllowSpdy) override;
NS_IMETHOD GetAllowHttp3(bool* aAllowHttp3) override;

View file

@ -360,6 +360,7 @@ NS_INTERFACE_MAP_BEGIN(TLSTransportLayer)
NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
NS_INTERFACE_MAP_ENTRY_CONCRETE(TLSTransportLayer)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransport)
NS_INTERFACE_MAP_END
TLSTransportLayer::TLSTransportLayer(nsISocketTransport* aTransport,

View file

@ -1,3 +1,4 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=4 sw=2 sts=2 et cin: */
/* This Source Code Form is subject to the terms of the Mozilla Public

View file

@ -186,6 +186,10 @@ extern const nsCString kHttp3Versions[];
// When set, disallow to connect to a HTTP/2 proxy.
#define NS_HTTP_DISALLOW_HTTP2_PROXY (1 << 28)
// When set, setup TLS tunnel even when HTTP proxy is used.
// Need to be used together with NS_HTTP_CONNECT_ONLY
#define NS_HTTP_TLS_TUNNEL (1 << 29)
#define NS_HTTP_TRR_FLAGS_FROM_MODE(x) ((static_cast<uint32_t>(x) & 3) << 19)
#define NS_HTTP_TRR_MODE_FROM_FLAGS(x) \

View file

@ -1067,25 +1067,26 @@ void nsHttpConnection::HandleTunnelResponse(uint16_t responseStatus,
*reset = true;
}
nsresult rv;
// CONNECT only flag doesn't do the tls setup. https here only
// ensures a proxy tunnel was used not that tls is setup.
if (isHttps) {
if (!onlyConnect) {
if (mConnInfo->UsingHttpsProxy()) {
LOG(("%p new TLSFilterTransaction %s %d\n", this, mConnInfo->Origin(),
mConnInfo->OriginPort()));
SetupSecondaryTLS();
}
bool skipSSL = false;
if (mConnInfo->UsingHttpsProxy() ||
mTransactionCaps & NS_HTTP_TLS_TUNNEL) {
LOG(("%p SetupSecondaryTLS %s %d\n", this, mConnInfo->Origin(),
mConnInfo->OriginPort()));
SetupSecondaryTLS();
} else if (onlyConnect) {
MOZ_ASSERT(mConnInfo->UsingOnlyHttpProxy(), "Must be a HTTP proxy");
// We have CONNECT only flag and a HTTP proxy is used here, so we can
// just skip setting up SSL. We have to mark this as complete to finish
// the transaction and be upgraded.
mTlsHandshaker->SetNPNComplete();
skipSSL = true;
}
if (!skipSSL) {
rv = mTlsHandshaker->InitSSLParams(false, true);
LOG(("InitSSLParams [rv=%" PRIx32 "]\n", static_cast<uint32_t>(rv)));
} else {
// We have an https protocol but the CONNECT only flag was
// specified. The consumer only wants a raw socket to the
// proxy. We have to mark this as complete to finish the
// transaction and be upgraded. OnSocketReadable() uses this
// to detect an inactive tunnel and blocks completion.
mTlsHandshaker->SetNPNComplete();
}
}
rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
@ -1612,14 +1613,15 @@ nsresult nsHttpConnection::OnSocketWritable() {
return NS_ERROR_FAILURE;
}
if (mState == HttpConnectionState::REQUEST) {
if (mState == HttpConnectionState::REQUEST &&
mTlsHandshaker->EnsureNPNComplete()) {
// Don't need to check this each write attempt since it is only
// updated after OnSocketWritable completes.
// We've already done primary tls (if needed) and sent our CONNECT.
// If we're doing a CONNECT only request there's no need to write
// the http transaction or do the SSL handshake here.
LOG(("return ok because proxy connect successful\n"));
return NS_OK;
// the http transaction.
LOG(("return NS_BASE_STREAM_CLOSED to make transaction closed\n"));
return NS_BASE_STREAM_CLOSED;
}
}

View file

@ -236,6 +236,9 @@ class nsHttpConnectionInfo final : public ARefBase {
// Returns true when proxying over HTTP or HTTPS
bool UsingHttpProxy() const { return mUsingHttpProxy || mUsingHttpsProxy; }
// Returns true when only proxying over HTTP
bool UsingOnlyHttpProxy() const { return mUsingHttpProxy; }
// Returns true when proxying over HTTPS
bool UsingHttpsProxy() const { return mUsingHttpsProxy; }

View file

@ -211,8 +211,11 @@ interface nsIHttpChannelInternal : nsISupports
*
* Proxy resolve flags are set with RESOLVE_PREFER_HTTPS_PROXY and
* RESOLVE_ALWAYS_TUNNEL.
*
* @param tlsTunnel
* When true, always setup TLS tunnel
*/
[must_use] void setConnectOnly();
[must_use] void setConnectOnly(in boolean tlsTunnel);
/**
* True iff the channel is CONNECT only.

View file

@ -229,7 +229,7 @@ function makeChan(url) {
var internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
internal.HTTPUpgrade(ALPN, upgradeListener);
internal.setConnectOnly();
internal.setConnectOnly(false);
return chan;
}

View file

@ -0,0 +1,62 @@
/* 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/. */
"use strict";
/* import-globals-from head_cache.js */
/* import-globals-from head_cookies.js */
/* import-globals-from head_channels.js */
/* import-globals-from head_servers.js */
const { TestUtils } = ChromeUtils.importESModule(
"resource://testing-common/TestUtils.sys.mjs"
);
function makeChan(uri) {
let chan = NetUtil.newChannel({
uri,
loadUsingSystemPrincipal: true,
}).QueryInterface(Ci.nsIHttpChannel);
chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
return chan;
}
let gotTransport = false;
var upgradeListener = {
onTransportAvailable: () => {
gotTransport = true;
},
QueryInterface: ChromeUtils.generateQI(["nsIHttpUpgradeListener"]),
};
add_task(async function test_connect_only_https() {
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
let proxy = new NodeHTTPSProxyServer();
await proxy.start();
let server = new NodeHTTPSServer();
await server.start();
registerCleanupFunction(async () => {
await proxy.stop();
await server.stop();
});
let chan = makeChan(`https://localhost:${server.port()}/test`);
var internal = chan.QueryInterface(Ci.nsIHttpChannelInternal);
internal.HTTPUpgrade("webrtc", upgradeListener);
internal.setConnectOnly(false);
await new Promise(resolve => {
chan.asyncOpen(new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL));
});
await TestUtils.waitForCondition(() => gotTransport);
Assert.ok(gotTransport);
await proxy.stop();
await server.stop();
});

View file

@ -1289,6 +1289,11 @@ skip-if = [
["test_connection_coalescing.js"]
["test_default_uri_bypass.js"]
["test_proxyconnect_https.js"]
skip-if = [
"socketprocess_networking",
]
["test_bug1883496.js"]
skip-if = [
"os != 'android'",