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