// Any copyright is dedicated to the Public Domain. // http://creativecommons.org/publicdomain/zero/1.0/ "use strict"; // Tests the dialog used for loading PKCS #11 modules. const { MockRegistrar } = ChromeUtils.importESModule( "resource://testing-common/MockRegistrar.sys.mjs" ); const gMockPKCS11ModuleDB = { addModuleCallCount: 0, expectedLibPath: "", expectedModuleName: "", throwOnAddModule: false, addModule(moduleName, libraryFullPath, cryptoMechanismFlags, cipherFlags) { this.addModuleCallCount++; Assert.equal( moduleName, this.expectedModuleName, "addModule: Name given should be what's in the name textbox" ); Assert.equal( libraryFullPath, this.expectedLibPath, "addModule: Path given should be what's in the path textbox" ); Assert.equal( cryptoMechanismFlags, 0, "addModule: No crypto mechanism flags should be passed" ); Assert.equal(cipherFlags, 0, "addModule: No cipher flags should be passed"); if (this.throwOnAddModule) { throw new Error(`addModule: Throwing exception`); } }, deleteModule(moduleName) { Assert.ok(false, `deleteModule: should not be called`); }, getInternal() { throw new Error("not expecting getInternal() to be called"); }, getInternalFIPS() { throw new Error("not expecting getInternalFIPS() to be called"); }, listModules() { throw new Error("not expecting listModules() to be called"); }, get canToggleFIPS() { throw new Error("not expecting get canToggleFIPS() to be called"); }, toggleFIPSMode() { throw new Error("not expecting toggleFIPSMode() to be called"); }, get isFIPSEnabled() { throw new Error("not expecting get isFIPSEnabled() to be called"); }, QueryInterface: ChromeUtils.generateQI(["nsIPKCS11ModuleDB"]), }; const gMockPromptService = { alertCallCount: 0, expectedText: "", expectedWindow: null, alert(parent, dialogTitle, text) { this.alertCallCount++; Assert.equal( parent, this.expectedWindow, "alert: Parent should be expected window" ); Assert.equal(dialogTitle, null, "alert: Title should be null"); Assert.equal( text, this.expectedText, "alert: Actual and expected text should match" ); }, QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]), }; var gMockPKCS11CID = MockRegistrar.register( "@mozilla.org/security/pkcs11moduledb;1", gMockPKCS11ModuleDB ); var gMockPromptServiceCID = MockRegistrar.register( "@mozilla.org/prompter;1", gMockPromptService ); var gMockFilePicker = SpecialPowers.MockFilePicker; gMockFilePicker.init(window); var gTempFile = Services.dirsvc.get("TmpD", Ci.nsIFile); gTempFile.append("browser_loadPKCS11Module_ui-fakeModule"); registerCleanupFunction(() => { gMockFilePicker.cleanup(); MockRegistrar.unregister(gMockPKCS11CID); MockRegistrar.unregister(gMockPromptServiceCID); }); function resetCallCounts() { gMockPKCS11ModuleDB.addModuleCallCount = 0; gMockPromptService.alertCallCount = 0; } /** * Opens the dialog shown to load a PKCS #11 module. * * @returns {Promise} * A promise that resolves when the dialog has finished loading, with * the window of the opened dialog. */ function openLoadModuleDialog() { let win = window.openDialog( "chrome://pippki/content/load_device.xhtml", "", "" ); return new Promise(resolve => { win.addEventListener( "load", function () { executeSoon(() => resolve(win)); }, { once: true } ); }); } /** * Presses the browse button and simulates interacting with the file picker that * should be triggered. * * @param {window} win * The dialog window. * @param {boolean} cancel * If true, the file picker is canceled. If false, gTempFile is chosen in * the file picker and the file picker is accepted. */ async function browseToTempFile(win, cancel) { gMockFilePicker.showCallback = () => { gMockFilePicker.setFiles([gTempFile]); if (cancel) { info("MockFilePicker returning cancel"); return Ci.nsIFilePicker.returnCancel; } info("MockFilePicker returning OK"); return Ci.nsIFilePicker.returnOK; }; info("Pressing browse button"); win.document.getElementById("browse").doCommand(); await TestUtils.topicObserved("LoadPKCS11Module:FilePickHandled"); } add_task(async function testBrowseButton() { let win = await openLoadModuleDialog(); let pathBox = win.document.getElementById("device_path"); let originalPathBoxValue = "expected path if picker is canceled"; pathBox.value = originalPathBoxValue; // Test what happens if the file picker is canceled. await browseToTempFile(win, true); Assert.equal( pathBox.value, originalPathBoxValue, "Path shown should be unchanged due to canceled picker" ); // Test what happens if the file picker is not canceled. await browseToTempFile(win, false); Assert.equal( pathBox.value, gTempFile.path, "Path shown should be same as the one chosen in the file picker" ); await BrowserTestUtils.closeWindow(win); }); function testAddModuleHelper(win, throwOnAddModule) { resetCallCounts(); gMockPKCS11ModuleDB.expectedLibPath = gTempFile.path; gMockPKCS11ModuleDB.expectedModuleName = "test module"; gMockPKCS11ModuleDB.throwOnAddModule = throwOnAddModule; win.document.getElementById("device_name").value = gMockPKCS11ModuleDB.expectedModuleName; win.document.getElementById("device_path").value = gMockPKCS11ModuleDB.expectedLibPath; info("Accepting dialog"); win.document.getElementById("loaddevice").acceptDialog(); } add_task(async function testAddModuleSuccess() { let win = await openLoadModuleDialog(); testAddModuleHelper(win, false); await BrowserTestUtils.windowClosed(win); Assert.equal( gMockPKCS11ModuleDB.addModuleCallCount, 1, "addModule() should have been called once" ); Assert.equal( gMockPromptService.alertCallCount, 0, "alert() should never have been called" ); }); add_task(async function testAddModuleFailure() { let win = await openLoadModuleDialog(); gMockPromptService.expectedText = "Unable to add module"; gMockPromptService.expectedWindow = win; // The exception we throw in addModule is first reported as an uncaught // exception by XPConnect before an exception is propagated to the actual // caller. expectUncaughtException(true); testAddModuleHelper(win, true); expectUncaughtException(false); // If adding a module fails, the dialog will not close. As such, we have to // close the window ourselves. await BrowserTestUtils.closeWindow(win); Assert.equal( gMockPKCS11ModuleDB.addModuleCallCount, 1, "addModule() should have been called once" ); Assert.equal( gMockPromptService.alertCallCount, 1, "alert() should have been called once" ); }); add_task(async function testCancel() { let win = await openLoadModuleDialog(); resetCallCounts(); info("Canceling dialog"); win.document.getElementById("loaddevice").cancelDialog(); Assert.equal( gMockPKCS11ModuleDB.addModuleCallCount, 0, "addModule() should never have been called" ); Assert.equal( gMockPromptService.alertCallCount, 0, "alert() should never have been called" ); await BrowserTestUtils.windowClosed(win); }); async function testModuleNameHelper(moduleName, acceptButtonShouldBeDisabled) { let win = await openLoadModuleDialog(); resetCallCounts(); info(`Setting Module Name to '${moduleName}'`); let moduleNameBox = win.document.getElementById("device_name"); moduleNameBox.value = moduleName; // this makes this not a great test, but it's the easiest way to simulate this moduleNameBox.onchange(); let dialogNode = win.document.querySelector("dialog"); Assert.equal( dialogNode.getAttribute("buttondisabledaccept"), acceptButtonShouldBeDisabled ? "true" : "", // it's a string `dialog accept button should ${ acceptButtonShouldBeDisabled ? "" : "not " }be disabled` ); return BrowserTestUtils.closeWindow(win); } add_task(async function testEmptyModuleName() { await testModuleNameHelper("", true); }); add_task(async function testReservedModuleName() { await testModuleNameHelper("Root Certs", true); }); add_task(async function testAcceptableModuleName() { await testModuleNameHelper("Some Module Name", false); });