summaryrefslogtreecommitdiff
path: root/netwerk/protocol/http/HSTSPrimerListener.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/http/HSTSPrimerListener.cpp')
-rw-r--r--netwerk/protocol/http/HSTSPrimerListener.cpp273
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