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