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