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