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