bug 786417 - filter the hsts preload list to sites that actually send the header r=bsmith, mayhemer

This commit is contained in:
David Keeler 2012-10-15 14:43:57 -07:00
Родитель bcd437d0de
Коммит 27d5157e2b
11 изменённых файлов: 473 добавлений и 278 удалений

Просмотреть файл

@ -1249,6 +1249,9 @@ pref("network.proxy.autoconfig_url", "");
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

Просмотреть файл

@ -8,7 +8,7 @@ interface nsIURI;
interface nsIObserver;
interface nsIHttpChannel;
[scriptable, uuid(16955eee-6c48-4152-9309-c42a465138a1)]
[scriptable, uuid(aee925d1-2bc9-469e-9582-b27b1d6b5192)]
interface nsIStrictTransportSecurityService : nsISupports
{
/**
@ -20,13 +20,17 @@ interface nsIStrictTransportSecurityService : nsISupports
*
* @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

Просмотреть файл

@ -1149,7 +1149,7 @@ nsHttpChannel::ProcessSTSHeader()
// 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;

Просмотреть файл

@ -39,6 +39,7 @@
bool
TestSuccess(const char* hdr, bool extraTokens,
uint64_t expectedMaxAge, bool expectedIncludeSubdomains,
nsIStrictTransportSecurityService* stss,
nsIPermissionManager* pm)
{
@ -46,9 +47,14 @@ TestSuccess(const char* hdr, bool extraTokens,
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.");
@ -68,7 +74,7 @@ bool TestFailure(const char* hdr,
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;
@ -106,36 +112,36 @@ main(int32_t argc, char *argv[])
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)

Просмотреть файл

@ -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

Просмотреть файл

@ -7,8 +7,6 @@
/* nsStrictTransportSecurityService.cpp, you shouldn't be #including it. */
/*****************************************************************************/
#include <prtypes.h>
class nsSTSPreload
{
public:
@ -17,119 +15,51 @@ class nsSTSPreload
};
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 },
};

Просмотреть файл

@ -16,6 +16,7 @@
#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
@ -67,7 +68,7 @@ nsSTSHostEntry::nsSTSHostEntry(const nsSTSHostEntry& toCopy)
nsStrictTransportSecurityService::nsStrictTransportSecurityService()
: mInPrivateMode(false)
: mInPrivateMode(false), mUsePreloadList(true)
{
}
@ -93,6 +94,8 @@ nsStrictTransportSecurityService::Init()
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);
@ -209,22 +212,35 @@ nsStrictTransportSecurityService::RemoveStsState(nsIURI* aURI)
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));
@ -317,6 +333,14 @@ nsStrictTransportSecurityService::ProcessStsHeaderMutating(nsIURI* aSourceURI,
// 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;
@ -350,11 +374,16 @@ int STSPreloadCompare(const void *key, const void *entry)
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
@ -552,6 +581,9 @@ nsStrictTransportSecurityService::Observe(nsISupports *subject,
mInPrivateMode = false;
}
}
else if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
mUsePreloadList = mozilla::Preferences::GetBool("network.stricttransportsecurity.preloadlist", true);
}
return NS_OK;
}

Просмотреть файл

@ -136,7 +136,8 @@ 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
@ -154,6 +155,7 @@ private:
bool mInPrivateMode;
nsTHashtable<nsSTSHostEntry> mPrivateModeHostTable;
bool mUsePreloadList;
};
#endif // __nsStrictTransportSecurityService_h__

Просмотреть файл

@ -35,9 +35,9 @@ 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");
@ -72,27 +72,33 @@ function test_part1() {
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."));
@ -112,35 +118,35 @@ function test_part1() {
// 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
@ -183,12 +189,12 @@ function test_private_browsing1() {
// 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;
@ -202,7 +208,7 @@ function test_private_browsing2() {
// 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();
}

Просмотреть файл

@ -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);
// ****************************************************************************

Просмотреть файл

@ -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())