mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-04 13:07:52 +00:00
Bug 1553265 - add a document.addCertException function to about:certerror pages and use it there; also treat GeckoView error pages as CallerIsTrusted(Net|Cert)Error; r=snorp,johannh,baku
Add a document.addCertException function to about:certerror pages, and use it on the desktop certerror page. Also, as the CallerIsTrusted* functions expect URLs like about:certerror, but GeckoView error pages are data URLs, and so need to be handled differently for these special error-page methods to be exposed on their documents. Example usage of document.addCertException: document.addCertException( true|false /* true == temporary, false == permanent */ ).then( () => { location.reload(); }, err => { console.error(err); } ); Differential Revision: https://phabricator.services.mozilla.com/D56974 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
af46e7328b
commit
1e12d53224
@ -98,41 +98,6 @@ class NetErrorParent extends JSWindowActorParent {
|
||||
return false;
|
||||
}
|
||||
|
||||
async addCertException(bcID, browser, location) {
|
||||
let securityInfo = await BrowsingContext.get(
|
||||
bcID
|
||||
).currentWindowGlobal.getSecurityInfo();
|
||||
securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
|
||||
|
||||
let overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
|
||||
Ci.nsICertOverrideService
|
||||
);
|
||||
let flags = 0;
|
||||
if (securityInfo.isUntrusted) {
|
||||
flags |= overrideService.ERROR_UNTRUSTED;
|
||||
}
|
||||
if (securityInfo.isDomainMismatch) {
|
||||
flags |= overrideService.ERROR_MISMATCH;
|
||||
}
|
||||
if (securityInfo.isNotValidAtThisTime) {
|
||||
flags |= overrideService.ERROR_TIME;
|
||||
}
|
||||
|
||||
let uri = Services.uriFixup.createFixupURI(location, 0);
|
||||
let permanentOverride =
|
||||
!PrivateBrowsingUtils.isBrowserPrivate(browser) &&
|
||||
Services.prefs.getBoolPref("security.certerrors.permanentOverride");
|
||||
let cert = securityInfo.serverCert;
|
||||
overrideService.rememberValidityOverride(
|
||||
uri.asciiHost,
|
||||
uri.port,
|
||||
cert,
|
||||
flags,
|
||||
!permanentOverride
|
||||
);
|
||||
browser.reload();
|
||||
}
|
||||
|
||||
async reportTLSError(bcID, host, port) {
|
||||
let securityInfo = await BrowsingContext.get(
|
||||
bcID
|
||||
@ -270,13 +235,6 @@ class NetErrorParent extends JSWindowActorParent {
|
||||
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "AddCertException":
|
||||
this.addCertException(
|
||||
this.browsingContext.id,
|
||||
this.browser,
|
||||
message.data.location
|
||||
);
|
||||
break;
|
||||
case "Browser:EnableOnlineMode":
|
||||
// Reset network state and refresh the page.
|
||||
Services.io.offline = false;
|
||||
|
@ -511,7 +511,15 @@ function initPageCertError() {
|
||||
}
|
||||
|
||||
function addCertException() {
|
||||
RPMSendAsyncMessage("AddCertException", { location: document.location.href });
|
||||
const isPermanent =
|
||||
!RPMIsWindowPrivate() &&
|
||||
RPMGetBoolPref("security.certerrors.permanentOverride");
|
||||
document.addCertException(!isPermanent).then(
|
||||
() => {
|
||||
location.reload();
|
||||
},
|
||||
err => {}
|
||||
);
|
||||
}
|
||||
|
||||
function onReturnButtonClick(e) {
|
||||
|
@ -78,6 +78,8 @@
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsScreen.h"
|
||||
#include "ChildIterator.h"
|
||||
#include "nsSerializationHelper.h"
|
||||
#include "nsICertOverrideService.h"
|
||||
#include "nsIX509Cert.h"
|
||||
#include "nsIX509CertValidity.h"
|
||||
#include "nsITransportSecurityInfo.h"
|
||||
@ -1381,6 +1383,8 @@ Document::Document(const char* aContentType)
|
||||
mReferrerInfo = new dom::ReferrerInfo(nullptr);
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
// unused by GeckoView
|
||||
static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
|
||||
if (NS_WARN_IF(!aWin)) {
|
||||
return false;
|
||||
@ -1402,10 +1406,148 @@ static bool IsAboutErrorPage(nsGlobalWindowInner* aWin, const char* aSpec) {
|
||||
|
||||
return aboutSpec.EqualsASCII(aSpec);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Document::CallerIsTrustedAboutNetError(JSContext* aCx, JSObject* aObject) {
|
||||
nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
|
||||
#ifdef ANDROID
|
||||
// GeckoView uses data URLs for error pages, so for now just check for any
|
||||
// error page
|
||||
return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
|
||||
#else
|
||||
return IsAboutErrorPage(win, "neterror");
|
||||
#endif
|
||||
}
|
||||
|
||||
already_AddRefed<mozilla::dom::Promise> Document::AddCertException(
|
||||
bool aIsTemporary) {
|
||||
nsIGlobalObject* global = GetScopeObject();
|
||||
if (!global) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ErrorResult er;
|
||||
RefPtr<Promise> promise =
|
||||
Promise::Create(global, er, Promise::ePropagateUserInteraction);
|
||||
if (er.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISupports> info;
|
||||
nsCOMPtr<nsITransportSecurityInfo> tsi;
|
||||
nsresult rv = NS_OK;
|
||||
if (NS_WARN_IF(!mFailedChannel)) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
rv = mFailedChannel->GetSecurityInfo(getter_AddRefs(info));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeReject(rv);
|
||||
return promise.forget();
|
||||
}
|
||||
nsCOMPtr<nsIURI> failedChannelURI;
|
||||
NS_GetFinalChannelURI(mFailedChannel, getter_AddRefs(failedChannelURI));
|
||||
if (!failedChannelURI) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
nsAutoCString host;
|
||||
failedChannelURI->GetAsciiHost(host);
|
||||
int32_t port;
|
||||
failedChannelURI->GetPort(&port);
|
||||
|
||||
tsi = do_QueryInterface(info);
|
||||
if (NS_WARN_IF(!tsi)) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
bool isUntrusted = true;
|
||||
rv = tsi->GetIsUntrusted(&isUntrusted);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeReject(rv);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
bool isDomainMismatch = true;
|
||||
rv = tsi->GetIsDomainMismatch(&isDomainMismatch);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeReject(rv);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
bool isNotValidAtThisTime = true;
|
||||
rv = tsi->GetIsNotValidAtThisTime(&isNotValidAtThisTime);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeReject(rv);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIX509Cert> cert;
|
||||
rv = tsi->GetServerCert(getter_AddRefs(cert));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeReject(rv);
|
||||
return promise.forget();
|
||||
}
|
||||
if (NS_WARN_IF(!cert)) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
uint32_t flags = 0;
|
||||
if (isUntrusted) {
|
||||
flags |= nsICertOverrideService::ERROR_UNTRUSTED;
|
||||
}
|
||||
if (isDomainMismatch) {
|
||||
flags |= nsICertOverrideService::ERROR_MISMATCH;
|
||||
}
|
||||
if (isNotValidAtThisTime) {
|
||||
flags |= nsICertOverrideService::ERROR_TIME;
|
||||
}
|
||||
|
||||
if (XRE_IsContentProcess()) {
|
||||
nsCOMPtr<nsISerializable> certSer = do_QueryInterface(cert);
|
||||
nsCString certSerialized;
|
||||
NS_SerializeToString(certSer, certSerialized);
|
||||
|
||||
ContentChild* cc = ContentChild::GetSingleton();
|
||||
MOZ_ASSERT(cc);
|
||||
cc->SendAddCertException(certSerialized, flags, host, port, aIsTemporary)
|
||||
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
||||
[promise](const mozilla::MozPromise<
|
||||
nsresult, mozilla::ipc::ResponseRejectReason,
|
||||
true>::ResolveOrRejectValue& aValue) {
|
||||
if (aValue.IsResolve()) {
|
||||
promise->MaybeResolve(aValue.ResolveValue());
|
||||
} else {
|
||||
promise->MaybeRejectWithUndefined();
|
||||
}
|
||||
});
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
if (XRE_IsParentProcess()) {
|
||||
nsCOMPtr<nsICertOverrideService> overrideService =
|
||||
do_GetService(NS_CERTOVERRIDE_CONTRACTID);
|
||||
if (!overrideService) {
|
||||
promise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
rv = overrideService->RememberValidityOverride(host, port, cert, flags,
|
||||
aIsTemporary);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeReject(rv);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
promise->MaybeResolveWithUndefined();
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_FAILURE);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
|
||||
@ -1440,7 +1582,18 @@ void Document::GetNetErrorInfo(NetErrorInfo& aInfo, ErrorResult& aRv) {
|
||||
bool Document::CallerIsTrustedAboutCertError(JSContext* aCx,
|
||||
JSObject* aObject) {
|
||||
nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
|
||||
#ifdef ANDROID
|
||||
// GeckoView uses data URLs for error pages, so for now just check for any
|
||||
// error page
|
||||
return win && win->GetDocument() && win->GetDocument()->IsErrorPage();
|
||||
#else
|
||||
return IsAboutErrorPage(win, "certerror");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Document::IsErrorPage() const {
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = mChannel ? mChannel->LoadInfo() : nullptr;
|
||||
return loadInfo && loadInfo->GetLoadErrorPage();
|
||||
}
|
||||
|
||||
void Document::GetFailedCertSecurityInfo(FailedCertSecurityInfo& aInfo,
|
||||
|
@ -4029,6 +4029,8 @@ class Document : public nsINode,
|
||||
virtual bool UseWidthDeviceWidthFallbackViewport() const;
|
||||
|
||||
private:
|
||||
bool IsErrorPage() const;
|
||||
|
||||
void InitializeLocalization(nsTArray<nsString>& aResourceIds);
|
||||
|
||||
// Takes the bits from mStyleUseCounters if appropriate, and sets them in
|
||||
@ -4186,6 +4188,8 @@ class Document : public nsINode,
|
||||
|
||||
static bool AutomaticStorageAccessCanBeGranted(nsIPrincipal* aPrincipal);
|
||||
|
||||
already_AddRefed<Promise> AddCertException(bool aIsTemporary);
|
||||
|
||||
protected:
|
||||
void DoUpdateSVGUseElementShadowTrees();
|
||||
|
||||
|
@ -225,6 +225,9 @@
|
||||
#include "nsIAsyncInputStream.h"
|
||||
#include "xpcpublic.h"
|
||||
#include "nsHyphenationManager.h"
|
||||
#include "nsICertOverrideService.h"
|
||||
#include "nsIX509Cert.h"
|
||||
#include "nsSerializationHelper.h"
|
||||
|
||||
#include "mozilla/Sprintf.h"
|
||||
|
||||
@ -5749,6 +5752,31 @@ mozilla::ipc::IPCResult ContentParent::RecvBHRThreadHang(
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult ContentParent::RecvAddCertException(
|
||||
const nsACString& aSerializedCert, uint32_t aFlags,
|
||||
const nsACString& aHostName, int32_t aPort, bool aIsTemporary,
|
||||
AddCertExceptionResolver&& aResolver) {
|
||||
nsCOMPtr<nsISupports> certObj;
|
||||
nsresult rv = NS_DeserializeObject(aSerializedCert, getter_AddRefs(certObj));
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(certObj);
|
||||
if (!cert) {
|
||||
rv = NS_ERROR_INVALID_ARG;
|
||||
} else {
|
||||
nsCOMPtr<nsICertOverrideService> overrideService =
|
||||
do_GetService(NS_CERTOVERRIDE_CONTRACTID);
|
||||
if (!overrideService) {
|
||||
rv = NS_ERROR_FAILURE;
|
||||
} else {
|
||||
rv = overrideService->RememberValidityOverride(aHostName, aPort, cert,
|
||||
aFlags, aIsTemporary);
|
||||
}
|
||||
}
|
||||
}
|
||||
aResolver(rv);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult ContentParent::RecvAutomaticStorageAccessCanBeGranted(
|
||||
const Principal& aPrincipal,
|
||||
AutomaticStorageAccessCanBeGrantedResolver&& aResolver) {
|
||||
|
@ -1187,6 +1187,11 @@ class ContentParent final
|
||||
|
||||
mozilla::ipc::IPCResult RecvBHRThreadHang(const HangDetails& aHangDetails);
|
||||
|
||||
mozilla::ipc::IPCResult RecvAddCertException(
|
||||
const nsACString& aSerializedCert, uint32_t aFlags,
|
||||
const nsACString& aHostName, int32_t aPort, bool aIsTemporary,
|
||||
AddCertExceptionResolver&& aResolver);
|
||||
|
||||
mozilla::ipc::IPCResult RecvAutomaticStorageAccessCanBeGranted(
|
||||
const Principal& aPrincipal,
|
||||
AutomaticStorageAccessCanBeGrantedResolver&& aResolver);
|
||||
|
@ -1467,6 +1467,14 @@ parent:
|
||||
|
||||
async AddPerformanceMetrics(nsID aID, PerformanceInfo[] aMetrics);
|
||||
|
||||
/*
|
||||
* Adds a certificate exception for the given hostname and port.
|
||||
*/
|
||||
async AddCertException(nsCString aSerializedCert, uint32_t aFlags,
|
||||
nsCString aHostName, int32_t aPort,
|
||||
bool aIsTemporary)
|
||||
returns (nsresult success);
|
||||
|
||||
/*
|
||||
* Determines whether storage access can be granted automatically by the
|
||||
* storage access API without showing a user prompt.
|
||||
|
@ -317,12 +317,14 @@ partial interface Document {
|
||||
attribute EventHandler onpointerlockerror;
|
||||
};
|
||||
|
||||
// Mozilla-internal document extensions specific to error pages.
|
||||
partial interface Document {
|
||||
[Func="Document::CallerIsTrustedAboutCertError"]
|
||||
Promise<any> addCertException(boolean isTemporary);
|
||||
|
||||
[Func="Document::CallerIsTrustedAboutCertError", Throws]
|
||||
FailedCertSecurityInfo getFailedCertSecurityInfo();
|
||||
};
|
||||
|
||||
partial interface Document {
|
||||
[Func="Document::CallerIsTrustedAboutNetError", Throws]
|
||||
NetErrorInfo getNetErrorInfo();
|
||||
};
|
||||
|
@ -20,6 +20,9 @@ const APIS = {
|
||||
GetPrefs: function({ prefs }) {
|
||||
return browser.test.getPrefs(prefs);
|
||||
},
|
||||
RemoveCertOverride: function({ host, port }) {
|
||||
browser.test.removeCertOverride(host, port);
|
||||
},
|
||||
RestorePrefs: function({ oldPrefs }) {
|
||||
return browser.test.restorePrefs(oldPrefs);
|
||||
},
|
||||
|
@ -118,6 +118,13 @@ this.test = class extends ExtensionAPI {
|
||||
return Services.telemetry.getHistogramById(id).add(value);
|
||||
},
|
||||
|
||||
removeCertOverride(host, port) {
|
||||
const overrideService = Cc[
|
||||
"@mozilla.org/security/certoverride;1"
|
||||
].getService(Ci.nsICertOverrideService);
|
||||
overrideService.clearValidityOverride(host, port);
|
||||
},
|
||||
|
||||
async setScalar(id, value) {
|
||||
return Services.telemetry.scalarSet(id, value);
|
||||
},
|
||||
|
@ -89,6 +89,22 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "removeCertOverride",
|
||||
"type": "function",
|
||||
"async": true,
|
||||
"description": "Revokes SSL certificate overrides for the given host+port.",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "host"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"name": "port"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "setScalar",
|
||||
"type": "function",
|
||||
|
@ -142,6 +142,16 @@ class NavigationDelegateTest : BaseSessionTest() {
|
||||
testLoadExpectError("file:///test.mozilla",
|
||||
WebRequestError.ERROR_CATEGORY_URI,
|
||||
WebRequestError.ERROR_FILE_NOT_FOUND)
|
||||
|
||||
val promise = mainSession.evaluatePromiseJS("document.addCertException(false)")
|
||||
var exceptionCaught = false
|
||||
try {
|
||||
val result = promise.value as Boolean
|
||||
assertThat("Promise should not resolve", result, equalTo(false))
|
||||
} catch (e: GeckoSessionTestRule.RejectedPromiseException) {
|
||||
exceptionCaught = true;
|
||||
}
|
||||
assertThat("document.addCertException failed with exception", exceptionCaught, equalTo(true))
|
||||
}
|
||||
|
||||
@Test fun loadUnknownHost() {
|
||||
@ -163,14 +173,32 @@ class NavigationDelegateTest : BaseSessionTest() {
|
||||
}
|
||||
|
||||
@Test fun loadUntrusted() {
|
||||
val uri = if (sessionRule.env.isAutomation) {
|
||||
"https://expired.example.com/"
|
||||
val host = if (sessionRule.env.isAutomation) {
|
||||
"expired.example.com"
|
||||
} else {
|
||||
"https://expired.badssl.com/"
|
||||
"expired.badssl.com"
|
||||
}
|
||||
val uri = "https://$host/"
|
||||
testLoadExpectError(uri,
|
||||
WebRequestError.ERROR_CATEGORY_SECURITY,
|
||||
WebRequestError.ERROR_SECURITY_BAD_CERT)
|
||||
|
||||
mainSession.waitForJS("document.addCertException(false)")
|
||||
mainSession.delegateDuringNextWait(
|
||||
object : Callbacks.ProgressDelegate, Callbacks.NavigationDelegate, Callbacks.ContentDelegate {
|
||||
@AssertCalled(count = 1, order = [1])
|
||||
override fun onPageStart(session: GeckoSession, url: String) {
|
||||
assertThat("URI should be " + uri, url, equalTo(uri))
|
||||
}
|
||||
|
||||
@AssertCalled(count = 1, order = [2])
|
||||
override fun onPageStop(session: GeckoSession, success: Boolean) {
|
||||
assertThat("Load should succeed", success, equalTo(true))
|
||||
sessionRule.removeCertOverride(host, -1)
|
||||
}
|
||||
})
|
||||
mainSession.evaluateJS("location.reload()")
|
||||
mainSession.waitForPageStop()
|
||||
}
|
||||
|
||||
@Test fun loadUnknownProtocol() {
|
||||
|
@ -2060,6 +2060,19 @@ public class GeckoSessionTestRule implements TestRule {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes SSL overrides set for a given host and port
|
||||
*
|
||||
* @param host the host.
|
||||
* @param port the port (-1 == 443).
|
||||
*/
|
||||
public void removeCertOverride(final String host, final long port) {
|
||||
webExtensionApiCall("RemoveCertOverride", args -> {
|
||||
args.put("host", host);
|
||||
args.put("port", port);
|
||||
});
|
||||
}
|
||||
|
||||
private interface SetArgs {
|
||||
void setArgs(JSONObject object) throws JSONException;
|
||||
}
|
||||
|
@ -3636,6 +3636,12 @@ public class GeckoSession implements Parcelable {
|
||||
* @param uri The URI that failed to load.
|
||||
* @param error A WebRequestError containing details about the error
|
||||
* @return A URI to display as an error. Returning null will halt the load entirely.
|
||||
* The following special methods are made available to the URI:
|
||||
* - document.addCertException(isTemporary), returns Promise
|
||||
* - document.getFailedCertSecurityInfo(), returns FailedCertSecurityInfo
|
||||
* - document.getNetErrorInfo(), returns NetErrorInfo
|
||||
* @see <a href="https://searchfox.org/mozilla-central/source/dom/webidl/FailedCertSecurityInfo.webidl">FailedCertSecurityInfo IDL</a>
|
||||
* @see <a href="https://searchfox.org/mozilla-central/source/dom/webidl/NetErrorInfo.webidl">NetErrorInfo IDL</a>
|
||||
*/
|
||||
@UiThread
|
||||
default @Nullable GeckoResult<String> onLoadError(@NonNull GeckoSession session,
|
||||
|
@ -41,6 +41,7 @@ let RPMAccessManager = {
|
||||
getFormatURLPref: ["app.support.baseURL"],
|
||||
getBoolPref: [
|
||||
"security.certerrors.mitm.priming.enabled",
|
||||
"security.certerrors.permanentOverride",
|
||||
"security.enterprise_roots.auto-enabled",
|
||||
"security.certerror.hideAddException",
|
||||
"security.ssl.errorReporting.automatic",
|
||||
@ -52,6 +53,7 @@ let RPMAccessManager = {
|
||||
"services.settings.last_update_seconds",
|
||||
],
|
||||
getAppBuildID: ["yes"],
|
||||
isWindowPrivate: ["yes"],
|
||||
recordTelemetryEvent: ["yes"],
|
||||
addToHistogram: ["yes"],
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user