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