Bug 641120 - Enhance PKCS #11 module load dialog. r=keeler

This changes does several things:
1. Changes some titles to include the word "driver" for better clarity.
2. Moves and cleans up the JS implementation of load_device.xul. Having a
   cleaner implementation in a separate file makes the code easier to discover
   and maintain.
3. Removes code that tries to show a special case message if a module was
   already loaded.
3A. The backend code doesn't provide distinction from this case and failure to
    add in general.
3B. The backend code would only return the error code being checked for if a
    blank module name was provided.
4. Adds tests.

MozReview-Commit-ID: 8BxKWKw5rvp

--HG--
extra : rebase_source : 15a29bf7d46f523a11eac37c9f0c6efb2b5d0114
This commit is contained in:
Cykesiopka 2017-04-18 22:18:53 +08:00
Родитель b607e644b6
Коммит 150742ba74
9 изменённых файлов: 267 добавлений и 45 удалений

Просмотреть файл

@ -243,7 +243,6 @@ PKCS12UnknownErr=The PKCS #12 operation failed for unknown reasons.
PKCS12InfoNoSmartcardBackup=It is not possible to back up certificates from a hardware security device such as a smart card.
PKCS12DupData=The certificate and private key already exist on the security device.
AddModuleFailure=Unable to add module
AddModuleDup=Security Module already exists
DelModuleWarning=Are you sure you want to delete this security module?
DelModuleError=Unable to delete module
AVATemplate=%S = %S

Просмотреть файл

@ -33,4 +33,4 @@
<!ENTITY loaddevice.browse "Browse…">
<!ENTITY loaddevice.browse.accesskey "B">
<!ENTITY loaddevice.title "Load PKCS#11 Device">
<!ENTITY loaddevice.title2 "Load PKCS#11 Device Driver">

Просмотреть файл

@ -129,7 +129,7 @@ pw_not_wanted=Warning! You have decided not to use a Master Password.
pw_empty_warning=Your stored web and email passwords, form data, and private keys will not be protected.
pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
login_failed=Failed to Login
loadPK11TokenDialog=Choose a PKCS#11 device to load
loadPK11ModuleFilePickerTitle=Choose a PKCS#11 device driver to load
devinfo_modname=Module
devinfo_modpath=Path
devinfo_label=Label

Просмотреть файл

@ -3,8 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const nsIFilePicker = Components.interfaces.nsIFilePicker;
const nsFilePicker = "@mozilla.org/filepicker;1";
const nsIPKCS11Slot = Components.interfaces.nsIPKCS11Slot;
const nsIPKCS11Module = Components.interfaces.nsIPKCS11Module;
const nsPKCS11ModuleDB = "@mozilla.org/security/pkcs11moduledb;1";
@ -398,37 +396,6 @@ function changePassword() {
enableButtons();
}
// browse fs for PKCS#11 device
function doBrowseFiles() {
var srbundle = document.getElementById("pippki_bundle");
var fp = Components.classes[nsFilePicker].createInstance(nsIFilePicker);
fp.init(window,
srbundle.getString("loadPK11TokenDialog"),
nsIFilePicker.modeOpen);
fp.appendFilters(nsIFilePicker.filterAll);
if (fp.show() == nsIFilePicker.returnOK) {
var pathbox = document.getElementById("device_path");
pathbox.setAttribute("value", fp.file.path);
}
}
function doLoadDevice() {
var name_box = document.getElementById("device_name");
var path_box = document.getElementById("device_path");
try {
getPKCS11().addModule(name_box.value, path_box.value, 0, 0);
} catch (e) {
if (e.result == Components.results.NS_ERROR_ILLEGAL_VALUE) {
doPrompt(getNSSString("AddModuleDup"));
} else {
doPrompt(getNSSString("AddModuleFailure"));
}
return false;
}
return true;
}
// ------------------------------------- Old code
function showTokenInfo() {

Просмотреть файл

@ -0,0 +1,52 @@
/* 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/. */
/* import-globals-from pippki.js */
"use strict";
/**
* @file Implements the functionality of load_device.xul: a dialog that allows
* a PKCS #11 module to be loaded into Firefox.
*/
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
function onBrowseBtnPress() {
let bundle = document.getElementById("pippki_bundle");
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, bundle.getString("loadPK11ModuleFilePickerTitle"),
Ci.nsIFilePicker.modeOpen);
fp.appendFilters(Ci.nsIFilePicker.filterAll);
fp.open(rv => {
if (rv == Ci.nsIFilePicker.returnOK) {
document.getElementById("device_path").value = fp.file.path;
}
// This notification gets sent solely for test purposes. It should not be
// used by production code.
Services.obs.notifyObservers(window, "LoadPKCS11Module:FilePickHandled");
});
}
/**
* ondialogaccept() handler.
*
* @returns {Boolean} true to make the dialog close, false otherwise.
*/
function onDialogAccept() {
let bundle = document.getElementById("pipnss_bundle");
let nameBox = document.getElementById("device_name");
let pathBox = document.getElementById("device_path");
let pkcs11 = Cc["@mozilla.org/security/pkcs11;1"].getService(Ci.nsIPKCS11);
try {
pkcs11.addModule(nameBox.value, pathBox.value, 0, 0);
} catch (e) {
alertPromptService(null, bundle.getString("AddModuleFailure"));
return false;
}
return true;
}

Просмотреть файл

@ -8,22 +8,22 @@
<!DOCTYPE dialog [
<!ENTITY % deviceManangerDTD SYSTEM "chrome://pippki/locale/deviceManager.dtd">
%deviceManangerDTD;
<!ENTITY % pippkiDTD SYSTEM "chrome://pippki/locale/pippki.dtd" >
%pippkiDTD;
]>
<dialog id="loaddevice"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&loaddevice.title;"
<dialog id="loaddevice"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="&loaddevice.title2;"
buttons="accept,cancel"
ondialogaccept="return doLoadDevice();">
ondialogaccept="return onDialogAccept();">
<stringbundleset id="stringbundleset">
<stringbundle id="pippki_bundle" src="chrome://pippki/locale/pippki.properties"/>
<stringbundle id="pipnss_bundle" src="chrome://pipnss/locale/pipnss.properties"/>
</stringbundleset>
<script type="application/javascript" src="chrome://pippki/content/device_manager.js"/>
<script type="application/javascript" src="chrome://pippki/content/pippki.js"/>
<script type="application/javascript"
src="chrome://pippki/content/load_device.js"/>
<description>&loaddevice.info;</description>
<hbox align="center">
@ -35,8 +35,9 @@
<label value="&loaddevice.filename;" accesskey="&loaddevice.filename.accesskey;"
control="device_path"/>
<textbox id="device_path" flex="1"/>
<button label="&loaddevice.browse;" flex="1"
accesskey="&loaddevice.browse.accesskey;" oncommand="doBrowseFiles();"/>
<button id="browse" label="&loaddevice.browse;" flex="1"
accesskey="&loaddevice.browse.accesskey;"
oncommand="onBrowseBtnPress();"/>
</hbox>
</dialog>

Просмотреть файл

@ -32,6 +32,7 @@ pippki.jar:
content/pippki/editcacert.xul (content/editcacert.xul)
content/pippki/exceptionDialog.js (content/exceptionDialog.js)
* content/pippki/exceptionDialog.xul (content/exceptionDialog.xul)
content/pippki/load_device.js (content/load_device.js)
content/pippki/load_device.xul (content/load_device.xul)
content/pippki/pippki.js (content/pippki.js)
content/pippki/protectedAuth.js (content/protectedAuth.js)

Просмотреть файл

@ -16,3 +16,4 @@ support-files =
# failures, almost entirely on Linux. See Bug 1309519.
skip-if = os == "linux"
[browser_exportP12_passwordUI.js]
[browser_loadPKCS11Module_ui.js]

Просмотреть файл

@ -0,0 +1,201 @@
// 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 } =
Cu.import("resource://testing-common/MockRegistrar.jsm", {});
const gMockPKCS11 = {
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`);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPKCS11])
};
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: XPCOMUtils.generateQI([Ci.nsIPromptService])
};
var gMockPKCS11CID =
MockRegistrar.register("@mozilla.org/security/pkcs11;1",
gMockPKCS11);
var gMockPromptServiceCID =
MockRegistrar.register("@mozilla.org/embedcomp/prompt-service;1",
gMockPromptService);
var gMockFilePicker = SpecialPowers.MockFilePicker;
gMockFilePicker.init(window);
var gTempFile = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties)
.get("TmpD", Ci.nsIFile);
gTempFile.append("browser_loadPKCS11Module_ui-fakeModule");
registerCleanupFunction(() => {
gMockFilePicker.cleanup();
MockRegistrar.unregister(gMockPKCS11CID);
MockRegistrar.unregister(gMockPromptServiceCID);
});
function resetCallCounts() {
gMockPKCS11.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.xul", "", "");
return new Promise(resolve => {
win.addEventListener("load", function() {
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();
gMockPKCS11.expectedLibPath = gTempFile.path;
gMockPKCS11.expectedModuleName = "test module";
gMockPKCS11.throwOnAddModule = throwOnAddModule;
win.document.getElementById("device_name").value =
gMockPKCS11.expectedModuleName;
win.document.getElementById("device_path").value =
gMockPKCS11.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(gMockPKCS11.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;
testAddModuleHelper(win, true);
// 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(gMockPKCS11.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(gMockPKCS11.addModuleCallCount, 0,
"addModule() should never have been called");
Assert.equal(gMockPromptService.alertCallCount, 0,
"alert() should never have been called");
await BrowserTestUtils.windowClosed(win);
});