CertUtils.jsm patch for
Bug 544442 - Add support for signed AUS update snippets. r=dveditz, r=dtownsend, a=blocking2.0-Beta3
--- a/toolkit/mozapps/shared/CertUtils.jsm
+++ b/toolkit/mozapps/shared/CertUtils.jsm
@@ -18,16 +18,17 @@
* The Initial Developer of the Original Code is Ben Goodger.
* Portions created by the Initial Developer are Copyright (C) 2004
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Darin Fisher <darin@meer.net>
* Daniel Veditz <dveditz@mozilla.com>
* Jesper Kristensen <mail@jesperkristensen.dk>
+ * Robert Strong <robert.bugzilla@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
@@ -35,43 +36,101 @@
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#endif
EXPORTED_SYMBOLS = [ "BadCertHandler", "checkCert" ];
+const Ce = Components.Exception;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
/**
- * Only allow built-in certs for HTTPS connections. See bug 340198.
+ * Checks if the connection must be HTTPS and if so, only allows built-in
+ * certificates and validates application specified certificate attribute
+ * values.
+ * See bug 340198 and bug 544442.
+ *
+ * @param aChannel
+ * The nsIChannel that will have its certificate checked.
+ * @param aCerts
+ * An array of JS objects with names / values corresponding to the
+ * channel's expected certificate's attribute names / values. This can
+ * be null or an empty array. If it isn't null the the scheme for the
+ * channel's originalURI must be https.
+ * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme
+ * is not https.
+ * NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
+ * cert param does not exist or the value for a certificate attribute
+ * from the aCerts param is different than the expected value.
+ * NS_ERROR_ABORT if the certificate issuer is not built-in.
*/
-function checkCert(channel) {
- if (!channel.originalURI.schemeIs("https")) // bypass
+function checkCert(aChannel, aCerts) {
+ if (!aChannel.originalURI.schemeIs("https")) {
+ // Require https if there are certificate values to verify
+ if (aCerts) {
+ throw new Ce("SSL is required and URI scheme is not https.",
+ Cr.NS_ERROR_UNEXPECTED);
+ }
return;
+ }
- const Ci = Components.interfaces;
var cert =
- channel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider).
+ aChannel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider).
SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
- var issuer = cert.issuer;
- while (issuer && !cert.equals(issuer)) {
- cert = issuer;
- issuer = cert.issuer;
+ if (aCerts) {
+ for (var i = 0; i < aCerts.length; ++i) {
+ var error = false;
+ var certAttrs = aCerts[i];
+ for (var name in certAttrs) {
+ if (!(name in cert)) {
+ error = true;
+ Cu.reportError("Expected attribute '" + name + "' not present in " +
+ "certificate.");
+ break;
+ }
+ if (cert[name] != certAttrs[name]) {
+ error = true;
+ Cu.reportError("Expected certificate attribute '" + name + "' " +
+ "value incorrect, expected: '" + certAttrs[name] +
+ "', got: '" + cert[name] + "'.");
+ break;
+ }
+ }
+
+ if (!error)
+ break;
+ }
+
+ if (error) {
+ const certCheckErr = "Certificate checks failed. See previous errors " +
+ "for details.";
+ Cu.reportError(certCheckErr);
+ throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
}
- var errorstring = "cert issuer is not built-in";
- if (!issuer)
- throw errorstring;
+
+ var issuerCert = cert;
+ while (issuerCert.issuer && !issuerCert.issuer.equals(issuerCert))
+ issuerCert = issuerCert.issuer;
- issuer = issuer.QueryInterface(Ci.nsIX509Cert3);
- var tokenNames = issuer.getAllTokenNames({});
+ const certNotBuiltInErr = "Certificate issuer is not built-in.";
+ if (!issuerCert)
+ throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
- if (!tokenNames.some(isBuiltinToken))
- throw errorstring;
+ issuerCert = issuerCert.QueryInterface(Ci.nsIX509Cert3);
+ var tokenNames = issuerCert.getAllTokenNames({});
+
+ if (!tokenNames || !tokenNames.some(isBuiltinToken))
+ throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
}
function isBuiltinToken(tokenName) {
return tokenName == "Builtin Object Token";
}
/**
* This class implements nsIBadCertListener. Its job is to prevent "bad cert"
@@ -86,17 +145,17 @@ BadCertHandler.prototype = {
// nsIChannelEventSink
onChannelRedirect: function(oldChannel, newChannel, flags) {
if (this.allowNonBuiltInCerts)
return;
// make sure the certificate of the old channel checks out before we follow
// a redirect from it. See bug 340198.
// Don't call checkCert for internal redirects. See bug 569648.
- if (!(flags & Components.interfaces.nsIChannelEventSink.REDIRECT_INTERNAL))
+ if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL))
checkCert(oldChannel);
},
// Suppress any certificate errors
notifyCertProblem: function(socketInfo, status, targetSite) {
return true;
},
@@ -107,17 +166,17 @@ BadCertHandler.prototype = {
// nsIInterfaceRequestor
getInterface: function(iid) {
return this.QueryInterface(iid);
},
// nsISupports
QueryInterface: function(iid) {
- if (!iid.equals(Components.interfaces.nsIChannelEventSink) &&
- !iid.equals(Components.interfaces.nsIBadCertListener2) &&
- !iid.equals(Components.interfaces.nsISSLErrorListener) &&
- !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
- !iid.equals(Components.interfaces.nsISupports))
- throw Components.results.NS_ERROR_NO_INTERFACE;
+ if (!iid.equals(Ci.nsIChannelEventSink) &&
+ !iid.equals(Ci.nsIBadCertListener2) &&
+ !iid.equals(Ci.nsISSLErrorListener) &&
+ !iid.equals(Ci.nsIInterfaceRequestor) &&
+ !iid.equals(Ci.nsISupports))
+ throw Cr.NS_ERROR_NO_INTERFACE;
return this;
}
};
--- a/toolkit/mozapps/shared/Makefile.in
+++ b/toolkit/mozapps/shared/Makefile.in
@@ -44,9 +44,13 @@ include $(DEPTH)/config/autoconf.mk
MODULE = toolkitShared
EXTRA_PP_JS_MODULES = \
CertUtils.jsm \
FileUtils.jsm \
$(NULL)
+ifdef ENABLE_TESTS
+DIRS += test/chrome
+endif
+
include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/shared/test/chrome/Makefile.in
@@ -0,0 +1,53 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# the Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Robert Strong <robert.bugzilla@gmail.com> (Original Author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH = ../../../../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+relativesrcdir = toolkit/mozapps/shared/test/chrome
+
+include $(DEPTH)/config/autoconf.mk
+
+_CHROME_FILES = \
+ test_bug544442_checkCert.xul \
+ $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+
+libs:: $(_CHROME_FILES)
+ $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/shared/test/chrome/test_bug544442_checkCert.xul
@@ -0,0 +1,142 @@
+<?xml version="1.0"?>
+<!--
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+-->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Test CertUtils.jsm checkCert - bug 340198 and bug 544442"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="testStart();">
+<script type="application/javascript"
+ src="chrome://mochikit/content/MochiKit/packed.js"/>
+<script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+SimpleTest.waitForExplicitFinish();
+
+Components.utils.import("resource://gre/modules/CertUtils.jsm");
+
+function testStart() {
+ ok(true, "Entering testStart");
+
+ var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Ci.nsIXMLHttpRequest);
+ request.open("GET", "https://example.com/", true);
+ request.channel.notificationCallbacks = new BadCertHandler(true);
+ request.onerror = function(event) { testXHRError(event); };
+ request.onload = function(event) { testXHRLoad(event); };
+ request.send(null);
+}
+
+function testXHRError(aEvent) {
+ ok(true, "Entering testXHRError - something went wrong");
+
+ var request = aEvent.target;
+ var status = 0;
+ try {
+ status = request.status;
+ }
+ catch (e) {
+ }
+
+ if (status == 0)
+ status = request.channel.QueryInterface(Ci.nsIRequest).status;
+
+ ok(false, "XHR onerror called: " + status);
+
+ SimpleTest.finish();
+}
+
+function getCheckCertResult(aChannel, aCerts) {
+ try {
+ checkCert(aChannel, aCerts);
+ }
+ catch (e) {
+ return e.result;
+ }
+ return Cr.NS_OK;
+}
+
+function testXHRLoad(aEvent) {
+ ok(true, "Entering testXHRLoad");
+
+ var channel = aEvent.target.channel;
+
+ var certs = null;
+ is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ABORT,
+ "checkCert should throw NS_ERROR_ABORT when the certificate attributes " +
+ "array passed to checkCert is null and the certificate is not builtin");
+
+ certs = [ { invalidAttribute: "Invalid attribute" } ];
+ is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ILLEGAL_VALUE,
+ "checkCert should throw NS_ERROR_ILLEGAL_VALUE when the certificate " +
+ "attributes array passed to checkCert has an element that has an " +
+ "attribute that does not exist on the certificate");
+
+ certs = [ { issuerName: "Incorrect issuerName" } ];
+ is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ILLEGAL_VALUE,
+ "checkCert should throw NS_ERROR_ILLEGAL_VALUE when the certificate " +
+ "attributes array passed to checkCert has an element that has an " +
+ "issuerName that is not the same as the certificate's");
+
+ var cert = channel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider).
+ SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
+
+ certs = [ { issuerName: cert.issuerName,
+ commonName: cert.commonName } ];
+ is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ABORT,
+ "checkCert should throw NS_ERROR_ABORT when the certificate attributes " +
+ "array passed to checkCert has a single element that has the same " +
+ "issuerName and commonName as the certificate's and the certificate is " +
+ "not builtin");
+
+ certs = [ { issuerName: "Incorrect issuerName",
+ invalidAttribute: "Invalid attribute" },
+ { issuerName: cert.issuerName,
+ commonName: "Invalid Common Name" },
+ { issuerName: cert.issuerName,
+ commonName: cert.commonName } ];
+ is(getCheckCertResult(channel, certs), Cr.NS_ERROR_ABORT,
+ "checkCert should throw NS_ERROR_ABORT when the certificate attributes " +
+ "array passed to checkCert has an element that has the same issuerName " +
+ "and commonName as the certificate's and the certificate is not builtin");
+
+ var mockChannel = { originalURI: Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).
+ newURI("http://example.com/", null, null) };
+
+ certs = [ ];
+ is(getCheckCertResult(mockChannel, certs), Cr.NS_ERROR_UNEXPECTED,
+ "checkCert should throw NS_ERROR_UNEXPECTED when the certificate " +
+ "attributes array passed to checkCert is not null and the channel's " +
+ "originalURI is not https");
+
+ certs = null;
+ is(getCheckCertResult(mockChannel, certs), Cr.NS_OK,
+ "checkCert should not throw when the certificate attributes object " +
+ "passed to checkCert is null and the the channel's originalURI is not " +
+ "https");
+
+ SimpleTest.finish();
+}
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+ <p id="display"></p>
+ <div id="content" style="display: none"></div>
+ <pre id="test"></pre>
+</body>
+</window>