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