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