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