Bug 1096197 - Ensure SSL Error reports work when there is no failed certificate chain. r=keeler

--HG--
rename : browser/base/content/test/general/browser_bug846489.js => browser/base/content/test/general/browser_ssl_error_reports.js
rename : browser/base/content/test/general/browser_bug846489_content.js => browser/base/content/test/general/browser_ssl_error_reports_content.js
This commit is contained in:
Mark Goodwin 2015-01-07 02:28:00 -05:00
parent 63bb0e7ce6
commit 1e76535956
8 changed files with 163 additions and 85 deletions

View File

@ -2643,11 +2643,6 @@ let BrowserOnClick = {
let transportSecurityInfo = serhelper.deserializeObject(securityInfo);
transportSecurityInfo.QueryInterface(Ci.nsITransportSecurityInfo)
if (transportSecurityInfo.failedCertChain == null) {
Cu.reportError("transportSecurityInfo didn't have a failedCertChain for a failedChannel");
return;
}
showReportStatus("activity");
/*
@ -2677,11 +2672,14 @@ let BrowserOnClick = {
// Convert the nsIX509CertList into a format that can be parsed into
// JSON
let asciiCertChain = [];
let certs = transportSecurityInfo.failedCertChain.getEnumerator();
while (certs.hasMoreElements()) {
let cert = certs.getNext();
cert.QueryInterface(Ci.nsIX509Cert);
asciiCertChain.push(btoa(getDERString(cert)));
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 = {

View File

@ -10,10 +10,10 @@ support-files =
browser_bug479408_sample.html
browser_bug678392-1.html
browser_bug678392-2.html
browser_bug846489_content.js
browser_bug970746.xhtml
browser_fxa_oauth.html
browser_registerProtocolHandler_notification.html
browser_ssl_error_reports_content.js
browser_star_hsts.sjs
browser_tab_dragdrop2_frame1.xul
browser_web_channel.html
@ -153,7 +153,6 @@ skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and
[browser_bug419612.js]
skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
[browser_bug422590.js]
[browser_bug846489.js]
[browser_bug423833.js]
skip-if = true # bug 428712
[browser_bug424101.js]
@ -399,6 +398,7 @@ support-files =
searchSuggestionUI.html
searchSuggestionUI.js
[browser_selectTabAtIndex.js]
[browser_ssl_error_reports.js]
[browser_star_hsts.js]
[browser_subframe_favicons_not_used.js]
[browser_tabDrop.js]

View File

@ -1,14 +1,15 @@
var badPin = "https://include-subdomains.pinning.example.com";
var enabledPref = false;
var automaticPref = false;
var urlPref = "security.ssl.errorReporting.url";
var enforcement_level = 1;
let badChainURL = "https://badchain.include-subdomains.pinning.example.com";
let noCertURL = "https://fail-handshake.example.com";
let enabledPref = false;
let automaticPref = false;
let urlPref = "security.ssl.errorReporting.url";
let enforcement_level = 1;
function loadFrameScript() {
let mm = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
const ROOT = getRootDirectory(gTestPath);
mm.loadFrameScript(ROOT+"browser_bug846489_content.js", true);
mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true);
}
add_task(function*(){
@ -16,7 +17,8 @@ add_task(function*(){
loadFrameScript();
SimpleTest.requestCompleteLog();
yield testSendReportDisabled();
yield testSendReportManual();
yield testSendReportManual(badChainURL, "succeed");
yield testSendReportManual(noCertURL, "nocert");
yield testSendReportAuto();
yield testSendReportError();
yield testSetAutomatic();
@ -26,9 +28,9 @@ add_task(function*(){
function createNetworkErrorMessagePromise(aBrowser) {
return new Promise(function(resolve, reject) {
// Error pages do not fire "load" events, so use a progressListener.
var originalDocumentURI = aBrowser.contentDocument.documentURI;
let originalDocumentURI = aBrowser.contentDocument.documentURI;
var progressListener = {
let progressListener = {
onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
// Make sure nothing other than an error page is loaded.
if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE)) {
@ -42,7 +44,7 @@ function createNetworkErrorMessagePromise(aBrowser) {
if (doc.getElementById("reportCertificateError")) {
// Wait until the documentURI changes (from about:blank) this should
// be the error page URI.
var documentURI = doc.documentURI;
let documentURI = doc.documentURI;
if (documentURI == originalDocumentURI) {
return;
}
@ -50,13 +52,13 @@ function createNetworkErrorMessagePromise(aBrowser) {
aWebProgress.removeProgressListener(progressListener,
Ci.nsIWebProgress.NOTIFY_LOCATION |
Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
var matchArray = /about:neterror\?.*&d=([^&]*)/.exec(documentURI);
let matchArray = /about:neterror\?.*&d=([^&]*)/.exec(documentURI);
if (!matchArray) {
reject("no network error message found in URI")
return;
}
var errorMsg = matchArray[1];
let errorMsg = matchArray[1];
resolve(decodeURIComponent(errorMsg));
}
},
@ -74,7 +76,7 @@ function createNetworkErrorMessagePromise(aBrowser) {
// check we can set the 'automatically send' pref
let testSetAutomatic = Task.async(function*() {
setup();
let tab = gBrowser.addTab(badPin, {skipAnimation: true});
let tab = gBrowser.addTab(badChainURL, {skipAnimation: true});
let browser = tab.linkedBrowser;
let mm = browser.messageManager;
@ -119,12 +121,13 @@ let testSetAutomatic = Task.async(function*() {
});
// test that manual report sending (with button clicks) works
let testSendReportManual = Task.async(function*() {
let testSendReportManual = Task.async(function*(testURL, suffix) {
setup();
Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", true);
Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?succeed");
Services.prefs.setCharPref("security.ssl.errorReporting.url",
"https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?" + suffix);
let tab = gBrowser.addTab(badPin, {skipAnimation: true});
let tab = gBrowser.addTab(testURL, {skipAnimation: true});
let browser = tab.linkedBrowser;
let mm = browser.messageManager;
@ -134,7 +137,8 @@ let testSendReportManual = Task.async(function*() {
let netError = createNetworkErrorMessagePromise(browser);
yield netError;
netError.then(function(val){
is(val.startsWith("An error occurred during a connection to include-subdomains.pinning.example.com"), true ,"ensure the correct error message came from about:neterror");
is(val.startsWith("An error occurred during a connection to"), true,
"ensure the correct error message came from about:neterror");
});
// Check the report starts on click
@ -185,7 +189,7 @@ let testSendReportAuto = Task.async(function*() {
Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", true);
Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?succeed");
let tab = gBrowser.addTab(badPin, {skipAnimation: true});
let tab = gBrowser.addTab(badChainURL, {skipAnimation: true});
let browser = tab.linkedBrowser;
let mm = browser.messageManager;
@ -234,7 +238,7 @@ let testSendReportError = Task.async(function*() {
Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", true);
Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?error");
let tab = gBrowser.addTab(badPin, {skipAnimation: true});
let tab = gBrowser.addTab(badChainURL, {skipAnimation: true});
let browser = tab.linkedBrowser;
let mm = browser.messageManager;
@ -255,12 +259,12 @@ let testSendReportError = Task.async(function*() {
let reportErrors = new Promise(function(resolve, reject) {
mm.addMessageListener("ssler-test:SSLErrorReportStatus", function(message) {
switch(message.data.reportStatus) {
case "complete":
reject(message.data.reportStatus);
break;
case "error":
resolve(message.data.reportStatus);
break;
case "complete":
reject(message.data.reportStatus);
break;
case "error":
resolve(message.data.reportStatus);
break;
}
});
});
@ -276,7 +280,7 @@ let testSendReportDisabled = Task.async(function*() {
Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", false);
Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://offdomain.com");
let tab = gBrowser.addTab(badPin, {skipAnimation: true});
let tab = gBrowser.addTab(badChainURL, {skipAnimation: true});
let browser = tab.linkedBrowser;
let mm = browser.messageManager;
@ -289,12 +293,12 @@ let testSendReportDisabled = Task.async(function*() {
let reportErrors = new Promise(function(resolve, reject) {
mm.addMessageListener("ssler-test:SSLErrorReportStatus", function(message) {
switch(message.data.reportStatus) {
case "complete":
reject(message.data.reportStatus);
break;
case "error":
resolve(message.data.reportStatus);
break;
case "complete":
reject(message.data.reportStatus);
break;
case "error":
resolve(message.data.reportStatus);
break;
}
});
});

View File

@ -3,39 +3,67 @@ const EXPECTED_CHAIN = [
"MIIC2jCCAcKgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwHhcNMTQwOTI1MjEyMTU0WhcNMjQwOTI1MjEyMTU0WjAmMSQwIgYDVQQDExtBbHRlcm5hdGUgVHJ1c3RlZCBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDBT+BwAhO52IWgSIdZZifU9LHOs3IR/+8DCC0WP5d/OuyKlZ6Rqd0tsd3i7durhQyjHSbLf2lJStcnFjcVEbEnNI76RuvlN8xLLn5eV+2Ayr4cZYKztudwRmw+DV/iYAiMSy0hs7m3ssfX7qpoi1aNRjUanwU0VTCPQhF1bEKAC2du+C5Z8e92zN5t87w7bYr7lt+m8197XliXEu+0s9RgnGwGaZ296BIRz6NOoJYTa43n06LU1I1+Z4d6lPdzUFrSR0GBaMhUSurUBtOin3yWiMhg1VHX/KwqGc4als5GyCVXy8HGrA/0zQPOhetxrlhEVAdK/xBt7CZvByj1Rcc7AgMBAAGjEzARMA8GA1UdEwQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAJq/hogSRqzPWTwX4wTn/DVSNdWwFLv53qep9YrSMJ8ZsfbfK9Es4VP4dBLRQAVMJ0Z5mW1I6d/n0KayTanuUBvemYdxPi/qQNSs8UJcllqdhqWzmzAg6a0LxrMnEeKzPBPD6q8PwQ7tYP+B4sBN9tnnsnyPgti9ZiNZn5FwXZliHXseQ7FE9/SqHlLw5LXW3YtKjuti6RmuV6fq3j+D4oeC5vb1mKgIyoTqGN6ze57v8RHi+pQ8Q+kmoUn/L3Z2YmFe4SKN/4WoyXr8TdejpThGOCGCAd3565s5gOx5QfSQX11P8NZKO8hcN0tme3VzmGpHK0Z/6MTmdpNaTwQ6odk="
];
function handleRequest(request, response)
{
if (request.queryString === "succeed") {
// read the report from the client
let inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
inputStream.init(request.bodyInputStream, 0x01, 0004, 0);
function parseReport(request) {
// read the report from the request
let inputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
inputStream.init(request.bodyInputStream, 0x01, 0004, 0);
let body = "";
if (inputStream) {
while (inputStream.available()) {
body = body + inputStream.read(inputStream.available());
}
let body = "";
if (inputStream) {
while (inputStream.available()) {
body = body + inputStream.read(inputStream.available());
}
// parse the report
let report = JSON.parse(body);
let certChain = report.failedCertChain;
}
// parse the report
return JSON.parse(body);
}
// ensure the cert chain is what we expect
for (idx in certChain) {
if (certChain[idx] !== EXPECTED_CHAIN[idx]) {
// if the chain differs, send an error response to cause test
// failure
function handleRequest(request, response) {
let report = {};
let certChain = [];
switch (request.queryString) {
case "succeed":
report = parseReport(request);
certChain = report.failedCertChain;
// ensure the cert chain is what we expect
for (idx in certChain) {
if (certChain[idx] !== EXPECTED_CHAIN[idx]) {
// if the chain differs, send an error response to cause test
// failure
response.setStatusLine("1.1", 500, "Server error");
response.write("<html>The report contained an unexpected chain</html>");
return;
}
}
// if all is as expected, send the 201 the client expects
response.setStatusLine("1.1", 201, "Created");
response.write("<html>OK</html>");
break;
case "nocert":
report = parseReport(request);
certChain = report.failedCertChain;
if (certChain && certChain.length > 0) {
// We're not expecting a chain; if there is one, send an error
response.setStatusLine("1.1", 500, "Server error");
response.write("<html>The report contained an unexpected chain</html>");
return;
}
}
// if all is as expected, send the 201 the client expects
response.setStatusLine("1.1", 201, "Created");
response.write("<html>OK</html>");
} else if (request.queryString === "error") {
response.setStatusLine("1.1", 500, "Server error");
response.write("<html>server error</html>");
// if all is as expected, send the 201 the client expects
response.setStatusLine("1.1", 201, "Created");
response.write("<html>OK</html>");
break;
case "error":
response.setStatusLine("1.1", 500, "Server error");
response.write("<html>server error</html>");
break;
default:
response.setStatusLine("1.1", 500, "Server error");
response.write("<html>succeed, nocert or error expected</html>");
break;
}
}

View File

@ -20,7 +20,8 @@
# number is the default for the protocol.
#
# Unrecognized options are ignored. Recognized options are "primary" and
# "privileged", "nocert", "cert=some_cert_nickname", "redir=hostname".
# "privileged", "nocert", "cert=some_cert_nickname", "redir=hostname" and
# "failHandshake".
#
# "primary" denotes a location which is the canonical location of
# the server; this location is the one assumed for requests which don't
@ -32,6 +33,9 @@
# "nocert" makes sense only for https:// hosts and means there is not
# any certificate automatically generated for this host.
#
# "failHandshake" causes the tls handshake to fail (by sending a client hello to
# the client).
#
# "cert=nickname" tells the pgo server to use a particular certificate
# for this host. The certificate is referenced by its nickname that must
# not contain any spaces. The certificate key files (PKCS12 modules)
@ -227,7 +231,8 @@ https://include-subdomains.pinning-dynamic.example.com:443 privileged,cer
https://bad.include-subdomains.pinning-dynamic.example.com:443 privileged,cert=dynamicPinningBad
# Host for static pin tests
https://include-subdomains.pinning.example.com:443 privileged,cert=staticPinningBad
https://badchain.include-subdomains.pinning.example.com:443 privileged,cert=staticPinningBad
https://fail-handshake.example.com:443 privileged,failHandshake
# Hosts for sha1 console warning tests
https://sha1ee.example.com:443 privileged,cert=sha1_end_entity

View File

@ -914,7 +914,7 @@ class SSLTunnel:
config.write("redirhost:%s:%s:%s:%s\n" %
(loc.host, loc.port, self.sslPort, redirhost))
if self.useSSLTunnelExts and option in ('ssl3', 'rc4'):
if self.useSSLTunnelExts and option in ('ssl3', 'rc4', 'failHandshake'):
config.write("%s:%s:%s:%s\n" % (option, loc.host, loc.port, self.sslPort))
def buildConfig(self, locations):

View File

@ -155,6 +155,7 @@ typedef struct {
PLHashTable* host_redir_table;
PLHashTable* host_ssl3_table;
PLHashTable* host_rc4_table;
PLHashTable* host_failhandshake_table;
} server_info_t;
typedef struct {
@ -259,10 +260,17 @@ void SignalShutdown()
PR_Unlock(shutdown_lock);
}
// available flags
enum {
USE_SSL3 = 1 << 0,
USE_RC4 = 1 << 1,
FAIL_HANDSHAKE = 1 << 2
};
bool ReadConnectRequest(server_info_t* server_info,
relayBuffer& buffer, int32_t* result, string& certificate,
client_auth_option* clientauth, string& host, string& location,
bool* ssl3, bool* rc4)
int32_t* flags)
{
if (buffer.present() < 4) {
LOG_DEBUG((" !! only %d bytes present in the buffer", (int)buffer.present()));
@ -311,9 +319,17 @@ bool ReadConnectRequest(server_info_t* server_info,
if (redir)
location = static_cast<char*>(redir);
*ssl3 = !!PL_HashTableLookup(server_info->host_ssl3_table, token);
if (PL_HashTableLookup(server_info->host_ssl3_table, token)) {
*flags |= USE_SSL3;
}
*rc4 = !!PL_HashTableLookup(server_info->host_rc4_table, token);
if (PL_HashTableLookup(server_info->host_rc4_table, token)) {
*flags |= USE_RC4;
}
if (PL_HashTableLookup(server_info->host_failhandshake_table, token)) {
*flags |= FAIL_HANDSHAKE;
}
token = strtok2(_caret, "/", &_caret);
if (strcmp(token, "HTTP")) {
@ -326,7 +342,7 @@ bool ReadConnectRequest(server_info_t* server_info,
}
bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, const string &certificate,
const client_auth_option clientAuth, bool ssl3, bool rc4)
const client_auth_option clientAuth, int32_t flags)
{
const char* certnick = certificate.empty() ?
si->cert_nickname.c_str() : certificate.c_str();
@ -349,6 +365,12 @@ bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, const strin
return false;
}
if (flags & FAIL_HANDSHAKE) {
// deliberately cause handshake to fail by sending the client a client hello
SSL_ResetHandshake(ssl_socket, false);
return true;
}
SSLKEAType certKEA = NSS_FindCertKEAType(cert);
if (SSL_ConfigSecureServer(ssl_socket, cert, privKey, certKEA)
!= SECSuccess) {
@ -366,13 +388,13 @@ bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, const strin
SSL_OptionSet(ssl_socket, SSL_REQUIRE_CERTIFICATE, clientAuth == caRequire);
}
if (ssl3) {
if (flags & USE_SSL3) {
SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
SSL_LIBRARY_VERSION_3_0 };
SSL_VersionRangeSet(ssl_socket, &range);
}
if (rc4) {
if (flags & USE_RC4) {
for (uint16_t i = 0; i < SSL_NumImplementedCiphers; ++i) {
uint16_t cipher_id = SSL_ImplementedCiphers[i];
switch (cipher_id) {
@ -580,8 +602,7 @@ void HandleConnection(void* data)
string locationHeader;
client_auth_option clientAuth;
string fullHost;
bool ssl3 = false;
bool rc4 = false;
int32_t flags = 0;
LOG_DEBUG(("SSLTUNNEL(%p)): incoming connection csock(0)=%p, ssock(1)=%p\n",
static_cast<void*>(data),
@ -595,8 +616,8 @@ void HandleConnection(void* data)
if (!do_http_proxy)
{
if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info, certificateToUse, caNone,
ssl3, rc4))
if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info,
certificateToUse, caNone, flags))
client_error = true;
else if (!ConnectSocket(other_sock, &remote_addr, connect_timeout))
client_error = true;
@ -715,7 +736,7 @@ void HandleConnection(void* data)
int32_t response;
if (!connect_accepted && ReadConnectRequest(ci->server_info, buffers[s],
&response, certificateToUse, &clientAuth, fullHost, locationHeader,
&ssl3, &rc4))
&flags))
{
// Mark this as a proxy-only connection (no SSL) if the CONNECT
// request didn't come for port 443 or from any of the server's
@ -737,6 +758,9 @@ void HandleConnection(void* data)
PL_HashTableEnumerateEntries(ci->server_info->host_rc4_table,
match_hostname,
&match);
PL_HashTableEnumerateEntries(ci->server_info->host_failhandshake_table,
match_hostname,
&match);
ci->http_proxy_only = !match.matched;
}
else
@ -872,7 +896,7 @@ void HandleConnection(void* data)
LOG_DEBUG((" not updating to SSL based on http_proxy_only for this socket"));
}
else if (!ConfigureSSLServerSocket(ci->client_sock, ci->server_info,
certificateToUse, clientAuth, ssl3, rc4))
certificateToUse, clientAuth, flags))
{
LOG_ERRORD((" failed to config server socket\n"));
client_error = true;
@ -1004,6 +1028,11 @@ PLHashTable* get_rc4_table(server_info_t* server)
return server->host_rc4_table;
}
PLHashTable* get_failhandshake_table(server_info_t* server)
{
return server->host_failhandshake_table;
}
int parseWeakCryptoConfig(char* const& keyword, char*& _caret,
PLHashTable* (*get_table)(server_info_t*))
{
@ -1179,6 +1208,14 @@ int processConfigLine(char* configLine)
return 1;
}
server.host_failhandshake_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings,
PL_CompareStrings, nullptr, nullptr);;
if (!server.host_failhandshake_table)
{
LOG_ERROR(("Internal, could not create hash table\n"));
return 1;
}
servers.push_back(server);
}
@ -1298,6 +1335,10 @@ int processConfigLine(char* configLine)
return parseWeakCryptoConfig(keyword, _caret, get_rc4_table);
}
if (!strcmp(keyword, "failHandshake")) {
return parseWeakCryptoConfig(keyword, _caret, get_failhandshake_table);
}
// Configure the NSS certificate database directory
if (!strcmp(keyword, "certdbdir"))
{
@ -1518,11 +1559,13 @@ int main(int argc, char** argv)
PL_HashTableEnumerateEntries(it->host_redir_table, freeHostRedirHashItems, nullptr);
PL_HashTableEnumerateEntries(it->host_ssl3_table, freeSSL3HashItems, nullptr);
PL_HashTableEnumerateEntries(it->host_rc4_table, freeRC4HashItems, nullptr);
PL_HashTableEnumerateEntries(it->host_failhandshake_table, freeRC4HashItems, nullptr);
PL_HashTableDestroy(it->host_cert_table);
PL_HashTableDestroy(it->host_clientauth_table);
PL_HashTableDestroy(it->host_redir_table);
PL_HashTableDestroy(it->host_ssl3_table);
PL_HashTableDestroy(it->host_rc4_table);
PL_HashTableDestroy(it->host_failhandshake_table);
}
PR_Cleanup();