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