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