Bug 787133 - (hpkp) Part 1/2. Header Parsing and interface within PSM. r=keeler, r=mcmanus
authorCamilo Viecco <cviecco@mozilla.com>
Wed, 03 Sep 2014 10:24:12 -0700
changeset 207836 aeacfe204b8d04e4aad1b17d390cf31f5ff160ba
parent 207835 37b7f512963a2b34debd6aaa181636080a554699
child 207837 f86a4cd9c0209a30393b80bc4bce9e54d15adc13
push id27568
push usercbook@mozilla.com
push dateTue, 30 Sep 2014 13:03:13 +0000
treeherdermozilla-central@4475aa556e69 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, mcmanus
bugs787133
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 787133 - (hpkp) Part 1/2. Header Parsing and interface within PSM. r=keeler, r=mcmanus
b2g/chrome/content/devtools/hud.js
browser/devtools/webconsole/webconsole.js
dom/locales/en-US/chrome/security/security.properties
modules/libpref/init/all.js
netwerk/base/public/nsISiteSecurityService.idl
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/test/TestSTSParser.cpp
security/certverifier/moz.build
security/manager/boot/src/PublicKeyPinningService.cpp
security/manager/boot/src/PublicKeyPinningService.h
security/manager/boot/src/nsSiteSecurityService.cpp
security/manager/boot/src/nsSiteSecurityService.h
security/manager/ssl/src/moz.build
security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
security/manager/ssl/tests/unit/head_psm.js
security/manager/ssl/tests/unit/test_ocsp_no_hsts_upgrade.js
security/manager/ssl/tests/unit/test_sss_eviction.js
security/manager/ssl/tests/unit/test_sss_savestate.js
security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
security/manager/ssl/tests/unit/test_sts_preloadlist_perwindowpb.js
--- a/b2g/chrome/content/devtools/hud.js
+++ b/b2g/chrome/content/devtools/hud.js
@@ -264,16 +264,17 @@ let consoleWatcher = {
     errors: false,
     security: false
   },
   _security: [
     'Mixed Content Blocker',
     'Mixed Content Message',
     'CSP',
     'Invalid HSTS Headers',
+    'Invalid HPKP Headers',
     'Insecure Password Field',
     'SSL',
     'CORS'
   ],
 
   init: function cw_init(client) {
     this._client = client;
     this.consoleListener = this.consoleListener.bind(this);
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -4669,16 +4669,17 @@ var Utils = {
       return CATEGORY_CSS;
     }
 
     switch (category) {
       case "Mixed Content Blocker":
       case "Mixed Content Message":
       case "CSP":
       case "Invalid HSTS Headers":
+      case "Invalid HPKP Headers":
       case "Insecure Password Field":
       case "SSL":
       case "CORS":
       case "Iframe Sandbox":
         return CATEGORY_SECURITY;
 
       default:
         return CATEGORY_JS;
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -3,15 +3,17 @@
 BlockMixedDisplayContent = Blocked loading mixed display content "%1$S"
 BlockMixedActiveContent = Blocked loading mixed active content "%1$S"
 
 # CORS
 CrossSiteRequestBlocked=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. This can be fixed by moving the resource to the same domain or enabling CORS.
 
 # LOCALIZATION NOTE: Do not translate "Strict-Transport-Security" or "HSTS"
 InvalidSTSHeaders=The site specified an invalid Strict-Transport-Security header.
+# LOCALIZATION NOTE: Do not translate "Public-Key-Pins or HPKP"
+InvalidPKPHeaders=The site specified an invalid Public-Key-Pins header.
 InsecurePasswordsPresentOnPage=Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen.
 InsecureFormActionPasswordsPresent=Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen.
 InsecurePasswordsPresentOnIframe=Password fields present on an insecure (http://) iframe. This is a security risk that allows user login credentials to be stolen.
 LoadingMixedActiveContent=Loading mixed (insecure) active content on a secure page "%1$S"
 LoadingMixedDisplayContent=Loading mixed (insecure) display content on a secure page "%1$S"
 # LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe"
 BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1749,16 +1749,20 @@ pref("security.csp.experimentalEnabled",
 pref("security.apps.privileged.CSP.default", "default-src *; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'");
 
 // Mixed content blocking
 pref("security.mixed_content.block_active_content", false);
 pref("security.mixed_content.block_display_content", false);
 
 // Disable pinning checks by default.
 pref("security.cert_pinning.enforcement_level", 0);
+// Do not process hpkp headers rooted by not built in roots by default.
+// This is to prevent accidental pinning from MITM devices and is used
+// for tests.
+pref("security.cert_pinning.process_headers_from_non_builtin_roots", false);
 
 // Modifier key prefs: default to Windows settings,
 // menu access key = alt, accelerator key = control.
 // Use 17 for Ctrl, 18 for Alt, 224 for Meta, 91 for Win, 0 for none. Mac settings in macprefs.js
 pref("ui.key.accelKey", 17);
 pref("ui.key.menuAccessKey", 18);
 pref("ui.key.generalAccessKey", -1);
 
--- a/netwerk/base/public/nsISiteSecurityService.idl
+++ b/netwerk/base/public/nsISiteSecurityService.idl
@@ -2,66 +2,84 @@
  * 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"
 
 interface nsIURI;
 interface nsIObserver;
 interface nsIHttpChannel;
+interface nsISSLStatus;
 
 %{C++
 template<class T> class nsTArray;
 class nsCString;
 namespace mozilla
 {
   namespace pkix
   {
     class Time;
   }
 }
 %}
 [ref] native nsCStringTArrayRef(nsTArray<nsCString>);
 [ref] native mozillaPkixTime(mozilla::pkix::Time);
 
-[scriptable, uuid(35816ea0-3ab5-11e4-8613-180373d97f23)]
+[scriptable, uuid(46555f70-3ab5-11e4-8613-180373d97f23)]
 interface nsISiteSecurityService : nsISupports
 {
     const uint32_t HEADER_HSTS = 0;
     const uint32_t HEADER_HPKP = 1;
     const uint32_t HEADER_OMS = 2;
 
     /**
      * Parses a given HTTP header and records the results internally.
-     * Currently the only header type supported is HSTS (aka STS).
+     * Currently two header types are supported: HSTS (aka STS) and HPKP
      * The format of the HSTS header is defined by the HSTS specification:
      * https://tools.ietf.org/html/rfc6797
      * and allows a host to specify that future HTTP requests should be
      * upgraded to HTTPS.
+     * The Format of the HPKP header is currently defined by:
+     * https://tools.ietf.org/html/draft-ietf-websec-key-pinning-20
+     * and allows a host to speficy a subset of trusted anchors to be used
+     * in future HTTPS connections.
      *
      * @param aType the type of security header in question.
      * @param aSourceURI the URI of the resource with the HTTP header.
+     * @param aSSLStatus the SSLStatus of the current channel
      * @param aHeader the HTTP response header specifying security data.
      * @param aFlags  options for this request as defined in nsISocketProvider:
      *                  NO_PERMANENT_STORAGE
      * @param aMaxAge the parsed max-age directive of the header.
      * @param aIncludeSubdomains the parsed includeSubdomains directive.
      * @return NS_OK            if it succeeds
      *         NS_ERROR_FAILURE if it can't be parsed
      *         NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
      *                          if there are unrecognized tokens in the header.
      */
     void processHeader(in uint32_t aType,
                        in nsIURI aSourceURI,
                        in string aHeader,
+                       in nsISSLStatus aSSLStatus,
                        in uint32_t aFlags,
                        [optional] out unsigned long long aMaxAge,
                        [optional] out boolean aIncludeSubdomains);
 
     /**
+     * Same as processHeader but without checking for the security properties
+     * of the connection. Use ONLY for testing.
+     */
+    void unsafeProcessHeader(in uint32_t aType,
+                             in nsIURI aSourceURI,
+                             in string aHeader,
+                             in uint32_t aFlags,
+                             [optional] out unsigned long long aMaxAge,
+                             [optional] out boolean aIncludeSubdomains);
+
+    /**
      * Given a header type, removes state relating to that header of a host,
      * including the includeSubdomains state that would affect subdomains.
      * This essentially removes the state for the domain tree rooted at this
      * host.
      * @param aType   the type of security state in question
      * @param aURI    the URI of the target host
      * @param aFlags  options for this request as defined in nsISocketProvider:
      *                  NO_PERMANENT_STORAGE
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1037,104 +1037,129 @@ nsHttpChannel::ProcessFailedProxyConnect
     LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n",
          this, httpStatus));
     Cancel(rv);
     CallOnStartRequest();
     return rv;
 }
 
 /**
+ * Process a single security header. Only two types are suported HSTS and HPKP.
+ */
+nsresult
+nsHttpChannel::ProcessSingleSecurityHeader(uint32_t aType,
+                                           nsISSLStatus *aSSLStatus,
+                                           uint32_t aFlags)
+{
+    nsHttpAtom atom;
+    switch (aType) {
+        case nsISiteSecurityService::HEADER_HSTS:
+            atom = nsHttp::ResolveAtom("Strict-Transport-Security");
+            break;
+        case nsISiteSecurityService::HEADER_HPKP:
+            atom = nsHttp::ResolveAtom("Public-Key-Pins");
+            break;
+        default:
+            NS_NOTREACHED("Invalid security header type");
+            return NS_ERROR_FAILURE;
+    }
+
+    nsAutoCString securityHeader;
+    nsresult rv = mResponseHead->GetHeader(atom, securityHeader);
+    if (NS_SUCCEEDED(rv)) {
+        nsISiteSecurityService* sss = gHttpHandler->GetSSService();
+        NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
+        // Process header will now discard the headers itself if the channel
+        // wasn't secure (whereas before it had to be checked manually)
+        rv = sss->ProcessHeader(aType, mURI, securityHeader.get(), aSSLStatus,
+                                aFlags, nullptr, nullptr);
+        if (NS_FAILED(rv)) {
+            nsAutoString consoleErrorCategory;
+            nsAutoString consoleErrorTag;
+            switch (aType) {
+                case nsISiteSecurityService::HEADER_HSTS:
+                    consoleErrorTag = NS_LITERAL_STRING("InvalidSTSHeaders");
+                    consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
+                    break;
+                case nsISiteSecurityService::HEADER_HPKP:
+                    consoleErrorTag = NS_LITERAL_STRING("InvalidPKPHeaders");
+                    consoleErrorCategory = NS_LITERAL_STRING("Invalid HPKP Headers");
+                    break;
+                default:
+                    return NS_ERROR_FAILURE;
+            }
+            AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
+            LOG(("nsHttpChannel: Failed to parse %s header, continuing load.\n",
+                 atom.get()));
+        }
+    } else {
+        if (rv != NS_ERROR_NOT_AVAILABLE) {
+            // All other errors are fatal
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
+        LOG(("nsHttpChannel: No %s header, continuing load.\n",
+             atom.get()));
+    }
+    return NS_OK;
+}
+
+/**
  * Decide whether or not to remember Strict-Transport-Security, and whether
  * or not to enforce channel integrity.
  *
  * @return NS_ERROR_FAILURE if there's security information missing even though
  *             it's an HTTPS connection.
  */
 nsresult
-nsHttpChannel::ProcessSTSHeader()
+nsHttpChannel::ProcessSecurityHeaders()
 {
     nsresult rv;
     bool isHttps = false;
     rv = mURI->SchemeIs("https", &isHttps);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // If this channel is not loading securely, STS doesn't do anything.
-    // The upgrade to HTTPS takes place earlier in the channel load process.
+    // If this channel is not loading securely, STS or PKP doesn't do anything.
+    // In the case of HSTS, the upgrade to HTTPS takes place earlier in the
+    // channel load process.
     if (!isHttps)
         return NS_OK;
 
     nsAutoCString asciiHost;
     rv = mURI->GetAsciiHost(asciiHost);
     NS_ENSURE_SUCCESS(rv, NS_OK);
 
-    // If the channel is not a hostname, but rather an IP, STS doesn't do
-    // anything.
+    // If the channel is not a hostname, but rather an IP, do not process STS
+    // or PKP headers
     PRNetAddr hostAddr;
     if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
         return NS_OK;
 
-    nsISiteSecurityService* sss = gHttpHandler->GetSSService();
-    NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
-
     // mSecurityInfo may not always be present, and if it's not then it is okay
-    // to just disregard any STS headers since we know nothing about the
+    // to just disregard any security headers since we know nothing about the
     // security of the connection.
     NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
 
-    // Check the trustworthiness of the channel (are there any cert errors?)
-    // If there are certificate errors, we still load the data, we just ignore
-    // any STS headers that are present.
-    bool tlsIsBroken = false;
-    rv = sss->ShouldIgnoreHeaders(mSecurityInfo, &tlsIsBroken);
-    NS_ENSURE_SUCCESS(rv, NS_OK);
-
-    // If this was already an STS host, the connection should have been aborted
-    // by the bad cert handler in the case of cert errors.  If it didn't abort the connection,
-    // there's probably something funny going on.
-    // If this wasn't an STS host, errors are allowed, but no more STS processing
-    // will happen during the session.
-    bool wasAlreadySTSHost;
     uint32_t flags =
       NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
-    rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, mURI, flags,
-                          &wasAlreadySTSHost);
-    // Failure here means STS is broken.  Don't prevent the load, but this
-    // shouldn't fail.
-    NS_ENSURE_SUCCESS(rv, NS_OK);
-    MOZ_ASSERT(!(wasAlreadySTSHost && tlsIsBroken),
-               "connection should have been aborted by nss-bad-cert-handler");
-
-    // Any STS header is ignored if the channel is not trusted due to
-    // certificate errors (STS Spec 7.1) -- there is nothing else to do, and
-    // the load may progress.
-    if (tlsIsBroken) {
-        LOG(("STS: Transport layer is not trustworthy, ignoring "
-             "STS headers and continuing load\n"));
-        return NS_OK;
-    }
-
-    // If there's a STS header, process it (STS Spec 7.1).  At this point in
-    // processing, the channel is trusted, so the header should not be ignored.
-    const nsHttpAtom atom = nsHttp::ResolveAtom("Strict-Transport-Security");
-    nsAutoCString stsHeader;
-    rv = mResponseHead->GetHeader(atom, stsHeader);
-    if (rv == NS_ERROR_NOT_AVAILABLE) {
-        LOG(("STS: No STS header, continuing load.\n"));
-        return NS_OK;
-    }
-    // All other failures are fatal.
+
+    // Get the SSLStatus
+    nsCOMPtr<nsISSLStatusProvider> sslprov = do_QueryInterface(mSecurityInfo);
+    NS_ENSURE_TRUE(sslprov, NS_ERROR_FAILURE);
+    nsCOMPtr<nsISSLStatus> sslStatus;
+    rv = sslprov->GetSSLStatus(getter_AddRefs(sslStatus));
     NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = sss->ProcessHeader(nsISiteSecurityService::HEADER_HSTS, mURI,
-                            stsHeader.get(), flags, nullptr, nullptr);
-    if (NS_FAILED(rv)) {
-        AddSecurityMessage(NS_LITERAL_STRING("InvalidSTSHeaders"),
-                NS_LITERAL_STRING("Invalid HSTS Headers"));
-        LOG(("STS: Failed to parse STS header, continuing load.\n"));
-    }
+    NS_ENSURE_TRUE(sslStatus, NS_ERROR_FAILURE);
+
+    rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HSTS,
+                                     sslStatus, flags);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HPKP,
+                                     sslStatus, flags);
+    NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
 bool
 nsHttpChannel::IsHTTPS()
 {
     bool isHttps;
@@ -1224,18 +1249,19 @@ nsHttpChannel::ProcessResponse()
 
     if (mTransaction->ProxyConnectFailed()) {
         // Only allow 407 (authentication required) to continue
         if (httpStatus != 407)
             return ProcessFailedProxyConnect(httpStatus);
         // If proxy CONNECT response needs to complete, wait to process connection
         // for Strict-Transport-Security.
     } else {
-        // Given a successful connection, process any STS data that's relevant.
-        rv = ProcessSTSHeader();
+        // Given a successful connection, process any STS or PKP data that's
+        // relevant.
+        rv = ProcessSecurityHeaders();
         MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
     }
 
     MOZ_ASSERT(!mCachedContentIsValid);
 
     ProcessSSLInformation();
 
     // notify "http-on-examine-response" observers
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -23,16 +23,17 @@
 #include "TimingStruct.h"
 #include "AutoClose.h"
 
 class nsIPrincipal;
 class nsDNSPrefetch;
 class nsICancelable;
 class nsIHttpChannelAuthProvider;
 class nsInputStreamPump;
+class nsISSLStatus;
 class nsPerformance;
 
 namespace mozilla { namespace net {
 
 //-----------------------------------------------------------------------------
 // nsHttpChannel
 //-----------------------------------------------------------------------------
 
@@ -283,22 +284,31 @@ private:
     nsresult DoAuthRetry(nsAHttpConnection *);
 
     void     HandleAsyncRedirectChannelToHttps();
     nsresult StartRedirectChannelToHttps();
     nsresult ContinueAsyncRedirectChannelToURI(nsresult rv);
     nsresult OpenRedirectChannel(nsresult rv);
 
     /**
-     * A function that takes care of reading STS headers and enforcing STS
-     * load rules.  After a secure channel is erected, STS requires the channel
-     * to be trusted or any STS header data on the channel is ignored.
-     * This is called from ProcessResponse.
+     * A function that takes care of reading STS and PKP headers and enforcing
+     * STS and PKP load rules. After a secure channel is erected, STS and PKP
+     * requires the channel to be trusted or any STS or PKP header data on
+     * the channel is ignored. This is called from ProcessResponse.
      */
-    nsresult ProcessSTSHeader();
+    nsresult ProcessSecurityHeaders();
+
+    /**
+     * A function to process a single security header (STS or PKP), assumes
+     * some basic sanity checks have been applied to the channel. Called
+     * from ProcessSecurityHeaders.
+     */
+    nsresult ProcessSingleSecurityHeader(uint32_t aType,
+                                         nsISSLStatus *aSSLStatus,
+                                         uint32_t aFlags);
 
     void InvalidateCacheEntryForLocation(const char *location);
     void AssembleCacheKey(const char *spec, uint32_t postID, nsACString &key);
     nsresult CreateNewURI(const char *loc, nsIURI **newURI);
     void DoInvalidateCacheEntry(nsIURI* aURI);
 
     // Ref RFC2616 13.10: "invalidation... MUST only be performed if
     // the host part is the same as in the Request-URI"
--- a/netwerk/test/TestSTSParser.cpp
+++ b/netwerk/test/TestSTSParser.cpp
@@ -38,18 +38,18 @@ TestSuccess(const char* hdr, bool extraT
             nsISiteSecurityService* sss)
 {
   nsCOMPtr<nsIURI> dummyUri;
   nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://foo.com/bar.html");
   EXPECT_SUCCESS(rv, "Failed to create URI");
 
   uint64_t maxAge = 0;
   bool includeSubdomains = false;
-  rv = sss->ProcessHeader(nsISiteSecurityService::HEADER_HSTS, dummyUri, hdr,
-                          0, &maxAge, &includeSubdomains);
+  rv = sss->UnsafeProcessHeader(nsISiteSecurityService::HEADER_HSTS, dummyUri,
+                                hdr, 0, &maxAge, &includeSubdomains);
   EXPECT_SUCCESS(rv, "Failed to process valid header: %s", hdr);
 
   REQUIRE_EQUAL(maxAge, expectedMaxAge, "Did not correctly parse maxAge");
   REQUIRE_EQUAL(includeSubdomains, expectedIncludeSubdomains, "Did not correctly parse presence/absence of includeSubdomains");
 
   if (extraTokens) {
     REQUIRE_EQUAL(rv, NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA,
                   "Extra tokens were expected when parsing, but were not encountered.");
@@ -63,18 +63,18 @@ TestSuccess(const char* hdr, bool extraT
 
 bool TestFailure(const char* hdr,
                  nsISiteSecurityService* sss)
 {
   nsCOMPtr<nsIURI> dummyUri;
   nsresult rv = NS_NewURI(getter_AddRefs(dummyUri), "https://foo.com/bar.html");
   EXPECT_SUCCESS(rv, "Failed to create URI");
 
-  rv = sss->ProcessHeader(nsISiteSecurityService::HEADER_HSTS, dummyUri, hdr,
-                          0, nullptr, nullptr);
+  rv = sss->UnsafeProcessHeader(nsISiteSecurityService::HEADER_HSTS, dummyUri,
+                                hdr, 0, nullptr, nullptr);
   EXPECT_FAILURE(rv, "Parsed invalid header: %s", hdr);
   passed(hdr);
   return true;
 }
 
 
 int
 main(int32_t argc, char *argv[])
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -1,14 +1,19 @@
 # -*- 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/.
 
+EXPORTS += [
+    'CertVerifier.h',
+    'OCSPCache.h',
+]
+
 UNIFIED_SOURCES += [
     'CertVerifier.cpp',
     'NSSCertDBTrustDomain.cpp',
     'OCSPCache.cpp',
     'OCSPRequestor.cpp',
 ]
 
 if not CONFIG['NSS_NO_EV_CERTS']:
--- a/security/manager/boot/src/PublicKeyPinningService.cpp
+++ b/security/manager/boot/src/PublicKeyPinningService.cpp
@@ -162,16 +162,23 @@ static int
 TransportSecurityPreloadCompare(const void *key, const void *entry) {
   const char *keyStr = reinterpret_cast<const char *>(key);
   const TransportSecurityPreload *preloadEntry =
     reinterpret_cast<const TransportSecurityPreload *>(entry);
 
   return strcmp(keyStr, preloadEntry->mHost);
 }
 
+bool
+PublicKeyPinningService::ChainMatchesPinset(const CERTCertList* certList,
+                                            const nsTArray<nsCString>& aSHA256keys)
+{
+  return EvalChainWithHashType(certList, SEC_OID_SHA256, nullptr, &aSHA256keys);
+}
+
 /**
  * Check PKPins on the given certlist against the specified hostname
  */
 static bool
 CheckPinsForHostname(const CERTCertList *certList, const char *hostname,
                      bool enforceTestMode, mozilla::pkix::Time time)
 {
   if (!certList) {
--- a/security/manager/boot/src/PublicKeyPinningService.h
+++ b/security/manager/boot/src/PublicKeyPinningService.h
@@ -1,16 +1,18 @@
 /* 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/. */
 
 #ifndef PublicKeyPinningService_h
 #define PublicKeyPinningService_h
 
 #include "cert.h"
+#include "nsString.h"
+#include "nsTArray.h"
 #include "pkix/Time.h"
 
 namespace mozilla {
 namespace psm {
 
 class PublicKeyPinningService
 {
 public:
@@ -24,13 +26,20 @@ public:
    * tail is the trust anchor.
    * Note: if an alt name is a wildcard, it won't necessarily find a pinset
    * that would otherwise be valid for it
    */
   static bool ChainHasValidPins(const CERTCertList* certList,
                                 const char* hostname,
                                 mozilla::pkix::Time time,
                                 bool enforceTestMode);
+  /**
+   * Returns true if there is any intersection between the certificate list
+   * and the pins specified in the aSHA256key array. Values passed in are
+   * assumed to be in base64 encoded form
+   */
+  static bool ChainMatchesPinset(const CERTCertList* certList,
+                                 const nsTArray<nsCString>& aSHA256keys);
 };
 
 }} // namespace mozilla::psm
 
 #endif // PublicKeyPinningServiceService_h
--- a/security/manager/boot/src/nsSiteSecurityService.cpp
+++ b/security/manager/boot/src/nsSiteSecurityService.cpp
@@ -3,33 +3,36 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsSiteSecurityService.h"
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Base64.h"
 #include "base64.h"
+#include "CertVerifier.h"
 #include "nsCRTGlue.h"
 #include "nsISSLStatus.h"
 #include "nsISSLStatusProvider.h"
 #include "nsISocketProvider.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
+#include "nsNSSComponent.h"
 #include "nsSecurityHeaderParser.h"
 #include "nsString.h"
 #include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
 #include "pkix/pkixtypes.h"
 #include "plstr.h"
 #include "prlog.h"
 #include "prnetdb.h"
 #include "prprf.h"
 #include "PublicKeyPinningService.h"
 #include "ScopedNSSTypes.h"
-#include "nsXULAppAPI.h"
+#include "SharedCertVerifier.h"
 
 // A note about the preload list:
 // When a site specifically disables HSTS by sending a header with
 // 'max-age: 0', we keep a "knockout" value that means "we have no information
 // regarding the HSTS state of this host" (any ancestor of "this host" can still
 // influence its HSTS status via include subdomains, however).
 // This prevents the preload list from overriding the site's current
 // desired HSTS status.
@@ -237,16 +240,20 @@ nsSiteSecurityService::Init()
     NS_NOTREACHED("nsSiteSecurityService initialized off main thread");
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
   mUsePreloadList = mozilla::Preferences::GetBool(
     "network.stricttransportsecurity.preloadlist", true);
   mozilla::Preferences::AddStrongObserver(this,
     "network.stricttransportsecurity.preloadlist");
+  mProcessPKPHeadersFromNonBuiltInRoots = mozilla::Preferences::GetBool(
+    "security.cert_pinning.process_headers_from_non_builtin_roots", false);
+  mozilla::Preferences::AddStrongObserver(this,
+    "security.cert_pinning.process_headers_from_non_builtin_roots");
   mPreloadListTimeOffset = mozilla::Preferences::GetInt(
     "test.currentTimeOffsetSeconds", 0);
   mozilla::Preferences::AddStrongObserver(this,
     "test.currentTimeOffsetSeconds");
   mSiteStateStorage =
     new mozilla::DataStorage(NS_LITERAL_STRING("SiteSecurityServiceState.txt"));
   bool storageWillPersist = false;
   nsresult rv = mSiteStateStorage->Init(storageWillPersist);
@@ -288,34 +295,38 @@ SetStorageKey(nsAutoCString& storageKey,
     case nsISiteSecurityService::HEADER_HPKP:
       storageKey.AppendLiteral(":HPKP");
       break;
     default:
       NS_ASSERTION(false, "SSS:SetStorageKey got invalid type");
   }
 }
 
+// Expire times are in millis.  Since Headers max-age is in seconds, and
+// PR_Now() is in micros, normalize the units at milliseconds.
+static int64_t
+ExpireTimeFromMaxAge(int64_t maxAge)
+{
+  return (PR_Now() / PR_USEC_PER_MSEC) + (maxAge * PR_MSEC_PER_SEC);
+}
+
 nsresult
 nsSiteSecurityService::SetHSTSState(uint32_t aType,
                                     nsIURI* aSourceURI,
                                     int64_t maxage,
                                     bool includeSubdomains,
                                     uint32_t flags)
 {
   // If max-age is zero, that's an indication to immediately remove the
   // security state, so here's a shortcut.
   if (!maxage) {
     return RemoveState(aType, aSourceURI, flags);
   }
 
-  // Expire time is millis from now.  Since STS max-age is in seconds, and
-  // PR_Now() is in micros, must equalize the units at milliseconds.
-  int64_t expiretime = (PR_Now() / PR_USEC_PER_MSEC) +
-                       (maxage * PR_MSEC_PER_SEC);
-
+  int64_t expiretime = ExpireTimeFromMaxAge(maxage);
   SiteHSTSState siteState(expiretime, SecurityPropertySet, includeSubdomains);
   nsAutoCString stateString;
   siteState.ToString(stateString);
   nsAutoCString hostname;
   nsresult rv = GetHost(aSourceURI, hostname);
   NS_ENSURE_SUCCESS(rv, rv);
   SSSLOG(("SSS: setting state for %s", hostname.get()));
   bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
@@ -330,17 +341,18 @@ nsSiteSecurityService::SetHSTSState(uint
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI,
                                    uint32_t aFlags)
 {
   // Only HSTS is supported at the moment.
-  NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
+  NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
+                 aType == nsISiteSecurityService::HEADER_HPKP,
                  NS_ERROR_NOT_IMPLEMENTED);
 
   nsAutoCString hostname;
   nsresult rv = GetHost(aURI, hostname);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
@@ -372,95 +384,176 @@ HostIsIPAddress(const char *hostname)
   PRNetAddr hostAddr;
   return (PR_StringToNetAddr(hostname, &hostAddr) == PR_SUCCESS);
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::ProcessHeader(uint32_t aType,
                                      nsIURI* aSourceURI,
                                      const char* aHeader,
+                                     nsISSLStatus* aSSLStatus,
                                      uint32_t aFlags,
-                                     uint64_t *aMaxAge,
-                                     bool *aIncludeSubdomains)
+                                     uint64_t* aMaxAge,
+                                     bool* aIncludeSubdomains)
 {
-  // Only HSTS is supported at the moment.
-  NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
+  NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
+                 aType == nsISiteSecurityService::HEADER_HPKP,
+                 NS_ERROR_NOT_IMPLEMENTED);
+
+  NS_ENSURE_ARG(aSSLStatus);
+  return ProcessHeaderInternal(aType, aSourceURI, aHeader, aSSLStatus, aFlags,
+                               aMaxAge, aIncludeSubdomains);
+}
+
+NS_IMETHODIMP
+nsSiteSecurityService::UnsafeProcessHeader(uint32_t aType,
+                                           nsIURI* aSourceURI,
+                                           const char* aHeader,
+                                           uint32_t aFlags,
+                                           uint64_t* aMaxAge,
+                                           bool* aIncludeSubdomains)
+{
+  return ProcessHeaderInternal(aType, aSourceURI, aHeader, nullptr, aFlags,
+                               aMaxAge, aIncludeSubdomains);
+}
+
+nsresult
+nsSiteSecurityService::ProcessHeaderInternal(uint32_t aType,
+                                             nsIURI* aSourceURI,
+                                             const char* aHeader,
+                                             nsISSLStatus* aSSLStatus,
+                                             uint32_t aFlags,
+                                             uint64_t* aMaxAge,
+                                             bool* aIncludeSubdomains)
+{
+  // Only HSTS and HPKP are supported at the moment.
+  NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
+                 aType == nsISiteSecurityService::HEADER_HPKP,
                  NS_ERROR_NOT_IMPLEMENTED);
 
   if (aMaxAge != nullptr) {
     *aMaxAge = 0;
   }
 
   if (aIncludeSubdomains != nullptr) {
     *aIncludeSubdomains = false;
   }
 
+  if (aSSLStatus) {
+    bool tlsIsBroken = false;
+    bool trustcheck;
+    nsresult rv;
+    rv = aSSLStatus->GetIsDomainMismatch(&trustcheck);
+    NS_ENSURE_SUCCESS(rv, rv);
+    tlsIsBroken = tlsIsBroken || trustcheck;
+
+    rv = aSSLStatus->GetIsNotValidAtThisTime(&trustcheck);
+    NS_ENSURE_SUCCESS(rv, rv);
+    tlsIsBroken = tlsIsBroken || trustcheck;
+
+    rv = aSSLStatus->GetIsUntrusted(&trustcheck);
+    NS_ENSURE_SUCCESS(rv, rv);
+    tlsIsBroken = tlsIsBroken || trustcheck;
+    if (tlsIsBroken) {
+       SSSLOG(("SSS: discarding header from untrustworthy connection"));
+      return NS_ERROR_FAILURE;
+    }
+  }
+
   nsAutoCString host;
   nsresult rv = GetHost(aSourceURI, host);
   NS_ENSURE_SUCCESS(rv, rv);
   if (HostIsIPAddress(host.get())) {
     /* Don't process headers if a site is accessed by IP address. */
     return NS_OK;
   }
 
-  char * header = NS_strdup(aHeader);
-  if (!header) return NS_ERROR_OUT_OF_MEMORY;
-  rv = ProcessHeaderMutating(aType, aSourceURI, header, aFlags,
-                             aMaxAge, aIncludeSubdomains);
-  NS_Free(header);
+  switch (aType) {
+    case nsISiteSecurityService::HEADER_HSTS:
+      rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aMaxAge,
+                            aIncludeSubdomains);
+      break;
+    case nsISiteSecurityService::HEADER_HPKP:
+      rv = ProcessPKPHeader(aSourceURI, aHeader, aSSLStatus, aFlags, aMaxAge,
+                            aIncludeSubdomains);
+      break;
+    default:
+      MOZ_CRASH("unexpected header type");
+  }
   return rv;
 }
 
-nsresult
-nsSiteSecurityService::ProcessHeaderMutating(uint32_t aType,
-                                             nsIURI* aSourceURI,
-                                             char* aHeader,
-                                             uint32_t aFlags,
-                                             uint64_t *aMaxAge,
-                                             bool *aIncludeSubdomains)
+static nsresult
+ParseSSSHeaders(uint32_t aType,
+                const char* aHeader,
+                bool& foundIncludeSubdomains,
+                bool& foundMaxAge,
+                bool& foundUnrecognizedDirective,
+                int64_t& maxAge,
+                nsTArray<nsCString>& sha256keys)
 {
-  SSSLOG(("SSS: processing header '%s'", aHeader));
+  // Stric transport security and Public Key Pinning have very similar
+  // Header formats.
 
   // "Strict-Transport-Security" ":" OWS
   //      STS-d  *( OWS ";" OWS STS-d  OWS)
   //
   //  ; STS directive
   //  STS-d      = maxAge / includeSubDomains
   //
   //  maxAge     = "max-age" "=" delta-seconds v-ext
   //
   //  includeSubDomains = [ "includeSubDomains" ]
   //
+
+  // "Public-Key-Pins ":" OWS
+  //      PKP-d  *( OWS ";" OWS PKP-d  OWS)
+  //
+  //  ; PKP directive
+  //  PKP-d      = maxAge / includeSubDomains / reportUri / pin-directive
+  //
+  //  maxAge     = "max-age" "=" delta-seconds v-ext
+  //
+  //  includeSubDomains = [ "includeSubDomains" ]
+  //
+  //  reportURi  = "report-uri" "=" quoted-string
+  //
+  //  pin-directive = "pin-" token "=" quoted-string
+  //
+  //  the only valid token currently specified is sha256
+  //  the quoted string for a pin directive is the base64 encoding
+  //  of the hash of the public key of the fingerprint
+  //
+
   //  The order of the directives is not significant.
   //  All directives must appear only once.
   //  Directive names are case-insensitive.
   //  The entire header is invalid if a directive not conforming to the
   //  syntax is encountered.
   //  Unrecognized directives (that are otherwise syntactically valid) are
   //  ignored, and the rest of the header is parsed as normal.
 
-  bool foundMaxAge = false;
-  bool foundIncludeSubdomains = false;
-  bool foundUnrecognizedDirective = false;
-  int64_t maxAge = 0;
+  bool foundReportURI = false;
 
   NS_NAMED_LITERAL_CSTRING(max_age_var, "max-age");
   NS_NAMED_LITERAL_CSTRING(include_subd_var, "includesubdomains");
-
+  NS_NAMED_LITERAL_CSTRING(pin_sha256_var, "pin-sha256");
+  NS_NAMED_LITERAL_CSTRING(report_uri_var, "report-uri");
 
   nsSecurityHeaderParser parser(aHeader);
   nsresult rv = parser.Parse();
   if (NS_FAILED(rv)) {
     SSSLOG(("SSS: could not parse header"));
     return rv;
   }
-  mozilla::LinkedList<nsSecurityHeaderDirective> *directives = parser.GetDirectives();
+  mozilla::LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
 
-  for (nsSecurityHeaderDirective *directive = directives->getFirst();
+  for (nsSecurityHeaderDirective* directive = directives->getFirst();
        directive != nullptr; directive = directive->getNext()) {
+    SSSLOG(("SSS: found directive %s\n", directive->mName.get()));
     if (directive->mName.Length() == max_age_var.Length() &&
         directive->mName.EqualsIgnoreCase(max_age_var.get(),
                                           max_age_var.Length())) {
       if (foundMaxAge) {
         SSSLOG(("SSS: found two max-age directives"));
         return NS_ERROR_FAILURE;
       }
 
@@ -493,22 +586,188 @@ nsSiteSecurityService::ProcessHeaderMuta
       SSSLOG(("SSS: found includeSubdomains directive"));
       foundIncludeSubdomains = true;
 
       if (directive->mValue.Length() != 0) {
         SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'",
                 directive->mValue.get()));
         return NS_ERROR_FAILURE;
       }
-    } else {
+    } else if (aType == nsISiteSecurityService::HEADER_HPKP &&
+               directive->mName.Length() == pin_sha256_var.Length() &&
+               directive->mName.EqualsIgnoreCase(pin_sha256_var.get(),
+                                                 pin_sha256_var.Length())) {
+       SSSLOG(("SSS: found pinning entry '%s' length=%d",
+               directive->mValue.get(), directive->mValue.Length()));
+       if (!stringIsBase64EncodingOf256bitValue(directive->mValue)) {
+         return NS_ERROR_FAILURE;
+       }
+       sha256keys.AppendElement(directive->mValue);
+   } else if (aType == nsISiteSecurityService::HEADER_HPKP &&
+              directive->mName.Length() == report_uri_var.Length() &&
+              directive->mName.EqualsIgnoreCase(report_uri_var.get(),
+                                                report_uri_var.Length())) {
+       // We doni't support the report-uri yet, but to avoid unrecognized
+       // directive warnings, we still have to handle its presence
+      if (foundReportURI) {
+        SSSLOG(("SSS: found two report-uri directives"));
+        return NS_ERROR_FAILURE;
+      }
+      SSSLOG(("SSS: found report-uri directive"));
+      foundReportURI = true;
+   } else {
       SSSLOG(("SSS: ignoring unrecognized directive '%s'",
               directive->mName.get()));
       foundUnrecognizedDirective = true;
     }
   }
+  return NS_OK;
+}
+
+nsresult
+nsSiteSecurityService::ProcessPKPHeader(nsIURI* aSourceURI,
+                                        const char* aHeader,
+                                        nsISSLStatus* aSSLStatus,
+                                        uint32_t aFlags,
+                                        uint64_t* aMaxAge,
+                                        bool* aIncludeSubdomains)
+{
+  SSSLOG(("SSS: processing HPKP header '%s'", aHeader));
+  NS_ENSURE_ARG(aSSLStatus);
+
+  const uint32_t aType = nsISiteSecurityService::HEADER_HPKP;
+  bool foundMaxAge = false;
+  bool foundIncludeSubdomains = false;
+  bool foundUnrecognizedDirective = false;
+  int64_t maxAge = 0;
+  nsTArray<nsCString> sha256keys;
+  nsresult rv = ParseSSSHeaders(aType, aHeader, foundIncludeSubdomains,
+                                foundMaxAge, foundUnrecognizedDirective,
+                                maxAge, sha256keys);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // after processing all the directives, make sure we came across max-age
+  // somewhere.
+  if (!foundMaxAge) {
+    SSSLOG(("SSS: did not encounter required max-age directive"));
+    return NS_ERROR_FAILURE;
+  }
+
+  // before we add the pin we need to ensure it will not break the site as
+  // currently visited so:
+  // 1. recompute a valid chain (no external ocsp)
+  // 2. use this chain to check if things would have broken!
+  nsAutoCString host;
+  rv = GetHost(aSourceURI, host);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsCOMPtr<nsIX509Cert> cert;
+  rv = aSSLStatus->GetServerCert(getter_AddRefs(cert));
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(cert, NS_ERROR_FAILURE);
+  ScopedCERTCertificate nssCert(cert->GetCert());
+  NS_ENSURE_TRUE(nssCert, NS_ERROR_FAILURE);
+
+  mozilla::pkix::Time now(mozilla::pkix::Now());
+  ScopedCERTCertList certList;
+  RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+  NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
+  if (certVerifier->VerifySSLServerCert(nssCert, nullptr, // stapled ocsp
+                                        now, nullptr, // pinarg
+                                        host.get(), // hostname
+                                        false, // don't store intermediates
+                                        CertVerifier::FLAG_LOCAL_ONLY,
+                                        &certList) != SECSuccess) {
+    return NS_ERROR_FAILURE;
+  }
+
+  CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
+  if (CERT_LIST_END(rootNode, certList)) {
+    return NS_ERROR_FAILURE;
+  }
+  bool isBuiltIn = false;
+  SECStatus srv = IsCertBuiltInRoot(rootNode->cert, isBuiltIn);
+  if (srv != SECSuccess) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!isBuiltIn && !mProcessPKPHeadersFromNonBuiltInRoots) {
+    return NS_OK;
+  }
+
+  // if maxAge == 0 we must delete all state, for now no hole-punching
+  if (maxAge == 0) {
+    return RemoveState(aType, aSourceURI, aFlags);
+  }
+
+  if (!PublicKeyPinningService::ChainMatchesPinset(certList, sha256keys)) {
+    // is invalid
+    SSSLOG(("SSS: Pins provided by %s are invalid no match with certList\n", host.get()));
+    return NS_ERROR_FAILURE;
+  }
+
+  // finally we need to ensure that there is a "backup pin" ie. There must be
+  // at least one fingerprint hash that does NOT valiate against the verified
+  // chain (Section 2.5 of the spec)
+  bool hasBackupPin = false;
+  for (uint32_t i = 0; i < sha256keys.Length(); i++) {
+    nsTArray<nsCString> singlePin;
+    singlePin.AppendElement(sha256keys[i]);
+    if (!PublicKeyPinningService::
+           ChainMatchesPinset(certList, singlePin)) {
+      hasBackupPin = true;
+    }
+  }
+  if (!hasBackupPin) {
+     // is invalid
+    SSSLOG(("SSS: Pins provided by %s are invalid no backupPin\n", host.get()));
+    return NS_ERROR_FAILURE;
+  }
+
+  int64_t expireTime = ExpireTimeFromMaxAge(maxAge);
+  SiteHPKPState dynamicEntry(expireTime, SecurityPropertySet,
+                             foundIncludeSubdomains, sha256keys);
+  SSSLOG(("SSS: about to set pins for  %s, expires=%ld now=%ld maxAge=%ld\n",
+           host.get(), expireTime, PR_Now() / PR_USEC_PER_MSEC, maxAge));
+
+  rv = SetHPKPState(host.get(), dynamicEntry, aFlags);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aMaxAge != nullptr) {
+    *aMaxAge = (uint64_t)maxAge;
+  }
+
+  if (aIncludeSubdomains != nullptr) {
+    *aIncludeSubdomains = foundIncludeSubdomains;
+  }
+
+  return foundUnrecognizedDirective
+           ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
+           : NS_OK;
+}
+
+nsresult
+nsSiteSecurityService::ProcessSTSHeader(nsIURI* aSourceURI,
+                                        const char* aHeader,
+                                        uint32_t aFlags,
+                                        uint64_t* aMaxAge,
+                                        bool* aIncludeSubdomains)
+{
+  SSSLOG(("SSS: processing HSTS header '%s'", aHeader));
+
+  const uint32_t aType = nsISiteSecurityService::HEADER_HSTS;
+  bool foundMaxAge = false;
+  bool foundIncludeSubdomains = false;
+  bool foundUnrecognizedDirective = false;
+  int64_t maxAge = 0;
+  nsTArray<nsCString> unusedSHA256keys; // Requred for sane internal interface
+
+  nsresult rv = ParseSSSHeaders(aType, aHeader, foundIncludeSubdomains,
+                                foundMaxAge, foundUnrecognizedDirective,
+                                maxAge, unusedSHA256keys);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // after processing all the directives, make sure we came across max-age
   // somewhere.
   if (!foundMaxAge) {
     SSSLOG(("SSS: did not encounter required max-age directive"));
     return NS_ERROR_FAILURE;
   }
 
@@ -518,18 +777,19 @@ nsSiteSecurityService::ProcessHeaderMuta
   if (aMaxAge != nullptr) {
     *aMaxAge = (uint64_t)maxAge;
   }
 
   if (aIncludeSubdomains != nullptr) {
     *aIncludeSubdomains = foundIncludeSubdomains;
   }
 
-  return foundUnrecognizedDirective ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
-                                    : NS_OK;
+  return foundUnrecognizedDirective
+           ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
+           : NS_OK;
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
                                    uint32_t aFlags, bool* aResult)
 {
   // Only HSTS is supported at the moment.
   NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
@@ -760,64 +1020,79 @@ nsSiteSecurityService::GetKeyPinsForHost
 
   nsAutoCString host(aHostname);
   nsAutoCString storageKey;
   SetStorageKey(storageKey, host, nsISiteSecurityService::HEADER_HPKP);
 
   SSSLOG(("storagekey '%s'\n", storageKey.get()));
   mozilla::DataStorageType storageType = mozilla::DataStorage_Persistent;
   nsCString value = mSiteStateStorage->Get(storageKey, storageType);
-  //decode now
+  // decode now
   SiteHPKPState foundEntry(value);
   if (foundEntry.mState != SecurityPropertySet ||
       foundEntry.IsExpired(aEvalTime) ||
       foundEntry.mSHA256keys.Length() < 1 ) {
-    return NS_OK;
+    // not in permanent storage, try now private
+    value = mSiteStateStorage->Get(storageKey, mozilla::DataStorage_Private);
+    SiteHPKPState privateEntry(value);
+    if (privateEntry.mState != SecurityPropertySet ||
+        privateEntry.IsExpired(aEvalTime) ||
+        privateEntry.mSHA256keys.Length() < 1 ) {
+      return NS_OK;
+    }
+    foundEntry = privateEntry;
   }
   pinArray = foundEntry.mSHA256keys;
   *aIncludeSubdomains = foundEntry.mIncludeSubdomains;
   *afound = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::SetKeyPins(const char* aHost, bool aIncludeSubdomains,
                                   uint32_t aMaxAge, uint32_t aPinCount,
                                   const char** aSha256Pins,
-                                  /*out*/bool* aResult)
+                                  /*out*/ bool* aResult)
 {
   NS_ENSURE_ARG_POINTER(aHost);
   NS_ENSURE_ARG_POINTER(aResult);
   NS_ENSURE_ARG_POINTER(aSha256Pins);
 
   SSSLOG(("Top of SetPins"));
 
-  // Expire time is millis from now. Since HPKP max-age is in seconds, and
-  // PR_Now() is in micros, must normalize the units at milliseconds.
-  int64_t expireTime = (PR_Now() / PR_USEC_PER_MSEC) +
-                       (aMaxAge * PR_MSEC_PER_SEC);
+  int64_t expireTime = ExpireTimeFromMaxAge(aMaxAge);
   nsTArray<nsCString> sha256keys;
   for (unsigned int i = 0; i < aPinCount; i++) {
     nsAutoCString pin(aSha256Pins[i]);
     SSSLOG(("SetPins pin=%s\n", pin.get()));
     if (!stringIsBase64EncodingOf256bitValue(pin)) {
       return NS_ERROR_INVALID_ARG;
     }
     sha256keys.AppendElement(pin);
   }
   SiteHPKPState dynamicEntry(expireTime, SecurityPropertySet,
                              aIncludeSubdomains, sha256keys);
+  // we always store data in permanent storage (ie no flags)
+  return SetHPKPState(aHost, dynamicEntry, 0);
+}
 
+nsresult
+nsSiteSecurityService::SetHPKPState(const char* aHost, SiteHPKPState& entry,
+                                    uint32_t aFlags)
+{
+  SSSLOG(("Top of SetPKPState"));
   nsAutoCString host(aHost);
   nsAutoCString storageKey;
   SetStorageKey(storageKey, host, nsISiteSecurityService::HEADER_HPKP);
-  // Note: setPins always stores data in the persistent storage
-  mozilla::DataStorageType storageType = mozilla::DataStorage_Persistent;
+  bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
+  mozilla::DataStorageType storageType = isPrivate
+                                         ? mozilla::DataStorage_Private
+                                         : mozilla::DataStorage_Persistent;
   nsAutoCString stateString;
-  dynamicEntry.ToString(stateString);
+  entry.ToString(stateString);
   nsresult rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 //------------------------------------------------------------
 // nsSiteSecurityService::nsIObserver
 //------------------------------------------------------------
@@ -833,12 +1108,14 @@ nsSiteSecurityService::Observe(nsISuppor
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
   if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
     mUsePreloadList = mozilla::Preferences::GetBool(
       "network.stricttransportsecurity.preloadlist", true);
     mPreloadListTimeOffset =
       mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds", 0);
+    mProcessPKPHeadersFromNonBuiltInRoots = mozilla::Preferences::GetBool(
+      "security.cert_pinning.process_headers_from_non_builtin_roots", false);
   }
 
   return NS_OK;
 }
--- a/security/manager/boot/src/nsSiteSecurityService.h
+++ b/security/manager/boot/src/nsSiteSecurityService.h
@@ -10,16 +10,17 @@
 #include "nsIObserver.h"
 #include "nsISiteSecurityService.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "pkix/pkixtypes.h"
 #include "prtime.h"
 
 class nsIURI;
+class nsISSLStatus;
 
 // {16955eee-6c48-4152-9309-c42a465138a1}
 #define NS_SITE_SECURITY_SERVICE_CID \
   {0x16955eee, 0x6c48, 0x4152, \
     {0x93, 0x09, 0xc4, 0x2a, 0x46, 0x51, 0x38, 0xa1} }
 
 /**
  * SecurityPropertyState: A utility enum for representing the different states
@@ -123,19 +124,29 @@ public:
 
 protected:
   virtual ~nsSiteSecurityService();
 
 private:
   nsresult GetHost(nsIURI *aURI, nsACString &aResult);
   nsresult SetHSTSState(uint32_t aType, nsIURI* aSourceURI, int64_t maxage,
                         bool includeSubdomains, uint32_t flags);
-  nsresult ProcessHeaderMutating(uint32_t aType, nsIURI* aSourceURI,
-                                 char* aHeader, uint32_t flags,
-                                 uint64_t *aMaxAge, bool *aIncludeSubdomains);
+  nsresult ProcessHeaderInternal(uint32_t aType, nsIURI* aSourceURI,
+                                 const char* aHeader, nsISSLStatus* aSSLStatus,
+                                 uint32_t aFlags, uint64_t* aMaxAge,
+                                 bool* aIncludeSubdomains);
+  nsresult ProcessSTSHeader(nsIURI* aSourceURI, const char* aHeader,
+                            uint32_t flags, uint64_t* aMaxAge,
+                            bool* aIncludeSubdomains);
+  nsresult ProcessPKPHeader(nsIURI* aSourceURI, const char* aHeader,
+                            nsISSLStatus* aSSLStatus, uint32_t flags,
+                            uint64_t* aMaxAge, bool* aIncludeSubdomains);
+  nsresult SetHPKPState(const char* aHost, SiteHPKPState& entry, uint32_t flags);
+
   const nsSTSPreload *GetPreloadListEntry(const char *aHost);
 
   bool mUsePreloadList;
   int64_t mPreloadListTimeOffset;
+  bool mProcessPKPHeadersFromNonBuiltInRoots;
   nsRefPtr<mozilla::DataStorage> mSiteStateStorage;
 };
 
 #endif // __nsSiteSecurityService_h__
--- a/security/manager/ssl/src/moz.build
+++ b/security/manager/ssl/src/moz.build
@@ -1,21 +1,27 @@
 # -*- 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/.
 
 EXPORTS += [
     'CryptoTask.h',
+    'nsClientAuthRemember.h',
     'nsCrypto.h',
+    'nsNSSCallbacks.h',
+    'nsNSSCertificate.h',
+    'nsNSSComponent.h',
+    'nsNSSHelper.h',
     'nsNSSShutDown.h',
     'nsRandomGenerator.h',
     'NSSErrorsService.h',
     'ScopedNSSTypes.h',
+    'SharedCertVerifier.h',
 ]
 
 EXPORTS.mozilla += [
     'PublicSSL.h',
 ]
 
 UNIFIED_SOURCES += [
     'CryptoTask.cpp',
--- a/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
+++ b/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
@@ -1,12 +1,35 @@
 /* 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/. */
 
+let FakeSSLStatus = function() {
+};
+
+FakeSSLStatus.prototype = {
+  serverCert: null,
+  cipherName: null,
+  keyLength: 2048,
+  isDomainMismatch: false,
+  isNotValidAtThisTime: false,
+  isUntrusted: false,
+  isExtendedValidation: false,
+  getInterface: function(aIID) {
+    return this.QueryInterface(aIID);
+  },
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsISSLStatus) ||
+        aIID.equals(Ci.nsISupports)) {
+      return this;
+    }
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+}
+
 // This is a template to help porting global private browsing tests
 // to per-window private browsing tests
 function test() {
   // initialization
   waitForExplicitFinish();
   let windowsToClose = [];
   let testURI = "about:blank";
   let uri;
@@ -15,20 +38,20 @@ function test() {
 
   function privacyFlags(aIsPrivateMode) {
     return aIsPrivateMode ? Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
   }
 
   function doTest(aIsPrivateMode, aWindow, aCallback) {
     aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
       aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
-
+      let sslStatus = new FakeSSLStatus();
       uri = aWindow.Services.io.newURI("https://localhost/img.png", null, null);
       gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                               "max-age=1000", privacyFlags(aIsPrivateMode));
+                               "max-age=1000", sslStatus, privacyFlags(aIsPrivateMode));
       ok(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS, "localhost", privacyFlags(aIsPrivateMode)), "checking sts host");
 
       aCallback();
     }, true);
 
     aWindow.gBrowser.selectedBrowser.loadURI(testURI);
   }
 
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -513,8 +513,32 @@ function startOCSPResponder(serverPort, 
       }
       if (expectedResponseTypes) {
         do_check_eq(expectedResponseTypes.length, 0);
       }
       httpServer.stop(callback);
     }
   };
 }
+
+// A prototype for a fake, error-free sslstatus
+let FakeSSLStatus = function() {
+};
+
+FakeSSLStatus.prototype = {
+  serverCert: null,
+  cipherName: null,
+  keyLength: 2048,
+  isDomainMismatch: false,
+  isNotValidAtThisTime: false,
+  isUntrusted: false,
+  isExtendedValidation: false,
+  getInterface: function(aIID) {
+    return this.QueryInterface(aIID);
+  },
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsISSLStatus) ||
+        aIID.equals(Ci.nsISupports)) {
+      return this;
+    }
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+}
--- a/security/manager/ssl/tests/unit/test_ocsp_no_hsts_upgrade.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_no_hsts_upgrade.js
@@ -37,15 +37,16 @@ function run_test() {
   add_connection_test("ocsp-stapling-none.example.com", Cr.NS_OK);
   add_test(function () { run_next_test(); });
 
   add_test(function () { ocspResponder.stop(run_next_test); });
 
   let SSService = Cc["@mozilla.org/ssservice;1"]
                     .getService(Ci.nsISiteSecurityService);
   let uri = Services.io.newURI("http://localhost", null, null);
+  let sslStatus = new FakeSSLStatus();
   SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                          "max-age=10000", 0);
+                          "max-age=10000", sslStatus, 0);
   do_check_true(SSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                        "localhost", 0));
 
   run_next_test();
 }
--- a/security/manager/ssl/tests/unit/test_sss_eviction.js
+++ b/security/manager/ssl/tests/unit/test_sss_eviction.js
@@ -39,20 +39,21 @@ function do_state_written(aSubject, aTop
   do_test_finished();
 }
 
 function do_state_read(aSubject, aTopic, aData) {
   do_check_eq(aData, SSS_STATE_FILE_NAME);
 
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "frequentlyused.example.com", 0));
+  let sslStatus = new FakeSSLStatus();
   for (let i = 0; i < 2000; i++) {
     let uri = Services.io.newURI("http://bad" + i + ".example.com", null, null);
     gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                            "max-age=1000", 0);
+                            "max-age=1000", sslStatus, 0);
   }
   do_test_pending();
   Services.obs.addObserver(do_state_written, "data-storage-written", false);
   do_test_finished();
 }
 
 function run_test() {
   Services.prefs.setIntPref("test.datastorage.write_timer_ms", 100);
--- a/security/manager/ssl/tests/unit/test_sss_savestate.js
+++ b/security/manager/ssl/tests/unit/test_sss_savestate.js
@@ -106,15 +106,17 @@ function run_test() {
                Services.io.newURI("http://d.example.com", null, null) ];
 
   for (let i = 0; i < 1000; i++) {
     let uriIndex = i % uris.length;
     // vary max-age
     let maxAge = "max-age=" + (i * 1000);
      // alternate setting includeSubdomains
     let includeSubdomains = (i % 2 == 0 ? "; includeSubdomains" : "");
+    let sslStatus = new FakeSSLStatus();
     SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS,
-                            uris[uriIndex], maxAge + includeSubdomains, 0);
+                            uris[uriIndex], maxAge + includeSubdomains,
+                            sslStatus, 0);
   }
 
   do_test_pending();
   Services.obs.addObserver(checkStateWritten, "data-storage-written", false);
 }
--- a/security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
+++ b/security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
@@ -1,9 +1,10 @@
 function check_ip(s, v, ip) {
+  let sslStatus = new FakeSSLStatus();
   do_check_false(s.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS, ip, 0));
 
   let str = "https://";
   if (v == 6) {
     str += "[";
   }
   str += ip;
   if (v == 6) {
@@ -11,17 +12,17 @@ function check_ip(s, v, ip) {
   }
   str += "/";
 
   let uri = Services.io.newURI(str, null, null);
 
   let parsedMaxAge = {};
   let parsedIncludeSubdomains = {};
   s.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                  "max-age=1000;includeSubdomains", 0,
+                  "max-age=1000;includeSubdomains", sslStatus , 0,
                   parsedMaxAge, parsedIncludeSubdomains);
 
   /* Test that processHeader will ignore headers for an uri, if the uri
    * contains an IP address not a hostname.
    * If processHeader indeed ignore the header, then the output parameters will
    * remain empty, and we shouldn't see the values passed as the header.
    */
   do_check_neq(parsedMaxAge.value, 1000);
--- a/security/manager/ssl/tests/unit/test_sts_preloadlist_perwindowpb.js
+++ b/security/manager/ssl/tests/unit/test_sts_preloadlist_perwindowpb.js
@@ -11,16 +11,17 @@ function Observer() {}
 Observer.prototype = {
   observe: function(subject, topic, data) {
     if (topic == "last-pb-context-exited")
       run_next_test();
   }
 };
 
 var gObserver = new Observer();
+var sslStatus = new FakeSSLStatus();
 
 function cleanup() {
   Services.obs.removeObserver(gObserver, "last-pb-context-exited");
   gSSService.clearAll();
 }
 
 function run_test() {
   do_register_cleanup(cleanup);
@@ -65,45 +66,45 @@ function test_part1() {
   // check that a host with a dot on the end won't break anything
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "notsts.nonexistent.mozilla.com.", 0));
 
   // check that processing a header with max-age: 0 will remove a preloaded
   // site from the list
   var uri = Services.io.newURI("http://bugzilla.mozilla.org", null, null);
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", 0);
+                           "max-age=0", sslStatus, 0);
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "bugzilla.mozilla.org", 0));
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "subdomain.bugzilla.mozilla.org", 0));
   // check that processing another header (with max-age non-zero) will
   // re-enable a site's sts status
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1000", 0);
+                           "max-age=1000", sslStatus, 0);
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "bugzilla.mozilla.org", 0));
   // but this time include subdomains was not set, so test for that
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "subdomain.bugzilla.mozilla.org", 0));
   gSSService.clearAll();
 
   // check that processing a header with max-age: 0 from a subdomain of a site
   // will not remove that (ancestor) site from the list
   var uri = Services.io.newURI("http://subdomain.www.torproject.org", null, null);
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", 0);
+                           "max-age=0", sslStatus, 0);
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "www.torproject.org", 0));
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "subdomain.www.torproject.org", 0));
 
   var uri = Services.io.newURI("http://subdomain.bugzilla.mozilla.org", null, null);
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", 0);
+                           "max-age=0", sslStatus, 0);
   // we received a header with "max-age=0", so we have "no information"
   // regarding the sts state of subdomain.bugzilla.mozilla.org specifically,
   // but it is actually still an STS host, because of the preloaded
   // bugzilla.mozilla.org including subdomains.
   // Here's a drawing:
   // |-- bugzilla.mozilla.org (in preload list, includes subdomains) IS sts host
   //     |-- subdomain.bugzilla.mozilla.org                          IS sts host
   //     |   `-- another.subdomain.bugzilla.mozilla.org              IS sts host
@@ -113,17 +114,17 @@ function test_part1() {
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "subdomain.bugzilla.mozilla.org", 0));
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "sibling.bugzilla.mozilla.org", 0));
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "another.subdomain.bugzilla.mozilla.org", 0));
 
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1000", 0);
+                           "max-age=1000", sslStatus, 0);
   // Here's what we have now:
   // |-- bugzilla.mozilla.org (in preload list, includes subdomains) IS sts host
   //     |-- subdomain.bugzilla.mozilla.org (include subdomains is false) IS sts host
   //     |   `-- another.subdomain.bugzilla.mozilla.org              IS NOT sts host
   //     `-- sibling.bugzilla.mozilla.org                            IS sts host
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "subdomain.bugzilla.mozilla.org", 0));
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
@@ -136,17 +137,17 @@ function test_part1() {
   // (This happens when we're in regular browsing mode, we get a header from
   // a site on the preload list, and that header later expires. We need to
   // then treat that host as no longer an sts host.)
   // (sanity check first - this should be in the preload list)
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "login.persona.org", 0));
   var uri = Services.io.newURI("http://login.persona.org", null, null);
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1", 0);
+                           "max-age=1", sslStatus, 0);
   do_timeout(1250, function() {
     do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                            "login.persona.org", 0));
     run_next_test();
   });
 }
 
 const IS_PRIVATE = Ci.nsISocketProvider.NO_PERMANENT_STORAGE;
@@ -156,50 +157,50 @@ function test_private_browsing1() {
   // sanity - bugzilla.mozilla.org is preloaded, includeSubdomains set
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "bugzilla.mozilla.org", IS_PRIVATE));
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "a.b.c.subdomain.bugzilla.mozilla.org", IS_PRIVATE));
 
   var uri = Services.io.newURI("http://bugzilla.mozilla.org", null, null);
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", IS_PRIVATE);
+                           "max-age=0", sslStatus, IS_PRIVATE);
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "bugzilla.mozilla.org", IS_PRIVATE));
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "a.b.subdomain.bugzilla.mozilla.org", IS_PRIVATE));
 
   // check adding it back in
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1000", IS_PRIVATE);
+                           "max-age=1000", sslStatus, IS_PRIVATE);
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "bugzilla.mozilla.org", IS_PRIVATE));
   // but no includeSubdomains this time
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "b.subdomain.bugzilla.mozilla.org", IS_PRIVATE));
 
   // do the hokey-pokey...
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=0", IS_PRIVATE);
+                           "max-age=0", sslStatus, IS_PRIVATE);
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "bugzilla.mozilla.org", IS_PRIVATE));
   do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                          "subdomain.bugzilla.mozilla.org", IS_PRIVATE));
 
   // Test that an expired private browsing entry results in correctly
   // identifying a host that is on the preload list as no longer sts.
   // (This happens when we're in private browsing mode, we get a header from
   // a site on the preload list, and that header later expires. We need to
   // then treat that host as no longer an sts host.)
   // (sanity check first - this should be in the preload list)
   do_check_true(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                         "login.persona.org", IS_PRIVATE));
   var uri = Services.io.newURI("http://login.persona.org", null, null);
   gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                           "max-age=1", IS_PRIVATE);
+                           "max-age=1", sslStatus, IS_PRIVATE);
   do_timeout(1250, function() {
     do_check_false(gSSService.isSecureHost(Ci.nsISiteSecurityService.HEADER_HSTS,
                                            "login.persona.org", IS_PRIVATE));
     // Simulate leaving private browsing mode
     Services.obs.notifyObservers(null, "last-pb-context-exited", null);
   });
 }