]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
c4cf148409d9c0e6737f19fe08388d03595b2e2d
[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 pinMasterKey = KeyUtils.createMasterKey();
1554 save();
1555 return pinMasterKey;
1556 }
1557
1558 private MasterKey getMasterKey() {
1559 if (pinMasterKey != null) {
1560 return pinMasterKey;
1561 } else if (accountEntropyPool != null) {
1562 return accountEntropyPool.deriveMasterKey();
1563 }
1564 return null;
1565 }
1566
1567 public void setMasterKey(MasterKey masterKey) {
1568 if (isPrimaryDevice()) {
1569 return;
1570 }
1571 this.pinMasterKey = masterKey;
1572 if (masterKey != null) {
1573 this.storageKey = null;
1574 }
1575 save();
1576 }
1577
1578 public StorageKey getOrCreateStorageKey() {
1579 if (storageKey != null) {
1580 return storageKey;
1581 } else if (pinMasterKey != null) {
1582 return pinMasterKey.deriveStorageServiceKey();
1583 } else if (accountEntropyPool != null) {
1584 return accountEntropyPool.deriveMasterKey().deriveStorageServiceKey();
1585 } else if (!isPrimaryDevice() || !isMultiDevice()) {
1586 // Only upload storage, if a pin master key already exists or linked devices exist
1587 return null;
1588 }
1589
1590 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1591 }
1592
1593 public void setStorageKey(final StorageKey storageKey) {
1594 if (isPrimaryDevice() || storageKey.equals(this.storageKey)) {
1595 return;
1596 }
1597 this.storageKey = storageKey;
1598 save();
1599 }
1600
1601 public AccountEntropyPool getOrCreateAccountEntropyPool() {
1602 if (accountEntropyPool == null) {
1603 accountEntropyPool = AccountEntropyPool.Companion.generate();
1604 save();
1605 }
1606 return accountEntropyPool;
1607 }
1608
1609 public void setAccountEntropyPool(final AccountEntropyPool accountEntropyPool) {
1610 this.accountEntropyPool = accountEntropyPool;
1611 if (accountEntropyPool != null) {
1612 this.storageKey = null;
1613 this.pinMasterKey = null;
1614 }
1615 save();
1616 }
1617
1618 public boolean needsStorageKeyMigration() {
1619 return isPrimaryDevice() && (storageKey != null || pinMasterKey != null);
1620 }
1621
1622 public MediaRootBackupKey getOrCreateMediaRootBackupKey() {
1623 if (mediaRootBackupKey == null) {
1624 mediaRootBackupKey = KeyUtils.createMediaRootBackupKey();
1625 save();
1626 }
1627 return mediaRootBackupKey;
1628 }
1629
1630 public void setMediaRootBackupKey(final MediaRootBackupKey mediaRootBackupKey) {
1631 this.mediaRootBackupKey = mediaRootBackupKey;
1632 save();
1633 }
1634
1635 public String getRecoveryPassword() {
1636 final var masterKey = getPinBackedMasterKey();
1637 if (masterKey == null) {
1638 return null;
1639 }
1640 return masterKey.deriveRegistrationRecoveryPassword();
1641 }
1642
1643 public long getStorageManifestVersion() {
1644 return getKeyValueStore().getEntry(storageManifestVersion);
1645 }
1646
1647 public void setStorageManifestVersion(final long value) {
1648 getKeyValueStore().storeEntry(storageManifestVersion, value);
1649 }
1650
1651 public Optional<SignalStorageManifest> getStorageManifest() {
1652 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1653 if (!storageManifestFile.exists()) {
1654 return Optional.empty();
1655 }
1656 try (var inputStream = new FileInputStream(storageManifestFile)) {
1657 return Optional.of(SignalStorageManifest.Companion.deserialize(inputStream.readAllBytes()));
1658 } catch (IOException e) {
1659 logger.warn("Failed to read local storage manifest.", e);
1660 return Optional.empty();
1661 }
1662 }
1663
1664 public void setStorageManifest(SignalStorageManifest manifest) {
1665 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1666 if (manifest == null) {
1667 if (storageManifestFile.exists()) {
1668 try {
1669 Files.delete(storageManifestFile.toPath());
1670 } catch (IOException e) {
1671 logger.error("Failed to delete local storage manifest.", e);
1672 }
1673 }
1674 return;
1675 }
1676
1677 final var manifestBytes = manifest.serialize();
1678 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1679 outputStream.write(manifestBytes);
1680 } catch (IOException e) {
1681 logger.error("Failed to store local storage manifest.", e);
1682 }
1683 }
1684
1685 public byte[] getCdsiToken() {
1686 return getKeyValueStore().getEntry(cdsiToken);
1687 }
1688
1689 public void setCdsiToken(final byte[] value) {
1690 getKeyValueStore().storeEntry(cdsiToken, value);
1691 }
1692
1693 public Long getLastRecipientsRefresh() {
1694 return getKeyValueStore().getEntry(lastRecipientsRefresh);
1695 }
1696
1697 public void setLastRecipientsRefresh(final Long value) {
1698 getKeyValueStore().storeEntry(lastRecipientsRefresh, value);
1699 }
1700
1701 public ProfileKey getProfileKey() {
1702 return profileKey;
1703 }
1704
1705 public void setProfileKey(final ProfileKey profileKey) {
1706 if (profileKey.equals(this.profileKey)) {
1707 return;
1708 }
1709 this.profileKey = profileKey;
1710 save();
1711 }
1712
1713 public byte[] getSelfUnidentifiedAccessKey() {
1714 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1715 }
1716
1717 public boolean isRegistered() {
1718 return registered;
1719 }
1720
1721 public void setRegistered(final boolean registered) {
1722 this.registered = registered;
1723 save();
1724 }
1725
1726 public boolean isMultiDevice() {
1727 return isMultiDevice;
1728 }
1729
1730 public void setMultiDevice(final boolean multiDevice) {
1731 if (isMultiDevice == multiDevice) {
1732 return;
1733 }
1734 isMultiDevice = multiDevice;
1735 save();
1736 }
1737
1738 public long getLastReceiveTimestamp() {
1739 return getKeyValueStore().getEntry(lastReceiveTimestamp);
1740 }
1741
1742 public void setLastReceiveTimestamp(final long value) {
1743 getKeyValueStore().storeEntry(lastReceiveTimestamp, value);
1744 }
1745
1746 public void setNeedsToRetryFailedMessages(final boolean value) {
1747 getKeyValueStore().storeEntry(needsToRetryFailedMessages, value);
1748 }
1749
1750 public boolean getNeedsToRetryFailedMessages() {
1751 return getKeyValueStore().getEntry(needsToRetryFailedMessages);
1752 }
1753
1754 public boolean isUnrestrictedUnidentifiedAccess() {
1755 return Boolean.TRUE.equals(getKeyValueStore().getEntry(unrestrictedUnidentifiedAccess));
1756 }
1757
1758 public void setUnrestrictedUnidentifiedAccess(boolean value) {
1759 getKeyValueStore().storeEntry(unrestrictedUnidentifiedAccess, value);
1760 }
1761
1762 public boolean isDiscoverableByPhoneNumber() {
1763 final var phoneNumberUnlisted = getConfigurationStore().getPhoneNumberUnlisted();
1764 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1765 }
1766
1767 private void trustSelfIdentity(ServiceIdType serviceIdType) {
1768 final var accountData = getAccountData(serviceIdType);
1769 final var serviceId = accountData.getServiceId();
1770 final var identityKeyPair = accountData.getIdentityKeyPair();
1771 if (serviceId == null || identityKeyPair == null) {
1772 return;
1773 }
1774 final var publicKey = identityKeyPair.getPublicKey();
1775 getIdentityKeyStore().saveIdentity(serviceId, publicKey);
1776 getIdentityKeyStore().setIdentityTrustLevel(serviceId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1777 }
1778
1779 public void deleteAccountData() throws IOException {
1780 close();
1781 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1782 .sorted(Comparator.reverseOrder())) {
1783 for (final var file = files.iterator(); file.hasNext(); ) {
1784 Files.delete(file.next());
1785 }
1786 }
1787 Files.delete(getFileName(dataPath, accountPath).toPath());
1788 }
1789
1790 @Override
1791 public void close() {
1792 synchronized (fileChannel) {
1793 if (accountDatabase != null) {
1794 accountDatabase.close();
1795 }
1796 if (messageSendLogStore != null) {
1797 messageSendLogStore.close();
1798 }
1799 try {
1800 try {
1801 lock.close();
1802 } catch (ClosedChannelException ignored) {
1803 }
1804 fileChannel.close();
1805 } catch (IOException e) {
1806 logger.warn("Failed to close account: {}", e.getMessage(), e);
1807 }
1808 }
1809 }
1810
1811 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1812 var value = supplier.get();
1813 if (value != null) {
1814 return value;
1815 }
1816
1817 synchronized (LOCK) {
1818 value = supplier.get();
1819 if (value != null) {
1820 return value;
1821 }
1822 creator.call();
1823 return supplier.get();
1824 }
1825 }
1826
1827 private interface Callable {
1828
1829 void call();
1830 }
1831
1832 public static class PreKeyMetadata {
1833
1834 private int nextPreKeyId = 1;
1835 private int nextSignedPreKeyId = 1;
1836 private int activeSignedPreKeyId = -1;
1837 private int nextKyberPreKeyId = 1;
1838 private int activeLastResortKyberPreKeyId = -1;
1839
1840 public int getNextPreKeyId() {
1841 return nextPreKeyId;
1842 }
1843
1844 public int getNextSignedPreKeyId() {
1845 return nextSignedPreKeyId;
1846 }
1847
1848 public int getActiveSignedPreKeyId() {
1849 return activeSignedPreKeyId;
1850 }
1851
1852 public int getNextKyberPreKeyId() {
1853 return nextKyberPreKeyId;
1854 }
1855
1856 public int getActiveLastResortKyberPreKeyId() {
1857 return activeLastResortKyberPreKeyId;
1858 }
1859 }
1860
1861 public class AccountData<SERVICE_ID extends ServiceId> {
1862
1863 private final ServiceIdType serviceIdType;
1864 private SERVICE_ID serviceId;
1865 private IdentityKeyPair identityKeyPair;
1866 private int localRegistrationId;
1867 private final PreKeyMetadata preKeyMetadata = new PreKeyMetadata();
1868
1869 private SignalProtocolStore signalProtocolStore;
1870 private PreKeyStore preKeyStore;
1871 private SignedPreKeyStore signedPreKeyStore;
1872 private KyberPreKeyStore kyberPreKeyStore;
1873 private SessionStore sessionStore;
1874 private SignalIdentityKeyStore identityKeyStore;
1875
1876 private AccountData(final ServiceIdType serviceIdType) {
1877 this.serviceIdType = serviceIdType;
1878 }
1879
1880 public SERVICE_ID getServiceId() {
1881 return serviceId;
1882 }
1883
1884 private void setServiceId(final SERVICE_ID serviceId) {
1885 this.serviceId = serviceId;
1886 }
1887
1888 public IdentityKeyPair getIdentityKeyPair() {
1889 return identityKeyPair;
1890 }
1891
1892 private void setIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1893 this.identityKeyPair = identityKeyPair;
1894 }
1895
1896 public int getLocalRegistrationId() {
1897 return localRegistrationId;
1898 }
1899
1900 private void setLocalRegistrationId(final int localRegistrationId) {
1901 this.localRegistrationId = localRegistrationId;
1902 this.identityKeyStore = null;
1903 }
1904
1905 public PreKeyMetadata getPreKeyMetadata() {
1906 return preKeyMetadata;
1907 }
1908
1909 private SignalServiceAccountDataStore getSignalServiceAccountDataStore() {
1910 return getOrCreate(() -> signalProtocolStore,
1911 () -> signalProtocolStore = new SignalProtocolStore(getPreKeyStore(),
1912 getSignedPreKeyStore(),
1913 getKyberPreKeyStore(),
1914 getSessionStore(),
1915 getIdentityKeyStore(),
1916 getSenderKeyStore(),
1917 SignalAccount.this::isMultiDevice));
1918 }
1919
1920 public PreKeyStore getPreKeyStore() {
1921 return getOrCreate(() -> preKeyStore,
1922 () -> preKeyStore = new PreKeyStore(getAccountDatabase(), serviceIdType));
1923 }
1924
1925 public SignedPreKeyStore getSignedPreKeyStore() {
1926 return getOrCreate(() -> signedPreKeyStore,
1927 () -> signedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), serviceIdType));
1928 }
1929
1930 public KyberPreKeyStore getKyberPreKeyStore() {
1931 return getOrCreate(() -> kyberPreKeyStore,
1932 () -> kyberPreKeyStore = new KyberPreKeyStore(getAccountDatabase(), serviceIdType));
1933 }
1934
1935 public SessionStore getSessionStore() {
1936 return getOrCreate(() -> sessionStore,
1937 () -> sessionStore = new SessionStore(getAccountDatabase(), serviceIdType));
1938 }
1939
1940 public SignalIdentityKeyStore getIdentityKeyStore() {
1941 return getOrCreate(() -> identityKeyStore,
1942 () -> identityKeyStore = new SignalIdentityKeyStore(() -> identityKeyPair,
1943 localRegistrationId,
1944 SignalAccount.this.getIdentityKeyStore()));
1945 }
1946 }
1947
1948 public record Storage(
1949 int version,
1950 long timestamp,
1951 String serviceEnvironment,
1952 boolean registered,
1953 String number,
1954 String username,
1955 String encryptedDeviceName,
1956 int deviceId,
1957 boolean isMultiDevice,
1958 String password,
1959 AccountData aciAccountData,
1960 AccountData pniAccountData,
1961 String registrationLockPin,
1962 String pinMasterKey,
1963 String storageKey,
1964 String accountEntropyPool,
1965 String mediaRootBackupKey,
1966 String profileKey,
1967 String usernameLinkEntropy,
1968 String usernameLinkServerId
1969 ) {
1970
1971 public record AccountData(
1972 String serviceId,
1973 int registrationId,
1974 String identityPrivateKey,
1975 String identityPublicKey,
1976
1977 int nextPreKeyId,
1978 int nextSignedPreKeyId,
1979 int activeSignedPreKeyId,
1980 int nextKyberPreKeyId,
1981 int activeLastResortKyberPreKeyId
1982 ) {
1983
1984 private static AccountData from(final SignalAccount.AccountData<?> accountData) {
1985 final var base64 = Base64.getEncoder();
1986 final var preKeyMetadata = accountData.getPreKeyMetadata();
1987 return new AccountData(accountData.getServiceId() == null
1988 ? null
1989 : accountData.getServiceId().toString(),
1990 accountData.getLocalRegistrationId(),
1991 accountData.getIdentityKeyPair() == null
1992 ? null
1993 : base64.encodeToString(accountData.getIdentityKeyPair().getPrivateKey().serialize()),
1994 accountData.getIdentityKeyPair() == null
1995 ? null
1996 : base64.encodeToString(accountData.getIdentityKeyPair().getPublicKey().serialize()),
1997 preKeyMetadata.getNextPreKeyId(),
1998 preKeyMetadata.getNextSignedPreKeyId(),
1999 preKeyMetadata.getActiveSignedPreKeyId(),
2000 preKeyMetadata.getNextKyberPreKeyId(),
2001 preKeyMetadata.getActiveLastResortKyberPreKeyId());
2002 }
2003 }
2004 }
2005 }