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