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