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