]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
db919269db5c693b5c6f844fca2993f415206abb
[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 = 5;
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 final var legacyAciPreKeysPath = getAciPreKeysPath(dataPath, accountPath);
638 if (legacyAciPreKeysPath.exists()) {
639 LegacyPreKeyStore.migrate(legacyAciPreKeysPath, getAciPreKeyStore());
640 migratedLegacyConfig = true;
641 }
642 final var legacyPniPreKeysPath = getPniPreKeysPath(dataPath, accountPath);
643 if (legacyPniPreKeysPath.exists()) {
644 LegacyPreKeyStore.migrate(legacyPniPreKeysPath, getPniPreKeyStore());
645 migratedLegacyConfig = true;
646 }
647 final var legacyAciSignedPreKeysPath = getAciSignedPreKeysPath(dataPath, accountPath);
648 if (legacyAciSignedPreKeysPath.exists()) {
649 LegacySignedPreKeyStore.migrate(legacyAciSignedPreKeysPath, getAciSignedPreKeyStore());
650 migratedLegacyConfig = true;
651 }
652 final var legacyPniSignedPreKeysPath = getPniSignedPreKeysPath(dataPath, accountPath);
653 if (legacyPniSignedPreKeysPath.exists()) {
654 LegacySignedPreKeyStore.migrate(legacyPniSignedPreKeysPath, getPniSignedPreKeyStore());
655 migratedLegacyConfig = true;
656 }
657 final var legacySessionsPath = getSessionsPath(dataPath, accountPath);
658 if (legacySessionsPath.exists()) {
659 LegacySessionStore.migrate(legacySessionsPath,
660 getRecipientResolver(),
661 getRecipientAddressResolver(),
662 getAciSessionStore());
663 migratedLegacyConfig = true;
664 }
665 final var legacyIdentitiesPath = getIdentitiesPath(dataPath, accountPath);
666 if (legacyIdentitiesPath.exists()) {
667 LegacyIdentityKeyStore.migrate(legacyIdentitiesPath,
668 getRecipientResolver(),
669 getRecipientAddressResolver(),
670 getIdentityKeyStore());
671 migratedLegacyConfig = true;
672 }
673 final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
674 ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
675 LegacyJsonSignalProtocolStore.class)
676 : null;
677 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
678 aciIdentityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
679 registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
680 migratedLegacyConfig = true;
681 }
682
683 this.aciIdentityKeyPair = aciIdentityKeyPair;
684 this.localRegistrationId = registrationId;
685 this.trustNewIdentity = trustNewIdentity;
686
687 migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
688
689 final var legacySenderKeysPath = getSenderKeysPath(dataPath, accountPath);
690 if (legacySenderKeysPath.exists()) {
691 LegacySenderKeyRecordStore.migrate(legacySenderKeysPath,
692 getRecipientResolver(),
693 getRecipientAddressResolver(),
694 getSenderKeyStore());
695 migratedLegacyConfig = true;
696 }
697 final var legacySenderKeysSharedPath = getSharedSenderKeysFile(dataPath, accountPath);
698 if (legacySenderKeysSharedPath.exists()) {
699 LegacySenderKeySharedStore.migrate(legacySenderKeysSharedPath,
700 getRecipientResolver(),
701 getRecipientAddressResolver(),
702 getSenderKeyStore());
703 migratedLegacyConfig = true;
704 }
705 if (rootNode.hasNonNull("groupStore")) {
706 final var groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"),
707 LegacyGroupStore.Storage.class);
708 LegacyGroupStore.migrate(groupStoreStorage,
709 getGroupCachePath(dataPath, accountPath),
710 getRecipientResolver(),
711 getGroupStore());
712 migratedLegacyConfig = true;
713 }
714
715 if (rootNode.hasNonNull("stickerStore")) {
716 final var storage = jsonProcessor.convertValue(rootNode.get("stickerStore"),
717 LegacyStickerStore.Storage.class);
718 LegacyStickerStore.migrate(storage, getStickerStore());
719 migratedLegacyConfig = true;
720 }
721
722 if (rootNode.hasNonNull("configurationStore")) {
723 configurationStoreStorage = jsonProcessor.convertValue(rootNode.get("configurationStore"),
724 ConfigurationStore.Storage.class);
725 configurationStore = ConfigurationStore.fromStorage(configurationStoreStorage,
726 this::saveConfigurationStore);
727 } else {
728 configurationStore = new ConfigurationStore(this::saveConfigurationStore);
729 }
730
731 migratedLegacyConfig = loadLegacyThreadStore(rootNode) || migratedLegacyConfig;
732
733 if (migratedLegacyConfig) {
734 save();
735 }
736 }
737
738 private boolean loadLegacyStores(
739 final JsonNode rootNode, final LegacyJsonSignalProtocolStore legacySignalProtocolStore
740 ) {
741 var migrated = false;
742 var legacyRecipientStoreNode = rootNode.get("recipientStore");
743 if (legacyRecipientStoreNode != null) {
744 logger.debug("Migrating legacy recipient store.");
745 var legacyRecipientStore = jsonProcessor.convertValue(legacyRecipientStoreNode, LegacyRecipientStore.class);
746 if (legacyRecipientStore != null) {
747 legacyRecipientStore.getAddresses()
748 .forEach(recipient -> getRecipientStore().resolveRecipientTrusted(recipient));
749 }
750 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
751 migrated = true;
752 }
753
754 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
755 logger.debug("Migrating legacy pre key store.");
756 for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
757 try {
758 getAciPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
759 } catch (InvalidMessageException e) {
760 logger.warn("Failed to migrate pre key, ignoring", e);
761 }
762 }
763 migrated = true;
764 }
765
766 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
767 logger.debug("Migrating legacy signed pre key store.");
768 for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
769 try {
770 getAciSignedPreKeyStore().storeSignedPreKey(entry.getKey(),
771 new SignedPreKeyRecord(entry.getValue()));
772 } catch (InvalidMessageException e) {
773 logger.warn("Failed to migrate signed pre key, ignoring", e);
774 }
775 }
776 migrated = true;
777 }
778
779 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
780 logger.debug("Migrating legacy session store.");
781 for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
782 try {
783 getAciSessionStore().storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
784 session.deviceId), new SessionRecord(session.sessionRecord));
785 } catch (Exception e) {
786 logger.warn("Failed to migrate session, ignoring", e);
787 }
788 }
789 migrated = true;
790 }
791
792 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
793 logger.debug("Migrating legacy identity session store.");
794 for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
795 if (identity.getAddress().serviceId().isEmpty()) {
796 continue;
797 }
798 final var serviceId = identity.getAddress().getServiceId();
799 getIdentityKeyStore().saveIdentity(serviceId, identity.getIdentityKey());
800 getIdentityKeyStore().setIdentityTrustLevel(serviceId,
801 identity.getIdentityKey(),
802 identity.getTrustLevel());
803 }
804 migrated = true;
805 }
806
807 if (rootNode.hasNonNull("contactStore")) {
808 logger.debug("Migrating legacy contact store.");
809 final var contactStoreNode = rootNode.get("contactStore");
810 final var contactStore = jsonProcessor.convertValue(contactStoreNode, LegacyJsonContactsStore.class);
811 for (var contact : contactStore.getContacts()) {
812 final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
813 getContactStore().storeContact(recipientId,
814 new Contact(contact.name,
815 null,
816 contact.color,
817 contact.messageExpirationTime,
818 contact.blocked,
819 contact.archived,
820 false));
821
822 // Store profile keys only in profile store
823 var profileKeyString = contact.profileKey;
824 if (profileKeyString != null) {
825 final ProfileKey profileKey;
826 try {
827 profileKey = new ProfileKey(Base64.getDecoder().decode(profileKeyString));
828 getProfileStore().storeProfileKey(recipientId, profileKey);
829 } catch (InvalidInputException e) {
830 logger.warn("Failed to parse legacy contact profile key: {}", e.getMessage());
831 }
832 }
833 }
834 migrated = true;
835 }
836
837 if (rootNode.hasNonNull("profileStore")) {
838 logger.debug("Migrating legacy profile store.");
839 var profileStoreNode = rootNode.get("profileStore");
840 final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
841 for (var profileEntry : legacyProfileStore.getProfileEntries()) {
842 var recipientId = getRecipientResolver().resolveRecipient(profileEntry.getAddress());
843 // Not migrating profile key credential here, it was changed to expiring profile key credentials
844 getProfileStore().storeProfileKey(recipientId, profileEntry.getProfileKey());
845 final var profile = profileEntry.getProfile();
846 if (profile != null) {
847 final var capabilities = new HashSet<Profile.Capability>();
848 if (profile.getCapabilities() != null) {
849 if (profile.getCapabilities().gv1Migration) {
850 capabilities.add(Profile.Capability.gv1Migration);
851 }
852 if (profile.getCapabilities().storage) {
853 capabilities.add(Profile.Capability.storage);
854 }
855 }
856 final var newProfile = new Profile(profileEntry.getLastUpdateTimestamp(),
857 profile.getGivenName(),
858 profile.getFamilyName(),
859 profile.getAbout(),
860 profile.getAboutEmoji(),
861 null,
862 null,
863 profile.isUnrestrictedUnidentifiedAccess()
864 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
865 : profile.getUnidentifiedAccess() != null
866 ? Profile.UnidentifiedAccessMode.ENABLED
867 : Profile.UnidentifiedAccessMode.DISABLED,
868 capabilities);
869 getProfileStore().storeProfile(recipientId, newProfile);
870 }
871 }
872 }
873
874 return migrated;
875 }
876
877 private boolean loadLegacyThreadStore(final JsonNode rootNode) {
878 var threadStoreNode = rootNode.get("threadStore");
879 if (threadStoreNode != null && !threadStoreNode.isNull()) {
880 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
881 // Migrate thread info to group and contact store
882 for (var thread : threadStore.getThreads()) {
883 if (thread.id == null || thread.id.isEmpty()) {
884 continue;
885 }
886 try {
887 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
888 final var recipientId = getRecipientResolver().resolveRecipient(thread.id);
889 var contact = getContactStore().getContact(recipientId);
890 if (contact != null) {
891 getContactStore().storeContact(recipientId,
892 Contact.newBuilder(contact)
893 .withMessageExpirationTime(thread.messageExpirationTime)
894 .build());
895 }
896 } else {
897 var groupInfo = getGroupStore().getGroup(GroupId.fromBase64(thread.id));
898 if (groupInfo instanceof GroupInfoV1) {
899 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
900 getGroupStore().updateGroup(groupInfo);
901 }
902 }
903 } catch (Exception e) {
904 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
905 }
906 }
907 return true;
908 }
909
910 return false;
911 }
912
913 private void saveConfigurationStore(ConfigurationStore.Storage storage) {
914 this.configurationStoreStorage = storage;
915 save();
916 }
917
918 private void save() {
919 synchronized (fileChannel) {
920 var rootNode = jsonProcessor.createObjectNode();
921 rootNode.put("version", CURRENT_STORAGE_VERSION)
922 .put("username", number)
923 .put("serviceEnvironment", serviceEnvironment == null ? null : serviceEnvironment.name())
924 .put("uuid", aci == null ? null : aci.toString())
925 .put("pni", pni == null ? null : pni.toString())
926 .put("deviceName", encryptedDeviceName)
927 .put("deviceId", deviceId)
928 .put("isMultiDevice", isMultiDevice)
929 .put("lastReceiveTimestamp", lastReceiveTimestamp)
930 .put("password", password)
931 .put("registrationId", localRegistrationId)
932 .put("pniRegistrationId", localPniRegistrationId)
933 .put("identityPrivateKey",
934 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPrivateKey().serialize()))
935 .put("identityKey",
936 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPublicKey().serialize()))
937 .put("pniIdentityPrivateKey",
938 pniIdentityKeyPair == null
939 ? null
940 : Base64.getEncoder()
941 .encodeToString(pniIdentityKeyPair.getPrivateKey().serialize()))
942 .put("pniIdentityKey",
943 pniIdentityKeyPair == null
944 ? null
945 : Base64.getEncoder().encodeToString(pniIdentityKeyPair.getPublicKey().serialize()))
946 .put("registrationLockPin", registrationLockPin)
947 .put("pinMasterKey",
948 pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
949 .put("storageKey",
950 storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
951 .put("storageManifestVersion", storageManifestVersion == -1 ? null : storageManifestVersion)
952 .put("preKeyIdOffset", aciPreKeyIdOffset)
953 .put("nextSignedPreKeyId", aciNextSignedPreKeyId)
954 .put("pniPreKeyIdOffset", pniPreKeyIdOffset)
955 .put("pniNextSignedPreKeyId", pniNextSignedPreKeyId)
956 .put("profileKey",
957 profileKey == null ? null : Base64.getEncoder().encodeToString(profileKey.serialize()))
958 .put("registered", registered)
959 .putPOJO("configurationStore", configurationStoreStorage);
960 try {
961 try (var output = new ByteArrayOutputStream()) {
962 // Write to memory first to prevent corrupting the file in case of serialization errors
963 jsonProcessor.writeValue(output, rootNode);
964 var input = new ByteArrayInputStream(output.toByteArray());
965 fileChannel.position(0);
966 input.transferTo(Channels.newOutputStream(fileChannel));
967 fileChannel.truncate(fileChannel.position());
968 fileChannel.force(false);
969 }
970 } catch (Exception e) {
971 logger.error("Error saving file: {}", e.getMessage(), e);
972 }
973 }
974 }
975
976 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
977 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
978 try {
979 var lock = fileChannel.tryLock();
980 if (lock == null) {
981 if (!waitForLock) {
982 logger.debug("Config file is in use by another instance.");
983 throw new IOException("Config file is in use by another instance.");
984 }
985 logger.info("Config file is in use by another instance, waiting…");
986 lock = fileChannel.lock();
987 logger.info("Config file lock acquired.");
988 }
989 final var result = new Pair<>(fileChannel, lock);
990 fileChannel = null;
991 return result;
992 } finally {
993 if (fileChannel != null) {
994 fileChannel.close();
995 }
996 }
997 }
998
999 public void resetPreKeyOffsets(final ServiceIdType serviceIdType) {
1000 if (serviceIdType.equals(ServiceIdType.ACI)) {
1001 this.aciPreKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE);
1002 this.aciNextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE);
1003 } else {
1004 this.pniPreKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE);
1005 this.pniNextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE);
1006 }
1007 save();
1008 }
1009
1010 public void addPreKeys(ServiceIdType serviceIdType, List<PreKeyRecord> records) {
1011 if (serviceIdType.equals(ServiceIdType.ACI)) {
1012 addAciPreKeys(records);
1013 } else {
1014 addPniPreKeys(records);
1015 }
1016 }
1017
1018 public void addAciPreKeys(List<PreKeyRecord> records) {
1019 for (var record : records) {
1020 if (aciPreKeyIdOffset != record.getId()) {
1021 logger.error("Invalid pre key id {}, expected {}", record.getId(), aciPreKeyIdOffset);
1022 throw new AssertionError("Invalid pre key id");
1023 }
1024 getAciPreKeyStore().storePreKey(record.getId(), record);
1025 aciPreKeyIdOffset = (aciPreKeyIdOffset + 1) % Medium.MAX_VALUE;
1026 }
1027 save();
1028 }
1029
1030 public void addPniPreKeys(List<PreKeyRecord> records) {
1031 for (var record : records) {
1032 if (pniPreKeyIdOffset != record.getId()) {
1033 logger.error("Invalid pre key id {}, expected {}", record.getId(), pniPreKeyIdOffset);
1034 throw new AssertionError("Invalid pre key id");
1035 }
1036 getPniPreKeyStore().storePreKey(record.getId(), record);
1037 pniPreKeyIdOffset = (pniPreKeyIdOffset + 1) % Medium.MAX_VALUE;
1038 }
1039 save();
1040 }
1041
1042 public void addSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord record) {
1043 if (serviceIdType.equals(ServiceIdType.ACI)) {
1044 addAciSignedPreKey(record);
1045 } else {
1046 addPniSignedPreKey(record);
1047 }
1048 }
1049
1050 public void addAciSignedPreKey(SignedPreKeyRecord record) {
1051 if (aciNextSignedPreKeyId != record.getId()) {
1052 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), aciNextSignedPreKeyId);
1053 throw new AssertionError("Invalid signed pre key id");
1054 }
1055 getAciSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
1056 aciNextSignedPreKeyId = (aciNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
1057 save();
1058 }
1059
1060 public void addPniSignedPreKey(SignedPreKeyRecord record) {
1061 if (pniNextSignedPreKeyId != record.getId()) {
1062 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), pniNextSignedPreKeyId);
1063 throw new AssertionError("Invalid signed pre key id");
1064 }
1065 getPniSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
1066 pniNextSignedPreKeyId = (pniNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
1067 save();
1068 }
1069
1070 public int getPreviousStorageVersion() {
1071 return previousStorageVersion;
1072 }
1073
1074 public SignalServiceDataStore getSignalServiceDataStore() {
1075 return new SignalServiceDataStore() {
1076 @Override
1077 public SignalServiceAccountDataStore get(final ServiceId accountIdentifier) {
1078 if (accountIdentifier.equals(aci)) {
1079 return aci();
1080 } else if (accountIdentifier.equals(pni)) {
1081 return pni();
1082 } else {
1083 throw new IllegalArgumentException("No matching store found for " + accountIdentifier);
1084 }
1085 }
1086
1087 @Override
1088 public SignalServiceAccountDataStore aci() {
1089 return getAciSignalServiceAccountDataStore();
1090 }
1091
1092 @Override
1093 public SignalServiceAccountDataStore pni() {
1094 return getPniSignalServiceAccountDataStore();
1095 }
1096
1097 @Override
1098 public boolean isMultiDevice() {
1099 return SignalAccount.this.isMultiDevice();
1100 }
1101 };
1102 }
1103
1104 private SignalServiceAccountDataStore getAciSignalServiceAccountDataStore() {
1105 return getOrCreate(() -> aciSignalProtocolStore,
1106 () -> aciSignalProtocolStore = new SignalProtocolStore(getAciPreKeyStore(),
1107 getAciSignedPreKeyStore(),
1108 getAciSessionStore(),
1109 getAciIdentityKeyStore(),
1110 getSenderKeyStore(),
1111 this::isMultiDevice));
1112 }
1113
1114 private SignalServiceAccountDataStore getPniSignalServiceAccountDataStore() {
1115 return getOrCreate(() -> pniSignalProtocolStore,
1116 () -> pniSignalProtocolStore = new SignalProtocolStore(getPniPreKeyStore(),
1117 getPniSignedPreKeyStore(),
1118 getPniSessionStore(),
1119 getPniIdentityKeyStore(),
1120 getSenderKeyStore(),
1121 this::isMultiDevice));
1122 }
1123
1124 private PreKeyStore getAciPreKeyStore() {
1125 return getOrCreate(() -> aciPreKeyStore,
1126 () -> aciPreKeyStore = new PreKeyStore(getAccountDatabase(), ServiceIdType.ACI));
1127 }
1128
1129 private SignedPreKeyStore getAciSignedPreKeyStore() {
1130 return getOrCreate(() -> aciSignedPreKeyStore,
1131 () -> aciSignedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), ServiceIdType.ACI));
1132 }
1133
1134 private PreKeyStore getPniPreKeyStore() {
1135 return getOrCreate(() -> pniPreKeyStore,
1136 () -> pniPreKeyStore = new PreKeyStore(getAccountDatabase(), ServiceIdType.PNI));
1137 }
1138
1139 private SignedPreKeyStore getPniSignedPreKeyStore() {
1140 return getOrCreate(() -> pniSignedPreKeyStore,
1141 () -> pniSignedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), ServiceIdType.PNI));
1142 }
1143
1144 public SessionStore getAciSessionStore() {
1145 return getOrCreate(() -> aciSessionStore,
1146 () -> aciSessionStore = new SessionStore(getAccountDatabase(), ServiceIdType.ACI));
1147 }
1148
1149 public SessionStore getPniSessionStore() {
1150 return getOrCreate(() -> pniSessionStore,
1151 () -> pniSessionStore = new SessionStore(getAccountDatabase(), ServiceIdType.PNI));
1152 }
1153
1154 public IdentityKeyStore getIdentityKeyStore() {
1155 return getOrCreate(() -> identityKeyStore,
1156 () -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(), trustNewIdentity));
1157 }
1158
1159 public SignalIdentityKeyStore getAciIdentityKeyStore() {
1160 return getOrCreate(() -> aciIdentityKeyStore,
1161 () -> aciIdentityKeyStore = new SignalIdentityKeyStore(getRecipientResolver(),
1162 () -> aciIdentityKeyPair,
1163 localRegistrationId,
1164 getIdentityKeyStore()));
1165 }
1166
1167 public SignalIdentityKeyStore getPniIdentityKeyStore() {
1168 return getOrCreate(() -> pniIdentityKeyStore,
1169 () -> pniIdentityKeyStore = new SignalIdentityKeyStore(getRecipientResolver(),
1170 () -> pniIdentityKeyPair,
1171 localRegistrationId,
1172 getIdentityKeyStore()));
1173 }
1174
1175 public GroupStore getGroupStore() {
1176 return getOrCreate(() -> groupStore,
1177 () -> groupStore = new GroupStore(getAccountDatabase(),
1178 getRecipientResolver(),
1179 getRecipientIdCreator()));
1180 }
1181
1182 public ContactsStore getContactStore() {
1183 return getRecipientStore();
1184 }
1185
1186 private RecipientIdCreator getRecipientIdCreator() {
1187 return recipientId -> getRecipientStore().create(recipientId);
1188 }
1189
1190 public RecipientResolver getRecipientResolver() {
1191 return new RecipientResolver.RecipientResolverWrapper(this::getRecipientStore);
1192 }
1193
1194 public RecipientTrustedResolver getRecipientTrustedResolver() {
1195 return new RecipientTrustedResolver.RecipientTrustedResolverWrapper(this::getRecipientStore);
1196 }
1197
1198 public RecipientAddressResolver getRecipientAddressResolver() {
1199 return recipientId -> getRecipientStore().resolveRecipientAddress(recipientId);
1200 }
1201
1202 public RecipientStore getRecipientStore() {
1203 return getOrCreate(() -> recipientStore,
1204 () -> recipientStore = new RecipientStore(this::mergeRecipients,
1205 this::getSelfRecipientAddress,
1206 getAccountDatabase()));
1207 }
1208
1209 public ProfileStore getProfileStore() {
1210 return getRecipientStore();
1211 }
1212
1213 public StickerStore getStickerStore() {
1214 return getOrCreate(() -> stickerStore, () -> stickerStore = new StickerStore(getAccountDatabase()));
1215 }
1216
1217 public SenderKeyStore getSenderKeyStore() {
1218 return getOrCreate(() -> senderKeyStore, () -> senderKeyStore = new SenderKeyStore(getAccountDatabase()));
1219 }
1220
1221 public ConfigurationStore getConfigurationStore() {
1222 return configurationStore;
1223 }
1224
1225 public MessageCache getMessageCache() {
1226 return getOrCreate(() -> messageCache,
1227 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
1228 }
1229
1230 public AccountDatabase getAccountDatabase() {
1231 return getOrCreate(() -> accountDatabase, () -> {
1232 try {
1233 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
1234 } catch (SQLException e) {
1235 throw new RuntimeException(e);
1236 }
1237 });
1238 }
1239
1240 public MessageSendLogStore getMessageSendLogStore() {
1241 return getOrCreate(() -> messageSendLogStore,
1242 () -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase()));
1243 }
1244
1245 public CredentialsProvider getCredentialsProvider() {
1246 return new CredentialsProvider() {
1247 @Override
1248 public ACI getAci() {
1249 return aci;
1250 }
1251
1252 @Override
1253 public PNI getPni() {
1254 return pni;
1255 }
1256
1257 @Override
1258 public String getE164() {
1259 return number;
1260 }
1261
1262 @Override
1263 public String getPassword() {
1264 return password;
1265 }
1266
1267 @Override
1268 public int getDeviceId() {
1269 return deviceId;
1270 }
1271 };
1272 }
1273
1274 public String getNumber() {
1275 return number;
1276 }
1277
1278 public void setNumber(final String number) {
1279 this.number = number;
1280 save();
1281 }
1282
1283 public ServiceEnvironment getServiceEnvironment() {
1284 return serviceEnvironment;
1285 }
1286
1287 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1288 this.serviceEnvironment = serviceEnvironment;
1289 save();
1290 }
1291
1292 public ServiceId getAccountId(ServiceIdType serviceIdType) {
1293 return serviceIdType.equals(ServiceIdType.ACI) ? aci : pni;
1294 }
1295
1296 public ACI getAci() {
1297 return aci;
1298 }
1299
1300 public void setAci(final ACI aci) {
1301 this.aci = aci;
1302 save();
1303 }
1304
1305 public PNI getPni() {
1306 return pni;
1307 }
1308
1309 public void setPni(final PNI updatedPni) {
1310 if (this.pni != null && !this.pni.equals(updatedPni)) {
1311 // Clear data for old PNI
1312 identityKeyStore.deleteIdentity(this.pni);
1313 getPniPreKeyStore().removeAllPreKeys();
1314 getPniSignedPreKeyStore().removeAllSignedPreKeys();
1315 }
1316
1317 this.pni = updatedPni;
1318 save();
1319 }
1320
1321 public void setPni(
1322 final PNI updatedPni,
1323 final IdentityKeyPair pniIdentityKeyPair,
1324 final SignedPreKeyRecord pniSignedPreKey,
1325 final int localPniRegistrationId
1326 ) {
1327 setPni(updatedPni);
1328
1329 setPniIdentityKeyPair(pniIdentityKeyPair);
1330 addPniSignedPreKey(pniSignedPreKey);
1331 setLocalPniRegistrationId(localPniRegistrationId);
1332 }
1333
1334 public SignalServiceAddress getSelfAddress() {
1335 return new SignalServiceAddress(aci, number);
1336 }
1337
1338 public RecipientAddress getSelfRecipientAddress() {
1339 return new RecipientAddress(aci, pni, number);
1340 }
1341
1342 public RecipientId getSelfRecipientId() {
1343 return getRecipientResolver().resolveRecipient(getSelfRecipientAddress());
1344 }
1345
1346 public byte[] getEncryptedDeviceName() {
1347 return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
1348 }
1349
1350 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1351 this.encryptedDeviceName = encryptedDeviceName;
1352 save();
1353 }
1354
1355 public int getDeviceId() {
1356 return deviceId;
1357 }
1358
1359 public boolean isPrimaryDevice() {
1360 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1361 }
1362
1363 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1364 return serviceIdType.equals(ServiceIdType.ACI) ? aciIdentityKeyPair : pniIdentityKeyPair;
1365 }
1366
1367 public IdentityKeyPair getAciIdentityKeyPair() {
1368 return aciIdentityKeyPair;
1369 }
1370
1371 public IdentityKeyPair getPniIdentityKeyPair() {
1372 return pniIdentityKeyPair;
1373 }
1374
1375 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1376 pniIdentityKeyPair = identityKeyPair;
1377 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
1378 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1379 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1380 save();
1381 }
1382
1383 public int getLocalRegistrationId() {
1384 return localRegistrationId;
1385 }
1386
1387 public int getLocalPniRegistrationId() {
1388 return localPniRegistrationId;
1389 }
1390
1391 public void setLocalPniRegistrationId(final int localPniRegistrationId) {
1392 this.localPniRegistrationId = localPniRegistrationId;
1393 save();
1394 }
1395
1396 public String getPassword() {
1397 return password;
1398 }
1399
1400 private void setPassword(final String password) {
1401 this.password = password;
1402 save();
1403 }
1404
1405 public void setRegistrationLockPin(final String registrationLockPin) {
1406 this.registrationLockPin = registrationLockPin;
1407 save();
1408 }
1409
1410 public String getRegistrationLockPin() {
1411 return registrationLockPin;
1412 }
1413
1414 public String getRegistrationLock() {
1415 final var masterKey = getPinBackedMasterKey();
1416 if (masterKey == null) {
1417 return null;
1418 }
1419 return masterKey.deriveRegistrationLock();
1420 }
1421
1422 public MasterKey getPinBackedMasterKey() {
1423 if (registrationLockPin == null) {
1424 return null;
1425 }
1426 return pinMasterKey;
1427 }
1428
1429 public MasterKey getOrCreatePinMasterKey() {
1430 if (pinMasterKey == null) {
1431 pinMasterKey = KeyUtils.createMasterKey();
1432 save();
1433 }
1434 return pinMasterKey;
1435 }
1436
1437 public StorageKey getStorageKey() {
1438 if (pinMasterKey != null) {
1439 return pinMasterKey.deriveStorageServiceKey();
1440 }
1441 return storageKey;
1442 }
1443
1444 public StorageKey getOrCreateStorageKey() {
1445 if (isPrimaryDevice()) {
1446 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1447 }
1448 return storageKey;
1449 }
1450
1451 public void setStorageKey(final StorageKey storageKey) {
1452 if (storageKey.equals(this.storageKey)) {
1453 return;
1454 }
1455 this.storageKey = storageKey;
1456 save();
1457 }
1458
1459 public long getStorageManifestVersion() {
1460 return this.storageManifestVersion;
1461 }
1462
1463 public void setStorageManifestVersion(final long storageManifestVersion) {
1464 if (storageManifestVersion == this.storageManifestVersion) {
1465 return;
1466 }
1467 this.storageManifestVersion = storageManifestVersion;
1468 save();
1469 }
1470
1471 public Optional<SignalStorageManifest> getStorageManifest() {
1472 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1473 if (!storageManifestFile.exists()) {
1474 return Optional.empty();
1475 }
1476 try (var inputStream = new FileInputStream(storageManifestFile)) {
1477 return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
1478 } catch (IOException e) {
1479 logger.warn("Failed to read local storage manifest.", e);
1480 return Optional.empty();
1481 }
1482 }
1483
1484 public void setStorageManifest(SignalStorageManifest manifest) {
1485 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1486 if (manifest == null) {
1487 if (storageManifestFile.exists()) {
1488 try {
1489 Files.delete(storageManifestFile.toPath());
1490 } catch (IOException e) {
1491 logger.error("Failed to delete local storage manifest.", e);
1492 }
1493 }
1494 return;
1495 }
1496
1497 final var manifestBytes = manifest.serialize();
1498 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1499 outputStream.write(manifestBytes);
1500 } catch (IOException e) {
1501 logger.error("Failed to store local storage manifest.", e);
1502 }
1503 }
1504
1505 public ProfileKey getProfileKey() {
1506 return profileKey;
1507 }
1508
1509 public void setProfileKey(final ProfileKey profileKey) {
1510 if (profileKey.equals(this.profileKey)) {
1511 return;
1512 }
1513 this.profileKey = profileKey;
1514 save();
1515 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
1516 }
1517
1518 public byte[] getSelfUnidentifiedAccessKey() {
1519 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1520 }
1521
1522 public int getPreKeyIdOffset(ServiceIdType serviceIdType) {
1523 return serviceIdType.equals(ServiceIdType.ACI) ? aciPreKeyIdOffset : pniPreKeyIdOffset;
1524 }
1525
1526 public int getNextSignedPreKeyId(ServiceIdType serviceIdType) {
1527 return serviceIdType.equals(ServiceIdType.ACI) ? aciNextSignedPreKeyId : pniNextSignedPreKeyId;
1528 }
1529
1530 public boolean isRegistered() {
1531 return registered;
1532 }
1533
1534 public void setRegistered(final boolean registered) {
1535 this.registered = registered;
1536 save();
1537 }
1538
1539 public boolean isMultiDevice() {
1540 return isMultiDevice;
1541 }
1542
1543 public void setMultiDevice(final boolean multiDevice) {
1544 if (isMultiDevice == multiDevice) {
1545 return;
1546 }
1547 isMultiDevice = multiDevice;
1548 save();
1549 }
1550
1551 public long getLastReceiveTimestamp() {
1552 return lastReceiveTimestamp;
1553 }
1554
1555 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1556 this.lastReceiveTimestamp = lastReceiveTimestamp;
1557 save();
1558 }
1559
1560 public boolean isUnrestrictedUnidentifiedAccess() {
1561 final var profile = getProfileStore().getProfile(getSelfRecipientId());
1562 return profile != null && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED;
1563 }
1564
1565 public boolean isDiscoverableByPhoneNumber() {
1566 final var phoneNumberUnlisted = configurationStore.getPhoneNumberUnlisted();
1567 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1568 }
1569
1570 public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
1571 this.pinMasterKey = masterKey;
1572 this.storageManifestVersion = -1;
1573 this.setStorageManifest(null);
1574 this.storageKey = null;
1575 this.encryptedDeviceName = null;
1576 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1577 this.isMultiDevice = false;
1578 this.registered = true;
1579 this.aci = aci;
1580 this.pni = pni;
1581 this.registrationLockPin = pin;
1582 this.lastReceiveTimestamp = 0;
1583 save();
1584
1585 clearAllPreKeys();
1586 getAciSessionStore().archiveAllSessions();
1587 getPniSessionStore().archiveAllSessions();
1588 getSenderKeyStore().deleteAll();
1589 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1590 final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
1591 getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
1592 getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
1593 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
1594 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1595 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1596 }
1597
1598 public void deleteAccountData() throws IOException {
1599 close();
1600 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1601 .sorted(Comparator.reverseOrder())) {
1602 for (final var file = files.iterator(); file.hasNext(); ) {
1603 Files.delete(file.next());
1604 }
1605 }
1606 Files.delete(getFileName(dataPath, accountPath).toPath());
1607 }
1608
1609 @Override
1610 public void close() {
1611 synchronized (fileChannel) {
1612 if (accountDatabase != null) {
1613 try {
1614 accountDatabase.close();
1615 } catch (SQLException e) {
1616 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1617 }
1618 }
1619 if (messageSendLogStore != null) {
1620 messageSendLogStore.close();
1621 }
1622 try {
1623 try {
1624 lock.close();
1625 } catch (ClosedChannelException ignored) {
1626 }
1627 fileChannel.close();
1628 } catch (IOException e) {
1629 logger.warn("Failed to close account: {}", e.getMessage(), e);
1630 }
1631 }
1632 }
1633
1634 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1635 var value = supplier.get();
1636 if (value != null) {
1637 return value;
1638 }
1639
1640 synchronized (LOCK) {
1641 value = supplier.get();
1642 if (value != null) {
1643 return value;
1644 }
1645 creator.call();
1646 return supplier.get();
1647 }
1648 }
1649
1650 private interface Callable {
1651
1652 void call();
1653 }
1654 }