bug 786417 - filter the hsts preload list to sites that actually send the header r=bsmith, mayhemer
authorDavid Keeler <dkeeler@mozilla.com>
Mon, 15 Oct 2012 14:43:57 -0700
changeset 110477 1cbaabb841d962f6a4c673fb5cd8b47f8add1c7c
parent 110476 007e45a1f6c9b05b52ea3e937c1baa5dd64b88c7
child 110478 e122361ed6de416b481592069d12c1957525b96d
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersbsmith, mayhemer
bugs786417
milestone19.0a1
bug 786417 - filter the hsts preload list to sites that actually send the header r=bsmith, mayhemer
modules/libpref/src/init/all.js
netwerk/base/public/nsIStrictTransportSecurityService.idl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/test/TestSTSParser.cpp
security/manager/boot/src/nsSTSPreloadList.errors
security/manager/boot/src/nsSTSPreloadList.inc
security/manager/boot/src/nsStrictTransportSecurityService.cpp
security/manager/boot/src/nsStrictTransportSecurityService.h
security/manager/ssl/tests/unit/test_sts_preloadlist.js
security/manager/tools/getHSTSPreloadList.js
security/manager/tools/getHSTSPreloadList.py
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1244,16 +1244,19 @@ pref("network.cookie.lifetime.days",    
 // The PAC file to load.  Ignored unless network.proxy.type is 2.
 pref("network.proxy.autoconfig_url", "");
 
 // If we cannot load the PAC file, then try again (doubling from interval_min
 // until we reach interval_max or the PAC file is successfully loaded).
 pref("network.proxy.autoconfig_retry_interval_min", 5);    // 5 seconds
 pref("network.proxy.autoconfig_retry_interval_max", 300);  // 5 minutes
 
+// Use the HSTS preload list by default
+pref("network.stricttransportsecurity.preloadlist", true);
+
 pref("converter.html2txt.structs",          true); // Output structured phrases (strong, em, code, sub, sup, b, i, u)
 pref("converter.html2txt.header_strategy",  1); // 0 = no indention; 1 = indention, increased with header level; 2 = numbering and slight indention
 
 pref("intl.accept_languages",               "chrome://global/locale/intl.properties");
 pref("intl.menuitems.alwaysappendaccesskeys","chrome://global/locale/intl.properties");
 pref("intl.menuitems.insertseparatorbeforeaccesskeys","chrome://global/locale/intl.properties");
 pref("intl.charsetmenu.browser.static",     "chrome://global/locale/intl.properties");
 pref("intl.charsetmenu.browser.more1",      "ISO-8859-1, ISO-8859-15, IBM850, x-mac-roman, windows-1252, ISO-8859-14, ISO-8859-7, x-mac-greek, windows-1253, x-mac-icelandic, ISO-8859-10, ISO-8859-3");
--- a/netwerk/base/public/nsIStrictTransportSecurityService.idl
+++ b/netwerk/base/public/nsIStrictTransportSecurityService.idl
@@ -3,35 +3,39 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIObserver;
 interface nsIHttpChannel;
 
-[scriptable, uuid(16955eee-6c48-4152-9309-c42a465138a1)]
+[scriptable, uuid(aee925d1-2bc9-469e-9582-b27b1d6b5192)]
 interface nsIStrictTransportSecurityService : nsISupports
 {
     /**
      * Parses a given HTTP header and records the results internally.
      * The format of the STS header is defined by the STS specification:
      * http://tools.ietf.org/html/draft-hodges-strict-transport-sec
      * and allows a host to specify that future requests on port 80 should be
      * upgraded to HTTPS.
      *
      * @param aSourceURI the URI of the resource with the HTTP header.
      * @param aHeader the HTTP response header specifying STS data.
+     * @param aMaxAge the parsed max-age directive of the header.
+     * @param aIncludeSubdomains the parsed includeSubdomains directive.
      * @return NS_OK            if it succeeds
      *         NS_ERROR_FAILURE if it can't be parsed
      *         NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
      *                          if there are unrecognized tokens in the header.
      */
     void processStsHeader(in nsIURI aSourceURI,
-                          in string aHeader);
+                          in string aHeader,
+                          [optional] out unsigned long long aMaxAge,
+                          [optional] out boolean aIncludeSubdomains);
 
     /**
      * Removes the STS state of a host, including the includeSubdomains state
      * that would affect subdomains.  This essentially removes STS state for
      * the domain tree rooted at this host.
      */
     void removeStsState(in nsIURI aURI);
 
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1144,17 +1144,17 @@ nsHttpChannel::ProcessSTSHeader()
     rv = mResponseHead->GetHeader(atom, stsHeader);
     if (rv == NS_ERROR_NOT_AVAILABLE) {
         LOG(("STS: No STS header, continuing load.\n"));
         return NS_OK;
     }
     // All other failures are fatal.
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = stss->ProcessStsHeader(mURI, stsHeader.get());
+    rv = stss->ProcessStsHeader(mURI, stsHeader.get(), NULL, NULL);
     if (NS_FAILED(rv)) {
         LOG(("STS: Failed to parse STS header, continuing load.\n"));
         return NS_OK;
     }
 
     return NS_OK;
 }
 
--- a/netwerk/test/TestSTSParser.cpp
+++ b/netwerk/test/TestSTSParser.cpp
@@ -34,26 +34,32 @@
   if (a != b) { \
     fail(__VA_ARGS__); \
     return false; \
   } \
   PR_END_MACRO
 
 bool
 TestSuccess(const char* hdr, bool extraTokens,
+            uint64_t expectedMaxAge, bool expectedIncludeSubdomains,
             nsIStrictTransportSecurityService* stss,
             nsIPermissionManager* pm)
 {
   nsCOMPtr<nsIURI> dummyUri;
   nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://foo.com/bar.html");
   EXPECT_SUCCESS(rv, "Failed to create URI");
 
-  rv = stss->ProcessStsHeader(dummyUri, hdr);
+  uint64_t maxAge = 0;
+  bool includeSubdomains = false;
+  rv = stss->ProcessStsHeader(dummyUri, hdr, &maxAge, &includeSubdomains);
   EXPECT_SUCCESS(rv, "Failed to process valid header: %s", hdr);
 
+  REQUIRE_EQUAL(maxAge, expectedMaxAge, "Did not correctly parse maxAge");
+  REQUIRE_EQUAL(includeSubdomains, expectedIncludeSubdomains, "Did not correctly parse presence/absence of includeSubdomains");
+
   if (extraTokens) {
     REQUIRE_EQUAL(rv, NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA,
                   "Extra tokens were expected when parsing, but were not encountered.");
   } else {
     REQUIRE_EQUAL(rv, NS_OK, "Unexpected tokens found during parsing.");
   }
 
   passed(hdr);
@@ -63,17 +69,17 @@ TestSuccess(const char* hdr, bool extraT
 bool TestFailure(const char* hdr,
                    nsIStrictTransportSecurityService* stss,
                    nsIPermissionManager* pm)
 {
   nsCOMPtr<nsIURI> dummyUri;
   nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://foo.com/bar.html");
   EXPECT_SUCCESS(rv, "Failed to create URI");
 
-  rv = stss->ProcessStsHeader(dummyUri, hdr);
+  rv = stss->ProcessStsHeader(dummyUri, hdr, NULL, NULL);
   EXPECT_FAILURE(rv, "Parsed invalid header: %s", hdr);
   passed(hdr);
   return true;
 }
 
 
 int
 main(int32_t argc, char *argv[])
@@ -101,46 +107,46 @@ main(int32_t argc, char *argv[])
     int rv0, rv1;
 
     nsTArray<bool> rvs(24);
 
     // *** parsing tests
     printf("*** Attempting to parse valid STS headers ...\n");
 
     // SHOULD SUCCEED:
-    rvs.AppendElement(TestSuccess("max-age=100", false, stss, pm));
-    rvs.AppendElement(TestSuccess("max-age  =100", false, stss, pm));
-    rvs.AppendElement(TestSuccess(" max-age=100", false, stss, pm));
-    rvs.AppendElement(TestSuccess("max-age = 100 ", false, stss, pm));
-    rvs.AppendElement(TestSuccess("max-age  =       100             ", false, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age=100", false, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age  =100", false, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess(" max-age=100", false, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age = 100 ", false, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age  =       100             ", false, 100, false, stss, pm));
 
-    rvs.AppendElement(TestSuccess("maX-aGe=100", false, stss, pm));
-    rvs.AppendElement(TestSuccess("MAX-age  =100", false, stss, pm));
-    rvs.AppendElement(TestSuccess("max-AGE=100", false, stss, pm));
-    rvs.AppendElement(TestSuccess("Max-Age = 100 ", false, stss, pm));
-    rvs.AppendElement(TestSuccess("MAX-AGE = 100 ", false, stss, pm));
+    rvs.AppendElement(TestSuccess("maX-aGe=100", false, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("MAX-age  =100", false, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("max-AGE=100", false, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("Max-Age = 100 ", false, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("MAX-AGE = 100 ", false, 100, false, stss, pm));
 
-    rvs.AppendElement(TestSuccess("max-age=100;includeSubdomains", false, stss, pm));
-    rvs.AppendElement(TestSuccess("max-age=100; includeSubdomains", false, stss, pm));
-    rvs.AppendElement(TestSuccess(" max-age=100; includeSubdomains", false, stss, pm));
-    rvs.AppendElement(TestSuccess("max-age = 100 ; includeSubdomains", false, stss, pm));
-    rvs.AppendElement(TestSuccess("max-age  =       100             ; includeSubdomains", false, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age=100;includeSubdomains", false, 100, true, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age=100; includeSubdomains", false, 100, true, stss, pm));
+    rvs.AppendElement(TestSuccess(" max-age=100; includeSubdomains", false, 100, true, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age = 100 ; includeSubdomains", false, 100, true, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age  =       100             ; includeSubdomains", false, 100, true, stss, pm));
 
-    rvs.AppendElement(TestSuccess("maX-aGe=100; includeSUBDOMAINS", false, stss, pm));
-    rvs.AppendElement(TestSuccess("MAX-age  =100; includeSubDomains", false, stss, pm));
-    rvs.AppendElement(TestSuccess("max-AGE=100; iNcLuDeSuBdoMaInS", false, stss, pm));
-    rvs.AppendElement(TestSuccess("Max-Age = 100; includesubdomains ", false, stss, pm));
-    rvs.AppendElement(TestSuccess("INCLUDESUBDOMAINS;MaX-AgE = 100 ", false, stss, pm));
+    rvs.AppendElement(TestSuccess("maX-aGe=100; includeSUBDOMAINS", false, 100, true, stss, pm));
+    rvs.AppendElement(TestSuccess("MAX-age  =100; includeSubDomains", false, 100, true, stss, pm));
+    rvs.AppendElement(TestSuccess("max-AGE=100; iNcLuDeSuBdoMaInS", false, 100, true, stss, pm));
+    rvs.AppendElement(TestSuccess("Max-Age = 100; includesubdomains ", false, 100, true, stss, pm));
+    rvs.AppendElement(TestSuccess("INCLUDESUBDOMAINS;MaX-AgE = 100 ", false, 100, true, stss, pm));
 
     // these are weird tests, but are testing that some extended syntax is
     // still allowed (but it is ignored)
-    rvs.AppendElement(TestSuccess("max-age=100randomstuffhere", true, stss, pm));
-    rvs.AppendElement(TestSuccess("max-age=100 includesubdomains", true, stss, pm));
-    rvs.AppendElement(TestSuccess("max-age=100 bar foo", true, stss, pm));
-    rvs.AppendElement(TestSuccess("max-age=100 ; includesubdomainsSomeStuff", true, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age=100randomstuffhere", true, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age=100 includesubdomains", true, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age=100 bar foo", true, 100, false, stss, pm));
+    rvs.AppendElement(TestSuccess("max-age=100 ; includesubdomainsSomeStuff", true, 100, false, stss, pm));
 
     rv0 = rvs.Contains(false) ? 1 : 0;
     if (rv0 == 0)
       passed("Successfully Parsed STS headers with mixed case and LWS");
 
     rvs.Clear();
 
     // SHOULD FAIL:
new file mode 100644
--- /dev/null
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -0,0 +1,85 @@
+accounts.google.com: max-age too low: 2592000
+aladdinschools.appspot.com: did not receive HSTS header
+api.recurly.com: did not receive HSTS header
+apis.google.com: did not receive HSTS header
+appengine.google.com: did not receive HSTS header
+betnet.fr: could not connect to host
+bigshinylock.minazo.net: could not connect to host
+braintreegateway.com: could not connect to host
+braintreepayments.com: did not receive HSTS header
+browserid.org: did not receive HSTS header
+cert.se: did not receive HSTS header
+checkout.google.com: did not receive HSTS header
+chrome.google.com: did not receive HSTS header
+chromiumcodereview.appspot.com: did not receive HSTS header
+codereview.appspot.com: did not receive HSTS header
+docs.google.com: did not receive HSTS header
+download.jitsi.org: did not receive HSTS header
+drive.google.com: did not receive HSTS header
+dropcam.com: did not receive HSTS header
+emailprivacytester.com: max-age too low: 8640000
+encrypted.google.com: did not receive HSTS header
+entropia.de: max-age too low: 2678402
+epoxate.com: max-age too low: 259200
+fatzebra.com.au: did not receive HSTS header
+gmail.com: did not receive HSTS header
+googlemail.com: did not receive HSTS header
+googleplex.com: could not connect to host
+greplin.com: did not receive HSTS header
+grepular.com: max-age too low: 8640000
+groups.google.com: did not receive HSTS header
+health.google.com: did not receive HSTS header
+hostedtalkgadget.google.com: did not receive HSTS header
+howrandom.org: max-age too low: 2592000
+iop.intuit.com: did not receive HSTS header
+irccloud.com: did not receive HSTS header
+jitsi.org: did not receive HSTS header
+jottit.com: could not connect to host
+kyps.net: did not receive HSTS header
+lastpass.com: max-age too low: 8640000
+ledgerscope.net: max-age too low: 86400
+linx.net: could not connect to host
+lists.mayfirst.org: did not receive HSTS header
+login.persona.org: max-age too low: 2592000
+lookout.com: did not receive HSTS header
+mail.google.com: did not receive HSTS header
+market.android.com: did not receive HSTS header
+mydigipass.com: did not receive HSTS header
+mylookout.com: did not receive HSTS header
+neonisi.com: could not connect to host
+ottospora.nl: could not connect to host
+packagist.org: max-age too low: 2592000
+plus.google.com: did not receive HSTS header
+profiles.google.com: did not receive HSTS header
+romab.com: max-age too low: 2628000
+script.google.com: did not receive HSTS header
+shops.neonisi.com: could not connect to host
+simon.butcher.name: max-age too low: 2629743
+sites.google.com: did not receive HSTS header
+sol.io: could not connect to host
+spreadsheets.google.com: did not receive HSTS header
+squareup.com: max-age too low: 1296000
+ssl.google-analytics.com: did not receive HSTS header
+sunshinepress.org: could not connect to host
+talk.google.com: did not receive HSTS header
+talkgadget.google.com: did not receive HSTS header
+torproject.org: did not receive HSTS header
+uprotect.it: could not connect to host
+www.developer.mydigipass.com: did not receive HSTS header
+www.dropcam.com: max-age too low: 2592000
+www.entropia.de: max-age too low: 2678402
+www.gmail.com: did not receive HSTS header
+www.googlemail.com: did not receive HSTS header
+www.greplin.com: did not receive HSTS header
+www.irccloud.com: did not receive HSTS header
+www.jitsi.org: did not receive HSTS header
+www.kyps.net: did not receive HSTS header
+www.lastpass.com: did not receive HSTS header
+www.ledgerscope.net: max-age too low: 86400
+www.logentries.com: did not receive HSTS header
+www.makeyourlaws.org: did not receive HSTS header
+www.moneybookers.com: did not receive HSTS header
+www.neonisi.com: could not connect to host
+www.paycheckrecords.com: did not receive HSTS header
+www.paypal.com: max-age too low: 14400
+www.sandbox.mydigipass.com: did not receive HSTS header
--- a/security/manager/boot/src/nsSTSPreloadList.inc
+++ b/security/manager/boot/src/nsSTSPreloadList.inc
@@ -2,134 +2,64 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*****************************************************************************/
 /* This is an automatically generated file. If you're not                    */
 /* nsStrictTransportSecurityService.cpp, you shouldn't be #including it.     */
 /*****************************************************************************/
 
-#include <prtypes.h>
-
 class nsSTSPreload
 {
   public:
     const char *mHost;
     const bool mIncludeSubdomains;
 };
 
 static const nsSTSPreload kSTSPreloadList[] = {
-  { "accounts.google.com", true },
-  { "aladdinschools.appspot.com", false },
   { "alpha.irccloud.com", false },
-  { "api.recurly.com", true },
-  { "apis.google.com", true },
-  { "app.recurly.com", true },
-  { "appengine.google.com", false },
+  { "api.intercom.io", false },
+  { "app.recurly.com", false },
   { "arivo.com.br", true },
-  { "betnet.fr", true },
-  { "bigshinylock.minazo.net", true },
-  { "blog.torproject.org", true },
-  { "braintreegateway.com", true },
-  { "braintreepayments.com", false },
-  { "browserid.org", true },
+  { "blog.torproject.org", false },
   { "business.medbank.com.mt", true },
-  { "cert.se", true },
-  { "check.torproject.org", true },
-  { "checkout.google.com", true },
-  { "chrome.google.com", true },
-  { "chromiumcodereview.appspot.com", true },
-  { "cloudsecurityalliance.org", true },
-  { "codereview.appspot.com", true },
+  { "check.torproject.org", false },
+  { "cloudsecurityalliance.org", false },
   { "crate.io", true },
   { "crypto.cat", true },
   { "crypto.is", true },
+  { "csawctf.poly.edu", true },
   { "developer.mydigipass.com", false },
-  { "docs.google.com", true },
-  { "download.jitsi.org", false },
-  { "drive.google.com", true },
-  { "dropcam.com", false },
-  { "ebanking.indovinabank.com.vn", true },
-  { "emailprivacytester.com", false },
-  { "encrypted.google.com", true },
-  { "entropia.de", false },
-  { "epoxate.com", false },
+  { "dm.lookout.com", false },
+  { "dm.mylookout.com", false },
+  { "ebanking.indovinabank.com.vn", false },
   { "factor.cc", false },
-  { "gmail.com", false },
-  { "googlemail.com", false },
-  { "googleplex.com", true },
-  { "greplin.com", false },
-  { "grepular.com", true },
-  { "groups.google.com", true },
-  { "health.google.com", true },
-  { "hostedtalkgadget.google.com", true },
-  { "howrandom.org", true },
   { "id.mayfirst.org", false },
-  { "irccloud.com", false },
-  { "jitsi.org", false },
-  { "jottit.com", true },
+  { "intercom.io", false },
   { "keyerror.com", true },
-  { "kyps.net", false },
-  { "lastpass.com", false },
-  { "ledgerscope.net", false },
-  { "linx.net", true },
-  { "lists.mayfirst.org", false },
   { "logentries.com", false },
-  { "login.persona.org", true },
   { "login.sapo.pt", true },
-  { "luneta.nearbuysystems.com", true },
-  { "mail.google.com", true },
-  { "market.android.com", true },
+  { "luneta.nearbuysystems.com", false },
+  { "makeyourlaws.org", false },
   { "mattmccutchen.net", true },
   { "members.mayfirst.org", false },
-  { "mydigipass.com", false },
   { "neg9.org", false },
-  { "neonisi.com", false },
-  { "ottospora.nl", true },
   { "passwd.io", true },
   { "piratenlogin.de", true },
   { "pixi.me", true },
-  { "plus.google.com", true },
-  { "profiles.google.com", true },
   { "riseup.net", true },
-  { "romab.com", true },
   { "sandbox.mydigipass.com", false },
-  { "script.google.com", true },
-  { "shops.neonisi.com", true },
-  { "simon.butcher.name", true },
-  { "sites.google.com", true },
-  { "sol.io", true },
-  { "spreadsheets.google.com", true },
-  { "squareup.com", false },
-  { "ssl.google-analytics.com", true },
   { "stripe.com", true },
-  { "sunshinepress.org", true },
   { "support.mayfirst.org", false },
-  { "talk.google.com", true },
-  { "talkgadget.google.com", true },
-  { "torproject.org", false },
+  { "surfeasy.com", false },
   { "ubertt.org", true },
-  { "uprotect.it", true },
   { "www.apollo-auto.com", true },
   { "www.braintreepayments.com", false },
-  { "www.cueup.com", true },
-  { "www.developer.mydigipass.com", false },
-  { "www.dropcam.com", false },
+  { "www.cueup.com", false },
   { "www.elanex.biz", false },
-  { "www.entropia.de", false },
-  { "www.gmail.com", false },
-  { "www.googlemail.com", false },
-  { "www.greplin.com", false },
-  { "www.irccloud.com", false },
-  { "www.jitsi.org", false },
-  { "www.kyps.net", false },
-  { "www.lastpass.com", false },
-  { "www.ledgerscope.net", false },
-  { "www.logentries.com", false },
-  { "www.moneybookers.com", true },
+  { "www.intercom.io", false },
+  { "www.lookout.com", false },
   { "www.mydigipass.com", false },
-  { "www.neonisi.com", true },
+  { "www.mylookout.com", false },
   { "www.noisebridge.net", false },
-  { "www.paycheckrecords.com", false },
-  { "www.paypal.com", false },
-  { "www.sandbox.mydigipass.com", false },
-  { "www.torproject.org", true },
+  { "www.surfeasy.com", false },
+  { "www.torproject.org", false },
 };
--- a/security/manager/boot/src/nsStrictTransportSecurityService.cpp
+++ b/security/manager/boot/src/nsStrictTransportSecurityService.cpp
@@ -11,16 +11,17 @@
 #include "nsISSLStatus.h"
 #include "nsISSLStatusProvider.h"
 #include "nsStrictTransportSecurityService.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsThreadUtils.h"
 #include "nsStringGlue.h"
 #include "nsIScriptSecurityManager.h"
+#include "mozilla/Preferences.h"
 
 // A note about the preload list:
 // When a site specifically disables sts by sending a header with
 // 'max-age: 0', we keep a "knockout" value that means "we have no information
 // regarding the sts state of this host" (any ancestor of "this host" can still
 // influence its sts status via include subdomains, however).
 // This prevents the preload list from overriding the site's current
 // desired sts status. Knockout values are indicated by permission values of
@@ -62,17 +63,17 @@ nsSTSHostEntry::nsSTSHostEntry(const nsS
   , mIncludeSubdomains(toCopy.mIncludeSubdomains)
 {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 
 nsStrictTransportSecurityService::nsStrictTransportSecurityService()
-  : mInPrivateMode(false)
+  : mInPrivateMode(false), mUsePreloadList(true)
 {
 }
 
 nsStrictTransportSecurityService::~nsStrictTransportSecurityService()
 {
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS2(nsStrictTransportSecurityService,
@@ -88,16 +89,18 @@ nsStrictTransportSecurityService::Init()
    NS_ENSURE_SUCCESS(rv, rv);
 
    // figure out if we're starting in private browsing mode
    nsCOMPtr<nsIPrivateBrowsingService> pbs =
      do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
    if (pbs)
      pbs->GetPrivateBrowsingEnabled(&mInPrivateMode);
 
+   mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true);
+   mozilla::Preferences::AddStrongObserver(this, "network.stricttransportsecurity.preloadlist");
    mObserverService = mozilla::services::GetObserverService();
    if (mObserverService)
      mObserverService->AddObserver(this, NS_PRIVATE_BROWSING_SWITCH_TOPIC, false);
 
    if (mInPrivateMode)
      mPrivateModeHostTable.Init();
 
    return NS_OK;
@@ -204,32 +207,45 @@ nsStrictTransportSecurityService::Remove
   NS_ENSURE_SUCCESS(rv, rv);
   STSLOG(("STS: deleted subdomains permission\n"));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsStrictTransportSecurityService::ProcessStsHeader(nsIURI* aSourceURI,
-                                                   const char* aHeader)
+                                                   const char* aHeader,
+                                                   uint64_t *aMaxAge,
+                                                   bool *aIncludeSubdomains)
 {
   // Should be called on the main thread (or via proxy) since the permission
   // manager is used and it's not threadsafe.
   NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
 
+  if (aMaxAge != nullptr) {
+    *aMaxAge = 0;
+  }
+
+  if (aIncludeSubdomains != nullptr) {
+    *aIncludeSubdomains = false;
+  }
+
   char * header = NS_strdup(aHeader);
   if (!header) return NS_ERROR_OUT_OF_MEMORY;
-  nsresult rv = ProcessStsHeaderMutating(aSourceURI, header);
+  nsresult rv = ProcessStsHeaderMutating(aSourceURI, header, aMaxAge,
+                                         aIncludeSubdomains);
   NS_Free(header);
   return rv;
 }
 
 nsresult
 nsStrictTransportSecurityService::ProcessStsHeaderMutating(nsIURI* aSourceURI,
-                                                           char* aHeader)
+                                                           char* aHeader,
+                                                           uint64_t *aMaxAge,
+                                                           bool *aIncludeSubdomains)
 {
   STSLOG(("STS: ProcessStrictTransportHeader(%s)\n", aHeader));
 
   // "Strict-Transport-Security" ":" OWS
   //      STS-d  *( OWS ";" OWS STS-d  OWS)
   //
   //  ; STS directive
   //  STS-d      = maxAge / includeSubDomains
@@ -312,16 +328,24 @@ nsStrictTransportSecurityService::Proces
   // after processing all the directives, make sure we came across max-age
   // somewhere.
   STS_PARSER_FAIL_IF(!foundMaxAge,
               ("Parse ERROR: couldn't locate max-age token\n"));
 
   // record the successfully parsed header data.
   SetStsState(aSourceURI, maxAge, includeSubdomains);
 
+  if (aMaxAge != nullptr) {
+    *aMaxAge = (uint64_t)maxAge;
+  }
+
+  if (aIncludeSubdomains != nullptr) {
+    *aIncludeSubdomains = includeSubdomains;
+  }
+
   return foundUnrecognizedTokens ?
          NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA :
          NS_OK;
 }
 
 NS_IMETHODIMP
 nsStrictTransportSecurityService::IsStsHost(const char* aHost, bool* aResult)
 {
@@ -345,21 +369,26 @@ int STSPreloadCompare(const void *key, c
 }
 
 // Returns the preload list entry for the given host, if it exists.
 // Only does exact host matching - the user must decide how to use the returned
 // data. May return null.
 const nsSTSPreload *
 nsStrictTransportSecurityService::GetPreloadListEntry(const char *aHost)
 {
-  return (const nsSTSPreload *) bsearch(aHost,
-                                        kSTSPreloadList,
-                                        PR_ARRAY_SIZE(kSTSPreloadList),
-                                        sizeof(nsSTSPreload),
-                                        STSPreloadCompare);
+  if (mUsePreloadList) {
+    return (const nsSTSPreload *) bsearch(aHost,
+                                          kSTSPreloadList,
+                                          PR_ARRAY_SIZE(kSTSPreloadList),
+                                          sizeof(nsSTSPreload),
+                                          STSPreloadCompare);
+  }
+  else {
+    return nullptr;
+  }
 }
 
 NS_IMETHODIMP
 nsStrictTransportSecurityService::IsStsURI(nsIURI* aURI, bool* aResult)
 {
   // Should be called on the main thread (or via proxy) since the permission
   // manager is used and it's not threadsafe.
   NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
@@ -547,16 +576,19 @@ nsStrictTransportSecurityService::Observ
       }
       mInPrivateMode = true;
     }
     else if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(data)) {
       mPrivateModeHostTable.Clear();
       mInPrivateMode = false;
     }
   }
+  else if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
+    mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true);
+  }
 
   return NS_OK;
 }
 
 //------------------------------------------------------------
 // Functions to overlay the permission manager calls in case
 // we're in private browsing mode.
 //------------------------------------------------------------
--- a/security/manager/boot/src/nsStrictTransportSecurityService.h
+++ b/security/manager/boot/src/nsStrictTransportSecurityService.h
@@ -131,17 +131,18 @@ public:
   nsStrictTransportSecurityService();
   nsresult Init();
   virtual ~nsStrictTransportSecurityService();
 
 private:
   nsresult GetHost(nsIURI *aURI, nsACString &aResult);
   nsresult GetPrincipalForURI(nsIURI *aURI, nsIPrincipal **aPrincipal);
   nsresult SetStsState(nsIURI* aSourceURI, int64_t maxage, bool includeSubdomains);
-  nsresult ProcessStsHeaderMutating(nsIURI* aSourceURI, char* aHeader);
+  nsresult ProcessStsHeaderMutating(nsIURI* aSourceURI, char* aHeader,
+                                    uint64_t *aMaxAge, bool *aIncludeSubdomains);
   const nsSTSPreload *GetPreloadListEntry(const char *aHost);
 
   // private-mode-preserving permission manager overlay functions
   nsresult AddPermission(nsIURI     *aURI,
                          const char *aType,
                          uint32_t   aPermission,
                          uint32_t   aExpireType,
                          int64_t    aExpireTime);
@@ -149,11 +150,12 @@ private:
                             const char       *aType);
 
   // cached services
   nsCOMPtr<nsIPermissionManager> mPermMgr;
   nsCOMPtr<nsIObserverService> mObserverService;
 
   bool mInPrivateMode;
   nsTHashtable<nsSTSHostEntry> mPrivateModeHostTable;
+  bool mUsePreloadList;
 };
 
 #endif // __nsStrictTransportSecurityService_h__
--- a/security/manager/ssl/tests/unit/test_sts_preloadlist.js
+++ b/security/manager/ssl/tests/unit/test_sts_preloadlist.js
@@ -30,19 +30,19 @@ Observer.prototype = {
   }
 };
 
 var gObserver = new Observer();
 
 // This is a list of every host we call processStsHeader with
 // (we have to remove any state added to the sts service so as to not muck
 // with other tests).
-var hosts = ["http://keyerror.com", "http://subdomain.kyps.net",
-             "http://subdomain.cert.se", "http://crypto.cat",
-             "http://www.logentries.com"];
+var hosts = ["http://keyerror.com", "http://subdomain.intercom.io",
+             "http://subdomain.pixi.me", "http://crypto.cat",
+             "http://logentries.com"];
 
 function cleanup() {
   Services.obs.removeObserver(gObserver, "private-browsing-transition-complete");
   if (getPBSvc())
     getPBSvc().privateBrowsingEnabled = false;
 
   for (var host of hosts) {
     var uri = Services.io.newURI(host, null, null);
@@ -67,37 +67,43 @@ function run_test() {
 function test_part1() {
   // check that a host not in the list is not identified as an sts host
   do_check_false(gSTSService.isStsHost("nonexistent.mozilla.com"));
 
   // check that an ancestor domain is not identified as an sts host
   do_check_false(gSTSService.isStsHost("com"));
 
   // Note: the following were taken from the STS preload list
-  // as of June 2012. If the list changes, this test will need to be modified.
+  // as of Sept. 2012. If the list changes, this test will need to be modified.
+  // check that the pref to toggle using the preload list works
+  Services.prefs.setBoolPref("network.stricttransportsecurity.preloadlist", false);
+  do_check_false(gSTSService.isStsHost("factor.cc"));
+  Services.prefs.setBoolPref("network.stricttransportsecurity.preloadlist", true);
+  do_check_true(gSTSService.isStsHost("factor.cc"));
+
   // check that an entry at the beginning of the list is an sts host
-  do_check_true(gSTSService.isStsHost("health.google.com"));
+  do_check_true(gSTSService.isStsHost("arivo.com.br"));
 
   // check that a subdomain is an sts host (includeSubdomains is set)
-  do_check_true(gSTSService.isStsHost("subdomain.health.google.com"));
+  do_check_true(gSTSService.isStsHost("subdomain.arivo.com.br"));
 
   // check that another subdomain is an sts host (includeSubdomains is set)
-  do_check_true(gSTSService.isStsHost("a.b.c.subdomain.health.google.com"));
+  do_check_true(gSTSService.isStsHost("a.b.c.subdomain.arivo.com.br"));
 
   // check that an entry in the middle of the list is an sts host
-  do_check_true(gSTSService.isStsHost("epoxate.com"));
+  do_check_true(gSTSService.isStsHost("neg9.org"));
 
   // check that a subdomain is not an sts host (includeSubdomains is not set)
-  do_check_false(gSTSService.isStsHost("subdomain.epoxate.com"));
+  do_check_false(gSTSService.isStsHost("subdomain.neg9.org"));
 
   // check that an entry at the end of the list is an sts host
-  do_check_true(gSTSService.isStsHost("www.googlemail.com"));
+  do_check_true(gSTSService.isStsHost("www.noisebridge.net"));
 
   // check that a subdomain is not an sts host (includeSubdomains is not set)
-  do_check_false(gSTSService.isStsHost("a.subdomain.www.googlemail.com"));
+  do_check_false(gSTSService.isStsHost("a.subdomain.www.noisebridge.net"));
 
   // check that a host with a dot on the end won't break anything
   do_check_false(gSTSService.isStsHost("notsts.nonexistent.mozilla.com."));
 
   // check that processing a header with max-age: 0 will remove a preloaded
   // site from the list
   var uri = Services.io.newURI("http://keyerror.com", null, null);
   gSTSService.processStsHeader(uri, "max-age=0");
@@ -107,45 +113,45 @@ function test_part1() {
   // re-enable a site's sts status
   gSTSService.processStsHeader(uri, "max-age=1000");
   do_check_true(gSTSService.isStsHost("keyerror.com"));
   // but this time include subdomains was not set, so test for that
   do_check_false(gSTSService.isStsHost("subdomain.keyerror.com"));
 
   // check that processing a header with max-age: 0 from a subdomain of a site
   // will not remove that (ancestor) site from the list
-  var uri = Services.io.newURI("http://subdomain.kyps.net", null, null);
+  var uri = Services.io.newURI("http://subdomain.intercom.io", null, null);
   gSTSService.processStsHeader(uri, "max-age=0");
-  do_check_true(gSTSService.isStsHost("kyps.net"));
-  do_check_false(gSTSService.isStsHost("subdomain.kyps.net"));
+  do_check_true(gSTSService.isStsHost("intercom.io"));
+  do_check_false(gSTSService.isStsHost("subdomain.intercom.io"));
 
-  var uri = Services.io.newURI("http://subdomain.cert.se", null, null);
+  var uri = Services.io.newURI("http://subdomain.pixi.me", null, null);
   gSTSService.processStsHeader(uri, "max-age=0");
   // we received a header with "max-age=0", so we have "no information"
-  // regarding the sts state of subdomain.cert.se specifically, but
-  // it is actually still an STS host, because of the preloaded cert.se
+  // regarding the sts state of subdomain.pixi.me specifically, but
+  // it is actually still an STS host, because of the preloaded pixi.me
   // including subdomains.
   // Here's a drawing:
-  // |-- cert.se (in preload list, includes subdomains)      IS sts host
-  //     |-- subdomain.cert.se                               IS sts host
-  //     |   `-- another.subdomain.cert.se                   IS sts host
-  //     `-- sibling.cert.se                                 IS sts host
-  do_check_true(gSTSService.isStsHost("subdomain.cert.se"));
-  do_check_true(gSTSService.isStsHost("sibling.cert.se"));
-  do_check_true(gSTSService.isStsHost("another.subdomain.cert.se"));
+  // |-- pixi.me (in preload list, includes subdomains)      IS sts host
+  //     |-- subdomain.pixi.me                               IS sts host
+  //     |   `-- another.subdomain.pixi.me                   IS sts host
+  //     `-- sibling.pixi.me                                 IS sts host
+  do_check_true(gSTSService.isStsHost("subdomain.pixi.me"));
+  do_check_true(gSTSService.isStsHost("sibling.pixi.me"));
+  do_check_true(gSTSService.isStsHost("another.subdomain.pixi.me"));
 
   gSTSService.processStsHeader(uri, "max-age=1000");
   // Here's what we have now:
-  // |-- cert.se (in preload list, includes subdomains)      IS sts host
-  //     |-- subdomain.cert.se (include subdomains is false) IS sts host
-  //     |   `-- another.subdomain.cert.se                   IS NOT sts host
-  //     `-- sibling.cert.se                                 IS sts host
-  do_check_true(gSTSService.isStsHost("subdomain.cert.se"));
-  do_check_true(gSTSService.isStsHost("sibling.cert.se"));
-  do_check_false(gSTSService.isStsHost("another.subdomain.cert.se"));
+  // |-- pixi.me (in preload list, includes subdomains)      IS sts host
+  //     |-- subdomain.pixi.me (include subdomains is false) IS sts host
+  //     |   `-- another.subdomain.pixi.me                   IS NOT sts host
+  //     `-- sibling.pixi.me                                 IS sts host
+  do_check_true(gSTSService.isStsHost("subdomain.pixi.me"));
+  do_check_true(gSTSService.isStsHost("sibling.pixi.me"));
+  do_check_false(gSTSService.isStsHost("another.subdomain.pixi.me"));
 
   // Test private browsing correctly interacts with removing preloaded sites.
   // If we don't have the private browsing service, don't run those tests
   // (which means we have to manually call run_next_test() instead of relying
   // on our observer to call it).
   if (getPBSvc()) {
     getPBSvc().privateBrowsingEnabled = true;
   } else {
@@ -178,31 +184,31 @@ function test_private_browsing1() {
   // has expired in the permission manager, so we can't yet extend this test
   // to that case.
   // Test that an expired private browsing entry results in correctly
   // identifying a host that is on the preload list as no longer sts.
   // (This happens when we're in private browsing mode, we get a header from
   // a site on the preload list, and that header later expires. We need to
   // then treat that host as no longer an sts host.)
   // (sanity check first - this should be in the preload list)
-  do_check_true(gSTSService.isStsHost("www.logentries.com"));
-  var uri = Services.io.newURI("http://www.logentries.com", null, null);
+  do_check_true(gSTSService.isStsHost("logentries.com"));
+  var uri = Services.io.newURI("http://logentries.com", null, null);
   // according to the rfc, max-age can't be negative, but this is a great
   // way to test an expired entry
   gSTSService.processStsHeader(uri, "max-age=-1000");
-  do_check_false(gSTSService.isStsHost("www.logentries.com"));
+  do_check_false(gSTSService.isStsHost("logentries.com"));
 
   // if this test gets this far, it means there's a private browsing service
   getPBSvc().privateBrowsingEnabled = false;
 }
 
 function test_private_browsing2() {
   // if this test gets this far, it means there's a private browsing service
   do_check_true(gSTSService.isStsHost("crypto.cat"));
   // the crypto.cat entry has includeSubdomains set
   do_check_true(gSTSService.isStsHost("subdomain.crypto.cat"));
 
   // Now that we're out of private browsing mode, we need to make sure
   // we've "forgotten" that we "forgot" this site's sts status.
-  do_check_true(gSTSService.isStsHost("www.logentries.com"));
+  do_check_true(gSTSService.isStsHost("logentries.com"));
 
   run_next_test();
 }
new file mode 100644
--- /dev/null
+++ b/security/manager/tools/getHSTSPreloadList.js
@@ -0,0 +1,242 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// <https://developer.mozilla.org/en/XPConnect/xpcshell/HOWTO>
+// <https://bugzilla.mozilla.org/show_bug.cgi?id=546628>
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+// Register resource://app/ URI
+let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+let resHandler = ios.getProtocolHandler("resource")
+                 .QueryInterface(Ci.nsIResProtocolHandler);
+let mozDir = Cc["@mozilla.org/file/directory_service;1"]
+             .getService(Ci.nsIProperties)
+             .get("CurProcD", Ci.nsILocalFile);
+let mozDirURI = ios.newFileURI(mozDir);
+resHandler.setSubstitution("app", mozDirURI);
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource:///modules/XPCOMUtils.jsm");
+
+const SOURCE = "https://src.chromium.org/viewvc/chrome/trunk/src/net/base/transport_security_state_static.json";
+const OUTPUT = "nsSTSPreloadList.inc";
+const ERROR_OUTPUT = "nsSTSPreloadList.errors";
+const MINIMUM_REQUIRED_MAX_AGE = 60 * 60 * 24 * 7 * 18;
+const PREFIX = "/* This Source Code Form is subject to the terms of the Mozilla Public\n" +
+" * License, v. 2.0. If a copy of the MPL was not distributed with this\n" +
+" * file, You can obtain one at http://mozilla.org/MPL/2.0/. */\n" +
+"\n" +
+"/*****************************************************************************/\n" +
+"/* This is an automatically generated file. If you're not                    */\n" +
+"/* nsStrictTransportSecurityService.cpp, you shouldn't be #including it.     */\n" +
+"/*****************************************************************************/\n" +
+"\n" +
+"class nsSTSPreload\n" +
+"{\n" +
+"  public:\n" +
+"    const char *mHost;\n" +
+"    const bool mIncludeSubdomains;\n" +
+"};\n" +
+"\n" +
+"static const nsSTSPreload kSTSPreloadList[] = {\n";
+const POSTFIX =  "};\n";
+
+function download() {
+  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+            .createInstance(Ci.nsIXMLHttpRequest);
+  req.open("GET", SOURCE, false); // doing the request synchronously
+  try {
+    req.send();
+  }
+  catch (e) {
+    throw "ERROR: problem downloading '" + SOURCE + "': " + e;
+  }
+
+  if (req.status != 200) {
+    throw "ERROR: problem downloading '" + SOURCE + "': status " + req.status;
+  }
+
+  // we have to filter out '//' comments
+  var result = req.responseText.replace(/\/\/[^\n]*\n/g, "");
+  var data = null;
+  try {
+    data = JSON.parse(result);
+  }
+  catch (e) {
+    throw "ERROR: could not parse data from '" + SOURCE + "': " + e;
+  }
+  return data;
+}
+
+function getHosts(rawdata) {
+  var hosts = [];
+
+  if (!rawdata || !rawdata.entries) {
+    throw "ERROR: source data not formatted correctly: 'entries' not found";
+  }
+
+  for (entry of rawdata.entries) {
+    if (entry.mode && entry.mode == "force-https") {
+      if (entry.name) {
+        hosts.push(entry);
+      } else {
+        throw "ERROR: entry not formatted correctly: no name found";
+      }
+    }
+  }
+
+  return hosts;
+}
+
+var gSTSService = Cc["@mozilla.org/stsservice;1"]
+                  .getService(Ci.nsIStrictTransportSecurityService);
+
+function processStsHeader(hostname, header, status) {
+  var maxAge = { value: 0 };
+  var includeSubdomains = { value: false };
+  var error = "no error";
+  if (header != null) {
+    try {
+      var uri = Services.io.newURI("https://" + host.name, null, null);
+      gSTSService.processStsHeader(uri, header, maxAge, includeSubdomains);
+    }
+    catch (e) {
+      dump("ERROR: could not process header '" + header + "' from " + hostname +
+           ": " + e + "\n");
+      error = e;
+    }
+  }
+  else {
+    if (status == 0) {
+      error = "could not connect to host";
+    } else {
+      error = "did not receive HSTS header";
+    }
+  }
+
+  return { hostname: hostname,
+           maxAge: maxAge.value,
+           includeSubdomains: includeSubdomains.value,
+           error: error };
+}
+
+function RedirectStopper() {};
+
+RedirectStopper.prototype = {
+  // nsIChannelEventSink
+  asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
+    throw Cr.NS_ERROR_ENTITY_CHANGED;
+  },
+
+  getInterface: function(iid) {
+    return this.QueryInterface(iid);
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink])
+};
+
+function getHSTSStatus(host, resultList) {
+  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+            .createInstance(Ci.nsIXMLHttpRequest);
+  var inResultList = false;
+  var uri = "https://" + host.name + "/";
+  req.open("GET", uri, true);
+  req.channel.notificationCallbacks = new RedirectStopper();
+  req.onreadystatechange = function(event) {
+    if (!inResultList && req.readyState == 4) {
+      inResultList = true;
+      var header = req.getResponseHeader("strict-transport-security");
+      resultList.push(processStsHeader(host.name, header, req.status));
+    }
+  };
+
+  try {
+    req.send();
+  }
+  catch (e) {
+    dump("ERROR: exception making request to " + host.name + ": " + e + "\n");
+  }
+}
+
+function compareHSTSStatus(a, b) {
+  return (a.hostname > b.hostname ? 1 : (a.hostname < b.hostname ? -1 : 0));
+}
+
+function writeTo(string, fos) {
+  fos.write(string, string.length);
+}
+
+function output(sortedStatuses) {
+  try {
+    var file = FileUtils.getFile("CurWorkD", [OUTPUT]);
+    var errorFile = FileUtils.getFile("CurWorkD", [ERROR_OUTPUT]);
+    var fos = FileUtils.openSafeFileOutputStream(file);
+    var eos = FileUtils.openSafeFileOutputStream(errorFile);
+    writeTo(PREFIX, fos);
+    for (var status of hstsStatuses) {
+      if (status.maxAge >= MINIMUM_REQUIRED_MAX_AGE) {
+        writeTo("  { \"" + status.hostname + "\", " +
+                 (status.includeSubdomains ? "true" : "false") + " },\n", fos);
+        dump("INFO: " + status.hostname + " ON the preload list\n");
+      }
+      else {
+        dump("INFO: " + status.hostname + " NOT ON the preload list\n");
+        if (status.maxAge != 0) {
+          status.error = "max-age too low: " + status.maxAge;
+        }
+        writeTo(status.hostname + ": " + status.error + "\n", eos);
+      }
+    }
+    writeTo(POSTFIX, fos);
+    FileUtils.closeSafeFileOutputStream(fos);
+    FileUtils.closeSafeFileOutputStream(eos);
+  }
+  catch (e) {
+    dump("ERROR: problem writing output to '" + OUTPUT + "': " + e + "\n");
+  }
+}
+
+// The idea is the output list will be the same size as the input list
+// when we've received all responses (or timed out).
+// Since all events are processed on the main thread, and since event
+// handlers are not preemptible, there shouldn't be any concurrency issues.
+function waitForResponses(inputList, outputList) {
+  // From <https://developer.mozilla.org/en/XPConnect/xpcshell/HOWTO>
+  var threadManager = Cc["@mozilla.org/thread-manager;1"]
+                      .getService(Ci.nsIThreadManager);
+  var mainThread = threadManager.currentThread;
+  while (inputList.length != outputList.length) {
+    mainThread.processNextEvent(true);
+  }
+  while (mainThread.hasPendingEvents()) {
+    mainThread.processNextEvent(true);
+  }
+}
+
+// ****************************************************************************
+// This is where the action happens:
+// disable the current preload list so it won't interfere with requests we make
+Services.prefs.setBoolPref("network.stricttransportsecurity.preloadlist", false);
+// download and parse the raw json file from the Chromium source
+var rawdata = download();
+// get just the hosts with mode: "force-https"
+var hosts = getHosts(rawdata);
+// spin off a request to each host
+var hstsStatuses = [];
+for (var host of hosts) {
+  getHSTSStatus(host, hstsStatuses);
+}
+// wait for those responses to come back
+waitForResponses(hosts, hstsStatuses);
+// sort the hosts alphabetically
+hstsStatuses.sort(compareHSTSStatus);
+// write the results to a file (this is where we filter out hosts that we
+// either couldn't connect to, didn't receive an HSTS header from, couldn't
+// parse the header, or had a header with too short a max-age)
+output(hstsStatuses);
+// ****************************************************************************
deleted file mode 100644
--- a/security/manager/tools/getHSTSPreloadList.py
+++ /dev/null
@@ -1,115 +0,0 @@
-#!/usr/bin/python
-
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import sys, subprocess, json, argparse
-
-SOURCE = "https://src.chromium.org/viewvc/chrome/trunk/src/net/base/transport_security_state_static.json"
-OUTPUT = "nsSTSPreloadList.inc"
-PREFIX = """/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/*****************************************************************************/
-/* This is an automatically generated file. If you're not                    */
-/* nsStrictTransportSecurityService.cpp, you shouldn't be #including it.     */
-/*****************************************************************************/
-
-#include <prtypes.h>
-
-class nsSTSPreload
-{
-  public:
-    const char *mHost;
-    const bool mIncludeSubdomains;
-};
-
-static const nsSTSPreload kSTSPreloadList[] = {
-"""
-POSTFIX = """};
-"""
-
-def filterComments(stream):
-  lines = []
-  for line in stream:
-    # each line still has '\n' at the end, so if find returns -1,
-    # the newline gets chopped off like we want
-    # (and otherwise, comments are filtered out like we want)
-    lines.append(line[0:line.find("//")])
-  return "".join(lines)
-
-def readFile(source):
-  if source != "-":
-    f = open(source, 'r')
-  else:
-    f = sys.stdin
-  return filterComments(f)
-
-def download(source):
-  download = subprocess.Popen(["wget", "-O", "-", source], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
-  contents = filterComments(download.stdout)
-  download.wait()
-  if download.returncode != 0:
-    raise Exception()
-  return contents
-
-def output(filename, jsonblob):
-  if filename != "-":
-    outstream = open(filename, 'w')
-  else:
-    outstream = sys.stdout
-  if not 'entries' in jsonblob:
-    raise Exception()
-  else:
-    outstream.write(PREFIX)
-    # use a dictionary to prevent duplicates
-    lines = {}
-    for entry in jsonblob['entries']:
-      if 'name' in entry and 'mode' in entry and entry['mode'] == "force-https":
-        line = "  { \"" + entry['name'] + "\", "
-        if 'include_subdomains' in entry and entry['include_subdomains']:
-          line = line + "true },\n"
-        else:
-          line = line + "false },\n"
-        lines[line] = True
-    # The data must be sorted by domain name because we do a binary search to
-    # determine if a host is in the preload list.
-    keys = lines.keys()
-    keys.sort()
-    for line in keys:
-      outstream.write(line)
-    outstream.write(POSTFIX);
-  outstream.close()
-
-def main():
-  parser = argparse.ArgumentParser(description="Download Chrome's STS preload list and format it for Firefox")
-  parser.add_argument("-s", "--source", default=SOURCE, help="Specify source for input list (can be a file, url, or '-' for stdin)")
-  parser.add_argument("-o", "--output", default=OUTPUT, help="Specify output file ('-' for stdout)")
-  args = parser.parse_args()
-  contents = None
-  try:
-    contents = readFile(args.source)
-  except:
-    pass
-  if not contents:
-    try:
-      contents = download(args.source)
-    except:
-      print >> sys.stderr, "Could not read source '%s'" % args.source
-      return 1
-  try:
-    jsonblob = json.loads(contents)
-  except:
-    print >> sys.stderr, "Could not parse contents of file '%s'" % args.source
-    return 1
-  try:
-    output(args.output, jsonblob)
-  except:
-    print >> sys.stderr, "Could not write to '%s'" % args.output
-    return 1
-  return 0
-
-if __name__ == "__main__":
-  sys.exit(main())