]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
60b53b24e8735d71afd3160b9164a96bc3df223d
[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.initAllPreKeyIds();
232 signalAccount.settings = settings;
233
234 signalAccount.registered = false;
235
236 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
237 signalAccount.migrateLegacyConfigs();
238 signalAccount.save();
239
240 return signalAccount;
241 }
242
243 public static SignalAccount createLinkedAccount(
244 final File dataPath,
245 final String accountPath,
246 final ServiceEnvironment serviceEnvironment,
247 final Settings settings
248 ) throws IOException {
249 IOUtils.createPrivateDirectories(dataPath);
250 var fileName = getFileName(dataPath, accountPath);
251 IOUtils.createPrivateFile(fileName);
252
253 final var pair = openFileChannel(fileName, true);
254 final var signalAccount = new SignalAccount(pair.first(), pair.second());
255
256 signalAccount.dataPath = dataPath;
257 signalAccount.accountPath = accountPath;
258 signalAccount.serviceEnvironment = serviceEnvironment;
259 signalAccount.aciAccountData.setLocalRegistrationId(KeyHelper.generateRegistrationId(false));
260 signalAccount.pniAccountData.setLocalRegistrationId(KeyHelper.generateRegistrationId(false));
261 signalAccount.settings = settings;
262
263 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
264
265 return signalAccount;
266 }
267
268 public void setProvisioningData(
269 final String number,
270 final ACI aci,
271 final PNI pni,
272 final String password,
273 final String encryptedDeviceName,
274 final IdentityKeyPair aciIdentity,
275 final IdentityKeyPair pniIdentity,
276 final ProfileKey profileKey
277 ) {
278 this.deviceId = 0;
279 this.number = number;
280 this.aciAccountData.setServiceId(aci);
281 this.pniAccountData.setServiceId(pni);
282 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
283 this.password = password;
284 this.profileKey = profileKey;
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
657 if (previousStorageVersion < 5) {
658 final var legacyRecipientsStoreFile = new File(userPath, "recipients-store");
659 if (legacyRecipientsStoreFile.exists()) {
660 LegacyRecipientStore2.migrate(legacyRecipientsStoreFile, getRecipientStore());
661 }
662 }
663 if (previousStorageVersion < 6) {
664 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
665 }
666 final var legacyAciPreKeysPath = new File(userPath, "pre-keys");
667 if (legacyAciPreKeysPath.exists()) {
668 LegacyPreKeyStore.migrate(legacyAciPreKeysPath, aciAccountData.getPreKeyStore());
669 }
670 final var legacyPniPreKeysPath = new File(userPath, "pre-keys-pni");
671 if (legacyPniPreKeysPath.exists()) {
672 LegacyPreKeyStore.migrate(legacyPniPreKeysPath, pniAccountData.getPreKeyStore());
673 }
674 final var legacyAciSignedPreKeysPath = new File(userPath, "signed-pre-keys");
675 if (legacyAciSignedPreKeysPath.exists()) {
676 LegacySignedPreKeyStore.migrate(legacyAciSignedPreKeysPath, aciAccountData.getSignedPreKeyStore());
677 }
678 final var legacyPniSignedPreKeysPath = new File(userPath, "signed-pre-keys-pni");
679 if (legacyPniSignedPreKeysPath.exists()) {
680 LegacySignedPreKeyStore.migrate(legacyPniSignedPreKeysPath, pniAccountData.getSignedPreKeyStore());
681 }
682 final var legacySessionsPath = new File(userPath, "sessions");
683 if (legacySessionsPath.exists()) {
684 LegacySessionStore.migrate(legacySessionsPath,
685 getRecipientResolver(),
686 getRecipientAddressResolver(),
687 aciAccountData.getSessionStore());
688 }
689 final var legacyIdentitiesPath = new File(userPath, "identities");
690 if (legacyIdentitiesPath.exists()) {
691 LegacyIdentityKeyStore.migrate(legacyIdentitiesPath,
692 getRecipientResolver(),
693 getRecipientAddressResolver(),
694 getIdentityKeyStore());
695 }
696 final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
697 ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
698 LegacyJsonSignalProtocolStore.class)
699 : null;
700 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
701 aciIdentityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
702 registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
703 }
704
705 this.aciAccountData.setIdentityKeyPair(aciIdentityKeyPair);
706 this.aciAccountData.setLocalRegistrationId(registrationId);
707
708 loadLegacyStores(rootNode, legacySignalProtocolStore);
709
710 final var legacySenderKeysPath = new File(userPath, "sender-keys");
711 if (legacySenderKeysPath.exists()) {
712 LegacySenderKeyRecordStore.migrate(legacySenderKeysPath,
713 getRecipientResolver(),
714 getRecipientAddressResolver(),
715 getSenderKeyStore());
716 }
717 final var legacySenderKeysSharedPath = new File(userPath, "shared-sender-keys-store");
718 if (legacySenderKeysSharedPath.exists()) {
719 LegacySenderKeySharedStore.migrate(legacySenderKeysSharedPath,
720 getRecipientResolver(),
721 getRecipientAddressResolver(),
722 getSenderKeyStore());
723 }
724 if (rootNode.hasNonNull("groupStore")) {
725 final var groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"),
726 LegacyGroupStore.Storage.class);
727 LegacyGroupStore.migrate(groupStoreStorage,
728 new File(userPath, "group-cache"),
729 getRecipientResolver(),
730 getGroupStore());
731 }
732
733 if (rootNode.hasNonNull("stickerStore")) {
734 final var storage = jsonProcessor.convertValue(rootNode.get("stickerStore"),
735 LegacyStickerStore.Storage.class);
736 LegacyStickerStore.migrate(storage, getStickerStore());
737 }
738
739 if (rootNode.hasNonNull("configurationStore")) {
740 final var configurationStoreStorage = jsonProcessor.convertValue(rootNode.get("configurationStore"),
741 LegacyConfigurationStore.Storage.class);
742 LegacyConfigurationStore.migrate(configurationStoreStorage, getConfigurationStore());
743 }
744
745 loadLegacyThreadStore(rootNode);
746 }
747
748 private void loadLegacyStores(
749 final JsonNode rootNode, final LegacyJsonSignalProtocolStore legacySignalProtocolStore
750 ) {
751 var legacyRecipientStoreNode = rootNode.get("recipientStore");
752 if (legacyRecipientStoreNode != null) {
753 logger.debug("Migrating legacy recipient store.");
754 var legacyRecipientStore = jsonProcessor.convertValue(legacyRecipientStoreNode, LegacyRecipientStore.class);
755 if (legacyRecipientStore != null) {
756 legacyRecipientStore.getAddresses()
757 .forEach(recipient -> getRecipientStore().resolveRecipientTrusted(recipient));
758 }
759 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
760 }
761
762 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
763 logger.debug("Migrating legacy pre key store.");
764 for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
765 try {
766 aciAccountData.getPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
767 } catch (InvalidMessageException e) {
768 logger.warn("Failed to migrate pre key, ignoring", e);
769 }
770 }
771 }
772
773 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
774 logger.debug("Migrating legacy signed pre key store.");
775 for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
776 try {
777 aciAccountData.getSignedPreKeyStore()
778 .storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue()));
779 } catch (InvalidMessageException e) {
780 logger.warn("Failed to migrate signed pre key, ignoring", e);
781 }
782 }
783 }
784
785 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
786 logger.debug("Migrating legacy session store.");
787 for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
788 try {
789 aciAccountData.getSessionStore()
790 .storeSession(new SignalProtocolAddress(session.address.getIdentifier(), session.deviceId),
791 new SessionRecord(session.sessionRecord));
792 } catch (Exception e) {
793 logger.warn("Failed to migrate session, ignoring", e);
794 }
795 }
796 }
797
798 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
799 logger.debug("Migrating legacy identity session store.");
800 for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
801 if (identity.getAddress().serviceId().isEmpty()) {
802 continue;
803 }
804 final var serviceId = identity.getAddress().serviceId().get();
805 getIdentityKeyStore().saveIdentity(serviceId, identity.getIdentityKey());
806 getIdentityKeyStore().setIdentityTrustLevel(serviceId,
807 identity.getIdentityKey(),
808 identity.getTrustLevel());
809 }
810 }
811
812 if (rootNode.hasNonNull("contactStore")) {
813 logger.debug("Migrating legacy contact store.");
814 final var contactStoreNode = rootNode.get("contactStore");
815 final var contactStore = jsonProcessor.convertValue(contactStoreNode, LegacyJsonContactsStore.class);
816 for (var contact : contactStore.getContacts()) {
817 final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
818 getContactStore().storeContact(recipientId,
819 new Contact(contact.name,
820 null,
821 contact.color,
822 contact.messageExpirationTime,
823 contact.blocked,
824 contact.archived,
825 false));
826
827 // Store profile keys only in profile store
828 var profileKeyString = contact.profileKey;
829 if (profileKeyString != null) {
830 final ProfileKey profileKey;
831 try {
832 profileKey = new ProfileKey(Base64.getDecoder().decode(profileKeyString));
833 getProfileStore().storeProfileKey(recipientId, profileKey);
834 } catch (InvalidInputException e) {
835 logger.warn("Failed to parse legacy contact profile key: {}", e.getMessage());
836 }
837 }
838 }
839 }
840
841 if (rootNode.hasNonNull("profileStore")) {
842 logger.debug("Migrating legacy profile store.");
843 var profileStoreNode = rootNode.get("profileStore");
844 final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
845 for (var profileEntry : legacyProfileStore.getProfileEntries()) {
846 var recipientId = getRecipientResolver().resolveRecipient(profileEntry.address());
847 // Not migrating profile key credential here, it was changed to expiring profile key credentials
848 getProfileStore().storeProfileKey(recipientId, profileEntry.profileKey());
849 final var profile = profileEntry.profile();
850 if (profile != null) {
851 final var capabilities = new HashSet<Profile.Capability>();
852 if (profile.getCapabilities() != null) {
853 if (profile.getCapabilities().gv1Migration) {
854 capabilities.add(Profile.Capability.gv1Migration);
855 }
856 if (profile.getCapabilities().storage) {
857 capabilities.add(Profile.Capability.storage);
858 }
859 }
860 final var newProfile = new Profile(profileEntry.lastUpdateTimestamp(),
861 profile.getGivenName(),
862 profile.getFamilyName(),
863 profile.getAbout(),
864 profile.getAboutEmoji(),
865 null,
866 null,
867 profile.isUnrestrictedUnidentifiedAccess()
868 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
869 : profile.getUnidentifiedAccess() != null
870 ? Profile.UnidentifiedAccessMode.ENABLED
871 : Profile.UnidentifiedAccessMode.DISABLED,
872 capabilities);
873 getProfileStore().storeProfile(recipientId, newProfile);
874 }
875 }
876 }
877 }
878
879 private void loadLegacyThreadStore(final JsonNode rootNode) {
880 var threadStoreNode = rootNode.get("threadStore");
881 if (threadStoreNode != null && !threadStoreNode.isNull()) {
882 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
883 // Migrate thread info to group and contact store
884 for (var thread : threadStore.getThreads()) {
885 if (thread.id == null || thread.id.isEmpty()) {
886 continue;
887 }
888 try {
889 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
890 final var recipientId = getRecipientResolver().resolveRecipient(thread.id);
891 var contact = getContactStore().getContact(recipientId);
892 if (contact != null) {
893 getContactStore().storeContact(recipientId,
894 Contact.newBuilder(contact)
895 .withMessageExpirationTime(thread.messageExpirationTime)
896 .build());
897 }
898 } else {
899 var groupInfo = getGroupStore().getGroup(GroupId.fromBase64(thread.id));
900 if (groupInfo instanceof GroupInfoV1) {
901 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
902 getGroupStore().updateGroup(groupInfo);
903 }
904 }
905 } catch (Exception e) {
906 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
907 }
908 }
909 }
910 }
911
912 private void save() {
913 synchronized (fileChannel) {
914 final var base64 = Base64.getEncoder();
915 final var storage = new Storage(CURRENT_STORAGE_VERSION,
916 serviceEnvironment.name(),
917 registered,
918 number,
919 username,
920 encryptedDeviceName,
921 deviceId,
922 isMultiDevice,
923 password,
924 Storage.AccountData.from(aciAccountData),
925 Storage.AccountData.from(pniAccountData),
926 registrationLockPin,
927 pinMasterKey == null ? null : base64.encodeToString(pinMasterKey.serialize()),
928 storageKey == null ? null : base64.encodeToString(storageKey.serialize()),
929 profileKey == null ? null : base64.encodeToString(profileKey.serialize()));
930 try {
931 try (var output = new ByteArrayOutputStream()) {
932 // Write to memory first to prevent corrupting the file in case of serialization errors
933 jsonProcessor.writeValue(output, storage);
934 var input = new ByteArrayInputStream(output.toByteArray());
935 fileChannel.position(0);
936 input.transferTo(Channels.newOutputStream(fileChannel));
937 fileChannel.truncate(fileChannel.position());
938 fileChannel.force(false);
939 }
940 } catch (Exception e) {
941 logger.error("Error saving file: {}", e.getMessage(), e);
942 }
943 }
944 }
945
946 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
947 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
948 try {
949 var lock = fileChannel.tryLock();
950 if (lock == null) {
951 if (!waitForLock) {
952 logger.debug("Config file is in use by another instance.");
953 throw new IOException("Config file is in use by another instance.");
954 }
955 logger.info("Config file is in use by another instance, waiting…");
956 lock = fileChannel.lock();
957 logger.info("Config file lock acquired.");
958 }
959 final var result = new Pair<>(fileChannel, lock);
960 fileChannel = null;
961 return result;
962 } finally {
963 if (fileChannel != null) {
964 fileChannel.close();
965 }
966 }
967 }
968
969 private void clearAllPreKeys() {
970 clearAllPreKeys(ServiceIdType.ACI);
971 clearAllPreKeys(ServiceIdType.PNI);
972 }
973
974 private void initAllPreKeyIds() {
975 resetPreKeyOffsets(ServiceIdType.ACI);
976 resetPreKeyOffsets(ServiceIdType.PNI);
977 resetKyberPreKeyOffsets(ServiceIdType.ACI);
978 resetKyberPreKeyOffsets(ServiceIdType.PNI);
979 }
980
981 private void clearAllPreKeys(ServiceIdType serviceIdType) {
982 final var accountData = getAccountData(serviceIdType);
983 resetPreKeyOffsets(serviceIdType);
984 resetKyberPreKeyOffsets(serviceIdType);
985 accountData.getPreKeyStore().removeAllPreKeys();
986 accountData.getSignedPreKeyStore().removeAllSignedPreKeys();
987 accountData.getKyberPreKeyStore().removeAllKyberPreKeys();
988 save();
989 }
990
991 private void setPreKeys(ServiceIdType serviceIdType, PreKeyCollection preKeyCollection) {
992 final var accountData = getAccountData(serviceIdType);
993 final var preKeyMetadata = accountData.getPreKeyMetadata();
994 preKeyMetadata.nextSignedPreKeyId = preKeyCollection.getSignedPreKey().getId();
995 preKeyMetadata.nextKyberPreKeyId = preKeyCollection.getLastResortKyberPreKey().getId();
996
997 accountData.getPreKeyStore().removeAllPreKeys();
998 accountData.getSignedPreKeyStore().removeAllSignedPreKeys();
999 accountData.getKyberPreKeyStore().removeAllKyberPreKeys();
1000
1001 addSignedPreKey(serviceIdType, preKeyCollection.getSignedPreKey());
1002 addLastResortKyberPreKey(serviceIdType, preKeyCollection.getLastResortKyberPreKey());
1003
1004 save();
1005 }
1006
1007 public void resetPreKeyOffsets(final ServiceIdType serviceIdType) {
1008 final var preKeyMetadata = getAccountData(serviceIdType).getPreKeyMetadata();
1009 preKeyMetadata.nextPreKeyId = getRandomPreKeyIdOffset();
1010 preKeyMetadata.nextSignedPreKeyId = getRandomPreKeyIdOffset();
1011 preKeyMetadata.activeSignedPreKeyId = -1;
1012 save();
1013 }
1014
1015 private static int getRandomPreKeyIdOffset() {
1016 return KeyUtils.getRandomInt(PREKEY_MAXIMUM_ID);
1017 }
1018
1019 public void addPreKeys(ServiceIdType serviceIdType, List<PreKeyRecord> records) {
1020 final var accountData = getAccountData(serviceIdType);
1021 final var preKeyMetadata = accountData.getPreKeyMetadata();
1022 logger.debug("Adding {} {} pre keys with offset {}",
1023 records.size(),
1024 serviceIdType,
1025 preKeyMetadata.nextPreKeyId);
1026 accountData.getSignalServiceAccountDataStore()
1027 .markAllOneTimeEcPreKeysStaleIfNecessary(System.currentTimeMillis());
1028 for (var record : records) {
1029 if (preKeyMetadata.nextPreKeyId != record.getId()) {
1030 logger.error("Invalid pre key id {}, expected {}", record.getId(), preKeyMetadata.nextPreKeyId);
1031 throw new AssertionError("Invalid pre key id");
1032 }
1033 accountData.getPreKeyStore().storePreKey(record.getId(), record);
1034 preKeyMetadata.nextPreKeyId = (preKeyMetadata.nextPreKeyId + 1) % PREKEY_MAXIMUM_ID;
1035 }
1036 save();
1037 }
1038
1039 public void addSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord record) {
1040 final var accountData = getAccountData(serviceIdType);
1041 final var preKeyMetadata = accountData.getPreKeyMetadata();
1042 logger.debug("Adding {} signed pre key with offset {}", serviceIdType, preKeyMetadata.nextSignedPreKeyId);
1043 if (preKeyMetadata.nextSignedPreKeyId != record.getId()) {
1044 logger.error("Invalid signed pre key id {}, expected {}",
1045 record.getId(),
1046 preKeyMetadata.nextSignedPreKeyId);
1047 throw new AssertionError("Invalid signed pre key id");
1048 }
1049 accountData.getSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
1050 preKeyMetadata.nextSignedPreKeyId = (preKeyMetadata.nextSignedPreKeyId + 1) % PREKEY_MAXIMUM_ID;
1051 preKeyMetadata.activeSignedPreKeyId = record.getId();
1052 save();
1053 }
1054
1055 public void resetKyberPreKeyOffsets(final ServiceIdType serviceIdType) {
1056 final var preKeyMetadata = getAccountData(serviceIdType).getPreKeyMetadata();
1057 preKeyMetadata.nextKyberPreKeyId = getRandomPreKeyIdOffset();
1058 preKeyMetadata.activeLastResortKyberPreKeyId = -1;
1059 save();
1060 }
1061
1062 public void addKyberPreKeys(ServiceIdType serviceIdType, List<KyberPreKeyRecord> records) {
1063 final var accountData = getAccountData(serviceIdType);
1064 final var preKeyMetadata = accountData.getPreKeyMetadata();
1065 logger.debug("Adding {} {} kyber pre keys with offset {}",
1066 records.size(),
1067 serviceIdType,
1068 preKeyMetadata.nextKyberPreKeyId);
1069 accountData.getSignalServiceAccountDataStore()
1070 .markAllOneTimeEcPreKeysStaleIfNecessary(System.currentTimeMillis());
1071 for (var record : records) {
1072 if (preKeyMetadata.nextKyberPreKeyId != record.getId()) {
1073 logger.error("Invalid kyber pre key id {}, expected {}",
1074 record.getId(),
1075 preKeyMetadata.nextKyberPreKeyId);
1076 throw new AssertionError("Invalid kyber pre key id");
1077 }
1078 accountData.getKyberPreKeyStore().storeKyberPreKey(record.getId(), record);
1079 preKeyMetadata.nextKyberPreKeyId = (preKeyMetadata.nextKyberPreKeyId + 1) % PREKEY_MAXIMUM_ID;
1080 }
1081 save();
1082 }
1083
1084 public void addLastResortKyberPreKey(ServiceIdType serviceIdType, KyberPreKeyRecord record) {
1085 final var accountData = getAccountData(serviceIdType);
1086 final var preKeyMetadata = accountData.getPreKeyMetadata();
1087 logger.debug("Adding {} last resort kyber pre key with offset {}",
1088 serviceIdType,
1089 preKeyMetadata.nextKyberPreKeyId);
1090 if (preKeyMetadata.nextKyberPreKeyId != record.getId()) {
1091 logger.error("Invalid last resort kyber pre key id {}, expected {}",
1092 record.getId(),
1093 preKeyMetadata.nextKyberPreKeyId);
1094 throw new AssertionError("Invalid last resort kyber pre key id");
1095 }
1096 accountData.getKyberPreKeyStore().storeLastResortKyberPreKey(record.getId(), record);
1097 preKeyMetadata.activeLastResortKyberPreKeyId = record.getId();
1098 preKeyMetadata.nextKyberPreKeyId = (preKeyMetadata.nextKyberPreKeyId + 1) % PREKEY_MAXIMUM_ID;
1099 save();
1100 }
1101
1102 public int getPreviousStorageVersion() {
1103 return previousStorageVersion;
1104 }
1105
1106 public AccountData<? extends ServiceId> getAccountData(ServiceIdType serviceIdType) {
1107 return switch (serviceIdType) {
1108 case ACI -> aciAccountData;
1109 case PNI -> pniAccountData;
1110 };
1111 }
1112
1113 public AccountData<? extends ServiceId> getAccountData(ServiceId accountIdentifier) {
1114 if (accountIdentifier.equals(aciAccountData.getServiceId())) {
1115 return aciAccountData;
1116 } else if (accountIdentifier.equals(pniAccountData.getServiceId())) {
1117 return pniAccountData;
1118 } else {
1119 throw new IllegalArgumentException("No matching account data found for " + accountIdentifier);
1120 }
1121 }
1122
1123 public SignalServiceDataStore getSignalServiceDataStore() {
1124 return new SignalServiceDataStore() {
1125 @Override
1126 public SignalServiceAccountDataStore get(final ServiceId accountIdentifier) {
1127 return getAccountData(accountIdentifier).getSignalServiceAccountDataStore();
1128 }
1129
1130 @Override
1131 public SignalServiceAccountDataStore aci() {
1132 return aciAccountData.getSignalServiceAccountDataStore();
1133 }
1134
1135 @Override
1136 public SignalServiceAccountDataStore pni() {
1137 return pniAccountData.getSignalServiceAccountDataStore();
1138 }
1139
1140 @Override
1141 public boolean isMultiDevice() {
1142 return SignalAccount.this.isMultiDevice();
1143 }
1144 };
1145 }
1146
1147 public IdentityKeyStore getIdentityKeyStore() {
1148 return getOrCreate(() -> identityKeyStore,
1149 () -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(), settings.trustNewIdentity()));
1150 }
1151
1152 public GroupStore getGroupStore() {
1153 return getOrCreate(() -> groupStore,
1154 () -> groupStore = new GroupStore(getAccountDatabase(),
1155 getRecipientResolver(),
1156 getRecipientIdCreator()));
1157 }
1158
1159 public ContactsStore getContactStore() {
1160 return getRecipientStore();
1161 }
1162
1163 public CdsiStore getCdsiStore() {
1164 return getOrCreate(() -> cdsiStore, () -> cdsiStore = new CdsiStore(getAccountDatabase()));
1165 }
1166
1167 private RecipientIdCreator getRecipientIdCreator() {
1168 return recipientId -> getRecipientStore().create(recipientId);
1169 }
1170
1171 public RecipientResolver getRecipientResolver() {
1172 return new RecipientResolver.RecipientResolverWrapper(this::getRecipientStore);
1173 }
1174
1175 public RecipientTrustedResolver getRecipientTrustedResolver() {
1176 return new RecipientTrustedResolver.RecipientTrustedResolverWrapper(this::getRecipientStore);
1177 }
1178
1179 public RecipientAddressResolver getRecipientAddressResolver() {
1180 return recipientId -> getRecipientStore().resolveRecipientAddress(recipientId);
1181 }
1182
1183 public RecipientStore getRecipientStore() {
1184 return getOrCreate(() -> recipientStore,
1185 () -> recipientStore = new RecipientStore(this::mergeRecipients,
1186 this::getSelfRecipientAddress,
1187 this::getProfileKey,
1188 getAccountDatabase()));
1189 }
1190
1191 public ProfileStore getProfileStore() {
1192 return getRecipientStore();
1193 }
1194
1195 public StickerStore getStickerStore() {
1196 return getOrCreate(() -> stickerStore, () -> stickerStore = new StickerStore(getAccountDatabase()));
1197 }
1198
1199 public SenderKeyStore getSenderKeyStore() {
1200 return getOrCreate(() -> senderKeyStore, () -> senderKeyStore = new SenderKeyStore(getAccountDatabase()));
1201 }
1202
1203 private KeyValueStore getKeyValueStore() {
1204 return getOrCreate(() -> keyValueStore, () -> keyValueStore = new KeyValueStore(getAccountDatabase()));
1205 }
1206
1207 public ConfigurationStore getConfigurationStore() {
1208 return getOrCreate(() -> configurationStore,
1209 () -> configurationStore = new ConfigurationStore(getKeyValueStore()));
1210 }
1211
1212 public MessageCache getMessageCache() {
1213 return getOrCreate(() -> messageCache,
1214 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
1215 }
1216
1217 public AccountDatabase getAccountDatabase() {
1218 return getOrCreate(() -> accountDatabase, () -> {
1219 try {
1220 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
1221 } catch (SQLException e) {
1222 throw new RuntimeException(e);
1223 }
1224 });
1225 }
1226
1227 public MessageSendLogStore getMessageSendLogStore() {
1228 return getOrCreate(() -> messageSendLogStore,
1229 () -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase(),
1230 settings.disableMessageSendLog()));
1231 }
1232
1233 public CredentialsProvider getCredentialsProvider() {
1234 return new CredentialsProvider() {
1235 @Override
1236 public ACI getAci() {
1237 return aciAccountData.getServiceId();
1238 }
1239
1240 @Override
1241 public PNI getPni() {
1242 return pniAccountData.getServiceId();
1243 }
1244
1245 @Override
1246 public String getE164() {
1247 return number;
1248 }
1249
1250 @Override
1251 public String getPassword() {
1252 return password;
1253 }
1254
1255 @Override
1256 public int getDeviceId() {
1257 return deviceId;
1258 }
1259 };
1260 }
1261
1262 public String getNumber() {
1263 return number;
1264 }
1265
1266 public void setNumber(final String number) {
1267 this.number = number;
1268 save();
1269 }
1270
1271 public String getUsername() {
1272 return username;
1273 }
1274
1275 public void setUsername(final String username) {
1276 this.username = username;
1277 save();
1278 }
1279
1280 public ServiceEnvironment getServiceEnvironment() {
1281 return serviceEnvironment;
1282 }
1283
1284 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1285 this.serviceEnvironment = serviceEnvironment;
1286 save();
1287 }
1288
1289 public AccountAttributes getAccountAttributes(String registrationLock) {
1290 return new AccountAttributes(null,
1291 aciAccountData.getLocalRegistrationId(),
1292 false,
1293 false,
1294 true,
1295 registrationLock != null ? registrationLock : getRegistrationLock(),
1296 getSelfUnidentifiedAccessKey(),
1297 isUnrestrictedUnidentifiedAccess(),
1298 isDiscoverableByPhoneNumber(),
1299 getAccountCapabilities(),
1300 encryptedDeviceName,
1301 pniAccountData.getLocalRegistrationId(),
1302 null); // TODO recoveryPassword?
1303 }
1304
1305 public AccountAttributes.Capabilities getAccountCapabilities() {
1306 return getCapabilities(isPrimaryDevice());
1307 }
1308
1309 public ServiceId getAccountId(ServiceIdType serviceIdType) {
1310 return getAccountData(serviceIdType).getServiceId();
1311 }
1312
1313 public ACI getAci() {
1314 return aciAccountData.getServiceId();
1315 }
1316
1317 public void setAci(final ACI aci) {
1318 this.aciAccountData.setServiceId(aci);
1319 save();
1320 }
1321
1322 public PNI getPni() {
1323 return pniAccountData.getServiceId();
1324 }
1325
1326 public void setPni(final PNI updatedPni) {
1327 final var oldPni = pniAccountData.getServiceId();
1328 if (oldPni != null && !oldPni.equals(updatedPni)) {
1329 // Clear data for old PNI
1330 identityKeyStore.deleteIdentity(oldPni);
1331 }
1332
1333 this.pniAccountData.setServiceId(updatedPni);
1334 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1335 trustSelfIdentity(ServiceIdType.PNI);
1336 save();
1337 }
1338
1339 public void setNewPniIdentity(
1340 final IdentityKeyPair pniIdentityKeyPair,
1341 final SignedPreKeyRecord pniSignedPreKey,
1342 final KyberPreKeyRecord lastResortKyberPreKey,
1343 final int localPniRegistrationId
1344 ) {
1345 setPniIdentityKeyPair(pniIdentityKeyPair);
1346 pniAccountData.setLocalRegistrationId(localPniRegistrationId);
1347
1348 final AccountData<? extends ServiceId> accountData = getAccountData(ServiceIdType.PNI);
1349 final var preKeyMetadata = accountData.getPreKeyMetadata();
1350 preKeyMetadata.nextSignedPreKeyId = pniSignedPreKey.getId();
1351 accountData.getSignedPreKeyStore().removeSignedPreKey(pniSignedPreKey.getId());
1352 addSignedPreKey(ServiceIdType.PNI, pniSignedPreKey);
1353 if (lastResortKyberPreKey != null) {
1354 preKeyMetadata.nextKyberPreKeyId = lastResortKyberPreKey.getId();
1355 accountData.getKyberPreKeyStore().removeKyberPreKey(lastResortKyberPreKey.getId());
1356 addLastResortKyberPreKey(ServiceIdType.PNI, lastResortKyberPreKey);
1357 }
1358 save();
1359 }
1360
1361 public SignalServiceAddress getSelfAddress() {
1362 return new SignalServiceAddress(getAci(), number);
1363 }
1364
1365 public RecipientAddress getSelfRecipientAddress() {
1366 return new RecipientAddress(getAci(), getPni(), number, username);
1367 }
1368
1369 public RecipientId getSelfRecipientId() {
1370 return getRecipientResolver().resolveRecipient(getSelfRecipientAddress());
1371 }
1372
1373 public String getSessionId(final String forNumber) {
1374 final var keyValueStore = getKeyValueStore();
1375 final var sessionNumber = keyValueStore.getEntry(verificationSessionNumber);
1376 if (!forNumber.equals(sessionNumber)) {
1377 return null;
1378 }
1379 return keyValueStore.getEntry(verificationSessionId);
1380 }
1381
1382 public void setSessionId(final String sessionNumber, final String sessionId) {
1383 final var keyValueStore = getKeyValueStore();
1384 keyValueStore.storeEntry(verificationSessionNumber, sessionNumber);
1385 keyValueStore.storeEntry(verificationSessionId, sessionId);
1386 }
1387
1388 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1389 this.encryptedDeviceName = encryptedDeviceName;
1390 save();
1391 }
1392
1393 public int getDeviceId() {
1394 return deviceId;
1395 }
1396
1397 public boolean isPrimaryDevice() {
1398 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1399 }
1400
1401 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1402 return getAccountData(serviceIdType).getIdentityKeyPair();
1403 }
1404
1405 public IdentityKeyPair getAciIdentityKeyPair() {
1406 return aciAccountData.getIdentityKeyPair();
1407 }
1408
1409 public IdentityKeyPair getPniIdentityKeyPair() {
1410 return pniAccountData.getIdentityKeyPair();
1411 }
1412
1413 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1414 pniAccountData.setIdentityKeyPair(identityKeyPair);
1415 trustSelfIdentity(ServiceIdType.PNI);
1416 save();
1417 }
1418
1419 public String getPassword() {
1420 return password;
1421 }
1422
1423 public void setRegistrationLockPin(final String registrationLockPin) {
1424 this.registrationLockPin = registrationLockPin;
1425 save();
1426 }
1427
1428 public String getRegistrationLockPin() {
1429 return registrationLockPin;
1430 }
1431
1432 public String getRegistrationLock() {
1433 final var masterKey = getPinBackedMasterKey();
1434 if (masterKey == null) {
1435 return null;
1436 }
1437 return masterKey.deriveRegistrationLock();
1438 }
1439
1440 public MasterKey getPinBackedMasterKey() {
1441 if (registrationLockPin == null) {
1442 return null;
1443 }
1444 return pinMasterKey;
1445 }
1446
1447 public MasterKey getOrCreatePinMasterKey() {
1448 if (pinMasterKey == null) {
1449 pinMasterKey = KeyUtils.createMasterKey();
1450 save();
1451 }
1452 return pinMasterKey;
1453 }
1454
1455 public StorageKey getStorageKey() {
1456 if (pinMasterKey != null) {
1457 return pinMasterKey.deriveStorageServiceKey();
1458 }
1459 return storageKey;
1460 }
1461
1462 public StorageKey getOrCreateStorageKey() {
1463 if (isPrimaryDevice()) {
1464 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1465 }
1466 return storageKey;
1467 }
1468
1469 public void setStorageKey(final StorageKey storageKey) {
1470 if (storageKey.equals(this.storageKey)) {
1471 return;
1472 }
1473 this.storageKey = storageKey;
1474 save();
1475 }
1476
1477 public long getStorageManifestVersion() {
1478 return getKeyValueStore().getEntry(storageManifestVersion);
1479 }
1480
1481 public void setStorageManifestVersion(final long value) {
1482 getKeyValueStore().storeEntry(storageManifestVersion, value);
1483 }
1484
1485 public Optional<SignalStorageManifest> getStorageManifest() {
1486 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1487 if (!storageManifestFile.exists()) {
1488 return Optional.empty();
1489 }
1490 try (var inputStream = new FileInputStream(storageManifestFile)) {
1491 return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
1492 } catch (IOException e) {
1493 logger.warn("Failed to read local storage manifest.", e);
1494 return Optional.empty();
1495 }
1496 }
1497
1498 public void setStorageManifest(SignalStorageManifest manifest) {
1499 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1500 if (manifest == null) {
1501 if (storageManifestFile.exists()) {
1502 try {
1503 Files.delete(storageManifestFile.toPath());
1504 } catch (IOException e) {
1505 logger.error("Failed to delete local storage manifest.", e);
1506 }
1507 }
1508 return;
1509 }
1510
1511 final var manifestBytes = manifest.serialize();
1512 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1513 outputStream.write(manifestBytes);
1514 } catch (IOException e) {
1515 logger.error("Failed to store local storage manifest.", e);
1516 }
1517 }
1518
1519 public byte[] getCdsiToken() {
1520 return getKeyValueStore().getEntry(cdsiToken);
1521 }
1522
1523 public void setCdsiToken(final byte[] value) {
1524 getKeyValueStore().storeEntry(cdsiToken, value);
1525 }
1526
1527 public Long getLastRecipientsRefresh() {
1528 return getKeyValueStore().getEntry(lastRecipientsRefresh);
1529 }
1530
1531 public void setLastRecipientsRefresh(final Long value) {
1532 getKeyValueStore().storeEntry(lastRecipientsRefresh, value);
1533 }
1534
1535 public ProfileKey getProfileKey() {
1536 return profileKey;
1537 }
1538
1539 public void setProfileKey(final ProfileKey profileKey) {
1540 if (profileKey.equals(this.profileKey)) {
1541 return;
1542 }
1543 this.profileKey = profileKey;
1544 save();
1545 }
1546
1547 public byte[] getSelfUnidentifiedAccessKey() {
1548 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1549 }
1550
1551 public boolean isRegistered() {
1552 return registered;
1553 }
1554
1555 public void setRegistered(final boolean registered) {
1556 this.registered = registered;
1557 save();
1558 }
1559
1560 public boolean isMultiDevice() {
1561 return isMultiDevice;
1562 }
1563
1564 public void setMultiDevice(final boolean multiDevice) {
1565 if (isMultiDevice == multiDevice) {
1566 return;
1567 }
1568 isMultiDevice = multiDevice;
1569 save();
1570 }
1571
1572 public long getLastReceiveTimestamp() {
1573 return getKeyValueStore().getEntry(lastReceiveTimestamp);
1574 }
1575
1576 public void setLastReceiveTimestamp(final long value) {
1577 getKeyValueStore().storeEntry(lastReceiveTimestamp, value);
1578 }
1579
1580 public boolean isUnrestrictedUnidentifiedAccess() {
1581 final var profile = getProfileStore().getProfile(getSelfRecipientId());
1582 return profile != null && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED;
1583 }
1584
1585 public boolean isDiscoverableByPhoneNumber() {
1586 final var phoneNumberUnlisted = getConfigurationStore().getPhoneNumberUnlisted();
1587 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1588 }
1589
1590 private void trustSelfIdentity(ServiceIdType serviceIdType) {
1591 final var accountData = getAccountData(serviceIdType);
1592 final var serviceId = accountData.getServiceId();
1593 final var identityKeyPair = accountData.getIdentityKeyPair();
1594 if (serviceId == null || identityKeyPair == null) {
1595 return;
1596 }
1597 final var publicKey = identityKeyPair.getPublicKey();
1598 getIdentityKeyStore().saveIdentity(serviceId, publicKey);
1599 getIdentityKeyStore().setIdentityTrustLevel(serviceId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1600 }
1601
1602 public void deleteAccountData() throws IOException {
1603 close();
1604 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1605 .sorted(Comparator.reverseOrder())) {
1606 for (final var file = files.iterator(); file.hasNext(); ) {
1607 Files.delete(file.next());
1608 }
1609 }
1610 Files.delete(getFileName(dataPath, accountPath).toPath());
1611 }
1612
1613 @Override
1614 public void close() {
1615 synchronized (fileChannel) {
1616 if (accountDatabase != null) {
1617 accountDatabase.close();
1618 }
1619 if (messageSendLogStore != null) {
1620 messageSendLogStore.close();
1621 }
1622 try {
1623 try {
1624 lock.close();
1625 } catch (ClosedChannelException ignored) {
1626 }
1627 fileChannel.close();
1628 } catch (IOException e) {
1629 logger.warn("Failed to close account: {}", e.getMessage(), e);
1630 }
1631 }
1632 }
1633
1634 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1635 var value = supplier.get();
1636 if (value != null) {
1637 return value;
1638 }
1639
1640 synchronized (LOCK) {
1641 value = supplier.get();
1642 if (value != null) {
1643 return value;
1644 }
1645 creator.call();
1646 return supplier.get();
1647 }
1648 }
1649
1650 private interface Callable {
1651
1652 void call();
1653 }
1654
1655 public static class PreKeyMetadata {
1656
1657 private int nextPreKeyId = 1;
1658 private int nextSignedPreKeyId = 1;
1659 private int activeSignedPreKeyId = -1;
1660 private int nextKyberPreKeyId = 1;
1661 private int activeLastResortKyberPreKeyId = -1;
1662
1663 public int getNextPreKeyId() {
1664 return nextPreKeyId;
1665 }
1666
1667 public int getNextSignedPreKeyId() {
1668 return nextSignedPreKeyId;
1669 }
1670
1671 public int getActiveSignedPreKeyId() {
1672 return activeSignedPreKeyId;
1673 }
1674
1675 public int getNextKyberPreKeyId() {
1676 return nextKyberPreKeyId;
1677 }
1678
1679 public int getActiveLastResortKyberPreKeyId() {
1680 return activeLastResortKyberPreKeyId;
1681 }
1682 }
1683
1684 public class AccountData<SERVICE_ID extends ServiceId> {
1685
1686 private final ServiceIdType serviceIdType;
1687 private SERVICE_ID serviceId;
1688 private IdentityKeyPair identityKeyPair;
1689 private int localRegistrationId;
1690 private final PreKeyMetadata preKeyMetadata = new PreKeyMetadata();
1691
1692 private SignalProtocolStore signalProtocolStore;
1693 private PreKeyStore preKeyStore;
1694 private SignedPreKeyStore signedPreKeyStore;
1695 private KyberPreKeyStore kyberPreKeyStore;
1696 private SessionStore sessionStore;
1697 private SignalIdentityKeyStore identityKeyStore;
1698
1699 private AccountData(final ServiceIdType serviceIdType) {
1700 this.serviceIdType = serviceIdType;
1701 }
1702
1703 public SERVICE_ID getServiceId() {
1704 return serviceId;
1705 }
1706
1707 private void setServiceId(final SERVICE_ID serviceId) {
1708 this.serviceId = serviceId;
1709 }
1710
1711 public IdentityKeyPair getIdentityKeyPair() {
1712 return identityKeyPair;
1713 }
1714
1715 private void setIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1716 this.identityKeyPair = identityKeyPair;
1717 }
1718
1719 public int getLocalRegistrationId() {
1720 return localRegistrationId;
1721 }
1722
1723 private void setLocalRegistrationId(final int localRegistrationId) {
1724 this.localRegistrationId = localRegistrationId;
1725 this.identityKeyStore = null;
1726 }
1727
1728 public PreKeyMetadata getPreKeyMetadata() {
1729 return preKeyMetadata;
1730 }
1731
1732 private SignalServiceAccountDataStore getSignalServiceAccountDataStore() {
1733 return getOrCreate(() -> signalProtocolStore,
1734 () -> signalProtocolStore = new SignalProtocolStore(getPreKeyStore(),
1735 getSignedPreKeyStore(),
1736 getKyberPreKeyStore(),
1737 getSessionStore(),
1738 getIdentityKeyStore(),
1739 getSenderKeyStore(),
1740 SignalAccount.this::isMultiDevice));
1741 }
1742
1743 public PreKeyStore getPreKeyStore() {
1744 return getOrCreate(() -> preKeyStore,
1745 () -> preKeyStore = new PreKeyStore(getAccountDatabase(), serviceIdType));
1746 }
1747
1748 public SignedPreKeyStore getSignedPreKeyStore() {
1749 return getOrCreate(() -> signedPreKeyStore,
1750 () -> signedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), serviceIdType));
1751 }
1752
1753 public KyberPreKeyStore getKyberPreKeyStore() {
1754 return getOrCreate(() -> kyberPreKeyStore,
1755 () -> kyberPreKeyStore = new KyberPreKeyStore(getAccountDatabase(), serviceIdType));
1756 }
1757
1758 public SessionStore getSessionStore() {
1759 return getOrCreate(() -> sessionStore,
1760 () -> sessionStore = new SessionStore(getAccountDatabase(), serviceIdType));
1761 }
1762
1763 public SignalIdentityKeyStore getIdentityKeyStore() {
1764 return getOrCreate(() -> identityKeyStore,
1765 () -> identityKeyStore = new SignalIdentityKeyStore(() -> identityKeyPair,
1766 localRegistrationId,
1767 SignalAccount.this.getIdentityKeyStore()));
1768 }
1769 }
1770
1771 public record Storage(
1772 int version,
1773 String serviceEnvironment,
1774 boolean registered,
1775 String number,
1776 String username,
1777 String encryptedDeviceName,
1778 int deviceId,
1779 boolean isMultiDevice,
1780 String password,
1781 AccountData aciAccountData,
1782 AccountData pniAccountData,
1783 String registrationLockPin,
1784 String pinMasterKey,
1785 String storageKey,
1786 String profileKey
1787 ) {
1788
1789 public record AccountData(
1790 String serviceId,
1791 int registrationId,
1792 String identityPrivateKey,
1793 String identityPublicKey,
1794
1795 int nextPreKeyId,
1796 int nextSignedPreKeyId,
1797 int activeSignedPreKeyId,
1798 int nextKyberPreKeyId,
1799 int activeLastResortKyberPreKeyId
1800 ) {
1801
1802 private static AccountData from(final SignalAccount.AccountData<?> accountData) {
1803 final var base64 = Base64.getEncoder();
1804 final var preKeyMetadata = accountData.getPreKeyMetadata();
1805 return new AccountData(accountData.getServiceId() == null
1806 ? null
1807 : accountData.getServiceId().toString(),
1808 accountData.getLocalRegistrationId(),
1809 accountData.getIdentityKeyPair() == null
1810 ? null
1811 : base64.encodeToString(accountData.getIdentityKeyPair().getPrivateKey().serialize()),
1812 accountData.getIdentityKeyPair() == null
1813 ? null
1814 : base64.encodeToString(accountData.getIdentityKeyPair().getPublicKey().serialize()),
1815 preKeyMetadata.getNextPreKeyId(),
1816 preKeyMetadata.getNextSignedPreKeyId(),
1817 preKeyMetadata.getActiveSignedPreKeyId(),
1818 preKeyMetadata.getNextKyberPreKeyId(),
1819 preKeyMetadata.getActiveLastResortKyberPreKeyId());
1820 }
1821 }
1822 }
1823 }