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