test_Chrome_passwords.js (10166B)
1 "use strict"; 2 3 const PROFILE = { 4 id: "Default", 5 name: "Person 1", 6 }; 7 8 const TEST_LOGINS = [ 9 { 10 id: 1, // id of the row in the chrome login db 11 username: "username", 12 password: "password", 13 origin: "https://c9.io", 14 formActionOrigin: "https://c9.io", 15 httpRealm: null, 16 usernameField: "inputEmail", 17 passwordField: "inputPassword", 18 timeCreated: 1437418416037, 19 timePasswordChanged: 1437418416037, 20 timesUsed: 1, 21 }, 22 { 23 id: 2, 24 username: "username@gmail.com", 25 password: "password2", 26 origin: "https://accounts.google.com", 27 formActionOrigin: "https://accounts.google.com", 28 httpRealm: null, 29 usernameField: "Email", 30 passwordField: "Passwd", 31 timeCreated: 1437418446598, 32 timePasswordChanged: 1437418446598, 33 timesUsed: 6, 34 }, 35 { 36 id: 3, 37 username: "username", 38 password: "password3", 39 origin: "https://www.facebook.com", 40 formActionOrigin: "https://www.facebook.com", 41 httpRealm: null, 42 usernameField: "email", 43 passwordField: "pass", 44 timeCreated: 1437418478851, 45 timePasswordChanged: 1437418478851, 46 timesUsed: 1, 47 }, 48 { 49 id: 4, 50 username: "user", 51 password: "اقرأPÀßwörd", 52 origin: "http://httpbin.org", 53 formActionOrigin: null, 54 httpRealm: "me@kennethreitz.com", // Digest auth. 55 usernameField: "", 56 passwordField: "", 57 timeCreated: 1437787462368, 58 timePasswordChanged: 1437787462368, 59 timesUsed: 1, 60 }, 61 { 62 id: 5, 63 username: "buser", 64 password: "bpassword", 65 origin: "http://httpbin.org", 66 formActionOrigin: null, 67 httpRealm: "Fake Realm", // Basic auth. 68 usernameField: "", 69 passwordField: "", 70 timeCreated: 1437787539233, 71 timePasswordChanged: 1437787539233, 72 timesUsed: 1, 73 }, 74 { 75 id: 6, 76 username: "username", 77 password: "password6", 78 origin: "https://www.example.com", 79 formActionOrigin: "", // NULL `action_url` 80 httpRealm: null, 81 usernameField: "", 82 passwordField: "pass", 83 timeCreated: 1557291348878, 84 timePasswordChanged: 1557291348878, 85 timesUsed: 1, 86 }, 87 { 88 id: 7, 89 version: "v10", 90 username: "username", 91 password: "password", 92 origin: "https://v10.io", 93 formActionOrigin: "https://v10.io", 94 httpRealm: null, 95 usernameField: "inputEmail", 96 passwordField: "inputPassword", 97 timeCreated: 1437418416037, 98 timePasswordChanged: 1437418416037, 99 timesUsed: 1, 100 }, 101 ]; 102 103 var loginCrypto; 104 var dbConn; 105 106 async function promiseSetPassword(login) { 107 const encryptedString = await loginCrypto.encryptData( 108 login.password, 109 login.version 110 ); 111 info(`promiseSetPassword: ${encryptedString}`); 112 const passwordValue = new Uint8Array( 113 loginCrypto.stringToArray(encryptedString) 114 ); 115 return dbConn.execute( 116 `UPDATE logins 117 SET password_value = :password_value 118 WHERE rowid = :rowid 119 `, 120 { password_value: passwordValue, rowid: login.id } 121 ); 122 } 123 124 function checkLoginsAreEqual(passwordManagerLogin, chromeLogin, id) { 125 passwordManagerLogin.QueryInterface(Ci.nsILoginMetaInfo); 126 127 Assert.equal( 128 passwordManagerLogin.username, 129 chromeLogin.username, 130 "The two logins ID " + id + " have the same username" 131 ); 132 Assert.equal( 133 passwordManagerLogin.password, 134 chromeLogin.password, 135 "The two logins ID " + id + " have the same password" 136 ); 137 Assert.equal( 138 passwordManagerLogin.origin, 139 chromeLogin.origin, 140 "The two logins ID " + id + " have the same origin" 141 ); 142 Assert.equal( 143 passwordManagerLogin.formActionOrigin, 144 chromeLogin.formActionOrigin, 145 "The two logins ID " + id + " have the same formActionOrigin" 146 ); 147 Assert.equal( 148 passwordManagerLogin.httpRealm, 149 chromeLogin.httpRealm, 150 "The two logins ID " + id + " have the same httpRealm" 151 ); 152 Assert.equal( 153 passwordManagerLogin.usernameField, 154 chromeLogin.usernameField, 155 "The two logins ID " + id + " have the same usernameElement" 156 ); 157 Assert.equal( 158 passwordManagerLogin.passwordField, 159 chromeLogin.passwordField, 160 "The two logins ID " + id + " have the same passwordElement" 161 ); 162 Assert.equal( 163 passwordManagerLogin.timeCreated, 164 chromeLogin.timeCreated, 165 "The two logins ID " + id + " have the same timeCreated" 166 ); 167 Assert.equal( 168 passwordManagerLogin.timePasswordChanged, 169 chromeLogin.timePasswordChanged, 170 "The two logins ID " + id + " have the same timePasswordChanged" 171 ); 172 Assert.equal( 173 passwordManagerLogin.timesUsed, 174 chromeLogin.timesUsed, 175 "The two logins ID " + id + " have the same timesUsed" 176 ); 177 } 178 179 function generateDifferentLogin(login) { 180 const newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance( 181 Ci.nsILoginInfo 182 ); 183 184 newLogin.init( 185 login.origin, 186 login.formActionOrigin, 187 null, 188 login.username, 189 login.password + 1, 190 login.usernameField + 1, 191 login.passwordField + 1 192 ); 193 newLogin.QueryInterface(Ci.nsILoginMetaInfo); 194 newLogin.timeCreated = login.timeCreated + 1; 195 newLogin.timePasswordChanged = login.timePasswordChanged + 1; 196 newLogin.timesUsed = login.timesUsed + 1; 197 return newLogin; 198 } 199 200 add_task(async function setup() { 201 let dirSvcPath; 202 let pathId; 203 let profilePathSegments; 204 205 // Use a mock service and account name to avoid a Keychain auth. prompt that 206 // would block the test from finishing if Chrome has already created a matching 207 // Keychain entry. This allows us to still exercise the keychain lookup code. 208 // The mock encryption passphrase is used when the Keychain item isn't found. 209 const mockMacOSKeychain = { 210 passphrase: "bW96aWxsYWZpcmVmb3g=", 211 serviceName: "TESTING Chrome Safe Storage", 212 accountName: "TESTING Chrome", 213 }; 214 if (AppConstants.platform == "macosx") { 215 const { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule( 216 "resource:///modules/ChromeMacOSLoginCrypto.sys.mjs" 217 ); 218 loginCrypto = new ChromeMacOSLoginCrypto( 219 mockMacOSKeychain.serviceName, 220 mockMacOSKeychain.accountName, 221 mockMacOSKeychain.passphrase 222 ); 223 dirSvcPath = "Library/"; 224 pathId = "ULibDir"; 225 profilePathSegments = [ 226 "Application Support", 227 "Google", 228 "Chrome", 229 "Default", 230 "Login Data", 231 ]; 232 } else if (AppConstants.platform == "win") { 233 const { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule( 234 "resource:///modules/ChromeWindowsLoginCrypto.sys.mjs" 235 ); 236 loginCrypto = new ChromeWindowsLoginCrypto("Chrome"); 237 dirSvcPath = "AppData/Local/"; 238 pathId = "LocalAppData"; 239 profilePathSegments = [ 240 "Google", 241 "Chrome", 242 "User Data", 243 "Default", 244 "Login Data", 245 ]; 246 } else { 247 throw new Error("Not implemented"); 248 } 249 const dirSvcFile = do_get_file(dirSvcPath); 250 registerFakePath(pathId, dirSvcFile); 251 252 info(PathUtils.join(dirSvcFile.path, ...profilePathSegments)); 253 const loginDataFilePath = PathUtils.join( 254 dirSvcFile.path, 255 ...profilePathSegments 256 ); 257 dbConn = await Sqlite.openConnection({ path: loginDataFilePath }); 258 259 if (AppConstants.platform == "macosx") { 260 const migrator = await MigrationUtils.getMigrator("chrome"); 261 Object.assign(migrator, { 262 _keychainServiceName: mockMacOSKeychain.serviceName, 263 _keychainAccountName: mockMacOSKeychain.accountName, 264 _keychainMockPassphrase: mockMacOSKeychain.passphrase, 265 }); 266 } 267 268 registerCleanupFunction(() => { 269 Services.logins.removeAllUserFacingLogins(); 270 if (loginCrypto.finalize) { 271 loginCrypto.finalize(); 272 } 273 return dbConn.close(); 274 }); 275 }); 276 277 add_task(async function test_importIntoEmptyDB() { 278 for (const login of TEST_LOGINS) { 279 await promiseSetPassword(login); 280 } 281 282 const migrator = await MigrationUtils.getMigrator("chrome"); 283 Assert.ok( 284 await migrator.isSourceAvailable(), 285 "Sanity check the source exists" 286 ); 287 288 let logins = await Services.logins.getAllLogins(); 289 Assert.equal(logins.length, 0, "There are no logins initially"); 290 291 // Migrate the logins. 292 await promiseMigration( 293 migrator, 294 MigrationUtils.resourceTypes.PASSWORDS, 295 PROFILE, 296 true 297 ); 298 299 logins = await Services.logins.getAllLogins(); 300 Assert.equal( 301 logins.length, 302 TEST_LOGINS.length, 303 "Check login count after importing the data" 304 ); 305 Assert.equal( 306 logins.length, 307 MigrationUtils._importQuantities.logins, 308 "Check telemetry matches the actual import." 309 ); 310 311 for (let i = 0; i < TEST_LOGINS.length; i++) { 312 checkLoginsAreEqual(logins[i], TEST_LOGINS[i], i + 1); 313 } 314 }); 315 316 // Test that existing logins for the same primary key don't get overwritten 317 add_task(async function test_importExistingLogins() { 318 const migrator = await MigrationUtils.getMigrator("chrome"); 319 Assert.ok( 320 await migrator.isSourceAvailable(), 321 "Sanity check the source exists" 322 ); 323 324 Services.logins.removeAllUserFacingLogins(); 325 let logins = await Services.logins.getAllLogins(); 326 Assert.equal( 327 logins.length, 328 0, 329 "There are no logins after removing all of them" 330 ); 331 332 const newLogins = []; 333 334 // Create 3 new logins that are different but where the key properties are still the same. 335 for (let i = 0; i < 3; i++) { 336 newLogins.push(generateDifferentLogin(TEST_LOGINS[i])); 337 await Services.logins.addLoginAsync(newLogins[i]); 338 } 339 340 logins = await Services.logins.getAllLogins(); 341 Assert.equal( 342 logins.length, 343 newLogins.length, 344 "Check login count after the insertion" 345 ); 346 347 for (let i = 0; i < newLogins.length; i++) { 348 checkLoginsAreEqual(logins[i], newLogins[i], i + 1); 349 } 350 // Migrate the logins. 351 await promiseMigration( 352 migrator, 353 MigrationUtils.resourceTypes.PASSWORDS, 354 PROFILE, 355 true 356 ); 357 358 logins = await Services.logins.getAllLogins(); 359 Assert.equal( 360 logins.length, 361 TEST_LOGINS.length, 362 "Check there are still the same number of logins after re-importing the data" 363 ); 364 Assert.equal( 365 logins.length, 366 MigrationUtils._importQuantities.logins, 367 "Check telemetry matches the actual import." 368 ); 369 370 for (let i = 0; i < newLogins.length; i++) { 371 checkLoginsAreEqual(logins[i], newLogins[i], i + 1); 372 } 373 });