1254 lines
46 KiB
C++
1254 lines
46 KiB
C++
/* -*- 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 "mozilla/Components.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/glean/DomSecurityMetrics.h"
|
|
#include "mozilla/NullPrincipal.h"
|
|
#include "mozilla/OriginAttributes.h"
|
|
#include "mozilla/StaticPrefs_dom.h"
|
|
#include "mozilla/net/DNS.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsDNSPrefetch.h"
|
|
#include "nsHTTPSOnlyUtils.h"
|
|
#include "nsIEffectiveTLDService.h"
|
|
#include "nsIHttpChannel.h"
|
|
#include "nsIHttpChannelInternal.h"
|
|
#include "nsIHttpsOnlyModePermission.h"
|
|
#include "nsILoadInfo.h"
|
|
#include "nsIPermissionManager.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIRedirectHistoryEntry.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIURIMutator.h"
|
|
#include "nsNetUtil.h"
|
|
#include "prnetdb.h"
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(bool aFromPrivateWindow) {
|
|
// if the general pref is set to true, then we always return
|
|
if (mozilla::StaticPrefs::dom_security_https_only_mode()) {
|
|
return true;
|
|
}
|
|
|
|
// otherwise we check if executing in private browsing mode and return true
|
|
// if the PBM pref for HTTPS-Only is set.
|
|
if (aFromPrivateWindow &&
|
|
mozilla::StaticPrefs::dom_security_https_only_mode_pbm()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(bool aFromPrivateWindow) {
|
|
// HTTPS-Only takes priority over HTTPS-First
|
|
if (IsHttpsOnlyModeEnabled(aFromPrivateWindow)) {
|
|
return false;
|
|
}
|
|
|
|
// if the general pref is set to true, then we always return
|
|
if (mozilla::StaticPrefs::dom_security_https_first()) {
|
|
return true;
|
|
}
|
|
|
|
// otherwise we check if executing in private browsing mode and return true
|
|
// if the PBM pref for HTTPS-First is set.
|
|
if (aFromPrivateWindow &&
|
|
mozilla::StaticPrefs::dom_security_https_first_pbm()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* static */
|
|
void nsHTTPSOnlyUtils::PotentiallyFireHttpRequestToShortenTimout(
|
|
mozilla::net::DocumentLoadListener* aDocumentLoadListener) {
|
|
// only send http background request to counter timeouts if the
|
|
// pref allows us to do that.
|
|
if (!mozilla::StaticPrefs::
|
|
dom_security_https_only_mode_send_http_background_request()) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIChannel> channel = aDocumentLoadListener->GetChannel();
|
|
if (!channel) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
|
|
bool isPrivateWin = loadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
|
|
// if neither HTTPS-Only nor HTTPS-First mode is enabled, then there is
|
|
// nothing to do here.
|
|
if ((!IsHttpsOnlyModeEnabled(isPrivateWin) &&
|
|
!IsHttpsFirstModeEnabled(isPrivateWin)) &&
|
|
!(loadInfo->GetSchemelessInput() ==
|
|
nsILoadInfo::SchemelessInputTypeSchemeless &&
|
|
mozilla::StaticPrefs::dom_security_https_first_schemeless())) {
|
|
return;
|
|
}
|
|
|
|
// if we are not dealing with a top-level load, then there is nothing to do
|
|
// here.
|
|
if (loadInfo->GetExternalContentPolicyType() !=
|
|
ExtContentPolicy::TYPE_DOCUMENT) {
|
|
return;
|
|
}
|
|
|
|
// if the load is exempt, then there is nothing to do here.
|
|
uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
|
|
if (httpsOnlyStatus & nsILoadInfo::nsILoadInfo::HTTPS_ONLY_EXEMPT) {
|
|
return;
|
|
}
|
|
|
|
// if it's not an http channel, then there is nothing to do here.
|
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
|
|
if (!httpChannel) {
|
|
return;
|
|
}
|
|
|
|
// if it's not a GET method, then there is nothing to do here either.
|
|
nsAutoCString method;
|
|
mozilla::Unused << httpChannel->GetRequestMethod(method);
|
|
if (!method.EqualsLiteral("GET")) {
|
|
return;
|
|
}
|
|
|
|
// if it's already an https channel, then there is nothing to do here.
|
|
nsCOMPtr<nsIURI> channelURI;
|
|
channel->GetURI(getter_AddRefs(channelURI));
|
|
if (!channelURI->SchemeIs("http")) {
|
|
return;
|
|
}
|
|
|
|
// Upgrades for custom ports may be disabled in that case
|
|
// HTTPS-First only applies to standard ports but HTTPS-Only brute forces
|
|
// all http connections to be https and overrules HTTPS-First. In case
|
|
// HTTPS-First is enabled, but HTTPS-Only is not enabled, we might return
|
|
// early if attempting to send a background request to a non standard port.
|
|
if (!mozilla::StaticPrefs::dom_security_https_first_for_custom_ports() &&
|
|
(IsHttpsFirstModeEnabled(isPrivateWin) ||
|
|
(loadInfo->GetSchemelessInput() ==
|
|
nsILoadInfo::SchemelessInputTypeSchemeless &&
|
|
mozilla::StaticPrefs::dom_security_https_first_schemeless()))) {
|
|
int32_t port = 0;
|
|
nsresult rv = channelURI->GetPort(&port);
|
|
int defaultPortforScheme = NS_GetDefaultPort("http");
|
|
if (NS_SUCCEEDED(rv) && port != defaultPortforScheme && port != -1) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for general exceptions
|
|
if (OnionException(channelURI) || LoopbackOrLocalException(channelURI)) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<nsIRunnable> task =
|
|
new TestHTTPAnswerRunnable(channelURI, aDocumentLoadListener);
|
|
NS_DispatchToMainThread(task.forget());
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::ShouldUpgradeRequest(nsIURI* aURI,
|
|
nsILoadInfo* aLoadInfo) {
|
|
// 1. Check if the HTTPS-Only Mode is even enabled, before we do anything else
|
|
bool isPrivateWin = aLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
if (!IsHttpsOnlyModeEnabled(isPrivateWin)) {
|
|
return false;
|
|
}
|
|
|
|
// 2. Check for general exceptions
|
|
if (OnionException(aURI) || LoopbackOrLocalException(aURI)) {
|
|
return false;
|
|
}
|
|
|
|
// 3. Check if NoUpgrade-flag is set in LoadInfo
|
|
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
|
|
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
|
|
AutoTArray<nsString, 1> params = {
|
|
NS_ConvertUTF8toUTF16(aURI->GetSpecOrDefault())};
|
|
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyNoUpgradeException", params,
|
|
nsIScriptError::infoFlag, aLoadInfo,
|
|
aURI);
|
|
return false;
|
|
}
|
|
|
|
// All subresources of an exempt triggering principal are also exempt
|
|
ExtContentPolicyType contentType = aLoadInfo->GetExternalContentPolicyType();
|
|
if (contentType != ExtContentPolicy::TYPE_DOCUMENT) {
|
|
if (!aLoadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
|
|
TestIfPrincipalIsExempt(aLoadInfo->TriggeringPrincipal())) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// We can not upgrade "Save-As" downloads, since we have no way of detecting
|
|
// if the upgrade failed (Bug 1674859). For now we will just allow the
|
|
// download, since there will still be a visual warning about the download
|
|
// being insecure.
|
|
if (contentType == ExtContentPolicyType::TYPE_SAVEAS_DOWNLOAD) {
|
|
return false;
|
|
}
|
|
|
|
// We can upgrade the request - let's log it to the console
|
|
// Appending an 's' to the scheme for the logging. (http -> https)
|
|
nsAutoCString scheme;
|
|
aURI->GetScheme(scheme);
|
|
scheme.AppendLiteral("s");
|
|
NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
|
|
NS_ConvertUTF8toUTF16 reportScheme(scheme);
|
|
|
|
bool isSpeculative = aLoadInfo->GetExternalContentPolicyType() ==
|
|
ExtContentPolicy::TYPE_SPECULATIVE;
|
|
AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
|
|
nsHTTPSOnlyUtils::LogLocalizedString(
|
|
isSpeculative ? "HTTPSOnlyUpgradeSpeculativeConnection"
|
|
: "HTTPSOnlyUpgradeRequest",
|
|
params, nsIScriptError::warningFlag, aLoadInfo, aURI);
|
|
|
|
// If the status was not determined before, we now indicate that the request
|
|
// will get upgraded, but no event-listener has been registered yet.
|
|
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UNINITIALIZED) {
|
|
httpsOnlyStatus ^= nsILoadInfo::HTTPS_ONLY_UNINITIALIZED;
|
|
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED;
|
|
aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(nsIURI* aURI,
|
|
nsILoadInfo* aLoadInfo) {
|
|
// 1. Check if the HTTPS-Only Mode is even enabled, before we do anything else
|
|
bool isPrivateWin = aLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
if (!IsHttpsOnlyModeEnabled(isPrivateWin)) {
|
|
return false;
|
|
}
|
|
|
|
// 2. Check for general exceptions
|
|
if (OnionException(aURI) || LoopbackOrLocalException(aURI)) {
|
|
return false;
|
|
}
|
|
|
|
// 3. Check if NoUpgrade-flag is set in LoadInfo
|
|
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
|
|
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
|
|
// Let's log to the console, that we didn't upgrade this request
|
|
AutoTArray<nsString, 1> params = {
|
|
NS_ConvertUTF8toUTF16(aURI->GetSpecOrDefault())};
|
|
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyNoUpgradeException", params,
|
|
nsIScriptError::infoFlag, aLoadInfo,
|
|
aURI);
|
|
return false;
|
|
}
|
|
|
|
// All subresources of an exempt triggering principal are also exempt.
|
|
if (!aLoadInfo->TriggeringPrincipal()->IsSystemPrincipal() &&
|
|
TestIfPrincipalIsExempt(aLoadInfo->TriggeringPrincipal())) {
|
|
return false;
|
|
}
|
|
|
|
// We can upgrade the request - let's log it to the console
|
|
// Appending an 's' to the scheme for the logging. (ws -> wss)
|
|
nsAutoCString scheme;
|
|
aURI->GetScheme(scheme);
|
|
scheme.AppendLiteral("s");
|
|
NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
|
|
NS_ConvertUTF8toUTF16 reportScheme(scheme);
|
|
|
|
AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
|
|
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyUpgradeRequest", params,
|
|
nsIScriptError::warningFlag, aLoadInfo,
|
|
aURI);
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
|
|
nsIURI* aOldURI, nsIURI* aNewURI, nsILoadInfo* aLoadInfo,
|
|
const mozilla::EnumSet<UpgradeDowngradeEndlessLoopOptions>& aOptions) {
|
|
// 1. Check if the HTTPS-Only/HTTPS-First is even enabled, before doing
|
|
// anything else
|
|
bool isPrivateWin = aLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
bool enforceForHTTPSOnlyMode =
|
|
IsHttpsOnlyModeEnabled(isPrivateWin) &&
|
|
aOptions.contains(
|
|
UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSOnlyMode);
|
|
bool enforceForHTTPSFirstMode =
|
|
IsHttpsFirstModeEnabled(isPrivateWin) &&
|
|
aOptions.contains(
|
|
UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSFirstMode);
|
|
bool enforceForHTTPSRR =
|
|
aOptions.contains(UpgradeDowngradeEndlessLoopOptions::EnforceForHTTPSRR);
|
|
if (!enforceForHTTPSOnlyMode && !enforceForHTTPSFirstMode &&
|
|
!enforceForHTTPSRR) {
|
|
return false;
|
|
}
|
|
|
|
// 2. Check if the upgrade downgrade pref even wants us to try to break the
|
|
// cycle. In the case that HTTPS RR is presented, we ignore this pref.
|
|
if (!mozilla::StaticPrefs::
|
|
dom_security_https_only_mode_break_upgrade_downgrade_endless_loop() &&
|
|
!enforceForHTTPSRR) {
|
|
return false;
|
|
}
|
|
|
|
// 3. If it's not a top-level load, then there is nothing to do here either.
|
|
if (aLoadInfo->GetExternalContentPolicyType() !=
|
|
ExtContentPolicy::TYPE_DOCUMENT) {
|
|
return false;
|
|
}
|
|
|
|
// 4. If the load is exempt, then it's defintely not related to https-only
|
|
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
|
|
if ((httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) &&
|
|
!enforceForHTTPSRR) {
|
|
return false;
|
|
}
|
|
|
|
// 5. Check HTTP 3xx redirects. If the Principal that kicked off the
|
|
// load/redirect is not https, then it's definitely not a redirect cause by
|
|
// https-only. If the scheme of the principal however is https and the
|
|
// asciiHost of the URI to be loaded and the asciiHost of the Principal are
|
|
// identical, then we are dealing with an upgrade downgrade scenario and we
|
|
// have to break the cycle.
|
|
if (IsHttpDowngrade(aOldURI, aNewURI)) {
|
|
return true;
|
|
}
|
|
|
|
// TODO(Bug 1896691): Don't depend on triggeringPrincipal for JS/Meta
|
|
// redirects. Call this function at the correct places instead
|
|
|
|
// 6. Bug 1725026: Disable JS/Meta loop detection when the load was triggered
|
|
// by a user gesture.
|
|
if (aLoadInfo->GetHasValidUserGestureActivation()) {
|
|
return false;
|
|
}
|
|
|
|
// 7. Meta redirects and JS based redirects (win.location). We detect them
|
|
// during the https upgrade internal redirect.
|
|
nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
|
|
if (!triggeringPrincipal->SchemeIs("https")) {
|
|
return false;
|
|
}
|
|
|
|
// We detect Meta and JS based redirects during the upgrade. Check whether
|
|
// we are currently in an upgrade situation here.
|
|
if (!IsHttpDowngrade(aNewURI, aOldURI)) {
|
|
return false;
|
|
}
|
|
// If we upgrade to the same URI that the load is origining from we are
|
|
// creating a redirect loop.
|
|
bool isLoop = false;
|
|
nsresult rv = triggeringPrincipal->EqualsURI(aNewURI, &isLoop);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
return isLoop;
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
|
|
nsILoadInfo* aLoadInfo) {
|
|
MOZ_ASSERT(aURI->SchemeIs("http"), "how come the request is not 'http'?");
|
|
|
|
// 1. Check if HTTPS-First Mode is enabled
|
|
bool isPrivateWin = aLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
if (!IsHttpsFirstModeEnabled(isPrivateWin) &&
|
|
!(aLoadInfo->GetSchemelessInput() ==
|
|
nsILoadInfo::SchemelessInputTypeSchemeless &&
|
|
mozilla::StaticPrefs::dom_security_https_first_schemeless())) {
|
|
return false;
|
|
}
|
|
// 2. HTTPS-First only upgrades top-level loads (and speculative connections)
|
|
ExtContentPolicyType contentType = aLoadInfo->GetExternalContentPolicyType();
|
|
if (contentType != ExtContentPolicy::TYPE_DOCUMENT &&
|
|
contentType != ExtContentPolicy::TYPE_SPECULATIVE) {
|
|
return false;
|
|
}
|
|
|
|
// 3. Check for general exceptions
|
|
if (OnionException(aURI) ||
|
|
(!mozilla::StaticPrefs::dom_security_https_first_for_local_addresses() &&
|
|
LoopbackOrLocalException(aURI)) ||
|
|
(!mozilla::StaticPrefs::dom_security_https_first_for_unknown_suffixes() &&
|
|
UnknownPublicSuffixException(aURI))) {
|
|
return false;
|
|
}
|
|
|
|
// 4. Don't upgrade if upgraded previously or exempt from upgrades
|
|
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
|
|
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
|
|
return false;
|
|
}
|
|
|
|
// 5. Don't upgrade if the user explicitly provided a scheme
|
|
if (aLoadInfo->GetSchemelessInput() ==
|
|
nsILoadInfo::SchemelessInputTypeSchemeful &&
|
|
aLoadInfo->GetExternalContentPolicyType() !=
|
|
ExtContentPolicy::TYPE_SPECULATIVE &&
|
|
aURI->SchemeIs("http")) {
|
|
AddHTTPSFirstException(aURI, aLoadInfo);
|
|
return false;
|
|
}
|
|
|
|
// 6. Make sure HTTPS-First does not upgrade custom ports when it is disabled
|
|
if (!mozilla::StaticPrefs::dom_security_https_first_for_custom_ports()) {
|
|
int defaultPortforScheme = NS_GetDefaultPort("http");
|
|
// If no port is specified, then the API returns -1 to indicate the default
|
|
// port.
|
|
int32_t port = 0;
|
|
nsresult rv = aURI->GetPort(&port);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
if (port != defaultPortforScheme && port != -1) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 7. Do not upgrade requests other than GET
|
|
if (!aLoadInfo->GetIsGETRequest()) {
|
|
return false;
|
|
}
|
|
|
|
// We can upgrade the request - let's log to the console and set the status
|
|
// so we know that we upgraded the request.
|
|
if (aLoadInfo->GetSchemelessInput() ==
|
|
nsILoadInfo::SchemelessInputTypeSchemeless &&
|
|
!IsHttpsFirstModeEnabled(isPrivateWin)) {
|
|
nsAutoCString urlCString;
|
|
aURI->GetSpec(urlCString);
|
|
NS_ConvertUTF8toUTF16 urlString(urlCString);
|
|
|
|
AutoTArray<nsString, 1> params = {urlString};
|
|
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSFirstSchemeless", params,
|
|
nsIScriptError::warningFlag, aLoadInfo,
|
|
aURI, true);
|
|
} else {
|
|
nsAutoCString scheme;
|
|
|
|
aURI->GetScheme(scheme);
|
|
scheme.AppendLiteral("s");
|
|
NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
|
|
NS_ConvertUTF8toUTF16 reportScheme(scheme);
|
|
|
|
bool isSpeculative = contentType == ExtContentPolicy::TYPE_SPECULATIVE;
|
|
AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
|
|
nsHTTPSOnlyUtils::LogLocalizedString(
|
|
isSpeculative ? "HTTPSOnlyUpgradeSpeculativeConnection"
|
|
: "HTTPSOnlyUpgradeRequest",
|
|
params, nsIScriptError::warningFlag, aLoadInfo, aURI, true);
|
|
}
|
|
|
|
// Set flag so we know that we upgraded the request
|
|
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST;
|
|
aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<nsIURI>
|
|
nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(
|
|
mozilla::net::DocumentLoadListener* aDocumentLoadListener,
|
|
nsresult aStatus) {
|
|
nsCOMPtr<nsIChannel> channel = aDocumentLoadListener->GetChannel();
|
|
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
|
|
uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
|
|
// Only downgrade if we this request was upgraded using HTTPS-First Mode
|
|
if (!(httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) {
|
|
return nullptr;
|
|
}
|
|
// Once loading is in progress we set that flag so that timeout counter
|
|
// measures do not kick in.
|
|
loadInfo->SetHttpsOnlyStatus(
|
|
httpsOnlyStatus | nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS);
|
|
|
|
nsresult status = aStatus;
|
|
// Since 4xx and 5xx errors return NS_OK instead of NS_ERROR_*, we need
|
|
// to check each NS_OK for those errors.
|
|
// Only downgrade an NS_OK status if it is an 4xx or 5xx error.
|
|
if (NS_SUCCEEDED(aStatus)) {
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
|
|
// If no httpChannel exists we have nothing to do here.
|
|
if (!httpChannel) {
|
|
return nullptr;
|
|
}
|
|
uint32_t responseStatus = 0;
|
|
if (NS_FAILED(httpChannel->GetResponseStatus(&responseStatus))) {
|
|
return nullptr;
|
|
}
|
|
|
|
// In case we found one 4xx or 5xx error we need to log it later on,
|
|
// for that reason we flip the nsresult 'status' from 'NS_OK' to the
|
|
// corresponding NS_ERROR_*.
|
|
// To do so we convert the response status to an nsresult error
|
|
// Every NS_OK that is NOT an 4xx or 5xx error code won't get downgraded.
|
|
if (responseStatus >= 400 && responseStatus < 600) {
|
|
// HttpProxyResponseToErrorCode() maps 400 and 404 on
|
|
// the same error as a 500 status which would lead to no downgrade
|
|
// later on. For that reason we explicit filter for 400 and 404 status
|
|
// codes to log them correctly and to downgrade them if possible.
|
|
switch (responseStatus) {
|
|
case 400:
|
|
status = NS_ERROR_PROXY_BAD_REQUEST;
|
|
break;
|
|
case 404:
|
|
status = NS_ERROR_PROXY_NOT_FOUND;
|
|
break;
|
|
default:
|
|
status = mozilla::net::HttpProxyResponseToErrorCode(responseStatus);
|
|
break;
|
|
}
|
|
}
|
|
if (NS_SUCCEEDED(status)) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// We're only downgrading if it's possible that the error was
|
|
// caused by the upgrade.
|
|
if (HttpsUpgradeUnrelatedErrorCode(status)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = channel->GetURI(getter_AddRefs(uri));
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
nsAutoCString spec;
|
|
nsCOMPtr<nsIURI> newURI;
|
|
|
|
// Only downgrade if the current scheme is (a) https or (b) view-source:https
|
|
if (uri->SchemeIs("https")) {
|
|
rv = uri->GetSpec(spec);
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
rv = NS_NewURI(getter_AddRefs(newURI), spec);
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
rv = NS_MutateURI(newURI).SetScheme("http"_ns).Finalize(
|
|
getter_AddRefs(newURI));
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
} else if (uri->SchemeIs("view-source")) {
|
|
nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(uri);
|
|
if (!nestedURI) {
|
|
return nullptr;
|
|
}
|
|
nsCOMPtr<nsIURI> innerURI;
|
|
rv = nestedURI->GetInnerURI(getter_AddRefs(innerURI));
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
if (!innerURI || !innerURI->SchemeIs("https")) {
|
|
return nullptr;
|
|
}
|
|
rv = NS_MutateURI(innerURI).SetScheme("http"_ns).Finalize(
|
|
getter_AddRefs(innerURI));
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
nsAutoCString innerSpec;
|
|
rv = innerURI->GetSpec(innerSpec);
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
spec.Append("view-source:");
|
|
spec.Append(innerSpec);
|
|
|
|
rv = NS_NewURI(getter_AddRefs(newURI), spec);
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
|
|
// Log downgrade to console
|
|
NS_ConvertUTF8toUTF16 reportSpec(uri->GetSpecOrDefault());
|
|
AutoTArray<nsString, 1> params = {reportSpec};
|
|
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyFailedDowngradeAgain", params,
|
|
nsIScriptError::warningFlag, loadInfo,
|
|
uri, true);
|
|
|
|
if (mozilla::StaticPrefs::
|
|
dom_security_https_first_add_exception_on_failure()) {
|
|
AddHTTPSFirstException(uri, loadInfo);
|
|
}
|
|
|
|
return newURI.forget();
|
|
}
|
|
|
|
void nsHTTPSOnlyUtils::UpdateLoadStateAfterHTTPSFirstDowngrade(
|
|
mozilla::net::DocumentLoadListener* aDocumentLoadListener,
|
|
nsDocShellLoadState* aLoadState) {
|
|
// We have to exempt the load from HTTPS-First to prevent a upgrade-downgrade
|
|
// loop
|
|
aLoadState->SetIsExemptFromHTTPSFirstMode(true);
|
|
|
|
// we can safely set the flag to indicate the downgrade here and it will be
|
|
// propagated all the way to nsHttpChannel::OnStopRequest() where we collect
|
|
// the telemetry.
|
|
nsCOMPtr<nsIChannel> channel = aDocumentLoadListener->GetChannel();
|
|
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
|
|
if (loadInfo->GetSchemelessInput() ==
|
|
nsILoadInfo::SchemelessInputTypeSchemeless) {
|
|
aLoadState->SetHttpsUpgradeTelemetry(
|
|
nsILoadInfo::HTTPS_FIRST_SCHEMELESS_UPGRADE_DOWNGRADE);
|
|
} else {
|
|
aLoadState->SetHttpsUpgradeTelemetry(
|
|
nsILoadInfo::HTTPS_FIRST_UPGRADE_DOWNGRADE);
|
|
}
|
|
|
|
// Add downgrade data for later telemetry usage to load state
|
|
nsDOMNavigationTiming* timing = aDocumentLoadListener->GetTiming();
|
|
if (timing) {
|
|
mozilla::TimeStamp navigationStart = timing->GetNavigationStartTimeStamp();
|
|
if (navigationStart) {
|
|
mozilla::TimeDuration duration =
|
|
mozilla::TimeStamp::Now() - navigationStart;
|
|
|
|
bool isPrivateWin = loadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
bool isSchemeless =
|
|
loadInfo->GetSchemelessInput() ==
|
|
nsILoadInfo::SchemelessInputTypeSchemeless &&
|
|
!nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin);
|
|
|
|
nsresult channelStatus;
|
|
channel->GetStatus(&channelStatus);
|
|
|
|
RefPtr downgradeData = mozilla::MakeRefPtr<HTTPSFirstDowngradeData>();
|
|
downgradeData->downgradeTime = duration;
|
|
downgradeData->isOnTimer = channelStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL;
|
|
downgradeData->isSchemeless = isSchemeless;
|
|
aLoadState->SetHttpsFirstDowngradeData(downgradeData);
|
|
}
|
|
}
|
|
}
|
|
|
|
void nsHTTPSOnlyUtils::SubmitHTTPSFirstTelemetry(
|
|
nsCOMPtr<nsILoadInfo> const& aLoadInfo,
|
|
RefPtr<HTTPSFirstDowngradeData> const& aHttpsFirstDowngradeData) {
|
|
using namespace mozilla::glean::httpsfirst;
|
|
if (aHttpsFirstDowngradeData) {
|
|
// Successfully downgraded load
|
|
|
|
auto downgradedMetric = aHttpsFirstDowngradeData->isSchemeless
|
|
? downgraded_schemeless
|
|
: downgraded;
|
|
auto downgradedOnTimerMetric = aHttpsFirstDowngradeData->isSchemeless
|
|
? downgraded_on_timer_schemeless
|
|
: downgraded_on_timer;
|
|
auto downgradeTimeMetric = aHttpsFirstDowngradeData->isSchemeless
|
|
? downgrade_time_schemeless
|
|
: downgrade_time;
|
|
|
|
if (aHttpsFirstDowngradeData->isOnTimer) {
|
|
downgradedOnTimerMetric.AddToNumerator();
|
|
}
|
|
downgradedMetric.Add();
|
|
downgradeTimeMetric.AccumulateRawDuration(
|
|
aHttpsFirstDowngradeData->downgradeTime);
|
|
} else if (aLoadInfo->GetHttpsOnlyStatus() &
|
|
nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST) {
|
|
// Successfully upgraded load
|
|
|
|
if (aLoadInfo->GetSchemelessInput() ==
|
|
nsILoadInfo::SchemelessInputTypeSchemeless) {
|
|
upgraded_schemeless.Add();
|
|
} else {
|
|
upgraded.Add();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(nsIChannel* aChannel,
|
|
nsresult aError) {
|
|
// If there is no failed channel, then there is nothing to do here.
|
|
if (!aChannel) {
|
|
return false;
|
|
}
|
|
|
|
// If HTTPS-Only Mode is not enabled, then there is nothing to do here.
|
|
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
|
|
bool isPrivateWin = loadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
if (!IsHttpsOnlyModeEnabled(isPrivateWin)) {
|
|
return false;
|
|
}
|
|
|
|
// If the load is exempt or did not get upgraded,
|
|
// then there is nothing to do here.
|
|
uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
|
|
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT ||
|
|
httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UNINITIALIZED) {
|
|
return false;
|
|
}
|
|
|
|
// If it's one of those errors, then most likely it's not a HTTPS-Only error
|
|
// (This list of errors is largely drawn from nsDocShell::DisplayLoadError())
|
|
return !HttpsUpgradeUnrelatedErrorCode(aError);
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::TestIfPrincipalIsExempt(nsIPrincipal* aPrincipal,
|
|
bool aCheckForHTTPSFirst) {
|
|
static nsCOMPtr<nsIPermissionManager> sPermMgr;
|
|
if (!sPermMgr) {
|
|
sPermMgr = mozilla::components::PermissionManager::Service();
|
|
mozilla::ClearOnShutdown(&sPermMgr);
|
|
}
|
|
NS_ENSURE_TRUE(sPermMgr, false);
|
|
|
|
uint32_t perm;
|
|
nsresult rv = sPermMgr->TestExactPermissionFromPrincipal(
|
|
aPrincipal, "https-only-load-insecure"_ns, &perm);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
return perm == nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW ||
|
|
perm == nsIHttpsOnlyModePermission::LOAD_INSECURE_ALLOW_SESSION ||
|
|
(aCheckForHTTPSFirst &&
|
|
perm == nsIHttpsOnlyModePermission::HTTPSFIRST_LOAD_INSECURE_ALLOW);
|
|
}
|
|
|
|
/* static */
|
|
void nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(
|
|
nsIChannel* aChannel) {
|
|
NS_ENSURE_TRUE_VOID(aChannel);
|
|
|
|
// If HTTPS-Only or HTTPS-First Mode is not enabled, then there is nothing to
|
|
// do here.
|
|
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
|
|
bool isPrivateWin = loadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
bool isHttpsOnly = IsHttpsOnlyModeEnabled(isPrivateWin);
|
|
bool isHttpsFirst = IsHttpsFirstModeEnabled(isPrivateWin);
|
|
bool isSchemelessHttpsFirst =
|
|
(loadInfo->GetSchemelessInput() ==
|
|
nsILoadInfo::SchemelessInputTypeSchemeless) &&
|
|
mozilla::StaticPrefs::dom_security_https_first_schemeless() &&
|
|
!isHttpsOnly && !isHttpsFirst;
|
|
if (!isHttpsOnly && !isHttpsFirst && !isSchemelessHttpsFirst) {
|
|
return;
|
|
}
|
|
|
|
// if it's not a top-level load then there is nothing to here.
|
|
ExtContentPolicyType type = loadInfo->GetExternalContentPolicyType();
|
|
if (type != ExtContentPolicy::TYPE_DOCUMENT) {
|
|
return;
|
|
}
|
|
|
|
// it it's not an http channel, then there is nothing to do here.
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
|
|
if (!httpChannel) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
|
|
aChannel, getter_AddRefs(principal));
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
|
|
bool isPrincipalExempt = TestIfPrincipalIsExempt(
|
|
principal, isHttpsFirst || isSchemelessHttpsFirst);
|
|
if (isPrincipalExempt) {
|
|
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT;
|
|
}
|
|
loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
|
|
|
|
// For the telemetry we do not want downgrade values to be overwritten
|
|
// in the loadinfo. We only want e.g. a reload() or a back() click
|
|
// to carry the upgrade exception.
|
|
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
|
|
nsILoadInfo::HTTPSUpgradeTelemetryType httpsTelemetry =
|
|
nsILoadInfo::NOT_INITIALIZED;
|
|
loadInfo->GetHttpsUpgradeTelemetry(&httpsTelemetry);
|
|
if (httpsTelemetry != nsILoadInfo::HTTPS_ONLY_UPGRADE_DOWNGRADE &&
|
|
httpsTelemetry != nsILoadInfo::HTTPS_FIRST_UPGRADE_DOWNGRADE &&
|
|
httpsTelemetry !=
|
|
nsILoadInfo::HTTPS_FIRST_SCHEMELESS_UPGRADE_DOWNGRADE) {
|
|
loadInfo->SetHttpsUpgradeTelemetry(nsILoadInfo::UPGRADE_EXCEPTION);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::IsSafeToAcceptCORSOrMixedContent(
|
|
nsILoadInfo* aLoadInfo) {
|
|
// Check if the request is exempt from upgrades
|
|
if ((aLoadInfo->GetHttpsOnlyStatus() & nsILoadInfo::HTTPS_ONLY_EXEMPT)) {
|
|
return false;
|
|
}
|
|
// Check if HTTPS-Only Mode is enabled for this request
|
|
bool isPrivateWin = aLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
return nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin);
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::HttpsUpgradeUnrelatedErrorCode(nsresult aError) {
|
|
return NS_ERROR_UNKNOWN_PROTOCOL == aError ||
|
|
NS_ERROR_FILE_NOT_FOUND == aError ||
|
|
NS_ERROR_FILE_ACCESS_DENIED == aError ||
|
|
NS_ERROR_UNKNOWN_HOST == aError || NS_ERROR_PHISHING_URI == aError ||
|
|
NS_ERROR_MALWARE_URI == aError || NS_ERROR_UNWANTED_URI == aError ||
|
|
NS_ERROR_HARMFUL_URI == aError || NS_ERROR_CONTENT_CRASHED == aError ||
|
|
NS_ERROR_FRAME_CRASHED == aError || NS_ERROR_SUPERFLUOS_AUTH == aError;
|
|
}
|
|
|
|
/* ------ Logging ------ */
|
|
|
|
/* static */
|
|
void nsHTTPSOnlyUtils::LogLocalizedString(const char* aName,
|
|
const nsTArray<nsString>& aParams,
|
|
uint32_t aFlags,
|
|
nsILoadInfo* aLoadInfo, nsIURI* aURI,
|
|
bool aUseHttpsFirst) {
|
|
nsAutoString logMsg;
|
|
nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
|
|
aName, aParams, logMsg);
|
|
LogMessage(logMsg, aFlags, aLoadInfo, aURI, aUseHttpsFirst);
|
|
}
|
|
|
|
/* static */
|
|
void nsHTTPSOnlyUtils::LogMessage(const nsAString& aMessage, uint32_t aFlags,
|
|
nsILoadInfo* aLoadInfo, nsIURI* aURI,
|
|
bool aUseHttpsFirst) {
|
|
// do not log to the console if the loadinfo says we should not!
|
|
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
|
|
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE) {
|
|
return;
|
|
}
|
|
|
|
// Prepending HTTPS-Only to the outgoing console message
|
|
nsString message;
|
|
message.Append(aUseHttpsFirst ? u"HTTPS-First Mode: "_ns
|
|
: u"HTTPS-Only Mode: "_ns);
|
|
message.Append(aMessage);
|
|
|
|
// Allow for easy distinction in devtools code.
|
|
auto category = aUseHttpsFirst ? "HTTPSFirst"_ns : "HTTPSOnly"_ns;
|
|
|
|
uint64_t windowId = aLoadInfo->GetInnerWindowID();
|
|
if (!windowId) {
|
|
windowId = aLoadInfo->GetTriggeringWindowId();
|
|
}
|
|
if (windowId) {
|
|
// Send to content console
|
|
nsContentUtils::ReportToConsoleByWindowID(
|
|
message, aFlags, category, windowId, mozilla::SourceLocation(aURI));
|
|
} else {
|
|
// Send to browser console
|
|
bool isPrivateWin = aLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
nsContentUtils::LogSimpleConsoleError(message, category, isPrivateWin,
|
|
true /* from chrome context */,
|
|
aFlags);
|
|
}
|
|
}
|
|
|
|
/* ------ Exceptions ------ */
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::OnionException(nsIURI* aURI) {
|
|
// Onion-host exception can get disabled with a pref
|
|
if (mozilla::StaticPrefs::dom_security_https_only_mode_upgrade_onion()) {
|
|
return false;
|
|
}
|
|
nsAutoCString host;
|
|
aURI->GetHost(host);
|
|
return StringEndsWith(host, ".onion"_ns);
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::LoopbackOrLocalException(nsIURI* aURI) {
|
|
nsAutoCString asciiHost;
|
|
nsresult rv = aURI->GetAsciiHost(asciiHost);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
// Let's make a quick check if the host matches these loopback strings
|
|
// before we do anything else
|
|
if (asciiHost.EqualsLiteral("localhost") || asciiHost.EqualsLiteral("::1")) {
|
|
return true;
|
|
}
|
|
|
|
mozilla::net::NetAddr addr;
|
|
if (NS_FAILED(addr.InitFromString(asciiHost))) {
|
|
return false;
|
|
}
|
|
// Loopback IPs are always exempt
|
|
if (addr.IsLoopbackAddr()) {
|
|
return true;
|
|
}
|
|
|
|
// Local IP exception can get disabled with a pref
|
|
bool upgradeLocal =
|
|
mozilla::StaticPrefs::dom_security_https_only_mode_upgrade_local();
|
|
return (!upgradeLocal && addr.IsIPAddrLocal());
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::UnknownPublicSuffixException(nsIURI* aURI) {
|
|
nsCOMPtr<nsIEffectiveTLDService> tldService =
|
|
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
|
|
NS_ENSURE_TRUE(tldService, false);
|
|
|
|
bool hasKnownPublicSuffix;
|
|
nsresult rv = tldService->HasKnownPublicSuffix(aURI, &hasKnownPublicSuffix);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
return !hasKnownPublicSuffix;
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::ShouldUpgradeConnection(nsILoadInfo* aLoadInfo) {
|
|
// Check if one of parameters is null then webpage can't be loaded yet
|
|
// and no further inspections are needed
|
|
if (!aLoadInfo) {
|
|
return false;
|
|
}
|
|
|
|
// Check if the HTTPS-Only Mode is even enabled, before we do anything else
|
|
bool isPrivateWin = aLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
|
if (!IsHttpsOnlyModeEnabled(isPrivateWin) &&
|
|
!IsHttpsFirstModeEnabled(isPrivateWin)) {
|
|
return false;
|
|
}
|
|
|
|
// If the load is exempt, then don't upgrade
|
|
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
|
|
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
bool nsHTTPSOnlyUtils::IsHttpDowngrade(nsIURI* aFromURI, nsIURI* aToURI) {
|
|
MOZ_ASSERT(aFromURI);
|
|
MOZ_ASSERT(aToURI);
|
|
|
|
if (!aFromURI || !aToURI) {
|
|
return false;
|
|
}
|
|
|
|
// 2. If the target URI is not http, then it's not a http downgrade
|
|
if (!aToURI->SchemeIs("http")) {
|
|
return false;
|
|
}
|
|
|
|
// 3. If the origin URI isn't https, then it's not a http downgrade either.
|
|
if (!aFromURI->SchemeIs("https")) {
|
|
return false;
|
|
}
|
|
|
|
// 4. Create a new target URI with 'https' instead of 'http' and compare it
|
|
// to the origin URI
|
|
int32_t port = 0;
|
|
nsresult rv = aToURI->GetPort(&port);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
// a port of -1 indicates the default port, hence we upgrade from port 80 to
|
|
// port 443
|
|
// otherwise we keep the port.
|
|
if (port == -1) {
|
|
port = NS_GetDefaultPort("https");
|
|
}
|
|
nsCOMPtr<nsIURI> newHTTPSchemeURI;
|
|
rv = NS_MutateURI(aToURI)
|
|
.SetScheme("https"_ns)
|
|
.SetPort(port)
|
|
.Finalize(newHTTPSchemeURI);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
bool uriEquals = false;
|
|
if (NS_FAILED(aFromURI->EqualsExceptRef(newHTTPSchemeURI, &uriEquals))) {
|
|
return false;
|
|
}
|
|
|
|
return uriEquals;
|
|
}
|
|
|
|
/* static */
|
|
nsresult nsHTTPSOnlyUtils::AddHTTPSFirstException(
|
|
nsCOMPtr<nsIURI> aURI, nsILoadInfo* const aLoadInfo) {
|
|
// We need to reconstruct a principal instead of taking one from the loadinfo,
|
|
// as the permission needs a http scheme, while the passed URL or principals
|
|
// on the loadinfo may have a https scheme.
|
|
nsresult rv =
|
|
NS_MutateURI(aURI).SetScheme("http"_ns).Finalize(getter_AddRefs(aURI));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mozilla::OriginAttributes oa = aLoadInfo->GetOriginAttributes();
|
|
oa.SetFirstPartyDomain(true, aURI);
|
|
|
|
nsCOMPtr<nsIPermissionManager> permMgr =
|
|
mozilla::components::PermissionManager::Service();
|
|
NS_ENSURE_TRUE(permMgr, nsresult::NS_ERROR_SERVICE_NOT_AVAILABLE);
|
|
|
|
nsCOMPtr<nsIPrincipal> principal =
|
|
mozilla::BasePrincipal::CreateContentPrincipal(aURI, oa);
|
|
|
|
nsCString host;
|
|
aURI->GetHost(host);
|
|
LogLocalizedString("HTTPSFirstAddingException", {NS_ConvertUTF8toUTF16(host)},
|
|
nsIScriptError::warningFlag, aLoadInfo, aURI, true);
|
|
|
|
uint32_t lifetime =
|
|
mozilla::StaticPrefs::dom_security_https_first_exception_lifetime();
|
|
int64_t expirationTime = (PR_Now() / PR_USEC_PER_MSEC) + lifetime;
|
|
rv = permMgr->AddFromPrincipal(
|
|
principal, "https-only-load-insecure"_ns,
|
|
nsIHttpsOnlyModePermission::HTTPSFIRST_LOAD_INSECURE_ALLOW,
|
|
nsIPermissionManager::EXPIRE_TIME, expirationTime);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
uint32_t nsHTTPSOnlyUtils::GetStatusForSubresourceLoad(
|
|
uint32_t aHttpsOnlyStatus) {
|
|
return aHttpsOnlyStatus & ~nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Implementation of TestHTTPAnswerRunnable
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(TestHTTPAnswerRunnable, mozilla::Runnable,
|
|
nsIStreamListener, nsIInterfaceRequestor,
|
|
nsITimerCallback)
|
|
|
|
TestHTTPAnswerRunnable::TestHTTPAnswerRunnable(
|
|
nsIURI* aURI, mozilla::net::DocumentLoadListener* aDocumentLoadListener)
|
|
: mozilla::Runnable("TestHTTPAnswerRunnable"),
|
|
mURI(aURI),
|
|
mDocumentLoadListener(aDocumentLoadListener) {}
|
|
|
|
/* static */
|
|
bool TestHTTPAnswerRunnable::IsBackgroundRequestRedirected(
|
|
nsIHttpChannel* aChannel) {
|
|
// If there is no background request (aChannel), then there is nothing
|
|
// to do here.
|
|
if (!aChannel) {
|
|
return false;
|
|
}
|
|
// If the request was not redirected, then there is nothing to do here.
|
|
nsCOMPtr<nsILoadInfo> loadinfo = aChannel->LoadInfo();
|
|
if (loadinfo->RedirectChain().IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
// If the final URI is not targeting an https scheme, then we definitely not
|
|
// dealing with a 'same-origin' redirect.
|
|
nsCOMPtr<nsIURI> finalURI;
|
|
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI));
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
if (!finalURI->SchemeIs("https")) {
|
|
return false;
|
|
}
|
|
|
|
// If the background request was not http, then there is nothing to do here.
|
|
nsCOMPtr<nsIPrincipal> firstURIPrincipal;
|
|
loadinfo->RedirectChain()[0]->GetPrincipal(getter_AddRefs(firstURIPrincipal));
|
|
if (!firstURIPrincipal || !firstURIPrincipal->SchemeIs("http")) {
|
|
return false;
|
|
}
|
|
|
|
// By now we have verified that the inital background request was http and
|
|
// that the redirected scheme is https. We want to find the following case
|
|
// where the background channel redirects to the https version of the
|
|
// top-level request.
|
|
// --> background channel: http://example.com
|
|
// |--> redirects to: https://example.com
|
|
// Now we have to check that the hosts are 'same-origin'.
|
|
nsAutoCString redirectHost;
|
|
nsAutoCString finalHost;
|
|
firstURIPrincipal->GetAsciiHost(redirectHost);
|
|
finalURI->GetAsciiHost(finalHost);
|
|
return finalHost.Equals(redirectHost);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TestHTTPAnswerRunnable::OnStartRequest(nsIRequest* aRequest) {
|
|
// If the request status is not OK, it means it encountered some
|
|
// kind of error in which case we do not want to do anything.
|
|
nsresult requestStatus;
|
|
aRequest->GetStatus(&requestStatus);
|
|
if (requestStatus != NS_OK) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Check if the original top-level channel which https-only is trying
|
|
// to upgrade is already in progress or if the channel is an auth channel.
|
|
// If it is in progress or Auth is in progress, then all good, if not
|
|
// then let's cancel that channel so we can dispaly the exception page.
|
|
nsCOMPtr<nsIChannel> docChannel = mDocumentLoadListener->GetChannel();
|
|
nsCOMPtr<nsIHttpChannel> httpsOnlyChannel = do_QueryInterface(docChannel);
|
|
if (httpsOnlyChannel) {
|
|
nsCOMPtr<nsILoadInfo> loadInfo = httpsOnlyChannel->LoadInfo();
|
|
uint32_t topLevelLoadInProgress =
|
|
loadInfo->GetHttpsOnlyStatus() &
|
|
nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS;
|
|
|
|
nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
|
|
do_QueryInterface(httpsOnlyChannel);
|
|
bool isAuthChannel = false;
|
|
mozilla::Unused << httpChannelInternal->GetIsAuthChannel(&isAuthChannel);
|
|
// some server configurations need a long time to respond to an https
|
|
// connection, but also redirect any http connection to the https version of
|
|
// it. If the top-level load has not started yet, but the http background
|
|
// request redirects to https, then do not show the error page, but keep
|
|
// waiting for the https response of the upgraded top-level request.
|
|
if (!topLevelLoadInProgress) {
|
|
nsCOMPtr<nsIHttpChannel> backgroundHttpChannel =
|
|
do_QueryInterface(aRequest);
|
|
topLevelLoadInProgress =
|
|
IsBackgroundRequestRedirected(backgroundHttpChannel);
|
|
}
|
|
if (!topLevelLoadInProgress && !isAuthChannel) {
|
|
// Only really cancel the original top-level channel if it's
|
|
// status is still NS_OK, otherwise it might have already
|
|
// encountered some other error and was cancelled.
|
|
nsresult httpsOnlyChannelStatus;
|
|
httpsOnlyChannel->GetStatus(&httpsOnlyChannelStatus);
|
|
if (httpsOnlyChannelStatus == NS_OK) {
|
|
httpsOnlyChannel->Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cancel this http request because it has reached the end of it's
|
|
// lifetime at this point.
|
|
aRequest->Cancel(NS_ERROR_ABORT);
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TestHTTPAnswerRunnable::OnDataAvailable(nsIRequest* aRequest,
|
|
nsIInputStream* aStream,
|
|
uint64_t aOffset, uint32_t aCount) {
|
|
// TestHTTPAnswerRunnable only cares about ::OnStartRequest which
|
|
// will also cancel the request, so we should in fact never even
|
|
// get here.
|
|
MOZ_ASSERT(false, "how come we get to ::OnDataAvailable");
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TestHTTPAnswerRunnable::OnStopRequest(nsIRequest* aRequest,
|
|
nsresult aStatusCode) {
|
|
// TestHTTPAnswerRunnable only cares about ::OnStartRequest
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TestHTTPAnswerRunnable::GetInterface(const nsIID& aIID, void** aResult) {
|
|
return QueryInterface(aIID, aResult);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TestHTTPAnswerRunnable::Run() {
|
|
{
|
|
// Before we start our timer we kick of a DNS request for HTTPS RR. If we
|
|
// find a HTTPS RR we will not downgrade later.
|
|
nsCOMPtr<nsIChannel> origChannel = mDocumentLoadListener->GetChannel();
|
|
mozilla::OriginAttributes originAttributes;
|
|
mozilla::StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(
|
|
origChannel, originAttributes);
|
|
RefPtr<nsDNSPrefetch> resolver =
|
|
new nsDNSPrefetch(mURI, originAttributes, origChannel->GetTRRMode());
|
|
nsCOMPtr<nsIHttpChannelInternal> internalChannel =
|
|
do_QueryInterface(origChannel);
|
|
uint32_t caps;
|
|
if (NS_SUCCEEDED(internalChannel->GetCaps(&caps))) {
|
|
mozilla::Unused << resolver->FetchHTTPSSVC(
|
|
caps & NS_HTTP_REFRESH_DNS, false,
|
|
[self = RefPtr{this}](nsIDNSHTTPSSVCRecord* aRecord) {
|
|
self->mHasHTTPSRR = (aRecord != nullptr);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Wait N milliseconds to give the original https request a heads start
|
|
// before firing up this http request in the background. If the https request
|
|
// has not received any signal from the server during that time, than it's
|
|
// almost certain the upgraded request will result in time out.
|
|
uint32_t background_timer_ms = mozilla::StaticPrefs::
|
|
dom_security_https_only_fire_http_request_background_timer_ms();
|
|
|
|
return NS_NewTimerWithCallback(getter_AddRefs(mTimer), this,
|
|
background_timer_ms, nsITimer::TYPE_ONE_SHOT);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TestHTTPAnswerRunnable::Notify(nsITimer* aTimer) {
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
mTimer = nullptr;
|
|
}
|
|
|
|
// If the original channel has already started loading at this point
|
|
// then there is no need to do the dance.
|
|
nsCOMPtr<nsIChannel> origChannel = mDocumentLoadListener->GetChannel();
|
|
nsCOMPtr<nsILoadInfo> origLoadInfo = origChannel->LoadInfo();
|
|
uint32_t origHttpsOnlyStatus = origLoadInfo->GetHttpsOnlyStatus();
|
|
uint32_t topLevelLoadInProgress =
|
|
origHttpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_TOP_LEVEL_LOAD_IN_PROGRESS;
|
|
uint32_t downloadInProgress =
|
|
origHttpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_DOWNLOAD_IN_PROGRESS;
|
|
|
|
// If the upgrade is caused by HSTS or HTTPS RR we do not allow downgrades
|
|
// so we do not need to start a racing request.
|
|
bool isClientRequestedUpgrade =
|
|
origHttpsOnlyStatus &
|
|
(nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED |
|
|
nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED |
|
|
nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST) &&
|
|
!mHasHTTPSRR;
|
|
|
|
if (topLevelLoadInProgress || downloadInProgress ||
|
|
!isClientRequestedUpgrade) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mozilla::OriginAttributes attrs = origLoadInfo->GetOriginAttributes();
|
|
RefPtr<nsIPrincipal> nullPrincipal = mozilla::NullPrincipal::Create(attrs);
|
|
|
|
uint32_t loadFlags =
|
|
nsIRequest::LOAD_ANONYMOUS | nsIRequest::INHIBIT_CACHING |
|
|
nsIRequest::INHIBIT_PERSISTENT_CACHING | nsIRequest::LOAD_BYPASS_CACHE |
|
|
nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
|
|
|
|
// No need to connect to the URI including the path because we only care about
|
|
// the round trip time if a server responds to an http request.
|
|
nsCOMPtr<nsIURI> backgroundChannelURI;
|
|
nsAutoCString prePathStr;
|
|
nsresult rv = mURI->GetPrePath(prePathStr);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
rv = NS_NewURI(getter_AddRefs(backgroundChannelURI), prePathStr);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// we are using TYPE_OTHER because TYPE_DOCUMENT might have side effects
|
|
nsCOMPtr<nsIChannel> testHTTPChannel;
|
|
rv = NS_NewChannel(getter_AddRefs(testHTTPChannel), backgroundChannelURI,
|
|
nullPrincipal,
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
|
|
nsIContentPolicy::TYPE_OTHER, nullptr, nullptr, nullptr,
|
|
nullptr, loadFlags);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// We have exempt that load from HTTPS-Only to avoid getting upgraded
|
|
// to https as well. Additonally let's not log that request to the console
|
|
// because it might confuse end users.
|
|
nsCOMPtr<nsILoadInfo> loadInfo = testHTTPChannel->LoadInfo();
|
|
uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
|
|
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_EXEMPT |
|
|
nsILoadInfo::HTTPS_ONLY_DO_NOT_LOG_TO_CONSOLE |
|
|
nsILoadInfo::HTTPS_ONLY_BYPASS_ORB;
|
|
loadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
|
|
|
|
testHTTPChannel->SetNotificationCallbacks(this);
|
|
testHTTPChannel->AsyncOpen(this);
|
|
return NS_OK;
|
|
}
|