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