mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
bug 664284: Add HSTS support for websockets. r=mcmanus
This commit is contained in:
parent
6d7a2597ed
commit
ea03e8ad77
@ -304,6 +304,7 @@ nsWebSocket::OnStart(nsISupports *aContext)
|
||||
}
|
||||
|
||||
mChannel->GetExtensions(mEstablishedExtensions);
|
||||
UpdateURI();
|
||||
|
||||
SetReadyState(nsIMozWebSocket::OPEN);
|
||||
return NS_OK;
|
||||
@ -1040,6 +1041,27 @@ nsWebSocket::DontKeepAliveAnyMore()
|
||||
mCheckMustKeepAlive = false;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsWebSocket::UpdateURI()
|
||||
{
|
||||
// Check for Redirections
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCAutoString spec;
|
||||
rv = uri->GetSpec(spec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
CopyUTF8toUTF16(spec, mEffectiveURL);
|
||||
|
||||
bool isWSS = false;
|
||||
rv = uri->SchemeIs("wss", &isWSS);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mSecure = isWSS ? true : false;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWebSocket::RemoveEventListener(const nsAString& aType,
|
||||
nsIDOMEventListener* aListener,
|
||||
@ -1081,7 +1103,11 @@ nsWebSocket::AddEventListener(const nsAString& aType,
|
||||
NS_IMETHODIMP
|
||||
nsWebSocket::GetUrl(nsAString& aURL)
|
||||
{
|
||||
aURL = mOriginalURL;
|
||||
if (mEffectiveURL.IsEmpty()) {
|
||||
aURL = mOriginalURL;
|
||||
} else {
|
||||
aURL = mEffectiveURL;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -147,6 +147,8 @@ protected:
|
||||
// (and possibly collected).
|
||||
void DontKeepAliveAnyMore();
|
||||
|
||||
nsresult UpdateURI();
|
||||
|
||||
nsCOMPtr<nsIWebSocketChannel> mChannel;
|
||||
|
||||
nsRefPtr<nsDOMEventListenerWrapper> mOnOpenListener;
|
||||
@ -156,6 +158,7 @@ protected:
|
||||
|
||||
// related to the WebSocket constructor steps
|
||||
nsString mOriginalURL;
|
||||
nsString mEffectiveURL; // after redirects
|
||||
bool mSecure; // if true it is using SSL and the wss scheme,
|
||||
// otherwise it is using the ws scheme with no SSL
|
||||
|
||||
|
@ -15,22 +15,19 @@ def web_socket_do_extra_handshake(request):
|
||||
|
||||
if request.ws_protocol == "test-2.1":
|
||||
time.sleep(3)
|
||||
pass
|
||||
elif request.ws_protocol == "test-9":
|
||||
time.sleep(3)
|
||||
pass
|
||||
elif request.ws_protocol == "test-10":
|
||||
time.sleep(3)
|
||||
pass
|
||||
elif request.ws_protocol == "test-19":
|
||||
raise ValueError('Aborting (test-19)')
|
||||
elif request.ws_protocol == "test-20" or request.ws_protocol == "test-17":
|
||||
time.sleep(3)
|
||||
pass
|
||||
elif request.ws_protocol == "test-22":
|
||||
# The timeout is 5 seconds
|
||||
time.sleep(13)
|
||||
pass
|
||||
elif request.ws_protocol == "test-41b":
|
||||
request.sts = "max-age=100"
|
||||
else:
|
||||
pass
|
||||
|
||||
|
@ -120,7 +120,7 @@ function ignoreError(e)
|
||||
{
|
||||
}
|
||||
|
||||
function CreateTestWS(ws_location, ws_protocol)
|
||||
function CreateTestWS(ws_location, ws_protocol, no_increment_test)
|
||||
{
|
||||
var ws;
|
||||
|
||||
@ -134,7 +134,7 @@ function CreateTestWS(ws_location, ws_protocol)
|
||||
|
||||
ws._testNumber = current_test;
|
||||
ws._receivedCloseEvent = false;
|
||||
ok(true, "added testNumber: " + ws._testNumber +"\n");
|
||||
ok(true, "Created websocket for test " + ws._testNumber +"\n");
|
||||
|
||||
ws.onerror = function(e)
|
||||
{
|
||||
@ -149,7 +149,9 @@ function CreateTestWS(ws_location, ws_protocol)
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
current_test++;
|
||||
if (!no_increment_test) {
|
||||
current_test++;
|
||||
}
|
||||
}
|
||||
|
||||
all_ws.push(ws);
|
||||
@ -1138,8 +1140,59 @@ function test40()
|
||||
|
||||
function test41()
|
||||
{
|
||||
// reserve test41 for HSTS - bug 664284
|
||||
doTest(42);
|
||||
var ws = CreateTestWS("ws://example.com/tests/content/base/test/file_websocket", "test-41a", 1);
|
||||
|
||||
ws.onopen = function(e)
|
||||
{
|
||||
ok(true, "test 41a open");
|
||||
ok(ws.url == "ws://example.com/tests/content/base/test/file_websocket",
|
||||
"test 41a initial ws should not be redirected");
|
||||
ws.close();
|
||||
};
|
||||
|
||||
ws.onclose = function(e)
|
||||
{
|
||||
ok(true, "test 41a close");
|
||||
|
||||
// establish a hsts policy for example.com
|
||||
var wsb = CreateTestWS("wss://example.com/tests/content/base/test/file_websocket", "test-41b", 1);
|
||||
wsb.onopen = function(e)
|
||||
{
|
||||
ok(true, "test 41b open");
|
||||
wsb.close();
|
||||
}
|
||||
|
||||
wsb.onclose = function(e)
|
||||
{
|
||||
ok(true, "test 41b close");
|
||||
|
||||
// try ws:// again, it should be done over wss:// now due to hsts
|
||||
var wsc = CreateTestWS("ws://example.com/tests/content/base/test/file_websocket", "test-41c");
|
||||
|
||||
wsc.onopen = function(e)
|
||||
{
|
||||
ok(true, "test 41c open");
|
||||
ok(wsc.url == "wss://example.com/tests/content/base/test/file_websocket",
|
||||
"test 41c ws should be redirected by hsts to wss");
|
||||
wsc.close();
|
||||
}
|
||||
|
||||
wsc.onclose = function(e)
|
||||
{
|
||||
ok(true, "test 41c close");
|
||||
|
||||
// clean up the STS state
|
||||
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);
|
||||
doTest(42);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function test42()
|
||||
|
@ -372,9 +372,11 @@ NS_IMPL_THREADSAFE_ISUPPORTS1(OutboundEnqueuer, nsIRunnable)
|
||||
// nsWSAdmissionManager
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Section 5.1 requires that a client rate limit its connects to a single
|
||||
// TCP session in the CONNECTING state (i.e. anything before the 101 upgrade
|
||||
// complete response comes back and an open javascript event is created)
|
||||
// Section 4.1 requires that only a single websocket at a time can be CONNECTING
|
||||
// to any given IP address (or hostname, if proxy doing DNS for us). This class
|
||||
// ensures that we delay connecting until any pending connection for the same
|
||||
// IP/addr is complete (i.e. until before the 101 upgrade complete response
|
||||
// comes back and an 'open' javascript event is created)
|
||||
|
||||
class nsWSAdmissionManager
|
||||
{
|
||||
@ -414,6 +416,9 @@ public:
|
||||
// we could hash this, but the dataset is expected to be
|
||||
// small
|
||||
|
||||
// There may already be another WS channel connecting to this IP address, in
|
||||
// which case we'll still create a new nsOpenConn but defer BeginOpen until
|
||||
// that channel completes connecting.
|
||||
bool found = (IndexOf(aStr) >= 0);
|
||||
nsOpenConn *newdata = new nsOpenConn(aStr, ws);
|
||||
mData.AppendElement(newdata);
|
||||
@ -1938,34 +1943,60 @@ WebSocketChannel::AsyncOnChannelRedirect(
|
||||
rv = newChannel->GetURI(getter_AddRefs(newuri));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (!mAutoFollowRedirects) {
|
||||
nsCAutoString spec;
|
||||
if (NS_SUCCEEDED(newuri->GetSpec(spec)))
|
||||
LOG(("WebSocketChannel: Redirect to %s denied by configuration\n",
|
||||
spec.get()));
|
||||
callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool isHttps = false;
|
||||
rv = newuri->SchemeIs("https", &isHttps);
|
||||
// newuri is expected to be http or https
|
||||
bool newuriIsHttps = false;
|
||||
rv = newuri->SchemeIs("https", &newuriIsHttps);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (mEncrypted && !isHttps) {
|
||||
if (!mAutoFollowRedirects) {
|
||||
// Even if redirects configured off, still allow them for HTTP Strict
|
||||
// Transport Security (from ws://FOO to https://FOO (mapped to wss://FOO)
|
||||
|
||||
nsCOMPtr<nsIURI> clonedNewURI;
|
||||
rv = newuri->Clone(getter_AddRefs(clonedNewURI));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = clonedNewURI->SetScheme(NS_LITERAL_CSTRING("ws"));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIURI> currentURI;
|
||||
rv = GetURI(getter_AddRefs(currentURI));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// currentURI is expected to be ws or wss
|
||||
bool currentIsHttps = false;
|
||||
rv = currentURI->SchemeIs("wss", ¤tIsHttps);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool uriEqual = false;
|
||||
rv = clonedNewURI->Equals(currentURI, &uriEqual);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// It's only a HSTS redirect if we started with non-secure, are going to
|
||||
// secure, and the new URI is otherwise the same as the old one.
|
||||
if (!(!currentIsHttps && newuriIsHttps && uriEqual)) {
|
||||
nsCAutoString newSpec;
|
||||
rv = newuri->GetSpec(newSpec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
LOG(("WebSocketChannel: Redirect to %s denied by configuration\n",
|
||||
newSpec.get()));
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (mEncrypted && !newuriIsHttps) {
|
||||
nsCAutoString spec;
|
||||
if (NS_SUCCEEDED(newuri->GetSpec(spec)))
|
||||
LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n",
|
||||
spec.get()));
|
||||
callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("WebSocketChannel: Redirect could not QI to HTTP\n"));
|
||||
callback->OnRedirectVerifyCallback(rv);
|
||||
return NS_OK;
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel =
|
||||
@ -1973,21 +2004,26 @@ WebSocketChannel::AsyncOnChannelRedirect(
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n"));
|
||||
callback->OnRedirectVerifyCallback(rv);
|
||||
return NS_OK;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// The redirect is likely OK
|
||||
|
||||
newChannel->SetNotificationCallbacks(this);
|
||||
mURI = newuri;
|
||||
|
||||
mEncrypted = newuriIsHttps;
|
||||
newuri->Clone(getter_AddRefs(mURI));
|
||||
if (mEncrypted)
|
||||
rv = mURI->SetScheme(NS_LITERAL_CSTRING("wss"));
|
||||
else
|
||||
rv = mURI->SetScheme(NS_LITERAL_CSTRING("ws"));
|
||||
|
||||
mHttpChannel = newHttpChannel;
|
||||
mChannel = newUpgradeChannel;
|
||||
rv = SetupRequest();
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("WebSocketChannel: Redirect could not SetupRequest()\n"));
|
||||
callback->OnRedirectVerifyCallback(rv);
|
||||
return NS_OK;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We cannot just tell the callback OK right now due to the 1 connect at a
|
||||
@ -2004,8 +2040,8 @@ WebSocketChannel::AsyncOnChannelRedirect(
|
||||
rv = ApplyForAdmission();
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("WebSocketChannel: Redirect failed due to DNS failure\n"));
|
||||
callback->OnRedirectVerifyCallback(rv);
|
||||
mRedirectCallback = nsnull;
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
@ -34,13 +34,8 @@ STEPS TO UPDATE MOZILLA TO NEWER PYWEBSOCKET VERSION
|
||||
# MOZILLA: do not normalize away symlinks in mochitest
|
||||
# path = os.path.realpath(path)
|
||||
|
||||
- There's also some code in mod_pywebsocket/_stream_base.py that may or may not
|
||||
need to change to support Python 2.5:
|
||||
|
||||
#raise ConnectionTerminatedException(
|
||||
# 'Receiving %d byte failed. Peer (%r) closed connection' %
|
||||
# (length, (self._request.connection.remote_addr,)))
|
||||
raise ConnectionTerminatedException('connection terminated: read failed')
|
||||
- We need to apply the patch to hybi.py that makes HSTS work: (attached at end
|
||||
of this README)
|
||||
|
||||
- Test and make sure the code works:
|
||||
|
||||
@ -49,3 +44,53 @@ STEPS TO UPDATE MOZILLA TO NEWER PYWEBSOCKET VERSION
|
||||
- If this doesn't take a look at the pywebsocket server log,
|
||||
$OBJDIR/_tests/testing/mochitest/websock.log
|
||||
|
||||
- Upgrade the svnversion number at top of this file to whatever version we're
|
||||
now based off of.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
PATCH TO hybi.py for HSTS support:
|
||||
|
||||
|
||||
diff --git a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py
|
||||
--- a/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py
|
||||
+++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi.py
|
||||
@@ -227,16 +227,19 @@ class Handshaker(object):
|
||||
|
||||
def _check_version(self):
|
||||
unused_value = validate_mandatory_header(
|
||||
self._request, common.SEC_WEBSOCKET_VERSION_HEADER,
|
||||
str(common.VERSION_HYBI_LATEST), fail_status=426)
|
||||
|
||||
def _set_protocol(self):
|
||||
self._request.ws_protocol = None
|
||||
+ # MOZILLA
|
||||
+ self._request.sts = None
|
||||
+ # /MOZILLA
|
||||
|
||||
protocol_header = self._request.headers_in.get(
|
||||
common.SEC_WEBSOCKET_PROTOCOL_HEADER)
|
||||
|
||||
if not protocol_header:
|
||||
self._request.ws_requested_protocols = None
|
||||
return
|
||||
|
||||
@@ -311,16 +314,21 @@ class Handshaker(object):
|
||||
response.append(format_header(
|
||||
common.SEC_WEBSOCKET_PROTOCOL_HEADER,
|
||||
self._request.ws_protocol))
|
||||
if (self._request.ws_extensions is not None and
|
||||
len(self._request.ws_extensions) != 0):
|
||||
response.append(format_header(
|
||||
common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
|
||||
format_extensions(self._request.ws_extensions)))
|
||||
+ # MOZILLA: Add HSTS header if requested to
|
||||
+ if self._request.sts is not None:
|
||||
+ response.append(format_header("Strict-Transport-Security",
|
||||
+ self._request.sts))
|
||||
+ # /MOZILLA
|
||||
response.append('\r\n')
|
||||
|
||||
raw_response = ''.join(response)
|
||||
self._logger.debug('Opening handshake response: %r', raw_response)
|
||||
self._request.connection.write(raw_response)
|
||||
|
||||
|
@ -232,6 +232,9 @@ class Handshaker(object):
|
||||
|
||||
def _set_protocol(self):
|
||||
self._request.ws_protocol = None
|
||||
# MOZILLA
|
||||
self._request.sts = None
|
||||
# /MOZILLA
|
||||
|
||||
protocol_header = self._request.headers_in.get(
|
||||
common.SEC_WEBSOCKET_PROTOCOL_HEADER)
|
||||
@ -316,6 +319,11 @@ class Handshaker(object):
|
||||
response.append(format_header(
|
||||
common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
|
||||
format_extensions(self._request.ws_extensions)))
|
||||
# MOZILLA: Add HSTS header if requested to
|
||||
if self._request.sts is not None:
|
||||
response.append(format_header("Strict-Transport-Security",
|
||||
self._request.sts))
|
||||
# /MOZILLA
|
||||
response.append('\r\n')
|
||||
|
||||
raw_response = ''.join(response)
|
||||
|
Loading…
Reference in New Issue
Block a user