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