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