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