ClientAuthDialogService.sys.mjs (4859B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // ClientAuthDialogService implements nsIClientAuthDialogService, and aims to 6 // open a dialog asking the user to select a client authentication certificate. 7 // Ideally the dialog will be tab-modal to the tab corresponding to the load 8 // that resulted in the request for the client authentication certificate. 9 export function ClientAuthDialogService() {} 10 11 const lazy = {}; 12 13 ChromeUtils.defineESModuleGetters(lazy, { 14 PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs", 15 }); 16 17 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; 18 19 if (AppConstants.platform == "android") { 20 ChromeUtils.defineESModuleGetters(lazy, { 21 GeckoViewPrompter: "resource://gre/modules/GeckoViewPrompter.sys.mjs", 22 }); 23 } 24 25 // Given a loadContext (CanonicalBrowsingContext), attempts to return a 26 // TabDialogBox for the browser corresponding to loadContext. 27 function getTabDialogBoxForLoadContext(loadContext) { 28 let tabBrowser = loadContext?.topFrameElement?.getTabBrowser(); 29 if (!tabBrowser) { 30 return null; 31 } 32 for (let browser of tabBrowser.browsers) { 33 if (browser.browserId == loadContext.top?.browserId) { 34 return tabBrowser.getTabDialogBox(browser); 35 } 36 } 37 return null; 38 } 39 40 ClientAuthDialogService.prototype = { 41 classID: Components.ID("{d7d2490d-2640-411b-9f09-a538803c11ee}"), 42 QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]), 43 44 chooseCertificate: function ClientAuthDialogService_chooseCertificate( 45 hostname, 46 certArray, 47 loadContext, 48 caNames, 49 callback 50 ) { 51 // On Android, the OS implements the prompt. However, we have to plumb the 52 // relevant information through to the frontend, which will return the 53 // alias of the certificate, or null if none was selected. 54 if (AppConstants.platform == "android") { 55 const prompt = new lazy.GeckoViewPrompter( 56 loadContext.topFrameElement.ownerGlobal 57 ); 58 let issuers = null; 59 if (caNames.length) { 60 issuers = []; 61 for (let caName of caNames) { 62 issuers.push(btoa(caName.map(b => String.fromCharCode(b)).join(""))); 63 } 64 } 65 prompt.asyncShowPrompt( 66 { type: "certificate", host: hostname, issuers }, 67 result => { 68 let certDB = Cc["@mozilla.org/security/x509certdb;1"].getService( 69 Ci.nsIX509CertDB 70 ); 71 let certificate = null; 72 if (result.alias) { 73 try { 74 certificate = certDB.getAndroidCertificateFromAlias(result.alias); 75 } catch (e) { 76 console.error("couldn't get certificate from alias", e); 77 } 78 } 79 // The UI provided by the OS has no option to choose how long to 80 // remember the decision. The most broadly useful default is to 81 // remember the decision for the session. 82 callback.certificateChosen( 83 certificate, 84 Ci.nsIClientAuthRememberService.Session 85 ); 86 } 87 ); 88 89 return; 90 } 91 92 const clientAuthAskURI = "chrome://pippki/content/clientauthask.xhtml"; 93 let retVals = { 94 cert: null, 95 rememberDuration: Ci.nsIClientAuthRememberService.Session, 96 }; 97 let args = lazy.PromptUtils.objectToPropBag({ 98 hostname, 99 certArray, 100 retVals, 101 }); 102 103 // First attempt to find a TabDialogBox for the loadContext. This allows 104 // for a tab-modal dialog specific to the tab causing the load, which is a 105 // better user experience. 106 let tabDialogBox = getTabDialogBoxForLoadContext(loadContext); 107 if (tabDialogBox) { 108 tabDialogBox.open(clientAuthAskURI, {}, args).closedPromise.then(() => { 109 callback.certificateChosen(retVals.cert, retVals.rememberDuration); 110 }); 111 return; 112 } 113 // Otherwise, attempt to open a window-modal dialog on the window that at 114 // least has the tab the load is occurring in. 115 let browserWindow = loadContext?.topFrameElement?.ownerGlobal; 116 // Failing that, open a window-modal dialog on the most recent window. 117 if (!browserWindow?.gDialogBox) { 118 browserWindow = Services.wm.getMostRecentBrowserWindow(); 119 } 120 121 if (browserWindow?.gDialogBox) { 122 browserWindow.gDialogBox.open(clientAuthAskURI, args).then(() => { 123 callback.certificateChosen(retVals.cert, retVals.rememberDuration); 124 }); 125 return; 126 } 127 128 let mostRecentWindow = Services.wm.getMostRecentWindow(""); 129 Services.ww.openWindow( 130 mostRecentWindow, 131 clientAuthAskURI, 132 "_blank", 133 "centerscreen,chrome,modal,titlebar", 134 args 135 ); 136 callback.certificateChosen(retVals.cert, retVals.rememberDuration); 137 }, 138 };