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