Bug 1192035 - Import passwords from Microsoft Edge / Windows 8+ draft
authorRiadh Chtara <rchtara@mozilla.com>
Wed, 16 Sep 2015 17:34:26 -0700
changeset 293601 a4d481041bdaa34cb8e3eb28358effb4a2c3446b
parent 291202 01ae99b53561a2c3b40533d8c1c92bd3efc42d00
child 509364 bb5bee755042f4943f075d721a6c5120e7583956
push id5503
push userrchtara@mozilla.com
push dateThu, 17 Sep 2015 00:40:41 +0000
bugs1192035
milestone43.0a1
Bug 1192035 - Import passwords from Microsoft Edge / Windows 8+
browser/components/migration/EdgeProfileMigrator.js
browser/components/migration/IEProfileMigrator.js
browser/components/migration/MSMigrationUtils.jsm
browser/components/migration/tests/unit/test_Edge_availability.js
browser/components/migration/tests/unit/test_IE7_passwords.js
toolkit/components/passwordmgr/LoginHelper.jsm
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -14,16 +14,20 @@ function EdgeProfileMigrator() {
 
 EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
 
 EdgeProfileMigrator.prototype.getResources = function() {
   let resources = [
     MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
     MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
   ];
+  let windowsVaultFormPasswordsMigrator =
+    MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
+  windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
+  resources.push(windowsVaultFormPasswordsMigrator);
   return resources.filter(r => r.exists);
 };
 
 /* Somewhat counterintuitively, this returns:
  * - |null| to indicate "There is only 1 (default) profile" (on win10+)
  * - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
  * See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
  */
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -7,33 +7,38 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
 const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
 
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource:///modules/MSMigrationUtils.jsm");
+Cu.import("resource://gre/modules/LoginHelper.jsm");
+
 
 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
                                   "resource://gre/modules/ctypes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
                                   "resource://gre/modules/OSCrypto.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
+let CtypesKernelHelpers = MSMigrationUtils.CtypesKernelHelpers;
+
 ////////////////////////////////////////////////////////////////////////////////
 //// Resources
 
 
 function History() {
 }
 
 History.prototype = {
@@ -120,22 +125,29 @@ History.prototype = {
         aCallback(this._success);
       }
     });
   }
 };
 
 // IE password migrator supporting windows from XP until 7 and IE from 7 until 11
 function IE7FormPasswords () {
+  // used to distinguish between this migrator and other passwords migrators in tests.
+  this.name = "IE7FormPasswords";
 }
 
 IE7FormPasswords.prototype = {
   type: MigrationUtils.resourceTypes.PASSWORDS,
 
   get exists() {
+    // work only on windows until 7
+    if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+      return false;
+    }
+
     try {
       let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
       let key = Cc["@mozilla.org/windows-registry-key;1"].
                 createInstance(nsIWindowsRegKey);
       key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
                nsIWindowsRegKey.ACCESS_READ);
       let count = key.valueCount;
       key.close();
@@ -165,17 +177,17 @@ IE7FormPasswords.prototype = {
     aCallback(true);
   },
 
   /**
    * Migrate the logins that were saved for the uris arguments.
    * @param {nsIURI[]} uris - the uris that are going to be migrated.
    */
   _migrateURIs(uris) {
-    this.ctypesHelpers = new MSMigrationUtils.CtypesHelpers();
+    this.ctypesKernelHelpers = new MSMigrationUtils.CtypesKernelHelpers();
     this._crypto = new OSCrypto();
     let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
     let key = Cc["@mozilla.org/windows-registry-key;1"].
               createInstance(nsIWindowsRegKey);
     key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
              nsIWindowsRegKey.ACCESS_READ);
 
     let urlsSet = new Set(); // set of the already processed urls.
@@ -234,72 +246,36 @@ IE7FormPasswords.prototype = {
       Cu.reportError("We failed to decrypt and import some logins. " +
                      "This is likely because we didn't find the URLs where these " +
                      "passwords were submitted in the IE history and which are needed to be used " +
                      "as keys in the decryption.");
     }
 
     key.close();
     this._crypto.finalize();
-    this.ctypesHelpers.finalize();
+    this.ctypesKernelHelpers.finalize();
   },
 
   _crypto: null,
 
   /**
    * Add the logins to the password manager.
    * @param {Object[]} logins - array of the login details.
    */
   _addLogins(ieLogins) {
-    function addLogin(login, existingLogins) {
-      // Add the login only if it doesn't already exist
-      // if the login is not already available, it s going to be added or merged with another
-      // login
-      if (existingLogins.some(l => login.matches(l, true))) {
-        return;
-      }
-      let isUpdate = false; // the login is just an update for an old one
-      for (let existingLogin of existingLogins) {
-        if (login.username == existingLogin.username && login.password != existingLogin.password) {
-          // if a login with the same username and different password already exists and it's older
-          // than the current one, that login needs to be updated using the current one details
-          if (login.timePasswordChanged > existingLogin.timePasswordChanged) {
-            // Bug 1187190: Password changes should be propagated depending on timestamps.
-
-            // the existing login password and timestamps should be updated
-            let propBag = Cc["@mozilla.org/hash-property-bag;1"].
-                          createInstance(Ci.nsIWritablePropertyBag);
-            propBag.setProperty("password", login.password);
-            propBag.setProperty("timePasswordChanged", login.timePasswordChanged);
-            Services.logins.modifyLogin(existingLogin, propBag);
-            // make sure not to add the new login
-            isUpdate = true;
-          }
-        }
-      }
-      // if the new login is not an update, add it.
-      if (!isUpdate) {
-        Services.logins.addLogin(login);
-      }
-    }
-
     for (let ieLogin of ieLogins) {
       try {
-        let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
-
-        login.init(ieLogin.url, "", null,
-                   ieLogin.username, ieLogin.password, "", "");
-        login.QueryInterface(Ci.nsILoginMetaInfo);
-        login.timeCreated = ieLogin.creation;
-        login.timeLastUsed = ieLogin.creation;
-        login.timePasswordChanged = ieLogin.creation;
-        // login.timesUsed is going to set to the default value 1
-        // Add the login only if there's not an existing entry
-        let existingLogins = Services.logins.findLogins({}, login.hostname, "", null);
-        addLogin(login, existingLogins);
+        // create a new login
+        let login = {
+          username: ieLogin.username,
+          password: ieLogin.password,
+          hostname: ieLogin.url,
+          timeCreated: ieLogin.creation,
+          };
+        LoginHelper.maybeImportLogin(login);
       } catch (e) {
         Cu.reportError(e);
       }
     }
   },
 
   /**
    * Extract the details of one or more logins from the raw decrypted data.
@@ -359,17 +335,17 @@ IE7FormPasswords.prototype = {
                                               loginItem.ptr);
     // currentLoginData.dataMax is the data count: each username and password is considered as
     // a data. So, the number of logins is the number of data dived by 2
     let numLogins = currentLoginData.dataMax / 2;
     for (let n = 0; n < numLogins; n++) {
       // Bytes 0-31 starting from currentInfoIndex contain the loginItem data structure for the
       // current login
       let currentLoginItem = currentLoginItemPointer.contents;
-      let creation = this.ctypesHelpers.
+      let creation = this.ctypesKernelHelpers.
                      fileTimeToSecondsSinceEpoch(currentLoginItem.hiDateTime,
                                                  currentLoginItem.loDateTime) * 1000;
       let currentResult = {
         creation: creation,
         url: url,
       };
       // The username is UTF-16 and null-terminated.
       currentResult.username =
@@ -522,16 +498,20 @@ IEProfileMigrator.prototype = Object.cre
 IEProfileMigrator.prototype.getResources = function IE_getResources() {
   let resources = [
     MSMigrationUtils.getBookmarksMigrator()
   , new History()
   , MSMigrationUtils.getCookiesMigrator()
   , new IE7FormPasswords()
   , new Settings()
   ];
+  let windowsVaultFormPasswordsMigrator =
+    MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
+  windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
+  resources.push(windowsVaultFormPasswordsMigrator);
   return [r for each (r in resources) if (r.exists)];
 };
 
 Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
   get: function IE_get_sourceHomePageURL() {
     let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
                                                       kMainKey, "Default_Page_URL");
     let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -3,92 +3,116 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["MSMigrationUtils"];
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
+Cu.import("resource://gre/modules/AppConstants.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/LoginHelper.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
                                   "resource://gre/modules/ctypes.jsm");
 
 const EDGE_COOKIE_PATH_OPTIONS = ["", "#!001\\", "#!002\\"];
 const EDGE_COOKIES_SUFFIX = "MicrosoftEdge\\Cookies";
 const EDGE_FAVORITES = "AC\\MicrosoftEdge\\User\\Default\\Favorites";
 const EDGE_READINGLIST = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
+const FREE_CLOSE_FAILED = 0;
+const INTERNET_EXPLORER_EDGE_GUID = [0x3CCD5499,
+                                     0x4B1087A8,
+                                     0x886015A2,
+                                     0x553BDD88];
+const RESULT_SUCCESS = 0;
+const VAULT_ENUMERATE_ALL_ITEMS = 512;
+const WEB_CREDENTIALS_VAULT_ID = [0x4BF4C442,
+                                  0x41A09B8A,
+                                  0x4ADD80B3,
+                                  0x28DB4D70];
 
 Cu.importGlobalProperties(["File"]);
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Helpers.
 
-function CtypesHelpers() {
+const wintypes = {
+  BOOL: ctypes.int,
+  DWORD: ctypes.uint32_t,
+  DWORDLONG: ctypes.uint64_t,
+  CHAR: ctypes.char,
+  PCHAR: ctypes.char.ptr,
+  LPCWSTR: ctypes.char16_t.ptr,
+  PDWORD: ctypes.uint32_t.ptr,
+  VOIDP: ctypes.voidptr_t,
+  WORD: ctypes.uint16_t,
+}
+
+// TODO: Bug 1202978 - Refactor MSMigrationUtils ctypes helpers
+function CtypesKernelHelpers() {
   this._structs = {};
   this._functions = {};
   this._libs = {};
 
-  const WORD = ctypes.uint16_t;
-  const DWORD = ctypes.uint32_t;
-  const BOOL = ctypes.int;
-
-  this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
-    {wYear: WORD},
-    {wMonth: WORD},
-    {wDayOfWeek: WORD},
-    {wDay: WORD},
-    {wHour: WORD},
-    {wMinute: WORD},
-    {wSecond: WORD},
-    {wMilliseconds: WORD}
+  this._structs.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [
+    {wYear: wintypes.WORD},
+    {wMonth: wintypes.WORD},
+    {wDayOfWeek: wintypes.WORD},
+    {wDay: wintypes.WORD},
+    {wHour: wintypes.WORD},
+    {wMinute: wintypes.WORD},
+    {wSecond: wintypes.WORD},
+    {wMilliseconds: wintypes.WORD}
   ]);
 
-  this._structs.FILETIME = new ctypes.StructType('FILETIME', [
-    {dwLowDateTime: DWORD},
-    {dwHighDateTime: DWORD}
+  this._structs.FILETIME = new ctypes.StructType("FILETIME", [
+    {dwLowDateTime: wintypes.DWORD},
+    {dwHighDateTime: wintypes.DWORD}
   ]);
 
   try {
     this._libs.kernel32 = ctypes.open("Kernel32");
+
     this._functions.FileTimeToSystemTime =
       this._libs.kernel32.declare("FileTimeToSystemTime",
                                   ctypes.default_abi,
-                                  BOOL,
+                                  wintypes.BOOL,
                                   this._structs.FILETIME.ptr,
                                   this._structs.SYSTEMTIME.ptr);
   } catch (ex) {
     this.finalize();
   }
 }
 
-CtypesHelpers.prototype = {
+CtypesKernelHelpers.prototype = {
   /**
    * Must be invoked once after last use of any of the provided helpers.
    */
   finalize() {
     this._structs = {};
     this._functions = {};
     for each (let lib in this._libs) {
       try {
         lib.close();
       } catch (ex) {}
     }
     this._libs = {};
   },
 
-  /**
+   /**
    * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct,
    * and then deduces the number of seconds since the epoch (which
    * is the data we want for the cookie expiry date).
    *
    * @param aTimeHi
    *        Least significant DWORD.
    * @param aTimeLo
    *        Most significant DWORD.
@@ -111,16 +135,145 @@ CtypesHelpers.prototype = {
                                systemTime.wDay,
                                systemTime.wHour,
                                systemTime.wMinute,
                                systemTime.wSecond,
                                systemTime.wMilliseconds) / 1000);
   }
 };
 
+function CtypesVaultHelpers() {
+  this._structs = {};
+  this._functions = {};
+  // the size of the vault handle in 32 bits version is 32 and 64 in 64 bits version
+  if (wintypes.VOIDP.size == 4) {
+    this._vaultHandleType = wintypes.DWORD;
+  } else {
+    this._vaultHandleType = wintypes.DWORDLONG;
+  }
+
+  this._structs.GUID = new ctypes.StructType("GUID", [
+    {id: wintypes.DWORD.array(4)},
+  ]);
+
+  this._structs.VAULT_ITEM_ELEMENT = new ctypes.StructType("VAULT_ITEM_ELEMENT", [
+    // not documented
+    {schemaElementId: wintypes.DWORD},
+    // not documented
+    {unknown1: wintypes.DWORD},
+    // vault type
+    {type: wintypes.DWORD},
+    // not documented
+    {unknown2: wintypes.DWORD},
+    // value of the item
+    {itemValue: wintypes.LPCWSTR},
+    // not documented
+    {unknown3: wintypes.CHAR.array(12)},
+  ]);
+
+  this._structs.VAULT_ELEMENT = new ctypes.StructType("VAULT_ELEMENT", [
+    // vault item schemaId
+    {schemaId: this._structs.GUID},
+    // a pointer to the name of the browser VAULT_ITEM_ELEMENT
+    {pszCredentialFriendlyName: wintypes.LPCWSTR},
+    // a pointer to the url VAULT_ITEM_ELEMENT
+    {pResourceElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+    // a pointer to the username VAULT_ITEM_ELEMENT
+    {pIdentityElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+    // not documented
+    {pAuthenticatorElement: this._structs.VAULT_ITEM_ELEMENT.ptr},
+    // not documented
+    {pPackageSid: this._structs.VAULT_ITEM_ELEMENT.ptr},
+    // time stamp in local format
+    {lowLastModified: wintypes.DWORD},
+    {highLastModified: wintypes.DWORD},
+    // not documented
+    {flags: wintypes.DWORD},
+    // not documented
+    {dwPropertiesCount: wintypes.DWORD},
+    // not documented
+    {pPropertyElements: this._structs.VAULT_ITEM_ELEMENT.ptr},
+  ]);
+
+  try {
+    this._vaultcliLib = ctypes.open("vaultcli.dll");
+
+    this._functions.VaultOpenVault =
+      this._vaultcliLib.declare("VaultOpenVault",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // GUID
+                                this._structs.GUID.ptr,
+                                // Flags
+                                wintypes.DWORD,
+                                // Vault Handle
+                                this._vaultHandleType.ptr);
+    this._functions.VaultEnumerateItems =
+      this._vaultcliLib.declare("VaultEnumerateItems",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // Vault Handle
+                                this._vaultHandleType,
+                                // Flags
+                                wintypes.DWORD,
+                                // Items Count
+                                wintypes.PDWORD,
+                                // Items
+                                ctypes.voidptr_t);
+    this._functions.VaultCloseVault =
+      this._vaultcliLib.declare("VaultCloseVault",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // Vault Handle
+                                this._vaultHandleType);
+    this._functions.VaultGetItem =
+      this._vaultcliLib.declare("VaultGetItem",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // Vault Handle
+                                this._vaultHandleType,
+                                // Schema Id
+                                this._structs.GUID.ptr,
+                                // Resource
+                                this._structs.VAULT_ITEM_ELEMENT.ptr,
+                                // Identity
+                                this._structs.VAULT_ITEM_ELEMENT.ptr,
+                                // Package Sid
+                                this._structs.VAULT_ITEM_ELEMENT.ptr,
+                                // HWND Owner
+                                wintypes.DWORD,
+                                // Flags
+                                wintypes.DWORD,
+                                // Items
+                                this._structs.VAULT_ELEMENT.ptr.ptr);
+    this._functions.VaultFree =
+      this._vaultcliLib.declare("VaultFree",
+                                ctypes.winapi_abi,
+                                wintypes.DWORD,
+                                // Memory
+                                this._structs.VAULT_ELEMENT.ptr);
+  } catch (ex) {
+    this.finalize();
+  }
+}
+
+CtypesVaultHelpers.prototype = {
+  /**
+   * Must be invoked once after last use of any of the provided helpers.
+   */
+  finalize() {
+    this._structs = {};
+    this._functions = {};
+    try {
+      this._vaultcliLib.close();
+    } catch (ex) {}
+    this._vaultcliLib = null;
+  }
+}
+
 /**
  * Checks whether an host is an IP (v4 or v6) address.
  *
  * @param aHost
  *        The host to check.
  * @return whether aHost is an IP address.
  */
 function hostIsIPAddress(aHost) {
@@ -157,17 +310,17 @@ function getEdgeLocalDataFolder() {
         gEdgeDir = subDir;
         return subDir.clone();
       }
     }
   } catch (ex) {
     Cu.reportError("Exception trying to find the Edge favorites directory: " + ex);
   }
   return null;
-}
+};
 
 
 function Bookmarks(migrationType) {
   this._migrationType = migrationType;
 }
 
 Bookmarks.prototype = {
   type: MigrationUtils.resourceTypes.BOOKMARKS,
@@ -425,17 +578,17 @@ Cookies.prototype = {
           folders.push(folder);
         }
       }
     }
     return this.__cookiesFolders = folders.length ? folders : null;
   },
 
   migrate(aCallback) {
-    this.ctypesHelpers = new CtypesHelpers();
+    this.ctypesKernelHelpers = new CtypesKernelHelpers();
 
     let cookiesGenerator = (function genCookie() {
       let success = false;
       let folders = this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE ?
                       this.__cookiesFolders : [this.__cookiesFolder];
       for (let folder of folders) {
         let entries = folder.directoryEntries;
         while (entries.hasMoreElements()) {
@@ -452,17 +605,17 @@ Cookies.prototype = {
               cookiesGenerator.next();
             } catch (ex) {}
           });
 
           yield undefined;
         }
       }
 
-      this.ctypesHelpers.finalize();
+      this.ctypesKernelHelpers.finalize();
 
       aCallback(success);
     }).apply(this);
     cookiesGenerator.next();
   },
 
   _readCookieFile(aFile, aCallback) {
     let fileReader = Cc["@mozilla.org/files/filereader;1"].
@@ -528,34 +681,173 @@ Cookies.prototype = {
       if (host.length > 0) {
         // Fist delete any possible extant matching host cookie.
         Services.cookies.remove(host, name, path, false);
         // Now make it a domain cookie.
         if (host[0] != "." && !hostIsIPAddress(host))
           host = "." + host;
       }
 
-      let expireTime = this.ctypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
+      let expireTime = this.ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
                                                                       Number(expireTimeLo));
       Services.cookies.add(host,
                            path,
                            name,
                            value,
                            Number(flags) & 0x1, // secure
                            false, // httpOnly
                            false, // session
                            expireTime);
     }
   }
 };
 
+// Migrator for form passwords on Windows 8 and higher.
+function WindowsVaultFormPasswords () {
+}
+
+WindowsVaultFormPasswords.prototype = {
+  type: MigrationUtils.resourceTypes.PASSWORDS,
+
+  get exists() {
+    // work only on windows 8+
+    if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+      // check if there are passwords available for migration.
+      return this.migrate(() => {}, true);
+    }
+    return false;
+  },
+
+  /**
+   * If aOnlyCheckExists is false, import the form passwords on Windows 8 and higher from the vault
+   * and then call the aCallback.
+   * Otherwise, check if there are passwords in the vault.
+   * @param {function} aCallback - a callback called when the migration is done.
+   * @param {boolean} [aOnlyCheckExists=false] - if aOnlyCheckExists is true, just check if there are some
+   * passwords to migrate. Import the passwords from the vault and call aCallback otherwise.
+   * @return true if there are passwords in the vault and aOnlyCheckExists is set to true,
+   * false if there is no password in the vault and aOnlyCheckExists is set to true, undefined if
+   * aOnlyCheckExists is set to false.
+   */
+  migrate(aCallback, aOnlyCheckExists = false) {
+    // check if the vault item is an IE/Edge one
+    function _isIEOrEdgePassword(id) {
+      return id[0] == INTERNET_EXPLORER_EDGE_GUID[0] &&
+             id[1] == INTERNET_EXPLORER_EDGE_GUID[1] &&
+             id[2] == INTERNET_EXPLORER_EDGE_GUID[2] &&
+             id[3] == INTERNET_EXPLORER_EDGE_GUID[3];
+    }
+
+    let ctypesVaultHelpers = new CtypesVaultHelpers();
+    let ctypesKernelHelpers = new CtypesKernelHelpers();
+    let migrationSucceeded = true;
+    let successfulVaultOpen = false;
+    let error, vault;
+    try {
+
+      // web credentials vault id
+      let vaultGuid = new ctypesVaultHelpers._structs.GUID(WEB_CREDENTIALS_VAULT_ID);
+      // number of available vaults
+      let vaultCount = new wintypes.DWORD;
+      error = new wintypes.DWORD;
+      // web credentials vault
+      vault = new ctypesVaultHelpers._vaultHandleType;
+      // open the current vault using the vaultGuid
+      error = ctypesVaultHelpers._functions.VaultOpenVault(vaultGuid.address(), 0, vault.address());
+      if (error != RESULT_SUCCESS) {
+        throw new Error("Unable to open Vault: " + error);
+      }
+      successfulVaultOpen = true;
+
+      let item = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr;
+      let itemCount = new wintypes.DWORD;
+      // enumerate all the available items. This api is going to return a table of all the
+      // available items and item is going to point to the first element of this table.
+      error = ctypesVaultHelpers._functions.VaultEnumerateItems(vault, VAULT_ENUMERATE_ALL_ITEMS,
+                                                                itemCount.address(),
+                                                                item.address());
+      if (error != RESULT_SUCCESS) {
+        throw new Error("Unable to enumerate Vault items: " + error);
+      }
+      for (let j = 0; j < itemCount.value; j++) {
+        try {
+          // if it's not an ie/edge password, skip it
+          if (!_isIEOrEdgePassword(item.contents.schemaId.id)) {
+            continue;
+          }
+          // if aOnlyCheckExists is set to true, the purpose of the call is to return true if there is at
+          // least a password which is true in this case because a password was by now already found
+          if (aOnlyCheckExists) {
+            return true;
+          }
+          let url = item.contents.pResourceElement.contents.itemValue.readString();
+          let username = item.contents.pIdentityElement.contents.itemValue.readString();
+          // the current login credential object
+          let credential = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr;
+          error = ctypesVaultHelpers._functions.VaultGetItem(vault,
+                                                             item.contents.schemaId.address(),
+                                                             item.contents.pResourceElement,
+                                                             item.contents.pIdentityElement, null,
+                                                             0, 0, credential.address());
+          if (error != RESULT_SUCCESS) {
+            throw new Error("Unable to get item: " + error);
+          }
+
+          let password = credential.contents.pAuthenticatorElement.contents.itemValue.readString();
+          let creation = ctypesKernelHelpers.
+                         fileTimeToSecondsSinceEpoch(item.contents.highLastModified,
+                                                     item.contents.lowLastModified) * 1000;
+          // create a new login
+          let login = {
+            username, password,
+            hostname: NetUtil.newURI(url).prePath,
+            timeCreated: creation,
+          };
+          LoginHelper.maybeImportLogin(login);
+
+          // close current item
+          error = ctypesVaultHelpers._functions.VaultFree(credential);
+          if (error == FREE_CLOSE_FAILED) {
+            throw new Error("Unable to free item: " + error);
+          }
+        } catch (e) {
+          migrationSucceeded = false;
+          Cu.reportError(e);
+        } finally {
+          // move to next item in the table returned by VaultEnumerateItems
+          item = item.increment();
+        }
+      }
+    } catch (e) {
+      Cu.reportError(e);
+      migrationSucceeded = false;
+    } finally {
+      if (successfulVaultOpen) {
+        // close current vault
+        error = ctypesVaultHelpers._functions.VaultCloseVault(vault);
+        if (error == FREE_CLOSE_FAILED) {
+          Cu.reportError("Unable to close vault: " + error);
+        }
+      }
+      ctypesKernelHelpers.finalize();
+      ctypesVaultHelpers.finalize();
+      aCallback(migrationSucceeded);
+    }
+    if (aOnlyCheckExists) {
+      return false;
+    }
+  }
+};
 
 let MSMigrationUtils = {
   MIGRATION_TYPE_IE: 1,
   MIGRATION_TYPE_EDGE: 2,
-  CtypesHelpers: CtypesHelpers,
+  CtypesKernelHelpers: CtypesKernelHelpers,
   getBookmarksMigrator(migrationType = this.MIGRATION_TYPE_IE) {
     return new Bookmarks(migrationType);
   },
   getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) {
     return new Cookies(migrationType);
   },
+  getWindowsVaultFormPasswordsMigrator() {
+    return new WindowsVaultFormPasswords();
+  },
 };
--- a/browser/components/migration/tests/unit/test_Edge_availability.js
+++ b/browser/components/migration/tests/unit/test_Edge_availability.js
@@ -1,11 +1,12 @@
 const EDGE_AVAILABLE_MIGRATIONS = 
   MigrationUtils.resourceTypes.COOKIES |
-  MigrationUtils.resourceTypes.BOOKMARKS;
+  MigrationUtils.resourceTypes.BOOKMARKS |
+  MigrationUtils.resourceTypes.PASSWORDS;
 
 add_task(function* () {
   let migrator = MigrationUtils.getMigrator("edge");
   Cu.import("resource://gre/modules/AppConstants.jsm");
   Assert.equal(!!(migrator && migrator.sourceExists), AppConstants.isPlatformAndVersionAtLeast("win", "10"),
                "Edge should be available for migration if and only if we're on Win 10+");
   if (migrator) {
     let migratableData = migrator.getMigrateData(null, false);
--- a/browser/components/migration/tests/unit/test_IE7_passwords.js
+++ b/browser/components/migration/tests/unit/test_IE7_passwords.js
@@ -2,16 +2,17 @@ Cu.import("resource://gre/modules/AppCon
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
                                   "resource://gre/modules/OSCrypto.jsm");
 
 const CRYPT_PROTECT_UI_FORBIDDEN = 1;
+const IE7_FORM_PASSWORDS_MIGRATOR_NAME = "IE7FormPasswords";
 const LOGINS_KEY =  "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
 const EXTENSION = "-backup";
 const TESTED_WEBSITES = {
   twitter: {
     uri: makeURI("https://twitter.com"),
     hash: "A89D42BC6406E27265B1AD0782B6F376375764A301",
     data: [12, 0, 0, 0, 56, 0, 0, 0, 38, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 8, 0, 0, 0, 18, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 9, 0, 0, 0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 0, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, 57, 0, 0, 0],
     logins: [
@@ -268,17 +269,17 @@ function createRegistryPath(path) {
 }
 
 function getFirstResourceOfType(type) {
   let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=ie"]
                  .createInstance(Ci.nsISupports)
                  .wrappedJSObject;
   let migrators = migrator.getResources();
   for (let m of migrators) {
-    if (m.type == type) {
+    if (m.name == IE7_FORM_PASSWORDS_MIGRATOR_NAME && m.type == type) {
       return m;
     }
   }
   throw new Error("failed to find the " + type + " migrator");
 }
 
 function makeURI(aURL) {
   return Services.io.newURI(aURL, null, null);
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -337,9 +337,69 @@ this.LoginHelper = {
         fieldType == "email" ||
         fieldType == "url"   ||
         fieldType == "tel"   ||
         fieldType == "number") {
       return true;
     }
     return false;
   },
+
+  /**
+   * Add the login to the password manager if a similar one doesn't already exist. Merge it
+   * otherwise with the similar existing ones.
+   * @param {Object} loginData - the data about the login that needs to be added.
+   */
+  maybeImportLogin(loginData) {
+    // create a new login
+    let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
+    login.init(loginData.hostname,
+               loginData.submitURL || (typeof(loginData.httpRealm) == "string" ? null : ""),
+               typeof(loginData.httpRealm) == "string" ? loginData.httpRealm : null,
+               loginData.username,
+               loginData.password,
+               loginData.usernameElement || "",
+               loginData.passwordElement || "");
+
+    login.QueryInterface(Ci.nsILoginMetaInfo);
+    login.timeCreated = loginData.timeCreated;
+    login.timeLastUsed = loginData.timeLastUsed || loginData.timeCreated;
+    login.timePasswordChanged = loginData.timePasswordChanged  || loginData.timeCreated;
+    login.timesUsed = loginData.timesUsed || 1;
+    // While here we're passing formSubmitURL and httpRealm, they could be empty/null and get
+    // ignored in that case, leading to multiple logins for the same username.
+    let existingLogins = Services.logins.findLogins({}, login.hostname,
+                                                    login.formSubmitURL,
+                                                    login.httpRealm);
+    // Add the login only if it doesn't already exist
+    // if the login is not already available, it's going to be added or merged with other
+    // logins
+    if (existingLogins.some(l => login.matches(l, true))) {
+      return;
+    }
+    // the login is just an update for an old one or the login is older than an existing one
+    let foundMatchingLogin = false;
+    for (let existingLogin of existingLogins) {
+      if (login.username == existingLogin.username) {
+        // Bug 1187190: Password changes should be propagated depending on timestamps.
+        // this an old login or a just an update, so make sure not to add it
+        foundMatchingLogin = true;
+        if(login.password != existingLogin.password &
+           login.timePasswordChanged > existingLogin.timePasswordChanged) {
+          // if a login with the same username and different password already exists and it's older
+          // than the current one, that login needs to be updated using the current one details
+
+          // the existing login password and timestamps should be updated
+          let propBag = Cc["@mozilla.org/hash-property-bag;1"].
+                        createInstance(Ci.nsIWritablePropertyBag);
+          propBag.setProperty("password", login.password);
+          propBag.setProperty("timePasswordChanged", login.timePasswordChanged);
+          Services.logins.modifyLogin(existingLogin, propBag);
+        }
+      }
+    }
+    // if the new login is an update or is older than an exiting login, don't add it.
+    if (foundMatchingLogin) {
+      return;
+    }
+    Services.logins.addLogin(login);
+  }
 };