diff --git a/browser/base/content/test/siteIdentity/browser.ini b/browser/base/content/test/siteIdentity/browser.ini index 8b7b6de3bddce..da2c04e1b2993 100644 --- a/browser/base/content/test/siteIdentity/browser.ini +++ b/browser/base/content/test/siteIdentity/browser.ini @@ -42,6 +42,10 @@ tags = mcb support-files = file_csp_block_all_mixedcontent.html file_csp_block_all_mixedcontent.js +[browser_deprecatedTLSVersions.js] +[browser_getSecurityInfo.js] +support-files = + dummy_iframe_page.html [browser_identity_UI.js] [browser_identityBlock_focus.js] support-files = ../permissions/permissions.html @@ -115,4 +119,3 @@ support-files = support-files = file_mixedPassiveContent.html file_bug1045809_1.html -[browser_deprecatedTLSVersions.js] diff --git a/browser/base/content/test/siteIdentity/browser_getSecurityInfo.js b/browser/base/content/test/siteIdentity/browser_getSecurityInfo.js new file mode 100644 index 0000000000000..5be82b22893cd --- /dev/null +++ b/browser/base/content/test/siteIdentity/browser_getSecurityInfo.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const MOZILLA_PKIX_ERROR_BASE = Ci.nsINSSErrorsService.MOZILLA_PKIX_ERROR_BASE; +const MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT = MOZILLA_PKIX_ERROR_BASE + 14; + +const IFRAME_PAGE = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com") + "dummy_iframe_page.html"; + +// Tests the getSecurityInfo() function exposed on WindowGlobalParent. +add_task(async function test() { + await BrowserTestUtils.withNewTab("about:blank", async function(browser) { + let loaded = BrowserTestUtils.waitForErrorPage(browser); + await BrowserTestUtils.loadURI(browser, "https://self-signed.example.com"); + await loaded; + + let securityInfo = await browser.browsingContext.currentWindowGlobal.getSecurityInfo(); + securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + ok(securityInfo, "Found some security info"); + ok(securityInfo.failedCertChain, "Has a failed cert chain"); + is(securityInfo.errorCode, MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT, "Has the correct error code"); + is(securityInfo.serverCert.commonName, "self-signed.example.com", "Has the correct certificate"); + + loaded = BrowserTestUtils.browserLoaded(browser); + await BrowserTestUtils.loadURI(browser, "http://example.com"); + await loaded; + + securityInfo = await browser.browsingContext.currentWindowGlobal.getSecurityInfo(); + is(securityInfo, null, "Found no security info"); + + loaded = BrowserTestUtils.browserLoaded(browser); + await BrowserTestUtils.loadURI(browser, "https://example.com"); + await loaded; + + securityInfo = await browser.browsingContext.currentWindowGlobal.getSecurityInfo(); + securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + ok(securityInfo, "Found some security info"); + ok(securityInfo.succeededCertChain, "Has a succeeded cert chain"); + is(securityInfo.errorCode, 0, "Has no error code"); + is(securityInfo.serverCert.commonName, "example.com", "Has the correct certificate"); + + loaded = BrowserTestUtils.browserLoaded(browser); + await BrowserTestUtils.loadURI(browser, IFRAME_PAGE); + await loaded; + + // Get the info of the parent, which is HTTP. + securityInfo = await browser.browsingContext.currentWindowGlobal.getSecurityInfo(); + is(securityInfo, null, "Found no security info"); + + // Get the info of the frame, which is HTTPS. + securityInfo = await browser.browsingContext.getChildren()[0].currentWindowGlobal.getSecurityInfo(); + securityInfo.QueryInterface(Ci.nsITransportSecurityInfo); + ok(securityInfo, "Found some security info"); + ok(securityInfo.succeededCertChain, "Has a succeeded cert chain"); + is(securityInfo.errorCode, 0, "Has no error code"); + is(securityInfo.serverCert.commonName, "example.com", "Has the correct certificate"); + }); +}); diff --git a/browser/base/content/test/siteIdentity/dummy_iframe_page.html b/browser/base/content/test/siteIdentity/dummy_iframe_page.html new file mode 100644 index 0000000000000..ea80367aa5f54 --- /dev/null +++ b/browser/base/content/test/siteIdentity/dummy_iframe_page.html @@ -0,0 +1,10 @@ + + +Dummy iframe test page + + + + +

Dummy test page

+ + diff --git a/dom/chrome-webidl/WindowGlobalActors.webidl b/dom/chrome-webidl/WindowGlobalActors.webidl index 2dd779e058d6e..f60b2d63b78e6 100644 --- a/dom/chrome-webidl/WindowGlobalActors.webidl +++ b/dom/chrome-webidl/WindowGlobalActors.webidl @@ -7,6 +7,7 @@ interface Principal; interface URI; interface nsIDocShell; interface RemoteTab; +interface nsITransportSecurityInfo; [Exposed=Window, ChromeOnly] interface WindowGlobalParent { @@ -54,6 +55,18 @@ interface WindowGlobalParent { Promise changeFrameRemoteness( BrowsingContext? bc, DOMString remoteType, unsigned long long pendingSwitchId); + + /** + * Fetches the securityInfo object for this window. This function will + * look for failed and successful channels to find the security info, + * thus it will work on regular HTTPS pages as well as certificate + * error pages. + * + * This returns a Promise which resolves to an nsITransportSecurity + * object with certificate data or undefined if no security info is available. + */ + [Throws] + Promise getSecurityInfo(); }; [Exposed=Window, ChromeOnly] diff --git a/dom/ipc/PWindowGlobal.ipdl b/dom/ipc/PWindowGlobal.ipdl index 7c72a13b83ab3..934bed20094d0 100644 --- a/dom/ipc/PWindowGlobal.ipdl +++ b/dom/ipc/PWindowGlobal.ipdl @@ -40,6 +40,11 @@ child: uint64_t aSwitchId) returns (nsresult rv, nullable PBrowserBridge bridge); + /** + * Returns the serialized security info associated with this window. + */ + async GetSecurityInfo() returns(nsCString? serializedSecInfo); + both: async RawMessage(JSWindowActorMessageMeta aMetadata, ClonedMessageData aData); diff --git a/dom/ipc/WindowGlobalChild.cpp b/dom/ipc/WindowGlobalChild.cpp index 96f0b532c205d..2bf2555ad0905 100644 --- a/dom/ipc/WindowGlobalChild.cpp +++ b/dom/ipc/WindowGlobalChild.cpp @@ -23,6 +23,7 @@ #include "nsGlobalWindowInner.h" #include "nsFrameLoaderOwner.h" #include "nsQueryObject.h" +#include "nsSerializationHelper.h" #include "mozilla/dom/JSWindowActorBinding.h" #include "mozilla/dom/JSWindowActorChild.h" @@ -253,6 +254,36 @@ IPCResult WindowGlobalChild::RecvChangeFrameRemoteness( return IPC_OK(); } +mozilla::ipc::IPCResult WindowGlobalChild::RecvGetSecurityInfo( + GetSecurityInfoResolver&& aResolve) { + Maybe result; + + if (nsCOMPtr doc = mWindowGlobal->GetDoc()) { + nsCOMPtr secInfo; + nsresult rv = NS_OK; + + // First check if there's a failed channel, in case of a certificate + // error. + if (nsIChannel* failedChannel = doc->GetFailedChannel()) { + rv = failedChannel->GetSecurityInfo(getter_AddRefs(secInfo)); + } else { + // When there's no failed channel we should have a regular + // security info on the document. In some cases there's no + // security info at all, i.e. on HTTP sites. + secInfo = doc->GetSecurityInfo(); + } + + if (NS_SUCCEEDED(rv) && secInfo) { + nsCOMPtr secInfoSer = do_QueryInterface(secInfo); + result.emplace(); + NS_SerializeToString(secInfoSer, result.ref()); + } + } + + aResolve(result); + return IPC_OK(); +} + IPCResult WindowGlobalChild::RecvRawMessage( const JSWindowActorMessageMeta& aMeta, const ClonedMessageData& aData) { StructuredCloneData data; diff --git a/dom/ipc/WindowGlobalChild.h b/dom/ipc/WindowGlobalChild.h index 46aeea5a14673..70e764c9927b2 100644 --- a/dom/ipc/WindowGlobalChild.h +++ b/dom/ipc/WindowGlobalChild.h @@ -103,6 +103,9 @@ class WindowGlobalChild final : public WindowGlobalActor, dom::BrowsingContext* aBc, const nsString& aRemoteType, uint64_t aPendingSwitchId, ChangeFrameRemotenessResolver&& aResolver); + mozilla::ipc::IPCResult RecvGetSecurityInfo( + GetSecurityInfoResolver&& aResolve); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; private: diff --git a/dom/ipc/WindowGlobalParent.cpp b/dom/ipc/WindowGlobalParent.cpp index 00f972bff9c5b..6c92ef8b0c553 100644 --- a/dom/ipc/WindowGlobalParent.cpp +++ b/dom/ipc/WindowGlobalParent.cpp @@ -27,6 +27,8 @@ #include "nsGlobalWindowInner.h" #include "nsQueryObject.h" #include "nsFrameLoaderOwner.h" +#include "nsSerializationHelper.h" +#include "nsITransportSecurityInfo.h" #include "mozilla/dom/JSWindowActorBinding.h" #include "mozilla/dom/JSWindowActorParent.h" @@ -319,6 +321,45 @@ already_AddRefed WindowGlobalParent::ChangeFrameRemoteness( return promise.forget(); } +already_AddRefed WindowGlobalParent::GetSecurityInfo( + ErrorResult& aRv) { + RefPtr browserParent = GetBrowserParent(); + if (NS_WARN_IF(!browserParent)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsIGlobalObject* global = xpc::NativeGlobal(xpc::PrivilegedJunkScope()); + RefPtr promise = Promise::Create(global, aRv); + if (aRv.Failed()) { + return nullptr; + } + + SendGetSecurityInfo( + [promise](Maybe&& aResult) { + if (aResult) { + nsCOMPtr infoObj; + nsresult rv = + NS_DeserializeObject(aResult.value(), getter_AddRefs(infoObj)); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->MaybeReject(NS_ERROR_FAILURE); + } + nsCOMPtr info = do_QueryInterface(infoObj); + if (!info) { + promise->MaybeReject(NS_ERROR_FAILURE); + } + promise->MaybeResolve(info); + } else { + promise->MaybeResolveWithUndefined(); + } + }, + [promise](ResponseRejectReason&& aReason) { + promise->MaybeReject(NS_ERROR_FAILURE); + }); + + return promise.forget(); +} + void WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy) { mIPCClosed = true; gWindowGlobalParentsById->Remove(mInnerWindowId); diff --git a/dom/ipc/WindowGlobalParent.h b/dom/ipc/WindowGlobalParent.h index fc3a33099f80d..9d8f6b3ef2a6f 100644 --- a/dom/ipc/WindowGlobalParent.h +++ b/dom/ipc/WindowGlobalParent.h @@ -107,6 +107,8 @@ class WindowGlobalParent final : public WindowGlobalActor, uint64_t aPendingSwitchId, ErrorResult& aRv); + already_AddRefed GetSecurityInfo(ErrorResult& aRv); + // Create a WindowGlobalParent from over IPC. This method should not be called // from outside of the IPC constructors. WindowGlobalParent(const WindowGlobalInit& aInit, bool aInProcess);