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