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