Bug 1312794 - Annotate OCSP requests by first party domain. (adapted from Tor Browser patch #13670) r=keeler
authorJonathan Hao <jhao@mozilla.com>
Thu, 03 Nov 2016 17:53:52 +0800
changeset 351713 8b988d56154b865c3e19786b073315971d3b6313
parent 351712 a348c799d6a62bbdc08da7cd0a9ce62cbdcf9eb1
child 351714 76b2f75e20f7794a8263f249ddc36907262c327d
push id6795
push userjlund@mozilla.com
push dateMon, 23 Jan 2017 14:19:46 +0000
treeherdermozilla-esr52@76101b503191 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs1312794, 13670
milestone52.0a1
Bug 1312794 - Annotate OCSP requests by first party domain. (adapted from Tor Browser patch #13670) r=keeler
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/OCSPRequestor.cpp
security/certverifier/OCSPRequestor.h
security/manager/ssl/nsNSSCallbacks.cpp
security/manager/ssl/nsNSSCallbacks.h
security/manager/ssl/tests/unit/test_ocsp_caching.js
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -553,17 +553,17 @@ NSSCertDBTrustDomain::CheckRevocation(En
     SECItem ocspRequestItem = {
       siBuffer,
       ocspRequest,
       static_cast<unsigned int>(ocspRequestLength)
     };
     // Owned by arena
     SECItem* responseSECItem = nullptr;
     Result tempRV =
-      DoOCSPRequest(arena, url, &ocspRequestItem,
+      DoOCSPRequest(arena, url, mFirstPartyDomain, &ocspRequestItem,
                     OCSPFetchingTypeToTimeoutTime(mOCSPFetching),
                     mOCSPGetConfig == CertVerifier::ocspGetEnabled,
                     responseSECItem);
     MOZ_ASSERT((tempRV != Success) || responseSECItem);
     if (tempRV != Success) {
       rv = tempRV;
     } else if (response.Init(responseSECItem->data, responseSECItem->len)
                  != Success) {
--- a/security/certverifier/OCSPRequestor.cpp
+++ b/security/certverifier/OCSPRequestor.cpp
@@ -69,18 +69,18 @@ AppendEscapedBase64Item(const SECItem* e
   base64Request.ReplaceSubstring("/", "%2F");
   base64Request.ReplaceSubstring("=", "%3D");
   path.Append(base64Request);
   return NS_OK;
 }
 
 Result
 DoOCSPRequest(const UniquePLArenaPool& arena, const char* url,
-              const SECItem* encodedRequest, PRIntervalTime timeout,
-              bool useGET,
+              const char* firstPartyDomain, const SECItem* encodedRequest,
+              PRIntervalTime timeout, bool useGET,
       /*out*/ SECItem*& encodedResponse)
 {
   MOZ_ASSERT(arena.get());
   MOZ_ASSERT(url);
   MOZ_ASSERT(encodedRequest);
   MOZ_ASSERT(encodedRequest->data);
   if (!arena.get() || !url || !encodedRequest || !encodedRequest->data) {
     return Result::FATAL_ERROR_INVALID_ARGS;
@@ -168,17 +168,18 @@ DoOCSPRequest(const UniquePLArenaPool& a
     nsresult nsrv = AppendEscapedBase64Item(encodedRequest, path);
     if (NS_WARN_IF(NS_FAILED(nsrv))) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
   }
 
   nsNSSHttpRequestSession* requestSessionPtr;
   rv = nsNSSHttpInterface::createFcn(serverSession.get(), "http", path.get(),
-                                     method.get(), timeout, &requestSessionPtr);
+                                     method.get(), firstPartyDomain, timeout,
+                                     &requestSessionPtr);
   if (rv != Success) {
     return rv;
   }
 
   UniqueHTTPRequestSession requestSession(requestSessionPtr);
 
   if (!useGET) {
     rv = nsNSSHttpInterface::setPostDataFcn(
--- a/security/certverifier/OCSPRequestor.h
+++ b/security/certverifier/OCSPRequestor.h
@@ -9,15 +9,16 @@
 
 #include "CertVerifier.h"
 #include "secmodt.h"
 
 namespace mozilla { namespace psm {
 
 // The memory returned via |encodedResponse| is owned by the given arena.
 Result DoOCSPRequest(const UniquePLArenaPool& arena, const char* url,
+                     const char* firstPartyDomain,
                      const SECItem* encodedRequest, PRIntervalTime timeout,
                      bool useGET,
              /*out*/ SECItem*& encodedResponse);
 
 } } // namespace mozilla::psm
 
 #endif // OCSPRequestor_h
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -109,16 +109,28 @@ nsHTTPDownloadEvent::Run()
   // high priority to accommodate real time OCSP transactions.
   nsCOMPtr<nsISupportsPriority> priorityChannel = do_QueryInterface(chan);
   if (priorityChannel)
     priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST);
 
   chan->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS |
                      nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
 
+  if (!mRequestSession->mFirstPartyDomain.IsEmpty()) {
+    NeckoOriginAttributes attrs;
+    attrs.mFirstPartyDomain =
+      NS_ConvertUTF8toUTF16(mRequestSession->mFirstPartyDomain);
+
+    nsCOMPtr<nsILoadInfo> loadInfo = chan->GetLoadInfo();
+    if (loadInfo) {
+      rv = loadInfo->SetOriginAttributes(attrs);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
   // Create a loadgroup for this new channel.  This way if the channel
   // is redirected, we'll have a way to cancel the resulting channel.
   nsCOMPtr<nsILoadGroup> lg = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
   chan->SetLoadGroup(lg);
 
   if (mRequestSession->mHasPostData)
   {
     nsCOMPtr<nsIInputStream> uploadStream;
@@ -213,16 +225,17 @@ nsNSSHttpServerSession::createSessionFcn
   return Success;
 }
 
 mozilla::pkix::Result
 nsNSSHttpRequestSession::createFcn(const nsNSSHttpServerSession* session,
                                    const char* http_protocol_variant,
                                    const char* path_and_query_string,
                                    const char* http_request_method,
+                                   const char* first_party_domain,
                                    const PRIntervalTime timeout,
                            /*out*/ nsNSSHttpRequestSession** pRequest)
 {
   if (!session || !http_protocol_variant || !path_and_query_string ||
       !http_request_method || !pRequest) {
     return Result::FATAL_ERROR_INVALID_ARGS;
   }
 
@@ -242,16 +255,18 @@ nsNSSHttpRequestSession::createFcn(const
 
   rs->mURL.Assign(http_protocol_variant);
   rs->mURL.AppendLiteral("://");
   rs->mURL.Append(session->mHost);
   rs->mURL.Append(':');
   rs->mURL.AppendInt(session->mPort);
   rs->mURL.Append(path_and_query_string);
 
+  rs->mFirstPartyDomain.Assign(first_party_domain);
+
   rs->mRequestMethod = http_request_method;
 
   *pRequest = rs;
   return Success;
 }
 
 mozilla::pkix::Result
 nsNSSHttpRequestSession::setPostDataFcn(const char* http_data,
--- a/security/manager/ssl/nsNSSCallbacks.h
+++ b/security/manager/ssl/nsNSSCallbacks.h
@@ -94,16 +94,17 @@ protected:
 
 public:
   typedef mozilla::pkix::Result Result;
 
   static Result createFcn(const nsNSSHttpServerSession* session,
                           const char* httpProtocolVariant,
                           const char* pathAndQueryString,
                           const char* httpRequestMethod,
+                          const char* firstPartyDomain,
                           const PRIntervalTime timeout,
                   /*out*/ nsNSSHttpRequestSession** pRequest);
 
   Result setPostDataFcn(const char* httpData,
                         const uint32_t httpDataLen,
                         const char* httpContentType);
 
   Result trySendAndReceiveFcn(PRPollDesc** pPollDesc,
@@ -118,16 +119,18 @@ public:
 
   nsCString mURL;
   nsCString mRequestMethod;
 
   bool mHasPostData;
   nsCString mPostData;
   nsCString mPostContentType;
 
+  nsCString mFirstPartyDomain;
+
   PRIntervalTime mTimeoutInterval;
 
   RefPtr<nsHTTPListener> mListener;
 
 protected:
   nsNSSHttpRequestSession();
   ~nsNSSHttpRequestSession();
 
@@ -151,23 +154,24 @@ public:
   {
     return nsNSSHttpServerSession::createSessionFcn(host, portnum, pSession);
   }
 
   static Result createFcn(const nsNSSHttpServerSession* session,
                           const char* httpProtocolVariant,
                           const char* pathAndQueryString,
                           const char* httpRequestMethod,
+                          const char* firstPartyDomain,
                           const PRIntervalTime timeout,
                   /*out*/ nsNSSHttpRequestSession** pRequest)
   {
     return nsNSSHttpRequestSession::createFcn(session, httpProtocolVariant,
                                               pathAndQueryString,
-                                              httpRequestMethod, timeout,
-                                              pRequest);
+                                              httpRequestMethod, firstPartyDomain,
+                                              timeout, pRequest);
   }
 
   static Result setPostDataFcn(nsNSSHttpRequestSession* request,
                                const char* httpData,
                                const uint32_t httpDataLen,
                                const char* httpContentType)
   {
     return request->setPostDataFcn(httpData, httpDataLen, httpContentType);
--- a/security/manager/ssl/tests/unit/test_ocsp_caching.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_caching.js
@@ -218,30 +218,81 @@ function add_tests() {
 
   //---------------------------------------------------------------------------
 
   // Reset state
   add_test(function() { clearOCSPCache(); run_next_test(); });
 
   // This test makes sure that OCSP cache are isolated by firstPartyDomain.
 
+  let gObservedCnt = 0;
+  let protocolProxyService = Cc["@mozilla.org/network/protocol-proxy-service;1"]
+                               .getService(Ci.nsIProtocolProxyService);
+
+  // Observe all channels and make sure the firstPartyDomain in their loadInfo's
+  // origin attributes are aFirstPartyDomain.
+  function startObservingChannels(aFirstPartyDomain) {
+    // We use a dummy proxy filter to catch all channels, even those that do not
+    // generate an "http-on-modify-request" notification.
+    let proxyFilter = {
+      applyFilter: function (aProxyService, aChannel, aProxy) {
+        // We have the channel; provide it to the callback.
+        if (aChannel.originalURI.spec == "http://localhost:8888/") {
+          gObservedCnt++;
+          equal(aChannel.loadInfo.originAttributes.firstPartyDomain,
+                aFirstPartyDomain, "firstPartyDomain should match");
+        }
+        // Pass on aProxy unmodified.
+        return aProxy;
+      }
+    };
+    protocolProxyService.registerChannelFilter(proxyFilter, 0);
+    // Return the stop() function:
+    return () => protocolProxyService.unregisterChannelFilter(proxyFilter);
+  }
+
+  let stopObservingChannels;
+  add_test(function() {
+    stopObservingChannels = startObservingChannels("foo.com");
+    run_next_test();
+  });
+
   // A good OCSP response will be cached.
   add_ocsp_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess,
                 [respondWithGoodOCSP],
                 "No stapled response (firstPartyDomain = foo.com) -> a fetch " +
                 "should have been attempted", "foo.com");
 
   // The cache will prevent a fetch from happening.
   add_ocsp_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess, [],
                 "Noted OCSP server failure (firstPartyDomain = foo.com) -> a " +
                 "fetch should not have been attempted", "foo.com");
 
+  add_test(function() {
+    stopObservingChannels();
+    equal(gObservedCnt, 1, "should have observed only 1 OCSP requests");
+    gObservedCnt = 0;
+    run_next_test();
+  });
+
+  add_test(function() {
+    stopObservingChannels = startObservingChannels("bar.com");
+    run_next_test();
+  });
+
   // But using a different firstPartyDomain should result in a fetch.
   add_ocsp_test("ocsp-stapling-none.example.com", PRErrorCodeSuccess,
                 [respondWithGoodOCSP],
                 "No stapled response (firstPartyDomain = bar.com) -> a fetch " +
                 "should have been attempted", "bar.com");
 
+  add_test(function() {
+    stopObservingChannels();
+    equal(gObservedCnt, 1, "should have observed only 1 OCSP requests");
+    gObservedCnt = 0;
+    run_next_test();
+  });
+
   //---------------------------------------------------------------------------
 
   // Reset state
   add_test(function() { clearOCSPCache(); run_next_test(); });
 }