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 500349 831b8aa6281e5e200d92be09a519d17f9367f5ca
parent 500323 bd60f5f2f402f09dca1f2178a07a8b544d0763d1
child 500350 e7d5d82e766d73f37d7f477962459609722b96dc
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1486954
milestone64.0a1
backs outc895888bdddc97e36675e49844e7c115e0d6cc12
27e9286503e8bfbb61ed2edfc374257b728fec6b
87e64652386dd1fc08ebcc4ba7f1405805de20a1
96a6e1ceb6977a32bc6a29f46f5a47622bfc4d50
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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}`);
 });