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