Backed out 4 changesets (bug 1486954) for hangs on Linux. a=backout
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Thu, 18 Oct 2018 12:40:21 +0300
changeset 490142 831b8aa6281e5e200d92be09a519d17f9367f5ca
parent 490136 bd60f5f2f402f09dca1f2178a07a8b544d0763d1
child 490219 e7d5d82e766d73f37d7f477962459609722b96dc
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersbackout
bugs1486954
milestone64.0a1
backs outc895888bdddc97e36675e49844e7c115e0d6cc12
27e9286503e8bfbb61ed2edfc374257b728fec6b
87e64652386dd1fc08ebcc4ba7f1405805de20a1
96a6e1ceb6977a32bc6a29f46f5a47622bfc4d50
Backed out 4 changesets (bug 1486954) for hangs on Linux. a=backout Backed out changeset c895888bdddc (bug 1486954) Backed out changeset 27e9286503e8 (bug 1486954) Backed out changeset 87e64652386d (bug 1486954) Backed out changeset 96a6e1ceb697 (bug 1486954)
browser/components/payments/content/paymentDialogWrapper.js
browser/components/payments/res/debugging.js
browser/extensions/formautofill/FormAutofillParent.jsm
browser/extensions/formautofill/FormAutofillStorage.jsm
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/MasterPassword.jsm
browser/extensions/formautofill/OSKeyStore.jsm
browser/extensions/formautofill/content/manageCreditCards.xhtml
browser/extensions/formautofill/content/manageDialog.js
browser/extensions/formautofill/locales/en-US/formautofill.properties
browser/extensions/formautofill/moz.build
browser/extensions/formautofill/test/browser/browser.ini
browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
browser/extensions/formautofill/test/browser/browser_creditCard_fill_cancel_login.js
browser/extensions/formautofill/test/browser/browser_creditCard_fill_master_password.js
browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
browser/extensions/formautofill/test/browser/head.js
browser/extensions/formautofill/test/fixtures/OSKeyStoreTestUtils.jsm
browser/extensions/formautofill/test/mochitest/formautofill_common.js
browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
browser/extensions/formautofill/test/mochitest/test_clear_form.html
browser/extensions/formautofill/test/unit/head.js
browser/extensions/formautofill/test/unit/test_autofillFormFields.js
browser/extensions/formautofill/test/unit/test_creditCardRecords.js
browser/extensions/formautofill/test/unit/test_getRecords.js
browser/extensions/formautofill/test/unit/test_masterPassword.js
browser/extensions/formautofill/test/unit/test_migrateRecords.js
browser/extensions/formautofill/test/unit/test_osKeyStore.js
browser/extensions/formautofill/test/unit/test_reconcile.js
browser/extensions/formautofill/test/unit/xpcshell.ini
security/manager/ssl/nsIOSKeyStore.idl
services/sync/tps/extensions/tps/resource/modules/formautofill.jsm
toolkit/modules/CreditCard.jsm
toolkit/modules/tests/browser/browser_CreditCard.js
--- a/browser/components/payments/content/paymentDialogWrapper.js
+++ b/browser/components/payments/content/paymentDialogWrapper.js
@@ -11,18 +11,18 @@
 
 const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
                      .getService(Ci.nsIPaymentRequestService);
 
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "OSKeyStore",
-                               "resource://formautofill/OSKeyStore.jsm");
+ChromeUtils.defineModuleGetter(this, "MasterPassword",
+                               "resource://formautofill/MasterPassword.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "formAutofillStorage", () => {
   let storage;
   try {
     storage = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {})
                          .formAutofillStorage;
@@ -144,30 +144,30 @@ var paymentDialogWrapper = {
     });
 
     return address;
   },
 
   /**
    * @param {string} guid The GUID of the basic card record from storage.
    * @param {string} cardSecurityCode The associated card security code (CVV/CCV/etc.)
-   * @throws If there is an error decrypting
+   * @throws if the user cancels entering their master password or an error decrypting
    * @returns {nsIBasicCardResponseData?} returns response data or null (if the
    *                                      master password dialog was cancelled);
    */
   async _convertProfileBasicCardToPaymentMethodData(guid, cardSecurityCode) {
     let cardData = this.temporaryStore.creditCards.get(guid) ||
                    await formAutofillStorage.creditCards.get(guid);
     if (!cardData) {
       throw new Error(`Basic card not found in storage: ${guid}`);
     }
 
     let cardNumber;
     try {
-      cardNumber = await OSKeyStore.decrypt(cardData["cc-number-encrypted"], true);
+      cardNumber = await MasterPassword.decrypt(cardData["cc-number-encrypted"], true);
     } catch (ex) {
       if (ex.result != Cr.NS_ERROR_ABORT) {
         throw ex;
       }
       // User canceled master password entry
       return null;
     }
 
@@ -498,26 +498,18 @@ var paymentDialogWrapper = {
     window.close();
   },
 
   async onPay({
     selectedPayerAddressGUID: payerGUID,
     selectedPaymentCardGUID: paymentCardGUID,
     selectedPaymentCardSecurityCode: cardSecurityCode,
   }) {
-    let methodData;
-    try {
-      methodData = await this._convertProfileBasicCardToPaymentMethodData(paymentCardGUID,
-                                                                          cardSecurityCode);
-    } catch (ex) {
-      // TODO (Bug 1498403): Some kind of "credit card storage error" here, perhaps asking user
-      // to re-enter credit card # from management UI.
-      Cu.reportError(ex);
-      return;
-    }
+    let methodData = await this._convertProfileBasicCardToPaymentMethodData(paymentCardGUID,
+                                                                            cardSecurityCode);
 
     if (!methodData) {
       // TODO (Bug 1429265/Bug 1429205): Handle when a user hits cancel on the
       // Master Password dialog.
       Cu.reportError("Bug 1429265/Bug 1429205: User canceled master password entry");
       return;
     }
 
--- a/browser/components/payments/res/debugging.js
+++ b/browser/components/payments/res/debugging.js
@@ -313,17 +313,17 @@ let DUPED_ADDRESSES = {
 };
 
 let BASIC_CARDS_1 = {
   "53f9d009aed2": {
     billingAddressGUID: "68gjdh354j",
     methodName: "basic-card",
     "cc-number": "************5461",
     "guid": "53f9d009aed2",
-    "version": 2,
+    "version": 1,
     "timeCreated": 1505240896213,
     "timeLastModified": 1515609524588,
     "timeLastUsed": 0,
     "timesUsed": 0,
     "cc-name": "John Smith",
     "cc-exp-month": 6,
     "cc-exp-year": 2024,
     "cc-type": "visa",
@@ -331,17 +331,17 @@ let BASIC_CARDS_1 = {
     "cc-additional-name": "",
     "cc-family-name": "Smith",
     "cc-exp": "2024-06",
   },
   "9h5d4h6f4d1s": {
     methodName: "basic-card",
     "cc-number": "************0954",
     "guid": "9h5d4h6f4d1s",
-    "version": 2,
+    "version": 1,
     "timeCreated": 1517890536491,
     "timeLastModified": 1517890564518,
     "timeLastUsed": 0,
     "timesUsed": 0,
     "cc-name": "Jane Doe",
     "cc-exp-month": 5,
     "cc-exp-year": 2023,
     "cc-type": "mastercard",
@@ -349,17 +349,17 @@ let BASIC_CARDS_1 = {
     "cc-additional-name": "",
     "cc-family-name": "Doe",
     "cc-exp": "2023-05",
   },
   "123456789abc": {
     methodName: "basic-card",
     "cc-number": "************1234",
     "guid": "123456789abc",
-    "version": 2,
+    "version": 1,
     "timeCreated": 1517890536491,
     "timeLastModified": 1517890564518,
     "timeLastUsed": 0,
     "timesUsed": 0,
     "cc-name": "Jane Fields",
     "cc-given-name": "Jane",
     "cc-additional-name": "",
     "cc-family-name": "Fields",
@@ -383,17 +383,17 @@ let BASIC_CARDS_1 = {
     "cc-exp-month": 6,
     "cc-exp-year": 2023,
     "cc-exp": "2023-06",
   },
   "missing-cc-name": {
     methodName: "basic-card",
     "cc-number": "************8563",
     "guid": "missing-cc-name",
-    "version": 2,
+    "version": 1,
     "timeCreated": 1517890536491,
     "timeLastModified": 1517890564518,
     "timeLastUsed": 0,
     "timesUsed": 0,
     "cc-exp-month": 8,
     "cc-exp-year": 2024,
     "cc-exp": "2024-08",
   },
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -37,17 +37,17 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://formautofill/FormAutofill.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   CreditCard: "resource://gre/modules/CreditCard.jsm",
   FormAutofillPreferences: "resource://formautofill/FormAutofillPreferences.jsm",
   FormAutofillDoorhanger: "resource://formautofill/FormAutofillDoorhanger.jsm",
   FormAutofillUtils: "resource://formautofill/FormAutofillUtils.jsm",
-  OSKeyStore: "resource://formautofill/OSKeyStore.jsm",
+  MasterPassword: "resource://formautofill/MasterPassword.jsm",
 });
 
 this.log = null;
 FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]);
 
 const {
   ENABLED_AUTOFILL_ADDRESSES_PREF,
   ENABLED_AUTOFILL_CREDITCARDS_PREF,
@@ -221,18 +221,18 @@ FormAutofillParent.prototype = {
         if (data.guid) {
           await this.formAutofillStorage.addresses.update(data.guid, data.address);
         } else {
           await this.formAutofillStorage.addresses.add(data.address);
         }
         break;
       }
       case "FormAutofill:SaveCreditCard": {
-        if (!await OSKeyStore.ensureLoggedIn()) {
-          log.warn("User canceled encryption login");
+        if (!await MasterPassword.ensureLoggedIn()) {
+          log.warn("User canceled master password entry");
           return;
         }
         await this.formAutofillStorage.creditCards.add(data.creditcard);
         break;
       }
       case "FormAutofill:RemoveAddresses": {
         data.guids.forEach(guid => this.formAutofillStorage.addresses.remove(guid));
         break;
@@ -249,22 +249,22 @@ FormAutofillParent.prototype = {
         const win = BrowserWindowTracker.getTopWindow();
         win.openPreferences("privacy-form-autofill", {origin: "autofillFooter"});
         break;
       }
       case "FormAutofill:GetDecryptedString": {
         let {cipherText, reauth} = data;
         let string;
         try {
-          string = await OSKeyStore.decrypt(cipherText, reauth);
+          string = await MasterPassword.decrypt(cipherText, reauth);
         } catch (e) {
           if (e.result != Cr.NS_ERROR_ABORT) {
             throw e;
           }
-          log.warn("User canceled encryption login");
+          log.warn("User canceled master password entry");
         }
         target.sendAsyncMessage("FormAutofill:DecryptedString", string);
         break;
       }
     }
   },
 
   /**
@@ -288,17 +288,17 @@ FormAutofillParent.prototype = {
       Services.ppmm.removeMessageListener("FormAutofill:GetDecryptedString", this);
       Services.prefs.removeObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
     }
   },
 
   /**
    * Get the records from profile store and return results back to content
    * process. It will decrypt the credit card number and append
-   * "cc-number-decrypted" to each record if OSKeyStore isn't set.
+   * "cc-number-decrypted" to each record if MasterPassword isn't set.
    *
    * @private
    * @param  {string} data.collectionName
    *         The name used to specify which collection to retrieve records.
    * @param  {string} data.searchString
    *         The typed string for filtering out the matched records.
    * @param  {string} data.info
    *         The input autocomplete property's information.
@@ -313,33 +313,44 @@ FormAutofillParent.prototype = {
     }
 
     let recordsInCollection = await collection.getAll();
     if (!info || !info.fieldName || !recordsInCollection.length) {
       target.sendAsyncMessage("FormAutofill:Records", recordsInCollection);
       return;
     }
 
-    let isCC = collectionName == CREDITCARDS_COLLECTION_NAME;
-    // We don't filter "cc-number"
-    if (isCC && info.fieldName == "cc-number") {
+    let isCCAndMPEnabled = collectionName == CREDITCARDS_COLLECTION_NAME && MasterPassword.isEnabled;
+    // We don't filter "cc-number" when MasterPassword is set.
+    if (isCCAndMPEnabled && info.fieldName == "cc-number") {
       recordsInCollection = recordsInCollection.filter(record => !!record["cc-number"]);
       target.sendAsyncMessage("FormAutofill:Records", recordsInCollection);
       return;
     }
 
     let records = [];
     let lcSearchString = searchString.toLowerCase();
 
     for (let record of recordsInCollection) {
       let fieldValue = record[info.fieldName];
       if (!fieldValue) {
         continue;
       }
 
+      // Cache the decrypted "cc-number" in each record for content to preview
+      // when MasterPassword isn't set.
+      if (!isCCAndMPEnabled && record["cc-number-encrypted"]) {
+        record["cc-number-decrypted"] = await MasterPassword.decrypt(record["cc-number-encrypted"]);
+      }
+
+      // Filter "cc-number" based on the decrypted one.
+      if (info.fieldName == "cc-number") {
+        fieldValue = record["cc-number-decrypted"];
+      }
+
       if (collectionName == ADDRESSES_COLLECTION_NAME && record.country
           && !FormAutofill.supportedCountries.includes(record.country)) {
         // Address autofill isn't supported for the record's country so we don't
         // want to attempt to potentially incorrectly fill the address fields.
         continue;
       }
 
       if (lcSearchString && !String(fieldValue).toLowerCase().startsWith(lcSearchString)) {
@@ -523,18 +534,18 @@ FormAutofillParent.prototype = {
         return;
       }
 
       if (state == "disable") {
         Services.prefs.setBoolPref("extensions.formautofill.creditCards.enabled", false);
         return;
       }
 
-      if (!await OSKeyStore.ensureLoggedIn()) {
-        log.warn("User canceled encryption login");
+      if (!await MasterPassword.ensureLoggedIn()) {
+        log.warn("User canceled master password entry");
         return;
       }
 
       let changedGUIDs = [];
       if (creditCard.guid) {
         if (state == "update") {
           await this.formAutofillStorage.creditCards.update(creditCard.guid, creditCard.record, true);
           changedGUIDs.push(creditCard.guid);
--- a/browser/extensions/formautofill/FormAutofillStorage.jsm
+++ b/browser/extensions/formautofill/FormAutofillStorage.jsm
@@ -137,36 +137,33 @@ ChromeUtils.import("resource://formautof
 ChromeUtils.defineModuleGetter(this, "CreditCard",
                                "resource://gre/modules/CreditCard.jsm");
 ChromeUtils.defineModuleGetter(this, "JSONFile",
                                "resource://gre/modules/JSONFile.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofillNameUtils",
                                "resource://formautofill/FormAutofillNameUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
                                "resource://formautofill/FormAutofillUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "OSKeyStore",
-                               "resource://formautofill/OSKeyStore.jsm");
+ChromeUtils.defineModuleGetter(this, "MasterPassword",
+                               "resource://formautofill/MasterPassword.jsm");
 ChromeUtils.defineModuleGetter(this, "PhoneNumber",
                                "resource://formautofill/phonenumberutils/PhoneNumber.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "cryptoSDR",
-                                   "@mozilla.org/login-manager/crypto/SDR;1",
-                                   Ci.nsILoginManagerCrypto);
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 const CryptoHash = Components.Constructor("@mozilla.org/security/hash;1",
                                           "nsICryptoHash", "initWithString");
 
 const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
 
 const STORAGE_SCHEMA_VERSION = 1;
 const ADDRESS_SCHEMA_VERSION = 1;
-const CREDIT_CARD_SCHEMA_VERSION = 2;
+const CREDIT_CARD_SCHEMA_VERSION = 1;
 
 const VALID_ADDRESS_FIELDS = [
   "given-name",
   "additional-name",
   "family-name",
   "organization",
   "street-address",
   "address-level3",
@@ -262,24 +259,23 @@ class AutofillRecords {
 
     this.VALID_FIELDS = validFields;
     this.VALID_COMPUTED_FIELDS = validComputedFields;
 
     this._store = store;
     this._collectionName = collectionName;
     this._schemaVersion = schemaVersion;
 
-    this._initializePromise =
-      Promise.all(this._data.map(async (record, index) => this._migrateRecord(record, index)))
-        .then(hasChangesArr => {
-          let dataHasChanges = hasChangesArr.includes(true);
-          if (dataHasChanges) {
-            this._store.saveSoon();
-          }
-        });
+    Promise.all(this._data.map(record => this._migrateRecord(record)))
+      .then(hasChangesArr => {
+        let dataHasChanges = hasChangesArr.find(hasChanges => hasChanges);
+        if (dataHasChanges) {
+          this._store.saveSoon();
+        }
+      });
   }
 
   /**
    * Gets the schema version number.
    *
    * @returns {number}
    *          The current schema version number.
    */
@@ -303,24 +299,16 @@ class AutofillRecords {
   _ensureMatchingVersion(record) {
     if (record.version != this.version) {
       throw new Error(`Got unknown record version ${
         record.version}; want ${this.version}`);
     }
   }
 
   /**
-   * Initialize the records in the collection, resolves when the migration completes.
-   * @returns {Promise}
-   */
-  initialize() {
-    return this._initializePromise;
-  }
-
-  /**
    * Adds a new record.
    *
    * @param {Object} record
    *        The new record for saving.
    * @param {boolean} [options.sourceSync = false]
    *        Did sync generate this addition?
    * @returns {Promise<string>}
    *          The GUID of the newly added item..
@@ -1183,47 +1171,36 @@ class AutofillRecords {
   }
 
   _findIndexByGUID(guid, {includeDeleted = false} = {}) {
     return this._data.findIndex(record => {
       return record.guid == guid && (!record.deleted || includeDeleted);
     });
   }
 
-  async _migrateRecord(record, index) {
+  async _migrateRecord(record) {
     let hasChanges = false;
 
     if (record.deleted) {
       return hasChanges;
     }
 
     if (!record.version || isNaN(record.version) || record.version < 1) {
       this.log.warn("Invalid record version:", record.version);
 
       // Force to run the migration.
       record.version = 0;
     }
 
     if (record.version < this.version) {
       hasChanges = true;
-
-      record = await this._computeMigratedRecord(record);
+      record.version = this.version;
 
-      if (record.deleted) {
-        // record is deleted by _computeMigratedRecord(),
-        // go ahead and put it in the store.
-        this._data[index] = record;
-        return hasChanges;
-      }
-
-      // Compute the computed fields before putting it to store.
-      await this.computeFields(record);
-      this._data[index] = record;
-
-      return hasChanges;
+      // Force to recompute fields if we upgrade the schema.
+      await this._stripComputedFields(record);
     }
 
     hasChanges |= await this.computeFields(record);
     return hasChanges;
   }
 
   _normalizeRecord(record, preserveEmptyFields = false) {
     this._normalizeFields(record);
@@ -1274,34 +1251,16 @@ class AutofillRecords {
     this._store.data[this._collectionName] = [];
     this._store.saveSoon();
     Services.obs.notifyObservers({wrappedJSObject: {
       sourceSync,
       collectionName: this._collectionName,
     }}, "formautofill-storage-changed", "removeAll");
   }
 
-  /**
-   * Strip the computed fields based on the record version.
-   * @param   {Object} record      The record to migrate
-   * @returns {Object}             Migrated record.
-   *                               Record is always cloned, with version updated,
-   *                               with computed fields stripped.
-   *                               Could be a tombstone record, if the record
-   *                               should be discorded.
-   */
-  async _computeMigratedRecord(record) {
-    if (!record.deleted) {
-      record = this._clone(record);
-      await this._stripComputedFields(record);
-      record.version = this.version;
-    }
-    return record;
-  }
-
   async _stripComputedFields(record) {
     this.VALID_COMPUTED_FIELDS.forEach(field => delete record[field]);
   }
 
   // An interface to be inherited.
   _recordReadProcessor(record) {}
 
   // An interface to be inherited.
@@ -1640,82 +1599,28 @@ class CreditCards extends AutofillRecord
       hasNewComputedFields = true;
     }
 
     // Encrypt credit card number
     if (!("cc-number-encrypted" in creditCard)) {
       if ("cc-number" in creditCard) {
         let ccNumber = creditCard["cc-number"];
         creditCard["cc-number"] = CreditCard.getLongMaskedNumber(ccNumber);
-        creditCard["cc-number-encrypted"] = await OSKeyStore.encrypt(ccNumber);
+        creditCard["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
       } else {
         creditCard["cc-number-encrypted"] = "";
       }
     }
 
     return hasNewComputedFields;
   }
 
-  async _computeMigratedRecord(creditCard) {
-    if (creditCard["cc-number-encrypted"]) {
-      switch (creditCard.version) {
-        case 1: {
-          if (!cryptoSDR.isLoggedIn) {
-            // We cannot decrypt the data, so silently remove the record for
-            // the user.
-            if (creditCard.deleted) {
-              break;
-            }
-
-            this.log.warn("Removing version 1 credit card record to migrate to new encryption:", creditCard.guid);
-
-            // Replace the record with a tombstone record here,
-            // regardless of existence of sync metadata.
-            let existingSync = this._getSyncMetaData(creditCard);
-            creditCard = {
-              guid: creditCard.guid,
-              timeLastModified: Date.now(),
-              deleted: true,
-            };
-
-            if (existingSync) {
-              creditCard._sync = existingSync;
-              existingSync.changeCounter++;
-            }
-            break;
-          }
-
-          creditCard = this._clone(creditCard);
-
-          // Decrypt the cc-number using version 1 encryption.
-          let ccNumber = cryptoSDR.decrypt(creditCard["cc-number-encrypted"]);
-          // Re-encrypt the cc-number with version 2 encryption.
-          creditCard["cc-number-encrypted"] = await OSKeyStore.encrypt(ccNumber);
-          break;
-        }
-
-        default:
-          throw new Error("Unknown credit card version to migrate: " + creditCard.version);
-      }
-    }
-    return super._computeMigratedRecord(creditCard);
-  }
-
   async _stripComputedFields(creditCard) {
     if (creditCard["cc-number-encrypted"]) {
-      try {
-        creditCard["cc-number"] = await OSKeyStore.decrypt(creditCard["cc-number-encrypted"]);
-      } catch (ex) {
-        if (ex.result == Cr.NS_ERROR_ABORT) {
-          throw ex;
-        }
-        // Quietly recover from encryption error,
-        // so existing credit card entry with undecryptable number
-        // can be updated.
-      }
+      creditCard["cc-number"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
     }
     await super._stripComputedFields(creditCard);
   }
 
   _normalizeFields(creditCard) {
     this._normalizeCCName(creditCard);
     this._normalizeCCNumber(creditCard);
     this._normalizeCCExpirationDate(creditCard);
@@ -1766,62 +1671,38 @@ class CreditCards extends AutofillRecord
   }
 
   _validateFields(creditCard) {
     if (!creditCard["cc-number"]) {
       throw new Error("Missing/invalid cc-number");
     }
   }
 
-  _ensureMatchingVersion(record) {
-    if (!record.version || isNaN(record.version) || record.version < 1) {
-      throw new Error(`Got invalid record version ${
-        record.version}; want ${this.version}`);
-    }
-
-    if (record.version < this.version) {
-      switch (record.version) {
-        case 1:
-          // The difference between version 1 and 2 is only about the encryption
-          // method used for the cc-number-encrypted field.
-          // As long as the record is already decrypted, it is safe to bump the
-          // version directly.
-          if (!record["cc-number-encrypted"]) {
-            record.version = this.version;
-          } else {
-            throw new Error("Unexpected record migration path.");
-          }
-          break;
-        default:
-          throw new Error("Unknown credit card version to match: " + record.version);
-      }
-    }
-
-    return super._ensureMatchingVersion(record);
-  }
-
   /**
    * Normalize the given record and return the first matched guid if storage has the same record.
    * @param {Object} targetCreditCard
    *        The credit card for duplication checking.
    * @returns {Promise<string|null>}
    *          Return the first guid if storage has the same credit card and null otherwise.
    */
   async getDuplicateGuid(targetCreditCard) {
     let clonedTargetCreditCard = this._clone(targetCreditCard);
     this._normalizeRecord(clonedTargetCreditCard);
     for (let creditCard of this._data) {
       let isDuplicate = await Promise.all(this.VALID_FIELDS.map(async field => {
         if (!clonedTargetCreditCard[field]) {
           return !creditCard[field];
         }
         if (field == "cc-number" && creditCard[field]) {
-          // Compare the masked numbers instead when decryption requires a password
-          // because we don't want to leak the credit card number.
-          return CreditCard.getLongMaskedNumber(clonedTargetCreditCard[field]) == creditCard[field];
+          if (MasterPassword.isEnabled) {
+            // Compare the masked numbers instead when the master password is
+            // enabled because we don't want to leak the credit card number.
+            return CreditCard.getLongMaskedNumber(clonedTargetCreditCard[field]) == creditCard[field];
+          }
+          return (clonedTargetCreditCard[field] == await MasterPassword.decrypt(creditCard["cc-number-encrypted"]));
         }
         return clonedTargetCreditCard[field] == creditCard[field];
       })).then(fieldResults => fieldResults.every(result => result));
       if (isDuplicate) {
         return creditCard.guid;
       }
     }
     return null;
@@ -1919,20 +1800,17 @@ FormAutofillStorage.prototype = {
    * @rejects  JavaScript exception.
    */
   initialize() {
     if (!this._initializePromise) {
       this._store = new JSONFile({
         path: this._path,
         dataPostProcessor: this._dataPostProcessor.bind(this),
       });
-      this._initializePromise = this._store.load()
-        .then(() => Promise.all([
-          this.addresses.initialize(),
-          this.creditCards.initialize()]));
+      this._initializePromise = this._store.load();
     }
     return this._initializePromise;
   },
 
   _dataPostProcessor(data) {
     data.version = this.version;
     if (!data.addresses) {
       data.addresses = [];
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -12,17 +12,17 @@ const ADDRESS_REFERENCES_EXT = "addressR
 
 const ADDRESSES_COLLECTION_NAME = "addresses";
 const CREDITCARDS_COLLECTION_NAME = "creditCards";
 const MANAGE_ADDRESSES_KEYWORDS = ["manageAddressesTitle", "addNewAddressTitle"];
 const EDIT_ADDRESS_KEYWORDS = [
   "givenName", "additionalName", "familyName", "organization2", "streetAddress",
   "state", "province", "city", "country", "zip", "postalCode", "email", "tel",
 ];
-const MANAGE_CREDITCARDS_KEYWORDS = ["manageCreditCardsTitle", "addNewCreditCardTitle"];
+const MANAGE_CREDITCARDS_KEYWORDS = ["manageCreditCardsTitle", "addNewCreditCardTitle", "showCreditCardsBtnLabel"];
 const EDIT_CREDITCARD_KEYWORDS = ["cardNumber", "nameOnCard", "cardExpiresMonth", "cardExpiresYear", "cardNetwork"];
 const FIELD_STATES = {
   NORMAL: "NORMAL",
   AUTO_FILLED: "AUTO_FILLED",
   PREVIEW: "PREVIEW",
 };
 const SECTION_TYPES = {
   ADDRESS: "address",
rename from browser/extensions/formautofill/OSKeyStore.jsm
rename to browser/extensions/formautofill/MasterPassword.jsm
--- a/browser/extensions/formautofill/OSKeyStore.jsm
+++ b/browser/extensions/formautofill/MasterPassword.jsm
@@ -1,251 +1,184 @@
 /* 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 using OS Key Store.
+ * Helpers for the Master Password Dialog.
+ * In the future the Master Password implementation may move here.
  */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = [
-  "OSKeyStore",
+  "MasterPassword",
 ];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "nativeOSKeyStore",
-                                   "@mozilla.org/security/oskeystore;1", Ci.nsIOSKeyStore);
-
-// Skip reauth during tests, only works in non-official builds.
-const TEST_ONLY_REAUTH = "extensions.formautofill.osKeyStore.unofficialBuildOnlyLogin";
-
-var OSKeyStore = {
-  /**
-   * On macOS this becomes part of the name label visible on Keychain Acesss as
-   * "org.mozilla.nss.keystore.firefox" (where "firefox" is the MOZ_APP_NAME).
-   */
-  STORE_LABEL: AppConstants.MOZ_APP_NAME,
+XPCOMUtils.defineLazyServiceGetter(this, "cryptoSDR",
+                                   "@mozilla.org/login-manager/crypto/SDR;1",
+                                   Ci.nsILoginManagerCrypto);
 
-  /**
-   * Consider the module is initialized as locked. OS might unlock without a
-   * prompt.
-   * @type {Boolean}
-   */
-  _isLocked: true,
-
-  _pendingUnlockPromise: null,
-
-  /**
-   * @returns {boolean} True if logged in (i.e. decrypt(reauth = false) will
-   *                    not retrigger a dialog) and false if not.
-   *                    User might log out elsewhere in the OS, so even if this
-   *                    is true a prompt might still pop up.
-   */
-  get isLoggedIn() {
-    return !this._isLocked;
+var MasterPassword = {
+  get _token() {
+    let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
+    return tokendb.getInternalKeyToken();
   },
 
   /**
-   * @returns {boolean} True if there is another login dialog existing and false
-   *                    otherwise.
+   * @returns {boolean} True if a master password is set and false otherwise.
    */
-  get isUIBusy() {
-    return !!this._pendingUnlockPromise;
+  get isEnabled() {
+    return this._token.hasPassword;
   },
 
   /**
-   * If the test pref exist and applicable,
-   * this method will dispatch a observer message and return
-   * to simulate successful reauth, or throw to simulate
-   * failed reauth.
-   *
-   * @returns {boolean} True when reauth should NOT be skipped,
-   *                    false when reauth has been skipped.
-   * @throws            If it needs to simulate reauth login failure.
+   * @returns {boolean} True if master password is logged in and false if not.
    */
-  _maybeSkipReauthForTest() {
-    // Don't take test reauth pref in the following configurations.
-    if (nativeOSKeyStore.isNSSKeyStore ||
-        AppConstants.MOZILLA_OFFICIAL ||
-        !this._testReauth) {
-      return true;
-    }
-
-    // Skip this reauth because there is no way to mock the
-    // native dialog in the testing environment, for now.
-    log.debug("_ensureReauth: _testReauth: ", this._testReauth);
-    switch (this._testReauth) {
-      case "pass":
-        Services.obs.notifyObservers(null, "oskeystore-testonly-reauth", "pass");
-        return false;
-      case "cancel":
-        Services.obs.notifyObservers(null, "oskeystore-testonly-reauth", "cancel");
-        throw new Components.Exception("Simulating user cancelling login dialog", Cr.NS_ERROR_FAILURE);
-      default:
-        throw new Components.Exception("Unknown test pref value", Cr.NS_ERROR_FAILURE);
-    }
+  get isLoggedIn() {
+    return Services.logins.isLoggedIn;
   },
 
   /**
-   * Ensure the store in use is logged in. It will display the OS login
-   * login prompt or do nothing if it's logged in already. If an existing login
+   * @returns {boolean} True if there is another master password login dialog
+   *                    existing and false otherwise.
+   */
+  get isUIBusy() {
+    return Services.logins.uiBusy;
+  },
+
+  /**
+   * Ensure the master password is logged in. It will display the master password
+   * login prompt or do nothing if it's logged in already. If an existing MP
    * prompt is already prompted, the result from it will be used instead.
    *
-   * Note: This method must set _pendingUnlockPromise before returning the
-   * promise (i.e. the first |await|), otherwise we'll risk re-entry.
-   * This is why there aren't an |await| in the method. The method is marked as
-   * |async| to communicate that it's async.
-   *
    * @param   {boolean} reauth Prompt the login dialog no matter it's logged in
    *                           or not if it's set to true.
    * @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 ensureLoggedIn(reauth = false) {
-    if (this._pendingUnlockPromise) {
-      log.debug("ensureLoggedIn: Has a pending unlock operation");
-      return this._pendingUnlockPromise;
+    if (!this.isEnabled) {
+      return true;
     }
-    log.debug("ensureLoggedIn: Creating new pending unlock promise. reauth: ", reauth);
-
-    // TODO: Implementing re-auth by passing this value to the native implementation
-    // in some way. Set this to false for now to ignore the reauth request (bug 1429265).
-    reauth = false;
-
-    let unlockPromise = Promise.resolve().then(async () => {
-      if (reauth) {
-        reauth = this._maybeSkipReauthForTest();
-      }
 
-      if (!await nativeOSKeyStore.asyncSecretAvailable(this.STORE_LABEL)) {
-        log.debug("ensureLoggedIn: Secret unavailable, attempt to generate new secret.");
-        let recoveryPhrase = await nativeOSKeyStore.asyncGenerateSecret(this.STORE_LABEL);
-        // TODO We should somehow have a dialog to ask the user to write this down,
-        // and another dialog somewhere for the user to restore the secret with it.
-        // (Intentionally not printing it out in the console)
-        log.debug("ensureLoggedIn: Secret generated. Recovery phrase length: " + recoveryPhrase.length);
-      }
-    });
+    if (this.isLoggedIn && !reauth) {
+      return true;
+    }
 
-    if (nativeOSKeyStore.isNSSKeyStore) {
-      // Workaround bug 1492305: NSS-implemented methods don't reject when user cancels.
-      unlockPromise = unlockPromise.then(() => {
-        log.debug("ensureLoggedIn: isNSSKeyStore: ", reauth, Services.logins.isLoggedIn);
-        // User has hit the cancel button on the master password prompt.
-        // We must reject the promise chain here.
-        if (!Services.logins.isLoggedIn) {
-          throw Components.Exception("User canceled OS unlock entry (Workaround)", Cr.NS_ERROR_FAILURE);
-        }
-      });
+    // If a prompt is already showing then wait for and focus it.
+    if (this.isUIBusy) {
+      return this.waitForExistingDialog();
     }
 
-    unlockPromise = unlockPromise.then(() => {
-      log.debug("ensureLoggedIn: Logged in");
-      this._pendingUnlockPromise = null;
-      this._isLocked = false;
+    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.
+    }
 
-      return true;
-    }, (err) => {
-      log.debug("ensureLoggedIn: Not logged in", err);
-      this._pendingUnlockPromise = null;
-      this._isLocked = true;
+    // 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 false;
-    });
-
-    this._pendingUnlockPromise = unlockPromise;
-
-    return this._pendingUnlockPromise;
+    return token.isLoggedIn();
   },
 
   /**
    * Decrypts cipherText.
    *
-   * Note: In the event of an rejection, check the result property of the Exception
-   *       object. Handles NS_ERROR_ABORT as user has cancelled the action (e.g.,
-   *       don't show that dialog), apart from other errors (e.g., gracefully
-   *       recover from that and still shows the dialog.)
-   *
    * @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) {
     if (!await this.ensureLoggedIn(reauth)) {
-      throw Components.Exception("User canceled OS unlock entry", Cr.NS_ERROR_ABORT);
+      throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
     }
-    let bytes = await nativeOSKeyStore.asyncDecryptBytes(this.STORE_LABEL, cipherText);
-    return String.fromCharCode.apply(String, bytes);
+    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 (!await this.ensureLoggedIn()) {
-      throw Components.Exception("User canceled OS unlock entry", Cr.NS_ERROR_ABORT);
+      throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
     }
 
-    // Convert plain text into a UTF-8 binary string
-    plainText = unescape(encodeURIComponent(plainText));
-
-    // Convert it to an array
-    let textArr = [];
-    for (let char of plainText) {
-      textArr.push(char.charCodeAt(0));
-    }
-
-    let rawEncryptedText = await nativeOSKeyStore.asyncEncryptBytes(this.STORE_LABEL, textArr.length, textArr);
-
-    // Mark the output with a version number.
-    return rawEncryptedText;
+    return cryptoSDR.encrypt(plainText);
   },
 
   /**
-   * Resolve when the login dialogs are closed, immediately if none are open.
+   * 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 (this.isUIBusy) {
-      return this._pendingUnlockPromise;
+    if (!this.isUIBusy) {
+      log.debug("waitForExistingDialog: Dialog isn't showing. isLoggedIn:", this.isLoggedIn);
+      return this.isLoggedIn;
     }
-    return this.isLoggedIn;
-  },
+
+    return new Promise((resolve) => {
+      log.debug("waitForExistingDialog: Observing the open dialog");
+      let observer = {
+        QueryInterface: ChromeUtils.generateQI([
+          Ci.nsIObserver,
+          Ci.nsISupportsWeakReference,
+        ]),
 
-  /**
-   * Remove the store. For tests.
-   */
-  async cleanup() {
-    return nativeOSKeyStore.asyncDeleteSecret(this.STORE_LABEL);
-  },
+        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);
+        },
+      };
 
-  /**
-   * Check if the implementation is using the NSS key store.
-   * If so, tests will be able to handle the reauth dialog.
-   */
-  get isNSSKeyStore() {
-    return nativeOSKeyStore.isNSSKeyStore;
+      // 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 = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
   return new ConsoleAPI({
-    maxLogLevelPref: "extensions.formautofill.loglevel",
-    prefix: "OSKeyStore",
+    maxLogLevelPref: "masterPassword.loglevel",
+    prefix: "Master Password",
   });
 });
-
-XPCOMUtils.defineLazyPreferenceGetter(OSKeyStore, "_testReauth", TEST_ONLY_REAUTH, "");
--- a/browser/extensions/formautofill/content/manageCreditCards.xhtml
+++ b/browser/extensions/formautofill/content/manageCreditCards.xhtml
@@ -15,27 +15,29 @@
 </head>
 <body dir="&locale.dir;">
   <fieldset>
     <legend data-localization="creditCardsListHeader"/>
     <select id="credit-cards" size="9" multiple="multiple"/>
   </fieldset>
   <div id="controls-container">
     <button id="remove" disabled="disabled" data-localization="removeBtnLabel"/>
+    <button id="show-hide-credit-cards" data-localization="showCreditCardsBtnLabel"/>
     <!-- Wrapper is used to properly compute the search tooltip position -->
     <div>
       <button id="add" data-localization="addBtnLabel"/>
     </div>
     <button id="edit" disabled="disabled" data-localization="editBtnLabel"/>
   </div>
   <script type="application/javascript">
     "use strict";
     /* global ManageCreditCards */
     new ManageCreditCards({
       records: document.getElementById("credit-cards"),
       controlsContainer: document.getElementById("controls-container"),
       remove: document.getElementById("remove"),
+      showHideCreditCards: document.getElementById("show-hide-credit-cards"),
       add: document.getElementById("add"),
       edit: document.getElementById("edit"),
     });
   </script>
 </body>
 </html>
--- a/browser/extensions/formautofill/content/manageDialog.js
+++ b/browser/extensions/formautofill/content/manageDialog.js
@@ -14,18 +14,18 @@ ChromeUtils.import("resource://formautof
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 ChromeUtils.defineModuleGetter(this, "CreditCard",
                                "resource://gre/modules/CreditCard.jsm");
 ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
                                "resource://formautofill/FormAutofillStorage.jsm");
 ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
                                "resource://formautofill/FormAutofillUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "OSKeyStore",
-                               "resource://formautofill/OSKeyStore.jsm");
+ChromeUtils.defineModuleGetter(this, "MasterPassword",
+                               "resource://formautofill/MasterPassword.jsm");
 
 this.log = null;
 FormAutofill.defineLazyLogGetter(this, "manageAddresses");
 
 class ManageRecords {
   constructor(subStorageName, elements) {
     this._storageInitPromise = formAutofillStorage.initialize();
     this._subStorageName = subStorageName;
@@ -308,43 +308,35 @@ class ManageAddresses extends ManageReco
 }
 
 class ManageCreditCards extends ManageRecords {
   constructor(elements) {
     super("creditCards", elements);
     elements.add.setAttribute("searchkeywords", FormAutofillUtils.EDIT_CREDITCARD_KEYWORDS
                                                   .map(key => FormAutofillUtils.stringBundle.GetStringFromName(key))
                                                   .join("\n"));
+    this._hasMasterPassword = MasterPassword.isEnabled;
     this._isDecrypted = false;
+    if (this._hasMasterPassword) {
+      elements.showHideCreditCards.setAttribute("hidden", true);
+    }
   }
 
   /**
    * Open the edit address dialog to create/edit a credit card.
    *
    * @param  {object} creditCard [optional]
    */
   async openEditDialog(creditCard) {
-    // Ask for reauth if user is trying to edit an existing credit card.
-    if (!creditCard || await OSKeyStore.ensureLoggedIn(true)) {
+    // If master password is set, ask for password if user is trying to edit an
+    // existing credit card.
+    if (!creditCard || !this._hasMasterPassword || await MasterPassword.ensureLoggedIn(true)) {
       let decryptedCCNumObj = {};
       if (creditCard) {
-        try {
-          decryptedCCNumObj["cc-number"] = await OSKeyStore.decrypt(creditCard["cc-number-encrypted"]);
-        } catch (ex) {
-          if (ex.result == Cr.NS_ERROR_ABORT) {
-            // User shouldn't be ask to reauth here, but it could happen.
-            // Return here and skip opening the dialog.
-            return;
-          }
-          // We've got ourselves a real error.
-          // Recover from encryption error so the user gets a chance to re-enter
-          // unencrypted credit card number.
-          decryptedCCNumObj["cc-number"] = "";
-          Cu.reportError(ex);
-        }
+        decryptedCCNumObj["cc-number"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
       }
       let decryptedCreditCard = Object.assign({}, creditCard, decryptedCCNumObj);
       this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, "resizable=no", {
         record: decryptedCreditCard,
       });
     }
   }
 
@@ -362,16 +354,22 @@ class ManageCreditCards extends ManageRe
       encryptedNumber: creditCard["cc-number-encrypted"],
       number: creditCard["cc-number"],
       name: creditCard["cc-name"],
       network: creditCard["cc-type"],
     });
     return cardObj.getLabel({showNumbers: showCreditCards});
   }
 
+  async toggleShowHideCards(options) {
+    this._isDecrypted = !this._isDecrypted;
+    this.updateShowHideButtonState();
+    await this.updateLabels(options, this._isDecrypted);
+  }
+
   async updateLabels(options, isDecrypted) {
     for (let option of options) {
       option.text = await this.getLabel(option.record, isDecrypted);
     }
     // For testing only: Notify when credit cards labels have been updated
     this._elements.records.dispatchEvent(new CustomEvent("LabelsUpdated"));
   }
 
@@ -389,15 +387,30 @@ class ManageCreditCards extends ManageRe
         option.setAttribute("cc-type", record["cc-type"]);
       } else {
         option.removeAttribute("cc-type");
       }
     }
   }
 
   updateButtonsStates(selectedCount) {
+    this.updateShowHideButtonState();
     super.updateButtonsStates(selectedCount);
   }
 
+  updateShowHideButtonState() {
+    if (this._elements.records.length) {
+      this._elements.showHideCreditCards.removeAttribute("disabled");
+    } else {
+      this._elements.showHideCreditCards.setAttribute("disabled", true);
+    }
+    this._elements.showHideCreditCards.textContent =
+      this._isDecrypted ? FormAutofillUtils.stringBundle.GetStringFromName("hideCreditCardsBtnLabel") :
+                          FormAutofillUtils.stringBundle.GetStringFromName("showCreditCardsBtnLabel");
+  }
+
   handleClick(event) {
+    if (event.target == this._elements.showHideCreditCards) {
+      this.toggleShowHideCards(this._elements.records.options);
+    }
     super.handleClick(event);
   }
 }
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -99,16 +99,18 @@ savedCreditCardsBtnLabel = Saved Credit Cards…
 # LOCALIZATION NOTE (manageAddressesTitle, manageCreditCardsTitle): The dialog title for the list of addresses or
 # credit cards in browser preferences.
 manageAddressesTitle = Saved Addresses
 manageCreditCardsTitle = Saved Credit Cards
 # LOCALIZATION NOTE (addressesListHeader, creditCardsListHeader): The header for the list of addresses or credit cards
 # in browser preferences.
 addressesListHeader = Addresses
 creditCardsListHeader = Credit Cards
+showCreditCardsBtnLabel = Show Credit Cards
+hideCreditCardsBtnLabel = Hide Credit Cards
 removeBtnLabel = Remove
 addBtnLabel = Add…
 editBtnLabel = Edit…
 # LOCALIZATION NOTE (manageDialogsWidth): This strings sets the default width for windows used to manage addresses and
 # credit cards.
 manageDialogsWidth = 560px
 
 # LOCALIZATION NOTE (addNewAddressTitle, editAddressTitle): The dialog title for creating or editing addresses
--- a/browser/extensions/formautofill/moz.build
+++ b/browser/extensions/formautofill/moz.build
@@ -27,18 +27,16 @@ elif CONFIG['OS_ARCH'] == 'Darwin':
         'skin/osx/editDialog.css',
     ]
 elif CONFIG['OS_ARCH'] == 'WINNT':
     FINAL_TARGET_FILES.features['formautofill@mozilla.org'].chrome.res += [
         'skin/windows/autocomplete-item.css',
         'skin/windows/editDialog.css',
     ]
 
-TESTING_JS_MODULES += ['test/fixtures/OSKeyStoreTestUtils.jsm']
-
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
--- a/browser/extensions/formautofill/test/browser/browser.ini
+++ b/browser/extensions/formautofill/test/browser/browser.ini
@@ -10,18 +10,17 @@ support-files =
 [browser_autocomplete_footer.js]
 skip-if = verify
 [browser_autocomplete_marked_back_forward.js]
 [browser_autocomplete_marked_detached_tab.js]
 skip-if = (verify && (os == 'win' || os == 'mac'))
 [browser_check_installed.js]
 [browser_creditCard_doorhanger.js]
 skip-if = (os == "linux") || (os == "mac" && debug) || (os == "win") # bug 1425884
-[browser_creditCard_fill_cancel_login.js]
-skip-if = true # Re-auth is not implemented, cannot cancel OS key store login (bug 1429265)
+[browser_creditCard_fill_master_password.js]
 [browser_dropdown_layout.js]
 [browser_editAddressDialog.js]
 [browser_editCreditCardDialog.js]
 skip-if = (verify && (os == 'linux'))
 [browser_first_time_use_doorhanger.js]
 skip-if = verify
 [browser_insecure_form.js]
 skip-if = (os == 'linux' && !debug) || (os == 'win') # bug 1456284
--- a/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_creditCard_doorhanger.js
@@ -46,17 +46,16 @@ add_task(async function test_submit_cred
     "set": [
       [CREDITCARDS_USED_STATUS_PREF, 0],
     ],
   });
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
-      let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
         let name = form.querySelector("#cc-name");
         name.focus();
         name.setUserInput("User 1");
 
         form.querySelector("#cc-number").setUserInput("5038146897157463");
         form.querySelector("#cc-exp-month").setUserInput("12");
@@ -65,66 +64,54 @@ add_task(async function test_submit_cred
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
-      await onChanged;
     }
   );
 
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
   is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
   is(creditCards[0]["cc-type"], "mastercard", "Verify the cc-type field");
   is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 2, "User has seen the doorhanger");
   SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
   await removeAllRecords();
 });
 
 add_task(async function test_submit_untouched_creditCard_form() {
-  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
-    todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
-    return;
-  }
-
   await SpecialPowers.pushPrefEnv({
     "set": [
       [CREDITCARDS_USED_STATUS_PREF, 0],
     ],
   });
   await saveCreditCard(TEST_CREDIT_CARD_1);
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
-
-  let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
-  let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
-                                       (subject, data) => data == "notifyUsed");
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       await openPopupOn(browser, "form #cc-name");
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
       await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
-      await osKeyStoreLoginShown;
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await sleep(1000);
       is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
     }
   );
-  await onUsed;
 
   creditCards = await getCreditCards();
   is(creditCards.length, 1, "Still 1 credit card");
   is(creditCards[0].timesUsed, 1, "timesUsed field set to 1");
   is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 3, "User has used autofill");
   SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
   await removeAllRecords();
 });
@@ -133,19 +120,16 @@ add_task(async function test_submit_chan
   await SpecialPowers.pushPrefEnv({
     "set": [
       [CREDITCARDS_USED_STATUS_PREF, 0],
     ],
   });
   await saveCreditCard(TEST_CREDIT_CARD_1);
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
-
-  let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
-                                       (subject, data) => data == "notifyUsed");
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
         let name = form.querySelector("#cc-name");
 
@@ -160,17 +144,16 @@ add_task(async function test_submit_chan
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
     }
   );
-  await onUsed;
 
   creditCards = await getCreditCards();
   is(creditCards.length, 1, "Still 1 credit card in storage");
   is(creditCards[0]["cc-name"], TEST_CREDIT_CARD_1["cc-name"], "name field still exists");
   is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 2, "User has seen the doorhanger");
   SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
   await removeAllRecords();
 });
@@ -286,16 +269,88 @@ add_task(async function test_submit_cred
   let creditCardPref = SpecialPowers.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
   is(creditCards.length, 0, "No credit card in storage");
   is(creditCardPref, false, "Credit card is disabled");
   is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 2, "User has seen the doorhanger");
   SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
   SpecialPowers.clearUserPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
 });
 
+add_task(async function test_submit_creditCard_saved_with_mp_enabled() {
+  LoginTestUtils.masterPassword.enable();
+  // Login with the masterPassword in LoginTestUtils.
+  let masterPasswordDialogShown = waitForMasterPasswordDialog(true);
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        name.setUserInput("User 0");
+
+        let number = form.querySelector("#cc-number");
+        number.setUserInput("6387060366272981");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      await clickDoorhangerButton(MAIN_BUTTON);
+      await masterPasswordDialogShown;
+      await TestUtils.topicObserved("formautofill-storage-changed");
+    }
+  );
+
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 1, "1 credit card in storage");
+  is(creditCards[0]["cc-name"], "User 0", "Verify the name field");
+  is(creditCards[0]["cc-number"], "************2981", "Verify the card number field");
+  LoginTestUtils.masterPassword.disable();
+  await removeAllRecords();
+});
+
+add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_canceled() {
+  LoginTestUtils.masterPassword.enable();
+  let masterPasswordDialogShown = waitForMasterPasswordDialog();
+  await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
+    async function(browser) {
+      let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
+                                                       "popupshown");
+      await ContentTask.spawn(browser, null, async function() {
+        let form = content.document.getElementById("form");
+        let name = form.querySelector("#cc-name");
+        name.focus();
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        name.setUserInput("User 2");
+
+        let number = form.querySelector("#cc-number");
+        number.setUserInput("5471839082338112");
+
+        // Wait 1000ms before submission to make sure the input value applied
+        await new Promise(resolve => setTimeout(resolve, 1000));
+        form.querySelector("input[type=submit]").click();
+      });
+
+      await promiseShown;
+      await clickDoorhangerButton(MAIN_BUTTON);
+      await masterPasswordDialogShown;
+    }
+  );
+
+  await sleep(1000);
+  let creditCards = await getCreditCards();
+  is(creditCards.length, 0, "No credit cards in storage");
+  LoginTestUtils.masterPassword.disable();
+});
+
 add_task(async function test_submit_creditCard_with_sync_account() {
   await SpecialPowers.pushPrefEnv({
     "set": [
       [SYNC_USERNAME_PREF, "foo@bar.com"],
       [SYNC_CREDITCARDS_AVAILABLE_PREF, true],
     ],
   });
 
@@ -385,18 +440,16 @@ add_task(async function test_submit_manu
   await SpecialPowers.pushPrefEnv({
     "set": [
       [CREDITCARDS_USED_STATUS_PREF, 0],
     ],
   });
   await saveCreditCard(TEST_CREDIT_CARD_3);
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
-  let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
-                                       (subject, data) => data == "notifyUsed");
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
         let name = form.querySelector("#cc-name");
         name.focus();
@@ -409,255 +462,195 @@ add_task(async function test_submit_manu
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
     }
   );
-  await onUsed;
 
   creditCards = await getCreditCards();
   is(creditCards.length, 1, "Still 1 credit card in storage");
   is(creditCards[0]["cc-name"], "User 3", "Verify the name field");
   is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 2, "User has seen the doorhanger");
   SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
   await removeAllRecords();
 });
 
 add_task(async function test_update_autofill_form_name() {
-  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
-    todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
-    return;
-  }
-
   await SpecialPowers.pushPrefEnv({
     "set": [
       [CREDITCARDS_USED_STATUS_PREF, 0],
     ],
   });
   await saveCreditCard(TEST_CREDIT_CARD_1);
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
-  let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
-  let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
-                                       (subject, data) => data == "notifyUsed");
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
       await openPopupOn(browser, "form #cc-name");
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
       await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
-      await osKeyStoreLoginShown;
       await ContentTask.spawn(browser, null, async function() {
-        await ContentTaskUtils.waitForCondition(() => {
-          let form = content.document.getElementById("form");
-          let name = form.querySelector("#cc-name");
-          return name.value == "John Doe";
-        }, "Credit card detail never fills");
         let form = content.document.getElementById("form");
         let name = form.querySelector("#cc-name");
         name.setUserInput("User 1");
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
+
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
     }
   );
-  await onUsed;
 
   creditCards = await getCreditCards();
   is(creditCards.length, 1, "Still 1 credit card");
   is(creditCards[0]["cc-name"], "User 1", "cc-name field is updated");
   is(creditCards[0]["cc-number"], "************1111", "Verify the card number field");
   is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 3, "User has used autofill");
   SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
   await removeAllRecords();
 });
 
 add_task(async function test_update_autofill_form_exp_date() {
-  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
-    todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
-    return;
-  }
-
   await SpecialPowers.pushPrefEnv({
     "set": [
       [CREDITCARDS_USED_STATUS_PREF, 0],
     ],
   });
   await saveCreditCard(TEST_CREDIT_CARD_1);
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
-  let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
-  let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
-                                       (subject, data) => data == "notifyUsed");
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
       await openPopupOn(browser, "form #cc-name");
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
       await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
       await ContentTask.spawn(browser, null, async function() {
-        await ContentTaskUtils.waitForCondition(() => {
-          let form = content.document.getElementById("form");
-          let name = form.querySelector("#cc-name");
-          return name.value == "John Doe";
-        }, "Credit card detail never fills");
         let form = content.document.getElementById("form");
         let year = form.querySelector("#cc-exp-year");
         year.setUserInput("2020");
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
-      await osKeyStoreLoginShown;
     }
   );
-  await onUsed;
 
   creditCards = await getCreditCards();
   is(creditCards.length, 1, "Still 1 credit card");
   is(creditCards[0]["cc-exp-year"], "2020", "cc-exp-year field is updated");
   is(creditCards[0]["cc-number"], "************1111", "Verify the card number field");
   is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 3, "User has used autofill");
   SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
   await removeAllRecords();
 });
 
 add_task(async function test_create_new_autofill_form() {
-  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
-    todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
-    return;
-  }
-
   await SpecialPowers.pushPrefEnv({
     "set": [
       [CREDITCARDS_USED_STATUS_PREF, 0],
     ],
   });
   await saveCreditCard(TEST_CREDIT_CARD_1);
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
-  let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
-      let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
       await openPopupOn(browser, "form #cc-name");
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
       await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
       await ContentTask.spawn(browser, null, async function() {
-        await ContentTaskUtils.waitForCondition(() => {
-          let form = content.document.getElementById("form");
-          let name = form.querySelector("#cc-name");
-          return name.value == "John Doe";
-        }, "Credit card detail never fills");
         let form = content.document.getElementById("form");
         let name = form.querySelector("#cc-name");
         name.setUserInput("User 1");
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
       await clickDoorhangerButton(SECONDARY_BUTTON);
-      await osKeyStoreLoginShown;
-      await onChanged;
     }
   );
 
   creditCards = await getCreditCards();
   is(creditCards.length, 2, "2 credit cards in storage");
   is(creditCards[0]["cc-name"], TEST_CREDIT_CARD_1["cc-name"],
      "Original record's cc-name field is unchanged");
   is(creditCards[1]["cc-name"], "User 1", "cc-name field in the new record");
   is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 3, "User has used autofill");
   SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
   await removeAllRecords();
 });
 
 add_task(async function test_update_duplicate_autofill_form() {
-  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
-    todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
-    return;
-  }
-
   await SpecialPowers.pushPrefEnv({
     "set": [
       [CREDITCARDS_USED_STATUS_PREF, 0],
     ],
   });
   await saveCreditCard({
     "cc-number": "6387060366272981",
   });
   await saveCreditCard({
     "cc-number": "5038146897157463",
   });
   let creditCards = await getCreditCards();
   is(creditCards.length, 2, "2 credit card in storage");
-  let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
-  let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
-                                       (subject, data) => data == "notifyUsed");
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       await openPopupOn(browser, "form #cc-number");
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
       await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
       await ContentTask.spawn(browser, null, async function() {
-        await ContentTaskUtils.waitForCondition(() => {
-          let form = content.document.getElementById("form");
-          let number = form.querySelector("#cc-number");
-          return number.value == "6387060366272981";
-        }, "Should be the first credit card number");
-
-        // Change number to the second credit card number
         let form = content.document.getElementById("form");
         let number = form.querySelector("#cc-number");
+        is(number.value, "6387060366272981", "Should be the first credit card number");
+        // Change number to the second credit card number
         number.setUserInput("5038146897157463");
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await sleep(1000);
       is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
-      await osKeyStoreLoginShown;
     }
   );
-  await onUsed;
 
   creditCards = await getCreditCards();
   is(creditCards.length, 2, "Still 2 credit card");
   is(SpecialPowers.getIntPref(CREDITCARDS_USED_STATUS_PREF), 1,
     "User neither sees the doorhanger nor uses autofill but somehow has a record in the storage");
   SpecialPowers.clearUserPref(CREDITCARDS_USED_STATUS_PREF);
   await removeAllRecords();
 });
 
 add_task(async function test_submit_creditCard_with_invalid_network() {
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
-      let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
         let name = form.querySelector("#cc-name");
         name.focus();
         name.setUserInput("User 1");
 
         form.querySelector("#cc-number").setUserInput("5038146897157463");
         form.querySelector("#cc-exp-month").setUserInput("12");
@@ -666,17 +659,16 @@ add_task(async function test_submit_cred
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
       await clickDoorhangerButton(MAIN_BUTTON);
-      await onChanged;
     }
   );
 
   let creditCards = await getCreditCards();
   is(creditCards.length, 1, "1 credit card in storage");
   is(creditCards[0]["cc-name"], "User 1", "Verify the name field");
   is(creditCards[0]["cc-type"], undefined, "Invalid network/cc-type was not saved");
 
rename from browser/extensions/formautofill/test/browser/browser_creditCard_fill_cancel_login.js
rename to browser/extensions/formautofill/test/browser/browser_creditCard_fill_master_password.js
--- a/browser/extensions/formautofill/test/browser/browser_creditCard_fill_cancel_login.js
+++ b/browser/extensions/formautofill/test/browser/browser_creditCard_fill_master_password.js
@@ -1,25 +1,25 @@
 "use strict";
 
-add_task(async function test_fill_creditCard_but_cancel_login() {
-  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
-    todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
-    return;
-  }
-
+add_task(async function test_fill_creditCard_with_mp_enabled_but_canceled() {
   await saveCreditCard(TEST_CREDIT_CARD_2);
 
-  let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(false); // cancel
+  LoginTestUtils.masterPassword.enable();
+  registerCleanupFunction(() => {
+    LoginTestUtils.masterPassword.disable();
+  });
+
+  let masterPasswordDialogShown = waitForMasterPasswordDialog(false); // cancel
   await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
     async function(browser) {
       await openPopupOn(browser, "#cc-name");
       const ccItem = getDisplayedPopupItems(browser)[0];
       await EventUtils.synthesizeMouseAtCenter(ccItem, {});
-      await Promise.all([osKeyStoreLoginShown, expectPopupClose(browser)]);
+      await Promise.all([masterPasswordDialogShown, expectPopupClose(browser)]);
 
       await ContentTask.spawn(browser, {}, async function() {
         is(content.document.querySelector("#cc-name").value, "", "Check name");
         is(content.document.querySelector("#cc-number").value, "", "Check number");
       });
     }
   );
 });
--- a/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
+++ b/browser/extensions/formautofill/test/browser/browser_manageCreditCardsDialog.js
@@ -1,29 +1,32 @@
 "use strict";
 
 const TEST_SELECTORS = {
   selRecords: "#credit-cards",
   btnRemove: "#remove",
+  btnShowHideCreditCards: "#show-hide-credit-cards",
   btnAdd: "#add",
   btnEdit: "#edit",
 };
 
 const DIALOG_SIZE = "width=600,height=400";
 
 add_task(async function test_manageCreditCardsInitialState() {
   await BrowserTestUtils.withNewTab({gBrowser, url: MANAGE_CREDIT_CARDS_DIALOG_URL}, async function(browser) {
     await ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
       let selRecords = content.document.querySelector(args.selRecords);
       let btnRemove = content.document.querySelector(args.btnRemove);
+      let btnShowHideCreditCards = content.document.querySelector(args.btnShowHideCreditCards);
       let btnAdd = content.document.querySelector(args.btnAdd);
       let btnEdit = content.document.querySelector(args.btnEdit);
 
       is(selRecords.length, 0, "No credit card");
       is(btnRemove.disabled, true, "Remove button disabled");
+      is(btnShowHideCreditCards.disabled, true, "Show Credit Cards button disabled");
       is(btnAdd.disabled, false, "Add button enabled");
       is(btnEdit.disabled, true, "Edit button disabled");
     });
   });
 });
 
 add_task(async function test_cancelManageCreditCardsDialogWithESC() {
   let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL);
@@ -99,16 +102,67 @@ add_task(async function test_creditCards
   is(selRecords.length, 1, "One credit card is shown");
 
   await removeCreditCards([selRecords.options[0].value]);
   await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
   is(selRecords.length, 0, "Credit card is removed");
   win.close();
 });
 
+add_task(async function test_showCreditCards() {
+  await SpecialPowers.pushPrefEnv({"set": [["privacy.reduceTimerPrecision", false]]});
+  await saveCreditCard(TEST_CREDIT_CARD_1);
+  await saveCreditCard(TEST_CREDIT_CARD_2);
+  await saveCreditCard(TEST_CREDIT_CARD_3);
+
+  let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
+  await waitForFocusAndFormReady(win);
+
+  let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
+  let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
+
+  is(btnShowHideCreditCards.disabled, false, "Show credit cards button enabled");
+  is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
+
+  // Show credit card numbers
+  EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
+  await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
+  is(selRecords[0].text, "5103059495477870", "Decrypted credit card 3");
+  is(selRecords[1].text, "4929001587121045, Timothy Berners-Lee", "Decrypted credit card 2");
+  is(selRecords[2].text, "4111111111111111, John Doe", "Decrypted credit card 1");
+  is(btnShowHideCreditCards.textContent, "Hide Credit Cards", "Label should be 'Hide Credit Cards'");
+
+  // Hide credit card numbers
+  EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
+  await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
+  is(selRecords[0].text, "**** 7870", "Masked credit card 3");
+  is(selRecords[1].text, "**** 1045, Timothy Berners-Lee", "Masked credit card 2");
+  is(selRecords[2].text, "**** 1111, John Doe", "Masked credit card 1");
+  is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
+
+  // Show credit card numbers again to test if they revert back to masked form when reloaded
+  EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
+  await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
+  // Ensure credit card numbers are shown again
+  is(selRecords[0].text, "5103059495477870", "Decrypted credit card 3");
+  // Remove a card to trigger reloading
+  await removeCreditCards([selRecords.options[2].value]);
+  await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+  is(selRecords[0].text, "**** 7870", "Masked credit card 3");
+  is(selRecords[1].text, "**** 1045, Timothy Berners-Lee", "Masked credit card 2");
+
+  // Remove the rest of the cards
+  await removeCreditCards([selRecords.options[1].value]);
+  await removeCreditCards([selRecords.options[0].value]);
+  await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
+  is(btnShowHideCreditCards.disabled, true, "Show credit cards button is disabled when there is no card");
+
+  win.close();
+});
+
 add_task(async function test_showCreditCardIcons() {
   await SpecialPowers.pushPrefEnv({"set": [["privacy.reduceTimerPrecision", false]]});
   await saveCreditCard(TEST_CREDIT_CARD_1);
   let unknownCard = Object.assign({}, TEST_CREDIT_CARD_3, {"cc-type": "gringotts"});
   await saveCreditCard(unknownCard);
 
   let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
   await waitForFocusAndFormReady(win);
@@ -134,51 +188,45 @@ add_task(async function test_showCreditC
   }
 
   await removeCreditCards([option0.value, option1.value]);
   await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
   is(selRecords.length, 0, "Credit card is removed");
   win.close();
 });
 
-add_task(async function test_hasEditLoginPrompt() {
-  if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
-    todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
-    return;
-  }
 
+add_task(async function test_hasMasterPassword() {
   await saveCreditCard(TEST_CREDIT_CARD_1);
+  LoginTestUtils.masterPassword.enable();
 
   let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
   await waitForFocusAndFormReady(win);
 
   let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
   let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
+  let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
   let btnAdd = win.document.querySelector(TEST_SELECTORS.btnAdd);
-  // let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
+  let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
+  let masterPasswordDialogShown = waitForMasterPasswordDialog();
 
-  EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
+  is(btnShowHideCreditCards.hidden, true, "Show credit cards button is hidden");
 
-  // Login dialog should show when trying to edit a credit card record.
-  // TODO: test disabled because re-auth is not implemented yet (bug 1429265).
-  /*
-  let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(); // cancel
+  // Master password dialog should show when trying to edit a credit card record.
+  EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
   EventUtils.synthesizeMouseAtCenter(btnEdit, {}, win);
-  await osKeyStoreLoginShown;
-  await new Promise(resolve => waitForFocus(resolve, win));
-  await new Promise(resolve => executeSoon(resolve));
-  */
+  await masterPasswordDialogShown;
 
-  // Login is not required for removing credit cards.
+  // Master password is not required for removing credit cards.
   EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
   await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
   is(selRecords.length, 0, "Credit card is removed");
 
   // gSubDialog.open should be called when trying to add a credit card,
-  // no OS login dialog is required.
+  // no master password is required.
   window.gSubDialog = {
     open: url => is(url, EDIT_CREDIT_CARD_DIALOG_URL, "Edit credit card dialog is called"),
   };
   EventUtils.synthesizeMouseAtCenter(btnAdd, {}, win);
   delete window.gSubDialog;
 
   win.close();
 });
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -3,31 +3,32 @@
             TEST_ADDRESS_IE_1,
             TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2, TEST_CREDIT_CARD_3, FORM_URL, CREDITCARD_FORM_URL,
             FTU_PREF, ENABLED_AUTOFILL_ADDRESSES_PREF, AUTOFILL_CREDITCARDS_AVAILABLE_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF,
             SUPPORTED_COUNTRIES_PREF,
             SYNC_USERNAME_PREF, SYNC_ADDRESSES_PREF, SYNC_CREDITCARDS_PREF, SYNC_CREDITCARDS_AVAILABLE_PREF, CREDITCARDS_USED_STATUS_PREF,
             DEFAULT_REGION_PREF,
             sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
             getAddresses, saveAddress, removeAddresses, saveCreditCard,
-            getDisplayedPopupItems, getDoorhangerCheckbox,
+            getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
             getNotification, getDoorhangerButton, removeAllRecords, testDialog */
 
 "use strict";
 
-ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", this);
-ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm", this);
+ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
+ChromeUtils.import("resource://formautofill/MasterPassword.jsm", this);
 
 const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
 const MANAGE_CREDIT_CARDS_DIALOG_URL = "chrome://formautofill/content/manageCreditCards.xhtml";
 const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml";
 const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCard.xhtml";
 const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
 const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
-const CREDITCARD_FORM_URL = "https://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
+const CREDITCARD_FORM_URL =
+  "https://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
 const FTU_PREF = "extensions.formautofill.firstTimeUse";
 const CREDITCARDS_USED_STATUS_PREF = "extensions.formautofill.creditCards.used";
 const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
 const AUTOFILL_CREDITCARDS_AVAILABLE_PREF = "extensions.formautofill.creditCards.available";
 const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled";
 const SUPPORTED_COUNTRIES_PREF = "extensions.formautofill.supportedCountries";
 const SYNC_USERNAME_PREF = "services.sync.username";
 const SYNC_ADDRESSES_PREF = "services.sync.engine.addresses";
@@ -320,16 +321,34 @@ async function clickDoorhangerButton(but
 function getDoorhangerCheckbox() {
   return getNotification().checkbox;
 }
 
 function getDoorhangerButton(button) {
   return getNotification()[button];
 }
 
+
+// Wait for the master password dialog to popup and enter the password to log in
+// if "login" is "true" or dismiss it directly if otherwise.
+function waitForMasterPasswordDialog(login = false) {
+  info("expecting master password dialog loaded");
+  let dialogShown = TestUtils.topicObserved("common-dialog-loaded");
+  return dialogShown.then(([subject]) => {
+    let dialog = subject.Dialog;
+    is(dialog.args.title, "Password Required", "Master password dialog shown");
+    if (login) {
+      dialog.ui.password1Textbox.value = LoginTestUtils.masterPassword.masterPassword;
+      dialog.ui.button0.click();
+    } else {
+      dialog.ui.button1.click();
+    }
+  });
+}
+
 async function removeAllRecords() {
   let addresses = await getAddresses();
   if (addresses.length) {
     await removeAddresses(addresses.map(address => address.guid));
   }
 
   let creditCards = await getCreditCards();
   if (creditCards.length) {
@@ -342,26 +361,19 @@ async function waitForFocusAndFormReady(
     new Promise(resolve => waitForFocus(resolve, win)),
     BrowserTestUtils.waitForEvent(win, "FormReady"),
   ]);
 }
 
 async function testDialog(url, testFn, arg = undefined) {
   if (url == EDIT_CREDIT_CARD_DIALOG_URL && arg && arg.record) {
     arg.record = Object.assign({}, arg.record, {
-      "cc-number": await OSKeyStore.decrypt(arg.record["cc-number-encrypted"]),
+      "cc-number": await MasterPassword.decrypt(arg.record["cc-number-encrypted"]),
     });
   }
   let win = window.openDialog(url, null, "width=600,height=600", arg);
   await waitForFocusAndFormReady(win);
   let unloadPromise = BrowserTestUtils.waitForEvent(win, "unload");
   await testFn(win);
   return unloadPromise;
 }
 
-add_task(function setup() {
-  OSKeyStoreTestUtils.setup();
-});
-
 registerCleanupFunction(removeAllRecords);
-registerCleanupFunction(async () => {
-  await OSKeyStoreTestUtils.cleanup();
-});
deleted file mode 100644
--- a/browser/extensions/formautofill/test/fixtures/OSKeyStoreTestUtils.jsm
+++ /dev/null
@@ -1,93 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var EXPORTED_SYMBOLS = [
-  "OSKeyStoreTestUtils",
-];
-
-ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", this);
-// TODO: Consider AppConstants.MOZILLA_OFFICIAL to decide if we could test re-auth (bug 1429265).
-/*
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-*/
-ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
-ChromeUtils.import("resource://testing-common/TestUtils.jsm");
-
-var OSKeyStoreTestUtils = {
-  /*
-  TEST_ONLY_REAUTH: "extensions.formautofill.osKeyStore.unofficialBuildOnlyLogin",
-  */
-
-  setup() {
-    // TODO: run tests with master password enabled to ensure NSS-implemented
-    // key store prompts on re-auth (bug 1429265)
-    /*
-    LoginTestUtils.masterPassword.enable();
-    */
-
-    this.ORIGINAL_STORE_LABEL = OSKeyStore.STORE_LABEL;
-    OSKeyStore.STORE_LABEL = "test-" + Math.random().toString(36).substr(2);
-  },
-
-  async cleanup() {
-    // TODO: run tests with master password enabled to ensure NSS-implemented
-    // key store prompts on re-auth (bug 1429265)
-    /*
-    LoginTestUtils.masterPassword.disable();
-    */
-
-    await OSKeyStore.cleanup();
-    OSKeyStore.STORE_LABEL = this.ORIGINAL_STORE_LABEL;
-  },
-
-  canTestOSKeyStoreLogin() {
-    // TODO: return true based on whether or not we could test the prompt on
-    // the platform (bug 1429265).
-    /*
-    return OSKeyStore.isNSSKeyStore || !AppConstants.MOZILLA_OFFICIAL;
-    */
-    return true;
-  },
-
-  // Wait for the master password dialog to popup and enter the password to log in
-  // if "login" is "true" or dismiss it directly if otherwise.
-  async waitForOSKeyStoreLogin(login = false) {
-    // TODO: Always resolves for now, because we are skipping re-auth on all
-    // platforms (bug 1429265).
-    /*
-    if (OSKeyStore.isNSSKeyStore) {
-      await this.waitForMasterPasswordDialog(login);
-      return;
-    }
-
-    const str = login ? "pass" : "cancel";
-
-    Services.prefs.setStringPref(this.TEST_ONLY_REAUTH, str);
-
-    await TestUtils.topicObserved("oskeystore-testonly-reauth",
-      (subject, data) => data == str);
-
-    Services.prefs.setStringPref(this.TEST_ONLY_REAUTH, "");
-    */
-  },
-
-  async waitForMasterPasswordDialog(login = false) {
-    let [subject] = await TestUtils.topicObserved("common-dialog-loaded");
-
-    let dialog = subject.Dialog;
-    if (dialog.args.title !== "Password Required") {
-      throw new Error("Incorrect master password dialog title");
-    }
-
-    if (login) {
-      dialog.ui.password1Textbox.value = LoginTestUtils.masterPassword.masterPassword;
-      dialog.ui.button0.click();
-    } else {
-      dialog.ui.button1.click();
-    }
-    await TestUtils.waitForTick();
-  },
-};
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -1,11 +1,10 @@
 /* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SimpleTest.js */
 /* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/EventUtils.js */
-/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/AddTask.js */
 /* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
 /* eslint-disable no-unused-vars */
 
 "use strict";
 
 let formFillChromeScript;
 let defaultTextColor;
 let expectingPopup = null;
@@ -197,25 +196,16 @@ async function cleanUpCreditCards() {
   return invokeAsyncChromeTask("FormAutofillTest:CleanUpCreditCards", "FormAutofillTest:CreditCardsCleanedUp");
 }
 
 async function cleanUpStorage() {
   await cleanUpAddresses();
   await cleanUpCreditCards();
 }
 
-async function canTestOSKeyStoreLogin() {
-  let {canTest} = await invokeAsyncChromeTask("FormAutofillTest:CanTestOSKeyStoreLogin", "FormAutofillTest:CanTestOSKeyStoreLoginResult");
-  return canTest;
-}
-
-async function waitForOSKeyStoreLogin(login = false) {
-  await invokeAsyncChromeTask("FormAutofillTest:OSKeyStoreLogin", "FormAutofillTest:OSKeyStoreLoggedIn", {login});
-}
-
 function patchRecordCCNumber(record) {
   const number = record["cc-number"];
   const ccNumberFmt = {
     affix: "****",
     label: number.substr(-4),
   };
 
   return Object.assign({}, record, {ccNumberFmt});
@@ -268,22 +258,16 @@ function formAutoFillCommonSetup() {
   formFillChromeScript = SpecialPowers.loadChromeScript(chromeURL);
   formFillChromeScript.addMessageListener("onpopupshown", ({results}) => {
     gLastAutoCompleteResults = results;
     if (gPopupShownListener) {
       gPopupShownListener({results});
     }
   });
 
-  add_task(async function setup() {
-    formFillChromeScript.sendAsyncMessage("setup");
-    info(`expecting the storage setup`);
-    await formFillChromeScript.promiseOneMessage("setup-finished");
-  });
-
   SimpleTest.registerCleanupFunction(async () => {
     formFillChromeScript.sendAsyncMessage("cleanup");
     info(`expecting the storage cleanup`);
     await formFillChromeScript.promiseOneMessage("cleanup-finished");
 
     formFillChromeScript.destroy();
     expectingPopup = null;
   });
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -1,19 +1,16 @@
 // assert is available to chrome scripts loaded via SpecialPowers.loadChromeScript.
 /* global assert */
 /* eslint-env mozilla/frame-script */
 
 "use strict";
 
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
-ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
-ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm");
 
 let {formAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
 
 const {ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME} = FormAutofillUtils;
 
 var ParentUtils = {
   async _getRecords(collectionName) {
     return new Promise(resolve => {
@@ -112,25 +109,19 @@ var ParentUtils = {
     if (guids.length == 0) {
       sendAsyncMessage("FormAutofillTest:CreditCardsCleanedUp");
       return;
     }
 
     await this.operateCreditCard("remove", {guids}, "FormAutofillTest:CreditCardsCleanedUp");
   },
 
-  setup() {
-    OSKeyStoreTestUtils.setup();
-  },
-
   async cleanup() {
     await this.cleanUpAddresses();
     await this.cleanUpCreditCards();
-    await OSKeyStoreTestUtils.cleanup();
-
     Services.obs.removeObserver(this, "formautofill-storage-changed");
   },
 
   _areRecordsMatching(recordA, recordB, collectionName) {
     for (let field of formAutofillStorage[collectionName].VALID_FIELDS) {
       if (recordA[field] !== recordB[field]) {
         return false;
       }
@@ -219,28 +210,13 @@ addMessageListener("FormAutofillTest:Rem
 addMessageListener("FormAutofillTest:CheckCreditCards", (msg) => {
   ParentUtils.checkCreditCards(msg);
 });
 
 addMessageListener("FormAutofillTest:CleanUpCreditCards", (msg) => {
   ParentUtils.cleanUpCreditCards();
 });
 
-addMessageListener("FormAutofillTest:CanTestOSKeyStoreLogin", (msg) => {
-  sendAsyncMessage("FormAutofillTest:CanTestOSKeyStoreLoginResult",
-    {canTest: OSKeyStore.isNSSKeyStore || !AppConstants.MOZILLA_OFFICIAL});
-});
-
-addMessageListener("FormAutofillTest:OSKeyStoreLogin", async (msg) => {
-  await OSKeyStoreTestUtils.waitForOSKeyStoreLogin(msg.login);
-  sendAsyncMessage("FormAutofillTest:OSKeyStoreLoggedIn");
+addMessageListener("cleanup", () => {
+  ParentUtils.cleanup().then(() => {
+    sendAsyncMessage("cleanup-finished", {});
+  });
 });
-
-addMessageListener("setup", async () => {
-  ParentUtils.setup();
-  sendAsyncMessage("setup-finished", {});
-});
-
-addMessageListener("cleanup", async () => {
-  await ParentUtils.cleanup();
-
-  sendAsyncMessage("cleanup-finished", {});
-});
--- a/browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_creditcard_autocomplete_form.html
@@ -145,60 +145,41 @@ add_task(async function check_search_res
   await setInput("#cc-name", "");
   synthesizeKey("KEY_ArrowDown");
   await expectPopup();
   checkMenuEntries(["John Smith"], false);
 
   await SpecialPowers.popPrefEnv();
 });
 
-let canTest;
-
 // Autofill the credit card from dropdown menu.
 add_task(async function check_fields_after_form_autofill() {
-  canTest = await canTestOSKeyStoreLogin();
-  if (!canTest) {
-    todo(canTest, "Cannot test OS key store login on official builds.");
-    return;
-  }
-
   await setInput("#cc-exp-year", 202);
 
   synthesizeKey("KEY_ArrowDown");
   await expectPopup();
   checkMenuEntries(MOCK_STORAGE.slice(1).map(patchRecordCCNumber).map(cc => JSON.stringify({
     primary: cc["cc-exp-year"],
     secondary: cc.ccNumberFmt.affix + cc.ccNumberFmt.label,
   })));
 
   synthesizeKey("KEY_ArrowDown");
-  let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true);
-  await new Promise(resolve => SimpleTest.executeSoon(resolve));
   await triggerAutofillAndCheckProfile(MOCK_STORAGE[1]);
-  await osKeyStoreLoginShown;
 });
 
 // Fallback to history search after autofill address.
 add_task(async function check_fallback_after_form_autofill() {
-  if (!canTest) {
-    return;
-  }
-
   await setInput("#cc-name", "", true);
   synthesizeKey("KEY_ArrowDown");
   await expectPopup();
   checkMenuEntries(["John Smith"], false);
 });
 
 // Resume form autofill once all the autofilled fileds are changed.
 add_task(async function check_form_autofill_resume() {
-  if (!canTest) {
-    return;
-  }
-
   document.querySelector("#cc-name").blur();
   document.querySelector("#form1").reset();
 
   await setInput("#cc-name", "");
   synthesizeKey("KEY_ArrowDown");
   await expectPopup();
   checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(cc => JSON.stringify({
     primary: cc["cc-name"],
--- a/browser/extensions/formautofill/test/mochitest/test_clear_form.html
+++ b/browser/extensions/formautofill/test/mochitest/test_clear_form.html
@@ -98,26 +98,19 @@ add_task(async function clear_modified_f
   await setInput("#tel", "+1111111111", true);
 
   await triggerPopupAndHoverItem("#street-address", 0);
   await confirmClear("#street-address");
   checkIsFormCleared({tel: "+1111111111"});
 });
 
 add_task(async function clear_distinct_section() {
-  if (!(await canTestOSKeyStoreLogin())) {
-    todo(false, "Cannot test OS key store login on official builds.");
-    return;
-  }
-
   document.getElementById("form1").reset();
   await triggerPopupAndHoverItem("#cc-name", 0);
-  let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true);
   await triggerAutofillAndCheckProfile(MOCK_CC_STORAGE[0]);
-  await osKeyStoreLoginShown;
 
   await triggerPopupAndHoverItem("#organization", 0);
   await triggerAutofillAndCheckProfile(MOCK_ADDR_STORAGE[0]);
   await triggerPopupAndHoverItem("#street-address", 0);
   await confirmClear("#street-address");
 
   for (const [id, val] of Object.entries(MOCK_CC_STORAGE[0])) {
     const element = document.getElementById(id);
--- a/browser/extensions/formautofill/test/unit/head.js
+++ b/browser/extensions/formautofill/test/unit/head.js
@@ -94,17 +94,17 @@ async function initProfileStorage(fileNa
 
   let onChanged = TestUtils.topicObserved(
     "formautofill-storage-changed",
     (subject, data) =>
       data == "add" &&
       subject.wrappedJSObject.collectionName == collectionName
   );
   for (let record of records) {
-    Assert.ok(await profileStorage[collectionName].add(record));
+    Assert.ok(profileStorage[collectionName].add(record));
     await onChanged;
   }
   await profileStorage._saveImmediately();
   return profileStorage;
 }
 
 function verifySectionFieldDetails(sections, expectedResults) {
   Assert.equal(sections.length, expectedResults.length, "Expected section count.");
@@ -218,18 +218,8 @@ add_task(async function head_initialize(
     Services.prefs.clearUserPref("extensions.formautofill.creditCards.available");
     Services.prefs.clearUserPref("extensions.formautofill.heuristics.enabled");
     Services.prefs.clearUserPref("extensions.formautofill.section.enabled");
     Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
   });
 
   await loadExtension();
 });
-
-let OSKeyStoreTestUtils;
-add_task(async function os_key_store_setup() {
-  ({OSKeyStoreTestUtils} =
-    ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm", {}));
-  OSKeyStoreTestUtils.setup();
-  registerCleanupFunction(async function cleanup() {
-    await OSKeyStoreTestUtils.cleanup();
-  });
-});
--- a/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
+++ b/browser/extensions/formautofill/test/unit/test_autofillFormFields.js
@@ -1,18 +1,19 @@
 /*
  * Test for form auto fill content helper fill all inputs function.
  */
 /* eslint-disable mozilla/no-arbitrary-setTimeout */
 
 "use strict";
 
+let MasterPassword;
 add_task(async function setup() {
   ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
-  ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
+  ({MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {}));
 });
 
 const TESTCASES = [
   {
     description: "Form without autocomplete property",
     document: `<form><input id="given-name"><input id="family-name">
                <input id="street-addr"><input id="city"><select id="country"></select>
                <input id='email'><input id="tel"></form>`,
@@ -471,31 +472,38 @@ const TESTCASES_FILL_SELECT = [
 function do_test(testcases, testFn) {
   for (let tc of testcases) {
     (function() {
       let testcase = tc;
       add_task(async function() {
         info("Starting testcase: " + testcase.description);
         let ccNumber = testcase.profileData["cc-number"];
         if (ccNumber) {
-          testcase.profileData["cc-number-encrypted"] = await OSKeyStore.encrypt(ccNumber);
+          testcase.profileData["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
           delete testcase.profileData["cc-number"];
         }
 
         let doc = MockDocument.createTestDocument("http://localhost:8080/test/",
                                                   testcase.document);
         let form = doc.querySelector("form");
         let formLike = FormLikeFactory.createFromForm(form);
         let handler = new FormAutofillHandler(formLike);
         let promises = [];
-        // Replace the internal decrypt method with OSKeyStore API,
-        // but don't pass the reauth parameter to avoid triggering
-        // reauth login dialog in these tests.
+        // Replace the interal decrypt method with MasterPassword API
         let decryptHelper = async (cipherText, reauth) => {
-          return OSKeyStore.decrypt(cipherText, false);
+          let string;
+          try {
+            string = await MasterPassword.decrypt(cipherText, reauth);
+          } catch (e) {
+            if (e.result != Cr.NS_ERROR_ABORT) {
+              throw e;
+            }
+            info("User canceled master password entry");
+          }
+          return string;
         };
 
         handler.collectFormFields();
 
         let focusedInput = doc.getElementById(testcase.focusedInputId);
         handler.focusedInput = focusedInput;
 
         for (let section of handler.sections) {
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -311,17 +311,17 @@ add_task(async function test_add() {
   let creditCards = await profileStorage.creditCards.getAll();
 
   Assert.equal(creditCards.length, 2);
 
   do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_1);
   do_check_credit_card_matches(creditCards[1], TEST_CREDIT_CARD_2);
 
   Assert.notEqual(creditCards[0].guid, undefined);
-  Assert.equal(creditCards[0].version, 2);
+  Assert.equal(creditCards[0].version, 1);
   Assert.notEqual(creditCards[0].timeCreated, undefined);
   Assert.equal(creditCards[0].timeLastModified, creditCards[0].timeCreated);
   Assert.equal(creditCards[0].timeLastUsed, 0);
   Assert.equal(creditCards[0].timesUsed, 0);
 
   // Empty string should be deleted before saving.
   await profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_EMPTY_FIELD);
   let creditCard = profileStorage.creditCards._data[2];
@@ -417,24 +417,16 @@ add_task(async function test_update() {
   creditCard = profileStorage.creditCards._data[0];
   Assert.equal(creditCard["cc-number"],
     CreditCard.getLongMaskedNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
   await profileStorage.creditCards.update(profileStorage.creditCards._data[1].guid, TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD, true);
   creditCard = profileStorage.creditCards._data[1];
   Assert.equal(creditCard["cc-number"],
     CreditCard.getLongMaskedNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
 
-  // Decryption failure of existing record should not prevent it from being updated.
-  creditCard = profileStorage.creditCards._data[0];
-  creditCard["cc-number-encrypted"] = "INVALID";
-  await profileStorage.creditCards.update(profileStorage.creditCards._data[0].guid, TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD, false);
-  creditCard = profileStorage.creditCards._data[0];
-  Assert.equal(creditCard["cc-number"],
-    CreditCard.getLongMaskedNumber(TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]));
-
   await Assert.rejects(profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
     /No matching record\./
   );
 
   await Assert.rejects(profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./
   );
 
@@ -658,17 +650,23 @@ add_task(async function test_getDuplicat
   Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), null);
 
   // Numbers with the same last 4 digits shouldn't be treated as a duplicate.
   record = Object.assign({}, TEST_CREDIT_CARD_3);
   let last4Digits = record["cc-number"].substr(-4);
   // This number differs from TEST_CREDIT_CARD_3 by swapping the order of the
   // 09 and 90 adjacent digits, which is still a valid credit card number.
   record["cc-number"] = "358999378390" + last4Digits;
+  Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), null);
 
-  // We treat numbers with the same last 4 digits as a duplicate.
+  // ... However, we treat numbers with the same last 4 digits as a duplicate if
+  // the master password is enabled.
+  let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
+  let token = tokendb.getInternalKeyToken();
+  token.reset();
+  token.initPassword("password");
   Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), guid);
 
-  // Even though the last 4 digits are the same, an invalid credit card number
-  // should never be treated as a duplicate.
+  // ... Even though the master password is enabled and the last 4 digits are the
+  // same, an invalid credit card number should never be treated as a duplicate.
   record["cc-number"] = "************" + last4Digits;
   Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), null);
 });
--- a/browser/extensions/formautofill/test/unit/test_getRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_getRecords.js
@@ -2,20 +2,19 @@
  * Test for make sure getRecords can retrieve right collection from storage.
  */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
 
 let FormAutofillParent;
-let OSKeyStore;
 add_task(async function setup() {
   ({FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {}));
-  ({OSKeyStore} = ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", {}));
+  ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
 });
 
 const TEST_ADDRESS_1 = {
   "given-name": "Timothy",
   "additional-name": "John",
   "family-name": "Berners-Lee",
   organization: "World Wide Web Consortium",
   "street-address": "32 Vassar Street\nMIT Room 32-G524",
@@ -172,63 +171,84 @@ add_task(async function test_getRecords_
   let formAutofillParent = new FormAutofillParent();
 
   await formAutofillParent.init();
   await formAutofillParent.formAutofillStorage.initialize();
   let collection = formAutofillParent.formAutofillStorage.creditCards;
   let encryptedCCRecords = await Promise.all([TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2].map(async record => {
     let clonedRecord = Object.assign({}, record);
     clonedRecord["cc-number"] = CreditCard.getLongMaskedNumber(record["cc-number"]);
-    clonedRecord["cc-number-encrypted"] = await OSKeyStore.encrypt(record["cc-number"]);
+    clonedRecord["cc-number-encrypted"] = await MasterPassword.encrypt(record["cc-number"]);
     return clonedRecord;
   }));
   sinon.stub(collection, "getAll", () =>
     Promise.resolve([Object.assign({}, encryptedCCRecords[0]), Object.assign({}, encryptedCCRecords[1])]));
+  let CreditCardsWithDecryptedNumber = [
+    Object.assign({}, encryptedCCRecords[0], {"cc-number-decrypted": TEST_CREDIT_CARD_1["cc-number"]}),
+    Object.assign({}, encryptedCCRecords[1], {"cc-number-decrypted": TEST_CREDIT_CARD_2["cc-number"]}),
+  ];
 
   let testCases = [
     {
-      description: "If the search string could match multiple creditCards",
+      description: "If the search string could match 1 creditCard (without masterpassword)",
+      filter: {
+        collectionName: "creditCards",
+        info: {fieldName: "cc-name"},
+        searchString: "John Doe",
+      },
+      expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
+    },
+    {
+      description: "If the search string could match multiple creditCards (without masterpassword)",
       filter: {
         collectionName: "creditCards",
         info: {fieldName: "cc-name"},
         searchString: "John",
       },
-      expectedResult: encryptedCCRecords,
+      expectedResult: CreditCardsWithDecryptedNumber,
     },
     {
-      description: "If the search string could not match any creditCard",
+      description: "If the search string could not match any creditCard (without masterpassword)",
       filter: {
         collectionName: "creditCards",
         info: {fieldName: "cc-name"},
         searchString: "T",
       },
       expectedResult: [],
     },
     {
-      description: "Return all creditCards if focused field is cc number; " +
-        "if the search string could match multiple creditCards",
+      description: "If the search number string could match 1 creditCard (without masterpassword)",
+      filter: {
+        collectionName: "creditCards",
+        info: {fieldName: "cc-number"},
+        searchString: "411",
+      },
+      expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
+    },
+    {
+      description: "If the search string could match multiple creditCards (without masterpassword)",
       filter: {
         collectionName: "creditCards",
         info: {fieldName: "cc-number"},
         searchString: "4",
       },
-      expectedResult: encryptedCCRecords,
+      expectedResult: CreditCardsWithDecryptedNumber,
     },
     {
-      description: "If the search string could match 1 creditCard",
+      description: "If the search string could match 1 creditCard (with masterpassword)",
       filter: {
         collectionName: "creditCards",
         info: {fieldName: "cc-name"},
         searchString: "John Doe",
       },
       mpEnabled: true,
       expectedResult: encryptedCCRecords.slice(0, 1),
     },
     {
-      description: "Return all creditCards if focused field is cc number",
+      description: "Return all creditCards if focused field is cc number (with masterpassword)",
       filter: {
         collectionName: "creditCards",
         info: {fieldName: "cc-number"},
         searchString: "411",
       },
       mpEnabled: true,
       expectedResult: encryptedCCRecords,
     },
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_masterPassword.js
@@ -0,0 +1,119 @@
+/**
+ * Tests of MasterPassword.jsm
+ */
+
+"use strict";
+const {MockRegistrar} =
+  ChromeUtils.import("resource://testing-common/MockRegistrar.jsm", {});
+
+let MasterPassword;
+add_task(async function setup() {
+  ({MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {}));
+});
+
+const TESTCASES = [{
+  description: "With master password set",
+  masterPassword: "fakemp",
+  mpEnabled: true,
+},
+{
+  description: "Without master password set",
+  masterPassword: "", // "" means no master password
+  mpEnabled: false,
+}];
+
+
+// Tests that PSM can successfully ask for a password from the user and relay it
+// back to NSS. Does so by mocking out the actual dialog and "filling in" the
+// password. Also tests that providing an incorrect password will fail (well,
+// technically the user will just get prompted again, but if they then cancel
+// the dialog the overall operation will fail).
+
+let gMockPrompter = {
+  passwordToTry: null,
+  numPrompts: 0,
+
+  // This intentionally does not use arrow function syntax to avoid an issue
+  // where in the context of the arrow function, |this != gMockPrompter| due to
+  // how objects get wrapped when going across xpcom boundaries.
+  promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
+    this.numPrompts++;
+    if (this.numPrompts > 1) { // don't keep retrying a bad password
+      return false;
+    }
+    equal(text,
+          "Please enter your master password.",
+          "password prompt text should be as expected");
+    equal(checkMsg, null, "checkMsg should be null");
+    ok(this.passwordToTry, "passwordToTry should be non-null");
+    password.value = this.passwordToTry;
+    return true;
+  },
+
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
+};
+
+// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt
+// to call promptPassword. We return the mock one, above.
+let gWindowWatcher = {
+  getNewPrompter: () => gMockPrompter,
+  QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowWatcher]),
+};
+
+// Ensure that the appropriate initialization has happened.
+do_get_profile();
+
+let windowWatcherCID =
+  MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
+                         gWindowWatcher);
+registerCleanupFunction(() => {
+  MockRegistrar.unregister(windowWatcherCID);
+});
+
+TESTCASES.forEach(testcase => {
+  add_task(async function test_encrypt_decrypt() {
+    info("Starting testcase: " + testcase.description);
+
+    let token = MasterPassword._token;
+    token.initPassword(testcase.masterPassword);
+
+    // Test only: Force the token login without asking for master password
+    token.login(/* force */ false);
+    Assert.equal(testcase.mpEnabled, token.isLoggedIn(), "Token should now be logged into");
+    Assert.equal(MasterPassword.isEnabled, testcase.mpEnabled);
+
+    let testText = "test string";
+    let cipherText = await MasterPassword.encrypt(testText);
+    Assert.notEqual(testText, cipherText);
+    let plainText = await MasterPassword.decrypt(cipherText);
+    Assert.equal(testText, plainText);
+    if (token.isLoggedIn()) {
+      // Reset state.
+      gMockPrompter.numPrompts = 0;
+      token.logoutSimple();
+
+      ok(!token.isLoggedIn(),
+         "Token should be logged out after calling logoutSimple()");
+
+      // Try with the correct password.
+      gMockPrompter.passwordToTry = testcase.masterPassword;
+      await MasterPassword.encrypt(testText);
+      Assert.equal(gMockPrompter.numPrompts, 1, "should have prompted for encryption");
+
+      // Reset state.
+      gMockPrompter.numPrompts = 0;
+      token.logoutSimple();
+
+      try {
+        // Try with the incorrect password.
+        gMockPrompter.passwordToTry = "XXX";
+        await MasterPassword.decrypt(cipherText);
+        throw new Error("Not receiving canceled master password error");
+      } catch (e) {
+        Assert.equal(e.message, "User canceled master password entry");
+      }
+    }
+
+    token.reset();
+  });
+});
--- a/browser/extensions/formautofill/test/unit/test_migrateRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_migrateRecords.js
@@ -1,27 +1,24 @@
 /**
  * Tests the migration algorithm in profileStorage.
  */
 
 "use strict";
 
-ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
+let FormAutofillStorage;
 
-let FormAutofillStorage;
-let OSKeyStore;
 add_task(async function setup() {
   ({FormAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {}));
-  ({OSKeyStore} = ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", {}));
 });
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
 const ADDRESS_SCHEMA_VERSION = 1;
-const CREDIT_CARD_SCHEMA_VERSION = 2;
+const CREDIT_CARD_SCHEMA_VERSION = 1;
 
 const ADDRESS_TESTCASES = [
   {
     description: "The record version is equal to the current version. The migration shouldn't be invoked.",
     record: {
       guid: "test-guid",
       version: ADDRESS_SCHEMA_VERSION,
       "given-name": "Timothy",
@@ -244,98 +241,27 @@ let do_check_record_matches = (expectedR
 };
 
 add_task(async function test_migrateAddressRecords() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
-  for (let testcase of ADDRESS_TESTCASES) {
+  await Promise.all(ADDRESS_TESTCASES.map(async testcase => {
     info(testcase.description);
-    profileStorage._store.data.addresses = [testcase.record];
-    await profileStorage.addresses._migrateRecord(testcase.record, 0);
-    do_check_record_matches(testcase.expectedResult, profileStorage.addresses._data[0]);
-  }
+    await profileStorage.addresses._migrateRecord(testcase.record);
+    do_check_record_matches(testcase.expectedResult, testcase.record);
+  }));
 });
 
 add_task(async function test_migrateCreditCardRecords() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new FormAutofillStorage(path);
   await profileStorage.initialize();
 
-  for (let testcase of CREDIT_CARD_TESTCASES) {
+  await Promise.all(CREDIT_CARD_TESTCASES.map(async testcase => {
     info(testcase.description);
-    profileStorage._store.data.creditCards = [testcase.record];
-    await profileStorage.creditCards._migrateRecord(testcase.record, 0);
-    do_check_record_matches(testcase.expectedResult, profileStorage.creditCards._data[0]);
-  }
+    await profileStorage.creditCards._migrateRecord(testcase.record);
+    do_check_record_matches(testcase.expectedResult, testcase.record);
+  }));
 });
-
-add_task(async function test_migrateEncryptedCreditCardNumber() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-
-  let profileStorage = new FormAutofillStorage(path);
-  await profileStorage.initialize();
-
-  const ccNumber = "4111111111111111";
-  let cryptoSDR = Cc["@mozilla.org/login-manager/crypto/SDR;1"]
-    .createInstance(Ci.nsILoginManagerCrypto);
-
-  info("Encrypted credit card should be migrated from v1 to v2");
-
-  let record = {
-    guid: "test-guid",
-    version: 1,
-    "cc-name": "Timothy",
-    "cc-number-encrypted": cryptoSDR.encrypt(ccNumber),
-  };
-
-  let expectedRecord = {
-    guid: "test-guid",
-    version: CREDIT_CARD_SCHEMA_VERSION,
-    "cc-name": "Timothy",
-    "cc-given-name": "Timothy",
-  };
-  profileStorage._store.data.creditCards = [record];
-  await profileStorage.creditCards._migrateRecord(record, 0);
-  record = profileStorage.creditCards._data[0];
-
-  Assert.equal(expectedRecord.guid, record.guid);
-  Assert.equal(expectedRecord.version, record.version);
-  Assert.equal(expectedRecord["cc-name"], record["cc-name"]);
-  Assert.equal(expectedRecord["cc-given-name"], record["cc-given-name"]);
-
-  // Ciphertext of OS Key Store is not stable, must compare decrypted text here.
-  Assert.equal(ccNumber, await OSKeyStore.decrypt(record["cc-number-encrypted"]));
-});
-
-add_task(async function test_migrateEncryptedCreditCardNumberWithMP() {
-  LoginTestUtils.masterPassword.enable();
-
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-
-  let profileStorage = new FormAutofillStorage(path);
-  await profileStorage.initialize();
-
-  info("Encrypted credit card should be migrated a tombstone if MP is enabled");
-
-  let record = {
-    guid: "test-guid",
-    version: 1,
-    "cc-name": "Timothy",
-    "cc-number-encrypted": "(encrypted to be discarded)",
-  };
-
-  profileStorage._store.data.creditCards = [record];
-  await profileStorage.creditCards._migrateRecord(record, 0);
-  record = profileStorage.creditCards._data[0];
-
-  Assert.equal(record.guid, "test-guid");
-  Assert.equal(record.deleted, true);
-  Assert.equal(typeof record.version, "undefined");
-  Assert.equal(typeof record["cc-name"], "undefined");
-  Assert.equal(typeof record["cc-number-encrypted"], "undefined");
-
-  LoginTestUtils.masterPassword.disable();
-});
-
deleted file mode 100644
--- a/browser/extensions/formautofill/test/unit/test_osKeyStore.js
+++ /dev/null
@@ -1,144 +0,0 @@
-/**
- * Tests of OSKeyStore.jsm
- */
-
-"use strict";
-
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
-ChromeUtils.import("resource://testing-common/MockRegistrar.jsm");
-
-let OSKeyStore;
-add_task(async function setup() {
-  ({OSKeyStore} = ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", {}));
-});
-
-// Ensure that the appropriate initialization has happened.
-do_get_profile();
-
-// For NSS key store, mocking out the dialog and control it from here.
-let gMockPrompter = {
-  passwordToTry: "hunter2",
-  resolve: null,
-  login: undefined,
-
-  // This intentionally does not use arrow function syntax to avoid an issue
-  // where in the context of the arrow function, |this != gMockPrompter| due to
-  // how objects get wrapped when going across xpcom boundaries.
-  promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
-    equal(text,
-          "Please enter your master password.",
-          "password prompt text should be as expected");
-    equal(checkMsg, null, "checkMsg should be null");
-    if (this.login) {
-      password.value = this.passwordToTry;
-    }
-    this.resolve();
-    this.resolve = null;
-
-    return this.login;
-  },
-
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
-};
-
-// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt
-// to call promptPassword. We return the mock one, above.
-let gWindowWatcher = {
-  getNewPrompter: () => gMockPrompter,
-  QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowWatcher]),
-};
-
-let nssToken;
-
-const TEST_ONLY_REAUTH = "extensions.formautofill.osKeyStore.unofficialBuildOnlyLogin";
-
-async function waitForReauth(login = false) {
-  if (OSKeyStore.isNSSKeyStore) {
-    gMockPrompter.login = login;
-    await new Promise(resolve => { gMockPrompter.resolve = resolve; });
-
-    return;
-  }
-
-  let value = login ? "pass" : "cancel";
-  Services.prefs.setStringPref(TEST_ONLY_REAUTH, value);
-  await TestUtils.topicObserved("oskeystore-testonly-reauth",
-    (subject, data) => data == value);
-}
-
-const testText = "test string";
-let cipherText;
-
-add_task(async function test_encrypt_decrypt() {
-  Assert.equal(await OSKeyStore.ensureLoggedIn(), true, "Started logged in.");
-
-  cipherText = await OSKeyStore.encrypt(testText);
-  Assert.notEqual(testText, cipherText);
-
-  let plainText = await OSKeyStore.decrypt(cipherText);
-  Assert.equal(testText, plainText);
-});
-
-// TODO: skipped because re-auth is not implemented (bug 1429265).
-add_task(async function test_reauth() {
-  let canTest = OSKeyStore.isNSSKeyStore || !AppConstants.MOZILLA_OFFICIAL;
-  if (!canTest) {
-    todo_check_false(canTest,
-      "test_reauth: Cannot test OS key store login on official builds.");
-    return;
-  }
-
-  if (OSKeyStore.isNSSKeyStore) {
-    let windowWatcherCID;
-    windowWatcherCID =
-      MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
-                             gWindowWatcher);
-    registerCleanupFunction(() => {
-      MockRegistrar.unregister(windowWatcherCID);
-    });
-
-    // If we use the NSS key store implementation test that everything works
-    // when a master password is set.
-    // Set an initial password.
-    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
-                    .getService(Ci.nsIPK11TokenDB);
-    nssToken = tokenDB.getInternalKeyToken();
-    nssToken.initPassword("hunter2");
-  }
-
-  let reauthObserved = waitForReauth(false);
-  await new Promise(resolve => TestUtils.executeSoon(resolve));
-  try {
-    await OSKeyStore.decrypt(cipherText, true);
-    throw new Error("Not receiving canceled OS unlock error");
-  } catch (ex) {
-    Assert.equal(ex.message, "User canceled OS unlock entry");
-    Assert.equal(ex.result, Cr.NS_ERROR_ABORT);
-  }
-  await reauthObserved;
-
-  reauthObserved = waitForReauth(false);
-  await new Promise(resolve => TestUtils.executeSoon(resolve));
-  Assert.equal(await OSKeyStore.ensureLoggedIn(true), false, "Reauth cancelled.");
-  await reauthObserved;
-
-  reauthObserved = waitForReauth(true);
-  await new Promise(resolve => TestUtils.executeSoon(resolve));
-  let plainText2 = await OSKeyStore.decrypt(cipherText, true);
-  await reauthObserved;
-  Assert.equal(testText, plainText2);
-
-  reauthObserved = waitForReauth(true);
-  await new Promise(resolve => TestUtils.executeSoon(resolve));
-  Assert.equal(await OSKeyStore.ensureLoggedIn(true), true, "Reauth logged in.");
-  await reauthObserved;
-}).skip();
-
-add_task(async function test_decryption_failure() {
-  try {
-    await OSKeyStore.decrypt("Malformed cipher text");
-    throw new Error("Not receiving decryption error");
-  } catch (ex) {
-    Assert.notEqual(ex.result, Cr.NS_ERROR_ABORT);
-  }
-});
--- a/browser/extensions/formautofill/test/unit/test_reconcile.js
+++ b/browser/extensions/formautofill/test/unit/test_reconcile.js
@@ -465,220 +465,220 @@ const ADDRESS_RECONCILE_TESTCASES = [
 ];
 
 const CREDIT_CARD_RECONCILE_TESTCASES = [
   {
     description: "Local change",
     parent: {
       // So when we last wrote the record to the server, it had these values.
       "guid": "2bbd2d8fbc6b",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     local: [{
       // The current local record - by comparing against parent we can see that
       // only the cc-number has changed locally.
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     }],
     remote: {
       // This is the incoming record. It has the same values as "parent", so
       // we can deduce the record hasn't actually been changed remotely so we
       // can safely ignore the incoming record and write our local changes.
       "guid": "2bbd2d8fbc6b",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     reconciled: {
       "guid": "2bbd2d8fbc6b",
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     },
   },
   {
     description: "Remote change",
     parent: {
       "guid": "e3680e9f890d",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     }],
     remote: {
       "guid": "e3680e9f890d",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     },
     reconciled: {
       "guid": "e3680e9f890d",
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     },
   },
 
   {
     description: "New local field",
     parent: {
       "guid": "0cba738b1be0",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     }],
     remote: {
       "guid": "0cba738b1be0",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     reconciled: {
       "guid": "0cba738b1be0",
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
   },
   {
     description: "New remote field",
     parent: {
       "guid": "be3ef97f8285",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     }],
     remote: {
       "guid": "be3ef97f8285",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
     reconciled: {
       "guid": "be3ef97f8285",
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
   },
   {
     description: "Deleted field locally",
     parent: {
       "guid": "9627322248ec",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     }],
     remote: {
       "guid": "9627322248ec",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
     reconciled: {
       "guid": "9627322248ec",
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
   },
   {
     description: "Deleted field remotely",
     parent: {
       "guid": "7d7509f3eeb2",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     }],
     remote: {
       "guid": "7d7509f3eeb2",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     reconciled: {
       "guid": "7d7509f3eeb2",
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
   },
   {
     description: "Local and remote changes to unrelated fields",
     parent: {
       // The last time we wrote this to the server, "cc-exp-month" was 12.
       "guid": "e087a06dfc57",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
     local: [{
       // The current local record - so locally we've changed "cc-number".
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
       "cc-exp-month": 12,
     }],
     remote: {
       // Remotely, we've changed "cc-exp-month" to 1.
       "guid": "e087a06dfc57",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 1,
     },
     reconciled: {
       "guid": "e087a06dfc57",
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
       "cc-exp-month": 1,
     },
   },
   {
     description: "Multiple local changes",
     parent: {
       "guid": "340a078c596f",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     local: [{
       "cc-name": "Skip",
       "cc-number": "4111111111111111",
     }, {
       "cc-name": "Skip",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     }],
     remote: {
       "guid": "340a078c596f",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-year": 2000,
     },
     reconciled: {
       "guid": "340a078c596f",
       "cc-name": "Skip",
       "cc-number": "4111111111111111",
@@ -687,54 +687,54 @@ const CREDIT_CARD_RECONCILE_TESTCASES = 
     },
   },
   {
     // Local and remote diverged from the shared parent, but the values are the
     // same, so we shouldn't fork.
     description: "Same change to local and remote",
     parent: {
       "guid": "0b3a72a1bea2",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     }],
     remote: {
       "guid": "0b3a72a1bea2",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     },
     reconciled: {
       "guid": "0b3a72a1bea2",
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     },
   },
   {
     description: "Conflicting changes to single field",
     parent: {
       // This is what we last wrote to the sync server.
       "guid": "62068784d089",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     local: [{
       // The current version of the local record - the cc-number has changed locally.
       "cc-name": "John Doe",
       "cc-number": "5103059495477870",
     }],
     remote: {
       // An incoming record has a different cc-number than any of the above!
       "guid": "62068784d089",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     },
     forked: {
       // So we've forked the local record to a new GUID (and the next sync is
       // going to write this as a new record)
       "cc-name": "John Doe",
       "cc-number": "5103059495477870",
@@ -745,29 +745,29 @@ const CREDIT_CARD_RECONCILE_TESTCASES = 
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     },
   },
   {
     description: "Conflicting changes to multiple fields",
     parent: {
       "guid": "244dbb692e94",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "5103059495477870",
       "cc-exp-month": 1,
     }],
     remote: {
       "guid": "244dbb692e94",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
       "cc-exp-month": 3,
     },
     forked: {
       "cc-name": "John Doe",
       "cc-number": "5103059495477870",
       "cc-exp-month": 1,
@@ -778,28 +778,28 @@ const CREDIT_CARD_RECONCILE_TESTCASES = 
       "cc-number": "4929001587121045",
       "cc-exp-month": 3,
     },
   },
   {
     description: "Field deleted locally, changed remotely",
     parent: {
       "guid": "6fc45e03d19a",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     }],
     remote: {
       "guid": "6fc45e03d19a",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 3,
     },
     forked: {
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
@@ -809,29 +809,29 @@ const CREDIT_CARD_RECONCILE_TESTCASES = 
       "cc-number": "4111111111111111",
       "cc-exp-month": 3,
     },
   },
   {
     description: "Field changed locally, deleted remotely",
     parent: {
       "guid": "fff9fa27fa18",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 12,
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 3,
     }],
     remote: {
       "guid": "fff9fa27fa18",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
     },
     forked: {
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "cc-exp-month": 3,
     },
@@ -843,28 +843,28 @@ const CREDIT_CARD_RECONCILE_TESTCASES = 
   },
   {
     // Created, last modified should be synced; last used and times used should
     // be local. Remote created time older than local, remote modified time
     // newer than local.
     description: "Created, last modified time reconciliation without local changes",
     parent: {
       "guid": "5113f329c42f",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "timeCreated": 1234,
       "timeLastModified": 5678,
       "timeLastUsed": 5678,
       "timesUsed": 6,
     },
     local: [],
     remote: {
       "guid": "5113f329c42f",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "timeCreated": 1200,
       "timeLastModified": 5700,
       "timeLastUsed": 5700,
       "timesUsed": 3,
     },
     reconciled: {
@@ -878,31 +878,31 @@ const CREDIT_CARD_RECONCILE_TESTCASES = 
     },
   },
   {
     // Local changes, remote created time newer than local, remote modified time
     // older than local.
     description: "Created, last modified time reconciliation with local changes",
     parent: {
       "guid": "791e5608b80a",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "timeCreated": 1234,
       "timeLastModified": 5678,
       "timeLastUsed": 5678,
       "timesUsed": 6,
     },
     local: [{
       "cc-name": "John Doe",
       "cc-number": "4929001587121045",
     }],
     remote: {
       "guid": "791e5608b80a",
-      "version": 2,
+      "version": 1,
       "cc-name": "John Doe",
       "cc-number": "4111111111111111",
       "timeCreated": 1300,
       "timeLastModified": 5000,
       "timeLastUsed": 5000,
       "timesUsed": 3,
     },
     reconciled: {
@@ -917,17 +917,17 @@ const CREDIT_CARD_RECONCILE_TESTCASES = 
 ];
 
 add_task(async function test_reconcile_unknown_version() {
   let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
 
   // Cross-version reconciliation isn't supported yet. See bug 1377204.
   await Assert.rejects(profileStorage.addresses.reconcile({
     "guid": "31d83d2725ec",
-    "version": 3,
+    "version": 2,
     "given-name": "Mark",
     "family-name": "Hammond",
   }), /Got unknown record version/);
 });
 
 add_task(async function test_reconcile_idempotent() {
   let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
 
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -33,20 +33,20 @@ support-files =
 [test_getCategoriesFromFieldNames.js]
 [test_getFormInputDetails.js]
 [test_getInfo.js]
 [test_getRecords.js]
 [test_isAvailable.js]
 [test_isCJKName.js]
 [test_isFieldEligibleForAutofill.js]
 [test_markAsAutofillField.js]
+[test_masterPassword.js]
 [test_migrateRecords.js]
 [test_nameUtils.js]
 [test_onFormSubmitted.js]
-[test_osKeyStore.js]
 [test_parseAddressFormat.js]
 [test_profileAutocompleteResult.js]
 [test_phoneNumber.js]
 [test_reconcile.js]
 [test_savedFieldNames.js]
 [test_toOneLineAddress.js]
 [test_storage_tombstones.js]
 [test_storage_remove.js]
--- a/security/manager/ssl/nsIOSKeyStore.idl
+++ b/security/manager/ssl/nsIOSKeyStore.idl
@@ -10,17 +10,17 @@ interface nsIOSKeyStore: nsISupports {
   /**
    * This interface provides encryption and decryption operations for data at
    * rest. The key used to encrypt and decrypt the data is stored in the OS
    * key store.
    *
    * Usage:
    *
    * // obtain the singleton OSKeyStore instance
-   * const oskeystore = Cc["@mozilla.org/security/oskeystore;1"].getService(Ci.nsIOSKeyStore);
+   * const oskeystore = Cc["@mozilla.org/oskeystore;1"].getService(Ci.nsIOSKeyStore);
    *
    * const PASSWORD_LABEL = "mylabel1";
    * const COOKIE_LABEL = "mylabel2";
    *
    * // Unlock the key store.
    * // Note that this is not necesssary. The key store will be unlocked
    * // automatically when an operation is performed on it.
    * await oskeystore.asyncUnlock();
--- a/services/sync/tps/extensions/tps/resource/modules/formautofill.jsm
+++ b/services/sync/tps/extensions/tps/resource/modules/formautofill.jsm
@@ -9,18 +9,18 @@
 
 var EXPORTED_SYMBOLS = ["Address", "CreditCard", "DumpAddresses", "DumpCreditCards"];
 
 ChromeUtils.import("resource://tps/logger.jsm");
 
 ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
                                "resource://formautofill/FormAutofillStorage.jsm");
 
-ChromeUtils.defineModuleGetter(this, "OSKeyStore",
-                               "resource://formautofill/OSKeyStore.jsm");
+ChromeUtils.defineModuleGetter(this, "MasterPassword",
+                               "resource://formautofill/MasterPassword.jsm");
 
 class FormAutofillBase {
   constructor(props, subStorageName, fields) {
     this._subStorageName = subStorageName;
     this._fields = fields;
 
     this.props = {};
     this.updateProps = null;
@@ -106,17 +106,17 @@ const CREDIT_CARD_FIELDS = [
 class CreditCard extends FormAutofillBase {
   constructor(props) {
     super(props, "creditCards", CREDIT_CARD_FIELDS);
   }
 
   async Find() {
     const storage = await this.getStorage();
     await Promise.all(storage._data.map(
-      async entry => entry["cc-number"] = await OSKeyStore.decrypt(entry["cc-number-encrypted"])));
+      async entry => entry["cc-number"] = await MasterPassword.decrypt(entry["cc-number-encrypted"])));
     return storage._data.find(entry => {
       return this._fields.every(field => entry[field] === this.props[field]);
     });
   }
 }
 
 async function DumpCreditCards() {
   await DumpStorage("creditCards");
--- a/toolkit/modules/CreditCard.jsm
+++ b/toolkit/modules/CreditCard.jsm
@@ -1,18 +1,18 @@
 /* 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";
 
 var EXPORTED_SYMBOLS = ["CreditCard"];
 
-ChromeUtils.defineModuleGetter(this, "OSKeyStore",
-                               "resource://formautofill/OSKeyStore.jsm");
+ChromeUtils.defineModuleGetter(this, "MasterPassword",
+                               "resource://formautofill/MasterPassword.jsm");
 
 // The list of known and supported credit card network ids ("types")
 // This list mirrors the networks from dom/payments/BasicCardPayment.cpp
 // and is defined by https://www.w3.org/Payments/card-network-ids
 const SUPPORTED_NETWORKS = Object.freeze([
   "amex",
   "cartebancaire",
   "diners",
@@ -203,23 +203,17 @@ class CreditCard {
    * true, decrypted credit card numbers are shown instead.
    */
   async getLabel({showNumbers} = {}) {
     let parts = [];
     let label;
 
     if (showNumbers) {
       if (this._encryptedNumber) {
-        try {
-          label = await OSKeyStore.decrypt(this._encryptedNumber);
-        } catch (ex) {
-          // Quietly recover from decryption error.
-          label = this._number;
-          Cu.reportError(ex);
-        }
+        label = await MasterPassword.decrypt(this._encryptedNumber);
       } else {
         label = this._number;
       }
     }
     if (this._unmodifiedNumber && !label) {
       if (this.isValidNumber()) {
         label = this.maskedNumber;
       } else {
--- a/toolkit/modules/tests/browser/browser_CreditCard.js
+++ b/toolkit/modules/tests/browser/browser_CreditCard.js
@@ -1,42 +1,45 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
-ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
-ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm");
+ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
 
 let oldGetters = {};
 let gFakeLoggedIn = true;
 
 add_task(function setup() {
-  OSKeyStoreTestUtils.setup();
-  oldGetters.isLoggedIn = Object.getOwnPropertyDescriptor(OSKeyStore, "isLoggedIn").get;
-  OSKeyStore.__defineGetter__("isLoggedIn", () => gFakeLoggedIn);
-  registerCleanupFunction(async () => {
-    OSKeyStore.__defineGetter__("isLoggedIn", oldGetters.isLoggedIn);
-    await OSKeyStoreTestUtils.cleanup();
+  oldGetters._token = Object.getOwnPropertyDescriptor(MasterPassword, "_token").get;
+  oldGetters.isEnabled = Object.getOwnPropertyDescriptor(MasterPassword, "isEnabled").get;
+  oldGetters.isLoggedIn = Object.getOwnPropertyDescriptor(MasterPassword, "isLoggedIn").get;
+  MasterPassword.__defineGetter__("_token", () => { return {hasPassword: true}; });
+  MasterPassword.__defineGetter__("isEnabled", () => true);
+  MasterPassword.__defineGetter__("isLoggedIn", () => gFakeLoggedIn);
+  registerCleanupFunction(() => {
+    MasterPassword.__defineGetter__("_token", oldGetters._token);
+    MasterPassword.__defineGetter__("isEnabled", oldGetters.isEnabled);
+    MasterPassword.__defineGetter__("isLoggedIn", oldGetters.isLoggedIn);
 
-    // CreditCard.jsm, OSKeyStore.jsm, and OSKeyStoreTestUtils.jsm are imported
-    // into the global scope -- the window -- above. If they're not deleted,
-    // they outlive the test and are reported as a leak.
-    delete window.OSKeyStore;
+    // CreditCard.jsm and MasterPassword.jsm are imported into the global scope
+    // -- the window -- above. If they're not deleted, they outlive the test and
+    // are reported as a leak.
+    delete window.MasterPassword;
     delete window.CreditCard;
-    delete window.OSKeyStoreTestUtils;
   });
 });
 
-add_task(async function test_getLabel_withOSKeyStore() {
-  ok(OSKeyStore.isLoggedIn, "Confirm that OSKeyStore is faked and thinks it is logged in");
+add_task(async function test_getLabel_withMasterPassword() {
+  ok(MasterPassword.isEnabled, "Confirm that MasterPassword is faked and thinks it is enabled");
+  ok(MasterPassword.isLoggedIn, "Confirm that MasterPassword is faked and thinks it is logged in");
 
   const ccNumber = "4111111111111111";
-  const encryptedNumber = await OSKeyStore.encrypt(ccNumber);
-  const decryptedNumber = await OSKeyStore.decrypt(encryptedNumber);
+  const encryptedNumber = await MasterPassword.encrypt(ccNumber);
+  const decryptedNumber = await MasterPassword.decrypt(encryptedNumber);
   is(decryptedNumber, ccNumber, "Decrypted CC number should match original");
 
   const name = "Foxkeh";
   const creditCard = new CreditCard({encryptedNumber, name: "Foxkeh"});
   const label = await creditCard.getLabel({showNumbers: true});
   is(label, `${ccNumber}, ${name}`);
 });