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