bug 557598 - Support strict-transport-security (STS) in private browsing mode; r=ehsan,dveditz a=blocking-betaN+

This commit is contained in:
Sid Stamm 2010-10-06 10:07:39 -07:00
Родитель 90e8b49655
Коммит deaa531dcc
8 изменённых файлов: 720 добавлений и 21 удалений

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

@ -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,
(PRUint32) nsIPermissionManager::ALLOW_ACTION,
(PRUint32) nsIPermissionManager::EXPIRE_TIME,
expiretime);
STSLOG(("STS: set subdomains permission\n"));
STSLOG(("STS: subdomains permission SET, adding permission\n"));
rv = AddPermission(aSourceURI,
STS_SUBDOMAIN_PERMISSION,
(PRUint32) nsIPermissionManager::ALLOW_ACTION,
(PRUint32) nsIPermissionManager::EXPIRE_TIME,
expiretime);
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>