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