]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
1759649e6a4c3d5c9795b13e166fb08da6afe415
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / SignalAccount.java
1 package org.asamk.signal.manager.storage;
2
3 import com.fasterxml.jackson.databind.JsonNode;
4 import com.fasterxml.jackson.databind.ObjectMapper;
5
6 import org.asamk.signal.manager.api.Pair;
7 import org.asamk.signal.manager.api.TrustLevel;
8 import org.asamk.signal.manager.config.ServiceEnvironment;
9 import org.asamk.signal.manager.groups.GroupId;
10 import org.asamk.signal.manager.helper.RecipientAddressResolver;
11 import org.asamk.signal.manager.storage.configuration.ConfigurationStore;
12 import org.asamk.signal.manager.storage.contacts.ContactsStore;
13 import org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore;
14 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
15 import org.asamk.signal.manager.storage.groups.GroupStore;
16 import org.asamk.signal.manager.storage.groups.LegacyGroupStore;
17 import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
18 import org.asamk.signal.manager.storage.identities.LegacyIdentityKeyStore;
19 import org.asamk.signal.manager.storage.identities.SignalIdentityKeyStore;
20 import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
21 import org.asamk.signal.manager.storage.messageCache.MessageCache;
22 import org.asamk.signal.manager.storage.prekeys.LegacyPreKeyStore;
23 import org.asamk.signal.manager.storage.prekeys.LegacySignedPreKeyStore;
24 import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
25 import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
26 import org.asamk.signal.manager.storage.profiles.LegacyProfileStore;
27 import org.asamk.signal.manager.storage.profiles.ProfileStore;
28 import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
29 import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
30 import org.asamk.signal.manager.storage.recipients.Contact;
31 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
32 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2;
33 import org.asamk.signal.manager.storage.recipients.Profile;
34 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
35 import org.asamk.signal.manager.storage.recipients.RecipientId;
36 import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
37 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
38 import org.asamk.signal.manager.storage.recipients.RecipientStore;
39 import org.asamk.signal.manager.storage.recipients.RecipientTrustedResolver;
40 import org.asamk.signal.manager.storage.sendLog.MessageSendLogStore;
41 import org.asamk.signal.manager.storage.senderKeys.LegacySenderKeyRecordStore;
42 import org.asamk.signal.manager.storage.senderKeys.LegacySenderKeySharedStore;
43 import org.asamk.signal.manager.storage.senderKeys.SenderKeyStore;
44 import org.asamk.signal.manager.storage.sessions.LegacySessionStore;
45 import org.asamk.signal.manager.storage.sessions.SessionStore;
46 import org.asamk.signal.manager.storage.stickers.LegacyStickerStore;
47 import org.asamk.signal.manager.storage.stickers.StickerStore;
48 import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
49 import org.asamk.signal.manager.util.IOUtils;
50 import org.asamk.signal.manager.util.KeyUtils;
51 import org.signal.libsignal.protocol.IdentityKeyPair;
52 import org.signal.libsignal.protocol.InvalidMessageException;
53 import org.signal.libsignal.protocol.SignalProtocolAddress;
54 import org.signal.libsignal.protocol.state.PreKeyRecord;
55 import org.signal.libsignal.protocol.state.SessionRecord;
56 import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
57 import org.signal.libsignal.protocol.util.KeyHelper;
58 import org.signal.libsignal.protocol.util.Medium;
59 import org.signal.libsignal.zkgroup.InvalidInputException;
60 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63 import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
64 import org.whispersystems.signalservice.api.SignalServiceDataStore;
65 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
66 import org.whispersystems.signalservice.api.kbs.MasterKey;
67 import org.whispersystems.signalservice.api.push.ACI;
68 import org.whispersystems.signalservice.api.push.PNI;
69 import org.whispersystems.signalservice.api.push.ServiceId;
70 import org.whispersystems.signalservice.api.push.ServiceIdType;
71 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
72 import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
73 import org.whispersystems.signalservice.api.storage.StorageKey;
74 import org.whispersystems.signalservice.api.util.CredentialsProvider;
75 import org.whispersystems.signalservice.api.util.UuidUtil;
76
77 import java.io.ByteArrayInputStream;
78 import java.io.ByteArrayOutputStream;
79 import java.io.Closeable;
80 import java.io.File;
81 import java.io.FileInputStream;
82 import java.io.FileOutputStream;
83 import java.io.IOException;
84 import java.io.RandomAccessFile;
85 import java.nio.channels.Channels;
86 import java.nio.channels.ClosedChannelException;
87 import java.nio.channels.FileChannel;
88 import java.nio.channels.FileLock;
89 import java.nio.file.Files;
90 import java.security.SecureRandom;
91 import java.sql.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 TrustNewIdentity trustNewIdentity;
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 TrustNewIdentity trustNewIdentity
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, trustNewIdentity);
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 TrustNewIdentity trustNewIdentity
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.trustNewIdentity = trustNewIdentity;
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 TrustNewIdentity trustNewIdentity
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 trustNewIdentity);
271 }
272
273 final var signalAccount = load(dataPath, accountPath, true, trustNewIdentity);
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 TrustNewIdentity trustNewIdentity
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.trustNewIdentity = trustNewIdentity;
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 TrustNewIdentity trustNewIdentity
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.trustNewIdentity = trustNewIdentity;
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(), 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 }
1247
1248 public CredentialsProvider getCredentialsProvider() {
1249 return new CredentialsProvider() {
1250 @Override
1251 public ACI getAci() {
1252 return aci;
1253 }
1254
1255 @Override
1256 public PNI getPni() {
1257 return pni;
1258 }
1259
1260 @Override
1261 public String getE164() {
1262 return number;
1263 }
1264
1265 @Override
1266 public String getPassword() {
1267 return password;
1268 }
1269
1270 @Override
1271 public int getDeviceId() {
1272 return deviceId;
1273 }
1274 };
1275 }
1276
1277 public String getNumber() {
1278 return number;
1279 }
1280
1281 public void setNumber(final String number) {
1282 this.number = number;
1283 save();
1284 }
1285
1286 public ServiceEnvironment getServiceEnvironment() {
1287 return serviceEnvironment;
1288 }
1289
1290 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1291 this.serviceEnvironment = serviceEnvironment;
1292 save();
1293 }
1294
1295 public ServiceId getAccountId(ServiceIdType serviceIdType) {
1296 return serviceIdType.equals(ServiceIdType.ACI) ? aci : pni;
1297 }
1298
1299 public ACI getAci() {
1300 return aci;
1301 }
1302
1303 public void setAci(final ACI aci) {
1304 this.aci = aci;
1305 save();
1306 }
1307
1308 public PNI getPni() {
1309 return pni;
1310 }
1311
1312 public void setPni(final PNI updatedPni) {
1313 if (this.pni != null && !this.pni.equals(updatedPni)) {
1314 // Clear data for old PNI
1315 identityKeyStore.deleteIdentity(this.pni);
1316 getPniPreKeyStore().removeAllPreKeys();
1317 getPniSignedPreKeyStore().removeAllSignedPreKeys();
1318 }
1319
1320 this.pni = updatedPni;
1321 save();
1322 }
1323
1324 public void setPni(
1325 final PNI updatedPni,
1326 final IdentityKeyPair pniIdentityKeyPair,
1327 final SignedPreKeyRecord pniSignedPreKey,
1328 final int localPniRegistrationId
1329 ) {
1330 setPni(updatedPni);
1331
1332 setPniIdentityKeyPair(pniIdentityKeyPair);
1333 addPniSignedPreKey(pniSignedPreKey);
1334 setLocalPniRegistrationId(localPniRegistrationId);
1335 }
1336
1337 public SignalServiceAddress getSelfAddress() {
1338 return new SignalServiceAddress(aci, number);
1339 }
1340
1341 public RecipientAddress getSelfRecipientAddress() {
1342 return new RecipientAddress(aci, pni, number);
1343 }
1344
1345 public RecipientId getSelfRecipientId() {
1346 return getRecipientResolver().resolveRecipient(getSelfRecipientAddress());
1347 }
1348
1349 public byte[] getEncryptedDeviceName() {
1350 return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
1351 }
1352
1353 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1354 this.encryptedDeviceName = encryptedDeviceName;
1355 save();
1356 }
1357
1358 public int getDeviceId() {
1359 return deviceId;
1360 }
1361
1362 public boolean isPrimaryDevice() {
1363 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1364 }
1365
1366 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1367 return serviceIdType.equals(ServiceIdType.ACI) ? aciIdentityKeyPair : pniIdentityKeyPair;
1368 }
1369
1370 public IdentityKeyPair getAciIdentityKeyPair() {
1371 return aciIdentityKeyPair;
1372 }
1373
1374 public IdentityKeyPair getPniIdentityKeyPair() {
1375 return pniIdentityKeyPair;
1376 }
1377
1378 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1379 pniIdentityKeyPair = identityKeyPair;
1380 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
1381 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1382 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1383 save();
1384 }
1385
1386 public int getLocalRegistrationId() {
1387 return localRegistrationId;
1388 }
1389
1390 public int getLocalPniRegistrationId() {
1391 return localPniRegistrationId;
1392 }
1393
1394 public void setLocalPniRegistrationId(final int localPniRegistrationId) {
1395 this.localPniRegistrationId = localPniRegistrationId;
1396 save();
1397 }
1398
1399 public String getPassword() {
1400 return password;
1401 }
1402
1403 private void setPassword(final String password) {
1404 this.password = password;
1405 save();
1406 }
1407
1408 public void setRegistrationLockPin(final String registrationLockPin) {
1409 this.registrationLockPin = registrationLockPin;
1410 save();
1411 }
1412
1413 public String getRegistrationLockPin() {
1414 return registrationLockPin;
1415 }
1416
1417 public String getRegistrationLock() {
1418 final var masterKey = getPinBackedMasterKey();
1419 if (masterKey == null) {
1420 return null;
1421 }
1422 return masterKey.deriveRegistrationLock();
1423 }
1424
1425 public MasterKey getPinBackedMasterKey() {
1426 if (registrationLockPin == null) {
1427 return null;
1428 }
1429 return pinMasterKey;
1430 }
1431
1432 public MasterKey getOrCreatePinMasterKey() {
1433 if (pinMasterKey == null) {
1434 pinMasterKey = KeyUtils.createMasterKey();
1435 save();
1436 }
1437 return pinMasterKey;
1438 }
1439
1440 public StorageKey getStorageKey() {
1441 if (pinMasterKey != null) {
1442 return pinMasterKey.deriveStorageServiceKey();
1443 }
1444 return storageKey;
1445 }
1446
1447 public StorageKey getOrCreateStorageKey() {
1448 if (isPrimaryDevice()) {
1449 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1450 }
1451 return storageKey;
1452 }
1453
1454 public void setStorageKey(final StorageKey storageKey) {
1455 if (storageKey.equals(this.storageKey)) {
1456 return;
1457 }
1458 this.storageKey = storageKey;
1459 save();
1460 }
1461
1462 public long getStorageManifestVersion() {
1463 return this.storageManifestVersion;
1464 }
1465
1466 public void setStorageManifestVersion(final long storageManifestVersion) {
1467 if (storageManifestVersion == this.storageManifestVersion) {
1468 return;
1469 }
1470 this.storageManifestVersion = storageManifestVersion;
1471 save();
1472 }
1473
1474 public Optional<SignalStorageManifest> getStorageManifest() {
1475 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1476 if (!storageManifestFile.exists()) {
1477 return Optional.empty();
1478 }
1479 try (var inputStream = new FileInputStream(storageManifestFile)) {
1480 return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
1481 } catch (IOException e) {
1482 logger.warn("Failed to read local storage manifest.", e);
1483 return Optional.empty();
1484 }
1485 }
1486
1487 public void setStorageManifest(SignalStorageManifest manifest) {
1488 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1489 if (manifest == null) {
1490 if (storageManifestFile.exists()) {
1491 try {
1492 Files.delete(storageManifestFile.toPath());
1493 } catch (IOException e) {
1494 logger.error("Failed to delete local storage manifest.", e);
1495 }
1496 }
1497 return;
1498 }
1499
1500 final var manifestBytes = manifest.serialize();
1501 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1502 outputStream.write(manifestBytes);
1503 } catch (IOException e) {
1504 logger.error("Failed to store local storage manifest.", e);
1505 }
1506 }
1507
1508 public ProfileKey getProfileKey() {
1509 return profileKey;
1510 }
1511
1512 public void setProfileKey(final ProfileKey profileKey) {
1513 if (profileKey.equals(this.profileKey)) {
1514 return;
1515 }
1516 this.profileKey = profileKey;
1517 save();
1518 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
1519 }
1520
1521 public byte[] getSelfUnidentifiedAccessKey() {
1522 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1523 }
1524
1525 public int getPreKeyIdOffset(ServiceIdType serviceIdType) {
1526 return serviceIdType.equals(ServiceIdType.ACI) ? aciPreKeyIdOffset : pniPreKeyIdOffset;
1527 }
1528
1529 public int getNextSignedPreKeyId(ServiceIdType serviceIdType) {
1530 return serviceIdType.equals(ServiceIdType.ACI) ? aciNextSignedPreKeyId : pniNextSignedPreKeyId;
1531 }
1532
1533 public boolean isRegistered() {
1534 return registered;
1535 }
1536
1537 public void setRegistered(final boolean registered) {
1538 this.registered = registered;
1539 save();
1540 }
1541
1542 public boolean isMultiDevice() {
1543 return isMultiDevice;
1544 }
1545
1546 public void setMultiDevice(final boolean multiDevice) {
1547 if (isMultiDevice == multiDevice) {
1548 return;
1549 }
1550 isMultiDevice = multiDevice;
1551 save();
1552 }
1553
1554 public long getLastReceiveTimestamp() {
1555 return lastReceiveTimestamp;
1556 }
1557
1558 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1559 this.lastReceiveTimestamp = lastReceiveTimestamp;
1560 save();
1561 }
1562
1563 public boolean isUnrestrictedUnidentifiedAccess() {
1564 final var profile = getProfileStore().getProfile(getSelfRecipientId());
1565 return profile != null && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED;
1566 }
1567
1568 public boolean isDiscoverableByPhoneNumber() {
1569 final var phoneNumberUnlisted = configurationStore.getPhoneNumberUnlisted();
1570 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1571 }
1572
1573 public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
1574 this.pinMasterKey = masterKey;
1575 this.storageManifestVersion = -1;
1576 this.setStorageManifest(null);
1577 this.storageKey = null;
1578 this.encryptedDeviceName = null;
1579 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1580 this.isMultiDevice = false;
1581 this.registered = true;
1582 this.aci = aci;
1583 this.pni = pni;
1584 this.registrationLockPin = pin;
1585 this.lastReceiveTimestamp = 0;
1586 save();
1587
1588 clearAllPreKeys();
1589 getAciSessionStore().archiveAllSessions();
1590 getPniSessionStore().archiveAllSessions();
1591 getSenderKeyStore().deleteAll();
1592 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1593 final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
1594 getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
1595 getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
1596 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
1597 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1598 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1599 }
1600
1601 public void deleteAccountData() throws IOException {
1602 close();
1603 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1604 .sorted(Comparator.reverseOrder())) {
1605 for (final var file = files.iterator(); file.hasNext(); ) {
1606 Files.delete(file.next());
1607 }
1608 }
1609 Files.delete(getFileName(dataPath, accountPath).toPath());
1610 }
1611
1612 @Override
1613 public void close() {
1614 synchronized (fileChannel) {
1615 if (accountDatabase != null) {
1616 try {
1617 accountDatabase.close();
1618 } catch (SQLException e) {
1619 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1620 }
1621 }
1622 if (messageSendLogStore != null) {
1623 messageSendLogStore.close();
1624 }
1625 try {
1626 try {
1627 lock.close();
1628 } catch (ClosedChannelException ignored) {
1629 }
1630 fileChannel.close();
1631 } catch (IOException e) {
1632 logger.warn("Failed to close account: {}", e.getMessage(), e);
1633 }
1634 }
1635 }
1636
1637 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1638 var value = supplier.get();
1639 if (value != null) {
1640 return value;
1641 }
1642
1643 synchronized (LOCK) {
1644 value = supplier.get();
1645 if (value != null) {
1646 return value;
1647 }
1648 creator.call();
1649 return supplier.get();
1650 }
1651 }
1652
1653 private interface Callable {
1654
1655 void call();
1656 }
1657 }