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