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