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