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