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