Bug 1241821 - Create a SecurityReporter component for TLS Error Reports r=mossop, keeler

This takes the TLS Error Reporting functionality used in the aboutNetError.xhtml
and aboutCertError.xhtml error pages and moves it to its own component. This
allows us to make use of this same error reporting functionality from elsewhere.
Notably, this allows us to send error reports for issues that occur when loading
subresources.
The xpcshell test included is in security/manager/ssl/tests because we need to
make use of tlsserver functionality from the PSM tests.
This commit is contained in:
Mark Goodwin 2016-01-29 13:45:17 +00:00
parent 4c18bc2045
commit e7ee60296d
11 changed files with 300 additions and 0 deletions

View File

@ -325,6 +325,7 @@
@RESPATH@/components/toolkit_finalizationwitness.xpt
@RESPATH@/components/toolkit_formautofill.xpt
@RESPATH@/components/toolkit_osfile.xpt
@RESPATH@/components/toolkit_securityreporter.xpt
#ifdef NIGHTLY_BUILD
@RESPATH@/components/toolkit_perfmonitoring.xpt
#endif
@ -707,6 +708,10 @@
@RESPATH@/components/PrivateBrowsing.manifest
@RESPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
; Security Reports
@RESPATH@/components/SecurityReporter.manifest
@RESPATH@/components/SecurityReporter.js
; Signed Packaged Content
@RESPATH@/components/InstallPackagedWebapp.manifest
@RESPATH@/components/InstallPackagedWebapp.js

View File

@ -319,6 +319,7 @@
@RESPATH@/components/toolkit_finalizationwitness.xpt
@RESPATH@/components/toolkit_formautofill.xpt
@RESPATH@/components/toolkit_osfile.xpt
@RESPATH@/components/toolkit_securityreporter.xpt
#ifdef NIGHTLY_BUILD
@RESPATH@/components/toolkit_perfmonitoring.xpt
#endif
@ -629,6 +630,10 @@
@RESPATH@/components/PrivateBrowsing.manifest
@RESPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
; Security Reports
@RESPATH@/components/SecurityReporter.manifest
@RESPATH@/components/SecurityReporter.js
; Signed Packaged Content
@RESPATH@/components/InstallPackagedWebapp.manifest
@RESPATH@/components/InstallPackagedWebapp.js

View File

@ -251,6 +251,7 @@
@BINPATH@/components/toolkit_finalizationwitness.xpt
@BINPATH@/components/toolkit_formautofill.xpt
@BINPATH@/components/toolkit_osfile.xpt
@RESPATH@/components/toolkit_securityreporter.xpt
#ifdef NIGHTLY_BUILD
@BINPATH@/components/toolkit_perfmonitoring.xpt
#endif
@ -466,6 +467,10 @@
@BINPATH@/components/url-classifier.xpt
#endif
; Security Reports
@RESPATH@/components/SecurityReporter.manifest
@RESPATH@/components/SecurityReporter.js
; [Browser Chrome Files]
@BINPATH@/chrome/browser@JAREXT@
@BINPATH@/chrome/browser.manifest

View File

@ -228,6 +228,7 @@
@BINPATH@/components/toolkit_finalizationwitness.xpt
@BINPATH@/components/toolkit_formautofill.xpt
@BINPATH@/components/toolkit_osfile.xpt
@RESPATH@/components/toolkit_securityreporter.xpt
#ifdef NIGHTLY_BUILD
@BINPATH@/components/toolkit_perfmonitoring.xpt
#endif
@ -434,6 +435,10 @@
@BINPATH@/components/PrivateBrowsing.manifest
@BINPATH@/components/PrivateBrowsingTrackingProtectionWhitelist.js
; Security Reports
@RESPATH@/components/SecurityReporter.manifest
@RESPATH@/components/SecurityReporter.js
; [Browser Chrome Files]
@BINPATH@/chrome/toolkit@JAREXT@
@BINPATH@/chrome/toolkit.manifest

View File

@ -0,0 +1,132 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */
/* This test is for the TLS error reporting functionality exposed by
* SecurityReporter.js in /toolkit/components/securityreporter. The test is
* here because we make use of the tlsserver functionality that lives with the
* PSM ssl tests.
*
* The testing here will be augmented by the existing mochitests for the
* error reporting functionality in aboutNetError.xhtml and
* aboutCertError.xhtml once these make use of this component.
*/
"use strict";
const CC = Components.Constructor;
const Cm = Components.manager;
Cu.import("resource://testing-common/AppInfo.jsm");
updateAppInfo();
// We must get the profile before performing operations on the cert db.
do_get_profile();
const certdb = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
const reporter = Cc["@mozilla.org/securityreporter;1"]
.getService(Ci.nsISecurityReporter);
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream", "setInputStream");
var server;
// this allows us to create a callback which checks that a report is as
// expected.
function getReportCheck(expectReport, expectedError) {
return function sendReportWithInfo(transportSecurityInfo) {
// register a path handler on the server
server.registerPathHandler("/submit/sslreports",
function(request, response) {
if (expectReport) {
let report = JSON.parse(readDataFromRequest(request));
do_check_eq(report.errorCode, expectedError);
response.setStatusLine(null, 201, "Created");
response.write("Created");
} else {
do_throw("No report should have been received");
}
});
reporter.reportTLSError(transportSecurityInfo, "example.com", -1);
}
}
// read the request body from a request
function readDataFromRequest(aRequest) {
if (aRequest.method == "POST" || aRequest.method == "PUT") {
if (aRequest.bodyInputStream) {
let inputStream = new BinaryInputStream(aRequest.bodyInputStream);
let bytes = [];
let available;
while ((available = inputStream.available()) > 0) {
Array.prototype.push.apply(bytes, inputStream.readByteArray(available));
}
return String.fromCharCode.apply(null, bytes);
}
}
return null;
}
function run_test() {
// start a report server
server = new HttpServer();
server.start(-1);
let port = server.identity.primaryPort;
// Set the reporting URL to ensure any reports are sent to the test server
Services.prefs.setCharPref("security.ssl.errorReporting.url",
`http://localhost:${port}/submit/sslreports`);
// set strict-mode pinning enforcement so we can cause connection failures.
Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2);
// start a TLS server
add_tls_server_setup("BadCertServer", "bad_certs");
// Add a user-specified trust anchor.
addCertFromFile(certdb, "bad_certs/other-test-ca.pem", "CTu,u,u");
// Cause a reportable condition with error reporting disabled. No report
// should be sent.
Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", false);
add_connection_test("expired.example.com",
SEC_ERROR_EXPIRED_CERTIFICATE, null,
getReportCheck(false));
// Now enable reporting
add_test(function () {
Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", true);
run_next_test();
});
// test calling the component with no transportSecurityInfo. No report should
// be sent even though reporting is enabled.
add_test(function() {
server.registerPathHandler("/submit/sslreports",
function(request, response){
do_throw("No report should be sent");
});
reporter.reportTLSError(null, "example.com", -1);
run_next_test();
});
// Test sending a report with no error. This allows us to check the case
// where there is no failed cert chain
add_connection_test("good.include-subdomains.pinning.example.com",
PRErrorCodeSuccess, null,
getReportCheck(true, PRErrorCodeSuccess));
// Test sending a report where there is an error and a failed cert chain.
add_connection_test("expired.example.com",
SEC_ERROR_EXPIRED_CERTIFICATE, null,
getReportCheck(true, SEC_ERROR_EXPIRED_CERTIFICATE));
run_next_test();
}

View File

@ -150,3 +150,6 @@ skip-if = toolkit == 'android' || toolkit == 'gonk'
run-sequentially = hardcoded ports
[test_weak_crypto.js]
firefox-appdir = browser
# The TLS error reporting functionality lives in /toolkit but needs tlsserver
[test_toolkit_securityreporter.js]

View File

@ -48,6 +48,7 @@ DIRS += [
'reader',
'remotebrowserutils',
'reflect',
'securityreporter',
'sqlite',
'startup',
'statusfilter',

View File

@ -0,0 +1,112 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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 { classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.importGlobalProperties(['fetch']);
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
const protocolHandler = Cc["@mozilla.org/network/protocol;1?name=http"]
.getService(Ci.nsIHttpProtocolHandler);
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const TLS_ERROR_REPORT_TELEMETRY_SUCCESS = 6;
const TLS_ERROR_REPORT_TELEMETRY_FAILURE = 7;
const HISTOGRAM_ID = "TLS_ERROR_REPORT_UI";
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
"resource://gre/modules/UpdateUtils.jsm");
function getDERString(cert)
{
var length = {};
var derArray = cert.getRawDER(length);
var derString = '';
for (var i = 0; i < derArray.length; i++) {
derString += String.fromCharCode(derArray[i]);
}
return derString;
}
function SecurityReporter() { }
SecurityReporter.prototype = {
classDescription: "Security reporter component",
classID: Components.ID("{8a997c9a-bea1-11e5-a1fa-be6aBc8e7f8b}"),
contractID: "@mozilla.org/securityreporter;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsISecurityReporter]),
reportTLSError: function(transportSecurityInfo, hostname, port) {
// don't send if there's no transportSecurityInfo (since the report cannot
// contain anything of interest)
if (!transportSecurityInfo) {
return;
}
// don't send a report if the pref is not enabled
if (!Services.prefs.getBoolPref("security.ssl.errorReporting.enabled")) {
return;
}
// Don't send a report if the host we're connecting to is the report
// server (otherwise we'll get loops when this fails)
let endpoint =
Services.prefs.getCharPref("security.ssl.errorReporting.url");
let reportURI = Services.io.newURI(endpoint, null, null);
if (reportURI.host == hostname) {
return;
}
// Convert the nsIX509CertList into a format that can be parsed into
// JSON
let asciiCertChain = [];
if (transportSecurityInfo.failedCertChain) {
let certs = transportSecurityInfo.failedCertChain.getEnumerator();
while (certs.hasMoreElements()) {
let cert = certs.getNext();
cert.QueryInterface(Ci.nsIX509Cert);
asciiCertChain.push(btoa(getDERString(cert)));
}
}
let report = {
hostname: hostname,
port: port,
timestamp: Math.round(Date.now() / 1000),
errorCode: transportSecurityInfo.errorCode,
failedCertChain: asciiCertChain,
userAgent: protocolHandler.userAgent,
version: 1,
build: Services.appinfo.appBuildID,
product: Services.appinfo.name,
channel: UpdateUtils.UpdateChannel
}
fetch(endpoint, {
method: "POST",
body: JSON.stringify(report),
headers: {
'Content-Type': 'application/json'
}
}).then(function (aResponse) {
if (!aResponse.ok) {
// request returned non-success status
Services.telemetry.getHistogramById(HISTOGRAM_ID)
.add(TLS_ERROR_REPORT_TELEMETRY_FAILURE);
} else {
Services.telemetry.getHistogramById(HISTOGRAM_ID)
.add(TLS_ERROR_REPORT_TELEMETRY_SUCCESS);
}
}).catch(function (e) {
// error making request to reportURL
Services.telemetry.getHistogramById(HISTOGRAM_ID)
.add(TLS_ERROR_REPORT_TELEMETRY_FAILURE);
});
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SecurityReporter]);

View File

@ -0,0 +1,2 @@
component {8a997c9a-bea1-11e5-a1fa-be6aBc8e7f8b} SecurityReporter.js
contract @mozilla.org/securityreporter;1 {8a997c9a-bea1-11e5-a1fa-be6aBc8e7f8b}

View File

@ -0,0 +1,16 @@
# -*- Mode: python; c-basic-offset: 4; 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/.
XPIDL_MODULE = 'toolkit_securityreporter'
XPIDL_SOURCES += [
'nsISecurityReporter.idl',
]
EXTRA_COMPONENTS += [
'SecurityReporter.js',
'SecurityReporter.manifest',
]

View File

@ -0,0 +1,14 @@
/* 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/. */
#include "nsISupports.idl"
#include "nsITransportSecurityInfo.idl"
[scriptable, uuid(8a997c9a-bea1-11e5-a1fa-be6aBc8e7f8b)]
interface nsISecurityReporter : nsISupports
{
void reportTLSError(in nsITransportSecurityInfo aSecurityInfo,
in AUTF8String aHostname,
in long aPort);
};