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