]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
ee6f41678f1742f0e058cc369c1cf7575dc8e136
[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 }
1342
1343 public ServiceEnvironment getServiceEnvironment() {
1344 return serviceEnvironment;
1345 }
1346
1347 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1348 this.serviceEnvironment = serviceEnvironment;
1349 save();
1350 }
1351
1352 public AccountAttributes getAccountAttributes(String registrationLock) {
1353 return new AccountAttributes(null,
1354 aciAccountData.getLocalRegistrationId(),
1355 false,
1356 false,
1357 true,
1358 registrationLock != null ? registrationLock : getRegistrationLock(),
1359 getSelfUnidentifiedAccessKey(),
1360 isUnrestrictedUnidentifiedAccess(),
1361 isDiscoverableByPhoneNumber(),
1362 getAccountCapabilities(),
1363 encryptedDeviceName,
1364 pniAccountData.getLocalRegistrationId(),
1365 getRecoveryPassword());
1366 }
1367
1368 public AccountAttributes.Capabilities getAccountCapabilities() {
1369 return getCapabilities(isPrimaryDevice());
1370 }
1371
1372 public ServiceId getAccountId(ServiceIdType serviceIdType) {
1373 return getAccountData(serviceIdType).getServiceId();
1374 }
1375
1376 public ACI getAci() {
1377 return aciAccountData.getServiceId();
1378 }
1379
1380 public void setAci(final ACI aci) {
1381 this.aciAccountData.setServiceId(aci);
1382 save();
1383 }
1384
1385 public PNI getPni() {
1386 return pniAccountData.getServiceId();
1387 }
1388
1389 public void setPni(final PNI updatedPni) {
1390 final var oldPni = pniAccountData.getServiceId();
1391 if (oldPni != null && !oldPni.equals(updatedPni)) {
1392 // Clear data for old PNI
1393 identityKeyStore.deleteIdentity(oldPni);
1394 }
1395
1396 this.pniAccountData.setServiceId(updatedPni);
1397 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1398 trustSelfIdentity(ServiceIdType.PNI);
1399 save();
1400 }
1401
1402 public void setNewPniIdentity(
1403 final IdentityKeyPair pniIdentityKeyPair,
1404 final SignedPreKeyRecord pniSignedPreKey,
1405 final KyberPreKeyRecord lastResortKyberPreKey,
1406 final int localPniRegistrationId
1407 ) {
1408 setPniIdentityKeyPair(pniIdentityKeyPair);
1409 pniAccountData.setLocalRegistrationId(localPniRegistrationId);
1410
1411 final AccountData<? extends ServiceId> accountData = getAccountData(ServiceIdType.PNI);
1412 final var preKeyMetadata = accountData.getPreKeyMetadata();
1413 preKeyMetadata.nextSignedPreKeyId = pniSignedPreKey.getId();
1414 accountData.getSignedPreKeyStore().removeSignedPreKey(pniSignedPreKey.getId());
1415 addSignedPreKey(ServiceIdType.PNI, pniSignedPreKey);
1416 if (lastResortKyberPreKey != null) {
1417 preKeyMetadata.nextKyberPreKeyId = lastResortKyberPreKey.getId();
1418 accountData.getKyberPreKeyStore().removeKyberPreKey(lastResortKyberPreKey.getId());
1419 addLastResortKyberPreKey(ServiceIdType.PNI, lastResortKyberPreKey);
1420 }
1421 save();
1422 }
1423
1424 public SignalServiceAddress getSelfAddress() {
1425 return new SignalServiceAddress(getAci(), number);
1426 }
1427
1428 public RecipientAddress getSelfRecipientAddress() {
1429 return new RecipientAddress(getAci(), getPni(), number, username);
1430 }
1431
1432 public RecipientId getSelfRecipientId() {
1433 return selfRecipientId;
1434 }
1435
1436 public String getSessionId(final String forNumber) {
1437 final var keyValueStore = getKeyValueStore();
1438 final var sessionNumber = keyValueStore.getEntry(verificationSessionNumber);
1439 if (!forNumber.equals(sessionNumber)) {
1440 return null;
1441 }
1442 return keyValueStore.getEntry(verificationSessionId);
1443 }
1444
1445 public void setSessionId(final String sessionNumber, final String sessionId) {
1446 final var keyValueStore = getKeyValueStore();
1447 keyValueStore.storeEntry(verificationSessionNumber, sessionNumber);
1448 keyValueStore.storeEntry(verificationSessionId, sessionId);
1449 }
1450
1451 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1452 this.encryptedDeviceName = encryptedDeviceName;
1453 save();
1454 }
1455
1456 public int getDeviceId() {
1457 return deviceId;
1458 }
1459
1460 public boolean isPrimaryDevice() {
1461 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1462 }
1463
1464 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1465 return getAccountData(serviceIdType).getIdentityKeyPair();
1466 }
1467
1468 public IdentityKeyPair getAciIdentityKeyPair() {
1469 return aciAccountData.getIdentityKeyPair();
1470 }
1471
1472 public IdentityKeyPair getPniIdentityKeyPair() {
1473 return pniAccountData.getIdentityKeyPair();
1474 }
1475
1476 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1477 pniAccountData.setIdentityKeyPair(identityKeyPair);
1478 trustSelfIdentity(ServiceIdType.PNI);
1479 save();
1480 }
1481
1482 public String getPassword() {
1483 return password;
1484 }
1485
1486 public void setRegistrationLockPin(final String registrationLockPin) {
1487 this.registrationLockPin = registrationLockPin;
1488 save();
1489 }
1490
1491 public String getRegistrationLockPin() {
1492 return registrationLockPin;
1493 }
1494
1495 public String getRegistrationLock() {
1496 final var masterKey = getPinBackedMasterKey();
1497 if (masterKey == null) {
1498 return null;
1499 }
1500 return masterKey.deriveRegistrationLock();
1501 }
1502
1503 public MasterKey getPinBackedMasterKey() {
1504 if (registrationLockPin == null) {
1505 return null;
1506 }
1507 return pinMasterKey;
1508 }
1509
1510 public MasterKey getOrCreatePinMasterKey() {
1511 if (pinMasterKey == null) {
1512 pinMasterKey = KeyUtils.createMasterKey();
1513 save();
1514 }
1515 return pinMasterKey;
1516 }
1517
1518 public void setMasterKey(MasterKey masterKey) {
1519 if (isPrimaryDevice()) {
1520 return;
1521 }
1522 this.pinMasterKey = masterKey;
1523 save();
1524 }
1525
1526 public StorageKey getOrCreateStorageKey() {
1527 if (pinMasterKey != null) {
1528 return pinMasterKey.deriveStorageServiceKey();
1529 } else if (storageKey != null) {
1530 return storageKey;
1531 } else if (!isPrimaryDevice() || !isMultiDevice()) {
1532 // Only upload storage, if a pin master key already exists or linked devices exist
1533 return null;
1534 }
1535
1536 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1537 }
1538
1539 public void setStorageKey(final StorageKey storageKey) {
1540 if (isPrimaryDevice() || storageKey.equals(this.storageKey)) {
1541 return;
1542 }
1543 this.storageKey = storageKey;
1544 save();
1545 }
1546
1547 public String getRecoveryPassword() {
1548 final var masterKey = getPinBackedMasterKey();
1549 if (masterKey == null) {
1550 return null;
1551 }
1552 return masterKey.deriveRegistrationRecoveryPassword();
1553 }
1554
1555 public long getStorageManifestVersion() {
1556 return getKeyValueStore().getEntry(storageManifestVersion);
1557 }
1558
1559 public void setStorageManifestVersion(final long value) {
1560 getKeyValueStore().storeEntry(storageManifestVersion, value);
1561 }
1562
1563 public Optional<SignalStorageManifest> getStorageManifest() {
1564 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1565 if (!storageManifestFile.exists()) {
1566 return Optional.empty();
1567 }
1568 try (var inputStream = new FileInputStream(storageManifestFile)) {
1569 return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
1570 } catch (IOException e) {
1571 logger.warn("Failed to read local storage manifest.", e);
1572 return Optional.empty();
1573 }
1574 }
1575
1576 public void setStorageManifest(SignalStorageManifest manifest) {
1577 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1578 if (manifest == null) {
1579 if (storageManifestFile.exists()) {
1580 try {
1581 Files.delete(storageManifestFile.toPath());
1582 } catch (IOException e) {
1583 logger.error("Failed to delete local storage manifest.", e);
1584 }
1585 }
1586 return;
1587 }
1588
1589 final var manifestBytes = manifest.serialize();
1590 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1591 outputStream.write(manifestBytes);
1592 } catch (IOException e) {
1593 logger.error("Failed to store local storage manifest.", e);
1594 }
1595 }
1596
1597 public byte[] getCdsiToken() {
1598 return getKeyValueStore().getEntry(cdsiToken);
1599 }
1600
1601 public void setCdsiToken(final byte[] value) {
1602 getKeyValueStore().storeEntry(cdsiToken, value);
1603 }
1604
1605 public Long getLastRecipientsRefresh() {
1606 return getKeyValueStore().getEntry(lastRecipientsRefresh);
1607 }
1608
1609 public void setLastRecipientsRefresh(final Long value) {
1610 getKeyValueStore().storeEntry(lastRecipientsRefresh, value);
1611 }
1612
1613 public ProfileKey getProfileKey() {
1614 return profileKey;
1615 }
1616
1617 public void setProfileKey(final ProfileKey profileKey) {
1618 if (profileKey.equals(this.profileKey)) {
1619 return;
1620 }
1621 this.profileKey = profileKey;
1622 save();
1623 }
1624
1625 public byte[] getSelfUnidentifiedAccessKey() {
1626 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1627 }
1628
1629 public boolean isRegistered() {
1630 return registered;
1631 }
1632
1633 public void setRegistered(final boolean registered) {
1634 this.registered = registered;
1635 save();
1636 }
1637
1638 public boolean isMultiDevice() {
1639 return isMultiDevice;
1640 }
1641
1642 public void setMultiDevice(final boolean multiDevice) {
1643 if (isMultiDevice == multiDevice) {
1644 return;
1645 }
1646 isMultiDevice = multiDevice;
1647 save();
1648 }
1649
1650 public long getLastReceiveTimestamp() {
1651 return getKeyValueStore().getEntry(lastReceiveTimestamp);
1652 }
1653
1654 public void setLastReceiveTimestamp(final long value) {
1655 getKeyValueStore().storeEntry(lastReceiveTimestamp, value);
1656 }
1657
1658 public void setNeedsToRetryFailedMessages(final boolean value) {
1659 getKeyValueStore().storeEntry(needsToRetryFailedMessages, value);
1660 }
1661
1662 public boolean getNeedsToRetryFailedMessages() {
1663 return getKeyValueStore().getEntry(needsToRetryFailedMessages);
1664 }
1665
1666 public boolean isUnrestrictedUnidentifiedAccess() {
1667 return Boolean.TRUE.equals(getKeyValueStore().getEntry(unrestrictedUnidentifiedAccess));
1668 }
1669
1670 public void setUnrestrictedUnidentifiedAccess(boolean value) {
1671 getKeyValueStore().storeEntry(unrestrictedUnidentifiedAccess, value);
1672 }
1673
1674 public boolean isDiscoverableByPhoneNumber() {
1675 final var phoneNumberUnlisted = getConfigurationStore().getPhoneNumberUnlisted();
1676 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1677 }
1678
1679 private void trustSelfIdentity(ServiceIdType serviceIdType) {
1680 final var accountData = getAccountData(serviceIdType);
1681 final var serviceId = accountData.getServiceId();
1682 final var identityKeyPair = accountData.getIdentityKeyPair();
1683 if (serviceId == null || identityKeyPair == null) {
1684 return;
1685 }
1686 final var publicKey = identityKeyPair.getPublicKey();
1687 getIdentityKeyStore().saveIdentity(serviceId, publicKey);
1688 getIdentityKeyStore().setIdentityTrustLevel(serviceId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1689 }
1690
1691 public void deleteAccountData() throws IOException {
1692 close();
1693 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1694 .sorted(Comparator.reverseOrder())) {
1695 for (final var file = files.iterator(); file.hasNext(); ) {
1696 Files.delete(file.next());
1697 }
1698 }
1699 Files.delete(getFileName(dataPath, accountPath).toPath());
1700 }
1701
1702 @Override
1703 public void close() {
1704 synchronized (fileChannel) {
1705 if (accountDatabase != null) {
1706 accountDatabase.close();
1707 }
1708 if (messageSendLogStore != null) {
1709 messageSendLogStore.close();
1710 }
1711 try {
1712 try {
1713 lock.close();
1714 } catch (ClosedChannelException ignored) {
1715 }
1716 fileChannel.close();
1717 } catch (IOException e) {
1718 logger.warn("Failed to close account: {}", e.getMessage(), e);
1719 }
1720 }
1721 }
1722
1723 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1724 var value = supplier.get();
1725 if (value != null) {
1726 return value;
1727 }
1728
1729 synchronized (LOCK) {
1730 value = supplier.get();
1731 if (value != null) {
1732 return value;
1733 }
1734 creator.call();
1735 return supplier.get();
1736 }
1737 }
1738
1739 private interface Callable {
1740
1741 void call();
1742 }
1743
1744 public static class PreKeyMetadata {
1745
1746 private int nextPreKeyId = 1;
1747 private int nextSignedPreKeyId = 1;
1748 private int activeSignedPreKeyId = -1;
1749 private int nextKyberPreKeyId = 1;
1750 private int activeLastResortKyberPreKeyId = -1;
1751
1752 public int getNextPreKeyId() {
1753 return nextPreKeyId;
1754 }
1755
1756 public int getNextSignedPreKeyId() {
1757 return nextSignedPreKeyId;
1758 }
1759
1760 public int getActiveSignedPreKeyId() {
1761 return activeSignedPreKeyId;
1762 }
1763
1764 public int getNextKyberPreKeyId() {
1765 return nextKyberPreKeyId;
1766 }
1767
1768 public int getActiveLastResortKyberPreKeyId() {
1769 return activeLastResortKyberPreKeyId;
1770 }
1771 }
1772
1773 public class AccountData<SERVICE_ID extends ServiceId> {
1774
1775 private final ServiceIdType serviceIdType;
1776 private SERVICE_ID serviceId;
1777 private IdentityKeyPair identityKeyPair;
1778 private int localRegistrationId;
1779 private final PreKeyMetadata preKeyMetadata = new PreKeyMetadata();
1780
1781 private SignalProtocolStore signalProtocolStore;
1782 private PreKeyStore preKeyStore;
1783 private SignedPreKeyStore signedPreKeyStore;
1784 private KyberPreKeyStore kyberPreKeyStore;
1785 private SessionStore sessionStore;
1786 private SignalIdentityKeyStore identityKeyStore;
1787
1788 private AccountData(final ServiceIdType serviceIdType) {
1789 this.serviceIdType = serviceIdType;
1790 }
1791
1792 public SERVICE_ID getServiceId() {
1793 return serviceId;
1794 }
1795
1796 private void setServiceId(final SERVICE_ID serviceId) {
1797 this.serviceId = serviceId;
1798 }
1799
1800 public IdentityKeyPair getIdentityKeyPair() {
1801 return identityKeyPair;
1802 }
1803
1804 private void setIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1805 this.identityKeyPair = identityKeyPair;
1806 }
1807
1808 public int getLocalRegistrationId() {
1809 return localRegistrationId;
1810 }
1811
1812 private void setLocalRegistrationId(final int localRegistrationId) {
1813 this.localRegistrationId = localRegistrationId;
1814 this.identityKeyStore = null;
1815 }
1816
1817 public PreKeyMetadata getPreKeyMetadata() {
1818 return preKeyMetadata;
1819 }
1820
1821 private SignalServiceAccountDataStore getSignalServiceAccountDataStore() {
1822 return getOrCreate(() -> signalProtocolStore,
1823 () -> signalProtocolStore = new SignalProtocolStore(getPreKeyStore(),
1824 getSignedPreKeyStore(),
1825 getKyberPreKeyStore(),
1826 getSessionStore(),
1827 getIdentityKeyStore(),
1828 getSenderKeyStore(),
1829 SignalAccount.this::isMultiDevice));
1830 }
1831
1832 public PreKeyStore getPreKeyStore() {
1833 return getOrCreate(() -> preKeyStore,
1834 () -> preKeyStore = new PreKeyStore(getAccountDatabase(), serviceIdType));
1835 }
1836
1837 public SignedPreKeyStore getSignedPreKeyStore() {
1838 return getOrCreate(() -> signedPreKeyStore,
1839 () -> signedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), serviceIdType));
1840 }
1841
1842 public KyberPreKeyStore getKyberPreKeyStore() {
1843 return getOrCreate(() -> kyberPreKeyStore,
1844 () -> kyberPreKeyStore = new KyberPreKeyStore(getAccountDatabase(), serviceIdType));
1845 }
1846
1847 public SessionStore getSessionStore() {
1848 return getOrCreate(() -> sessionStore,
1849 () -> sessionStore = new SessionStore(getAccountDatabase(), serviceIdType));
1850 }
1851
1852 public SignalIdentityKeyStore getIdentityKeyStore() {
1853 return getOrCreate(() -> identityKeyStore,
1854 () -> identityKeyStore = new SignalIdentityKeyStore(() -> identityKeyPair,
1855 localRegistrationId,
1856 SignalAccount.this.getIdentityKeyStore()));
1857 }
1858 }
1859
1860 public record Storage(
1861 int version,
1862 long timestamp,
1863 String serviceEnvironment,
1864 boolean registered,
1865 String number,
1866 String username,
1867 String encryptedDeviceName,
1868 int deviceId,
1869 boolean isMultiDevice,
1870 String password,
1871 AccountData aciAccountData,
1872 AccountData pniAccountData,
1873 String registrationLockPin,
1874 String pinMasterKey,
1875 String storageKey,
1876 String profileKey,
1877 String usernameLinkEntropy,
1878 String usernameLinkServerId
1879 ) {
1880
1881 public record AccountData(
1882 String serviceId,
1883 int registrationId,
1884 String identityPrivateKey,
1885 String identityPublicKey,
1886
1887 int nextPreKeyId,
1888 int nextSignedPreKeyId,
1889 int activeSignedPreKeyId,
1890 int nextKyberPreKeyId,
1891 int activeLastResortKyberPreKeyId
1892 ) {
1893
1894 private static AccountData from(final SignalAccount.AccountData<?> accountData) {
1895 final var base64 = Base64.getEncoder();
1896 final var preKeyMetadata = accountData.getPreKeyMetadata();
1897 return new AccountData(accountData.getServiceId() == null
1898 ? null
1899 : accountData.getServiceId().toString(),
1900 accountData.getLocalRegistrationId(),
1901 accountData.getIdentityKeyPair() == null
1902 ? null
1903 : base64.encodeToString(accountData.getIdentityKeyPair().getPrivateKey().serialize()),
1904 accountData.getIdentityKeyPair() == null
1905 ? null
1906 : base64.encodeToString(accountData.getIdentityKeyPair().getPublicKey().serialize()),
1907 preKeyMetadata.getNextPreKeyId(),
1908 preKeyMetadata.getNextSignedPreKeyId(),
1909 preKeyMetadata.getActiveSignedPreKeyId(),
1910 preKeyMetadata.getNextKyberPreKeyId(),
1911 preKeyMetadata.getActiveLastResortKyberPreKeyId());
1912 }
1913 }
1914 }
1915 }