]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
3de3ed1bd78d961ba7cbfa670d1e5b5c6db59c2e
[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.Settings;
7 import org.asamk.signal.manager.api.Contact;
8 import org.asamk.signal.manager.api.GroupId;
9 import org.asamk.signal.manager.api.Pair;
10 import org.asamk.signal.manager.api.Profile;
11 import org.asamk.signal.manager.api.ServiceEnvironment;
12 import org.asamk.signal.manager.api.TrustLevel;
13 import org.asamk.signal.manager.helper.RecipientAddressResolver;
14 import org.asamk.signal.manager.storage.configuration.ConfigurationStore;
15 import org.asamk.signal.manager.storage.configuration.LegacyConfigurationStore;
16 import org.asamk.signal.manager.storage.contacts.ContactsStore;
17 import org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore;
18 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
19 import org.asamk.signal.manager.storage.groups.GroupStore;
20 import org.asamk.signal.manager.storage.groups.LegacyGroupStore;
21 import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
22 import org.asamk.signal.manager.storage.identities.LegacyIdentityKeyStore;
23 import org.asamk.signal.manager.storage.identities.SignalIdentityKeyStore;
24 import org.asamk.signal.manager.storage.keyValue.KeyValueEntry;
25 import org.asamk.signal.manager.storage.keyValue.KeyValueStore;
26 import org.asamk.signal.manager.storage.messageCache.MessageCache;
27 import org.asamk.signal.manager.storage.prekeys.KyberPreKeyStore;
28 import org.asamk.signal.manager.storage.prekeys.LegacyPreKeyStore;
29 import org.asamk.signal.manager.storage.prekeys.LegacySignedPreKeyStore;
30 import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
31 import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
32 import org.asamk.signal.manager.storage.profiles.LegacyProfileStore;
33 import org.asamk.signal.manager.storage.profiles.ProfileStore;
34 import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
35 import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
36 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
37 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2;
38 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
39 import org.asamk.signal.manager.storage.recipients.RecipientId;
40 import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
41 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
42 import org.asamk.signal.manager.storage.recipients.RecipientStore;
43 import org.asamk.signal.manager.storage.recipients.RecipientTrustedResolver;
44 import org.asamk.signal.manager.storage.sendLog.MessageSendLogStore;
45 import org.asamk.signal.manager.storage.senderKeys.LegacySenderKeyRecordStore;
46 import org.asamk.signal.manager.storage.senderKeys.LegacySenderKeySharedStore;
47 import org.asamk.signal.manager.storage.senderKeys.SenderKeyStore;
48 import org.asamk.signal.manager.storage.sessions.LegacySessionStore;
49 import org.asamk.signal.manager.storage.sessions.SessionStore;
50 import org.asamk.signal.manager.storage.stickers.LegacyStickerStore;
51 import org.asamk.signal.manager.storage.stickers.StickerStore;
52 import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
53 import org.asamk.signal.manager.util.IOUtils;
54 import org.asamk.signal.manager.util.KeyUtils;
55 import org.signal.libsignal.protocol.IdentityKeyPair;
56 import org.signal.libsignal.protocol.InvalidMessageException;
57 import org.signal.libsignal.protocol.SignalProtocolAddress;
58 import org.signal.libsignal.protocol.state.KyberPreKeyRecord;
59 import org.signal.libsignal.protocol.state.PreKeyRecord;
60 import org.signal.libsignal.protocol.state.SessionRecord;
61 import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
62 import org.signal.libsignal.protocol.util.KeyHelper;
63 import org.signal.libsignal.zkgroup.InvalidInputException;
64 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67 import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
68 import org.whispersystems.signalservice.api.SignalServiceDataStore;
69 import org.whispersystems.signalservice.api.account.AccountAttributes;
70 import org.whispersystems.signalservice.api.account.PreKeyCollection;
71 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
72 import org.whispersystems.signalservice.api.kbs.MasterKey;
73 import org.whispersystems.signalservice.api.push.ServiceId;
74 import org.whispersystems.signalservice.api.push.ServiceId.ACI;
75 import org.whispersystems.signalservice.api.push.ServiceId.PNI;
76 import org.whispersystems.signalservice.api.push.ServiceIdType;
77 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
78 import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
79 import org.whispersystems.signalservice.api.storage.StorageKey;
80 import org.whispersystems.signalservice.api.util.CredentialsProvider;
81 import org.whispersystems.signalservice.api.util.UuidUtil;
82
83 import java.io.ByteArrayInputStream;
84 import java.io.ByteArrayOutputStream;
85 import java.io.Closeable;
86 import java.io.File;
87 import java.io.FileInputStream;
88 import java.io.FileOutputStream;
89 import java.io.IOException;
90 import java.io.RandomAccessFile;
91 import java.nio.channels.Channels;
92 import java.nio.channels.ClosedChannelException;
93 import java.nio.channels.FileChannel;
94 import java.nio.channels.FileLock;
95 import java.nio.file.Files;
96 import java.sql.Connection;
97 import java.sql.SQLException;
98 import java.util.Base64;
99 import java.util.Comparator;
100 import java.util.HashSet;
101 import java.util.List;
102 import java.util.Optional;
103 import java.util.function.Supplier;
104
105 import static org.asamk.signal.manager.config.ServiceConfig.PREKEY_MAXIMUM_ID;
106 import static org.asamk.signal.manager.config.ServiceConfig.getCapabilities;
107
108 public class SignalAccount implements Closeable {
109
110 private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
111
112 private static final int MINIMUM_STORAGE_VERSION = 1;
113 private static final int CURRENT_STORAGE_VERSION = 7;
114
115 private final Object LOCK = new Object();
116
117 private final ObjectMapper jsonProcessor = Utils.createStorageObjectMapper();
118
119 private final FileChannel fileChannel;
120 private final FileLock lock;
121
122 private int previousStorageVersion;
123
124 private File dataPath;
125 private String accountPath;
126
127 private ServiceEnvironment serviceEnvironment;
128 private String number;
129 private String username;
130 private ACI aci;
131 private PNI pni;
132 private String encryptedDeviceName;
133 private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
134 private String password;
135 private String registrationLockPin;
136 private MasterKey pinMasterKey;
137 private StorageKey storageKey;
138 private ProfileKey profileKey;
139
140 private Settings settings;
141
142 private final KeyValueEntry<String> verificationSessionId = new KeyValueEntry<>("verification-session-id",
143 String.class);
144 private final KeyValueEntry<String> verificationSessionNumber = new KeyValueEntry<>("verification-session-number",
145 String.class);
146 private final KeyValueEntry<Long> lastReceiveTimestamp = new KeyValueEntry<>("last-receive-timestamp",
147 long.class,
148 0L);
149 private final KeyValueEntry<Long> storageManifestVersion = new KeyValueEntry<>("storage-manifest-version",
150 long.class,
151 -1L);
152 private boolean isMultiDevice = false;
153 private boolean registered = false;
154
155 private final AccountData aciAccountData = new AccountData(ServiceIdType.ACI);
156 private final AccountData pniAccountData = new AccountData(ServiceIdType.PNI);
157 private IdentityKeyStore identityKeyStore;
158 private SenderKeyStore senderKeyStore;
159 private GroupStore groupStore;
160 private RecipientStore recipientStore;
161 private StickerStore stickerStore;
162 private ConfigurationStore configurationStore;
163 private KeyValueStore keyValueStore;
164
165 private MessageCache messageCache;
166 private MessageSendLogStore messageSendLogStore;
167
168 private AccountDatabase accountDatabase;
169
170 private SignalAccount(final FileChannel fileChannel, final FileLock lock) {
171 this.fileChannel = fileChannel;
172 this.lock = lock;
173 }
174
175 public static SignalAccount load(
176 File dataPath, String accountPath, boolean waitForLock, final Settings settings
177 ) throws IOException {
178 logger.trace("Opening account file");
179 final var fileName = getFileName(dataPath, accountPath);
180 final var pair = openFileChannel(fileName, waitForLock);
181 try {
182 var signalAccount = new SignalAccount(pair.first(), pair.second());
183 logger.trace("Loading account file");
184 signalAccount.load(dataPath, accountPath, settings);
185 logger.trace("Migrating legacy parts of account file");
186 signalAccount.migrateLegacyConfigs();
187
188 return signalAccount;
189 } catch (Throwable e) {
190 pair.second().close();
191 pair.first().close();
192 throw e;
193 }
194 }
195
196 public static SignalAccount create(
197 File dataPath,
198 String accountPath,
199 String number,
200 ServiceEnvironment serviceEnvironment,
201 IdentityKeyPair aciIdentityKey,
202 IdentityKeyPair pniIdentityKey,
203 int registrationId,
204 int pniRegistrationId,
205 ProfileKey profileKey,
206 final Settings settings
207 ) throws IOException {
208 IOUtils.createPrivateDirectories(dataPath);
209 var fileName = getFileName(dataPath, accountPath);
210 if (!fileName.exists()) {
211 IOUtils.createPrivateFile(fileName);
212 }
213
214 final var pair = openFileChannel(fileName, true);
215 var signalAccount = new SignalAccount(pair.first(), pair.second());
216
217 signalAccount.accountPath = accountPath;
218 signalAccount.number = number;
219 signalAccount.serviceEnvironment = serviceEnvironment;
220 signalAccount.profileKey = profileKey;
221
222 signalAccount.dataPath = dataPath;
223 signalAccount.aciAccountData.setIdentityKeyPair(aciIdentityKey);
224 signalAccount.pniAccountData.setIdentityKeyPair(pniIdentityKey);
225 signalAccount.aciAccountData.setLocalRegistrationId(registrationId);
226 signalAccount.pniAccountData.setLocalRegistrationId(pniRegistrationId);
227 signalAccount.settings = settings;
228
229 signalAccount.registered = false;
230
231 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
232 signalAccount.migrateLegacyConfigs();
233 signalAccount.save();
234
235 return signalAccount;
236 }
237
238 private static SignalAccount createLinkedAccount(
239 File dataPath,
240 String accountPath,
241 String number,
242 ServiceEnvironment serviceEnvironment,
243 ACI aci,
244 PNI pni,
245 String password,
246 String encryptedDeviceName,
247 int deviceId,
248 IdentityKeyPair aciIdentityKey,
249 IdentityKeyPair pniIdentityKey,
250 int registrationId,
251 int pniRegistrationId,
252 ProfileKey profileKey,
253 final Settings settings
254 ) throws IOException {
255 var fileName = getFileName(dataPath, accountPath);
256 IOUtils.createPrivateFile(fileName);
257
258 final var pair = openFileChannel(fileName, true);
259 var signalAccount = new SignalAccount(pair.first(), pair.second());
260
261 signalAccount.dataPath = dataPath;
262 signalAccount.accountPath = accountPath;
263 signalAccount.serviceEnvironment = serviceEnvironment;
264 signalAccount.aciAccountData.setLocalRegistrationId(registrationId);
265 signalAccount.pniAccountData.setLocalRegistrationId(pniRegistrationId);
266 signalAccount.settings = settings;
267 signalAccount.setProvisioningData(number,
268 aci,
269 pni,
270 password,
271 encryptedDeviceName,
272 deviceId,
273 aciIdentityKey,
274 pniIdentityKey,
275 profileKey);
276
277 signalAccount.getRecipientTrustedResolver()
278 .resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress());
279 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
280 signalAccount.migrateLegacyConfigs();
281 signalAccount.clearAllPreKeys();
282 signalAccount.save();
283
284 return signalAccount;
285 }
286
287 public static SignalAccount createOrUpdateLinkedAccount(
288 File dataPath,
289 String accountPath,
290 String number,
291 ServiceEnvironment serviceEnvironment,
292 ACI aci,
293 PNI pni,
294 String password,
295 String encryptedDeviceName,
296 int deviceId,
297 IdentityKeyPair aciIdentityKey,
298 IdentityKeyPair pniIdentityKey,
299 int registrationId,
300 int pniRegistrationId,
301 ProfileKey profileKey,
302 final Settings settings
303 ) throws IOException {
304 IOUtils.createPrivateDirectories(dataPath);
305 var fileName = getFileName(dataPath, accountPath);
306 if (!fileName.exists()) {
307 return createLinkedAccount(dataPath,
308 accountPath,
309 number,
310 serviceEnvironment,
311 aci,
312 pni,
313 password,
314 encryptedDeviceName,
315 deviceId,
316 aciIdentityKey,
317 pniIdentityKey,
318 registrationId,
319 pniRegistrationId,
320 profileKey,
321 settings);
322 }
323
324 final var signalAccount = load(dataPath, accountPath, true, settings);
325 signalAccount.setProvisioningData(number,
326 aci,
327 pni,
328 password,
329 encryptedDeviceName,
330 deviceId,
331 aciIdentityKey,
332 pniIdentityKey,
333 profileKey);
334 signalAccount.getRecipientTrustedResolver()
335 .resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress());
336 signalAccount.aciAccountData.getSessionStore().archiveAllSessions();
337 signalAccount.pniAccountData.getSessionStore().archiveAllSessions();
338 signalAccount.getSenderKeyStore().deleteAll();
339 signalAccount.clearAllPreKeys();
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 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
360 this.encryptedDeviceName = encryptedDeviceName;
361 this.deviceId = deviceId;
362 this.aciAccountData.setIdentityKeyPair(aciIdentity);
363 this.pniAccountData.setIdentityKeyPair(pniIdentity);
364 this.registered = true;
365 this.isMultiDevice = true;
366 getKeyValueStore().storeEntry(lastReceiveTimestamp, 0L);
367 this.pinMasterKey = null;
368 getKeyValueStore().storeEntry(storageManifestVersion, -1L);
369 this.setStorageManifest(null);
370 this.storageKey = null;
371 trustSelfIdentity(ServiceIdType.ACI);
372 trustSelfIdentity(ServiceIdType.PNI);
373 }
374
375 public void finishRegistration(
376 final ACI aci,
377 final PNI pni,
378 final MasterKey masterKey,
379 final String pin,
380 final PreKeyCollection aciPreKeys,
381 final PreKeyCollection pniPreKeys
382 ) {
383 this.pinMasterKey = masterKey;
384 getKeyValueStore().storeEntry(storageManifestVersion, -1L);
385 this.setStorageManifest(null);
386 this.storageKey = null;
387 this.encryptedDeviceName = null;
388 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
389 this.isMultiDevice = false;
390 this.registered = true;
391 this.aci = aci;
392 this.pni = pni;
393 this.registrationLockPin = pin;
394 getKeyValueStore().storeEntry(lastReceiveTimestamp, 0L);
395 save();
396
397 setPreKeys(ServiceIdType.ACI, aciPreKeys);
398 setPreKeys(ServiceIdType.PNI, pniPreKeys);
399 aciAccountData.getSessionStore().archiveAllSessions();
400 pniAccountData.getSessionStore().archiveAllSessions();
401 getSenderKeyStore().deleteAll();
402 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
403 trustSelfIdentity(ServiceIdType.ACI);
404 trustSelfIdentity(ServiceIdType.PNI);
405 }
406
407 public void initDatabase() {
408 getAccountDatabase();
409 }
410
411 private void migrateLegacyConfigs() {
412 if (getPassword() == null) {
413 setPassword(KeyUtils.createPassword());
414 }
415
416 if (getProfileKey() == null) {
417 // Old config file, creating new profile key
418 setProfileKey(KeyUtils.createProfileKey());
419 }
420 getProfileStore().storeProfileKey(getSelfRecipientId(), getProfileKey());
421 if (isPrimaryDevice() && getPniIdentityKeyPair() == null) {
422 setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
423 }
424 }
425
426 private void mergeRecipients(
427 final Connection connection, RecipientId recipientId, RecipientId toBeMergedRecipientId
428 ) throws SQLException {
429 getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId);
430 getGroupStore().mergeRecipients(connection, recipientId, toBeMergedRecipientId);
431 }
432
433 public void removeRecipient(final RecipientId recipientId) {
434 final var recipientAddress = getRecipientStore().resolveRecipientAddress(recipientId);
435 getRecipientStore().deleteRecipientData(recipientId);
436 getMessageCache().deleteMessages(recipientId);
437 if (recipientAddress.serviceId().isPresent()) {
438 final var serviceId = recipientAddress.serviceId().get();
439 aciAccountData.getSessionStore().deleteAllSessions(serviceId);
440 pniAccountData.getSessionStore().deleteAllSessions(serviceId);
441 getIdentityKeyStore().deleteIdentity(serviceId);
442 getSenderKeyStore().deleteAll(serviceId);
443 }
444 }
445
446 public static File getFileName(File dataPath, String account) {
447 return new File(dataPath, account);
448 }
449
450 private static File getUserPath(final File dataPath, final String account) {
451 final var path = new File(dataPath, account + ".d");
452 try {
453 IOUtils.createPrivateDirectories(path);
454 } catch (IOException e) {
455 throw new AssertionError("Failed to create user path", e);
456 }
457 return path;
458 }
459
460 private static File getMessageCachePath(File dataPath, String account) {
461 return new File(getUserPath(dataPath, account), "msg-cache");
462 }
463
464 private static File getGroupCachePath(File dataPath, String account) {
465 return new File(getUserPath(dataPath, account), "group-cache");
466 }
467
468 private static File getAciPreKeysPath(File dataPath, String account) {
469 return new File(getUserPath(dataPath, account), "pre-keys");
470 }
471
472 private static File getAciSignedPreKeysPath(File dataPath, String account) {
473 return new File(getUserPath(dataPath, account), "signed-pre-keys");
474 }
475
476 private static File getPniPreKeysPath(File dataPath, String account) {
477 return new File(getUserPath(dataPath, account), "pre-keys-pni");
478 }
479
480 private static File getPniSignedPreKeysPath(File dataPath, String account) {
481 return new File(getUserPath(dataPath, account), "signed-pre-keys-pni");
482 }
483
484 private static File getIdentitiesPath(File dataPath, String account) {
485 return new File(getUserPath(dataPath, account), "identities");
486 }
487
488 private static File getSessionsPath(File dataPath, String account) {
489 return new File(getUserPath(dataPath, account), "sessions");
490 }
491
492 private static File getSenderKeysPath(File dataPath, String account) {
493 return new File(getUserPath(dataPath, account), "sender-keys");
494 }
495
496 private static File getSharedSenderKeysFile(File dataPath, String account) {
497 return new File(getUserPath(dataPath, account), "shared-sender-keys-store");
498 }
499
500 private static File getRecipientsStoreFile(File dataPath, String account) {
501 return new File(getUserPath(dataPath, account), "recipients-store");
502 }
503
504 private static File getStorageManifestFile(File dataPath, String account) {
505 return new File(getUserPath(dataPath, account), "storage-manifest");
506 }
507
508 private static File getDatabaseFile(File dataPath, String account) {
509 return new File(getUserPath(dataPath, account), "account.db");
510 }
511
512 public static boolean accountFileExists(File dataPath, String account) {
513 if (account == null) {
514 return false;
515 }
516 var f = getFileName(dataPath, account);
517 return !(!f.exists() || f.isDirectory());
518 }
519
520 private void load(
521 File dataPath, String accountPath, final Settings settings
522 ) throws IOException {
523 this.dataPath = dataPath;
524 this.accountPath = accountPath;
525 this.settings = settings;
526 final JsonNode rootNode;
527 synchronized (fileChannel) {
528 fileChannel.position(0);
529 rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
530 }
531
532 var migratedLegacyConfig = false;
533
534 if (rootNode.hasNonNull("version")) {
535 var accountVersion = rootNode.get("version").asInt(1);
536 if (accountVersion > CURRENT_STORAGE_VERSION) {
537 throw new IOException("Config file was created by a more recent version: " + accountVersion);
538 } else if (accountVersion < MINIMUM_STORAGE_VERSION) {
539 throw new IOException("Config file was created by a no longer supported older version: "
540 + accountVersion);
541 }
542 previousStorageVersion = accountVersion;
543 if (accountVersion < CURRENT_STORAGE_VERSION) {
544 migratedLegacyConfig = true;
545 }
546 }
547
548 number = Utils.getNotNullNode(rootNode, "username").asText();
549 if (rootNode.hasNonNull("password")) {
550 password = rootNode.get("password").asText();
551 }
552 if (rootNode.hasNonNull("serviceEnvironment")) {
553 serviceEnvironment = ServiceEnvironment.valueOf(rootNode.get("serviceEnvironment").asText());
554 }
555 registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
556 if (rootNode.hasNonNull("usernameIdentifier")) {
557 username = rootNode.get("usernameIdentifier").asText();
558 }
559 if (rootNode.hasNonNull("uuid")) {
560 try {
561 aci = ACI.parseOrThrow(rootNode.get("uuid").asText());
562 } catch (IllegalArgumentException e) {
563 throw new IOException("Config file contains an invalid aci/uuid, needs to be a valid UUID", e);
564 }
565 }
566 if (rootNode.hasNonNull("pni")) {
567 try {
568 pni = PNI.parseOrThrow(rootNode.get("pni").asText());
569 } catch (IllegalArgumentException e) {
570 throw new IOException("Config file contains an invalid pni, needs to be a valid UUID", e);
571 }
572 }
573 if (rootNode.hasNonNull("sessionId")) {
574 getKeyValueStore().storeEntry(verificationSessionId, rootNode.get("sessionId").asText());
575 migratedLegacyConfig = true;
576 }
577 if (rootNode.hasNonNull("sessionNumber")) {
578 getKeyValueStore().storeEntry(verificationSessionNumber, rootNode.get("sessionNumber").asText());
579 migratedLegacyConfig = true;
580 }
581 if (rootNode.hasNonNull("deviceName")) {
582 encryptedDeviceName = rootNode.get("deviceName").asText();
583 }
584 if (rootNode.hasNonNull("deviceId")) {
585 deviceId = rootNode.get("deviceId").asInt();
586 }
587 if (rootNode.hasNonNull("isMultiDevice")) {
588 isMultiDevice = rootNode.get("isMultiDevice").asBoolean();
589 }
590 if (rootNode.hasNonNull("lastReceiveTimestamp")) {
591 getKeyValueStore().storeEntry(lastReceiveTimestamp, rootNode.get("lastReceiveTimestamp").asLong());
592 migratedLegacyConfig = true;
593 }
594 int registrationId = 0;
595 if (rootNode.hasNonNull("registrationId")) {
596 registrationId = rootNode.get("registrationId").asInt();
597 }
598 if (rootNode.hasNonNull("pniRegistrationId")) {
599 pniAccountData.setLocalRegistrationId(rootNode.get("pniRegistrationId").asInt());
600 } else {
601 pniAccountData.setLocalRegistrationId(KeyHelper.generateRegistrationId(false));
602 }
603 IdentityKeyPair aciIdentityKeyPair = null;
604 if (rootNode.hasNonNull("identityPrivateKey") && rootNode.hasNonNull("identityKey")) {
605 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("identityKey").asText());
606 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("identityPrivateKey").asText());
607 aciIdentityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
608 }
609 if (rootNode.hasNonNull("pniIdentityPrivateKey") && rootNode.hasNonNull("pniIdentityKey")) {
610 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityKey").asText());
611 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityPrivateKey").asText());
612 pniAccountData.setIdentityKeyPair(KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes));
613 }
614
615 if (rootNode.hasNonNull("registrationLockPin")) {
616 registrationLockPin = rootNode.get("registrationLockPin").asText();
617 }
618 if (rootNode.hasNonNull("pinMasterKey")) {
619 pinMasterKey = new MasterKey(Base64.getDecoder().decode(rootNode.get("pinMasterKey").asText()));
620 }
621 if (rootNode.hasNonNull("storageKey")) {
622 storageKey = new StorageKey(Base64.getDecoder().decode(rootNode.get("storageKey").asText()));
623 }
624 if (rootNode.hasNonNull("storageManifestVersion")) {
625 getKeyValueStore().storeEntry(storageManifestVersion, rootNode.get("storageManifestVersion").asLong());
626 migratedLegacyConfig = true;
627 }
628 if (rootNode.hasNonNull("preKeyIdOffset")) {
629 aciAccountData.preKeyMetadata.preKeyIdOffset = rootNode.get("preKeyIdOffset").asInt(1);
630 } else {
631 aciAccountData.preKeyMetadata.preKeyIdOffset = getRandomPreKeyIdOffset();
632 }
633 if (rootNode.hasNonNull("nextSignedPreKeyId")) {
634 aciAccountData.preKeyMetadata.nextSignedPreKeyId = rootNode.get("nextSignedPreKeyId").asInt(1);
635 } else {
636 aciAccountData.preKeyMetadata.nextSignedPreKeyId = getRandomPreKeyIdOffset();
637 }
638 if (rootNode.hasNonNull("activeSignedPreKeyId")) {
639 aciAccountData.preKeyMetadata.activeSignedPreKeyId = rootNode.get("activeSignedPreKeyId").asInt(-1);
640 } else {
641 aciAccountData.preKeyMetadata.activeSignedPreKeyId = -1;
642 }
643 if (rootNode.hasNonNull("pniPreKeyIdOffset")) {
644 pniAccountData.preKeyMetadata.preKeyIdOffset = rootNode.get("pniPreKeyIdOffset").asInt(1);
645 } else {
646 pniAccountData.preKeyMetadata.preKeyIdOffset = getRandomPreKeyIdOffset();
647 }
648 if (rootNode.hasNonNull("pniNextSignedPreKeyId")) {
649 pniAccountData.preKeyMetadata.nextSignedPreKeyId = rootNode.get("pniNextSignedPreKeyId").asInt(1);
650 } else {
651 pniAccountData.preKeyMetadata.nextSignedPreKeyId = getRandomPreKeyIdOffset();
652 }
653 if (rootNode.hasNonNull("pniActiveSignedPreKeyId")) {
654 pniAccountData.preKeyMetadata.activeSignedPreKeyId = rootNode.get("pniActiveSignedPreKeyId").asInt(-1);
655 } else {
656 pniAccountData.preKeyMetadata.activeSignedPreKeyId = -1;
657 }
658 if (rootNode.hasNonNull("kyberPreKeyIdOffset")) {
659 aciAccountData.preKeyMetadata.kyberPreKeyIdOffset = rootNode.get("kyberPreKeyIdOffset").asInt(1);
660 } else {
661 aciAccountData.preKeyMetadata.kyberPreKeyIdOffset = getRandomPreKeyIdOffset();
662 }
663 if (rootNode.hasNonNull("activeLastResortKyberPreKeyId")) {
664 aciAccountData.preKeyMetadata.activeLastResortKyberPreKeyId = rootNode.get("activeLastResortKyberPreKeyId")
665 .asInt(-1);
666 } else {
667 aciAccountData.preKeyMetadata.activeLastResortKyberPreKeyId = -1;
668 }
669 if (rootNode.hasNonNull("pniKyberPreKeyIdOffset")) {
670 pniAccountData.preKeyMetadata.kyberPreKeyIdOffset = rootNode.get("pniKyberPreKeyIdOffset").asInt(1);
671 } else {
672 pniAccountData.preKeyMetadata.kyberPreKeyIdOffset = getRandomPreKeyIdOffset();
673 }
674 if (rootNode.hasNonNull("pniActiveLastResortKyberPreKeyId")) {
675 pniAccountData.preKeyMetadata.activeLastResortKyberPreKeyId = rootNode.get(
676 "pniActiveLastResortKyberPreKeyId").asInt(-1);
677 } else {
678 pniAccountData.preKeyMetadata.activeLastResortKyberPreKeyId = -1;
679 }
680 if (rootNode.hasNonNull("profileKey")) {
681 try {
682 profileKey = new ProfileKey(Base64.getDecoder().decode(rootNode.get("profileKey").asText()));
683 } catch (InvalidInputException e) {
684 throw new IOException(
685 "Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
686 e);
687 }
688 }
689
690 if (previousStorageVersion < 5) {
691 final var legacyRecipientsStoreFile = getRecipientsStoreFile(dataPath, accountPath);
692 if (legacyRecipientsStoreFile.exists()) {
693 LegacyRecipientStore2.migrate(legacyRecipientsStoreFile, getRecipientStore());
694 // Ensure our profile key is stored in profile store
695 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
696 migratedLegacyConfig = true;
697 }
698 }
699 if (previousStorageVersion < 6) {
700 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
701 }
702 final var legacyAciPreKeysPath = getAciPreKeysPath(dataPath, accountPath);
703 if (legacyAciPreKeysPath.exists()) {
704 LegacyPreKeyStore.migrate(legacyAciPreKeysPath, aciAccountData.getPreKeyStore());
705 migratedLegacyConfig = true;
706 }
707 final var legacyPniPreKeysPath = getPniPreKeysPath(dataPath, accountPath);
708 if (legacyPniPreKeysPath.exists()) {
709 LegacyPreKeyStore.migrate(legacyPniPreKeysPath, pniAccountData.getPreKeyStore());
710 migratedLegacyConfig = true;
711 }
712 final var legacyAciSignedPreKeysPath = getAciSignedPreKeysPath(dataPath, accountPath);
713 if (legacyAciSignedPreKeysPath.exists()) {
714 LegacySignedPreKeyStore.migrate(legacyAciSignedPreKeysPath, aciAccountData.getSignedPreKeyStore());
715 migratedLegacyConfig = true;
716 }
717 final var legacyPniSignedPreKeysPath = getPniSignedPreKeysPath(dataPath, accountPath);
718 if (legacyPniSignedPreKeysPath.exists()) {
719 LegacySignedPreKeyStore.migrate(legacyPniSignedPreKeysPath, pniAccountData.getSignedPreKeyStore());
720 migratedLegacyConfig = true;
721 }
722 final var legacySessionsPath = getSessionsPath(dataPath, accountPath);
723 if (legacySessionsPath.exists()) {
724 LegacySessionStore.migrate(legacySessionsPath,
725 getRecipientResolver(),
726 getRecipientAddressResolver(),
727 aciAccountData.getSessionStore());
728 migratedLegacyConfig = true;
729 }
730 final var legacyIdentitiesPath = getIdentitiesPath(dataPath, accountPath);
731 if (legacyIdentitiesPath.exists()) {
732 LegacyIdentityKeyStore.migrate(legacyIdentitiesPath,
733 getRecipientResolver(),
734 getRecipientAddressResolver(),
735 getIdentityKeyStore());
736 migratedLegacyConfig = true;
737 }
738 final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
739 ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
740 LegacyJsonSignalProtocolStore.class)
741 : null;
742 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
743 aciIdentityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
744 registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
745 migratedLegacyConfig = true;
746 }
747
748 this.aciAccountData.setIdentityKeyPair(aciIdentityKeyPair);
749 this.aciAccountData.setLocalRegistrationId(registrationId);
750
751 migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
752
753 final var legacySenderKeysPath = getSenderKeysPath(dataPath, accountPath);
754 if (legacySenderKeysPath.exists()) {
755 LegacySenderKeyRecordStore.migrate(legacySenderKeysPath,
756 getRecipientResolver(),
757 getRecipientAddressResolver(),
758 getSenderKeyStore());
759 migratedLegacyConfig = true;
760 }
761 final var legacySenderKeysSharedPath = getSharedSenderKeysFile(dataPath, accountPath);
762 if (legacySenderKeysSharedPath.exists()) {
763 LegacySenderKeySharedStore.migrate(legacySenderKeysSharedPath,
764 getRecipientResolver(),
765 getRecipientAddressResolver(),
766 getSenderKeyStore());
767 migratedLegacyConfig = true;
768 }
769 if (rootNode.hasNonNull("groupStore")) {
770 final var groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"),
771 LegacyGroupStore.Storage.class);
772 LegacyGroupStore.migrate(groupStoreStorage,
773 getGroupCachePath(dataPath, accountPath),
774 getRecipientResolver(),
775 getGroupStore());
776 migratedLegacyConfig = true;
777 }
778
779 if (rootNode.hasNonNull("stickerStore")) {
780 final var storage = jsonProcessor.convertValue(rootNode.get("stickerStore"),
781 LegacyStickerStore.Storage.class);
782 LegacyStickerStore.migrate(storage, getStickerStore());
783 migratedLegacyConfig = true;
784 }
785
786 if (rootNode.hasNonNull("configurationStore")) {
787 final var configurationStoreStorage = jsonProcessor.convertValue(rootNode.get("configurationStore"),
788 LegacyConfigurationStore.Storage.class);
789 LegacyConfigurationStore.migrate(configurationStoreStorage, getConfigurationStore());
790 migratedLegacyConfig = true;
791 }
792
793 migratedLegacyConfig = loadLegacyThreadStore(rootNode) || migratedLegacyConfig;
794
795 if (migratedLegacyConfig) {
796 save();
797 }
798 }
799
800 private boolean loadLegacyStores(
801 final JsonNode rootNode, final LegacyJsonSignalProtocolStore legacySignalProtocolStore
802 ) {
803 var migrated = false;
804 var legacyRecipientStoreNode = rootNode.get("recipientStore");
805 if (legacyRecipientStoreNode != null) {
806 logger.debug("Migrating legacy recipient store.");
807 var legacyRecipientStore = jsonProcessor.convertValue(legacyRecipientStoreNode, LegacyRecipientStore.class);
808 if (legacyRecipientStore != null) {
809 legacyRecipientStore.getAddresses()
810 .forEach(recipient -> getRecipientStore().resolveRecipientTrusted(recipient));
811 }
812 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
813 migrated = true;
814 }
815
816 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
817 logger.debug("Migrating legacy pre key store.");
818 for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
819 try {
820 aciAccountData.getPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
821 } catch (InvalidMessageException e) {
822 logger.warn("Failed to migrate pre key, ignoring", e);
823 }
824 }
825 migrated = true;
826 }
827
828 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
829 logger.debug("Migrating legacy signed pre key store.");
830 for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
831 try {
832 aciAccountData.getSignedPreKeyStore()
833 .storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue()));
834 } catch (InvalidMessageException e) {
835 logger.warn("Failed to migrate signed pre key, ignoring", e);
836 }
837 }
838 migrated = true;
839 }
840
841 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
842 logger.debug("Migrating legacy session store.");
843 for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
844 try {
845 aciAccountData.getSessionStore()
846 .storeSession(new SignalProtocolAddress(session.address.getIdentifier(), session.deviceId),
847 new SessionRecord(session.sessionRecord));
848 } catch (Exception e) {
849 logger.warn("Failed to migrate session, ignoring", e);
850 }
851 }
852 migrated = true;
853 }
854
855 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
856 logger.debug("Migrating legacy identity session store.");
857 for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
858 if (identity.getAddress().serviceId().isEmpty()) {
859 continue;
860 }
861 final var serviceId = identity.getAddress().serviceId().get();
862 getIdentityKeyStore().saveIdentity(serviceId, identity.getIdentityKey());
863 getIdentityKeyStore().setIdentityTrustLevel(serviceId,
864 identity.getIdentityKey(),
865 identity.getTrustLevel());
866 }
867 migrated = true;
868 }
869
870 if (rootNode.hasNonNull("contactStore")) {
871 logger.debug("Migrating legacy contact store.");
872 final var contactStoreNode = rootNode.get("contactStore");
873 final var contactStore = jsonProcessor.convertValue(contactStoreNode, LegacyJsonContactsStore.class);
874 for (var contact : contactStore.getContacts()) {
875 final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
876 getContactStore().storeContact(recipientId,
877 new Contact(contact.name,
878 null,
879 contact.color,
880 contact.messageExpirationTime,
881 contact.blocked,
882 contact.archived,
883 false));
884
885 // Store profile keys only in profile store
886 var profileKeyString = contact.profileKey;
887 if (profileKeyString != null) {
888 final ProfileKey profileKey;
889 try {
890 profileKey = new ProfileKey(Base64.getDecoder().decode(profileKeyString));
891 getProfileStore().storeProfileKey(recipientId, profileKey);
892 } catch (InvalidInputException e) {
893 logger.warn("Failed to parse legacy contact profile key: {}", e.getMessage());
894 }
895 }
896 }
897 migrated = true;
898 }
899
900 if (rootNode.hasNonNull("profileStore")) {
901 logger.debug("Migrating legacy profile store.");
902 var profileStoreNode = rootNode.get("profileStore");
903 final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
904 for (var profileEntry : legacyProfileStore.getProfileEntries()) {
905 var recipientId = getRecipientResolver().resolveRecipient(profileEntry.address());
906 // Not migrating profile key credential here, it was changed to expiring profile key credentials
907 getProfileStore().storeProfileKey(recipientId, profileEntry.profileKey());
908 final var profile = profileEntry.profile();
909 if (profile != null) {
910 final var capabilities = new HashSet<Profile.Capability>();
911 if (profile.getCapabilities() != null) {
912 if (profile.getCapabilities().gv1Migration) {
913 capabilities.add(Profile.Capability.gv1Migration);
914 }
915 if (profile.getCapabilities().storage) {
916 capabilities.add(Profile.Capability.storage);
917 }
918 }
919 final var newProfile = new Profile(profileEntry.lastUpdateTimestamp(),
920 profile.getGivenName(),
921 profile.getFamilyName(),
922 profile.getAbout(),
923 profile.getAboutEmoji(),
924 null,
925 null,
926 profile.isUnrestrictedUnidentifiedAccess()
927 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
928 : profile.getUnidentifiedAccess() != null
929 ? Profile.UnidentifiedAccessMode.ENABLED
930 : Profile.UnidentifiedAccessMode.DISABLED,
931 capabilities);
932 getProfileStore().storeProfile(recipientId, newProfile);
933 }
934 }
935 }
936
937 return migrated;
938 }
939
940 private boolean loadLegacyThreadStore(final JsonNode rootNode) {
941 var threadStoreNode = rootNode.get("threadStore");
942 if (threadStoreNode != null && !threadStoreNode.isNull()) {
943 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
944 // Migrate thread info to group and contact store
945 for (var thread : threadStore.getThreads()) {
946 if (thread.id == null || thread.id.isEmpty()) {
947 continue;
948 }
949 try {
950 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
951 final var recipientId = getRecipientResolver().resolveRecipient(thread.id);
952 var contact = getContactStore().getContact(recipientId);
953 if (contact != null) {
954 getContactStore().storeContact(recipientId,
955 Contact.newBuilder(contact)
956 .withMessageExpirationTime(thread.messageExpirationTime)
957 .build());
958 }
959 } else {
960 var groupInfo = getGroupStore().getGroup(GroupId.fromBase64(thread.id));
961 if (groupInfo instanceof GroupInfoV1) {
962 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
963 getGroupStore().updateGroup(groupInfo);
964 }
965 }
966 } catch (Exception e) {
967 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
968 }
969 }
970 return true;
971 }
972
973 return false;
974 }
975
976 private void save() {
977 synchronized (fileChannel) {
978 var rootNode = jsonProcessor.createObjectNode();
979 rootNode.put("version", CURRENT_STORAGE_VERSION)
980 .put("username", number)
981 .put("serviceEnvironment", serviceEnvironment == null ? null : serviceEnvironment.name())
982 .put("usernameIdentifier", username)
983 .put("uuid", aci == null ? null : aci.toString())
984 .put("pni", pni == null ? null : pni.toStringWithoutPrefix())
985 .put("deviceName", encryptedDeviceName)
986 .put("deviceId", deviceId)
987 .put("isMultiDevice", isMultiDevice)
988 .put("password", password)
989 .put("registrationId", aciAccountData.getLocalRegistrationId())
990 .put("pniRegistrationId", pniAccountData.getLocalRegistrationId())
991 .put("identityPrivateKey",
992 Base64.getEncoder()
993 .encodeToString(aciAccountData.getIdentityKeyPair().getPrivateKey().serialize()))
994 .put("identityKey",
995 Base64.getEncoder()
996 .encodeToString(aciAccountData.getIdentityKeyPair().getPublicKey().serialize()))
997 .put("pniIdentityPrivateKey",
998 pniAccountData.getIdentityKeyPair() == null
999 ? null
1000 : Base64.getEncoder()
1001 .encodeToString(pniAccountData.getIdentityKeyPair()
1002 .getPrivateKey()
1003 .serialize()))
1004 .put("pniIdentityKey",
1005 pniAccountData.getIdentityKeyPair() == null
1006 ? null
1007 : Base64.getEncoder()
1008 .encodeToString(pniAccountData.getIdentityKeyPair()
1009 .getPublicKey()
1010 .serialize()))
1011 .put("registrationLockPin", registrationLockPin)
1012 .put("pinMasterKey",
1013 pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
1014 .put("storageKey",
1015 storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
1016 .put("preKeyIdOffset", aciAccountData.getPreKeyMetadata().preKeyIdOffset)
1017 .put("nextSignedPreKeyId", aciAccountData.getPreKeyMetadata().nextSignedPreKeyId)
1018 .put("activeSignedPreKeyId", aciAccountData.getPreKeyMetadata().activeSignedPreKeyId)
1019 .put("pniPreKeyIdOffset", pniAccountData.getPreKeyMetadata().preKeyIdOffset)
1020 .put("pniNextSignedPreKeyId", pniAccountData.getPreKeyMetadata().nextSignedPreKeyId)
1021 .put("pniActiveSignedPreKeyId", pniAccountData.getPreKeyMetadata().activeSignedPreKeyId)
1022 .put("kyberPreKeyIdOffset", aciAccountData.getPreKeyMetadata().kyberPreKeyIdOffset)
1023 .put("activeLastResortKyberPreKeyId",
1024 aciAccountData.getPreKeyMetadata().activeLastResortKyberPreKeyId)
1025 .put("pniKyberPreKeyIdOffset", pniAccountData.getPreKeyMetadata().kyberPreKeyIdOffset)
1026 .put("pniActiveLastResortKyberPreKeyId",
1027 pniAccountData.getPreKeyMetadata().activeLastResortKyberPreKeyId)
1028 .put("profileKey",
1029 profileKey == null ? null : Base64.getEncoder().encodeToString(profileKey.serialize()))
1030 .put("registered", registered);
1031 try {
1032 try (var output = new ByteArrayOutputStream()) {
1033 // Write to memory first to prevent corrupting the file in case of serialization errors
1034 jsonProcessor.writeValue(output, rootNode);
1035 var input = new ByteArrayInputStream(output.toByteArray());
1036 fileChannel.position(0);
1037 input.transferTo(Channels.newOutputStream(fileChannel));
1038 fileChannel.truncate(fileChannel.position());
1039 fileChannel.force(false);
1040 }
1041 } catch (Exception e) {
1042 logger.error("Error saving file: {}", e.getMessage(), e);
1043 }
1044 }
1045 }
1046
1047 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
1048 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
1049 try {
1050 var lock = fileChannel.tryLock();
1051 if (lock == null) {
1052 if (!waitForLock) {
1053 logger.debug("Config file is in use by another instance.");
1054 throw new IOException("Config file is in use by another instance.");
1055 }
1056 logger.info("Config file is in use by another instance, waiting…");
1057 lock = fileChannel.lock();
1058 logger.info("Config file lock acquired.");
1059 }
1060 final var result = new Pair<>(fileChannel, lock);
1061 fileChannel = null;
1062 return result;
1063 } finally {
1064 if (fileChannel != null) {
1065 fileChannel.close();
1066 }
1067 }
1068 }
1069
1070 private void clearAllPreKeys() {
1071 clearAllPreKeys(ServiceIdType.ACI);
1072 clearAllPreKeys(ServiceIdType.PNI);
1073 }
1074
1075 private void clearAllPreKeys(ServiceIdType serviceIdType) {
1076 final var accountData = getAccountData(serviceIdType);
1077 resetPreKeyOffsets(serviceIdType);
1078 resetKyberPreKeyOffsets(serviceIdType);
1079 accountData.getPreKeyStore().removeAllPreKeys();
1080 accountData.getSignedPreKeyStore().removeAllSignedPreKeys();
1081 accountData.getKyberPreKeyStore().removeAllKyberPreKeys();
1082 save();
1083 }
1084
1085 private void setPreKeys(ServiceIdType serviceIdType, PreKeyCollection preKeyCollection) {
1086 final var accountData = getAccountData(serviceIdType);
1087 final var preKeyMetadata = accountData.getPreKeyMetadata();
1088 preKeyMetadata.nextSignedPreKeyId = preKeyCollection.getSignedPreKey().getId();
1089 preKeyMetadata.kyberPreKeyIdOffset = preKeyCollection.getLastResortKyberPreKey().getId();
1090
1091 accountData.getPreKeyStore().removeAllPreKeys();
1092 accountData.getSignedPreKeyStore().removeAllSignedPreKeys();
1093 accountData.getKyberPreKeyStore().removeAllKyberPreKeys();
1094
1095 addSignedPreKey(serviceIdType, preKeyCollection.getSignedPreKey());
1096 addLastResortKyberPreKey(serviceIdType, preKeyCollection.getLastResortKyberPreKey());
1097
1098 save();
1099 }
1100
1101 public void resetPreKeyOffsets(final ServiceIdType serviceIdType) {
1102 final var preKeyMetadata = getAccountData(serviceIdType).getPreKeyMetadata();
1103 preKeyMetadata.preKeyIdOffset = getRandomPreKeyIdOffset();
1104 preKeyMetadata.nextSignedPreKeyId = getRandomPreKeyIdOffset();
1105 preKeyMetadata.activeSignedPreKeyId = -1;
1106 save();
1107 }
1108
1109 private static int getRandomPreKeyIdOffset() {
1110 return KeyUtils.getRandomInt(PREKEY_MAXIMUM_ID);
1111 }
1112
1113 public void addPreKeys(ServiceIdType serviceIdType, List<PreKeyRecord> records) {
1114 final var accountData = getAccountData(serviceIdType);
1115 final var preKeyMetadata = accountData.getPreKeyMetadata();
1116 logger.debug("Adding {} {} pre keys with offset {}",
1117 records.size(),
1118 serviceIdType,
1119 preKeyMetadata.preKeyIdOffset);
1120 accountData.signalProtocolStore.markAllOneTimeEcPreKeysStaleIfNecessary(System.currentTimeMillis());
1121 for (var record : records) {
1122 if (preKeyMetadata.preKeyIdOffset != record.getId()) {
1123 logger.error("Invalid pre key id {}, expected {}", record.getId(), preKeyMetadata.preKeyIdOffset);
1124 throw new AssertionError("Invalid pre key id");
1125 }
1126 accountData.getPreKeyStore().storePreKey(record.getId(), record);
1127 preKeyMetadata.preKeyIdOffset = (preKeyMetadata.preKeyIdOffset + 1) % PREKEY_MAXIMUM_ID;
1128 }
1129 save();
1130 }
1131
1132 public void addSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord record) {
1133 final var accountData = getAccountData(serviceIdType);
1134 final var preKeyMetadata = accountData.getPreKeyMetadata();
1135 logger.debug("Adding {} signed pre key with offset {}", serviceIdType, preKeyMetadata.nextSignedPreKeyId);
1136 if (preKeyMetadata.nextSignedPreKeyId != record.getId()) {
1137 logger.error("Invalid signed pre key id {}, expected {}",
1138 record.getId(),
1139 preKeyMetadata.nextSignedPreKeyId);
1140 throw new AssertionError("Invalid signed pre key id");
1141 }
1142 accountData.getSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
1143 preKeyMetadata.nextSignedPreKeyId = (preKeyMetadata.nextSignedPreKeyId + 1) % PREKEY_MAXIMUM_ID;
1144 preKeyMetadata.activeSignedPreKeyId = record.getId();
1145 save();
1146 }
1147
1148 public void resetKyberPreKeyOffsets(final ServiceIdType serviceIdType) {
1149 final var preKeyMetadata = getAccountData(serviceIdType).getPreKeyMetadata();
1150 preKeyMetadata.kyberPreKeyIdOffset = getRandomPreKeyIdOffset();
1151 preKeyMetadata.activeLastResortKyberPreKeyId = -1;
1152 save();
1153 }
1154
1155 public void addKyberPreKeys(ServiceIdType serviceIdType, List<KyberPreKeyRecord> records) {
1156 final var accountData = getAccountData(serviceIdType);
1157 final var preKeyMetadata = accountData.getPreKeyMetadata();
1158 logger.debug("Adding {} {} kyber pre keys with offset {}",
1159 records.size(),
1160 serviceIdType,
1161 preKeyMetadata.kyberPreKeyIdOffset);
1162 accountData.signalProtocolStore.markAllOneTimeEcPreKeysStaleIfNecessary(System.currentTimeMillis());
1163 for (var record : records) {
1164 if (preKeyMetadata.kyberPreKeyIdOffset != record.getId()) {
1165 logger.error("Invalid kyber pre key id {}, expected {}",
1166 record.getId(),
1167 preKeyMetadata.kyberPreKeyIdOffset);
1168 throw new AssertionError("Invalid kyber pre key id");
1169 }
1170 accountData.getKyberPreKeyStore().storeKyberPreKey(record.getId(), record);
1171 preKeyMetadata.kyberPreKeyIdOffset = (preKeyMetadata.kyberPreKeyIdOffset + 1) % PREKEY_MAXIMUM_ID;
1172 }
1173 save();
1174 }
1175
1176 public void addLastResortKyberPreKey(ServiceIdType serviceIdType, KyberPreKeyRecord record) {
1177 final var accountData = getAccountData(serviceIdType);
1178 final var preKeyMetadata = accountData.getPreKeyMetadata();
1179 logger.debug("Adding {} last resort kyber pre key with offset {}",
1180 serviceIdType,
1181 preKeyMetadata.kyberPreKeyIdOffset);
1182 if (preKeyMetadata.kyberPreKeyIdOffset != record.getId()) {
1183 logger.error("Invalid last resort kyber pre key id {}, expected {}",
1184 record.getId(),
1185 preKeyMetadata.kyberPreKeyIdOffset);
1186 throw new AssertionError("Invalid last resort kyber pre key id");
1187 }
1188 accountData.getKyberPreKeyStore().storeLastResortKyberPreKey(record.getId(), record);
1189 preKeyMetadata.activeLastResortKyberPreKeyId = record.getId();
1190 preKeyMetadata.kyberPreKeyIdOffset = (preKeyMetadata.kyberPreKeyIdOffset + 1) % PREKEY_MAXIMUM_ID;
1191 save();
1192 }
1193
1194 public int getPreviousStorageVersion() {
1195 return previousStorageVersion;
1196 }
1197
1198 public AccountData getAccountData(ServiceIdType serviceIdType) {
1199 return switch (serviceIdType) {
1200 case ACI -> aciAccountData;
1201 case PNI -> pniAccountData;
1202 };
1203 }
1204
1205 public AccountData getAccountData(ServiceId accountIdentifier) {
1206 if (accountIdentifier.equals(aci)) {
1207 return aciAccountData;
1208 } else if (accountIdentifier.equals(pni)) {
1209 return pniAccountData;
1210 } else {
1211 throw new IllegalArgumentException("No matching account data found for " + accountIdentifier);
1212 }
1213 }
1214
1215 public SignalServiceDataStore getSignalServiceDataStore() {
1216 return new SignalServiceDataStore() {
1217 @Override
1218 public SignalServiceAccountDataStore get(final ServiceId accountIdentifier) {
1219 if (accountIdentifier.equals(aci)) {
1220 return aci();
1221 } else if (accountIdentifier.equals(pni)) {
1222 return pni();
1223 } else {
1224 throw new IllegalArgumentException("No matching store found for " + accountIdentifier);
1225 }
1226 }
1227
1228 @Override
1229 public SignalServiceAccountDataStore aci() {
1230 return aciAccountData.getSignalServiceAccountDataStore();
1231 }
1232
1233 @Override
1234 public SignalServiceAccountDataStore pni() {
1235 return pniAccountData.getSignalServiceAccountDataStore();
1236 }
1237
1238 @Override
1239 public boolean isMultiDevice() {
1240 return SignalAccount.this.isMultiDevice();
1241 }
1242 };
1243 }
1244
1245 public IdentityKeyStore getIdentityKeyStore() {
1246 return getOrCreate(() -> identityKeyStore,
1247 () -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(), settings.trustNewIdentity()));
1248 }
1249
1250 public GroupStore getGroupStore() {
1251 return getOrCreate(() -> groupStore,
1252 () -> groupStore = new GroupStore(getAccountDatabase(),
1253 getRecipientResolver(),
1254 getRecipientIdCreator()));
1255 }
1256
1257 public ContactsStore getContactStore() {
1258 return getRecipientStore();
1259 }
1260
1261 private RecipientIdCreator getRecipientIdCreator() {
1262 return recipientId -> getRecipientStore().create(recipientId);
1263 }
1264
1265 public RecipientResolver getRecipientResolver() {
1266 return new RecipientResolver.RecipientResolverWrapper(this::getRecipientStore);
1267 }
1268
1269 public RecipientTrustedResolver getRecipientTrustedResolver() {
1270 return new RecipientTrustedResolver.RecipientTrustedResolverWrapper(this::getRecipientStore);
1271 }
1272
1273 public RecipientAddressResolver getRecipientAddressResolver() {
1274 return recipientId -> getRecipientStore().resolveRecipientAddress(recipientId);
1275 }
1276
1277 public RecipientStore getRecipientStore() {
1278 return getOrCreate(() -> recipientStore,
1279 () -> recipientStore = new RecipientStore(this::mergeRecipients,
1280 this::getSelfRecipientAddress,
1281 getAccountDatabase()));
1282 }
1283
1284 public ProfileStore getProfileStore() {
1285 return getRecipientStore();
1286 }
1287
1288 public StickerStore getStickerStore() {
1289 return getOrCreate(() -> stickerStore, () -> stickerStore = new StickerStore(getAccountDatabase()));
1290 }
1291
1292 public SenderKeyStore getSenderKeyStore() {
1293 return getOrCreate(() -> senderKeyStore, () -> senderKeyStore = new SenderKeyStore(getAccountDatabase()));
1294 }
1295
1296 private KeyValueStore getKeyValueStore() {
1297 return getOrCreate(() -> keyValueStore, () -> keyValueStore = new KeyValueStore(getAccountDatabase()));
1298 }
1299
1300 public ConfigurationStore getConfigurationStore() {
1301 return getOrCreate(() -> configurationStore,
1302 () -> configurationStore = new ConfigurationStore(getKeyValueStore()));
1303 }
1304
1305 public MessageCache getMessageCache() {
1306 return getOrCreate(() -> messageCache,
1307 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
1308 }
1309
1310 public AccountDatabase getAccountDatabase() {
1311 return getOrCreate(() -> accountDatabase, () -> {
1312 try {
1313 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
1314 } catch (SQLException e) {
1315 throw new RuntimeException(e);
1316 }
1317 });
1318 }
1319
1320 public MessageSendLogStore getMessageSendLogStore() {
1321 return getOrCreate(() -> messageSendLogStore,
1322 () -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase(),
1323 settings.disableMessageSendLog()));
1324 }
1325
1326 public CredentialsProvider getCredentialsProvider() {
1327 return new CredentialsProvider() {
1328 @Override
1329 public ACI getAci() {
1330 return aci;
1331 }
1332
1333 @Override
1334 public PNI getPni() {
1335 return pni;
1336 }
1337
1338 @Override
1339 public String getE164() {
1340 return number;
1341 }
1342
1343 @Override
1344 public String getPassword() {
1345 return password;
1346 }
1347
1348 @Override
1349 public int getDeviceId() {
1350 return deviceId;
1351 }
1352 };
1353 }
1354
1355 public String getNumber() {
1356 return number;
1357 }
1358
1359 public void setNumber(final String number) {
1360 this.number = number;
1361 save();
1362 }
1363
1364 public String getUsername() {
1365 return username;
1366 }
1367
1368 public void setUsername(final String username) {
1369 this.username = username;
1370 save();
1371 }
1372
1373 public ServiceEnvironment getServiceEnvironment() {
1374 return serviceEnvironment;
1375 }
1376
1377 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1378 this.serviceEnvironment = serviceEnvironment;
1379 save();
1380 }
1381
1382 public AccountAttributes getAccountAttributes(String registrationLock) {
1383 return new AccountAttributes(null,
1384 aciAccountData.getLocalRegistrationId(),
1385 false,
1386 false,
1387 true,
1388 registrationLock != null ? registrationLock : getRegistrationLock(),
1389 getSelfUnidentifiedAccessKey(),
1390 isUnrestrictedUnidentifiedAccess(),
1391 isDiscoverableByPhoneNumber(),
1392 getAccountCapabilities(),
1393 encryptedDeviceName,
1394 pniAccountData.getLocalRegistrationId(),
1395 null); // TODO recoveryPassword?
1396 }
1397
1398 public AccountAttributes.Capabilities getAccountCapabilities() {
1399 return getCapabilities(isPrimaryDevice());
1400 }
1401
1402 public ServiceId getAccountId(ServiceIdType serviceIdType) {
1403 return serviceIdType.equals(ServiceIdType.ACI) ? aci : pni;
1404 }
1405
1406 public ACI getAci() {
1407 return aci;
1408 }
1409
1410 public void setAci(final ACI aci) {
1411 this.aci = aci;
1412 save();
1413 }
1414
1415 public PNI getPni() {
1416 return pni;
1417 }
1418
1419 public void setPni(final PNI updatedPni) {
1420 if (this.pni != null && !this.pni.equals(updatedPni)) {
1421 // Clear data for old PNI
1422 identityKeyStore.deleteIdentity(this.pni);
1423 clearAllPreKeys(ServiceIdType.PNI);
1424 }
1425
1426 this.pni = updatedPni;
1427 trustSelfIdentity(ServiceIdType.PNI);
1428 save();
1429 }
1430
1431 public void setNewPniIdentity(
1432 final IdentityKeyPair pniIdentityKeyPair,
1433 final SignedPreKeyRecord pniSignedPreKey,
1434 final KyberPreKeyRecord lastResortKyberPreKey,
1435 final int localPniRegistrationId
1436 ) {
1437 setPniIdentityKeyPair(pniIdentityKeyPair);
1438 pniAccountData.setLocalRegistrationId(localPniRegistrationId);
1439
1440 final var preKeyMetadata = getAccountData(ServiceIdType.PNI).getPreKeyMetadata();
1441 preKeyMetadata.nextSignedPreKeyId = pniSignedPreKey.getId();
1442 addSignedPreKey(ServiceIdType.PNI, pniSignedPreKey);
1443 if (lastResortKyberPreKey != null) {
1444 preKeyMetadata.kyberPreKeyIdOffset = lastResortKyberPreKey.getId();
1445 addLastResortKyberPreKey(ServiceIdType.PNI, lastResortKyberPreKey);
1446 }
1447 save();
1448 }
1449
1450 public SignalServiceAddress getSelfAddress() {
1451 return new SignalServiceAddress(aci, number);
1452 }
1453
1454 public RecipientAddress getSelfRecipientAddress() {
1455 return new RecipientAddress(aci, pni, number, username);
1456 }
1457
1458 public RecipientId getSelfRecipientId() {
1459 return getRecipientResolver().resolveRecipient(getSelfRecipientAddress());
1460 }
1461
1462 public String getSessionId(final String forNumber) {
1463 final var keyValueStore = getKeyValueStore();
1464 final var sessionNumber = keyValueStore.getEntry(verificationSessionNumber);
1465 if (!forNumber.equals(sessionNumber)) {
1466 return null;
1467 }
1468 return keyValueStore.getEntry(verificationSessionId);
1469 }
1470
1471 public void setSessionId(final String sessionNumber, final String sessionId) {
1472 final var keyValueStore = getKeyValueStore();
1473 keyValueStore.storeEntry(verificationSessionNumber, sessionNumber);
1474 keyValueStore.storeEntry(verificationSessionId, sessionId);
1475 }
1476
1477 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1478 this.encryptedDeviceName = encryptedDeviceName;
1479 save();
1480 }
1481
1482 public int getDeviceId() {
1483 return deviceId;
1484 }
1485
1486 public boolean isPrimaryDevice() {
1487 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1488 }
1489
1490 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1491 return getAccountData(serviceIdType).getIdentityKeyPair();
1492 }
1493
1494 public IdentityKeyPair getAciIdentityKeyPair() {
1495 return aciAccountData.getIdentityKeyPair();
1496 }
1497
1498 public IdentityKeyPair getPniIdentityKeyPair() {
1499 return pniAccountData.getIdentityKeyPair();
1500 }
1501
1502 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1503 pniAccountData.setIdentityKeyPair(identityKeyPair);
1504 trustSelfIdentity(ServiceIdType.PNI);
1505 save();
1506 }
1507
1508 public String getPassword() {
1509 return password;
1510 }
1511
1512 private void setPassword(final String password) {
1513 this.password = password;
1514 save();
1515 }
1516
1517 public void setRegistrationLockPin(final String registrationLockPin) {
1518 this.registrationLockPin = registrationLockPin;
1519 save();
1520 }
1521
1522 public String getRegistrationLockPin() {
1523 return registrationLockPin;
1524 }
1525
1526 public String getRegistrationLock() {
1527 final var masterKey = getPinBackedMasterKey();
1528 if (masterKey == null) {
1529 return null;
1530 }
1531 return masterKey.deriveRegistrationLock();
1532 }
1533
1534 public MasterKey getPinBackedMasterKey() {
1535 if (registrationLockPin == null) {
1536 return null;
1537 }
1538 return pinMasterKey;
1539 }
1540
1541 public MasterKey getOrCreatePinMasterKey() {
1542 if (pinMasterKey == null) {
1543 pinMasterKey = KeyUtils.createMasterKey();
1544 save();
1545 }
1546 return pinMasterKey;
1547 }
1548
1549 public StorageKey getStorageKey() {
1550 if (pinMasterKey != null) {
1551 return pinMasterKey.deriveStorageServiceKey();
1552 }
1553 return storageKey;
1554 }
1555
1556 public StorageKey getOrCreateStorageKey() {
1557 if (isPrimaryDevice()) {
1558 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1559 }
1560 return storageKey;
1561 }
1562
1563 public void setStorageKey(final StorageKey storageKey) {
1564 if (storageKey.equals(this.storageKey)) {
1565 return;
1566 }
1567 this.storageKey = storageKey;
1568 save();
1569 }
1570
1571 public long getStorageManifestVersion() {
1572 return getKeyValueStore().getEntry(storageManifestVersion);
1573 }
1574
1575 public void setStorageManifestVersion(final long value) {
1576 getKeyValueStore().storeEntry(storageManifestVersion, value);
1577 }
1578
1579 public Optional<SignalStorageManifest> getStorageManifest() {
1580 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1581 if (!storageManifestFile.exists()) {
1582 return Optional.empty();
1583 }
1584 try (var inputStream = new FileInputStream(storageManifestFile)) {
1585 return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
1586 } catch (IOException e) {
1587 logger.warn("Failed to read local storage manifest.", e);
1588 return Optional.empty();
1589 }
1590 }
1591
1592 public void setStorageManifest(SignalStorageManifest manifest) {
1593 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1594 if (manifest == null) {
1595 if (storageManifestFile.exists()) {
1596 try {
1597 Files.delete(storageManifestFile.toPath());
1598 } catch (IOException e) {
1599 logger.error("Failed to delete local storage manifest.", e);
1600 }
1601 }
1602 return;
1603 }
1604
1605 final var manifestBytes = manifest.serialize();
1606 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1607 outputStream.write(manifestBytes);
1608 } catch (IOException e) {
1609 logger.error("Failed to store local storage manifest.", e);
1610 }
1611 }
1612
1613 public ProfileKey getProfileKey() {
1614 return profileKey;
1615 }
1616
1617 public void setProfileKey(final ProfileKey profileKey) {
1618 if (profileKey.equals(this.profileKey)) {
1619 return;
1620 }
1621 this.profileKey = profileKey;
1622 save();
1623 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
1624 }
1625
1626 public byte[] getSelfUnidentifiedAccessKey() {
1627 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1628 }
1629
1630 public boolean isRegistered() {
1631 return registered;
1632 }
1633
1634 public void setRegistered(final boolean registered) {
1635 this.registered = registered;
1636 save();
1637 }
1638
1639 public boolean isMultiDevice() {
1640 return isMultiDevice;
1641 }
1642
1643 public void setMultiDevice(final boolean multiDevice) {
1644 if (isMultiDevice == multiDevice) {
1645 return;
1646 }
1647 isMultiDevice = multiDevice;
1648 save();
1649 }
1650
1651 public long getLastReceiveTimestamp() {
1652 return getKeyValueStore().getEntry(lastReceiveTimestamp);
1653 }
1654
1655 public void setLastReceiveTimestamp(final long value) {
1656 getKeyValueStore().storeEntry(lastReceiveTimestamp, value);
1657 }
1658
1659 public boolean isUnrestrictedUnidentifiedAccess() {
1660 final var profile = getProfileStore().getProfile(getSelfRecipientId());
1661 return profile != null && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED;
1662 }
1663
1664 public boolean isDiscoverableByPhoneNumber() {
1665 final var phoneNumberUnlisted = getConfigurationStore().getPhoneNumberUnlisted();
1666 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1667 }
1668
1669 private void trustSelfIdentity(ServiceIdType serviceIdType) {
1670 final var accountData = getAccountData(serviceIdType);
1671 final var serviceId = accountData.getServiceId();
1672 final var identityKeyPair = accountData.getIdentityKeyPair();
1673 if (serviceId == null || identityKeyPair == null) {
1674 return;
1675 }
1676 final var publicKey = identityKeyPair.getPublicKey();
1677 getIdentityKeyStore().saveIdentity(serviceId, publicKey);
1678 getIdentityKeyStore().setIdentityTrustLevel(serviceId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1679 }
1680
1681 public void deleteAccountData() throws IOException {
1682 close();
1683 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1684 .sorted(Comparator.reverseOrder())) {
1685 for (final var file = files.iterator(); file.hasNext(); ) {
1686 Files.delete(file.next());
1687 }
1688 }
1689 Files.delete(getFileName(dataPath, accountPath).toPath());
1690 }
1691
1692 @Override
1693 public void close() {
1694 synchronized (fileChannel) {
1695 if (accountDatabase != null) {
1696 try {
1697 accountDatabase.close();
1698 } catch (SQLException e) {
1699 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1700 }
1701 }
1702 if (messageSendLogStore != null) {
1703 messageSendLogStore.close();
1704 }
1705 try {
1706 try {
1707 lock.close();
1708 } catch (ClosedChannelException ignored) {
1709 }
1710 fileChannel.close();
1711 } catch (IOException e) {
1712 logger.warn("Failed to close account: {}", e.getMessage(), e);
1713 }
1714 }
1715 }
1716
1717 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1718 var value = supplier.get();
1719 if (value != null) {
1720 return value;
1721 }
1722
1723 synchronized (LOCK) {
1724 value = supplier.get();
1725 if (value != null) {
1726 return value;
1727 }
1728 creator.call();
1729 return supplier.get();
1730 }
1731 }
1732
1733 private interface Callable {
1734
1735 void call();
1736 }
1737
1738 public static class PreKeyMetadata {
1739
1740 private int preKeyIdOffset = 1;
1741 private int nextSignedPreKeyId = 1;
1742 private int activeSignedPreKeyId = -1;
1743 private int kyberPreKeyIdOffset = 1;
1744 private int activeLastResortKyberPreKeyId = -1;
1745
1746 public int getPreKeyIdOffset() {
1747 return preKeyIdOffset;
1748 }
1749
1750 public int getNextSignedPreKeyId() {
1751 return nextSignedPreKeyId;
1752 }
1753
1754 public int getActiveSignedPreKeyId() {
1755 return activeSignedPreKeyId;
1756 }
1757
1758 public int getKyberPreKeyIdOffset() {
1759 return kyberPreKeyIdOffset;
1760 }
1761
1762 public int getActiveLastResortKyberPreKeyId() {
1763 return activeLastResortKyberPreKeyId;
1764 }
1765 }
1766
1767 public class AccountData {
1768
1769 private final ServiceIdType serviceIdType;
1770 private IdentityKeyPair identityKeyPair;
1771 private int localRegistrationId;
1772 private final PreKeyMetadata preKeyMetadata = new PreKeyMetadata();
1773
1774 private SignalProtocolStore signalProtocolStore;
1775 private PreKeyStore preKeyStore;
1776 private SignedPreKeyStore signedPreKeyStore;
1777 private KyberPreKeyStore kyberPreKeyStore;
1778 private SessionStore sessionStore;
1779 private SignalIdentityKeyStore identityKeyStore;
1780
1781 public AccountData(final ServiceIdType serviceIdType) {
1782 this.serviceIdType = serviceIdType;
1783 }
1784
1785 public ServiceId getServiceId() {
1786 return getAccountId(serviceIdType);
1787 }
1788
1789 public IdentityKeyPair getIdentityKeyPair() {
1790 return identityKeyPair;
1791 }
1792
1793 private void setIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1794 this.identityKeyPair = identityKeyPair;
1795 }
1796
1797 public int getLocalRegistrationId() {
1798 return localRegistrationId;
1799 }
1800
1801 private void setLocalRegistrationId(final int localRegistrationId) {
1802 this.localRegistrationId = localRegistrationId;
1803 this.identityKeyStore = null;
1804 }
1805
1806 public PreKeyMetadata getPreKeyMetadata() {
1807 return preKeyMetadata;
1808 }
1809
1810 private SignalServiceAccountDataStore getSignalServiceAccountDataStore() {
1811 return getOrCreate(() -> signalProtocolStore,
1812 () -> signalProtocolStore = new SignalProtocolStore(getPreKeyStore(),
1813 getSignedPreKeyStore(),
1814 getKyberPreKeyStore(),
1815 getSessionStore(),
1816 getIdentityKeyStore(),
1817 getSenderKeyStore(),
1818 SignalAccount.this::isMultiDevice));
1819 }
1820
1821 public PreKeyStore getPreKeyStore() {
1822 return getOrCreate(() -> preKeyStore,
1823 () -> preKeyStore = new PreKeyStore(getAccountDatabase(), serviceIdType));
1824 }
1825
1826 public SignedPreKeyStore getSignedPreKeyStore() {
1827 return getOrCreate(() -> signedPreKeyStore,
1828 () -> signedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), serviceIdType));
1829 }
1830
1831 public KyberPreKeyStore getKyberPreKeyStore() {
1832 return getOrCreate(() -> kyberPreKeyStore,
1833 () -> kyberPreKeyStore = new KyberPreKeyStore(getAccountDatabase(), serviceIdType));
1834 }
1835
1836 public SessionStore getSessionStore() {
1837 return getOrCreate(() -> sessionStore,
1838 () -> sessionStore = new SessionStore(getAccountDatabase(), serviceIdType));
1839 }
1840
1841 public SignalIdentityKeyStore getIdentityKeyStore() {
1842 return getOrCreate(() -> identityKeyStore,
1843 () -> identityKeyStore = new SignalIdentityKeyStore(() -> identityKeyPair,
1844 localRegistrationId,
1845 SignalAccount.this.getIdentityKeyStore()));
1846 }
1847 }
1848 }