]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
85e5cadd032ed8859536fe5f0c81324f1bebdc6f
[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.RecipientResolverWrapper(this::getRecipientStore);
1183 }
1184
1185 public RecipientTrustedResolver getRecipientTrustedResolver() {
1186 return new RecipientTrustedResolver.RecipientTrustedResolverWrapper(this::getRecipientStore);
1187 }
1188
1189 public RecipientAddressResolver getRecipientAddressResolver() {
1190 return recipientId -> getRecipientStore().resolveRecipientAddress(recipientId);
1191 }
1192
1193 public RecipientStore getRecipientStore() {
1194 return getOrCreate(() -> recipientStore,
1195 () -> recipientStore = new RecipientStore(this::mergeRecipients,
1196 this::getSelfRecipientAddress,
1197 getAccountDatabase()));
1198 }
1199
1200 public ProfileStore getProfileStore() {
1201 return getRecipientStore();
1202 }
1203
1204 public StickerStore getStickerStore() {
1205 return getOrCreate(() -> stickerStore, () -> stickerStore = new StickerStore(getAccountDatabase()));
1206 }
1207
1208 public SenderKeyStore getSenderKeyStore() {
1209 return getOrCreate(() -> senderKeyStore, () -> senderKeyStore = new SenderKeyStore(getAccountDatabase()));
1210 }
1211
1212 public ConfigurationStore getConfigurationStore() {
1213 return configurationStore;
1214 }
1215
1216 public MessageCache getMessageCache() {
1217 return getOrCreate(() -> messageCache,
1218 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
1219 }
1220
1221 public AccountDatabase getAccountDatabase() {
1222 return getOrCreate(() -> accountDatabase, () -> {
1223 try {
1224 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
1225 } catch (SQLException e) {
1226 throw new RuntimeException(e);
1227 }
1228 });
1229 }
1230
1231 public MessageSendLogStore getMessageSendLogStore() {
1232 return getOrCreate(() -> messageSendLogStore,
1233 () -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase()));
1234 }
1235
1236 public CredentialsProvider getCredentialsProvider() {
1237 return new CredentialsProvider() {
1238 @Override
1239 public ACI getAci() {
1240 return aci;
1241 }
1242
1243 @Override
1244 public PNI getPni() {
1245 return pni;
1246 }
1247
1248 @Override
1249 public String getE164() {
1250 return number;
1251 }
1252
1253 @Override
1254 public String getPassword() {
1255 return password;
1256 }
1257
1258 @Override
1259 public int getDeviceId() {
1260 return deviceId;
1261 }
1262 };
1263 }
1264
1265 public String getNumber() {
1266 return number;
1267 }
1268
1269 public void setNumber(final String number) {
1270 this.number = number;
1271 save();
1272 }
1273
1274 public ServiceEnvironment getServiceEnvironment() {
1275 return serviceEnvironment;
1276 }
1277
1278 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1279 this.serviceEnvironment = serviceEnvironment;
1280 save();
1281 }
1282
1283 public ServiceId getAccountId(ServiceIdType serviceIdType) {
1284 return serviceIdType.equals(ServiceIdType.ACI) ? aci : pni;
1285 }
1286
1287 public ACI getAci() {
1288 return aci;
1289 }
1290
1291 public void setAci(final ACI aci) {
1292 this.aci = aci;
1293 save();
1294 }
1295
1296 public PNI getPni() {
1297 return pni;
1298 }
1299
1300 public void setPni(final PNI pni) {
1301 this.pni = pni;
1302 save();
1303 }
1304
1305 public SignalServiceAddress getSelfAddress() {
1306 return new SignalServiceAddress(aci, number);
1307 }
1308
1309 public RecipientAddress getSelfRecipientAddress() {
1310 return new RecipientAddress(aci, pni, number);
1311 }
1312
1313 public RecipientId getSelfRecipientId() {
1314 return getRecipientResolver().resolveRecipient(getSelfRecipientAddress());
1315 }
1316
1317 public byte[] getEncryptedDeviceName() {
1318 return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
1319 }
1320
1321 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1322 this.encryptedDeviceName = encryptedDeviceName;
1323 save();
1324 }
1325
1326 public int getDeviceId() {
1327 return deviceId;
1328 }
1329
1330 public boolean isPrimaryDevice() {
1331 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1332 }
1333
1334 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1335 return serviceIdType.equals(ServiceIdType.ACI) ? aciIdentityKeyPair : pniIdentityKeyPair;
1336 }
1337
1338 public IdentityKeyPair getAciIdentityKeyPair() {
1339 return aciIdentityKeyPair;
1340 }
1341
1342 public IdentityKeyPair getPniIdentityKeyPair() {
1343 return pniIdentityKeyPair;
1344 }
1345
1346 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1347 pniIdentityKeyPair = identityKeyPair;
1348 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
1349 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1350 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1351 save();
1352 }
1353
1354 public int getLocalRegistrationId() {
1355 return localRegistrationId;
1356 }
1357
1358 public int getLocalPniRegistrationId() {
1359 return localPniRegistrationId;
1360 }
1361
1362 public String getPassword() {
1363 return password;
1364 }
1365
1366 private void setPassword(final String password) {
1367 this.password = password;
1368 save();
1369 }
1370
1371 public void setRegistrationLockPin(final String registrationLockPin) {
1372 this.registrationLockPin = registrationLockPin;
1373 save();
1374 }
1375
1376 public String getRegistrationLockPin() {
1377 return registrationLockPin;
1378 }
1379
1380 public String getRegistrationLock() {
1381 final var masterKey = getPinBackedMasterKey();
1382 if (masterKey == null) {
1383 return null;
1384 }
1385 return masterKey.deriveRegistrationLock();
1386 }
1387
1388 public MasterKey getPinBackedMasterKey() {
1389 if (registrationLockPin == null) {
1390 return null;
1391 }
1392 return pinMasterKey;
1393 }
1394
1395 public MasterKey getOrCreatePinMasterKey() {
1396 if (pinMasterKey == null) {
1397 pinMasterKey = KeyUtils.createMasterKey();
1398 save();
1399 }
1400 return pinMasterKey;
1401 }
1402
1403 public StorageKey getStorageKey() {
1404 if (pinMasterKey != null) {
1405 return pinMasterKey.deriveStorageServiceKey();
1406 }
1407 return storageKey;
1408 }
1409
1410 public StorageKey getOrCreateStorageKey() {
1411 if (isPrimaryDevice()) {
1412 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1413 }
1414 return storageKey;
1415 }
1416
1417 public void setStorageKey(final StorageKey storageKey) {
1418 if (storageKey.equals(this.storageKey)) {
1419 return;
1420 }
1421 this.storageKey = storageKey;
1422 save();
1423 }
1424
1425 public long getStorageManifestVersion() {
1426 return this.storageManifestVersion;
1427 }
1428
1429 public void setStorageManifestVersion(final long storageManifestVersion) {
1430 if (storageManifestVersion == this.storageManifestVersion) {
1431 return;
1432 }
1433 this.storageManifestVersion = storageManifestVersion;
1434 save();
1435 }
1436
1437 public Optional<SignalStorageManifest> getStorageManifest() {
1438 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1439 if (!storageManifestFile.exists()) {
1440 return Optional.empty();
1441 }
1442 try (var inputStream = new FileInputStream(storageManifestFile)) {
1443 return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
1444 } catch (IOException e) {
1445 logger.warn("Failed to read local storage manifest.", e);
1446 return Optional.empty();
1447 }
1448 }
1449
1450 public void setStorageManifest(SignalStorageManifest manifest) {
1451 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1452 if (manifest == null) {
1453 if (storageManifestFile.exists()) {
1454 try {
1455 Files.delete(storageManifestFile.toPath());
1456 } catch (IOException e) {
1457 logger.error("Failed to delete local storage manifest.", e);
1458 }
1459 }
1460 return;
1461 }
1462
1463 final var manifestBytes = manifest.serialize();
1464 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1465 outputStream.write(manifestBytes);
1466 } catch (IOException e) {
1467 logger.error("Failed to store local storage manifest.", e);
1468 }
1469 }
1470
1471 public ProfileKey getProfileKey() {
1472 return profileKey;
1473 }
1474
1475 public void setProfileKey(final ProfileKey profileKey) {
1476 if (profileKey.equals(this.profileKey)) {
1477 return;
1478 }
1479 this.profileKey = profileKey;
1480 save();
1481 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
1482 }
1483
1484 public byte[] getSelfUnidentifiedAccessKey() {
1485 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1486 }
1487
1488 public int getPreKeyIdOffset(ServiceIdType serviceIdType) {
1489 return serviceIdType.equals(ServiceIdType.ACI) ? aciPreKeyIdOffset : pniPreKeyIdOffset;
1490 }
1491
1492 public int getNextSignedPreKeyId(ServiceIdType serviceIdType) {
1493 return serviceIdType.equals(ServiceIdType.ACI) ? aciNextSignedPreKeyId : pniNextSignedPreKeyId;
1494 }
1495
1496 public boolean isRegistered() {
1497 return registered;
1498 }
1499
1500 public void setRegistered(final boolean registered) {
1501 this.registered = registered;
1502 save();
1503 }
1504
1505 public boolean isMultiDevice() {
1506 return isMultiDevice;
1507 }
1508
1509 public void setMultiDevice(final boolean multiDevice) {
1510 if (isMultiDevice == multiDevice) {
1511 return;
1512 }
1513 isMultiDevice = multiDevice;
1514 save();
1515 }
1516
1517 public long getLastReceiveTimestamp() {
1518 return lastReceiveTimestamp;
1519 }
1520
1521 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1522 this.lastReceiveTimestamp = lastReceiveTimestamp;
1523 save();
1524 }
1525
1526 public boolean isUnrestrictedUnidentifiedAccess() {
1527 final var profile = getProfileStore().getProfile(getSelfRecipientId());
1528 return profile != null && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED;
1529 }
1530
1531 public boolean isDiscoverableByPhoneNumber() {
1532 final var phoneNumberUnlisted = configurationStore.getPhoneNumberUnlisted();
1533 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1534 }
1535
1536 public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
1537 this.pinMasterKey = masterKey;
1538 this.storageManifestVersion = -1;
1539 this.setStorageManifest(null);
1540 this.storageKey = null;
1541 this.encryptedDeviceName = null;
1542 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1543 this.isMultiDevice = false;
1544 this.registered = true;
1545 this.aci = aci;
1546 this.pni = pni;
1547 this.registrationLockPin = pin;
1548 this.lastReceiveTimestamp = 0;
1549 save();
1550
1551 clearAllPreKeys();
1552 getAciSessionStore().archiveAllSessions();
1553 getPniSessionStore().archiveAllSessions();
1554 getSenderKeyStore().deleteAll();
1555 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1556 final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
1557 getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
1558 getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
1559 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
1560 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1561 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1562 }
1563
1564 public void deleteAccountData() throws IOException {
1565 close();
1566 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1567 .sorted(Comparator.reverseOrder())) {
1568 for (final var file = files.iterator(); file.hasNext(); ) {
1569 Files.delete(file.next());
1570 }
1571 }
1572 Files.delete(getFileName(dataPath, accountPath).toPath());
1573 }
1574
1575 @Override
1576 public void close() {
1577 synchronized (fileChannel) {
1578 if (accountDatabase != null) {
1579 try {
1580 accountDatabase.close();
1581 } catch (SQLException e) {
1582 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1583 }
1584 }
1585 if (messageSendLogStore != null) {
1586 messageSendLogStore.close();
1587 }
1588 try {
1589 try {
1590 lock.close();
1591 } catch (ClosedChannelException ignored) {
1592 }
1593 fileChannel.close();
1594 } catch (IOException e) {
1595 logger.warn("Failed to close account: {}", e.getMessage(), e);
1596 }
1597 }
1598 }
1599
1600 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1601 var value = supplier.get();
1602 if (value != null) {
1603 return value;
1604 }
1605
1606 synchronized (LOCK) {
1607 value = supplier.get();
1608 if (value != null) {
1609 return value;
1610 }
1611 creator.call();
1612 return supplier.get();
1613 }
1614 }
1615
1616 private interface Callable {
1617
1618 void call();
1619 }
1620 }