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