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