bug 1119778 - make "Forget About This Site" clear HSTS and HPKP info r?mgoodwin r?MattN draft
authorDavid Keeler <dkeeler@mozilla.com>
Wed, 27 Jul 2016 17:11:52 -0700
changeset 395060 c9467496901631b4e6e8ac876c4e91eda558aaff
parent 394995 ffac2798999c5b84f1b4605a1280994bb665a406
child 526945 6e53f7464b247eead534e0b24b7a0128b239ce02
push id24713
push userdkeeler@mozilla.com
push dateMon, 01 Aug 2016 19:47:18 +0000
reviewersmgoodwin, MattN
bugs1119778
milestone51.0a1
bug 1119778 - make "Forget About This Site" clear HSTS and HPKP info r?mgoodwin r?MattN MozReview-Commit-ID: IJVQBsryfHq
security/manager/ssl/tests/unit/test_forget_about_site_security_headers.js
security/manager/ssl/tests/unit/xpcshell.ini
toolkit/forgetaboutsite/ForgetAboutSite.jsm
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_forget_about_site_security_headers.js
@@ -0,0 +1,100 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=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/. */
+
+"use strict";
+
+// Ensures that HSTS (HTTP Strict Transport Security) and HPKP (HTTP Public key
+// pinning) are cleared when using "Forget About This Site".
+
+var { ForgetAboutSite } = Cu.import("resource://gre/modules/ForgetAboutSite.jsm", {});
+
+do_register_cleanup(() => {
+  Services.prefs.clearUserPref("security.cert_pinning.enforcement_level");
+  Services.prefs.clearUserPref(
+    "security.cert_pinning.process_headers_from_non_builtin_roots");
+});
+
+const GOOD_MAX_AGE_SECONDS = 69403;
+const NON_ISSUED_KEY_HASH = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+const PINNING_ROOT_KEY_HASH = "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=";
+const VALID_PIN = `pin-sha256="${PINNING_ROOT_KEY_HASH}";`;
+const BACKUP_PIN = `pin-sha256="${NON_ISSUED_KEY_HASH}";`;
+const GOOD_MAX_AGE = `max-age=${GOOD_MAX_AGE_SECONDS};`;
+
+do_get_profile(); // must be done before instantiating nsIX509CertDB
+
+Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2);
+Services.prefs.setBoolPref(
+  "security.cert_pinning.process_headers_from_non_builtin_roots", true);
+
+var certdb = Cc["@mozilla.org/security/x509certdb;1"]
+               .getService(Ci.nsIX509CertDB);
+addCertFromFile(certdb, "test_pinning_dynamic/pinningroot.pem", "CTu,CTu,CTu");
+
+var sss = Cc["@mozilla.org/ssservice;1"]
+            .getService(Ci.nsISiteSecurityService);
+var uri = Services.io.newURI("https://a.pinning2.example.com", null, null);
+
+// This test re-uses certificates from pinning tests because that's easier and
+// simpler than recreating new certificates, hence the slightly longer than
+// necessary domain name.
+var sslStatus = new FakeSSLStatus(constructCertFromFile(
+  "test_pinning_dynamic/a.pinning2.example.com-pinningroot.pem"));
+
+// Test the normal case of processing HSTS and HPKP headers for
+// a.pinning2.example.com, using "Forget About Site" on a.pinning2.example.com,
+// and then checking that the platform doesn't consider a.pinning2.example.com
+// to be HSTS or HPKP any longer.
+add_task(function* () {
+  sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, GOOD_MAX_AGE,
+                    sslStatus, 0);
+  sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
+                    GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0);
+
+  Assert.ok(sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
+                             "a.pinning2.example.com", 0),
+            "a.pinning2.example.com should be HSTS");
+  Assert.ok(sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HPKP,
+                             "a.pinning2.example.com", 0),
+            "a.pinning2.example.com should be HPKP");
+
+  yield ForgetAboutSite.removeDataFromDomain("a.pinning2.example.com");
+
+  Assert.ok(!sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
+                              "a.pinning2.example.com", 0),
+            "a.pinning2.example.com should not be HSTS now");
+  Assert.ok(!sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HPKP,
+                              "a.pinning2.example.com", 0),
+            "a.pinning2.example.com should not be HPKP now");
+});
+
+// TODO (bug 1290529): the platform does not support this yet.
+// Test the case of processing HSTS and HPKP headers for a.pinning2.example.com,
+// using "Forget About Site" on example.com, and then checking that the platform
+// doesn't consider the subdomain to be HSTS or HPKP any longer.
+add_task(function* () {
+  sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, GOOD_MAX_AGE,
+                    sslStatus, 0);
+  sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
+                    GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0);
+
+  Assert.ok(sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
+                             "a.pinning2.example.com", 0),
+            "a.pinning2.example.com should be HSTS (subdomain case)");
+  Assert.ok(sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HPKP,
+                             "a.pinning2.example.com", 0),
+            "a.pinning2.example.com should be HPKP (subdomain case)");
+
+  yield ForgetAboutSite.removeDataFromDomain("example.com");
+
+  // TODO (bug 1290529):
+  // Assert.ok(!sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
+  //                             "a.pinning2.example.com", 0),
+  //           "a.pinning2.example.com should not be HSTS now (subdomain case)");
+  // Assert.ok(!sss.isSecureHost(Ci.nsISiteSecurityService.HEADER_HPKP,
+  //                             "a.pinning2.example.com", 0),
+  //           "a.pinning2.example.com should not be HPKP now (subdomain case)");
+});
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -56,16 +56,18 @@ run-sequentially = hardcoded ports
 skip-if = toolkit == 'android' || buildapp == 'b2g'
 [test_constructX509FromBase64.js]
 [test_content_signing.js]
 [test_datasignatureverifier.js]
 [test_enterprise_roots.js]
 skip-if = os != 'win' # tests a Windows-specific feature
 [test_ev_certs.js]
 run-sequentially = hardcoded ports
+[test_forget_about_site_security_headers.js]
+skip-if = toolkit == 'android' || toolkit == 'gonk'
 [test_getchain.js]
 [test_hash_algorithms.js]
 [test_hash_algorithms_wrap.js]
 # bug 1124289 - run_test_in_child violates the sandbox on b2g and android
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 [test_hmac.js]
 [test_intermediate_basic_usage_constraints.js]
 [test_js_cert_override_service.js]
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -208,14 +208,29 @@ this.ForgetAboutSite = {
     // Push notifications.
     promises.push(new Promise(resolve => {
       var push = Cc["@mozilla.org/push/Service;1"]
                   .getService(Ci.nsIPushService);
       push.clearForDomain(aDomain, status => {
         (Components.isSuccessCode(status) ? resolve : reject)(status);
       });
     }).catch(e => {
-      dump("Web Push may not be available.\n");
+      Cu.reportError("Exception thrown while clearing Push notifications: " +
+                     e.toString());
     }));
 
+    // HSTS and HPKP
+    // TODO (bug 1290529): also remove HSTS/HPKP information for subdomains.
+    // Since we can't enumerate the information in the site security service
+    // (bug 1115712), we can't implement this right now.
+    try {
+      let sss = Cc["@mozilla.org/ssservice;1"].
+                getService(Ci.nsISiteSecurityService);
+      sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, httpsURI, 0);
+      sss.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, httpsURI, 0);
+    } catch (e) {
+      Cu.reportError("Exception thrown while clearing HSTS/HPKP: " +
+                     e.toString());
+    }
+
     return Promise.all(promises);
   }
 };