mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 760307 - Preloaded strict-transport-security site list. r=mayhemer, bsmith
This commit is contained in:
parent
b9ae21b684
commit
d28bb14e18
135
security/manager/boot/src/nsSTSPreloadList.inc
Normal file
135
security/manager/boot/src/nsSTSPreloadList.inc
Normal file
@ -0,0 +1,135 @@
|
||||
/* 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[] = {
|
||||
{ "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 },
|
||||
{ "arivo.com.br", true },
|
||||
{ "betnet.fr", true },
|
||||
{ "bigshinylock.minazo.net", true },
|
||||
{ "blog.torproject.org", true },
|
||||
{ "braintreegateway.com", true },
|
||||
{ "braintreepayments.com", false },
|
||||
{ "browserid.org", true },
|
||||
{ "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 },
|
||||
{ "crate.io", true },
|
||||
{ "crypto.cat", true },
|
||||
{ "crypto.is", 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 },
|
||||
{ "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 },
|
||||
{ "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 },
|
||||
{ "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 },
|
||||
{ "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.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.mydigipass.com", false },
|
||||
{ "www.neonisi.com", true },
|
||||
{ "www.noisebridge.net", false },
|
||||
{ "www.paycheckrecords.com", false },
|
||||
{ "www.paypal.com", false },
|
||||
{ "www.sandbox.mydigipass.com", false },
|
||||
{ "www.torproject.org", true },
|
||||
};
|
@ -17,6 +17,20 @@
|
||||
#include "nsStringGlue.h"
|
||||
#include "nsIScriptSecurityManager.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
|
||||
// STS_KNOCKOUT.
|
||||
#include "nsSTSPreloadList.inc"
|
||||
|
||||
#define STS_SET (nsIPermissionManager::ALLOW_ACTION)
|
||||
#define STS_UNSET (nsIPermissionManager::UNKNOWN_ACTION)
|
||||
#define STS_KNOCKOUT (nsIPermissionManager::DENY_ACTION)
|
||||
|
||||
#if defined(PR_LOGGING)
|
||||
PRLogModuleInfo *gSTSLog = PR_NewLogModule("nsSTSService");
|
||||
#endif
|
||||
@ -29,34 +43,13 @@ PRLogModuleInfo *gSTSLog = PR_NewLogModule("nsSTSService");
|
||||
return NS_ERROR_FAILURE; \
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Returns a principal (aPrincipal) corresponding to aURI.
|
||||
* This is used to interact with the permission manager.
|
||||
*/
|
||||
nsresult
|
||||
GetPrincipalForURI(nsIURI* aURI, nsIPrincipal** aPrincipal)
|
||||
{
|
||||
// The permission manager wants a principal but don't actually check a
|
||||
// permission but a data we saved in the permission manager so we are good by
|
||||
// creating a no-app codebase principal and send it to the permission manager.
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIScriptSecurityManager> securityManager =
|
||||
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return securityManager->GetNoAppCodebasePrincipal(aURI, aPrincipal);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
nsSTSHostEntry::nsSTSHostEntry(const char* aHost)
|
||||
: mHost(aHost)
|
||||
, mExpireTime(0)
|
||||
, mDeleted(false)
|
||||
, mExpired(false)
|
||||
, mStsPermission(STS_UNSET)
|
||||
, mIncludeSubdomains(false)
|
||||
{
|
||||
}
|
||||
@ -64,7 +57,8 @@ nsSTSHostEntry::nsSTSHostEntry(const char* aHost)
|
||||
nsSTSHostEntry::nsSTSHostEntry(const nsSTSHostEntry& toCopy)
|
||||
: mHost(toCopy.mHost)
|
||||
, mExpireTime(toCopy.mExpireTime)
|
||||
, mDeleted(toCopy.mDeleted)
|
||||
, mExpired(toCopy.mExpired)
|
||||
, mStsPermission(toCopy.mStsPermission)
|
||||
, mIncludeSubdomains(toCopy.mIncludeSubdomains)
|
||||
{
|
||||
}
|
||||
@ -123,6 +117,29 @@ nsStrictTransportSecurityService::GetHost(nsIURI *aURI, nsACString &aResult)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsStrictTransportSecurityService::GetPrincipalForURI(nsIURI* aURI,
|
||||
nsIPrincipal** aPrincipal)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIScriptSecurityManager> securityManager =
|
||||
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// We have to normalize the scheme of the URIs we're using, so just use https.
|
||||
// HSTS information is shared across all ports for a given host.
|
||||
nsCAutoString host;
|
||||
rv = GetHost(aURI, host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// We want all apps to share HSTS state, so this is one of the few places
|
||||
// where we do not silo persistent state by extended origin.
|
||||
return securityManager->GetNoAppCodebasePrincipal(uri, aPrincipal);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsStrictTransportSecurityService::SetStsState(nsIURI* aSourceURI,
|
||||
int64_t maxage,
|
||||
@ -135,13 +152,14 @@ nsStrictTransportSecurityService::SetStsState(nsIURI* aSourceURI,
|
||||
|
||||
// Expire time is millis from now. Since STS max-age is in seconds, and
|
||||
// PR_Now() is in micros, must equalize the units at milliseconds.
|
||||
int64_t expiretime = (PR_Now() / 1000) + (maxage * 1000);
|
||||
int64_t expiretime = (PR_Now() / PR_USEC_PER_MSEC) +
|
||||
(maxage * PR_MSEC_PER_SEC);
|
||||
|
||||
// record entry for this host with max-age in the permissions manager
|
||||
STSLOG(("STS: maxage permission SET, adding permission\n"));
|
||||
nsresult rv = AddPermission(aSourceURI,
|
||||
STS_PERMISSION,
|
||||
(uint32_t) nsIPermissionManager::ALLOW_ACTION,
|
||||
(uint32_t) STS_SET,
|
||||
(uint32_t) nsIPermissionManager::EXPIRE_TIME,
|
||||
expiretime);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -151,7 +169,7 @@ nsStrictTransportSecurityService::SetStsState(nsIURI* aSourceURI,
|
||||
STSLOG(("STS: subdomains permission SET, adding permission\n"));
|
||||
rv = AddPermission(aSourceURI,
|
||||
STS_SUBDOMAIN_PERMISSION,
|
||||
(uint32_t) nsIPermissionManager::ALLOW_ACTION,
|
||||
(uint32_t) STS_SET,
|
||||
(uint32_t) nsIPermissionManager::EXPIRE_TIME,
|
||||
expiretime);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -319,6 +337,26 @@ nsStrictTransportSecurityService::IsStsHost(const char* aHost, bool* aResult)
|
||||
return IsStsURI(uri, aResult);
|
||||
}
|
||||
|
||||
int STSPreloadCompare(const void *key, const void *entry)
|
||||
{
|
||||
const char *keyStr = (const char *)key;
|
||||
const nsSTSPreload *preloadEntry = (const nsSTSPreload *)entry;
|
||||
return strcmp(keyStr, preloadEntry->mHost);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsStrictTransportSecurityService::IsStsURI(nsIURI* aURI, bool* aResult)
|
||||
{
|
||||
@ -326,19 +364,134 @@ nsStrictTransportSecurityService::IsStsURI(nsIURI* aURI, bool* aResult)
|
||||
// manager is used and it's not threadsafe.
|
||||
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_UNEXPECTED);
|
||||
|
||||
nsresult rv;
|
||||
uint32_t permExact, permGeneral;
|
||||
// If this domain has the forcehttps permission, this is an STS host.
|
||||
rv = TestPermission(aURI, STS_PERMISSION, &permExact, true);
|
||||
// set default in case if we can't find any STS information
|
||||
*aResult = false;
|
||||
|
||||
nsCAutoString host;
|
||||
nsresult rv = GetHost(aURI, host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// If any super-domain has the includeSubdomains permission, this is an
|
||||
// STS host.
|
||||
rv = TestPermission(aURI, STS_SUBDOMAIN_PERMISSION, &permGeneral, false);
|
||||
const nsSTSPreload *preload = nullptr;
|
||||
nsSTSHostEntry *pbEntry = nullptr;
|
||||
|
||||
if (mInPrivateMode) {
|
||||
pbEntry = mPrivateModeHostTable.GetEntry(host.get());
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
rv = GetPrincipalForURI(aURI, getter_AddRefs(principal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*aResult = ((permExact == nsIPermissionManager::ALLOW_ACTION) ||
|
||||
(permGeneral == nsIPermissionManager::ALLOW_ACTION));
|
||||
PRUint32 permMgrPermission;
|
||||
rv = mPermMgr->TestExactPermissionFromPrincipal(principal, STS_PERMISSION,
|
||||
&permMgrPermission);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// First check the exact host. This involves first checking for an entry in
|
||||
// the private browsing table. If that entry exists, we don't want to check
|
||||
// in either the permission manager or the preload list. We only want to use
|
||||
// the stored permission if it is not a knockout entry, however.
|
||||
// Additionally, if it is a knockout entry, we want to stop looking for data
|
||||
// on the host, because the knockout entry indicates "we have no information
|
||||
// regarding the sts status of this host".
|
||||
if (pbEntry && pbEntry->mStsPermission != STS_UNSET) {
|
||||
STSLOG(("Found private browsing table entry for %s", host.get()));
|
||||
if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) {
|
||||
*aResult = true;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
// Next we look in the permission manager. Same story here regarding
|
||||
// knockout entries.
|
||||
else if (permMgrPermission != STS_UNSET) {
|
||||
STSLOG(("Found permission manager entry for %s", host.get()));
|
||||
if (permMgrPermission == STS_SET) {
|
||||
*aResult = true;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
// Finally look in the preloaded list. This is the exact host,
|
||||
// so if an entry exists at all, this host is sts.
|
||||
else if (GetPreloadListEntry(host.get())) {
|
||||
STSLOG(("%s is a preloaded STS host", host.get()));
|
||||
*aResult = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Used for testing permissions as we walk up the domain tree.
|
||||
nsCOMPtr<nsIURI> domainWalkURI;
|
||||
nsCOMPtr<nsIPrincipal> domainWalkPrincipal;
|
||||
const char *subdomain;
|
||||
|
||||
STSLOG(("no HSTS data for %s found, walking up domain", host.get()));
|
||||
PRUint32 offset = 0;
|
||||
for (offset = host.FindChar('.', offset) + 1;
|
||||
offset > 0;
|
||||
offset = host.FindChar('.', offset) + 1) {
|
||||
|
||||
subdomain = host.get() + offset;
|
||||
|
||||
// If we get an empty string, don't continue.
|
||||
if (strlen(subdomain) < 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mInPrivateMode) {
|
||||
pbEntry = mPrivateModeHostTable.GetEntry(subdomain);
|
||||
}
|
||||
|
||||
// normalize all URIs with https://
|
||||
rv = NS_NewURI(getter_AddRefs(domainWalkURI),
|
||||
NS_LITERAL_CSTRING("https://") + Substring(host, offset));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = GetPrincipalForURI(domainWalkURI, getter_AddRefs(domainWalkPrincipal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal,
|
||||
STS_PERMISSION,
|
||||
&permMgrPermission);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Do the same thing as with the exact host, except now we're looking at
|
||||
// ancestor domains of the original host. So, we have to look at the
|
||||
// include subdomains permissions (although we still have to check for the
|
||||
// STS_PERMISSION first to check that this is an sts host and not a
|
||||
// knockout entry - and again, if it is a knockout entry, we stop looking
|
||||
// for data on it and skip to the next higher up ancestor domain).
|
||||
if (pbEntry && pbEntry->mStsPermission != STS_UNSET) {
|
||||
STSLOG(("Found private browsing table entry for %s", subdomain));
|
||||
if (!pbEntry->IsExpired() && pbEntry->mStsPermission == STS_SET) {
|
||||
*aResult = pbEntry->mIncludeSubdomains;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (permMgrPermission != STS_UNSET) {
|
||||
STSLOG(("Found permission manager entry for %s", subdomain));
|
||||
if (permMgrPermission == STS_SET) {
|
||||
PRUint32 subdomainPermission;
|
||||
rv = mPermMgr->TestExactPermissionFromPrincipal(domainWalkPrincipal,
|
||||
STS_SUBDOMAIN_PERMISSION,
|
||||
&subdomainPermission);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
*aResult = (subdomainPermission == STS_SET);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This is an ancestor, so if we get a match, we have to check if the
|
||||
// preloaded entry includes subdomains.
|
||||
else if ((preload = GetPreloadListEntry(subdomain)) != nullptr) {
|
||||
if (preload->mIncludeSubdomains) {
|
||||
STSLOG(("%s is a preloaded STS host", subdomain));
|
||||
*aResult = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
STSLOG(("no HSTS data for %s found, walking up domain", subdomain));
|
||||
}
|
||||
|
||||
// Use whatever we ended up with, which defaults to false.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -429,7 +582,7 @@ nsStrictTransportSecurityService::AddPermission(nsIURI *aURI,
|
||||
nsCAutoString host;
|
||||
nsresult rv = GetHost(aURI, host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
STSLOG(("AddPermission for entry for for %s", host.get()));
|
||||
STSLOG(("AddPermission for entry for %s", host.get()));
|
||||
|
||||
// Update in mPrivateModeHostTable only, so any changes will be rolled
|
||||
// back when exiting private mode.
|
||||
@ -444,7 +597,10 @@ nsStrictTransportSecurityService::AddPermission(nsIURI *aURI,
|
||||
// PutEntry returns an existing entry if there already is one, or it
|
||||
// creates a new one if there isn't.
|
||||
nsSTSHostEntry* entry = mPrivateModeHostTable.PutEntry(host.get());
|
||||
STSLOG(("Created private mode entry for for %s", host.get()));
|
||||
if (!entry) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
STSLOG(("Created private mode entry for %s", host.get()));
|
||||
|
||||
// AddPermission() will be called twice if the STS header encountered has
|
||||
// includeSubdomains (first for the main permission and second for the
|
||||
@ -454,14 +610,13 @@ nsStrictTransportSecurityService::AddPermission(nsIURI *aURI,
|
||||
if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) {
|
||||
entry->mIncludeSubdomains = true;
|
||||
}
|
||||
// for the case where PutEntry() returned an existing host entry, make
|
||||
// sure it's not set as deleted (which might have happened in the past).
|
||||
entry->mDeleted = false;
|
||||
else if (strcmp(aType, STS_PERMISSION) == 0) {
|
||||
entry->mStsPermission = aPermission;
|
||||
}
|
||||
|
||||
// Also refresh the expiration time.
|
||||
entry->mExpireTime = aExpireTime;
|
||||
entry->SetExpireTime(aExpireTime);
|
||||
return NS_OK;
|
||||
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -469,9 +624,10 @@ nsStrictTransportSecurityService::RemovePermission(const nsCString &aHost,
|
||||
const char *aType)
|
||||
{
|
||||
// Build up a principal for use with the permission manager.
|
||||
// normalize all URIs with https://
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri),
|
||||
NS_LITERAL_CSTRING("http://") + aHost);
|
||||
NS_LITERAL_CSTRING("https://") + aHost);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
@ -480,149 +636,31 @@ nsStrictTransportSecurityService::RemovePermission(const nsCString &aHost,
|
||||
|
||||
if (!mInPrivateMode) {
|
||||
// Not in private mode: remove permissions persistently.
|
||||
return mPermMgr->RemoveFromPrincipal(principal, aType);
|
||||
// This means setting the permission to STS_KNOCKOUT in case
|
||||
// this host is on the preload list (so we can override it).
|
||||
return mPermMgr->AddFromPrincipal(principal, aType,
|
||||
STS_KNOCKOUT,
|
||||
nsIPermissionManager::EXPIRE_NEVER, 0);
|
||||
}
|
||||
|
||||
// Make changes in mPrivateModeHostTable only, so any changes will be
|
||||
// rolled back when exiting private mode.
|
||||
nsSTSHostEntry* entry = mPrivateModeHostTable.GetEntry(aHost.get());
|
||||
|
||||
// Check to see if there's STS data stored for this host in the
|
||||
// permission manager (probably set outside private mode).
|
||||
uint32_t permmgrValue;
|
||||
rv = mPermMgr->TestExactPermissionFromPrincipal(principal, aType,
|
||||
&permmgrValue);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// If there is STS data in the permission manager, store a "deleted" mask
|
||||
// for the permission in mPrivateModeHostTable (either update
|
||||
// mPrivateModeHostTable to have the deleted mask, or add one).
|
||||
// This is because we don't want removals that happen in private mode to
|
||||
// be reflected when private mode is exited -- but while in private mode
|
||||
// we still want the effect of the removal.
|
||||
if (permmgrValue != nsIPermissionManager::UNKNOWN_ACTION) {
|
||||
// if there's no entry in mPrivateModeHostTable, we have to make one.
|
||||
if (!entry) {
|
||||
entry = mPrivateModeHostTable.PutEntry(aHost.get());
|
||||
if (!entry) {
|
||||
entry = mPrivateModeHostTable.PutEntry(aHost.get());
|
||||
STSLOG(("Created private mode deleted mask for for %s", aHost.get()));
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
entry->mDeleted = true;
|
||||
STSLOG(("Created private mode deleted mask for %s", aHost.get()));
|
||||
}
|
||||
|
||||
if (strcmp(aType, STS_PERMISSION) == 0) {
|
||||
entry->mStsPermission = STS_KNOCKOUT;
|
||||
}
|
||||
else if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) {
|
||||
entry->mIncludeSubdomains = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Otherwise, permission doesn't exist in the real permission manager, so
|
||||
// there's nothing to "pretend" to delete. I'ts ok to delete any copy in
|
||||
// mPrivateModeHostTable.
|
||||
if (entry) mPrivateModeHostTable.RawRemoveEntry(entry);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsStrictTransportSecurityService::TestPermission(nsIURI *aURI,
|
||||
const char *aType,
|
||||
uint32_t *aPermission,
|
||||
bool testExact)
|
||||
{
|
||||
// set default for if we can't find any STS information
|
||||
*aPermission = nsIPermissionManager::UNKNOWN_ACTION;
|
||||
|
||||
if (!mInPrivateMode) {
|
||||
// if not in private mode, just delegate to the permission manager.
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
nsresult rv = GetPrincipalForURI(aURI, getter_AddRefs(principal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (testExact)
|
||||
return mPermMgr->TestExactPermissionFromPrincipal(principal, aType, aPermission);
|
||||
else
|
||||
return mPermMgr->TestPermissionFromPrincipal(principal, aType, aPermission);
|
||||
}
|
||||
|
||||
nsCAutoString host;
|
||||
nsresult rv = GetHost(aURI, host);
|
||||
if (NS_FAILED(rv)) return NS_OK;
|
||||
|
||||
nsSTSHostEntry *entry;
|
||||
uint32_t actualExactPermission;
|
||||
uint32_t offset = 0;
|
||||
int64_t now = PR_Now() / 1000;
|
||||
|
||||
// Used for testing permissions as we walk up the domain tree.
|
||||
nsCOMPtr<nsIURI> domainWalkURI;
|
||||
|
||||
// In parallel, loop over private mode cache and also the real permission
|
||||
// manager--ignoring any masked as "deleted" in the local cache. We have
|
||||
// to do this here since the most specific permission in *either* the
|
||||
// permission manager or mPrivateModeHostTable should be used.
|
||||
do {
|
||||
entry = mPrivateModeHostTable.GetEntry(host.get() + offset);
|
||||
STSLOG(("Checking PM Table entry and permmgr for %s", host.get()+offset));
|
||||
|
||||
// flag as deleted any entries encountered that have expired. We only
|
||||
// flag the nsSTSHostEntry because there could be some data in the
|
||||
// permission manager that -- if not in private mode -- would have been
|
||||
// overwritten by newly encountered STS data.
|
||||
if (entry && (now > entry->mExpireTime)) {
|
||||
STSLOG(("Deleting expired PM Table entry for %s", host.get()+offset));
|
||||
entry->mDeleted = true;
|
||||
entry->mIncludeSubdomains = false;
|
||||
}
|
||||
|
||||
rv = NS_NewURI(getter_AddRefs(domainWalkURI),
|
||||
NS_LITERAL_CSTRING("http://") + Substring(host, offset));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal;
|
||||
nsresult rv = GetPrincipalForURI(domainWalkURI, getter_AddRefs(principal));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mPermMgr->TestExactPermissionFromPrincipal(principal, aType,
|
||||
&actualExactPermission);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// There are three cases as we walk up the hostname testing
|
||||
// permissions:
|
||||
// 1. There's no entry in mPrivateModeHostTable for this host; rely
|
||||
// on data in the permission manager
|
||||
if (!entry) {
|
||||
if (actualExactPermission != nsIPermissionManager::UNKNOWN_ACTION) {
|
||||
// no cached data but a permission in the permission manager so use
|
||||
// it and stop looking.
|
||||
*aPermission = actualExactPermission;
|
||||
STSLOG(("no PM Table entry for %s, using permmgr", host.get()+offset));
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 2. There's a "deleted" mask in mPrivateModeHostTable for this host
|
||||
// or we're looking for includeSubdomain information and it's not set:
|
||||
// any data in the permission manager must be ignored, since the
|
||||
// permission would have been deleted if not in private mode.
|
||||
else if (entry->mDeleted || (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0
|
||||
&& !entry->mIncludeSubdomains)) {
|
||||
STSLOG(("no entry at all for %s, walking up", host.get()+offset));
|
||||
// keep looking
|
||||
}
|
||||
// 3. There's a non-deleted entry in mPrivateModeHostTable for this
|
||||
// host, so it should be used.
|
||||
else {
|
||||
// All STS permissions' values are ALLOW_ACTION or they are not
|
||||
// known (as in, not set or turned off).
|
||||
*aPermission = nsIPermissionManager::ALLOW_ACTION;
|
||||
STSLOG(("PM Table entry for %s: forcing", host.get()+offset));
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't continue walking up the host segments if the test was for an
|
||||
// exact match only.
|
||||
if (testExact) break;
|
||||
|
||||
STSLOG(("no PM Table entry or permmgr data for %s, walking up domain",
|
||||
host.get()+offset));
|
||||
// walk up the host segments
|
||||
offset = host.FindChar('.', offset) + 1;
|
||||
} while (offset > 0);
|
||||
|
||||
// Use whatever we ended up with, which defaults to UNKNOWN_ACTION.
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -29,12 +29,15 @@
|
||||
// permissions.
|
||||
//
|
||||
// Each nsSTSHostEntry contains:
|
||||
// - Expiry time
|
||||
// - Deleted flag (boolean, default false)
|
||||
// - Subdomains flag (boolean, default false)
|
||||
// - Expiry time (PRTime, milliseconds)
|
||||
// - Expired flag (bool, default false)
|
||||
// - STS permission (uint32_t, default STS_UNSET)
|
||||
// - Include subdomains flag (bool, default false)
|
||||
//
|
||||
// Note: the subdomains flag has no meaning if the STS permission is STS_UNSET.
|
||||
//
|
||||
// The existence of the nsSTSHostEntry implies STS state is set for the given
|
||||
// host -- unless the deleted flag is set, in which case not only is the STS
|
||||
// host -- unless the expired flag is set, in which case not only is the STS
|
||||
// state not set for the host, but any permission actually present in the
|
||||
// permission manager should be ignored.
|
||||
//
|
||||
@ -58,9 +61,10 @@ class nsSTSHostEntry : public PLDHashEntryHdr
|
||||
explicit nsSTSHostEntry(const nsSTSHostEntry& toCopy);
|
||||
|
||||
nsCString mHost;
|
||||
int64_t mExpireTime;
|
||||
bool mDeleted;
|
||||
bool mIncludeSubdomains;
|
||||
PRTime mExpireTime;
|
||||
uint32_t mStsPermission;
|
||||
bool mExpired;
|
||||
bool mIncludeSubdomains;
|
||||
|
||||
// Hash methods
|
||||
typedef const char* KeyType;
|
||||
@ -86,11 +90,36 @@ class nsSTSHostEntry : public PLDHashEntryHdr
|
||||
return PL_DHashStringKey(nullptr, aKey);
|
||||
}
|
||||
|
||||
void SetExpireTime(PRTime aExpireTime)
|
||||
{
|
||||
mExpireTime = aExpireTime;
|
||||
mExpired = false;
|
||||
}
|
||||
|
||||
bool IsExpired()
|
||||
{
|
||||
// If mExpireTime is 0, this entry never expires (this is the case for
|
||||
// knockout entries).
|
||||
// If we've already expired or we never expire, return early.
|
||||
if (mExpired || mExpireTime == 0) {
|
||||
return mExpired;
|
||||
}
|
||||
|
||||
PRTime now = PR_Now() / PR_USEC_PER_MSEC;
|
||||
if (now > mExpireTime) {
|
||||
mExpired = true;
|
||||
}
|
||||
|
||||
return mExpired;
|
||||
}
|
||||
|
||||
// force the hashtable to use the copy constructor.
|
||||
enum { ALLOW_MEMMOVE = false };
|
||||
};
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class nsSTSPreload;
|
||||
|
||||
class nsStrictTransportSecurityService : public nsIStrictTransportSecurityService
|
||||
, public nsIObserver
|
||||
{
|
||||
@ -105,8 +134,10 @@ public:
|
||||
|
||||
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);
|
||||
const nsSTSPreload *GetPreloadListEntry(const char *aHost);
|
||||
|
||||
// private-mode-preserving permission manager overlay functions
|
||||
nsresult AddPermission(nsIURI *aURI,
|
||||
@ -116,10 +147,6 @@ private:
|
||||
int64_t aExpireTime);
|
||||
nsresult RemovePermission(const nsCString &aHost,
|
||||
const char *aType);
|
||||
nsresult TestPermission(nsIURI *aURI,
|
||||
const char *aType,
|
||||
uint32_t *aPermission,
|
||||
bool testExact);
|
||||
|
||||
// cached services
|
||||
nsCOMPtr<nsIPermissionManager> mPermMgr;
|
||||
|
180
security/manager/ssl/tests/unit/test_sts_preloadlist.js
Normal file
180
security/manager/ssl/tests/unit/test_sts_preloadlist.js
Normal file
@ -0,0 +1,180 @@
|
||||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
var gPBService = Cc["@mozilla.org/privatebrowsing;1"]
|
||||
.getService(Ci.nsIPrivateBrowsingService);
|
||||
var gSTSService = Cc["@mozilla.org/stsservice;1"]
|
||||
.getService(Ci.nsIStrictTransportSecurityService);
|
||||
|
||||
function Observer() {}
|
||||
Observer.prototype = {
|
||||
observe: function(subject, topic, data) {
|
||||
run_next_test();
|
||||
}
|
||||
};
|
||||
|
||||
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"];
|
||||
|
||||
function cleanup() {
|
||||
Services.obs.removeObserver(gObserver, "private-browsing-transition-complete");
|
||||
gPBService.privateBrowsingEnabled = false;
|
||||
for (var host of hosts) {
|
||||
var uri = Services.io.newURI(host, null, null);
|
||||
gSTSService.removeStsState(uri);
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
do_register_cleanup(cleanup);
|
||||
Services.obs.addObserver(gObserver, "private-browsing-transition-complete", false);
|
||||
Services.prefs.setBoolPref("browser.privatebrowsing.keep_current_session", true);
|
||||
|
||||
add_test(test_part1);
|
||||
add_test(test_private_browsing1);
|
||||
add_test(test_private_browsing2);
|
||||
|
||||
run_next_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.
|
||||
// check that an entry at the beginning of the list is an sts host
|
||||
do_check_true(gSTSService.isStsHost("health.google.com"));
|
||||
|
||||
// check that a subdomain is an sts host (includeSubdomains is set)
|
||||
do_check_true(gSTSService.isStsHost("subdomain.health.google.com"));
|
||||
|
||||
// check that another subdomain is an sts host (includeSubdomains is set)
|
||||
do_check_true(gSTSService.isStsHost("a.b.c.subdomain.health.google.com"));
|
||||
|
||||
// check that an entry in the middle of the list is an sts host
|
||||
do_check_true(gSTSService.isStsHost("epoxate.com"));
|
||||
|
||||
// check that a subdomain is not an sts host (includeSubdomains is not set)
|
||||
do_check_false(gSTSService.isStsHost("subdomain.epoxate.com"));
|
||||
|
||||
// check that an entry at the end of the list is an sts host
|
||||
do_check_true(gSTSService.isStsHost("www.googlemail.com"));
|
||||
|
||||
// check that a subdomain is not an sts host (includeSubdomains is not set)
|
||||
do_check_false(gSTSService.isStsHost("a.subdomain.www.googlemail.com"));
|
||||
|
||||
// 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");
|
||||
do_check_false(gSTSService.isStsHost("keyerror.com"));
|
||||
do_check_false(gSTSService.isStsHost("subdomain.keyerror.com"));
|
||||
// check that processing another header (with max-age non-zero) will
|
||||
// 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);
|
||||
gSTSService.processStsHeader(uri, "max-age=0");
|
||||
do_check_true(gSTSService.isStsHost("kyps.net"));
|
||||
do_check_false(gSTSService.isStsHost("subdomain.kyps.net"));
|
||||
|
||||
var uri = Services.io.newURI("http://subdomain.cert.se", 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
|
||||
// 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"));
|
||||
|
||||
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"));
|
||||
|
||||
// test private browsing correctly interacts with removing preloaded sites
|
||||
gPBService.privateBrowsingEnabled = true;
|
||||
}
|
||||
|
||||
function test_private_browsing1() {
|
||||
// sanity - crypto.cat is preloaded, includeSubdomains set
|
||||
do_check_true(gSTSService.isStsHost("crypto.cat"));
|
||||
do_check_true(gSTSService.isStsHost("a.b.c.subdomain.crypto.cat"));
|
||||
|
||||
var uri = Services.io.newURI("http://crypto.cat", null, null);
|
||||
gSTSService.processStsHeader(uri, "max-age=0");
|
||||
do_check_false(gSTSService.isStsHost("crypto.cat"));
|
||||
do_check_false(gSTSService.isStsHost("a.b.subdomain.crypto.cat"));
|
||||
|
||||
// check adding it back in
|
||||
gSTSService.processStsHeader(uri, "max-age=1000");
|
||||
do_check_true(gSTSService.isStsHost("crypto.cat"));
|
||||
// but no includeSubdomains this time
|
||||
do_check_false(gSTSService.isStsHost("b.subdomain.crypto.cat"));
|
||||
|
||||
// do the hokey-pokey...
|
||||
gSTSService.processStsHeader(uri, "max-age=0");
|
||||
do_check_false(gSTSService.isStsHost("crypto.cat"));
|
||||
do_check_false(gSTSService.isStsHost("subdomain.crypto.cat"));
|
||||
|
||||
// TODO unfortunately we don't have a good way to know when an entry
|
||||
// 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);
|
||||
// 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"));
|
||||
|
||||
gPBService.privateBrowsingEnabled = false;
|
||||
}
|
||||
|
||||
function test_private_browsing2() {
|
||||
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"));
|
||||
|
||||
run_next_test();
|
||||
}
|
@ -11,3 +11,4 @@ skip-if = os == "android"
|
||||
[test_hmac.js]
|
||||
# Bug 676972: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
[test_sts_preloadlist.js]
|
||||
|
115
security/manager/tools/getHSTSPreloadList.py
Normal file
115
security/manager/tools/getHSTSPreloadList.py
Normal file
@ -0,0 +1,115 @@
|
||||
#!/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())
|
Loading…
Reference in New Issue
Block a user