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