summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/unit/test_logins_decrypt_failure.js
blob: 533bd9dd8de51c8fbb8840632a1b58b1b7c0dfe4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

/**
 * Tests the case where there are logins that cannot be decrypted.
 */

"use strict";

// Globals

/**
 * Resets the token used to decrypt logins.  This is equivalent to resetting the
 * primary password when it is not known.
 */
function resetPrimaryPassword() {
  let token = Cc["@mozilla.org/security/pk11tokendb;1"]
    .getService(Ci.nsIPK11TokenDB)
    .getInternalKeyToken();
  token.reset();
  token.initPassword("");
}

// Tests

/**
 * Resets the primary password after some logins were added to the database.
 */
add_task(async function test_logins_decrypt_failure() {
  let logins = TestData.loginList();
  await Services.logins.addLogins(logins);

  // This makes the existing logins non-decryptable.
  resetPrimaryPassword();

  // These functions don't see the non-decryptable entries anymore.
  let savedLogins = await Services.logins.getAllLogins();
  Assert.equal(savedLogins.length, 0, "getAllLogins length");
  await Assert.rejects(Services.logins.searchLoginsAsync({}), /is required/);
  Assert.equal(Services.logins.searchLogins(newPropertyBag()).length, 0);
  Assert.throws(
    () => Services.logins.modifyLogin(logins[0], newPropertyBag()),
    /No matching logins/
  );
  Assert.throws(
    () => Services.logins.removeLogin(logins[0]),
    /No matching logins/
  );

  // The function that counts logins sees the non-decryptable entries also.
  Assert.equal(Services.logins.countLogins("", "", ""), logins.length);

  // Equivalent logins can be added.
  await Services.logins.addLogins(logins);
  await LoginTestUtils.checkLogins(logins);
  Assert.equal(
    (await Services.logins.getAllLogins()).length,
    logins.length,
    "getAllLogins length"
  );
  Assert.equal(Services.logins.countLogins("", "", ""), logins.length * 2);

  // Finding logins doesn't return the non-decryptable duplicates.
  Assert.equal(
    (
      await Services.logins.searchLoginsAsync({
        origin: "http://www.example.com",
      })
    ).length,
    1
  );
  let matchData = newPropertyBag({ origin: "http://www.example.com" });
  Assert.equal(Services.logins.searchLogins(matchData).length, 1);

  // Removing single logins does not remove non-decryptable logins.
  for (let loginInfo of TestData.loginList()) {
    Services.logins.removeLogin(loginInfo);
  }
  Assert.equal((await Services.logins.getAllLogins()).length, 0);
  Assert.equal(Services.logins.countLogins("", "", ""), logins.length);

  // Removing all logins removes the non-decryptable entries also.
  Services.logins.removeAllUserFacingLogins();
  Assert.equal((await Services.logins.getAllLogins()).length, 0);
  Assert.equal(Services.logins.countLogins("", "", ""), 0);
});

// Bug 621846 - If a login has a GUID but can't be decrypted, a search for
// that GUID will (correctly) fail. Ensure we can add a new login with that
// same GUID.
add_task(async function test_add_logins_with_decrypt_failure() {
  // a login with a GUID.
  let login = new LoginInfo(
    "http://www.example2.com",
    "http://www.example2.com",
    null,
    "the username",
    "the password for www.example.com",
    "form_field_username",
    "form_field_password"
  );

  login.QueryInterface(Ci.nsILoginMetaInfo);
  login.guid = "{4bc50d2f-dbb6-4aa3-807c-c4c2065a2c35}";

  // A different login but with the same GUID.
  let loginDupeGuid = new LoginInfo(
    "http://www.example3.com",
    "http://www.example3.com",
    null,
    "the username",
    "the password",
    "form_field_username",
    "form_field_password"
  );
  loginDupeGuid.QueryInterface(Ci.nsILoginMetaInfo);
  loginDupeGuid.guid = login.guid;

  await Services.logins.addLoginAsync(login);

  // We can search for this login by GUID.
  let searchProp = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
    Ci.nsIWritablePropertyBag2
  );
  searchProp.setPropertyAsAUTF8String("guid", login.guid);

  equal(Services.logins.searchLogins(searchProp).length, 1);

  // We should fail to re-add it as it remains good.
  await Assert.rejects(
    Services.logins.addLoginAsync(login),
    /This login already exists./
  );
  // We should fail to re-add a different login with the same GUID.
  await Assert.rejects(
    Services.logins.addLoginAsync(loginDupeGuid),
    /specified GUID already exists/
  );

  // This makes the existing login non-decryptable.
  resetPrimaryPassword();

  // We can no longer find it in our search.
  equal(Services.logins.searchLogins(searchProp).length, 0);

  // So we should be able to re-add a login with that same GUID.
  await Services.logins.addLoginAsync(login);
  equal(Services.logins.searchLogins(searchProp).length, 1);

  Services.logins.removeAllUserFacingLogins();
});

// Test the "syncID" metadata works as expected on decryption failure.
add_task(async function test_sync_metadata_with_decrypt_failure() {
  // And some sync metadata
  await Services.logins.setSyncID("sync-id");
  await Services.logins.setLastSync(123);
  equal(await Services.logins.getSyncID(), "sync-id");
  equal(await Services.logins.getLastSync(), 123);

  // This makes the existing login and syncID non-decryptable.
  resetPrimaryPassword();

  // The syncID is now null.
  equal(await Services.logins.getSyncID(), null);
  // The sync timestamp isn't impacted.
  equal(await Services.logins.getLastSync(), 123);

  // But we should be able to set it again.
  await Services.logins.setSyncID("new-id");
  equal(await Services.logins.getSyncID(), "new-id");
});