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