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