Skip to content

Commit

Permalink
Bug 28005: Implement .onion alias urlbar rewrites
Browse files Browse the repository at this point in the history
  • Loading branch information
acatarineu committed Feb 14, 2020
1 parent 3b2165b commit 963fa8b
Show file tree
Hide file tree
Showing 28 changed files with 415 additions and 3 deletions.
16 changes: 16 additions & 0 deletions browser/actors/ClickHandlerChild.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ class ClickHandlerChild extends ActorChild {
json.originPrincipal = ownerDoc.nodePrincipal;
json.triggeringPrincipal = ownerDoc.nodePrincipal;

if (this.mm.docShell.allowOnionUrlbarRewrites) {
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.allowOnionUrlbarRewrites = 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
Expand Down
4 changes: 4 additions & 0 deletions browser/actors/ContextMenuChild.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,9 @@ class ContextMenuChild extends ActorChild {
// The same-origin check will be done in nsContextMenu.openLinkInTab.
let parentAllowsMixedContent = !!this.docShell.mixedContentChannel;

let parentAllowsOnionUrlbarRewrites = this.docShell
.allowOnionUrlbarRewrites;

// Get referrer attribute from clicked link and parse it
let referrerAttrValue = Services.netUtils.parseAttributePolicyString(
aEvent.composedTarget.getAttribute("referrerpolicy")
Expand Down Expand Up @@ -659,6 +662,7 @@ class ContextMenuChild extends ActorChild {
popupNodeSelectors,
disableSetDesktopBg,
parentAllowsMixedContent,
parentAllowsOnionUrlbarRewrites,
};

Services.obs.notifyObservers(
Expand Down
20 changes: 20 additions & 0 deletions browser/base/content/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.jsm",
Translation: "resource:///modules/translation/Translation.jsm",
OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
UITour: "resource:///modules/UITour.jsm",
UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
UrlbarInput: "resource:///modules/UrlbarInput.jsm",
Expand Down Expand Up @@ -3335,6 +3336,12 @@ function URLBarSetURI(aURI, updatePopupNotifications) {
) {
value = "";
} else {
if (
gBrowser.selectedBrowser.allowOnionUrlbarRewrites &&
(uri.schemeIs("http") || uri.schemeIs("https"))
) {
uri = OnionAliasStore.getShortURI(uri) || uri;
}
// We should deal with losslessDecodeURI throwing for exotic URIs
try {
value = losslessDecodeURI(uri);
Expand Down Expand Up @@ -7613,6 +7620,18 @@ function handleLinkClick(event, href, linkNode) {
} catch (e) {}
}

let persistAllowOnionUrlbarRewritesInChildTab = false;
if (where == "tab" && gBrowser.docShell.allowOnionUrlbarRewrites) {
const sm = Services.scriptSecurityManager;
try {
let tURI = makeURI(href);
let isPrivateWin =
doc.nodePrincipal.originAttributes.privateBrowsingId > 0;
sm.checkSameOriginURI(referrerURI, tURI, false, isPrivateWin);
persistAllowOnionUrlbarRewritesInChildTab = true;
} catch (e) {}
}

// first get document wide referrer policy, then
// get referrer attribute from clicked link and parse it and
// allow per element referrer to overrule the document wide referrer if enabled
Expand Down Expand Up @@ -7645,6 +7664,7 @@ function handleLinkClick(event, href, linkNode) {
triggeringPrincipal: doc.nodePrincipal,
csp,
frameOuterWindowID,
allowOnionUrlbarRewrites: persistAllowOnionUrlbarRewritesInChildTab,
};

// The new tab/window must use the same userContextId
Expand Down
15 changes: 15 additions & 0 deletions browser/base/content/nsContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ function openContextMenu(aMessage) {
disableSetDesktopBackground: data.disableSetDesktopBg,
loginFillInfo: data.loginFillInfo,
parentAllowsMixedContent: data.parentAllowsMixedContent,
parentAllowsOnionUrlbarRewrites: data.parentAllowsOnionUrlbarRewrites,
userContextId: data.userContextId,
webExtContextData: data.webExtContextData,
};
Expand Down Expand Up @@ -1122,9 +1123,23 @@ nsContextMenu.prototype = {
} catch (e) {}
}

let persistAllowOnionUrlbarRewrites = false;

if (gContextMenuContentData.parentAllowsOnionUrlbarRewrites) {
const sm = Services.scriptSecurityManager;
try {
let targetURI = this.linkURI;
let isPrivateWin =
this.browser.contentPrincipal.originAttributes.privateBrowsingId > 0;
sm.checkSameOriginURI(referrerURI, targetURI, false, isPrivateWin);
persistAllowOnionUrlbarRewrites = true;
} catch (e) {}
}

let params = {
allowMixedContent: persistAllowMixedContentInChildTab,
userContextId: parseInt(event.target.getAttribute("data-usercontextid")),
allowOnionUrlbarRewrites: persistAllowOnionUrlbarRewrites,
};

openLinkIn(this.linkURL, "tab", this._openLinkInParameters(params));
Expand Down
7 changes: 7 additions & 0 deletions browser/base/content/tabbrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,7 @@
var aRelatedToCurrent;
var aAllowInheritPrincipal;
var aAllowMixedContent;
var aAllowOnionUrlbarRewrites;
var aSkipAnimation;
var aForceNotRemote;
var aPreferredRemoteType;
Expand Down Expand Up @@ -1618,6 +1619,7 @@
aRelatedToCurrent = params.relatedToCurrent;
aAllowInheritPrincipal = !!params.allowInheritPrincipal;
aAllowMixedContent = params.allowMixedContent;
aAllowOnionUrlbarRewrites = params.allowOnionUrlbarRewrites;
aSkipAnimation = params.skipAnimation;
aForceNotRemote = params.forceNotRemote;
aPreferredRemoteType = params.preferredRemoteType;
Expand Down Expand Up @@ -1658,6 +1660,7 @@
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipAnimation,
allowMixedContent: aAllowMixedContent,
allowOnionUrlbarRewrites: aAllowOnionUrlbarRewrites,
forceNotRemote: aForceNotRemote,
createLazyBrowser: aCreateLazyBrowser,
preferredRemoteType: aPreferredRemoteType,
Expand Down Expand Up @@ -2530,6 +2533,7 @@
{
allowInheritPrincipal,
allowMixedContent,
allowOnionUrlbarRewrites,
allowThirdPartyFixup,
bulkOrderedOpen,
charset,
Expand Down Expand Up @@ -2902,6 +2906,9 @@
if (allowMixedContent) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
}
if (allowOnionUrlbarRewrites) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_ONION_URLBAR_REWRITES;
}
if (!allowInheritPrincipal) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
Expand Down
2 changes: 2 additions & 0 deletions browser/base/content/utilityOverlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ function openLinkIn(url, where, params) {
var aRelatedToCurrent = params.relatedToCurrent;
var aAllowInheritPrincipal = !!params.allowInheritPrincipal;
var aAllowMixedContent = params.allowMixedContent;
var aAllowOnionUrlbarRewrites = params.allowOnionUrlbarRewrites;
var aForceAllowDataURI = params.forceAllowDataURI;
var aInBackground = params.inBackground;
var aInitiatingDoc = params.initiatingDoc;
Expand Down Expand Up @@ -680,6 +681,7 @@ function openLinkIn(url, where, params) {
relatedToCurrent: aRelatedToCurrent,
skipAnimation: aSkipTabAnimation,
allowMixedContent: aAllowMixedContent,
allowOnionUrlbarRewrites: aAllowOnionUrlbarRewrites,
userContextId: aUserContextId,
originPrincipal: aPrincipal,
triggeringPrincipal: aTriggeringPrincipal,
Expand Down
6 changes: 6 additions & 0 deletions browser/components/BrowserGlue.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
ShellService: "resource:///modules/ShellService.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
TabUnloader: "resource:///modules/TabUnloader.jsm",
OnionAliasStore: "resource:///modules/OnionAliasStore.jsm",
UIState: "resource://services-sync/UIState.jsm",
UITour: "resource:///modules/UITour.jsm",
WebChannel: "resource://gre/modules/WebChannel.jsm",
Expand Down Expand Up @@ -1839,6 +1840,7 @@ BrowserGlue.prototype = {

Normandy.uninit();
RFPHelper.uninit();
OnionAliasStore.uninit();
},

// Set up a listener to enable/disable the screenshots extension
Expand Down Expand Up @@ -2109,6 +2111,10 @@ BrowserGlue.prototype = {
RFPHelper.init();
});

Services.tm.idleDispatchToMainThread(() => {
OnionAliasStore.init();
});

ChromeUtils.idleDispatch(() => {
Blocklist.loadBlocklistAsync();
});
Expand Down
1 change: 1 addition & 0 deletions browser/components/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ DIRS += [
'library',
'migration',
'newtab',
'onionalias',
'originattributes',
'places',
'pocket',
Expand Down
84 changes: 84 additions & 0 deletions browser/components/onionalias/ExtensionMessaging.jsm
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use strict";

const EXPORTED_SYMBOLS = ["ExtensionMessaging"];

const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { ExtensionUtils } = ChromeUtils.import(
"resource://gre/modules/ExtensionUtils.jsm"
);
const { MessageChannel } = ChromeUtils.import(
"resource://gre/modules/MessageChannel.jsm"
);
const { AddonManager } = ChromeUtils.import(
"resource://gre/modules/AddonManager.jsm"
);

class ExtensionMessaging {
constructor() {
this._callback = null;
this._handlers = new Map();
this._messageManager = Services.cpmm;
}

async sendMessage(msg, extensionId) {
this._init();

const addon = await AddonManager.getAddonByID(extensionId);
if (!addon) {
throw new Error(`extension '${extensionId} does not exist`);
}
await addon.startupPromise;

const channelId = ExtensionUtils.getUniqueId();
return new Promise((resolve, reject) => {
this._handlers.set(channelId, { resolve, reject });
this._messageManager.sendAsyncMessage("MessageChannel:Messages", [
{
messageName: "Extension:Message",
sender: {
id: extensionId,
extensionId,
},
recipient: { extensionId },
data: new StructuredCloneHolder(msg),
channelId,
responseType: MessageChannel.RESPONSE_FIRST,
},
]);
});
}

unload() {
if (this._callback) {
this._handlers.clear();
this._messageManager.removeMessageListener(
"MessageChannel:Response",
this._callback
);
this._callback = null;
}
}

_onMessage({ data }) {
const channelId = data.messageName;
if (this._handlers.has(channelId)) {
const { resolve, reject } = this._handlers.get(channelId);
this._handlers.delete(channelId);
if (data.error) {
reject(new Error(data.error.message));
} else {
resolve(data.value);
}
}
}

_init() {
if (this._callback === null) {
this._callback = this._onMessage.bind(this);
this._messageManager.addMessageListener(
"MessageChannel:Response",
this._callback
);
}
}
}
79 changes: 79 additions & 0 deletions browser/components/onionalias/HttpsEverywhereControl.jsm
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use strict";

const EXPORTED_SYMBOLS = ["HttpsEverywhereControl"];

const { ExtensionMessaging } = ChromeUtils.import(
"resource:///modules/ExtensionMessaging.jsm"
);
const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");

const EXTENSION_ID = "[email protected]";
const SECUREDROP_TOR_ONION_CHANNEL = {
name: "SecureDropTorOnion",
jwk: {
kty: "RSA",
e: "AQAB",
n:
"2IGvrYj_UwpO8EMNeEiitb8imzuJA-BGngLYtMkY72wx6aPhuWWvGs-Dlh4lHB4fi8rxfeb0N6ZwCyfCKBeHgfuTgbH14HB-ZSA47JHKEO4fSiay1jjhsGDzQlZDVdn_Wfyxi5je80pizMsOulHKEKzRx4HIcrXY1nf8iiOHEWaB0GX8H1pJjyIlqTxN9Pm5Q4h7kRLUoq3O9EE3o7q1dVeaxYM221WPqaSq9mawAhih4wo_79mb6JinG--s9F3jfqmnOvQk-xAoSTA-XNqTvO-7Mg_WajoA7Lnx1pJbGuvhqNOdodWOFxdMMNaPL8JXmPywl6zAqMESU1rXcaVKVdx1LpcmTyz8dGi3u_GTb6fo5GqXUgByvvRcOXA6DAFC7tbLEy1QinU0q4cRZJYf6s4QxgyRsCgxrcJ9kuDwDHviAm9Yn3eEFRbD2e3hRFfZyvkkLepEWywEfBGdBjQ_Kz9gkQTzmpVef1J-sSD6dnW5OEVEXAPO0sEdr5o-Ng9NSvDGZ3Sw-4AgFO6aynLnpvbVOYneppLF7MKwGVQv0tQ8XY3zBEsxidTIkvmpzKZp6QElpfCwbYnl9aQ9hQ3BmOPIhM2VunP47MPOgyAp4s2m3knwCWbPSR5Gm8agDwIGA1Va1eFAtS-YAYk8v-J20iTyuXrpWqrQmFjEnVLsav8",
},
update_path_prefix: "https://people.torproject.org/~acat/misc/rulesets/",
scope:
"^https?:\\/\\/[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.securedrop\\.tor\\.onion\\/",
replaces_default_rulesets: false,
};

class HttpsEverywhereControl {
constructor() {
this._extensionMessaging = null;
}

static async wait(seconds = 1) {
return new Promise(resolve => setTimeout(resolve, seconds * 1000));
}

async installTorOnionUpdateChannel(retries = 5) {
this._init();

// FIXME: https-everywhere store is initialized asynchronously, so sending a message
// immediately results in a `store.get is undefined` error.
// For now, let's wait a bit and retry a few times if there is an error.
await HttpsEverywhereControl.wait();

try {
await this._extensionMessaging.sendMessage(
{
type: "create_update_channel",
object: SECUREDROP_TOR_ONION_CHANNEL.name,
},
EXTENSION_ID
);
} catch (e) {
if (retries <= 0) {
throw new Error("Could not install SecureDropTorOnion update channel");
}
await this.installTorOnionUpdateChannel(retries - 1);
return;
}

await this._extensionMessaging.sendMessage(
{
type: "update_update_channel",
object: SECUREDROP_TOR_ONION_CHANNEL,
},
EXTENSION_ID
);
}

unload() {
if (this._extensionMessaging) {
this._extensionMessaging.unload();
this._extensionMessaging = null;
}
}

_init() {
if (!this._extensionMessaging) {
this._extensionMessaging = new ExtensionMessaging();
}
}
}
Loading

0 comments on commit 963fa8b

Please sign in to comment.