Files
tubestation/browser/extensions/formautofill/MasterPassword.jsm
2017-08-15 18:42:01 -07:00

173 lines
5.7 KiB
JavaScript

/* 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/. */
/**
* Helpers for the Master Password Dialog.
* In the future the Master Password implementation may move here.
*/
"use strict";
this.EXPORTED_SYMBOLS = [
"MasterPassword",
];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cryptoSDR",
"@mozilla.org/login-manager/crypto/SDR;1",
Ci.nsILoginManagerCrypto);
this.MasterPassword = {
get _token() {
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
return tokendb.getInternalKeyToken();
},
/**
* @returns {boolean} True if a master password is set and false otherwise.
*/
get isEnabled() {
return this._token.hasPassword;
},
/**
* Display the master password login prompt no matter it's logged in or not.
* If an existing MP prompt is already open, the result from it will be used instead.
*
* @returns {Promise<boolean>} True if it's logged in or no password is set and false
* if it's still not logged in (prompt canceled or other error).
*/
async prompt() {
if (!this.isEnabled) {
return true;
}
// If a prompt is already showing then wait for and focus it.
if (Services.logins.uiBusy) {
return this.waitForExistingDialog();
}
let token = this._token;
try {
// 'true' means always prompt for token password. User will be prompted until
// clicking 'Cancel' or entering the correct password.
token.login(true);
} catch (e) {
// An exception will be thrown if the user cancels the login prompt dialog.
// User is also logged out.
}
// If we triggered a master password prompt, notify observers.
if (token.isLoggedIn()) {
Services.obs.notifyObservers(null, "passwordmgr-crypto-login");
} else {
Services.obs.notifyObservers(null, "passwordmgr-crypto-loginCanceled");
}
return token.isLoggedIn();
},
/**
* Decrypts cipherText.
*
* @param {string} cipherText Encrypted string including the algorithm details.
* @param {boolean} reauth True if we want to force the prompt to show up
* even if the user is already logged in.
* @returns {Promise<string>} resolves to the decrypted string, or rejects otherwise.
*/
async decrypt(cipherText, reauth = false) {
let loggedIn = false;
if (reauth) {
loggedIn = await this.prompt();
} else {
loggedIn = await this.waitForExistingDialog();
}
if (!loggedIn) {
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
}
return cryptoSDR.decrypt(cipherText);
},
/**
* Encrypts a string and returns cipher text containing algorithm information used for decryption.
*
* @param {string} plainText Original string without encryption.
* @returns {Promise<string>} resolves to the encrypted string (with algorithm), otherwise rejects.
*/
async encrypt(plainText) {
if (Services.logins.uiBusy && !await this.waitForExistingDialog()) {
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
}
return cryptoSDR.encrypt(plainText);
},
/**
* Resolve when master password dialogs are closed, immediately if none are open.
*
* An existing MP dialog will be focused and will request attention.
*
* @returns {Promise<boolean>}
* Resolves with whether the user is logged in to MP.
*/
async waitForExistingDialog() {
if (!Services.logins.uiBusy) {
log.debug("waitForExistingDialog: Dialog isn't showing. isLoggedIn:",
Services.logins.isLoggedIn);
return Services.logins.isLoggedIn;
}
return new Promise((resolve) => {
log.debug("waitForExistingDialog: Observing the open dialog");
let observer = {
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
]),
observe(subject, topic, data) {
log.debug("waitForExistingDialog: Got notification:", topic);
// Only run observer once.
Services.obs.removeObserver(this, "passwordmgr-crypto-login");
Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
if (topic == "passwordmgr-crypto-loginCanceled") {
resolve(false);
return;
}
resolve(true);
},
};
// Possible leak: it's possible that neither of these notifications
// will fire, and if that happens, we'll leak the observer (and
// never return). We should guarantee that at least one of these
// will fire.
// See bug XXX.
Services.obs.addObserver(observer, "passwordmgr-crypto-login");
Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled");
// Focus and draw attention to the existing master password dialog for the
// occassions where it's not attached to the current window.
let promptWin = Services.wm.getMostRecentWindow("prompt:promptPassword");
promptWin.focus();
promptWin.getAttention();
});
},
};
XPCOMUtils.defineLazyGetter(this, "log", () => {
let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
return new ConsoleAPI({
maxLogLevelPref: "masterPassword.loglevel",
prefix: "Master Password",
});
});