tor-commits
Threads by month
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
February 2022
- 14 participants
- 1454 discussions
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 13379: Sign our MAR files.
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 38a6f4daaee6cee4cdf4c4ebb8835a498830958e
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Wed Dec 17 16:37:11 2014 -0500
Bug 13379: Sign our MAR files.
Configure with --enable-verify-mar (when updating, require a valid
signature on the MAR file before it is applied).
Use the Tor Browser version instead of the Firefox version inside the
MAR file info block (necessary to prevent downgrade attacks).
Use NSS on all platforms for checking MAR signatures (instead of using
OS-native APIs, which Mozilla does on Mac OS and Windows). So that the
NSS and NSPR libraries the updater depends on can be found at runtime,
we add the firefox directory to the shared library search path on macOS.
On Linux, rpath is used by Mozilla to solve that problem, but that
approach won't work on macOS because the updater executable is copied
during the update process to a location that is under TorBrowser-Data,
and the location of TorBrowser-Data varies.
Also includes the fix for bug 18900.
Bug 19121: reinstate the update.xml hash check
Revert most changes from Mozilla Bug 1373267 "Remove hashFunction and
hashValue attributes from nsIUpdatePatch and code related to these
attributes." Changes to the tests were not reverted; the tests have
been changed significantly and we do not run automated updater tests
for Tor Browser at this time.
Also partial revert of commit f1241db6986e4b54473a1ed870f7584c75d51122.
Revert the nsUpdateService.js changes from Mozilla Bug 862173 "don't
verify mar file hash when using mar signing to verify the mar file
(lessens main thread I/O)."
Changes to the tests were not reverted; the tests have been changed
significantly and we do not run automated updater tests for
Tor Browser at this time.
We kept the addition to the AppConstants API in case other JS code
references it in the future.
---
.mozconfig | 1 +
.mozconfig-asan | 1 +
.mozconfig-mac | 1 +
.mozconfig-mingw | 1 +
modules/libmar/tool/mar.c | 6 +--
modules/libmar/tool/moz.build | 12 +++--
modules/libmar/verify/moz.build | 14 ++---
toolkit/modules/AppConstants.jsm | 7 +++
toolkit/mozapps/update/UpdateService.jsm | 63 +++++++++++++++++++++-
toolkit/mozapps/update/UpdateTelemetry.jsm | 1 +
toolkit/mozapps/update/nsIUpdateService.idl | 11 ++++
.../mozapps/update/updater/updater-common.build | 24 +++++++--
toolkit/mozapps/update/updater/updater.cpp | 25 +++++----
toolkit/xre/moz.build | 3 ++
toolkit/xre/nsUpdateDriver.cpp | 50 +++++++++++++++++
15 files changed, 194 insertions(+), 26 deletions(-)
diff --git a/.mozconfig b/.mozconfig
index 7fe8633a9ef4..7655f628415e 100755
--- a/.mozconfig
+++ b/.mozconfig
@@ -37,3 +37,4 @@ ac_add_options MOZ_TELEMETRY_REPORTING=
ac_add_options --enable-tor-launcher
ac_add_options --with-tor-browser-version=dev-build
ac_add_options --disable-tor-browser-update
+ac_add_options --enable-verify-mar
diff --git a/.mozconfig-asan b/.mozconfig-asan
index 98ea6ac6f3fe..8bee813bfee8 100644
--- a/.mozconfig-asan
+++ b/.mozconfig-asan
@@ -30,6 +30,7 @@ ac_add_options --enable-official-branding
ac_add_options --enable-default-toolkit=cairo-gtk3
ac_add_options --enable-tor-browser-update
+ac_add_options --enable-verify-mar
ac_add_options --disable-strip
ac_add_options --disable-install-strip
diff --git a/.mozconfig-mac b/.mozconfig-mac
index 26e2b6b92fdb..5b4624ef1f67 100644
--- a/.mozconfig-mac
+++ b/.mozconfig-mac
@@ -43,6 +43,7 @@ ac_add_options --disable-debug
ac_add_options --enable-tor-browser-data-outside-app-dir
ac_add_options --enable-tor-browser-update
+ac_add_options --enable-verify-mar
ac_add_options --disable-crashreporter
ac_add_options --disable-webrtc
diff --git a/.mozconfig-mingw b/.mozconfig-mingw
index 3ec6ff18a3e9..ce6ace1dad67 100644
--- a/.mozconfig-mingw
+++ b/.mozconfig-mingw
@@ -15,6 +15,7 @@ ac_add_options --enable-strip
ac_add_options --enable-official-branding
ac_add_options --enable-tor-browser-update
+ac_add_options --enable-verify-mar
ac_add_options --disable-bits-download
# Let's make sure no preference is enabling either Adobe's or Google's CDM.
diff --git a/modules/libmar/tool/mar.c b/modules/libmar/tool/mar.c
index 0bf2cb4bd1d4..ea2b79924914 100644
--- a/modules/libmar/tool/mar.c
+++ b/modules/libmar/tool/mar.c
@@ -65,7 +65,7 @@ static void print_usage() {
"signed_input_archive.mar base_64_encoded_signature_file "
"changed_signed_output.mar\n");
printf("(i) is the index of the certificate to extract\n");
-# if defined(XP_MACOSX) || (defined(XP_WIN) && !defined(MAR_NSS))
+# if (defined(XP_MACOSX) || defined(XP_WIN)) && !defined(MAR_NSS)
printf("Verify a MAR file:\n");
printf(" mar [-C workingDir] -D DERFilePath -v signed_archive.mar\n");
printf(
@@ -149,7 +149,7 @@ int main(int argc, char** argv) {
memset((void*)certBuffers, 0, sizeof(certBuffers));
#endif
#if !defined(NO_SIGN_VERIFY) && \
- ((!defined(MAR_NSS) && defined(XP_WIN)) || defined(XP_MACOSX))
+ (!defined(MAR_NSS) && (defined(XP_WIN) || defined(XP_MACOSX)))
memset(DERFilePaths, 0, sizeof(DERFilePaths));
memset(fileSizes, 0, sizeof(fileSizes));
#endif
@@ -181,7 +181,7 @@ int main(int argc, char** argv) {
argc -= 2;
}
#if !defined(NO_SIGN_VERIFY)
-# if (!defined(MAR_NSS) && defined(XP_WIN)) || defined(XP_MACOSX)
+# if (!defined(MAR_NSS) && (defined(XP_WIN) || defined(XP_MACOSX)))
/* -D DERFilePath, also matches -D[index] DERFilePath
We allow an index for verifying to be symmetric
with the import and export command line arguments. */
diff --git a/modules/libmar/tool/moz.build b/modules/libmar/tool/moz.build
index a6d26c66a668..d6fa1677ddf1 100644
--- a/modules/libmar/tool/moz.build
+++ b/modules/libmar/tool/moz.build
@@ -43,15 +43,21 @@ if CONFIG["MOZ_BUILD_APP"] != "tools/update-packaging":
"verifymar",
]
+ if CONFIG["TOR_BROWSER_UPDATE"]:
+ DEFINES["MAR_NSS"] = True
+
if CONFIG["OS_ARCH"] == "WINNT":
USE_STATIC_LIBS = True
OS_LIBS += [
"ws2_32",
- "crypt32",
- "advapi32",
]
- elif CONFIG["OS_ARCH"] == "Darwin":
+ if not CONFIG["TOR_BROWSER_UPDATE"]:
+ OS_LIBS += [
+ "crypt32",
+ "advapi32",
+ ]
+ elif CONFIG["OS_ARCH"] == "Darwin" and not CONFIG["TOR_BROWSER_UPDATE"]:
OS_LIBS += [
"-framework Security",
]
diff --git a/modules/libmar/verify/moz.build b/modules/libmar/verify/moz.build
index b07475655f0d..03718eee50b4 100644
--- a/modules/libmar/verify/moz.build
+++ b/modules/libmar/verify/moz.build
@@ -16,15 +16,12 @@ FORCE_STATIC_LIB = True
if CONFIG["OS_ARCH"] == "WINNT":
USE_STATIC_LIBS = True
elif CONFIG["OS_ARCH"] == "Darwin":
- UNIFIED_SOURCES += [
- "MacVerifyCrypto.cpp",
- ]
- OS_LIBS += [
- "-framework Security",
+ USE_LIBS += [
+ "nspr",
+ "nss",
+ "signmar",
]
else:
- DEFINES["MAR_NSS"] = True
- LOCAL_INCLUDES += ["../sign"]
USE_LIBS += [
"nspr",
"nss",
@@ -38,6 +35,9 @@ else:
"-Wl,-rpath=\\$$ORIGIN",
]
+DEFINES["MAR_NSS"] = True
+LOCAL_INCLUDES += ["../sign"]
+
LOCAL_INCLUDES += [
"../src",
]
diff --git a/toolkit/modules/AppConstants.jsm b/toolkit/modules/AppConstants.jsm
index ea10dc97535d..3cb1518f2ab3 100644
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -212,6 +212,13 @@ this.AppConstants = Object.freeze({
false,
#endif
+ MOZ_VERIFY_MAR_SIGNATURE:
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+ true,
+#else
+ false,
+#endif
+
MOZ_MAINTENANCE_SERVICE:
#ifdef MOZ_MAINTENANCE_SERVICE
true,
diff --git a/toolkit/mozapps/update/UpdateService.jsm b/toolkit/mozapps/update/UpdateService.jsm
index 8552240a1df6..f0a48d021638 100644
--- a/toolkit/mozapps/update/UpdateService.jsm
+++ b/toolkit/mozapps/update/UpdateService.jsm
@@ -999,6 +999,20 @@ function LOG(string) {
}
}
+/**
+ * Convert a string containing binary values to hex.
+ */
+function binaryToHex(input) {
+ var result = "";
+ for (var i = 0; i < input.length; ++i) {
+ var hex = input.charCodeAt(i).toString(16);
+ if (hex.length == 1)
+ hex = "0" + hex;
+ result += hex;
+ }
+ return result;
+}
+
/**
* Gets the specified directory at the specified hierarchy under the
* update root directory and creates it if it doesn't exist.
@@ -2022,6 +2036,8 @@ function UpdatePatch(patch) {
}
break;
case "finalURL":
+ case "hashFunction":
+ case "hashValue":
case "state":
case "type":
case "URL":
@@ -2041,6 +2057,8 @@ UpdatePatch.prototype = {
// over writing nsIUpdatePatch attributes.
_attrNames: [
"errorCode",
+ "hashFunction",
+ "hashValue",
"finalURL",
"selected",
"size",
@@ -2054,6 +2072,8 @@ UpdatePatch.prototype = {
*/
serialize: function UpdatePatch_serialize(updates) {
var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
+ patch.setAttribute("hashFunction", this.hashFunction);
+ patch.setAttribute("hashValue", this.hashValue);
patch.setAttribute("size", this.size);
patch.setAttribute("type", this.type);
patch.setAttribute("URL", this.URL);
@@ -5278,7 +5298,42 @@ Downloader.prototype = {
}
LOG("Downloader:_verifyDownload downloaded size == expected size.");
- return true;
+ let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ fileStream.init(destination, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+
+ let digest;
+ try {
+ let hash = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ var hashFunction = Ci.nsICryptoHash[this._patch.hashFunction.toUpperCase()];
+ if (hashFunction == undefined) {
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+ hash.init(hashFunction);
+ hash.updateFromStream(fileStream, -1);
+ // NOTE: For now, we assume that the format of _patch.hashValue is hex
+ // encoded binary (such as what is typically output by programs like
+ // sha1sum). In the future, this may change to base64 depending on how
+ // we choose to compute these hashes.
+ digest = binaryToHex(hash.finish(false));
+ } catch (e) {
+ LOG("Downloader:_verifyDownload - failed to compute hash of the " +
+ "downloaded update archive");
+ digest = "";
+ }
+
+ fileStream.close();
+
+ if (digest == this._patch.hashValue.toLowerCase()) {
+ LOG("Downloader:_verifyDownload hashes match.");
+ return true;
+ }
+
+ LOG("Downloader:_verifyDownload hashes do not match. ");
+ AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
+ AUSTLMY.DWNLD_ERR_VERIFY_NO_HASH_MATCH);
+ return false;
},
/**
@@ -5875,6 +5930,9 @@ Downloader.prototype = {
" is higher than patch size: " +
this._patch.size
);
+ // It's important that we use a different code than
+ // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
+ // between a hash error and a wrong download error.
AUSTLMY.pingDownloadCode(
this.isCompleteUpdate,
AUSTLMY.DWNLD_ERR_PATCH_SIZE_LARGER
@@ -5893,6 +5951,9 @@ Downloader.prototype = {
" is not equal to expected patch size: " +
this._patch.size
);
+ // It's important that we use a different code than
+ // NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
+ // between a hash error and a wrong download error.
AUSTLMY.pingDownloadCode(
this.isCompleteUpdate,
AUSTLMY.DWNLD_ERR_PATCH_SIZE_NOT_EQUAL
diff --git a/toolkit/mozapps/update/UpdateTelemetry.jsm b/toolkit/mozapps/update/UpdateTelemetry.jsm
index dae76e09acd0..df5b8917970e 100644
--- a/toolkit/mozapps/update/UpdateTelemetry.jsm
+++ b/toolkit/mozapps/update/UpdateTelemetry.jsm
@@ -192,6 +192,7 @@ var AUSTLMY = {
DWNLD_ERR_VERIFY_NO_REQUEST: 13,
DWNLD_ERR_VERIFY_PATCH_SIZE_NOT_EQUAL: 14,
DWNLD_ERR_WRITE_FAILURE: 15,
+ DWNLD_ERR_VERIFY_NO_HASH_MATCH: 16,
// Temporary failure code to see if there are failures without an update phase
DWNLD_UNKNOWN_PHASE_ERR_WRITE_FAILURE: 40,
diff --git a/toolkit/mozapps/update/nsIUpdateService.idl b/toolkit/mozapps/update/nsIUpdateService.idl
index 78929e1cef44..5db1db71fc81 100644
--- a/toolkit/mozapps/update/nsIUpdateService.idl
+++ b/toolkit/mozapps/update/nsIUpdateService.idl
@@ -39,6 +39,17 @@ interface nsIUpdatePatch : nsISupports
*/
attribute AString finalURL;
+ /**
+ * The hash function to use when determining this file's integrity
+ */
+ attribute AString hashFunction;
+
+ /**
+ * The value of the hash function named above that should be computed if
+ * this file is not corrupt.
+ */
+ attribute AString hashValue;
+
/**
* The size of this file, in bytes.
*/
diff --git a/toolkit/mozapps/update/updater/updater-common.build b/toolkit/mozapps/update/updater/updater-common.build
index 13926ea82046..a4173889271b 100644
--- a/toolkit/mozapps/update/updater/updater-common.build
+++ b/toolkit/mozapps/update/updater/updater-common.build
@@ -4,6 +4,10 @@
# 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/.
+DEFINES["MAR_NSS"] = True
+
+link_with_nss = DEFINES["MAR_NSS"] or (CONFIG["OS_ARCH"] == "Linux" and CONFIG["MOZ_VERIFY_MAR_SIGNATURE"])
+
srcs = [
"archivereader.cpp",
"updater.cpp",
@@ -36,10 +40,14 @@ if CONFIG["OS_ARCH"] == "WINNT":
"ws2_32",
"shell32",
"shlwapi",
- "crypt32",
- "advapi32",
]
+ if not link_with_nss:
+ OS_LIBS += [
+ "crypt32",
+ "advapi32",
+ ]
+
USE_LIBS += [
"bspatch",
"mar",
@@ -47,6 +55,13 @@ USE_LIBS += [
"xz-embedded",
]
+if link_with_nss:
+ USE_LIBS += [
+ "nspr",
+ "nss",
+ "signmar",
+ ]
+
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
have_progressui = 1
srcs += [
@@ -61,9 +76,12 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
]
OS_LIBS += [
"-framework Cocoa",
- "-framework Security",
"-framework SystemConfiguration",
]
+ if not link_with_nss:
+ OS_LIBS += [
+ "-framework Security",
+ ]
UNIFIED_SOURCES += [
"/toolkit/xre/updaterfileutils_osx.mm",
]
diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
index 646261c70967..70ca36dcc3d2 100644
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -110,9 +110,11 @@ struct UpdateServerThreadArgs {
# define stat64 stat
#endif
-#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX)
-# include "nss.h"
-# include "prerror.h"
+#if defined(MOZ_VERIFY_MAR_SIGNATURE)
+# if defined(MAR_NSS) || (!defined(XP_WIN) && !defined(XP_MACOSX))
+# include "nss.h"
+# include "prerror.h"
+# endif
#endif
#include "crctable.h"
@@ -2726,8 +2728,13 @@ static void UpdateThreadFunc(void* param) {
if (ReadMARChannelIDs(updateSettingsPath, &MARStrings) != OK) {
rv = UPDATE_SETTINGS_FILE_CHANNEL;
} else {
+# ifdef TOR_BROWSER_UPDATE
+ const char* appVersion = TOR_BROWSER_VERSION_QUOTED;
+# else
+ const char* appVersion = MOZ_APP_VERSION;
+# endif
rv = gArchiveReader.VerifyProductInformation(
- MARStrings.MARChannelID.get(), MOZ_APP_VERSION);
+ MARStrings.MARChannelID.get(), appVersion);
}
}
}
@@ -2957,11 +2964,10 @@ int NS_main(int argc, NS_tchar** argv) {
}
#endif
-#if defined(MOZ_VERIFY_MAR_SIGNATURE) && !defined(XP_WIN) && !defined(XP_MACOSX)
- // On Windows and Mac we rely on native APIs to do verifications so we don't
- // need to initialize NSS at all there.
- // Otherwise, minimize the amount of NSS we depend on by avoiding all the NSS
- // databases.
+#if defined(MOZ_VERIFY_MAR_SIGNATURE)
+# if defined(MAR_NSS) || (!defined(XP_WIN) && !defined(XP_MACOSX))
+ // If using NSS for signature verification, initialize NSS but minimize
+ // the portion we depend on by avoiding all of the NSS databases.
if (NSS_NoDB_Init(nullptr) != SECSuccess) {
PRErrorCode error = PR_GetError();
fprintf(stderr, "Could not initialize NSS: %s (%d)", PR_ErrorToName(error),
@@ -2969,6 +2975,7 @@ int NS_main(int argc, NS_tchar** argv) {
_exit(1);
}
#endif
+#endif
#ifdef XP_MACOSX
if (!isElevated) {
diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build
index 90d06481ee9e..56a2d7173d3c 100644
--- a/toolkit/xre/moz.build
+++ b/toolkit/xre/moz.build
@@ -233,6 +233,9 @@ for var in ("APP_VERSION", "APP_ID"):
if CONFIG["MOZ_BUILD_APP"] == "browser":
DEFINES["MOZ_BUILD_APP_IS_BROWSER"] = True
+if CONFIG['TOR_BROWSER_UPDATE']:
+ DEFINES['MAR_NSS'] = True
+
LOCAL_INCLUDES += [
"../../other-licenses/nsis/Contrib/CityHash/cityhash",
"../components/find",
diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp
index f83f28288786..4d2ca85928a9 100644
--- a/toolkit/xre/nsUpdateDriver.cpp
+++ b/toolkit/xre/nsUpdateDriver.cpp
@@ -366,6 +366,42 @@ static nsresult GetUpdateDirFromAppDir(nsIFile* aAppDir, nsIFile** aResult) {
# endif
#endif
+#if defined(TOR_BROWSER_UPDATE) && defined(MOZ_VERIFY_MAR_SIGNATURE) && \
+ defined(MAR_NSS) && defined(XP_MACOSX)
+/**
+ * Ideally we would save and restore the original library path value after
+ * the updater finishes its work (and before firefox is re-launched).
+ * Doing so would avoid potential problems like the following bug:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1434033
+ */
+/**
+ * Appends the specified path to the library path.
+ * This is used so that the updater can find libnss3.dylib and other
+ * shared libs.
+ *
+ * @param pathToAppend A new library path to prepend to the dynamic linker's
+ * search path.
+ */
+# include "prprf.h"
+# define PATH_SEPARATOR ":"
+# define LD_LIBRARY_PATH_ENVVAR_NAME "DYLD_LIBRARY_PATH"
+static void AppendToLibPath(const char* pathToAppend) {
+ char* pathValue = getenv(LD_LIBRARY_PATH_ENVVAR_NAME);
+ if (nullptr == pathValue || '\0' == *pathValue) {
+ // Leak the string because that is required by PR_SetEnv.
+ char* s =
+ Smprintf("%s=%s", LD_LIBRARY_PATH_ENVVAR_NAME, pathToAppend).release();
+ PR_SetEnv(s);
+ } else {
+ // Leak the string because that is required by PR_SetEnv.
+ char* s = Smprintf("%s=%s" PATH_SEPARATOR "%s", LD_LIBRARY_PATH_ENVVAR_NAME,
+ pathToAppend, pathValue)
+ .release();
+ PR_SetEnv(s);
+ }
+}
+#endif
+
/**
* Applies, switches, or stages an update.
*
@@ -612,6 +648,20 @@ static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir,
PR_SetEnv("MOZ_SAFE_MODE_RESTART=1");
}
+#if defined(TOR_BROWSER_UPDATE) && defined(MOZ_VERIFY_MAR_SIGNATURE) && \
+ defined(MAR_NSS) && defined(XP_MACOSX)
+ // On macOS, append the app directory to the shared library search path
+ // so the system can locate the shared libraries that are needed by the
+ // updater, e.g., libnss3.dylib).
+ nsAutoCString appPath;
+ nsresult rv2 = appDir->GetNativePath(appPath);
+ if (NS_SUCCEEDED(rv2)) {
+ AppendToLibPath(appPath.get());
+ } else {
+ LOG(("ApplyUpdate -- appDir->GetNativePath() failed (0x%x)\n", rv2));
+ }
+#endif
+
LOG(("spawning updater process [%s]\n", updaterPath.get()));
#ifdef DEBUG
dump_argv("ApplyUpdate updater", argv, argc);
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 25658: Replace security slider with security level UI
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 0687be303374b98b7837a6a57d724f3aa87de3aa
Author: Richard Pospesel <richard(a)torproject.org>
Date: Mon Mar 4 16:09:51 2019 -0800
Bug 25658: Replace security slider with security level UI
This patch adds a new 'securitylevel' component to Tor Browser intended
to replace the torbutton 'Security Slider'.
This component adds a new Security Level toolbar button which visually
indicates the current global security level via icon (as defined by the
extensions.torbutton.security_slider pref), a drop-down hanger with a
short description of the current security level, and a new section in
the about:preferences#privacy page where users can change their current
security level. In addition, the hanger and the preferences page will
show a visual warning when the user has modified prefs associated with
the security level and provide a one-click 'Restore Defaults' button to
get the user back on recommended settings.
Strings used by this patch are pulled from the torbutton extension, but
en-US defaults are provided if there is an error loading from the
extension. With this patch applied, the usual work-flow of "./mach build
&& ./mach run" work as expected, even if the torbutton extension is
disabled.
---
browser/base/content/browser.js | 10 +
browser/base/content/browser.xhtml | 2 +
browser/base/content/main-popupset.inc.xhtml | 1 +
browser/base/content/navigator-toolbox.inc.xhtml | 2 +
browser/components/moz.build | 1 +
browser/components/preferences/preferences.xhtml | 1 +
browser/components/preferences/privacy.inc.xhtml | 2 +
browser/components/preferences/privacy.js | 20 +
.../securitylevel/content/securityLevel.js | 527 +++++++++++++++++++++
.../securitylevel/content/securityLevelButton.css | 18 +
.../content/securityLevelButton.inc.xhtml | 7 +
.../securitylevel/content/securityLevelIcon.svg | 40 ++
.../securitylevel/content/securityLevelPanel.css | 74 +++
.../content/securityLevelPanel.inc.xhtml | 47 ++
.../content/securityLevelPreferences.css | 52 ++
.../content/securityLevelPreferences.inc.xhtml | 67 +++
browser/components/securitylevel/jar.mn | 6 +
browser/components/securitylevel/moz.build | 1 +
browser/modules/TorStrings.jsm | 4 +
.../themes/shared/customizableui/panelUI.inc.css | 3 +-
20 files changed, 884 insertions(+), 1 deletion(-)
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 9fe5302c5f58..1b1439a73c56 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -225,6 +225,11 @@ XPCOMUtils.defineLazyScriptGetter(
["DownloadsButton", "DownloadsIndicatorView"],
"chrome://browser/content/downloads/indicator.js"
);
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["SecurityLevelButton"],
+ "chrome://browser/content/securitylevel/securityLevel.js"
+);
XPCOMUtils.defineLazyScriptGetter(
this,
"gEditItemOverlay",
@@ -1761,6 +1766,9 @@ var gBrowserInit = {
// doesn't flicker as the window is being shown.
DownloadsButton.init();
+ // Init the SecuritySettingsButton
+ SecurityLevelButton.init();
+
// Certain kinds of automigration rely on this notification to complete
// their tasks BEFORE the browser window is shown. SessionStore uses it to
// restore tabs into windows AFTER important parts like gMultiProcessBrowser
@@ -2494,6 +2502,8 @@ var gBrowserInit = {
DownloadsButton.uninit();
+ SecurityLevelButton.uninit();
+
TorBootstrapUrlbar.uninit();
gAccessibilityServiceIndicator.uninit();
diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml
index f16307365728..627e6ac0f8a0 100644
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -21,6 +21,8 @@
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/tabbrowser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPanel.css"?>
+<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelButton.css"?>
<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/usercontext/usercontext.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml
index e5bf9460b75d..3fc665c65d79 100644
--- a/browser/base/content/main-popupset.inc.xhtml
+++ b/browser/base/content/main-popupset.inc.xhtml
@@ -520,6 +520,7 @@
#include ../../components/controlcenter/content/protectionsPanel.inc.xhtml
#include ../../components/downloads/content/downloadsPanel.inc.xhtml
#include ../../../devtools/startup/enableDevToolsPopup.inc.xhtml
+#include ../../components/securitylevel/content/securityLevelPanel.inc.xhtml
#include browser-allTabsMenu.inc.xhtml
<tooltip id="dynamic-shortcut-tooltip"
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index e7f63116ff39..6ac72cb889bc 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -413,6 +413,8 @@
</box>
</toolbarbutton>
+#include ../../components/securitylevel/content/securityLevelButton.inc.xhtml
+
<toolbarbutton id="fxa-toolbar-menu-button" class="toolbarbutton-1 chromeclass-toolbar-additional subviewbutton-nav"
badged="true"
onmousedown="gSync.toggleAccountPanel(this, event)"
diff --git a/browser/components/moz.build b/browser/components/moz.build
index d15ff3051593..ec8fab7fbc8f 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -48,6 +48,7 @@ DIRS += [
"protocolhandler",
"resistfingerprinting",
"search",
+ "securitylevel",
"sessionstore",
"shell",
"syncedtabs",
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 0923005c8b90..0139abf95cbd 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -12,6 +12,7 @@
<?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
+<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?>
<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
<!DOCTYPE html [
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml
index 8d51e60cb32b..d2cf2ea9c89c 100644
--- a/browser/components/preferences/privacy.inc.xhtml
+++ b/browser/components/preferences/privacy.inc.xhtml
@@ -919,6 +919,8 @@
<html:h1 data-l10n-id="security-header"/>
</hbox>
+#include ../securitylevel/content/securityLevelPreferences.inc.xhtml
+
<!-- addons, forgery (phishing) UI Security -->
<groupbox id="browsingProtectionGroup" data-category="panePrivacy" hidden="true">
<label><html:h2 data-l10n-id="security-browsing-protection"/></label>
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index 35b37b099e93..bce7bb7e8a9c 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -80,6 +80,13 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
}
});
+// TODO: module import via ChromeUtils.defineModuleGetter
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["SecurityLevelPreferences"],
+ "chrome://browser/content/securitylevel/securityLevel.js"
+);
+
XPCOMUtils.defineLazyServiceGetter(
this,
"listManager",
@@ -308,6 +315,18 @@ function setUpContentBlockingWarnings() {
var gPrivacyPane = {
_pane: null,
+ /**
+ * Show the Security Level UI
+ */
+ _initSecurityLevel() {
+ SecurityLevelPreferences.init();
+ let unload = () => {
+ window.removeEventListener("unload", unload);
+ SecurityLevelPreferences.uninit();
+ };
+ window.addEventListener("unload", unload);
+ },
+
/**
* Whether the prompt to restart Firefox should appear when changing the autostart pref.
*/
@@ -503,6 +522,7 @@ var gPrivacyPane = {
this.trackingProtectionReadPrefs();
this.networkCookieBehaviorReadPrefs();
this._initTrackingProtectionExtensionControl();
+ this._initSecurityLevel();
Services.telemetry.setEventRecordingEnabled("pwmgr", true);
diff --git a/browser/components/securitylevel/content/securityLevel.js b/browser/components/securitylevel/content/securityLevel.js
new file mode 100644
index 000000000000..8b8babe5b58e
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevel.js
@@ -0,0 +1,527 @@
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ CustomizableUI: "resource:///modules/CustomizableUI.jsm",
+ PanelMultiView: "resource:///modules/PanelMultiView.jsm",
+});
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorStrings",
+ "resource:///modules/TorStrings.jsm"
+);
+
+/*
+ Security Level Prefs
+
+ Getters and Setters for relevant torbutton prefs
+*/
+const SecurityLevelPrefs = {
+ security_slider_pref : "extensions.torbutton.security_slider",
+ security_custom_pref : "extensions.torbutton.security_custom",
+
+ get securitySlider() {
+ try {
+ return Services.prefs.getIntPref(this.security_slider_pref);
+ } catch(e) {
+ // init pref to 4 (standard)
+ const val = 4;
+ Services.prefs.setIntPref(this.security_slider_pref, val);
+ return val;
+ }
+ },
+
+ set securitySlider(val) {
+ Services.prefs.setIntPref(this.security_slider_pref, val);
+ },
+
+ get securityCustom() {
+ try {
+ return Services.prefs.getBoolPref(this.security_custom_pref);
+ } catch(e) {
+ // init custom to false
+ const val = false;
+ Services.prefs.setBoolPref(this.security_custom_pref, val);
+ return val;
+ }
+ },
+
+ set securityCustom(val) {
+ Services.prefs.setBoolPref(this.security_custom_pref, val);
+ },
+}; /* Security Level Prefs */
+
+/*
+ Security Level Button Code
+
+ Controls init and update of the security level toolbar button
+*/
+
+const SecurityLevelButton = {
+ _securityPrefsBranch : null,
+
+ _populateXUL : function(securityLevelButton) {
+ if (securityLevelButton != null) {
+ securityLevelButton.setAttribute("tooltiptext", TorStrings.securityLevel.securityLevel);
+ securityLevelButton.setAttribute("label", TorStrings.securityLevel.securityLevel);
+ }
+ },
+
+ _configUIFromPrefs : function(securityLevelButton) {
+ if (securityLevelButton != null) {
+ let securitySlider = SecurityLevelPrefs.securitySlider;
+ securityLevelButton.removeAttribute("level");
+ const securityCustom = SecurityLevelPrefs.securityCustom;
+ switch(securitySlider) {
+ case 4:
+ securityLevelButton.setAttribute("level", `standard${securityCustom ? "_custom" : ""}`);
+ securityLevelButton.setAttribute("tooltiptext", TorStrings.securityLevel.standard.tooltip);
+ break;
+ case 2:
+ securityLevelButton.setAttribute("level", `safer${securityCustom ? "_custom" : ""}`);
+ securityLevelButton.setAttribute("tooltiptext", TorStrings.securityLevel.safer.tooltip);
+ break;
+ case 1:
+ securityLevelButton.setAttribute("level", `safest${securityCustom ? "_custom" : ""}`);
+ securityLevelButton.setAttribute("tooltiptext", TorStrings.securityLevel.safest.tooltip);
+ break;
+ }
+ }
+ },
+
+ get button() {
+ let button = document.getElementById("security-level-button");
+ if (!button) {
+ return null;
+ }
+ return button;
+ },
+
+ get anchor() {
+ let anchor = this.button.icon;
+ if (!anchor) {
+ return null;
+ }
+
+ anchor.setAttribute("consumeanchor", SecurityLevelButton.button.id);
+ return anchor;
+ },
+
+ init : function() {
+ // set the initial class based off of the current pref
+ let button = this.button;
+ this._populateXUL(button);
+ this._configUIFromPrefs(button);
+
+ this._securityPrefsBranch = Services.prefs.getBranch("extensions.torbutton.");
+ this._securityPrefsBranch.addObserver("", this, false);
+
+ CustomizableUI.addListener(this);
+
+ SecurityLevelPanel.init();
+ },
+
+ uninit : function() {
+ CustomizableUI.removeListener(this);
+
+ this._securityPrefsBranch.removeObserver("", this);
+ this._securityPrefsBranch = null;
+
+ SecurityLevelPanel.uninit();
+ },
+
+ observe : function(subject, topic, data) {
+ switch(topic) {
+ case "nsPref:changed":
+ if (data === "security_slider" || data === "security_custom") {
+ this._configUIFromPrefs(this.button);
+ }
+ break;
+ }
+ },
+
+ // callback for entering the 'Customize Firefox' screen to set icon
+ onCustomizeStart : function(window) {
+ let navigatorToolbox = document.getElementById("navigator-toolbox");
+ let button = navigatorToolbox.palette.querySelector("#security-level-button");
+ this._populateXUL(button);
+ this._configUIFromPrefs(button);
+ },
+
+ // callback when CustomizableUI modifies DOM
+ onWidgetAfterDOMChange : function(aNode, aNextNode, aContainer, aWasRemoval) {
+ if (aNode.id == "security-level-button" && !aWasRemoval) {
+ this._populateXUL(aNode);
+ this._configUIFromPrefs(aNode);
+ }
+ },
+
+ // for when the toolbar button needs to be activated and displays the Security Level panel
+ //
+ // In the toolbarbutton xul you'll notice we register this callback for both onkeypress and
+ // onmousedown. We do this to match the behavior of other panel spawning buttons such as Downloads,
+ // Library, and the Hamburger menus. Using oncommand alone would result in only getting fired
+ // after onclick, which is mousedown followed by mouseup.
+ onCommand : function(aEvent) {
+ // snippet borrowed from /browser/components/downloads/content/indicator.js DownloadsIndicatorView.onCommand(evt)
+ if (
+ // On Mac, ctrl-click will send a context menu event from the widget, so
+ // we don't want to bring up the panel when ctrl key is pressed.
+ (aEvent.type == "mousedown" &&
+ (aEvent.button != 0 ||
+ (AppConstants.platform == "macosx" && aEvent.ctrlKey))) ||
+ (aEvent.type == "keypress" && aEvent.key != " " && aEvent.key != "Enter")
+ ) {
+ return;
+ }
+
+ // we need to set this attribute for the button to be shaded correctly to look like it is pressed
+ // while the security level panel is open
+ this.button.setAttribute("open", "true");
+ SecurityLevelPanel.show();
+ aEvent.stopPropagation();
+ },
+}; /* Security Level Button */
+
+/*
+ Security Level Panel Code
+
+ Controls init and update of the panel in the security level hanger
+*/
+
+const SecurityLevelPanel = {
+ _securityPrefsBranch : null,
+ _panel : null,
+ _anchor : null,
+ _populated : false,
+
+ _selectors: Object.freeze({
+ panel: "panel#securityLevel-panel",
+ icon: "vbox#securityLevel-vbox>vbox",
+ header: "h1#securityLevel-header",
+ level: "label#securityLevel-level",
+ custom: "label#securityLevel-custom",
+ summary: "description#securityLevel-summary",
+ learnMore: "label#securityLevel-learnMore",
+ restoreDefaults: "button#securityLevel-restoreDefaults",
+ advancedSecuritySettings: "button#securityLevel-advancedSecuritySettings",
+ }),
+
+ _populateXUL : function() {
+ let selectors = this._selectors;
+
+ this._elements = {
+ panel: document.querySelector(selectors.panel),
+ icon: document.querySelector(selectors.icon),
+ header: document.querySelector(selectors.header),
+ levelLabel: document.querySelector(selectors.level),
+ customLabel: document.querySelector(selectors.custom),
+ summaryDescription: document.querySelector(selectors.summary),
+ learnMoreLabel: document.querySelector(selectors.learnMore),
+ restoreDefaultsButton: document.querySelector(selectors.restoreDefaults),
+ changeButton: document.querySelector(selectors.advancedSecuritySettings),
+ };
+ let elements = this._elements;
+
+ elements.header.textContent = TorStrings.securityLevel.securityLevel;
+ elements.customLabel.setAttribute("value", TorStrings.securityLevel.customWarning);
+ elements.learnMoreLabel.setAttribute("value", TorStrings.securityLevel.learnMore);
+ elements.learnMoreLabel.setAttribute("href", TorStrings.securityLevel.learnMoreURL);
+ elements.restoreDefaultsButton.setAttribute("label", TorStrings.securityLevel.restoreDefaults);
+ elements.changeButton.setAttribute("label", TorStrings.securityLevel.change);
+
+ this._configUIFromPrefs();
+ this._populated = true;
+ },
+
+ _configUIFromPrefs : function() {
+ // get security prefs
+ let securitySlider = SecurityLevelPrefs.securitySlider;
+ let securityCustom = SecurityLevelPrefs.securityCustom;
+
+ // get the panel elements we need to populate
+ let elements = this._elements;
+ let icon = elements.icon;
+ let labelLevel = elements.levelLabel;
+ let labelCustomWarning = elements.customLabel;
+ let summary = elements.summaryDescription;
+ let buttonRestoreDefaults = elements.restoreDefaultsButton;
+ let buttonAdvancedSecuritySettings = elements.changeButton;
+
+ // only visible when user is using custom settings
+ labelCustomWarning.hidden = !securityCustom;
+ buttonRestoreDefaults.hidden = !securityCustom;
+
+ // Descriptions change based on security level
+ switch(securitySlider) {
+ // standard
+ case 4:
+ icon.setAttribute("level", "standard");
+ labelLevel.setAttribute("value", TorStrings.securityLevel.standard.level);
+ summary.textContent = TorStrings.securityLevel.standard.summary;
+ break;
+ // safer
+ case 2:
+ icon.setAttribute("level", "safer");
+ labelLevel.setAttribute("value", TorStrings.securityLevel.safer.level);
+ summary.textContent = TorStrings.securityLevel.safer.summary;
+ break;
+ // safest
+ case 1:
+ icon.setAttribute("level", "safest");
+ labelLevel.setAttribute("value", TorStrings.securityLevel.safest.level);
+ summary.textContent = TorStrings.securityLevel.safest.summary;
+ break;
+ }
+
+ // override the summary text with custom warning
+ if (securityCustom) {
+ summary.textContent = TorStrings.securityLevel.custom.summary;
+ }
+ },
+
+ init : function() {
+ this._securityPrefsBranch = Services.prefs.getBranch("extensions.torbutton.");
+ this._securityPrefsBranch.addObserver("", this, false);
+ },
+
+ uninit : function() {
+ this._securityPrefsBranch.removeObserver("", this);
+ this._securityPrefsBranch = null;
+ },
+
+ show : function() {
+ // we have to defer this until after the browser has finished init'ing before
+ // we can populate the panel
+ if (!this._populated) {
+ this._populateXUL();
+ }
+
+ let panel = document.getElementById("securityLevel-panel");
+ panel.hidden = false;
+ PanelMultiView.openPopup(panel, SecurityLevelButton.anchor, "bottomcenter topright",
+ 0, 0, false, null).catch(Cu.reportError);
+ },
+
+ hide : function() {
+ let panel = document.getElementById("securityLevel-panel");
+ PanelMultiView.hidePopup(panel);
+ },
+
+ restoreDefaults : function() {
+ SecurityLevelPrefs.securityCustom = false;
+ // hide and reshow so that layout re-renders properly
+ this.hide();
+ this.show(this._anchor);
+ },
+
+ openAdvancedSecuritySettings : function() {
+ openPreferences("privacy-securitylevel");
+ this.hide();
+ },
+
+ // callback when prefs change
+ observe : function(subject, topic, data) {
+ switch(topic) {
+ case "nsPref:changed":
+ if (data == "security_slider" || data == "security_custom") {
+ this._configUIFromPrefs();
+ }
+ break;
+ }
+ },
+
+ // callback when the panel is displayed
+ onPopupShown : function(event) {
+ SecurityLevelButton.button.setAttribute("open", "true");
+ },
+
+ // callback when the panel is hidden
+ onPopupHidden : function(event) {
+ SecurityLevelButton.button.removeAttribute("open");
+ }
+}; /* Security Level Panel */
+
+/*
+ Security Level Preferences Code
+
+ Code to handle init and update of security level section in about:preferences#privacy
+*/
+
+const SecurityLevelPreferences =
+{
+ _securityPrefsBranch : null,
+
+ _populateXUL : function() {
+ let groupbox = document.getElementById("securityLevel-groupbox");
+
+ let labelHeader = groupbox.querySelector("#securityLevel-header");
+ labelHeader.textContent = TorStrings.securityLevel.securityLevel;
+
+ let spanOverview = groupbox.querySelector("#securityLevel-overview");
+ spanOverview.textContent = TorStrings.securityLevel.overview;
+
+ let labelLearnMore = groupbox.querySelector("#securityLevel-learnMore");
+ labelLearnMore.setAttribute("value", TorStrings.securityLevel.learnMore);
+ labelLearnMore.setAttribute("href", TorStrings.securityLevel.learnMoreURL);
+
+ let radiogroup = document.getElementById("securityLevel-radiogroup");
+ radiogroup.addEventListener("command", SecurityLevelPreferences.selectSecurityLevel);
+
+ let populateRadioElements = function(vboxQuery, stringStruct) {
+ let vbox = groupbox.querySelector(vboxQuery);
+
+ let radio = vbox.querySelector("radio");
+ radio.setAttribute("label", stringStruct.level);
+
+ let customWarning = vbox.querySelector("#securityLevel-customWarning");
+ customWarning.setAttribute("value", TorStrings.securityLevel.customWarning);
+
+ let labelSummary = vbox.querySelector("#securityLevel-summary");
+ labelSummary.textContent = stringStruct.summary;
+
+ let labelRestoreDefaults = vbox.querySelector("#securityLevel-restoreDefaults");
+ labelRestoreDefaults.setAttribute("value", TorStrings.securityLevel.restoreDefaults);
+ labelRestoreDefaults.addEventListener("click", SecurityLevelPreferences.restoreDefaults);
+
+ let description1 = vbox.querySelector("#securityLevel-description1");
+ if (description1) {
+ description1.textContent = stringStruct.description1;
+ }
+ let description2 = vbox.querySelector("#securityLevel-description2");
+ if (description2) {
+ description2.textContent = stringStruct.description2;
+ }
+ let description3 = vbox.querySelector("#securityLevel-description3");
+ if (description3) {
+ description3.textContent = stringStruct.description3;
+ }
+ };
+
+ populateRadioElements("#securityLevel-vbox-standard", TorStrings.securityLevel.standard);
+ populateRadioElements("#securityLevel-vbox-safer", TorStrings.securityLevel.safer);
+ populateRadioElements("#securityLevel-vbox-safest", TorStrings.securityLevel.safest);
+ },
+
+ _configUIFromPrefs : function() {
+ // read our prefs
+ let securitySlider = SecurityLevelPrefs.securitySlider;
+ let securityCustom = SecurityLevelPrefs.securityCustom;
+
+ // get our elements
+ let groupbox = document.getElementById("securityLevel-groupbox");
+
+ let radiogroup = groupbox.querySelector("#securityLevel-radiogroup");
+ let labelStandardCustom = groupbox.querySelector("#securityLevel-vbox-standard label#securityLevel-customWarning");
+ let labelSaferCustom = groupbox.querySelector("#securityLevel-vbox-safer label#securityLevel-customWarning");
+ let labelSafestCustom = groupbox.querySelector("#securityLevel-vbox-safest label#securityLevel-customWarning");
+ let labelStandardRestoreDefaults = groupbox.querySelector("#securityLevel-vbox-standard label#securityLevel-restoreDefaults");
+ let labelSaferRestoreDefaults = groupbox.querySelector("#securityLevel-vbox-safer label#securityLevel-restoreDefaults");
+ let labelSafestRestoreDefaults = groupbox.querySelector("#securityLevel-vbox-safest label#securityLevel-restoreDefaults");
+
+ // hide custom label by default until we know which level we're at
+ labelStandardCustom.hidden = true;
+ labelSaferCustom.hidden = true;
+ labelSafestCustom.hidden = true;
+
+ labelStandardRestoreDefaults.hidden = true;
+ labelSaferRestoreDefaults.hidden = true;
+ labelSafestRestoreDefaults.hidden = true;
+
+ switch(securitySlider) {
+ // standard
+ case 4:
+ radiogroup.value = "standard";
+ labelStandardCustom.hidden = !securityCustom;
+ labelStandardRestoreDefaults.hidden = !securityCustom;
+ break;
+ // safer
+ case 2:
+ radiogroup.value = "safer";
+ labelSaferCustom.hidden = !securityCustom;
+ labelSaferRestoreDefaults.hidden = !securityCustom;
+ break;
+ // safest
+ case 1:
+ radiogroup.value = "safest";
+ labelSafestCustom.hidden = !securityCustom;
+ labelSafestRestoreDefaults.hidden = !securityCustom;
+ break;
+ }
+ },
+
+ init : function() {
+ // populate XUL with localized strings
+ this._populateXUL();
+
+ // read prefs and populate UI
+ this._configUIFromPrefs();
+
+ // register for pref chagnes
+ this._securityPrefsBranch = Services.prefs.getBranch("extensions.torbutton.");
+ this._securityPrefsBranch.addObserver("", this, false);
+ },
+
+ uninit : function() {
+ // unregister for pref change events
+ this._securityPrefsBranch.removeObserver("", this);
+ this._securityPrefsBranch = null;
+ },
+
+ // callback for when prefs change
+ observe : function(subject, topic, data) {
+ switch(topic) {
+ case "nsPref:changed":
+ if (data == "security_slider" ||
+ data == "security_custom") {
+ this._configUIFromPrefs();
+ }
+ break;
+ }
+ },
+
+ selectSecurityLevel : function() {
+ // radio group elements
+ let radiogroup = document.getElementById("securityLevel-radiogroup");
+
+ // update pref based on selected radio option
+ switch (radiogroup.value) {
+ case "standard":
+ SecurityLevelPrefs.securitySlider = 4;
+ break;
+ case "safer":
+ SecurityLevelPrefs.securitySlider = 2;
+ break;
+ case "safest":
+ SecurityLevelPrefs.securitySlider = 1;
+ break;
+ }
+
+ SecurityLevelPreferences.restoreDefaults();
+ },
+
+ restoreDefaults : function() {
+ SecurityLevelPrefs.securityCustom = false;
+ },
+}; /* Security Level Prefereces */
+
+Object.defineProperty(this, "SecurityLevelButton", {
+ value: SecurityLevelButton,
+ enumerable: true,
+ writable: false
+});
+
+Object.defineProperty(this, "SecurityLevelPanel", {
+ value: SecurityLevelPanel,
+ enumerable: true,
+ writable: false
+});
+
+Object.defineProperty(this, "SecurityLevelPreferences", {
+ value: SecurityLevelPreferences,
+ enumerable: true,
+ writable: false
+});
diff --git a/browser/components/securitylevel/content/securityLevelButton.css b/browser/components/securitylevel/content/securityLevelButton.css
new file mode 100644
index 000000000000..38701250e9c9
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelButton.css
@@ -0,0 +1,18 @@
+toolbarbutton#security-level-button[level="standard"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
+}
+toolbarbutton#security-level-button[level="safer"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
+}
+toolbarbutton#security-level-button[level="safest"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
+}
+toolbarbutton#security-level-button[level="standard_custom"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard_custom");
+}
+toolbarbutton#security-level-button[level="safer_custom"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer_custom");
+}
+toolbarbutton#security-level-button[level="safest_custom"] {
+ list-style-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest_custom");
+}
\ No newline at end of file
diff --git a/browser/components/securitylevel/content/securityLevelButton.inc.xhtml b/browser/components/securitylevel/content/securityLevelButton.inc.xhtml
new file mode 100644
index 000000000000..96ee1ec0ca49
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelButton.inc.xhtml
@@ -0,0 +1,7 @@
+<toolbarbutton id="security-level-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ badged="true"
+ removable="true"
+ onmousedown="SecurityLevelButton.onCommand(event);"
+ onkeypress="SecurityLevelButton.onCommand(event);"
+ closemenu="none"
+ cui-areatype="toolbar"/>
diff --git a/browser/components/securitylevel/content/securityLevelIcon.svg b/browser/components/securitylevel/content/securityLevelIcon.svg
new file mode 100644
index 000000000000..38cdbcb68afc
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelIcon.svg
@@ -0,0 +1,40 @@
+<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <style>
+ use:not(:target) {
+ display: none;
+ }
+ </style>
+ <defs>
+ <g id="standard_icon" stroke="none" stroke-width="1">
+ <path clip-rule="evenodd" d="m8.49614.283505c-.30743-.175675-.68485-.175675-.99228.000001l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 4.0133-.9164 7-6 7-10v-1.41968c0-.35886-.1923-.6902-.5039-.86824zm-.49614 1.216495-5.75 3.28571v1.2746c0 1.71749.65238 3.7522 1.78726 5.46629 1.07287 1.6204 2.47498 2.8062 3.96274 3.2425 1.48776-.4363 2.8899-1.6221 3.9627-3.2425 1.1349-1.71409 1.7873-3.7488 1.7873-5.46629v-1.2746z" fill-rule="evenodd" />
+ </g>
+ <g id="safer_icon" stroke="none" stroke-width="1">
+ <path clip-rule="evenodd" d="m8.49614.283505c-.30743-.175675-.68485-.175675-.99228.000001l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 4.0133-.9164 7-6 7-10v-1.41968c0-.35886-.1923-.6902-.5039-.86824zm-.49614 1.216495-5.75 3.28571v1.2746c0 1.71749.65238 3.7522 1.78726 5.46629 1.07287 1.6204 2.47498 2.8062 3.96274 3.2425 1.48776-.4363 2.8899-1.6221 3.9627-3.2425 1.1349-1.71409 1.7873-3.7488 1.7873-5.46629v-1.2746z" fill-rule="evenodd"/>
+ <path d="m3.5 6.12062v-.40411c0-.08972.04807-.17255.12597-.21706l4-2.28572c.16666-.09523.37403.02511.37403.21707v10.0766c-1.01204-.408-2.054-1.3018-2.92048-2.6105-1.02134-1.54265-1.57952-3.34117-1.57952-4.77628z"/>
+ </g>
+ <g id="safest_icon" stroke="none" stroke-width="1">
+ <path clip-rule="evenodd" d="m8.49614.283505c-.30743-.175675-.68485-.175675-.99228.000001l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 4.0133-.9164 7-6 7-10v-1.41968c0-.35886-.1923-.6902-.5039-.86824zm-.49614 1.216495-5.75 3.28571v1.2746c0 1.71749.65238 3.7522 1.78726 5.46629 1.07287 1.6204 2.47498 2.8062 3.96274 3.2425 1.48776-.4363 2.8899-1.6221 3.9627-3.2425 1.1349-1.71409 1.7873-3.7488 1.7873-5.46629v-1.2746z" fill-rule="evenodd"/>
+ <path d="m3.5 6.12062v-.40411c0-.08972.04807-.17255.12597-.21706l4.25-2.42857c.07685-.04392.17121-.04392.24806 0l4.24997 2.42857c.0779.04451.126.12734.126.21706v.40411c0 1.43511-.5582 3.23363-1.5795 4.77628-.8665 1.3087-1.90846 2.2025-2.9205 2.6105-1.01204-.408-2.054-1.3018-2.92048-2.6105-1.02134-1.54265-1.57952-3.34117-1.57952-4.77628z"/>
+ </g>
+ <g id="standard_custom_icon" stroke="none" stroke-width="1">
+ <path d="m9.37255.784312-.87641-.500806c-.30743-.175676-.68485-.175676-.99228 0l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 3.7599-.8585 6.6186-5.3745 6.9647-9.23043-.4008.20936-.8392.35666-1.3024.42914-.2132 1.43414-.8072 2.98009-1.6996 4.32789-1.0728 1.6204-2.47494 2.8062-3.9627 3.2425-1.48776-.4363-2.88987-1.6221-3.96274-3.2425-1.13488-1.71409-1.78726-3.7488-1.78726-5.46629v-1.2746l5.75-3.28571.86913.49664c.10502-.43392.27664-.84184.50342-1.212328z"/>
+ <circle cx="13" cy="3" fill="#ffbd2e" r="3"/>
+ </g>
+ <g id="safer_custom_icon" stroke="none" stroke-width="1">
+ <path d="m9.37255.784312-.87641-.500806c-.30743-.175676-.68485-.175676-.99228 0l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 3.7599-.8585 6.6186-5.3745 6.9647-9.23043-.4008.20936-.8392.35666-1.3024.42914-.2132 1.43414-.8072 2.98009-1.6996 4.32789-1.0728 1.6204-2.47494 2.8062-3.9627 3.2425-1.48776-.4363-2.88987-1.6221-3.96274-3.2425-1.13488-1.71409-1.78726-3.7488-1.78726-5.46629v-1.2746l5.75-3.28571.86913.49664c.10502-.43392.27664-.84184.50342-1.212328z"/>
+ <path d="m3.5 6.12062v-.40411c0-.08972.04807-.17255.12597-.21706l4-2.28572c.16666-.09523.37403.02511.37403.21707v10.0766c-1.01204-.408-2.054-1.3018-2.92048-2.6105-1.02134-1.54265-1.57952-3.34117-1.57952-4.77628z"/>
+ <circle cx="13" cy="3" fill="#ffbd2e" r="3"/>
+ </g>
+ <g id="safest_custom_icon" stroke="none" stroke-width="1">
+ <path d="m9.37255.784312-.87641-.500806c-.30743-.175676-.68485-.175676-.99228 0l-6 3.428574c-.31157.17804-.50386.50938-.50386.86824v1.41968c0 4 2.98667 9.0836 7 10 3.7599-.8585 6.6186-5.3745 6.9647-9.23043-.4008.20936-.8392.35666-1.3024.42914-.2132 1.43414-.8072 2.98009-1.6996 4.32789-1.0728 1.6204-2.47494 2.8062-3.9627 3.2425-1.48776-.4363-2.88987-1.6221-3.96274-3.2425-1.13488-1.71409-1.78726-3.7488-1.78726-5.46629v-1.2746l5.75-3.28571.86913.49664c.10502-.43392.27664-.84184.50342-1.212328z"/>
+ <path d="m8.77266 3.44151-.64863-.37064c-.07685-.04392-.17121-.04392-.24806 0l-4.25 2.42857c-.0779.04451-.12597.12735-.12597.21706v.40412c0 1.4351.55818 3.23362 1.57952 4.77618.86648 1.3087 1.90844 2.2026 2.92048 2.6106 1.01204-.408 2.054-1.3018 2.9205-2.6106.7761-1.17217 1.2847-2.49215 1.4843-3.68816-1.9219-.26934-3.43158-1.82403-3.63214-3.76713z"/>
+ <circle cx="13" cy="3" fill="#ffbd2e" r="3"/>
+ </g>
+ </defs>
+ <use id="standard" fill="context-fill" fill-opacity="context-fill-opacity" href="#standard_icon" />
+ <use id="safer" fill="context-fill" fill-opacity="context-fill-opacity" href="#safer_icon" />
+ <use id="safest" fill="context-fill" fill-opacity="context-fill-opacity" href="#safest_icon" />
+ <use id="standard_custom" fill="context-fill" fill-opacity="context-fill-opacity" href="#standard_custom_icon" />
+ <use id="safer_custom" fill="context-fill" fill-opacity="context-fill-opacity" href="#safer_custom_icon" />
+ <use id="safest_custom" fill="context-fill" fill-opacity="context-fill-opacity" href="#safest_custom_icon" />
+</svg>
diff --git a/browser/components/securitylevel/content/securityLevelPanel.css b/browser/components/securitylevel/content/securityLevelPanel.css
new file mode 100644
index 000000000000..6462c02f1594
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelPanel.css
@@ -0,0 +1,74 @@
+/* Security Level CSS */
+
+panelview#securityLevel-panelview {
+ width: 25em;
+}
+
+vbox#securityLevel-vbox > vbox {
+ background-repeat: no-repeat;
+ /* icon center-line should be in-line with right margin */
+ /* -margin + panelWidth - imageWidth/2 */
+ background-position: calc(-16px + 25em - 4.5em) 0.4em;
+ background-size: 9em 9em;
+ -moz-context-properties: fill, fill-opacity;
+ fill-opacity: 1;
+ fill: var(--button-bgcolor);
+ min-height: 10em;
+}
+
+vbox#securityLevel-vbox > vbox[level="standard"] {
+ background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#standard");
+}
+vbox#securityLevel-vbox > vbox[level="safer"] {
+ background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safer");
+}
+vbox#securityLevel-vbox > vbox[level="safest"] {
+ background-image: url("chrome://browser/content/securitylevel/securityLevelIcon.svg#safest");
+}
+
+vbox#securityLevel-vbox > toolbarseparator {
+ margin-inline: 16px;
+}
+
+vbox#securityLevel-vbox > vbox {
+ margin-inline: 0;
+ padding-inline: 16px;
+}
+
+vbox#securityLevel-vbox > vbox * {
+ margin-inline: 0;
+}
+
+vbox#securityLevel-vbox > vbox > hbox {
+}
+
+label#securityLevel-level {
+ font-size: 1.25em;
+ font-weight: 600;
+ padding-top: 0.15em;
+}
+
+label#securityLevel-custom {
+ border-radius: 4px;
+ background-color: var(--yellow-50);
+ color: black;
+ font-size: 1em;
+ height: 1.6em;
+ line-height: 1.0em;
+ padding: 0.4em 0.5em;
+ margin-left: 1em!important;
+}
+
+description#securityLevel-summary {
+ margin-top: 1em;
+ padding-right: 5em;
+}
+
+vbox#securityLevel-vbox > hbox.panel-footer {
+ display: flex;
+}
+
+
+button#securityLevel-advancedSecuritySettings {
+ margin-block: 0;
+}
diff --git a/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
new file mode 100644
index 000000000000..02d93b738ff5
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelPanel.inc.xhtml
@@ -0,0 +1,47 @@
+<panel id="securityLevel-panel"
+ role="group"
+ type="arrow"
+ orient="vertical"
+ level="top"
+ hidden="true"
+ class="panel-no-padding"
+ onpopupshown="SecurityLevelPanel.onPopupShown(event);"
+ onpopuphidden="SecurityLevelPanel.onPopupHidden(event);">
+ <panelmultiview mainViewId="securityLevel-panelview">
+ <panelview id="securityLevel-panelview" descriptionheightworkaround="true">
+ <vbox id="securityLevel-vbox">
+ <box class="panel-header">
+ <html:h1 id="securityLevel-header"/>
+ </box>
+ <toolbarseparator></toolbarseparator>
+ <vbox>
+ <hbox>
+ <label id="securityLevel-level"/>
+ <vbox>
+ <spacer flex="1"/>
+ <label id="securityLevel-custom"/>
+ <spacer flex="1"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <description id="securityLevel-summary"/>
+ <hbox>
+ <label
+ id="securityLevel-learnMore"
+ class="learnMore text-link"
+ onclick="SecurityLevelPanel.hide();"
+ is="text-link"/>
+ <spacer/>
+ </hbox>
+ </vbox>
+ <hbox class="panel-footer">
+ <button id="securityLevel-restoreDefaults"
+ oncommand="SecurityLevelPanel.restoreDefaults();"/>
+ <button id="securityLevel-advancedSecuritySettings"
+ default="true"
+ oncommand="SecurityLevelPanel.openAdvancedSecuritySettings();"/>
+ </hbox>
+ </vbox>
+ </panelview>
+ </panelmultiview>
+</panel>
diff --git a/browser/components/securitylevel/content/securityLevelPreferences.css b/browser/components/securitylevel/content/securityLevelPreferences.css
new file mode 100644
index 000000000000..12a7cccffe09
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelPreferences.css
@@ -0,0 +1,52 @@
+label#securityLevel-customWarning {
+ border-radius: 4px;
+ background-color: var(--yellow-50);
+ color: black;
+ font-size: 1em;
+ height: 1.6em;
+ padding: 0.4em 0.5em;
+}
+
+radiogroup#securityLevel-radiogroup description {
+ color: var(--in-content-page-color)!important;
+}
+
+radiogroup#securityLevel-radiogroup radio {
+ font-weight: bold;
+}
+
+radiogroup#securityLevel-radiogroup > vbox {
+ border: 1px solid var(--in-content-box-border-color);
+ border-radius: 4px;
+ margin: 3px 0;
+ padding: 9px;
+}
+
+radiogroup#securityLevel-radiogroup[value=standard] > vbox#securityLevel-vbox-standard,
+radiogroup#securityLevel-radiogroup[value=safer] > vbox#securityLevel-vbox-safer,
+radiogroup#securityLevel-radiogroup[value=safest] > vbox#securityLevel-vbox-safest {
+ --section-highlight-background-color: color-mix(in srgb, var(--in-content-accent-color) 20%, transparent);
+ background-color: var(--section-highlight-background-color);
+ border: 1px solid var(--in-content-accent-color);
+
+}
+
+vbox#securityLevel-descriptionList {
+ display: none;
+ margin-inline-start:
+}
+
+radiogroup#securityLevel-radiogroup[value=safer] > vbox#securityLevel-vbox-safer > vbox#securityLevel-descriptionList,
+radiogroup#securityLevel-radiogroup[value=safest] > vbox#securityLevel-vbox-safest > vbox#securityLevel-descriptionList {
+ display: inherit;
+}
+
+vbox#securityLevel-descriptionList > description {
+ display: list-item;
+}
+
+vbox#securityLevel-vbox-standard,
+vbox#securityLevel-vbox-safer,
+vbox#securityLevel-vbox-safest {
+ margin-top: 0.4em;
+}
diff --git a/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml b/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
new file mode 100644
index 000000000000..b050dad81621
--- /dev/null
+++ b/browser/components/securitylevel/content/securityLevelPreferences.inc.xhtml
@@ -0,0 +1,67 @@
+<groupbox id="securityLevel-groupbox" data-category="panePrivacy" hidden="true">
+ <label><html:h2 id="securityLevel-header"/></label>
+ <vbox data-subcategory="securitylevel" flex="1">
+ <description flex="1">
+ <html:span id="securityLevel-overview" class="tail-with-learn-more"/>
+ <label id="securityLevel-learnMore" class="learnMore text-link" is="text-link"/>
+ </description>
+ <radiogroup id="securityLevel-radiogroup">
+ <vbox id="securityLevel-vbox-standard">
+ <hbox>
+ <radio value="standard"/>
+ <vbox>
+ <spacer flex="1"/>
+ <label id="securityLevel-customWarning"/>
+ <spacer flex="1"/>
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+ <description flex="1" class="indent">
+ <html:span id="securityLevel-summary" class="tail-with-learn-more"/>
+ <label id="securityLevel-restoreDefaults"
+ class="learnMore text-link"/>
+ </description>
+ </vbox>
+ <vbox id="securityLevel-vbox-safer">
+ <hbox>
+ <radio value="safer"/>
+ <vbox>
+ <spacer flex="1"/>
+ <label id="securityLevel-customWarning"/>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+ <description flex="1" class="indent">
+ <html:span id="securityLevel-summary" class="tail-with-learn-more"/>
+ <label id="securityLevel-restoreDefaults"
+ class="learnMore text-link"/>
+ </description>
+ <vbox id="securityLevel-descriptionList" class="indent">
+ <description id="securityLevel-description1" class="indent"/>
+ <description id="securityLevel-description2" class="indent"/>
+ <description id="securityLevel-description3" class="indent"/>
+ </vbox>
+ </vbox>
+ <vbox id="securityLevel-vbox-safest">
+ <hbox>
+ <radio value="safest"/>
+ <vbox>
+ <spacer flex="1"/>
+ <label id="securityLevel-customWarning"/>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+ <description flex="1" class="indent">
+ <html:span id="securityLevel-summary" class="tail-with-learn-more"/>
+ <label id="securityLevel-restoreDefaults"
+ class="learnMore text-link"/>
+ </description>
+ <vbox id="securityLevel-descriptionList" class="indent">
+ <description id="securityLevel-description1" class="indent"/>
+ <description id="securityLevel-description2" class="indent"/>
+ <description id="securityLevel-description3" class="indent"/>
+ </vbox>
+ </vbox>
+ </radiogroup>
+ </vbox>
+</groupbox>
diff --git a/browser/components/securitylevel/jar.mn b/browser/components/securitylevel/jar.mn
new file mode 100644
index 000000000000..61aa4169f9ec
--- /dev/null
+++ b/browser/components/securitylevel/jar.mn
@@ -0,0 +1,6 @@
+browser.jar:
+ content/browser/securitylevel/securityLevel.js (content/securityLevel.js)
+ content/browser/securitylevel/securityLevelPanel.css (content/securityLevelPanel.css)
+ content/browser/securitylevel/securityLevelButton.css (content/securityLevelButton.css)
+ content/browser/securitylevel/securityLevelPreferences.css (content/securityLevelPreferences.css)
+ content/browser/securitylevel/securityLevelIcon.svg (content/securityLevelIcon.svg)
diff --git a/browser/components/securitylevel/moz.build b/browser/components/securitylevel/moz.build
new file mode 100644
index 000000000000..2661ad7cb9f3
--- /dev/null
+++ b/browser/components/securitylevel/moz.build
@@ -0,0 +1 @@
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm
index 96d3de8186e2..0ccbbb41a782 100644
--- a/browser/modules/TorStrings.jsm
+++ b/browser/modules/TorStrings.jsm
@@ -230,6 +230,10 @@ var TorStrings = {
"advanced_security_settings",
"Advanced Security Settings\u2026"
),
+ change: getString(
+ "change",
+ "Change\u2026"
+ ),
};
return retval;
})() /* Security Level Strings */,
diff --git a/browser/themes/shared/customizableui/panelUI.inc.css b/browser/themes/shared/customizableui/panelUI.inc.css
index e1d64c707518..abecf34cdb92 100644
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -1430,7 +1430,8 @@ menuitem.panel-subview-footer@menuStateActive@,
#editBookmarkPanel toolbarseparator,
#downloadsPanel-mainView toolbarseparator,
.cui-widget-panelview menuseparator,
-.cui-widget-panel toolbarseparator {
+.cui-widget-panel toolbarseparator,
+#securityLevel-panel toolbarseparator {
appearance: none;
min-height: 0;
border-top: 1px solid var(--panel-separator-color);
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 40091: Load HTTPS Everywhere as a builtin addon in desktop
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 5024367261743d3ca9fd8ab0453946af5315c978
Author: Alex Catarineu <acat(a)torproject.org>
Date: Fri Sep 4 12:34:35 2020 +0200
Bug 40091: Load HTTPS Everywhere as a builtin addon in desktop
This loads HTTPS Everywhere as a builtin addon from a hardcoded
resource:// URI in desktop. It also ensures that the non-builtin
HTTPS Everywhere addon is always uninstalled on browser startup.
The reason of making this desktop-only is that there are some issues
when installing a builtin extension from geckoview side, making
the extension not available on first startup. So, at least for
now we handle the Fenix case separately. See #40118 for a followup
for investigating these.
---
browser/components/BrowserGlue.jsm | 37 ++++++++++++++++++++++
toolkit/components/extensions/Extension.jsm | 10 ++++--
.../mozapps/extensions/internal/XPIProvider.jsm | 13 ++++++++
3 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index fdb8a35f96ac..2953decbd4e4 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -45,6 +45,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
DownloadsViewableInternally:
"resource:///modules/DownloadsViewableInternally.jsm",
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
+ ExtensionData: "resource://gre/modules/Extension.jsm",
ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
FeatureGate: "resource://featuregates/FeatureGate.jsm",
FxAccounts: "resource://gre/modules/FxAccounts.jsm",
@@ -119,6 +120,13 @@ XPCOMUtils.defineLazyServiceGetters(this, {
PushService: ["@mozilla.org/push/Service;1", "nsIPushService"],
});
+XPCOMUtils.defineLazyServiceGetters(this, {
+ resProto: [
+ "@mozilla.org/network/protocol;1?name=resource",
+ "nsISubstitutingProtocolHandler",
+ ],
+});
+
const PREF_PDFJS_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state";
/**
@@ -1399,6 +1407,35 @@ BrowserGlue.prototype = {
"resource://builtin-themes/alpenglow/"
);
+ // Install https-everywhere builtin addon if needed.
+ (async () => {
+ const HTTPS_EVERYWHERE_ID = "https-everywhere-eff(a)eff.org";
+ const HTTPS_EVERYWHERE_BUILTIN_URL =
+ "resource://torbutton/content/extensions/https-everywhere/";
+ // This does something similar as GeckoViewWebExtension.jsm: it tries
+ // to load the manifest to retrieve the version of the builtin and
+ // compares it to the currently installed one to see whether we need
+ // to install or not. Here we delegate that to
+ // AddonManager.maybeInstallBuiltinAddon.
+ try {
+ const resolvedURI = Services.io.newURI(
+ resProto.resolveURI(Services.io.newURI(HTTPS_EVERYWHERE_BUILTIN_URL))
+ );
+ const extensionData = new ExtensionData(resolvedURI);
+ const manifest = await extensionData.loadManifest();
+
+ await AddonManager.maybeInstallBuiltinAddon(
+ HTTPS_EVERYWHERE_ID,
+ manifest.version,
+ HTTPS_EVERYWHERE_BUILTIN_URL
+ );
+ } catch (e) {
+ const log = Log.repository.getLogger("HttpsEverywhereBuiltinLoader");
+ log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
+ log.error("Could not install https-everywhere extension", e);
+ }
+ })();
+
if (AppConstants.MOZ_NORMANDY) {
Normandy.init();
}
diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm
index 08c5cf8a9190..783ec7c3391d 100644
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -267,6 +267,7 @@ const LOGGER_ID_BASE = "addons.webextension.";
const UUID_MAP_PREF = "extensions.webextensions.uuids";
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
+const PERSISTENT_EXTENSIONS = new Set(["https-everywhere-eff(a)eff.org"]);
const COMMENT_REGEXP = new RegExp(
String.raw`
@@ -413,7 +414,8 @@ var ExtensionAddonObserver = {
);
}
- if (!Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false)) {
+ if (!Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) &&
+ !PERSISTENT_EXTENSIONS.has(addon.id)) {
// Clear browser.storage.local backends.
AsyncShutdown.profileChangeTeardown.addBlocker(
`Clear Extension Storage ${addon.id} (File Backend)`,
@@ -461,7 +463,8 @@ var ExtensionAddonObserver = {
ExtensionPermissions.removeAll(addon.id);
- if (!Services.prefs.getBoolPref(LEAVE_UUID_PREF, false)) {
+ if (!Services.prefs.getBoolPref(LEAVE_UUID_PREF, false) &&
+ !PERSISTENT_EXTENSIONS.has(addon.id)) {
// Clear the entry in the UUID map
UUIDMap.remove(addon.id);
}
@@ -2696,7 +2699,8 @@ class Extension extends ExtensionData {
);
} else if (
this.startupReason === "ADDON_INSTALL" &&
- !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false)
+ !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false) &&
+ !PERSISTENT_EXTENSIONS.has(this.id)
) {
// If the extension has been just installed, set it as migrated,
// because there will not be any data to migrate.
diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
index 04d57a42348e..fd1e3f75935d 100644
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1495,6 +1495,19 @@ var XPIStates = {
continue;
}
+ // Uninstall HTTPS Everywhere if it is installed in the user profile.
+ if (
+ id === "https-everywhere-eff(a)eff.org" &&
+ loc.name === KEY_APP_PROFILE
+ ) {
+ logger.debug(
+ "Uninstalling the HTTPS Everywhere extension from user profile."
+ );
+ loc.installer.uninstallAddon(id);
+ changed = true;
+ continue;
+ }
+
let xpiState = loc.get(id);
if (!xpiState) {
// If the location is not supported for sideloading, skip new
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 40253: Explicitly allow NoScript in Private Browsing mode.
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 3af2a833492fee98ff88a81e9884bebadffa11cf
Author: Matthew Finkel <sysrqb(a)torproject.org>
Date: Fri Sep 3 14:58:28 2021 +0000
Bug 40253: Explicitly allow NoScript in Private Browsing mode.
---
toolkit/components/extensions/Extension.jsm | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm
index 783ec7c3391d..1d72f88b276d 100644
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -2644,6 +2644,15 @@ class Extension extends ExtensionData {
this.permissions.add(PRIVATE_ALLOWED_PERMISSION);
}
+ // Bug 40253: Explicitly allow NoScript in Private Browsing mode.
+ if (this.id === "{73a6fe31-595d-460b-a920-fcc0f8843232}") {
+ ExtensionPermissions.add(this.id, {
+ permissions: [PRIVATE_ALLOWED_PERMISSION],
+ origins: [],
+ });
+ this.permissions.add(PRIVATE_ALLOWED_PERMISSION);
+ }
+
// We only want to update the SVG_CONTEXT_PROPERTIES_PERMISSION during install and
// upgrade/downgrade startups.
if (INSTALL_AND_UPDATE_STARTUP_REASONS.has(this.startupReason)) {
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 21952: Implement Onion-Location
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 250c5c5f2f1486adffab64bd717adeacf17bb972
Author: Alex Catarineu <acat(a)torproject.org>
Date: Thu Mar 5 22:16:39 2020 +0100
Bug 21952: Implement Onion-Location
Whenever a valid Onion-Location HTTP header (or corresponding HTML
<meta> http-equiv attribute) is found in a document load, we either
redirect to it (if the user opted-in via preference) or notify the
presence of an onionsite alternative with a badge in the urlbar.
---
browser/base/content/browser.js | 12 ++
browser/base/content/navigator-toolbox.inc.xhtml | 3 +
browser/components/BrowserGlue.jsm | 13 ++
.../onionservices/OnionLocationChild.jsm | 39 +++++
.../onionservices/OnionLocationParent.jsm | 168 +++++++++++++++++++++
.../content/onionlocation-notification-icons.css | 5 +
.../onionservices/content/onionlocation-urlbar.css | 60 ++++++++
.../content/onionlocation-urlbar.inc.xhtml | 10 ++
.../onionservices/content/onionlocation.svg | 3 +
.../content/onionlocationPreferences.inc.xhtml | 11 ++
.../content/onionlocationPreferences.js | 31 ++++
browser/components/onionservices/jar.mn | 2 +
browser/components/onionservices/moz.build | 2 +
browser/components/preferences/privacy.inc.xhtml | 2 +
browser/components/preferences/privacy.js | 17 +++
browser/themes/shared/notification-icons.inc.css | 2 +
browser/themes/shared/urlbar-searchbar.inc.css | 1 +
dom/base/Document.cpp | 34 ++++-
dom/base/Document.h | 2 +
dom/webidl/Document.webidl | 8 +
modules/libpref/init/StaticPrefList.yaml | 5 +
xpcom/ds/StaticAtoms.py | 1 +
22 files changed, 430 insertions(+), 1 deletion(-)
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 13f330c20be3..34d9550057ee 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -49,6 +49,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
NetUtil: "resource://gre/modules/NetUtil.jsm",
NewTabUtils: "resource://gre/modules/NewTabUtils.jsm",
OpenInTabsUtils: "resource:///modules/OpenInTabsUtils.jsm",
+ OnionLocationParent: "resource:///modules/OnionLocationParent.jsm",
PageActions: "resource:///modules/PageActions.jsm",
PageThumbs: "resource://gre/modules/PageThumbs.jsm",
PanelMultiView: "resource:///modules/PanelMultiView.jsm",
@@ -5320,6 +5321,7 @@ var XULBrowserWindow = {
CFRPageActions.updatePageActions(gBrowser.selectedBrowser);
AboutReaderParent.updateReaderButton(gBrowser.selectedBrowser);
+ OnionLocationParent.updateOnionLocationBadge(gBrowser.selectedBrowser);
if (!gMultiProcessBrowser) {
// Bug 1108553 - Cannot rotate images with e10s
@@ -5810,6 +5812,16 @@ var CombinedStopReload = {
var TabsProgressListener = {
onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ // Clear OnionLocation UI
+ if (
+ aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
+ aRequest &&
+ aWebProgress.isTopLevel
+ ) {
+ OnionLocationParent.onStateChange(aBrowser);
+ }
+
// Collect telemetry data about tab load times.
if (
aWebProgress.isTopLevel &&
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index 810a77e57766..b881492864ae 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -359,6 +359,9 @@
onclick="FullZoom.reset(); FullZoom.resetScalingZoom();"
tooltip="dynamic-shortcut-tooltip"
hidden="true"/>
+
+#include ../../components/onionservices/content/onionlocation-urlbar.inc.xhtml
+
<hbox id="pageActionButton"
class="urlbar-page-action urlbar-icon-wrapper"
role="button"
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 4c340b71587c..5588d1463d94 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -570,6 +570,19 @@ let JSWINDOWACTORS = {
allFrames: true,
},
+ OnionLocation: {
+ parent: {
+ moduleURI: "resource:///modules/OnionLocationParent.jsm",
+ },
+ child: {
+ moduleURI: "resource:///modules/OnionLocationChild.jsm",
+ events: {
+ pageshow: { mozSystemGroup: true },
+ },
+ },
+ messageManagerGroups: ["browsers"],
+ },
+
PageInfo: {
child: {
moduleURI: "resource:///actors/PageInfoChild.jsm",
diff --git a/browser/components/onionservices/OnionLocationChild.jsm b/browser/components/onionservices/OnionLocationChild.jsm
new file mode 100644
index 000000000000..9e00054ac56c
--- /dev/null
+++ b/browser/components/onionservices/OnionLocationChild.jsm
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["OnionLocationChild"];
+
+class OnionLocationChild extends JSWindowActorChild {
+ handleEvent(event) {
+ this.onPageShow(event);
+ }
+
+ onPageShow(event) {
+ if (event.target != this.document) {
+ return;
+ }
+ const onionLocationURI = this.document.onionLocationURI;
+ if (onionLocationURI) {
+ this.sendAsyncMessage("OnionLocation:Set");
+ }
+ }
+
+ receiveMessage(aMessage) {
+ if (aMessage.name == "OnionLocation:Refresh") {
+ const doc = this.document;
+ const docShell = this.docShell;
+ const onionLocationURI = doc.onionLocationURI;
+ const refreshURI = docShell.QueryInterface(Ci.nsIRefreshURI);
+ if (onionLocationURI && refreshURI) {
+ refreshURI.refreshURI(
+ onionLocationURI,
+ doc.nodePrincipal,
+ 0,
+ false,
+ true
+ );
+ }
+ }
+ }
+}
diff --git a/browser/components/onionservices/OnionLocationParent.jsm b/browser/components/onionservices/OnionLocationParent.jsm
new file mode 100644
index 000000000000..f6250e554862
--- /dev/null
+++ b/browser/components/onionservices/OnionLocationParent.jsm
@@ -0,0 +1,168 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["OnionLocationParent"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
+// Prefs
+const NOTIFICATION_PREF = "privacy.prioritizeonions.showNotification";
+const PRIORITIZE_ONIONS_PREF = "privacy.prioritizeonions.enabled";
+
+// Element IDs
+const ONIONLOCATION_BOX_ID = "onion-location-box";
+const ONIONLOCATION_BUTTON_ID = "onion-location-button";
+const ONIONLOCATION_LABEL_ID = "onion-label";
+
+// Notification IDs
+const NOTIFICATION_ID = "onion-location";
+const NOTIFICATION_ANCHOR_ID = "onionlocation";
+
+// Strings
+const STRING_ONION_AVAILABLE = TorStrings.onionLocation.onionAvailable;
+const NOTIFICATION_CANCEL_LABEL = TorStrings.onionLocation.notNow;
+const NOTIFICATION_CANCEL_ACCESSKEY = TorStrings.onionLocation.notNowAccessKey;
+const NOTIFICATION_OK_LABEL = TorStrings.onionLocation.alwaysPrioritize;
+const NOTIFICATION_OK_ACCESSKEY =
+ TorStrings.onionLocation.alwaysPrioritizeAccessKey;
+const NOTIFICATION_TITLE = TorStrings.onionLocation.tryThis;
+const NOTIFICATION_DESCRIPTION = TorStrings.onionLocation.description;
+const NOTIFICATION_LEARN_MORE_URL = TorStrings.onionLocation.learnMoreURL;
+
+class OnionLocationParent extends JSWindowActorParent {
+ // Listeners are added in BrowserGlue.jsm
+ receiveMessage(aMsg) {
+ switch (aMsg.name) {
+ case "OnionLocation:Set":
+ let browser = this.browsingContext.embedderElement;
+ OnionLocationParent.setOnionLocation(browser);
+ break;
+ }
+ }
+
+ static buttonClick(event) {
+ if (event.button !== 0) {
+ return;
+ }
+ const win = event.target.ownerGlobal;
+ if (win.gBrowser) {
+ const browser = win.gBrowser.selectedBrowser;
+ OnionLocationParent.redirect(browser);
+ }
+ }
+
+ static redirect(browser) {
+ let windowGlobal = browser.browsingContext.currentWindowGlobal;
+ let actor = windowGlobal.getActor("OnionLocation");
+ if (actor) {
+ actor.sendAsyncMessage("OnionLocation:Refresh", {});
+ OnionLocationParent.setDisabled(browser);
+ }
+ }
+
+ static onStateChange(browser) {
+ delete browser._onionLocation;
+ OnionLocationParent.hideNotification(browser);
+ }
+
+ static setOnionLocation(browser) {
+ browser._onionLocation = true;
+ let tabBrowser = browser.getTabBrowser();
+ if (tabBrowser && browser === tabBrowser.selectedBrowser) {
+ OnionLocationParent.updateOnionLocationBadge(browser);
+ }
+ }
+
+ static hideNotification(browser) {
+ const win = browser.ownerGlobal;
+ if (browser._onionLocationPrompt) {
+ win.PopupNotifications.remove(browser._onionLocationPrompt);
+ }
+ }
+
+ static showNotification(browser) {
+ const mustShow = Services.prefs.getBoolPref(NOTIFICATION_PREF, true);
+ if (!mustShow) {
+ return;
+ }
+
+ const win = browser.ownerGlobal;
+ Services.prefs.setBoolPref(NOTIFICATION_PREF, false);
+
+ const mainAction = {
+ label: NOTIFICATION_OK_LABEL,
+ accessKey: NOTIFICATION_OK_ACCESSKEY,
+ callback() {
+ Services.prefs.setBoolPref(PRIORITIZE_ONIONS_PREF, true);
+ OnionLocationParent.redirect(browser);
+ win.openPreferences("privacy-onionservices");
+ },
+ };
+
+ const cancelAction = {
+ label: NOTIFICATION_CANCEL_LABEL,
+ accessKey: NOTIFICATION_CANCEL_ACCESSKEY,
+ callback: () => {},
+ };
+
+ const options = {
+ autofocus: true,
+ persistent: true,
+ removeOnDismissal: false,
+ eventCallback(aTopic) {
+ if (aTopic === "removed") {
+ delete browser._onionLocationPrompt;
+ delete browser.onionpopupnotificationanchor;
+ }
+ },
+ learnMoreURL: NOTIFICATION_LEARN_MORE_URL,
+ displayURI: {
+ hostPort: NOTIFICATION_TITLE, // This is hacky, but allows us to have a title without extra markup/css.
+ },
+ hideClose: true,
+ popupIconClass: "onionlocation-notification-icon",
+ };
+
+ // A hacky way of setting the popup anchor outside the usual url bar icon box
+ // onionlocationpopupnotificationanchor comes from `${ANCHOR_ID}popupnotificationanchor`
+ // From https://searchfox.org/mozilla-esr68/rev/080f9ed47742644d2ff84f7aa0b10aea5c4…
+ browser.onionlocationpopupnotificationanchor = win.document.getElementById(
+ ONIONLOCATION_BUTTON_ID
+ );
+
+ browser._onionLocationPrompt = win.PopupNotifications.show(
+ browser,
+ NOTIFICATION_ID,
+ NOTIFICATION_DESCRIPTION,
+ NOTIFICATION_ANCHOR_ID,
+ mainAction,
+ [cancelAction],
+ options
+ );
+ }
+
+ static setEnabled(browser) {
+ const win = browser.ownerGlobal;
+ const label = win.document.getElementById(ONIONLOCATION_LABEL_ID);
+ label.textContent = STRING_ONION_AVAILABLE;
+ const elem = win.document.getElementById(ONIONLOCATION_BOX_ID);
+ elem.removeAttribute("hidden");
+ }
+
+ static setDisabled(browser) {
+ const win = browser.ownerGlobal;
+ const elem = win.document.getElementById(ONIONLOCATION_BOX_ID);
+ elem.setAttribute("hidden", true);
+ }
+
+ static updateOnionLocationBadge(browser) {
+ if (browser._onionLocation) {
+ OnionLocationParent.setEnabled(browser);
+ OnionLocationParent.showNotification(browser);
+ } else {
+ OnionLocationParent.setDisabled(browser);
+ }
+ }
+}
diff --git a/browser/components/onionservices/content/onionlocation-notification-icons.css b/browser/components/onionservices/content/onionlocation-notification-icons.css
new file mode 100644
index 000000000000..7c8a6d892c6f
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocation-notification-icons.css
@@ -0,0 +1,5 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+
+.onionlocation-notification-icon {
+ display: none;
+}
\ No newline at end of file
diff --git a/browser/components/onionservices/content/onionlocation-urlbar.css b/browser/components/onionservices/content/onionlocation-urlbar.css
new file mode 100644
index 000000000000..581175fdbf6b
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocation-urlbar.css
@@ -0,0 +1,60 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+
+#onion-location-box {
+ height: 28px;
+
+ background-color: var(--purple-60);
+ -moz-context-properties: fill;
+ fill: white;
+}
+
+#onion-location-box:hover {
+ background-color: var(--purple-70);
+}
+
+#onion-location-box:active {
+ background-color: var(--purple-80);
+}
+
+@media (prefers-color-scheme: dark) {
+ #onion-location-box {
+ background-color: var(--purple-50);
+ }
+
+ #onion-location-box:hover {
+ background-color: var(--purple-60);
+ }
+
+ #onion-location-box:active {
+ background-color: var(--purple-70);
+ }
+}
+
+#onion-location-button {
+ list-style-image: url(chrome://browser/content/onionservices/onionlocation.svg);
+ padding-inline-start: 0.5em;
+}
+
+label#onion-label {
+ line-height: 28px;
+ margin: 0;
+ padding-block: 0;
+ padding-inline: 0.5em;
+ color: white;
+ font-weight: normal;
+}
+
+/* set appropriate sizes for the non-standard ui densities */
+:root[uidensity=compact] hbox.urlbar-page-action#onion-location-box {
+ height: 24px;
+}
+:root[uidensity=compact] label#onion-label {
+ line-height: 24px;
+}
+
+:root[uidensity=touch] hbox.urlbar-page-action#onion-location-box {
+ height: 30px;
+}
+:root[uidensity=touch] label#onion-label {
+ line-height: 30px;
+}
diff --git a/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml b/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml
new file mode 100644
index 000000000000..b612a4236f3c
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocation-urlbar.inc.xhtml
@@ -0,0 +1,10 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<hbox id="onion-location-box"
+ class="urlbar-icon-wrapper urlbar-page-action"
+ role="button"
+ hidden="true"
+ onclick="OnionLocationParent.buttonClick(event);">
+ <image id="onion-location-button" role="presentation"/>
+ <hbox id="onion-label-container"><label id="onion-label"/></hbox>
+</hbox>
diff --git a/browser/components/onionservices/content/onionlocation.svg b/browser/components/onionservices/content/onionlocation.svg
new file mode 100644
index 000000000000..37f40ac1812f
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocation.svg
@@ -0,0 +1,3 @@
+<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="m8.016411 14.54499v-0.969784c3.071908-0.0089 5.559239-2.501304 5.559239-5.575429 0-3.073903-2.487331-5.566336-5.559239-5.575206v-0.9697843c3.607473 0.00909 6.528802 2.935521 6.528802 6.544991 0 3.609691-2.921329 6.536342-6.528802 6.545213zm0-3.394356c1.732661-0.0091 3.135111-1.415756 3.135111-3.150857 0-1.734878-1.402451-3.141542-3.135111-3.150634v-0.9695626c2.268448 0.00887 4.104895 1.849753 4.104895 4.120197 0 2.270666-1.836447 4.111549-4.104895 4.120419zm0-4.846926c0.9294227 0.00887 1.680545 0.7644289 1.680545 1.696069 0 0.9318627-0.7511226 1.687421-1.680545 1.696291zm-8.016411 1.696069c0 4.418473 3.581527 8.000222 8 8.000222 4.418251 0 8-3.581749 8-8.000222 0-4.418251-3.581749-7.999778-8-7.999778-4.418473 0-8 3.581527-8 7.999778z" />
+</svg>
\ No newline at end of file
diff --git a/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml b/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml
new file mode 100644
index 000000000000..c285f403f99b
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocationPreferences.inc.xhtml
@@ -0,0 +1,11 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<groupbox id="onionServicesGroup" data-category="panePrivacy" data-subcategory="onionservices" hidden="true">
+ <label><html:h2 id="onionServicesTitle"></html:h2></label>
+ <label><label class="tail-with-learn-more" id="prioritizeOnionsDesc"></label><label
+ class="learnMore" is="text-link" id="onionServicesLearnMore"></label></label>
+ <radiogroup id="prioritizeOnionsRadioGroup" aria-labelledby="prioritizeOnionsDesc" preference="privacy.prioritizeonions.enabled">
+ <radio id="onionServicesRadioAlways" value="true"/>
+ <radio id="onionServicesRadioAsk" value="false"/>
+ </radiogroup>
+</groupbox>
diff --git a/browser/components/onionservices/content/onionlocationPreferences.js b/browser/components/onionservices/content/onionlocationPreferences.js
new file mode 100644
index 000000000000..aa569b54721c
--- /dev/null
+++ b/browser/components/onionservices/content/onionlocationPreferences.js
@@ -0,0 +1,31 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorStrings",
+ "resource:///modules/TorStrings.jsm"
+);
+
+const OnionLocationPreferences = {
+ init() {
+ document.getElementById("onionServicesTitle").textContent =
+ TorStrings.onionLocation.onionServicesTitle;
+ document.getElementById("prioritizeOnionsDesc").textContent =
+ TorStrings.onionLocation.prioritizeOnionsDescription;
+ const learnMore = document.getElementById("onionServicesLearnMore");
+ learnMore.textContent = TorStrings.onionLocation.learnMore;
+ learnMore.href = TorStrings.onionLocation.learnMoreURL;
+ document.getElementById("onionServicesRadioAlways").label =
+ TorStrings.onionLocation.always;
+ document.getElementById("onionServicesRadioAsk").label =
+ TorStrings.onionLocation.askEverytime;
+ },
+};
+
+Object.defineProperty(this, "OnionLocationPreferences", {
+ value: OnionLocationPreferences,
+ enumerable: true,
+ writable: false,
+});
diff --git a/browser/components/onionservices/jar.mn b/browser/components/onionservices/jar.mn
index 9d6ce88d1841..f45b16dc5d29 100644
--- a/browser/components/onionservices/jar.mn
+++ b/browser/components/onionservices/jar.mn
@@ -7,3 +7,5 @@ browser.jar:
content/browser/onionservices/onionservices.css (content/onionservices.css)
content/browser/onionservices/savedKeysDialog.js (content/savedKeysDialog.js)
content/browser/onionservices/savedKeysDialog.xhtml (content/savedKeysDialog.xhtml)
+ content/browser/onionservices/onionlocationPreferences.js (content/onionlocationPreferences.js)
+ content/browser/onionservices/onionlocation.svg (content/onionlocation.svg)
diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build
index 815685322024..8027233d65a6 100644
--- a/browser/components/onionservices/moz.build
+++ b/browser/components/onionservices/moz.build
@@ -4,4 +4,6 @@ EXTRA_JS_MODULES += [
"ExtensionMessaging.jsm",
"HttpsEverywhereControl.jsm",
"OnionAliasStore.jsm",
+ "OnionLocationChild.jsm",
+ "OnionLocationParent.jsm",
]
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml
index 18d01fee6b33..66d7ef724f83 100644
--- a/browser/components/preferences/privacy.inc.xhtml
+++ b/browser/components/preferences/privacy.inc.xhtml
@@ -14,6 +14,8 @@
<html:h1 data-l10n-id="privacy-header"/>
</hbox>
+#include ../onionservices/content/onionlocationPreferences.inc.xhtml
+
<!-- Tracking / Content Blocking -->
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" aria-describedby="contentBlockingDescription">
<label id="contentBlockingHeader"><html:h2 data-l10n-id="content-blocking-enhanced-tracking-protection"/></label>
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index 932d4291e486..1985b5489fc1 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -93,6 +93,12 @@ XPCOMUtils.defineLazyScriptGetter(
"chrome://browser/content/securitylevel/securityLevel.js"
);
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["OnionLocationPreferences"],
+ "chrome://browser/content/onionservices/onionlocationPreferences.js"
+);
+
XPCOMUtils.defineLazyServiceGetter(
this,
"listManager",
@@ -169,6 +175,9 @@ Preferences.addAll([
// Do not track
{ id: "privacy.donottrackheader.enabled", type: "bool" },
+ // Onion Location
+ { id: "privacy.prioritizeonions.enabled", type: "bool" },
+
// Media
{ id: "media.autoplay.default", type: "int" },
@@ -333,6 +342,13 @@ var gPrivacyPane = {
window.addEventListener("unload", unload);
},
+ /**
+ * Show the OnionLocation preferences UI
+ */
+ _initOnionLocation() {
+ OnionLocationPreferences.init();
+ },
+
/**
* Whether the prompt to restart Firefox should appear when changing the autostart pref.
*/
@@ -530,6 +546,7 @@ var gPrivacyPane = {
this._initTrackingProtectionExtensionControl();
OnionServicesAuthPreferences.init();
this._initSecurityLevel();
+ this._initOnionLocation();
Services.telemetry.setEventRecordingEnabled("pwmgr", true);
diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css
index 67dd640baf16..83248f71c60d 100644
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -449,3 +449,5 @@
-moz-context-properties: fill;
fill: var(--panel-banner-item-warning-icon-bgcolor);
}
+
+%include ../../components/onionservices/content/onionlocation-notification-icons.css
diff --git a/browser/themes/shared/urlbar-searchbar.inc.css b/browser/themes/shared/urlbar-searchbar.inc.css
index f91278ce5ed3..d7dc7df17f19 100644
--- a/browser/themes/shared/urlbar-searchbar.inc.css
+++ b/browser/themes/shared/urlbar-searchbar.inc.css
@@ -746,5 +746,6 @@ moz-input-box > menupopup .context-menu-add-engine > .menu-iconic-left::after {
opacity: 0.69;
}
+%include ../../components/onionservices/content/onionlocation-urlbar.css
%include ../../components/torconnect/content/torconnect-urlbar.css
diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp
index 1e75ed7fe032..2973457ec3de 100644
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -2779,6 +2779,7 @@ void Document::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
// mDocumentURI.
mDocumentBaseURI = nullptr;
mChromeXHRDocBaseURI = nullptr;
+ mOnionLocationURI = nullptr;
// Check if the current document is the top-level DevTools document.
// For inner DevTools frames, mIsDevToolsDocument will be set when
@@ -6486,6 +6487,22 @@ void Document::GetHeaderData(nsAtom* aHeaderField, nsAString& aData) const {
}
}
+static bool IsValidOnionLocation(nsIURI* aDocumentURI,
+ nsIURI* aOnionLocationURI) {
+ bool isHttpish;
+ nsAutoCString host;
+ return aDocumentURI && aOnionLocationURI &&
+ NS_SUCCEEDED(aDocumentURI->SchemeIs("https", &isHttpish)) &&
+ isHttpish && NS_SUCCEEDED(aDocumentURI->GetAsciiHost(host)) &&
+ !StringEndsWith(host, ".onion"_ns) &&
+ ((NS_SUCCEEDED(aOnionLocationURI->SchemeIs("http", &isHttpish)) &&
+ isHttpish) ||
+ (NS_SUCCEEDED(aOnionLocationURI->SchemeIs("https", &isHttpish)) &&
+ isHttpish)) &&
+ NS_SUCCEEDED(aOnionLocationURI->GetAsciiHost(host)) &&
+ StringEndsWith(host, ".onion"_ns);
+}
+
void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
if (!aHeaderField) {
NS_ERROR("null headerField");
@@ -6560,6 +6577,21 @@ void Document::SetHeaderData(nsAtom* aHeaderField, const nsAString& aData) {
aHeaderField == nsGkAtoms::handheldFriendly) {
mViewportType = Unknown;
}
+
+ if (aHeaderField == nsGkAtoms::headerOnionLocation && !aData.IsEmpty()) {
+ nsCOMPtr<nsIURI> onionURI;
+ if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(onionURI), aData)) &&
+ IsValidOnionLocation(Document::GetDocumentURI(), onionURI)) {
+ if (StaticPrefs::privacy_prioritizeonions_enabled()) {
+ nsCOMPtr<nsIRefreshURI> refresher(mDocumentContainer);
+ if (refresher) {
+ refresher->RefreshURI(onionURI, NodePrincipal(), 0, false, true);
+ }
+ } else {
+ mOnionLocationURI = onionURI;
+ }
+ }
+ }
}
void Document::TryChannelCharset(nsIChannel* aChannel, int32_t& aCharsetSource,
@@ -10697,7 +10729,7 @@ void Document::RetrieveRelevantHeaders(nsIChannel* aChannel) {
static const char* const headers[] = {
"default-style", "content-style-type", "content-language",
"content-disposition", "refresh", "x-dns-prefetch-control",
- "x-frame-options",
+ "x-frame-options", "onion-location",
// add more http headers if you need
// XXXbz don't add content-location support without reading bug
// 238654 and its dependencies/dups first.
diff --git a/dom/base/Document.h b/dom/base/Document.h
index 69e59d09b924..7ef73651d47f 100644
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -3371,6 +3371,7 @@ class Document : public nsINode,
void ReleaseCapture() const;
void MozSetImageElement(const nsAString& aImageElementId, Element* aElement);
nsIURI* GetDocumentURIObject() const;
+ nsIURI* GetOnionLocationURI() const { return mOnionLocationURI; }
// Not const because all the fullscreen goop is not const
const char* GetFullscreenError(CallerType);
bool FullscreenEnabled(CallerType aCallerType) {
@@ -4354,6 +4355,7 @@ class Document : public nsINode,
nsCOMPtr<nsIURI> mChromeXHRDocURI;
nsCOMPtr<nsIURI> mDocumentBaseURI;
nsCOMPtr<nsIURI> mChromeXHRDocBaseURI;
+ nsCOMPtr<nsIURI> mOnionLocationURI;
// The base domain of the document for third-party checks.
nsCString mBaseDomain;
diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl
index a139ace11d4a..d934cf8da045 100644
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -711,3 +711,11 @@ partial interface Document {
[ChromeOnly]
void setNotifyFormOrPasswordRemoved(boolean aShouldNotify);
};
+
+/**
+ * Extension to allows chrome JS to know whether the document has a valid
+ * Onion-Location that we could redirect to.
+ */
+partial interface Document {
+ [ChromeOnly] readonly attribute URI? onionLocationURI;
+};
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
index 26ba559b79b0..eeea2e3c41ce 100644
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -10483,6 +10483,11 @@
value: ""
mirror: never
+- name: privacy.prioritizeonions.enabled
+ type: RelaxedAtomicBool
+ value: false
+ mirror: always
+
#---------------------------------------------------------------------------
# Prefs starting with "prompts."
#---------------------------------------------------------------------------
diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
index 2f5be143517b..f620f57a1213 100644
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -821,6 +821,7 @@ STATIC_ATOMS = [
Atom("oninputsourceschange", "oninputsourceschange"),
Atom("oninstall", "oninstall"),
Atom("oninvalid", "oninvalid"),
+ Atom("headerOnionLocation", "onion-location"),
Atom("onkeydown", "onkeydown"),
Atom("onkeypress", "onkeypress"),
Atom("onkeyup", "onkeyup"),
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 27511: Add new identity button to toolbar
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit ce592e7bb31b3c948147e634b914c1013673f5db
Author: Alex Catarineu <acat(a)torproject.org>
Date: Fri Oct 4 19:08:33 2019 +0200
Bug 27511: Add new identity button to toolbar
Also added 'New circuit for this site' button to CustomizableUI, but
not visible by default.
---
browser/base/content/navigator-toolbox.inc.xhtml | 10 ++++++++++
.../components/customizableui/CustomizableUI.jsm | 21 +++++++++++++++++++++
browser/themes/shared/icons/new_circuit.svg | 6 ++++++
browser/themes/shared/icons/new_identity.svg | 9 +++++++++
browser/themes/shared/jar.inc.mn | 3 +++
browser/themes/shared/toolbarbutton-icons.inc.css | 8 ++++++++
6 files changed, 57 insertions(+)
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index 6ac72cb889bc..e10e0580b8ec 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -573,6 +573,16 @@
ondragenter="newWindowButtonObserver.onDragOver(event)"
ondragexit="newWindowButtonObserver.onDragExit(event)"/>
+ <toolbarbutton id="new-identity-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&torbutton.context_menu.new_identity;"
+ oncommand="torbutton_new_identity();"
+ tooltiptext="&torbutton.context_menu.new_identity;"/>
+
+ <toolbarbutton id="new-circuit-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&torbutton.context_menu.new_circuit;"
+ oncommand="torbutton_new_circuit();"
+ tooltiptext="&torbutton.context_menu.new_circuit;"/>
+
<toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
observes="View:FullScreen"
type="checkbox"
diff --git a/browser/components/customizableui/CustomizableUI.jsm b/browser/components/customizableui/CustomizableUI.jsm
index 8649d93347c4..5c5ab909b9a4 100644
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -79,6 +79,8 @@ const kSubviewEvents = ["ViewShowing", "ViewHiding"];
*/
var kVersion = 17;
+var kTorVersion = 1;
+
/**
* Buttons removed from built-ins by version they were removed. kVersion must be
* bumped any time a new id is added to this. Use the button id as key, and
@@ -619,6 +621,20 @@ var CustomizableUIInternal = {
navbarPlacements.splice(newPosition, 0, "save-to-pocket-button");
}
}
+
+ let currentTorVersion = gSavedState.currentTorVersion;
+ if (currentTorVersion < 1 && gSavedState.placements) {
+ let navbarPlacements = gSavedState.placements[CustomizableUI.AREA_NAVBAR];
+ if (navbarPlacements) {
+ let secLevelIndex = navbarPlacements.indexOf("security-level-button");
+ if (secLevelIndex === -1) {
+ let urlbarIndex = navbarPlacements.indexOf("urlbar-container");
+ secLevelIndex = urlbarIndex + 1;
+ navbarPlacements.splice(secLevelIndex, 0, "security-level-button");
+ }
+ navbarPlacements.splice(secLevelIndex + 1, 0, "new-identity-button");
+ }
+ }
},
_updateForNewProtonVersion() {
@@ -2528,6 +2544,10 @@ var CustomizableUIInternal = {
gSavedState.currentVersion = 0;
}
+ if (!("currentTorVersion" in gSavedState)) {
+ gSavedState.currentTorVersion = 0;
+ }
+
gSeenWidgets = new Set(gSavedState.seen || []);
gDirtyAreaCache = new Set(gSavedState.dirtyAreaCache || []);
gNewElementCount = gSavedState.newElementCount || 0;
@@ -2606,6 +2626,7 @@ var CustomizableUIInternal = {
seen: gSeenWidgets,
dirtyAreaCache: gDirtyAreaCache,
currentVersion: kVersion,
+ currentTorVersion: kTorVersion,
newElementCount: gNewElementCount,
};
diff --git a/browser/themes/shared/icons/new_circuit.svg b/browser/themes/shared/icons/new_circuit.svg
new file mode 100644
index 000000000000..ddc819946818
--- /dev/null
+++ b/browser/themes/shared/icons/new_circuit.svg
@@ -0,0 +1,6 @@
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g stroke="none" stroke-width="1" fill="context-fill" fill-rule="evenodd" opacity="context-fill-opacity">
+ <path d="m10.707 6h3.993l.3-.3v-3.993c.0002-.09902-.0291-.19586-.084-.27825s-.1331-.14661-.2245-.18453c-.0915-.03792-.1922-.04782-.2893-.02845-.0971.01936-.1863.06713-.2562.13723l-1.459 1.459c-1.2817-1.16743-2.95335-1.813714-4.687-1.812-3.859 0-7 3.141-7 7s3.141 7 7 7c1.74123.007 3.422-.6379 4.7116-1.8079 1.2896-1.1701 2.0945-2.7804 2.2564-4.5141.0156-.1649-.0348-.32927-.1401-.4571s-.2571-.2087-.4219-.2249c-.1644-.01324-.3275.03801-.4548.1429s-.2088.2552-.2272.4191c-.1334 1.42392-.7948 2.7464-1.854 3.7072-1.0593.9609-2.43986 1.4905-3.87 1.4848-3.171 0-5.75-2.579-5.75-5.75s2.579-5.75 5.75-5.75c1.40277-.00207 2.7572.5123 3.805 1.445l-1.451 1.451c-.07.06987-.1178.15895-.1372.25597-.0194.09701-.0096.1976.0282.28903.0378.09144.1019.1696.1841.22461.0823.055.179.08437.2779.08439z"/>
+ <path d="m8 12.5c-2.48528 0-4.5-2.0147-4.5-4.5 0-2.48528 2.01472-4.5 4.5-4.5z"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/icons/new_identity.svg b/browser/themes/shared/icons/new_identity.svg
new file mode 100644
index 000000000000..096ff169c02f
--- /dev/null
+++ b/browser/themes/shared/icons/new_identity.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <g fill="context-fill" fill-opacity="context-fill-opacity">
+ <path d="m13.5383 14.5627c-.1712-.0053-.3194-.1334-.3505-.3028-.0419-.294-.1441-.5789-.3001-.8369-.2583-.1558-.5436-.2579-.838-.2998-.1694-.0313-.2974-.1793-.3026-.3501-.0053-.1708.1136-.3146.2813-.3402.2944-.0329.5762-.1254.8284-.272.1426-.2476.2313-.5243.2608-.8129.0237-.1679.1662-.2884.3372-.2851.1699.0042.3181.1295.3517.2973.0471.2931.1533.5763.312.8323.2565.1573.5396.263.8326.3109.1682.0345.2929.1836.2958.3536.0028.17-.1171.3116-.2843.3357-.2894.0285-.5669.1172-.8147.2604-.1462.2521-.2386.5335-.2717.8274-.025.167-.1675.2861-.3379.2822z"/>
+ <path d="m6.49858 2.99992c-.14675-.00459-.27377-.11436-.3004-.25961-.03593-.25196-.12354-.49621-.25729-.71731-.22137-.13358-.46594-.22109-.71822-.25699-.14526-.02682-.25492-.15363-.25945-.30004-.00454-.14641.09737-.26967.24112-.29164.25236-.02817.49393-.10747.71013-.233093.12217-.2123.19825-.449454.22353-.696834.0203-.143878.14242-.24714456.28897-.24434753.14565.00358504.27273.11100153.30149.25484453.0404.251183.13139.493923.2674.713349.21988.134841.46256.225461.71364.266481.14417.02957.25114.15744.25358.30313.00244.1457-.10035.26707-.24368.28774-.2481.02441-.48592.10041-.69835.22319-.1253.2161-.20449.45729-.23284.7092-.0214.14312-.14361.24521-.28963.24193z"/>
+ <path d="m1.82093 5.3609c-.15279-.00473-.28512-.11875-.31315-.26981-.02739-.18014-.08781-.35525-.1782-.51643-.16152-.09021-.336989-.15052-.517512-.17788-.151437-.02794-.265749-.16003-.270474-.31254-.004724-.15251.101518-.2809.251381-.30378.181146-.02145.355265-.07593.513815-.16075.08209-.15545.13363-.32622.15197-.50355.02095-.15059.14903-.25861.3025-.25512.15164.00368.28404.11525.31428.26484.03021.18029.09338.35503.18632.51538.16048.09192.33508.15452.51517.18469.1503.0308.26181.164.26435.31577.00254.15176-.10462.27819-.25404.29971-.17764.01914-.34855.07141-.50396.15412-.08502.1582-.13963.33194-.16114.5127-.022.14911-.14912.25571-.30131.25265z"/>
+ <path clip-rule="evenodd" d="m15.3213 1.06694c.2441-.244076.2441-.639804 0-.883882-.2441-.2440775-.6398-.2440774-.8839 0l-5.96506 5.965062h-.50519c-1.996-1.09517-4.49023.42233-6.49079 1.63948-.41545.25277-.80961.49258-1.173597.69335-.16756.10002-.289261.26641-.30145394.48048-.01219156.21407.06079654.41038.21802994.56743l1.243691 1.24224 2.37084-1.02603c.15392-.06661.30331.14022.18601.25753l-1.66213 1.6621 1.46329 1.4616 1.66126-1.6613c.1173-.1173.32413.0321.25752.186l-1.02482 2.3682 1.25462 1.2531c.15724.157.35379.23.56815.2178.19095-.0561.35851-.1561.45869-.3234.20012-.3592.43577-.7455.68321-1.1511 1.22241-2.0039 2.73233-4.47901 1.66484-6.47533v-.49654zm-7.46715 6.55077c1.12692 1.12692.64113 2.69369-.05278 3.70149h-.50137l-3.13-3.1492v-.5c1.00858-.68566 2.56556-1.17088 3.68415-.05229z" fill-rule="evenodd"/>
+ </g>
+</svg>
diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn
index f76184171ddd..b2e469b90aa8 100644
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -236,3 +236,6 @@
skin/classic/browser/places/tree-icons.css (../shared/places/tree-icons.css)
skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css)
skin/classic/browser/privatebrowsing/favicon.svg (../shared/privatebrowsing/favicon.svg)
+
+ skin/classic/browser/new_circuit.svg (../shared/icons/new_circuit.svg)
+ skin/classic/browser/new_identity.svg (../shared/icons/new_identity.svg)
diff --git a/browser/themes/shared/toolbarbutton-icons.inc.css b/browser/themes/shared/toolbarbutton-icons.inc.css
index 76d3f4212406..e3e6f6486999 100644
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -253,6 +253,14 @@ toolbar[brighttext]:-moz-lwtheme {
list-style-image: url("chrome://browser/skin/new-tab.svg");
}
+#new-identity-button {
+ list-style-image: url("chrome://browser/skin/new_identity.svg");
+}
+
+#new-circuit-button {
+ list-style-image: url("chrome://browser/skin/new_circuit.svg");
+}
+
#privatebrowsing-button {
list-style-image: url("chrome://browser/skin/privateBrowsing.svg");
}
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 30237: Add v3 onion services client authentication prompt
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit d07242fde5cab23362bebbfbfeab3286f9d76c6c
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Tue Nov 12 16:11:05 2019 -0500
Bug 30237: Add v3 onion services client authentication prompt
When Tor informs the browser that client authentication is needed,
temporarily load about:blank instead of about:neterror and prompt
for the user's key.
If a correctly formatted key is entered, use Tor's ONION_CLIENT_AUTH_ADD
control port command to add the key (via Torbutton's control port
module) and reload the page.
If the user cancels the prompt, display the standard about:neterror
"Unable to connect" page. This requires a small change to
browser/actors/NetErrorChild.jsm to account for the fact that the
docShell no longer has the failedChannel information. The failedChannel
is used to extract TLS-related error info, which is not applicable
in the case of a canceled .onion authentication prompt.
Add a leaveOpen option to PopupNotifications.show so we can display
error messages within the popup notification doorhanger without
closing the prompt.
Add support for onion services strings to the TorStrings module.
Add support for Tor extended SOCKS errors (Tor proposal 304) to the
socket transport and SOCKS layers. Improved display of all of these
errors will be implemented as part of bug 30025.
Also fixes bug 19757:
Add a "Remember this key" checkbox to the client auth prompt.
Add an "Onion Services Authentication" section within the
about:preferences "Privacy & Security section" to allow
viewing and removal of v3 onion client auth keys that have
been stored on disk.
Also fixes bug 19251: use enhanced error pages for onion service errors.
---
browser/actors/NetErrorChild.jsm | 7 +
browser/base/content/browser.js | 10 +
browser/base/content/browser.xhtml | 1 +
browser/base/content/certerror/aboutNetError.js | 10 +-
browser/base/content/certerror/aboutNetError.xhtml | 1 +
browser/base/content/main-popupset.inc.xhtml | 1 +
browser/base/content/navigator-toolbox.inc.xhtml | 1 +
browser/base/content/tab-content.js | 6 +
browser/components/moz.build | 1 +
.../content/authNotificationIcon.inc.xhtml | 6 +
.../onionservices/content/authPopup.inc.xhtml | 16 ++
.../onionservices/content/authPreferences.css | 20 ++
.../content/authPreferences.inc.xhtml | 19 ++
.../onionservices/content/authPreferences.js | 66 +++++
.../components/onionservices/content/authPrompt.js | 316 +++++++++++++++++++++
.../components/onionservices/content/authUtil.jsm | 47 +++
.../onionservices/content/netError/browser.svg | 3 +
.../onionservices/content/netError/network.svg | 3 +
.../content/netError/onionNetError.css | 88 ++++++
.../content/netError/onionNetError.js | 243 ++++++++++++++++
.../onionservices/content/netError/onionsite.svg | 8 +
.../onionservices/content/onionservices.css | 69 +++++
.../onionservices/content/savedKeysDialog.js | 259 +++++++++++++++++
.../onionservices/content/savedKeysDialog.xhtml | 42 +++
browser/components/onionservices/jar.mn | 9 +
browser/components/onionservices/moz.build | 1 +
browser/components/preferences/preferences.xhtml | 1 +
browser/components/preferences/privacy.inc.xhtml | 2 +
browser/components/preferences/privacy.js | 7 +
browser/themes/shared/notification-icons.inc.css | 3 +
docshell/base/nsDocShell.cpp | 81 +++++-
dom/ipc/BrowserParent.cpp | 21 ++
dom/ipc/BrowserParent.h | 3 +
dom/ipc/PBrowser.ipdl | 9 +
js/xpconnect/src/xpc.msg | 10 +
netwerk/base/nsSocketTransport2.cpp | 6 +
netwerk/socket/nsSOCKSIOLayer.cpp | 49 ++++
toolkit/modules/PopupNotifications.jsm | 6 +
toolkit/modules/RemotePageAccessManager.jsm | 1 +
.../lib/environments/frame-script.js | 1 +
xpcom/base/ErrorList.py | 22 ++
41 files changed, 1473 insertions(+), 2 deletions(-)
diff --git a/browser/actors/NetErrorChild.jsm b/browser/actors/NetErrorChild.jsm
index 82978412fe24..164fb7c95cd1 100644
--- a/browser/actors/NetErrorChild.jsm
+++ b/browser/actors/NetErrorChild.jsm
@@ -13,6 +13,8 @@ const { RemotePageChild } = ChromeUtils.import(
"resource://gre/actors/RemotePageChild.jsm"
);
+const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm");
+
XPCOMUtils.defineLazyServiceGetter(
this,
"gSerializationHelper",
@@ -33,6 +35,7 @@ class NetErrorChild extends RemotePageChild {
"RPMAddToHistogram",
"RPMRecordTelemetryEvent",
"RPMGetHttpResponseHeader",
+ "RPMGetTorStrings",
];
this.exportFunctions(exportableFunctions);
}
@@ -115,4 +118,8 @@ class NetErrorChild extends RemotePageChild {
return "";
}
+
+ RPMGetTorStrings() {
+ return Cu.cloneInto(TorStrings.onionServices, this.contentWindow);
+ }
}
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index d1a07e4cf887..4f7852d1b510 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -230,6 +230,11 @@ XPCOMUtils.defineLazyScriptGetter(
["SecurityLevelButton"],
"chrome://browser/content/securitylevel/securityLevel.js"
);
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["OnionAuthPrompt"],
+ "chrome://browser/content/onionservices/authPrompt.js"
+);
XPCOMUtils.defineLazyScriptGetter(
this,
"gEditItemOverlay",
@@ -1773,6 +1778,9 @@ var gBrowserInit = {
// Init the SecuritySettingsButton
SecurityLevelButton.init();
+ // Init the OnionAuthPrompt
+ OnionAuthPrompt.init();
+
// Certain kinds of automigration rely on this notification to complete
// their tasks BEFORE the browser window is shown. SessionStore uses it to
// restore tabs into windows AFTER important parts like gMultiProcessBrowser
@@ -2508,6 +2516,8 @@ var gBrowserInit = {
SecurityLevelButton.uninit();
+ OnionAuthPrompt.uninit();
+
TorBootstrapUrlbar.uninit();
gAccessibilityServiceIndicator.uninit();
diff --git a/browser/base/content/browser.xhtml b/browser/base/content/browser.xhtml
index 627e6ac0f8a0..394a46414018 100644
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -34,6 +34,7 @@
<?xml-stylesheet href="chrome://browser/skin/places/editBookmark.css" type="text/css"?>
<?xml-stylesheet href="chrome://torbutton/skin/tor-circuit-display.css" type="text/css"?>
<?xml-stylesheet href="chrome://torbutton/skin/torbutton.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/onionservices/onionservices.css" type="text/css"?>
# All DTD information is stored in a separate file so that it can be shared by
# hiddenWindowMac.xhtml.
diff --git a/browser/base/content/certerror/aboutNetError.js b/browser/base/content/certerror/aboutNetError.js
index edf97c2a5daf..60f602ba6530 100644
--- a/browser/base/content/certerror/aboutNetError.js
+++ b/browser/base/content/certerror/aboutNetError.js
@@ -3,6 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env mozilla/frame-script */
+/* import-globals-from ../../components/onionservices/content/netError/onionNetError.js */
import "chrome://global/content/certviewer/pvutils_bundle.js";
import "chrome://global/content/certviewer/asn1js_bundle.js";
@@ -317,7 +318,10 @@ async function initPage() {
errDesc = document.getElementById("ed_generic");
}
- setErrorPageStrings(err);
+ const isOnionError = err.startsWith("onionServices.");
+ if (!isOnionError) {
+ setErrorPageStrings(err);
+ }
var sd = document.getElementById("errorShortDescText");
if (sd) {
@@ -469,6 +473,10 @@ async function initPage() {
span.textContent = HOST_NAME;
}
}
+
+ if (isOnionError) {
+ OnionServicesAboutNetError.initPage(document);
+ }
}
function setupBlockingReportingUI() {
diff --git a/browser/base/content/certerror/aboutNetError.xhtml b/browser/base/content/certerror/aboutNetError.xhtml
index c645a2f2cc77..bf9a8fd58347 100644
--- a/browser/base/content/certerror/aboutNetError.xhtml
+++ b/browser/base/content/certerror/aboutNetError.xhtml
@@ -209,5 +209,6 @@
</div>
</body>
<script src="chrome://browser/content/certerror/aboutNetErrorCodes.js"/>
+ <script src="chrome://browser/content/onionservices/netError/onionNetError.js"/>
<script type="module" src="chrome://browser/content/certerror/aboutNetError.js"/>
</html>
diff --git a/browser/base/content/main-popupset.inc.xhtml b/browser/base/content/main-popupset.inc.xhtml
index 3fc665c65d79..a96aaa9c187d 100644
--- a/browser/base/content/main-popupset.inc.xhtml
+++ b/browser/base/content/main-popupset.inc.xhtml
@@ -521,6 +521,7 @@
#include ../../components/downloads/content/downloadsPanel.inc.xhtml
#include ../../../devtools/startup/enableDevToolsPopup.inc.xhtml
#include ../../components/securitylevel/content/securityLevelPanel.inc.xhtml
+#include ../../components/onionservices/content/authPopup.inc.xhtml
#include browser-allTabsMenu.inc.xhtml
<tooltip id="dynamic-shortcut-tooltip"
diff --git a/browser/base/content/navigator-toolbox.inc.xhtml b/browser/base/content/navigator-toolbox.inc.xhtml
index e10e0580b8ec..810a77e57766 100644
--- a/browser/base/content/navigator-toolbox.inc.xhtml
+++ b/browser/base/content/navigator-toolbox.inc.xhtml
@@ -268,6 +268,7 @@
data-l10n-id="urlbar-indexed-db-notification-anchor"/>
<image id="password-notification-icon" class="notification-anchor-icon login-icon" role="button"
data-l10n-id="urlbar-password-notification-anchor"/>
+#include ../../components/onionservices/content/authNotificationIcon.inc.xhtml
<stack id="plugins-notification-icon" class="notification-anchor-icon" role="button" align="center" data-l10n-id="urlbar-plugins-notification-anchor">
<image class="plugin-icon" />
<image id="plugin-icon-badge" />
diff --git a/browser/base/content/tab-content.js b/browser/base/content/tab-content.js
index 83e55cf5ed87..96360a4307d2 100644
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -7,4 +7,10 @@
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { OnionAuthUtil } = ChromeUtils.import(
+ "chrome://browser/content/onionservices/authUtil.jsm"
+);
+
Services.obs.notifyObservers(this, "tab-content-frameloader-created");
+
+OnionAuthUtil.addCancelMessageListener(this, docShell);
diff --git a/browser/components/moz.build b/browser/components/moz.build
index d29df1d3df99..c30497374912 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -38,6 +38,7 @@ DIRS += [
"extensions",
"migration",
"newtab",
+ "onionservices",
"originattributes",
"ion",
"places",
diff --git a/browser/components/onionservices/content/authNotificationIcon.inc.xhtml b/browser/components/onionservices/content/authNotificationIcon.inc.xhtml
new file mode 100644
index 000000000000..91274d612739
--- /dev/null
+++ b/browser/components/onionservices/content/authNotificationIcon.inc.xhtml
@@ -0,0 +1,6 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<image id="tor-clientauth-notification-icon"
+ class="notification-anchor-icon tor-clientauth-icon"
+ role="button"
+ tooltiptext="&torbutton.onionServices.authPrompt.tooltip;"/>
diff --git a/browser/components/onionservices/content/authPopup.inc.xhtml b/browser/components/onionservices/content/authPopup.inc.xhtml
new file mode 100644
index 000000000000..bd0ec3aa0b00
--- /dev/null
+++ b/browser/components/onionservices/content/authPopup.inc.xhtml
@@ -0,0 +1,16 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<popupnotification id="tor-clientauth-notification" hidden="true">
+ <popupnotificationcontent orient="vertical">
+ <description id="tor-clientauth-notification-desc"/>
+ <label id="tor-clientauth-notification-learnmore"
+ class="text-link popup-notification-learnmore-link"
+ is="text-link"/>
+ <html:div>
+ <html:input id="tor-clientauth-notification-key" type="password"/>
+ <html:div id="tor-clientauth-warning"/>
+ <checkbox id="tor-clientauth-persistkey-checkbox"
+ label="&torbutton.onionServices.authPrompt.persistCheckboxLabel;"/>
+ </html:div>
+ </popupnotificationcontent>
+</popupnotification>
diff --git a/browser/components/onionservices/content/authPreferences.css b/browser/components/onionservices/content/authPreferences.css
new file mode 100644
index 000000000000..b3fb79b26ddc
--- /dev/null
+++ b/browser/components/onionservices/content/authPreferences.css
@@ -0,0 +1,20 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+
+#torOnionServiceKeys-overview-container {
+ margin-right: 30px;
+}
+
+#onionservices-savedkeys-tree treechildren::-moz-tree-cell-text {
+ font-size: 80%;
+}
+
+#onionservices-savedkeys-errorContainer {
+ margin-top: 4px;
+ min-height: 3em;
+}
+
+#onionservices-savedkeys-errorIcon {
+ margin-right: 4px;
+ list-style-image: url("chrome://browser/skin/warning.svg");
+ visibility: hidden;
+}
diff --git a/browser/components/onionservices/content/authPreferences.inc.xhtml b/browser/components/onionservices/content/authPreferences.inc.xhtml
new file mode 100644
index 000000000000..f69c9dde66a2
--- /dev/null
+++ b/browser/components/onionservices/content/authPreferences.inc.xhtml
@@ -0,0 +1,19 @@
+# Copyright (c) 2020, The Tor Project, Inc.
+
+<groupbox id="torOnionServiceKeys" orient="vertical"
+ data-category="panePrivacy" hidden="true">
+ <label><html:h2 id="torOnionServiceKeys-header"/></label>
+ <hbox>
+ <description id="torOnionServiceKeys-overview-container" flex="1">
+ <html:span id="torOnionServiceKeys-overview"
+ class="tail-with-learn-more"/>
+ <label id="torOnionServiceKeys-learnMore" class="learnMore text-link"
+ is="text-link"/>
+ </description>
+ <vbox align="end">
+ <button id="torOnionServiceKeys-savedKeys"
+ is="highlightable-button"
+ class="accessory-button"/>
+ </vbox>
+ </hbox>
+</groupbox>
diff --git a/browser/components/onionservices/content/authPreferences.js b/browser/components/onionservices/content/authPreferences.js
new file mode 100644
index 000000000000..52f8272020cc
--- /dev/null
+++ b/browser/components/onionservices/content/authPreferences.js
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorStrings",
+ "resource:///modules/TorStrings.jsm"
+);
+
+/*
+ Onion Services Client Authentication Preferences Code
+
+ Code to handle init and update of onion services authentication section
+ in about:preferences#privacy
+*/
+
+const OnionServicesAuthPreferences = {
+ selector: {
+ groupBox: "#torOnionServiceKeys",
+ header: "#torOnionServiceKeys-header",
+ overview: "#torOnionServiceKeys-overview",
+ learnMore: "#torOnionServiceKeys-learnMore",
+ savedKeysButton: "#torOnionServiceKeys-savedKeys",
+ },
+
+ init() {
+ // populate XUL with localized strings
+ this._populateXUL();
+ },
+
+ _populateXUL() {
+ const groupbox = document.querySelector(this.selector.groupBox);
+
+ let elem = groupbox.querySelector(this.selector.header);
+ elem.textContent = TorStrings.onionServices.authPreferences.header;
+
+ elem = groupbox.querySelector(this.selector.overview);
+ elem.textContent = TorStrings.onionServices.authPreferences.overview;
+
+ elem = groupbox.querySelector(this.selector.learnMore);
+ elem.setAttribute("value", TorStrings.onionServices.learnMore);
+ elem.setAttribute("href", TorStrings.onionServices.learnMoreURL);
+
+ elem = groupbox.querySelector(this.selector.savedKeysButton);
+ elem.setAttribute(
+ "label",
+ TorStrings.onionServices.authPreferences.savedKeys
+ );
+ elem.addEventListener("command", () =>
+ OnionServicesAuthPreferences.onViewSavedKeys()
+ );
+ },
+
+ onViewSavedKeys() {
+ gSubDialog.open(
+ "chrome://browser/content/onionservices/savedKeysDialog.xhtml"
+ );
+ },
+}; // OnionServicesAuthPreferences
+
+Object.defineProperty(this, "OnionServicesAuthPreferences", {
+ value: OnionServicesAuthPreferences,
+ enumerable: true,
+ writable: false,
+});
diff --git a/browser/components/onionservices/content/authPrompt.js b/browser/components/onionservices/content/authPrompt.js
new file mode 100644
index 000000000000..d4a59ac46487
--- /dev/null
+++ b/browser/components/onionservices/content/authPrompt.js
@@ -0,0 +1,316 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ OnionAuthUtil: "chrome://browser/content/onionservices/authUtil.jsm",
+ CommonUtils: "resource://services-common/utils.js",
+ TorStrings: "resource:///modules/TorStrings.jsm",
+});
+
+const OnionAuthPrompt = (function() {
+ // OnionServicesAuthPrompt objects run within the main/chrome process.
+ // aReason is the topic passed within the observer notification that is
+ // causing this auth prompt to be displayed.
+ function OnionServicesAuthPrompt(aBrowser, aFailedURI, aReason, aOnionName) {
+ this._browser = aBrowser;
+ this._failedURI = aFailedURI;
+ this._reasonForPrompt = aReason;
+ this._onionName = aOnionName;
+ }
+
+ OnionServicesAuthPrompt.prototype = {
+ show(aWarningMessage) {
+ let mainAction = {
+ label: TorStrings.onionServices.authPrompt.done,
+ accessKey: TorStrings.onionServices.authPrompt.doneAccessKey,
+ leaveOpen: true, // Callback is responsible for closing the notification.
+ callback: this._onDone.bind(this),
+ };
+
+ let dialogBundle = Services.strings.createBundle(
+ "chrome://global/locale/dialog.properties");
+
+ let cancelAccessKey = dialogBundle.GetStringFromName("accesskey-cancel");
+ if (!cancelAccessKey)
+ cancelAccessKey = "c"; // required by PopupNotifications.show()
+
+ let cancelAction = {
+ label: dialogBundle.GetStringFromName("button-cancel"),
+ accessKey: cancelAccessKey,
+ callback: this._onCancel.bind(this),
+ };
+
+ let _this = this;
+ let options = {
+ autofocus: true,
+ hideClose: true,
+ persistent: true,
+ removeOnDismissal: false,
+ eventCallback(aTopic) {
+ if (aTopic === "showing") {
+ _this._onPromptShowing(aWarningMessage);
+ } else if (aTopic === "shown") {
+ _this._onPromptShown();
+ } else if (aTopic === "removed") {
+ _this._onPromptRemoved();
+ }
+ }
+ };
+
+ this._prompt = PopupNotifications.show(this._browser,
+ OnionAuthUtil.domid.notification, "",
+ OnionAuthUtil.domid.anchor,
+ mainAction, [cancelAction], options);
+ },
+
+ _onPromptShowing(aWarningMessage) {
+ let xulDoc = this._browser.ownerDocument;
+ let descElem = xulDoc.getElementById(OnionAuthUtil.domid.description);
+ if (descElem) {
+ // Handle replacement of the onion name within the localized
+ // string ourselves so we can show the onion name as bold text.
+ // We do this by splitting the localized string and creating
+ // several HTML <span> elements.
+ while (descElem.firstChild)
+ descElem.removeChild(descElem.firstChild);
+
+ let fmtString = TorStrings.onionServices.authPrompt.description;
+ let prefix = "";
+ let suffix = "";
+ const kToReplace = "%S";
+ let idx = fmtString.indexOf(kToReplace);
+ if (idx < 0) {
+ prefix = fmtString;
+ } else {
+ prefix = fmtString.substring(0, idx);
+ suffix = fmtString.substring(idx + kToReplace.length);
+ }
+
+ const kHTMLNS = "http://www.w3.org/1999/xhtml";
+ let span = xulDoc.createElementNS(kHTMLNS, "span");
+ span.textContent = prefix;
+ descElem.appendChild(span);
+ span = xulDoc.createElementNS(kHTMLNS, "span");
+ span.id = OnionAuthUtil.domid.onionNameSpan;
+ span.textContent = this._onionName;
+ descElem.appendChild(span);
+ span = xulDoc.createElementNS(kHTMLNS, "span");
+ span.textContent = suffix;
+ descElem.appendChild(span);
+ }
+
+ // Set "Learn More" label and href.
+ let learnMoreElem = xulDoc.getElementById(OnionAuthUtil.domid.learnMore);
+ if (learnMoreElem) {
+ learnMoreElem.setAttribute("value", TorStrings.onionServices.learnMore);
+ learnMoreElem.setAttribute("href", TorStrings.onionServices.learnMoreURL);
+ }
+
+ this._showWarning(aWarningMessage);
+ let checkboxElem = this._getCheckboxElement();
+ if (checkboxElem) {
+ checkboxElem.checked = false;
+ }
+ },
+
+ _onPromptShown() {
+ let keyElem = this._getKeyElement();
+ if (keyElem) {
+ keyElem.setAttribute("placeholder",
+ TorStrings.onionServices.authPrompt.keyPlaceholder);
+ this._boundOnKeyFieldKeyPress = this._onKeyFieldKeyPress.bind(this);
+ this._boundOnKeyFieldInput = this._onKeyFieldInput.bind(this);
+ keyElem.addEventListener("keypress", this._boundOnKeyFieldKeyPress);
+ keyElem.addEventListener("input", this._boundOnKeyFieldInput);
+ keyElem.focus();
+ }
+ },
+
+ _onPromptRemoved() {
+ if (this._boundOnKeyFieldKeyPress) {
+ let keyElem = this._getKeyElement();
+ if (keyElem) {
+ keyElem.value = "";
+ keyElem.removeEventListener("keypress",
+ this._boundOnKeyFieldKeyPress);
+ this._boundOnKeyFieldKeyPress = undefined;
+ keyElem.removeEventListener("input", this._boundOnKeyFieldInput);
+ this._boundOnKeyFieldInput = undefined;
+ }
+ }
+ },
+
+ _onKeyFieldKeyPress(aEvent) {
+ if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
+ this._onDone();
+ } else if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ this._prompt.remove();
+ this._onCancel();
+ }
+ },
+
+ _onKeyFieldInput(aEvent) {
+ this._showWarning(undefined); // Remove the warning.
+ },
+
+ _onDone() {
+ let keyElem = this._getKeyElement();
+ if (!keyElem)
+ return;
+
+ let base64key = this._keyToBase64(keyElem.value);
+ if (!base64key) {
+ this._showWarning(TorStrings.onionServices.authPrompt.invalidKey);
+ return;
+ }
+
+ this._prompt.remove();
+
+ // Use Torbutton's controller module to add the private key to Tor.
+ let controllerFailureMsg =
+ TorStrings.onionServices.authPrompt.failedToSetKey;
+ try {
+ let { controller } =
+ Cu.import("resource://torbutton/modules/tor-control-port.js", {});
+ let torController = controller(aError => {
+ this.show(controllerFailureMsg);
+ });
+ let onionAddr = this._onionName.toLowerCase().replace(/\.onion$/, "");
+ let checkboxElem = this._getCheckboxElement();
+ let isPermanent = (checkboxElem && checkboxElem.checked);
+ torController.onionAuthAdd(onionAddr, base64key, isPermanent)
+ .then(aResponse => {
+ // Success! Reload the page.
+ this._browser.sendMessageToActor(
+ "Browser:Reload",
+ {},
+ "BrowserTab"
+ );
+ })
+ .catch(aError => {
+ if (aError.torMessage)
+ this.show(aError.torMessage);
+ else
+ this.show(controllerFailureMsg);
+ });
+ } catch (e) {
+ this.show(controllerFailureMsg);
+ }
+ },
+
+ _onCancel() {
+ // Arrange for an error page to be displayed.
+ this._browser.messageManager.sendAsyncMessage(
+ OnionAuthUtil.message.authPromptCanceled,
+ {failedURI: this._failedURI.spec,
+ reasonForPrompt: this._reasonForPrompt});
+ },
+
+ _getKeyElement() {
+ let xulDoc = this._browser.ownerDocument;
+ return xulDoc.getElementById(OnionAuthUtil.domid.keyElement);
+ },
+
+ _getCheckboxElement() {
+ let xulDoc = this._browser.ownerDocument;
+ return xulDoc.getElementById(OnionAuthUtil.domid.checkboxElement);
+ },
+
+ _showWarning(aWarningMessage) {
+ let xulDoc = this._browser.ownerDocument;
+ let warningElem =
+ xulDoc.getElementById(OnionAuthUtil.domid.warningElement);
+ let keyElem = this._getKeyElement();
+ if (warningElem) {
+ if (aWarningMessage) {
+ warningElem.textContent = aWarningMessage;
+ warningElem.removeAttribute("hidden");
+ if (keyElem)
+ keyElem.className = "invalid";
+ } else {
+ warningElem.setAttribute("hidden", "true");
+ if (keyElem)
+ keyElem.className = "";
+ }
+ }
+ },
+
+ // Returns undefined if the key is the wrong length or format.
+ _keyToBase64(aKeyString) {
+ if (!aKeyString)
+ return undefined;
+
+ let base64key;
+ if (aKeyString.length == 52) {
+ // The key is probably base32-encoded. Attempt to decode.
+ // Although base32 specifies uppercase letters, we accept lowercase
+ // as well because users may type in lowercase or copy a key out of
+ // a tor onion-auth file (which uses lowercase).
+ let rawKey;
+ try {
+ rawKey = CommonUtils.decodeBase32(aKeyString.toUpperCase());
+ } catch (e) {}
+
+ if (rawKey) try {
+ base64key = btoa(rawKey);
+ } catch (e) {}
+ } else if ((aKeyString.length == 44) &&
+ /^[a-zA-Z0-9+/]*=*$/.test(aKeyString)) {
+ // The key appears to be a correctly formatted base64 value. If not,
+ // tor will return an error when we try to add the key via the
+ // control port.
+ base64key = aKeyString;
+ }
+
+ return base64key;
+ },
+ };
+
+ let retval = {
+ init() {
+ Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthMissing);
+ Services.obs.addObserver(this, OnionAuthUtil.topic.clientAuthIncorrect);
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, OnionAuthUtil.topic.clientAuthMissing);
+ Services.obs.removeObserver(this, OnionAuthUtil.topic.clientAuthIncorrect);
+ },
+
+ // aSubject is the DOM Window or browser where the prompt should be shown.
+ // aData contains the .onion name.
+ observe(aSubject, aTopic, aData) {
+ if ((aTopic != OnionAuthUtil.topic.clientAuthMissing) &&
+ (aTopic != OnionAuthUtil.topic.clientAuthIncorrect)) {
+ return;
+ }
+
+ let browser;
+ if (aSubject instanceof Ci.nsIDOMWindow) {
+ let contentWindow = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ browser = contentWindow.docShell.chromeEventHandler;
+ } else {
+ browser = aSubject.QueryInterface(Ci.nsIBrowser);
+ }
+
+ if (!gBrowser.browsers.some(aBrowser => aBrowser == browser)) {
+ return; // This window does not contain the subject browser; ignore.
+ }
+
+ let failedURI = browser.currentURI;
+ let authPrompt = new OnionServicesAuthPrompt(browser, failedURI,
+ aTopic, aData);
+ authPrompt.show(undefined);
+ }
+ };
+
+ return retval;
+})(); /* OnionAuthPrompt */
+
+
+Object.defineProperty(this, "OnionAuthPrompt", {
+ value: OnionAuthPrompt,
+ enumerable: true,
+ writable: false
+});
diff --git a/browser/components/onionservices/content/authUtil.jsm b/browser/components/onionservices/content/authUtil.jsm
new file mode 100644
index 000000000000..c9d83774da1f
--- /dev/null
+++ b/browser/components/onionservices/content/authUtil.jsm
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+var EXPORTED_SYMBOLS = [
+ "OnionAuthUtil",
+];
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const OnionAuthUtil = {
+ topic: {
+ clientAuthMissing: "tor-onion-services-clientauth-missing",
+ clientAuthIncorrect: "tor-onion-services-clientauth-incorrect",
+ },
+ message: {
+ authPromptCanceled: "Tor:OnionServicesAuthPromptCanceled",
+ },
+ domid: {
+ anchor: "tor-clientauth-notification-icon",
+ notification: "tor-clientauth",
+ description: "tor-clientauth-notification-desc",
+ learnMore: "tor-clientauth-notification-learnmore",
+ onionNameSpan: "tor-clientauth-notification-onionname",
+ keyElement: "tor-clientauth-notification-key",
+ warningElement: "tor-clientauth-warning",
+ checkboxElement: "tor-clientauth-persistkey-checkbox",
+ },
+
+ addCancelMessageListener(aTabContent, aDocShell) {
+ aTabContent.addMessageListener(this.message.authPromptCanceled,
+ (aMessage) => {
+ // Upon cancellation of the client authentication prompt, display
+ // the appropriate error page. When calling the docShell
+ // displayLoadError() function, we pass undefined for the failed
+ // channel so that displayLoadError() can determine that it should
+ // not display the client authentication prompt a second time.
+ let failedURI = Services.io.newURI(aMessage.data.failedURI);
+ let reasonForPrompt = aMessage.data.reasonForPrompt;
+ let errorCode =
+ (reasonForPrompt === this.topic.clientAuthMissing) ?
+ Cr.NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH :
+ Cr.NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH;
+ aDocShell.displayLoadError(errorCode, failedURI, undefined, undefined);
+ });
+ },
+};
diff --git a/browser/components/onionservices/content/netError/browser.svg b/browser/components/onionservices/content/netError/browser.svg
new file mode 100644
index 000000000000..1359679f7171
--- /dev/null
+++ b/browser/components/onionservices/content/netError/browser.svg
@@ -0,0 +1,3 @@
+<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="m49 6h-37.5c-1.98912 0-3.89678.79018-5.3033 2.1967s-2.1967 3.3142-2.1967 5.3033v33.75c0 1.9891.79018 3.8968 2.1967 5.3033s3.31418 2.1967 5.3033 2.1967h37.5c1.9891 0 3.8968-.7902 5.3033-2.1967s2.1967-3.3142 2.1967-5.3033v-33.75c0-1.9891-.7902-3.89678-2.1967-5.3033s-3.3142-2.1967-5.3033-2.1967zm-38.0625 4.6875h38.625l2.25 2.25v8.0625h-43.125v-8.0625zm38.625 39.375h-38.625l-2.25-2.25v-22.125h43.125v22.125z"/>
+</svg>
diff --git a/browser/components/onionservices/content/netError/network.svg b/browser/components/onionservices/content/netError/network.svg
new file mode 100644
index 000000000000..68610e30bfca
--- /dev/null
+++ b/browser/components/onionservices/content/netError/network.svg
@@ -0,0 +1,3 @@
+<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg">
+ <path fill="context-fill" fill-opacity="context-fill-opacity" d="m30 1.875c-7.4592 0-14.6129 2.96316-19.8874 8.2376-5.27444 5.2745-8.2376 12.4282-8.2376 19.8874s2.96316 14.6129 8.2376 19.8874c5.2745 5.2744 12.4282 8.2376 19.8874 8.2376s14.6129-2.9632 19.8874-8.2376c5.2744-5.2745 8.2376-12.4282 8.2376-19.8874s-2.9632-14.6129-8.2376-19.8874c-5.2745-5.27444-12.4282-8.2376-19.8874-8.2376zm9.1762 6.5625c3.8504 1.6533 7.1876 4.3079 9.6646 7.6877 2.477 3.3799 4.0034 7.3615 4.4205 11.531h-8.3588c-.4617-6.9829-2.9858-13.6716-7.2525-19.2187zm-7.6837 0c5.0739 5.1814 8.1562 11.9874 8.7037 19.2187h-20.3924c.5475-7.2313 3.6298-14.0373 8.7037-19.2187zm-10.6725 0h1.53c-4.2651 5.548-6.789 12.2362-7.2525 19.2187h-8.35875c.41632-4.1692 1.942-8.1508 4.41835-11.5306 2.4764-3.3799 5.813-6.0346 9.6629-7.6881zm0 43.125c-3.8504-1.6528-7.1874-4.3074-9.6639-7.6874-2.47642-3.38-4.0018-7.3619-4.41735-11.5313h8.35875c.4617 6.9829 2.9858 13.6716 7.2525 19.2187zm7.6875 0c-5.0739-5.1814-8.1562-11.9874-8.7037-19.2
187h20.3887c-.5475 7.2313-3.6298 14.0373-8.7037 19.2187zm10.6725 0h-1.5338c4.2683-5.5462 6.7926-12.2354 7.2525-19.2187h8.3588c-.4156 4.1689-1.9406 8.1504-4.4163 11.5302-2.4757 3.3799-5.8118 6.0348-9.6612 7.6885z"/>
+</svg>
diff --git a/browser/components/onionservices/content/netError/onionNetError.css b/browser/components/onionservices/content/netError/onionNetError.css
new file mode 100644
index 000000000000..2c92b187b71c
--- /dev/null
+++ b/browser/components/onionservices/content/netError/onionNetError.css
@@ -0,0 +1,88 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+
+#onionErrorDiagramContainer {
+ margin: 0px auto 40px 0px;
+ /* 3 icons 64px wide each seperated by a 64px gap */
+ width: 384px;
+ display: grid;
+ grid-row-gap: 15px;
+ grid-column-gap: 64px;
+ grid-template-columns: 1fr 1fr 1fr;
+}
+
+#onionErrorDiagramContainer > div {
+ margin: auto;
+ position: relative; /* needed to allow overlay of the ok or error icon */
+}
+
+.onionErrorImage {
+ width: 64px;
+ height: 64px;
+ background-size: 64px 64px;
+ background-position: center;
+ background-repeat: no-repeat;
+ -moz-context-properties: fill;
+ fill: var(--in-content-icon-color);
+ opacity: 50%;
+}
+
+/* TODO: remove these --warning-color definitions after we
+ are esr92 based (tor-browser#40640 */
+.onionErrorImage {
+ --warning-color: #ffa436;
+}
+
+@media (-moz-toolbar-prefers-color-scheme: dark) {
+ .onionErrorImage {
+ --warning-color: #ffbd4f;
+ }
+}
+
+@media (prefers-contrast) {
+ .onionErrorImage {
+ --warning-color: var(--in-content-page-color);
+ }
+}
+
+.onionErrorImage[status] {
+ opacity: 100%;
+}
+
+#onionErrorBrowserImage {
+ background-image: url("browser.svg");
+}
+
+#onionErrorNetworkImage {
+ background-image: url("network.svg");
+}
+
+#onionErrorOnionSiteImage {
+ background-image: url("onionsite.svg");
+}
+
+/* rules to support overlay of the ok or error icon */
+.onionErrorImage[status]::after {
+ content: " ";
+ position: absolute;
+ left: -8px;
+ top: calc((64px - 24px) / 2);
+ width: 24px;
+ height: 24px;
+ -moz-context-properties: fill;
+ fill: var(--in-content-page-background);
+
+ background-repeat: no-repeat;
+ background-position: center;
+ border: 3px solid var(--in-content-page-background);
+ border-radius: 50%;
+}
+
+.onionErrorImage[status="ok"]::after {
+ background-color: var(--in-content-icon-color);
+ background-image: url("chrome://global/skin/icons/check.svg");
+}
+
+.onionErrorImage[status="error"]::after {
+ background-color: var(--warning-color);
+ background-image: url("chrome://global/skin/icons/close.svg");
+}
diff --git a/browser/components/onionservices/content/netError/onionNetError.js b/browser/components/onionservices/content/netError/onionNetError.js
new file mode 100644
index 000000000000..745c58ec6124
--- /dev/null
+++ b/browser/components/onionservices/content/netError/onionNetError.js
@@ -0,0 +1,243 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+/* eslint-env mozilla/frame-script */
+
+var OnionServicesAboutNetError = {
+ _selector: {
+ textContainer: "div#text-container",
+ header: ".title-text",
+ longDesc: "#errorLongDesc",
+ learnMoreContainer: "#learnMoreContainer",
+ learnMoreLink: "#learnMoreLink",
+ contentContainer: "#errorLongContent",
+ tryAgainButtonContainer: "#netErrorButtonContainer",
+ },
+ _status: {
+ ok: "ok",
+ error: "error",
+ },
+
+ _diagramInfoMap: undefined,
+
+ // Public functions (called from outside this file).
+ //
+ // This initPage() function may need to be updated if the structure of
+ // browser/base/content/aboutNetError.xhtml changes. Specifically, it
+ // references the following elements:
+ // query string parameter e
+ // class title-text
+ // id errorLongDesc
+ // id learnMoreContainer
+ // id learnMoreLink
+ // id errorLongContent
+ initPage(aDoc) {
+ const searchParams = new URLSearchParams(aDoc.documentURI.split("?")[1]);
+ const err = searchParams.get("e");
+
+ const errPrefix = "onionServices.";
+ const errName = err.substring(errPrefix.length);
+
+ this._strings = RPMGetTorStrings();
+
+ const stringsObj = this._strings[errName];
+ if (!stringsObj) {
+ return;
+ }
+
+ this._insertStylesheet(aDoc);
+
+ const pageTitle = stringsObj.pageTitle;
+ const header = stringsObj.header;
+ const longDescription = stringsObj.longDescription; // optional
+ const learnMoreURL = stringsObj.learnMoreURL;
+
+ if (pageTitle) {
+ aDoc.title = pageTitle;
+ }
+
+ if (header) {
+ const headerElem = aDoc.querySelector(this._selector.header);
+ if (headerElem) {
+ headerElem.textContent = header;
+ }
+ }
+
+ const ld = aDoc.querySelector(this._selector.longDesc);
+ if (ld) {
+ if (longDescription) {
+ const hexErr = this._hexErrorFromName(errName);
+ ld.textContent = longDescription.replace("%S", hexErr);
+ } else {
+ // This onion service error does not have a long description. Since
+ // it is set to a generic error string by the code in
+ // browser/base/content/aboutNetError.js, hide it here.
+ ld.style.display = "none";
+ }
+ }
+
+ if (learnMoreURL) {
+ const lmContainer = aDoc.querySelector(this._selector.learnMoreContainer);
+ if (lmContainer) {
+ lmContainer.style.display = "block";
+ }
+ const lmLink = lmContainer.querySelector(this._selector.learnMoreLink);
+ if (lmLink) {
+ lmLink.setAttribute("href", learnMoreURL);
+ }
+ }
+
+ // Remove the "Try Again" button if the user made a typo in the .onion
+ // address since it is not useful in that case.
+ if (errName === "badAddress") {
+ const tryAgainButton = aDoc.querySelector(
+ this._selector.tryAgainButtonContainer
+ );
+ if (tryAgainButton) {
+ tryAgainButton.style.display = "none";
+ }
+ }
+
+ this._insertDiagram(aDoc, errName);
+ }, // initPage()
+
+ _insertStylesheet(aDoc) {
+ const url =
+ "chrome://browser/content/onionservices/netError/onionNetError.css";
+ let linkElem = aDoc.createElement("link");
+ linkElem.rel = "stylesheet";
+ linkElem.href = url;
+ linkElem.type = "text/css";
+ aDoc.head.appendChild(linkElem);
+ },
+
+ _insertDiagram(aDoc, aErrorName) {
+ // The onion error diagram consists of a grid of div elements.
+ // The first row contains three images (Browser, Network, Onionsite) and
+ // the second row contains labels for the images that are in the first row.
+ // The _diagramInfoMap describes for each type of onion service error
+ // whether a small ok or error status icon is overlaid on top of the main
+ // Browser/Network/Onionsite images.
+ if (!this._diagramInfoMap) {
+ this._diagramInfoMap = new Map();
+ this._diagramInfoMap.set("descNotFound", {
+ browser: this._status.ok,
+ network: this._status.ok,
+ onionSite: this._status.error,
+ });
+ this._diagramInfoMap.set("descInvalid", {
+ browser: this._status.ok,
+ network: this._status.error,
+ });
+ this._diagramInfoMap.set("introFailed", {
+ browser: this._status.ok,
+ network: this._status.error,
+ });
+ this._diagramInfoMap.set("rendezvousFailed", {
+ browser: this._status.ok,
+ network: this._status.error,
+ });
+ this._diagramInfoMap.set("clientAuthMissing", {
+ browser: this._status.error,
+ });
+ this._diagramInfoMap.set("clientAuthIncorrect", {
+ browser: this._status.error,
+ });
+ this._diagramInfoMap.set("badAddress", {
+ browser: this._status.error,
+ });
+ this._diagramInfoMap.set("introTimedOut", {
+ browser: this._status.ok,
+ network: this._status.error,
+ });
+ }
+
+ const diagramInfo = this._diagramInfoMap.get(aErrorName);
+
+ const container = this._createDiv(aDoc, "onionErrorDiagramContainer");
+ const imageClass = "onionErrorImage";
+
+ const browserImage = this._createDiv(
+ aDoc,
+ "onionErrorBrowserImage",
+ imageClass,
+ container
+ );
+ if (diagramInfo && diagramInfo.browser) {
+ browserImage.setAttribute("status", diagramInfo.browser);
+ }
+
+ const networkImage = this._createDiv(
+ aDoc,
+ "onionErrorNetworkImage",
+ imageClass,
+ container
+ );
+ if (diagramInfo && diagramInfo.network) {
+ networkImage.setAttribute("status", diagramInfo.network);
+ }
+
+ const onionSiteImage = this._createDiv(
+ aDoc,
+ "onionErrorOnionSiteImage",
+ imageClass,
+ container
+ );
+ if (diagramInfo && diagramInfo.onionSite) {
+ onionSiteImage.setAttribute("status", diagramInfo.onionSite);
+ }
+
+ let labelDiv = this._createDiv(aDoc, undefined, undefined, container);
+ labelDiv.textContent = this._strings.errorPage.browser;
+ labelDiv = this._createDiv(aDoc, undefined, undefined, container);
+ labelDiv.textContent = this._strings.errorPage.network;
+ labelDiv = this._createDiv(aDoc, undefined, undefined, container);
+ labelDiv.textContent = this._strings.errorPage.onionSite;
+
+ const textContainer = aDoc.querySelector(
+ this._selector.textContainer
+ );
+ textContainer?.insertBefore(container, textContainer.firstChild);
+ }, // _insertDiagram()
+
+ _createDiv(aDoc, aID, aClass, aParentElem) {
+ const div = aDoc.createElement("div");
+ if (aID) {
+ div.id = aID;
+ }
+ if (aClass) {
+ div.setAttribute("class", aClass);
+ }
+ if (aParentElem) {
+ aParentElem.appendChild(div);
+ }
+
+ return div;
+ },
+
+ _hexErrorFromName(aErrorName) {
+ // We do not have access to the original Tor SOCKS error code here, so
+ // perform a reverse mapping from the error name.
+ switch (aErrorName) {
+ case "descNotFound":
+ return "0xF0";
+ case "descInvalid":
+ return "0xF1";
+ case "introFailed":
+ return "0xF2";
+ case "rendezvousFailed":
+ return "0xF3";
+ case "clientAuthMissing":
+ return "0xF4";
+ case "clientAuthIncorrect":
+ return "0xF5";
+ case "badAddress":
+ return "0xF6";
+ case "introTimedOut":
+ return "0xF7";
+ }
+
+ return "";
+ },
+};
diff --git a/browser/components/onionservices/content/netError/onionsite.svg b/browser/components/onionservices/content/netError/onionsite.svg
new file mode 100644
index 000000000000..c1b2d7382dc9
--- /dev/null
+++ b/browser/components/onionservices/content/netError/onionsite.svg
@@ -0,0 +1,8 @@
+<svg fill="none" height="60" viewBox="0 0 60 60" width="60" xmlns="http://www.w3.org/2000/svg">
+ <g fill="context-fill" fill-opacity="context-fill-opacity">
+ <path clip-rule="evenodd" d="m11.25 6h37.5c1.9891 0 3.8968.79018 5.3033 2.1967s2.1967 3.3142 2.1967 5.3033v33.75c0 1.9891-.7902 3.8968-2.1967 5.3033s-3.3142 2.1967-5.3033 2.1967h-37.5c-1.98912 0-3.89678-.7902-5.3033-2.1967s-2.1967-3.3142-2.1967-5.3033v-33.75c0-1.9891.79018-3.89678 2.1967-5.3033s3.31418-2.1967 5.3033-2.1967zm-.5625 4.6875h38.625l2.25 2.25v34.875l-2.25 2.25h-38.625l-2.25-2.25v-34.875z" fill-rule="evenodd"/>
+ <path d="m15.9606 22c-.52 0-1.0187-.2107-1.3863-.5858-.3677-.3751-.5743-.8838-.5743-1.4142s.2066-1.0391.5743-1.4142c.3676-.3751.8663-.5858 1.3863-.5858h14.0788c.52 0 1.0187.2107 1.3863.5858.3677.3751.5743.8838.5743 1.4142s-.2066 1.0391-.5743 1.4142c-.3676.3751-.8663.5858-1.3863.5858z"/>
+ <path d="m44.0709 32h-28.1418c-.5116 0-1.0023-.2107-1.3641-.5858s-.565-.8838-.565-1.4142.2032-1.0391.565-1.4142.8525-.5858 1.3641-.5858h28.1418c.5116 0 1.0023.2107 1.3641.5858s.565.8838.565 1.4142-.2032 1.0391-.565 1.4142-.8525.5858-1.3641.5858z"/>
+ <path d="m44.0709 42h-28.1418c-.5116 0-1.0023-.2107-1.3641-.5858s-.565-.8838-.565-1.4142.2032-1.0391.565-1.4142.8525-.5858 1.3641-.5858h28.1418c.5116 0 1.0023.2107 1.3641.5858s.565.8838.565 1.4142-.2032 1.0391-.565 1.4142-.8525.5858-1.3641.5858z"/>
+ </g>
+</svg>
diff --git a/browser/components/onionservices/content/onionservices.css b/browser/components/onionservices/content/onionservices.css
new file mode 100644
index 000000000000..e2621ec8266d
--- /dev/null
+++ b/browser/components/onionservices/content/onionservices.css
@@ -0,0 +1,69 @@
+/* Copyright (c) 2020, The Tor Project, Inc. */
+
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+html|*#tor-clientauth-notification-onionname {
+ font-weight: bold;
+}
+
+html|*#tor-clientauth-notification-key {
+ box-sizing: border-box;
+ width: 100%;
+ margin-top: 15px;
+ padding: 6px;
+}
+
+/* Start of rules adapted from
+ * browser/components/newtab/css/activity-stream-mac.css (linux and windows
+ * use the same rules).
+ */
+html|*#tor-clientauth-notification-key.invalid {
+ border: 1px solid #D70022;
+ box-shadow: 0 0 0 1px #D70022, 0 0 0 4px rgba(215, 0, 34, 0.3);
+}
+
+html|*#tor-clientauth-warning {
+ display: inline-block;
+ animation: fade-up-tt 450ms;
+ background: #D70022;
+ border-radius: 2px;
+ color: #FFF;
+ inset-inline-start: 3px;
+ padding: 5px 12px;
+ position: relative;
+ top: 6px;
+ z-index: 1;
+}
+
+html|*#tor-clientauth-warning[hidden] {
+ display: none;
+}
+
+html|*#tor-clientauth-warning::before {
+ background: #D70022;
+ bottom: -8px;
+ content: '.';
+ height: 16px;
+ inset-inline-start: 12px;
+ position: absolute;
+ text-indent: -999px;
+ top: -7px;
+ transform: rotate(45deg);
+ white-space: nowrap;
+ width: 16px;
+ z-index: -1;
+}
+
+@keyframes fade-up-tt {
+ 0% {
+ opacity: 0;
+ transform: translateY(15px);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+/* End of rules adapted from
+ * browser/components/newtab/css/activity-stream-mac.css
+ */
diff --git a/browser/components/onionservices/content/savedKeysDialog.js b/browser/components/onionservices/content/savedKeysDialog.js
new file mode 100644
index 000000000000..b1376bbabe85
--- /dev/null
+++ b/browser/components/onionservices/content/savedKeysDialog.js
@@ -0,0 +1,259 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "TorStrings",
+ "resource:///modules/TorStrings.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "controller",
+ "resource://torbutton/modules/tor-control-port.js"
+);
+
+var gOnionServicesSavedKeysDialog = {
+ selector: {
+ dialog: "#onionservices-savedkeys-dialog",
+ intro: "#onionservices-savedkeys-intro",
+ tree: "#onionservices-savedkeys-tree",
+ onionSiteCol: "#onionservices-savedkeys-siteCol",
+ onionKeyCol: "#onionservices-savedkeys-keyCol",
+ errorIcon: "#onionservices-savedkeys-errorIcon",
+ errorMessage: "#onionservices-savedkeys-errorMessage",
+ removeButton: "#onionservices-savedkeys-remove",
+ removeAllButton: "#onionservices-savedkeys-removeall",
+ },
+
+ _tree: undefined,
+ _isBusy: false, // true when loading data, deleting a key, etc.
+
+ // Public functions (called from outside this file).
+ async deleteSelectedKeys() {
+ this._setBusyState(true);
+
+ const indexesToDelete = [];
+ const count = this._tree.view.selection.getRangeCount();
+ for (let i = 0; i < count; ++i) {
+ const minObj = {};
+ const maxObj = {};
+ this._tree.view.selection.getRangeAt(i, minObj, maxObj);
+ for (let idx = minObj.value; idx <= maxObj.value; ++idx) {
+ indexesToDelete.push(idx);
+ }
+ }
+
+ if (indexesToDelete.length > 0) {
+ const controllerFailureMsg =
+ TorStrings.onionServices.authPreferences.failedToRemoveKey;
+ try {
+ const torController = controller(aError => {
+ this._showError(controllerFailureMsg);
+ });
+
+ // Remove in reverse index order to avoid issues caused by index changes.
+ for (let i = indexesToDelete.length - 1; i >= 0; --i) {
+ await this._deleteOneKey(torController, indexesToDelete[i]);
+ }
+ } catch (e) {
+ if (e.torMessage) {
+ this._showError(e.torMessage);
+ } else {
+ this._showError(controllerFailureMsg);
+ }
+ }
+ }
+
+ this._setBusyState(false);
+ },
+
+ async deleteAllKeys() {
+ this._tree.view.selection.selectAll();
+ await this.deleteSelectedKeys();
+ },
+
+ updateButtonsState() {
+ const haveSelection = this._tree.view.selection.getRangeCount() > 0;
+ const dialog = document.querySelector(this.selector.dialog);
+ const removeSelectedBtn = dialog.querySelector(this.selector.removeButton);
+ removeSelectedBtn.disabled = this._isBusy || !haveSelection;
+ const removeAllBtn = dialog.querySelector(this.selector.removeAllButton);
+ removeAllBtn.disabled = this._isBusy || this.rowCount === 0;
+ },
+
+ // Private functions.
+ _onLoad() {
+ document.mozSubdialogReady = this._init();
+ },
+
+ async _init() {
+ await this._populateXUL();
+
+ window.addEventListener("keypress", this._onWindowKeyPress.bind(this));
+
+ // We don't use await here because we want _loadSavedKeys() to run
+ // in the background and not block loading of this dialog.
+ this._loadSavedKeys();
+ },
+
+ async _populateXUL() {
+ const dialog = document.querySelector(this.selector.dialog);
+ const authPrefStrings = TorStrings.onionServices.authPreferences;
+ dialog.setAttribute("title", authPrefStrings.dialogTitle);
+
+ let elem = dialog.querySelector(this.selector.intro);
+ elem.textContent = authPrefStrings.dialogIntro;
+
+ elem = dialog.querySelector(this.selector.onionSiteCol);
+ elem.setAttribute("label", authPrefStrings.onionSite);
+
+ elem = dialog.querySelector(this.selector.onionKeyCol);
+ elem.setAttribute("label", authPrefStrings.onionKey);
+
+ elem = dialog.querySelector(this.selector.removeButton);
+ elem.setAttribute("label", authPrefStrings.remove);
+
+ elem = dialog.querySelector(this.selector.removeAllButton);
+ elem.setAttribute("label", authPrefStrings.removeAll);
+
+ this._tree = dialog.querySelector(this.selector.tree);
+ },
+
+ async _loadSavedKeys() {
+ const controllerFailureMsg =
+ TorStrings.onionServices.authPreferences.failedToGetKeys;
+ this._setBusyState(true);
+
+ try {
+ this._tree.view = this;
+
+ const torController = controller(aError => {
+ this._showError(controllerFailureMsg);
+ });
+
+ const keyInfoList = await torController.onionAuthViewKeys();
+ if (keyInfoList) {
+ // Filter out temporary keys.
+ this._keyInfoList = keyInfoList.filter(aKeyInfo => {
+ if (!aKeyInfo.Flags) {
+ return false;
+ }
+
+ const flags = aKeyInfo.Flags.split(",");
+ return flags.includes("Permanent");
+ });
+
+ // Sort by the .onion address.
+ this._keyInfoList.sort((aObj1, aObj2) => {
+ const hsAddr1 = aObj1.hsAddress.toLowerCase();
+ const hsAddr2 = aObj2.hsAddress.toLowerCase();
+ if (hsAddr1 < hsAddr2) {
+ return -1;
+ }
+ return hsAddr1 > hsAddr2 ? 1 : 0;
+ });
+ }
+
+ // Render the tree content.
+ this._tree.rowCountChanged(0, this.rowCount);
+ } catch (e) {
+ if (e.torMessage) {
+ this._showError(e.torMessage);
+ } else {
+ this._showError(controllerFailureMsg);
+ }
+ }
+
+ this._setBusyState(false);
+ },
+
+ // This method may throw; callers should catch errors.
+ async _deleteOneKey(aTorController, aIndex) {
+ const keyInfoObj = this._keyInfoList[aIndex];
+ await aTorController.onionAuthRemove(keyInfoObj.hsAddress);
+ this._tree.view.selection.clearRange(aIndex, aIndex);
+ this._keyInfoList.splice(aIndex, 1);
+ this._tree.rowCountChanged(aIndex + 1, -1);
+ },
+
+ _setBusyState(aIsBusy) {
+ this._isBusy = aIsBusy;
+ this.updateButtonsState();
+ },
+
+ _onWindowKeyPress(event) {
+ if (event.keyCode === KeyEvent.DOM_VK_ESCAPE) {
+ window.close();
+ } else if (event.keyCode === KeyEvent.DOM_VK_DELETE) {
+ this.deleteSelectedKeys();
+ }
+ },
+
+ _showError(aMessage) {
+ const dialog = document.querySelector(this.selector.dialog);
+ const errorIcon = dialog.querySelector(this.selector.errorIcon);
+ errorIcon.style.visibility = aMessage ? "visible" : "hidden";
+ const errorDesc = dialog.querySelector(this.selector.errorMessage);
+ errorDesc.textContent = aMessage ? aMessage : "";
+ },
+
+ // XUL tree widget view implementation.
+ get rowCount() {
+ return this._keyInfoList ? this._keyInfoList.length : 0;
+ },
+
+ getCellText(aRow, aCol) {
+ let val = "";
+ if (this._keyInfoList && aRow < this._keyInfoList.length) {
+ const keyInfo = this._keyInfoList[aRow];
+ if (aCol.id.endsWith("-siteCol")) {
+ val = keyInfo.hsAddress;
+ } else if (aCol.id.endsWith("-keyCol")) {
+ val = keyInfo.typeAndKey;
+ // Omit keyType because it is always "x25519".
+ const idx = val.indexOf(":");
+ if (idx > 0) {
+ val = val.substring(idx + 1);
+ }
+ }
+ }
+
+ return val;
+ },
+
+ isSeparator(index) {
+ return false;
+ },
+
+ isSorted() {
+ return false;
+ },
+
+ isContainer(index) {
+ return false;
+ },
+
+ setTree(tree) {},
+
+ getImageSrc(row, column) {},
+
+ getCellValue(row, column) {},
+
+ cycleHeader(column) {},
+
+ getRowProperties(row) {
+ return "";
+ },
+
+ getColumnProperties(column) {
+ return "";
+ },
+
+ getCellProperties(row, column) {
+ return "";
+ },
+};
+
+window.addEventListener("load", () => gOnionServicesSavedKeysDialog._onLoad());
diff --git a/browser/components/onionservices/content/savedKeysDialog.xhtml b/browser/components/onionservices/content/savedKeysDialog.xhtml
new file mode 100644
index 000000000000..3db9bb05ea82
--- /dev/null
+++ b/browser/components/onionservices/content/savedKeysDialog.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!-- Copyright (c) 2020, The Tor Project, Inc. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/onionservices/authPreferences.css" type="text/css"?>
+
+<window id="onionservices-savedkeys-dialog"
+ windowtype="OnionServices:SavedKeys"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: 45em;">
+
+ <script src="chrome://browser/content/onionservices/savedKeysDialog.js"/>
+
+ <vbox id="onionservices-savedkeys" class="contentPane" flex="1">
+ <label id="onionservices-savedkeys-intro"
+ control="onionservices-savedkeys-tree"/>
+ <separator class="thin"/>
+ <tree id="onionservices-savedkeys-tree" flex="1" hidecolumnpicker="true"
+ width="750"
+ style="height: 20em;"
+ onselect="gOnionServicesSavedKeysDialog.updateButtonsState();">
+ <treecols>
+ <treecol id="onionservices-savedkeys-siteCol" flex="1" persist="width"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="onionservices-savedkeys-keyCol" flex="1" persist="width"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ <hbox id="onionservices-savedkeys-errorContainer" align="baseline" flex="1">
+ <image id="onionservices-savedkeys-errorIcon"/>
+ <description id="onionservices-savedkeys-errorMessage" flex="1"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox id="onionservices-savedkeys-buttons">
+ <button id="onionservices-savedkeys-remove" disabled="true"
+ oncommand="gOnionServicesSavedKeysDialog.deleteSelectedKeys();"/>
+ <button id="onionservices-savedkeys-removeall"
+ oncommand="gOnionServicesSavedKeysDialog.deleteAllKeys();"/>
+ </hbox>
+ </vbox>
+</window>
diff --git a/browser/components/onionservices/jar.mn b/browser/components/onionservices/jar.mn
new file mode 100644
index 000000000000..9d6ce88d1841
--- /dev/null
+++ b/browser/components/onionservices/jar.mn
@@ -0,0 +1,9 @@
+browser.jar:
+ content/browser/onionservices/authPreferences.css (content/authPreferences.css)
+ content/browser/onionservices/authPreferences.js (content/authPreferences.js)
+ content/browser/onionservices/authPrompt.js (content/authPrompt.js)
+ content/browser/onionservices/authUtil.jsm (content/authUtil.jsm)
+ content/browser/onionservices/netError/ (content/netError/*)
+ content/browser/onionservices/onionservices.css (content/onionservices.css)
+ content/browser/onionservices/savedKeysDialog.js (content/savedKeysDialog.js)
+ content/browser/onionservices/savedKeysDialog.xhtml (content/savedKeysDialog.xhtml)
diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build
new file mode 100644
index 000000000000..2661ad7cb9f3
--- /dev/null
+++ b/browser/components/onionservices/moz.build
@@ -0,0 +1 @@
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml
index 0139abf95cbd..07ab5cc7b626 100644
--- a/browser/components/preferences/preferences.xhtml
+++ b/browser/components/preferences/preferences.xhtml
@@ -12,6 +12,7 @@
<?xml-stylesheet href="chrome://browser/skin/preferences/search.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/containers.css"?>
<?xml-stylesheet href="chrome://browser/skin/preferences/privacy.css"?>
+<?xml-stylesheet href="chrome://browser/content/onionservices/authPreferences.css"?>
<?xml-stylesheet href="chrome://browser/content/securitylevel/securityLevelPreferences.css"?>
<?xml-stylesheet href="chrome://browser/content/torpreferences/torPreferences.css"?>
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml
index d2cf2ea9c89c..18d01fee6b33 100644
--- a/browser/components/preferences/privacy.inc.xhtml
+++ b/browser/components/preferences/privacy.inc.xhtml
@@ -505,6 +505,8 @@
<label id="fips-desc" hidden="true" data-l10n-id="forms-master-pw-fips-desc"></label>
</groupbox>
+#include ../onionservices/content/authPreferences.inc.xhtml
+
<!-- The form autofill section is inserted in to this box
after the form autofill extension has initialized. -->
<groupbox id="formAutofillGroupBox"
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
index bce7bb7e8a9c..932d4291e486 100644
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -80,6 +80,12 @@ XPCOMUtils.defineLazyGetter(this, "AlertsServiceDND", function() {
}
});
+XPCOMUtils.defineLazyScriptGetter(
+ this,
+ ["OnionServicesAuthPreferences"],
+ "chrome://browser/content/onionservices/authPreferences.js"
+);
+
// TODO: module import via ChromeUtils.defineModuleGetter
XPCOMUtils.defineLazyScriptGetter(
this,
@@ -522,6 +528,7 @@ var gPrivacyPane = {
this.trackingProtectionReadPrefs();
this.networkCookieBehaviorReadPrefs();
this._initTrackingProtectionExtensionControl();
+ OnionServicesAuthPreferences.init();
this._initSecurityLevel();
Services.telemetry.setEventRecordingEnabled("pwmgr", true);
diff --git a/browser/themes/shared/notification-icons.inc.css b/browser/themes/shared/notification-icons.inc.css
index 658fa7f7430a..67dd640baf16 100644
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -137,6 +137,9 @@
list-style-image: url(chrome://browser/skin/notification-icons/persistent-storage.svg);
}
+/* Reuse Firefox's login (key) icon for the Tor onion services auth. prompt */
+.popup-notification-icon[popupid="tor-clientauth"],
+.tor-clientauth-icon,
.popup-notification-icon[popupid="password"],
.login-icon {
list-style-image: url(chrome://browser/skin/login.svg);
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index 2a792c314d4c..11583bec32a5 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3587,6 +3587,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
}
} else {
// Errors requiring simple formatting
+ bool isOnionAuthError = false;
switch (aError) {
case NS_ERROR_MALFORMED_URI:
// URI is malformed
@@ -3669,10 +3670,44 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
// HTTP/2 or HTTP/3 stack detected a protocol error
error = "networkProtocolError";
break;
-
+ case NS_ERROR_TOR_ONION_SVC_NOT_FOUND:
+ error = "onionServices.descNotFound";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_IS_INVALID:
+ error = "onionServices.descInvalid";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_INTRO_FAILED:
+ error = "onionServices.introFailed";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_REND_FAILED:
+ error = "onionServices.rendezvousFailed";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH:
+ error = "onionServices.clientAuthMissing";
+ isOnionAuthError = true;
+ break;
+ case NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH:
+ error = "onionServices.clientAuthIncorrect";
+ isOnionAuthError = true;
+ break;
+ case NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS:
+ error = "onionServices.badAddress";
+ break;
+ case NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT:
+ error = "onionServices.introTimedOut";
+ break;
default:
break;
}
+
+ // The presence of aFailedChannel indicates that we arrived here due to a
+ // failed connection attempt. Note that we will arrive here a second time
+ // if the user cancels the Tor client auth prompt, but in that case we
+ // will not have a failed channel and therefore we will not prompt again.
+ if (isOnionAuthError && aFailedChannel) {
+ // Display about:blank while the Tor client auth prompt is open.
+ errorPage.AssignLiteral("blank");
+ }
}
// If the HTTPS-Only Mode upgraded this request and the upgrade might have
@@ -3755,6 +3790,20 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
nsAutoString str;
rv =
stringBundle->FormatStringFromName(errorDescriptionID, formatStrs, str);
+ if (NS_FAILED(rv)) {
+ // As a fallback, check torbutton.properties for the error string.
+ const char bundleURL[] = "chrome://torbutton/locale/torbutton.properties";
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::services::GetStringBundleService();
+ if (stringBundleService) {
+ nsCOMPtr<nsIStringBundle> tbStringBundle;
+ if (NS_SUCCEEDED(stringBundleService->CreateBundle(
+ bundleURL, getter_AddRefs(tbStringBundle)))) {
+ rv = tbStringBundle->FormatStringFromName(errorDescriptionID,
+ formatStrs, str);
+ }
+ }
+ }
NS_ENSURE_SUCCESS(rv, rv);
messageStr.Assign(str);
}
@@ -6173,6 +6222,7 @@ nsresult nsDocShell::FilterStatusForErrorPage(
aStatus == NS_ERROR_FILE_ACCESS_DENIED ||
aStatus == NS_ERROR_CORRUPTED_CONTENT ||
aStatus == NS_ERROR_INVALID_CONTENT_ENCODING ||
+ NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_TOR ||
NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
// Errors to be shown for any frame
return aStatus;
@@ -7956,6 +8006,35 @@ nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType,
FireOnLocationChange(this, aRequest, mCurrentURI, locationFlags);
}
+ // Arrange to show a Tor onion service client authentication prompt if
+ // appropriate.
+ if ((mLoadType == LOAD_ERROR_PAGE) && failedChannel) {
+ nsresult status = NS_OK;
+ if (NS_SUCCEEDED(failedChannel->GetStatus(&status)) &&
+ ((status == NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH) ||
+ (status == NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH))) {
+ nsAutoCString onionHost;
+ failedURI->GetHost(onionHost);
+ const char* topic = (status == NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH)
+ ? "tor-onion-services-clientauth-missing"
+ : "tor-onion-services-clientauth-incorrect";
+ if (XRE_IsContentProcess()) {
+ nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild();
+ if (browserChild) {
+ static_cast<BrowserChild*>(browserChild.get())
+ ->SendShowOnionServicesAuthPrompt(onionHost, nsCString(topic));
+ }
+ } else {
+ nsCOMPtr<nsPIDOMWindowOuter> browserWin = GetWindow();
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (browserWin && obsSvc) {
+ obsSvc->NotifyObservers(browserWin, topic,
+ NS_ConvertUTF8toUTF16(onionHost).get());
+ }
+ }
+ }
+ }
+
return NS_OK;
}
diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp
index 05d77937f986..4145111ae849 100644
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -3810,6 +3810,27 @@ mozilla::ipc::IPCResult BrowserParent::RecvShowCanvasPermissionPrompt(
return IPC_OK();
}
+mozilla::ipc::IPCResult BrowserParent::RecvShowOnionServicesAuthPrompt(
+ const nsCString& aOnionName, const nsCString& aTopic) {
+ nsCOMPtr<nsIBrowser> browser =
+ mFrameElement ? mFrameElement->AsBrowser() : nullptr;
+ if (!browser) {
+ // If the tab is being closed, the browser may not be available.
+ // In this case we can ignore the request.
+ return IPC_OK();
+ }
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ if (!os) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ nsresult rv = os->NotifyObservers(browser, aTopic.get(),
+ NS_ConvertUTF8toUTF16(aOnionName).get());
+ if (NS_FAILED(rv)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+}
+
mozilla::ipc::IPCResult BrowserParent::RecvVisitURI(nsIURI* aURI,
nsIURI* aLastVisitedURI,
const uint32_t& aFlags) {
diff --git a/dom/ipc/BrowserParent.h b/dom/ipc/BrowserParent.h
index 80e4d055e26c..a36ebfc8ca05 100644
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -736,6 +736,9 @@ class BrowserParent final : public PBrowserParent,
mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt(
const nsCString& aOrigin, const bool& aHideDoorHanger);
+ mozilla::ipc::IPCResult RecvShowOnionServicesAuthPrompt(
+ const nsCString& aOnionName, const nsCString& aTopic);
+
mozilla::ipc::IPCResult RecvSetSystemFont(const nsCString& aFontName);
mozilla::ipc::IPCResult RecvGetSystemFont(nsCString* aFontName);
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl
index 9750219fa46a..5706c7f5da00 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -577,6 +577,15 @@ parent:
async RequestPointerCapture(uint32_t aPointerId) returns (bool aSuccess);
async ReleasePointerCapture(uint32_t aPointerId);
+ /**
+ * This function is used to notify the parent that it should display a
+ * onion services client authentication prompt.
+ *
+ * @param aOnionHost The hostname of the .onion that needs authentication.
+ * @param aTopic The reason for the prompt.
+ */
+ async ShowOnionServicesAuthPrompt(nsCString aOnionHost, nsCString aTopic);
+
child:
async NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
async UpdateEpoch(uint32_t aEpoch);
diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg
index c7fbdd23f378..07f529957bd0 100644
--- a/js/xpconnect/src/xpc.msg
+++ b/js/xpconnect/src/xpc.msg
@@ -248,5 +248,15 @@ XPC_MSG_DEF(NS_ERROR_FINGERPRINTING_URI , "The URI is fingerprinti
XPC_MSG_DEF(NS_ERROR_CRYPTOMINING_URI , "The URI is cryptomining")
XPC_MSG_DEF(NS_ERROR_SOCIALTRACKING_URI , "The URI is social tracking")
+/* Codes related to Tor */
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_NOT_FOUND , "Tor onion service descriptor cannot be found")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_IS_INVALID , "Tor onion service descriptor is invalid")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_INTRO_FAILED , "Tor onion service introduction failed")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_REND_FAILED , "Tor onion service rendezvous failed")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH, "Tor onion service missing client authorization")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH , "Tor onion service wrong client authorization")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS , "Tor onion service bad address")
+XPC_MSG_DEF(NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT , "Tor onion service introduction timed out")
+
/* Profile manager error codes */
XPC_MSG_DEF(NS_ERROR_DATABASE_CHANGED , "Flushing the profiles to disk would have overwritten changes made elsewhere.")
diff --git a/netwerk/base/nsSocketTransport2.cpp b/netwerk/base/nsSocketTransport2.cpp
index 8f44441e1fd0..99a6f3b60ac3 100644
--- a/netwerk/base/nsSocketTransport2.cpp
+++ b/netwerk/base/nsSocketTransport2.cpp
@@ -216,6 +216,12 @@ nsresult ErrorAccordingToNSPR(PRErrorCode errorCode) {
default:
if (psm::IsNSSErrorCode(errorCode)) {
rv = psm::GetXPCOMFromNSSError(errorCode);
+ } else {
+ // If we received a Tor extended error code via SOCKS, pass it through.
+ nsresult res = nsresult(errorCode);
+ if (NS_ERROR_GET_MODULE(res) == NS_ERROR_MODULE_TOR) {
+ rv = res;
+ }
}
break;
diff --git a/netwerk/socket/nsSOCKSIOLayer.cpp b/netwerk/socket/nsSOCKSIOLayer.cpp
index 119a3cbf4c51..f9fc29552ace 100644
--- a/netwerk/socket/nsSOCKSIOLayer.cpp
+++ b/netwerk/socket/nsSOCKSIOLayer.cpp
@@ -979,6 +979,55 @@ PRStatus nsSOCKSSocketInfo::ReadV5ConnectResponseTop() {
"08, Address type not supported."));
c = PR_BAD_ADDRESS_ERROR;
break;
+ case 0xF0: // Tor SOCKS5_HS_NOT_FOUND
+ LOGERROR(
+ ("socks5: connect failed: F0,"
+ " Tor onion service descriptor can not be found."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_NOT_FOUND);
+ break;
+ case 0xF1: // Tor SOCKS5_HS_IS_INVALID
+ LOGERROR(
+ ("socks5: connect failed: F1,"
+ " Tor onion service descriptor is invalid."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_IS_INVALID);
+ break;
+ case 0xF2: // Tor SOCKS5_HS_INTRO_FAILED
+ LOGERROR(
+ ("socks5: connect failed: F2,"
+ " Tor onion service introduction failed."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_INTRO_FAILED);
+ break;
+ case 0xF3: // Tor SOCKS5_HS_REND_FAILED
+ LOGERROR(
+ ("socks5: connect failed: F3,"
+ " Tor onion service rendezvous failed."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_REND_FAILED);
+ break;
+ case 0xF4: // Tor SOCKS5_HS_MISSING_CLIENT_AUTH
+ LOGERROR(
+ ("socks5: connect failed: F4,"
+ " Tor onion service missing client authorization."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH);
+ break;
+ case 0xF5: // Tor SOCKS5_HS_BAD_CLIENT_AUTH
+ LOGERROR(
+ ("socks5: connect failed: F5,"
+ " Tor onion service wrong client authorization."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH);
+ break;
+ case 0xF6: // Tor SOCKS5_HS_BAD_ADDRESS
+ LOGERROR(
+ ("socks5: connect failed: F6,"
+ " Tor onion service bad address."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS);
+ break;
+ case 0xF7: // Tor SOCKS5_HS_INTRO_TIMEDOUT
+ LOGERROR(
+ ("socks5: connect failed: F7,"
+ " Tor onion service introduction timed out."));
+ c = static_cast<uint32_t>(NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT);
+ break;
+
default:
LOGERROR(("socks5: connect failed."));
break;
diff --git a/toolkit/modules/PopupNotifications.jsm b/toolkit/modules/PopupNotifications.jsm
index d6518723afab..9764cfd496c3 100644
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -410,6 +410,8 @@ PopupNotifications.prototype = {
* will be dismissed instead of removed after running the callback.
* - [optional] disabled (boolean): If this is true, the button
* will be disabled.
+ * - [optional] leaveOpen (boolean): If this is true, the notification
+ * will not be removed after running the callback.
* - [optional] disableHighlight (boolean): If this is true, the button
* will not apply the default highlight style.
* If null, the notification will have a default "OK" action button
@@ -1916,6 +1918,10 @@ PopupNotifications.prototype = {
this._dismiss();
return;
}
+
+ if (action.leaveOpen) {
+ return;
+ }
}
this._remove(notification);
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index 9a762dc19d76..5125203866b8 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -102,6 +102,7 @@ let RemotePageAccessManager = {
RPMAddToHistogram: ["*"],
RPMGetInnerMostURI: ["*"],
RPMGetHttpResponseHeader: ["*"],
+ RPMGetTorStrings: ["*"],
RPMSendQuery: ["ShouldShowTorConnect"],
},
"about:plugins": {
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js
index 15c15615ad97..57458ba0bf5e 100644
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js
@@ -41,5 +41,6 @@ module.exports = {
RPMGetHttpResponseHeader: false,
RPMTryPingSecureWWWLink: false,
RPMOpenSecureWWWLink: false,
+ RPMGetTorStrings: false,
},
};
diff --git a/xpcom/base/ErrorList.py b/xpcom/base/ErrorList.py
index c22c27be8546..8fbcc7f663df 100755
--- a/xpcom/base/ErrorList.py
+++ b/xpcom/base/ErrorList.py
@@ -89,6 +89,7 @@ modules["ERRORRESULT"] = Mod(43)
# Win32 system error codes, which are not mapped to a specific other value,
# see Bug 1686041.
modules["WIN32"] = Mod(44)
+modules["TOR"] = Mod(45)
# NS_ERROR_MODULE_GENERAL should be used by modules that do not
# care if return code values overlap. Callers of methods that
@@ -1181,6 +1182,27 @@ with modules["ERRORRESULT"]:
errors["NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR"] = FAILURE(5)
+# =======================================================================
+# 45: Tor-specific error codes.
+# =======================================================================
+with modules["TOR"]:
+ # Tor onion service descriptor can not be found.
+ errors["NS_ERROR_TOR_ONION_SVC_NOT_FOUND"] = FAILURE(1)
+ # Tor onion service descriptor is invalid.
+ errors["NS_ERROR_TOR_ONION_SVC_IS_INVALID"] = FAILURE(2)
+ # Tor onion service introduction failed.
+ errors["NS_ERROR_TOR_ONION_SVC_INTRO_FAILED"] = FAILURE(3)
+ # Tor onion service rendezvous failed.
+ errors["NS_ERROR_TOR_ONION_SVC_REND_FAILED"] = FAILURE(4)
+ # Tor onion service missing client authorization.
+ errors["NS_ERROR_TOR_ONION_SVC_MISSING_CLIENT_AUTH"] = FAILURE(5)
+ # Tor onion service wrong client authorization.
+ errors["NS_ERROR_TOR_ONION_SVC_BAD_CLIENT_AUTH"] = FAILURE(6)
+ # Tor onion service bad address.
+ errors["NS_ERROR_TOR_ONION_SVC_BAD_ADDRESS"] = FAILURE(7)
+ # Tor onion service introduction timed out.
+ errors["NS_ERROR_TOR_ONION_SVC_INTRO_TIMEDOUT"] = FAILURE(8)
+
# =======================================================================
# 51: NS_ERROR_MODULE_GENERAL
# =======================================================================
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 16940: After update, load local change notes.
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 824fb2e096e268beb8ca8197cdb59123ba70392e
Author: Kathy Brade <brade(a)pearlcrescent.com>
Date: Wed Nov 25 11:36:20 2015 -0500
Bug 16940: After update, load local change notes.
Add an about:tbupdate page that displays the first section from
TorBrowser/Docs/ChangeLog.txt and includes a link to the remote
post-update page (typically our blog entry for the release).
Always load about:tbupdate in a content process, but implement the
code that reads the file system (changelog) in the chrome process
for compatibility with future sandboxing efforts.
Also fix bug 29440. Now about:tbupdate is styled as a fairly simple
changelog page that is designed to be displayed via a link that is on
about:tor.
---
browser/actors/AboutTBUpdateChild.jsm | 12 +++
browser/actors/AboutTBUpdateParent.jsm | 120 +++++++++++++++++++++
browser/actors/moz.build | 6 ++
.../base/content/abouttbupdate/aboutTBUpdate.css | 74 +++++++++++++
.../base/content/abouttbupdate/aboutTBUpdate.js | 27 +++++
.../base/content/abouttbupdate/aboutTBUpdate.xhtml | 39 +++++++
browser/base/content/browser-siteIdentity.js | 4 +-
browser/base/content/browser.js | 4 +
browser/base/jar.mn | 5 +
browser/components/BrowserContentHandler.jsm | 55 +++++++---
browser/components/BrowserGlue.jsm | 15 +++
browser/components/about/AboutRedirector.cpp | 6 ++
browser/components/about/components.conf | 3 +
browser/components/moz.build | 5 +-
.../locales/en-US/chrome/browser/aboutTBUpdate.dtd | 8 ++
browser/locales/jar.mn | 3 +
toolkit/modules/RemotePageAccessManager.jsm | 5 +
17 files changed, 375 insertions(+), 16 deletions(-)
diff --git a/browser/actors/AboutTBUpdateChild.jsm b/browser/actors/AboutTBUpdateChild.jsm
new file mode 100644
index 000000000000..4670da19b3db
--- /dev/null
+++ b/browser/actors/AboutTBUpdateChild.jsm
@@ -0,0 +1,12 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+// See LICENSE for licensing information.
+//
+// vim: set sw=2 sts=2 ts=8 et syntax=javascript:
+
+var EXPORTED_SYMBOLS = ["AboutTBUpdateChild"];
+
+const { RemotePageChild } = ChromeUtils.import(
+ "resource://gre/actors/RemotePageChild.jsm"
+);
+
+class AboutTBUpdateChild extends RemotePageChild {}
diff --git a/browser/actors/AboutTBUpdateParent.jsm b/browser/actors/AboutTBUpdateParent.jsm
new file mode 100644
index 000000000000..56a10394565a
--- /dev/null
+++ b/browser/actors/AboutTBUpdateParent.jsm
@@ -0,0 +1,120 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+// See LICENSE for licensing information.
+//
+// vim: set sw=2 sts=2 ts=8 et syntax=javascript:
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["AboutTBUpdateParent"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+const kRequestUpdateMessageName = "FetchUpdateData";
+
+/**
+ * This code provides services to the about:tbupdate page. Whenever
+ * about:tbupdate needs to do something chrome-privileged, it sends a
+ * message that's handled here. It is modeled after Mozilla's about:home
+ * implementation.
+ */
+class AboutTBUpdateParent extends JSWindowActorParent {
+ receiveMessage(aMessage) {
+ if (aMessage.name == kRequestUpdateMessageName) {
+ return this.releaseNoteInfo;
+ }
+ return undefined;
+ }
+
+ get moreInfoURL() {
+ try {
+ return Services.prefs.getCharPref("torbrowser.post_update.url");
+ } catch (e) {}
+
+ // Use the default URL as a fallback.
+ return Services.urlFormatter.formatURLPref("startup.homepage_override_url");
+ }
+
+ // Read the text from the beginning of the changelog file that is located
+ // at TorBrowser/Docs/ChangeLog.txt and return an object that contains
+ // the following properties:
+ // version e.g., Tor Browser 8.5
+ // releaseDate e.g., March 31 2019
+ // releaseNotes details of changes (lines 2 - end of ChangeLog.txt)
+ // We attempt to parse the first line of ChangeLog.txt to extract the
+ // version and releaseDate. If parsing fails, we return the entire first
+ // line in version and omit releaseDate.
+ //
+ // On Mac OS, when building with --enable-tor-browser-data-outside-app-dir
+ // to support Gatekeeper signing, the ChangeLog.txt file is located in
+ // TorBrowser.app/Contents/Resources/TorBrowser/Docs/.
+ get releaseNoteInfo() {
+ let info = { moreInfoURL: this.moreInfoURL };
+
+ try {
+ let f;
+ if (AppConstants.TOR_BROWSER_DATA_OUTSIDE_APP_DIR) {
+ // "XREExeF".parent is the directory that contains firefox, i.e.,
+ // Browser/ or, on Mac OS, TorBrowser.app/Contents/MacOS/.
+ f = Services.dirsvc.get("XREExeF", Ci.nsIFile).parent;
+ if (AppConstants.platform === "macosx") {
+ f = f.parent;
+ f.append("Resources");
+ }
+ f.append("TorBrowser");
+ } else {
+ // "DefProfRt" is .../TorBrowser/Data/Browser
+ f = Services.dirsvc.get("DefProfRt", Ci.nsIFile);
+ f = f.parent.parent; // Remove "Data/Browser"
+ }
+
+ f.append("Docs");
+ f.append("ChangeLog.txt");
+
+ let fs = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fs.init(f, -1, 0, 0);
+ let s = NetUtil.readInputStreamToString(fs, fs.available());
+ fs.close();
+
+ // Truncate at the first empty line.
+ s = s.replace(/[\r\n][\r\n][\s\S]*$/m, "");
+
+ // Split into first line (version plus releaseDate) and
+ // remainder (releaseNotes).
+ // This first match() uses multiline mode with two capture groups:
+ // first line: (.*$)
+ // remaining lines: ([\s\S]+)
+ // [\s\S] matches all characters including end of line. This trick
+ // is needed because when using JavaScript regex in multiline mode,
+ // . does not match an end of line character.
+ let matchArray = s.match(/(.*$)\s*([\s\S]+)/m);
+ if (matchArray && matchArray.length == 3) {
+ info.releaseNotes = matchArray[2];
+ let line1 = matchArray[1];
+ // Extract the version and releaseDate. The first line looks like:
+ // Tor Browser 8.5 -- May 1 2019
+ // The regex uses two capture groups:
+ // text that does not include a hyphen: (^[^-]*)
+ // remaining text: (.*$)
+ // In between we match optional whitespace, one or more hyphens, and
+ // optional whitespace by using: \s*-+\s*
+ matchArray = line1.match(/(^[^-]*)\s*-+\s*(.*$)/);
+ if (matchArray && matchArray.length == 3) {
+ info.version = matchArray[1];
+ info.releaseDate = matchArray[2];
+ } else {
+ info.version = line1; // Match failed: return entire line in version.
+ }
+ } else {
+ info.releaseNotes = s; // Only one line: use as releaseNotes.
+ }
+ } catch (e) {}
+
+ return info;
+ }
+}
diff --git a/browser/actors/moz.build b/browser/actors/moz.build
index 48e3dde6b832..00b0a3630f7c 100644
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -89,3 +89,9 @@ FINAL_TARGET_FILES.actors += [
"WebRTCChild.jsm",
"WebRTCParent.jsm",
]
+
+if CONFIG["TOR_BROWSER_UPDATE"]:
+ FINAL_TARGET_FILES.actors += [
+ "AboutTBUpdateChild.jsm",
+ "AboutTBUpdateParent.jsm",
+ ]
diff --git a/browser/base/content/abouttbupdate/aboutTBUpdate.css b/browser/base/content/abouttbupdate/aboutTBUpdate.css
new file mode 100644
index 000000000000..7c1a34b77f17
--- /dev/null
+++ b/browser/base/content/abouttbupdate/aboutTBUpdate.css
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019, The Tor Project, Inc.
+ * See LICENSE for licensing information.
+ *
+ * vim: set sw=2 sts=2 ts=8 et syntax=css:
+ */
+
+:root {
+ --abouttor-text-color: white;
+ --abouttor-bg-toron-color: #420C5D;
+}
+
+body {
+ font-family: Helvetica, Arial, sans-serif;
+ color: var(--abouttor-text-color);
+ background-color: var(--abouttor-bg-toron-color);
+ background-attachment: fixed;
+ background-size: 100% 100%;
+}
+
+a {
+ color: var(--abouttor-text-color);
+}
+
+.two-column-grid {
+ display: inline-grid;
+ grid-template-columns: auto auto;
+ grid-column-gap: 50px;
+ margin: 10px 0px 0px 50px;
+}
+
+.two-column-grid div {
+ margin-top: 40px;
+ align-self: baseline; /* Align baseline of text across the row. */
+}
+
+.label-column {
+ font-size: 14px;
+ font-weight: 400;
+}
+
+/*
+ * Use a reduced top margin to bring the row that contains the
+ * "visit our website" link closer to the row that precedes it. This
+ * looks better because the "visit our website" row does not have a
+ * label in the left column.
+ */
+div.more-info-row {
+ margin-top: 5px;
+ font-size: 14px;
+}
+
+#version-content {
+ font-size: 50px;
+ font-weight: 300;
+}
+
+body:not([havereleasedate]) .release-date-cell {
+ display: none;
+}
+
+#releasedate-content {
+ font-size: 17px;
+}
+
+#releasenotes-label {
+ align-self: start; /* Anchor "Release Notes" label at the top. */
+}
+
+#releasenotes-content {
+ font-family: monospace;
+ font-size: 15px;
+ white-space: pre;
+}
diff --git a/browser/base/content/abouttbupdate/aboutTBUpdate.js b/browser/base/content/abouttbupdate/aboutTBUpdate.js
new file mode 100644
index 000000000000..ec070e2cb131
--- /dev/null
+++ b/browser/base/content/abouttbupdate/aboutTBUpdate.js
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+// See LICENSE for licensing information.
+//
+// vim: set sw=2 sts=2 ts=8 et syntax=javascript:
+
+/* eslint-env mozilla/frame-script */
+
+// aData may contain the following string properties:
+// version
+// releaseDate
+// moreInfoURL
+// releaseNotes
+function onUpdate(aData) {
+ document.getElementById("version-content").textContent = aData.version;
+ if (aData.releaseDate) {
+ document.body.setAttribute("havereleasedate", "true");
+ document.getElementById("releasedate-content").textContent =
+ aData.releaseDate;
+ }
+ if (aData.moreInfoURL) {
+ document.getElementById("infolink").setAttribute("href", aData.moreInfoURL);
+ }
+ document.getElementById("releasenotes-content").textContent =
+ aData.releaseNotes;
+}
+
+RPMSendQuery("FetchUpdateData").then(onUpdate);
diff --git a/browser/base/content/abouttbupdate/aboutTBUpdate.xhtml b/browser/base/content/abouttbupdate/aboutTBUpdate.xhtml
new file mode 100644
index 000000000000..8489cfef5083
--- /dev/null
+++ b/browser/base/content/abouttbupdate/aboutTBUpdate.xhtml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % tbUpdateDTD SYSTEM "chrome://browser/locale/aboutTBUpdate.dtd">
+ %tbUpdateDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
+ <title>&aboutTBUpdate.changelogTitle;</title>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://browser/content/abouttbupdate/aboutTBUpdate.css"/>
+ <script src="chrome://browser/content/abouttbupdate/aboutTBUpdate.js"
+ type="text/javascript"/>
+</head>
+<body dir="&locale.dir;">
+<div class="two-column-grid">
+ <div class="label-column">&aboutTBUpdate.version;</div>
+ <div id="version-content"/>
+
+ <div class="label-column release-date-cell">&aboutTBUpdate.releaseDate;</div>
+ <div id="releasedate-content" class="release-date-cell"/>
+
+ <div class="more-info-row"/>
+ <div class="more-info-row">&aboutTBUpdate.linkPrefix;<a id="infolink">&aboutTBUpdate.linkLabel;</a>&aboutTBUpdate.linkSuffix;</div>
+
+ <div id="releasenotes-label"
+ class="label-column">&aboutTBUpdate.releaseNotes;</div>
+ <div id="releasenotes-content"></div>
+</div>
+</body>
+</html>
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js
index 6901ce71814a..acbcc79fb21e 100644
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -57,7 +57,9 @@ var gIdentityHandler = {
* RegExp used to decide if an about url should be shown as being part of
* the browser UI.
*/
- _secureInternalPages: /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i,
+ _secureInternalPages: (AppConstants.TOR_BROWSER_UPDATE ?
+ /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect|tbupdate)(?:[?#]|$)/i :
+ /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i),
/**
* Whether the established HTTPS connection is considered "broken".
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 1b1439a73c56..d1a07e4cf887 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -650,6 +650,10 @@ var gInitialPages = [
"about:welcome",
];
+if (AppConstants.TOR_BROWSER_UPDATE) {
+ gInitialPages.push("about:tbupdate");
+}
+
function isInitialPage(url) {
if (!(url instanceof Ci.nsIURI)) {
try {
diff --git a/browser/base/jar.mn b/browser/base/jar.mn
index 7be13da2dd5d..6554f6a5707e 100644
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -33,6 +33,11 @@ browser.jar:
content/browser/aboutTabCrashed.css (content/aboutTabCrashed.css)
content/browser/aboutTabCrashed.js (content/aboutTabCrashed.js)
content/browser/aboutTabCrashed.xhtml (content/aboutTabCrashed.xhtml)
+#ifdef TOR_BROWSER_UPDATE
+ content/browser/abouttbupdate/aboutTBUpdate.xhtml (content/abouttbupdate/aboutTBUpdate.xhtml)
+ content/browser/abouttbupdate/aboutTBUpdate.js (content/abouttbupdate/aboutTBUpdate.js)
+ content/browser/abouttbupdate/aboutTBUpdate.css (content/abouttbupdate/aboutTBUpdate.css)
+#endif
* content/browser/browser.css (content/browser.css)
content/browser/browser.js (content/browser.js)
* content/browser/browser.xhtml (content/browser.xhtml)
diff --git a/browser/components/BrowserContentHandler.jsm b/browser/components/BrowserContentHandler.jsm
index d8e24e641447..9f6c8a33a730 100644
--- a/browser/components/BrowserContentHandler.jsm
+++ b/browser/components/BrowserContentHandler.jsm
@@ -629,6 +629,23 @@ nsBrowserContentHandler.prototype = {
}
}
+ // Retrieve the home page early so we can compare it against about:tor
+ // to decide whether or not we need an override page (second tab) after
+ // an update was applied.
+ var startPage = "";
+ try {
+ var choice = prefb.getIntPref("browser.startup.page");
+ if (choice == 1 || choice == 3) {
+ startPage = HomePage.get();
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ }
+
+ if (startPage == "about:blank") {
+ startPage = "";
+ }
+
var override;
var overridePage = "";
var additionalPage = "";
@@ -674,6 +691,16 @@ nsBrowserContentHandler.prototype = {
// into account because that requires waiting for the session file
// to be read. If a crash occurs after updating, before restarting,
// we may open the startPage in addition to restoring the session.
+ //
+ // Tor Browser: Instead of opening the post-update "override page"
+ // directly, we ensure that about:tor will be opened in a special
+ // mode that notifies the user that their browser was updated.
+ // The about:tor page will provide a link to the override page
+ // where the user can learn more about the update, as well as a
+ // link to the Tor Browser changelog page (about:tbupdate). The
+ // override page URL comes from the openURL attribute within the
+ // updates.xml file or, if no showURL action is present, from the
+ // startup.homepage_override_url pref.
willRestoreSession = SessionStartup.isAutomaticRestoreEnabled();
overridePage = Services.urlFormatter.formatURLPref(
@@ -693,6 +720,20 @@ nsBrowserContentHandler.prototype = {
overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
overridePage = overridePage.replace("%OLD_TOR_BROWSER_VERSION%",
old_tbversion);
+#ifdef TOR_BROWSER_UPDATE
+ if (overridePage)
+ {
+ prefb.setCharPref("torbrowser.post_update.url", overridePage);
+ prefb.setBoolPref("torbrowser.post_update.shouldNotify", true);
+ // If the user's homepage is about:tor, we will inform them
+ // about the update on that page; otherwise, we arrange to
+ // open about:tor in a secondary tab.
+ if (startPage === "about:tor")
+ overridePage = "";
+ else
+ overridePage = "about:tor";
+ }
+#endif
break;
case OVERRIDE_NEW_BUILD_ID:
if (UpdateManager.readyUpdate) {
@@ -765,20 +806,6 @@ nsBrowserContentHandler.prototype = {
}
}
- var startPage = "";
- try {
- var choice = prefb.getIntPref("browser.startup.page");
- if (choice == 1 || choice == 3) {
- startPage = HomePage.get();
- }
- } catch (e) {
- Cu.reportError(e);
- }
-
- if (startPage == "about:blank") {
- startPage = "";
- }
-
let skipStartPage =
override == OVERRIDE_NEW_PROFILE &&
prefb.getBoolPref("browser.startup.firstrunSkipsHomepage");
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 2953decbd4e4..21912b6913f2 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -765,6 +765,21 @@ let JSWINDOWACTORS = {
},
};
+if (AppConstants.TOR_BROWSER_UPDATE) {
+ JSWINDOWACTORS["AboutTBUpdate"] = {
+ parent: {
+ moduleURI: "resource:///actors/AboutTBUpdateParent.jsm",
+ },
+ child: {
+ moduleURI: "resource:///actors/AboutTBUpdateChild.jsm",
+ events: {
+ DOMWindowCreated: { capture: true },
+ },
+ },
+ matches: ["about:tbupdate"],
+ };
+}
+
(function earlyBlankFirstPaint() {
let startTime = Cu.now();
if (
diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp
index 21f673f601d2..fd828a630c92 100644
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -122,6 +122,12 @@ static const RedirEntry kRedirMap[] = {
nsIAboutModule::HIDE_FROM_ABOUTABOUT},
{"restartrequired", "chrome://browser/content/aboutRestartRequired.xhtml",
nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+#ifdef TOR_BROWSER_UPDATE
+ {"tbupdate", "chrome://browser/content/abouttbupdate/aboutTBUpdate.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+#endif
{"torconnect", "chrome://browser/content/torconnect/aboutTorConnect.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf
index 733abef1a80f..0916bb75e1d5 100644
--- a/browser/components/about/components.conf
+++ b/browser/components/about/components.conf
@@ -31,6 +31,9 @@ pages = [
'welcomeback',
]
+if defined('TOR_BROWSER_UPDATE'):
+ pages.append('tbupdate')
+
Classes = [
{
'cid': '{7e4bb6ad-2fc4-4dc6-89ef-23e8e5ccf980}',
diff --git a/browser/components/moz.build b/browser/components/moz.build
index ec8fab7fbc8f..d29df1d3df99 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -88,11 +88,14 @@ EXTRA_COMPONENTS += [
]
EXTRA_JS_MODULES += [
- "BrowserContentHandler.jsm",
"BrowserGlue.jsm",
"distribution.js",
]
+EXTRA_PP_JS_MODULES += [
+ "BrowserContentHandler.jsm",
+]
+
BROWSER_CHROME_MANIFESTS += [
"safebrowsing/content/test/browser.ini",
"tests/browser/browser.ini",
diff --git a/browser/locales/en-US/chrome/browser/aboutTBUpdate.dtd b/browser/locales/en-US/chrome/browser/aboutTBUpdate.dtd
new file mode 100644
index 000000000000..2d1e59b40eaf
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/aboutTBUpdate.dtd
@@ -0,0 +1,8 @@
+<!ENTITY aboutTBUpdate.changelogTitle "Tor Browser Changelog">
+<!ENTITY aboutTBUpdate.updated "Tor Browser has been updated.">
+<!ENTITY aboutTBUpdate.linkPrefix "For the most up-to-date information about this release, ">
+<!ENTITY aboutTBUpdate.linkLabel "visit our website">
+<!ENTITY aboutTBUpdate.linkSuffix ".">
+<!ENTITY aboutTBUpdate.version "Version">
+<!ENTITY aboutTBUpdate.releaseDate "Release Date">
+<!ENTITY aboutTBUpdate.releaseNotes "Release Notes">
diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn
index fd6e0ac76843..90bc7a0d4757 100644
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -20,6 +20,9 @@
locale/browser/accounts.properties (%chrome/browser/accounts.properties)
locale/browser/app-extension-fields.properties (%chrome/browser/app-extension-fields.properties)
+#ifdef TOR_BROWSER_UPDATE
+ locale/browser/aboutTBUpdate.dtd (%chrome/browser/aboutTBUpdate.dtd)
+#endif
locale/browser/browser.dtd (%chrome/browser/browser.dtd)
locale/browser/browser.properties (%chrome/browser/browser.properties)
locale/browser/customizableui/customizableWidgets.properties (%chrome/browser/customizableui/customizableWidgets.properties)
diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm
index 486409ab5c8b..9a762dc19d76 100644
--- a/toolkit/modules/RemotePageAccessManager.jsm
+++ b/toolkit/modules/RemotePageAccessManager.jsm
@@ -214,6 +214,11 @@ let RemotePageAccessManager = {
RPMAddMessageListener: ["*"],
RPMRemoveMessageListener: ["*"],
},
+ "about:tbupdate": {
+ RPMSendQuery: [
+ "FetchUpdateData",
+ ],
+ },
"about:torconnect": {
RPMAddMessageListener: [
"torconnect:state-change",
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 28005: Implement .onion alias urlbar rewrites
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 1b79ebd22d459cb3d014afe052703415546afcab
Author: Alex Catarineu <acat(a)torproject.org>
Date: Thu Feb 13 13:24:33 2020 +0100
Bug 28005: Implement .onion alias urlbar rewrites
A custom HTTPS Everywhere update channel is installed,
which provides rules for locally redirecting some memorable
.tor.onion URLs to non-memorable .onion URLs.
When these redirects occur, we also rewrite the URL in the urlbar
to display the human-memorable hostname instead of the actual
.onion.
Bug 34196: Update site info URL with the onion name
Bug 40456: Update the SecureDrop HTTPS-Everywhere update channel
Bug 40478: Onion alias url rewrite is broken
---
browser/actors/ClickHandlerChild.jsm | 20 ++
browser/actors/ClickHandlerParent.jsm | 1 +
browser/actors/ContextMenuChild.jsm | 4 +
browser/base/content/browser-places.js | 12 +-
browser/base/content/browser-siteIdentity.js | 12 +-
browser/base/content/browser.js | 43 ++++-
browser/base/content/nsContextMenu.js | 18 ++
browser/base/content/pageinfo/pageInfo.js | 2 +-
browser/base/content/pageinfo/pageInfo.xhtml | 10 +
browser/base/content/pageinfo/security.js | 17 +-
browser/base/content/tabbrowser.js | 7 +
browser/base/content/utilityOverlay.js | 12 ++
browser/components/BrowserGlue.jsm | 29 +++
.../onionservices/ExtensionMessaging.jsm | 77 ++++++++
.../onionservices/HttpsEverywhereControl.jsm | 162 +++++++++++++++++
.../components/onionservices/OnionAliasStore.jsm | 201 +++++++++++++++++++++
browser/components/onionservices/moz.build | 6 +
browser/components/urlbar/UrlbarInput.jsm | 13 +-
docshell/base/nsDocShell.cpp | 52 ++++++
docshell/base/nsDocShell.h | 6 +
docshell/base/nsDocShellLoadState.cpp | 4 +
docshell/base/nsIDocShell.idl | 5 +
docshell/base/nsIWebNavigation.idl | 5 +
docshell/shistory/SessionHistoryEntry.cpp | 14 ++
docshell/shistory/SessionHistoryEntry.h | 1 +
docshell/shistory/nsISHEntry.idl | 5 +
docshell/shistory/nsSHEntry.cpp | 22 ++-
docshell/shistory/nsSHEntry.h | 1 +
dom/interfaces/base/nsIBrowser.idl | 3 +-
dom/ipc/BrowserChild.cpp | 2 +
dom/ipc/BrowserParent.cpp | 3 +-
dom/ipc/PBrowser.ipdl | 1 +
modules/libpref/init/StaticPrefList.yaml | 6 +
netwerk/dns/effective_tld_names.dat | 2 +
netwerk/ipc/DocumentLoadListener.cpp | 10 +
toolkit/content/widgets/browser-custom-element.js | 13 +-
toolkit/modules/sessionstore/SessionHistory.jsm | 5 +
xpcom/reflect/xptinfo/xptinfo.h | 3 +-
38 files changed, 786 insertions(+), 23 deletions(-)
diff --git a/browser/actors/ClickHandlerChild.jsm b/browser/actors/ClickHandlerChild.jsm
index 3c33deddd248..9c8aa9531da9 100644
--- a/browser/actors/ClickHandlerChild.jsm
+++ b/browser/actors/ClickHandlerChild.jsm
@@ -122,6 +122,26 @@ class ClickHandlerChild extends JSWindowActorChild {
json.originStoragePrincipal = ownerDoc.effectiveStoragePrincipal;
json.triggeringPrincipal = ownerDoc.nodePrincipal;
+ // Check if the link needs to be opened with .tor.onion urlbar rewrites
+ // allowed. Only when the owner doc has onionUrlbarRewritesAllowed = true
+ // and the same origin we should allow this.
+ json.onionUrlbarRewritesAllowed = false;
+ if (this.docShell.onionUrlbarRewritesAllowed) {
+ const sm = Services.scriptSecurityManager;
+ try {
+ let targetURI = Services.io.newURI(href);
+ let isPrivateWin =
+ ownerDoc.nodePrincipal.originAttributes.privateBrowsingId > 0;
+ sm.checkSameOriginURI(
+ docshell.currentDocumentChannel.URI,
+ targetURI,
+ false,
+ isPrivateWin
+ );
+ json.onionUrlbarRewritesAllowed = true;
+ } catch (e) {}
+ }
+
// If a link element is clicked with middle button, user wants to open
// the link somewhere rather than pasting clipboard content. Therefore,
// when it's clicked with middle button, we should prevent multiple
diff --git a/browser/actors/ClickHandlerParent.jsm b/browser/actors/ClickHandlerParent.jsm
index 89363074ed14..3a5be306be46 100644
--- a/browser/actors/ClickHandlerParent.jsm
+++ b/browser/actors/ClickHandlerParent.jsm
@@ -103,6 +103,7 @@ class ClickHandlerParent extends JSWindowActorParent {
let params = {
charset: browser.characterSet,
referrerInfo: E10SUtils.deserializeReferrerInfo(data.referrerInfo),
+ onionUrlbarRewritesAllowed: data.onionUrlbarRewritesAllowed,
isContentWindowPrivate: data.isContentWindowPrivate,
originPrincipal: data.originPrincipal,
originStoragePrincipal: data.originStoragePrincipal,
diff --git a/browser/actors/ContextMenuChild.jsm b/browser/actors/ContextMenuChild.jsm
index a9521642e495..dd7809eeb1ca 100644
--- a/browser/actors/ContextMenuChild.jsm
+++ b/browser/actors/ContextMenuChild.jsm
@@ -545,6 +545,9 @@ class ContextMenuChild extends JSWindowActorChild {
doc.defaultView
).getFieldContext(aEvent.composedTarget);
+ let parentAllowsOnionUrlbarRewrites = this.docShell
+ .onionUrlbarRewritesAllowed;
+
let disableSetDesktopBackground = null;
// Media related cache info parent needs for saving
@@ -656,6 +659,7 @@ class ContextMenuChild extends JSWindowActorChild {
frameID,
frameBrowsingContextID,
disableSetDesktopBackground,
+ parentAllowsOnionUrlbarRewrites,
};
if (context.inFrame && !context.inSrcdocFrame) {
diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js
index b0c9f6623097..d90dc636f8db 100644
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -470,7 +470,8 @@ var PlacesCommandHook = {
*/
async bookmarkPage() {
let browser = gBrowser.selectedBrowser;
- let url = new URL(browser.currentURI.spec);
+ const uri = browser.currentOnionAliasURI || browser.currentURI;
+ let url = new URL(uri.spec);
let info = await PlacesUtils.bookmarks.fetch({ url });
let isNewBookmark = !info;
let showEditUI = !isNewBookmark || StarUI.showForNewBookmarks;
@@ -581,7 +582,7 @@ var PlacesCommandHook = {
tabs.forEach(tab => {
let browser = tab.linkedBrowser;
- let uri = browser.currentURI;
+ let uri = browser.currentOnionAliasURI || browser.currentURI;
let title = browser.contentTitle || tab.label;
let spec = uri.spec;
if (!(spec in uniquePages)) {
@@ -1828,14 +1829,17 @@ var BookmarkingUI = {
},
onLocationChange: function BUI_onLocationChange() {
- if (this._uri && gBrowser.currentURI.equals(this._uri)) {
+ const uri =
+ gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI;
+ if (this._uri && uri.equals(this._uri)) {
return;
}
this.updateStarState();
},
updateStarState: function BUI_updateStarState() {
- this._uri = gBrowser.currentURI;
+ this._uri =
+ gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI;
this._itemGuids.clear();
let guids = new Set();
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js
index 6682ae8b096f..45b992c14fca 100644
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -658,13 +658,13 @@ var gIdentityHandler = {
* nsIURI for which the identity UI should be displayed, already
* processed by createExposableURI.
*/
- updateIdentity(state, uri) {
+ updateIdentity(state, uri, onionAliasURI) {
let shouldHidePopup = this._uri && this._uri.spec != uri.spec;
this._state = state;
// Firstly, populate the state properties required to display the UI. See
// the documentation of the individual properties for details.
- this.setURI(uri);
+ this.setURI(uri, onionAliasURI);
this._secInfo = gBrowser.securityUI.secInfo;
this._isSecureContext = gBrowser.securityUI.isSecureContext;
@@ -687,17 +687,18 @@ var gIdentityHandler = {
* Attempt to provide proper IDN treatment for host names
*/
getEffectiveHost() {
+ let uri = this._onionAliasURI || this._uri;
if (!this._IDNService) {
this._IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(
Ci.nsIIDNService
);
}
try {
- return this._IDNService.convertToDisplayIDN(this._uri.host, {});
+ return this._IDNService.convertToDisplayIDN(uri.host, {});
} catch (e) {
// If something goes wrong (e.g. host is an IP address) just fail back
// to the full domain.
- return this._uri.host;
+ return uri.host;
}
},
@@ -1140,11 +1141,12 @@ var gIdentityHandler = {
this._identityPopupContentVerif.textContent = verifier;
},
- setURI(uri) {
+ setURI(uri, onionAliasURI) {
if (uri.schemeIs("view-source")) {
uri = Services.io.newURI(uri.spec.replace(/^view-source:/i, ""));
}
this._uri = uri;
+ this._onionAliasURI = onionAliasURI;
try {
// Account for file: urls and catch when "" is the value
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 4f7852d1b510..13f330c20be3 100644
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -82,6 +82,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
TorConnect: "resource:///modules/TorConnect.jsm",
Translation: "resource:///modules/translation/TranslationParent.jsm",
+ OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
UITour: "resource:///modules/UITour.jsm",
UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
UrlbarInput: "resource:///modules/UrlbarInput.jsm",
@@ -2248,6 +2249,7 @@ var gBrowserInit = {
// [9]: allowInheritPrincipal (bool)
// [10]: csp (nsIContentSecurityPolicy)
// [11]: nsOpenWindowInfo
+ // [12]: onionUrlbarRewritesAllowed (bool)
let userContextId =
window.arguments[5] != undefined
? window.arguments[5]
@@ -2267,7 +2269,8 @@ var gBrowserInit = {
// TODO fix allowInheritPrincipal to default to false.
// Default to true unless explicitly set to false because of bug 1475201.
window.arguments[9] !== false,
- window.arguments[10]
+ window.arguments[10],
+ window.arguments[12]
);
window.focus();
} else {
@@ -3065,7 +3068,8 @@ function loadURI(
forceAboutBlankViewerInCurrent,
triggeringPrincipal,
allowInheritPrincipal = false,
- csp = null
+ csp = null,
+ onionUrlbarRewritesAllowed = false
) {
if (!triggeringPrincipal) {
throw new Error("Must load with a triggering Principal");
@@ -3083,6 +3087,7 @@ function loadURI(
csp,
forceAboutBlankViewerInCurrent,
allowInheritPrincipal,
+ onionUrlbarRewritesAllowed,
});
} catch (e) {
Cu.reportError(e);
@@ -5209,11 +5214,24 @@ var XULBrowserWindow = {
this.reloadCommand.removeAttribute("disabled");
}
+ // The onion memorable alias needs to be used in gURLBar.setURI, but also in
+ // other parts of the code (like the bookmarks UI), so we save it.
+ if (gBrowser.selectedBrowser.onionUrlbarRewritesAllowed) {
+ gBrowser.selectedBrowser.currentOnionAliasURI = OnionAliasStore.getShortURI(
+ aLocationURI
+ );
+ } else {
+ gBrowser.selectedBrowser.currentOnionAliasURI = null;
+ }
+
// We want to update the popup visibility if we received this notification
// via simulated locationchange events such as switching between tabs, however
// if this is a document navigation then PopupNotifications will be updated
// via TabsProgressListener.onLocationChange and we do not want it called twice
- gURLBar.setURI(aLocationURI, aIsSimulated);
+ gURLBar.setURI(
+ gBrowser.selectedBrowser.currentOnionAliasURI || aLocationURI,
+ aIsSimulated
+ );
BookmarkingUI.onLocationChange();
// If we've actually changed document, update the toolbar visibility.
@@ -5436,6 +5454,7 @@ var XULBrowserWindow = {
// Don't need to do anything if the data we use to update the UI hasn't
// changed
let uri = gBrowser.currentURI;
+ let onionAliasURI = gBrowser.selectedBrowser.currentOnionAliasURI;
let spec = uri.spec;
let isSecureContext = gBrowser.securityUI.isSecureContext;
if (
@@ -5459,7 +5478,7 @@ var XULBrowserWindow = {
try {
uri = Services.io.createExposableURI(uri);
} catch (e) {}
- gIdentityHandler.updateIdentity(this._state, uri);
+ gIdentityHandler.updateIdentity(this._state, uri, onionAliasURI);
},
// simulate all change notifications after switching tabs
@@ -6968,6 +6987,21 @@ function handleLinkClick(event, href, linkNode) {
return true;
}
+ // Check if the link needs to be opened with .tor.onion urlbar rewrites
+ // allowed. Only when the owner doc has onionUrlbarRewritesAllowed = true
+ // and the same origin we should allow this.
+ let persistOnionUrlbarRewritesAllowedInChildTab = false;
+ if (where == "tab" && gBrowser.docShell.onionUrlbarRewritesAllowed) {
+ const sm = Services.scriptSecurityManager;
+ try {
+ let tURI = makeURI(href);
+ let isPrivateWin =
+ doc.nodePrincipal.originAttributes.privateBrowsingId > 0;
+ sm.checkSameOriginURI(doc.documentURIObject, tURI, false, isPrivateWin);
+ persistOnionUrlbarRewritesAllowedInChildTab = true;
+ } catch (e) {}
+ }
+
let frameID = WebNavigationFrames.getFrameId(doc.defaultView);
urlSecurityCheck(href, doc.nodePrincipal);
@@ -6979,6 +7013,7 @@ function handleLinkClick(event, href, linkNode) {
triggeringPrincipal: doc.nodePrincipal,
csp: doc.csp,
frameID,
+ onionUrlbarRewritesAllowed: persistOnionUrlbarRewritesAllowedInChildTab,
};
// The new tab/window must use the same userContextId
diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js
index 1ff16ffbab9f..e96df23142f8 100644
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -58,6 +58,7 @@ function openContextMenu(aMessage, aBrowser, aActor) {
selectionInfo: data.selectionInfo,
disableSetDesktopBackground: data.disableSetDesktopBackground,
loginFillInfo: data.loginFillInfo,
+ parentAllowsOnionUrlbarRewrites: data.parentAllowsOnionUrlbarRewrites,
userContextId: data.userContextId,
webExtContextData: data.webExtContextData,
cookieJarSettings: E10SUtils.deserializeCookieJarSettings(
@@ -1197,6 +1198,7 @@ class nsContextMenu {
triggeringPrincipal: this.principal,
csp: this.csp,
frameID: this.contentData.frameID,
+ onionUrlbarRewritesAllowed: false,
};
for (let p in extra) {
params[p] = extra[p];
@@ -1220,6 +1222,22 @@ class nsContextMenu {
}
params.referrerInfo = referrerInfo;
+
+ // Check if the link needs to be opened with .tor.onion urlbar rewrites
+ // allowed. Only when parent has onionUrlbarRewritesAllowed = true
+ // and the same origin we should allow this.
+ if (this.contentData.parentAllowsOnionUrlbarRewrites) {
+ let referrerURI = this.contentData.documentURIObject;
+ const sm = Services.scriptSecurityManager;
+ try {
+ let targetURI = this.linkURI;
+ let isPrivateWin =
+ this.browser.contentPrincipal.originAttributes.privateBrowsingId > 0;
+ sm.checkSameOriginURI(referrerURI, targetURI, false, isPrivateWin);
+ params.onionUrlbarRewritesAllowed = true;
+ } catch (e) {}
+ }
+
return params;
}
diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js
index cd02b73bd0c7..dd1a4a90fedf 100644
--- a/browser/base/content/pageinfo/pageInfo.js
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -398,7 +398,7 @@ async function onNonMediaPageInfoLoad(browser, pageInfoData, imageInfo) {
);
}
onLoadPermission(uri, principal);
- securityOnLoad(uri, windowInfo);
+ securityOnLoad(uri, windowInfo, browser.currentOnionAliasURI);
}
function resetPageInfo(args) {
diff --git a/browser/base/content/pageinfo/pageInfo.xhtml b/browser/base/content/pageinfo/pageInfo.xhtml
index f40ffd3778d8..a23f2bb5748c 100644
--- a/browser/base/content/pageinfo/pageInfo.xhtml
+++ b/browser/base/content/pageinfo/pageInfo.xhtml
@@ -312,6 +312,16 @@
<input id="security-identity-domain-value" readonly="readonly"/>
</td>
</tr>
+ <!-- Onion Alias -->
+ <tr id="security-view-identity-onionalias-row">
+ <th>
+ <xul:label id="security-view-identity-onionalias"
+ control="security-view-identity-onionalias-value"/>
+ </th>
+ <td>
+ <input id="security-view-identity-onionalias-value" readonly="true"/>
+ </td>
+ </tr>
<!-- Owner -->
<tr>
<th>
diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js
index 8d10c8df814c..2e22f4670503 100644
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -248,7 +248,7 @@ var security = {
},
};
-async function securityOnLoad(uri, windowInfo) {
+async function securityOnLoad(uri, windowInfo, onionAliasURI) {
await security.init(uri, windowInfo);
let info = security.securityInfo;
@@ -261,6 +261,21 @@ async function securityOnLoad(uri, windowInfo) {
}
document.getElementById("securityTab").hidden = false;
+ if (onionAliasURI) {
+ setText(
+ "security-view-identity-onionalias",
+ gTorButtonBundle.GetStringFromName("pageInfo_OnionName")
+ );
+ setText("security-view-identity-onionalias-value", onionAliasURI.host);
+ document.getElementById(
+ "security-view-identity-onionalias-row"
+ ).hidden = false;
+ } else {
+ document.getElementById(
+ "security-view-identity-onionalias-row"
+ ).hidden = true;
+ }
+
/* Set Identity section text */
setText("security-identity-domain-value", windowInfo.hostName);
diff --git a/browser/base/content/tabbrowser.js b/browser/base/content/tabbrowser.js
index e47c81541bfa..520fea7cc345 100644
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -1635,6 +1635,7 @@
var aFromExternal;
var aRelatedToCurrent;
var aAllowInheritPrincipal;
+ var aOnionUrlbarRewritesAllowed;
var aSkipAnimation;
var aForceNotRemote;
var aPreferredRemoteType;
@@ -1664,6 +1665,7 @@
aFromExternal = params.fromExternal;
aRelatedToCurrent = params.relatedToCurrent;
aAllowInheritPrincipal = !!params.allowInheritPrincipal;
+ aOnionUrlbarRewritesAllowed = params.onionUrlbarRewritesAllowed;
aSkipAnimation = params.skipAnimation;
aForceNotRemote = params.forceNotRemote;
aPreferredRemoteType = params.preferredRemoteType;
@@ -1704,6 +1706,7 @@
fromExternal: aFromExternal,
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipAnimation,
+ onionUrlbarRewritesAllowed: aOnionUrlbarRewritesAllowed,
forceNotRemote: aForceNotRemote,
createLazyBrowser: aCreateLazyBrowser,
preferredRemoteType: aPreferredRemoteType,
@@ -2536,6 +2539,7 @@
aURI,
{
allowInheritPrincipal,
+ onionUrlbarRewritesAllowed,
allowThirdPartyFixup,
bulkOrderedOpen,
charset,
@@ -2877,6 +2881,9 @@
// lands.
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD;
}
+ if (onionUrlbarRewritesAllowed) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
if (!allowInheritPrincipal) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
index 010e438b33f8..5f629dece94e 100644
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -381,6 +381,7 @@ function openLinkIn(url, where, params) {
: new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, null);
var aRelatedToCurrent = params.relatedToCurrent;
var aAllowInheritPrincipal = !!params.allowInheritPrincipal;
+ var aOnionUrlbarRewritesAllowed = params.onionUrlbarRewritesAllowed;
var aForceAllowDataURI = params.forceAllowDataURI;
var aInBackground = params.inBackground;
var aInitiatingDoc = params.initiatingDoc;
@@ -497,6 +498,11 @@ function openLinkIn(url, where, params) {
].createInstance(Ci.nsISupportsPRBool);
allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
+ var onionUrlbarRewritesAllowed = Cc[
+ "@mozilla.org/supports-PRBool;1"
+ ].createInstance(Ci.nsISupportsPRBool);
+ onionUrlbarRewritesAllowed.data = aOnionUrlbarRewritesAllowed;
+
var userContextIdSupports = Cc[
"@mozilla.org/supports-PRUint32;1"
].createInstance(Ci.nsISupportsPRUint32);
@@ -513,6 +519,8 @@ function openLinkIn(url, where, params) {
sa.appendElement(aTriggeringPrincipal);
sa.appendElement(null); // allowInheritPrincipal
sa.appendElement(aCsp);
+ sa.appendElement(null); // nsOpenWindowInfo
+ sa.appendElement(onionUrlbarRewritesAllowed);
const sourceWindow = w || window;
let win;
@@ -630,6 +638,9 @@ function openLinkIn(url, where, params) {
if (aForceAllowDataURI) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
}
+ if (aOnionUrlbarRewritesAllowed) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler;
if (
@@ -676,6 +687,7 @@ function openLinkIn(url, where, params) {
allowThirdPartyFixup: aAllowThirdPartyFixup,
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipTabAnimation,
+ onionUrlbarRewritesAllowed: aOnionUrlbarRewritesAllowed,
userContextId: aUserContextId,
originPrincipal: aPrincipal,
originStoragePrincipal: aStoragePrincipal,
diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm
index 21912b6913f2..4c340b71587c 100644
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -85,6 +85,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabUnloader: "resource:///modules/TabUnloader.jsm",
TelemetryUtils: "resource://gre/modules/TelemetryUtils.jsm",
TRRRacer: "resource:///modules/TRRPerformance.jsm",
+ OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
UIState: "resource://services-sync/UIState.jsm",
UrlbarQuickSuggest: "resource:///modules/UrlbarQuickSuggest.jsm",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
@@ -2015,6 +2016,7 @@ BrowserGlue.prototype = {
Normandy.uninit();
RFPHelper.uninit();
ASRouterNewTabHook.destroy();
+ OnionAliasStore.uninit();
},
// Set up a listener to enable/disable the screenshots extension
@@ -2519,6 +2521,33 @@ BrowserGlue.prototype = {
},
},
+ {
+ task: () => {
+ const { TorConnect, TorConnectTopics } = ChromeUtils.import(
+ "resource:///modules/TorConnect.jsm"
+ );
+ if (!TorConnect.shouldShowTorConnect) {
+ // we will take this path when the user is using the legacy tor launcher or
+ // when Tor Browser didn't launch its own tor.
+ OnionAliasStore.init();
+ } else {
+ // this path is taken when using about:torconnect, we wait to init
+ // after we are bootstrapped and connected to tor
+ const topic = TorConnectTopics.BootstrapComplete;
+ let bootstrapObserver = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic === topic) {
+ OnionAliasStore.init();
+ // we only need to init once, so remove ourselves as an obvserver
+ Services.obs.removeObserver(this, topic);
+ }
+ }
+ };
+ Services.obs.addObserver(bootstrapObserver, topic);
+ }
+ },
+ },
+
{
task: () => {
Blocklist.loadBlocklistAsync();
diff --git a/browser/components/onionservices/ExtensionMessaging.jsm b/browser/components/onionservices/ExtensionMessaging.jsm
new file mode 100644
index 000000000000..c93b8c6edf85
--- /dev/null
+++ b/browser/components/onionservices/ExtensionMessaging.jsm
@@ -0,0 +1,77 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["ExtensionMessaging"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { ExtensionUtils } = ChromeUtils.import(
+ "resource://gre/modules/ExtensionUtils.jsm"
+);
+const { MessageChannel } = ChromeUtils.import(
+ "resource://gre/modules/MessageChannel.jsm"
+);
+const { AddonManager } = ChromeUtils.import(
+ "resource://gre/modules/AddonManager.jsm"
+);
+
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ ExtensionParent: "resource://gre/modules/ExtensionParent.jsm",
+});
+
+class ExtensionMessaging {
+ constructor() {
+ this._callback = null;
+ this._handlers = new Map();
+ this._messageManager = Services.cpmm;
+ }
+
+ async sendMessage(message, extensionId) {
+ const addon = await AddonManager.getAddonByID(extensionId);
+ if (!addon) {
+ throw new Error(`extension '${extensionId} does not exist`);
+ }
+ await addon.startupPromise;
+
+ const { torSendExtensionMessage } = ExtensionParent;
+ return torSendExtensionMessage(extensionId, message);
+ }
+
+ unload() {
+ if (this._callback) {
+ this._handlers.clear();
+ this._messageManager.removeMessageListener(
+ "MessageChannel:Response",
+ this._callback
+ );
+ this._callback = null;
+ }
+ }
+
+ _onMessage({ data }) {
+ const channelId = data.messageName;
+ if (this._handlers.has(channelId)) {
+ const { resolve, reject } = this._handlers.get(channelId);
+ this._handlers.delete(channelId);
+ if (data.error) {
+ reject(new Error(data.error.message));
+ } else {
+ resolve(data.value);
+ }
+ }
+ }
+
+ _init() {
+ if (this._callback === null) {
+ this._callback = this._onMessage.bind(this);
+ this._messageManager.addMessageListener(
+ "MessageChannel:Response",
+ this._callback
+ );
+ }
+ }
+}
diff --git a/browser/components/onionservices/HttpsEverywhereControl.jsm b/browser/components/onionservices/HttpsEverywhereControl.jsm
new file mode 100644
index 000000000000..d673de4cd6e5
--- /dev/null
+++ b/browser/components/onionservices/HttpsEverywhereControl.jsm
@@ -0,0 +1,162 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["HttpsEverywhereControl"];
+
+const { ExtensionMessaging } = ChromeUtils.import(
+ "resource:///modules/ExtensionMessaging.jsm"
+);
+const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+const EXTENSION_ID = "https-everywhere-eff(a)eff.org";
+const SECUREDROP_TOR_ONION_CHANNEL_2020 = {
+ name: "SecureDropTorOnion",
+ jwk: {
+ kty: "RSA",
+ e: "AQAB",
+ n:
+ "p10BbUVc5Xj2S_-MH3bACNBaISo_r9e3PVPyTTjsGsdg2qSXvqUO42fBtpFAy0zUzIGS83v4JjiRdvKJaZTIvbC8AcpymzdsTqujMm8RPTSy3hO_8mXzGa4DEsIB1uNLnUWRBKXvSGCmT9kFyxhTpkYqokNBzafVihTU34tN2Md1xFHnmZGqfYtPtbJLWAa5Z1M11EyR4lIyUxIiPTV9t1XstDbWr3iS83REJrGEFmjG1-BAgx8_lDUTa41799N2yYEhgZud7bL0M3ei8s5OERjiion5uANkUV3-s2QqUZjiVA-XR_HizXjciaUWNd683KqekpNOZ_0STh_UGwpcwU-KwG07QyiCrLrRpz8S_vH8CqGrrcWY3GSzYe9dp34jJdO65oA-G8tK6fMXtvTCFDZI6oNNaXJH71F5J0YbqO2ZqwKYc2WSi0gKVl2wd9roOVjaBmkJqvocntYuNM7t38fDEWHn5KUkmrTbiG68Cy56tDUfpKl3D9Uj4LaMvxJ1tKGvzQ4k_60odT7gIxu6DqYjXUHZpwPsSGBq3njaD7boe4CUXF2K7ViOc87BsKxRNCzDD8OklRjjXzOTOBH3PqFJ93CJ-4ECE5t9STU20aZ8E-2zKB8vjKyCySE4-kcIvBBsnkwVaJTPy9Ft1qYybo-soXEWVEZATANNWklBt8k",
+ },
+ update_path_prefix: "https://securedrop.org/https-everywhere/",
+ scope:
+ "^https?:\\/\\/[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.securedrop\\.tor\\.onion\\/",
+ replaces_default_rulesets: false,
+};
+
+const SECUREDROP_TOR_ONION_CHANNEL = {
+ name: "SecureDropTorOnion2021",
+ jwk: {
+ kty: "RSA",
+ e: "AQAB",
+ n:
+ "vsC7BNafkRe8Uh1DUgCkv6RbPQMdJgAKKnWdSqQd7tQzU1mXfmo_k1Py_2MYMZXOWmqSZ9iwIYkykZYywJ2VyMGve4byj1sLn6YQoOkG8g5Z3V4y0S2RpEfmYumNjTzfq8nxtLnwjaYd4sCUd5wa0SzeLrpRQuXo2bF3QuUF2xcbLJloxX1MmlsMMCdBc-qGNonLJ7bpn_JuyXlDWy1Fkeyw1qgjiOdiRIbMC1x302zgzX6dSrBrNB8Cpsh-vCE0ZjUo8M9caEv06F6QbYmdGJHM0ZZY34OHMSNdf-_qUKIV_SuxuSuFE99tkAeWnbWpyI1V-xhVo1sc7NzChP8ci2TdPvI3_0JyAuCvL6zIFqJUJkZibEUghhg6F09-oNJKpy7rhUJq7zZyLXJsvuXnn0gnIxfjRvMcDfZAKUVMZKRdw7fwWzwQril4Ib0MQOVda9vb_4JMk7Gup-TUI4sfuS4NKwsnKoODIO-2U5QpJWdtp1F4AQ1pBv8ajFl1WTrVGvkRGK0woPWaO6pWyJ4kRnhnxrV2FyNNt3JSR-0JEjhFWws47kjBvpr0VRiVRFppKA-plKs4LPlaaCff39TleYmY3mETe3w1GIGc2Lliad32Jpbx496IgDe1K3FMBEoKFZfhmtlRSXft8NKgSzPt2zkatM9bFKfaCYRaSy7akbk",
+ },
+ update_path_prefix: "https://securedrop.org/https-everywhere-2021/",
+ scope:
+ "^https?:\\/\\/[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.securedrop\\.tor\\.onion\\/",
+ replaces_default_rulesets: false,
+};
+
+class HttpsEverywhereControl {
+ constructor() {
+ this._extensionMessaging = null;
+ this._init();
+ }
+
+ async _sendMessage(type, object) {
+ return this._extensionMessaging.sendMessage(
+ {
+ type,
+ object,
+ },
+ EXTENSION_ID
+ );
+ }
+
+ static async wait(seconds = 1) {
+ return new Promise(resolve => setTimeout(resolve, seconds * 1000));
+ }
+
+ /**
+ * Installs the .tor.onion update channel in https-everywhere
+ */
+ async installTorOnionUpdateChannel(retries = 5) {
+
+ // TODO: https-everywhere store is initialized asynchronously, so sending a message
+ // immediately results in a `store.get is undefined` error.
+ // For now, let's wait a bit and retry a few times if there is an error, but perhaps
+ // we could suggest https-everywhere to send a message when that happens and listen
+ // for that here.
+ await HttpsEverywhereControl.wait();
+
+ try {
+ // Delete the previous channel signing key, and add the new one below.
+ await this._sendMessage(
+ "delete_update_channel",
+ SECUREDROP_TOR_ONION_CHANNEL_2020.name
+ );
+ } catch (e) {
+ if (retries <= 0) {
+ throw new Error("Could not uninstall SecureDropTorOnion update channel");
+ }
+ await this.installTorOnionUpdateChannel(retries - 1);
+ return;
+ }
+
+ try {
+ // TODO: we may want a way to "lock" this update channel, so that it cannot be modified
+ // by the user via UI, but I think this is not possible at the time of writing via
+ // the existing messages in https-everywhere.
+ await this._sendMessage(
+ "create_update_channel",
+ SECUREDROP_TOR_ONION_CHANNEL.name
+ );
+ } catch (e) {
+ if (retries <= 0) {
+ throw new Error("Could not install SecureDropTorOnion update channel");
+ }
+ await this.installTorOnionUpdateChannel(retries - 1);
+ return;
+ }
+
+ await this._sendMessage(
+ "update_update_channel",
+ SECUREDROP_TOR_ONION_CHANNEL
+ );
+ }
+
+ /**
+ * Returns the .tor.onion rulesets available in https-everywhere
+ */
+ async getTorOnionRules() {
+ return this._sendMessage("get_simple_rules_ending_with", ".tor.onion");
+ }
+
+ /**
+ * Returns the timestamp of the last .tor.onion update channel update.
+ */
+ async getRulesetTimestamp() {
+ const rulesets = await this._sendMessage("get_update_channel_timestamps");
+ const securedrop =
+ rulesets &&
+ rulesets.find(([{ name }]) => name === SECUREDROP_TOR_ONION_CHANNEL.name);
+ if (securedrop) {
+ const [
+ updateChannel, // This has the same structure as SECUREDROP_TOR_ONION_CHANNEL
+ lastUpdatedTimestamp, // An integer, 0 if the update channel was never updated
+ ] = securedrop;
+ void updateChannel; // Ignore eslint unused warning for ruleset
+ return lastUpdatedTimestamp;
+ }
+ return null;
+ }
+
+ unload() {
+ if (this._extensionMessaging) {
+ this._extensionMessaging.unload();
+ this._extensionMessaging = null;
+ }
+ }
+
+ _init() {
+ if (!this._extensionMessaging) {
+ this._extensionMessaging = new ExtensionMessaging();
+ }
+
+ // update all of the existing https-everywhere channels
+ setTimeout(async () => {
+ let pinnedChannels = await this._sendMessage("get_pinned_update_channels");
+ for(let channel of pinnedChannels.update_channels) {
+ this._sendMessage("update_update_channel", channel);
+ }
+
+ let storedChannels = await this._sendMessage("get_stored_update_channels");
+ for(let channel of storedChannels.update_channels) {
+ this._sendMessage("update_update_channel", channel);
+ }
+ }, 0);
+
+
+ }
+}
diff --git a/browser/components/onionservices/OnionAliasStore.jsm b/browser/components/onionservices/OnionAliasStore.jsm
new file mode 100644
index 000000000000..66cf569227bf
--- /dev/null
+++ b/browser/components/onionservices/OnionAliasStore.jsm
@@ -0,0 +1,201 @@
+// Copyright (c) 2020, The Tor Project, Inc.
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["OnionAliasStore"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+const { setTimeout, clearTimeout } = ChromeUtils.import(
+ "resource://gre/modules/Timer.jsm"
+);
+const { HttpsEverywhereControl } = ChromeUtils.import(
+ "resource:///modules/HttpsEverywhereControl.jsm"
+);
+
+// Logger adapted from CustomizableUI.jsm
+const kPrefOnionAliasDebug = "browser.onionalias.debug";
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "gDebuggingEnabled",
+ kPrefOnionAliasDebug,
+ false,
+ (pref, oldVal, newVal) => {
+ if (typeof log != "undefined") {
+ log.maxLogLevel = newVal ? "all" : "log";
+ }
+ }
+);
+XPCOMUtils.defineLazyGetter(this, "log", () => {
+ let scope = {};
+ ChromeUtils.import("resource://gre/modules/Console.jsm", scope);
+ let consoleOptions = {
+ maxLogLevel: gDebuggingEnabled ? "all" : "log",
+ prefix: "OnionAlias",
+ };
+ return new scope.ConsoleAPI(consoleOptions);
+});
+
+function observe(topic, callback) {
+ let observer = {
+ observe(aSubject, aTopic, aData) {
+ if (topic === aTopic) {
+ callback(aSubject, aData);
+ }
+ },
+ };
+ Services.obs.addObserver(observer, topic);
+ return () => Services.obs.removeObserver(observer, topic);
+}
+
+class _OnionAliasStore {
+ static get RULESET_CHECK_INTERVAL() {
+ return 1000 * 60; // 1 minute
+ }
+
+ static get RULESET_CHECK_INTERVAL_FAST() {
+ return 1000 * 5; // 5 seconds
+ }
+
+ constructor() {
+ this._onionMap = new Map();
+ this._rulesetTimeout = null;
+ this._removeObserver = () => {};
+ this._canLoadRules = false;
+ this._rulesetTimestamp = null;
+ this._updateChannelInstalled = false;
+ }
+
+ async _periodicRulesetCheck() {
+ // TODO: it would probably be preferable to listen to some message broadcasted by
+ // the https-everywhere extension when some update channel is updated, instead of
+ // polling every N seconds.
+ log.debug("Checking for new rules");
+ const ts = await this.httpsEverywhereControl.getRulesetTimestamp();
+ log.debug(
+ `Found ruleset timestamp ${ts}, current is ${this._rulesetTimestamp}`
+ );
+ if (ts !== this._rulesetTimestamp) {
+ this._rulesetTimestamp = ts;
+ log.debug("New rules found, updating");
+ // We clear the mappings even if we cannot load the rules from https-everywhere,
+ // since we cannot be sure if the stored mappings are correct anymore.
+ this._clear();
+ if (this._canLoadRules) {
+ await this._loadRules();
+ }
+ }
+ // If the timestamp is 0, that means the update channel was not yet updated, so
+ // we schedule a check soon.
+ this._rulesetTimeout = setTimeout(
+ () => this._periodicRulesetCheck(),
+ ts === 0
+ ? _OnionAliasStore.RULESET_CHECK_INTERVAL_FAST
+ : _OnionAliasStore.RULESET_CHECK_INTERVAL
+ );
+ }
+
+ async init() {
+ this.httpsEverywhereControl = new HttpsEverywhereControl();
+
+ // Setup .tor.onion rule loading.
+ // The http observer is a fallback, and is removed in _loadRules() as soon as we are able
+ // to load some rules from HTTPS Everywhere.
+ this._loadHttpObserver();
+ try {
+ await this.httpsEverywhereControl.installTorOnionUpdateChannel();
+ this._updateChannelInstalled = true;
+ await this.httpsEverywhereControl.getTorOnionRules();
+ this._canLoadRules = true;
+ } catch (e) {
+ // Loading rules did not work, probably because "get_simple_rules_ending_with" is not yet
+ // working in https-everywhere. Use an http observer as a fallback for learning the rules.
+ log.debug(`Could not load rules: ${e.message}`);
+ }
+
+ // Setup checker for https-everywhere ruleset updates
+ if (this._updateChannelInstalled) {
+ this._periodicRulesetCheck();
+ }
+ }
+
+ /**
+ * Loads the .tor.onion mappings from https-everywhere.
+ */
+ async _loadRules() {
+ const rules = await this.httpsEverywhereControl.getTorOnionRules();
+ // Remove http observer if we are able to load some rules directly.
+ if (rules.length) {
+ this._removeObserver();
+ this._removeObserver = () => {};
+ }
+ this._clear();
+ log.debug(`Loading ${rules.length} rules`, rules);
+ for (const rule of rules) {
+ // Here we are trusting that the securedrop ruleset follows some conventions so that we can
+ // assume there is a host mapping from `rule.host` to the hostname of the URL in `rule.to`.
+ try {
+ const url = new URL(rule.to);
+ const shortHost = rule.host;
+ const longHost = url.hostname;
+ this._addMapping(shortHost, longHost);
+ } catch (e) {
+ log.error("Could not process rule:", rule);
+ }
+ }
+ }
+
+ /**
+ * Loads a http observer to listen for local redirects for populating
+ * the .tor.onion -> .onion mappings. Should only be used if we cannot ask https-everywhere
+ * directly for the mappings.
+ */
+ _loadHttpObserver() {
+ this._removeObserver = observe("http-on-before-connect", channel => {
+ if (
+ channel.isMainDocumentChannel &&
+ channel.originalURI.host.endsWith(".tor.onion")
+ ) {
+ this._addMapping(channel.originalURI.host, channel.URI.host);
+ }
+ });
+ }
+
+ uninit() {
+ this._clear();
+ this._removeObserver();
+ this._removeObserver = () => {};
+ if (this.httpsEverywhereControl) {
+ this.httpsEverywhereControl.unload();
+ delete this.httpsEverywhereControl;
+ }
+ clearTimeout(this._rulesetTimeout);
+ this._rulesetTimeout = null;
+ this._rulesetTimestamp = null;
+ }
+
+ _clear() {
+ this._onionMap.clear();
+ }
+
+ _addMapping(shortOnionHost, longOnionHost) {
+ this._onionMap.set(longOnionHost, shortOnionHost);
+ }
+
+ getShortURI(onionURI) {
+ if (
+ (onionURI.schemeIs("http") || onionURI.schemeIs("https")) &&
+ this._onionMap.has(onionURI.host)
+ ) {
+ return onionURI
+ .mutate()
+ .setHost(this._onionMap.get(onionURI.host))
+ .finalize();
+ }
+ return null;
+ }
+}
+
+let OnionAliasStore = new _OnionAliasStore();
diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build
index 2661ad7cb9f3..815685322024 100644
--- a/browser/components/onionservices/moz.build
+++ b/browser/components/onionservices/moz.build
@@ -1 +1,7 @@
JAR_MANIFESTS += ["jar.mn"]
+
+EXTRA_JS_MODULES += [
+ "ExtensionMessaging.jsm",
+ "HttpsEverywhereControl.jsm",
+ "OnionAliasStore.jsm",
+]
diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm
index c62edbe9a907..e7ab9db0a66c 100644
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -358,7 +358,10 @@ class UrlbarInput {
// user makes the input empty, switches tabs, and switches back, we want the
// URI to become visible again so the user knows what URI they're viewing.
if (value === null || (!value && dueToTabSwitch)) {
- uri = uri || this.window.gBrowser.currentURI;
+ uri =
+ uri ||
+ this.window.gBrowser.selectedBrowser.currentOnionAliasURI ||
+ this.window.gBrowser.currentURI;
// Strip off usernames and passwords for the location bar
try {
uri = Services.io.createExposableURI(uri);
@@ -2128,7 +2131,13 @@ class UrlbarInput {
}
let uri;
- if (this.getAttribute("pageproxystate") == "valid") {
+ // When we rewrite .onion to an alias, gBrowser.currentURI will be different than
+ // the URI displayed in the urlbar. We need to use the urlbar value to copy the
+ // alias instead of the actual .onion URI that is loaded.
+ if (
+ this.getAttribute("pageproxystate") == "valid" &&
+ !this.window.gBrowser.selectedBrowser.currentOnionAliasURI
+ ) {
uri = this.window.gBrowser.currentURI;
} else {
// The value could be:
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index 11583bec32a5..b2798350c2af 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5764,6 +5764,10 @@ void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel,
return;
}
+ if (!mOnionUrlbarRewritesAllowed && IsTorOnionRedirect(oldURI, newURI)) {
+ mOnionUrlbarRewritesAllowed = true;
+ }
+
// DocumentChannel adds redirect chain to global history in the parent
// process. The redirect chain can't be queried from the content process, so
// there's no need to update global history here.
@@ -9184,6 +9188,20 @@ static bool NavigationShouldTakeFocus(nsDocShell* aDocShell,
return !Preferences::GetBool("browser.tabs.loadDivertedInBackground", false);
}
+/* static */
+bool nsDocShell::IsTorOnionRedirect(nsIURI* aOldURI, nsIURI* aNewURI) {
+ nsAutoCString oldHost;
+ nsAutoCString newHost;
+ if (aOldURI && aNewURI && NS_SUCCEEDED(aOldURI->GetHost(oldHost)) &&
+ StringEndsWith(oldHost, ".tor.onion"_ns) &&
+ NS_SUCCEEDED(aNewURI->GetHost(newHost)) &&
+ StringEndsWith(newHost, ".onion"_ns) &&
+ !StringEndsWith(newHost, ".tor.onion"_ns)) {
+ return true;
+ }
+ return false;
+}
+
nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
Maybe<uint32_t> aCacheKey) {
MOZ_ASSERT(aLoadState, "need a load state!");
@@ -9337,6 +9355,30 @@ nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
mAllowKeywordFixup = aLoadState->HasInternalLoadFlags(
INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP);
+
+ if (mOnionUrlbarRewritesAllowed) {
+ mOnionUrlbarRewritesAllowed = false;
+ nsCOMPtr<nsIURI> referrer;
+ nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo();
+ if (referrerInfo) {
+ referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer));
+ bool isPrivateWin = false;
+ Document* doc = GetDocument();
+ if (doc) {
+ isPrivateWin =
+ doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ mOnionUrlbarRewritesAllowed =
+ secMan && NS_SUCCEEDED(secMan->CheckSameOriginURI(
+ aLoadState->URI(), referrer, false, isPrivateWin));
+ }
+ }
+ }
+ mOnionUrlbarRewritesAllowed =
+ mOnionUrlbarRewritesAllowed ||
+ aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES);
+
mURIResultedInDocument = false; // reset the clock...
// See if this is actually a load between two history entries for the same
@@ -11710,6 +11752,7 @@ nsresult nsDocShell::AddToSessionHistory(
HistoryID(), GetCreatedDynamically(), originalURI,
resultPrincipalURI, loadReplace, referrerInfo, srcdoc,
srcdocEntry, baseURI, saveLayoutState, expired, userActivation);
+ entry->SetOnionUrlbarRewritesAllowed(mOnionUrlbarRewritesAllowed);
if (mBrowsingContext->IsTop() && GetSessionHistory()) {
bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel);
@@ -13603,3 +13646,12 @@ void nsDocShell::MaybeDisconnectChildListenersOnPageHide() {
mChannelToDisconnectOnPageHide = 0;
}
}
+
+NS_IMETHODIMP
+nsDocShell::GetOnionUrlbarRewritesAllowed(bool* aOnionUrlbarRewritesAllowed) {
+ NS_ENSURE_ARG(aOnionUrlbarRewritesAllowed);
+ *aOnionUrlbarRewritesAllowed =
+ StaticPrefs::browser_urlbar_onionRewrites_enabled() &&
+ mOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
index 0be1c873204e..9d7febb3ed7a 100644
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -134,6 +134,9 @@ class nsDocShell final : public nsDocLoader,
// Whether the load should go through LoadURIDelegate.
INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x2000,
+
+ // Whether rewriting the urlbar to a short .onion alias is allowed.
+ INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES = 0x4000,
};
// Event type dispatched by RestorePresentation
@@ -567,6 +570,8 @@ class nsDocShell final : public nsDocLoader,
virtual void DestroyChildren() override;
+ static bool IsTorOnionRedirect(nsIURI* aOldURI, nsIURI* aNewURI);
+
// Overridden from nsDocLoader, this provides more information than the
// normal OnStateChange with flags STATE_REDIRECTING
virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
@@ -1255,6 +1260,7 @@ class nsDocShell final : public nsDocLoader,
bool mCSSErrorReportingEnabled : 1;
bool mAllowAuth : 1;
bool mAllowKeywordFixup : 1;
+ bool mOnionUrlbarRewritesAllowed : 1;
bool mDisableMetaRefreshWhenInactive : 1;
bool mIsAppTab : 1;
bool mDeviceSizeIsPageSize : 1;
diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp
index 6cac48a51728..9eb0e9307113 100644
--- a/docshell/base/nsDocShellLoadState.cpp
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -874,6 +874,10 @@ void nsDocShellLoadState::CalculateLoadURIFlags() {
mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FIRST_LOAD;
}
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
+
if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CLASSIFIER) {
mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER;
}
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
index 352b70d12030..bd373c54a632 100644
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -838,4 +838,9 @@ interface nsIDocShell : nsIDocShellTreeItem
* until session history state is moved into the parent process.
*/
void persistLayoutHistoryState();
+
+ /**
+ * Whether rewriting the urlbar to a short .onion alias is allowed.
+ */
+ [infallible] readonly attribute boolean onionUrlbarRewritesAllowed;
};
diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl
index bec4f13d8b2b..2ee46f3d6886 100644
--- a/docshell/base/nsIWebNavigation.idl
+++ b/docshell/base/nsIWebNavigation.idl
@@ -268,6 +268,11 @@ interface nsIWebNavigation : nsISupports
*/
const unsigned long LOAD_FLAGS_USER_ACTIVATION = 0x8000000;
+ /**
+ * Allow rewriting the urlbar to a short .onion alias.
+ */
+ const unsigned long LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES = 0x10000000;
+
/**
* Loads a given URI. This will give priority to loading the requested URI
* in the object implementing this interface. If it can't be loaded here
diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp
index 0361fb0c28c5..a6ac18d41c36 100644
--- a/docshell/shistory/SessionHistoryEntry.cpp
+++ b/docshell/shistory/SessionHistoryEntry.cpp
@@ -930,6 +930,20 @@ SessionHistoryEntry::SetPersist(bool aPersist) {
return NS_OK;
}
+NS_IMETHODIMP
+SessionHistoryEntry::GetOnionUrlbarRewritesAllowed(
+ bool* aOnionUrlbarRewritesAllowed) {
+ *aOnionUrlbarRewritesAllowed = mInfo->mOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetOnionUrlbarRewritesAllowed(
+ bool aOnionUrlbarRewritesAllowed) {
+ mInfo->mOnionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
+
NS_IMETHODIMP
SessionHistoryEntry::GetScrollPosition(int32_t* aX, int32_t* aY) {
*aX = mInfo->mScrollPositionX;
diff --git a/docshell/shistory/SessionHistoryEntry.h b/docshell/shistory/SessionHistoryEntry.h
index a05fa42a2a94..6be4f2c4673f 100644
--- a/docshell/shistory/SessionHistoryEntry.h
+++ b/docshell/shistory/SessionHistoryEntry.h
@@ -170,6 +170,7 @@ class SessionHistoryInfo {
bool mPersist = true;
bool mHasUserInteraction = false;
bool mHasUserActivation = false;
+ bool mOnionUrlbarRewritesAllowed = false;
union SharedState {
SharedState();
diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl
index 73ac40551d4e..622402456d07 100644
--- a/docshell/shistory/nsISHEntry.idl
+++ b/docshell/shistory/nsISHEntry.idl
@@ -260,6 +260,11 @@ interface nsISHEntry : nsISupports
*/
[infallible] attribute boolean persist;
+ /**
+ * Whether rewriting the urlbar to a short .onion alias is allowed.
+ */
+ [infallible] attribute boolean onionUrlbarRewritesAllowed;
+
/**
* Set/Get the visual viewport scroll position if session history is
* changed through anchor navigation or pushState.
diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp
index 1e4000eacd2b..41ea6086df8b 100644
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -44,7 +44,8 @@ nsSHEntry::nsSHEntry()
mLoadedInThisProcess(false),
mPersist(true),
mHasUserInteraction(false),
- mHasUserActivation(false) {}
+ mHasUserActivation(false),
+ mOnionUrlbarRewritesAllowed(false) {}
nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
: mShared(aOther.mShared),
@@ -72,7 +73,8 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
mLoadedInThisProcess(aOther.mLoadedInThisProcess),
mPersist(aOther.mPersist),
mHasUserInteraction(false),
- mHasUserActivation(aOther.mHasUserActivation) {}
+ mHasUserActivation(aOther.mHasUserActivation),
+ mOnionUrlbarRewritesAllowed(aOther.mOnionUrlbarRewritesAllowed) {}
nsSHEntry::~nsSHEntry() {
// Null out the mParent pointers on all our kids.
@@ -880,6 +882,18 @@ nsSHEntry::SetPersist(bool aPersist) {
return NS_OK;
}
+NS_IMETHODIMP
+nsSHEntry::GetOnionUrlbarRewritesAllowed(bool* aOnionUrlbarRewritesAllowed) {
+ *aOnionUrlbarRewritesAllowed = mOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetOnionUrlbarRewritesAllowed(bool aOnionUrlbarRewritesAllowed) {
+ mOnionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed;
+ return NS_OK;
+}
+
NS_IMETHODIMP
nsSHEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) {
nsCOMPtr<nsIURI> uri = GetURI();
@@ -929,6 +943,10 @@ nsSHEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) {
} else {
srcdoc = VoidString();
}
+ if (GetOnionUrlbarRewritesAllowed()) {
+ flags |= nsDocShell::InternalLoad::
+ INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
loadState->SetSrcdocData(srcdoc);
loadState->SetBaseURI(baseURI);
loadState->SetInternalLoadFlags(flags);
diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h
index 326b0092cf94..76be0ac65050 100644
--- a/docshell/shistory/nsSHEntry.h
+++ b/docshell/shistory/nsSHEntry.h
@@ -66,6 +66,7 @@ class nsSHEntry : public nsISHEntry {
bool mPersist;
bool mHasUserInteraction;
bool mHasUserActivation;
+ bool mOnionUrlbarRewritesAllowed;
};
#endif /* nsSHEntry_h */
diff --git a/dom/interfaces/base/nsIBrowser.idl b/dom/interfaces/base/nsIBrowser.idl
index 973a9244b8f8..b8a25de3629e 100644
--- a/dom/interfaces/base/nsIBrowser.idl
+++ b/dom/interfaces/base/nsIBrowser.idl
@@ -127,7 +127,8 @@ interface nsIBrowser : nsISupports
in boolean aIsSynthetic,
in boolean aHasRequestContextID,
in uint64_t aRequestContextID,
- in AString aContentType);
+ in AString aContentType,
+ in boolean aOnionUrlbarRewritesAllowed);
/**
* Determine what process switching behavior this browser element should have.
diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp
index 9f1bccda2efe..bb368b38c5f4 100644
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -3714,6 +3714,8 @@ NS_IMETHODIMP BrowserChild::OnLocationChange(nsIWebProgress* aWebProgress,
locationChangeData->mayEnableCharacterEncodingMenu() =
docShell->GetMayEnableCharacterEncodingMenu();
+ locationChangeData->onionUrlbarRewritesAllowed() =
+ docShell->GetOnionUrlbarRewritesAllowed();
locationChangeData->contentPrincipal() = document->NodePrincipal();
locationChangeData->contentPartitionedPrincipal() =
diff --git a/dom/ipc/BrowserParent.cpp b/dom/ipc/BrowserParent.cpp
index 4145111ae849..10f94926a53e 100644
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -2788,7 +2788,8 @@ mozilla::ipc::IPCResult BrowserParent::RecvOnLocationChange(
aLocationChangeData->isSyntheticDocument(),
aLocationChangeData->requestContextID().isSome(),
aLocationChangeData->requestContextID().valueOr(0),
- aLocationChangeData->contentType());
+ aLocationChangeData->contentType(),
+ aLocationChangeData->onionUrlbarRewritesAllowed());
}
}
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl
index 5706c7f5da00..5b21a809251e 100644
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -143,6 +143,7 @@ struct WebProgressLocationChangeData
bool isNavigating;
bool isSyntheticDocument;
bool mayEnableCharacterEncodingMenu;
+ bool onionUrlbarRewritesAllowed;
nsString contentType;
nsString title;
nsString charset;
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
index 516b5c4877f7..26ba559b79b0 100644
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -1338,6 +1338,12 @@
value: true
mirror: always
+ # Whether rewriting the urlbar to a short .onion alias is allowed.
+- name: browser.urlbar.onionRewrites.enabled
+ type: RelaxedAtomicBool
+ value: true
+ mirror: always
+
- name: browser.viewport.desktopWidth
type: RelaxedAtomicInt32
value: 980
diff --git a/netwerk/dns/effective_tld_names.dat b/netwerk/dns/effective_tld_names.dat
index fb018d626a70..3f6fd236fa5b 100644
--- a/netwerk/dns/effective_tld_names.dat
+++ b/netwerk/dns/effective_tld_names.dat
@@ -5529,6 +5529,8 @@ pro.om
// onion : https://tools.ietf.org/html/rfc7686
onion
+tor.onion
+securedrop.tor.onion
// org : https://en.wikipedia.org/wiki/.org
org
diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp
index 9d1143eb3396..6f99c558e927 100644
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -2531,6 +2531,16 @@ DocumentLoadListener::AsyncOnChannelRedirect(
"mHaveVisibleRedirect=%c",
this, mHaveVisibleRedirect ? 'T' : 'F'));
+ // Like the code above for allowing mixed content, we need to check this here
+ // in case the redirect is not handled in the docshell.
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (nsDocShell::IsTorOnionRedirect(oldURI, newURI)) {
+ mLoadStateInternalLoadFlags |=
+ nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
+ }
+
// We need the original URI of the current channel to use to open the real
// channel in the content process. Unfortunately we overwrite the original
// uri of the new channel with the original pre-redirect URI, so grab
diff --git a/toolkit/content/widgets/browser-custom-element.js b/toolkit/content/widgets/browser-custom-element.js
index 59a7a5b43522..8120ca995103 100644
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -255,6 +255,8 @@
this._mayEnableCharacterEncodingMenu = null;
+ this._onionUrlbarRewritesAllowed = false;
+
this._contentPrincipal = null;
this._contentPartitionedPrincipal = null;
@@ -583,6 +585,12 @@
}
}
+ get onionUrlbarRewritesAllowed() {
+ return this.isRemoteBrowser
+ ? this._onionUrlbarRewritesAllowed
+ : this.docShell.onionUrlbarRewritesAllowed;
+ }
+
get contentPrincipal() {
return this.isRemoteBrowser
? this._contentPrincipal
@@ -1112,7 +1120,8 @@
aIsSynthetic,
aHaveRequestContextID,
aRequestContextID,
- aContentType
+ aContentType,
+ aOnionUrlbarRewritesAllowed
) {
if (this.isRemoteBrowser && this.messageManager) {
if (aCharset != null) {
@@ -1134,6 +1143,7 @@
this._contentRequestContextID = aHaveRequestContextID
? aRequestContextID
: null;
+ this._onionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed;
}
}
@@ -1535,6 +1545,7 @@
"_contentPrincipal",
"_contentPartitionedPrincipal",
"_isSyntheticDocument",
+ "_onionUrlbarRewritesAllowed",
]
);
}
diff --git a/toolkit/modules/sessionstore/SessionHistory.jsm b/toolkit/modules/sessionstore/SessionHistory.jsm
index f02930aa6e22..e78ec8ddf6b7 100644
--- a/toolkit/modules/sessionstore/SessionHistory.jsm
+++ b/toolkit/modules/sessionstore/SessionHistory.jsm
@@ -310,6 +310,7 @@ var SessionHistoryInternal = {
}
entry.persist = shEntry.persist;
+ entry.onionUrlbarRewritesAllowed = shEntry.onionUrlbarRewritesAllowed;
return entry;
},
@@ -604,6 +605,10 @@ var SessionHistoryInternal = {
}
}
+ if (entry.onionUrlbarRewritesAllowed) {
+ shEntry.onionUrlbarRewritesAllowed = entry.onionUrlbarRewritesAllowed;
+ }
+
return shEntry;
},
diff --git a/xpcom/reflect/xptinfo/xptinfo.h b/xpcom/reflect/xptinfo/xptinfo.h
index efee881c1421..4295efb39f1f 100644
--- a/xpcom/reflect/xptinfo/xptinfo.h
+++ b/xpcom/reflect/xptinfo/xptinfo.h
@@ -514,7 +514,8 @@ static_assert(sizeof(nsXPTMethodInfo) == 8, "wrong size");
#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
# define PARAM_BUFFER_COUNT 18
#else
-# define PARAM_BUFFER_COUNT 14
+// The max is currently updateForLocationChange in nsIBrowser.idl
+# define PARAM_BUFFER_COUNT 15
#endif
/**
1
0
[tor-browser/tor-browser-91.5.0esr-11.5-2] Bug 23247: Communicating security expectations for .onion
by richard@torproject.org 01 Feb '22
by richard@torproject.org 01 Feb '22
01 Feb '22
commit 6e5144990a08dbbc61d57b8072c67262ac002ed3
Author: Richard Pospesel <richard(a)torproject.org>
Date: Fri Jun 8 13:38:40 2018 -0700
Bug 23247: Communicating security expectations for .onion
Encrypting pages hosted on Onion Services with SSL/TLS is redundant
(in terms of hiding content) as all traffic within the Tor network is
already fully encrypted. Therefore, serving HTTP pages from an Onion
Service is more or less fine.
Prior to this patch, Tor Browser would mostly treat pages delivered
via Onion Services as well as pages delivered in the ordinary fashion
over the internet in the same way. This created some inconsistencies
in behaviour and misinformation presented to the user relating to the
security of pages delivered via Onion Services:
- HTTP Onion Service pages did not have any 'lock' icon indicating
the site was secure
- HTTP Onion Service pages would be marked as unencrypted in the Page
Info screen
- Mixed-mode content restrictions did not apply to HTTP Onion Service
pages embedding Non-Onion HTTP content
This patch fixes the above issues, and also adds several new 'Onion'
icons to the mix to indicate all of the various permutations of Onion
Services hosted HTTP or HTTPS pages with HTTP or HTTPS content.
Strings for Onion Service Page Info page are pulled from Torbutton's
localization strings.
---
browser/base/content/browser-siteIdentity.js | 39 ++++++++-----
browser/base/content/pageinfo/security.js | 64 ++++++++++++++++++----
.../shared/identity-block/identity-block.inc.css | 19 +++++++
dom/base/nsContentUtils.cpp | 19 +++++++
dom/base/nsContentUtils.h | 5 ++
dom/base/nsGlobalWindowOuter.cpp | 3 +-
dom/ipc/WindowGlobalActor.cpp | 4 +-
dom/ipc/WindowGlobalChild.cpp | 6 +-
dom/security/nsMixedContentBlocker.cpp | 16 +++++-
.../modules/geckoview/GeckoViewProgress.jsm | 4 ++
security/manager/ssl/nsSecureBrowserUI.cpp | 12 ++++
11 files changed, 160 insertions(+), 31 deletions(-)
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js
index acbcc79fb21e..6682ae8b096f 100644
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -142,6 +142,10 @@ var gIdentityHandler = {
);
},
+ get _uriIsOnionHost() {
+ return this._uriHasHost ? this._uri.host.toLowerCase().endsWith(".onion") : false;
+ },
+
get _isAboutNetErrorPage() {
return (
gBrowser.selectedBrowser.documentURI &&
@@ -745,9 +749,9 @@ var gIdentityHandler = {
get pointerlockFsWarningClassName() {
// Note that the fullscreen warning does not handle _isSecureInternalUI.
if (this._uriHasHost && this._isSecureConnection) {
- return "verifiedDomain";
+ return this._uriIsOnionHost ? "onionVerifiedDomain" : "verifiedDomain";
}
- return "unknownIdentity";
+ return this._uriIsOnionHost ? "onionUnknownIdentity" : "unknownIdentity";
},
/**
@@ -755,6 +759,10 @@ var gIdentityHandler = {
* built-in (returns false) or imported (returns true).
*/
_hasCustomRoot() {
+ if (!this._secInfo) {
+ return false;
+ }
+
let issuerCert = null;
issuerCert = this._secInfo.succeededCertChain[
this._secInfo.succeededCertChain.length - 1
@@ -797,11 +805,13 @@ var gIdentityHandler = {
"identity.extension.label",
[extensionName]
);
- } else if (this._uriHasHost && this._isSecureConnection) {
+ } else if (this._uriHasHost && this._isSecureConnection && this._secInfo) {
// This is a secure connection.
- this._identityBox.className = "verifiedDomain";
+ // _isSecureConnection implicitly includes onion services, which may not have an SSL certificate
+ const uriIsOnionHost = this._uriIsOnionHost;
+ this._identityBox.className = uriIsOnionHost ? "onionVerifiedDomain" : "verifiedDomain";
if (this._isMixedActiveContentBlocked) {
- this._identityBox.classList.add("mixedActiveBlocked");
+ this._identityBox.classList.add(uriIsOnionHost ? "onionMixedActiveBlocked" : "mixedActiveBlocked");
}
if (!this._isCertUserOverridden) {
// It's a normal cert, verifier is the CA Org.
@@ -812,17 +822,17 @@ var gIdentityHandler = {
}
} else if (this._isBrokenConnection) {
// This is a secure connection, but something is wrong.
- this._identityBox.className = "unknownIdentity";
+ const uriIsOnionHost = this._uriIsOnionHost;
+ this._identityBox.className = uriIsOnionHost ? "onionUnknownIdentity" : "unknownIdentity";
if (this._isMixedActiveContentLoaded) {
- this._identityBox.classList.add("mixedActiveContent");
+ this._identityBox.classList.add(uriIsOnionHost ? "onionMixedActiveContent" : "mixedActiveContent");
} else if (this._isMixedActiveContentBlocked) {
- this._identityBox.classList.add(
- "mixedDisplayContentLoadedActiveBlocked"
- );
+ this._identityBox.classList.add(uriIsOnionHost ? "onionMixedDisplayContentLoadedActiveBlocked" : "mixedDisplayContentLoadedActiveBlocked");
} else if (this._isMixedPassiveContentLoaded) {
- this._identityBox.classList.add("mixedDisplayContent");
+ this._identityBox.classList.add(uriIsOnionHost ? "onionMixedDisplayContent" : "mixedDisplayContent");
} else {
+ // TODO: ignore weak https cipher for onionsites?
this._identityBox.classList.add("weakCipher");
}
} else if (this._isAboutCertErrorPage) {
@@ -835,8 +845,8 @@ var gIdentityHandler = {
// Network errors and blocked pages get a more neutral icon
this._identityBox.className = "unknownIdentity";
} else if (this._isPotentiallyTrustworthy) {
- // This is a local resource (and shouldn't be marked insecure).
- this._identityBox.className = "localResource";
+ // This is a local resource or an onion site (and shouldn't be marked insecure).
+ this._identityBox.className = this._uriIsOnionHost ? "onionUnknownIdentity" : "localResource";
} else {
// This is an insecure connection.
let warnOnInsecure =
@@ -860,7 +870,8 @@ var gIdentityHandler = {
}
if (this._isCertUserOverridden) {
- this._identityBox.classList.add("certUserOverridden");
+ const uriIsOnionHost = this._uriIsOnionHost;
+ this._identityBox.classList.add(uriIsOnionHost ? "onionCertUserOverridden" : "certUserOverridden");
// Cert is trusted because of a security exception, verifier is a special string.
tooltip = gNavigatorBundle.getString(
"identity.identified.verified_by_you"
diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js
index 1222c8b0ec35..8d10c8df814c 100644
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -22,6 +22,13 @@ ChromeUtils.defineModuleGetter(
"PluralForm",
"resource://gre/modules/PluralForm.jsm"
);
+XPCOMUtils.defineLazyGetter(
+ this,
+ "gTorButtonBundle",
+ function() {
+ return Services.strings.createBundle("chrome://torbutton/locale/torbutton.properties");
+ }
+);
var security = {
async init(uri, windowInfo) {
@@ -60,6 +67,11 @@ var security = {
(Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
var isEV = ui.state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL;
+ var isOnion = false;
+ const hostName = this.windowInfo.hostName;
+ if (hostName && hostName.endsWith(".onion")) {
+ isOnion = true;
+ }
let retval = {
cAName: "",
@@ -69,6 +81,7 @@ var security = {
isBroken,
isMixed,
isEV,
+ isOnion,
cert: null,
certificateTransparency: null,
};
@@ -107,6 +120,7 @@ var security = {
isBroken,
isMixed,
isEV,
+ isOnion,
cert,
certChain: certChainArray,
certificateTransparency: undefined,
@@ -348,22 +362,50 @@ async function securityOnLoad(uri, windowInfo) {
}
msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
} else if (info.encryptionStrength > 0) {
- hdr = pkiBundle.getFormattedString(
- "pageInfo_EncryptionWithBitsAndProtocol",
- [info.encryptionAlgorithm, info.encryptionStrength + "", info.version]
- );
+ if (!info.isOnion) {
+ hdr = pkiBundle.getFormattedString(
+ "pageInfo_EncryptionWithBitsAndProtocol",
+ [info.encryptionAlgorithm, info.encryptionStrength + "", info.version]
+ );
+ } else {
+ try {
+ hdr = gTorButtonBundle.formatStringFromName(
+ "pageInfo_OnionEncryptionWithBitsAndProtocol",
+ [info.encryptionAlgorithm, info.encryptionStrength + "", info.version]
+ );
+ } catch(err) {
+ hdr = "Connection Encrypted (Onion Service, "
+ + info.encryptionAlgorithm
+ + ", "
+ + info.encryptionStrength
+ + " bit keys, "
+ + info.version
+ + ")";
+ }
+ }
msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1");
msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2");
} else {
- hdr = pkiBundle.getString("pageInfo_NoEncryption");
- if (windowInfo.hostName != null) {
- msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [
- windowInfo.hostName,
- ]);
+ if (!info.isOnion) {
+ hdr = pkiBundle.getString("pageInfo_NoEncryption");
+ if (windowInfo.hostName != null) {
+ msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [
+ windowInfo.hostName,
+ ]);
+ } else {
+ msg1 = pkiBundle.getString("pageInfo_Privacy_None4");
+ }
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
} else {
- msg1 = pkiBundle.getString("pageInfo_Privacy_None4");
+ try {
+ hdr = gTorButtonBundle.GetStringFromName("pageInfo_OnionEncryption");
+ } catch (err) {
+ hdr = "Connection Encrypted (Onion Service)";
+ }
+
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2");
}
- msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
}
setText("security-technical-shortform", hdr);
setText("security-technical-longform1", msg1);
diff --git a/browser/themes/shared/identity-block/identity-block.inc.css b/browser/themes/shared/identity-block/identity-block.inc.css
index a01e7d705cd7..68ed0beed684 100644
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -203,6 +203,25 @@
list-style-image: url(chrome://global/skin/icons/security-broken.svg);
}
+#identity-box[pageproxystate="valid"].onionUnknownIdentity #identity-icon,
+#identity-box[pageproxystate="valid"].onionVerifiedDomain #identity-icon,
+#identity-box[pageproxystate="valid"].onionMixedActiveBlocked #identity-icon {
+ list-style-image: url(chrome://browser/skin/onion.svg);
+ visibility: visible;
+}
+
+#identity-box[pageproxystate="valid"].onionMixedDisplayContent #identity-icon,
+#identity-box[pageproxystate="valid"].onionMixedDisplayContentLoadedActiveBlocked #identity-icon,
+#identity-box[pageproxystate="valid"].onionCertUserOverridden #identity-icon {
+ list-style-image: url(chrome://browser/skin/onion-warning.svg);
+ visibility: visible;
+}
+
+#identity-box[pageproxystate="valid"].onionMixedActiveContent #identity-icon {
+ list-style-image: url(chrome://browser/skin/onion-slash.svg);
+ visibility: visible;
+}
+
#permissions-granted-icon {
list-style-image: url(chrome://browser/skin/permissions.svg);
}
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
index 4cc0cd114ce5..b816f8b05067 100644
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -9325,6 +9325,25 @@ bool nsContentUtils::ComputeIsSecureContext(nsIChannel* aChannel) {
return principal->GetIsOriginPotentiallyTrustworthy();
}
+/* static */ bool nsContentUtils::DocumentHasOnionURI(Document* aDocument) {
+ if (!aDocument) {
+ return false;
+ }
+
+ nsIURI* uri = aDocument->GetDocumentURI();
+ if (!uri) {
+ return false;
+ }
+
+ nsAutoCString host;
+ if (NS_SUCCEEDED(uri->GetHost(host))) {
+ bool hasOnionURI = StringEndsWith(host, ".onion"_ns);
+ return hasOnionURI;
+ }
+
+ return false;
+}
+
/* static */
void nsContentUtils::TryToUpgradeElement(Element* aElement) {
NodeInfo* nodeInfo = aElement->NodeInfo();
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
index 94805199a8fd..4b6256fb9c2c 100644
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2999,6 +2999,11 @@ class nsContentUtils {
*/
static bool HttpsStateIsModern(Document* aDocument);
+ /**
+ * Returns true of the document's URI is a .onion
+ */
+ static bool DocumentHasOnionURI(Document* aDocument);
+
/**
* Returns true if the channel is for top-level window and is over secure
* context.
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp
index aab4a37e78a8..e981573e9822 100644
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -1880,7 +1880,8 @@ bool nsGlobalWindowOuter::ComputeIsSecureContext(Document* aDocument,
return false;
}
- if (nsContentUtils::HttpsStateIsModern(aDocument)) {
+ if (nsContentUtils::HttpsStateIsModern(aDocument) ||
+ nsContentUtils::DocumentHasOnionURI(aDocument)) {
return true;
}
diff --git a/dom/ipc/WindowGlobalActor.cpp b/dom/ipc/WindowGlobalActor.cpp
index 8a3b49edd4d7..9975136e8e18 100644
--- a/dom/ipc/WindowGlobalActor.cpp
+++ b/dom/ipc/WindowGlobalActor.cpp
@@ -21,6 +21,7 @@
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
#include "nsGlobalWindowInner.h"
#include "nsNetUtil.h"
@@ -131,7 +132,8 @@ WindowGlobalInit WindowGlobalActor::WindowInitializer(
// Init Mixed Content Fields
nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(doc->GetDocumentURI());
- fields.mIsSecure = innerDocURI && innerDocURI->SchemeIs("https");
+ fields.mIsSecure = innerDocURI && (innerDocURI->SchemeIs("https") ||
+ nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(innerDocURI));
nsCOMPtr<nsITransportSecurityInfo> securityInfo;
if (nsCOMPtr<nsIChannel> channel = doc->GetChannel()) {
diff --git a/dom/ipc/WindowGlobalChild.cpp b/dom/ipc/WindowGlobalChild.cpp
index 84c060c41534..73ac6a0cf96d 100644
--- a/dom/ipc/WindowGlobalChild.cpp
+++ b/dom/ipc/WindowGlobalChild.cpp
@@ -48,6 +48,8 @@
# include "GeckoProfiler.h"
#endif
+#include "mozilla/dom/nsMixedContentBlocker.h"
+
using namespace mozilla::ipc;
using namespace mozilla::dom::ipc;
@@ -234,7 +236,9 @@ void WindowGlobalChild::OnNewDocument(Document* aDocument) {
nsCOMPtr<nsIURI> innerDocURI =
NS_GetInnermostURI(aDocument->GetDocumentURI());
if (innerDocURI) {
- txn.SetIsSecure(innerDocURI->SchemeIs("https"));
+ txn.SetIsSecure(
+ innerDocURI->SchemeIs("https") ||
+ nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(innerDocURI));
}
MOZ_DIAGNOSTIC_ASSERT(mDocumentPrincipal->GetIsLocalIpAddress() ==
diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp
index 01c7877e020d..dab3f19bad40 100644
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -634,8 +634,8 @@ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
return NS_OK;
}
- // Check the parent scheme. If it is not an HTTPS page then mixed content
- // restrictions do not apply.
+ // Check the parent scheme. If it is not an HTTPS or .onion page then mixed
+ // content restrictions do not apply.
nsCOMPtr<nsIURI> innerRequestingLocation =
NS_GetInnermostURI(requestingLocation);
if (!innerRequestingLocation) {
@@ -650,6 +650,17 @@ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
bool parentIsHttps = innerRequestingLocation->SchemeIs("https");
if (!parentIsHttps) {
+ bool parentIsOnion = IsPotentiallyTrustworthyOnion(innerRequestingLocation);
+ if (!parentIsOnion) {
+ *aDecision = ACCEPT;
+ return NS_OK;
+ }
+ }
+
+ bool isHttpScheme = innerContentLocation->SchemeIs("http");
+ // .onion URLs are encrypted and authenticated. Don't treat them as mixed
+ // content if potentially trustworthy (i.e. whitelisted).
+ if (isHttpScheme && IsPotentiallyTrustworthyOnion(innerContentLocation)) {
*aDecision = ACCEPT;
MOZ_LOG(sMCBLog, LogLevel::Verbose,
(" -> decision: Request will be allowed because the requesting "
@@ -676,7 +687,6 @@ nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
return NS_OK;
}
- bool isHttpScheme = innerContentLocation->SchemeIs("http");
if (isHttpScheme && IsPotentiallyTrustworthyOrigin(innerContentLocation)) {
*aDecision = ACCEPT;
return NS_OK;
diff --git a/mobile/android/modules/geckoview/GeckoViewProgress.jsm b/mobile/android/modules/geckoview/GeckoViewProgress.jsm
index 17069dbe657f..c1346b1858cf 100644
--- a/mobile/android/modules/geckoview/GeckoViewProgress.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewProgress.jsm
@@ -145,6 +145,10 @@ var IdentityHandler = {
result.host = uri.host;
}
+ if (!aBrowser.securityUI.secInfo) {
+ return result;
+ }
+
const cert = aBrowser.securityUI.secInfo.serverCert;
result.certificate = aBrowser.securityUI.secInfo.serverCert.getBase64DERString();
diff --git a/security/manager/ssl/nsSecureBrowserUI.cpp b/security/manager/ssl/nsSecureBrowserUI.cpp
index b4de1a331ffc..f1ce39582854 100644
--- a/security/manager/ssl/nsSecureBrowserUI.cpp
+++ b/security/manager/ssl/nsSecureBrowserUI.cpp
@@ -9,6 +9,7 @@
#include "mozilla/Logging.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/Document.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
#include "nsContentUtils.h"
#include "nsIChannel.h"
#include "nsDocShell.h"
@@ -85,6 +86,17 @@ void nsSecureBrowserUI::RecomputeSecurityFlags() {
}
}
}
+
+ // any protocol routed over tor is secure
+ if (!(mState & nsIWebProgressListener::STATE_IS_SECURE)) {
+ nsCOMPtr<nsIURI> innerDocURI = NS_GetInnermostURI(win->GetDocumentURI());
+ if (innerDocURI &&
+ nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(innerDocURI)) {
+ MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" is onion"));
+ mState = (mState & ~nsIWebProgressListener::STATE_IS_INSECURE) |
+ nsIWebProgressListener::STATE_IS_SECURE;
+ }
+ }
}
// Add upgraded-state flags when request has been
1
0