Bug 1312794 - Annotate OCSP requests by first party domain. (adapted from Tor Browser patch #13670) r=keeler
--- 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
@@ -106,16 +106,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;
@@ -210,16 +222,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;
}
@@ -239,16 +252,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(); });
}