// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
"use strict";
// a fork of test_be_conservative
// Tests that nsIHttpChannelInternal.tlsFlags can be used to set the
// client max version level. Flags can also be used to set the
// level of intolerance rollback and to test out an experimental 1.3
// hello, though they are not tested here.
// Get a profile directory and ensure PSM initializes NSS.
do_get_profile();
Cc[
"@mozilla.org/psm;1"].getService(Ci.nsISupports);
class InputStreamCallback {
constructor(output) {
this.output = output;
this.stopped =
false;
}
onInputStreamReady(stream) {
info(
"input stream ready");
if (
this.stopped) {
info(
"input stream callback stopped - bailing");
return;
}
let available = 0;
try {
available = stream.available();
}
catch (e) {
// onInputStreamReady may fire when the stream has been closed.
equal(
e.result,
Cr.NS_BASE_STREAM_CLOSED,
"error should be NS_BASE_STREAM_CLOSED"
);
}
if (available > 0) {
let request = NetUtil.readInputStreamToString(stream, available, {
charset:
"utf8",
});
ok(
request.startsWith(
"GET / HTTP/1.1\r\n"),
"Should get a simple GET / HTTP/1.1 request"
);
let response =
"HTTP/1.1 200 OK\r\n" +
"Content-Length: 2\r\n" +
"Content-Type: text/plain\r\n" +
"\r\nOK";
let written =
this.output.write(response, response.length);
equal(
written,
response.length,
"should have been able to write entire response"
);
}
this.output.close();
info(
"done with input stream ready");
}
stop() {
this.stopped =
true;
this.output.close();
}
}
class TLSServerSecurityObserver {
constructor(input, output, expectedVersion) {
this.input = input;
this.output = output;
this.expectedVersion = expectedVersion;
this.callbacks = [];
this.stopped =
false;
}
onHandshakeDone(socket, status) {
info(
"TLS handshake done");
info(`TLS version used: ${status.tlsVersionUsed}`);
info(
this.expectedVersion);
equal(
status.tlsVersionUsed,
this.expectedVersion,
"expected version check"
);
if (
this.stopped) {
info(
"handshake done callback stopped - bailing");
return;
}
let callback =
new InputStreamCallback(
this.output);
this.callbacks.push(callback);
this.input.asyncWait(callback, 0, 0, Services.tm.currentThread);
}
stop() {
this.stopped =
true;
this.input.close();
this.output.close();
this.callbacks.forEach(callback => {
callback.stop();
});
}
}
function startServer(
cert,
minServerVersion,
maxServerVersion,
expectedVersion
) {
let tlsServer = Cc[
"@mozilla.org/network/tls-server-socket;1"].createInstance(
Ci.nsITLSServerSocket
);
tlsServer.init(-1,
true, -1);
tlsServer.serverCert = cert;
tlsServer.setVersionRange(minServerVersion, maxServerVersion);
tlsServer.setSessionTickets(
false);
let listener = {
securityObservers: [],
onSocketAccepted(socket, transport) {
info(
"accepted TLS client connection");
let connectionInfo = transport.securityCallbacks.getInterface(
Ci.nsITLSServerConnectionInfo
);
let input = transport.openInputStream(0, 0, 0);
let output = transport.openOutputStream(0, 0, 0);
let securityObserver =
new TLSServerSecurityObserver(
input,
output,
expectedVersion
);
this.securityObservers.push(securityObserver);
connectionInfo.setSecurityObserver(securityObserver);
},
// For some reason we get input stream callback events after we've stopped
// listening, so this ensures we just drop those events.
onStopListening() {
info(
"onStopListening");
this.securityObservers.forEach(observer => {
observer.stop();
});
},
};
tlsServer.asyncListen(listener);
return tlsServer;
}
const hostname =
"example.com";
function storeCertOverride(port, cert) {
let certOverrideService = Cc[
"@mozilla.org/security/certoverride;1"
].getService(Ci.nsICertOverrideService);
certOverrideService.rememberValidityOverride(hostname, port, {}, cert,
true);
}
function startClient(port, tlsFlags, expectSuccess) {
let req =
new XMLHttpRequest();
req.open(
"GET", `https:
//${hostname}:${port}`);
let internalChannel = req.channel.QueryInterface(Ci.nsIHttpChannelInternal);
internalChannel.tlsFlags = tlsFlags;
return new Promise(resolve => {
req.onload = () => {
ok(
expectSuccess,
`should ${expectSuccess ?
"" :
"not "}have gotten load event`
);
equal(req.responseText,
"OK",
"response text should be 'OK'");
resolve();
};
req.onerror = () => {
ok(
!expectSuccess,
`should ${!expectSuccess ?
"" :
"not "}have gotten an error`
);
resolve();
};
req.send();
});
}
add_task(async
function () {
Services.prefs.setIntPref(
"security.tls.version.max", 4);
Services.prefs.setCharPref(
"network.dns.localDomains", hostname);
let cert = getTestServerCertificate();
// server that accepts 1.1->1.3 and a client max 1.3. expect 1.3
info(
"TEST 1");
let server = startServer(
cert,
Ci.nsITLSClientStatus.TLS_VERSION_1_1,
Ci.nsITLSClientStatus.TLS_VERSION_1_3,
Ci.nsITLSClientStatus.TLS_VERSION_1_3
);
storeCertOverride(server.port, cert);
await startClient(server.port, 4,
true /*should succeed*/);
server.close();
// server that accepts 1.1->1.3 and a client max 1.1. expect 1.1
info(
"TEST 2");
server = startServer(
cert,
Ci.nsITLSClientStatus.TLS_VERSION_1_1,
Ci.nsITLSClientStatus.TLS_VERSION_1_3,
Ci.nsITLSClientStatus.TLS_VERSION_1_1
);
storeCertOverride(server.port, cert);
await startClient(server.port, 2,
true);
server.close();
// server that accepts 1.2->1.2 and a client max 1.3. expect 1.2
info(
"TEST 3");
server = startServer(
cert,
Ci.nsITLSClientStatus.TLS_VERSION_1_2,
Ci.nsITLSClientStatus.TLS_VERSION_1_2,
Ci.nsITLSClientStatus.TLS_VERSION_1_2
);
storeCertOverride(server.port, cert);
await startClient(server.port, 4,
true);
server.close();
// server that accepts 1.2->1.2 and a client max 1.1. expect fail
info(
"TEST 4");
server = startServer(
cert,
Ci.nsITLSClientStatus.TLS_VERSION_1_2,
Ci.nsITLSClientStatus.TLS_VERSION_1_2,
0
);
storeCertOverride(server.port, cert);
await startClient(server.port, 2,
false);
server.close();
});
registerCleanupFunction(
function () {
Services.prefs.clearUserPref(
"security.tls.version.max");
Services.prefs.clearUserPref(
"network.dns.localDomains");
});