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