]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
e8f2db45247e5617fed70e739fac7eecaedc3b66
[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 contact.color,
863 contact.messageExpirationTime,
864 0,
865 false,
866 contact.blocked,
867 contact.archived,
868 false,
869 false,
870 null));
871
872 // Store profile keys only in profile store
873 var profileKeyString = contact.profileKey;
874 if (profileKeyString != null) {
875 final ProfileKey profileKey;
876 try {
877 profileKey = new ProfileKey(Base64.getDecoder().decode(profileKeyString));
878 getProfileStore().storeProfileKey(recipientId, profileKey);
879 } catch (InvalidInputException e) {
880 logger.warn("Failed to parse legacy contact profile key: {}", e.getMessage());
881 }
882 }
883 }
884 }
885
886 if (rootNode.hasNonNull("profileStore")) {
887 logger.debug("Migrating legacy profile store.");
888 var profileStoreNode = rootNode.get("profileStore");
889 final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
890 for (var profileEntry : legacyProfileStore.getProfileEntries()) {
891 var recipientId = getRecipientResolver().resolveRecipient(profileEntry.address());
892 // Not migrating profile key credential here, it was changed to expiring profile key credentials
893 getProfileStore().storeProfileKey(recipientId, profileEntry.profileKey());
894 final var profile = profileEntry.profile();
895 if (profile != null) {
896 final var capabilities = new HashSet<Profile.Capability>();
897 if (profile.getCapabilities() != null) {
898 if (profile.getCapabilities().gv1Migration) {
899 capabilities.add(Profile.Capability.gv1Migration);
900 }
901 if (profile.getCapabilities().storage) {
902 capabilities.add(Profile.Capability.storage);
903 }
904 }
905 final var newProfile = new Profile(profileEntry.lastUpdateTimestamp(),
906 profile.getGivenName(),
907 profile.getFamilyName(),
908 profile.getAbout(),
909 profile.getAboutEmoji(),
910 null,
911 null,
912 profile.isUnrestrictedUnidentifiedAccess()
913 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
914 : profile.getUnidentifiedAccess() != null
915 ? Profile.UnidentifiedAccessMode.ENABLED
916 : Profile.UnidentifiedAccessMode.DISABLED,
917 capabilities);
918 getProfileStore().storeProfile(recipientId, newProfile);
919 }
920 }
921 }
922 }
923
924 private void loadLegacyThreadStore(final JsonNode rootNode) {
925 var threadStoreNode = rootNode.get("threadStore");
926 if (threadStoreNode != null && !threadStoreNode.isNull()) {
927 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
928 // Migrate thread info to group and contact store
929 for (var thread : threadStore.getThreads()) {
930 if (thread.id == null || thread.id.isEmpty()) {
931 continue;
932 }
933 try {
934 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
935 final var recipientId = getRecipientResolver().resolveRecipient(thread.id);
936 var contact = getContactStore().getContact(recipientId);
937 if (contact != null) {
938 getContactStore().storeContact(recipientId,
939 Contact.newBuilder(contact)
940 .withMessageExpirationTime(thread.messageExpirationTime)
941 .build());
942 }
943 } else {
944 var groupInfo = getGroupStore().getGroup(GroupId.fromBase64(thread.id));
945 if (groupInfo instanceof GroupInfoV1) {
946 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
947 getGroupStore().updateGroup(groupInfo);
948 }
949 }
950 } catch (Exception e) {
951 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
952 }
953 }
954 }
955 }
956
957 private void save() {
958 synchronized (fileChannel) {
959 final var base64 = Base64.getEncoder();
960 final var storage = new Storage(CURRENT_STORAGE_VERSION,
961 serviceEnvironment.name(),
962 registered,
963 number,
964 username,
965 encryptedDeviceName,
966 deviceId,
967 isMultiDevice,
968 password,
969 Storage.AccountData.from(aciAccountData),
970 Storage.AccountData.from(pniAccountData),
971 registrationLockPin,
972 pinMasterKey == null ? null : base64.encodeToString(pinMasterKey.serialize()),
973 storageKey == null ? null : base64.encodeToString(storageKey.serialize()),
974 profileKey == null ? null : base64.encodeToString(profileKey.serialize()),
975 usernameLink == null ? null : base64.encodeToString(usernameLink.getEntropy()),
976 usernameLink == null ? null : usernameLink.getServerId().toString());
977 try {
978 try (var output = new ByteArrayOutputStream()) {
979 // Write to memory first to prevent corrupting the file in case of serialization errors
980 jsonProcessor.writeValue(output, storage);
981 var input = new ByteArrayInputStream(output.toByteArray());
982 fileChannel.position(0);
983 input.transferTo(Channels.newOutputStream(fileChannel));
984 fileChannel.truncate(fileChannel.position());
985 fileChannel.force(false);
986 }
987 } catch (Exception e) {
988 logger.error("Error saving file: {}", e.getMessage(), e);
989 }
990 }
991 }
992
993 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
994 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
995 try {
996 var lock = fileChannel.tryLock();
997 if (lock == null) {
998 if (!waitForLock) {
999 logger.debug("Config file is in use by another instance.");
1000 throw new IOException("Config file is in use by another instance.");
1001 }
1002 logger.info("Config file is in use by another instance, waiting…");
1003 lock = fileChannel.lock();
1004 logger.info("Config file lock acquired.");
1005 }
1006 final var result = new Pair<>(fileChannel, lock);
1007 fileChannel = null;
1008 return result;
1009 } finally {
1010 if (fileChannel != null) {
1011 fileChannel.close();
1012 }
1013 }
1014 }
1015
1016 private void clearAllPreKeys() {
1017 clearAllPreKeys(ServiceIdType.ACI);
1018 clearAllPreKeys(ServiceIdType.PNI);
1019 }
1020
1021 private void initAllPreKeyIds() {
1022 resetPreKeyOffsets(ServiceIdType.ACI);
1023 resetPreKeyOffsets(ServiceIdType.PNI);
1024 resetKyberPreKeyOffsets(ServiceIdType.ACI);
1025 resetKyberPreKeyOffsets(ServiceIdType.PNI);
1026 }
1027
1028 private void clearAllPreKeys(ServiceIdType serviceIdType) {
1029 final var accountData = getAccountData(serviceIdType);
1030 resetPreKeyOffsets(serviceIdType);
1031 resetKyberPreKeyOffsets(serviceIdType);
1032 accountData.getPreKeyStore().removeAllPreKeys();
1033 accountData.getSignedPreKeyStore().removeAllSignedPreKeys();
1034 accountData.getKyberPreKeyStore().removeAllKyberPreKeys();
1035 save();
1036 }
1037
1038 private void setPreKeys(ServiceIdType serviceIdType, PreKeyCollection preKeyCollection) {
1039 final var accountData = getAccountData(serviceIdType);
1040 final var preKeyMetadata = accountData.getPreKeyMetadata();
1041 preKeyMetadata.nextSignedPreKeyId = preKeyCollection.getSignedPreKey().getId();
1042 preKeyMetadata.nextKyberPreKeyId = preKeyCollection.getLastResortKyberPreKey().getId();
1043
1044 accountData.getPreKeyStore().removeAllPreKeys();
1045 accountData.getSignedPreKeyStore().removeAllSignedPreKeys();
1046 accountData.getKyberPreKeyStore().removeAllKyberPreKeys();
1047
1048 addSignedPreKey(serviceIdType, preKeyCollection.getSignedPreKey());
1049 addLastResortKyberPreKey(serviceIdType, preKeyCollection.getLastResortKyberPreKey());
1050
1051 save();
1052 }
1053
1054 public void resetPreKeyOffsets(final ServiceIdType serviceIdType) {
1055 final var preKeyMetadata = getAccountData(serviceIdType).getPreKeyMetadata();
1056 preKeyMetadata.nextPreKeyId = getRandomPreKeyIdOffset();
1057 preKeyMetadata.nextSignedPreKeyId = getRandomPreKeyIdOffset();
1058 preKeyMetadata.activeSignedPreKeyId = -1;
1059 save();
1060 }
1061
1062 private static int getRandomPreKeyIdOffset() {
1063 return KeyUtils.getRandomInt(PREKEY_MAXIMUM_ID);
1064 }
1065
1066 public void addPreKeys(ServiceIdType serviceIdType, List<PreKeyRecord> records) {
1067 final var accountData = getAccountData(serviceIdType);
1068 final var preKeyMetadata = accountData.getPreKeyMetadata();
1069 logger.debug("Adding {} {} pre keys with offset {}",
1070 records.size(),
1071 serviceIdType,
1072 preKeyMetadata.nextPreKeyId);
1073 accountData.getSignalServiceAccountDataStore()
1074 .markAllOneTimeEcPreKeysStaleIfNecessary(System.currentTimeMillis());
1075 for (var record : records) {
1076 if (preKeyMetadata.nextPreKeyId != record.getId()) {
1077 logger.error("Invalid pre key id {}, expected {}", record.getId(), preKeyMetadata.nextPreKeyId);
1078 throw new AssertionError("Invalid pre key id");
1079 }
1080 accountData.getPreKeyStore().storePreKey(record.getId(), record);
1081 preKeyMetadata.nextPreKeyId = (preKeyMetadata.nextPreKeyId + 1) % PREKEY_MAXIMUM_ID;
1082 }
1083 save();
1084 }
1085
1086 public void addSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord record) {
1087 final var accountData = getAccountData(serviceIdType);
1088 final var preKeyMetadata = accountData.getPreKeyMetadata();
1089 logger.debug("Adding {} signed pre key with offset {}", serviceIdType, preKeyMetadata.nextSignedPreKeyId);
1090 if (preKeyMetadata.nextSignedPreKeyId != record.getId()) {
1091 logger.error("Invalid signed pre key id {}, expected {}",
1092 record.getId(),
1093 preKeyMetadata.nextSignedPreKeyId);
1094 throw new AssertionError("Invalid signed pre key id");
1095 }
1096 accountData.getSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
1097 preKeyMetadata.nextSignedPreKeyId = (preKeyMetadata.nextSignedPreKeyId + 1) % PREKEY_MAXIMUM_ID;
1098 preKeyMetadata.activeSignedPreKeyId = record.getId();
1099 save();
1100 }
1101
1102 public void resetKyberPreKeyOffsets(final ServiceIdType serviceIdType) {
1103 final var preKeyMetadata = getAccountData(serviceIdType).getPreKeyMetadata();
1104 preKeyMetadata.nextKyberPreKeyId = getRandomPreKeyIdOffset();
1105 preKeyMetadata.activeLastResortKyberPreKeyId = -1;
1106 save();
1107 }
1108
1109 public void addKyberPreKeys(ServiceIdType serviceIdType, List<KyberPreKeyRecord> records) {
1110 final var accountData = getAccountData(serviceIdType);
1111 final var preKeyMetadata = accountData.getPreKeyMetadata();
1112 logger.debug("Adding {} {} kyber pre keys with offset {}",
1113 records.size(),
1114 serviceIdType,
1115 preKeyMetadata.nextKyberPreKeyId);
1116 accountData.getSignalServiceAccountDataStore()
1117 .markAllOneTimeKyberPreKeysStaleIfNecessary(System.currentTimeMillis());
1118 for (var record : records) {
1119 if (preKeyMetadata.nextKyberPreKeyId != record.getId()) {
1120 logger.error("Invalid kyber pre key id {}, expected {}",
1121 record.getId(),
1122 preKeyMetadata.nextKyberPreKeyId);
1123 throw new AssertionError("Invalid kyber pre key id");
1124 }
1125 accountData.getKyberPreKeyStore().storeKyberPreKey(record.getId(), record);
1126 preKeyMetadata.nextKyberPreKeyId = (preKeyMetadata.nextKyberPreKeyId + 1) % PREKEY_MAXIMUM_ID;
1127 }
1128 save();
1129 }
1130
1131 public void addLastResortKyberPreKey(ServiceIdType serviceIdType, KyberPreKeyRecord record) {
1132 final var accountData = getAccountData(serviceIdType);
1133 final var preKeyMetadata = accountData.getPreKeyMetadata();
1134 logger.debug("Adding {} last resort kyber pre key with offset {}",
1135 serviceIdType,
1136 preKeyMetadata.nextKyberPreKeyId);
1137 if (preKeyMetadata.nextKyberPreKeyId != record.getId()) {
1138 logger.error("Invalid last resort kyber pre key id {}, expected {}",
1139 record.getId(),
1140 preKeyMetadata.nextKyberPreKeyId);
1141 throw new AssertionError("Invalid last resort kyber pre key id");
1142 }
1143 accountData.getKyberPreKeyStore().storeLastResortKyberPreKey(record.getId(), record);
1144 preKeyMetadata.activeLastResortKyberPreKeyId = record.getId();
1145 preKeyMetadata.nextKyberPreKeyId = (preKeyMetadata.nextKyberPreKeyId + 1) % PREKEY_MAXIMUM_ID;
1146 save();
1147 }
1148
1149 public int getPreviousStorageVersion() {
1150 return previousStorageVersion;
1151 }
1152
1153 public AccountData<? extends ServiceId> getAccountData(ServiceIdType serviceIdType) {
1154 return switch (serviceIdType) {
1155 case ACI -> aciAccountData;
1156 case PNI -> pniAccountData;
1157 };
1158 }
1159
1160 public AccountData<? extends ServiceId> getAccountData(ServiceId accountIdentifier) {
1161 if (accountIdentifier.equals(aciAccountData.getServiceId())) {
1162 return aciAccountData;
1163 } else if (accountIdentifier.equals(pniAccountData.getServiceId())) {
1164 return pniAccountData;
1165 } else {
1166 throw new IllegalArgumentException("No matching account data found for " + accountIdentifier);
1167 }
1168 }
1169
1170 public SignalServiceDataStore getSignalServiceDataStore() {
1171 return new SignalServiceDataStore() {
1172 @Override
1173 public SignalServiceAccountDataStore get(final ServiceId accountIdentifier) {
1174 return getAccountData(accountIdentifier).getSignalServiceAccountDataStore();
1175 }
1176
1177 @Override
1178 public SignalServiceAccountDataStore aci() {
1179 return aciAccountData.getSignalServiceAccountDataStore();
1180 }
1181
1182 @Override
1183 public SignalServiceAccountDataStore pni() {
1184 return pniAccountData.getSignalServiceAccountDataStore();
1185 }
1186
1187 @Override
1188 public boolean isMultiDevice() {
1189 return SignalAccount.this.isMultiDevice();
1190 }
1191 };
1192 }
1193
1194 public IdentityKeyStore getIdentityKeyStore() {
1195 return getOrCreate(() -> identityKeyStore,
1196 () -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(),
1197 settings.trustNewIdentity(),
1198 getRecipientStore()));
1199 }
1200
1201 public GroupStore getGroupStore() {
1202 return getOrCreate(() -> groupStore,
1203 () -> groupStore = new GroupStore(getAccountDatabase(),
1204 getRecipientResolver(),
1205 getRecipientIdCreator()));
1206 }
1207
1208 public ContactsStore getContactStore() {
1209 return getRecipientStore();
1210 }
1211
1212 public CdsiStore getCdsiStore() {
1213 return getOrCreate(() -> cdsiStore, () -> cdsiStore = new CdsiStore(getAccountDatabase()));
1214 }
1215
1216 private RecipientIdCreator getRecipientIdCreator() {
1217 return recipientId -> getRecipientStore().create(recipientId);
1218 }
1219
1220 public RecipientResolver getRecipientResolver() {
1221 return new RecipientResolver.RecipientResolverWrapper(this::getRecipientStore);
1222 }
1223
1224 public RecipientTrustedResolver getRecipientTrustedResolver() {
1225 return new RecipientTrustedResolver.RecipientTrustedResolverWrapper(this::getRecipientStore);
1226 }
1227
1228 public RecipientAddressResolver getRecipientAddressResolver() {
1229 return recipientId -> getRecipientStore().resolveRecipientAddress(recipientId);
1230 }
1231
1232 public RecipientStore getRecipientStore() {
1233 return getOrCreate(() -> recipientStore,
1234 () -> recipientStore = new RecipientStore(this::mergeRecipients,
1235 this::getSelfRecipientAddress,
1236 this::getProfileKey,
1237 getAccountDatabase()));
1238 }
1239
1240 public ProfileStore getProfileStore() {
1241 return getRecipientStore();
1242 }
1243
1244 public StickerStore getStickerStore() {
1245 return getOrCreate(() -> stickerStore, () -> stickerStore = new StickerStore(getAccountDatabase()));
1246 }
1247
1248 public SenderKeyStore getSenderKeyStore() {
1249 return getOrCreate(() -> senderKeyStore, () -> senderKeyStore = new SenderKeyStore(getAccountDatabase()));
1250 }
1251
1252 private KeyValueStore getKeyValueStore() {
1253 return getOrCreate(() -> keyValueStore, () -> keyValueStore = new KeyValueStore(getAccountDatabase()));
1254 }
1255
1256 public UnknownStorageIdStore getUnknownStorageIdStore() {
1257 return getOrCreate(() -> unknownStorageIdStore, () -> unknownStorageIdStore = new UnknownStorageIdStore());
1258 }
1259
1260 public ConfigurationStore getConfigurationStore() {
1261 return getOrCreate(() -> configurationStore,
1262 () -> configurationStore = new ConfigurationStore(getKeyValueStore(), getRecipientStore()));
1263 }
1264
1265 public MessageCache getMessageCache() {
1266 return getOrCreate(() -> messageCache,
1267 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
1268 }
1269
1270 public AccountDatabase getAccountDatabase() {
1271 return getOrCreate(() -> accountDatabase, () -> {
1272 try {
1273 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
1274 } catch (SQLException e) {
1275 throw new RuntimeException(e);
1276 }
1277 });
1278 }
1279
1280 public MessageSendLogStore getMessageSendLogStore() {
1281 return getOrCreate(() -> messageSendLogStore,
1282 () -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase(),
1283 settings.disableMessageSendLog()));
1284 }
1285
1286 public CredentialsProvider getCredentialsProvider() {
1287 return new CredentialsProvider() {
1288 @Override
1289 public ACI getAci() {
1290 return aciAccountData.getServiceId();
1291 }
1292
1293 @Override
1294 public PNI getPni() {
1295 return pniAccountData.getServiceId();
1296 }
1297
1298 @Override
1299 public String getE164() {
1300 return number;
1301 }
1302
1303 @Override
1304 public String getPassword() {
1305 return password;
1306 }
1307
1308 @Override
1309 public int getDeviceId() {
1310 return deviceId;
1311 }
1312 };
1313 }
1314
1315 public String getNumber() {
1316 return number;
1317 }
1318
1319 public void setNumber(final String number) {
1320 this.number = number;
1321 save();
1322 }
1323
1324 public String getUsername() {
1325 return username;
1326 }
1327
1328 public void setUsername(final String username) {
1329 this.username = username;
1330 save();
1331 }
1332
1333 public UsernameLinkComponents getUsernameLink() {
1334 return usernameLink;
1335 }
1336
1337 public void setUsernameLink(final UsernameLinkComponents usernameLink) {
1338 this.usernameLink = usernameLink;
1339 }
1340
1341 public ServiceEnvironment getServiceEnvironment() {
1342 return serviceEnvironment;
1343 }
1344
1345 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1346 this.serviceEnvironment = serviceEnvironment;
1347 save();
1348 }
1349
1350 public AccountAttributes getAccountAttributes(String registrationLock) {
1351 return new AccountAttributes(null,
1352 aciAccountData.getLocalRegistrationId(),
1353 false,
1354 false,
1355 true,
1356 registrationLock != null ? registrationLock : getRegistrationLock(),
1357 getSelfUnidentifiedAccessKey(),
1358 isUnrestrictedUnidentifiedAccess(),
1359 isDiscoverableByPhoneNumber(),
1360 getAccountCapabilities(),
1361 encryptedDeviceName,
1362 pniAccountData.getLocalRegistrationId(),
1363 getRecoveryPassword());
1364 }
1365
1366 public AccountAttributes.Capabilities getAccountCapabilities() {
1367 return getCapabilities(isPrimaryDevice());
1368 }
1369
1370 public ServiceId getAccountId(ServiceIdType serviceIdType) {
1371 return getAccountData(serviceIdType).getServiceId();
1372 }
1373
1374 public ACI getAci() {
1375 return aciAccountData.getServiceId();
1376 }
1377
1378 public void setAci(final ACI aci) {
1379 this.aciAccountData.setServiceId(aci);
1380 save();
1381 }
1382
1383 public PNI getPni() {
1384 return pniAccountData.getServiceId();
1385 }
1386
1387 public void setPni(final PNI updatedPni) {
1388 final var oldPni = pniAccountData.getServiceId();
1389 if (oldPni != null && !oldPni.equals(updatedPni)) {
1390 // Clear data for old PNI
1391 identityKeyStore.deleteIdentity(oldPni);
1392 }
1393
1394 this.pniAccountData.setServiceId(updatedPni);
1395 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1396 trustSelfIdentity(ServiceIdType.PNI);
1397 save();
1398 }
1399
1400 public void setNewPniIdentity(
1401 final IdentityKeyPair pniIdentityKeyPair,
1402 final SignedPreKeyRecord pniSignedPreKey,
1403 final KyberPreKeyRecord lastResortKyberPreKey,
1404 final int localPniRegistrationId
1405 ) {
1406 setPniIdentityKeyPair(pniIdentityKeyPair);
1407 pniAccountData.setLocalRegistrationId(localPniRegistrationId);
1408
1409 final AccountData<? extends ServiceId> accountData = getAccountData(ServiceIdType.PNI);
1410 final var preKeyMetadata = accountData.getPreKeyMetadata();
1411 preKeyMetadata.nextSignedPreKeyId = pniSignedPreKey.getId();
1412 accountData.getSignedPreKeyStore().removeSignedPreKey(pniSignedPreKey.getId());
1413 addSignedPreKey(ServiceIdType.PNI, pniSignedPreKey);
1414 if (lastResortKyberPreKey != null) {
1415 preKeyMetadata.nextKyberPreKeyId = lastResortKyberPreKey.getId();
1416 accountData.getKyberPreKeyStore().removeKyberPreKey(lastResortKyberPreKey.getId());
1417 addLastResortKyberPreKey(ServiceIdType.PNI, lastResortKyberPreKey);
1418 }
1419 save();
1420 }
1421
1422 public SignalServiceAddress getSelfAddress() {
1423 return new SignalServiceAddress(getAci(), number);
1424 }
1425
1426 public RecipientAddress getSelfRecipientAddress() {
1427 return new RecipientAddress(getAci(), getPni(), number, username);
1428 }
1429
1430 public RecipientId getSelfRecipientId() {
1431 return selfRecipientId;
1432 }
1433
1434 public String getSessionId(final String forNumber) {
1435 final var keyValueStore = getKeyValueStore();
1436 final var sessionNumber = keyValueStore.getEntry(verificationSessionNumber);
1437 if (!forNumber.equals(sessionNumber)) {
1438 return null;
1439 }
1440 return keyValueStore.getEntry(verificationSessionId);
1441 }
1442
1443 public void setSessionId(final String sessionNumber, final String sessionId) {
1444 final var keyValueStore = getKeyValueStore();
1445 keyValueStore.storeEntry(verificationSessionNumber, sessionNumber);
1446 keyValueStore.storeEntry(verificationSessionId, sessionId);
1447 }
1448
1449 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1450 this.encryptedDeviceName = encryptedDeviceName;
1451 save();
1452 }
1453
1454 public int getDeviceId() {
1455 return deviceId;
1456 }
1457
1458 public boolean isPrimaryDevice() {
1459 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1460 }
1461
1462 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1463 return getAccountData(serviceIdType).getIdentityKeyPair();
1464 }
1465
1466 public IdentityKeyPair getAciIdentityKeyPair() {
1467 return aciAccountData.getIdentityKeyPair();
1468 }
1469
1470 public IdentityKeyPair getPniIdentityKeyPair() {
1471 return pniAccountData.getIdentityKeyPair();
1472 }
1473
1474 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1475 pniAccountData.setIdentityKeyPair(identityKeyPair);
1476 trustSelfIdentity(ServiceIdType.PNI);
1477 save();
1478 }
1479
1480 public String getPassword() {
1481 return password;
1482 }
1483
1484 public void setRegistrationLockPin(final String registrationLockPin) {
1485 this.registrationLockPin = registrationLockPin;
1486 save();
1487 }
1488
1489 public String getRegistrationLockPin() {
1490 return registrationLockPin;
1491 }
1492
1493 public String getRegistrationLock() {
1494 final var masterKey = getPinBackedMasterKey();
1495 if (masterKey == null) {
1496 return null;
1497 }
1498 return masterKey.deriveRegistrationLock();
1499 }
1500
1501 public MasterKey getPinBackedMasterKey() {
1502 if (registrationLockPin == null) {
1503 return null;
1504 }
1505 return pinMasterKey;
1506 }
1507
1508 public MasterKey getOrCreatePinMasterKey() {
1509 if (pinMasterKey == null) {
1510 pinMasterKey = KeyUtils.createMasterKey();
1511 save();
1512 }
1513 return pinMasterKey;
1514 }
1515
1516 public void setMasterKey(MasterKey masterKey) {
1517 if (isPrimaryDevice()) {
1518 return;
1519 }
1520 this.pinMasterKey = masterKey;
1521 save();
1522 }
1523
1524 public StorageKey getOrCreateStorageKey() {
1525 if (pinMasterKey != null) {
1526 return pinMasterKey.deriveStorageServiceKey();
1527 } else if (storageKey != null) {
1528 return storageKey;
1529 } else if (!isPrimaryDevice() || !isMultiDevice()) {
1530 // Only upload storage, if a pin master key already exists or linked devices exist
1531 return null;
1532 }
1533
1534 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1535 }
1536
1537 public void setStorageKey(final StorageKey storageKey) {
1538 if (isPrimaryDevice() || storageKey.equals(this.storageKey)) {
1539 return;
1540 }
1541 this.storageKey = storageKey;
1542 save();
1543 }
1544
1545 public String getRecoveryPassword() {
1546 final var masterKey = getPinBackedMasterKey();
1547 if (masterKey == null) {
1548 return null;
1549 }
1550 return masterKey.deriveRegistrationRecoveryPassword();
1551 }
1552
1553 public long getStorageManifestVersion() {
1554 return getKeyValueStore().getEntry(storageManifestVersion);
1555 }
1556
1557 public void setStorageManifestVersion(final long value) {
1558 getKeyValueStore().storeEntry(storageManifestVersion, value);
1559 }
1560
1561 public Optional<SignalStorageManifest> getStorageManifest() {
1562 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1563 if (!storageManifestFile.exists()) {
1564 return Optional.empty();
1565 }
1566 try (var inputStream = new FileInputStream(storageManifestFile)) {
1567 return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
1568 } catch (IOException e) {
1569 logger.warn("Failed to read local storage manifest.", e);
1570 return Optional.empty();
1571 }
1572 }
1573
1574 public void setStorageManifest(SignalStorageManifest manifest) {
1575 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1576 if (manifest == null) {
1577 if (storageManifestFile.exists()) {
1578 try {
1579 Files.delete(storageManifestFile.toPath());
1580 } catch (IOException e) {
1581 logger.error("Failed to delete local storage manifest.", e);
1582 }
1583 }
1584 return;
1585 }
1586
1587 final var manifestBytes = manifest.serialize();
1588 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1589 outputStream.write(manifestBytes);
1590 } catch (IOException e) {
1591 logger.error("Failed to store local storage manifest.", e);
1592 }
1593 }
1594
1595 public byte[] getCdsiToken() {
1596 return getKeyValueStore().getEntry(cdsiToken);
1597 }
1598
1599 public void setCdsiToken(final byte[] value) {
1600 getKeyValueStore().storeEntry(cdsiToken, value);
1601 }
1602
1603 public Long getLastRecipientsRefresh() {
1604 return getKeyValueStore().getEntry(lastRecipientsRefresh);
1605 }
1606
1607 public void setLastRecipientsRefresh(final Long value) {
1608 getKeyValueStore().storeEntry(lastRecipientsRefresh, value);
1609 }
1610
1611 public ProfileKey getProfileKey() {
1612 return profileKey;
1613 }
1614
1615 public void setProfileKey(final ProfileKey profileKey) {
1616 if (profileKey.equals(this.profileKey)) {
1617 return;
1618 }
1619 this.profileKey = profileKey;
1620 save();
1621 }
1622
1623 public byte[] getSelfUnidentifiedAccessKey() {
1624 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1625 }
1626
1627 public boolean isRegistered() {
1628 return registered;
1629 }
1630
1631 public void setRegistered(final boolean registered) {
1632 this.registered = registered;
1633 save();
1634 }
1635
1636 public boolean isMultiDevice() {
1637 return isMultiDevice;
1638 }
1639
1640 public void setMultiDevice(final boolean multiDevice) {
1641 if (isMultiDevice == multiDevice) {
1642 return;
1643 }
1644 isMultiDevice = multiDevice;
1645 save();
1646 }
1647
1648 public long getLastReceiveTimestamp() {
1649 return getKeyValueStore().getEntry(lastReceiveTimestamp);
1650 }
1651
1652 public void setLastReceiveTimestamp(final long value) {
1653 getKeyValueStore().storeEntry(lastReceiveTimestamp, value);
1654 }
1655
1656 public void setNeedsToRetryFailedMessages(final boolean value) {
1657 getKeyValueStore().storeEntry(needsToRetryFailedMessages, value);
1658 }
1659
1660 public boolean getNeedsToRetryFailedMessages() {
1661 return getKeyValueStore().getEntry(needsToRetryFailedMessages);
1662 }
1663
1664 public boolean isUnrestrictedUnidentifiedAccess() {
1665 return Boolean.TRUE.equals(getKeyValueStore().getEntry(unrestrictedUnidentifiedAccess));
1666 }
1667
1668 public void setUnrestrictedUnidentifiedAccess(boolean value) {
1669 getKeyValueStore().storeEntry(unrestrictedUnidentifiedAccess, value);
1670 }
1671
1672 public boolean isDiscoverableByPhoneNumber() {
1673 final var phoneNumberUnlisted = getConfigurationStore().getPhoneNumberUnlisted();
1674 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1675 }
1676
1677 private void trustSelfIdentity(ServiceIdType serviceIdType) {
1678 final var accountData = getAccountData(serviceIdType);
1679 final var serviceId = accountData.getServiceId();
1680 final var identityKeyPair = accountData.getIdentityKeyPair();
1681 if (serviceId == null || identityKeyPair == null) {
1682 return;
1683 }
1684 final var publicKey = identityKeyPair.getPublicKey();
1685 getIdentityKeyStore().saveIdentity(serviceId, publicKey);
1686 getIdentityKeyStore().setIdentityTrustLevel(serviceId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1687 }
1688
1689 public void deleteAccountData() throws IOException {
1690 close();
1691 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1692 .sorted(Comparator.reverseOrder())) {
1693 for (final var file = files.iterator(); file.hasNext(); ) {
1694 Files.delete(file.next());
1695 }
1696 }
1697 Files.delete(getFileName(dataPath, accountPath).toPath());
1698 }
1699
1700 @Override
1701 public void close() {
1702 synchronized (fileChannel) {
1703 if (accountDatabase != null) {
1704 accountDatabase.close();
1705 }
1706 if (messageSendLogStore != null) {
1707 messageSendLogStore.close();
1708 }
1709 try {
1710 try {
1711 lock.close();
1712 } catch (ClosedChannelException ignored) {
1713 }
1714 fileChannel.close();
1715 } catch (IOException e) {
1716 logger.warn("Failed to close account: {}", e.getMessage(), e);
1717 }
1718 }
1719 }
1720
1721 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1722 var value = supplier.get();
1723 if (value != null) {
1724 return value;
1725 }
1726
1727 synchronized (LOCK) {
1728 value = supplier.get();
1729 if (value != null) {
1730 return value;
1731 }
1732 creator.call();
1733 return supplier.get();
1734 }
1735 }
1736
1737 private interface Callable {
1738
1739 void call();
1740 }
1741
1742 public static class PreKeyMetadata {
1743
1744 private int nextPreKeyId = 1;
1745 private int nextSignedPreKeyId = 1;
1746 private int activeSignedPreKeyId = -1;
1747 private int nextKyberPreKeyId = 1;
1748 private int activeLastResortKyberPreKeyId = -1;
1749
1750 public int getNextPreKeyId() {
1751 return nextPreKeyId;
1752 }
1753
1754 public int getNextSignedPreKeyId() {
1755 return nextSignedPreKeyId;
1756 }
1757
1758 public int getActiveSignedPreKeyId() {
1759 return activeSignedPreKeyId;
1760 }
1761
1762 public int getNextKyberPreKeyId() {
1763 return nextKyberPreKeyId;
1764 }
1765
1766 public int getActiveLastResortKyberPreKeyId() {
1767 return activeLastResortKyberPreKeyId;
1768 }
1769 }
1770
1771 public class AccountData<SERVICE_ID extends ServiceId> {
1772
1773 private final ServiceIdType serviceIdType;
1774 private SERVICE_ID serviceId;
1775 private IdentityKeyPair identityKeyPair;
1776 private int localRegistrationId;
1777 private final PreKeyMetadata preKeyMetadata = new PreKeyMetadata();
1778
1779 private SignalProtocolStore signalProtocolStore;
1780 private PreKeyStore preKeyStore;
1781 private SignedPreKeyStore signedPreKeyStore;
1782 private KyberPreKeyStore kyberPreKeyStore;
1783 private SessionStore sessionStore;
1784 private SignalIdentityKeyStore identityKeyStore;
1785
1786 private AccountData(final ServiceIdType serviceIdType) {
1787 this.serviceIdType = serviceIdType;
1788 }
1789
1790 public SERVICE_ID getServiceId() {
1791 return serviceId;
1792 }
1793
1794 private void setServiceId(final SERVICE_ID serviceId) {
1795 this.serviceId = serviceId;
1796 }
1797
1798 public IdentityKeyPair getIdentityKeyPair() {
1799 return identityKeyPair;
1800 }
1801
1802 private void setIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1803 this.identityKeyPair = identityKeyPair;
1804 }
1805
1806 public int getLocalRegistrationId() {
1807 return localRegistrationId;
1808 }
1809
1810 private void setLocalRegistrationId(final int localRegistrationId) {
1811 this.localRegistrationId = localRegistrationId;
1812 this.identityKeyStore = null;
1813 }
1814
1815 public PreKeyMetadata getPreKeyMetadata() {
1816 return preKeyMetadata;
1817 }
1818
1819 private SignalServiceAccountDataStore getSignalServiceAccountDataStore() {
1820 return getOrCreate(() -> signalProtocolStore,
1821 () -> signalProtocolStore = new SignalProtocolStore(getPreKeyStore(),
1822 getSignedPreKeyStore(),
1823 getKyberPreKeyStore(),
1824 getSessionStore(),
1825 getIdentityKeyStore(),
1826 getSenderKeyStore(),
1827 SignalAccount.this::isMultiDevice));
1828 }
1829
1830 public PreKeyStore getPreKeyStore() {
1831 return getOrCreate(() -> preKeyStore,
1832 () -> preKeyStore = new PreKeyStore(getAccountDatabase(), serviceIdType));
1833 }
1834
1835 public SignedPreKeyStore getSignedPreKeyStore() {
1836 return getOrCreate(() -> signedPreKeyStore,
1837 () -> signedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), serviceIdType));
1838 }
1839
1840 public KyberPreKeyStore getKyberPreKeyStore() {
1841 return getOrCreate(() -> kyberPreKeyStore,
1842 () -> kyberPreKeyStore = new KyberPreKeyStore(getAccountDatabase(), serviceIdType));
1843 }
1844
1845 public SessionStore getSessionStore() {
1846 return getOrCreate(() -> sessionStore,
1847 () -> sessionStore = new SessionStore(getAccountDatabase(), serviceIdType));
1848 }
1849
1850 public SignalIdentityKeyStore getIdentityKeyStore() {
1851 return getOrCreate(() -> identityKeyStore,
1852 () -> identityKeyStore = new SignalIdentityKeyStore(() -> identityKeyPair,
1853 localRegistrationId,
1854 SignalAccount.this.getIdentityKeyStore()));
1855 }
1856 }
1857
1858 public record Storage(
1859 int version,
1860 String serviceEnvironment,
1861 boolean registered,
1862 String number,
1863 String username,
1864 String encryptedDeviceName,
1865 int deviceId,
1866 boolean isMultiDevice,
1867 String password,
1868 AccountData aciAccountData,
1869 AccountData pniAccountData,
1870 String registrationLockPin,
1871 String pinMasterKey,
1872 String storageKey,
1873 String profileKey,
1874 String usernameLinkEntropy,
1875 String usernameLinkServerId
1876 ) {
1877
1878 public record AccountData(
1879 String serviceId,
1880 int registrationId,
1881 String identityPrivateKey,
1882 String identityPublicKey,
1883
1884 int nextPreKeyId,
1885 int nextSignedPreKeyId,
1886 int activeSignedPreKeyId,
1887 int nextKyberPreKeyId,
1888 int activeLastResortKyberPreKeyId
1889 ) {
1890
1891 private static AccountData from(final SignalAccount.AccountData<?> accountData) {
1892 final var base64 = Base64.getEncoder();
1893 final var preKeyMetadata = accountData.getPreKeyMetadata();
1894 return new AccountData(accountData.getServiceId() == null
1895 ? null
1896 : accountData.getServiceId().toString(),
1897 accountData.getLocalRegistrationId(),
1898 accountData.getIdentityKeyPair() == null
1899 ? null
1900 : base64.encodeToString(accountData.getIdentityKeyPair().getPrivateKey().serialize()),
1901 accountData.getIdentityKeyPair() == null
1902 ? null
1903 : base64.encodeToString(accountData.getIdentityKeyPair().getPublicKey().serialize()),
1904 preKeyMetadata.getNextPreKeyId(),
1905 preKeyMetadata.getNextSignedPreKeyId(),
1906 preKeyMetadata.getActiveSignedPreKeyId(),
1907 preKeyMetadata.getNextKyberPreKeyId(),
1908 preKeyMetadata.getActiveLastResortKyberPreKeyId());
1909 }
1910 }
1911 }
1912 }