Bug 1192035 - Import passwords from Microsoft Edge / Windows 8+ r=Gijs,MattN
authorRiadh Chtara <rchtara@mozilla.com>
Wed, 16 Sep 2015 17:34:26 -0700
changeset 295459 888d001bcc3c17043e0800bc13c08274a1fec0ae
parent 295458 ef5b842c975a28a77abd0a3ecd942de81cede633
child 295460 45922303c4ee1fea78cba83475dadb1b59ef1a16
child 295584 47f4d82b8129c8183fb4e3566850510e59b60acb
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, MattN
bugs1192035
milestone43.0a1
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
Bug 1192035 - Import passwords from Microsoft Edge / Windows 8+ r=Gijs,MattN
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
@@ -13,28 +13,32 @@ const kLoginsKey = "Software\\Microsoft\
 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 = {
@@ -121,22 +125,29 @@ History.prototype = {
         aCallback(this._success);
       }
     });
   }
 };
 
 // IE form 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();
@@ -166,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.
@@ -235,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.
@@ -360,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 =
@@ -526,16 +501,20 @@ IEProfileMigrator.prototype.getResources
   , new History()
   , MSMigrationUtils.getCookiesMigrator()
   , new Settings()
   ];
   // Only support the form password migrator for Windows XP to 7.
   if (AppConstants.isPlatformAndVersionAtMost("win", "6.1")) {
     resources.push(new IE7FormPasswords());
   }
+  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;
+    }
+  }
+};
 
 var 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);
+  }
 };