This is an automated email from the git hooks/post-receive script.
pierov pushed a change to branch tor-browser-91.9.0esr-11.5-1 in repository tor-browser.
from 16e50d4adb966 fixup! Bug 31286: Implementation of bridge, proxy, and firewall settings in about:preferences#connection new 7c3b775c6efca Fixed a Firefox include problem new a4f7580cfdaf1 fixup! Bug 28005: Implement .onion alias urlbar rewrites new 47e2e10954dd6 Bug 40458: Implement .tor.onion aliases new f4a2fa31273b7 fixup! Add TorStrings module for localization
The 4 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "add" were already present in the repository and have only been added to this reference.
Summary of changes: 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 | 16 +- 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 | 13 + browser/components/about/AboutRedirector.cpp | 4 + browser/components/about/components.conf | 1 + browser/components/moz.build | 1 + .../onionservices/HttpsEverywhereControl.jsm | 69 +-- .../components/onionservices/OnionAliasStore.jsm | 569 ++++++++++++++++----- browser/components/rulesets/RulesetsChild.jsm | 11 + browser/components/rulesets/RulesetsParent.jsm | 79 +++ .../components/rulesets/content/aboutRulesets.css | 319 ++++++++++++ .../components/rulesets/content/aboutRulesets.html | 110 ++++ .../components/rulesets/content/aboutRulesets.js | 531 +++++++++++++++++++ browser/components/rulesets/content/securedrop.svg | 173 +++++++ browser/components/rulesets/jar.mn | 5 + browser/components/rulesets/moz.build | 6 + browser/components/urlbar/UrlbarInput.jsm | 13 +- browser/modules/TorStrings.jsm | 76 +++ 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/build/components.conf | 11 + netwerk/build/nsNetCID.h | 10 + netwerk/dns/IOnionAliasService.idl | 34 ++ netwerk/dns/OnionAliasService.cpp | 94 ++++ netwerk/dns/OnionAliasService.h | 36 ++ netwerk/dns/TRR.cpp | 2 + netwerk/dns/effective_tld_names.dat | 2 - netwerk/dns/moz.build | 4 + netwerk/ipc/DocumentLoadListener.cpp | 10 - netwerk/socket/nsSOCKSIOLayer.cpp | 24 +- security/manager/ssl/SSLServerCertVerification.cpp | 9 + security/manager/ssl/SSLServerCertVerification.h | 4 +- toolkit/content/widgets/browser-custom-element.js | 13 +- toolkit/modules/RemotePageAccessManager.jsm | 14 + toolkit/modules/sessionstore/SessionHistory.jsm | 5 - xpcom/reflect/xptinfo/xptinfo.h | 3 +- 59 files changed, 2051 insertions(+), 496 deletions(-) create mode 100644 browser/components/rulesets/RulesetsChild.jsm create mode 100644 browser/components/rulesets/RulesetsParent.jsm create mode 100644 browser/components/rulesets/content/aboutRulesets.css create mode 100644 browser/components/rulesets/content/aboutRulesets.html create mode 100644 browser/components/rulesets/content/aboutRulesets.js create mode 100644 browser/components/rulesets/content/securedrop.svg create mode 100644 browser/components/rulesets/jar.mn create mode 100644 browser/components/rulesets/moz.build create mode 100644 netwerk/dns/IOnionAliasService.idl create mode 100644 netwerk/dns/OnionAliasService.cpp create mode 100644 netwerk/dns/OnionAliasService.h
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-1 in repository tor-browser.
commit 7c3b775c6efca16f69d0af421e4446093192d8e1 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Tue Feb 22 18:03:02 2022 +0100
Fixed a Firefox include problem
Firefox code missed some includes, but Mozilla devs did not notice because of the unified build mechanism. They have already been fixed in repid release, but not in ESR. See https://bugzilla.mozilla.org/show_bug.cgi?id=1733356 for further information. --- netwerk/dns/TRR.cpp | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/netwerk/dns/TRR.cpp b/netwerk/dns/TRR.cpp index 2ee7110f9e2b1..4b6832af96ad7 100644 --- a/netwerk/dns/TRR.cpp +++ b/netwerk/dns/TRR.cpp @@ -6,6 +6,7 @@
#include "DNS.h" #include "DNSUtils.h" +#include "ODoH.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" #include "nsHttpHandler.h" @@ -20,6 +21,7 @@ #include "nsIUploadChannel2.h" #include "nsIURIMutator.h" #include "nsNetUtil.h" +#include "nsQueryObject.h" #include "nsStringStream.h" #include "nsThreadUtils.h" #include "nsURLHelper.h"
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-1 in repository tor-browser.
commit a4f7580cfdaf1440e1be6d7fa841d401658c4354 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Wed Feb 23 11:36:33 2022 +0100
fixup! Bug 28005: Implement .onion alias urlbar rewrites
The old patch to support .tor.onion hosts was implemented through HTTPS-Everywhere. Now that the extension has been deprecated (or is going to soon), we have a new implementation, so we need only the code that interacts with HTTPS-Everywhere, which we use to remove all SecureDrop channels, rather than adding the 2021 one. --- 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/HttpsEverywhereControl.jsm | 69 ++----- .../components/onionservices/OnionAliasStore.jsm | 201 --------------------- browser/components/onionservices/moz.build | 1 - 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 +- 37 files changed, 40 insertions(+), 594 deletions(-)
diff --git a/browser/actors/ClickHandlerChild.jsm b/browser/actors/ClickHandlerChild.jsm index 0f0f9330197fd..0f3bf42e2290c 100644 --- a/browser/actors/ClickHandlerChild.jsm +++ b/browser/actors/ClickHandlerChild.jsm @@ -146,26 +146,6 @@ 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 3a5be306be467..89363074ed148 100644 --- a/browser/actors/ClickHandlerParent.jsm +++ b/browser/actors/ClickHandlerParent.jsm @@ -103,7 +103,6 @@ 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 dd7809eeb1cad..a9521642e4953 100644 --- a/browser/actors/ContextMenuChild.jsm +++ b/browser/actors/ContextMenuChild.jsm @@ -545,9 +545,6 @@ 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 @@ -659,7 +656,6 @@ 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 d90dc636f8db6..b0c9f6623097a 100644 --- a/browser/base/content/browser-places.js +++ b/browser/base/content/browser-places.js @@ -470,8 +470,7 @@ var PlacesCommandHook = { */ async bookmarkPage() { let browser = gBrowser.selectedBrowser; - const uri = browser.currentOnionAliasURI || browser.currentURI; - let url = new URL(uri.spec); + let url = new URL(browser.currentURI.spec); let info = await PlacesUtils.bookmarks.fetch({ url }); let isNewBookmark = !info; let showEditUI = !isNewBookmark || StarUI.showForNewBookmarks; @@ -582,7 +581,7 @@ var PlacesCommandHook = {
tabs.forEach(tab => { let browser = tab.linkedBrowser; - let uri = browser.currentOnionAliasURI || browser.currentURI; + let uri = browser.currentURI; let title = browser.contentTitle || tab.label; let spec = uri.spec; if (!(spec in uniquePages)) { @@ -1829,17 +1828,14 @@ var BookmarkingUI = { },
onLocationChange: function BUI_onLocationChange() { - const uri = - gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI; - if (this._uri && uri.equals(this._uri)) { + if (this._uri && gBrowser.currentURI.equals(this._uri)) { return; } this.updateStarState(); },
updateStarState: function BUI_updateStarState() { - this._uri = - gBrowser.selectedBrowser.currentOnionAliasURI || gBrowser.currentURI; + this._uri = 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 45b992c14fca0..6682ae8b096fe 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, onionAliasURI) { + updateIdentity(state, uri) { 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, onionAliasURI); + this.setURI(uri); this._secInfo = gBrowser.securityUI.secInfo; this._isSecureContext = gBrowser.securityUI.isSecureContext;
@@ -687,18 +687,17 @@ 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(uri.host, {}); + return this._IDNService.convertToDisplayIDN(this._uri.host, {}); } catch (e) { // If something goes wrong (e.g. host is an IP address) just fail back // to the full domain. - return uri.host; + return this._uri.host; } },
@@ -1141,12 +1140,11 @@ var gIdentityHandler = { this._identityPopupContentVerif.textContent = verifier; },
- setURI(uri, onionAliasURI) { + setURI(uri) { 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 16123f02ff49b..faf6433ccacf8 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -82,7 +82,6 @@ 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", @@ -2249,7 +2248,6 @@ var gBrowserInit = { // [9]: allowInheritPrincipal (bool) // [10]: csp (nsIContentSecurityPolicy) // [11]: nsOpenWindowInfo - // [12]: onionUrlbarRewritesAllowed (bool) let userContextId = window.arguments[5] != undefined ? window.arguments[5] @@ -2269,8 +2267,7 @@ 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[12] + window.arguments[10] ); window.focus(); } else { @@ -3068,8 +3065,7 @@ function loadURI( forceAboutBlankViewerInCurrent, triggeringPrincipal, allowInheritPrincipal = false, - csp = null, - onionUrlbarRewritesAllowed = false + csp = null ) { if (!triggeringPrincipal) { throw new Error("Must load with a triggering Principal"); @@ -3087,7 +3083,6 @@ function loadURI( csp, forceAboutBlankViewerInCurrent, allowInheritPrincipal, - onionUrlbarRewritesAllowed, }); } catch (e) { Cu.reportError(e); @@ -5214,24 +5209,11 @@ 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( - gBrowser.selectedBrowser.currentOnionAliasURI || aLocationURI, - aIsSimulated - ); + gURLBar.setURI(aLocationURI, aIsSimulated);
BookmarkingUI.onLocationChange(); // If we've actually changed document, update the toolbar visibility. @@ -5455,7 +5437,6 @@ 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 ( @@ -5479,7 +5460,7 @@ var XULBrowserWindow = { try { uri = Services.io.createExposableURI(uri); } catch (e) {} - gIdentityHandler.updateIdentity(this._state, uri, onionAliasURI); + gIdentityHandler.updateIdentity(this._state, uri); },
// simulate all change notifications after switching tabs @@ -6998,21 +6979,6 @@ 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); @@ -7024,7 +6990,6 @@ 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 e96df23142f86..1ff16ffbab9f1 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -58,7 +58,6 @@ 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( @@ -1198,7 +1197,6 @@ class nsContextMenu { triggeringPrincipal: this.principal, csp: this.csp, frameID: this.contentData.frameID, - onionUrlbarRewritesAllowed: false, }; for (let p in extra) { params[p] = extra[p]; @@ -1222,22 +1220,6 @@ 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 dd1a4a90fedf8..cd02b73bd0c76 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, browser.currentOnionAliasURI); + securityOnLoad(uri, windowInfo); }
function resetPageInfo(args) { diff --git a/browser/base/content/pageinfo/pageInfo.xhtml b/browser/base/content/pageinfo/pageInfo.xhtml index a23f2bb5748c6..f40ffd3778d8c 100644 --- a/browser/base/content/pageinfo/pageInfo.xhtml +++ b/browser/base/content/pageinfo/pageInfo.xhtml @@ -312,16 +312,6 @@ <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 2e22f4670503d..8d10c8df814c4 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, onionAliasURI) { +async function securityOnLoad(uri, windowInfo) { await security.init(uri, windowInfo);
let info = security.securityInfo; @@ -261,21 +261,6 @@ async function securityOnLoad(uri, windowInfo, onionAliasURI) { } 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 520fea7cc3457..e47c81541bfab 100644 --- a/browser/base/content/tabbrowser.js +++ b/browser/base/content/tabbrowser.js @@ -1635,7 +1635,6 @@ var aFromExternal; var aRelatedToCurrent; var aAllowInheritPrincipal; - var aOnionUrlbarRewritesAllowed; var aSkipAnimation; var aForceNotRemote; var aPreferredRemoteType; @@ -1665,7 +1664,6 @@ aFromExternal = params.fromExternal; aRelatedToCurrent = params.relatedToCurrent; aAllowInheritPrincipal = !!params.allowInheritPrincipal; - aOnionUrlbarRewritesAllowed = params.onionUrlbarRewritesAllowed; aSkipAnimation = params.skipAnimation; aForceNotRemote = params.forceNotRemote; aPreferredRemoteType = params.preferredRemoteType; @@ -1706,7 +1704,6 @@ fromExternal: aFromExternal, relatedToCurrent: aRelatedToCurrent, skipAnimation: aSkipAnimation, - onionUrlbarRewritesAllowed: aOnionUrlbarRewritesAllowed, forceNotRemote: aForceNotRemote, createLazyBrowser: aCreateLazyBrowser, preferredRemoteType: aPreferredRemoteType, @@ -2539,7 +2536,6 @@ aURI, { allowInheritPrincipal, - onionUrlbarRewritesAllowed, allowThirdPartyFixup, bulkOrderedOpen, charset, @@ -2881,9 +2877,6 @@ // 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 4926885cca3bd..a95717544b80f 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -303,7 +303,6 @@ 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; @@ -420,11 +419,6 @@ 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); @@ -441,8 +435,6 @@ 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; @@ -560,9 +552,6 @@ 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 ( @@ -609,7 +598,6 @@ 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 e2824bffdf070..08fed785874fd 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -85,7 +85,6 @@ 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", @@ -2029,7 +2028,6 @@ BrowserGlue.prototype = { Normandy.uninit(); RFPHelper.uninit(); ASRouterNewTabHook.destroy(); - OnionAliasStore.uninit(); },
// Set up a listener to enable/disable the screenshots extension @@ -2534,33 +2532,6 @@ 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/HttpsEverywhereControl.jsm b/browser/components/onionservices/HttpsEverywhereControl.jsm index d673de4cd6e57..9e14b7d88b501 100644 --- a/browser/components/onionservices/HttpsEverywhereControl.jsm +++ b/browser/components/onionservices/HttpsEverywhereControl.jsm @@ -59,9 +59,9 @@ class HttpsEverywhereControl { }
/** - * Installs the .tor.onion update channel in https-everywhere + * Uninstalls old .tor.onion update channels from https-everywhere */ - async installTorOnionUpdateChannel(retries = 5) { + async uninstallTorOnionUpdateChannel(retries = 5) {
// TODO: https-everywhere store is initialized asynchronously, so sending a message // immediately results in a `store.get is undefined` error. @@ -70,73 +70,35 @@ class HttpsEverywhereControl { // for that here. await HttpsEverywhereControl.wait();
+ // We now handle .tor.onion domains with our first-party component, so we + // remove known rules from HTTPS-Everywhere. + 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"); + console.warn("Cannot uninstall the SecureDropTorOnion 2020 channel", e); + throw new Error("Could not uninstall the SecureDropTorOnion update channel"); } - await this.installTorOnionUpdateChannel(retries - 1); + await this.uninstallTorOnionUpdateChannel(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", + "delete_update_channel", SECUREDROP_TOR_ONION_CHANNEL.name ); } catch (e) { if (retries <= 0) { - throw new Error("Could not install SecureDropTorOnion update channel"); + console.warn("Cannot uninstall the SecureDropTorOnion 2021 channel", e); + throw new Error("Could not uninstall the SecureDropTorOnion update channel"); } - await this.installTorOnionUpdateChannel(retries - 1); + await this.uninstallTorOnionUpdateChannel(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() { @@ -146,6 +108,8 @@ class HttpsEverywhereControl {
// update all of the existing https-everywhere channels setTimeout(async () => { + await this.uninstallTorOnionUpdateChannel(); + let pinnedChannels = await this._sendMessage("get_pinned_update_channels"); for(let channel of pinnedChannels.update_channels) { this._sendMessage("update_update_channel", channel); @@ -155,8 +119,9 @@ class HttpsEverywhereControl { for(let channel of storedChannels.update_channels) { this._sendMessage("update_update_channel", channel); } - }, 0); -
+ this._extensionMessaging.unload(); + this._extensionMessaging = null; + }, 0); } } diff --git a/browser/components/onionservices/OnionAliasStore.jsm b/browser/components/onionservices/OnionAliasStore.jsm deleted file mode 100644 index 66cf569227bf7..0000000000000 --- a/browser/components/onionservices/OnionAliasStore.jsm +++ /dev/null @@ -1,201 +0,0 @@ -// 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 8027233d65a68..6187035ca00af 100644 --- a/browser/components/onionservices/moz.build +++ b/browser/components/onionservices/moz.build @@ -3,7 +3,6 @@ JAR_MANIFESTS += ["jar.mn"] EXTRA_JS_MODULES += [ "ExtensionMessaging.jsm", "HttpsEverywhereControl.jsm", - "OnionAliasStore.jsm", "OnionLocationChild.jsm", "OnionLocationParent.jsm", ] diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm index 29ee12914719b..db83e09109bf2 100644 --- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -359,10 +359,7 @@ 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.selectedBrowser.currentOnionAliasURI || - this.window.gBrowser.currentURI; + uri = uri || this.window.gBrowser.currentURI; // Strip off usernames and passwords for the location bar try { uri = Services.io.createExposableURI(uri); @@ -2132,13 +2129,7 @@ class UrlbarInput { }
let uri; - // 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 - ) { + if (this.getAttribute("pageproxystate") == "valid") { uri = this.window.gBrowser.currentURI; } else { // The value could be: diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 0632b6fac2598..d8a059910a0f9 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5766,10 +5766,6 @@ 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. @@ -9191,20 +9187,6 @@ 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!"); @@ -9358,30 +9340,6 @@ 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 @@ -11804,7 +11762,6 @@ 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); @@ -13735,12 +13692,3 @@ 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 adbbf38ef2775..1b06af5c84e5c 100644 --- a/docshell/base/nsDocShell.h +++ b/docshell/base/nsDocShell.h @@ -134,9 +134,6 @@ 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 @@ -571,8 +568,6 @@ 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, @@ -1270,7 +1265,6 @@ 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 9eb0e9307113e..6cac48a517283 100644 --- a/docshell/base/nsDocShellLoadState.cpp +++ b/docshell/base/nsDocShellLoadState.cpp @@ -874,10 +874,6 @@ 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 bd373c54a6327..352b70d120305 100644 --- a/docshell/base/nsIDocShell.idl +++ b/docshell/base/nsIDocShell.idl @@ -838,9 +838,4 @@ 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 2ee46f3d68869..bec4f13d8b2b2 100644 --- a/docshell/base/nsIWebNavigation.idl +++ b/docshell/base/nsIWebNavigation.idl @@ -268,11 +268,6 @@ 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 3a882dd1ffe79..509955485108e 100644 --- a/docshell/shistory/SessionHistoryEntry.cpp +++ b/docshell/shistory/SessionHistoryEntry.cpp @@ -934,20 +934,6 @@ 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 8f7e6f3193e97..2fa195cf3b8f0 100644 --- a/docshell/shistory/SessionHistoryEntry.h +++ b/docshell/shistory/SessionHistoryEntry.h @@ -170,7 +170,6 @@ 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 622402456d076..73ac40551d4e4 100644 --- a/docshell/shistory/nsISHEntry.idl +++ b/docshell/shistory/nsISHEntry.idl @@ -260,11 +260,6 @@ 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 41ea6086df8b4..1e4000eacd2bc 100644 --- a/docshell/shistory/nsSHEntry.cpp +++ b/docshell/shistory/nsSHEntry.cpp @@ -44,8 +44,7 @@ nsSHEntry::nsSHEntry() mLoadedInThisProcess(false), mPersist(true), mHasUserInteraction(false), - mHasUserActivation(false), - mOnionUrlbarRewritesAllowed(false) {} + mHasUserActivation(false) {}
nsSHEntry::nsSHEntry(const nsSHEntry& aOther) : mShared(aOther.mShared), @@ -73,8 +72,7 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aOther) mLoadedInThisProcess(aOther.mLoadedInThisProcess), mPersist(aOther.mPersist), mHasUserInteraction(false), - mHasUserActivation(aOther.mHasUserActivation), - mOnionUrlbarRewritesAllowed(aOther.mOnionUrlbarRewritesAllowed) {} + mHasUserActivation(aOther.mHasUserActivation) {}
nsSHEntry::~nsSHEntry() { // Null out the mParent pointers on all our kids. @@ -882,18 +880,6 @@ 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(); @@ -943,10 +929,6 @@ 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 76be0ac650505..326b0092cf940 100644 --- a/docshell/shistory/nsSHEntry.h +++ b/docshell/shistory/nsSHEntry.h @@ -66,7 +66,6 @@ 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 b8a25de3629e1..973a9244b8f8b 100644 --- a/dom/interfaces/base/nsIBrowser.idl +++ b/dom/interfaces/base/nsIBrowser.idl @@ -127,8 +127,7 @@ interface nsIBrowser : nsISupports in boolean aIsSynthetic, in boolean aHasRequestContextID, in uint64_t aRequestContextID, - in AString aContentType, - in boolean aOnionUrlbarRewritesAllowed); + in AString aContentType);
/** * Determine what process switching behavior this browser element should have. diff --git a/dom/ipc/BrowserChild.cpp b/dom/ipc/BrowserChild.cpp index bb368b38c5f42..9f1bccda2efef 100644 --- a/dom/ipc/BrowserChild.cpp +++ b/dom/ipc/BrowserChild.cpp @@ -3714,8 +3714,6 @@ 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 10f94926a53ef..4145111ae8490 100644 --- a/dom/ipc/BrowserParent.cpp +++ b/dom/ipc/BrowserParent.cpp @@ -2788,8 +2788,7 @@ mozilla::ipc::IPCResult BrowserParent::RecvOnLocationChange( aLocationChangeData->isSyntheticDocument(), aLocationChangeData->requestContextID().isSome(), aLocationChangeData->requestContextID().valueOr(0), - aLocationChangeData->contentType(), - aLocationChangeData->onionUrlbarRewritesAllowed()); + aLocationChangeData->contentType()); } }
diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 5b21a809251e5..5706c7f5da003 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -143,7 +143,6 @@ 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 8567b4e5a2272..e6efcc4c16d63 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -1338,12 +1338,6 @@ 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 bdad4efc7f99b..4c0c6e9a98695 100644 --- a/netwerk/dns/effective_tld_names.dat +++ b/netwerk/dns/effective_tld_names.dat @@ -5527,8 +5527,6 @@ 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 d76a14e1203af..0b460750971a0 100644 --- a/netwerk/ipc/DocumentLoadListener.cpp +++ b/netwerk/ipc/DocumentLoadListener.cpp @@ -2539,16 +2539,6 @@ 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 8120ca995103a..59a7a5b435229 100644 --- a/toolkit/content/widgets/browser-custom-element.js +++ b/toolkit/content/widgets/browser-custom-element.js @@ -255,8 +255,6 @@
this._mayEnableCharacterEncodingMenu = null;
- this._onionUrlbarRewritesAllowed = false; - this._contentPrincipal = null;
this._contentPartitionedPrincipal = null; @@ -585,12 +583,6 @@ } }
- get onionUrlbarRewritesAllowed() { - return this.isRemoteBrowser - ? this._onionUrlbarRewritesAllowed - : this.docShell.onionUrlbarRewritesAllowed; - } - get contentPrincipal() { return this.isRemoteBrowser ? this._contentPrincipal @@ -1120,8 +1112,7 @@ aIsSynthetic, aHaveRequestContextID, aRequestContextID, - aContentType, - aOnionUrlbarRewritesAllowed + aContentType ) { if (this.isRemoteBrowser && this.messageManager) { if (aCharset != null) { @@ -1143,7 +1134,6 @@ this._contentRequestContextID = aHaveRequestContextID ? aRequestContextID : null; - this._onionUrlbarRewritesAllowed = aOnionUrlbarRewritesAllowed; } }
@@ -1545,7 +1535,6 @@ "_contentPrincipal", "_contentPartitionedPrincipal", "_isSyntheticDocument", - "_onionUrlbarRewritesAllowed", ] ); } diff --git a/toolkit/modules/sessionstore/SessionHistory.jsm b/toolkit/modules/sessionstore/SessionHistory.jsm index e78ec8ddf6b74..f02930aa6e22d 100644 --- a/toolkit/modules/sessionstore/SessionHistory.jsm +++ b/toolkit/modules/sessionstore/SessionHistory.jsm @@ -310,7 +310,6 @@ var SessionHistoryInternal = { }
entry.persist = shEntry.persist; - entry.onionUrlbarRewritesAllowed = shEntry.onionUrlbarRewritesAllowed;
return entry; }, @@ -605,10 +604,6 @@ 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 4295efb39f1fc..efee881c14217 100644 --- a/xpcom/reflect/xptinfo/xptinfo.h +++ b/xpcom/reflect/xptinfo/xptinfo.h @@ -514,8 +514,7 @@ static_assert(sizeof(nsXPTMethodInfo) == 8, "wrong size"); #if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) # define PARAM_BUFFER_COUNT 18 #else -// The max is currently updateForLocationChange in nsIBrowser.idl -# define PARAM_BUFFER_COUNT 15 +# define PARAM_BUFFER_COUNT 14 #endif
/**
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-1 in repository tor-browser.
commit 47e2e10954dd6aa95d4491a6af06a5ee0b477361 Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Mon Feb 21 15:39:11 2022 +0100
Bug 40458: Implement .tor.onion aliases
We have enabled HTTPS-Only mode, therefore we do not need HTTPS-Everywhere anymore. However, we want to keep supporting .tor.onion aliases (especially for securedrop). Therefore, in this patch we implemented the parsing of HTTPS-Everywhere rulesets, and the redirect of .tor.onion domains. Actually, Tor Browser believes they are actual domains. We change them on the fly on the SOCKS proxy requests to resolve the domain, and on the code that verifies HTTPS certificates. --- browser/base/content/browser-siteIdentity.js | 4 +- browser/components/BrowserGlue.jsm | 42 ++ browser/components/about/AboutRedirector.cpp | 4 + browser/components/about/components.conf | 1 + browser/components/moz.build | 1 + .../components/onionservices/OnionAliasStore.jsm | 520 ++++++++++++++++++++ browser/components/onionservices/moz.build | 1 + browser/components/rulesets/RulesetsChild.jsm | 11 + browser/components/rulesets/RulesetsParent.jsm | 79 +++ .../components/rulesets/content/aboutRulesets.css | 319 +++++++++++++ .../components/rulesets/content/aboutRulesets.html | 110 +++++ .../components/rulesets/content/aboutRulesets.js | 531 +++++++++++++++++++++ browser/components/rulesets/content/securedrop.svg | 173 +++++++ browser/components/rulesets/jar.mn | 5 + browser/components/rulesets/moz.build | 6 + netwerk/build/components.conf | 11 + netwerk/build/nsNetCID.h | 10 + netwerk/dns/IOnionAliasService.idl | 34 ++ netwerk/dns/OnionAliasService.cpp | 94 ++++ netwerk/dns/OnionAliasService.h | 36 ++ netwerk/dns/moz.build | 4 + netwerk/socket/nsSOCKSIOLayer.cpp | 24 +- security/manager/ssl/SSLServerCertVerification.cpp | 9 + security/manager/ssl/SSLServerCertVerification.h | 4 +- toolkit/modules/RemotePageAccessManager.jsm | 14 + 25 files changed, 2039 insertions(+), 8 deletions(-)
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js index 6682ae8b096fe..5e70d458094cf 100644 --- a/browser/base/content/browser-siteIdentity.js +++ b/browser/base/content/browser-siteIdentity.js @@ -58,8 +58,8 @@ var gIdentityHandler = { * the browser UI. */ _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), + /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|rulesets|sessionrestore|support|welcomeback|tor|torconnect|tbupdate)(?:[?#]|$)/i : + /^(?:accounts|addons|cache|certificate|config|crashes|downloads|license|logins|preferences|protections|rights|rulesets|sessionrestore|support|welcomeback|tor|torconnect)(?:[?#]|$)/i),
/** * Whether the established HTTPS connection is considered "broken". diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index 08fed785874fd..882a1724bb353 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", @@ -669,6 +670,19 @@ let JSWINDOWACTORS = { enablePreference: "accessibility.blockautorefresh", },
+ Rulesets: { + parent: { + moduleURI: "resource:///modules/RulesetsParent.jsm", + }, + child: { + moduleURI: "resource:///modules/RulesetsChild.jsm", + events: { + DOMWindowCreated: {}, + }, + }, + matches: ["about:rulesets*"], + }, + SearchSERPTelemetry: { parent: { moduleURI: "resource:///actors/SearchSERPTelemetryParent.jsm", @@ -2028,6 +2042,7 @@ BrowserGlue.prototype = { Normandy.uninit(); RFPHelper.uninit(); ASRouterNewTabHook.destroy(); + OnionAliasStore.uninit(); },
// Set up a listener to enable/disable the screenshots extension @@ -2532,6 +2547,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/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp index fd828a630c92a..57c4d40c8ef56 100644 --- a/browser/components/about/AboutRedirector.cpp +++ b/browser/components/about/AboutRedirector.cpp @@ -68,6 +68,10 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, + {"rulesets", "chrome://browser/content/rulesets/aboutRulesets.html", + nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT}, {"tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml", nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT}, diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf index 0916bb75e1d57..df7c05b3193d5 100644 --- a/browser/components/about/components.conf +++ b/browser/components/about/components.conf @@ -24,6 +24,7 @@ pages = [ 'restartrequired', 'rights', 'robots', + 'rulesets', 'sessionrestore', 'tabcrashed', 'torconnect', diff --git a/browser/components/moz.build b/browser/components/moz.build index c304973749122..0eefdd0a06349 100644 --- a/browser/components/moz.build +++ b/browser/components/moz.build @@ -48,6 +48,7 @@ DIRS += [ "prompts", "protocolhandler", "resistfingerprinting", + "rulesets", "search", "securitylevel", "sessionstore", diff --git a/browser/components/onionservices/OnionAliasStore.jsm b/browser/components/onionservices/OnionAliasStore.jsm new file mode 100644 index 0000000000000..cc48288e356ee --- /dev/null +++ b/browser/components/onionservices/OnionAliasStore.jsm @@ -0,0 +1,520 @@ +// Copyright (c) 2022, The Tor Project, Inc. + +"use strict"; + +const EXPORTED_SYMBOLS = ["OnionAliasStore", "OnionAliasStoreTopics"]; + +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" +); + +ChromeUtils.defineModuleGetter( + this, + "JSONFile", + "resource://gre/modules/JSONFile.jsm" +); + +const { HttpsEverywhereControl } = ChromeUtils.import( + "resource:///modules/HttpsEverywhereControl.jsm" +); + +Cu.importGlobalProperties(["crypto", "fetch"]); + +/* OnionAliasStore observer topics */ +const OnionAliasStoreTopics = Object.freeze({ + ChannelsChanged: "onionaliasstore:channels-changed", +}); + +const SECURE_DROP = { + name: "SecureDropTorOnion2021", + pathPrefix: "https://securedrop.org/https-everywhere-2021/", + jwk: { + kty: "RSA", + e: "AQAB", + n: + "vsC7BNafkRe8Uh1DUgCkv6RbPQMdJgAKKnWdSqQd7tQzU1mXfmo_k1Py_2MYMZXOWmqSZ9iwIYkykZYywJ2VyMGve4byj1sLn6YQoOkG8g5Z3V4y0S2RpEfmYumNjTzfq8nxtLnwjaYd4sCUd5wa0SzeLrpRQuXo2bF3QuUF2xcbLJloxX1MmlsMMCdBc-qGNonLJ7bpn_JuyXlDWy1Fkeyw1qgjiOdiRIbMC1x302zgzX6dSrBrNB8Cpsh-vCE0ZjUo8M9caEv06F6QbYmdGJHM0ZZY34OHMSNdf-_qUKIV_SuxuSuFE99tkAeWnbWpyI1V-xhVo1sc7NzChP8ci2TdPvI3_0JyAuCvL6zIFqJUJkZibEUghhg6F09-oNJKpy7rhUJq7zZyLXJsvuXnn0gnIxfjRvMcDfZAKUVMZKRdw7fwWzwQril4Ib0MQOVda9vb_4JMk7Gup-TUI4sfuS4NKwsnKoODIO-2U [...] + }, + scope: /^https?://[a-z0-9-]+(?:.[a-z0-9-]+)*.securedrop.tor.onion//, + enabled: true, + mappings: [], + currentTimestamp: 0, +}; + +// 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); +}); + +// Inspired by aboutMemory.js and PingCentre.jsm +function gunzip(buffer) { + return new Promise((resolve, reject) => { + const listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance( + Ci.nsIStreamLoader + ); + listener.init({ + onStreamComplete(loader, context, status, length, result) { + resolve(String.fromCharCode(...result)); + }, + }); + const scs = Cc["@mozilla.org/streamConverters;1"].getService( + Ci.nsIStreamConverterService + ); + const converter = scs.asyncConvertData( + "gzip", + "uncompressed", + listener, + null + ); + const stream = Cc[ + "@mozilla.org/io/arraybuffer-input-stream;1" + ].createInstance(Ci.nsIArrayBufferInputStream); + stream.setData(buffer, 0, buffer.byteLength); + converter.onStartRequest(null, null); + converter.onDataAvailable(null, stream, 0, buffer.byteLength); + converter.onStopRequest(null, null, null); + }); +} + +class Channel { + static get SIGN_ALGORITHM() { + return { + name: "RSA-PSS", + saltLength: 32, + hash: { name: "SHA-256" }, + }; + } + + constructor(name, pathPrefix, jwk, scope, enabled) { + this.name = name; + this.pathPrefix = pathPrefix; + this.jwk = jwk; + this.scope = scope; + this._enabled = enabled; + + this.mappings = []; + this.currentTimestamp = 0; + this.latestTimestamp = 0; + } + + async updateLatestTimestamp() { + const timestampUrl = this.pathPrefix + "/latest-rulesets-timestamp"; + log.debug(`Updating ${this.name} timestamp from ${timestampUrl}`); + const response = await fetch(timestampUrl); + if (!response.ok) { + throw Error(`Could not fetch timestamp for ${this.name}`, { + cause: response.status, + }); + } + const timestampStr = await response.text(); + const timestamp = parseInt(timestampStr); + // Avoid hijacking, sanitize the timestamp + if (isNaN(timestamp)) { + throw Error("Latest timestamp is not a number"); + } + log.debug(`Updated ${this.name} timestamp: ${timestamp}`); + this.latestTimestamp = timestamp; + } + + async makeKey() { + return crypto.subtle.importKey( + "jwk", + this.jwk, + Channel.SIGN_ALGORITHM, + false, + ["verify"] + ); + } + + async downloadVerifiedRules() { + log.debug(`Downloading and verifying ruleset for ${this.name}`); + + const key = await this.makeKey(); + const signatureUrl = + this.pathPrefix + `/rulesets-signature.${this.latestTimestamp}.sha256`; + const signatureResponse = await fetch(signatureUrl); + if (!signatureResponse.ok) { + throw Error("Could not fetch the rules signature"); + } + const signature = await signatureResponse.arrayBuffer(); + + const rulesUrl = + this.pathPrefix + `/default.rulesets.${this.latestTimestamp}.gz`; + const rulesResponse = await fetch(rulesUrl); + if (!rulesResponse.ok) { + throw Error("Could not fetch rules"); + } + const rulesGz = await rulesResponse.arrayBuffer(); + + if ( + !(await crypto.subtle.verify( + Channel.SIGN_ALGORITHM, + key, + signature, + rulesGz + )) + ) { + throw Error("Could not verify rules signature"); + } + log.debug( + `Downloaded and verified rules for ${this.name}, now uncompressing` + ); + this._makeMappings(JSON.parse(await gunzip(rulesGz))); + } + + _makeMappings(rules) { + const toTest = /http[s]?://[a-zA-Z0-9.]{56}.onion/; + const mappings = []; + rules.rulesets.forEach(rule => { + if (rule.rule.length != 1) { + log.warn(`Unsupported rule lenght: ${rule.rule.length}`); + return; + } + if (!toTest.test(rule.rule[0].to)) { + log.warn( + `Ignoring rule, because of a malformed to: ${rule.rule[0].to}` + ); + return; + } + let toHostname; + try { + const toUrl = new URL(rule.rule[0].to); + toHostname = toUrl.hostname; + } catch (err) { + log.error( + "Cannot detect the hostname from the to rule", + rule.rule[0].to, + err + ); + } + let fromRe; + try { + fromRe = new RegExp(rule.rule[0].from); + } catch (err) { + log.error("Malformed from field", rule.rule[0].from, err); + return; + } + for (const target of rule.target) { + if ( + target.endsWith(".tor.onion") && + this.scope.test(`http://$%7Btarget%7D/%60) && + fromRe.test(`http://$%7Btarget%7D/%60) + ) { + mappings.push([target, toHostname]); + } else { + log.warn("Ignoring malformed rule", rule); + } + } + }); + this.mappings = mappings; + this.currentTimestamp = rules.timestamp; + log.debug(`Updated mappings for ${this.name}`, mappings); + } + + async updateMappings(force) { + force = force === undefined ? false : !!force; + if (!this._enabled && !force) { + return; + } + await this.updateLatestTimestamp(); + if (this.latestTimestamp <= this.currentTimestamp && !force) { + log.debug( + `Rules for ${this.name} are already up to date, skipping update` + ); + return; + } + await this.downloadVerifiedRules(); + } + + get enabled() { + return this._enabled; + } + set enabled(enabled) { + this._enabled = enabled; + if (!enabled) { + this.mappings = []; + this.currentTimestamp = 0; + this.latestTimestamp = 0; + } + } + + toJSON() { + let scope = this.scope.toString(); + scope = scope.substr(1, scope.length - 2); + return { + name: this.name, + pathPrefix: this.pathPrefix, + jwk: this.jwk, + scope, + enabled: this._enabled, + mappings: this.mappings, + currentTimestamp: this.currentTimestamp, + }; + } + + static fromJSON(obj) { + let channel = new Channel( + obj.name, + obj.pathPrefix, + obj.jwk, + new RegExp(obj.scope), + obj.enabled + ); + if (obj.enabled) { + channel.mappings = obj.mappings; + channel.currentTimestamp = obj.currentTimestamp; + } + return channel; + } +} + +class _OnionAliasStore { + static get RULESET_CHECK_INTERVAL() { + return 86400 * 1000; // 1 day, like HTTPS-Everywhere + } + + constructor() { + this._channels = new Map(); + this._rulesetTimeout = null; + this._lastCheck = 0; + this._storage = null; + } + + async init() { + await this._loadSettings(); + const dt = Date.now() - this._lastCheck; + let force = false; + for (const ch of this._channels.values()) { + if (ch.enabled && !ch.currentTimestamp) { + // Edited while being offline or some other error happened + force = true; + break; + } + } + if (dt > _OnionAliasStore.RULESET_CHECK_INTERVAL || force) { + log.debug( + `Mappings are stale (${dt}), or force check requested (${force}), checking them immediately` + ); + await this._periodicRulesetCheck(); + } else { + this._scheduleCheck(_OnionAliasStore.RULESET_CHECK_INTERVAL - dt); + } + + new HttpsEverywhereControl(); + } + + uninit() { + this._clear(); + clearTimeout(this._rulesetTimeout); + this._rulesetTimeout = null; + } + + async getChannels() { + if (this._storage === null) { + await this._loadSettings(); + } + return Array.from(this._channels.values(), ch => ch.toJSON()); + } + + async setChannel(chanData) { + const name = chanData.name?.trim(); + if (!name) { + throw Error("Name cannot be empty"); + } + + new URL(chanData.pathPrefix); + const scope = new RegExp(chanData.scope); + const ch = new Channel( + name, + chanData.pathPrefix, + chanData.jwk, + scope, + !!chanData.enabled + ); + // Call makeKey to make it throw if the key is invalid + await ch.makeKey(); + this._channels.set(name, ch); + this._applyMappings(); + this._saveSettings(); + setTimeout(this._notifyChanges.bind(this), 1); + return ch; + } + + enableChannel(name, enabled) { + const channel = this._channels.get(name); + if (channel !== null) { + channel.enabled = enabled; + this._applyMappings(); + this._saveSettings(); + this._notifyChanges(); + if (enabled && !channel.currentTimestamp) { + this.updateChannel(name); + } + } + } + + async updateChannel(name) { + const channel = this._channels.get(name); + if (channel === null) { + throw Error("Channel not found"); + } + await channel.updateMappings(true); + this._saveSettings(); + this._applyMappings(); + setTimeout(this._notifyChanges.bind(this), 1); + return channel; + } + + deleteChannel(name) { + if (this._channels.delete(name)) { + this._saveSettings(); + this._applyMappings(); + this._notifyChanges(); + } + } + + async _loadSettings() { + if (this._storage !== null) { + return; + } + this._channels = new Map(); + this._storage = new JSONFile({ + path: PathUtils.join( + Services.dirsvc.get("ProfD", Ci.nsIFile).path, + "onion-aliases.json" + ), + dataPostProcessor: this._settingsProcessor.bind(this), + }); + await this._storage.load(); + log.debug("Loaded settings", this._storage.data, this._storage.path); + this._applyMappings(); + this._notifyChanges(); + } + + _settingsProcessor(data) { + if ("lastCheck" in data) { + this._lastCheck = data.lastCheck; + } else { + data.lastCheck = 0; + } + if (!("channels" in data) || !Array.isArray(data.channels)) { + data.channels = [SECURE_DROP]; + // Force updating + data.lastCheck = 0; + } + const channels = new Map(); + data.channels = data.channels.filter(ch => { + try { + channels.set(ch.name, Channel.fromJSON(ch)); + } catch (err) { + log.error("Could not load a channel", err, ch); + return false; + } + return true; + }); + this._channels = channels; + return data; + } + + _saveSettings() { + if (this._storage === null) { + throw Error("Settings have not been loaded"); + } + this._storage.data.lastCheck = this._lastCheck; + this._storage.data.channels = Array.from(this._channels.values(), ch => + ch.toJSON() + ); + this._storage.saveSoon(); + } + + _addMapping(shortOnionHost, longOnionHost) { + const service = Cc["@torproject.org/onion-alias-service;1"].getService( + Ci.IOnionAliasService + ); + service.addOnionAlias(shortOnionHost, longOnionHost); + } + + _clear() { + const service = Cc["@torproject.org/onion-alias-service;1"].getService( + Ci.IOnionAliasService + ); + service.clearOnionAliases(); + } + + _applyMappings() { + this._clear(); + for (const ch of this._channels.values()) { + if (!ch.enabled) { + continue; + } + for (const [short, long] of ch.mappings) { + this._addMapping(short, long); + } + } + } + + async _periodicRulesetCheck() { + log.debug("Begin scheduled ruleset update"); + this._lastCheck = Date.now(); + let anyUpdated = false; + for (const ch of this._channels.values()) { + if (!ch.enabled) { + log.debug(`Not updating ${ch.name} because not enabled`); + continue; + } + log.debug(`Updating ${ch.name}`); + try { + await ch.updateMappings(); + anyUpdated = true; + } catch (err) { + log.error(`Could not update mappings for channel ${ch.name}`, err); + } + } + if (anyUpdated) { + this._saveSettings(); + this._applyMappings(); + this._notifyChanges(); + } else { + log.debug("No channel has been updated, avoid saving"); + } + this._scheduleCheck(_OnionAliasStore.RULESET_CHECK_INTERVAL); + } + + _scheduleCheck(dt) { + log.debug(`Scheduling ruleset update in ${dt}`); + this._rulesetTimeout = setTimeout( + this._periodicRulesetCheck.bind(this), + dt + ); + } + + _notifyChanges() { + Services.obs.notifyObservers( + Array.from(this._channels.values(), ch => ch.toJSON()), + OnionAliasStoreTopics.ChannelsChanged + ); + } +} + +const OnionAliasStore = new _OnionAliasStore(); diff --git a/browser/components/onionservices/moz.build b/browser/components/onionservices/moz.build index 6187035ca00af..8027233d65a68 100644 --- a/browser/components/onionservices/moz.build +++ b/browser/components/onionservices/moz.build @@ -3,6 +3,7 @@ JAR_MANIFESTS += ["jar.mn"] EXTRA_JS_MODULES += [ "ExtensionMessaging.jsm", "HttpsEverywhereControl.jsm", + "OnionAliasStore.jsm", "OnionLocationChild.jsm", "OnionLocationParent.jsm", ] diff --git a/browser/components/rulesets/RulesetsChild.jsm b/browser/components/rulesets/RulesetsChild.jsm new file mode 100644 index 0000000000000..e30de9c7201bd --- /dev/null +++ b/browser/components/rulesets/RulesetsChild.jsm @@ -0,0 +1,11 @@ +// Copyright (c) 2022, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = ["RulesetsChild"]; + +const { RemotePageChild } = ChromeUtils.import( + "resource://gre/actors/RemotePageChild.jsm" +); + +class RulesetsChild extends RemotePageChild {} diff --git a/browser/components/rulesets/RulesetsParent.jsm b/browser/components/rulesets/RulesetsParent.jsm new file mode 100644 index 0000000000000..6a9553644cb17 --- /dev/null +++ b/browser/components/rulesets/RulesetsParent.jsm @@ -0,0 +1,79 @@ +// Copyright (c) 2022, The Tor Project, Inc. + +"use strict"; + +var EXPORTED_SYMBOLS = ["RulesetsParent"]; + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { TorStrings } = ChromeUtils.import("resource:///modules/TorStrings.jsm"); +const { OnionAliasStore, OnionAliasStoreTopics } = ChromeUtils.import( + "resource:///modules/OnionAliasStore.jsm" +); + +const kShowWarningPref = "torbrowser.rulesets.show_warning"; + +// This class allows about:rulesets to get TorStrings and to load/save the +// preference for skipping the warning +class RulesetsParent extends JSWindowActorParent { + constructor(...args) { + super(...args); + + const self = this; + this.observer = { + observe(aSubject, aTopic, aData) { + const obj = aSubject?.wrappedJSObject; + if (aTopic === OnionAliasStoreTopics.ChannelsChanged && obj) { + self.sendAsyncMessage("rulesets:channels-change", obj); + } + }, + }; + Services.obs.addObserver( + this.observer, + OnionAliasStoreTopics.ChannelsChanged + ); + } + + willDestroy() { + Services.obs.removeObserver( + this.observer, + OnionAliasStoreTopics.ChannelsChanged + ); + } + + async receiveMessage(message) { + switch (message.name) { + // RPMSendAsyncMessage + case "rulesets:delete-channel": + OnionAliasStore.deleteChannel(message.data); + break; + case "rulesets:enable-channel": + OnionAliasStore.enableChannel(message.data.name, message.data.enabled); + break; + case "rulesets:set-show-warning": + Services.prefs.setBoolPref(kShowWarningPref, message.data); + break; + // RPMSendQuery + case "rulesets:get-channels": + return OnionAliasStore.getChannels(); + case "rulesets:get-init-args": + return { + TorStrings, + showWarning: Services.prefs.getBoolPref(kShowWarningPref, true), + }; + case "rulesets:set-channel": + const ch = await OnionAliasStore.setChannel(message.data); + return ch; + case "rulesets:update-channel": + // We need to catch any error in this way, because in case of an + // exception, RPMSendQuery does not return on the other side + try { + const channel = await OnionAliasStore.updateChannel(message.data); + return channel; + } catch (err) { + console.error("Cannot update the channel", err); + return { error: err.toString() }; + } + } + return undefined; + } +} diff --git a/browser/components/rulesets/content/aboutRulesets.css b/browser/components/rulesets/content/aboutRulesets.css new file mode 100644 index 0000000000000..60b699fe8a02a --- /dev/null +++ b/browser/components/rulesets/content/aboutRulesets.css @@ -0,0 +1,319 @@ +/* Copyright (c) 2022, The Tor Project, Inc. */ + +/* General rules */ + +html, body { + margin: 0; + padding: 0; + width: 100%; + height: 100%; +} + +body { + font: message-box; + background-color: var(--in-content-page-background); + color: var(--in-content-page-color); + font-size: 15px; + cursor: default; +} + +label { + display: flex; + align-items: center; + padding: 6px 0; +} + +input[type=text] { + margin: 0; + width: 360px; + max-width: 100%; +} + +textarea { + margin: 0; + width: var(--content-width); + max-width: 100%; + box-sizing: border-box; +} + +select, option { + font-weight: 700; +} + +dt { + margin: var(--ruleset-vmargin) 0 0 0; + padding: 0; + color: var(--in-content-deemphasized-text); + font-size: 85%; +} + +dd { + margin: 8px 0 0 0; + padding: 0; + max-width: 600px; + box-sizing: border-box; + line-height: 1.4; +} + +hr { + width: 40px; + margin: 0; + border: none; + border-top: 1px solid var(--in-content-border-color); +} + +.hidden { + display: none !important; +} + +/* Initial warning */ + +#warning-wrapper { + display: none; +} + +.state-warning #warning-wrapper { + display: flex; + align-items: center; + height: 100%; +} + +#warning { + margin-top: -20vh; + padding: 0 160px; + background-image: url("chrome://global/skin/icons/warning.svg"); + background-position: 84px 0; + background-repeat: no-repeat; + background-size: 48px; + fill: #ffbd4f; + -moz-context-properties: fill; +} + +#warning:dir(rtl) { + background-position: right 84px top 0; +} + +#warning-description { + margin: 30px 0 16px 0; +} + +#warning-buttonbar { + margin-top: 30px; + text-align: right; +} + +/* Actual content */ + +:root { + --sidebar-width: 320px; + --content-width: 600px; + --ruleset-vmargin: 40px; +} + +#main-content { + display: flex; + height: 100%; +} + +.state-warning #main-content { + display: none; +} + +section { + display: none; + flex: 1 0 auto; + padding: 40px; +} + +.title { + display: flex; + align-items: center; + width: var(--content-width); + max-width: 100%; + padding-bottom: 16px; + border-bottom: 1px solid var(--in-content-border-color); +} + +.title h1 { + margin: 0; + padding: 0; + padding-inline-start: 35px; + font-size: 20px; + font-weight: 700; + line-height: 30px; + background-image: url("chrome://browser/content/rulesets/securedrop.svg"); + background-position: 0 4px; + background-size: 22px; + background-repeat: no-repeat; +} + +#main-content h1:dir(rtl) { + background-position: right 0 top 4px; +} + +/* Ruleset list */ + +aside { + display: flex; + flex-direction: column; + flex: 0 0 var(--sidebar-width); + box-sizing: border-box; + + border-inline-end: 1px solid var(--in-content-border-color); + background-color: var(--in-content-box-background); +} + +#ruleset-heading { + padding: 16px; + text-align: center; + font-weight: 700; + border-bottom: 1px solid var(--in-content-border-color); +} + +#ruleset-list-container { + flex: 1; +} + +#ruleset-list-empty { + padding: 16px; + text-align: center; +} + +#ruleset-list-empty-description { + font-size: 80%; +} + +#ruleset-list { + margin: 0; + padding: 0; +} + +#ruleset-list li { + display: flex; + align-items: center; + margin: 0; + padding: 10px 18px; + list-style: none; + border-inline-start: 4px solid transparent; + border-bottom: 1px solid var(--in-content-border-color); +} + +#ruleset-list li:last-child { + border-bottom: none; +} + +#ruleset-list .icon { + width: 16px; + height: 16px; + margin-inline-end: 12px; + background-image: url("chrome://browser/content/rulesets/securedrop.svg"); + background-size: 16px; +} + +#ruleset-list .icon.has-favicon { + background: transparent; +} + +#ruleset-list .name { + font-weight: 700; +} + +#ruleset-list .description { + font-size: 85%; + color: var(--in-content-deemphasized-text); +} + +#ruleset-list .selected { + border-inline-start-color: var(--in-content-accent-color); +} + +#ruleset-list .selected.disabled { + border-inline-start-color: var(--in-content-border-color); +} + +#ruleset-list li:not(.selected):hover { + background-color: var(--in-content-button-background-hover); + color: var(--in-content-button-text-color-hover); +} + +#ruleset-list li:not(.selected):hover:active { + background-color: var(--in-content-button-background-active); +} + +#ruleset-list #ruleset-template { + display: none; +} + +/* Ruleset details */ + +.state-details #ruleset-details { + display: block; +} + +#ruleset-jwk-value { + padding: 8px; + border-radius: 2px; + background-color: var(--in-content-box-background); + font-size: 85%; + line-break: anywhere; +} + +#ruleset-edit { + margin-inline-start: auto; + padding-inline-start: 32px; + background-image: url("chrome://global/skin/icons/edit.svg"); + background-repeat: no-repeat; + background-position: 8px; + -moz-context-properties: fill; + fill: currentColor; + min-width: auto; + flex: 0 0 auto; +} + +#ruleset-enable { + margin-top: var(--ruleset-vmargin); +} + +#ruleset-buttonbar { + margin: var(--ruleset-vmargin) 0; +} + +#ruleset-updated { + margin-top: 24px; + color: var(--in-content-deemphasized-text); + font-size: 85%; +} + +/* Edit ruleset */ + +.state-edit #edit-ruleset { + display: block; +} + +#edit-ruleset label { + color: var(--in-content-deemphasized-text); + display: block; +} + +#edit-ruleset label, #edit-buttonbar { + margin-top: var(--ruleset-vmargin); +} + +label#edit-enable { + display: flex; + align-items: center; +} + +/* No rulesets */ + +#no-rulesets { + max-width: 100%; + background-image: url(chrome://browser/skin/preferences/no-search-results.svg); + background-size: 275px 212px; + background-position: center center; + background-repeat: no-repeat; +} + +.state-noRulesets #no-rulesets { + display: block; +} diff --git a/browser/components/rulesets/content/aboutRulesets.html b/browser/components/rulesets/content/aboutRulesets.html new file mode 100644 index 0000000000000..d5b03435b1e72 --- /dev/null +++ b/browser/components/rulesets/content/aboutRulesets.html @@ -0,0 +1,110 @@ +<!-- Copyright (c) 2022, The Tor Project, Inc. --> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" /> + <link rel="stylesheet" href="chrome://global/skin/in-content/common.css"> + <link rel="stylesheet" href="chrome://browser/content/rulesets/aboutRulesets.css"> + </head> + <body> + <!-- Warning --> + <div id="warning-wrapper"> + <div id="warning"> + <h1 id="warning-title"></h1> + <p id="warning-description"></p> + <p> + <label> + <input id="warning-enable-checkbox" type="checkbox" checked="checked"> + <span id="warning-enable-label"></span> + </label> + </p> + <div id="warning-buttonbar"> + <button id="warning-button" autofocus="autofocus"></button> + </div> + </div> + </div> + + <div id="main-content"> + <!-- Ruleset list --> + <aside> + <div id="ruleset-heading"></div> + <div id="ruleset-list-container"> + <div id="ruleset-list-empty"> + <p id="ruleset-list-empty-title"></p> + <p id="ruleset-list-empty-description"></p> + </div> + <ul id="ruleset-list"> + <li id="ruleset-template"> + <div class="icon"> + </div> + <div> + <div class="name"></div> + <div class="description"></div> + </div> + </li> + </ul> + </div> + </aside> + + <!-- Ruleset details --> + <section id="ruleset-details"> + <div class="title"> + <h1 id="ruleset-title"></h1> + <button id="ruleset-edit" class="ghost-button"></button> + </div> + <dl> + <dt id="ruleset-jwk-label"></dt> + <dd id="ruleset-jwk-value"></dd> + <dt id="ruleset-path-prefix-label"></dt> + <dd> + <a id="ruleset-path-prefix-value" target="_blank"></a> + </dd> + <dt id="ruleset-scope-label"></dt> + <dd id="ruleset-scope-value"></dd> + </dl> + <label id="ruleset-enable"> + <input type="checkbox" id="ruleset-enable-checkbox"> + <span id="ruleset-enable-label"></span> + </label> + <div id="ruleset-buttonbar"> + <button id="ruleset-update-button"></button> + </div> + <hr> + <p id="ruleset-updated"></p> + </section> + + <!-- Edit ruleset --> + <section id="edit-ruleset"> + <div class="title"> + <h1 id="edit-title"></h1> + </div> + <form id="edit-ruleset-form"> + <label> + <div id="edit-jwk-label"></div> + <textarea id="edit-jwk-textarea" rows="10"></textarea> + </label> + <label> + <div id="edit-path-prefix-label"></div> + <input id="edit-path-prefix-input" type="text"> + </label> + <label> + <div id="edit-scope-label"></div> + <input id="edit-scope-input" type="text"> + </label> + <label id="edit-enable"> + <input type="checkbox" id="edit-enable-checkbox"> + <span id="edit-enable-label"></span> + </label> + <div id="edit-buttonbar"> + <button id="edit-save" class="primary"></button> + <button id="edit-cancel"></button> + </div> + </form> + </section> + + <!-- No rulesets --> + <section id="no-rulesets"></section> + </div> + <script src="chrome://browser/content/rulesets/aboutRulesets.js"></script> + </body> +</html> diff --git a/browser/components/rulesets/content/aboutRulesets.js b/browser/components/rulesets/content/aboutRulesets.js new file mode 100644 index 0000000000000..4fabdca1b93d1 --- /dev/null +++ b/browser/components/rulesets/content/aboutRulesets.js @@ -0,0 +1,531 @@ +"use strict"; + +/* globals RPMAddMessageListener, RPMSendQuery, RPMSendAsyncMessage */ + +let TorStrings; + +const Orders = Object.freeze({ + Name: "name", + NameDesc: "name-desc", + LastUpdate: "last-update", +}); + +const States = Object.freeze({ + Warning: "warning", + Details: "details", + Edit: "edit", + NoRulesets: "noRulesets", +}); + +function setUpdateDate(ruleset, element) { + if (!ruleset.enabled) { + element.textContent = TorStrings.rulesets.disabled; + return; + } + if (!ruleset.currentTimestamp) { + element.textContent = TorStrings.rulesets.neverUpdated; + return; + } + + const formatter = new Intl.DateTimeFormat(navigator.languages, { + year: "numeric", + month: "long", + day: "numeric", + }); + element.textContent = TorStrings.rulesets.lastUpdated.replace( + "%S", + formatter.format(new Date(ruleset.currentTimestamp * 1000)) + ); +} + +class WarningState { + selectors = Object.freeze({ + wrapper: "#warning-wrapper", + title: "#warning-title", + description: "#warning-description", + enableCheckbox: "#warning-enable-checkbox", + enableLabel: "#warning-enable-label", + button: "#warning-button", + }); + + elements = Object.freeze({ + wrapper: document.querySelector(this.selectors.wrapper), + title: document.querySelector(this.selectors.title), + description: document.querySelector(this.selectors.description), + enableCheckbox: document.querySelector(this.selectors.enableCheckbox), + enableLabel: document.querySelector(this.selectors.enableLabel), + button: document.querySelector(this.selectors.button), + }); + + constructor() { + const elements = this.elements; + elements.title.textContent = TorStrings.rulesets.warningTitle; + elements.description.textContent = TorStrings.rulesets.warningDescription; + elements.enableLabel.textContent = TorStrings.rulesets.warningEnable; + elements.button.textContent = TorStrings.rulesets.warningButton; + elements.enableCheckbox.addEventListener( + "change", + this.onEnableChange.bind(this) + ); + elements.button.addEventListener("click", this.onButtonClick.bind(this)); + } + + show() { + this.elements.button.focus(); + } + + hide() {} + + onEnableChange() { + RPMSendAsyncMessage( + "rulesets:set-show-warning", + this.elements.enableCheckbox.checked + ); + } + + onButtonClick() { + gAboutRulesets.selectFirst(); + } +} + +class DetailsState { + selectors = Object.freeze({ + title: "#ruleset-title", + edit: "#ruleset-edit", + jwkLabel: "#ruleset-jwk-label", + jwkValue: "#ruleset-jwk-value", + pathPrefixLabel: "#ruleset-path-prefix-label", + pathPrefixValue: "#ruleset-path-prefix-value", + scopeLabel: "#ruleset-scope-label", + scopeValue: "#ruleset-scope-value", + enableCheckbox: "#ruleset-enable-checkbox", + enableLabel: "#ruleset-enable-label", + updateButton: "#ruleset-update-button", + updated: "#ruleset-updated", + }); + + elements = Object.freeze({ + title: document.querySelector(this.selectors.title), + edit: document.querySelector(this.selectors.edit), + jwkLabel: document.querySelector(this.selectors.jwkLabel), + jwkValue: document.querySelector(this.selectors.jwkValue), + pathPrefixLabel: document.querySelector(this.selectors.pathPrefixLabel), + pathPrefixValue: document.querySelector(this.selectors.pathPrefixValue), + scopeLabel: document.querySelector(this.selectors.scopeLabel), + scopeValue: document.querySelector(this.selectors.scopeValue), + enableCheckbox: document.querySelector(this.selectors.enableCheckbox), + enableLabel: document.querySelector(this.selectors.enableLabel), + updateButton: document.querySelector(this.selectors.updateButton), + updated: document.querySelector(this.selectors.updated), + }); + + constructor() { + const elements = this.elements; + elements.edit.textContent = TorStrings.rulesets.edit; + elements.edit.addEventListener("click", this.onEdit.bind(this)); + elements.jwkLabel.textContent = TorStrings.rulesets.jwk; + elements.pathPrefixLabel.textContent = TorStrings.rulesets.pathPrefix; + elements.scopeLabel.textContent = TorStrings.rulesets.scope; + elements.enableCheckbox.addEventListener( + "change", + this.onEnable.bind(this) + ); + elements.enableLabel.textContent = TorStrings.rulesets.enable; + elements.updateButton.textContent = TorStrings.rulesets.checkUpdates; + elements.updateButton.addEventListener("click", this.onUpdate.bind(this)); + } + + show(ruleset) { + const elements = this.elements; + elements.title.textContent = ruleset.name; + elements.jwkValue.textContent = JSON.stringify(ruleset.jwk); + elements.pathPrefixValue.setAttribute("href", ruleset.pathPrefix); + elements.pathPrefixValue.textContent = ruleset.pathPrefix; + elements.scopeValue.textContent = ruleset.scope; + elements.enableCheckbox.checked = ruleset.enabled; + if (ruleset.enabled) { + elements.updateButton.removeAttribute("disabled"); + } else { + elements.updateButton.setAttribute("disabled", "disabled"); + } + setUpdateDate(ruleset, elements.updated); + this._showing = ruleset; + + gAboutRulesets.list.setItemSelected(ruleset.name); + } + + hide() { + this._showing = null; + } + + onEdit() { + gAboutRulesets.setState(States.Edit, this._showing); + } + + async onEnable() { + await RPMSendAsyncMessage("rulesets:enable-channel", { + name: this._showing.name, + enabled: this.elements.enableCheckbox.checked, + }); + } + + async onUpdate() { + try { + await RPMSendQuery("rulesets:update-channel", this._showing.name); + } catch (err) { + console.error("Could not update the rulesets", err); + } + } +} + +class EditState { + selectors = Object.freeze({ + form: "#edit-ruleset-form", + title: "#edit-title", + nameGroup: "#edit-name-group", + nameLabel: "#edit-name-label", + nameInput: "#edit-name-input", + jwkLabel: "#edit-jwk-label", + jwkTextarea: "#edit-jwk-textarea", + pathPrefixLabel: "#edit-path-prefix-label", + pathPrefixInput: "#edit-path-prefix-input", + scopeLabel: "#edit-scope-label", + scopeInput: "#edit-scope-input", + enableCheckbox: "#edit-enable-checkbox", + enableLabel: "#edit-enable-label", + save: "#edit-save", + cancel: "#edit-cancel", + }); + + elements = Object.freeze({ + form: document.querySelector(this.selectors.form), + title: document.querySelector(this.selectors.title), + jwkLabel: document.querySelector(this.selectors.jwkLabel), + jwkTextarea: document.querySelector(this.selectors.jwkTextarea), + pathPrefixLabel: document.querySelector(this.selectors.pathPrefixLabel), + pathPrefixInput: document.querySelector(this.selectors.pathPrefixInput), + scopeLabel: document.querySelector(this.selectors.scopeLabel), + scopeInput: document.querySelector(this.selectors.scopeInput), + enableCheckbox: document.querySelector(this.selectors.enableCheckbox), + enableLabel: document.querySelector(this.selectors.enableLabel), + save: document.querySelector(this.selectors.save), + cancel: document.querySelector(this.selectors.cancel), + }); + + constructor() { + const elements = this.elements; + elements.jwkLabel.textContent = TorStrings.rulesets.jwk; + elements.jwkTextarea.setAttribute( + "placeholder", + TorStrings.rulesets.jwkPlaceholder + ); + elements.pathPrefixLabel.textContent = TorStrings.rulesets.pathPrefix; + elements.pathPrefixInput.setAttribute( + "placeholder", + TorStrings.rulesets.pathPrefixPlaceholder + ); + elements.scopeLabel.textContent = TorStrings.rulesets.scope; + elements.scopeInput.setAttribute( + "placeholder", + TorStrings.rulesets.scopePlaceholder + ); + elements.enableLabel.textContent = TorStrings.rulesets.enable; + elements.save.textContent = TorStrings.rulesets.save; + elements.save.addEventListener("click", this.onSave.bind(this)); + elements.cancel.textContent = TorStrings.rulesets.cancel; + elements.cancel.addEventListener("click", this.onCancel.bind(this)); + } + + show(ruleset) { + const elements = this.elements; + elements.form.reset(); + elements.title.textContent = ruleset.name; + elements.jwkTextarea.value = JSON.stringify(ruleset.jwk); + elements.pathPrefixInput.value = ruleset.pathPrefix; + elements.scopeInput.value = ruleset.scope; + elements.enableCheckbox.checked = ruleset.enabled; + this._editing = ruleset; + } + + hide() { + this.elements.form.reset(); + this._editing = null; + } + + async onSave(e) { + e.preventDefault(); + const elements = this.elements; + + let valid = true; + const name = this._editing.name; + + let jwk; + try { + jwk = JSON.parse(elements.jwkTextarea.value); + await crypto.subtle.importKey( + "jwk", + jwk, + { + name: "RSA-PSS", + saltLength: 32, + hash: { name: "SHA-256" }, + }, + true, + ["verify"] + ); + elements.jwkTextarea.setCustomValidity(""); + } catch (err) { + console.error("Invalid JSON or invalid JWK", err); + elements.jwkTextarea.setCustomValidity(TorStrings.rulesets.jwkInvalid); + valid = false; + } + + const pathPrefix = elements.pathPrefixInput.value.trim(); + try { + const url = new URL(pathPrefix); + if (url.protocol !== "http:" && url.protocol !== "https:") { + elements.pathPrefixInput.setCustomValidity( + TorStrings.rulesets.pathPrefixInvalid + ); + valid = false; + } else { + elements.pathPrefixInput.setCustomValidity(""); + } + } catch (err) { + console.error("The path prefix is not a valid URL", err); + elements.pathPrefixInput.setCustomValidity( + TorStrings.rulesets.pathPrefixInvalid + ); + valid = false; + } + + let scope; + try { + scope = new RegExp(elements.scopeInput.value.trim()); + elements.scopeInput.setCustomValidity(""); + } catch (err) { + elements.scopeInput.setCustomValidity(TorStrings.rulesets.scopeInvalid); + valid = false; + } + + if (!valid) { + return; + } + + const enabled = elements.enableCheckbox.checked; + + const rulesetData = { name, jwk, pathPrefix, scope, enabled }; + const ruleset = await RPMSendQuery("rulesets:set-channel", rulesetData); + gAboutRulesets.setState(States.Details, ruleset); + if (enabled) { + try { + await RPMSendQuery("rulesets:update-channel", name); + } catch (err) { + console.warn("Could not update the ruleset after adding it", err); + } + } + } + + onCancel(e) { + e.preventDefault(); + if (this._editing === null) { + gAboutRulesets.selectFirst(); + } else { + gAboutRulesets.setState(States.Details, this._editing); + } + } +} + +class NoRulesetsState { + show() {} + hide() {} +} + +class RulesetList { + selectors = Object.freeze({ + heading: "#ruleset-heading", + list: "#ruleset-list", + emptyContainer: "#ruleset-list-empty", + emptyTitle: "#ruleset-list-empty-title", + emptyDescription: "#ruleset-list-empty-description", + itemTemplate: "#ruleset-template", + itemName: ".name", + itemDescr: ".description", + }); + + elements = Object.freeze({ + heading: document.querySelector(this.selectors.heading), + list: document.querySelector(this.selectors.list), + emptyContainer: document.querySelector(this.selectors.emptyContainer), + emptyTitle: document.querySelector(this.selectors.emptyTitle), + emptyDescription: document.querySelector(this.selectors.emptyDescription), + itemTemplate: document.querySelector(this.selectors.itemTemplate), + }); + + nameAttribute = "data-name"; + + rulesets = []; + + constructor() { + const elements = this.elements; + + // Header + elements.heading.textContent = TorStrings.rulesets.rulesets; + // Empty + elements.emptyTitle.textContent = TorStrings.rulesets.noRulesets; + elements.emptyDescription.textContent = TorStrings.rulesets.noRulesetsDescr; + + RPMAddMessageListener( + "rulesets:channels-change", + this.onRulesetsChanged.bind(this) + ); + } + + getSelectedRuleset() { + const name = this.elements.list + .querySelector(".selected") + ?.getAttribute(this.nameAttribute); + for (const ruleset of this.rulesets) { + if (ruleset.name == name) { + return ruleset; + } + } + return null; + } + + isEmpty() { + return !this.rulesets.length; + } + + async update() { + this.rulesets = await RPMSendQuery("rulesets:get-channels"); + await this._populateRulesets(); + } + + setItemSelected(name) { + name = name.replace(/["\]/g, "\$&"); + const item = this.elements.list.querySelector( + `.item[${this.nameAttribute}="${name}"]` + ); + this._selectItem(item); + } + + async _populateRulesets() { + if (this.isEmpty()) { + this.elements.emptyContainer.classList.remove("hidden"); + } else { + this.elements.emptyContainer.classList.add("hidden"); + } + + const list = this.elements.list; + const selName = list + .querySelector(".item.selected") + ?.getAttribute(this.nameAttribute); + const items = list.querySelectorAll(".item"); + for (const item of items) { + item.remove(); + } + + for (const ruleset of this.rulesets) { + const item = this._addItem(ruleset); + if (ruleset.name === selName) { + this._selectItem(item); + } + } + } + + _addItem(ruleset) { + const item = this.elements.itemTemplate.cloneNode(true); + item.removeAttribute("id"); + item.classList.add("item"); + item.querySelector(this.selectors.itemName).textContent = ruleset.name; + const descr = item.querySelector(this.selectors.itemDescr); + if (ruleset.enabled) { + setUpdateDate(ruleset, descr); + } else { + descr.textContent = TorStrings.rulesets.disabled; + item.classList.add("disabled"); + } + item.setAttribute(this.nameAttribute, ruleset.name); + item.addEventListener("click", () => { + this.onRulesetClick(ruleset); + }); + this.elements.list.append(item); + return item; + } + + _selectItem(item) { + this.elements.list.querySelector(".selected")?.classList.remove("selected"); + item?.classList.add("selected"); + } + + onRulesetClick(ruleset) { + gAboutRulesets.setState(States.Details, ruleset); + } + + onRulesetsChanged(data) { + this.rulesets = data.data; + this._populateRulesets(); + const selected = this.getSelectedRuleset(); + if (selected !== null) { + gAboutRulesets.setState(States.Details, selected); + } + } +} + +class AboutRulesets { + _state = null; + + async init() { + const args = await RPMSendQuery("rulesets:get-init-args"); + TorStrings = args.TorStrings; + const showWarning = args.showWarning; + + this.list = new RulesetList(); + this._states = {}; + this._states[States.Warning] = new WarningState(); + this._states[States.Details] = new DetailsState(); + this._states[States.Edit] = new EditState(); + this._states[States.NoRulesets] = new NoRulesetsState(); + + await this.refreshRulesets(); + + if (showWarning) { + this.setState(States.Warning); + } else { + this.selectFirst(); + } + } + + setState(state, ...args) { + document.querySelector("body").className = `state-${state}`; + this._state?.hide(); + this._state = this._states[state]; + this._state.show(...args); + } + + async refreshRulesets() { + await this.list.update(); + if (this._state === this._states[States.Details]) { + const ruleset = this.list.getSelectedRuleset(); + if (ruleset !== null) { + this.setState(States.Details, ruleset); + } else { + this.selectFirst(); + } + } else if (this.list.isEmpty()) { + this.setState(States.NoRulesets); + } + } + + selectFirst() { + if (this.list.isEmpty()) { + this.setState(States.NoRulesets); + } else { + this.setState("details", this.list.rulesets[0]); + } + } +} + +const gAboutRulesets = new AboutRulesets(); +gAboutRulesets.init(); diff --git a/browser/components/rulesets/content/securedrop.svg b/browser/components/rulesets/content/securedrop.svg new file mode 100644 index 0000000000000..69cd584ac1edf --- /dev/null +++ b/browser/components/rulesets/content/securedrop.svg @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 23.0.5, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + version="1.1" + id="Layer_1" + x="0px" + y="0px" + viewBox="0 0 423.3 423.3" + xml:space="preserve" + width="423.29999" + height="423.29999" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/%22%3E<defs + id="defs49"> + + + + + + + + + + + <defs + id="defs24"> + <filter + id="Adobe_OpacityMaskFilter_1_" + filterUnits="userSpaceOnUse" + x="-66" + y="-0.89999998" + width="183.3" + height="318.20001"> + <feColorMatrix + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" + id="feColorMatrix21" /> + </filter> + </defs> + <mask + maskUnits="userSpaceOnUse" + x="-66" + y="-0.9" + width="183.3" + height="318.2" + id="mask-4_1_"> + <g + class="st4" + id="g27"> + <polygon + id="path-3_1_" + class="st2" + points="117.3,-0.9 117.3,317.3 -66,317.3 -66,-0.9 " /> + </g> + </mask> + + + + <defs + id="defs36"> + <filter + id="Adobe_OpacityMaskFilter_2_" + filterUnits="userSpaceOnUse" + x="-66" + y="-1" + width="366.29999" + height="211.3"> + <feColorMatrix + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" + id="feColorMatrix33" /> + </filter> + </defs> + <mask + maskUnits="userSpaceOnUse" + x="-66" + y="-1" + width="366.3" + height="211.3" + id="mask-6_1_"> + <g + class="st6" + id="g39"> + <polygon + id="path-5_1_" + class="st2" + points="300.3,-1 300.3,210.3 -66,210.3 -66,-1 " /> + </g> + </mask> + + + + + <defs + id="defs11"> + <filter + id="Adobe_OpacityMaskFilter" + filterUnits="userSpaceOnUse" + x="-65.199997" + y="-0.89999998" + width="183.5" + height="318.20001"> + <feColorMatrix + type="matrix" + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" + id="feColorMatrix8" /> + </filter> + </defs> + <mask + maskUnits="userSpaceOnUse" + x="-65.2" + y="-0.9" + width="183.5" + height="318.2" + id="mask-2_1_"> + <g + class="st1" + id="g14"> + <polygon + id="path-1_1_" + class="st2" + points="-65.2,317.3 -65.2,-0.9 118.3,-0.9 118.3,317.3 " /> + </g> + </mask> + + + + </defs> +<style + type="text/css" + id="style2"> + .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#055FB4;} + .st1{filter:url(#Adobe_OpacityMaskFilter);} + .st2{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;} + .st3{mask:url(#mask-2_1_);fill-rule:evenodd;clip-rule:evenodd;fill:#055FB4;} + .st4{filter:url(#Adobe_OpacityMaskFilter_1_);} + .st5{mask:url(#mask-4_1_);fill-rule:evenodd;clip-rule:evenodd;fill:#093D70;} + .st6{filter:url(#Adobe_OpacityMaskFilter_2_);} + .st7{mask:url(#mask-6_1_);fill-rule:evenodd;clip-rule:evenodd;fill:#2E8AE8;} +</style> +<title + id="title4">Big Logo HP</title> +<circle + style="fill:#ffffff;stroke:none;stroke-width:2.66667" + id="path1626" + r="176.46054" + cy="211.64999" + cx="211.64999" /><path + id="Fill-1" + class="st0" + d="m 327.99999,225.5 -41.8,23.9 0.2,58.5 42.5,-23.6 c 5.1,-2.8 8.3,-8.3 8.3,-14 v -39.7 c -0.2,-0.9 -0.2,-2.1 -0.9,-2.8 -1.9,-2.8 -5.6,-3.9 -8.3,-2.3" /><path + id="Fill-3" + class="st3" + d="m 85.9,173.2 c 0,9.9 -5.3,19 -14,24.1 l -90.7,52.3 V 127.3 l 84,-48.6 c 2.1,-1.1 4.4,-1.8 6.9,-1.8 7.6,0 13.8,6.2 13.8,13.8 z M -65.2,104.9 V 317.3 L 118.3,211.5 V -0.9 Z" + mask="url(#mask-2_1_)" + transform="translate(276.49999,106)" /><path + id="Fill-7" + class="st5" + d="M 71.7,158.3 3.3,118.8 v 14 l 68.4,39.5 v 73.9 L -22.2,192 v -30.1 l 64,37.2 v -13.8 l -64,-37.2 V 75 l 93.8,54.2 v 29.1 z M -66,-0.9 V 211.5 L 117.3,317.3 V 104.9 Z" + mask="url(#mask-4_1_)" + transform="translate(94.499994,106)" /><path + id="Fill-10" + class="st7" + d="m 135,143.2 55.3,-31.1 -62.2,-17.2 c 1.1,-2.1 1.8,-4.4 1.8,-6.6 0,-11.5 -16.7,-21.1 -37.4,-21.1 -20.6,0 -37.4,9.4 -37.4,21.1 0,11.7 16.7,21.1 37.4,21.1 2.8,0 5.3,-0.2 8,-0.5 z M 117,210.3 -66,104.7 117,-1 300.3,104.7 Z" + mask="url(#mask-6_1_)" + transform="translate(94.499994,1)" /> +<metadata + id="metadata866">rdf:RDF<cc:Work + rdf:about="">dc:titleBig Logo HP</dc:title></cc:Work></rdf:RDF></metadata></svg> diff --git a/browser/components/rulesets/jar.mn b/browser/components/rulesets/jar.mn new file mode 100644 index 0000000000000..e0b67442d89c0 --- /dev/null +++ b/browser/components/rulesets/jar.mn @@ -0,0 +1,5 @@ +browser.jar: + content/browser/rulesets/aboutRulesets.css (content/aboutRulesets.css) + content/browser/rulesets/aboutRulesets.html (content/aboutRulesets.html) + content/browser/rulesets/aboutRulesets.js (content/aboutRulesets.js) + content/browser/rulesets/securedrop.svg (content/securedrop.svg) diff --git a/browser/components/rulesets/moz.build b/browser/components/rulesets/moz.build new file mode 100644 index 0000000000000..daec4c3025244 --- /dev/null +++ b/browser/components/rulesets/moz.build @@ -0,0 +1,6 @@ +JAR_MANIFESTS += ['jar.mn'] + +EXTRA_JS_MODULES += [ + 'RulesetsChild.jsm', + 'RulesetsParent.jsm', +] diff --git a/netwerk/build/components.conf b/netwerk/build/components.conf index 69e4f547e8eb3..d1348783da12e 100644 --- a/netwerk/build/components.conf +++ b/netwerk/build/components.conf @@ -652,3 +652,14 @@ if link_service: 'singleton': True, }, **link_service) ] + +Classes += [ + { + 'cid': '{0df7784b-7316-486d-bc99-bf47b7a05974}', + 'contract_ids': ['@torproject.org/onion-alias-service;1'], + 'singleton': True, + 'type': 'IOnionAliasService', + 'constructor': 'torproject::OnionAliasService::GetSingleton', + 'headers': ['torproject/OnionAliasService.h'], + }, +] diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index 3dce83342516b..e818a481595e3 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -842,4 +842,14 @@ } \ }
+// Onion alias service implementing IOnionAliasService +#define ONIONALIAS_CONTRACTID \ + "@torproject.org/onion-alias-service;1" +#define ONIONALIAS_CID \ + { /* 0df7784b-7316-486d-bc99-bf47b7a05974 */ \ + 0x0df7784b, 0x7316, 0x486d, { \ + 0xbc, 0x99, 0xbf, 0x47, 0xb7, 0xa0, 0x59, 0x74 \ + } \ + } + #endif // nsNetCID_h__ diff --git a/netwerk/dns/IOnionAliasService.idl b/netwerk/dns/IOnionAliasService.idl new file mode 100644 index 0000000000000..692c74b917930 --- /dev/null +++ b/netwerk/dns/IOnionAliasService.idl @@ -0,0 +1,34 @@ +#include "nsISupports.idl" + +/** + * Service used for .tor.onion aliases. + * It stores the real .onion address that correspond to .tor.onion addresses, + * so that both C++ code and JS can access them. + */ +[scriptable, uuid(0df7784b-7316-486d-bc99-bf47b7a05974)] +interface IOnionAliasService : nsISupports +{ + /** + * Add a new Onion alias + * @param aShortHostname + * The short hostname that is being rewritten + * @param aLongHostname + * The complete onion v3 hostname + */ + void addOnionAlias(in ACString aShortHostname, + in ACString aLongHostname); + + /** + * Return an onion alias. + * + * @param aShortHostname + * The .tor.onion hostname to resolve + * @return a v3 address, or the input, if the short hostname is not known + */ + ACString getOnionAlias(in ACString aShortHostname); + + /** + * Clears Onion aliases. + */ + void clearOnionAliases(); +}; diff --git a/netwerk/dns/OnionAliasService.cpp b/netwerk/dns/OnionAliasService.cpp new file mode 100644 index 0000000000000..2d24bb68023b4 --- /dev/null +++ b/netwerk/dns/OnionAliasService.cpp @@ -0,0 +1,94 @@ +#include "torproject/OnionAliasService.h" + +#include "nsUnicharUtils.h" + +/** + * Check if a hostname is a valid Onion v3 hostname. + * + * @param aHostname + * The hostname to verify. It is not a const reference because any + * uppercase character will be transformed to lowercase during the + * verification. + * @return Tells whether the input string is an Onion v3 address + */ +static bool ValidateOnionV3(nsACString &aHostname) +{ + constexpr nsACString::size_type v3Length = 56 + 6; + if (aHostname.Length() != v3Length) { + return false; + } + ToLowerCase(aHostname); + if (!StringEndsWith(aHostname, ".onion"_ns)) { + return false; + } + + char* cur = aHostname.BeginWriting(); + // We have already checked that it ends by ".onion" + const char* end = aHostname.EndWriting() - 6; + for (; cur < end; ++cur) { + if (!(islower(*cur) || ('2' <= *cur && *cur <= '7'))) { + return false; + } + } + + return true; +} + +namespace torproject { + +NS_IMPL_ISUPPORTS(OnionAliasService, IOnionAliasService) + +static mozilla::StaticRefPtr<OnionAliasService> gOAService; + +// static +already_AddRefed<IOnionAliasService> OnionAliasService::GetSingleton() { + if (gOAService) { + return do_AddRef(gOAService); + } + + gOAService = new OnionAliasService(); + ClearOnShutdown(&gOAService); + return do_AddRef(gOAService); +} + +NS_IMETHODIMP +OnionAliasService::AddOnionAlias(const nsACString& aShortHostname, + const nsACString& aLongHostname) { + nsAutoCString shortHostname; + ToLowerCase(aShortHostname, shortHostname); + mozilla::UniquePtr<nsAutoCString> longHostname = + mozilla::MakeUnique<nsAutoCString>(aLongHostname); + if (!longHostname) { + return NS_ERROR_OUT_OF_MEMORY; + } + if (!StringEndsWith(shortHostname, ".tor.onion"_ns) || + !ValidateOnionV3(*longHostname)) { + return NS_ERROR_INVALID_ARG; + } + mozilla::MutexAutoLock lock(mLock); + mOnionAliases.InsertOrUpdate(shortHostname, std::move(longHostname)); + return NS_OK; +} + +NS_IMETHODIMP +OnionAliasService::GetOnionAlias(const nsACString& aShortHostname, nsACString& aLongHostname) +{ + aLongHostname = aShortHostname; + if (StringEndsWith(aShortHostname, ".tor.onion"_ns)) { + nsAutoCString* alias = nullptr; + if (mOnionAliases.Get(aShortHostname, &alias)) { + // We take for granted aliases have already been validated + aLongHostname.Assign(*alias); + } + } + return NS_OK; +} + +NS_IMETHODIMP +OnionAliasService::ClearOnionAliases() { + mozilla::MutexAutoLock lock(mLock); + mOnionAliases.Clear(); + return NS_OK; +} + +} // namespace torproject diff --git a/netwerk/dns/OnionAliasService.h b/netwerk/dns/OnionAliasService.h new file mode 100644 index 0000000000000..913157e61ccb6 --- /dev/null +++ b/netwerk/dns/OnionAliasService.h @@ -0,0 +1,36 @@ +#ifndef OnionAliasService_h_ +#define OnionAliasService_h_ + +#include "ScopedNSSTypes.h" +#include "IOnionAliasService.h" + +namespace torproject { + +class OnionAliasService final : public IOnionAliasService { +public: + NS_DECL_ISUPPORTS + NS_DECL_IONIONALIASSERVICE + + static already_AddRefed<IOnionAliasService> GetSingleton(); + +private: + + OnionAliasService() = default; + OnionAliasService(const OnionAliasService&) = delete; + OnionAliasService(OnionAliasService&&) = delete; + OnionAliasService &operator=(const OnionAliasService&) = delete; + OnionAliasService &operator=(OnionAliasService&&) = delete; + virtual ~OnionAliasService() = default; + + // mLock protects access to mOnionAliases + mozilla::Mutex mLock{"OnionAliasService.mLock"}; + + // AutoCStrings have a 64 byte buffer, so it is advised not to use them for + // long storage. However, it is enough to contain onion addresses, so we use + // them instead, and avoid allocating on heap for each alias + nsClassHashtable<nsCStringHashKey, nsAutoCString> mOnionAliases; +}; + +} + +#endif // OnionAliasService_h_ diff --git a/netwerk/dns/moz.build b/netwerk/dns/moz.build index 1498dd2ceb9e0..560916367e825 100644 --- a/netwerk/dns/moz.build +++ b/netwerk/dns/moz.build @@ -110,3 +110,7 @@ USE_LIBS += ["icu"]
if CONFIG["CC_TYPE"] in ("clang", "gcc"): CXXFLAGS += ["-Wno-error=shadow"] + +XPIDL_SOURCES += ["IOnionAliasService.idl"] +UNIFIED_SOURCES += ["OnionAliasService.cpp"] +EXPORTS.torproject += ["OnionAliasService.h"] diff --git a/netwerk/socket/nsSOCKSIOLayer.cpp b/netwerk/socket/nsSOCKSIOLayer.cpp index f9fc29552aceb..a9dc8f9dde119 100644 --- a/netwerk/socket/nsSOCKSIOLayer.cpp +++ b/netwerk/socket/nsSOCKSIOLayer.cpp @@ -25,6 +25,8 @@ #include "mozilla/net/DNS.h" #include "mozilla/Unused.h"
+#include "IOnionAliasService.h" + using mozilla::LogLevel; using namespace mozilla::net;
@@ -861,11 +863,23 @@ PRStatus nsSOCKSSocketInfo::WriteV5ConnectRequest() { // Add the address to the SOCKS 5 request. SOCKS 5 supports several // address types, so we pick the one that works best for us. if (proxy_resolve) { - // Add the host name. Only a single byte is used to store the length, - // so we must prevent long names from being used. - buf2 = buf.WriteUint8(0x03) // addr type -- domainname - .WriteUint8(mDestinationHost.Length()) // name length - .WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname + if (StringEndsWith(mDestinationHost, ".tor.onion"_ns)) { + nsAutoCString realHost; + nsCOMPtr<IOnionAliasService> oas = do_GetService(ONIONALIAS_CID); + if (NS_FAILED(oas->GetOnionAlias(mDestinationHost, realHost))) { + HandshakeFinished(PR_BAD_ADDRESS_ERROR); + return PR_FAILURE; + } + buf2 = buf.WriteUint8(0x03) + .WriteUint8(realHost.Length()) + .WriteString<MAX_HOSTNAME_LEN>(realHost); + } else { + // Add the host name. Only a single byte is used to store the length, + // so we must prevent long names from being used. + buf2 = buf.WriteUint8(0x03) // addr type -- domainname + .WriteUint8(mDestinationHost.Length()) // name length + .WriteString<MAX_HOSTNAME_LEN>(mDestinationHost); // Hostname + } if (!buf2) { LOGERROR(("socks5: destination host name is too long!")); HandshakeFinished(PR_BAD_ADDRESS_ERROR); diff --git a/security/manager/ssl/SSLServerCertVerification.cpp b/security/manager/ssl/SSLServerCertVerification.cpp index c20c1c0583784..0a84aecc6c724 100644 --- a/security/manager/ssl/SSLServerCertVerification.cpp +++ b/security/manager/ssl/SSLServerCertVerification.cpp @@ -137,6 +137,8 @@ #include "sslerr.h" #include "sslexp.h"
+#include "IOnionAliasService.h" + extern mozilla::LazyLogModule gPIPNSSLog;
using namespace mozilla::pkix; @@ -1037,6 +1039,13 @@ SECStatus SSLServerCertVerificationJob::Dispatch( return SECWouldBlock; }
+void SSLServerCertVerificationJob::FixOnionAlias() { + if (StringEndsWith(mHostName, ".tor.onion"_ns)) { + nsCOMPtr<IOnionAliasService> oas = do_GetService(ONIONALIAS_CID); + oas->GetOnionAlias(mHostName, mHostName); + } +} + NS_IMETHODIMP SSLServerCertVerificationJob::Run() { // Runs on a cert verification thread and only on parent process. diff --git a/security/manager/ssl/SSLServerCertVerification.h b/security/manager/ssl/SSLServerCertVerification.h index a315b30835d48..9ae7ac47b0566 100644 --- a/security/manager/ssl/SSLServerCertVerification.h +++ b/security/manager/ssl/SSLServerCertVerification.h @@ -141,7 +141,9 @@ class SSLServerCertVerificationJob : public Runnable { mStapledOCSPResponse(std::move(stapledOCSPResponse)), mSCTsFromTLSExtension(std::move(sctsFromTLSExtension)), mDCInfo(std::move(dcInfo)), - mResultTask(aResultTask) {} + mResultTask(aResultTask) { FixOnionAlias(); } + + void FixOnionAlias();
uint64_t mAddrForLogging; void* mPinArg; diff --git a/toolkit/modules/RemotePageAccessManager.jsm b/toolkit/modules/RemotePageAccessManager.jsm index c195ab688b236..f9cbf0badf69a 100644 --- a/toolkit/modules/RemotePageAccessManager.jsm +++ b/toolkit/modules/RemotePageAccessManager.jsm @@ -211,6 +211,20 @@ let RemotePageAccessManager = { ], RPMRecordTelemetryEvent: ["*"], }, + "about:rulesets": { + RPMAddMessageListener: ["rulesets:channels-change"], + RPMSendAsyncMessage: [ + "rulesets:delete-channel", + "rulesets:enable-channel", + "rulesets:set-show-warning", + ], + RPMSendQuery: [ + "rulesets:get-channels", + "rulesets:get-init-args", + "rulesets:set-channel", + "rulesets:update-channel", + ], + }, "about:tabcrashed": { RPMSendAsyncMessage: ["Load", "closeTab", "restoreTab", "restoreAll"], RPMAddMessageListener: ["*"],
This is an automated email from the git hooks/post-receive script.
pierov pushed a commit to branch tor-browser-91.9.0esr-11.5-1 in repository tor-browser.
commit f4a2fa31273b7cc17855f932c64fd9e654d5c0bd Author: Pier Angelo Vendrame pierov@torproject.org AuthorDate: Mon Jun 6 15:24:26 2022 +0200
fixup! Add TorStrings module for localization
Strings for about:rulesets --- browser/modules/TorStrings.jsm | 76 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+)
diff --git a/browser/modules/TorStrings.jsm b/browser/modules/TorStrings.jsm index 5baa775e85d43..1586a3f787e39 100644 --- a/browser/modules/TorStrings.jsm +++ b/browser/modules/TorStrings.jsm @@ -799,6 +799,82 @@ var TorStrings = { return retval; })() /* OnionLocation */,
+ /* + Rulesets + */ + rulesets: (() => { + const tsb = new TorPropertyStringBundle( + ["chrome://torbutton/locale/torbutton.properties"], + "rulesets." + ); + const getString/*(key, fallback)*/ = tsb.getString; + + const retval = { + // Initial warning + warningTitle: getString("warningTitle", "Proceed with Caution"), + warningDescription: getString( + "warningDescription", + "Adding or modifying rulesets can cause attackers to hijack your browser. Proceed only if you know what you are doing." + ), + warningEnable: getString( + "warningEnable", + "Warn me when I attempt to access these preferences", + ), + warningButton: getString("warningButton", "Accept the Risk and Continue"), + // Ruleset list + rulesets: getString("rulesets", "Rulesets"), + noRulesets: getString("noRulesets", "No rulesets found"), + noRulesetsDescr: getString( + "noRulesetsDescr", + "When you save a ruleset in Tor Browser, it will show up here." + ), + lastUpdated: getString("lastUpdated", "Last updated %S"), + neverUpdated: getString( + "neverUpdated", + "Never updated, or last update failed" + ), + enabled: getString("enabled", "Enabled"), + disabled: getString("disabled", "Disabled"), + // Ruleset details + edit: getString("edit", "Edit"), + name: getString("name", "Name"), + jwk: getString("jwk", "JWK"), + pathPrefix: getString("pathPrefix", "Path Prefix"), + scope: getString("scope", "Scope"), + enable: getString("enable", "Enable this ruleset"), + checkUpdates: getString("checkUpdates", "Check for Updates"), + // Add ruleset + jwkPlaceholder: getString( + "jwkPlaceholder", + "The key used to sign this ruleset in the JWK (JSON Web Key) format" + ), + jwkInvalid: getString( + "jwkInvalid", + "The JWK could not be parsed, or it is not a valid key" + ), + pathPrefixPlaceholder: getString( + "pathPrefixPlaceholder", + "URL prefix that contains the files needed by the ruleset" + ), + pathPrefixInvalid: getString( + "pathPrefixInvalid", + "The path prefix is not a valid HTTP(S) URL" + ), + scopePlaceholder: getString( + "scopePlaceholder", + "Regular expression for the scope of the rules" + ), + scopeInvalid: getString( + "scopeInvalid", + "The scope could not be parsed as a regular expression" + ), + save: getString("save", "Save"), + cancel: getString("cancel", "Cancel"), + }; + + return retval; + })() /* Rulesets */, + /* Tor Deamon Configuration Key Strings */
tor-commits@lists.torproject.org