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