Bug 787133 - (hpkp) Internal storage of hpkp data. r=keeler.
authorCamilo Viecco <cviecco@mozilla.com>
Fri, 12 Sep 2014 14:59:37 -0700
changeset 205893 605c11a57482fa7c59dde1ca52e1f2338aa85f37
parent 205892 495dcc54c2b25829e268197a54611a19098f3293
child 205894 7b3ba487c01b6c55a4d290b1f1ab826f42c37eec
push id27507
push userryanvm@gmail.com
push dateThu, 18 Sep 2014 02:16:54 +0000
treeherdermozilla-central@488d490da742 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
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) Internal storage of hpkp data. r=keeler.
security/manager/boot/src/PublicKeyPinningService.cpp
security/manager/boot/src/nsSiteSecurityService.cpp
security/manager/boot/src/nsSiteSecurityService.h
security/manager/ssl/src/nsNSSComponent.cpp
security/manager/ssl/tests/unit/test_sss_eviction.js
security/manager/ssl/tests/unit/test_sss_readstate.js
security/manager/ssl/tests/unit/test_sss_readstate_garbage.js
security/manager/ssl/tests/unit/test_sss_readstate_huge.js
security/manager/ssl/tests/unit/test_sss_savestate.js
--- a/security/manager/boot/src/PublicKeyPinningService.cpp
+++ b/security/manager/boot/src/PublicKeyPinningService.cpp
@@ -4,17 +4,21 @@
 
 #include "PublicKeyPinningService.h"
 #include "pkix/nullptr.h"
 #include "StaticHPKPins.h" // autogenerated by genHPKPStaticpins.js
 
 #include "cert.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Telemetry.h"
+#include "nsISiteSecurityService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSiteSecurityService.h"
 #include "nsString.h"
+#include "nsTArray.h"
 #include "nssb64.h"
 #include "pkix/pkixtypes.h"
 #include "prlog.h"
 #include "ScopedNSSTypes.h"
 #include "seccomon.h"
 #include "sechash.h"
 
 using namespace mozilla;
@@ -48,96 +52,112 @@ GetBase64HashSPKI(const CERTCertificate*
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return SECFailure;
   }
   return SECSuccess;
 }
 
 /*
  * Returns true if a given cert matches any hashType fingerprints from the
- * given pinset, false otherwise.
+ * given pinset or the dynamicFingeprints array, false otherwise.
  */
 static bool
 EvalCertWithHashType(const CERTCertificate* cert, SECOidTag hashType,
-                     const StaticFingerprints* fingerprints)
+                     const StaticFingerprints* fingerprints,
+                     const nsTArray<nsCString>* dynamicFingerprints)
 {
-  if (!fingerprints) {
+  if (!fingerprints && !dynamicFingerprints) {
     PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
            ("pkpin: No hashes found for hash type: %d\n", hashType));
     return false;
   }
 
   nsAutoCString base64Out;
   SECStatus srv = GetBase64HashSPKI(cert, hashType, base64Out);
   if (srv != SECSuccess) {
     PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
            ("pkpin: GetBase64HashSPKI failed!\n"));
     return false;
   }
 
-  for (size_t i = 0; i < fingerprints->size; i++) {
-    if (base64Out.Equals(fingerprints->data[i])) {
-      PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
-             ("pkpin: found pin base_64 ='%s'\n", base64Out.get()));
-      return true;
+  if (fingerprints) {
+    for (size_t i = 0; i < fingerprints->size; i++) {
+      if (base64Out.Equals(fingerprints->data[i])) {
+        PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
+               ("pkpin: found pin base_64 ='%s'\n", base64Out.get()));
+       return true;
+      }
+    }
+  }
+  if (dynamicFingerprints) {
+    for (size_t i = 0; i < dynamicFingerprints->Length(); i++) {
+      if (base64Out.Equals((*dynamicFingerprints)[i])) {
+        PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
+               ("pkpin: found pin base_64 ='%s'\n", base64Out.get()));
+        return true;
+      }
     }
   }
   return false;
 }
 
 /*
  * Returns true if a given chain matches any hashType fingerprints from the
- * given pinset, false otherwise.
+ * given pinset or the dynamicFingerprints array, false otherwise.
  */
 static bool
 EvalChainWithHashType(const CERTCertList* certList, SECOidTag hashType,
-                      const StaticPinset* pinset)
+                      const StaticPinset* pinset,
+                      const nsTArray<nsCString>* dynamicFingerprints)
 {
   CERTCertificate* currentCert;
 
   const StaticFingerprints* fingerprints = nullptr;
-  if (hashType == SEC_OID_SHA256) {
-    fingerprints = pinset->sha256;
-  } else if (hashType == SEC_OID_SHA1) {
-    fingerprints = pinset->sha1;
+  if (pinset) {
+    if (hashType == SEC_OID_SHA256) {
+      fingerprints = pinset->sha256;
+    } else if (hashType == SEC_OID_SHA1) {
+      fingerprints = pinset->sha1;
+    }
   }
-  if (!fingerprints) {
+  if (!fingerprints && !dynamicFingerprints) {
     return false;
   }
 
   CERTCertListNode* node;
   for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList);
        node = CERT_LIST_NEXT(node)) {
     currentCert = node->cert;
     PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
            ("pkpin: certArray subject: '%s'\n",
             currentCert->subjectName));
     PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
            ("pkpin: certArray common_name: '%s'\n",
             CERT_GetCommonName(&(currentCert->issuer))));
-    if (EvalCertWithHashType(currentCert, hashType, fingerprints)) {
+    if (EvalCertWithHashType(currentCert, hashType, fingerprints,
+                             dynamicFingerprints)) {
       return true;
     }
   }
   PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG, ("pkpin: no matches found\n"));
   return false;
 }
 
 /**
  * Given a pinset and certlist, return true if one of the certificates on
  * the list matches a fingerprint in the pinset, false otherwise.
  */
 static bool
 EvalChainWithPinset(const CERTCertList* certList,
                     const StaticPinset* pinset) {
   // SHA256 is more trustworthy, try that first.
-  if (EvalChainWithHashType(certList, SEC_OID_SHA256, pinset)) {
+  if (EvalChainWithHashType(certList, SEC_OID_SHA256, pinset, nullptr)) {
     return true;
   }
-  return EvalChainWithHashType(certList, SEC_OID_SHA1, pinset);
+  return EvalChainWithHashType(certList, SEC_OID_SHA1, pinset, nullptr);
 }
 
 /**
   Comparator for the is public key pinned host.
 */
 static int
 TransportSecurityPreloadCompare(const void *key, const void *entry) {
   const char *keyStr = reinterpret_cast<const char *>(key);
@@ -147,32 +167,55 @@ TransportSecurityPreloadCompare(const vo
   return strcmp(keyStr, preloadEntry->mHost);
 }
 
 /**
  * Check PKPins on the given certlist against the specified hostname
  */
 static bool
 CheckPinsForHostname(const CERTCertList *certList, const char *hostname,
-                     bool enforceTestMode)
+                     bool enforceTestMode, mozilla::pkix::Time time)
 {
   if (!certList) {
     return false;
   }
   if (!hostname || hostname[0] == 0) {
     return false;
   }
 
+  nsCOMPtr<nsISiteSecurityService> sssService =
+    do_GetService(NS_SSSERVICE_CONTRACTID);
+  if (!sssService) {
+    return false;
+  }
+  SiteHPKPState dynamicEntry;
   TransportSecurityPreload *foundEntry = nullptr;
   char *evalHost = const_cast<char*>(hostname);
   char *evalPart;
   // Notice how the (xx = strchr) prevents pins for unqualified domain names.
   while (!foundEntry && (evalPart = strchr(evalHost, '.'))) {
     PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
            ("pkpin: Querying pinsets for host: '%s'\n", evalHost));
+    // Attempt dynamic pins first
+    nsresult rv;
+    bool found;
+    bool includeSubdomains;
+    nsTArray<nsCString> pinArray;
+    rv = sssService->GetKeyPinsForHostname(evalHost, time, pinArray,
+                                           &includeSubdomains, &found);
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+    if (found && (evalHost == hostname || includeSubdomains)) {
+      PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
+             ("pkpin: Found dyn match for host: '%s'\n", evalHost));
+      return EvalChainWithHashType(certList, SEC_OID_SHA256, nullptr,
+                                   &pinArray);
+    }
+
     foundEntry = (TransportSecurityPreload *)bsearch(evalHost,
       kPublicKeyPinningPreloadList,
       sizeof(kPublicKeyPinningPreloadList) / sizeof(TransportSecurityPreload),
       sizeof(TransportSecurityPreload),
       TransportSecurityPreloadCompare);
     if (foundEntry) {
       PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
              ("pkpin: Found pinset for host: '%s'\n", evalHost));
@@ -186,16 +229,20 @@ CheckPinsForHostname(const CERTCertList 
       PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
              ("pkpin: Didn't find pinset for host: '%s'\n", evalHost));
     }
     // Add one for '.'
     evalHost = evalPart + 1;
   }
 
   if (foundEntry && foundEntry->pinset) {
+    if (time > TimeFromEpochInSeconds(kPreloadPKPinsExpirationTime /
+                                      PR_USEC_PER_SEC)) {
+      return true;
+    }
     bool result = EvalChainWithPinset(certList, foundEntry->pinset);
     bool retval = result;
     Telemetry::ID histogram = foundEntry->mIsMoz
       ? Telemetry::CERT_PINNING_MOZ_RESULTS
       : Telemetry::CERT_PINNING_RESULTS;
     if (foundEntry->mTestMode) {
       histogram = foundEntry->mIsMoz
         ? Telemetry::CERT_PINNING_MOZ_TEST_RESULTS
@@ -226,17 +273,18 @@ CheckPinsForHostname(const CERTCertList 
 }
 
 /**
  * Extract all the DNS names for a host (including CN) and evaluate the
  * certifiate pins against all of them (Currently is an OR so we stop
  * evaluating at the first OK pin).
  */
 static bool
-CheckChainAgainstAllNames(const CERTCertList* certList, bool enforceTestMode)
+CheckChainAgainstAllNames(const CERTCertList* certList, bool enforceTestMode,
+                          mozilla::pkix::Time time)
 {
   PR_LOG(gPublicKeyPinningLog, PR_LOG_DEBUG,
          ("pkpin: top of checkChainAgainstAllNames"));
   CERTCertListNode* node = CERT_LIST_HEAD(certList);
   if (!node) {
     return false;
   }
   CERTCertificate* cert = node->cert;
@@ -271,17 +319,17 @@ CheckChainAgainstAllNames(const CERTCert
       // null terminated.
       hostName[currentName->name.other.len] = 0;
       memcpy(hostName, currentName->name.other.data,
              currentName->name.other.len);
       if (!hostName[0]) {
         // cannot call CheckPinsForHostname on empty or null hostname
         break;
       }
-      if (CheckPinsForHostname(certList, hostName, enforceTestMode)) {
+      if (CheckPinsForHostname(certList, hostName, enforceTestMode, time)) {
         hasValidPins = true;
         break;
       }
     }
     currentName = CERT_GetNextGeneralName(currentName);
   } while (currentName != nameList);
 
   return hasValidPins;
@@ -291,17 +339,13 @@ bool
 PublicKeyPinningService::ChainHasValidPins(const CERTCertList* certList,
                                            const char* hostname,
                                            mozilla::pkix::Time time,
                                            bool enforceTestMode)
 {
   if (!certList) {
     return false;
   }
-  if (time > TimeFromEpochInSeconds(kPreloadPKPinsExpirationTime /
-                                    PR_USEC_PER_SEC)) {
-    return true;
+  if (!hostname || hostname[0] == 0) {
+    return CheckChainAgainstAllNames(certList, enforceTestMode, time);
   }
-  if (!hostname || hostname[0] == 0) {
-    return CheckChainAgainstAllNames(certList, enforceTestMode);
-  }
-  return CheckPinsForHostname(certList, hostname, enforceTestMode);
+  return CheckPinsForHostname(certList, hostname, enforceTestMode, time);
 }
--- a/security/manager/boot/src/nsSiteSecurityService.cpp
+++ b/security/manager/boot/src/nsSiteSecurityService.cpp
@@ -1,56 +1,64 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsSiteSecurityService.h"
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Base64.h"
+#include "base64.h"
 #include "nsCRTGlue.h"
 #include "nsISSLStatus.h"
 #include "nsISSLStatusProvider.h"
 #include "nsISocketProvider.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsSecurityHeaderParser.h"
 #include "nsString.h"
 #include "nsThreadUtils.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"
 
 // 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.
 #include "nsSTSPreloadList.inc"
 
+using namespace mozilla;
+using namespace mozilla::psm;
+
 #if defined(PR_LOGGING)
 static PRLogModuleInfo *
 GetSSSLog()
 {
   static PRLogModuleInfo *gSSSLog;
   if (!gSSSLog)
     gSSSLog = PR_NewLogModule("nsSSService");
   return gSSSLog;
 }
 #endif
 
 #define SSSLOG(args) PR_LOG(GetSSSLog(), 4, args)
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SiteSecurityState::SiteSecurityState(nsCString& aStateString)
+SiteHSTSState::SiteHSTSState(nsCString& aStateString)
   : mHSTSExpireTime(0)
   , mHSTSState(SecurityPropertyUnset)
   , mHSTSIncludeSubdomains(false)
 {
   uint32_t hstsState = 0;
   uint32_t hstsIncludeSubdomains = 0; // PR_sscanf doesn't handle bools.
   int32_t matches = PR_sscanf(aStateString.get(), "%lld,%lu,%lu",
                               &mHSTSExpireTime, &hstsState,
@@ -59,45 +67,153 @@ SiteSecurityState::SiteSecurityState(nsC
                 (hstsIncludeSubdomains == 0 || hstsIncludeSubdomains == 1) &&
                 ((SecurityPropertyState)hstsState == SecurityPropertyUnset ||
                  (SecurityPropertyState)hstsState == SecurityPropertySet ||
                  (SecurityPropertyState)hstsState == SecurityPropertyKnockout));
   if (valid) {
     mHSTSState = (SecurityPropertyState)hstsState;
     mHSTSIncludeSubdomains = (hstsIncludeSubdomains == 1);
   } else {
-    SSSLOG(("%s is not a valid SiteSecurityState", aStateString.get()));
+    SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get()));
     mHSTSExpireTime = 0;
     mHSTSState = SecurityPropertyUnset;
     mHSTSIncludeSubdomains = false;
   }
 }
 
-SiteSecurityState::SiteSecurityState(PRTime aHSTSExpireTime,
-                                     SecurityPropertyState aHSTSState,
-                                     bool aHSTSIncludeSubdomains)
+SiteHSTSState::SiteHSTSState(PRTime aHSTSExpireTime,
+                             SecurityPropertyState aHSTSState,
+                             bool aHSTSIncludeSubdomains)
 
   : mHSTSExpireTime(aHSTSExpireTime)
   , mHSTSState(aHSTSState)
   , mHSTSIncludeSubdomains(aHSTSIncludeSubdomains)
 {
 }
 
 void
-SiteSecurityState::ToString(nsCString& aString)
+SiteHSTSState::ToString(nsCString& aString)
 {
   aString.Truncate();
   aString.AppendInt(mHSTSExpireTime);
   aString.Append(',');
   aString.AppendInt(mHSTSState);
   aString.Append(',');
   aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+static bool
+stringIsBase64EncodingOf256bitValue(nsCString& encodedString) {
+  nsAutoCString binaryValue;
+  nsresult rv = mozilla::Base64Decode(encodedString, binaryValue);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  if (binaryValue.Length() != SHA256_LENGTH) {
+    return false;
+  }
+  return true;
+}
+
+SiteHPKPState::SiteHPKPState()
+  : mExpireTime(0)
+  , mState(SecurityPropertyUnset)
+  , mIncludeSubdomains(false)
+{
+}
+
+SiteHPKPState::SiteHPKPState(nsCString& aStateString)
+  : mExpireTime(0)
+  , mState(SecurityPropertyUnset)
+  , mIncludeSubdomains(false)
+{
+  uint32_t hpkpState = 0;
+  uint32_t hpkpIncludeSubdomains = 0; // PR_sscanf doesn't handle bools.
+  const uint32_t MaxMergedHPKPPinSize = 1024;
+  char mergedHPKPins[MaxMergedHPKPPinSize];
+  memset(mergedHPKPins, 0, MaxMergedHPKPPinSize);
+
+  if (aStateString.Length() >= MaxMergedHPKPPinSize) {
+    SSSLOG(("SSS: Cannot parse PKPState string, too large\n"));
+    return;
+  }
+
+  int32_t matches = PR_sscanf(aStateString.get(), "%lld,%lu,%lu,%s",
+                              &mExpireTime, &hpkpState,
+                              &hpkpIncludeSubdomains, mergedHPKPins);
+  bool valid = (matches == 4 &&
+                (hpkpIncludeSubdomains == 0 || hpkpIncludeSubdomains == 1) &&
+                ((SecurityPropertyState)hpkpState == SecurityPropertyUnset ||
+                 (SecurityPropertyState)hpkpState == SecurityPropertySet ||
+                 (SecurityPropertyState)hpkpState == SecurityPropertyKnockout));
+
+  SSSLOG(("SSS: loading SiteHPKPState matches=%d\n", matches));
+  const uint32_t SHA256Base64Len = 44;
+
+  if (valid && (SecurityPropertyState)hpkpState == SecurityPropertySet) {
+    // try to expand the merged PKPins
+    const char* cur = mergedHPKPins;
+    nsAutoCString pin;
+    uint32_t collectedLen = 0;
+    mergedHPKPins[MaxMergedHPKPPinSize - 1] = 0;
+    size_t totalLen = strlen(mergedHPKPins);
+    while (collectedLen + SHA256Base64Len <= totalLen) {
+      pin.Assign(cur, SHA256Base64Len);
+      if (stringIsBase64EncodingOf256bitValue(pin)) {
+        mSHA256keys.AppendElement(pin);
+      }
+      cur += SHA256Base64Len;
+      collectedLen += SHA256Base64Len;
+    }
+    if (mSHA256keys.IsEmpty()) {
+      valid = false;
+    }
+  }
+  if (valid) {
+    mState = (SecurityPropertyState)hpkpState;
+    mIncludeSubdomains = (hpkpIncludeSubdomains == 1);
+  } else {
+    SSSLOG(("%s is not a valid SiteHPKPState", aStateString.get()));
+    mExpireTime = 0;
+    mState = SecurityPropertyUnset;
+    mIncludeSubdomains = false;
+    if (!mSHA256keys.IsEmpty()) {
+      mSHA256keys.Clear();
+    }
+  }
+}
+
+SiteHPKPState::SiteHPKPState(PRTime aExpireTime,
+                             SecurityPropertyState aState,
+                             bool aIncludeSubdomains,
+                             nsTArray<nsCString>& aSHA256keys)
+  : mExpireTime(aExpireTime)
+  , mState(aState)
+  , mIncludeSubdomains(aIncludeSubdomains)
+  , mSHA256keys(aSHA256keys)
+{
+}
+
+void
+SiteHPKPState::ToString(nsCString& aString)
+{
+  aString.Truncate();
+  aString.AppendInt(mExpireTime);
+  aString.Append(',');
+  aString.AppendInt(mState);
+  aString.Append(',');
+  aString.AppendInt(static_cast<uint32_t>(mIncludeSubdomains));
+  aString.Append(',');
+  for (unsigned int i = 0; i < mSHA256keys.Length(); i++) {
+    aString.Append(mSHA256keys[i]);
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////
 
 nsSiteSecurityService::nsSiteSecurityService()
   : mUsePreloadList(true)
   , mPreloadListTimeOffset(0)
 {
 }
 
 nsSiteSecurityService::~nsSiteSecurityService()
@@ -156,47 +272,64 @@ nsSiteSecurityService::GetHost(nsIURI *a
   nsresult rv = innerURI->GetAsciiHost(aResult);
 
   if (NS_FAILED(rv) || aResult.IsEmpty())
     return NS_ERROR_UNEXPECTED;
 
   return NS_OK;
 }
 
+static void
+SetStorageKey(nsAutoCString& storageKey, nsCString& hostname, uint32_t aType)
+{
+  storageKey = hostname;
+  switch (aType) {
+    case nsISiteSecurityService::HEADER_HSTS:
+      storageKey.AppendLiteral(":HSTS");
+      break;
+    case nsISiteSecurityService::HEADER_HPKP:
+      storageKey.AppendLiteral(":HPKP");
+      break;
+    default:
+      NS_ASSERTION(false, "SSS:SetStorageKey got invalid type");
+  }
+}
+
 nsresult
-nsSiteSecurityService::SetState(uint32_t aType,
-                                nsIURI* aSourceURI,
-                                int64_t maxage,
-                                bool includeSubdomains,
-                                uint32_t flags)
+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);
 
-  SiteSecurityState siteState(expiretime, SecurityPropertySet,
-                              includeSubdomains);
+  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;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
-  rv = mSiteStateStorage->Put(hostname, stateString, storageType);
+  nsAutoCString storageKey;
+  SetStorageKey(storageKey, hostname, aType);
+  rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI,
                                    uint32_t aFlags)
@@ -211,24 +344,28 @@ nsSiteSecurityService::RemoveState(uint3
 
   bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
   // If this host is in the preload list, we have to store a knockout entry.
   if (GetPreloadListEntry(hostname.get())) {
     SSSLOG(("SSS: storing knockout entry for %s", hostname.get()));
-    SiteSecurityState siteState(0, SecurityPropertyKnockout, false);
+    SiteHSTSState siteState(0, SecurityPropertyKnockout, false);
     nsAutoCString stateString;
     siteState.ToString(stateString);
-    rv = mSiteStateStorage->Put(hostname, stateString, storageType);
+    nsAutoCString storageKey;
+    SetStorageKey(storageKey, hostname, aType);
+    rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
     NS_ENSURE_SUCCESS(rv, rv);
   } else {
     SSSLOG(("SSS: removing entry for %s", hostname.get()));
-    mSiteStateStorage->Remove(hostname, storageType);
+    nsAutoCString storageKey;
+    SetStorageKey(storageKey, hostname, aType);
+    mSiteStateStorage->Remove(storageKey, storageType);
   }
 
   return NS_OK;
 }
 
 static bool
 HostIsIPAddress(const char *hostname)
 {
@@ -371,17 +508,17 @@ nsSiteSecurityService::ProcessHeaderMuta
   // 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;
   }
 
   // record the successfully parsed header data.
-  SetState(aType, aSourceURI, maxAge, foundIncludeSubdomains, aFlags);
+  SetHSTSState(aType, aSourceURI, maxAge, foundIncludeSubdomains, aFlags);
 
   if (aMaxAge != nullptr) {
     *aMaxAge = (uint64_t)maxAge;
   }
 
   if (aIncludeSubdomains != nullptr) {
     *aIncludeSubdomains = foundIncludeSubdomains;
   }
@@ -434,28 +571,43 @@ nsSiteSecurityService::GetPreloadListEnt
 
   return nullptr;
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost,
                                     uint32_t aFlags, bool* aResult)
 {
-  // Only HSTS is supported at the moment.
-  NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS,
+  // Only HSTS and HPKP are supported at the moment.
+  NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
+                 aType == nsISiteSecurityService::HEADER_HPKP,
                  NS_ERROR_NOT_IMPLEMENTED);
 
   // set default in case if we can't find any STS information
   *aResult = false;
 
   /* An IP address never qualifies as a secure URI. */
   if (HostIsIPAddress(aHost)) {
     return NS_OK;
   }
 
+  if (aType == nsISiteSecurityService::HEADER_HPKP) {
+    ScopedCERTCertList certList(CERT_NewCertList());
+    if (!certList) {
+      return NS_ERROR_FAILURE;
+    }
+    // Todo: we need to update ChainHasValidPins to distinguish between there
+    // are no pins or there was an internal failure.
+    *aResult = !PublicKeyPinningService::ChainHasValidPins(certList,
+                                                           aHost,
+                                                           mozilla::pkix::Now(),
+                                                           false);
+    return NS_OK;
+  }
+
   // Holepunch chart.apis.google.com and subdomains.
   nsAutoCString host(aHost);
   ToLowerCase(host);
   if (host.EqualsLiteral("chart.apis.google.com") ||
       StringEndsWith(host, NS_LITERAL_CSTRING(".chart.apis.google.com"))) {
     return NS_OK;
   }
 
@@ -467,29 +619,31 @@ nsSiteSecurityService::IsSecureHost(uint
   // knockout entry, however.
   // Additionally, if it is a knockout entry, we want to stop looking for data
   // on the host, because the knockout entry indicates "we have no information
   // regarding the security status of this host".
   bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
-  nsCString value = mSiteStateStorage->Get(host, storageType);
-  SiteSecurityState siteState(value);
+  nsAutoCString storageKey;
+  SetStorageKey(storageKey, host, aType);
+  nsCString value = mSiteStateStorage->Get(storageKey, storageType);
+  SiteHSTSState siteState(value);
   if (siteState.mHSTSState != SecurityPropertyUnset) {
     SSSLOG(("Found entry for %s", host.get()));
     bool expired = siteState.IsExpired(aType);
     if (!expired && siteState.mHSTSState == SecurityPropertySet) {
       *aResult = true;
       return NS_OK;
     }
 
     // If the entry is expired and not in the preload list, we can remove it.
     if (expired && !GetPreloadListEntry(host.get())) {
-      mSiteStateStorage->Remove(host, storageType);
+      mSiteStateStorage->Remove(storageKey, storageType);
     }
   }
   // Finally look in the preloaded list. This is the exact host,
   // so if an entry exists at all, this host is HSTS.
   else if (GetPreloadListEntry(host.get())) {
     SSSLOG(("%s is a preloaded STS host", host.get()));
     *aResult = true;
     return NS_OK;
@@ -512,29 +666,31 @@ nsSiteSecurityService::IsSecureHost(uint
 
     // Do the same thing as with the exact host, except now we're looking at
     // ancestor domains of the original host. So, we have to look at the
     // include subdomains flag (although we still have to check for a
     // SecurityPropertySet flag first to check that this is a secure host and
     // not a knockout entry - and again, if it is a knockout entry, we stop
     // looking for data on it and skip to the next higher up ancestor domain).
     nsCString subdomainString(subdomain);
-    value = mSiteStateStorage->Get(subdomainString, storageType);
-    SiteSecurityState siteState(value);
+    nsAutoCString storageKey;
+    SetStorageKey(storageKey, subdomainString, aType);
+    value = mSiteStateStorage->Get(storageKey, storageType);
+    SiteHSTSState siteState(value);
     if (siteState.mHSTSState != SecurityPropertyUnset) {
       SSSLOG(("Found entry for %s", subdomain));
       bool expired = siteState.IsExpired(aType);
       if (!expired && siteState.mHSTSState == SecurityPropertySet) {
         *aResult = siteState.mHSTSIncludeSubdomains;
         break;
       }
 
       // If the entry is expired and not in the preload list, we can remove it.
       if (expired && !GetPreloadListEntry(subdomain)) {
-        mSiteStateStorage->Remove(subdomainString, storageType);
+        mSiteStateStorage->Remove(storageKey, storageType);
       }
     }
     // This is an ancestor, so if we get a match, we have to check if the
     // preloaded entry includes subdomains.
     else if ((preload = GetPreloadListEntry(subdomain)) != nullptr) {
       if (preload->mIncludeSubdomains) {
         SSSLOG(("%s is a preloaded STS host", subdomain));
         *aResult = true;
@@ -583,16 +739,90 @@ nsSiteSecurityService::ShouldIgnoreHeade
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::ClearAll()
 {
   return mSiteStateStorage->Clear();
 }
 
+NS_IMETHODIMP
+nsSiteSecurityService::GetKeyPinsForHostname(const char* aHostname,
+                                             mozilla::pkix::Time& aEvalTime,
+                                             /*out*/ nsTArray<nsCString>& pinArray,
+                                             /*out*/ bool* aIncludeSubdomains,
+                                             /*out*/ bool* afound) {
+  NS_ENSURE_ARG(afound);
+  NS_ENSURE_ARG(aHostname);
+
+  SSSLOG(("Top of GetKeyPinsForHostname"));
+  *afound = false;
+  *aIncludeSubdomains = false;
+  pinArray.Clear();
+
+  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
+  SiteHPKPState foundEntry(value);
+  if (foundEntry.mState != SecurityPropertySet ||
+      foundEntry.IsExpired(aEvalTime) ||
+      foundEntry.mSHA256keys.Length() < 1 ) {
+    return NS_OK;
+  }
+  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)
+{
+  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);
+  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);
+
+  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;
+  nsAutoCString stateString;
+  dynamicEntry.ToString(stateString);
+  nsresult rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
+  NS_ENSURE_SUCCESS(rv, rv);
+  return NS_OK;
+}
+
 //------------------------------------------------------------
 // nsSiteSecurityService::nsIObserver
 //------------------------------------------------------------
 
 NS_IMETHODIMP
 nsSiteSecurityService::Observe(nsISupports *subject,
                                const char *topic,
                                const char16_t *data)
--- a/security/manager/boot/src/nsSiteSecurityService.h
+++ b/security/manager/boot/src/nsSiteSecurityService.h
@@ -5,16 +5,18 @@
 #ifndef __nsSiteSecurityService_h__
 #define __nsSiteSecurityService_h__
 
 #include "mozilla/DataStorage.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "nsISiteSecurityService.h"
 #include "nsString.h"
+#include "nsTArray.h"
+#include "pkix/pkixtypes.h"
 #include "prtime.h"
 
 class nsIURI;
 
 // {16955eee-6c48-4152-9309-c42a465138a1}
 #define NS_SITE_SECURITY_SERVICE_CID \
   {0x16955eee, 0x6c48, 0x4152, \
     {0x93, 0x09, 0xc4, 0x2a, 0x46, 0x51, 0x38, 0xa1} }
@@ -30,29 +32,63 @@ class nsIURI;
  */
 enum SecurityPropertyState {
   SecurityPropertyUnset = 0,
   SecurityPropertySet = 1,
   SecurityPropertyKnockout = 2
 };
 
 /**
- * SiteSecurityState: A utility class that encodes/decodes a string describing
+ * SiteHPKPState: A utility class that encodes/decodes a string describing
+ * the public key pins of a site.
+ * HPKP state consists of:
+ *  - Expiry time (PRTime (aka int64_t) in milliseconds)
+ *  - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
+ *  - An include subdomains flag (bool, default false)
+ *  - An array of sha-256 hashed base 64 encoded fingerprints of required keys
+ */
+class SiteHPKPState
+{
+public:
+  SiteHPKPState();
+  SiteHPKPState(nsCString& aStateString);
+  SiteHPKPState(PRTime aExpireTime, SecurityPropertyState aState,
+                bool aIncludeSubdomains, nsTArray<nsCString>& SHA256keys);
+
+  PRTime mExpireTime;
+  SecurityPropertyState mState;
+  bool mIncludeSubdomains;
+  nsTArray<nsCString> mSHA256keys;
+
+  bool IsExpired(mozilla::pkix::Time aTime)
+  {
+    if (aTime > mozilla::pkix::TimeFromEpochInSeconds(mExpireTime /
+                                                      PR_MSEC_PER_SEC)) {
+      return true;
+    }
+    return false;
+  }
+
+  void ToString(nsCString& aString);
+};
+
+/**
+ * SiteHSTSState: A utility class that encodes/decodes a string describing
  * the security state of a site. Currently only handles HSTS.
  * HSTS state consists of:
  *  - Expiry time (PRTime (aka int64_t) in milliseconds)
  *  - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
  *  - An include subdomains flag (bool, default false)
  */
-class SiteSecurityState
+class SiteHSTSState
 {
 public:
-  explicit SiteSecurityState(nsCString& aStateString);
-  SiteSecurityState(PRTime aHSTSExpireTime, SecurityPropertyState aHSTSState,
-                    bool aHSTSIncludeSubdomains);
+  SiteHSTSState(nsCString& aStateString);
+  SiteHSTSState(PRTime aHSTSExpireTime, SecurityPropertyState aHSTSState,
+                bool aHSTSIncludeSubdomains);
 
   PRTime mHSTSExpireTime;
   SecurityPropertyState mHSTSState;
   bool mHSTSIncludeSubdomains;
 
   bool IsExpired(uint32_t aType)
   {
     // If mHSTSExpireTime is 0, this entry never expires (this is the case for
@@ -85,18 +121,18 @@ public:
   nsSiteSecurityService();
   nsresult Init();
 
 protected:
   virtual ~nsSiteSecurityService();
 
 private:
   nsresult GetHost(nsIURI *aURI, nsACString &aResult);
-  nsresult SetState(uint32_t aType, nsIURI* aSourceURI, int64_t maxage,
-                    bool includeSubdomains, uint32_t flags);
+  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);
   const nsSTSPreload *GetPreloadListEntry(const char *aHost);
 
   bool mUsePreloadList;
   int64_t mPreloadListTimeOffset;
   nsRefPtr<mozilla::DataStorage> mSiteStateStorage;
--- a/security/manager/ssl/src/nsNSSComponent.cpp
+++ b/security/manager/ssl/src/nsNSSComponent.cpp
@@ -30,16 +30,17 @@
 #include "nsCRT.h"
 #include "nsNTLMAuthModule.h"
 #include "nsIFile.h"
 #include "nsIProperties.h"
 #include "nsIWindowWatcher.h"
 #include "nsIPrompt.h"
 #include "nsIBufEntropyCollector.h"
 #include "nsITokenPasswordDialogs.h"
+#include "nsISiteSecurityService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsNSSShutDown.h"
 #include "SharedSSLState.h"
 #include "NSSErrorsService.h"
 
 #include "nss.h"
 #include "pkix/pkixnss.h"
 #include "ssl.h"
@@ -1034,16 +1035,25 @@ nsNSSComponent::InitializeNSS()
   mHttpForNSS.initTable();
 
 #ifndef MOZ_NO_SMART_CARDS
   LaunchSmartCardThreads();
 #endif
 
   mozilla::pkix::RegisterErrorTable();
 
+  // Initialize the site security service
+  nsCOMPtr<nsISiteSecurityService> sssService =
+    do_GetService(NS_SSSERVICE_CONTRACTID);
+  if (!sssService) {
+    PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Cannot initialize site security service\n"));
+    return NS_ERROR_FAILURE;
+  }
+
+
   PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("NSS Initialization done\n"));
   return NS_OK;
 }
 
 void
 nsNSSComponent::ShutdownNSS()
 {
   // Can be called both during init and profile change,
--- a/security/manager/ssl/tests/unit/test_sss_eviction.js
+++ b/security/manager/ssl/tests/unit/test_sss_eviction.js
@@ -24,17 +24,17 @@ function do_state_written(aSubject, aTop
   // so we return and wait for the next event if things aren't as we expect.
   // There should be 1024 entries.
   if (lines.length != 1024) {
     return;
   }
 
   let foundLegitSite = false;
   for (let line of lines) {
-    if (line.startsWith("frequentlyused.example.com")) {
+    if (line.startsWith("frequentlyused.example.com:HSTS")) {
       foundLegitSite = true;
       break;
     }
   }
 
   do_check_true(foundLegitSite);
   do_test_finished();
 }
@@ -59,17 +59,17 @@ function run_test() {
   gProfileDir = do_get_profile();
   let stateFile = gProfileDir.clone();
   stateFile.append(SSS_STATE_FILE_NAME);
   // Assuming we're working with a clean slate, the file shouldn't exist
   // until we create it.
   do_check_false(stateFile.exists());
   let outputStream = FileUtils.openFileOutputStream(stateFile);
   let now = (new Date()).getTime();
-  let line = "frequentlyused.example.com\t4\t0\t" + (now + 100000) + ",1,0\n";
+  let line = "frequentlyused.example.com:HSTS\t4\t0\t" + (now + 100000) + ",1,0\n";
   outputStream.write(line, line.length);
   outputStream.close();
   Services.obs.addObserver(do_state_read, "data-storage-ready", false);
   do_test_pending();
   gSSService = Cc["@mozilla.org/ssservice;1"]
                  .getService(Ci.nsISiteSecurityService);
   do_check_true(gSSService != null);
 }
--- a/security/manager/ssl/tests/unit/test_sss_readstate.js
+++ b/security/manager/ssl/tests/unit/test_sss_readstate.js
@@ -56,22 +56,22 @@ function run_test() {
   let profileDir = do_get_profile();
   let stateFile = profileDir.clone();
   stateFile.append(SSS_STATE_FILE_NAME);
   // Assuming we're working with a clean slate, the file shouldn't exist
   // until we create it.
   do_check_false(stateFile.exists());
   let outputStream = FileUtils.openFileOutputStream(stateFile);
   let now = (new Date()).getTime();
-  writeLine("expired.example.com\t0\t0\t" + (now - 100000) + ",1,0\n", outputStream);
-  writeLine("notexpired.example.com\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
+  writeLine("expired.example.com:HSTS\t0\t0\t" + (now - 100000) + ",1,0\n", outputStream);
+  writeLine("notexpired.example.com:HSTS\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
   // This overrides an entry on the preload list.
-  writeLine("bugzilla.mozilla.org\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
-  writeLine("incsubdomain.example.com\t0\t0\t" + (now + 100000) + ",1,1\n", outputStream);
+  writeLine("bugzilla.mozilla.org:HSTS\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
+  writeLine("incsubdomain.example.com:HSTS\t0\t0\t" + (now + 100000) + ",1,1\n", outputStream);
   // This overrides an entry on the preload list.
-  writeLine("login.persona.org\t0\t0\t0,2,0\n", outputStream);
+  writeLine("login.persona.org:HSTS\t0\t0\t0,2,0\n", outputStream);
   outputStream.close();
   Services.obs.addObserver(checkStateRead, "data-storage-ready", false);
   do_test_pending();
   gSSService = Cc["@mozilla.org/ssservice;1"]
                  .getService(Ci.nsISiteSecurityService);
   do_check_true(gSSService != null);
 }
--- a/security/manager/ssl/tests/unit/test_sss_readstate_garbage.js
+++ b/security/manager/ssl/tests/unit/test_sss_readstate_garbage.js
@@ -29,23 +29,23 @@ function run_test() {
   let profileDir = do_get_profile();
   let stateFile = profileDir.clone();
   stateFile.append(SSS_STATE_FILE_NAME);
   // Assuming we're working with a clean slate, the file shouldn't exist
   // until we create it.
   do_check_false(stateFile.exists());
   let outputStream = FileUtils.openFileOutputStream(stateFile);
   let now = (new Date()).getTime();
-  writeLine("example1.example.com\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
+  writeLine("example1.example.com:HSTS\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
   writeLine("I'm a lumberjack and I'm okay; I work all night and I sleep all day!\n", outputStream);
   writeLine("This is a totally bogus entry\t\n", outputStream);
   writeLine("0\t0\t0\t0\t\n", outputStream);
   writeLine("\t\t\t\t\t\t\t\n", outputStream);
-  writeLine("example.com\t\t\t\t\t\t\t\n", outputStream);
-  writeLine("example3.example.com\t0\t\t\t\t\t\t\n", outputStream);
-  writeLine("example2.example.com\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
+  writeLine("example.com:HSTS\t\t\t\t\t\t\t\n", outputStream);
+  writeLine("example3.example.com:HSTS\t0\t\t\t\t\t\t\n", outputStream);
+  writeLine("example2.example.com:HSTS\t0\t0\t" + (now + 100000) + ",1,0\n", outputStream);
   outputStream.close();
   Services.obs.addObserver(checkStateRead, "data-storage-ready", false);
   do_test_pending();
   gSSService = Cc["@mozilla.org/ssservice;1"]
                  .getService(Ci.nsISiteSecurityService);
   do_check_true(gSSService != null);
 }
--- a/security/manager/ssl/tests/unit/test_sss_readstate_huge.js
+++ b/security/manager/ssl/tests/unit/test_sss_readstate_huge.js
@@ -39,17 +39,17 @@ function run_test() {
   // Assuming we're working with a clean slate, the file shouldn't exist
   // until we create it.
   do_check_false(stateFile.exists());
   let outputStream = FileUtils.openFileOutputStream(stateFile);
   let now = (new Date()).getTime();
   for (let i = 0; i < 10000; i++) {
     // The 0s will all get squashed down into one 0 when they are read.
     // This is just to make the file size large (>2MB).
-    writeLine("example" + i + ".example.com\t0000000000000000000000000000000000000000000000000\t00000000000000000000000000000000000000\t" + (now + 100000) + ",1,0000000000000000000000000000000000000000000000000000000000000000000000000\n", outputStream);
+    writeLine("example" + i + ".example.com:HSTS\t0000000000000000000000000000000000000000000000000\t00000000000000000000000000000000000000\t" + (now + 100000) + ",1,0000000000000000000000000000000000000000000000000000000000000000000000000\n", outputStream);
   }
   outputStream.close();
   Services.obs.addObserver(checkStateRead, "data-storage-ready", false);
   do_test_pending();
   gSSService = Cc["@mozilla.org/ssservice;1"]
                  .getService(Ci.nsISiteSecurityService);
   do_check_true(gSSService != null);
 }
--- a/security/manager/ssl/tests/unit/test_sss_savestate.js
+++ b/security/manager/ssl/tests/unit/test_sss_savestate.js
@@ -37,44 +37,44 @@ function checkStateWritten(aSubject, aTo
   // We can receive multiple data-storage-written events. In particular, we
   // may receive one where DataStorage wrote out data before we were done
   // processing all of our headers. In this case, the data may not be
   // as we expect. We only care about the final one being correct, however,
   // so we return and wait for the next event if things aren't as we expect.
   // sites[url][1] corresponds to SecurityPropertySet (if 1) and
   //                              SecurityPropertyUnset (if 0)
   // sites[url][2] corresponds to includeSubdomains
-  if (sites["bugzilla.mozilla.org"][1] != 1) {
+  if (sites["bugzilla.mozilla.org:HSTS"][1] != 1) {
     return;
   }
-  if (sites["bugzilla.mozilla.org"][2] != 0) {
+  if (sites["bugzilla.mozilla.org:HSTS"][2] != 0) {
     return;
   }
-  if (sites["a.example.com"][1] != 1) {
+  if (sites["a.example.com:HSTS"][1] != 1) {
     return;
   }
-  if (sites["a.example.com"][2] != 1) {
+  if (sites["a.example.com:HSTS"][2] != 1) {
     return;
   }
-  if (sites["b.example.com"][1] != 1) {
+  if (sites["b.example.com:HSTS"][1] != 1) {
     return;
   }
-  if (sites["b.example.com"][2] != 0) {
+  if (sites["b.example.com:HSTS"][2] != 0) {
     return;
   }
-  if (sites["c.c.example.com"][1] != 1) {
+  if (sites["c.c.example.com:HSTS"][1] != 1) {
     return;
   }
-  if (sites["c.c.example.com"][2] != 1) {
+  if (sites["c.c.example.com:HSTS"][2] != 1) {
     return;
   }
-  if (sites["d.example.com"][1] != 1) {
+  if (sites["d.example.com:HSTS"][1] != 1) {
     return;
   }
-  if (sites["d.example.com"][2] != 0) {
+  if (sites["d.example.com:HSTS"][2] != 0) {
     return;
   }
 
   do_test_finished();
 }
 
 function run_test() {
   Services.prefs.setIntPref("test.datastorage.write_timer_ms", 100);