]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
f23aea03f3276717bc58caa8d8f2924edc0d55f2
[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.TrustLevel;
7 import org.asamk.signal.manager.api.Pair;
8 import org.asamk.signal.manager.groups.GroupId;
9 import org.asamk.signal.manager.storage.configuration.ConfigurationStore;
10 import org.asamk.signal.manager.storage.contacts.ContactsStore;
11 import org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore;
12 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
13 import org.asamk.signal.manager.storage.groups.GroupInfoV2;
14 import org.asamk.signal.manager.storage.groups.GroupStore;
15 import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
16 import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
17 import org.asamk.signal.manager.storage.messageCache.MessageCache;
18 import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
19 import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
20 import org.asamk.signal.manager.storage.profiles.LegacyProfileStore;
21 import org.asamk.signal.manager.storage.profiles.ProfileStore;
22 import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
23 import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
24 import org.asamk.signal.manager.storage.recipients.Contact;
25 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
26 import org.asamk.signal.manager.storage.recipients.Profile;
27 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
28 import org.asamk.signal.manager.storage.recipients.RecipientId;
29 import org.asamk.signal.manager.storage.recipients.RecipientStore;
30 import org.asamk.signal.manager.storage.senderKeys.SenderKeyStore;
31 import org.asamk.signal.manager.storage.sessions.SessionStore;
32 import org.asamk.signal.manager.storage.stickers.StickerStore;
33 import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
34 import org.asamk.signal.manager.util.IOUtils;
35 import org.asamk.signal.manager.util.KeyUtils;
36 import org.signal.zkgroup.InvalidInputException;
37 import org.signal.zkgroup.profiles.ProfileKey;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40 import org.whispersystems.libsignal.IdentityKeyPair;
41 import org.whispersystems.libsignal.SignalProtocolAddress;
42 import org.whispersystems.libsignal.state.PreKeyRecord;
43 import org.whispersystems.libsignal.state.SessionRecord;
44 import org.whispersystems.libsignal.state.SignedPreKeyRecord;
45 import org.whispersystems.libsignal.util.Medium;
46 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
47 import org.whispersystems.signalservice.api.kbs.MasterKey;
48 import org.whispersystems.signalservice.api.push.ACI;
49 import org.whispersystems.signalservice.api.push.DistributionId;
50 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
51 import org.whispersystems.signalservice.api.storage.StorageKey;
52 import org.whispersystems.signalservice.api.util.UuidUtil;
53
54 import java.io.ByteArrayInputStream;
55 import java.io.ByteArrayOutputStream;
56 import java.io.Closeable;
57 import java.io.File;
58 import java.io.IOException;
59 import java.io.RandomAccessFile;
60 import java.nio.channels.Channels;
61 import java.nio.channels.ClosedChannelException;
62 import java.nio.channels.FileChannel;
63 import java.nio.channels.FileLock;
64 import java.security.SecureRandom;
65 import java.util.Base64;
66 import java.util.Date;
67 import java.util.HashSet;
68 import java.util.List;
69 import java.util.function.Supplier;
70
71 public class SignalAccount implements Closeable {
72
73 private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
74
75 private static final int MINIMUM_STORAGE_VERSION = 1;
76 private static final int CURRENT_STORAGE_VERSION = 3;
77
78 private final Object LOCK = new Object();
79
80 private final ObjectMapper jsonProcessor = Utils.createStorageObjectMapper();
81
82 private final FileChannel fileChannel;
83 private final FileLock lock;
84
85 private int previousStorageVersion;
86
87 private File dataPath;
88 private String account;
89 private ACI aci;
90 private String encryptedDeviceName;
91 private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
92 private boolean isMultiDevice = false;
93 private String password;
94 private String registrationLockPin;
95 private MasterKey pinMasterKey;
96 private StorageKey storageKey;
97 private long storageManifestVersion = -1;
98 private ProfileKey profileKey;
99 private int preKeyIdOffset;
100 private int nextSignedPreKeyId;
101 private IdentityKeyPair identityKeyPair;
102 private int localRegistrationId;
103 private TrustNewIdentity trustNewIdentity;
104 private long lastReceiveTimestamp = 0;
105
106 private boolean registered = false;
107
108 private SignalProtocolStore signalProtocolStore;
109 private PreKeyStore preKeyStore;
110 private SignedPreKeyStore signedPreKeyStore;
111 private SessionStore sessionStore;
112 private IdentityKeyStore identityKeyStore;
113 private SenderKeyStore senderKeyStore;
114 private GroupStore groupStore;
115 private GroupStore.Storage groupStoreStorage;
116 private RecipientStore recipientStore;
117 private StickerStore stickerStore;
118 private StickerStore.Storage stickerStoreStorage;
119 private ConfigurationStore configurationStore;
120 private ConfigurationStore.Storage configurationStoreStorage;
121
122 private MessageCache messageCache;
123
124 private SignalAccount(final FileChannel fileChannel, final FileLock lock) {
125 this.fileChannel = fileChannel;
126 this.lock = lock;
127 }
128
129 public static SignalAccount load(
130 File dataPath, String account, boolean waitForLock, final TrustNewIdentity trustNewIdentity
131 ) throws IOException {
132 logger.trace("Opening account file");
133 final var fileName = getFileName(dataPath, account);
134 final var pair = openFileChannel(fileName, waitForLock);
135 try {
136 var signalAccount = new SignalAccount(pair.first(), pair.second());
137 logger.trace("Loading account file");
138 signalAccount.load(dataPath, trustNewIdentity);
139 logger.trace("Migrating legacy parts of account file");
140 signalAccount.migrateLegacyConfigs();
141
142 if (!account.equals(signalAccount.getAccount())) {
143 throw new IOException("Number in account file doesn't match expected number: "
144 + signalAccount.getAccount());
145 }
146
147 return signalAccount;
148 } catch (Throwable e) {
149 pair.second().close();
150 pair.first().close();
151 throw e;
152 }
153 }
154
155 public static SignalAccount create(
156 File dataPath,
157 String account,
158 IdentityKeyPair identityKey,
159 int registrationId,
160 ProfileKey profileKey,
161 final TrustNewIdentity trustNewIdentity
162 ) throws IOException {
163 IOUtils.createPrivateDirectories(dataPath);
164 var fileName = getFileName(dataPath, account);
165 if (!fileName.exists()) {
166 IOUtils.createPrivateFile(fileName);
167 }
168
169 final var pair = openFileChannel(fileName, true);
170 var signalAccount = new SignalAccount(pair.first(), pair.second());
171
172 signalAccount.account = account;
173 signalAccount.profileKey = profileKey;
174
175 signalAccount.dataPath = dataPath;
176 signalAccount.identityKeyPair = identityKey;
177 signalAccount.localRegistrationId = registrationId;
178 signalAccount.trustNewIdentity = trustNewIdentity;
179 signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
180 signalAccount.getRecipientStore(),
181 signalAccount::saveGroupStore);
182 signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
183 signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
184
185 signalAccount.registered = false;
186
187 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
188 signalAccount.migrateLegacyConfigs();
189 signalAccount.save();
190
191 return signalAccount;
192 }
193
194 public static SignalAccount createOrUpdateLinkedAccount(
195 File dataPath,
196 String account,
197 ACI aci,
198 String password,
199 String encryptedDeviceName,
200 int deviceId,
201 IdentityKeyPair identityKey,
202 int registrationId,
203 ProfileKey profileKey,
204 final TrustNewIdentity trustNewIdentity
205 ) throws IOException {
206 IOUtils.createPrivateDirectories(dataPath);
207 var fileName = getFileName(dataPath, account);
208 if (!fileName.exists()) {
209 return createLinkedAccount(dataPath,
210 account,
211 aci,
212 password,
213 encryptedDeviceName,
214 deviceId,
215 identityKey,
216 registrationId,
217 profileKey,
218 trustNewIdentity);
219 }
220
221 final var signalAccount = load(dataPath, account, true, trustNewIdentity);
222 signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
223 signalAccount.getRecipientStore().resolveRecipientTrusted(signalAccount.getSelfAddress());
224 signalAccount.getSessionStore().archiveAllSessions();
225 signalAccount.getSenderKeyStore().deleteAll();
226 signalAccount.clearAllPreKeys();
227 return signalAccount;
228 }
229
230 private void clearAllPreKeys() {
231 this.preKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE);
232 this.nextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE);
233 this.getPreKeyStore().removeAllPreKeys();
234 this.getSignedPreKeyStore().removeAllSignedPreKeys();
235 save();
236 }
237
238 private static SignalAccount createLinkedAccount(
239 File dataPath,
240 String account,
241 ACI aci,
242 String password,
243 String encryptedDeviceName,
244 int deviceId,
245 IdentityKeyPair identityKey,
246 int registrationId,
247 ProfileKey profileKey,
248 final TrustNewIdentity trustNewIdentity
249 ) throws IOException {
250 var fileName = getFileName(dataPath, account);
251 IOUtils.createPrivateFile(fileName);
252
253 final var pair = openFileChannel(fileName, true);
254 var signalAccount = new SignalAccount(pair.first(), pair.second());
255
256 signalAccount.setProvisioningData(account, aci, password, encryptedDeviceName, deviceId, profileKey);
257
258 signalAccount.dataPath = dataPath;
259 signalAccount.identityKeyPair = identityKey;
260 signalAccount.localRegistrationId = registrationId;
261 signalAccount.trustNewIdentity = trustNewIdentity;
262 signalAccount.groupStore = new GroupStore(getGroupCachePath(dataPath, account),
263 signalAccount.getRecipientStore(),
264 signalAccount::saveGroupStore);
265 signalAccount.stickerStore = new StickerStore(signalAccount::saveStickerStore);
266 signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
267
268 signalAccount.getRecipientStore().resolveRecipientTrusted(signalAccount.getSelfAddress());
269 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
270 signalAccount.migrateLegacyConfigs();
271 signalAccount.save();
272
273 return signalAccount;
274 }
275
276 private void setProvisioningData(
277 final String account,
278 final ACI aci,
279 final String password,
280 final String encryptedDeviceName,
281 final int deviceId,
282 final ProfileKey profileKey
283 ) {
284 this.account = account;
285 this.aci = aci;
286 this.password = password;
287 this.profileKey = profileKey;
288 this.encryptedDeviceName = encryptedDeviceName;
289 this.deviceId = deviceId;
290 this.registered = true;
291 this.isMultiDevice = true;
292 this.lastReceiveTimestamp = 0;
293 this.pinMasterKey = null;
294 this.storageManifestVersion = -1;
295 this.storageKey = null;
296 }
297
298 private void migrateLegacyConfigs() {
299 if (getPassword() == null) {
300 setPassword(KeyUtils.createPassword());
301 }
302
303 if (getProfileKey() == null) {
304 // Old config file, creating new profile key
305 setProfileKey(KeyUtils.createProfileKey());
306 }
307 // Ensure our profile key is stored in profile store
308 getProfileStore().storeProfileKey(getSelfRecipientId(), getProfileKey());
309 if (previousStorageVersion < 3) {
310 for (final var group : groupStore.getGroups()) {
311 if (group instanceof GroupInfoV2 && group.getDistributionId() == null) {
312 ((GroupInfoV2) group).setDistributionId(DistributionId.create());
313 groupStore.updateGroup(group);
314 }
315 }
316 save();
317 }
318 }
319
320 private void mergeRecipients(RecipientId recipientId, RecipientId toBeMergedRecipientId) {
321 getSessionStore().mergeRecipients(recipientId, toBeMergedRecipientId);
322 getIdentityKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
323 getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId);
324 getGroupStore().mergeRecipients(recipientId, toBeMergedRecipientId);
325 getSenderKeyStore().mergeRecipients(recipientId, toBeMergedRecipientId);
326 }
327
328 public void removeRecipient(final RecipientId recipientId) {
329 getSessionStore().deleteAllSessions(recipientId);
330 getIdentityKeyStore().deleteIdentity(recipientId);
331 getMessageCache().deleteMessages(recipientId);
332 getSenderKeyStore().deleteAll(recipientId);
333 getRecipientStore().deleteRecipientData(recipientId);
334 }
335
336 public static File getFileName(File dataPath, String account) {
337 return new File(dataPath, account);
338 }
339
340 private static File getUserPath(final File dataPath, final String account) {
341 final var path = new File(dataPath, account + ".d");
342 try {
343 IOUtils.createPrivateDirectories(path);
344 } catch (IOException e) {
345 throw new AssertionError("Failed to create user path", e);
346 }
347 return path;
348 }
349
350 private static File getMessageCachePath(File dataPath, String account) {
351 return new File(getUserPath(dataPath, account), "msg-cache");
352 }
353
354 private static File getGroupCachePath(File dataPath, String account) {
355 return new File(getUserPath(dataPath, account), "group-cache");
356 }
357
358 private static File getPreKeysPath(File dataPath, String account) {
359 return new File(getUserPath(dataPath, account), "pre-keys");
360 }
361
362 private static File getSignedPreKeysPath(File dataPath, String account) {
363 return new File(getUserPath(dataPath, account), "signed-pre-keys");
364 }
365
366 private static File getIdentitiesPath(File dataPath, String account) {
367 return new File(getUserPath(dataPath, account), "identities");
368 }
369
370 private static File getSessionsPath(File dataPath, String account) {
371 return new File(getUserPath(dataPath, account), "sessions");
372 }
373
374 private static File getSenderKeysPath(File dataPath, String account) {
375 return new File(getUserPath(dataPath, account), "sender-keys");
376 }
377
378 private static File getSharedSenderKeysFile(File dataPath, String account) {
379 return new File(getUserPath(dataPath, account), "shared-sender-keys-store");
380 }
381
382 private static File getRecipientsStoreFile(File dataPath, String account) {
383 return new File(getUserPath(dataPath, account), "recipients-store");
384 }
385
386 public static boolean userExists(File dataPath, String account) {
387 if (account == null) {
388 return false;
389 }
390 var f = getFileName(dataPath, account);
391 return !(!f.exists() || f.isDirectory());
392 }
393
394 private void load(
395 File dataPath, final TrustNewIdentity trustNewIdentity
396 ) throws IOException {
397 JsonNode rootNode;
398 synchronized (fileChannel) {
399 fileChannel.position(0);
400 rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
401 }
402
403 if (rootNode.hasNonNull("version")) {
404 var accountVersion = rootNode.get("version").asInt(1);
405 if (accountVersion > CURRENT_STORAGE_VERSION) {
406 throw new IOException("Config file was created by a more recent version!");
407 } else if (accountVersion < MINIMUM_STORAGE_VERSION) {
408 throw new IOException("Config file was created by a no longer supported older version!");
409 }
410 previousStorageVersion = accountVersion;
411 }
412
413 account = Utils.getNotNullNode(rootNode, "username").asText();
414 if (rootNode.hasNonNull("password")) {
415 password = rootNode.get("password").asText();
416 }
417 registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
418 if (rootNode.hasNonNull("uuid")) {
419 try {
420 aci = ACI.parseOrThrow(rootNode.get("uuid").asText());
421 } catch (IllegalArgumentException e) {
422 throw new IOException("Config file contains an invalid uuid, needs to be a valid UUID", e);
423 }
424 }
425 if (rootNode.hasNonNull("deviceName")) {
426 encryptedDeviceName = rootNode.get("deviceName").asText();
427 }
428 if (rootNode.hasNonNull("deviceId")) {
429 deviceId = rootNode.get("deviceId").asInt();
430 }
431 if (rootNode.hasNonNull("isMultiDevice")) {
432 isMultiDevice = rootNode.get("isMultiDevice").asBoolean();
433 }
434 if (rootNode.hasNonNull("lastReceiveTimestamp")) {
435 lastReceiveTimestamp = rootNode.get("lastReceiveTimestamp").asLong();
436 }
437 int registrationId = 0;
438 if (rootNode.hasNonNull("registrationId")) {
439 registrationId = rootNode.get("registrationId").asInt();
440 }
441 IdentityKeyPair identityKeyPair = null;
442 if (rootNode.hasNonNull("identityPrivateKey") && rootNode.hasNonNull("identityKey")) {
443 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("identityKey").asText());
444 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("identityPrivateKey").asText());
445 identityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
446 }
447
448 if (rootNode.hasNonNull("registrationLockPin")) {
449 registrationLockPin = rootNode.get("registrationLockPin").asText();
450 }
451 if (rootNode.hasNonNull("pinMasterKey")) {
452 pinMasterKey = new MasterKey(Base64.getDecoder().decode(rootNode.get("pinMasterKey").asText()));
453 }
454 if (rootNode.hasNonNull("storageKey")) {
455 storageKey = new StorageKey(Base64.getDecoder().decode(rootNode.get("storageKey").asText()));
456 }
457 if (rootNode.hasNonNull("storageManifestVersion")) {
458 storageManifestVersion = rootNode.get("storageManifestVersion").asLong();
459 }
460 if (rootNode.hasNonNull("preKeyIdOffset")) {
461 preKeyIdOffset = rootNode.get("preKeyIdOffset").asInt(0);
462 } else {
463 preKeyIdOffset = 0;
464 }
465 if (rootNode.hasNonNull("nextSignedPreKeyId")) {
466 nextSignedPreKeyId = rootNode.get("nextSignedPreKeyId").asInt();
467 } else {
468 nextSignedPreKeyId = 0;
469 }
470 if (rootNode.hasNonNull("profileKey")) {
471 try {
472 profileKey = new ProfileKey(Base64.getDecoder().decode(rootNode.get("profileKey").asText()));
473 } catch (InvalidInputException e) {
474 throw new IOException(
475 "Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
476 e);
477 }
478 }
479
480 var migratedLegacyConfig = false;
481 final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
482 ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
483 LegacyJsonSignalProtocolStore.class)
484 : null;
485 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
486 identityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
487 registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
488 migratedLegacyConfig = true;
489 }
490
491 this.dataPath = dataPath;
492 this.identityKeyPair = identityKeyPair;
493 this.localRegistrationId = registrationId;
494 this.trustNewIdentity = trustNewIdentity;
495
496 migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
497
498 if (rootNode.hasNonNull("groupStore")) {
499 groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"), GroupStore.Storage.class);
500 groupStore = GroupStore.fromStorage(groupStoreStorage,
501 getGroupCachePath(dataPath, account),
502 getRecipientStore(),
503 this::saveGroupStore);
504 } else {
505 groupStore = new GroupStore(getGroupCachePath(dataPath, account),
506 getRecipientStore(),
507 this::saveGroupStore);
508 }
509
510 if (rootNode.hasNonNull("stickerStore")) {
511 stickerStoreStorage = jsonProcessor.convertValue(rootNode.get("stickerStore"), StickerStore.Storage.class);
512 stickerStore = StickerStore.fromStorage(stickerStoreStorage, this::saveStickerStore);
513 } else {
514 stickerStore = new StickerStore(this::saveStickerStore);
515 }
516
517 if (rootNode.hasNonNull("configurationStore")) {
518 configurationStoreStorage = jsonProcessor.convertValue(rootNode.get("configurationStore"),
519 ConfigurationStore.Storage.class);
520 configurationStore = ConfigurationStore.fromStorage(configurationStoreStorage,
521 this::saveConfigurationStore);
522 } else {
523 configurationStore = new ConfigurationStore(this::saveConfigurationStore);
524 }
525
526 migratedLegacyConfig = loadLegacyThreadStore(rootNode) || migratedLegacyConfig;
527
528 if (migratedLegacyConfig) {
529 save();
530 }
531 }
532
533 private boolean loadLegacyStores(
534 final JsonNode rootNode, final LegacyJsonSignalProtocolStore legacySignalProtocolStore
535 ) {
536 var migrated = false;
537 var legacyRecipientStoreNode = rootNode.get("recipientStore");
538 if (legacyRecipientStoreNode != null) {
539 logger.debug("Migrating legacy recipient store.");
540 var legacyRecipientStore = jsonProcessor.convertValue(legacyRecipientStoreNode, LegacyRecipientStore.class);
541 if (legacyRecipientStore != null) {
542 getRecipientStore().resolveRecipientsTrusted(legacyRecipientStore.getAddresses());
543 }
544 getSelfRecipientId();
545 migrated = true;
546 }
547
548 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
549 logger.debug("Migrating legacy pre key store.");
550 for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
551 try {
552 getPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
553 } catch (IOException e) {
554 logger.warn("Failed to migrate pre key, ignoring", e);
555 }
556 }
557 migrated = true;
558 }
559
560 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
561 logger.debug("Migrating legacy signed pre key store.");
562 for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
563 try {
564 getSignedPreKeyStore().storeSignedPreKey(entry.getKey(), new SignedPreKeyRecord(entry.getValue()));
565 } catch (IOException e) {
566 logger.warn("Failed to migrate signed pre key, ignoring", e);
567 }
568 }
569 migrated = true;
570 }
571
572 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
573 logger.debug("Migrating legacy session store.");
574 for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
575 try {
576 getSessionStore().storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
577 session.deviceId), new SessionRecord(session.sessionRecord));
578 } catch (Exception e) {
579 logger.warn("Failed to migrate session, ignoring", e);
580 }
581 }
582 migrated = true;
583 }
584
585 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
586 logger.debug("Migrating legacy identity session store.");
587 for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
588 RecipientId recipientId = getRecipientStore().resolveRecipientTrusted(identity.getAddress());
589 getIdentityKeyStore().saveIdentity(recipientId, identity.getIdentityKey(), identity.getDateAdded());
590 getIdentityKeyStore().setIdentityTrustLevel(recipientId,
591 identity.getIdentityKey(),
592 identity.getTrustLevel());
593 }
594 migrated = true;
595 }
596
597 if (rootNode.hasNonNull("contactStore")) {
598 logger.debug("Migrating legacy contact store.");
599 final var contactStoreNode = rootNode.get("contactStore");
600 final var contactStore = jsonProcessor.convertValue(contactStoreNode, LegacyJsonContactsStore.class);
601 for (var contact : contactStore.getContacts()) {
602 final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
603 getRecipientStore().storeContact(recipientId,
604 new Contact(contact.name,
605 contact.color,
606 contact.messageExpirationTime,
607 contact.blocked,
608 contact.archived));
609
610 // Store profile keys only in profile store
611 var profileKeyString = contact.profileKey;
612 if (profileKeyString != null) {
613 final ProfileKey profileKey;
614 try {
615 profileKey = new ProfileKey(Base64.getDecoder().decode(profileKeyString));
616 getProfileStore().storeProfileKey(recipientId, profileKey);
617 } catch (InvalidInputException e) {
618 logger.warn("Failed to parse legacy contact profile key: {}", e.getMessage());
619 }
620 }
621 }
622 migrated = true;
623 }
624
625 if (rootNode.hasNonNull("profileStore")) {
626 logger.debug("Migrating legacy profile store.");
627 var profileStoreNode = rootNode.get("profileStore");
628 final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
629 for (var profileEntry : legacyProfileStore.getProfileEntries()) {
630 var recipientId = getRecipientStore().resolveRecipient(profileEntry.getAddress());
631 getRecipientStore().storeProfileKeyCredential(recipientId, profileEntry.getProfileKeyCredential());
632 getRecipientStore().storeProfileKey(recipientId, profileEntry.getProfileKey());
633 final var profile = profileEntry.getProfile();
634 if (profile != null) {
635 final var capabilities = new HashSet<Profile.Capability>();
636 if (profile.getCapabilities() != null) {
637 if (profile.getCapabilities().gv1Migration) {
638 capabilities.add(Profile.Capability.gv1Migration);
639 }
640 if (profile.getCapabilities().gv2) {
641 capabilities.add(Profile.Capability.gv2);
642 }
643 if (profile.getCapabilities().storage) {
644 capabilities.add(Profile.Capability.storage);
645 }
646 }
647 final var newProfile = new Profile(profileEntry.getLastUpdateTimestamp(),
648 profile.getGivenName(),
649 profile.getFamilyName(),
650 profile.getAbout(),
651 profile.getAboutEmoji(),
652 null,
653 profile.isUnrestrictedUnidentifiedAccess()
654 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
655 : profile.getUnidentifiedAccess() != null
656 ? Profile.UnidentifiedAccessMode.ENABLED
657 : Profile.UnidentifiedAccessMode.DISABLED,
658 capabilities);
659 getRecipientStore().storeProfile(recipientId, newProfile);
660 }
661 }
662 }
663
664 return migrated;
665 }
666
667 private boolean loadLegacyThreadStore(final JsonNode rootNode) {
668 var threadStoreNode = rootNode.get("threadStore");
669 if (threadStoreNode != null && !threadStoreNode.isNull()) {
670 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
671 // Migrate thread info to group and contact store
672 for (var thread : threadStore.getThreads()) {
673 if (thread.id == null || thread.id.isEmpty()) {
674 continue;
675 }
676 try {
677 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
678 final var recipientId = getRecipientStore().resolveRecipient(thread.id);
679 var contact = getRecipientStore().getContact(recipientId);
680 if (contact != null) {
681 getRecipientStore().storeContact(recipientId,
682 Contact.newBuilder(contact)
683 .withMessageExpirationTime(thread.messageExpirationTime)
684 .build());
685 }
686 } else {
687 var groupInfo = groupStore.getGroup(GroupId.fromBase64(thread.id));
688 if (groupInfo instanceof GroupInfoV1) {
689 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
690 groupStore.updateGroup(groupInfo);
691 }
692 }
693 } catch (Exception e) {
694 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
695 }
696 }
697 return true;
698 }
699
700 return false;
701 }
702
703 private void saveStickerStore(StickerStore.Storage storage) {
704 this.stickerStoreStorage = storage;
705 save();
706 }
707
708 private void saveGroupStore(GroupStore.Storage storage) {
709 this.groupStoreStorage = storage;
710 save();
711 }
712
713 private void saveConfigurationStore(ConfigurationStore.Storage storage) {
714 this.configurationStoreStorage = storage;
715 save();
716 }
717
718 private void save() {
719 synchronized (fileChannel) {
720 var rootNode = jsonProcessor.createObjectNode();
721 rootNode.put("version", CURRENT_STORAGE_VERSION)
722 .put("username", account)
723 .put("uuid", aci == null ? null : aci.toString())
724 .put("deviceName", encryptedDeviceName)
725 .put("deviceId", deviceId)
726 .put("isMultiDevice", isMultiDevice)
727 .put("lastReceiveTimestamp", lastReceiveTimestamp)
728 .put("password", password)
729 .put("registrationId", localRegistrationId)
730 .put("identityPrivateKey",
731 Base64.getEncoder().encodeToString(identityKeyPair.getPrivateKey().serialize()))
732 .put("identityKey", Base64.getEncoder().encodeToString(identityKeyPair.getPublicKey().serialize()))
733 .put("registrationLockPin", registrationLockPin)
734 .put("pinMasterKey",
735 pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
736 .put("storageKey",
737 storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
738 .put("storageManifestVersion", storageManifestVersion == -1 ? null : storageManifestVersion)
739 .put("preKeyIdOffset", preKeyIdOffset)
740 .put("nextSignedPreKeyId", nextSignedPreKeyId)
741 .put("profileKey",
742 profileKey == null ? null : Base64.getEncoder().encodeToString(profileKey.serialize()))
743 .put("registered", registered)
744 .putPOJO("groupStore", groupStoreStorage)
745 .putPOJO("stickerStore", stickerStoreStorage)
746 .putPOJO("configurationStore", configurationStoreStorage);
747 try {
748 try (var output = new ByteArrayOutputStream()) {
749 // Write to memory first to prevent corrupting the file in case of serialization errors
750 jsonProcessor.writeValue(output, rootNode);
751 var input = new ByteArrayInputStream(output.toByteArray());
752 fileChannel.position(0);
753 input.transferTo(Channels.newOutputStream(fileChannel));
754 fileChannel.truncate(fileChannel.position());
755 fileChannel.force(false);
756 }
757 } catch (Exception e) {
758 logger.error("Error saving file: {}", e.getMessage());
759 }
760 }
761 }
762
763 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
764 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
765 var lock = fileChannel.tryLock();
766 if (lock == null) {
767 if (!waitForLock) {
768 logger.debug("Config file is in use by another instance.");
769 throw new IOException("Config file is in use by another instance.");
770 }
771 logger.info("Config file is in use by another instance, waiting…");
772 lock = fileChannel.lock();
773 logger.info("Config file lock acquired.");
774 }
775 return new Pair<>(fileChannel, lock);
776 }
777
778 public void addPreKeys(List<PreKeyRecord> records) {
779 for (var record : records) {
780 if (preKeyIdOffset != record.getId()) {
781 logger.error("Invalid pre key id {}, expected {}", record.getId(), preKeyIdOffset);
782 throw new AssertionError("Invalid pre key id");
783 }
784 getPreKeyStore().storePreKey(record.getId(), record);
785 preKeyIdOffset = (preKeyIdOffset + 1) % Medium.MAX_VALUE;
786 }
787 save();
788 }
789
790 public void addSignedPreKey(SignedPreKeyRecord record) {
791 if (nextSignedPreKeyId != record.getId()) {
792 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), nextSignedPreKeyId);
793 throw new AssertionError("Invalid signed pre key id");
794 }
795 getSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
796 nextSignedPreKeyId = (nextSignedPreKeyId + 1) % Medium.MAX_VALUE;
797 save();
798 }
799
800 public SignalProtocolStore getSignalProtocolStore() {
801 return getOrCreate(() -> signalProtocolStore,
802 () -> signalProtocolStore = new SignalProtocolStore(getPreKeyStore(),
803 getSignedPreKeyStore(),
804 getSessionStore(),
805 getIdentityKeyStore(),
806 getSenderKeyStore(),
807 this::isMultiDevice));
808 }
809
810 private PreKeyStore getPreKeyStore() {
811 return getOrCreate(() -> preKeyStore, () -> preKeyStore = new PreKeyStore(getPreKeysPath(dataPath, account)));
812 }
813
814 private SignedPreKeyStore getSignedPreKeyStore() {
815 return getOrCreate(() -> signedPreKeyStore,
816 () -> signedPreKeyStore = new SignedPreKeyStore(getSignedPreKeysPath(dataPath, account)));
817 }
818
819 public SessionStore getSessionStore() {
820 return getOrCreate(() -> sessionStore,
821 () -> sessionStore = new SessionStore(getSessionsPath(dataPath, account), getRecipientStore()));
822 }
823
824 public IdentityKeyStore getIdentityKeyStore() {
825 return getOrCreate(() -> identityKeyStore,
826 () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, account),
827 getRecipientStore(),
828 identityKeyPair,
829 localRegistrationId,
830 trustNewIdentity));
831 }
832
833 public GroupStore getGroupStore() {
834 return groupStore;
835 }
836
837 public ContactsStore getContactStore() {
838 return getRecipientStore();
839 }
840
841 public RecipientStore getRecipientStore() {
842 return getOrCreate(() -> recipientStore,
843 () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, account),
844 this::mergeRecipients));
845 }
846
847 public ProfileStore getProfileStore() {
848 return getRecipientStore();
849 }
850
851 public StickerStore getStickerStore() {
852 return stickerStore;
853 }
854
855 public SenderKeyStore getSenderKeyStore() {
856 return getOrCreate(() -> senderKeyStore,
857 () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, account),
858 getSenderKeysPath(dataPath, account),
859 getRecipientStore()::resolveRecipientAddress,
860 getRecipientStore()));
861 }
862
863 public ConfigurationStore getConfigurationStore() {
864 return configurationStore;
865 }
866
867 public MessageCache getMessageCache() {
868 return getOrCreate(() -> messageCache,
869 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, account)));
870 }
871
872 public String getAccount() {
873 return account;
874 }
875
876 public ACI getAci() {
877 return aci;
878 }
879
880 public void setAci(final ACI aci) {
881 this.aci = aci;
882 save();
883 }
884
885 public SignalServiceAddress getSelfAddress() {
886 return new SignalServiceAddress(aci, account);
887 }
888
889 public RecipientId getSelfRecipientId() {
890 return getRecipientStore().resolveRecipientTrusted(new RecipientAddress(aci == null ? null : aci.uuid(),
891 account));
892 }
893
894 public String getEncryptedDeviceName() {
895 return encryptedDeviceName;
896 }
897
898 public void setEncryptedDeviceName(final String encryptedDeviceName) {
899 this.encryptedDeviceName = encryptedDeviceName;
900 save();
901 }
902
903 public int getDeviceId() {
904 return deviceId;
905 }
906
907 public boolean isMasterDevice() {
908 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
909 }
910
911 public IdentityKeyPair getIdentityKeyPair() {
912 return identityKeyPair;
913 }
914
915 public int getLocalRegistrationId() {
916 return localRegistrationId;
917 }
918
919 public String getPassword() {
920 return password;
921 }
922
923 private void setPassword(final String password) {
924 this.password = password;
925 save();
926 }
927
928 public void setRegistrationLockPin(final String registrationLockPin, final MasterKey pinMasterKey) {
929 this.registrationLockPin = registrationLockPin;
930 this.pinMasterKey = pinMasterKey;
931 save();
932 }
933
934 public MasterKey getPinMasterKey() {
935 return pinMasterKey;
936 }
937
938 public StorageKey getStorageKey() {
939 if (pinMasterKey != null) {
940 return pinMasterKey.deriveStorageServiceKey();
941 }
942 return storageKey;
943 }
944
945 public void setStorageKey(final StorageKey storageKey) {
946 if (storageKey.equals(this.storageKey)) {
947 return;
948 }
949 this.storageKey = storageKey;
950 save();
951 }
952
953 public long getStorageManifestVersion() {
954 return this.storageManifestVersion;
955 }
956
957 public void setStorageManifestVersion(final long storageManifestVersion) {
958 if (storageManifestVersion == this.storageManifestVersion) {
959 return;
960 }
961 this.storageManifestVersion = storageManifestVersion;
962 save();
963 }
964
965 public ProfileKey getProfileKey() {
966 return profileKey;
967 }
968
969 public void setProfileKey(final ProfileKey profileKey) {
970 if (profileKey.equals(this.profileKey)) {
971 return;
972 }
973 this.profileKey = profileKey;
974 save();
975 }
976
977 public byte[] getSelfUnidentifiedAccessKey() {
978 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
979 }
980
981 public int getPreKeyIdOffset() {
982 return preKeyIdOffset;
983 }
984
985 public int getNextSignedPreKeyId() {
986 return nextSignedPreKeyId;
987 }
988
989 public boolean isRegistered() {
990 return registered;
991 }
992
993 public void setRegistered(final boolean registered) {
994 this.registered = registered;
995 save();
996 }
997
998 public boolean isMultiDevice() {
999 return isMultiDevice;
1000 }
1001
1002 public void setMultiDevice(final boolean multiDevice) {
1003 if (isMultiDevice == multiDevice) {
1004 return;
1005 }
1006 isMultiDevice = multiDevice;
1007 save();
1008 }
1009
1010 public long getLastReceiveTimestamp() {
1011 return lastReceiveTimestamp;
1012 }
1013
1014 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1015 this.lastReceiveTimestamp = lastReceiveTimestamp;
1016 save();
1017 }
1018
1019 public boolean isUnrestrictedUnidentifiedAccess() {
1020 // TODO make configurable
1021 return false;
1022 }
1023
1024 public boolean isDiscoverableByPhoneNumber() {
1025 return configurationStore.getPhoneNumberUnlisted() == null || !configurationStore.getPhoneNumberUnlisted();
1026 }
1027
1028 public void finishRegistration(final ACI aci, final MasterKey masterKey, final String pin) {
1029 this.pinMasterKey = masterKey;
1030 this.storageManifestVersion = -1;
1031 this.storageKey = null;
1032 this.encryptedDeviceName = null;
1033 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1034 this.isMultiDevice = false;
1035 this.registered = true;
1036 this.aci = aci;
1037 this.registrationLockPin = pin;
1038 this.lastReceiveTimestamp = 0;
1039 save();
1040
1041 clearAllPreKeys();
1042 getSessionStore().archiveAllSessions();
1043 getSenderKeyStore().deleteAll();
1044 final var recipientId = getRecipientStore().resolveRecipientTrusted(getSelfAddress());
1045 final var publicKey = getIdentityKeyPair().getPublicKey();
1046 getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date());
1047 getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1048 }
1049
1050 @Override
1051 public void close() {
1052 synchronized (fileChannel) {
1053 try {
1054 try {
1055 lock.close();
1056 } catch (ClosedChannelException ignored) {
1057 }
1058 fileChannel.close();
1059 } catch (IOException e) {
1060 logger.warn("Failed to close account: {}", e.getMessage(), e);
1061 }
1062 }
1063 }
1064
1065 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1066 var value = supplier.get();
1067 if (value != null) {
1068 return value;
1069 }
1070
1071 synchronized (LOCK) {
1072 value = supplier.get();
1073 if (value != null) {
1074 return value;
1075 }
1076 creator.call();
1077 return supplier.get();
1078 }
1079 }
1080
1081 private interface Callable {
1082
1083 void call();
1084 }
1085 }