gecko-dev/security/manager/ssl/nsSecureBrowserUIImpl.cpp

461 строка
15 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "nsSecureBrowserUIImpl.h"
#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/Document.h"
#include "nsContentUtils.h"
#include "nsIChannel.h"
#include "nsDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsITransportSecurityInfo.h"
#include "nsIWebProgress.h"
using namespace mozilla;
LazyLogModule gSecureBrowserUILog("nsSecureBrowserUI");
nsSecureBrowserUIImpl::nsSecureBrowserUIImpl() : mState(0), mEvent(0) {
MOZ_ASSERT(NS_IsMainThread());
}
NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl, nsISecureBrowserUI,
nsIWebProgressListener, nsISupportsWeakReference)
NS_IMETHODIMP
nsSecureBrowserUIImpl::Init(nsIDocShell* aDocShell) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aDocShell);
aDocShell->SetSecurityUI(this);
// The Docshell will own the SecureBrowserUI object, we keep a weak ref.
nsresult rv;
mDocShell = do_GetWeakReference(aDocShell, &rv);
if (NS_FAILED(rv)) {
return rv;
}
// hook up to the webprogress notifications.
nsCOMPtr<nsIWebProgress> wp(do_GetInterface(aDocShell));
if (!wp) {
return NS_ERROR_FAILURE;
}
// Save this so we can compare it to the web progress in OnLocationChange.
mWebProgress = do_GetWeakReference(wp, &rv);
if (NS_FAILED(rv)) {
return rv;
}
return wp->AddProgressListener(this, nsIWebProgress::NOTIFY_LOCATION);
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::GetState(uint32_t* aState) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aState);
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, ("GetState %p", this));
// With respect to mixed content and tracking protection, we won't know when
// the state of our document (or a subdocument) has changed, so we ask the
// docShell.
CheckForMixedContent();
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" mState: %x", mState));
*aState = mState;
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::GetContentBlockingEvent(uint32_t* aEvent) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aEvent);
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
("GetContentBlockingEvent %p", this));
// With respect to mixed content and tracking protection, we won't know when
// the state of our document (or a subdocument) has changed, so we ask the
// docShell.
CheckForContentBlockingEvents();
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" mEvent: %x", mEvent));
*aEvent = mEvent;
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::GetSecInfo(nsITransportSecurityInfo** result) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG_POINTER(result);
*result = mTopLevelSecurityInfo;
NS_IF_ADDREF(*result);
return NS_OK;
}
already_AddRefed<dom::Document>
nsSecureBrowserUIImpl::PrepareForContentChecks() {
nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
if (!docShell) {
return nullptr;
}
// For content docShells, the mixed content security state is set on the root
// docShell.
if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(docShell);
nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
Unused << docShellTreeItem->GetSameTypeRootTreeItem(
getter_AddRefs(sameTypeRoot));
MOZ_ASSERT(
sameTypeRoot,
"No document shell root tree item from document shell tree item!");
docShell = do_QueryInterface(sameTypeRoot);
if (!docShell) {
return nullptr;
}
}
RefPtr<dom::Document> doc = docShell->GetDocument();
return doc.forget();
}
// Ask the docShell if we've blocked or loaded any mixed content.
void nsSecureBrowserUIImpl::CheckForMixedContent() {
RefPtr<dom::Document> doc = PrepareForContentChecks();
if (!doc) {
// If the docshell has no document, then there is no need to update mState.
return;
}
// Has mixed content been loaded or blocked in nsMixedContentBlocker?
// This only applies to secure documents even if they're affected by mixed
// content blocking in which case the STATE_IS_BROKEN bit would be set rather
// than STATE_IS_SECURE.
if (((mState & STATE_IS_SECURE) != 0) || ((mState & STATE_IS_BROKEN) != 0)) {
if (doc->GetHasMixedActiveContentLoaded()) {
mState |= STATE_IS_BROKEN | STATE_LOADED_MIXED_ACTIVE_CONTENT;
mState &= ~STATE_IS_SECURE;
}
if (doc->GetHasMixedDisplayContentLoaded()) {
mState |= STATE_IS_BROKEN | STATE_LOADED_MIXED_DISPLAY_CONTENT;
mState &= ~STATE_IS_SECURE;
}
if (doc->GetHasMixedActiveContentBlocked()) {
mState |= STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
}
if (doc->GetHasMixedDisplayContentBlocked()) {
mState |= STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
}
}
}
// Ask the docShell if we have any content blocking events.
void nsSecureBrowserUIImpl::CheckForContentBlockingEvents() {
RefPtr<dom::Document> doc = PrepareForContentChecks();
if (!doc) {
// If the docshell has no document, then there is no need to update mState.
return;
}
// Has tracking content been blocked or loaded?
if (doc->GetHasTrackingContentBlocked()) {
mEvent |= STATE_BLOCKED_TRACKING_CONTENT;
}
if (doc->GetHasTrackingContentLoaded()) {
mEvent |= STATE_LOADED_TRACKING_CONTENT;
}
if (doc->GetHasCookiesBlockedByPermission()) {
mEvent |= STATE_COOKIES_BLOCKED_BY_PERMISSION;
}
if (doc->GetHasTrackingCookiesBlocked()) {
mEvent |= STATE_COOKIES_BLOCKED_TRACKER;
}
if (doc->GetHasForeignCookiesBlocked()) {
mEvent |= STATE_COOKIES_BLOCKED_FOREIGN;
}
if (doc->GetHasAllCookiesBlocked()) {
mEvent |= STATE_COOKIES_BLOCKED_ALL;
}
if (doc->GetHasCookiesLoaded()) {
mEvent |= STATE_COOKIES_LOADED;
}
}
// Helper function to determine if the given URI can be considered secure.
// Essentially, only "https" URIs can be considered secure. However, the URI we
// have may be e.g. view-source:https://example.com or
// wyciwyg://https://example.com, in which case we have to evaluate the
// innermost URI.
static nsresult URICanBeConsideredSecure(
nsIURI* uri, /* out */ bool& canBeConsideredSecure) {
MOZ_ASSERT(uri);
NS_ENSURE_ARG(uri);
canBeConsideredSecure = false;
nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(uri);
if (!innermostURI) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" couldn't get innermost URI"));
return NS_ERROR_FAILURE;
}
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" innermost URI is '%s'", innermostURI->GetSpecOrDefault().get()));
// Unfortunately, wyciwyg URIs don't know about innermost URIs, so we have to
// manually get the innermost URI if we have such a URI.
bool isWyciwyg;
nsresult rv = innermostURI->SchemeIs("wyciwyg", &isWyciwyg);
if (NS_FAILED(rv)) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" nsIURI->SchemeIs failed"));
return rv;
}
if (isWyciwyg) {
nsCOMPtr<nsIURI> nonWyciwygURI;
rv = nsContentUtils::RemoveWyciwygScheme(innermostURI,
getter_AddRefs(nonWyciwygURI));
if (NS_FAILED(rv)) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" nsContentUtils::RemoveWyciwygScheme failed"));
return rv;
}
if (!nonWyciwygURI) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" apparently that wasn't a valid wyciwyg URI"));
return NS_ERROR_FAILURE;
}
innermostURI = nonWyciwygURI;
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" innermost URI is now '%s'",
innermostURI->GetSpecOrDefault().get()));
}
bool isHttps;
rv = innermostURI->SchemeIs("https", &isHttps);
if (NS_FAILED(rv)) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" nsIURI->SchemeIs failed"));
return rv;
}
canBeConsideredSecure = isHttps;
return NS_OK;
}
// Helper function to get the securityInfo from a channel as a
// nsITransportSecurityInfo. The out parameter will be set to null if there is
// no securityInfo set.
static void GetSecurityInfoFromChannel(
nsIChannel* channel, nsITransportSecurityInfo** securityInfoOut) {
MOZ_ASSERT(channel);
MOZ_ASSERT(securityInfoOut);
NS_ENSURE_TRUE_VOID(channel);
NS_ENSURE_TRUE_VOID(securityInfoOut);
*securityInfoOut = nullptr;
nsCOMPtr<nsISupports> securityInfoSupports;
nsresult rv = channel->GetSecurityInfo(getter_AddRefs(securityInfoSupports));
// GetSecurityInfo may return an error, but it's not necessarily fatal - the
// underlying channel may simply not have a securityInfo.
if (NS_FAILED(rv)) {
return;
}
nsCOMPtr<nsITransportSecurityInfo> securityInfo(
do_QueryInterface(securityInfoSupports));
securityInfo.forget(securityInfoOut);
}
nsresult nsSecureBrowserUIImpl::UpdateStateAndSecurityInfo(nsIChannel* channel,
nsIURI* uri) {
MOZ_ASSERT(channel);
MOZ_ASSERT(uri);
NS_ENSURE_ARG(channel);
NS_ENSURE_ARG(uri);
mState = STATE_IS_INSECURE;
mTopLevelSecurityInfo = nullptr;
// Only https is considered secure (it is possible to have e.g. an http URI
// with a channel that has a securityInfo that indicates the connection is
// secure - e.g. h2/alt-svc or by visiting an http URI over an https proxy).
bool canBeConsideredSecure;
nsresult rv = URICanBeConsideredSecure(uri, canBeConsideredSecure);
if (NS_FAILED(rv)) {
return rv;
}
if (!canBeConsideredSecure) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" URI can't be considered secure"));
return NS_OK;
}
nsCOMPtr<nsITransportSecurityInfo> securityInfo;
GetSecurityInfoFromChannel(channel, getter_AddRefs(securityInfo));
if (securityInfo) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" we have a security info %p", securityInfo.get()));
rv = securityInfo->GetSecurityState(&mState);
if (NS_FAILED(rv)) {
return rv;
}
// If the security state is STATE_IS_INSECURE, the TLS handshake never
// completed. Don't set any further state.
if (mState == STATE_IS_INSECURE) {
return NS_OK;
}
mTopLevelSecurityInfo = securityInfo;
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" set mTopLevelSecurityInfo"));
bool isEV;
rv = mTopLevelSecurityInfo->GetIsExtendedValidation(&isEV);
if (NS_FAILED(rv)) {
return rv;
}
if (isEV) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" is EV"));
mState |= STATE_IDENTITY_EV_TOPLEVEL;
}
}
return NS_OK;
}
// We receive this notification for the nsIWebProgress we added ourselves to
// (i.e. the window we were passed in Init, which should be the top-level
// window or whatever corresponds to an <iframe mozbrowser> element). In some
// cases, we also receive it from nsIWebProgress instances that are children of
// that nsIWebProgress. We ignore notifications from children because they don't
// change the top-level state (if children load mixed or tracking content, the
// docShell will know and will tell us in GetState when we call
// CheckForMixedContent).
// When we receive a notification from the top-level nsIWebProgress, we extract
// any relevant security information and set our state accordingly. We then call
// OnSecurityChange on the docShell corresponding to the nsIWebProgress we were
// initialized with to notify any downstream listeners of the security state.
NS_IMETHODIMP
nsSecureBrowserUIImpl::OnLocationChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, nsIURI* aLocation,
uint32_t aFlags) {
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aWebProgress);
NS_ENSURE_ARG(aLocation);
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
("%p OnLocationChange: %p %p %s %x", this, aWebProgress, aRequest,
aLocation->GetSpecOrDefault().get(), aFlags));
// Filter out events from children. See comment at the top of this function.
// It would be nice if the attribute isTopLevel worked for this, but that
// filters out events for <iframe mozbrowser> elements, which means they don't
// get OnSecurityChange events from this implementation. Instead, we check to
// see if the web progress we were handed here is the same one as we were
// initialized with.
nsCOMPtr<nsIWebProgress> originalWebProgress = do_QueryReferent(mWebProgress);
if (aWebProgress != originalWebProgress) {
return NS_OK;
}
// If this is a same-document location change, we don't need to update our
// state or notify anyone.
if (aFlags & LOCATION_CHANGE_SAME_DOCUMENT) {
return NS_OK;
}
mState = 0;
mEvent = 0;
mTopLevelSecurityInfo = nullptr;
if (aFlags & LOCATION_CHANGE_ERROR_PAGE) {
mState = STATE_IS_INSECURE;
mTopLevelSecurityInfo = nullptr;
} else {
// NB: aRequest may be null. It may also not be QI-able to nsIChannel.
nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
if (channel) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" we have a channel %p", channel.get()));
nsresult rv = UpdateStateAndSecurityInfo(channel, aLocation);
// Even if this failed, we still want to notify downstream so that we
// don't leave a stale security indicator. We set everything to "not
// secure" to be safe.
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" Failed to update security info. "
"Setting everything to 'not secure' to be safe."));
mState = STATE_IS_INSECURE;
mTopLevelSecurityInfo = nullptr;
}
}
}
nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
if (docShell) {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug,
(" calling OnSecurityChange %p %x", aRequest, mState));
nsDocShell::Cast(docShell)->nsDocLoader::OnSecurityChange(aRequest, mState);
} else {
MOZ_LOG(gSecureBrowserUILog, LogLevel::Debug, (" no docShell?"));
}
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::OnStateChange(nsIWebProgress*, nsIRequest*, uint32_t,
nsresult) {
MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::OnProgressChange(nsIWebProgress*, nsIRequest*, int32_t,
int32_t, int32_t, int32_t) {
MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
return NS_OK;
}
NS_IMETHODIMP
nsSecureBrowserUIImpl::OnStatusChange(nsIWebProgress*, nsIRequest*, nsresult,
const char16_t*) {
MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
return NS_OK;
}
nsresult nsSecureBrowserUIImpl::OnSecurityChange(nsIWebProgress*, nsIRequest*,
uint32_t) {
MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
return NS_OK;
}
nsresult nsSecureBrowserUIImpl::OnContentBlockingEvent(nsIWebProgress*,
nsIRequest*, uint32_t) {
MOZ_ASSERT_UNREACHABLE("Should have been excluded in AddProgressListener()");
return NS_OK;
}