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