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