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