browser_loadPKCS11Module_ui.js (8532B)
1 // Any copyright is dedicated to the Public Domain. 2 // http://creativecommons.org/publicdomain/zero/1.0/ 3 "use strict"; 4 5 // Tests the dialog used for loading PKCS #11 modules. 6 7 const { MockRegistrar } = ChromeUtils.importESModule( 8 "resource://testing-common/MockRegistrar.sys.mjs" 9 ); 10 11 const gMockPKCS11ModuleDB = { 12 addModuleCallCount: 0, 13 expectedLibPath: "", 14 expectedModuleName: "", 15 throwOnAddModule: false, 16 17 addModule(moduleName, libraryFullPath, cryptoMechanismFlags, cipherFlags) { 18 this.addModuleCallCount++; 19 Assert.equal( 20 moduleName, 21 this.expectedModuleName, 22 "addModule: Name given should be what's in the name textbox" 23 ); 24 Assert.equal( 25 libraryFullPath, 26 this.expectedLibPath, 27 "addModule: Path given should be what's in the path textbox" 28 ); 29 Assert.equal( 30 cryptoMechanismFlags, 31 0, 32 "addModule: No crypto mechanism flags should be passed" 33 ); 34 Assert.equal(cipherFlags, 0, "addModule: No cipher flags should be passed"); 35 36 if (this.throwOnAddModule) { 37 throw new Error(`addModule: Throwing exception`); 38 } 39 }, 40 41 deleteModule() { 42 Assert.ok(false, `deleteModule: should not be called`); 43 }, 44 45 getInternal() { 46 throw new Error("not expecting getInternal() to be called"); 47 }, 48 49 getInternalFIPS() { 50 throw new Error("not expecting getInternalFIPS() to be called"); 51 }, 52 53 listModules() { 54 throw new Error("not expecting listModules() to be called"); 55 }, 56 57 get canToggleFIPS() { 58 throw new Error("not expecting get canToggleFIPS() to be called"); 59 }, 60 61 toggleFIPSMode() { 62 throw new Error("not expecting toggleFIPSMode() to be called"); 63 }, 64 65 get isFIPSEnabled() { 66 throw new Error("not expecting get isFIPSEnabled() to be called"); 67 }, 68 69 QueryInterface: ChromeUtils.generateQI(["nsIPKCS11ModuleDB"]), 70 }; 71 72 const gMockPromptService = { 73 alertCallCount: 0, 74 expectedText: "", 75 expectedWindow: null, 76 77 alert(parent, dialogTitle, text) { 78 this.alertCallCount++; 79 Assert.equal( 80 parent, 81 this.expectedWindow, 82 "alert: Parent should be expected window" 83 ); 84 Assert.equal(dialogTitle, null, "alert: Title should be null"); 85 Assert.equal( 86 text, 87 this.expectedText, 88 "alert: Actual and expected text should match" 89 ); 90 }, 91 92 QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]), 93 }; 94 95 var gMockPKCS11CID = MockRegistrar.register( 96 "@mozilla.org/security/pkcs11moduledb;1", 97 gMockPKCS11ModuleDB 98 ); 99 var gMockPromptServiceCID = MockRegistrar.register( 100 "@mozilla.org/prompter;1", 101 gMockPromptService 102 ); 103 104 var gMockFilePicker = SpecialPowers.MockFilePicker; 105 gMockFilePicker.init(window.browsingContext); 106 107 var gTempFile = Services.dirsvc.get("TmpD", Ci.nsIFile); 108 gTempFile.append("browser_loadPKCS11Module_ui-fakeModule"); 109 110 registerCleanupFunction(() => { 111 gMockFilePicker.cleanup(); 112 MockRegistrar.unregister(gMockPKCS11CID); 113 MockRegistrar.unregister(gMockPromptServiceCID); 114 }); 115 116 function resetCallCounts() { 117 gMockPKCS11ModuleDB.addModuleCallCount = 0; 118 gMockPromptService.alertCallCount = 0; 119 } 120 121 /** 122 * Opens the dialog shown to load a PKCS #11 module. 123 * 124 * @returns {Promise} 125 * A promise that resolves when the dialog has finished loading, with 126 * the window of the opened dialog. 127 */ 128 function openLoadModuleDialog() { 129 let win = window.openDialog( 130 "chrome://pippki/content/load_device.xhtml", 131 "", 132 "" 133 ); 134 return new Promise(resolve => { 135 win.addEventListener( 136 "load", 137 function () { 138 executeSoon(() => resolve(win)); 139 }, 140 { once: true } 141 ); 142 }); 143 } 144 145 /** 146 * Presses the browse button and simulates interacting with the file picker that 147 * should be triggered. 148 * 149 * @param {window} win 150 * The dialog window. 151 * @param {boolean} cancel 152 * If true, the file picker is canceled. If false, gTempFile is chosen in 153 * the file picker and the file picker is accepted. 154 */ 155 async function browseToTempFile(win, cancel) { 156 gMockFilePicker.showCallback = () => { 157 gMockFilePicker.setFiles([gTempFile]); 158 159 if (cancel) { 160 info("MockFilePicker returning cancel"); 161 return Ci.nsIFilePicker.returnCancel; 162 } 163 164 info("MockFilePicker returning OK"); 165 return Ci.nsIFilePicker.returnOK; 166 }; 167 168 info("Pressing browse button"); 169 win.document.getElementById("browse").doCommand(); 170 await TestUtils.topicObserved("LoadPKCS11Module:FilePickHandled"); 171 } 172 173 add_task(async function testBrowseButton() { 174 let win = await openLoadModuleDialog(); 175 let pathBox = win.document.getElementById("device_path"); 176 let originalPathBoxValue = "expected path if picker is canceled"; 177 pathBox.value = originalPathBoxValue; 178 179 // Test what happens if the file picker is canceled. 180 await browseToTempFile(win, true); 181 Assert.equal( 182 pathBox.value, 183 originalPathBoxValue, 184 "Path shown should be unchanged due to canceled picker" 185 ); 186 187 // Test what happens if the file picker is not canceled. 188 await browseToTempFile(win, false); 189 Assert.equal( 190 pathBox.value, 191 gTempFile.path, 192 "Path shown should be same as the one chosen in the file picker" 193 ); 194 195 await BrowserTestUtils.closeWindow(win); 196 }); 197 198 function testAddModuleHelper(win, throwOnAddModule) { 199 resetCallCounts(); 200 gMockPKCS11ModuleDB.expectedLibPath = gTempFile.path; 201 gMockPKCS11ModuleDB.expectedModuleName = "test module"; 202 gMockPKCS11ModuleDB.throwOnAddModule = throwOnAddModule; 203 204 win.document.getElementById("device_name").value = 205 gMockPKCS11ModuleDB.expectedModuleName; 206 win.document.getElementById("device_path").value = 207 gMockPKCS11ModuleDB.expectedLibPath; 208 209 info("Accepting dialog"); 210 win.document.getElementById("loaddevice").acceptDialog(); 211 } 212 213 add_task(async function testAddModuleSuccess() { 214 let win = await openLoadModuleDialog(); 215 216 testAddModuleHelper(win, false); 217 await BrowserTestUtils.windowClosed(win); 218 219 Assert.equal( 220 gMockPKCS11ModuleDB.addModuleCallCount, 221 1, 222 "addModule() should have been called once" 223 ); 224 Assert.equal( 225 gMockPromptService.alertCallCount, 226 0, 227 "alert() should never have been called" 228 ); 229 }); 230 231 add_task(async function testAddModuleFailure() { 232 let win = await openLoadModuleDialog(); 233 gMockPromptService.expectedText = "Unable to add module"; 234 gMockPromptService.expectedWindow = win; 235 236 // The exception we throw in addModule is first reported as an uncaught 237 // exception by XPConnect before an exception is propagated to the actual 238 // caller. 239 expectUncaughtException(true); 240 241 testAddModuleHelper(win, true); 242 expectUncaughtException(false); 243 // If adding a module fails, the dialog will not close. As such, we have to 244 // close the window ourselves. 245 await BrowserTestUtils.closeWindow(win); 246 247 Assert.equal( 248 gMockPKCS11ModuleDB.addModuleCallCount, 249 1, 250 "addModule() should have been called once" 251 ); 252 Assert.equal( 253 gMockPromptService.alertCallCount, 254 1, 255 "alert() should have been called once" 256 ); 257 }); 258 259 add_task(async function testCancel() { 260 let win = await openLoadModuleDialog(); 261 resetCallCounts(); 262 263 info("Canceling dialog"); 264 win.document.getElementById("loaddevice").cancelDialog(); 265 266 Assert.equal( 267 gMockPKCS11ModuleDB.addModuleCallCount, 268 0, 269 "addModule() should never have been called" 270 ); 271 Assert.equal( 272 gMockPromptService.alertCallCount, 273 0, 274 "alert() should never have been called" 275 ); 276 277 await BrowserTestUtils.windowClosed(win); 278 }); 279 280 async function testModuleNameHelper(moduleName, acceptButtonShouldBeDisabled) { 281 let win = await openLoadModuleDialog(); 282 resetCallCounts(); 283 284 info(`Setting Module Name to '${moduleName}'`); 285 let moduleNameBox = win.document.getElementById("device_name"); 286 moduleNameBox.value = moduleName; 287 // this makes this not a great test, but it's the easiest way to simulate this 288 moduleNameBox.dispatchEvent(new Event("change", { bubbles: true })); 289 290 let dialogNode = win.document.querySelector("dialog"); 291 Assert.equal( 292 dialogNode.hasAttribute("buttondisabledaccept"), 293 acceptButtonShouldBeDisabled, 294 `dialog accept button should ${ 295 acceptButtonShouldBeDisabled ? "" : "not " 296 }be disabled` 297 ); 298 299 return BrowserTestUtils.closeWindow(win); 300 } 301 302 add_task(async function testEmptyModuleName() { 303 await testModuleNameHelper("", true); 304 }); 305 306 add_task(async function testReservedModuleName() { 307 await testModuleNameHelper("Root Certs", true); 308 }); 309 310 add_task(async function testAcceptableModuleName() { 311 await testModuleNameHelper("Some Module Name", false); 312 });