bug 1311479 - determine how many users are behind a TLS intercepting proxy r=jcj data-review=bsmedberg
authorDavid Keeler <dkeeler@mozilla.com>
Wed, 02 Nov 2016 13:34:33 -0700
changeset 99 475a4d815e0ace87c47cefdde6b48012d31fdab1
parent 98 fe92fc07b8f8229211792aa73799ee336eb0d3c9
child 100 08b7d157b12fca49d3d9f37bc604976c1ea1048b
push id76
push userdkeeler@mozilla.com
push dateTue, 15 Nov 2016 20:25:49 +0000
reviewersjcj
bugs1311479
bug 1311479 - determine how many users are behind a TLS intercepting proxy r=jcj data-review=bsmedberg
experiments/mitm-prevalence-beta51/README.md
experiments/mitm-prevalence-beta51/code/bootstrap.js
experiments/mitm-prevalence-beta51/code/install.rdf
experiments/mitm-prevalence-beta51/experiment.xpi
experiments/mitm-prevalence-beta51/manifest.json
copy from experiments/tls13-compat-nightly52/README.md
copy to experiments/mitm-prevalence-beta51/README.md
--- a/experiments/tls13-compat-nightly52/README.md
+++ b/experiments/mitm-prevalence-beta51/README.md
@@ -1,18 +1,64 @@
-This experiment compares performance/behavior of various TLS servers
-(which can be configured differently) by doing a GET to each URL and
-then reporting the results.
+This experiment attempts to determine how common it is for users to be behind a
+TLS intercepting proxy that uses a root certificate that is trusted by the web
+public key infrastructure. It does this by attempting to connect to
+https://telemetry.mozilla.org and reporting some details on the success or
+failure of this operation.
+
+Answering this question is important for at least two reasons:
 
-The report payload is a JSON list with each entry in the list consisting
-of a dictionary with the following values:
+1. Such TLS intercepting proxies are a violation of the Baseline Requirements
+   and would require action on our part to protect users.
+2. The existence of these proxies would hamper our efforts to eliminate the use
+   of SHA1 in signatures on certificates issued from CAs trusted by the web
+   public key infrastructure. We should measure their prevalence before breaking
+   the web for users.
+
+The report payload is a JSON dictionary containing the following values:
 
-* url -- the URL being tested
-* index -- the order in which the URL was tested
-* start_time -- the time when the request was started in milliseconds since the epoch
-* status -- the HTTP status code
-* secure -- whether we got TLS
-* prError -- the TLS-level error code if we got a failure
-* certfp -- the certificate fingerprint
-* version -- the TLS version negotiated
-* elapsed -- the time to complete the request in milliseconds
+* errorCode -- 0 for successful connections, some PR error code otherwise
+* error -- a short description of one of four error conditions encountered, if
+  applicable, and an empty string otherwise:
+  1. "timeout" if the connection to telemetry.mozilla.org timed out
+  2. "user override" if the user has stored a permanent certificate exception
+     override for telemetry.mozilla.org (due to technical limitations, we can't
+     gather much information in this situation)
+  3. "certificate reverification" if re-building the certificate chain after
+     connecting failed for some reason (unfortunately this step is necessary
+     due to technical limitations)
+  4. "connection error" if the connection to telemetry.mozilla.org failed for
+     another reason
+* chain -- a list of dictionaries each corresponding to a certificate in the
+  verified certificate chain, if it was successfully constructed. The first
+  entry is the end-entity certificate. The last entry is the root certificate.
+  This will be empty if the connection failed or if reverification failed. Each
+  element in the list contains the following values:
+  * sha256Fingerprint -- a hex string representing the SHA-256 hash of the
+    certificate
+  * isBuiltInRoot -- true if the certificate is a trust anchor in the web PKI,
+    false otherwise
+  * signatureAlgorithm -- a description of the algorithm used to sign the
+    certificate. Will be one of "md2WithRSAEncryption", "md5WithRSAEncryption",
+    "sha1WithRSAEncryption", "sha256WithRSAEncryption",
+    "sha384WithRSAEncryption", "sha512WithRSAEncryption", "ecdsaWithSHA1",
+    "ecdsaWithSHA224", "ecdsaWithSHA256", "ecdsaWithSHA384", "ecdsaWithSHA512",
+    or "unknown".
 
+For a connection not intercepted by a TLS proxy, the expected result will be:
 
+    { "errorCode": 0,
+      "error": "",
+      "chain": [
+        { "sha256Fingerprint": "197feaf3faa0f0ad637a89c97cb91336bfc114b6b3018203cbd9c3d10c7fa86c",
+          "isBuiltInRoot": false,
+          "signatureAlgorithm": "sha256WithRSAEncryption"
+        },
+        { "sha256Fingerprint": "154c433c491929c5ef686e838e323664a00e6a0d822ccc958fb4dab03e49a08f",
+          "isBuiltInRoot": false,
+          "signatureAlgorithm": "sha256WithRSAEncryption"
+        },
+        { "sha256Fingerprint": "4348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c70161",
+          "isBuiltInRoot": true,
+          "signatureAlgorithm": "sha1WithRSAEncryption"
+        }
+      ]
+    }
copy from experiments/tls13-compat-nightly52/code/bootstrap.js
copy to experiments/mitm-prevalence-beta51/code/bootstrap.js
--- a/experiments/tls13-compat-nightly52/code/bootstrap.js
+++ b/experiments/mitm-prevalence-beta51/code/bootstrap.js
@@ -1,133 +1,169 @@
-let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+"use strict";
+
+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");
 
-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.
+function delocalizeAlgorithm(localizedString) {
+  let bundle = Services.strings.createBundle(
+    "chrome://pipnss/locale/pipnss.properties");
+  let algorithmStringIdsToOIDDescriptionMap = {
+    "CertDumpMD2WithRSA":                       "md2WithRSAEncryption",
+    "CertDumpMD5WithRSA":                       "md5WithRSAEncryption",
+    "CertDumpSHA1WithRSA":                      "sha1WithRSAEncryption",
+    "CertDumpSHA256WithRSA":                    "sha256WithRSAEncryption",
+    "CertDumpSHA384WithRSA":                    "sha384WithRSAEncryption",
+    "CertDumpSHA512WithRSA":                    "sha512WithRSAEncryption",
+    "CertDumpAnsiX962ECDsaSignatureWithSha1":   "ecdsaWithSHA1",
+    "CertDumpAnsiX962ECDsaSignatureWithSha224": "ecdsaWithSHA224",
+    "CertDumpAnsiX962ECDsaSignatureWithSha256": "ecdsaWithSHA256",
+    "CertDumpAnsiX962ECDsaSignatureWithSha384": "ecdsaWithSHA384",
+    "CertDumpAnsiX962ECDsaSignatureWithSha512": "ecdsaWithSHA512",
+  };
 
-const kURLs = [
-   "https://disabled.tls13.com/",
-   "https://enabled.tls13.com/"
-];
-
-// These variables are unreliable for some reason.
-function read(obj, field) {
-  try {
-    return obj[field];
-  } catch (e) {
-    Cu.reportError(e);
+  let description;
+  Object.keys(algorithmStringIdsToOIDDescriptionMap).forEach((l10nID) => {
+    let candidateLocalizedString = bundle.GetStringFromName(l10nID);
+    if (localizedString == candidateLocalizedString) {
+      description = algorithmStringIdsToOIDDescriptionMap[l10nID];
+    }
+  });
+  if (!description) {
+    return "unknown";
   }
-  return undefined;
+  return description;
 }
 
-// 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');
+function getSignatureAlgorithm(cert) {
+  // Certificate  ::=  SEQUENCE  {
+  //      tbsCertificate       TBSCertificate,
+  //      signatureAlgorithm   AlgorithmIdentifier,
+  //      signatureValue       BIT STRING  }
+  let certificate = cert.ASN1Structure.QueryInterface(Ci.nsIASN1Sequence);
+  let signatureAlgorithm = certificate.ASN1Objects
+                                      .queryElementAt(1, Ci.nsIASN1Sequence);
+  // AlgorithmIdentifier  ::=  SEQUENCE  {
+  //      algorithm               OBJECT IDENTIFIER,
+  //      parameters              ANY DEFINED BY algorithm OPTIONAL  }
+
+  // If parameters is NULL (or empty), signatureAlgorithm won't be a container
+  // under this implementation. Just get its displayValue.
+  if (!signatureAlgorithm.isValidContainer) {
+    return signatureAlgorithm.displayValue;
   }
-  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');
+  let oid = signatureAlgorithm.ASN1Objects.queryElementAt(0, Ci.nsIASN1Object);
+  return oid.displayValue;
+}
+
+function processCertChain(chain) {
+  let output = [];
+  let enumerator = chain.getEnumerator();
+  while (enumerator.hasMoreElements()) {
+    let cert = enumerator.getNext().QueryInterface(Ci.nsIX509Cert);
+    output.push({
+      sha256Fingerprint: cert.sha256Fingerprint.replace(/:/g, "").toLowerCase(),
+      isBuiltInRoot: cert.isBuiltInRoot,
+      signatureAlgorithm: delocalizeAlgorithm(getSignatureAlgorithm(cert)),
+    });
+  }
+  return output;
+}
+
+class CertificateVerificationResult {
+  constructor(resolve) {
+    this.resolve = resolve;
+  }
+
+  verifyCertFinished(aPRErrorCode, aVerifiedChain, aEVStatus) {
+    let result = { errorCode: aPRErrorCode, error: "", chain: [] };
+    if (aPRErrorCode == 0) {
+      result.chain = processCertChain(aVerifiedChain);
+    } else {
+      result.error = "certificate reverification";
+    }
+    this.resolve(result);
   }
 }
 
-function makeRequest(index, url, body) {
-  return new Promise(resolve => {
-    let t0 = Date.now();
+function makeRequest() {
+  return new Promise((resolve) => {
+    let hostname = "telemetry.mozilla.org";
     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;
+                .createInstance(Ci.nsIXMLHttpRequest);
+    req.open("GET", "https://" + hostname);
+    req.timeout = 30000;
+    req.addEventListener("error", (evt) => {
+      // If we can't connect to telemetry.mozilla.org, then how did we even
+      // download the experiment? In any case, we may still be able to get some
+      // information.
+      let result = { error: "connection error" };
+      if (evt.target.channel && evt.target.channel.securityInfo) {
+        let securityInfo = evt.target.channel.securityInfo
+                             .QueryInterface(Ci.nsITransportSecurityInfo);
+        if (securityInfo) {
+          result.errorCode = securityInfo.errorCode;
+        }
+        if (securityInfo && securityInfo.failedCertChain) {
+          result.chain = processCertChain(securityInfo.failedCertChain);
+        }
+      }
       resolve(result);
     });
-    req.addEventListener("load", e => {
-      result.status = e.target.status;
-      recordSecInfo(e.target.channel, result);
-      result.elapsed = Date.now() - t0;
-      resolve(result);
+    req.addEventListener("timeout", (evt) => {
+      resolve({ error: "timeout" });
     });
-
-    if (body) {
-      req.send(JSON.stringify(body));
-    } else {
-      req.send();
-    }
+    req.addEventListener("load", (evt) => {
+      let securityInfo = evt.target.channel.securityInfo
+                           .QueryInterface(Ci.nsITransportSecurityInfo);
+      if (securityInfo.securityState &
+          Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN) {
+        resolve({ error: "user override" });
+        return;
+      }
+      let sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
+                        .SSLStatus;
+      let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                     .getService(Ci.nsIX509CertDB);
+      let result = new CertificateVerificationResult(resolve);
+      // Unfortunately, we don't have direct access to the verified certificate
+      // chain as built by the AuthCertificate hook, so we have to re-build it
+      // here. In theory we are likely to get the same result.
+      certdb.asyncVerifyCertAtTime(sslStatus.serverCert,
+                                   2, // certificateUsageSSLServer
+                                   0, // flags
+                                   hostname,
+                                   Date.now() / 1000,
+                                   result);
+    });
+    req.send();
   });
 }
 
 function report(result) {
-  console.log("Result");
+  console.log("mitm-prevalence-beta51 telemetry experiment results:");
   console.log(result);
-  
-  return TelemetryController.submitExternalPing(
-    "tls-13-study-v1",
-    {
-      results: result
-    }, {});
+  return TelemetryController.submitExternalPing("mitm-prevalence-beta51",
+                                                result, {});
 }
 
 function disable() {
   Experiments.instance().disableExperiment("FROM_API");
 }
 
-// Inefficient shuffle algorithm, but n <= 10
-function shuffleArray(inarr) {
-  var out = [];
-    while(inarr.length > 0) {
-        x = Math.floor(Math.random() * inarr.length);
-        out.push(inarr.splice(x,1)[0])
-  }
-  return out;
-}
-
 // This is a simple experiment:
 // - Install
-// - Connect to a bunch of servers and record the results
+// - Connect to a server and record the results
 //   (see README.md for details on report format)
 // - Deactivate.
 function install() {
-  let todo = [];
-  let shuffled = shuffleArray(kURLs);
-  
-  for (var i in shuffled) {
-    todo.push(makeRequest(i, shuffled[i], null ));
-  }
-
-  return Promise.all(todo)
-    .then(result => report(result))
-    .catch(e => Cu.reportError(e))
-    .then(_ => {
-      disable();
-    });
+  return makeRequest()
+    .then(report)
+    .catch(Cu.reportError)
+    .then(disable);
 }
 
 function startup() {}
 function shutdown() {}
 function uninstall() {}
-
copy from experiments/tls13-compat-nightly52/code/install.rdf
copy to experiments/mitm-prevalence-beta51/code/install.rdf
--- a/experiments/tls13-compat-nightly52/code/install.rdf
+++ b/experiments/mitm-prevalence-beta51/code/install.rdf
@@ -1,24 +1,24 @@
 <?xml version="1.0" encoding="utf-8"?>
 <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>tls13-compat-nightly52@experiments.mozilla.org</em:id>
+    <em:id>mitm-prevalence-beta51@experiments.mozilla.org</em:id>
     <em:version>1.0.0</em:version>
     <em:type>128</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:unpack>false</em:unpack>
 
     <!-- Firefox -->
     <em:targetApplication>
       <Description>
         <em:id>{5428e386-68cf-4283-8ee3-04c12d3f4f4e}</em:id>
-        <em:minVersion>52.0a1</em:minVersion>
-        <em:maxVersion>52.0a1</em:maxVersion>
+        <em:minVersion>50.0</em:minVersion>
+        <em:maxVersion>51.0</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=1310338</em:aboutURL>
+    <em:name>TLS Intercepting Proxy Prevalence Measurer</em:name>
+    <em:description>Measures prevalence to TLS intercepting proxies</em:description>
+    <em:aboutURL>https://bugzilla.mozilla.org/show_bug.cgi?id=1311479</em:aboutURL>
   </Description>
 </RDF>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0b93d83a0b29425f718a329a98222338b6eabd45
GIT binary patch
literal 6952
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
copy from experiments/tls13-compat-nightly52/manifest.json
copy to experiments/mitm-prevalence-beta51/manifest.json
--- a/experiments/tls13-compat-nightly52/manifest.json
+++ b/experiments/mitm-prevalence-beta51/manifest.json
@@ -1,18 +1,18 @@
 {
   "publish"     : true,
   "priority"    : 2,
-  "name"        : "TLS 1.3 Compatibility Testing 1",
-  "description" : "Measure the compatibility of TLS 1.3",
-  "info"        : "<p><a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1310338\">Related bug</a></p>",
+  "name"        : "MitM Prevalence",
+  "description" : "Measure the prevalence of TLS intercepting proxies (MitM)",
+  "info"        : "<p><a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=1311479\">Related bug</a></p>",
   "manifest"    : {
-    "id"               : "tls13-compat-nightly52@experiments.mozilla.org",
-    "startTime"        : 1477637000,
-    "endTime"          : 1481760000,
+    "id"               : "mitm-prevalence-beta51@experiments.mozilla.org",
+    "startTime"        : 1479081600,
+    "endTime"          : 1480291200,
     "maxActiveSeconds" : 86400,
     "appName"          : ["Firefox"],
-    "channel"          : ["nightly"],
-    "minVersion"       : "52.0a1*",
-    "maxVersion"       : "52.0a1",
+    "channel"          : ["beta"],
+    "minVersion"       : "50.0",
+    "maxVersion"       : "51.0",
     "sample"           : 0.1
   }
 }