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