Bug 1391506 - test for tls_flags max version draft
authorPatrick McManus <mcmanus@ducksong.com>
Tue, 29 Aug 2017 15:26:39 -0400
changeset 655231 9637ede1fd7c6c7d4d43f21e44ca0293d027eb75
parent 655230 0e8441b3c6bf8d8319cc222053e9167180fb0365
child 728771 0bb98000aed8bda13c622d61f74f7371df4591bf
push id76804
push userbmo:mcmanus@ducksong.com
push dateTue, 29 Aug 2017 19:27:56 +0000
bugs1391506
milestone57.0a1
Bug 1391506 - test for tls_flags max version MozReview-Commit-ID: 8XgAzY4XT3A
netwerk/test/unit/test_tls_flags.js
netwerk/test/unit/xpcshell.ini
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_tls_flags.js
@@ -0,0 +1,220 @@
+// -*- 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.
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+
+// Get a profile directory and ensure PSM initializes NSS.
+do_get_profile();
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+function getCert() {
+  return new Promise((resolve, reject) => {
+    let certService = Cc["@mozilla.org/security/local-cert-service;1"]
+                        .getService(Ci.nsILocalCertService);
+    certService.getOrCreateCert("tlsflags-test", {
+      handleCert: function(c, rv) {
+        if (rv) {
+          reject(rv);
+          return;
+        }
+        resolve(c);
+      }
+    });
+  });
+}
+
+class InputStreamCallback {
+  constructor(output) {
+    this.output = output;
+    this.stopped = false;
+  }
+
+  onInputStreamReady(stream) {
+    do_print("input stream ready");
+    if (this.stopped) {
+      do_print("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();
+    do_print("done with input stream ready");
+  }
+
+  stop() {
+    this.stopped = true;
+    this.output.close();
+  }
+}
+
+class TLSServerSecurityObserver {
+  constructor(input, output) {
+    this.input = input;
+    this.output = output;
+    this.callbacks = [];
+    this.stopped = false;
+  }
+
+  onHandshakeDone(socket, status) {
+    do_print("TLS handshake done");
+    do_print(`TLS version used: ${status.tlsVersionUsed}`);
+
+    if (this.stopped) {
+      do_print("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();
+    });
+  }
+}
+
+class ServerSocketListener {
+  constructor() {
+    this.securityObservers = [];
+  }
+
+  onSocketAccepted(socket, transport) {
+    do_print("accepted TLS client connection");
+    let connectionInfo = transport.securityInfo
+                         .QueryInterface(Ci.nsITLSServerConnectionInfo);
+    let input = transport.openInputStream(0, 0, 0);
+    let output = transport.openOutputStream(0, 0, 0);
+    let securityObserver = new TLSServerSecurityObserver(input, output);
+    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() {
+    do_print("onStopListening");
+    this.securityObservers.forEach((observer) => {
+      observer.stop();
+    });
+  }
+}
+
+function startServer(cert, minServerVersion, maxServerVersion) {
+  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.setSessionCache(false);
+  tlsServer.setSessionTickets(false);
+  tlsServer.asyncListen(new ServerSocketListener());
+  return tlsServer;
+}
+
+const hostname = "example.com"
+
+function storeCertOverride(port, cert) {
+  let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
+                              .getService(Ci.nsICertOverrideService);
+  let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                     Ci.nsICertOverrideService.ERROR_MISMATCH;
+  certOverrideService.rememberValidityOverride(hostname, port, cert,
+                                               overrideBits, 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, reject) => {
+    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 = await getCert();
+
+  // server that accepts 1.1->1.3 and a client max 1.3
+  do_print("TEST 1");
+  let server = startServer(cert,
+                           Ci.nsITLSClientStatus.TLS_VERSION_1_1,
+                           Ci.nsITLSClientStatus.TLS_VERSION_1_3);
+  storeCertOverride(server.port, cert);
+  await startClient(server.port, 0x44, true /*should succeed*/);
+
+  // server that accepts 1.1->1.3 and a client max 1.1
+  do_print("TEST 1A");
+  await startClient(server.port, 2, true);
+
+  server.close();
+
+  // server that accepts 1.2->1.2 and a client max 1.3
+  do_print("TEST 2");
+  server = startServer(cert,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_2,
+                       Ci.nsITLSClientStatus.TLS_VERSION_1_2);
+  storeCertOverride(server.port, cert);
+  await startClient(server.port, 4, true);
+
+  // server that accepts 1.2->1.2 and a client max 1.1
+  do_print("TEST 3");
+  await startClient(server.port, 2, false);
+
+  server.close();
+});
+
+do_register_cleanup(function() {
+  Services.prefs.clearUserPref("security.tls.version.max");
+  Services.prefs.clearUserPref("network.dns.localDomains");
+});
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -392,8 +392,9 @@ skip-if = os == "android"
 [test_race_cache_with_network.js]
 [test_channel_priority.js]
 [test_bug1312774_http1.js]
 [test_1351443-missing-NewChannel2.js]
 [test_bug1312782_http1.js]
 [test_bug1355539_http1.js]
 [test_bug1378385_http1.js]
 [test_tls_flags_separate_connections.js]
+[test_tls_flags.js]