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