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