diff options
Diffstat (limited to 'netwerk/protocol/http/HSTSPrimerListener.cpp')
-rw-r--r-- | netwerk/protocol/http/HSTSPrimerListener.cpp | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/netwerk/protocol/http/HSTSPrimerListener.cpp b/netwerk/protocol/http/HSTSPrimerListener.cpp new file mode 100644 index 0000000000..8c9d28d363 --- /dev/null +++ b/netwerk/protocol/http/HSTSPrimerListener.cpp @@ -0,0 +1,273 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsHttp.h" + +#include "HSTSPrimerListener.h" +#include "nsIHstsPrimingCallback.h" +#include "nsIPrincipal.h" +#include "nsSecurityHeaderParser.h" +#include "nsISiteSecurityService.h" +#include "nsISocketProvider.h" +#include "nsISSLStatus.h" +#include "nsISSLStatusProvider.h" +#include "nsStreamUtils.h" +#include "nsHttpChannel.h" +#include "LoadInfo.h" + +namespace mozilla { +namespace net { + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(HSTSPrimingListener, nsIStreamListener, + nsIRequestObserver, nsIInterfaceRequestor) + +NS_IMETHODIMP +HSTSPrimingListener::GetInterface(const nsIID & aIID, void **aResult) +{ + return QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +HSTSPrimingListener::OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) +{ + nsresult primingResult = CheckHSTSPrimingRequestStatus(aRequest); + nsCOMPtr<nsIHstsPrimingCallback> callback(mCallback); + mCallback = nullptr; + + nsCOMPtr<nsITimedChannel> timingChannel = + do_QueryInterface(callback); + if (timingChannel) { + TimeStamp channelCreationTime; + nsresult rv = timingChannel->GetChannelCreation(&channelCreationTime); + if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) { + PRUint32 interval = + (PRUint32) (TimeStamp::Now() - channelCreationTime).ToMilliseconds(); + Telemetry::Accumulate(Telemetry::HSTS_PRIMING_REQUEST_DURATION, + (NS_SUCCEEDED(primingResult)) ? NS_LITERAL_CSTRING("success") + : NS_LITERAL_CSTRING("failure"), + interval); + } + } + + if (NS_FAILED(primingResult)) { + LOG(("HSTS Priming Failed (request was not approved)")); + return callback->OnHSTSPrimingFailed(primingResult, false); + } + + LOG(("HSTS Priming Succeeded (request was approved)")); + return callback->OnHSTSPrimingSucceeded(false); +} + +NS_IMETHODIMP +HSTSPrimingListener::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatus) +{ + return NS_OK; +} + +nsresult +HSTSPrimingListener::CheckHSTSPrimingRequestStatus(nsIRequest* aRequest) +{ + nsresult status; + nsresult rv = aRequest->GetStatus(&status); + NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(status)) { + return NS_ERROR_CONTENT_BLOCKED; + } + + // Test that things worked on a HTTP level + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); + NS_ENSURE_STATE(httpChannel); + nsCOMPtr<nsIHttpChannelInternal> internal = do_QueryInterface(aRequest); + NS_ENSURE_STATE(internal); + + bool succeedded; + rv = httpChannel->GetRequestSucceeded(&succeedded); + if (NS_FAILED(rv) || !succeedded) { + // If the request did not return a 2XX response, don't process it + return NS_ERROR_CONTENT_BLOCKED; + } + + bool synthesized = false; + nsHttpChannel* rawHttpChannel = static_cast<nsHttpChannel*>(httpChannel.get()); + rv = rawHttpChannel->GetResponseSynthesized(&synthesized); + NS_ENSURE_SUCCESS(rv, rv); + if (synthesized) { + // Don't consider synthesized responses + return NS_ERROR_CONTENT_BLOCKED; + } + + // check to see if the HSTS cache was updated + nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> uri; + rv = httpChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(uri, NS_ERROR_CONTENT_BLOCKED); + + bool hsts; + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0, nullptr, &hsts); + NS_ENSURE_SUCCESS(rv, rv); + + if (hsts) { + // An HSTS upgrade was found + return NS_OK; + } + + // There is no HSTS upgrade available + return NS_ERROR_CONTENT_BLOCKED; +} + +/** nsIStreamListener methods **/ + +NS_IMETHODIMP +HSTSPrimingListener::OnDataAvailable(nsIRequest *aRequest, + nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, + uint32_t count) +{ + uint32_t totalRead; + return inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &totalRead); +} + +// static +nsresult +HSTSPrimingListener::StartHSTSPriming(nsIChannel* aRequestChannel, + nsIHstsPrimingCallback* aCallback) +{ + + nsCOMPtr<nsIURI> finalChannelURI; + nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(finalChannelURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> uri; + rv = NS_GetSecureUpgradedURI(finalChannelURI, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv,rv); + + // check the HSTS cache + bool hsts; + bool cached; + nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0, &cached, &hsts); + NS_ENSURE_SUCCESS(rv, rv); + + if (hsts) { + // already saw this host and will upgrade if allowed by preferences + return aCallback->OnHSTSPrimingSucceeded(true); + } + + if (cached) { + // there is a non-expired entry in the cache that doesn't allow us to + // upgrade, so go ahead and fail early. + return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true); + } + + // Either it wasn't cached or the cached result has expired. Build a + // channel for the HEAD request. + + nsCOMPtr<nsILoadInfo> originalLoadInfo = aRequestChannel->GetLoadInfo(); + MOZ_ASSERT(originalLoadInfo, "can not perform HSTS priming without a loadInfo"); + if (!originalLoadInfo) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsILoadInfo> loadInfo = static_cast<mozilla::LoadInfo*> + (originalLoadInfo.get())->CloneForNewRequest(); + + // the LoadInfo must have a security flag set in order to pass through priming + // if none of these security flags are set, go ahead and fail now instead of + // crashing in nsContentSecurityManager::ValidateSecurityFlags + nsSecurityFlags securityMode = loadInfo->GetSecurityMode(); + if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS && + securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED && + securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS && + securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL && + securityMode != nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS) { + return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true); + } + + nsCOMPtr<nsILoadGroup> loadGroup; + rv = aRequestChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + NS_ENSURE_SUCCESS(rv, rv); + + nsLoadFlags loadFlags; + rv = aRequestChannel->GetLoadFlags(&loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + + loadFlags &= HttpBaseChannel::INHIBIT_CACHING | + HttpBaseChannel::INHIBIT_PERSISTENT_CACHING | + HttpBaseChannel::LOAD_BYPASS_CACHE | + HttpBaseChannel::LOAD_FROM_CACHE | + HttpBaseChannel::VALIDATE_ALWAYS; + // Priming requests should never be intercepted by service workers and + // are always anonymous. + loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER | + nsIRequest::LOAD_ANONYMOUS; + + // Create a new channel to send the priming request + nsCOMPtr<nsIChannel> primingChannel; + rv = NS_NewChannelInternal(getter_AddRefs(primingChannel), + uri, + loadInfo, + loadGroup, + nullptr, // aCallbacks are set later + loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + + // Set method and headers + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(primingChannel); + if (!httpChannel) { + NS_ERROR("HSTSPrimingListener: Failed to QI to nsIHttpChannel!"); + return NS_ERROR_FAILURE; + } + + // Currently using HEAD per the draft, but under discussion to change to GET + // with credentials so if the upgrade is approved the result is already cached. + rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("HEAD")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = httpChannel-> + SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"), + NS_LITERAL_CSTRING("1"), false); + NS_ENSURE_SUCCESS(rv, rv); + + // attempt to set the class of service flags on the new channel + nsCOMPtr<nsIClassOfService> requestClass = do_QueryInterface(aRequestChannel); + if (!requestClass) { + NS_ERROR("HSTSPrimingListener: aRequestChannel is not an nsIClassOfService"); + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIClassOfService> primingClass = do_QueryInterface(httpChannel); + if (!primingClass) { + NS_ERROR("HSTSPrimingListener: aRequestChannel is not an nsIClassOfService"); + return NS_ERROR_FAILURE; + } + + uint32_t classFlags = 0; + rv = requestClass ->GetClassFlags(&classFlags); + NS_ENSURE_SUCCESS(rv, rv); + rv = primingClass->SetClassFlags(classFlags); + NS_ENSURE_SUCCESS(rv, rv); + + // Set up listener which will start the original channel + nsCOMPtr<nsIStreamListener> primingListener(new HSTSPrimingListener(aCallback)); + + // Start priming + rv = primingChannel->AsyncOpen2(primingListener); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +} // namespace net +} // namespace mozilla |