mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 591070: Support specifying an XPI hash through the initial HTTPS request such that redirects to HTTP can be followed securely. r=robstrong, a=blocking-b6
This commit is contained in:
parent
4dd683b489
commit
d67000418a
@ -4011,50 +4011,6 @@ var XPIDatabase = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles callbacks for HTTP channels of XPI downloads. We support
|
||||
* prompting for auth dialogs and, optionally, to ignore bad certs.
|
||||
*
|
||||
* @param aWindow
|
||||
* An optional DOM Element related to the request
|
||||
* @param aNeedBadCertHandling
|
||||
* Whether we should handle bad certs or not
|
||||
*/
|
||||
function XPINotificationCallbacks(aWindow, aNeedBadCertHandling) {
|
||||
this.window = aWindow;
|
||||
|
||||
// Verify that we don't end up on an insecure channel if we haven't got a
|
||||
// hash to verify with (see bug 537761 for discussion)
|
||||
this.needBadCertHandling = aNeedBadCertHandling;
|
||||
|
||||
if (this.needBadCertHandling) {
|
||||
Components.utils.import("resource://gre/modules/CertUtils.jsm");
|
||||
let requireBuiltIn = Prefs.getBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true);
|
||||
this.badCertHandler = new BadCertHandler(!requireBuiltIn);
|
||||
}
|
||||
}
|
||||
|
||||
XPINotificationCallbacks.prototype = {
|
||||
QueryInterface: function(iid) {
|
||||
if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIInterfaceRequestor))
|
||||
return this;
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
getInterface: function(iid) {
|
||||
if (iid.equals(Components.interfaces.nsIAuthPrompt2)) {
|
||||
var factory = Cc["@mozilla.org/prompter;1"].
|
||||
getService(Ci.nsIPromptFactory);
|
||||
return factory.getPrompt(this.window, Ci.nsIAuthPrompt);
|
||||
}
|
||||
|
||||
if (this.needBadCertHandling)
|
||||
return this.badCertHandler.getInterface(iid);
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiates an AddonInstall and passes the new object to a callback when
|
||||
* it is complete.
|
||||
@ -4196,6 +4152,7 @@ AddonInstall.prototype = {
|
||||
crypto: null,
|
||||
hash: null,
|
||||
loadGroup: null,
|
||||
badCertHandler: null,
|
||||
listeners: null,
|
||||
|
||||
name: null,
|
||||
@ -4546,30 +4503,6 @@ AddonInstall.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
this.crypto = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
if (this.hash) {
|
||||
[alg, this.hash] = this.hash.split(":", 2);
|
||||
|
||||
try {
|
||||
this.crypto.initWithString(alg);
|
||||
}
|
||||
catch (e) {
|
||||
WARN("Unknown hash algorithm " + alg);
|
||||
this.state = AddonManager.STATE_DOWNLOAD_FAILED;
|
||||
this.error = AddonManager.ERROR_INCORRECT_HASH;
|
||||
XPIProvider.removeActiveInstall(this);
|
||||
AddonManagerPrivate.callInstallListeners("onDownloadFailed",
|
||||
this.listeners, this.wrapper);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We always need something to consume data from the inputstream passed
|
||||
// to onDataAvailable so just create a dummy cryptohasher to do that.
|
||||
this.crypto.initWithString("sha1");
|
||||
}
|
||||
|
||||
try {
|
||||
this.file = getTemporaryFile();
|
||||
this.ownsTempFile = true;
|
||||
@ -4592,9 +4525,12 @@ AddonInstall.prototype = {
|
||||
createInstance(Ci.nsIStreamListenerTee);
|
||||
listener.init(this, this.stream);
|
||||
try {
|
||||
Components.utils.import("resource://gre/modules/CertUtils.jsm");
|
||||
let requireBuiltIn = Prefs.getBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true);
|
||||
this.badCertHandler = new BadCertHandler(!requireBuiltIn);
|
||||
|
||||
this.channel = NetUtil.newChannel(this.sourceURI);
|
||||
this.channel.notificationCallbacks =
|
||||
new XPINotificationCallbacks(this.window, !this.hash);
|
||||
this.channel.notificationCallbacks = this;
|
||||
this.channel.QueryInterface(Ci.nsIHttpChannelInternal)
|
||||
.forceAllowThirdPartyCookie = true;
|
||||
this.channel.asyncOpen(listener, null);
|
||||
@ -4626,12 +4562,61 @@ AddonInstall.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check the redirect response for a hash of the target XPI and verify that
|
||||
* we don't end up on an insecure channel.
|
||||
*
|
||||
* @see nsIChannelEventSink
|
||||
*/
|
||||
asyncOnChannelRedirect: function(aOldChannel, aNewChannel, aFlags, aCallback) {
|
||||
if (!this.hash && aOldChannel.originalURI.schemeIs("https") &&
|
||||
aOldChannel instanceof Ci.nsIHttpChannel) {
|
||||
try {
|
||||
this.hash = aOldChannel.getResponseHeader("X-Target-Digest");
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that we don't end up on an insecure channel if we haven't got a
|
||||
// hash to verify with (see bug 537761 for discussion)
|
||||
if (!this.hash)
|
||||
this.badCertHandler.asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback);
|
||||
else
|
||||
aCallback.onRedirectVerifyCallback(Cr.NS_OK);
|
||||
},
|
||||
|
||||
/**
|
||||
* This is the first chance to get at real headers on the channel.
|
||||
*
|
||||
* @see nsIStreamListener
|
||||
*/
|
||||
onStartRequest: function AI_onStartRequest(aRequest, aContext) {
|
||||
this.crypto = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
if (this.hash) {
|
||||
[alg, this.hash] = this.hash.split(":", 2);
|
||||
|
||||
try {
|
||||
this.crypto.initWithString(alg);
|
||||
}
|
||||
catch (e) {
|
||||
WARN("Unknown hash algorithm " + alg);
|
||||
this.state = AddonManager.STATE_DOWNLOAD_FAILED;
|
||||
this.error = AddonManager.ERROR_INCORRECT_HASH;
|
||||
XPIProvider.removeActiveInstall(this);
|
||||
AddonManagerPrivate.callInstallListeners("onDownloadFailed",
|
||||
this.listeners, this.wrapper);
|
||||
aRequest.cancel(Cr.NS_BINDING_ABORTED);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// We always need something to consume data from the inputstream passed
|
||||
// to onDataAvailable so just create a dummy cryptohasher to do that.
|
||||
this.crypto.initWithString("sha1");
|
||||
}
|
||||
|
||||
this.progress = 0;
|
||||
if (aRequest instanceof Ci.nsIChannel) {
|
||||
try {
|
||||
@ -4652,6 +4637,7 @@ AddonInstall.prototype = {
|
||||
onStopRequest: function AI_onStopRequest(aRequest, aContext, aStatus) {
|
||||
this.stream.close();
|
||||
this.channel = null;
|
||||
this.badCerthandler = null;
|
||||
Services.obs.removeObserver(this, "network:offline-about-to-go-offline");
|
||||
|
||||
// If the download was cancelled then all events will have already been sent
|
||||
@ -4931,6 +4917,19 @@ AddonInstall.prototype = {
|
||||
finally {
|
||||
this.removeTemporaryFile();
|
||||
}
|
||||
},
|
||||
|
||||
getInterface: function(iid) {
|
||||
if (iid.equals(Ci.nsIAuthPrompt2)) {
|
||||
var factory = Cc["@mozilla.org/prompter;1"].
|
||||
getService(Ci.nsIPromptFactory);
|
||||
return factory.getPrompt(null, Ci.nsIAuthPrompt);
|
||||
}
|
||||
else if (iid.equals(Ci.nsIChannelEventSink)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.badCertHandler.getInterface(iid);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,6 +85,11 @@ _BROWSER_FILES = head.js \
|
||||
browser_cancel.js \
|
||||
browser_multipackage.js \
|
||||
browser_trigger_redirect.js \
|
||||
browser_httphash.js \
|
||||
browser_httphash2.js \
|
||||
browser_httphash3.js \
|
||||
browser_httphash4.js \
|
||||
browser_httphash5.js \
|
||||
unsigned.xpi \
|
||||
signed.xpi \
|
||||
signed2.xpi \
|
||||
@ -105,6 +110,7 @@ _BROWSER_FILES = head.js \
|
||||
triggerredirect.html \
|
||||
authRedirect.sjs \
|
||||
cookieRedirect.sjs \
|
||||
hashRedirect.sjs \
|
||||
bug540558.html \
|
||||
$(NULL)
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Test whether an install succeeds when a valid hash is included in the HTTPS
|
||||
// request
|
||||
// This verifies bug 591070
|
||||
function test() {
|
||||
Harness.installEndedCallback = install_ended;
|
||||
Harness.installsCompletedCallback = finish_test;
|
||||
Harness.setup();
|
||||
|
||||
var pm = Services.perms;
|
||||
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
|
||||
Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
|
||||
|
||||
var url = "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs";
|
||||
url += "?sha1:3d0dc22e1f394e159b08aaf5f0f97de4d5c65f4f|" + TESTROOT + "unsigned.xpi";
|
||||
|
||||
var triggers = encodeURIComponent(JSON.stringify({
|
||||
"Unsigned XPI": {
|
||||
URL: url,
|
||||
toString: function() { return this.URL; }
|
||||
}
|
||||
}));
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
|
||||
}
|
||||
|
||||
function install_ended(install, addon) {
|
||||
install.cancel();
|
||||
}
|
||||
|
||||
function finish_test(count) {
|
||||
is(count, 1, "1 Add-on should have been successfully installed");
|
||||
|
||||
Services.perms.remove("example.com", "install");
|
||||
Services.prefs.clearUserPref(PREF_INSTALL_REQUIREBUILTINCERTS);
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
Harness.finish();
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Test whether an install fails when a invalid hash is included in the HTTPS
|
||||
// request
|
||||
// This verifies bug 591070
|
||||
function test() {
|
||||
Harness.downloadFailedCallback = download_failed;
|
||||
Harness.installsCompletedCallback = finish_test;
|
||||
Harness.setup();
|
||||
|
||||
var pm = Services.perms;
|
||||
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
|
||||
Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
|
||||
|
||||
var url = "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs";
|
||||
url += "?sha1:foobar|" + TESTROOT + "unsigned.xpi";
|
||||
|
||||
var triggers = encodeURIComponent(JSON.stringify({
|
||||
"Unsigned XPI": {
|
||||
URL: url,
|
||||
toString: function() { return this.URL; }
|
||||
}
|
||||
}));
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
|
||||
}
|
||||
|
||||
function download_failed(install) {
|
||||
is(install.error, AddonManager.ERROR_INCORRECT_HASH, "Download should fail");
|
||||
}
|
||||
|
||||
function finish_test(count) {
|
||||
is(count, 0, "0 Add-ons should have been successfully installed");
|
||||
|
||||
Services.perms.remove("example.com", "install");
|
||||
Services.prefs.clearUserPref(PREF_INSTALL_REQUIREBUILTINCERTS);
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
Harness.finish();
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Tests that the HTTPS hash is ignored when InstallTrigger is passed a hash.
|
||||
// This verifies bug 591070
|
||||
function test() {
|
||||
Harness.installEndedCallback = install_ended;
|
||||
Harness.installsCompletedCallback = finish_test;
|
||||
Harness.setup();
|
||||
|
||||
var pm = Services.perms;
|
||||
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
|
||||
Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
|
||||
|
||||
var url = "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs";
|
||||
url += "?sha1:foobar|" + TESTROOT + "unsigned.xpi";
|
||||
|
||||
var triggers = encodeURIComponent(JSON.stringify({
|
||||
"Unsigned XPI": {
|
||||
URL: url,
|
||||
Hash: "sha1:3d0dc22e1f394e159b08aaf5f0f97de4d5c65f4f",
|
||||
toString: function() { return this.URL; }
|
||||
}
|
||||
}));
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
|
||||
}
|
||||
|
||||
function install_ended(install, addon) {
|
||||
install.cancel();
|
||||
}
|
||||
|
||||
function finish_test(count) {
|
||||
is(count, 1, "1 Add-on should have been successfully installed");
|
||||
|
||||
Services.perms.remove("example.com", "install");
|
||||
Services.prefs.clearUserPref(PREF_INSTALL_REQUIREBUILTINCERTS);
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
Harness.finish();
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Test that hashes are ignored in the headers of HTTP requests
|
||||
// This verifies bug 591070
|
||||
function test() {
|
||||
Harness.installEndedCallback = install_ended;
|
||||
Harness.installsCompletedCallback = finish_test;
|
||||
Harness.setup();
|
||||
|
||||
var pm = Services.perms;
|
||||
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
|
||||
|
||||
var url = "http://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs";
|
||||
url += "?sha1:foobar|" + TESTROOT + "unsigned.xpi";
|
||||
|
||||
var triggers = encodeURIComponent(JSON.stringify({
|
||||
"Unsigned XPI": {
|
||||
URL: url,
|
||||
toString: function() { return this.URL; }
|
||||
}
|
||||
}));
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
|
||||
}
|
||||
|
||||
function install_ended(install, addon) {
|
||||
install.cancel();
|
||||
}
|
||||
|
||||
function finish_test(count) {
|
||||
is(count, 1, "1 Add-on should have been successfully installed");
|
||||
|
||||
Services.perms.remove("example.com", "install");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
Harness.finish();
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// Test that only the first HTTPS hash is used
|
||||
// This verifies bug 591070
|
||||
function test() {
|
||||
Harness.installEndedCallback = install_ended;
|
||||
Harness.installsCompletedCallback = finish_test;
|
||||
Harness.setup();
|
||||
|
||||
var pm = Services.perms;
|
||||
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
|
||||
Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
|
||||
|
||||
var url = "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs";
|
||||
url += "?sha1:3d0dc22e1f394e159b08aaf5f0f97de4d5c65f4f|";
|
||||
url += "https://example.com/browser/" + RELATIVE_DIR + "hashRedirect.sjs";
|
||||
url += "?sha1:foobar|" + TESTROOT + "unsigned.xpi";
|
||||
|
||||
var triggers = encodeURIComponent(JSON.stringify({
|
||||
"Unsigned XPI": {
|
||||
URL: url,
|
||||
toString: function() { return this.URL; }
|
||||
}
|
||||
}));
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
|
||||
}
|
||||
|
||||
function install_ended(install, addon) {
|
||||
install.cancel();
|
||||
}
|
||||
|
||||
function finish_test(count) {
|
||||
is(count, 1, "1 Add-on should have been successfully installed");
|
||||
|
||||
Services.perms.remove("example.com", "install");
|
||||
Services.prefs.clearUserPref(PREF_INSTALL_REQUIREBUILTINCERTS);
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
Harness.finish();
|
||||
}
|
15
toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs
Normal file
15
toolkit/mozapps/extensions/test/xpinstall/hashRedirect.sjs
Normal file
@ -0,0 +1,15 @@
|
||||
// Simple script redirects takes the query part of te request and splits it on
|
||||
// the | character. Anything before is included as the X-Target-Digest header
|
||||
// the latter part is used as the url to redirect to
|
||||
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
let pos = request.queryString.indexOf("|");
|
||||
let header = request.queryString.substring(0, pos);
|
||||
let url = request.queryString.substring(pos + 1);
|
||||
|
||||
response.setStatusLine(request.httpVersion, 302, "Found");
|
||||
response.setHeader("X-Target-Digest", header);
|
||||
response.setHeader("Location", url);
|
||||
response.write("See " + url);
|
||||
}
|
@ -7,6 +7,7 @@ const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
|
||||
const PROMPT_URL = "chrome://global/content/commonDialog.xul";
|
||||
const ADDONS_URL = "chrome://mozapps/content/extensions/extensions.xul";
|
||||
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
|
||||
const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
|
||||
|
||||
Components.utils.import("resource://gre/modules/AddonManager.jsm");
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
Loading…
Reference in New Issue
Block a user