Skip to content

Instantly share code, notes, and snippets.

@rhelmer
Last active March 4, 2017 08:24
Show Gist options
  • Save rhelmer/13193fd0f9d48559521d80129c83e70d to your computer and use it in GitHub Desktop.
Save rhelmer/13193fd0f9d48559521d80129c83e70d to your computer and use it in GitHub Desktop.
# HG changeset patch
# User Robert Helmer <[email protected]>
# Date 1488615603 28800
# Sat Mar 04 00:20:03 2017 -0800
# Node ID 003b53ae28d334d7cdce242a5d45ef1aed091c1f
# Parent ed3f666f580073edf76b538c3dde6badd81c7d40
Bug 1325872 - system add-on for TLS 1.3 Compatibility Testing
MozReview-Commit-ID: IASvvAIxOGh
diff --git a/browser/extensions/moz.build b/browser/extensions/moz.build
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -5,16 +5,17 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'aushelper',
'e10srollout',
'pdfjs',
'pocket',
'webcompat',
+ 'tls13-compat-ff51',
]
# Only include the following system add-ons if building Aurora or Nightly
if 'a' in CONFIG['GRE_MILESTONE']:
DIRS += [
'flyweb',
'formautofill',
]
diff --git a/browser/extensions/tls13-compat-ff51/bootstrap.js b/browser/extensions/tls13-compat-ff51/bootstrap.js
new file mode 100644
--- /dev/null
+++ b/browser/extensions/tls13-compat-ff51/bootstrap.js
@@ -0,0 +1,219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+const { Preferences } = Cu.import("resource://gre/modules/Preferences.jsm", {});
+const { setTimeout, clearTimeout } = Cu.import("resource://gre/modules/Timer.jsm", {});
+const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+const { TelemetryController } = Cu.import("resource://gre/modules/TelemetryController.jsm", {});
+
+// The Firefox TLS setting. 3 is TLS 1.2, 4 is TLS 1.3
+const VERSION_MAX_PREF = "security.tls.version.max";
+
+// Whether or not to print messages to the console.
+const DEBUG = false;
+
+// Timeout after 2 minutes.
+const TIMEOUT = 120000;
+
+// These should be different hosts so that we don't bias any performance test
+// toward 1.2.
+/*
+const URLs = {
+ "https://enabled.tls13.com/" : true,
+ "https://disabled.tls13.com/" : true,
+ "https://short.tls13.com/" : true,
+ "https://control.tls12.com/" : false
+};
+*/
+
+const URLs = {
+ "https://test1.example.com": true,
+ "https://test2.example.com": true,
+ //"https://control.example.com": false,
+};
+
+let gStarted = false;
+let gNewInstall = false;
+let gPrefs = new Preferences({ defaultBranch: true });
+let gTimer;
+
+function debug(msg) {
+ if (DEBUG) {
+ console.log(`TLSEXP: ${msg}`);
+ }
+}
+
+// These variables are unreliable for some reason.
+function read(obj, field) {
+ try {
+ return obj[field];
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ return undefined;
+}
+
+function setTlsPref(prefs, value) {
+ debug("Setting pref to " + value);
+ prefs.set(VERSION_MAX_PREF, value);
+}
+
+// This might help us work out if there was a MitM
+function recordSecInfo(channel, result) {
+ let secInfo = channel.securityInfo;
+ if (secInfo instanceof Ci.nsITransportSecurityInfo) {
+ secInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ const isSecure = Ci.nsIWebProgressListener.STATE_IS_SECURE;
+ result.secure = !!(read(secInfo, 'securityState') & isSecure);
+ result.prError = read(secInfo, 'errorCode');
+ }
+ if (secInfo instanceof Ci.nsISSLStatusProvider) {
+ let sslStatus = secInfo.QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus.QueryInterface(Ci.nsISSLStatus);
+ let cert = read(sslStatus, 'serverCert');
+ result.certfp = read(cert, 'sha256Fingerprint'); // A hex string
+ result.version = read(sslStatus, 'protocolVersion');
+ }
+}
+
+function makeRequest(prefs, index, url, body) {
+ return new Promise(resolve => {
+ if (!gStarted) {
+ return;
+ }
+
+ debug("Setting pref");
+ setTlsPref(prefs, URLs[url] ? 4 : 3);
+
+ let t0 = Date.now();
+ let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ req.open(
+ body ? "POST" : "GET", url, true);
+ req.setRequestHeader("Content-Type", "application/json");
+
+ var result = {
+ "index" : index,
+ "url" : url,
+ "start_time" : t0
+ };
+ req.timeout = 10000; // 10s is low intentionally
+ req.addEventListener("error", e => {
+ debug("Finished with error");
+ let channel = e.target.channel;
+ let nsireq = channel.QueryInterface(Ci.nsIRequest);
+ result.error= nsireq ? nsireq.status : NS_ERROR_NOT_AVAILABLE;
+ recordSecInfo(channel, result);
+ result.elapsed = Date.now() - t0;
+ debug("Re-setting pref");
+ setTlsPref(prefs, 3);
+ resolve(result);
+ });
+ req.addEventListener("loadend", e => {
+ debug("Finished with load");
+ result.status = e.target.status;
+ recordSecInfo(e.target.channel, result);
+ result.elapsed = Date.now() - t0;
+ debug("Resetting pref");
+ setTlsPref(prefs, 3);
+ resolve(result);
+ });
+
+ debug("Starting request for " + url + " TLS 1.3=" + URLs[url]);
+ if (body) {
+ req.send(JSON.stringify(body));
+ } else {
+ req.send();
+ }
+ });
+}
+
+
+function report(result) {
+ return TelemetryController.submitExternalPing(
+ "tls-13-study-v4",
+ {
+ time: Date.now(),
+ results: result
+ }, {});
+}
+
+// Inefficient shuffle algorithm, but n <= 10
+function shuffleArray(orig) {
+ var inarr = [];
+ for (let i in orig) {
+ inarr.push(orig[i]);
+ }
+ var out = [];
+ while (inarr.length > 0) {
+ let x = Math.floor(Math.random() * inarr.length);
+ out.push(inarr.splice(x, 1)[0])
+ }
+ return out;
+}
+
+function startup() { // eslint-disable-line no-unused-vars
+ if (!gNewInstall) {
+ return;
+ }
+
+ // deadman timer to ensure we reset pref after 2 minutes
+ gTimer = setTimeout(() => {
+ try {
+ debug("compat test timed out");
+ gStarted = false;
+ setTlsPref(gPrefs, 3);
+ report([{ "status": "timeout" }]);
+ } catch (ex) {
+ debug("timer failed:", ex);
+ }
+ }, TIMEOUT);
+}
+
+function shutdown() {} // eslint-disable-line no-unused-vars
+
+// This is a simple experiment:
+// - Install
+// - Enable TLS 1.3.
+// - Connect to a bunch of servers and record the results
+// (see README.md for details on report format)
+function install() { // eslint-disable-line no-unused-vars
+ // Don't do anything if the user has already messed with this
+ // setting.
+ let userprefs = new Preferences();
+ if (userprefs.isSet(VERSION_MAX_PREF)) {
+ console.log("User has changed TLS max version. Skipping");
+ return;
+ }
+
+ // Only ever want to run once.
+ gNewInstall = true;
+ gStarted = true;
+
+ report([{ "status": "startup" }]);
+
+ let shuffled = shuffleArray(Object.keys(URLs));
+ let results = [];
+
+ Task.spawn(function* () {
+ for (var i in shuffled) {
+ results.push(yield makeRequest(gPrefs, i, shuffled[i], null));
+ }
+
+ report(results);
+ })
+ .catch(e => Cu.reportError(e))
+ .then(_ => {
+ // Make sure we re-set to TLS 1.2.
+ setTlsPref(gPrefs, 3);
+
+ clearTimeout(gTimer);
+ report([{ "status": "finished" }]);
+ });
+}
+function uninstall() {} // eslint-disable-line no-unused-vars
diff --git a/browser/extensions/tls13-compat-ff51/install.rdf.in b/browser/extensions/tls13-compat-ff51/install.rdf.in
new file mode 100644
--- /dev/null
+++ b/browser/extensions/tls13-compat-ff51/install.rdf.in
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>[email protected]</em:id>
+ <em:version>1.0.4</em:version>
+ <em:type>2</em:type>
+ <em:bootstrap>true</em:bootstrap>
+ <em:unpack>false</em:unpack>
+ <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+ <!-- Firefox -->
+ <em:targetApplication>
+ <Description>
+ <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+ <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+ <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>TLS 1.3 Compatibility Testing</em:name>
+ <em:description>Testing compatibility of TLS 1.3.</em:description>
+ <em:aboutURL>https://bugzilla.mozilla.org/show_bug.cgi?id=1325872</em:aboutURL>
+ </Description>
+</RDF>
diff --git a/browser/extensions/tls13-compat-ff51/jar.mn b/browser/extensions/tls13-compat-ff51/jar.mn
new file mode 100644
--- /dev/null
+++ b/browser/extensions/tls13-compat-ff51/jar.mn
@@ -0,0 +1,3 @@
+[features/[email protected]] chrome.jar:
+% content tls13-compat-ff51 %content/
+ content/ (content/*)
diff --git a/browser/extensions/tls13-compat-ff51/moz.build b/browser/extensions/tls13-compat-ff51/moz.build
new file mode 100644
--- /dev/null
+++ b/browser/extensions/tls13-compat-ff51/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+FINAL_TARGET_FILES.features['[email protected]'] += [
+ 'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['[email protected]'] += [
+ 'install.rdf.in'
+]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+JAR_MANIFESTS += ['jar.mn']
+
+with Files('**'):
+ BUG_COMPONENT = ('Web Compatibility', 'Go Faster')
diff --git a/browser/extensions/tls13-compat-ff51/test/.eslintrc.js b/browser/extensions/tls13-compat-ff51/test/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/browser/extensions/tls13-compat-ff51/test/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/browser/extensions/tls13-compat-ff51/test/browser.ini b/browser/extensions/tls13-compat-ff51/test/browser.ini
new file mode 100644
--- /dev/null
+++ b/browser/extensions/tls13-compat-ff51/test/browser.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+
+[browser_check_installed.js]
+[browser_test_tls_setting.js]
diff --git a/browser/extensions/tls13-compat-ff51/test/browser_check_installed.js b/browser/extensions/tls13-compat-ff51/test/browser_check_installed.js
new file mode 100644
--- /dev/null
+++ b/browser/extensions/tls13-compat-ff51/test/browser_check_installed.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global AddonManager */
+
+"use strict";
+
+add_task(function* installed() {
+ let addon = yield new Promise(
+ (resolve) => AddonManager.getAddonByID("[email protected]", resolve)
+ );
+ isnot(addon, null, "addon should exist");
+ is(addon.name, "TLS 1.3 Compatibility Testing");
+ ok(addon.isCompatible, "addon is compatible with Firefox");
+ ok(!addon.appDisabled, "addon is not app disabled");
+ ok(addon.isActive, "addon is active");
+ is(addon.type, "extension", "addon is type extension");
+});
diff --git a/browser/extensions/tls13-compat-ff51/test/browser_test_tls_setting.js b/browser/extensions/tls13-compat-ff51/test/browser_test_tls_setting.js
new file mode 100644
--- /dev/null
+++ b/browser/extensions/tls13-compat-ff51/test/browser_test_tls_setting.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { Preferences } = Cu.import("resource://gre/modules/Preferences.jsm", {});
+const { TelemetryArchive } = Cu.import("resource://gre/modules/TelemetryArchive.jsm", {});
+
+
+"use strict";
+
+// The Firefox TLS setting. 3 is TLS 1.2, 4 is TLS 1.3
+const VERSION_MAX_PREF = "security.tls.version.max";
+
+add_task(function* installed() {
+ let userprefs = new Preferences();
+ ok(!userprefs.isSet(VERSION_MAX_PREF), "setting should not have been user modified");
+
+ let addon = yield new Promise(
+ (resolve) => AddonManager.getAddonByID("[email protected]", resolve)
+ );
+ ok(addon.isActive, "addon is active");
+ is(userprefs.get(VERSION_MAX_PREF), 3, "setting should be reset after add-on is active");
+
+ let list = yield TelemetryArchive.promiseArchivedPingList();
+ is(list.length, 3, "correct number of telemetry entries")
+
+ let expectedType = "tls-13-study-v4";
+
+ let startPing = yield TelemetryArchive.promiseArchivedPingById(list[0].id);
+ is(startPing.type, expectedType, "startup telemetry ping type is correct")
+ is(startPing.payload.results[0].status, "startup", "first telemetry ping is startup");
+
+ let dataPing = yield TelemetryArchive.promiseArchivedPingById(list[1].id);
+ is(dataPing.type, expectedType, "data telemetry ping type is correct");
+ is(dataPing.payload.results.length, 2, "data telemetry ping correct number of results");
+
+ // the add-on shuffles the order it loads URLs.
+ for (let result of dataPing.payload.results) {
+ let expectedUrl = (result.url == "https://test1.example.com" ||
+ result.url == "https://test2.example.com");
+ ok(expectedUrl, "data ping URL is expected");
+ is(result.status, "200", "data ping result status is 200");
+ ok(result.secure, "data ping result secure is true");
+ }
+
+ let finishedPing = yield TelemetryArchive.promiseArchivedPingById(list[2].id);
+ is(finishedPing.type, expectedType, "finished telemetry ping type is correct")
+ is(finishedPing.payload.results[0].status, "finished", "first telemetry ping is startup");
+});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment