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