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