]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
e80f79017d971e5285fc9caa5d75f8262e9fbf2d
[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 final var recipientAddress = getRecipientStore().resolveRecipientAddress(recipientId);
419 getRecipientStore().deleteRecipientData(recipientId);
420 getMessageCache().deleteMessages(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 this.settings = settings;
510 final JsonNode rootNode;
511 synchronized (fileChannel) {
512 fileChannel.position(0);
513 rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
514 }
515
516 var migratedLegacyConfig = false;
517
518 if (rootNode.hasNonNull("version")) {
519 var accountVersion = rootNode.get("version").asInt(1);
520 if (accountVersion > CURRENT_STORAGE_VERSION) {
521 throw new IOException("Config file was created by a more recent version: " + accountVersion);
522 } else if (accountVersion < MINIMUM_STORAGE_VERSION) {
523 throw new IOException("Config file was created by a no longer supported older version: "
524 + accountVersion);
525 }
526 previousStorageVersion = accountVersion;
527 if (accountVersion < CURRENT_STORAGE_VERSION) {
528 migratedLegacyConfig = true;
529 }
530 }
531
532 number = Utils.getNotNullNode(rootNode, "username").asText();
533 if (rootNode.hasNonNull("password")) {
534 password = rootNode.get("password").asText();
535 }
536 if (rootNode.hasNonNull("serviceEnvironment")) {
537 serviceEnvironment = ServiceEnvironment.valueOf(rootNode.get("serviceEnvironment").asText());
538 }
539 registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
540 if (rootNode.hasNonNull("uuid")) {
541 try {
542 aci = ACI.parseOrThrow(rootNode.get("uuid").asText());
543 } catch (IllegalArgumentException e) {
544 throw new IOException("Config file contains an invalid aci/uuid, needs to be a valid UUID", e);
545 }
546 }
547 if (rootNode.hasNonNull("pni")) {
548 try {
549 pni = PNI.parseOrThrow(rootNode.get("pni").asText());
550 } catch (IllegalArgumentException e) {
551 throw new IOException("Config file contains an invalid pni, needs to be a valid UUID", e);
552 }
553 }
554 if (rootNode.hasNonNull("deviceName")) {
555 encryptedDeviceName = rootNode.get("deviceName").asText();
556 }
557 if (rootNode.hasNonNull("deviceId")) {
558 deviceId = rootNode.get("deviceId").asInt();
559 }
560 if (rootNode.hasNonNull("isMultiDevice")) {
561 isMultiDevice = rootNode.get("isMultiDevice").asBoolean();
562 }
563 if (rootNode.hasNonNull("lastReceiveTimestamp")) {
564 lastReceiveTimestamp = rootNode.get("lastReceiveTimestamp").asLong();
565 }
566 int registrationId = 0;
567 if (rootNode.hasNonNull("registrationId")) {
568 registrationId = rootNode.get("registrationId").asInt();
569 }
570 if (rootNode.hasNonNull("pniRegistrationId")) {
571 localPniRegistrationId = rootNode.get("pniRegistrationId").asInt();
572 } else {
573 localPniRegistrationId = KeyHelper.generateRegistrationId(false);
574 }
575 IdentityKeyPair aciIdentityKeyPair = null;
576 if (rootNode.hasNonNull("identityPrivateKey") && rootNode.hasNonNull("identityKey")) {
577 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("identityKey").asText());
578 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("identityPrivateKey").asText());
579 aciIdentityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
580 }
581 if (rootNode.hasNonNull("pniIdentityPrivateKey") && rootNode.hasNonNull("pniIdentityKey")) {
582 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityKey").asText());
583 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityPrivateKey").asText());
584 pniIdentityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
585 }
586
587 if (rootNode.hasNonNull("registrationLockPin")) {
588 registrationLockPin = rootNode.get("registrationLockPin").asText();
589 }
590 if (rootNode.hasNonNull("pinMasterKey")) {
591 pinMasterKey = new MasterKey(Base64.getDecoder().decode(rootNode.get("pinMasterKey").asText()));
592 }
593 if (rootNode.hasNonNull("storageKey")) {
594 storageKey = new StorageKey(Base64.getDecoder().decode(rootNode.get("storageKey").asText()));
595 }
596 if (rootNode.hasNonNull("storageManifestVersion")) {
597 storageManifestVersion = rootNode.get("storageManifestVersion").asLong();
598 }
599 if (rootNode.hasNonNull("preKeyIdOffset")) {
600 aciPreKeyIdOffset = rootNode.get("preKeyIdOffset").asInt(1);
601 } else {
602 aciPreKeyIdOffset = 1;
603 }
604 if (rootNode.hasNonNull("nextSignedPreKeyId")) {
605 aciNextSignedPreKeyId = rootNode.get("nextSignedPreKeyId").asInt(1);
606 } else {
607 aciNextSignedPreKeyId = 1;
608 }
609 if (rootNode.hasNonNull("pniPreKeyIdOffset")) {
610 pniPreKeyIdOffset = rootNode.get("pniPreKeyIdOffset").asInt(1);
611 } else {
612 pniPreKeyIdOffset = 1;
613 }
614 if (rootNode.hasNonNull("pniNextSignedPreKeyId")) {
615 pniNextSignedPreKeyId = rootNode.get("pniNextSignedPreKeyId").asInt(1);
616 } else {
617 pniNextSignedPreKeyId = 1;
618 }
619 if (rootNode.hasNonNull("profileKey")) {
620 try {
621 profileKey = new ProfileKey(Base64.getDecoder().decode(rootNode.get("profileKey").asText()));
622 } catch (InvalidInputException e) {
623 throw new IOException(
624 "Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
625 e);
626 }
627 }
628
629 if (previousStorageVersion < 5) {
630 final var legacyRecipientsStoreFile = getRecipientsStoreFile(dataPath, accountPath);
631 if (legacyRecipientsStoreFile.exists()) {
632 LegacyRecipientStore2.migrate(legacyRecipientsStoreFile, getRecipientStore());
633 // Ensure our profile key is stored in profile store
634 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
635 migratedLegacyConfig = true;
636 }
637 }
638 if (previousStorageVersion < 6) {
639 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
640 }
641 final var legacyAciPreKeysPath = getAciPreKeysPath(dataPath, accountPath);
642 if (legacyAciPreKeysPath.exists()) {
643 LegacyPreKeyStore.migrate(legacyAciPreKeysPath, getAciPreKeyStore());
644 migratedLegacyConfig = true;
645 }
646 final var legacyPniPreKeysPath = getPniPreKeysPath(dataPath, accountPath);
647 if (legacyPniPreKeysPath.exists()) {
648 LegacyPreKeyStore.migrate(legacyPniPreKeysPath, getPniPreKeyStore());
649 migratedLegacyConfig = true;
650 }
651 final var legacyAciSignedPreKeysPath = getAciSignedPreKeysPath(dataPath, accountPath);
652 if (legacyAciSignedPreKeysPath.exists()) {
653 LegacySignedPreKeyStore.migrate(legacyAciSignedPreKeysPath, getAciSignedPreKeyStore());
654 migratedLegacyConfig = true;
655 }
656 final var legacyPniSignedPreKeysPath = getPniSignedPreKeysPath(dataPath, accountPath);
657 if (legacyPniSignedPreKeysPath.exists()) {
658 LegacySignedPreKeyStore.migrate(legacyPniSignedPreKeysPath, getPniSignedPreKeyStore());
659 migratedLegacyConfig = true;
660 }
661 final var legacySessionsPath = getSessionsPath(dataPath, accountPath);
662 if (legacySessionsPath.exists()) {
663 LegacySessionStore.migrate(legacySessionsPath,
664 getRecipientResolver(),
665 getRecipientAddressResolver(),
666 getAciSessionStore());
667 migratedLegacyConfig = true;
668 }
669 final var legacyIdentitiesPath = getIdentitiesPath(dataPath, accountPath);
670 if (legacyIdentitiesPath.exists()) {
671 LegacyIdentityKeyStore.migrate(legacyIdentitiesPath,
672 getRecipientResolver(),
673 getRecipientAddressResolver(),
674 getIdentityKeyStore());
675 migratedLegacyConfig = true;
676 }
677 final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
678 ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
679 LegacyJsonSignalProtocolStore.class)
680 : null;
681 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
682 aciIdentityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
683 registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
684 migratedLegacyConfig = true;
685 }
686
687 this.aciIdentityKeyPair = aciIdentityKeyPair;
688 this.localRegistrationId = registrationId;
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 = identityKeyPair.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 if (getPniIdentityKeyPair() == null) {
1598 setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
1599 } else {
1600 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
1601 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1602 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1603 }
1604 }
1605
1606 public void deleteAccountData() throws IOException {
1607 close();
1608 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1609 .sorted(Comparator.reverseOrder())) {
1610 for (final var file = files.iterator(); file.hasNext(); ) {
1611 Files.delete(file.next());
1612 }
1613 }
1614 Files.delete(getFileName(dataPath, accountPath).toPath());
1615 }
1616
1617 @Override
1618 public void close() {
1619 synchronized (fileChannel) {
1620 if (accountDatabase != null) {
1621 try {
1622 accountDatabase.close();
1623 } catch (SQLException e) {
1624 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1625 }
1626 }
1627 if (messageSendLogStore != null) {
1628 messageSendLogStore.close();
1629 }
1630 try {
1631 try {
1632 lock.close();
1633 } catch (ClosedChannelException ignored) {
1634 }
1635 fileChannel.close();
1636 } catch (IOException e) {
1637 logger.warn("Failed to close account: {}", e.getMessage(), e);
1638 }
1639 }
1640 }
1641
1642 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1643 var value = supplier.get();
1644 if (value != null) {
1645 return value;
1646 }
1647
1648 synchronized (LOCK) {
1649 value = supplier.get();
1650 if (value != null) {
1651 return value;
1652 }
1653 creator.call();
1654 return supplier.get();
1655 }
1656 }
1657
1658 private interface Callable {
1659
1660 void call();
1661 }
1662 }