Skip to content
Snippets Groups Projects
Verified Commit 1c30f9a7 authored by Pier Angelo Vendrame's avatar Pier Angelo Vendrame :jack_o_lantern:
Browse files

fixup! Bug 40933: Add tor-launcher functionality

Hashing the control port password is needed only on the process, so move
this function to TorProcess.
parent 22c518f3
Branches
Tags
1 merge request!734Bug 42030: Rebased 13.0 alpha onto Firefox 115.2.0esr
/* 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/. */
import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";
import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";
import { Subprocess } from "resource://gre/modules/Subprocess.sys.mjs";
......@@ -32,14 +36,14 @@ export class TorProcess {
// Have we ever made a connection on the control port?
#didConnectToTorControlPort = false;
onExit = () => {};
onExit = exitCode => {};
onRestart = () => {};
constructor(controlSettings, socksSettings) {
if (
controlSettings &&
!controlSettings.password &&
!controlSettings.cookieFile
!controlSettings.cookieFilePath
) {
throw new Error("Unauthenticated control port is not supported");
}
......@@ -176,8 +180,10 @@ export class TorProcess {
if (!watched) {
return;
}
let processExitCode;
try {
const { exitCode } = await watched.wait();
processExitCode = exitCode;
if (watched !== this.#subprocess) {
logger.debug(`A Tor process exited with code ${exitCode}.`);
......@@ -191,11 +197,11 @@ export class TorProcess {
}
if (watched === this.#subprocess) {
this.#processExitedUnexpectedly();
this.#processExitedUnexpectedly(processExitCode);
}
}
#processExitedUnexpectedly() {
#processExitedUnexpectedly(exitCode) {
this.#subprocess = null;
this.#status = TorProcessStatus.Exited;
......@@ -231,13 +237,9 @@ export class TorProcess {
cancelBtnLabel
);
if (restart) {
this.start().then(() => {
if (this.onRestart) {
this.onRestart();
}
});
} else if (this.onExit) {
this.onExit();
this.start().then(this.onRestart);
} else {
this.onExit(exitCode);
}
}
......@@ -317,11 +319,14 @@ export class TorProcess {
}
if (this.#controlSettings.password) {
this.#args.push("HashedControlPassword", this.#controlSettings.password);
this.#args.push(
"HashedControlPassword",
this.#hashPassword(this.#controlSettings.password)
);
}
if (this.#controlSettings.cookieFile) {
if (this.#controlSettings.cookieFilePath) {
this.#args.push("CookieAuthentication", "1");
this.#args.push("CookieAuthFile", this.#controlSettings.cookieFile);
this.#args.push("CookieAuthFile", this.#controlSettings.cookieFilePath);
}
}
......@@ -351,4 +356,59 @@ export class TorProcess {
this.#args.push("+__SocksPort", socksPortArg);
}
}
// Based on Vidalia's TorSettings::hashPassword().
#hashPassword(aHexPassword) {
if (!aHexPassword) {
return null;
}
// Generate a random, 8 byte salt value.
const salt = Array.from(crypto.getRandomValues(new Uint8Array(8)));
// Convert hex-encoded password to an array of bytes.
const password = [];
for (let i = 0; i < aHexPassword.length; i += 2) {
password.push(parseInt(aHexPassword.substring(i, i + 2), 16));
}
// Run through the S2K algorithm and convert to a string.
const toHex = v => v.toString(16).padStart(2, "0");
const arrayToHex = aArray => aArray.map(toHex).join("");
const kCodedCount = 96;
const hashVal = this.#cryptoSecretToKey(password, salt, kCodedCount);
return "16:" + arrayToHex(salt) + toHex(kCodedCount) + arrayToHex(hashVal);
}
// #cryptoSecretToKey() is similar to Vidalia's crypto_secret_to_key().
// It generates and returns a hash of aPassword by following the iterated
// and salted S2K algorithm (see RFC 2440 section 3.6.1.3).
// See also https://gitlab.torproject.org/tpo/core/torspec/-/blob/main/control-spec.txt#L3824.
// Returns an array of bytes.
#cryptoSecretToKey(aPassword, aSalt, aCodedCount) {
const inputArray = aSalt.concat(aPassword);
// Subtle crypto only has the final digest, and does not allow incremental
// updates.
const hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
Ci.nsICryptoHash
);
hasher.init(hasher.SHA1);
const kEXPBIAS = 6;
let count = (16 + (aCodedCount & 15)) << ((aCodedCount >> 4) + kEXPBIAS);
while (count > 0) {
if (count > inputArray.length) {
hasher.update(inputArray, inputArray.length);
count -= inputArray.length;
} else {
const finalArray = inputArray.slice(0, count);
hasher.update(finalArray, finalArray.length);
count = 0;
}
}
return hasher
.finish(false)
.split("")
.map(b => b.charCodeAt(0));
}
}
......@@ -289,9 +289,8 @@ export const TorProtocolService = {
// are also used in torbutton.
// Returns Tor password string or null if an error occurs.
torGetPassword(aPleaseHash) {
const pw = this._controlPassword;
return aPleaseHash ? this._hashPassword(pw) : pw;
torGetPassword() {
return this._controlPassword;
},
torGetControlIPCFile() {
......@@ -308,7 +307,7 @@ export const TorProtocolService = {
get torControlPortInfo() {
const info = {
password: this._hashPassword(this._controlPassword),
password: this._controlPassword,
};
if (this._controlIPCFile) {
info.ipcFile = this._controlIPCFile?.clone();
......@@ -692,38 +691,6 @@ export const TorProtocolService = {
return pwd;
},
// Based on Vidalia's TorSettings::hashPassword().
_hashPassword(aHexPassword) {
if (!aHexPassword) {
return null;
}
// Generate a random, 8 byte salt value.
const salt = Array.from(crypto.getRandomValues(new Uint8Array(8)));
// Convert hex-encoded password to an array of bytes.
const password = [];
for (let i = 0; i < aHexPassword.length; i += 2) {
password.push(parseInt(aHexPassword.substring(i, i + 2), 16));
}
// Run through the S2K algorithm and convert to a string.
const kCodedCount = 96;
const hashVal = this._cryptoSecretToKey(password, salt, kCodedCount);
if (!hashVal) {
logger.error("_cryptoSecretToKey() failed");
return null;
}
const arrayToHex = aArray =>
aArray.map(item => this._toHex(item, 2)).join("");
let rv = "16:";
rv += arrayToHex(salt);
rv += this._toHex(kCodedCount, 2);
rv += arrayToHex(hashVal);
return rv;
},
// Returns -1 upon failure.
_cryptoRandInt(aMax) {
// Based on tor's crypto_rand_int().
......@@ -742,43 +709,6 @@ export const TorProtocolService = {
return val % aMax;
},
// _cryptoSecretToKey() is similar to Vidalia's crypto_secret_to_key().
// It generates and returns a hash of aPassword by following the iterated
// and salted S2K algorithm (see RFC 2440 section 3.6.1.3).
// Returns an array of bytes.
_cryptoSecretToKey(aPassword, aSalt, aCodedCount) {
if (!aPassword || !aSalt) {
return null;
}
const inputArray = aSalt.concat(aPassword);
// Subtle crypto only has the final digest, and does not allow incremental
// updates. Also, it is async, so we should hash and keep the hash in a
// variable if we wanted to switch to getters.
// So, keeping this implementation should be okay for now.
const hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
Ci.nsICryptoHash
);
hasher.init(hasher.SHA1);
const kEXPBIAS = 6;
let count = (16 + (aCodedCount & 15)) << ((aCodedCount >> 4) + kEXPBIAS);
while (count > 0) {
if (count > inputArray.length) {
hasher.update(inputArray, inputArray.length);
count -= inputArray.length;
} else {
const finalArray = inputArray.slice(0, count);
hasher.update(finalArray, finalArray.length);
count = 0;
}
}
return hasher
.finish(false)
.split("")
.map(b => b.charCodeAt(0));
},
_toHex(aValue, aMinLen) {
return aValue.toString(16).padStart(aMinLen, "0");
},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment