/* -*- 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 callback(mCallback); mCallback = nullptr; nsCOMPtr 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 httpChannel = do_QueryInterface(aRequest); NS_ENSURE_STATE(httpChannel); nsCOMPtr 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(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 sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 finalChannelURI; nsresult rv = NS_GetFinalChannelURI(aRequestChannel, getter_AddRefs(finalChannelURI)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr uri; rv = NS_GetSecureUpgradedURI(finalChannelURI, getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv,rv); // check the HSTS cache bool hsts; bool cached; nsCOMPtr 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 originalLoadInfo = aRequestChannel->GetLoadInfo(); MOZ_ASSERT(originalLoadInfo, "can not perform HSTS priming without a loadInfo"); if (!originalLoadInfo) { return NS_ERROR_FAILURE; } nsCOMPtr loadInfo = static_cast (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 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 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 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 requestClass = do_QueryInterface(aRequestChannel); if (!requestClass) { NS_ERROR("HSTSPrimingListener: aRequestChannel is not an nsIClassOfService"); return NS_ERROR_FAILURE; } nsCOMPtr 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 primingListener(new HSTSPrimingListener(aCallback)); // Start priming rv = primingChannel->AsyncOpen2(primingListener); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } } // namespace net } // namespace mozilla