]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
66f515aef0bfc33f60db0501775efb746cfe4619
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / SignalAccount.java
1 package org.asamk.signal.manager.storage;
2
3 import com.fasterxml.jackson.databind.JsonNode;
4 import com.fasterxml.jackson.databind.ObjectMapper;
5
6 import org.asamk.signal.manager.api.Pair;
7 import org.asamk.signal.manager.api.TrustLevel;
8 import org.asamk.signal.manager.groups.GroupId;
9 import org.asamk.signal.manager.storage.configuration.ConfigurationStore;
10 import org.asamk.signal.manager.storage.contacts.ContactsStore;
11 import org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore;
12 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
13 import org.asamk.signal.manager.storage.groups.GroupInfoV2;
14 import org.asamk.signal.manager.storage.groups.GroupStore;
15 import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
16 import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
17 import org.asamk.signal.manager.storage.messageCache.MessageCache;
18 import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
19 import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
20 import org.asamk.signal.manager.storage.profiles.LegacyProfileStore;
21 import org.asamk.signal.manager.storage.profiles.ProfileStore;
22 import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
23 import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
24 import org.asamk.signal.manager.storage.recipients.Contact;
25 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
26 import org.asamk.signal.manager.storage.recipients.Profile;
27 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
28 import org.asamk.signal.manager.storage.recipients.RecipientId;
29 import org.asamk.signal.manager.storage.recipients.RecipientStore;
30 import org.asamk.signal.manager.storage.sendLog.MessageSendLogStore;
31 import org.asamk.signal.manager.storage.senderKeys.SenderKeyStore;
32 import org.asamk.signal.manager.storage.sessions.SessionStore;
33 import org.asamk.signal.manager.storage.stickers.StickerStore;
34 import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
35 import org.asamk.signal.manager.util.IOUtils;
36 import org.asamk.signal.manager.util.KeyUtils;
37 import org.signal.libsignal.protocol.IdentityKeyPair;
38 import org.signal.libsignal.protocol.InvalidMessageException;
39 import org.signal.libsignal.protocol.SignalProtocolAddress;
40 import org.signal.libsignal.protocol.state.PreKeyRecord;
41 import org.signal.libsignal.protocol.state.SessionRecord;
42 import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
43 import org.signal.libsignal.protocol.util.Medium;
44 import org.signal.libsignal.zkgroup.InvalidInputException;
45 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48 import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
49 import org.whispersystems.signalservice.api.SignalServiceDataStore;
50 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
51 import org.whispersystems.signalservice.api.kbs.MasterKey;
52 import org.whispersystems.signalservice.api.push.ACI;
53 import org.whispersystems.signalservice.api.push.DistributionId;
54 import org.whispersystems.signalservice.api.push.PNI;
55 import org.whispersystems.signalservice.api.push.ServiceId;
56 import org.whispersystems.signalservice.api.push.ServiceIdType;
57 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
58 import org.whispersystems.signalservice.api.storage.StorageKey;
59 import org.whispersystems.signalservice.api.util.CredentialsProvider;
60 import org.whispersystems.signalservice.api.util.UuidUtil;
61
62 import java.io.ByteArrayInputStream;
63 import java.io.ByteArrayOutputStream;
64 import java.io.Closeable;
65 import java.io.File;
66 import java.io.IOException;
67 import java.io.RandomAccessFile;
68 import java.nio.channels.Channels;
69 import java.nio.channels.ClosedChannelException;
70 import java.nio.channels.FileChannel;
71 import java.nio.channels.FileLock;
72 import java.security.SecureRandom;
73 import java.sql.SQLException;
74 import java.util.Base64;
75 import java.util.Date;
76 import java.util.HashSet;
77 import java.util.List;
78 import java.util.function.Supplier;
79
80 public class SignalAccount implements Closeable {
81
82 private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
83
84 private static final int MINIMUM_STORAGE_VERSION = 1;
85 private static final int CURRENT_STORAGE_VERSION = 3;
86
87 private final Object LOCK = new Object();
88
89 private final ObjectMapper jsonProcessor = Utils.createStorageObjectMapper();
90
91 private final FileChannel fileChannel;
92 private final FileLock lock;
93
94 private int previousStorageVersion;
95
96 private File dataPath;
97 private String accountPath;
98 private String number;
99 private ACI aci;
100 private PNI pni;
101 private String encryptedDeviceName;
102 private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
103 private boolean isMultiDevice = false;
104 private String password;
105 private String registrationLockPin;
106 private MasterKey pinMasterKey;
107 private StorageKey storageKey;
108 private long storageManifestVersion = -1;
109 private ProfileKey profileKey;
110 private int aciPreKeyIdOffset = 1;
111 private int aciNextSignedPreKeyId = 1;
112 private int pniPreKeyIdOffset = 1;
113 private int pniNextSignedPreKeyId = 1;
114 private IdentityKeyPair aciIdentityKeyPair;
115 private IdentityKeyPair pniIdentityKeyPair;
116 private int localRegistrationId;
117 private TrustNewIdentity trustNewIdentity;
118 private long lastReceiveTimestamp = 0;
119
120 private boolean registered = false;
121
122 private SignalProtocolStore signalProtocolStore;
123 private PreKeyStore aciPreKeyStore;
124 private SignedPreKeyStore aciSignedPreKeyStore;
125 private PreKeyStore pniPreKeyStore;
126 private SignedPreKeyStore pniSignedPreKeyStore;
127 private SessionStore sessionStore;
128 private IdentityKeyStore identityKeyStore;
129 private SenderKeyStore senderKeyStore;
130 private GroupStore groupStore;
131 private GroupStore.Storage groupStoreStorage;
132 private RecipientStore recipientStore;
133 private StickerStore stickerStore;
134 private StickerStore.Storage stickerStoreStorage;
135 private ConfigurationStore configurationStore;
136 private ConfigurationStore.Storage configurationStoreStorage;
137
138 private MessageCache messageCache;
139 private MessageSendLogStore messageSendLogStore;
140
141 private AccountDatabase accountDatabase;
142
143 private SignalAccount(final FileChannel fileChannel, final FileLock lock) {
144 this.fileChannel = fileChannel;
145 this.lock = lock;
146 }
147
148 public static SignalAccount load(
149 File dataPath, String accountPath, boolean waitForLock, final TrustNewIdentity trustNewIdentity
150 ) throws IOException {
151 logger.trace("Opening account file");
152 final var fileName = getFileName(dataPath, accountPath);
153 final var pair = openFileChannel(fileName, waitForLock);
154 try {
155 var signalAccount = new SignalAccount(pair.first(), pair.second());
156 logger.trace("Loading account file");
157 signalAccount.load(dataPath, accountPath, trustNewIdentity);
158 logger.trace("Migrating legacy parts of account file");
159 signalAccount.migrateLegacyConfigs();
160
161 return signalAccount;
162 } catch (Throwable e) {
163 pair.second().close();
164 pair.first().close();
165 throw e;
166 }
167 }
168
169 public static SignalAccount create(
170 File dataPath,
171 String accountPath,
172 String number,
173 IdentityKeyPair aciIdentityKey,
174 IdentityKeyPair pniIdentityKey,
175 int registrationId,
176 ProfileKey profileKey,
177 final TrustNewIdentity trustNewIdentity
178 ) throws IOException {
179 IOUtils.createPrivateDirectories(dataPath);
180 var fileName = getFileName(dataPath, accountPath);
181 if (!fileName.exists()) {
182 IOUtils.createPrivateFile(fileName);
183 }
184
185 final var pair = openFileChannel(fileName, true);
186 var signalAccount = new SignalAccount(pair.first(), pair.second());
187
188 signalAccount.accountPath = accountPath;
189 signalAccount.number = number;
190 signalAccount.profileKey = profileKey;
191
192 signalAccount.dataPath = dataPath;
193 signalAccount.aciIdentityKeyPair = aciIdentityKey;
194 signalAccount.pniIdentityKeyPair = pniIdentityKey;
195 signalAccount.localRegistrationId = registrationId;
196 signalAccount.trustNewIdentity = trustNewIdentity;
197 signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath),
198 signalAccount.getRecipientStore(),
199 signalAccount::saveGroupStore);
200 signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
201 signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
202
203 signalAccount.registered = false;
204
205 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
206 signalAccount.migrateLegacyConfigs();
207 signalAccount.save();
208
209 return signalAccount;
210 }
211
212 public static SignalAccount createOrUpdateLinkedAccount(
213 File dataPath,
214 String accountPath,
215 String number,
216 ACI aci,
217 PNI pni,
218 String password,
219 String encryptedDeviceName,
220 int deviceId,
221 IdentityKeyPair aciIdentityKey,
222 IdentityKeyPair pniIdentityKey,
223 int registrationId,
224 ProfileKey profileKey,
225 final TrustNewIdentity trustNewIdentity
226 ) throws IOException {
227 IOUtils.createPrivateDirectories(dataPath);
228 var fileName = getFileName(dataPath, accountPath);
229 if (!fileName.exists()) {
230 return createLinkedAccount(dataPath,
231 accountPath,
232 number,
233 aci,
234 pni,
235 password,
236 encryptedDeviceName,
237 deviceId,
238 aciIdentityKey,
239 pniIdentityKey,
240 registrationId,
241 profileKey,
242 trustNewIdentity);
243 }
244
245 final var signalAccount = load(dataPath, accountPath, true, trustNewIdentity);
246 signalAccount.setProvisioningData(number,
247 aci,
248 pni,
249 password,
250 encryptedDeviceName,
251 deviceId,
252 aciIdentityKey,
253 pniIdentityKey,
254 profileKey);
255 signalAccount.getRecipientStore().resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress());
256 signalAccount.getSessionStore().archiveAllSessions();
257 signalAccount.getSenderKeyStore().deleteAll();
258 signalAccount.clearAllPreKeys();
259 return signalAccount;
260 }
261
262 public void initDatabase() {
263 getAccountDatabase();
264 }
265
266 private void clearAllPreKeys() {
267 this.aciPreKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE);
268 this.aciNextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE);
269 this.pniPreKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE);
270 this.pniNextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE);
271 this.getAciPreKeyStore().removeAllPreKeys();
272 this.getAciSignedPreKeyStore().removeAllSignedPreKeys();
273 this.getPniPreKeyStore().removeAllPreKeys();
274 this.getPniSignedPreKeyStore().removeAllSignedPreKeys();
275 save();
276 }
277
278 private static SignalAccount createLinkedAccount(
279 File dataPath,
280 String accountPath,
281 String number,
282 ACI aci,
283 PNI pni,
284 String password,
285 String encryptedDeviceName,
286 int deviceId,
287 IdentityKeyPair aciIdentityKey,
288 IdentityKeyPair pniIdentityKey,
289 int registrationId,
290 ProfileKey profileKey,
291 final TrustNewIdentity trustNewIdentity
292 ) throws IOException {
293 var fileName = getFileName(dataPath, accountPath);
294 IOUtils.createPrivateFile(fileName);
295
296 final var pair = openFileChannel(fileName, true);
297 var signalAccount = new SignalAccount(pair.first(), pair.second());
298
299 signalAccount.setProvisioningData(number,
300 aci,
301 pni,
302 password,
303 encryptedDeviceName,
304 deviceId,
305 aciIdentityKey,
306 pniIdentityKey,
307 profileKey);
308
309 signalAccount.dataPath = dataPath;
310 signalAccount.accountPath = accountPath;
311 signalAccount.localRegistrationId = registrationId;
312 signalAccount.trustNewIdentity = trustNewIdentity;
313 signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath),
314 signalAccount.getRecipientStore(),
315 signalAccount::saveGroupStore);
316 signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
317 signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
318
319 signalAccount.getRecipientStore().resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress());
320 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
321 signalAccount.migrateLegacyConfigs();
322 signalAccount.clearAllPreKeys();
323 signalAccount.save();
324
325 return signalAccount;
326 }
327
328 private void setProvisioningData(
329 final String number,
330 final ACI aci,
331 final PNI pni,
332 final String password,
333 final String encryptedDeviceName,
334 final int deviceId,
335 final IdentityKeyPair aciIdentity,
336 final IdentityKeyPair pniIdentity,
337 final ProfileKey profileKey
338 ) {
339 this.number = number;
340 this.aci = aci;
341 this.pni = pni;
342 this.password = password;
343 this.profileKey = profileKey;
344 this.encryptedDeviceName = encryptedDeviceName;
345 this.deviceId = deviceId;
346 this.aciIdentityKeyPair = aciIdentity;
347 this.pniIdentityKeyPair = pniIdentity;
348 this.registered = true;
349 this.isMultiDevice = true;
350 this.lastReceiveTimestamp = 0;
351 this.pinMasterKey = null;
352 this.storageManifestVersion = -1;
353 this.storageKey = null;
354 }
355
356 private void migrateLegacyConfigs() {
357 if (getPassword() == null) {
358 setPassword(KeyUtils.createPassword());
359 }
360
361 if (getProfileKey() == null) {
362 // Old config file, creating new profile key
363 setProfileKey(KeyUtils.createProfileKey());
364 }
365 // Ensure our profile key is stored in profile store
366 getProfileStore().storeProfileKey(getSelfRecipientId(), getProfileKey());
367 if (previousStorageVersion < 3) {
368 for (final var group : groupStore.getGroups()) {
369 if (group instanceof GroupInfoV2 && group.getDistributionId() == null) {
370 ((GroupInfoV2) group).setDistributionId(DistributionId.create());
371 groupStore.updateGroup(group);
372 }
373 }
374 save();
375 }
376 if (isMasterDevice() && getPniIdentityKeyPair() == null) {
377 setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
378 }
379 }
380
381 private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
382 getSessionStore().mergeRecipients(recipientId, toBeMergedRecipientId);
383 getIdentityKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
384 getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId);
385 getGroupStore().mergeRecipients(recipientId, toBeMergedRecipientId);
386 getSenderKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
387 }
388
389 public void removeRecipient(final RecipientId recipientId) {
390 getSessionStore().deleteAllSessions(recipientId);
391 getIdentityKeyStore().deleteIdentity(recipientId);
392 getMessageCache().deleteMessages(recipientId);
393 getSenderKeyStore().deleteAll(recipientId);
394 getRecipientStore().deleteRecipientData(recipientId);
395 }
396
397 public static File getFileName(File dataPath, String account) {
398 return new File(dataPath, account);
399 }
400
401 private static File getUserPath(final File dataPath, final String account) {
402 final var path = new File(dataPath, account + ".d");
403 try {
404 IOUtils.createPrivateDirectories(path);
405 } catch (IOException e) {
406 throw new AssertionError("Failed to create user path", e);
407 }
408 return path;
409 }
410
411 private static File getMessageCachePath(File dataPath, String account) {
412 return new File(getUserPath(dataPath, account), "msg-cache");
413 }
414
415 private static File getGroupCachePath(File dataPath, String account) {
416 return new File(getUserPath(dataPath, account), "group-cache");
417 }
418
419 private static File getAciPreKeysPath(File dataPath, String account) {
420 return new File(getUserPath(dataPath, account), "pre-keys");
421 }
422
423 private static File getAciSignedPreKeysPath(File dataPath, String account) {
424 return new File(getUserPath(dataPath, account), "signed-pre-keys");
425 }
426
427 private static File getPniPreKeysPath(File dataPath, String account) {
428 return new File(getUserPath(dataPath, account), "pre-keys-pni");
429 }
430
431 private static File getPniSignedPreKeysPath(File dataPath, String account) {
432 return new File(getUserPath(dataPath, account), "signed-pre-keys-pni");
433 }
434
435 private static File getIdentitiesPath(File dataPath, String account) {
436 return new File(getUserPath(dataPath, account), "identities");
437 }
438
439 private static File getSessionsPath(File dataPath, String account) {
440 return new File(getUserPath(dataPath, account), "sessions");
441 }
442
443 private static File getSenderKeysPath(File dataPath, String account) {
444 return new File(getUserPath(dataPath, account), "sender-keys");
445 }
446
447 private static File getSharedSenderKeysFile(File dataPath, String account) {
448 return new File(getUserPath(dataPath, account), "shared-sender-keys-store");
449 }
450
451 private static File getRecipientsStoreFile(File dataPath, String account) {
452 return new File(getUserPath(dataPath, account), "recipients-store");
453 }
454
455 private static File getDatabaseFile(File dataPath, String account) {
456 return new File(getUserPath(dataPath, account), "account.db");
457 }
458
459 public static boolean accountFileExists(File dataPath, String account) {
460 if (account == null) {
461 return false;
462 }
463 var f = getFileName(dataPath, account);
464 return !(!f.exists() || f.isDirectory());
465 }
466
467 private void load(
468 File dataPath, String accountPath, final TrustNewIdentity trustNewIdentity
469 ) throws IOException {
470 this.dataPath = dataPath;
471 this.accountPath = accountPath;
472 final JsonNode rootNode;
473 synchronized (fileChannel) {
474 fileChannel.position(0);
475 rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
476 }
477
478 if (rootNode.hasNonNull("version")) {
479 var accountVersion = rootNode.get("version").asInt(1);
480 if (accountVersion > CURRENT_STORAGE_VERSION) {
481 throw new IOException("Config file was created by a more recent version!");
482 } else if (accountVersion < MINIMUM_STORAGE_VERSION) {
483 throw new IOException("Config file was created by a no longer supported older version!");
484 }
485 previousStorageVersion = accountVersion;
486 }
487
488 number = Utils.getNotNullNode(rootNode, "username").asText();
489 if (rootNode.hasNonNull("password")) {
490 password = rootNode.get("password").asText();
491 }
492 registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
493 if (rootNode.hasNonNull("uuid")) {
494 try {
495 aci = ACI.parseOrThrow(rootNode.get("uuid").asText());
496 } catch (IllegalArgumentException e) {
497 throw new IOException("Config file contains an invalid aci/uuid, needs to be a valid UUID", e);
498 }
499 }
500 if (rootNode.hasNonNull("pni")) {
501 try {
502 pni = PNI.parseOrThrow(rootNode.get("pni").asText());
503 } catch (IllegalArgumentException e) {
504 throw new IOException("Config file contains an invalid pni, needs to be a valid UUID", e);
505 }
506 }
507 if (rootNode.hasNonNull("deviceName")) {
508 encryptedDeviceName = rootNode.get("deviceName").asText();
509 }
510 if (rootNode.hasNonNull("deviceId")) {
511 deviceId = rootNode.get("deviceId").asInt();
512 }
513 if (rootNode.hasNonNull("isMultiDevice")) {
514 isMultiDevice = rootNode.get("isMultiDevice").asBoolean();
515 }
516 if (rootNode.hasNonNull("lastReceiveTimestamp")) {
517 lastReceiveTimestamp = rootNode.get("lastReceiveTimestamp").asLong();
518 }
519 int registrationId = 0;
520 if (rootNode.hasNonNull("registrationId")) {
521 registrationId = rootNode.get("registrationId").asInt();
522 }
523 IdentityKeyPair aciIdentityKeyPair = null;
524 if (rootNode.hasNonNull("identityPrivateKey") && rootNode.hasNonNull("identityKey")) {
525 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("identityKey").asText());
526 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("identityPrivateKey").asText());
527 aciIdentityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
528 }
529 if (rootNode.hasNonNull("pniIdentityPrivateKey") && rootNode.hasNonNull("pniIdentityKey")) {
530 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityKey").asText());
531 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityPrivateKey").asText());
532 pniIdentityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
533 }
534
535 if (rootNode.hasNonNull("registrationLockPin")) {
536 registrationLockPin = rootNode.get("registrationLockPin").asText();
537 }
538 if (rootNode.hasNonNull("pinMasterKey")) {
539 pinMasterKey = new MasterKey(Base64.getDecoder().decode(rootNode.get("pinMasterKey").asText()));
540 }
541 if (rootNode.hasNonNull("storageKey")) {
542 storageKey = new StorageKey(Base64.getDecoder().decode(rootNode.get("storageKey").asText()));
543 }
544 if (rootNode.hasNonNull("storageManifestVersion")) {
545 storageManifestVersion = rootNode.get("storageManifestVersion").asLong();
546 }
547 if (rootNode.hasNonNull("preKeyIdOffset")) {
548 aciPreKeyIdOffset = rootNode.get("preKeyIdOffset").asInt(1);
549 } else {
550 aciPreKeyIdOffset = 1;
551 }
552 if (rootNode.hasNonNull("nextSignedPreKeyId")) {
553 aciNextSignedPreKeyId = rootNode.get("nextSignedPreKeyId").asInt(1);
554 } else {
555 aciNextSignedPreKeyId = 1;
556 }
557 if (rootNode.hasNonNull("pniPreKeyIdOffset")) {
558 pniPreKeyIdOffset = rootNode.get("pniPreKeyIdOffset").asInt(1);
559 } else {
560 pniPreKeyIdOffset = 1;
561 }
562 if (rootNode.hasNonNull("pniNextSignedPreKeyId")) {
563 pniNextSignedPreKeyId = rootNode.get("pniNextSignedPreKeyId").asInt(1);
564 } else {
565 pniNextSignedPreKeyId = 1;
566 }
567 if (rootNode.hasNonNull("profileKey")) {
568 try {
569 profileKey = new ProfileKey(Base64.getDecoder().decode(rootNode.get("profileKey").asText()));
570 } catch (InvalidInputException e) {
571 throw new IOException(
572 "Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
573 e);
574 }
575 }
576
577 var migratedLegacyConfig = false;
578 final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
579 ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
580 LegacyJsonSignalProtocolStore.class)
581 : null;
582 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
583 aciIdentityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
584 registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
585 migratedLegacyConfig = true;
586 }
587
588 this.aciIdentityKeyPair = aciIdentityKeyPair;
589 this.localRegistrationId = registrationId;
590 this.trustNewIdentity = trustNewIdentity;
591
592 migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
593
594 if (rootNode.hasNonNull("groupStore")) {
595 groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class);
596 groupStore = GroupStore.fromStorage(groupStoreStorage,
597 getGroupCachePath(dataPath, accountPath),
598 getRecipientStore(),
599 this::saveGroupStore);
600 } else {
601 groupStore = new GroupStore(getGroupCachePath(dataPath, accountPath),
602 getRecipientStore(),
603 this::saveGroupStore);
604 }
605
606 if (rootNode.hasNonNull("stickerStore")) {
607 stickerStoreStorage = jsonProcessor.convertValue(rootNode.get("stickerStore"), StickerStore.Storage.class);
608 stickerStore = StickerStore.fromStorage(stickerStoreStorage, this::saveStickerStore);
609 } else {
610 stickerStore = new StickerStore(this::saveStickerStore);
611 }
612
613 if (rootNode.hasNonNull("configurationStore")) {
614 configurationStoreStorage = jsonProcessor.convertValue(rootNode.get("configurationStore"),
615 ConfigurationStore.Storage.class);
616 configurationStore = ConfigurationStore.fromStorage(configurationStoreStorage,
617 this::saveConfigurationStore);
618 } else {
619 configurationStore = new ConfigurationStore(this::saveConfigurationStore);
620 }
621
622 migratedLegacyConfig = loadLegacyThreadStore(rootNode) || migratedLegacyConfig;
623
624 if (migratedLegacyConfig) {
625 save();
626 }
627 }
628
629 private boolean loadLegacyStores(
630 final JsonNode rootNode, final LegacyJsonSignalProtocolStore legacySignalProtocolStore
631 ) {
632 var migrated = false;
633 var legacyRecipientStoreNode = rootNode.get("recipientStore");
634 if (legacyRecipientStoreNode != null) {
635 logger.debug("Migrating legacy recipient store.");
636 var legacyRecipientStore = jsonProcessor.convertValue(legacyRecipientStoreNode, LegacyRecipientStore.class);
637 if (legacyRecipientStore != null) {
638 getRecipientStore().resolveRecipientsTrusted(legacyRecipientStore.getAddresses());
639 }
640 getRecipientStore().resolveSelfRecipientTrusted(getSelfRecipientAddress());
641 migrated = true;
642 }
643
644 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
645 logger.debug("Migrating legacy pre key store.");
646 for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
647 try {
648 getAciPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
649 } catch (InvalidMessageException e) {
650 logger.warn("Failed to migrate pre key, ignoring", e);
651 }
652 }
653 migrated = true;
654 }
655
656 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
657 logger.debug("Migrating legacy signed pre key store.");
658 for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
659 try {
660 getAciSignedPreKeyStore().storeSignedPreKey(entry.getKey(),
661 new SignedPreKeyRecord(entry.getValue()));
662 } catch (InvalidMessageException e) {
663 logger.warn("Failed to migrate signed pre key, ignoring", e);
664 }
665 }
666 migrated = true;
667 }
668
669 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
670 logger.debug("Migrating legacy session store.");
671 for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
672 try {
673 getSessionStore().storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
674 session.deviceId), new SessionRecord(session.sessionRecord));
675 } catch (Exception e) {
676 logger.warn("Failed to migrate session, ignoring", e);
677 }
678 }
679 migrated = true;
680 }
681
682 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
683 logger.debug("Migrating legacy identity session store.");
684 for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
685 RecipientId recipientId = getRecipientStore().resolveRecipientTrusted(identity.getAddress());
686 getIdentityKeyStore().saveIdentity(recipientId, identity.getIdentityKey(), identity.getDateAdded());
687 getIdentityKeyStore().setIdentityTrustLevel(recipientId,
688 identity.getIdentityKey(),
689 identity.getTrustLevel());
690 }
691 migrated = true;
692 }
693
694 if (rootNode.hasNonNull("contactStore")) {
695 logger.debug("Migrating legacy contact store.");
696 final var contactStoreNode = rootNode.get("contactStore");
697 final var contactStore = jsonProcessor.convertValue(contactStoreNode, LegacyJsonContactsStore.class);
698 for (var contact : contactStore.getContacts()) {
699 final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
700 getRecipientStore().storeContact(recipientId,
701 new Contact(contact.name,
702 contact.color,
703 contact.messageExpirationTime,
704 contact.blocked,
705 contact.archived));
706
707 // Store profile keys only in profile store
708 var profileKeyString = contact.profileKey;
709 if (profileKeyString != null) {
710 final ProfileKey profileKey;
711 try {
712 profileKey = new ProfileKey(Base64.getDecoder().decode(profileKeyString));
713 getProfileStore().storeProfileKey(recipientId, profileKey);
714 } catch (InvalidInputException e) {
715 logger.warn("Failed to parse legacy contact profile key: {}", e.getMessage());
716 }
717 }
718 }
719 migrated = true;
720 }
721
722 if (rootNode.hasNonNull("profileStore")) {
723 logger.debug("Migrating legacy profile store.");
724 var profileStoreNode = rootNode.get("profileStore");
725 final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
726 for (var profileEntry : legacyProfileStore.getProfileEntries()) {
727 var recipientId = getRecipientStore().resolveRecipient(profileEntry.getAddress());
728 getRecipientStore().storeProfileKeyCredential(recipientId, profileEntry.getProfileKeyCredential());
729 getRecipientStore().storeProfileKey(recipientId, profileEntry.getProfileKey());
730 final var profile = profileEntry.getProfile();
731 if (profile != null) {
732 final var capabilities = new HashSet<Profile.Capability>();
733 if (profile.getCapabilities() != null) {
734 if (profile.getCapabilities().gv1Migration) {
735 capabilities.add(Profile.Capability.gv1Migration);
736 }
737 if (profile.getCapabilities().storage) {
738 capabilities.add(Profile.Capability.storage);
739 }
740 }
741 final var newProfile = new Profile(profileEntry.getLastUpdateTimestamp(),
742 profile.getGivenName(),
743 profile.getFamilyName(),
744 profile.getAbout(),
745 profile.getAboutEmoji(),
746 null,
747 profile.isUnrestrictedUnidentifiedAccess()
748 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
749 : profile.getUnidentifiedAccess() != null
750 ? Profile.UnidentifiedAccessMode.ENABLED
751 : Profile.UnidentifiedAccessMode.DISABLED,
752 capabilities);
753 getRecipientStore().storeProfile(recipientId, newProfile);
754 }
755 }
756 }
757
758 return migrated;
759 }
760
761 private boolean loadLegacyThreadStore(final JsonNode rootNode) {
762 var threadStoreNode = rootNode.get("threadStore");
763 if (threadStoreNode != null && !threadStoreNode.isNull()) {
764 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
765 // Migrate thread info to group and contact store
766 for (var thread : threadStore.getThreads()) {
767 if (thread.id == null || thread.id.isEmpty()) {
768 continue;
769 }
770 try {
771 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
772 final var recipientId = getRecipientStore().resolveRecipient(thread.id);
773 var contact = getRecipientStore().getContact(recipientId);
774 if (contact != null) {
775 getRecipientStore().storeContact(recipientId,
776 Contact.newBuilder(contact)
777 .withMessageExpirationTime(thread.messageExpirationTime)
778 .build());
779 }
780 } else {
781 var groupInfo = groupStore.getGroup(GroupId.fromBase64(thread.id));
782 if (groupInfo instanceof GroupInfoV1) {
783 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
784 groupStore.updateGroup(groupInfo);
785 }
786 }
787 } catch (Exception e) {
788 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
789 }
790 }
791 return true;
792 }
793
794 return false;
795 }
796
797 private void saveStickerStore(StickerStore.Storage storage) {
798 this.stickerStoreStorage = storage;
799 save();
800 }
801
802 private void saveGroupStore(GroupStore.Storage storage) {
803 this.groupStoreStorage = storage;
804 save();
805 }
806
807 private void saveConfigurationStore(ConfigurationStore.Storage storage) {
808 this.configurationStoreStorage = storage;
809 save();
810 }
811
812 private void save() {
813 synchronized (fileChannel) {
814 var rootNode = jsonProcessor.createObjectNode();
815 rootNode.put("version", CURRENT_STORAGE_VERSION)
816 .put("username", number)
817 .put("uuid", aci == null ? null : aci.toString())
818 .put("pni", pni == null ? null : pni.toString())
819 .put("deviceName", encryptedDeviceName)
820 .put("deviceId", deviceId)
821 .put("isMultiDevice", isMultiDevice)
822 .put("lastReceiveTimestamp", lastReceiveTimestamp)
823 .put("password", password)
824 .put("registrationId", localRegistrationId)
825 .put("identityPrivateKey",
826 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPrivateKey().serialize()))
827 .put("identityKey",
828 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPublicKey().serialize()))
829 .put("pniIdentityPrivateKey",
830 pniIdentityKeyPair == null
831 ? null
832 : Base64.getEncoder()
833 .encodeToString(pniIdentityKeyPair.getPrivateKey().serialize()))
834 .put("pniIdentityKey",
835 pniIdentityKeyPair == null
836 ? null
837 : Base64.getEncoder().encodeToString(pniIdentityKeyPair.getPublicKey().serialize()))
838 .put("registrationLockPin", registrationLockPin)
839 .put("pinMasterKey",
840 pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
841 .put("storageKey",
842 storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
843 .put("storageManifestVersion", storageManifestVersion == -1 ? null : storageManifestVersion)
844 .put("preKeyIdOffset", aciPreKeyIdOffset)
845 .put("nextSignedPreKeyId", aciNextSignedPreKeyId)
846 .put("pniPreKeyIdOffset", pniPreKeyIdOffset)
847 .put("pniNextSignedPreKeyId", pniNextSignedPreKeyId)
848 .put("profileKey",
849 profileKey == null ? null : Base64.getEncoder().encodeToString(profileKey.serialize()))
850 .put("registered", registered)
851 .putPOJO("groupStore", groupStoreStorage)
852 .putPOJO("stickerStore", stickerStoreStorage)
853 .putPOJO("configurationStore", configurationStoreStorage);
854 try {
855 try (var output = new ByteArrayOutputStream()) {
856 // Write to memory first to prevent corrupting the file in case of serialization errors
857 jsonProcessor.writeValue(output, rootNode);
858 var input = new ByteArrayInputStream(output.toByteArray());
859 fileChannel.position(0);
860 input.transferTo(Channels.newOutputStream(fileChannel));
861 fileChannel.truncate(fileChannel.position());
862 fileChannel.force(false);
863 }
864 } catch (Exception e) {
865 logger.error("Error saving file: {}", e.getMessage(), e);
866 }
867 }
868 }
869
870 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
871 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
872 var lock = fileChannel.tryLock();
873 if (lock == null) {
874 if (!waitForLock) {
875 logger.debug("Config file is in use by another instance.");
876 throw new IOException("Config file is in use by another instance.");
877 }
878 logger.info("Config file is in use by another instance, waiting…");
879 lock = fileChannel.lock();
880 logger.info("Config file lock acquired.");
881 }
882 return new Pair<>(fileChannel, lock);
883 }
884
885 public void addPreKeys(ServiceIdType serviceIdType, List<PreKeyRecord> records) {
886 if (serviceIdType.equals(ServiceIdType.ACI)) {
887 addAciPreKeys(records);
888 } else {
889 addPniPreKeys(records);
890 }
891 }
892
893 public void addAciPreKeys(List<PreKeyRecord> records) {
894 for (var record : records) {
895 if (aciPreKeyIdOffset != record.getId()) {
896 logger.error("Invalid pre key id {}, expected {}", record.getId(), aciPreKeyIdOffset);
897 throw new AssertionError("Invalid pre key id");
898 }
899 getAciPreKeyStore().storePreKey(record.getId(), record);
900 aciPreKeyIdOffset = (aciPreKeyIdOffset + 1) % Medium.MAX_VALUE;
901 }
902 save();
903 }
904
905 public void addPniPreKeys(List<PreKeyRecord> records) {
906 for (var record : records) {
907 if (pniPreKeyIdOffset != record.getId()) {
908 logger.error("Invalid pre key id {}, expected {}", record.getId(), pniPreKeyIdOffset);
909 throw new AssertionError("Invalid pre key id");
910 }
911 getPniPreKeyStore().storePreKey(record.getId(), record);
912 pniPreKeyIdOffset = (pniPreKeyIdOffset + 1) % Medium.MAX_VALUE;
913 }
914 save();
915 }
916
917 public void addSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord record) {
918 if (serviceIdType.equals(ServiceIdType.ACI)) {
919 addAciSignedPreKey(record);
920 } else {
921 addPniSignedPreKey(record);
922 }
923 }
924
925 public void addAciSignedPreKey(SignedPreKeyRecord record) {
926 if (aciNextSignedPreKeyId != record.getId()) {
927 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), aciNextSignedPreKeyId);
928 throw new AssertionError("Invalid signed pre key id");
929 }
930 getAciSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
931 aciNextSignedPreKeyId = (aciNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
932 save();
933 }
934
935 public void addPniSignedPreKey(SignedPreKeyRecord record) {
936 if (pniNextSignedPreKeyId != record.getId()) {
937 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), pniNextSignedPreKeyId);
938 throw new AssertionError("Invalid signed pre key id");
939 }
940 getPniSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
941 pniNextSignedPreKeyId = (pniNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
942 save();
943 }
944
945 public SignalServiceDataStore getSignalServiceDataStore() {
946 return new SignalServiceDataStore() {
947 @Override
948 public SignalServiceAccountDataStore get(final ServiceId accountIdentifier) {
949 if (accountIdentifier.equals(aci)) {
950 return getSignalServiceAccountDataStore();
951 } else if (accountIdentifier.equals(pni)) {
952 throw new AssertionError("PNI not to be used yet!");
953 } else {
954 throw new IllegalArgumentException("No matching store found for " + accountIdentifier);
955 }
956 }
957
958 @Override
959 public SignalServiceAccountDataStore aci() {
960 return getSignalServiceAccountDataStore();
961 }
962
963 @Override
964 public SignalServiceAccountDataStore pni() {
965 throw new AssertionError("PNI not to be used yet!");
966 }
967
968 @Override
969 public boolean isMultiDevice() {
970 return SignalAccount.this.isMultiDevice();
971 }
972 };
973 }
974
975 public SignalServiceAccountDataStore getSignalServiceAccountDataStore() {
976 return getOrCreate(() -> signalProtocolStore,
977 () -> signalProtocolStore = new SignalProtocolStore(getAciPreKeyStore(),
978 getAciSignedPreKeyStore(),
979 getSessionStore(),
980 getIdentityKeyStore(),
981 getSenderKeyStore(),
982 this::isMultiDevice));
983 }
984
985 private PreKeyStore getAciPreKeyStore() {
986 return getOrCreate(() -> aciPreKeyStore,
987 () -> aciPreKeyStore = new PreKeyStore(getAciPreKeysPath(dataPath, accountPath)));
988 }
989
990 private SignedPreKeyStore getAciSignedPreKeyStore() {
991 return getOrCreate(() -> aciSignedPreKeyStore,
992 () -> aciSignedPreKeyStore = new SignedPreKeyStore(getAciSignedPreKeysPath(dataPath, accountPath)));
993 }
994
995 private PreKeyStore getPniPreKeyStore() {
996 return getOrCreate(() -> pniPreKeyStore,
997 () -> pniPreKeyStore = new PreKeyStore(getPniPreKeysPath(dataPath, accountPath)));
998 }
999
1000 private SignedPreKeyStore getPniSignedPreKeyStore() {
1001 return getOrCreate(() -> pniSignedPreKeyStore,
1002 () -> pniSignedPreKeyStore = new SignedPreKeyStore(getPniSignedPreKeysPath(dataPath, accountPath)));
1003 }
1004
1005 public SessionStore getSessionStore() {
1006 return getOrCreate(() -> sessionStore,
1007 () -> sessionStore = new SessionStore(getSessionsPath(dataPath, accountPath), getRecipientStore()));
1008 }
1009
1010 public IdentityKeyStore getIdentityKeyStore() {
1011 return getOrCreate(() -> identityKeyStore,
1012 () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, accountPath),
1013 getRecipientStore(),
1014 aciIdentityKeyPair,
1015 localRegistrationId,
1016 trustNewIdentity));
1017 }
1018
1019 public GroupStore getGroupStore() {
1020 return groupStore;
1021 }
1022
1023 public ContactsStore getContactStore() {
1024 return getRecipientStore();
1025 }
1026
1027 public RecipientStore getRecipientStore() {
1028 return getOrCreate(() -> recipientStore,
1029 () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, accountPath),
1030 this::mergeRecipients,
1031 this::getSelfRecipientAddress));
1032 }
1033
1034 public ProfileStore getProfileStore() {
1035 return getRecipientStore();
1036 }
1037
1038 public StickerStore getStickerStore() {
1039 return stickerStore;
1040 }
1041
1042 public SenderKeyStore getSenderKeyStore() {
1043 return getOrCreate(() -> senderKeyStore,
1044 () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, accountPath),
1045 getSenderKeysPath(dataPath, accountPath),
1046 getRecipientStore()::resolveRecipientAddress,
1047 getRecipientStore()));
1048 }
1049
1050 public ConfigurationStore getConfigurationStore() {
1051 return configurationStore;
1052 }
1053
1054 public MessageCache getMessageCache() {
1055 return getOrCreate(() -> messageCache,
1056 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
1057 }
1058
1059 public AccountDatabase getAccountDatabase() {
1060 return getOrCreate(() -> accountDatabase, () -> {
1061 try {
1062 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
1063 } catch (SQLException e) {
1064 throw new RuntimeException(e);
1065 }
1066 });
1067 }
1068
1069 public MessageSendLogStore getMessageSendLogStore() {
1070 return getOrCreate(() -> messageSendLogStore,
1071 () -> messageSendLogStore = new MessageSendLogStore(getRecipientStore(), getAccountDatabase()));
1072 }
1073
1074 public CredentialsProvider getCredentialsProvider() {
1075 return new CredentialsProvider() {
1076 @Override
1077 public ACI getAci() {
1078 return aci;
1079 }
1080
1081 @Override
1082 public PNI getPni() {
1083 return pni;
1084 }
1085
1086 @Override
1087 public String getE164() {
1088 return number;
1089 }
1090
1091 @Override
1092 public String getPassword() {
1093 return password;
1094 }
1095
1096 @Override
1097 public int getDeviceId() {
1098 return deviceId;
1099 }
1100 };
1101 }
1102
1103 public String getNumber() {
1104 return number;
1105 }
1106
1107 public void setNumber(final String number) {
1108 this.number = number;
1109 save();
1110 }
1111
1112 public ACI getAci() {
1113 return aci;
1114 }
1115
1116 public void setAci(final ACI aci) {
1117 this.aci = aci;
1118 save();
1119 }
1120
1121 public PNI getPni() {
1122 return pni;
1123 }
1124
1125 public void setPni(final PNI pni) {
1126 this.pni = pni;
1127 save();
1128 }
1129
1130 public SignalServiceAddress getSelfAddress() {
1131 return new SignalServiceAddress(aci, number);
1132 }
1133
1134 public RecipientAddress getSelfRecipientAddress() {
1135 return new RecipientAddress(aci == null ? null : aci.uuid(), number);
1136 }
1137
1138 public RecipientId getSelfRecipientId() {
1139 return getRecipientStore().resolveRecipient(getSelfRecipientAddress());
1140 }
1141
1142 public byte[] getEncryptedDeviceName() {
1143 return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
1144 }
1145
1146 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1147 this.encryptedDeviceName = encryptedDeviceName;
1148 save();
1149 }
1150
1151 public int getDeviceId() {
1152 return deviceId;
1153 }
1154
1155 public boolean isMasterDevice() {
1156 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1157 }
1158
1159 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1160 return serviceIdType.equals(ServiceIdType.ACI) ? aciIdentityKeyPair : pniIdentityKeyPair;
1161 }
1162
1163 public IdentityKeyPair getAciIdentityKeyPair() {
1164 return aciIdentityKeyPair;
1165 }
1166
1167 public IdentityKeyPair getPniIdentityKeyPair() {
1168 return pniIdentityKeyPair;
1169 }
1170
1171 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1172 pniIdentityKeyPair = identityKeyPair;
1173 save();
1174 }
1175
1176 public int getLocalRegistrationId() {
1177 return localRegistrationId;
1178 }
1179
1180 public String getPassword() {
1181 return password;
1182 }
1183
1184 private void setPassword(final String password) {
1185 this.password = password;
1186 save();
1187 }
1188
1189 public void setRegistrationLockPin(final String registrationLockPin, final MasterKey pinMasterKey) {
1190 this.registrationLockPin = registrationLockPin;
1191 this.pinMasterKey = pinMasterKey;
1192 save();
1193 }
1194
1195 public MasterKey getPinMasterKey() {
1196 return pinMasterKey;
1197 }
1198
1199 public StorageKey getStorageKey() {
1200 if (pinMasterKey != null) {
1201 return pinMasterKey.deriveStorageServiceKey();
1202 }
1203 return storageKey;
1204 }
1205
1206 public void setStorageKey(final StorageKey storageKey) {
1207 if (storageKey.equals(this.storageKey)) {
1208 return;
1209 }
1210 this.storageKey = storageKey;
1211 save();
1212 }
1213
1214 public long getStorageManifestVersion() {
1215 return this.storageManifestVersion;
1216 }
1217
1218 public void setStorageManifestVersion(final long storageManifestVersion) {
1219 if (storageManifestVersion == this.storageManifestVersion) {
1220 return;
1221 }
1222 this.storageManifestVersion = storageManifestVersion;
1223 save();
1224 }
1225
1226 public ProfileKey getProfileKey() {
1227 return profileKey;
1228 }
1229
1230 public void setProfileKey(final ProfileKey profileKey) {
1231 if (profileKey.equals(this.profileKey)) {
1232 return;
1233 }
1234 this.profileKey = profileKey;
1235 save();
1236 }
1237
1238 public byte[] getSelfUnidentifiedAccessKey() {
1239 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1240 }
1241
1242 public int getPreKeyIdOffset(ServiceIdType serviceIdType) {
1243 return serviceIdType.equals(ServiceIdType.ACI) ? aciPreKeyIdOffset : pniPreKeyIdOffset;
1244 }
1245
1246 public int getNextSignedPreKeyId(ServiceIdType serviceIdType) {
1247 return serviceIdType.equals(ServiceIdType.ACI) ? aciNextSignedPreKeyId : pniNextSignedPreKeyId;
1248 }
1249
1250 public boolean isRegistered() {
1251 return registered;
1252 }
1253
1254 public void setRegistered(final boolean registered) {
1255 this.registered = registered;
1256 save();
1257 }
1258
1259 public boolean isMultiDevice() {
1260 return isMultiDevice;
1261 }
1262
1263 public void setMultiDevice(final boolean multiDevice) {
1264 if (isMultiDevice == multiDevice) {
1265 return;
1266 }
1267 isMultiDevice = multiDevice;
1268 save();
1269 }
1270
1271 public long getLastReceiveTimestamp() {
1272 return lastReceiveTimestamp;
1273 }
1274
1275 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1276 this.lastReceiveTimestamp = lastReceiveTimestamp;
1277 save();
1278 }
1279
1280 public boolean isUnrestrictedUnidentifiedAccess() {
1281 // TODO make configurable
1282 return false;
1283 }
1284
1285 public boolean isDiscoverableByPhoneNumber() {
1286 return configurationStore.getPhoneNumberUnlisted() == null || !configurationStore.getPhoneNumberUnlisted();
1287 }
1288
1289 public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
1290 this.pinMasterKey = masterKey;
1291 this.storageManifestVersion = -1;
1292 this.storageKey = null;
1293 this.encryptedDeviceName = null;
1294 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1295 this.isMultiDevice = false;
1296 this.registered = true;
1297 this.aci = aci;
1298 this.pni = pni;
1299 this.registrationLockPin = pin;
1300 this.lastReceiveTimestamp = 0;
1301 save();
1302
1303 clearAllPreKeys();
1304 getSessionStore().archiveAllSessions();
1305 getSenderKeyStore().deleteAll();
1306 final var recipientId = getRecipientStore().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1307 final var publicKey = getAciIdentityKeyPair().getPublicKey();
1308 getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date());
1309 getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1310 }
1311
1312 @Override
1313 public void close() {
1314 synchronized (fileChannel) {
1315 if (accountDatabase != null) {
1316 try {
1317 accountDatabase.close();
1318 } catch (SQLException e) {
1319 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1320 }
1321 }
1322 if (messageSendLogStore != null) {
1323 messageSendLogStore.close();
1324 }
1325 try {
1326 try {
1327 lock.close();
1328 } catch (ClosedChannelException ignored) {
1329 }
1330 fileChannel.close();
1331 } catch (IOException e) {
1332 logger.warn("Failed to close account: {}", e.getMessage(), e);
1333 }
1334 }
1335 }
1336
1337 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1338 var value = supplier.get();
1339 if (value != null) {
1340 return value;
1341 }
1342
1343 synchronized (LOCK) {
1344 value = supplier.get();
1345 if (value != null) {
1346 return value;
1347 }
1348 creator.call();
1349 return supplier.get();
1350 }
1351 }
1352
1353 private interface Callable {
1354
1355 void call();
1356 }
1357 }