Bug 1124649 - Part 1 - Add specific error messages for various types of STS and PKP header failures. r=keeler,hurley
authorCykesiopka <cykesiopka.bmo@gmail.com>
Wed, 05 Aug 2015 07:51:00 +0200
changeset 288152 fe549ef1f881a57b946663213b6c45724e87d353
parent 288151 1a54709ce22475d9ccbb52e21cc7729d2618c97a
child 288153 b3d049d0dbe81fd5678ea0e4014fb91e8a476d4f
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, hurley
bugs1124649
milestone42.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 1124649 - Part 1 - Add specific error messages for various types of STS and PKP header failures. r=keeler,hurley
browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js
browser/devtools/webconsole/test/browser_webconsole_show_subresource_security_errors.js
dom/locales/en-US/chrome/security/security.properties
netwerk/protocol/http/nsHttpChannel.cpp
security/manager/ssl/nsISiteSecurityService.idl
security/manager/ssl/nsSiteSecurityService.cpp
security/manager/ssl/nsSiteSecurityService.h
security/manager/ssl/tests/compiled/TestSTSParser.cpp
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js
@@ -2,18 +2,19 @@
   * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* Tests that errors about invalid HSTS security headers are logged
  *  to the web console */
 
 "use strict";
 
 const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/" +
                  "test/test-bug-846918-hsts-invalid-headers.html";
-const HSTS_INVALID_HEADER_MSG = "The site specified an invalid " +
-                                "Strict-Transport-Security header.";
+const HSTS_INVALID_HEADER_MSG = "Strict-Transport-Security: The site " +
+                                "specified a header that did not include a " +
+                                "'max-age' directive.";
 const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/" +
                        "HTTP_Strict_Transport_Security";
 
 let test = asyncTest(function* () {
   yield loadTab(TEST_URI);
 
   let hud = yield openConsole();
 
--- a/browser/devtools/webconsole/test/browser_webconsole_show_subresource_security_errors.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_show_subresource_security_errors.js
@@ -3,21 +3,21 @@
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Ensure non-toplevel security errors are displayed
 
 "use strict";
 
-const TEST_URI = "data:text/html;charset=utf8,Web Console subresource STS " +
+const TEST_URI = "data:text/html;charset=utf-8,Web Console subresource STS " +
                  "warning test";
 const TEST_DOC = "https://example.com/browser/browser/devtools/webconsole/" +
                  "test/test_bug1092055_shouldwarn.html";
-const SAMPLE_MSG = "invalid Strict-Transport-Security header";
+const SAMPLE_MSG = "specified a header that could not be parsed successfully.";
 
 let test = asyncTest(function* () {
   let { browser } = yield loadTab(TEST_URI);
 
   let hud = yield openConsole();
 
   hud.jsterm.clearOutput();
 
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -11,20 +11,42 @@ CORSMissingAllowOrigin=Cross-Origin Requ
 CORSAllowOriginNotMatchingOrigin=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header 'Access-Control-Allow-Origin' does not match '%2$S').
 CORSMethodNotFound=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: Did not find method in CORS header 'Access-Control-Allow-Methods').
 CORSMissingAllowCredentials=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: expected 'true' in CORS header 'Access-Control-Allow-Credentials').
 CORSPreflightDidNotSucceed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS preflight channel did not succeed).
 CORSInvalidAllowMethod=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token '%2$S' in CORS header 'Access-Control-Allow-Methods').
 CORSInvalidAllowHeader=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token '%2$S' in CORS header 'Access-Control-Allow-Headers').
 CORSMissingAllowHeaderFromPreflight=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: missing token '%2$S' in CORS header 'Access-Control-Allow-Headers' from CORS preflight channel).
 
-# 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.
+# LOCALIZATION NOTE: Do not translate "Strict-Transport-Security", "HSTS", "max-age" or "includeSubDomains"
+STSUnknownError=Strict-Transport-Security: An unknown error occurred processing the header specified by the site.
+STSUntrustworthyConnection=Strict-Transport-Security: The connection to the site is untrustworthy, so the specified header was ignored.
+STSCouldNotParseHeader=Strict-Transport-Security: The site specified a header that could not be parsed successfully.
+STSNoMaxAge=Strict-Transport-Security: The site specified a header that did not include a 'max-age' directive.
+STSMultipleMaxAges=Strict-Transport-Security: The site specified a header that included multiple 'max-age' directives.
+STSInvalidMaxAge=Strict-Transport-Security: The site specified a header that included an invalid 'max-age' directive.
+STSMultipleIncludeSubdomains=Strict-Transport-Security: The site specified a header that included multiple 'includeSubDomains' directives.
+STSInvalidIncludeSubdomains=Strict-Transport-Security: The site specified a header that included an invalid 'includeSubDomains' directive.
+STSCouldNotSaveState=Strict-Transport-Security: An error occurred noting the site as a Strict-Transport-Security host.
+
+# LOCALIZATION NOTE: Do not translate "Public-Key-Pins", "HPKP", "max-age" or "includeSubDomains"
+PKPUnknownError=Public-Key-Pins: An unknown error occurred processing the header specified by the site.
+PKPUntrustworthyConnection=Public-Key-Pins: The connection to the site is untrustworthy, so the specified header was ignored.
+PKPCouldNotParseHeader=Public-Key-Pins: The site specified a header that could not be parsed successfully.
+PKPNoMaxAge=Public-Key-Pins: The site specified a header that did not include a 'max-age' directive.
+PKPMultipleMaxAges=Public-Key-Pins: The site specified a header that included multiple 'max-age' directives.
+PKPInvalidMaxAge=Public-Key-Pins: The site specified a header that included an invalid 'max-age' directive.
+PKPMultipleIncludeSubdomains=Public-Key-Pins: The site specified a header that included multiple 'includeSubDomains' directives.
+PKPInvalidIncludeSubdomains=Public-Key-Pins: The site specified a header that included an invalid 'includeSubDomains' directive.
+PKPInvalidPin=Public-Key-Pins: The site specified a header that included an invalid pin.
+PKPMultipleReportURIs=Public-Key-Pins: The site specified a header that included multiple 'report-uri' directives.
+PKPPinsetDoesNotMatch=Public-Key-Pins: The site specified a header that did not include a matching pin.
+PKPNoBackupPin=Public-Key-Pins: The site specified a header that did not include a backup pin.
+PKPCouldNotSaveState=Public-Key-Pins: An error occurred noting the site as a Public-Key-Pins host.
+
 # LOCALIZATION NOTE: Do not translate "SHA-1"
 SHA1Sig=This site makes use of a SHA-1 Certificate; it's recommended you use certificates with signature algorithms that use hash functions stronger than SHA-1.
 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.
 # LOCALIZATION NOTE: "%1$S" is the URI of the insecure mixed content resource
 LoadingMixedActiveContent2=Loading mixed (insecure) active content "%1$S" on a secure page
 LoadingMixedDisplayContent2=Loading mixed (insecure) display content "%1$S" on a secure page
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1145,18 +1145,98 @@ nsHttpChannel::ProcessFailedProxyConnect
     }
     LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n",
          this, httpStatus));
     Cancel(rv);
     CallOnStartRequest();
     return rv;
 }
 
+static void
+GetSTSConsoleErrorTag(uint32_t failureResult, nsAString& consoleErrorTag)
+{
+    switch (failureResult) {
+        case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
+            consoleErrorTag = NS_LITERAL_STRING("STSUntrustworthyConnection");
+            break;
+        case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
+            consoleErrorTag = NS_LITERAL_STRING("STSCouldNotParseHeader");
+            break;
+        case nsISiteSecurityService::ERROR_NO_MAX_AGE:
+            consoleErrorTag = NS_LITERAL_STRING("STSNoMaxAge");
+            break;
+        case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
+            consoleErrorTag = NS_LITERAL_STRING("STSMultipleMaxAges");
+            break;
+        case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
+            consoleErrorTag = NS_LITERAL_STRING("STSInvalidMaxAge");
+            break;
+        case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
+            consoleErrorTag = NS_LITERAL_STRING("STSMultipleIncludeSubdomains");
+            break;
+        case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
+            consoleErrorTag = NS_LITERAL_STRING("STSInvalidIncludeSubdomains");
+            break;
+        case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
+            consoleErrorTag = NS_LITERAL_STRING("STSCouldNotSaveState");
+            break;
+        default:
+            consoleErrorTag = NS_LITERAL_STRING("STSUnknownError");
+            break;
+    }
+}
+
+static void
+GetPKPConsoleErrorTag(uint32_t failureResult, nsAString& consoleErrorTag)
+{
+    switch (failureResult) {
+        case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
+            consoleErrorTag = NS_LITERAL_STRING("PKPUntrustworthyConnection");
+            break;
+        case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
+            consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotParseHeader");
+            break;
+        case nsISiteSecurityService::ERROR_NO_MAX_AGE:
+            consoleErrorTag = NS_LITERAL_STRING("PKPNoMaxAge");
+            break;
+        case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
+            consoleErrorTag = NS_LITERAL_STRING("PKPMultipleMaxAges");
+            break;
+        case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
+            consoleErrorTag = NS_LITERAL_STRING("PKPInvalidMaxAge");
+            break;
+        case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
+            consoleErrorTag = NS_LITERAL_STRING("PKPMultipleIncludeSubdomains");
+            break;
+        case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
+            consoleErrorTag = NS_LITERAL_STRING("PKPInvalidIncludeSubdomains");
+            break;
+        case nsISiteSecurityService::ERROR_INVALID_PIN:
+            consoleErrorTag = NS_LITERAL_STRING("PKPInvalidPin");
+            break;
+        case nsISiteSecurityService::ERROR_MULTIPLE_REPORT_URIS:
+            consoleErrorTag = NS_LITERAL_STRING("PKPMultipleReportURIs");
+            break;
+        case nsISiteSecurityService::ERROR_PINSET_DOES_NOT_MATCH_CHAIN:
+            consoleErrorTag = NS_LITERAL_STRING("PKPPinsetDoesNotMatch");
+            break;
+        case nsISiteSecurityService::ERROR_NO_BACKUP_PIN:
+            consoleErrorTag = NS_LITERAL_STRING("PKPNoBackupPin");
+            break;
+        case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
+            consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotSaveState");
+            break;
+        default:
+            consoleErrorTag = NS_LITERAL_STRING("PKPUnknownError");
+            break;
+    }
+}
+
 /**
- * Process a single security header. Only two types are suported HSTS and HPKP.
+ * Process a single security header. Only two types are supported: HSTS and HPKP.
  */
 nsresult
 nsHttpChannel::ProcessSingleSecurityHeader(uint32_t aType,
                                            nsISSLStatus *aSSLStatus,
                                            uint32_t aFlags)
 {
     nsHttpAtom atom;
     switch (aType) {
@@ -1173,28 +1253,29 @@ nsHttpChannel::ProcessSingleSecurityHead
 
     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)
+        uint32_t failureResult;
         rv = sss->ProcessHeader(aType, mURI, securityHeader.get(), aSSLStatus,
-                                aFlags, nullptr, nullptr);
+                                aFlags, nullptr, nullptr, &failureResult);
         if (NS_FAILED(rv)) {
             nsAutoString consoleErrorCategory;
             nsAutoString consoleErrorTag;
             switch (aType) {
                 case nsISiteSecurityService::HEADER_HSTS:
-                    consoleErrorTag = NS_LITERAL_STRING("InvalidSTSHeaders");
+                    GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
                     consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
                     break;
                 case nsISiteSecurityService::HEADER_HPKP:
-                    consoleErrorTag = NS_LITERAL_STRING("InvalidPKPHeaders");
+                    GetPKPConsoleErrorTag(failureResult, consoleErrorTag);
                     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()));
--- a/security/manager/ssl/nsISiteSecurityService.idl
+++ b/security/manager/ssl/nsISiteSecurityService.idl
@@ -18,66 +18,85 @@ namespace mozilla
   {
     class Time;
   }
 }
 %}
 [ref] native nsCStringTArrayRef(nsTArray<nsCString>);
 [ref] native mozillaPkixTime(mozilla::pkix::Time);
 
-[scriptable, uuid(e219eace-0e04-42ba-b203-58a8b327867c)]
+[scriptable, uuid(e6cac961-9f03-4cc3-ad00-a829ae7304dc)]
 interface nsISiteSecurityService : nsISupports
 {
     const uint32_t HEADER_HSTS = 0;
     const uint32_t HEADER_HPKP = 1;
     const uint32_t HEADER_OMS = 2;
 
+    const uint32_t Success = 0;
+    const uint32_t ERROR_UNKNOWN = 1;
+    const uint32_t ERROR_UNTRUSTWORTHY_CONNECTION = 2;
+    const uint32_t ERROR_COULD_NOT_PARSE_HEADER = 3;
+    const uint32_t ERROR_NO_MAX_AGE = 4;
+    const uint32_t ERROR_MULTIPLE_MAX_AGES = 5;
+    const uint32_t ERROR_INVALID_MAX_AGE = 6;
+    const uint32_t ERROR_MULTIPLE_INCLUDE_SUBDOMAINS = 7;
+    const uint32_t ERROR_INVALID_INCLUDE_SUBDOMAINS = 8;
+    const uint32_t ERROR_INVALID_PIN = 9;
+    const uint32_t ERROR_MULTIPLE_REPORT_URIS = 10;
+    const uint32_t ERROR_PINSET_DOES_NOT_MATCH_CHAIN = 11;
+    const uint32_t ERROR_NO_BACKUP_PIN = 12;
+    const uint32_t ERROR_COULD_NOT_SAVE_STATE = 13;
+
     /**
      * Parses a given HTTP header and records the results internally.
      * 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
+     * The format of the HPKP header is defined by the HPKP specification:
+     * https://tools.ietf.org/html/rfc7469
+     * and allows a host to specify 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.
+     * @param aFailureResult a more specific failure result if NS_ERROR_FAILURE
+                             was returned.
      * @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);
+                       [optional] out boolean aIncludeSubdomains,
+                       [optional] out uint32_t aFailureResult);
 
     /**
      * 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);
+                             [optional] out boolean aIncludeSubdomains,
+                             [optional] out uint32_t aFailureResult);
 
     /**
      * 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
--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -392,48 +392,57 @@ HostIsIPAddress(const char *hostname)
 
 NS_IMETHODIMP
 nsSiteSecurityService::ProcessHeader(uint32_t aType,
                                      nsIURI* aSourceURI,
                                      const char* aHeader,
                                      nsISSLStatus* aSSLStatus,
                                      uint32_t aFlags,
                                      uint64_t* aMaxAge,
-                                     bool* aIncludeSubdomains)
+                                     bool* aIncludeSubdomains,
+                                     uint32_t* aFailureResult)
 {
+  if (aFailureResult) {
+    *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
+  }
   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);
+                               aMaxAge, aIncludeSubdomains, aFailureResult);
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::UnsafeProcessHeader(uint32_t aType,
                                            nsIURI* aSourceURI,
                                            const char* aHeader,
                                            uint32_t aFlags,
                                            uint64_t* aMaxAge,
-                                           bool* aIncludeSubdomains)
+                                           bool* aIncludeSubdomains,
+                                           uint32_t* aFailureResult)
 {
   return ProcessHeaderInternal(aType, aSourceURI, aHeader, nullptr, aFlags,
-                               aMaxAge, aIncludeSubdomains);
+                               aMaxAge, aIncludeSubdomains, aFailureResult);
 }
 
 nsresult
 nsSiteSecurityService::ProcessHeaderInternal(uint32_t aType,
                                              nsIURI* aSourceURI,
                                              const char* aHeader,
                                              nsISSLStatus* aSSLStatus,
                                              uint32_t aFlags,
                                              uint64_t* aMaxAge,
-                                             bool* aIncludeSubdomains)
+                                             bool* aIncludeSubdomains,
+                                             uint32_t* aFailureResult)
 {
+  if (aFailureResult) {
+    *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
+  }
   // 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;
   }
@@ -454,53 +463,56 @@ nsSiteSecurityService::ProcessHeaderInte
     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"));
+       if (aFailureResult) {
+         *aFailureResult = nsISiteSecurityService::ERROR_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;
   }
 
   switch (aType) {
     case nsISiteSecurityService::HEADER_HSTS:
       rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aMaxAge,
-                            aIncludeSubdomains);
+                            aIncludeSubdomains, aFailureResult);
       break;
     case nsISiteSecurityService::HEADER_HPKP:
       rv = ProcessPKPHeader(aSourceURI, aHeader, aSSLStatus, aFlags, aMaxAge,
-                            aIncludeSubdomains);
+                            aIncludeSubdomains, aFailureResult);
       break;
     default:
       MOZ_CRASH("unexpected header type");
   }
   return rv;
 }
 
-static nsresult
+static uint32_t
 ParseSSSHeaders(uint32_t aType,
                 const char* aHeader,
                 bool& foundIncludeSubdomains,
                 bool& foundMaxAge,
                 bool& foundUnrecognizedDirective,
                 int64_t& maxAge,
                 nsTArray<nsCString>& sha256keys)
 {
-  // Stric transport security and Public Key Pinning have very similar
+  // Strict 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
   //
@@ -542,131 +554,143 @@ ParseSSSHeaders(uint32_t aType,
   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;
+    return nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER;
   }
   mozilla::LinkedList<nsSecurityHeaderDirective>* directives = parser.GetDirectives();
 
   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;
+        return nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES;
       }
 
       SSSLOG(("SSS: found max-age directive"));
       foundMaxAge = true;
 
       size_t len = directive->mValue.Length();
       for (size_t i = 0; i < len; i++) {
         char chr = directive->mValue.CharAt(i);
         if (chr < '0' || chr > '9') {
           SSSLOG(("SSS: invalid value for max-age directive"));
-          return NS_ERROR_FAILURE;
+          return nsISiteSecurityService::ERROR_INVALID_MAX_AGE;
         }
       }
 
       if (PR_sscanf(directive->mValue.get(), "%lld", &maxAge) != 1) {
         SSSLOG(("SSS: could not parse delta-seconds"));
-        return NS_ERROR_FAILURE;
+        return nsISiteSecurityService::ERROR_INVALID_MAX_AGE;
       }
 
       SSSLOG(("SSS: parsed delta-seconds: %lld", maxAge));
     } else if (directive->mName.Length() == include_subd_var.Length() &&
                directive->mName.EqualsIgnoreCase(include_subd_var.get(),
                                                  include_subd_var.Length())) {
       if (foundIncludeSubdomains) {
         SSSLOG(("SSS: found two includeSubdomains directives"));
-        return NS_ERROR_FAILURE;
+        return nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS;
       }
 
       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;
+        return nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS;
       }
     } 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;
+         return nsISiteSecurityService::ERROR_INVALID_PIN;
        }
        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
+       // We don'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;
+        return nsISiteSecurityService::ERROR_MULTIPLE_REPORT_URIS;
       }
       SSSLOG(("SSS: found report-uri directive"));
       foundReportURI = true;
    } else {
       SSSLOG(("SSS: ignoring unrecognized directive '%s'",
               directive->mName.get()));
       foundUnrecognizedDirective = true;
     }
   }
-  return NS_OK;
+  return nsISiteSecurityService::Success;
 }
 
 nsresult
 nsSiteSecurityService::ProcessPKPHeader(nsIURI* aSourceURI,
                                         const char* aHeader,
                                         nsISSLStatus* aSSLStatus,
                                         uint32_t aFlags,
                                         uint64_t* aMaxAge,
-                                        bool* aIncludeSubdomains)
+                                        bool* aIncludeSubdomains,
+                                        uint32_t* aFailureResult)
 {
+  if (aFailureResult) {
+    *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
+  }
   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);
+  uint32_t sssrv = ParseSSSHeaders(aType, aHeader, foundIncludeSubdomains,
+                                   foundMaxAge, foundUnrecognizedDirective,
+                                   maxAge, sha256keys);
+  if (sssrv != nsISiteSecurityService::Success) {
+    if (aFailureResult) {
+      *aFailureResult = sssrv;
+    }
+    return NS_ERROR_FAILURE;
+  }
 
   // after processing all the directives, make sure we came across max-age
   // somewhere.
   if (!foundMaxAge) {
     SSSLOG(("SSS: did not encounter required max-age directive"));
+    if (aFailureResult) {
+      *aFailureResult = nsISiteSecurityService::ERROR_NO_MAX_AGE;
+    }
     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);
+  nsresult 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);
 
@@ -706,21 +730,24 @@ nsSiteSecurityService::ProcessPKPHeader(
   rv = PublicKeyPinningService::ChainMatchesPinset(certList, sha256keys,
                                                    chainMatchesPinset);
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (!chainMatchesPinset) {
     // is invalid
     SSSLOG(("SSS: Pins provided by %s are invalid no match with certList\n", host.get()));
+    if (aFailureResult) {
+      *aFailureResult = nsISiteSecurityService::ERROR_PINSET_DOES_NOT_MATCH_CHAIN;
+    }
     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
+  // at least one fingerprint hash that does NOT validate 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]);
     rv = PublicKeyPinningService::ChainMatchesPinset(certList, singlePin,
                                                      chainMatchesPinset);
     if (NS_FAILED(rv)) {
@@ -728,27 +755,36 @@ nsSiteSecurityService::ProcessPKPHeader(
     }
     if (!chainMatchesPinset) {
       hasBackupPin = true;
     }
   }
   if (!hasBackupPin) {
      // is invalid
     SSSLOG(("SSS: Pins provided by %s are invalid no backupPin\n", host.get()));
+    if (aFailureResult) {
+      *aFailureResult = nsISiteSecurityService::ERROR_NO_BACKUP_PIN;
+    }
     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 (NS_FAILED(rv)) {
+    SSSLOG(("SSS: failed to set pins for %s\n", host.get()));
+    if (aFailureResult) {
+      *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
+    }
+    return rv;
+  }
 
   if (aMaxAge != nullptr) {
     *aMaxAge = (uint64_t)maxAge;
   }
 
   if (aIncludeSubdomains != nullptr) {
     *aIncludeSubdomains = foundIncludeSubdomains;
   }
@@ -758,41 +794,61 @@ nsSiteSecurityService::ProcessPKPHeader(
            : NS_OK;
 }
 
 nsresult
 nsSiteSecurityService::ProcessSTSHeader(nsIURI* aSourceURI,
                                         const char* aHeader,
                                         uint32_t aFlags,
                                         uint64_t* aMaxAge,
-                                        bool* aIncludeSubdomains)
+                                        bool* aIncludeSubdomains,
+                                        uint32_t* aFailureResult)
 {
+  if (aFailureResult) {
+    *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
+  }
   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
+  nsTArray<nsCString> unusedSHA256keys; // Required for sane internal interface
 
-  nsresult rv = ParseSSSHeaders(aType, aHeader, foundIncludeSubdomains,
-                                foundMaxAge, foundUnrecognizedDirective,
-                                maxAge, unusedSHA256keys);
-  NS_ENSURE_SUCCESS(rv, rv);
+  uint32_t sssrv = ParseSSSHeaders(aType, aHeader, foundIncludeSubdomains,
+                                   foundMaxAge, foundUnrecognizedDirective,
+                                   maxAge, unusedSHA256keys);
+  if (sssrv != nsISiteSecurityService::Success) {
+    if (aFailureResult) {
+      *aFailureResult = sssrv;
+    }
+    return NS_ERROR_FAILURE;
+  }
 
   // after processing all the directives, make sure we came across max-age
   // somewhere.
   if (!foundMaxAge) {
     SSSLOG(("SSS: did not encounter required max-age directive"));
+    if (aFailureResult) {
+      *aFailureResult = nsISiteSecurityService::ERROR_NO_MAX_AGE;
+    }
     return NS_ERROR_FAILURE;
   }
 
   // record the successfully parsed header data.
-  SetHSTSState(aType, aSourceURI, maxAge, foundIncludeSubdomains, aFlags);
+  nsresult rv = SetHSTSState(aType, aSourceURI, maxAge, foundIncludeSubdomains,
+                             aFlags);
+  if (NS_FAILED(rv)) {
+    SSSLOG(("SSS: failed to set STS state"));
+    if (aFailureResult) {
+      *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
+    }
+    return rv;
+  }
 
   if (aMaxAge != nullptr) {
     *aMaxAge = (uint64_t)maxAge;
   }
 
   if (aIncludeSubdomains != nullptr) {
     *aIncludeSubdomains = foundIncludeSubdomains;
   }
--- a/security/manager/ssl/nsSiteSecurityService.h
+++ b/security/manager/ssl/nsSiteSecurityService.h
@@ -127,23 +127,25 @@ protected:
 
 private:
   nsresult GetHost(nsIURI *aURI, nsACString &aResult);
   nsresult SetHSTSState(uint32_t aType, nsIURI* aSourceURI, int64_t maxage,
                         bool includeSubdomains, uint32_t flags);
   nsresult ProcessHeaderInternal(uint32_t aType, nsIURI* aSourceURI,
                                  const char* aHeader, nsISSLStatus* aSSLStatus,
                                  uint32_t aFlags, uint64_t* aMaxAge,
-                                 bool* aIncludeSubdomains);
+                                 bool* aIncludeSubdomains,
+                                 uint32_t* aFailureResult);
   nsresult ProcessSTSHeader(nsIURI* aSourceURI, const char* aHeader,
                             uint32_t flags, uint64_t* aMaxAge,
-                            bool* aIncludeSubdomains);
+                            bool* aIncludeSubdomains, uint32_t* aFailureResult);
   nsresult ProcessPKPHeader(nsIURI* aSourceURI, const char* aHeader,
                             nsISSLStatus* aSSLStatus, uint32_t flags,
-                            uint64_t* aMaxAge, bool* aIncludeSubdomains);
+                            uint64_t* aMaxAge, bool* aIncludeSubdomains,
+                            uint32_t* aFailureResult);
   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;
--- a/security/manager/ssl/tests/compiled/TestSTSParser.cpp
+++ b/security/manager/ssl/tests/compiled/TestSTSParser.cpp
@@ -39,17 +39,17 @@ TestSuccess(const char* hdr, bool extraT
 {
   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->UnsafeProcessHeader(nsISiteSecurityService::HEADER_HSTS, dummyUri,
-                                hdr, 0, &maxAge, &includeSubdomains);
+                                hdr, 0, &maxAge, &includeSubdomains, nullptr);
   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.");
@@ -64,17 +64,17 @@ 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->UnsafeProcessHeader(nsISiteSecurityService::HEADER_HSTS, dummyUri,
-                                hdr, 0, nullptr, nullptr);
+                                hdr, 0, nullptr, nullptr, nullptr);
   EXPECT_FAILURE(rv, "Parsed invalid header: %s", hdr);
   passed(hdr);
   return true;
 }
 
 
 int
 main(int32_t argc, char *argv[])