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