/* 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/. */ "use strict"; ChromeUtils.defineESModuleGetters(this, { NativeManifests: "resource://gre/modules/NativeManifests.sys.mjs", ctypes: "resource://gre/modules/ctypes.sys.mjs", }); XPCOMUtils.defineLazyServiceGetter( this, "pkcs11db", "@mozilla.org/security/pkcs11moduledb;1", "nsIPKCS11ModuleDB" ); // eslint-disable-next-line mozilla/reject-importGlobalProperties Cu.importGlobalProperties(["PathUtils"]); var { DefaultMap } = ExtensionUtils; const findModuleByPath = function (path) { for (let module of pkcs11db.listModules()) { if (module && module.libName === path) { return module; } } return null; }; this.pkcs11 = class extends ExtensionAPI { getAPI(context) { let manifestCache = new DefaultMap(async name => { let hostInfo = await NativeManifests.lookupManifest( "pkcs11", name, context ); if (hostInfo) { // We don't normalize the absolute path below because // `Path.normalize` throws when the target file doesn't // exist, and that might be the case on non Windows // builds. let absolutePath = PathUtils.isAbsolute(hostInfo.manifest.path) ? hostInfo.manifest.path : PathUtils.joinRelative( PathUtils.parent(hostInfo.path), hostInfo.manifest.path ); if (AppConstants.platform === "win") { // On Windows, `hostInfo.manifest.path` is expected to be a normalized // absolute path. On other platforms, this path may be relative but we // cannot use `PathUtils.normalize()` on non-absolute paths. absolutePath = PathUtils.normalize(absolutePath); hostInfo.manifest.path = absolutePath; } // PathUtils.filename throws if the path is not an absolute path. // The result is expected to be the basename of the file (without // the dir path and the extension) so it is fine to use an absolute // path that may not be normalized (non-Windows platforms). let manifestLib = PathUtils.filename(absolutePath); if (AppConstants.platform !== "linux") { manifestLib = manifestLib.toLowerCase(manifestLib); } if ( manifestLib !== ctypes.libraryName("nssckbi") && manifestLib !== ctypes.libraryName("osclientcerts") && manifestLib !== ctypes.libraryName("ipcclientcerts") ) { return hostInfo.manifest; } } return Promise.reject({ message: `No such PKCS#11 module ${name}` }); }); return { pkcs11: { /** * Verify whether a given PKCS#11 module is installed. * * @param {string} name The name of the module, as specified in * the manifest file. * @returns {Promise} A Promise that resolves to true if the package * is installed, or false if it is not. May be * rejected if the module could not be found. */ async isModuleInstalled(name) { let manifest = await manifestCache.get(name); return findModuleByPath(manifest.path) !== null; }, /** * Install a PKCS#11 module * * @param {string} name The name of the module, as specified in * the manifest file. * @param {integer} [flags = 0] Any flags to be passed on to the * nsIPKCS11ModuleDB.addModule method * @returns {Promise} When the Promise resolves, the module will have * been installed. When it is rejected, the module * either is already installed or could not be * installed for some reason. */ async installModule(name, flags = 0) { let manifest = await manifestCache.get(name); if (!manifest.description) { return Promise.reject({ message: `The description field in the manifest for PKCS#11 module ${name} must have a value`, }); } pkcs11db.addModule(manifest.description, manifest.path, flags, 0); }, /** * Uninstall a PKCS#11 module * * @param {string} name The name of the module, as specified in * the manifest file. * @returns {Promise}. When the Promise resolves, the module will have * been uninstalled. When it is rejected, the * module either was not installed or could not be * uninstalled for some reason. */ async uninstallModule(name) { let manifest = await manifestCache.get(name); let module = findModuleByPath(manifest.path); if (!module) { return Promise.reject({ message: `The PKCS#11 module ${name} is not loaded`, }); } pkcs11db.deleteModule(module.name); }, /** * Get a list of slots for a given PKCS#11 module, with * information on the token (if any) in the slot. * * The PKCS#11 standard defines slots as an abstract concept * that may or may not have at most one token. In practice, when * using PKCS#11 for smartcards (the most likely use case of * PKCS#11 for Firefox), a slot corresponds to a cardreader, and * a token corresponds to a card. * * @param {string} name The name of the PKCS#11 module, as * specified in the manifest file. * @returns {Promise} A promise that resolves to an array of objects * with two properties. The `name` object contains * the name of the slot; the `token` object is null * if there is no token in the slot, or is an object * describing various properties of the token if * there is. */ async getModuleSlots(name) { let manifest = await manifestCache.get(name); let module = findModuleByPath(manifest.path); if (!module) { return Promise.reject({ message: `The module ${name} is not installed`, }); } let rv = []; for (let slot of module.listSlots()) { let token = slot.getToken(); let slotobj = { name: slot.name, token: null, }; if (slot.status != 1 /* SLOT_NOT_PRESENT */) { slotobj.token = { name: token.tokenName, manufacturer: token.tokenManID, HWVersion: token.tokenHWVersion, FWVersion: token.tokenFWVersion, serial: token.tokenSerialNumber, isLoggedIn: token.isLoggedIn(), }; } rv.push(slotobj); } return rv; }, }, }; } };