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