зеркало из https://github.com/mozilla/pjs.git
bug 557598 - Support strict-transport-security (STS) in private browsing mode; r=ehsan,dveditz a=blocking-betaN+
This commit is contained in:
Родитель
90e8b49655
Коммит
deaa531dcc
|
@ -38,6 +38,7 @@
|
|||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIURI;
|
||||
interface nsIObserver;
|
||||
interface nsIHttpChannel;
|
||||
|
||||
[scriptable, uuid(16955eee-6c48-4152-9309-c42a465138a1)]
|
||||
|
|
|
@ -910,13 +910,6 @@ nsresult
|
|||
nsHttpChannel::ProcessSTSHeader()
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
// We need to check private browsing mode here since some permissions are
|
||||
// allowed to be tweaked when private browsing mode is enabled, but STS is
|
||||
// not allowed to operate at all in PBM.
|
||||
if (gHttpHandler->InPrivateBrowsingMode())
|
||||
return NS_OK;
|
||||
|
||||
PRBool isHttps = PR_FALSE;
|
||||
rv = mURI->SchemeIs("https", &isHttps);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "prprf.h"
|
||||
#include "nsCRTGlue.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsIPrivateBrowsingService.h"
|
||||
#include "nsISSLStatus.h"
|
||||
#include "nsISSLStatusProvider.h"
|
||||
#include "nsStrictTransportSecurityService.h"
|
||||
|
@ -61,7 +62,30 @@ PRLogModuleInfo *gSTSLog = PR_NewLogModule("nsSTSService");
|
|||
return NS_ERROR_FAILURE; \
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
nsSTSHostEntry::nsSTSHostEntry(const char* aHost)
|
||||
: mHost(aHost)
|
||||
, mExpireTime(0)
|
||||
, mDeleted(PR_FALSE)
|
||||
, mIncludeSubdomains(PR_FALSE)
|
||||
{
|
||||
}
|
||||
|
||||
nsSTSHostEntry::nsSTSHostEntry(const nsSTSHostEntry& toCopy)
|
||||
: mHost(toCopy.mHost)
|
||||
, mExpireTime(toCopy.mExpireTime)
|
||||
, mDeleted(toCopy.mDeleted)
|
||||
, mIncludeSubdomains(toCopy.mIncludeSubdomains)
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
nsStrictTransportSecurityService::nsStrictTransportSecurityService()
|
||||
: mInPrivateMode(PR_FALSE)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -69,7 +93,8 @@ nsStrictTransportSecurityService::~nsStrictTransportSecurityService()
|
|||
{
|
||||
}
|
||||
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS1(nsStrictTransportSecurityService,
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS2(nsStrictTransportSecurityService,
|
||||
nsIObserver,
|
||||
nsIStrictTransportSecurityService)
|
||||
|
||||
nsresult
|
||||
|
@ -80,6 +105,19 @@ nsStrictTransportSecurityService::Init()
|
|||
mPermMgr = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
|
||||
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);
|
||||
|
||||
mObserverService = mozilla::services::GetObserverService();
|
||||
if (mObserverService)
|
||||
mObserverService->AddObserver(this, NS_PRIVATE_BROWSING_SWITCH_TOPIC, PR_FALSE);
|
||||
|
||||
if (mInPrivateMode && !mPrivateModeHostTable.Init())
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -112,25 +150,31 @@ nsStrictTransportSecurityService::SetStsState(nsIURI* aSourceURI,
|
|||
PRInt64 expiretime = (PR_Now() / 1000) + (maxage * 1000);
|
||||
|
||||
// record entry for this host with max-age in the permissions manager
|
||||
mPermMgr->Add(aSourceURI, STS_PERMISSION,
|
||||
STSLOG(("STS: maxage permission SET, adding permission\n"));
|
||||
nsresult rv = AddPermission(aSourceURI,
|
||||
STS_PERMISSION,
|
||||
(PRUint32) nsIPermissionManager::ALLOW_ACTION,
|
||||
(PRUint32) nsIPermissionManager::EXPIRE_TIME,
|
||||
expiretime);
|
||||
STSLOG(("STS: set maxage permission\n"));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (includeSubdomains) {
|
||||
// record entry for this host with include subdomains in the permissions manager
|
||||
mPermMgr->Add(aSourceURI, STS_SUBDOMAIN_PERMISSION,
|
||||
STSLOG(("STS: subdomains permission SET, adding permission\n"));
|
||||
rv = AddPermission(aSourceURI,
|
||||
STS_SUBDOMAIN_PERMISSION,
|
||||
(PRUint32) nsIPermissionManager::ALLOW_ACTION,
|
||||
(PRUint32) nsIPermissionManager::EXPIRE_TIME,
|
||||
expiretime);
|
||||
STSLOG(("STS: set subdomains permission\n"));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else { // !includeSubdomains
|
||||
nsCAutoString hostname;
|
||||
nsresult rv = GetHost(aSourceURI, hostname);
|
||||
rv = GetHost(aSourceURI, hostname);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mPermMgr->Remove(hostname, STS_SUBDOMAIN_PERMISSION);
|
||||
STSLOG(("STS: subdomains permission UNSET, removing any existing ones\n"));
|
||||
rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -146,10 +190,12 @@ nsStrictTransportSecurityService::RemoveStsState(nsIURI* aURI)
|
|||
nsresult rv = GetHost(aURI, hostname);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mPermMgr->Remove(hostname, STS_PERMISSION);
|
||||
rv = RemovePermission(hostname, STS_PERMISSION);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
STSLOG(("STS: deleted maxage permission\n"));
|
||||
|
||||
mPermMgr->Remove(hostname, STS_SUBDOMAIN_PERMISSION);
|
||||
rv = RemovePermission(hostname, STS_SUBDOMAIN_PERMISSION);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
STSLOG(("STS: deleted subdomains permission\n"));
|
||||
|
||||
return NS_OK;
|
||||
|
@ -295,11 +341,12 @@ nsStrictTransportSecurityService::IsStsURI(nsIURI* aURI, PRBool* aResult)
|
|||
nsresult rv;
|
||||
PRUint32 permExact, permGeneral;
|
||||
// If this domain has the forcehttps permission, this is an STS host.
|
||||
rv = mPermMgr->TestExactPermission(aURI, STS_PERMISSION, &permExact);
|
||||
rv = TestPermission(aURI, STS_PERMISSION, &permExact, PR_TRUE);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// If any super-domain has the includeSubdomains permission, this is an
|
||||
// STS host.
|
||||
rv = mPermMgr->TestPermission(aURI, STS_SUBDOMAIN_PERMISSION, &permGeneral);
|
||||
rv = TestPermission(aURI, STS_SUBDOMAIN_PERMISSION, &permGeneral, PR_FALSE);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*aResult = ((permExact == nsIPermissionManager::ALLOW_ACTION) ||
|
||||
|
@ -341,3 +388,239 @@ nsStrictTransportSecurityService::ShouldIgnoreStsHeader(nsISupports* aSecurityIn
|
|||
*aResult = tlsIsBroken;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// nsStrictTransportSecurityService::nsIObserver
|
||||
//------------------------------------------------------------
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsStrictTransportSecurityService::Observe(nsISupports *subject,
|
||||
const char *topic,
|
||||
const PRUnichar *data)
|
||||
{
|
||||
if (strcmp(topic, NS_PRIVATE_BROWSING_SWITCH_TOPIC) == 0) {
|
||||
if(NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).Equals(data)) {
|
||||
// Indication to start recording stuff locally and not writing changes
|
||||
// out to the permission manager.
|
||||
|
||||
if (!mPrivateModeHostTable.IsInitialized()
|
||||
&& !mPrivateModeHostTable.Init()) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
mInPrivateMode = PR_TRUE;
|
||||
}
|
||||
else if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(data)) {
|
||||
mPrivateModeHostTable.Clear();
|
||||
mInPrivateMode = PR_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// Functions to overlay the permission manager calls in case
|
||||
// we're in private browsing mode.
|
||||
//------------------------------------------------------------
|
||||
nsresult
|
||||
nsStrictTransportSecurityService::AddPermission(nsIURI *aURI,
|
||||
const char *aType,
|
||||
PRUint32 aPermission,
|
||||
PRUint32 aExpireType,
|
||||
PRInt64 aExpireTime)
|
||||
{
|
||||
// Private mode doesn't address user-set (EXPIRE_NEVER) permissions: let
|
||||
// those be stored persistently.
|
||||
if (!mInPrivateMode || aExpireType == nsIPermissionManager::EXPIRE_NEVER) {
|
||||
// Not in private mode, or manually-set permission
|
||||
return mPermMgr->Add(aURI, aType, aPermission, aExpireType, aExpireTime);
|
||||
}
|
||||
|
||||
nsCAutoString host;
|
||||
nsresult rv = GetHost(aURI, host);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
STSLOG(("AddPermission for entry for for %s", host.get()));
|
||||
|
||||
// Update in mPrivateModeHostTable only, so any changes will be rolled
|
||||
// back when exiting private mode.
|
||||
|
||||
// Note: EXPIRE_NEVER permissions should trump anything that shows up in
|
||||
// the HTTP header, so if there's an EXPIRE_NEVER permission already
|
||||
// don't store anything new.
|
||||
// Currently there's no way to get the type of expiry out of the
|
||||
// permission manager, but that's okay since there's nothing that stores
|
||||
// EXPIRE_NEVER permissions.
|
||||
|
||||
// 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()));
|
||||
|
||||
// AddPermission() will be called twice if the STS header encountered has
|
||||
// includeSubdomains (first for the main permission and second for the
|
||||
// subdomains permission). If AddPermission() gets called a second time
|
||||
// with the STS_SUBDOMAIN_PERMISSION, we just have to flip that bit in
|
||||
// the nsSTSHostEntry.
|
||||
if (strcmp(aType, STS_SUBDOMAIN_PERMISSION) == 0) {
|
||||
entry->mIncludeSubdomains = PR_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 = PR_FALSE;
|
||||
|
||||
// Also refresh the expiration time.
|
||||
entry->mExpireTime = aExpireTime;
|
||||
return NS_OK;
|
||||
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsStrictTransportSecurityService::RemovePermission(const nsCString &aHost,
|
||||
const char *aType)
|
||||
{
|
||||
if (!mInPrivateMode) {
|
||||
// Not in private mode: remove permissions persistently.
|
||||
return mPermMgr->Remove(aHost, aType);
|
||||
}
|
||||
|
||||
// Make changes in mPrivateModeHostTable only, so any changes will be
|
||||
// rolled back when exiting private mode.
|
||||
nsSTSHostEntry* entry = mPrivateModeHostTable.GetEntry(aHost.get());
|
||||
|
||||
// Build up an nsIURI for use with the permission manager.
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri),
|
||||
NS_LITERAL_CSTRING("http://") + aHost);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Check to see if there's STS data stored for this host in the
|
||||
// permission manager (probably set outside private mode).
|
||||
PRUint32 permmgrValue;
|
||||
rv = mPermMgr->TestExactPermission(uri, 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());
|
||||
STSLOG(("Created private mode deleted mask for for %s", aHost.get()));
|
||||
}
|
||||
entry->mDeleted = PR_TRUE;
|
||||
entry->mIncludeSubdomains = PR_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,
|
||||
PRUint32 *aPermission,
|
||||
PRBool 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.
|
||||
if (testExact)
|
||||
return mPermMgr->TestExactPermission(aURI, aType, aPermission);
|
||||
else
|
||||
return mPermMgr->TestPermission(aURI, aType, aPermission);
|
||||
}
|
||||
|
||||
nsCAutoString host;
|
||||
nsresult rv = GetHost(aURI, host);
|
||||
if (NS_FAILED(rv)) return NS_OK;
|
||||
|
||||
nsSTSHostEntry *entry;
|
||||
PRUint32 actualExactPermission;
|
||||
PRUint32 offset = 0;
|
||||
PRInt64 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 = PR_TRUE;
|
||||
entry->mIncludeSubdomains = PR_FALSE;
|
||||
}
|
||||
|
||||
rv = NS_NewURI(getter_AddRefs(domainWalkURI),
|
||||
NS_LITERAL_CSTRING("http://") + Substring(host, offset));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mPermMgr->TestExactPermission(domainWalkURI,
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -43,20 +43,93 @@
|
|||
#define __nsStrictTransportSecurityService_h__
|
||||
|
||||
#include "nsIStrictTransportSecurityService.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIPermissionManager.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIURI.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTHashtable.h"
|
||||
|
||||
// {16955eee-6c48-4152-9309-c42a465138a1}
|
||||
#define NS_STRICT_TRANSPORT_SECURITY_CID \
|
||||
{0x16955eee, 0x6c48, 0x4152, \
|
||||
{0x93, 0x09, 0xc4, 0x2a, 0x46, 0x51, 0x38, 0xa1} }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsSTSHostEntry - similar to the nsHostEntry class in
|
||||
// nsPermissionManager.cpp, but specific to private-mode caching of STS
|
||||
// permissions.
|
||||
//
|
||||
// Each nsSTSHostEntry contains:
|
||||
// - Expiry time
|
||||
// - Deleted flag (boolean, default PR_FALSE)
|
||||
// - Subdomains flag (boolean, default PR_FALSE)
|
||||
//
|
||||
// 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
|
||||
// state not set for the host, but any permission actually present in the
|
||||
// permission manager should be ignored.
|
||||
//
|
||||
// Note: Only one expiry time is stored since the subdomains and STS
|
||||
// permissions are both encountered at the same time in the HTTP header; if the
|
||||
// includeSubdomains directive isn't present in the header, it means to delete
|
||||
// the permission, so the subdomains flag in the nsSTSHostEntry means both that
|
||||
// the permission doesn't exist and any permission in the real permission
|
||||
// manager should be ignored since newer information about it has been
|
||||
// encountered in private browsing mode.
|
||||
//
|
||||
// Note: If there's a permission set by the user (EXPIRE_NEVER), STS is not set
|
||||
// for the host (including the subdomains permission) when the header is
|
||||
// encountered. Furthermore, any user-set permissions are stored persistently
|
||||
// and can't be shadowed.
|
||||
|
||||
class nsSTSHostEntry : public PLDHashEntryHdr
|
||||
{
|
||||
public:
|
||||
explicit nsSTSHostEntry(const char* aHost);
|
||||
explicit nsSTSHostEntry(const nsSTSHostEntry& toCopy);
|
||||
|
||||
nsCString mHost;
|
||||
PRInt64 mExpireTime;
|
||||
PRPackedBool mDeleted;
|
||||
PRPackedBool mIncludeSubdomains;
|
||||
|
||||
// Hash methods
|
||||
typedef const char* KeyType;
|
||||
typedef const char* KeyTypePointer;
|
||||
|
||||
KeyType GetKey() const
|
||||
{
|
||||
return mHost.get();
|
||||
}
|
||||
|
||||
PRBool KeyEquals(KeyTypePointer aKey) const
|
||||
{
|
||||
return !strcmp(mHost.get(), aKey);
|
||||
}
|
||||
|
||||
static KeyTypePointer KeyToPointer(KeyType aKey)
|
||||
{
|
||||
return aKey;
|
||||
}
|
||||
|
||||
static PLDHashNumber HashKey(KeyTypePointer aKey)
|
||||
{
|
||||
return PL_DHashStringKey(nsnull, aKey);
|
||||
}
|
||||
|
||||
// force the hashtable to use the copy constructor.
|
||||
enum { ALLOW_MEMMOVE = PR_FALSE };
|
||||
};
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class nsStrictTransportSecurityService : public nsIStrictTransportSecurityService
|
||||
, public nsIObserver
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIOBSERVER
|
||||
NS_DECL_NSISTRICTTRANSPORTSECURITYSERVICE
|
||||
|
||||
nsStrictTransportSecurityService();
|
||||
|
@ -67,7 +140,26 @@ private:
|
|||
nsresult GetHost(nsIURI *aURI, nsACString &aResult);
|
||||
nsresult SetStsState(nsIURI* aSourceURI, PRInt64 maxage, PRBool includeSubdomains);
|
||||
nsresult ProcessStsHeaderMutating(nsIURI* aSourceURI, char* aHeader);
|
||||
|
||||
// private-mode-preserving permission manager overlay functions
|
||||
nsresult AddPermission(nsIURI *aURI,
|
||||
const char *aType,
|
||||
PRUint32 aPermission,
|
||||
PRUint32 aExpireType,
|
||||
PRInt64 aExpireTime);
|
||||
nsresult RemovePermission(const nsCString &aHost,
|
||||
const char *aType);
|
||||
nsresult TestPermission(nsIURI *aURI,
|
||||
const char *aType,
|
||||
PRUint32 *aPermission,
|
||||
PRBool testExact);
|
||||
|
||||
// cached services
|
||||
nsCOMPtr<nsIPermissionManager> mPermMgr;
|
||||
nsCOMPtr<nsIObserverService> mObserverService;
|
||||
|
||||
PRBool mInPrivateMode;
|
||||
nsTHashtable<nsSTSHostEntry> mPrivateModeHostTable;
|
||||
};
|
||||
|
||||
#endif // __nsStrictTransportSecurityService_h__
|
||||
|
|
|
@ -49,8 +49,11 @@ _TEST_FILES = \
|
|||
plain_bootstrap.html^headers^ \
|
||||
subdom_bootstrap.html \
|
||||
subdom_bootstrap.html^headers^ \
|
||||
nosts_bootstrap.html \
|
||||
nosts_bootstrap.html^headers^ \
|
||||
verify.sjs \
|
||||
test_stricttransportsecurity.html \
|
||||
test_sts_privatebrowsing.html \
|
||||
$(NULL)
|
||||
|
||||
libs:: $(_TEST_FILES)
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<!-- ***** BEGIN LICENSE BLOCK *****
|
||||
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
-
|
||||
- The contents of this file are subject to the Mozilla Public License Version
|
||||
- 1.1 (the "License"); you may not use this file except in compliance with
|
||||
- the License. You may obtain a copy of the License at
|
||||
- http://www.mozilla.org/MPL/
|
||||
-
|
||||
- Software distributed under the License is distributed on an "AS IS" basis,
|
||||
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
- for the specific language governing rights and limitations under the
|
||||
- License.
|
||||
-
|
||||
- The Original Code is Strict-Transport-Security.
|
||||
-
|
||||
- The Initial Developer of the Original Code is
|
||||
- Mozilla Foundation.
|
||||
- Portions created by the Initial Developer are Copyright (C) 2010
|
||||
- the Initial Developer. All Rights Reserved.
|
||||
-
|
||||
- Contributor(s):
|
||||
- Sid Stamm <sid@mozilla.com>
|
||||
-
|
||||
- Alternatively, the contents of this file may be used under the terms of
|
||||
- either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
- in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
- of those above. If you wish to allow use of your version of this file only
|
||||
- under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
- use your version of this file under the terms of the MPL, indicate your
|
||||
- decision by deleting the provisions above and replace them with the notice
|
||||
- and other provisions required by the LGPL or the GPL. If you do not delete
|
||||
- the provisions above, a recipient may use your version of this file under
|
||||
- the terms of any one of the MPL, the GPL or the LGPL.
|
||||
-
|
||||
- ***** END LICENSE BLOCK ***** -->
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>STS test iframe</title>
|
||||
<script>
|
||||
var self = window;
|
||||
window.addEventListener("load", function() {
|
||||
self.parent.postMessage("BOOTSTRAP plain", "http://mochi.test:8888");
|
||||
}, false);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- This frame should be loaded over HTTPS to set the STS header. -->
|
||||
This frame was loaded using
|
||||
<script>
|
||||
document.write(document.location.protocol);
|
||||
</script>
|
||||
and set the STS header to force this site and allow subdomain upgrading.
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
Cache-Control: no-cache
|
|
@ -0,0 +1,269 @@
|
|||
<!-- ***** BEGIN LICENSE BLOCK *****
|
||||
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
-
|
||||
- The contents of this file are subject to the Mozilla Public License Version
|
||||
- 1.1 (the "License"); you may not use this file except in compliance with
|
||||
- the License. You may obtain a copy of the License at
|
||||
- http://www.mozilla.org/MPL/
|
||||
-
|
||||
- Software distributed under the License is distributed on an "AS IS" basis,
|
||||
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
- for the specific language governing rights and limitations under the
|
||||
- License.
|
||||
-
|
||||
- The Original Code is Strict-Transport-Security.
|
||||
-
|
||||
- The Initial Developer of the Original Code is
|
||||
- Mozilla Foundation.
|
||||
- Portions created by the Initial Developer are Copyright (C) 2010
|
||||
- the Initial Developer. All Rights Reserved.
|
||||
-
|
||||
- Contributor(s):
|
||||
- Sid Stamm <sid@mozilla.com>
|
||||
-
|
||||
- Alternatively, the contents of this file may be used under the terms of
|
||||
- either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
- in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
- of those above. If you wish to allow use of your version of this file only
|
||||
- under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
- use your version of this file under the terms of the MPL, indicate your
|
||||
- decision by deleting the provisions above and replace them with the notice
|
||||
- and other provisions required by the LGPL or the GPL. If you do not delete
|
||||
- the provisions above, a recipient may use your version of this file under
|
||||
- the terms of any one of the MPL, the GPL or the LGPL.
|
||||
-
|
||||
- ***** END LICENSE BLOCK ***** -->
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>opens additional content that should be converted to https</title>
|
||||
<script type="text/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
const STSPATH = "/tests/security/ssl/stricttransportsecurity";
|
||||
|
||||
const NUM_TEST_FRAMES = 4;
|
||||
var testframes = {
|
||||
'samedom':
|
||||
{'url': "http://example.com" + STSPATH + "/verify.sjs",
|
||||
'expected': {'plain': 'SECURE',
|
||||
'subdom': 'SECURE',
|
||||
'nosts': 'INSECURE'}},
|
||||
'subdom':
|
||||
{'url': "http://test1.example.com" + STSPATH + "/verify.sjs",
|
||||
'expected': {'plain': 'INSECURE',
|
||||
'subdom': 'SECURE',
|
||||
'nosts': 'INSECURE'}},
|
||||
'otherdom':
|
||||
{'url': "http://example.org" + STSPATH + "/verify.sjs",
|
||||
'expected': {'plain': 'INSECURE',
|
||||
'subdom': 'INSECURE',
|
||||
'nosts': 'INSECURE'}},
|
||||
'alreadysecure':
|
||||
{'url': "https://test2.example.com" + STSPATH + "/verify.sjs",
|
||||
'expected': {'plain': 'SECURE',
|
||||
'subdom': 'SECURE',
|
||||
'nosts': 'SECURE'}},
|
||||
};
|
||||
|
||||
// This is how many sub-tests (testframes) in each round.
|
||||
// When the round begins, this will be initialized.
|
||||
var testsleftinround = 0;
|
||||
var currentround = "";
|
||||
|
||||
var _PBSvc = null;
|
||||
var _PrefSvc = null;
|
||||
|
||||
function _getPBService() {
|
||||
if (_PBSvc)
|
||||
return _PBSvc;
|
||||
|
||||
// not all apps will have the private browsing service.
|
||||
try {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
_PBSvc = Components.classes["@mozilla.org/privatebrowsing;1"]
|
||||
.getService(Components.interfaces.nsIPrivateBrowsingService);
|
||||
return _PBSvc;
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
function _getPrefService() {
|
||||
if (_PrefSvc)
|
||||
return _PrefSvc;
|
||||
|
||||
// not all apps will have the private browsing service.
|
||||
try {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
_PrefSvc = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefService)
|
||||
.QueryInterface(Components.interfaces.nsIPrefBranch2);
|
||||
return _PrefSvc;
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
function startRound(round) {
|
||||
currentround = round;
|
||||
testsleftinround = NUM_TEST_FRAMES;
|
||||
dump("TESTS LEFT IN ROUND: " + testsleftinround + "\n");
|
||||
var frame = document.createElement("iframe");
|
||||
frame.setAttribute('id', 'ifr_bootstrap');
|
||||
frame.setAttribute('src', "https://example.com" + STSPATH +
|
||||
"/" + round + "_bootstrap.html");
|
||||
document.body.appendChild(frame);
|
||||
}
|
||||
|
||||
function loadVerifyFrames(round) {
|
||||
for (var test in testframes) {
|
||||
var frame = document.createElement("iframe");
|
||||
frame.setAttribute('id', 'ifr_' + test);
|
||||
frame.setAttribute('src', testframes[test].url + '?id=' + test);
|
||||
document.body.appendChild(frame);
|
||||
}
|
||||
}
|
||||
|
||||
/* Messages received are in this format:
|
||||
* (BOOTSTRAP|SECURE|INSECURE) testid
|
||||
* For example: "BOOTSTRAP subdom"
|
||||
* or: "INSECURE otherdom"
|
||||
*/
|
||||
function onMessageReceived(event) {
|
||||
|
||||
// otherwise, it's a test result
|
||||
var result = event.data.split(/\s+/);
|
||||
if (result.length != 2) {
|
||||
SimpleTest.ok(false, event.data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result[0] === "BOOTSTRAP") {
|
||||
loadVerifyFrames(currentround);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the result (SECURE/INSECURE) is expected for this round/test
|
||||
// combo
|
||||
dump_STSState();
|
||||
dump( "*** in ROUND " + currentround +
|
||||
", test " + result[1] +
|
||||
" is " + result[0] + "\n");
|
||||
SimpleTest.is(result[0], testframes[result[1]].expected[currentround],
|
||||
"in ROUND " + currentround +
|
||||
", test " + result[1]);
|
||||
testsleftinround--;
|
||||
|
||||
// if this round is complete...
|
||||
if (testsleftinround < 1) {
|
||||
dump("DONE WITH ROUND " + currentround + "\n");
|
||||
// remove all the iframes in the document
|
||||
document.body.removeChild(document.getElementById('ifr_bootstrap'));
|
||||
for (var test in testframes)
|
||||
document.body.removeChild(document.getElementById('ifr_' + test));
|
||||
currentround = "";
|
||||
|
||||
// And advance to the next test.
|
||||
// Defer this so it doesn't muck with the stack too much.
|
||||
SimpleTest.executeSoon(nextTest);
|
||||
}
|
||||
}
|
||||
|
||||
function test_sts_before_private_mode() {
|
||||
dump_STSState();
|
||||
dump("*** not in private browsing mode\n");
|
||||
startRound('plain');
|
||||
}
|
||||
|
||||
function test_sts_in_private_mode() {
|
||||
dump_STSState();
|
||||
dump("*** Entering private browsing mode\n");
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
_getPrefService().setBoolPref("browser.privatebrowsing.keep_current_session",
|
||||
true);
|
||||
_getPBService().privateBrowsingEnabled = true;
|
||||
dump("*** ... done\n");
|
||||
dump_STSState();
|
||||
startRound('subdom');
|
||||
}
|
||||
|
||||
function test_sts_after_exiting_private_mode() {
|
||||
dump_STSState();
|
||||
dump("*** Exiting private browsing mode\n");
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
_getPBService().privateBrowsingEnabled = false;
|
||||
_getPrefService().clearUserPref("browser.privatebrowsing.keep_current_session");
|
||||
dump("*** ... done\n");
|
||||
dump_STSState();
|
||||
startRound('nosts');
|
||||
}
|
||||
|
||||
function clean_up_sts_state() {
|
||||
// erase all signs that this test ran.
|
||||
dump("*** Cleaning up STS data.\n");
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
var thehost = ios.newURI("http://example.com", null, null);
|
||||
var stss = Cc["@mozilla.org/stsservice;1"]
|
||||
.getService(Ci.nsIStrictTransportSecurityService);
|
||||
stss.removeStsState(thehost);
|
||||
dump_STSState();
|
||||
SimpleTest.executeSoon(nextTest);
|
||||
}
|
||||
|
||||
function dump_STSState() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
var stss = Components.classes["@mozilla.org/stsservice;1"]
|
||||
.getService(Components.interfaces.nsIStrictTransportSecurityService);
|
||||
dump("*** State of example.com: " + stss.isStsHost("example.com") + "\n");
|
||||
}
|
||||
|
||||
// these are executed in the order presented.
|
||||
// 0. test that STS works before entering private browsing mode.
|
||||
// (load sts-bootstrapped "plain" tests)
|
||||
// ... clear any STS data ...
|
||||
// 1. test that STS works in private browsing mode
|
||||
// (load sts-bootstrapped "subdomain" tests)
|
||||
// 2. test that after exiting private browsing, STS data is forgotten
|
||||
// (verified with non-sts-bootstrapped pages)
|
||||
var tests = [];
|
||||
{ // skip these tests if there's no private mode support
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
if ("@mozilla.org/privatebrowsing;1" in Components.classes) {
|
||||
tests = [
|
||||
test_sts_before_private_mode,
|
||||
clean_up_sts_state,
|
||||
test_sts_in_private_mode,
|
||||
test_sts_after_exiting_private_mode,
|
||||
clean_up_sts_state,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (tests.length)
|
||||
SimpleTest.executeSoon(tests.shift());
|
||||
else
|
||||
SimpleTest.executeSoon(SimpleTest.finish);
|
||||
}
|
||||
|
||||
// listen for calls back from the sts-setting iframe and then
|
||||
// the verification frames.
|
||||
window.addEventListener("message", onMessageReceived, false);
|
||||
window.addEventListener('load', nextTest, false);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
This test will load some iframes and do some tests.
|
||||
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче