[tbb-commits] [Git][tpo/applications/tor-browser][tor-browser-115.6.0esr-13.5-1] fixup! Bug 40597: Implement TorSettings module
Pier Angelo Vendrame (@pierov)
git at gitlab.torproject.org
Thu Jan 11 15:50:55 UTC 2024
Pier Angelo Vendrame pushed to branch tor-browser-115.6.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
da0f3108 by Pier Angelo Vendrame at 2024-01-09T18:38:42+01:00
fixup! Bug 40597: Implement TorSettings module
Bug 42358: Extract the domain fronting request functionality form MoatRPC.
- - - - -
3 changed files:
- + toolkit/modules/DomainFrontedRequests.sys.mjs
- toolkit/modules/Moat.sys.mjs
- toolkit/modules/moz.build
Changes:
=====================================
toolkit/modules/DomainFrontedRequests.sys.mjs
=====================================
@@ -0,0 +1,525 @@
+/* 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/. */
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
+ Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
+ TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
+ TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+ TorSettings: "resource://gre/modules/TorSettings.sys.mjs",
+});
+
+/**
+ * The meek pluggable transport takes the reflector URL and front domain as
+ * proxy credentials, which can be prepared with this function.
+ *
+ * @param {string} proxyType The proxy type (socks for socks5 or socks4)
+ * @param {string} reflector The URL of the service hosted by the CDN
+ * @param {string} front The domain to use as a front
+ * @returns {string[]} An array containing [username, password]
+ */
+function makeMeekCredentials(proxyType, reflector, front) {
+ // Construct the per-connection arguments.
+ let meekClientEscapedArgs = "";
+
+ // Escape aValue per section 3.5 of the PT specification:
+ // First the "<Key>=<Value>" formatted arguments MUST be escaped,
+ // such that all backslash, equal sign, and semicolon characters
+ // are escaped with a backslash.
+ const escapeArgValue = aValue =>
+ aValue
+ ? aValue
+ .replaceAll("\\", "\\\\")
+ .replaceAll("=", "\\=")
+ .replaceAll(";", "\\;")
+ : "";
+
+ if (reflector) {
+ meekClientEscapedArgs += "url=";
+ meekClientEscapedArgs += escapeArgValue(reflector);
+ }
+
+ if (front) {
+ if (meekClientEscapedArgs.length) {
+ meekClientEscapedArgs += ";";
+ }
+ meekClientEscapedArgs += "front=";
+ meekClientEscapedArgs += escapeArgValue(front);
+ }
+
+ // socks5
+ if (proxyType === "socks") {
+ if (meekClientEscapedArgs.length <= 255) {
+ return [meekClientEscapedArgs, "\x00"];
+ }
+ return [
+ meekClientEscapedArgs.substring(0, 255),
+ meekClientEscapedArgs.substring(255),
+ ];
+ } else if (proxyType === "socks4") {
+ return [meekClientEscapedArgs, undefined];
+ }
+ throw new Error(`Unsupported proxy type ${proxyType}.`);
+}
+
+/**
+ * Subprocess-based implementation to launch and control a PT process.
+ */
+class MeekTransport {
+ // These members are used by consumers to setup the proxy to do requests over
+ // meek. They are passed to newProxyInfoWithAuth.
+ proxyType = null;
+ proxyAddress = null;
+ proxyPort = 0;
+ proxyUsername = null;
+ proxyPassword = null;
+
+ #inited = false;
+ #meekClientProcess = null;
+
+ // launches the meekprocess
+ async init(reflector, front) {
+ // ensure we haven't already init'd
+ if (this.#inited) {
+ throw new Error("MeekTransport: Already initialized");
+ }
+
+ try {
+ // figure out which pluggable transport to use
+ const supportedTransports = ["meek", "meek_lite"];
+ const provider = await lazy.TorProviderBuilder.build();
+ const proxy = (await provider.getPluggableTransports()).find(
+ pt =>
+ pt.type === "exec" &&
+ supportedTransports.some(t => pt.transports.includes(t))
+ );
+ if (!proxy) {
+ throw new Error("No supported transport found.");
+ }
+
+ const meekTransport = proxy.transports.find(t =>
+ supportedTransports.includes(t)
+ );
+ // Convert meek client path to absolute path if necessary
+ const meekWorkDir = lazy.TorLauncherUtil.getTorFile(
+ "pt-startup-dir",
+ false
+ );
+ if (lazy.TorLauncherUtil.isPathRelative(proxy.pathToBinary)) {
+ const meekPath = meekWorkDir.clone();
+ meekPath.appendRelativePath(proxy.pathToBinary);
+ proxy.pathToBinary = meekPath.path;
+ }
+
+ // Setup env and start meek process
+ const ptStateDir = lazy.TorLauncherUtil.getTorFile("tordatadir", false);
+ ptStateDir.append("pt_state"); // Match what tor uses.
+
+ const envAdditions = {
+ TOR_PT_MANAGED_TRANSPORT_VER: "1",
+ TOR_PT_STATE_LOCATION: ptStateDir.path,
+ TOR_PT_EXIT_ON_STDIN_CLOSE: "1",
+ TOR_PT_CLIENT_TRANSPORTS: meekTransport,
+ };
+ if (lazy.TorSettings.proxy.enabled) {
+ envAdditions.TOR_PT_PROXY = lazy.TorSettings.proxy.uri;
+ }
+
+ const opts = {
+ command: proxy.pathToBinary,
+ arguments: proxy.options.split(/s+/),
+ workdir: meekWorkDir.path,
+ environmentAppend: true,
+ environment: envAdditions,
+ stderr: "pipe",
+ };
+
+ // Launch meek client
+ this.#meekClientProcess = await lazy.Subprocess.call(opts);
+
+ // Callback chain for reading stderr
+ const stderrLogger = async () => {
+ while (this.#meekClientProcess) {
+ const errString = await this.#meekClientProcess.stderr.readString();
+ if (errString) {
+ console.log(`MeekTransport: stderr => ${errString}`);
+ }
+ }
+ };
+ stderrLogger();
+
+ // Read pt's stdout until terminal (CMETHODS DONE) is reached
+ // returns array of lines for parsing
+ const getInitLines = async (stdout = "") => {
+ stdout += await this.#meekClientProcess.stdout.readString();
+
+ // look for the final message
+ const CMETHODS_DONE = "CMETHODS DONE";
+ let endIndex = stdout.lastIndexOf(CMETHODS_DONE);
+ if (endIndex !== -1) {
+ endIndex += CMETHODS_DONE.length;
+ return stdout.substring(0, endIndex).split("\n");
+ }
+ return getInitLines(stdout);
+ };
+
+ // read our lines from pt's stdout
+ const meekInitLines = await getInitLines();
+ // tokenize our pt lines
+ const meekInitTokens = meekInitLines.map(line => {
+ const tokens = line.split(" ");
+ return {
+ keyword: tokens[0],
+ args: tokens.slice(1),
+ };
+ });
+
+ // parse our pt tokens
+ for (const { keyword, args } of meekInitTokens) {
+ const argsJoined = args.join(" ");
+ let keywordError = false;
+ switch (keyword) {
+ case "VERSION": {
+ if (args.length !== 1 || args[0] !== "1") {
+ keywordError = true;
+ }
+ break;
+ }
+ case "PROXY": {
+ if (args.length !== 1 || args[0] !== "DONE") {
+ keywordError = true;
+ }
+ break;
+ }
+ case "CMETHOD": {
+ if (args.length !== 3) {
+ keywordError = true;
+ break;
+ }
+ const transport = args[0];
+ const proxyType = args[1];
+ const addrPortString = args[2];
+ const addrPort = addrPortString.split(":");
+
+ if (transport !== meekTransport) {
+ throw new Error(
+ `MeekTransport: Expected ${meekTransport} but found ${transport}`
+ );
+ }
+ if (!["socks4", "socks4a", "socks5"].includes(proxyType)) {
+ throw new Error(
+ `MeekTransport: Invalid proxy type => ${proxyType}`
+ );
+ }
+ if (addrPort.length !== 2) {
+ throw new Error(
+ `MeekTransport: Invalid proxy address => ${addrPortString}`
+ );
+ }
+ const addr = addrPort[0];
+ const port = parseInt(addrPort[1]);
+ if (port < 1 || port > 65535) {
+ throw new Error(`MeekTransport: Invalid proxy port => ${port}`);
+ }
+
+ // convert proxy type to strings used by protocol-proxy-servce
+ this.proxyType = proxyType === "socks5" ? "socks" : "socks4";
+ this.proxyAddress = addr;
+ this.proxyPort = port;
+
+ break;
+ }
+ // terminal
+ case "CMETHODS": {
+ if (args.length !== 1 || args[0] !== "DONE") {
+ keywordError = true;
+ }
+ break;
+ }
+ // errors (all fall through):
+ case "VERSION-ERROR":
+ case "ENV-ERROR":
+ case "PROXY-ERROR":
+ case "CMETHOD-ERROR":
+ throw new Error(`MeekTransport: ${keyword} => '${argsJoined}'`);
+ }
+ if (keywordError) {
+ throw new Error(
+ `MeekTransport: Invalid ${keyword} keyword args => '${argsJoined}'`
+ );
+ }
+ }
+
+ // register callback to cleanup on process exit
+ this.#meekClientProcess.wait().then(exitObj => {
+ this.#meekClientProcess = null;
+ this.uninit();
+ });
+ [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
+ this.proxyType,
+ reflector,
+ front
+ );
+ this.#inited = true;
+ } catch (ex) {
+ if (this.#meekClientProcess) {
+ this.#meekClientProcess.kill();
+ this.#meekClientProcess = null;
+ }
+ throw ex;
+ }
+ }
+
+ async uninit() {
+ this.#inited = false;
+
+ await this.#meekClientProcess?.kill();
+ this.#meekClientProcess = null;
+ this.proxyType = null;
+ this.proxyAddress = null;
+ this.proxyPort = 0;
+ this.proxyUsername = null;
+ this.proxyPassword = null;
+ }
+}
+
+/**
+ * Android implementation of the Meek process.
+ *
+ * GeckoView does not provide the subprocess module, so we have to use the
+ * EventDispatcher, and have a Java handler start and stop the proxy process.
+ */
+class MeekTransportAndroid {
+ // These members are used by consumers to setup the proxy to do requests over
+ // meek. They are passed to newProxyInfoWithAuth.
+ proxyType = null;
+ proxyAddress = null;
+ proxyPort = 0;
+ proxyUsername = null;
+ proxyPassword = null;
+
+ /**
+ * An id for process this instance is linked to.
+ *
+ * Since we do not restrict the transport to be a singleton, we need a handle to
+ * identify the process we want to stop when the transport owner is done.
+ * We use a counter incremented on the Java side for now.
+ *
+ * This number must be a positive integer (i.e., 0 is an invalid handler).
+ *
+ * @type {number}
+ */
+ #id = 0;
+
+ async init(reflector, front) {
+ // ensure we haven't already init'd
+ if (this.#id) {
+ throw new Error("MeekTransport: Already initialized");
+ }
+ const details = await lazy.EventDispatcher.instance.sendRequestForResult({
+ type: "GeckoView:Tor:StartMeek",
+ });
+ this.#id = details.id;
+ this.proxyType = "socks";
+ this.proxyAddress = details.address;
+ this.proxyPort = details.port;
+ [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
+ this.proxyType,
+ reflector,
+ front
+ );
+ }
+
+ async uninit() {
+ lazy.EventDispatcher.instance.sendRequest({
+ type: "GeckoView:Tor:StopMeek",
+ id: this.#id,
+ });
+ this.#id = 0;
+ this.proxyType = null;
+ this.proxyAddress = null;
+ this.proxyPort = 0;
+ this.proxyUsername = null;
+ this.proxyPassword = null;
+ }
+}
+
+/**
+ * Callback object to promisify the XPCOM request.
+ */
+class ResponseListener {
+ #response = "";
+ #responsePromise;
+ #resolve;
+ #reject;
+ constructor() {
+ this.#response = "";
+ // we need this promise here because await nsIHttpChannel::asyncOpen does
+ // not return only once the request is complete, it seems to return
+ // after it begins, so we have to get the result from this listener object.
+ // This promise is only resolved once onStopRequest is called
+ this.#responsePromise = new Promise((resolve, reject) => {
+ this.#resolve = resolve;
+ this.#reject = reject;
+ });
+ }
+
+ // callers wait on this for final response
+ response() {
+ return this.#responsePromise;
+ }
+
+ // noop
+ onStartRequest(request) {}
+
+ // resolve or reject our Promise
+ onStopRequest(request, status) {
+ try {
+ if (!Components.isSuccessCode(status)) {
+ const errorMessage =
+ lazy.TorLauncherUtil.getLocalizedStringForError(status);
+ this.#reject(new Error(errorMessage));
+ }
+ if (request.responseStatus !== 200) {
+ this.#reject(new Error(request.responseStatusText));
+ }
+ } catch (err) {
+ this.#reject(err);
+ }
+ this.#resolve(this.#response);
+ }
+
+ // read response data
+ onDataAvailable(request, stream, offset, length) {
+ const scriptableStream = Cc[
+ "@mozilla.org/scriptableinputstream;1"
+ ].createInstance(Ci.nsIScriptableInputStream);
+ scriptableStream.init(stream);
+ this.#response += scriptableStream.read(length);
+ }
+}
+
+// constructs the json objects and sends the request over moat
+export class DomainFrontRequestBuilder {
+ #inited = false;
+ #meekTransport = null;
+
+ get inited() {
+ return this.#inited;
+ }
+
+ async init(reflector, front) {
+ if (this.#inited) {
+ throw new Error("MoatRPC: Already initialized");
+ }
+
+ const meekTransport =
+ Services.appinfo.OS === "Android"
+ ? new MeekTransportAndroid()
+ : new MeekTransport();
+ await meekTransport.init(reflector, front);
+ this.#meekTransport = meekTransport;
+ this.#inited = true;
+ }
+
+ async uninit() {
+ await this.#meekTransport?.uninit();
+ this.#meekTransport = null;
+ this.#inited = false;
+ }
+
+ buildHttpHandler(uriString) {
+ if (!this.#inited) {
+ throw new Error("MoatRPC: Not initialized");
+ }
+
+ const { proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword } =
+ this.#meekTransport;
+
+ const proxyPS = Cc[
+ "@mozilla.org/network/protocol-proxy-service;1"
+ ].getService(Ci.nsIProtocolProxyService);
+ const flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
+ const noTimeout = 0xffffffff; // UINT32_MAX
+ const proxyInfo = proxyPS.newProxyInfoWithAuth(
+ proxyType,
+ proxyAddress,
+ proxyPort,
+ proxyUsername,
+ proxyPassword,
+ undefined,
+ undefined,
+ flags,
+ noTimeout,
+ undefined
+ );
+
+ const uri = Services.io.newURI(uriString);
+ // There does not seem to be a way to directly create an nsILoadInfo from
+ // JavaScript, so we create a throw away non-proxied channel to get one.
+ const secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+ const loadInfo = Services.io.newChannelFromURI(
+ uri,
+ undefined,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ undefined,
+ secFlags,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ ).loadInfo;
+
+ const httpHandler = Services.io
+ .getProtocolHandler("http")
+ .QueryInterface(Ci.nsIHttpProtocolHandler);
+ const ch = httpHandler
+ .newProxiedChannel(uri, proxyInfo, 0, undefined, loadInfo)
+ .QueryInterface(Ci.nsIHttpChannel);
+
+ // remove all headers except for 'Host"
+ const headers = [];
+ ch.visitRequestHeaders({
+ visitHeader: (key, val) => {
+ if (key !== "Host") {
+ headers.push(key);
+ }
+ },
+ });
+ headers.forEach(key => ch.setRequestHeader(key, "", false));
+
+ return ch;
+ }
+
+ /**
+ * Make a POST request with a JSON body.
+ *
+ * @param {string} url The URL to load
+ * @param {object} args The arguments to send to the procedure. It will be
+ * serialized to JSON by this function and then set as POST body
+ * @returns {Promise<object>} A promise with the parsed response
+ */
+ async buildPostRequest(url, args) {
+ const ch = this.buildHttpHandler(url);
+
+ const argsJson = JSON.stringify(args);
+ const inStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ inStream.setData(argsJson, argsJson.length);
+ const upChannel = ch.QueryInterface(Ci.nsIUploadChannel);
+ const contentType = "application/vnd.api+json";
+ upChannel.setUploadStream(inStream, contentType, argsJson.length);
+ ch.requestMethod = "POST";
+
+ // Make request
+ const listener = new ResponseListener();
+ await ch.asyncOpen(listener, ch);
+
+ // wait for response
+ const responseJSON = await listener.response();
+
+ // parse that JSON
+ return JSON.parse(responseJSON);
+ }
+}
=====================================
toolkit/modules/Moat.sys.mjs
=====================================
@@ -10,10 +10,8 @@ import {
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
- Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
- TorLauncherUtil: "resource://gre/modules/TorLauncherUtil.sys.mjs",
- TorProviderBuilder: "resource://gre/modules/TorProviderBuilder.sys.mjs",
+ DomainFrontRequestBuilder:
+ "resource://gre/modules/DomainFrontedRequests.sys.mjs",
});
const TorLauncherPrefs = Object.freeze({
@@ -22,372 +20,9 @@ const TorLauncherPrefs = Object.freeze({
moat_service: "extensions.torlauncher.moat_service",
});
-function makeMeekCredentials(proxyType) {
- // Construct the per-connection arguments.
- let meekClientEscapedArgs = "";
- const meekReflector = Services.prefs.getStringPref(
- TorLauncherPrefs.bridgedb_reflector
- );
-
- // Escape aValue per section 3.5 of the PT specification:
- // First the "<Key>=<Value>" formatted arguments MUST be escaped,
- // such that all backslash, equal sign, and semicolon characters
- // are escaped with a backslash.
- const escapeArgValue = aValue =>
- aValue
- ? aValue
- .replaceAll("\\", "\\\\")
- .replaceAll("=", "\\=")
- .replaceAll(";", "\\;")
- : "";
-
- if (meekReflector) {
- meekClientEscapedArgs += "url=";
- meekClientEscapedArgs += escapeArgValue(meekReflector);
- }
- const meekFront = Services.prefs.getStringPref(
- TorLauncherPrefs.bridgedb_front
- );
- if (meekFront) {
- if (meekClientEscapedArgs.length) {
- meekClientEscapedArgs += ";";
- }
- meekClientEscapedArgs += "front=";
- meekClientEscapedArgs += escapeArgValue(meekFront);
- }
-
- // socks5
- if (proxyType === "socks") {
- if (meekClientEscapedArgs.length <= 255) {
- return [meekClientEscapedArgs, "\x00"];
- } else {
- return [
- meekClientEscapedArgs.substring(0, 255),
- meekClientEscapedArgs.substring(255),
- ];
- }
- // socks4
- } else {
- return [meekClientEscapedArgs, undefined];
- }
-}
-
-//
-// Launches and controls the PT process lifetime
-//
-class MeekTransport {
- // These members are used by consumers to setup the proxy to do requests over
- // meek. They are passed to newProxyInfoWithAuth.
- proxyType = null;
- proxyAddress = null;
- proxyPort = 0;
- proxyUsername = null;
- proxyPassword = null;
-
- #inited = false;
- #meekClientProcess = null;
-
- // launches the meekprocess
- async init() {
- // ensure we haven't already init'd
- if (this.#inited) {
- throw new Error("MeekTransport: Already initialized");
- }
-
- try {
- // figure out which pluggable transport to use
- const supportedTransports = ["meek", "meek_lite"];
- const provider = await lazy.TorProviderBuilder.build();
- const proxy = (await provider.getPluggableTransports()).find(
- pt =>
- pt.type === "exec" &&
- supportedTransports.some(t => pt.transports.includes(t))
- );
- if (!proxy) {
- throw new Error("No supported transport found.");
- }
-
- const meekTransport = proxy.transports.find(t =>
- supportedTransports.includes(t)
- );
- // Convert meek client path to absolute path if necessary
- const meekWorkDir = lazy.TorLauncherUtil.getTorFile(
- "pt-startup-dir",
- false
- );
- if (lazy.TorLauncherUtil.isPathRelative(proxy.pathToBinary)) {
- const meekPath = meekWorkDir.clone();
- meekPath.appendRelativePath(proxy.pathToBinary);
- proxy.pathToBinary = meekPath.path;
- }
-
- // Setup env and start meek process
- const ptStateDir = lazy.TorLauncherUtil.getTorFile("tordatadir", false);
- ptStateDir.append("pt_state"); // Match what tor uses.
-
- const envAdditions = {
- TOR_PT_MANAGED_TRANSPORT_VER: "1",
- TOR_PT_STATE_LOCATION: ptStateDir.path,
- TOR_PT_EXIT_ON_STDIN_CLOSE: "1",
- TOR_PT_CLIENT_TRANSPORTS: meekTransport,
- };
- if (TorSettings.proxy.enabled) {
- envAdditions.TOR_PT_PROXY = TorSettings.proxy.uri;
- }
-
- const opts = {
- command: proxy.pathToBinary,
- arguments: proxy.options.split(/s+/),
- workdir: meekWorkDir.path,
- environmentAppend: true,
- environment: envAdditions,
- stderr: "pipe",
- };
-
- // Launch meek client
- this.#meekClientProcess = await lazy.Subprocess.call(opts);
-
- // Callback chain for reading stderr
- const stderrLogger = async () => {
- while (this.#meekClientProcess) {
- const errString = await this.#meekClientProcess.stderr.readString();
- if (errString) {
- console.log(`MeekTransport: stderr => ${errString}`);
- }
- }
- };
- stderrLogger();
-
- // Read pt's stdout until terminal (CMETHODS DONE) is reached
- // returns array of lines for parsing
- const getInitLines = async (stdout = "") => {
- stdout += await this.#meekClientProcess.stdout.readString();
-
- // look for the final message
- const CMETHODS_DONE = "CMETHODS DONE";
- let endIndex = stdout.lastIndexOf(CMETHODS_DONE);
- if (endIndex != -1) {
- endIndex += CMETHODS_DONE.length;
- return stdout.substring(0, endIndex).split("\n");
- }
- return getInitLines(stdout);
- };
-
- // read our lines from pt's stdout
- const meekInitLines = await getInitLines();
- // tokenize our pt lines
- const meekInitTokens = meekInitLines.map(line => {
- const tokens = line.split(" ");
- return {
- keyword: tokens[0],
- args: tokens.slice(1),
- };
- });
-
- // parse our pt tokens
- for (const { keyword, args } of meekInitTokens) {
- const argsJoined = args.join(" ");
- let keywordError = false;
- switch (keyword) {
- case "VERSION": {
- if (args.length != 1 || args[0] !== "1") {
- keywordError = true;
- }
- break;
- }
- case "PROXY": {
- if (args.length != 1 || args[0] !== "DONE") {
- keywordError = true;
- }
- break;
- }
- case "CMETHOD": {
- if (args.length != 3) {
- keywordError = true;
- break;
- }
- const transport = args[0];
- const proxyType = args[1];
- const addrPortString = args[2];
- const addrPort = addrPortString.split(":");
-
- if (transport !== meekTransport) {
- throw new Error(
- `MeekTransport: Expected ${meekTransport} but found ${transport}`
- );
- }
- if (!["socks4", "socks4a", "socks5"].includes(proxyType)) {
- throw new Error(
- `MeekTransport: Invalid proxy type => ${proxyType}`
- );
- }
- if (addrPort.length != 2) {
- throw new Error(
- `MeekTransport: Invalid proxy address => ${addrPortString}`
- );
- }
- const addr = addrPort[0];
- const port = parseInt(addrPort[1]);
- if (port < 1 || port > 65535) {
- throw new Error(`MeekTransport: Invalid proxy port => ${port}`);
- }
-
- // convert proxy type to strings used by protocol-proxy-servce
- this.proxyType = proxyType === "socks5" ? "socks" : "socks4";
- this.proxyAddress = addr;
- this.proxyPort = port;
-
- break;
- }
- // terminal
- case "CMETHODS": {
- if (args.length != 1 || args[0] !== "DONE") {
- keywordError = true;
- }
- break;
- }
- // errors (all fall through):
- case "VERSION-ERROR":
- case "ENV-ERROR":
- case "PROXY-ERROR":
- case "CMETHOD-ERROR":
- throw new Error(`MeekTransport: ${keyword} => '${argsJoined}'`);
- }
- if (keywordError) {
- throw new Error(
- `MeekTransport: Invalid ${keyword} keyword args => '${argsJoined}'`
- );
- }
- }
-
- // register callback to cleanup on process exit
- this.#meekClientProcess.wait().then(exitObj => {
- this.#meekClientProcess = null;
- this.uninit();
- });
- [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
- this.proxyType
- );
- this.#inited = true;
- } catch (ex) {
- if (this.#meekClientProcess) {
- this.#meekClientProcess.kill();
- this.#meekClientProcess = null;
- }
- throw ex;
- }
- }
-
- async uninit() {
- this.#inited = false;
-
- await this.#meekClientProcess?.kill();
- this.#meekClientProcess = null;
- this.proxyType = null;
- this.proxyAddress = null;
- this.proxyPort = 0;
- this.proxyUsername = null;
- this.proxyPassword = null;
- }
-}
-
-class MeekTransportAndroid {
- // These members are used by consumers to setup the proxy to do requests over
- // meek. They are passed to newProxyInfoWithAuth.
- proxyType = null;
- proxyAddress = null;
- proxyPort = 0;
- proxyUsername = null;
- proxyPassword = null;
-
- #id = 0;
-
- async init() {
- // ensure we haven't already init'd
- if (this.#id) {
- throw new Error("MeekTransport: Already initialized");
- }
- const details = await lazy.EventDispatcher.instance.sendRequestForResult({
- type: "GeckoView:Tor:StartMeek",
- });
- this.#id = details.id;
- this.proxyType = "socks";
- this.proxyAddress = details.address;
- this.proxyPort = details.port;
- [this.proxyUsername, this.proxyPassword] = makeMeekCredentials(
- this.proxyType
- );
- }
-
- async uninit() {
- lazy.EventDispatcher.instance.sendRequest({
- type: "GeckoView:Tor:StopMeek",
- id: this.#id,
- });
- this.#id = 0;
- this.proxyType = null;
- this.proxyAddress = null;
- this.proxyPort = 0;
- this.proxyUsername = null;
- this.proxyPassword = null;
- }
-}
-
-//
-// Callback object with a cached promise for the returned Moat data
-//
-class MoatResponseListener {
- #response = "";
- #responsePromise;
- #resolve;
- #reject;
- constructor() {
- this.#response = "";
- // we need this promise here because await nsIHttpChannel::asyncOpen does
- // not return only once the request is complete, it seems to return
- // after it begins, so we have to get the result from this listener object.
- // This promise is only resolved once onStopRequest is called
- this.#responsePromise = new Promise((resolve, reject) => {
- this.#resolve = resolve;
- this.#reject = reject;
- });
- }
-
- // callers wait on this for final response
- response() {
- return this.#responsePromise;
- }
-
- // noop
- onStartRequest(request) {}
-
- // resolve or reject our Promise
- onStopRequest(request, status) {
- try {
- if (!Components.isSuccessCode(status)) {
- const errorMessage =
- lazy.TorLauncherUtil.getLocalizedStringForError(status);
- this.#reject(new Error(errorMessage));
- }
- if (request.responseStatus != 200) {
- this.#reject(new Error(request.responseStatusText));
- }
- } catch (err) {
- this.#reject(err);
- }
- this.#resolve(this.#response);
- }
-
- // read response data
- onDataAvailable(request, stream, offset, length) {
- const scriptableStream = Cc[
- "@mozilla.org/scriptableinputstream;1"
- ].createInstance(Ci.nsIScriptableInputStream);
- scriptableStream.init(stream);
- this.#response += scriptableStream.read(length);
- }
-}
-
+/**
+ * A special response listener that collects the received headers.
+ */
class InternetTestResponseListener {
#promise;
#resolve;
@@ -436,129 +71,45 @@ class InternetTestResponseListener {
}
}
-// constructs the json objects and sends the request over moat
+/**
+ * Constructs JSON objects and sends requests over Moat.
+ * The documentation about the JSON schemas to use are available at
+ * https://gitlab.torproject.org/tpo/anti-censorship/rdsys/-/blob/main/doc/moat.md.
+ */
export class MoatRPC {
- #inited = false;
- #meekTransport = null;
-
- get inited() {
- return this.#inited;
- }
+ #requestBuilder = null;
async init() {
- if (this.#inited) {
- throw new Error("MoatRPC: Already initialized");
+ if (this.#requestBuilder !== null) {
+ return;
}
- const meekTransport =
- Services.appinfo.OS === "Android"
- ? new MeekTransportAndroid()
- : new MeekTransport();
- await meekTransport.init();
- this.#meekTransport = meekTransport;
- this.#inited = true;
+ const reflector = Services.prefs.getStringPref(
+ TorLauncherPrefs.bridgedb_reflector
+ );
+ const front = Services.prefs.getStringPref(TorLauncherPrefs.bridgedb_front);
+ const builder = new lazy.DomainFrontRequestBuilder();
+ await builder.init(reflector, front);
+ this.#requestBuilder = builder;
}
async uninit() {
- await this.#meekTransport?.uninit();
- this.#meekTransport = null;
- this.#inited = false;
- }
-
- #makeHttpHandler(uriString) {
- if (!this.#inited) {
- throw new Error("MoatRPC: Not initialized");
- }
-
- const { proxyType, proxyAddress, proxyPort, proxyUsername, proxyPassword } =
- this.#meekTransport;
-
- const proxyPS = Cc[
- "@mozilla.org/network/protocol-proxy-service;1"
- ].getService(Ci.nsIProtocolProxyService);
- const flags = Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST;
- const noTimeout = 0xffffffff; // UINT32_MAX
- const proxyInfo = proxyPS.newProxyInfoWithAuth(
- proxyType,
- proxyAddress,
- proxyPort,
- proxyUsername,
- proxyPassword,
- undefined,
- undefined,
- flags,
- noTimeout,
- undefined
- );
-
- const uri = Services.io.newURI(uriString);
- // There does not seem to be a way to directly create an nsILoadInfo from
- // JavaScript, so we create a throw away non-proxied channel to get one.
- const secFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
- const loadInfo = Services.io.newChannelFromURI(
- uri,
- undefined,
- Services.scriptSecurityManager.getSystemPrincipal(),
- undefined,
- secFlags,
- Ci.nsIContentPolicy.TYPE_OTHER
- ).loadInfo;
-
- const httpHandler = Services.io
- .getProtocolHandler("http")
- .QueryInterface(Ci.nsIHttpProtocolHandler);
- const ch = httpHandler
- .newProxiedChannel(uri, proxyInfo, 0, undefined, loadInfo)
- .QueryInterface(Ci.nsIHttpChannel);
-
- // remove all headers except for 'Host"
- const headers = [];
- ch.visitRequestHeaders({
- visitHeader: (key, val) => {
- if (key !== "Host") {
- headers.push(key);
- }
- },
- });
- headers.forEach(key => ch.setRequestHeader(key, "", false));
-
- return ch;
+ await this.#requestBuilder?.uninit();
+ this.#requestBuilder = null;
}
async #makeRequest(procedure, args) {
const procedureURIString = `${Services.prefs.getStringPref(
TorLauncherPrefs.moat_service
)}/${procedure}`;
- const ch = this.#makeHttpHandler(procedureURIString);
-
- // Arrange for the POST data to be sent.
- const argsJson = JSON.stringify(args);
-
- const inStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
- Ci.nsIStringInputStream
- );
- inStream.setData(argsJson, argsJson.length);
- const upChannel = ch.QueryInterface(Ci.nsIUploadChannel);
- const contentType = "application/vnd.api+json";
- upChannel.setUploadStream(inStream, contentType, argsJson.length);
- ch.requestMethod = "POST";
-
- // Make request
- const listener = new MoatResponseListener();
- await ch.asyncOpen(listener, ch);
-
- // wait for response
- const responseJSON = await listener.response();
-
- // parse that JSON
- return JSON.parse(responseJSON);
+ return this.#requestBuilder.buildPostRequest(procedureURIString, args);
}
async testInternetConnection() {
const uri = `${Services.prefs.getStringPref(
TorLauncherPrefs.moat_service
)}/circumvention/countries`;
- const ch = this.#makeHttpHandler(uri);
+ const ch = this.#requestBuilder.buildHttpHandler(uri);
ch.requestMethod = "HEAD";
const listener = new InternetTestResponseListener();
@@ -566,10 +117,6 @@ export class MoatRPC {
return listener.status;
}
- //
- // Moat APIs
- //
-
// Receive a CAPTCHA challenge, takes the following parameters:
// - transports: array of transport strings available to us eg: ["obfs4", "meek"]
//
=====================================
toolkit/modules/moz.build
=====================================
@@ -166,6 +166,7 @@ EXTRA_JS_MODULES += [
"DateTimePickerPanel.sys.mjs",
"DeferredTask.sys.mjs",
"Deprecated.sys.mjs",
+ "DomainFrontedRequests.sys.mjs",
"DragDropFilter.sys.mjs",
"E10SUtils.sys.mjs",
"EventEmitter.sys.mjs",
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/da0f3108b990fc6bd5d38eff08c2e7abec02d3db
--
View it on GitLab: https://gitlab.torproject.org/tpo/applications/tor-browser/-/commit/da0f3108b990fc6bd5d38eff08c2e7abec02d3db
You're receiving this email because of your account on gitlab.torproject.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.torproject.org/pipermail/tbb-commits/attachments/20240111/affcf933/attachment-0001.htm>
More information about the tbb-commits
mailing list