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