]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
7b5411e68f35a4fd7fef56a87f80efcd564b964a
[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 null,
761 profile.isUnrestrictedUnidentifiedAccess()
762 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
763 : profile.getUnidentifiedAccess() != null
764 ? Profile.UnidentifiedAccessMode.ENABLED
765 : Profile.UnidentifiedAccessMode.DISABLED,
766 capabilities);
767 getRecipientStore().storeProfile(recipientId, newProfile);
768 }
769 }
770 }
771
772 return migrated;
773 }
774
775 private boolean loadLegacyThreadStore(final JsonNode rootNode) {
776 var threadStoreNode = rootNode.get("threadStore");
777 if (threadStoreNode != null && !threadStoreNode.isNull()) {
778 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
779 // Migrate thread info to group and contact store
780 for (var thread : threadStore.getThreads()) {
781 if (thread.id == null || thread.id.isEmpty()) {
782 continue;
783 }
784 try {
785 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
786 final var recipientId = getRecipientStore().resolveRecipient(thread.id);
787 var contact = getRecipientStore().getContact(recipientId);
788 if (contact != null) {
789 getRecipientStore().storeContact(recipientId,
790 Contact.newBuilder(contact)
791 .withMessageExpirationTime(thread.messageExpirationTime)
792 .build());
793 }
794 } else {
795 var groupInfo = groupStore.getGroup(GroupId.fromBase64(thread.id));
796 if (groupInfo instanceof GroupInfoV1) {
797 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
798 groupStore.updateGroup(groupInfo);
799 }
800 }
801 } catch (Exception e) {
802 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
803 }
804 }
805 return true;
806 }
807
808 return false;
809 }
810
811 private void saveStickerStore(StickerStore.Storage storage) {
812 this.stickerStoreStorage = storage;
813 save();
814 }
815
816 private void saveGroupStore(GroupStore.Storage storage) {
817 this.groupStoreStorage = storage;
818 save();
819 }
820
821 private void saveConfigurationStore(ConfigurationStore.Storage storage) {
822 this.configurationStoreStorage = storage;
823 save();
824 }
825
826 private void save() {
827 synchronized (fileChannel) {
828 var rootNode = jsonProcessor.createObjectNode();
829 rootNode.put("version", CURRENT_STORAGE_VERSION)
830 .put("username", number)
831 .put("serviceEnvironment", serviceEnvironment == null ? null : serviceEnvironment.name())
832 .put("uuid", aci == null ? null : aci.toString())
833 .put("pni", pni == null ? null : pni.toString())
834 .put("deviceName", encryptedDeviceName)
835 .put("deviceId", deviceId)
836 .put("isMultiDevice", isMultiDevice)
837 .put("lastReceiveTimestamp", lastReceiveTimestamp)
838 .put("password", password)
839 .put("registrationId", localRegistrationId)
840 .put("identityPrivateKey",
841 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPrivateKey().serialize()))
842 .put("identityKey",
843 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPublicKey().serialize()))
844 .put("pniIdentityPrivateKey",
845 pniIdentityKeyPair == null
846 ? null
847 : Base64.getEncoder()
848 .encodeToString(pniIdentityKeyPair.getPrivateKey().serialize()))
849 .put("pniIdentityKey",
850 pniIdentityKeyPair == null
851 ? null
852 : Base64.getEncoder().encodeToString(pniIdentityKeyPair.getPublicKey().serialize()))
853 .put("registrationLockPin", registrationLockPin)
854 .put("pinMasterKey",
855 pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
856 .put("storageKey",
857 storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
858 .put("storageManifestVersion", storageManifestVersion == -1 ? null : storageManifestVersion)
859 .put("preKeyIdOffset", aciPreKeyIdOffset)
860 .put("nextSignedPreKeyId", aciNextSignedPreKeyId)
861 .put("pniPreKeyIdOffset", pniPreKeyIdOffset)
862 .put("pniNextSignedPreKeyId", pniNextSignedPreKeyId)
863 .put("profileKey",
864 profileKey == null ? null : Base64.getEncoder().encodeToString(profileKey.serialize()))
865 .put("registered", registered)
866 .putPOJO("groupStore", groupStoreStorage)
867 .putPOJO("stickerStore", stickerStoreStorage)
868 .putPOJO("configurationStore", configurationStoreStorage);
869 try {
870 try (var output = new ByteArrayOutputStream()) {
871 // Write to memory first to prevent corrupting the file in case of serialization errors
872 jsonProcessor.writeValue(output, rootNode);
873 var input = new ByteArrayInputStream(output.toByteArray());
874 fileChannel.position(0);
875 input.transferTo(Channels.newOutputStream(fileChannel));
876 fileChannel.truncate(fileChannel.position());
877 fileChannel.force(false);
878 }
879 } catch (Exception e) {
880 logger.error("Error saving file: {}", e.getMessage(), e);
881 }
882 }
883 }
884
885 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
886 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
887 var lock = fileChannel.tryLock();
888 if (lock == null) {
889 if (!waitForLock) {
890 logger.debug("Config file is in use by another instance.");
891 throw new IOException("Config file is in use by another instance.");
892 }
893 logger.info("Config file is in use by another instance, waiting…");
894 lock = fileChannel.lock();
895 logger.info("Config file lock acquired.");
896 }
897 return new Pair<>(fileChannel, lock);
898 }
899
900 public void addPreKeys(ServiceIdType serviceIdType, List<PreKeyRecord> records) {
901 if (serviceIdType.equals(ServiceIdType.ACI)) {
902 addAciPreKeys(records);
903 } else {
904 addPniPreKeys(records);
905 }
906 }
907
908 public void addAciPreKeys(List<PreKeyRecord> records) {
909 for (var record : records) {
910 if (aciPreKeyIdOffset != record.getId()) {
911 logger.error("Invalid pre key id {}, expected {}", record.getId(), aciPreKeyIdOffset);
912 throw new AssertionError("Invalid pre key id");
913 }
914 getAciPreKeyStore().storePreKey(record.getId(), record);
915 aciPreKeyIdOffset = (aciPreKeyIdOffset + 1) % Medium.MAX_VALUE;
916 }
917 save();
918 }
919
920 public void addPniPreKeys(List<PreKeyRecord> records) {
921 for (var record : records) {
922 if (pniPreKeyIdOffset != record.getId()) {
923 logger.error("Invalid pre key id {}, expected {}", record.getId(), pniPreKeyIdOffset);
924 throw new AssertionError("Invalid pre key id");
925 }
926 getPniPreKeyStore().storePreKey(record.getId(), record);
927 pniPreKeyIdOffset = (pniPreKeyIdOffset + 1) % Medium.MAX_VALUE;
928 }
929 save();
930 }
931
932 public void addSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord record) {
933 if (serviceIdType.equals(ServiceIdType.ACI)) {
934 addAciSignedPreKey(record);
935 } else {
936 addPniSignedPreKey(record);
937 }
938 }
939
940 public void addAciSignedPreKey(SignedPreKeyRecord record) {
941 if (aciNextSignedPreKeyId != record.getId()) {
942 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), aciNextSignedPreKeyId);
943 throw new AssertionError("Invalid signed pre key id");
944 }
945 getAciSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
946 aciNextSignedPreKeyId = (aciNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
947 save();
948 }
949
950 public void addPniSignedPreKey(SignedPreKeyRecord record) {
951 if (pniNextSignedPreKeyId != record.getId()) {
952 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), pniNextSignedPreKeyId);
953 throw new AssertionError("Invalid signed pre key id");
954 }
955 getPniSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
956 pniNextSignedPreKeyId = (pniNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
957 save();
958 }
959
960 public SignalServiceDataStore getSignalServiceDataStore() {
961 return new SignalServiceDataStore() {
962 @Override
963 public SignalServiceAccountDataStore get(final ServiceId accountIdentifier) {
964 if (accountIdentifier.equals(aci)) {
965 return getSignalServiceAccountDataStore();
966 } else if (accountIdentifier.equals(pni)) {
967 throw new AssertionError("PNI not to be used yet!");
968 } else {
969 throw new IllegalArgumentException("No matching store found for " + accountIdentifier);
970 }
971 }
972
973 @Override
974 public SignalServiceAccountDataStore aci() {
975 return getSignalServiceAccountDataStore();
976 }
977
978 @Override
979 public SignalServiceAccountDataStore pni() {
980 throw new AssertionError("PNI not to be used yet!");
981 }
982
983 @Override
984 public boolean isMultiDevice() {
985 return SignalAccount.this.isMultiDevice();
986 }
987 };
988 }
989
990 public SignalServiceAccountDataStore getSignalServiceAccountDataStore() {
991 return getOrCreate(() -> signalProtocolStore,
992 () -> signalProtocolStore = new SignalProtocolStore(getAciPreKeyStore(),
993 getAciSignedPreKeyStore(),
994 getSessionStore(),
995 getIdentityKeyStore(),
996 getSenderKeyStore(),
997 this::isMultiDevice));
998 }
999
1000 private PreKeyStore getAciPreKeyStore() {
1001 return getOrCreate(() -> aciPreKeyStore,
1002 () -> aciPreKeyStore = new PreKeyStore(getAciPreKeysPath(dataPath, accountPath)));
1003 }
1004
1005 private SignedPreKeyStore getAciSignedPreKeyStore() {
1006 return getOrCreate(() -> aciSignedPreKeyStore,
1007 () -> aciSignedPreKeyStore = new SignedPreKeyStore(getAciSignedPreKeysPath(dataPath, accountPath)));
1008 }
1009
1010 private PreKeyStore getPniPreKeyStore() {
1011 return getOrCreate(() -> pniPreKeyStore,
1012 () -> pniPreKeyStore = new PreKeyStore(getPniPreKeysPath(dataPath, accountPath)));
1013 }
1014
1015 private SignedPreKeyStore getPniSignedPreKeyStore() {
1016 return getOrCreate(() -> pniSignedPreKeyStore,
1017 () -> pniSignedPreKeyStore = new SignedPreKeyStore(getPniSignedPreKeysPath(dataPath, accountPath)));
1018 }
1019
1020 public SessionStore getSessionStore() {
1021 return getOrCreate(() -> sessionStore,
1022 () -> sessionStore = new SessionStore(getSessionsPath(dataPath, accountPath), getRecipientStore()));
1023 }
1024
1025 public IdentityKeyStore getIdentityKeyStore() {
1026 return getOrCreate(() -> identityKeyStore,
1027 () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, accountPath),
1028 getRecipientStore(),
1029 aciIdentityKeyPair,
1030 localRegistrationId,
1031 trustNewIdentity));
1032 }
1033
1034 public GroupStore getGroupStore() {
1035 return groupStore;
1036 }
1037
1038 public ContactsStore getContactStore() {
1039 return getRecipientStore();
1040 }
1041
1042 public RecipientStore getRecipientStore() {
1043 return getOrCreate(() -> recipientStore,
1044 () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, accountPath),
1045 this::mergeRecipients,
1046 this::getSelfRecipientAddress));
1047 }
1048
1049 public ProfileStore getProfileStore() {
1050 return getRecipientStore();
1051 }
1052
1053 public StickerStore getStickerStore() {
1054 return stickerStore;
1055 }
1056
1057 public SenderKeyStore getSenderKeyStore() {
1058 return getOrCreate(() -> senderKeyStore,
1059 () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, accountPath),
1060 getSenderKeysPath(dataPath, accountPath),
1061 getRecipientStore()::resolveRecipientAddress,
1062 getRecipientStore()));
1063 }
1064
1065 public ConfigurationStore getConfigurationStore() {
1066 return configurationStore;
1067 }
1068
1069 public MessageCache getMessageCache() {
1070 return getOrCreate(() -> messageCache,
1071 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
1072 }
1073
1074 public AccountDatabase getAccountDatabase() {
1075 return getOrCreate(() -> accountDatabase, () -> {
1076 try {
1077 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
1078 } catch (SQLException e) {
1079 throw new RuntimeException(e);
1080 }
1081 });
1082 }
1083
1084 public MessageSendLogStore getMessageSendLogStore() {
1085 return getOrCreate(() -> messageSendLogStore,
1086 () -> messageSendLogStore = new MessageSendLogStore(getRecipientStore(), getAccountDatabase()));
1087 }
1088
1089 public CredentialsProvider getCredentialsProvider() {
1090 return new CredentialsProvider() {
1091 @Override
1092 public ACI getAci() {
1093 return aci;
1094 }
1095
1096 @Override
1097 public PNI getPni() {
1098 return pni;
1099 }
1100
1101 @Override
1102 public String getE164() {
1103 return number;
1104 }
1105
1106 @Override
1107 public String getPassword() {
1108 return password;
1109 }
1110
1111 @Override
1112 public int getDeviceId() {
1113 return deviceId;
1114 }
1115 };
1116 }
1117
1118 public String getNumber() {
1119 return number;
1120 }
1121
1122 public void setNumber(final String number) {
1123 this.number = number;
1124 save();
1125 }
1126
1127 public ServiceEnvironment getServiceEnvironment() {
1128 return serviceEnvironment;
1129 }
1130
1131 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1132 this.serviceEnvironment = serviceEnvironment;
1133 save();
1134 }
1135
1136 public ACI getAci() {
1137 return aci;
1138 }
1139
1140 public void setAci(final ACI aci) {
1141 this.aci = aci;
1142 save();
1143 }
1144
1145 public PNI getPni() {
1146 return pni;
1147 }
1148
1149 public void setPni(final PNI pni) {
1150 this.pni = pni;
1151 save();
1152 }
1153
1154 public SignalServiceAddress getSelfAddress() {
1155 return new SignalServiceAddress(aci, number);
1156 }
1157
1158 public RecipientAddress getSelfRecipientAddress() {
1159 return new RecipientAddress(aci == null ? null : aci.uuid(), number);
1160 }
1161
1162 public RecipientId getSelfRecipientId() {
1163 return getRecipientStore().resolveRecipient(getSelfRecipientAddress());
1164 }
1165
1166 public byte[] getEncryptedDeviceName() {
1167 return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
1168 }
1169
1170 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1171 this.encryptedDeviceName = encryptedDeviceName;
1172 save();
1173 }
1174
1175 public int getDeviceId() {
1176 return deviceId;
1177 }
1178
1179 public boolean isMasterDevice() {
1180 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1181 }
1182
1183 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1184 return serviceIdType.equals(ServiceIdType.ACI) ? aciIdentityKeyPair : pniIdentityKeyPair;
1185 }
1186
1187 public IdentityKeyPair getAciIdentityKeyPair() {
1188 return aciIdentityKeyPair;
1189 }
1190
1191 public IdentityKeyPair getPniIdentityKeyPair() {
1192 return pniIdentityKeyPair;
1193 }
1194
1195 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1196 pniIdentityKeyPair = identityKeyPair;
1197 save();
1198 }
1199
1200 public int getLocalRegistrationId() {
1201 return localRegistrationId;
1202 }
1203
1204 public String getPassword() {
1205 return password;
1206 }
1207
1208 private void setPassword(final String password) {
1209 this.password = password;
1210 save();
1211 }
1212
1213 public void setRegistrationLockPin(final String registrationLockPin, final MasterKey pinMasterKey) {
1214 this.registrationLockPin = registrationLockPin;
1215 this.pinMasterKey = pinMasterKey;
1216 save();
1217 }
1218
1219 public MasterKey getPinMasterKey() {
1220 return pinMasterKey;
1221 }
1222
1223 public StorageKey getStorageKey() {
1224 if (pinMasterKey != null) {
1225 return pinMasterKey.deriveStorageServiceKey();
1226 }
1227 return storageKey;
1228 }
1229
1230 public void setStorageKey(final StorageKey storageKey) {
1231 if (storageKey.equals(this.storageKey)) {
1232 return;
1233 }
1234 this.storageKey = storageKey;
1235 save();
1236 }
1237
1238 public long getStorageManifestVersion() {
1239 return this.storageManifestVersion;
1240 }
1241
1242 public void setStorageManifestVersion(final long storageManifestVersion) {
1243 if (storageManifestVersion == this.storageManifestVersion) {
1244 return;
1245 }
1246 this.storageManifestVersion = storageManifestVersion;
1247 save();
1248 }
1249
1250 public ProfileKey getProfileKey() {
1251 return profileKey;
1252 }
1253
1254 public void setProfileKey(final ProfileKey profileKey) {
1255 if (profileKey.equals(this.profileKey)) {
1256 return;
1257 }
1258 this.profileKey = profileKey;
1259 save();
1260 }
1261
1262 public byte[] getSelfUnidentifiedAccessKey() {
1263 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1264 }
1265
1266 public int getPreKeyIdOffset(ServiceIdType serviceIdType) {
1267 return serviceIdType.equals(ServiceIdType.ACI) ? aciPreKeyIdOffset : pniPreKeyIdOffset;
1268 }
1269
1270 public int getNextSignedPreKeyId(ServiceIdType serviceIdType) {
1271 return serviceIdType.equals(ServiceIdType.ACI) ? aciNextSignedPreKeyId : pniNextSignedPreKeyId;
1272 }
1273
1274 public boolean isRegistered() {
1275 return registered;
1276 }
1277
1278 public void setRegistered(final boolean registered) {
1279 this.registered = registered;
1280 save();
1281 }
1282
1283 public boolean isMultiDevice() {
1284 return isMultiDevice;
1285 }
1286
1287 public void setMultiDevice(final boolean multiDevice) {
1288 if (isMultiDevice == multiDevice) {
1289 return;
1290 }
1291 isMultiDevice = multiDevice;
1292 save();
1293 }
1294
1295 public long getLastReceiveTimestamp() {
1296 return lastReceiveTimestamp;
1297 }
1298
1299 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1300 this.lastReceiveTimestamp = lastReceiveTimestamp;
1301 save();
1302 }
1303
1304 public boolean isUnrestrictedUnidentifiedAccess() {
1305 // TODO make configurable
1306 return false;
1307 }
1308
1309 public boolean isDiscoverableByPhoneNumber() {
1310 return configurationStore.getPhoneNumberUnlisted() == null || !configurationStore.getPhoneNumberUnlisted();
1311 }
1312
1313 public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
1314 this.pinMasterKey = masterKey;
1315 this.storageManifestVersion = -1;
1316 this.storageKey = null;
1317 this.encryptedDeviceName = null;
1318 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1319 this.isMultiDevice = false;
1320 this.registered = true;
1321 this.aci = aci;
1322 this.pni = pni;
1323 this.registrationLockPin = pin;
1324 this.lastReceiveTimestamp = 0;
1325 save();
1326
1327 clearAllPreKeys();
1328 getSessionStore().archiveAllSessions();
1329 getSenderKeyStore().deleteAll();
1330 final var recipientId = getRecipientStore().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1331 final var publicKey = getAciIdentityKeyPair().getPublicKey();
1332 getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date());
1333 getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1334 }
1335
1336 public void deleteAccountData() throws IOException {
1337 close();
1338 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1339 .sorted(Comparator.reverseOrder())) {
1340 for (final var file = files.iterator(); file.hasNext(); ) {
1341 Files.delete(file.next());
1342 }
1343 }
1344 Files.delete(getFileName(dataPath, accountPath).toPath());
1345 }
1346
1347 @Override
1348 public void close() {
1349 synchronized (fileChannel) {
1350 if (accountDatabase != null) {
1351 try {
1352 accountDatabase.close();
1353 } catch (SQLException e) {
1354 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1355 }
1356 }
1357 if (messageSendLogStore != null) {
1358 messageSendLogStore.close();
1359 }
1360 try {
1361 try {
1362 lock.close();
1363 } catch (ClosedChannelException ignored) {
1364 }
1365 fileChannel.close();
1366 } catch (IOException e) {
1367 logger.warn("Failed to close account: {}", e.getMessage(), e);
1368 }
1369 }
1370 }
1371
1372 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1373 var value = supplier.get();
1374 if (value != null) {
1375 return value;
1376 }
1377
1378 synchronized (LOCK) {
1379 value = supplier.get();
1380 if (value != null) {
1381 return value;
1382 }
1383 creator.call();
1384 return supplier.get();
1385 }
1386 }
1387
1388 private interface Callable {
1389
1390 void call();
1391 }
1392 }