139 lines
4.8 KiB
JavaScript
139 lines
4.8 KiB
JavaScript
/* 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/. */
|
|
|
|
// ClientAuthDialogService implements nsIClientAuthDialogService, and aims to
|
|
// open a dialog asking the user to select a client authentication certificate.
|
|
// Ideally the dialog will be tab-modal to the tab corresponding to the load
|
|
// that resulted in the request for the client authentication certificate.
|
|
export function ClientAuthDialogService() {}
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
|
|
});
|
|
|
|
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
|
|
|
if (AppConstants.platform == "android") {
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.sys.mjs",
|
|
});
|
|
}
|
|
|
|
// Given a loadContext (CanonicalBrowsingContext), attempts to return a
|
|
// TabDialogBox for the browser corresponding to loadContext.
|
|
function getTabDialogBoxForLoadContext(loadContext) {
|
|
let tabBrowser = loadContext?.topFrameElement?.getTabBrowser();
|
|
if (!tabBrowser) {
|
|
return null;
|
|
}
|
|
for (let browser of tabBrowser.browsers) {
|
|
if (browser.browserId == loadContext.top?.browserId) {
|
|
return tabBrowser.getTabDialogBox(browser);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
ClientAuthDialogService.prototype = {
|
|
classID: Components.ID("{d7d2490d-2640-411b-9f09-a538803c11ee}"),
|
|
QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),
|
|
|
|
chooseCertificate: function ClientAuthDialogService_chooseCertificate(
|
|
hostname,
|
|
certArray,
|
|
loadContext,
|
|
caNames,
|
|
callback
|
|
) {
|
|
// On Android, the OS implements the prompt. However, we have to plumb the
|
|
// relevant information through to the frontend, which will return the
|
|
// alias of the certificate, or null if none was selected.
|
|
if (AppConstants.platform == "android") {
|
|
const prompt = new lazy.GeckoViewPrompter(
|
|
loadContext.topFrameElement.ownerGlobal
|
|
);
|
|
let issuers = null;
|
|
if (caNames.length) {
|
|
issuers = [];
|
|
let decoder = new TextDecoder();
|
|
for (let caName of caNames) {
|
|
issuers.push(btoa(decoder.decode(caName)));
|
|
}
|
|
}
|
|
prompt.asyncShowPrompt(
|
|
{ type: "certificate", host: hostname, issuers },
|
|
result => {
|
|
let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
|
|
Ci.nsIX509CertDB
|
|
);
|
|
let certificate = null;
|
|
if (result.alias) {
|
|
try {
|
|
certificate = certDB.getAndroidCertificateFromAlias(result.alias);
|
|
} catch (e) {
|
|
console.error("couldn't get certificate from alias", e);
|
|
}
|
|
}
|
|
// The UI provided by the OS has no option to choose how long to
|
|
// remember the decision. The most broadly useful default is to
|
|
// remember the decision for the session.
|
|
callback.certificateChosen(
|
|
certificate,
|
|
Ci.nsIClientAuthRememberService.Session
|
|
);
|
|
}
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
const clientAuthAskURI = "chrome://pippki/content/clientauthask.xhtml";
|
|
let retVals = {
|
|
cert: null,
|
|
rememberDuration: Ci.nsIClientAuthRememberService.Session,
|
|
};
|
|
let args = lazy.PromptUtils.objectToPropBag({
|
|
hostname,
|
|
certArray,
|
|
retVals,
|
|
});
|
|
|
|
// First attempt to find a TabDialogBox for the loadContext. This allows
|
|
// for a tab-modal dialog specific to the tab causing the load, which is a
|
|
// better user experience.
|
|
let tabDialogBox = getTabDialogBoxForLoadContext(loadContext);
|
|
if (tabDialogBox) {
|
|
tabDialogBox.open(clientAuthAskURI, {}, args).closedPromise.then(() => {
|
|
callback.certificateChosen(retVals.cert, retVals.rememberDuration);
|
|
});
|
|
return;
|
|
}
|
|
// Otherwise, attempt to open a window-modal dialog on the window that at
|
|
// least has the tab the load is occurring in.
|
|
let browserWindow = loadContext?.topFrameElement?.ownerGlobal;
|
|
// Failing that, open a window-modal dialog on the most recent window.
|
|
if (!browserWindow?.gDialogBox) {
|
|
browserWindow = Services.wm.getMostRecentBrowserWindow();
|
|
}
|
|
|
|
if (browserWindow?.gDialogBox) {
|
|
browserWindow.gDialogBox.open(clientAuthAskURI, args).then(() => {
|
|
callback.certificateChosen(retVals.cert, retVals.rememberDuration);
|
|
});
|
|
return;
|
|
}
|
|
|
|
let mostRecentWindow = Services.wm.getMostRecentWindow("");
|
|
Services.ww.openWindow(
|
|
mostRecentWindow,
|
|
clientAuthAskURI,
|
|
"_blank",
|
|
"centerscreen,chrome,modal,titlebar",
|
|
args
|
|
);
|
|
callback.certificateChosen(retVals.cert, retVals.rememberDuration);
|
|
},
|
|
};
|