Skip to content

Instantly share code, notes, and snippets.

@rhelmer
Created January 31, 2017 23:51
Show Gist options
  • Select an option

  • Save rhelmer/c27dffd9bbd3a5e1499ccc9f3fb2a26e to your computer and use it in GitHub Desktop.

Select an option

Save rhelmer/c27dffd9bbd3a5e1499ccc9f3fb2a26e to your computer and use it in GitHub Desktop.
let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource:///modules/experiments/Experiments.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/TelemetryController.jsm");
var gStarted = false;
const kSELF_ID = "tls-compat-beta51@experiments.mozilla.org";
const kVERSION_MAX_PREF = "security.tls.version.max";
// These should be different hosts so that we don't bias any performance test
// toward 1.2.
const kURLs = [
// This must be first because we use it as a test for TLS 1.3 working
"https://enabled.tls13.com/",
"https://disabled.tls13.com/"
];
// These variables are unreliable for some reason.
function read(obj, field) {
try {
return obj[field];
} catch (e) {
Cu.reportError(e);
}
return undefined;
}
// 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(index, url, body) {
return new Promise(resolve => {
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 => {
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;
resolve(result);
});
req.addEventListener("load", e => {
result.status = e.target.status;
recordSecInfo(e.target.channel, result);
result.elapsed = Date.now() - t0;
resolve(result);
});
if (body) {
req.send(JSON.stringify(body));
} else {
req.send();
}
});
}
function report(result) {
console.log("Result");
console.log(result);
return TelemetryController.submitExternalPing(
"tls-13-study-v1",
{
results: result
}, {});
}
function disable() {
Experiments.instance().disableExperiment("FROM_API");
}
// Inefficient shuffle algorithm, but n <= 10
function shuffleArray(orig) {
var inarr = [];
for (i in orig) {
inarr.push(orig[i]);
}
var out = [];
while(inarr.length > 0) {
x = Math.floor(Math.random() * inarr.length);
out.push(inarr.splice(x,1)[0])
}
return out;
}
function ensureExperimentBranch() {
return new Promise(resolve => {
let experiments = Experiments.instance();
// If we have already determined the branch, return the
// response.
let branch = experiments.getActiveExperimentBranch();
if (branch) {
console.log("TLS 1.3 experiment branch already selected: " + branch);
resolve(branch);
return;
}
// OK, we need to run an experiment to determine the
// branch.
// Start by making it disabled.
branch = "disabled";
let prefs = new Preferences({ defaultBranch: true });
prefs.set(kVERSION_MAX_PREF, 4);
let todo = [];
let shuffled = shuffleArray(kURLs);
for (var i in shuffled) {
todo.push(makeRequest(i, shuffled[i], null ));
}
Promise.all(todo)
.then(result => {
for (r in result) {
// If we successfully do a TLS 1.3 fetch at all, mark it enabled.
if ((result[r].url == kURLs[0]) && result[r].status === 200) {
console.log("TLS 1.3 succeeded");
branch = "enabled";
}
}
report(result);
})
.catch(e => Cu.reportError(e))
.then(_ => {
prefs.set(kVERSION_MAX_PREF, 3);
})
.then(_ => {
let id = experiments.getActiveExperimentID();
console.log("TLS 1.3 experiment branch result was: " + branch);
return experiments.setExperimentBranch(id, branch);
}).
then(_ => resolve(branch));
});
}
// 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)
// - If the TLS 1.3 connection succeeded, leave TLS 1.3 on.
function startup() {
// Don't do anything if the user has already messed with this
// setting.
let userprefs = new Preferences();
if (userprefs.isSet(kVERSION_MAX_PREF)) {
console.log("User has changed TLS max version. Skipping");
experiments.setExperimentBranch(id, "skipped");
disable();
return;
}
// Seems startup() function is launched twice after install, we're
// unsure why so far. We only want it to run once.
if (gStarted) {
return;
}
gStarted = true;
ensureExperimentBranch().then(branch => {
let prefs = new Preferences({defaultBranch: true});
switch (branch) {
case "enabled":
prefs.set(kVERSION_MAX_PREF, 4);
return;
case "disabled":
// Do nothing.
return;
default:
throw new Error("Unexpected experiment branch: " + branch);
}
}, e => {
Cu.reportError("Got error during bootstrap startup: " + e);
});
}
function shutdown() {
gStarted = false;
}
function install() {}
function uninstall() {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment