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