]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
25847c6cb61592d6986c73d61d9126182f8ebd43
[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.Settings;
7 import org.asamk.signal.manager.api.Pair;
8 import org.asamk.signal.manager.api.TrustLevel;
9 import org.asamk.signal.manager.config.ServiceEnvironment;
10 import org.asamk.signal.manager.groups.GroupId;
11 import org.asamk.signal.manager.helper.RecipientAddressResolver;
12 import org.asamk.signal.manager.storage.configuration.ConfigurationStore;
13 import org.asamk.signal.manager.storage.contacts.ContactsStore;
14 import org.asamk.signal.manager.storage.contacts.LegacyJsonContactsStore;
15 import org.asamk.signal.manager.storage.groups.GroupInfoV1;
16 import org.asamk.signal.manager.storage.groups.GroupStore;
17 import org.asamk.signal.manager.storage.groups.LegacyGroupStore;
18 import org.asamk.signal.manager.storage.identities.IdentityKeyStore;
19 import org.asamk.signal.manager.storage.identities.LegacyIdentityKeyStore;
20 import org.asamk.signal.manager.storage.identities.SignalIdentityKeyStore;
21 import org.asamk.signal.manager.storage.messageCache.MessageCache;
22 import org.asamk.signal.manager.storage.prekeys.LegacyPreKeyStore;
23 import org.asamk.signal.manager.storage.prekeys.LegacySignedPreKeyStore;
24 import org.asamk.signal.manager.storage.prekeys.PreKeyStore;
25 import org.asamk.signal.manager.storage.prekeys.SignedPreKeyStore;
26 import org.asamk.signal.manager.storage.profiles.LegacyProfileStore;
27 import org.asamk.signal.manager.storage.profiles.ProfileStore;
28 import org.asamk.signal.manager.storage.protocol.LegacyJsonSignalProtocolStore;
29 import org.asamk.signal.manager.storage.protocol.SignalProtocolStore;
30 import org.asamk.signal.manager.storage.recipients.Contact;
31 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore;
32 import org.asamk.signal.manager.storage.recipients.LegacyRecipientStore2;
33 import org.asamk.signal.manager.storage.recipients.Profile;
34 import org.asamk.signal.manager.storage.recipients.RecipientAddress;
35 import org.asamk.signal.manager.storage.recipients.RecipientId;
36 import org.asamk.signal.manager.storage.recipients.RecipientIdCreator;
37 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
38 import org.asamk.signal.manager.storage.recipients.RecipientStore;
39 import org.asamk.signal.manager.storage.recipients.RecipientTrustedResolver;
40 import org.asamk.signal.manager.storage.sendLog.MessageSendLogStore;
41 import org.asamk.signal.manager.storage.senderKeys.LegacySenderKeyRecordStore;
42 import org.asamk.signal.manager.storage.senderKeys.LegacySenderKeySharedStore;
43 import org.asamk.signal.manager.storage.senderKeys.SenderKeyStore;
44 import org.asamk.signal.manager.storage.sessions.LegacySessionStore;
45 import org.asamk.signal.manager.storage.sessions.SessionStore;
46 import org.asamk.signal.manager.storage.stickers.LegacyStickerStore;
47 import org.asamk.signal.manager.storage.stickers.StickerStore;
48 import org.asamk.signal.manager.storage.threads.LegacyJsonThreadStore;
49 import org.asamk.signal.manager.util.IOUtils;
50 import org.asamk.signal.manager.util.KeyUtils;
51 import org.signal.libsignal.protocol.IdentityKeyPair;
52 import org.signal.libsignal.protocol.InvalidMessageException;
53 import org.signal.libsignal.protocol.SignalProtocolAddress;
54 import org.signal.libsignal.protocol.state.PreKeyRecord;
55 import org.signal.libsignal.protocol.state.SessionRecord;
56 import org.signal.libsignal.protocol.state.SignedPreKeyRecord;
57 import org.signal.libsignal.protocol.util.KeyHelper;
58 import org.signal.libsignal.protocol.util.Medium;
59 import org.signal.libsignal.zkgroup.InvalidInputException;
60 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63 import org.whispersystems.signalservice.api.SignalServiceAccountDataStore;
64 import org.whispersystems.signalservice.api.SignalServiceDataStore;
65 import org.whispersystems.signalservice.api.account.AccountAttributes;
66 import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess;
67 import org.whispersystems.signalservice.api.kbs.MasterKey;
68 import org.whispersystems.signalservice.api.push.ACI;
69 import org.whispersystems.signalservice.api.push.PNI;
70 import org.whispersystems.signalservice.api.push.ServiceId;
71 import org.whispersystems.signalservice.api.push.ServiceIdType;
72 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
73 import org.whispersystems.signalservice.api.storage.SignalStorageManifest;
74 import org.whispersystems.signalservice.api.storage.StorageKey;
75 import org.whispersystems.signalservice.api.util.CredentialsProvider;
76 import org.whispersystems.signalservice.api.util.UuidUtil;
77
78 import java.io.ByteArrayInputStream;
79 import java.io.ByteArrayOutputStream;
80 import java.io.Closeable;
81 import java.io.File;
82 import java.io.FileInputStream;
83 import java.io.FileOutputStream;
84 import java.io.IOException;
85 import java.io.RandomAccessFile;
86 import java.nio.channels.Channels;
87 import java.nio.channels.ClosedChannelException;
88 import java.nio.channels.FileChannel;
89 import java.nio.channels.FileLock;
90 import java.nio.file.Files;
91 import java.security.SecureRandom;
92 import java.sql.Connection;
93 import java.sql.SQLException;
94 import java.util.Base64;
95 import java.util.Comparator;
96 import java.util.HashSet;
97 import java.util.List;
98 import java.util.Optional;
99 import java.util.function.Supplier;
100
101 import static org.asamk.signal.manager.config.ServiceConfig.capabilities;
102
103 public class SignalAccount implements Closeable {
104
105 private final static Logger logger = LoggerFactory.getLogger(SignalAccount.class);
106
107 private static final int MINIMUM_STORAGE_VERSION = 1;
108 private static final int CURRENT_STORAGE_VERSION = 6;
109
110 private final Object LOCK = new Object();
111
112 private final ObjectMapper jsonProcessor = Utils.createStorageObjectMapper();
113
114 private final FileChannel fileChannel;
115 private final FileLock lock;
116
117 private int previousStorageVersion;
118
119 private File dataPath;
120 private String accountPath;
121 private ServiceEnvironment serviceEnvironment;
122 private String number;
123 private ACI aci;
124 private PNI pni;
125 private String sessionId;
126 private String sessionNumber;
127 private String encryptedDeviceName;
128 private int deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
129 private boolean isMultiDevice = false;
130 private String password;
131 private String registrationLockPin;
132 private MasterKey pinMasterKey;
133 private StorageKey storageKey;
134 private long storageManifestVersion = -1;
135 private ProfileKey profileKey;
136 private int aciPreKeyIdOffset = 1;
137 private int aciNextSignedPreKeyId = 1;
138 private int pniPreKeyIdOffset = 1;
139 private int pniNextSignedPreKeyId = 1;
140 private IdentityKeyPair aciIdentityKeyPair;
141 private IdentityKeyPair pniIdentityKeyPair;
142 private int localRegistrationId;
143 private int localPniRegistrationId;
144 private Settings settings;
145 private long lastReceiveTimestamp = 0;
146
147 private boolean registered = false;
148
149 private SignalProtocolStore aciSignalProtocolStore;
150 private SignalProtocolStore pniSignalProtocolStore;
151 private PreKeyStore aciPreKeyStore;
152 private SignedPreKeyStore aciSignedPreKeyStore;
153 private PreKeyStore pniPreKeyStore;
154 private SignedPreKeyStore pniSignedPreKeyStore;
155 private SessionStore aciSessionStore;
156 private SessionStore pniSessionStore;
157 private IdentityKeyStore identityKeyStore;
158 private SignalIdentityKeyStore aciIdentityKeyStore;
159 private SignalIdentityKeyStore pniIdentityKeyStore;
160 private SenderKeyStore senderKeyStore;
161 private GroupStore groupStore;
162 private RecipientStore recipientStore;
163 private StickerStore stickerStore;
164 private ConfigurationStore configurationStore;
165 private ConfigurationStore.Storage configurationStoreStorage;
166
167 private MessageCache messageCache;
168 private MessageSendLogStore messageSendLogStore;
169
170 private AccountDatabase accountDatabase;
171
172 private SignalAccount(final FileChannel fileChannel, final FileLock lock) {
173 this.fileChannel = fileChannel;
174 this.lock = lock;
175 }
176
177 public static SignalAccount load(
178 File dataPath, String accountPath, boolean waitForLock, final Settings settings
179 ) throws IOException {
180 logger.trace("Opening account file");
181 final var fileName = getFileName(dataPath, accountPath);
182 final var pair = openFileChannel(fileName, waitForLock);
183 try {
184 var signalAccount = new SignalAccount(pair.first(), pair.second());
185 logger.trace("Loading account file");
186 signalAccount.load(dataPath, accountPath, settings);
187 logger.trace("Migrating legacy parts of account file");
188 signalAccount.migrateLegacyConfigs();
189
190 return signalAccount;
191 } catch (Throwable e) {
192 pair.second().close();
193 pair.first().close();
194 throw e;
195 }
196 }
197
198 public static SignalAccount create(
199 File dataPath,
200 String accountPath,
201 String number,
202 ServiceEnvironment serviceEnvironment,
203 IdentityKeyPair aciIdentityKey,
204 IdentityKeyPair pniIdentityKey,
205 int registrationId,
206 int pniRegistrationId,
207 ProfileKey profileKey,
208 final Settings settings
209 ) throws IOException {
210 IOUtils.createPrivateDirectories(dataPath);
211 var fileName = getFileName(dataPath, accountPath);
212 if (!fileName.exists()) {
213 IOUtils.createPrivateFile(fileName);
214 }
215
216 final var pair = openFileChannel(fileName, true);
217 var signalAccount = new SignalAccount(pair.first(), pair.second());
218
219 signalAccount.accountPath = accountPath;
220 signalAccount.number = number;
221 signalAccount.serviceEnvironment = serviceEnvironment;
222 signalAccount.profileKey = profileKey;
223
224 signalAccount.dataPath = dataPath;
225 signalAccount.aciIdentityKeyPair = aciIdentityKey;
226 signalAccount.pniIdentityKeyPair = pniIdentityKey;
227 signalAccount.localRegistrationId = registrationId;
228 signalAccount.localPniRegistrationId = pniRegistrationId;
229 signalAccount.settings = settings;
230 signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
231
232 signalAccount.registered = false;
233
234 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
235 signalAccount.migrateLegacyConfigs();
236 signalAccount.save();
237
238 return signalAccount;
239 }
240
241 public static SignalAccount createOrUpdateLinkedAccount(
242 File dataPath,
243 String accountPath,
244 String number,
245 ServiceEnvironment serviceEnvironment,
246 ACI aci,
247 PNI pni,
248 String password,
249 String encryptedDeviceName,
250 int deviceId,
251 IdentityKeyPair aciIdentityKey,
252 IdentityKeyPair pniIdentityKey,
253 int registrationId,
254 int pniRegistrationId,
255 ProfileKey profileKey,
256 final Settings settings
257 ) throws IOException {
258 IOUtils.createPrivateDirectories(dataPath);
259 var fileName = getFileName(dataPath, accountPath);
260 if (!fileName.exists()) {
261 return createLinkedAccount(dataPath,
262 accountPath,
263 number,
264 serviceEnvironment,
265 aci,
266 pni,
267 password,
268 encryptedDeviceName,
269 deviceId,
270 aciIdentityKey,
271 pniIdentityKey,
272 registrationId,
273 pniRegistrationId,
274 profileKey,
275 settings);
276 }
277
278 final var signalAccount = load(dataPath, accountPath, true, settings);
279 signalAccount.setProvisioningData(number,
280 aci,
281 pni,
282 password,
283 encryptedDeviceName,
284 deviceId,
285 aciIdentityKey,
286 pniIdentityKey,
287 profileKey);
288 signalAccount.getRecipientTrustedResolver()
289 .resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress());
290 signalAccount.getAciSessionStore().archiveAllSessions();
291 signalAccount.getPniSessionStore().archiveAllSessions();
292 signalAccount.getSenderKeyStore().deleteAll();
293 signalAccount.clearAllPreKeys();
294 return signalAccount;
295 }
296
297 public void initDatabase() {
298 getAccountDatabase();
299 }
300
301 private void clearAllPreKeys() {
302 resetPreKeyOffsets(ServiceIdType.ACI);
303 resetPreKeyOffsets(ServiceIdType.PNI);
304 this.getAciPreKeyStore().removeAllPreKeys();
305 this.getAciSignedPreKeyStore().removeAllSignedPreKeys();
306 this.getPniPreKeyStore().removeAllPreKeys();
307 this.getPniSignedPreKeyStore().removeAllSignedPreKeys();
308 save();
309 }
310
311 private static SignalAccount createLinkedAccount(
312 File dataPath,
313 String accountPath,
314 String number,
315 ServiceEnvironment serviceEnvironment,
316 ACI aci,
317 PNI pni,
318 String password,
319 String encryptedDeviceName,
320 int deviceId,
321 IdentityKeyPair aciIdentityKey,
322 IdentityKeyPair pniIdentityKey,
323 int registrationId,
324 int pniRegistrationId,
325 ProfileKey profileKey,
326 final Settings settings
327 ) throws IOException {
328 var fileName = getFileName(dataPath, accountPath);
329 IOUtils.createPrivateFile(fileName);
330
331 final var pair = openFileChannel(fileName, true);
332 var signalAccount = new SignalAccount(pair.first(), pair.second());
333
334 signalAccount.dataPath = dataPath;
335 signalAccount.accountPath = accountPath;
336 signalAccount.serviceEnvironment = serviceEnvironment;
337 signalAccount.localRegistrationId = registrationId;
338 signalAccount.localPniRegistrationId = pniRegistrationId;
339 signalAccount.settings = settings;
340 signalAccount.setProvisioningData(number,
341 aci,
342 pni,
343 password,
344 encryptedDeviceName,
345 deviceId,
346 aciIdentityKey,
347 pniIdentityKey,
348 profileKey);
349
350 signalAccount.configurationStore = new ConfigurationStore(signalAccount::saveConfigurationStore);
351
352 signalAccount.getRecipientTrustedResolver()
353 .resolveSelfRecipientTrusted(signalAccount.getSelfRecipientAddress());
354 signalAccount.previousStorageVersion = CURRENT_STORAGE_VERSION;
355 signalAccount.migrateLegacyConfigs();
356 signalAccount.clearAllPreKeys();
357 signalAccount.save();
358
359 return signalAccount;
360 }
361
362 private void setProvisioningData(
363 final String number,
364 final ACI aci,
365 final PNI pni,
366 final String password,
367 final String encryptedDeviceName,
368 final int deviceId,
369 final IdentityKeyPair aciIdentity,
370 final IdentityKeyPair pniIdentity,
371 final ProfileKey profileKey
372 ) {
373 this.number = number;
374 this.aci = aci;
375 this.pni = pni;
376 this.password = password;
377 this.profileKey = profileKey;
378 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
379 this.encryptedDeviceName = encryptedDeviceName;
380 this.deviceId = deviceId;
381 this.aciIdentityKeyPair = aciIdentity;
382 this.pniIdentityKeyPair = pniIdentity;
383 this.registered = true;
384 this.isMultiDevice = true;
385 this.lastReceiveTimestamp = 0;
386 this.pinMasterKey = null;
387 this.storageManifestVersion = -1;
388 this.setStorageManifest(null);
389 this.storageKey = null;
390 final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
391 getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
392 getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
393 if (getPniIdentityKeyPair() != null) {
394 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
395 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
396 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
397 }
398 }
399
400 private void migrateLegacyConfigs() {
401 if (getPassword() == null) {
402 setPassword(KeyUtils.createPassword());
403 }
404
405 if (getProfileKey() == null) {
406 // Old config file, creating new profile key
407 setProfileKey(KeyUtils.createProfileKey());
408 }
409 getProfileStore().storeProfileKey(getSelfRecipientId(), getProfileKey());
410 if (isPrimaryDevice() && getPniIdentityKeyPair() == null && getPni() != null) {
411 setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
412 }
413 }
414
415 private void mergeRecipients(
416 final Connection connection, RecipientId recipientId, RecipientId toBeMergedRecipientId
417 ) throws SQLException {
418 getMessageCache().mergeRecipients(recipientId, toBeMergedRecipientId);
419 getGroupStore().mergeRecipients(connection, recipientId, toBeMergedRecipientId);
420 }
421
422 public void removeRecipient(final RecipientId recipientId) {
423 final var recipientAddress = getRecipientStore().resolveRecipientAddress(recipientId);
424 getRecipientStore().deleteRecipientData(recipientId);
425 getMessageCache().deleteMessages(recipientId);
426 if (recipientAddress.serviceId().isPresent()) {
427 final var serviceId = recipientAddress.serviceId().get();
428 getAciSessionStore().deleteAllSessions(serviceId);
429 getPniSessionStore().deleteAllSessions(serviceId);
430 getIdentityKeyStore().deleteIdentity(serviceId);
431 getSenderKeyStore().deleteAll(serviceId);
432 }
433 }
434
435 public static File getFileName(File dataPath, String account) {
436 return new File(dataPath, account);
437 }
438
439 private static File getUserPath(final File dataPath, final String account) {
440 final var path = new File(dataPath, account + ".d");
441 try {
442 IOUtils.createPrivateDirectories(path);
443 } catch (IOException e) {
444 throw new AssertionError("Failed to create user path", e);
445 }
446 return path;
447 }
448
449 private static File getMessageCachePath(File dataPath, String account) {
450 return new File(getUserPath(dataPath, account), "msg-cache");
451 }
452
453 private static File getGroupCachePath(File dataPath, String account) {
454 return new File(getUserPath(dataPath, account), "group-cache");
455 }
456
457 private static File getAciPreKeysPath(File dataPath, String account) {
458 return new File(getUserPath(dataPath, account), "pre-keys");
459 }
460
461 private static File getAciSignedPreKeysPath(File dataPath, String account) {
462 return new File(getUserPath(dataPath, account), "signed-pre-keys");
463 }
464
465 private static File getPniPreKeysPath(File dataPath, String account) {
466 return new File(getUserPath(dataPath, account), "pre-keys-pni");
467 }
468
469 private static File getPniSignedPreKeysPath(File dataPath, String account) {
470 return new File(getUserPath(dataPath, account), "signed-pre-keys-pni");
471 }
472
473 private static File getIdentitiesPath(File dataPath, String account) {
474 return new File(getUserPath(dataPath, account), "identities");
475 }
476
477 private static File getSessionsPath(File dataPath, String account) {
478 return new File(getUserPath(dataPath, account), "sessions");
479 }
480
481 private static File getSenderKeysPath(File dataPath, String account) {
482 return new File(getUserPath(dataPath, account), "sender-keys");
483 }
484
485 private static File getSharedSenderKeysFile(File dataPath, String account) {
486 return new File(getUserPath(dataPath, account), "shared-sender-keys-store");
487 }
488
489 private static File getRecipientsStoreFile(File dataPath, String account) {
490 return new File(getUserPath(dataPath, account), "recipients-store");
491 }
492
493 private static File getStorageManifestFile(File dataPath, String account) {
494 return new File(getUserPath(dataPath, account), "storage-manifest");
495 }
496
497 private static File getDatabaseFile(File dataPath, String account) {
498 return new File(getUserPath(dataPath, account), "account.db");
499 }
500
501 public static boolean accountFileExists(File dataPath, String account) {
502 if (account == null) {
503 return false;
504 }
505 var f = getFileName(dataPath, account);
506 return !(!f.exists() || f.isDirectory());
507 }
508
509 private void load(
510 File dataPath, String accountPath, final Settings settings
511 ) throws IOException {
512 this.dataPath = dataPath;
513 this.accountPath = accountPath;
514 this.settings = settings;
515 final JsonNode rootNode;
516 synchronized (fileChannel) {
517 fileChannel.position(0);
518 rootNode = jsonProcessor.readTree(Channels.newInputStream(fileChannel));
519 }
520
521 var migratedLegacyConfig = false;
522
523 if (rootNode.hasNonNull("version")) {
524 var accountVersion = rootNode.get("version").asInt(1);
525 if (accountVersion > CURRENT_STORAGE_VERSION) {
526 throw new IOException("Config file was created by a more recent version: " + accountVersion);
527 } else if (accountVersion < MINIMUM_STORAGE_VERSION) {
528 throw new IOException("Config file was created by a no longer supported older version: "
529 + accountVersion);
530 }
531 previousStorageVersion = accountVersion;
532 if (accountVersion < CURRENT_STORAGE_VERSION) {
533 migratedLegacyConfig = true;
534 }
535 }
536
537 number = Utils.getNotNullNode(rootNode, "username").asText();
538 if (rootNode.hasNonNull("password")) {
539 password = rootNode.get("password").asText();
540 }
541 if (rootNode.hasNonNull("serviceEnvironment")) {
542 serviceEnvironment = ServiceEnvironment.valueOf(rootNode.get("serviceEnvironment").asText());
543 }
544 registered = Utils.getNotNullNode(rootNode, "registered").asBoolean();
545 if (rootNode.hasNonNull("uuid")) {
546 try {
547 aci = ACI.parseOrThrow(rootNode.get("uuid").asText());
548 } catch (IllegalArgumentException e) {
549 throw new IOException("Config file contains an invalid aci/uuid, needs to be a valid UUID", e);
550 }
551 }
552 if (rootNode.hasNonNull("pni")) {
553 try {
554 pni = PNI.parseOrThrow(rootNode.get("pni").asText());
555 } catch (IllegalArgumentException e) {
556 throw new IOException("Config file contains an invalid pni, needs to be a valid UUID", e);
557 }
558 }
559 if (rootNode.hasNonNull("sessionId")) {
560 sessionId = rootNode.get("sessionId").asText();
561 }
562 if (rootNode.hasNonNull("sessionNumber")) {
563 sessionNumber = rootNode.get("sessionNumber").asText();
564 }
565 if (rootNode.hasNonNull("deviceName")) {
566 encryptedDeviceName = rootNode.get("deviceName").asText();
567 }
568 if (rootNode.hasNonNull("deviceId")) {
569 deviceId = rootNode.get("deviceId").asInt();
570 }
571 if (rootNode.hasNonNull("isMultiDevice")) {
572 isMultiDevice = rootNode.get("isMultiDevice").asBoolean();
573 }
574 if (rootNode.hasNonNull("lastReceiveTimestamp")) {
575 lastReceiveTimestamp = rootNode.get("lastReceiveTimestamp").asLong();
576 }
577 int registrationId = 0;
578 if (rootNode.hasNonNull("registrationId")) {
579 registrationId = rootNode.get("registrationId").asInt();
580 }
581 if (rootNode.hasNonNull("pniRegistrationId")) {
582 localPniRegistrationId = rootNode.get("pniRegistrationId").asInt();
583 } else {
584 localPniRegistrationId = KeyHelper.generateRegistrationId(false);
585 }
586 IdentityKeyPair aciIdentityKeyPair = null;
587 if (rootNode.hasNonNull("identityPrivateKey") && rootNode.hasNonNull("identityKey")) {
588 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("identityKey").asText());
589 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("identityPrivateKey").asText());
590 aciIdentityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
591 }
592 if (rootNode.hasNonNull("pniIdentityPrivateKey") && rootNode.hasNonNull("pniIdentityKey")) {
593 final var publicKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityKey").asText());
594 final var privateKeyBytes = Base64.getDecoder().decode(rootNode.get("pniIdentityPrivateKey").asText());
595 pniIdentityKeyPair = KeyUtils.getIdentityKeyPair(publicKeyBytes, privateKeyBytes);
596 }
597
598 if (rootNode.hasNonNull("registrationLockPin")) {
599 registrationLockPin = rootNode.get("registrationLockPin").asText();
600 }
601 if (rootNode.hasNonNull("pinMasterKey")) {
602 pinMasterKey = new MasterKey(Base64.getDecoder().decode(rootNode.get("pinMasterKey").asText()));
603 }
604 if (rootNode.hasNonNull("storageKey")) {
605 storageKey = new StorageKey(Base64.getDecoder().decode(rootNode.get("storageKey").asText()));
606 }
607 if (rootNode.hasNonNull("storageManifestVersion")) {
608 storageManifestVersion = rootNode.get("storageManifestVersion").asLong();
609 }
610 if (rootNode.hasNonNull("preKeyIdOffset")) {
611 aciPreKeyIdOffset = rootNode.get("preKeyIdOffset").asInt(1);
612 } else {
613 aciPreKeyIdOffset = 1;
614 }
615 if (rootNode.hasNonNull("nextSignedPreKeyId")) {
616 aciNextSignedPreKeyId = rootNode.get("nextSignedPreKeyId").asInt(1);
617 } else {
618 aciNextSignedPreKeyId = 1;
619 }
620 if (rootNode.hasNonNull("pniPreKeyIdOffset")) {
621 pniPreKeyIdOffset = rootNode.get("pniPreKeyIdOffset").asInt(1);
622 } else {
623 pniPreKeyIdOffset = 1;
624 }
625 if (rootNode.hasNonNull("pniNextSignedPreKeyId")) {
626 pniNextSignedPreKeyId = rootNode.get("pniNextSignedPreKeyId").asInt(1);
627 } else {
628 pniNextSignedPreKeyId = 1;
629 }
630 if (rootNode.hasNonNull("profileKey")) {
631 try {
632 profileKey = new ProfileKey(Base64.getDecoder().decode(rootNode.get("profileKey").asText()));
633 } catch (InvalidInputException e) {
634 throw new IOException(
635 "Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
636 e);
637 }
638 }
639
640 if (previousStorageVersion < 5) {
641 final var legacyRecipientsStoreFile = getRecipientsStoreFile(dataPath, accountPath);
642 if (legacyRecipientsStoreFile.exists()) {
643 LegacyRecipientStore2.migrate(legacyRecipientsStoreFile, getRecipientStore());
644 // Ensure our profile key is stored in profile store
645 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
646 migratedLegacyConfig = true;
647 }
648 }
649 if (previousStorageVersion < 6) {
650 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
651 }
652 final var legacyAciPreKeysPath = getAciPreKeysPath(dataPath, accountPath);
653 if (legacyAciPreKeysPath.exists()) {
654 LegacyPreKeyStore.migrate(legacyAciPreKeysPath, getAciPreKeyStore());
655 migratedLegacyConfig = true;
656 }
657 final var legacyPniPreKeysPath = getPniPreKeysPath(dataPath, accountPath);
658 if (legacyPniPreKeysPath.exists()) {
659 LegacyPreKeyStore.migrate(legacyPniPreKeysPath, getPniPreKeyStore());
660 migratedLegacyConfig = true;
661 }
662 final var legacyAciSignedPreKeysPath = getAciSignedPreKeysPath(dataPath, accountPath);
663 if (legacyAciSignedPreKeysPath.exists()) {
664 LegacySignedPreKeyStore.migrate(legacyAciSignedPreKeysPath, getAciSignedPreKeyStore());
665 migratedLegacyConfig = true;
666 }
667 final var legacyPniSignedPreKeysPath = getPniSignedPreKeysPath(dataPath, accountPath);
668 if (legacyPniSignedPreKeysPath.exists()) {
669 LegacySignedPreKeyStore.migrate(legacyPniSignedPreKeysPath, getPniSignedPreKeyStore());
670 migratedLegacyConfig = true;
671 }
672 final var legacySessionsPath = getSessionsPath(dataPath, accountPath);
673 if (legacySessionsPath.exists()) {
674 LegacySessionStore.migrate(legacySessionsPath,
675 getRecipientResolver(),
676 getRecipientAddressResolver(),
677 getAciSessionStore());
678 migratedLegacyConfig = true;
679 }
680 final var legacyIdentitiesPath = getIdentitiesPath(dataPath, accountPath);
681 if (legacyIdentitiesPath.exists()) {
682 LegacyIdentityKeyStore.migrate(legacyIdentitiesPath,
683 getRecipientResolver(),
684 getRecipientAddressResolver(),
685 getIdentityKeyStore());
686 migratedLegacyConfig = true;
687 }
688 final var legacySignalProtocolStore = rootNode.hasNonNull("axolotlStore")
689 ? jsonProcessor.convertValue(Utils.getNotNullNode(rootNode, "axolotlStore"),
690 LegacyJsonSignalProtocolStore.class)
691 : null;
692 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
693 aciIdentityKeyPair = legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentityKeyPair();
694 registrationId = legacySignalProtocolStore.getLegacyIdentityKeyStore().getLocalRegistrationId();
695 migratedLegacyConfig = true;
696 }
697
698 this.aciIdentityKeyPair = aciIdentityKeyPair;
699 this.localRegistrationId = registrationId;
700
701 migratedLegacyConfig = loadLegacyStores(rootNode, legacySignalProtocolStore) || migratedLegacyConfig;
702
703 final var legacySenderKeysPath = getSenderKeysPath(dataPath, accountPath);
704 if (legacySenderKeysPath.exists()) {
705 LegacySenderKeyRecordStore.migrate(legacySenderKeysPath,
706 getRecipientResolver(),
707 getRecipientAddressResolver(),
708 getSenderKeyStore());
709 migratedLegacyConfig = true;
710 }
711 final var legacySenderKeysSharedPath = getSharedSenderKeysFile(dataPath, accountPath);
712 if (legacySenderKeysSharedPath.exists()) {
713 LegacySenderKeySharedStore.migrate(legacySenderKeysSharedPath,
714 getRecipientResolver(),
715 getRecipientAddressResolver(),
716 getSenderKeyStore());
717 migratedLegacyConfig = true;
718 }
719 if (rootNode.hasNonNull("groupStore")) {
720 final var groupStoreStorage = jsonProcessor.convertValue(rootNode.get("groupStore"),
721 LegacyGroupStore.Storage.class);
722 LegacyGroupStore.migrate(groupStoreStorage,
723 getGroupCachePath(dataPath, accountPath),
724 getRecipientResolver(),
725 getGroupStore());
726 migratedLegacyConfig = true;
727 }
728
729 if (rootNode.hasNonNull("stickerStore")) {
730 final var storage = jsonProcessor.convertValue(rootNode.get("stickerStore"),
731 LegacyStickerStore.Storage.class);
732 LegacyStickerStore.migrate(storage, getStickerStore());
733 migratedLegacyConfig = true;
734 }
735
736 if (rootNode.hasNonNull("configurationStore")) {
737 configurationStoreStorage = jsonProcessor.convertValue(rootNode.get("configurationStore"),
738 ConfigurationStore.Storage.class);
739 configurationStore = ConfigurationStore.fromStorage(configurationStoreStorage,
740 this::saveConfigurationStore);
741 } else {
742 configurationStore = new ConfigurationStore(this::saveConfigurationStore);
743 }
744
745 migratedLegacyConfig = loadLegacyThreadStore(rootNode) || migratedLegacyConfig;
746
747 if (migratedLegacyConfig) {
748 save();
749 }
750 }
751
752 private boolean loadLegacyStores(
753 final JsonNode rootNode, final LegacyJsonSignalProtocolStore legacySignalProtocolStore
754 ) {
755 var migrated = false;
756 var legacyRecipientStoreNode = rootNode.get("recipientStore");
757 if (legacyRecipientStoreNode != null) {
758 logger.debug("Migrating legacy recipient store.");
759 var legacyRecipientStore = jsonProcessor.convertValue(legacyRecipientStoreNode, LegacyRecipientStore.class);
760 if (legacyRecipientStore != null) {
761 legacyRecipientStore.getAddresses()
762 .forEach(recipient -> getRecipientStore().resolveRecipientTrusted(recipient));
763 }
764 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
765 migrated = true;
766 }
767
768 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyPreKeyStore() != null) {
769 logger.debug("Migrating legacy pre key store.");
770 for (var entry : legacySignalProtocolStore.getLegacyPreKeyStore().getPreKeys().entrySet()) {
771 try {
772 getAciPreKeyStore().storePreKey(entry.getKey(), new PreKeyRecord(entry.getValue()));
773 } catch (InvalidMessageException e) {
774 logger.warn("Failed to migrate pre key, ignoring", e);
775 }
776 }
777 migrated = true;
778 }
779
780 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySignedPreKeyStore() != null) {
781 logger.debug("Migrating legacy signed pre key store.");
782 for (var entry : legacySignalProtocolStore.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
783 try {
784 getAciSignedPreKeyStore().storeSignedPreKey(entry.getKey(),
785 new SignedPreKeyRecord(entry.getValue()));
786 } catch (InvalidMessageException e) {
787 logger.warn("Failed to migrate signed pre key, ignoring", e);
788 }
789 }
790 migrated = true;
791 }
792
793 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacySessionStore() != null) {
794 logger.debug("Migrating legacy session store.");
795 for (var session : legacySignalProtocolStore.getLegacySessionStore().getSessions()) {
796 try {
797 getAciSessionStore().storeSession(new SignalProtocolAddress(session.address.getIdentifier(),
798 session.deviceId), new SessionRecord(session.sessionRecord));
799 } catch (Exception e) {
800 logger.warn("Failed to migrate session, ignoring", e);
801 }
802 }
803 migrated = true;
804 }
805
806 if (legacySignalProtocolStore != null && legacySignalProtocolStore.getLegacyIdentityKeyStore() != null) {
807 logger.debug("Migrating legacy identity session store.");
808 for (var identity : legacySignalProtocolStore.getLegacyIdentityKeyStore().getIdentities()) {
809 if (identity.getAddress().serviceId().isEmpty()) {
810 continue;
811 }
812 final var serviceId = identity.getAddress().getServiceId();
813 getIdentityKeyStore().saveIdentity(serviceId, identity.getIdentityKey());
814 getIdentityKeyStore().setIdentityTrustLevel(serviceId,
815 identity.getIdentityKey(),
816 identity.getTrustLevel());
817 }
818 migrated = true;
819 }
820
821 if (rootNode.hasNonNull("contactStore")) {
822 logger.debug("Migrating legacy contact store.");
823 final var contactStoreNode = rootNode.get("contactStore");
824 final var contactStore = jsonProcessor.convertValue(contactStoreNode, LegacyJsonContactsStore.class);
825 for (var contact : contactStore.getContacts()) {
826 final var recipientId = getRecipientStore().resolveRecipientTrusted(contact.getAddress());
827 getContactStore().storeContact(recipientId,
828 new Contact(contact.name,
829 null,
830 contact.color,
831 contact.messageExpirationTime,
832 contact.blocked,
833 contact.archived,
834 false));
835
836 // Store profile keys only in profile store
837 var profileKeyString = contact.profileKey;
838 if (profileKeyString != null) {
839 final ProfileKey profileKey;
840 try {
841 profileKey = new ProfileKey(Base64.getDecoder().decode(profileKeyString));
842 getProfileStore().storeProfileKey(recipientId, profileKey);
843 } catch (InvalidInputException e) {
844 logger.warn("Failed to parse legacy contact profile key: {}", e.getMessage());
845 }
846 }
847 }
848 migrated = true;
849 }
850
851 if (rootNode.hasNonNull("profileStore")) {
852 logger.debug("Migrating legacy profile store.");
853 var profileStoreNode = rootNode.get("profileStore");
854 final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
855 for (var profileEntry : legacyProfileStore.getProfileEntries()) {
856 var recipientId = getRecipientResolver().resolveRecipient(profileEntry.getAddress());
857 // Not migrating profile key credential here, it was changed to expiring profile key credentials
858 getProfileStore().storeProfileKey(recipientId, profileEntry.getProfileKey());
859 final var profile = profileEntry.getProfile();
860 if (profile != null) {
861 final var capabilities = new HashSet<Profile.Capability>();
862 if (profile.getCapabilities() != null) {
863 if (profile.getCapabilities().gv1Migration) {
864 capabilities.add(Profile.Capability.gv1Migration);
865 }
866 if (profile.getCapabilities().storage) {
867 capabilities.add(Profile.Capability.storage);
868 }
869 }
870 final var newProfile = new Profile(profileEntry.getLastUpdateTimestamp(),
871 profile.getGivenName(),
872 profile.getFamilyName(),
873 profile.getAbout(),
874 profile.getAboutEmoji(),
875 null,
876 null,
877 profile.isUnrestrictedUnidentifiedAccess()
878 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
879 : profile.getUnidentifiedAccess() != null
880 ? Profile.UnidentifiedAccessMode.ENABLED
881 : Profile.UnidentifiedAccessMode.DISABLED,
882 capabilities);
883 getProfileStore().storeProfile(recipientId, newProfile);
884 }
885 }
886 }
887
888 return migrated;
889 }
890
891 private boolean loadLegacyThreadStore(final JsonNode rootNode) {
892 var threadStoreNode = rootNode.get("threadStore");
893 if (threadStoreNode != null && !threadStoreNode.isNull()) {
894 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
895 // Migrate thread info to group and contact store
896 for (var thread : threadStore.getThreads()) {
897 if (thread.id == null || thread.id.isEmpty()) {
898 continue;
899 }
900 try {
901 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
902 final var recipientId = getRecipientResolver().resolveRecipient(thread.id);
903 var contact = getContactStore().getContact(recipientId);
904 if (contact != null) {
905 getContactStore().storeContact(recipientId,
906 Contact.newBuilder(contact)
907 .withMessageExpirationTime(thread.messageExpirationTime)
908 .build());
909 }
910 } else {
911 var groupInfo = getGroupStore().getGroup(GroupId.fromBase64(thread.id));
912 if (groupInfo instanceof GroupInfoV1) {
913 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
914 getGroupStore().updateGroup(groupInfo);
915 }
916 }
917 } catch (Exception e) {
918 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
919 }
920 }
921 return true;
922 }
923
924 return false;
925 }
926
927 private void saveConfigurationStore(ConfigurationStore.Storage storage) {
928 this.configurationStoreStorage = storage;
929 save();
930 }
931
932 private void save() {
933 synchronized (fileChannel) {
934 var rootNode = jsonProcessor.createObjectNode();
935 rootNode.put("version", CURRENT_STORAGE_VERSION)
936 .put("username", number)
937 .put("serviceEnvironment", serviceEnvironment == null ? null : serviceEnvironment.name())
938 .put("uuid", aci == null ? null : aci.toString())
939 .put("pni", pni == null ? null : pni.toString())
940 .put("sessionId", sessionId)
941 .put("sessionNumber", sessionNumber)
942 .put("deviceName", encryptedDeviceName)
943 .put("deviceId", deviceId)
944 .put("isMultiDevice", isMultiDevice)
945 .put("lastReceiveTimestamp", lastReceiveTimestamp)
946 .put("password", password)
947 .put("registrationId", localRegistrationId)
948 .put("pniRegistrationId", localPniRegistrationId)
949 .put("identityPrivateKey",
950 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPrivateKey().serialize()))
951 .put("identityKey",
952 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPublicKey().serialize()))
953 .put("pniIdentityPrivateKey",
954 pniIdentityKeyPair == null
955 ? null
956 : Base64.getEncoder()
957 .encodeToString(pniIdentityKeyPair.getPrivateKey().serialize()))
958 .put("pniIdentityKey",
959 pniIdentityKeyPair == null
960 ? null
961 : Base64.getEncoder().encodeToString(pniIdentityKeyPair.getPublicKey().serialize()))
962 .put("registrationLockPin", registrationLockPin)
963 .put("pinMasterKey",
964 pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
965 .put("storageKey",
966 storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
967 .put("storageManifestVersion", storageManifestVersion == -1 ? null : storageManifestVersion)
968 .put("preKeyIdOffset", aciPreKeyIdOffset)
969 .put("nextSignedPreKeyId", aciNextSignedPreKeyId)
970 .put("pniPreKeyIdOffset", pniPreKeyIdOffset)
971 .put("pniNextSignedPreKeyId", pniNextSignedPreKeyId)
972 .put("profileKey",
973 profileKey == null ? null : Base64.getEncoder().encodeToString(profileKey.serialize()))
974 .put("registered", registered)
975 .putPOJO("configurationStore", configurationStoreStorage);
976 try {
977 try (var output = new ByteArrayOutputStream()) {
978 // Write to memory first to prevent corrupting the file in case of serialization errors
979 jsonProcessor.writeValue(output, rootNode);
980 var input = new ByteArrayInputStream(output.toByteArray());
981 fileChannel.position(0);
982 input.transferTo(Channels.newOutputStream(fileChannel));
983 fileChannel.truncate(fileChannel.position());
984 fileChannel.force(false);
985 }
986 } catch (Exception e) {
987 logger.error("Error saving file: {}", e.getMessage(), e);
988 }
989 }
990 }
991
992 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
993 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
994 try {
995 var lock = fileChannel.tryLock();
996 if (lock == null) {
997 if (!waitForLock) {
998 logger.debug("Config file is in use by another instance.");
999 throw new IOException("Config file is in use by another instance.");
1000 }
1001 logger.info("Config file is in use by another instance, waiting…");
1002 lock = fileChannel.lock();
1003 logger.info("Config file lock acquired.");
1004 }
1005 final var result = new Pair<>(fileChannel, lock);
1006 fileChannel = null;
1007 return result;
1008 } finally {
1009 if (fileChannel != null) {
1010 fileChannel.close();
1011 }
1012 }
1013 }
1014
1015 public void resetPreKeyOffsets(final ServiceIdType serviceIdType) {
1016 if (serviceIdType.equals(ServiceIdType.ACI)) {
1017 this.aciPreKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE);
1018 this.aciNextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE);
1019 } else {
1020 this.pniPreKeyIdOffset = new SecureRandom().nextInt(Medium.MAX_VALUE);
1021 this.pniNextSignedPreKeyId = new SecureRandom().nextInt(Medium.MAX_VALUE);
1022 }
1023 save();
1024 }
1025
1026 public void addPreKeys(ServiceIdType serviceIdType, List<PreKeyRecord> records) {
1027 if (serviceIdType.equals(ServiceIdType.ACI)) {
1028 addAciPreKeys(records);
1029 } else {
1030 addPniPreKeys(records);
1031 }
1032 }
1033
1034 public void addAciPreKeys(List<PreKeyRecord> records) {
1035 for (var record : records) {
1036 if (aciPreKeyIdOffset != record.getId()) {
1037 logger.error("Invalid pre key id {}, expected {}", record.getId(), aciPreKeyIdOffset);
1038 throw new AssertionError("Invalid pre key id");
1039 }
1040 getAciPreKeyStore().storePreKey(record.getId(), record);
1041 aciPreKeyIdOffset = (aciPreKeyIdOffset + 1) % Medium.MAX_VALUE;
1042 }
1043 save();
1044 }
1045
1046 public void addPniPreKeys(List<PreKeyRecord> records) {
1047 for (var record : records) {
1048 if (pniPreKeyIdOffset != record.getId()) {
1049 logger.error("Invalid pre key id {}, expected {}", record.getId(), pniPreKeyIdOffset);
1050 throw new AssertionError("Invalid pre key id");
1051 }
1052 getPniPreKeyStore().storePreKey(record.getId(), record);
1053 pniPreKeyIdOffset = (pniPreKeyIdOffset + 1) % Medium.MAX_VALUE;
1054 }
1055 save();
1056 }
1057
1058 public void addSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord record) {
1059 if (serviceIdType.equals(ServiceIdType.ACI)) {
1060 addAciSignedPreKey(record);
1061 } else {
1062 addPniSignedPreKey(record);
1063 }
1064 }
1065
1066 public void addAciSignedPreKey(SignedPreKeyRecord record) {
1067 if (aciNextSignedPreKeyId != record.getId()) {
1068 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), aciNextSignedPreKeyId);
1069 throw new AssertionError("Invalid signed pre key id");
1070 }
1071 getAciSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
1072 aciNextSignedPreKeyId = (aciNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
1073 save();
1074 }
1075
1076 public void addPniSignedPreKey(SignedPreKeyRecord record) {
1077 if (pniNextSignedPreKeyId != record.getId()) {
1078 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), pniNextSignedPreKeyId);
1079 throw new AssertionError("Invalid signed pre key id");
1080 }
1081 getPniSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
1082 pniNextSignedPreKeyId = (pniNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
1083 save();
1084 }
1085
1086 public int getPreviousStorageVersion() {
1087 return previousStorageVersion;
1088 }
1089
1090 public SignalServiceDataStore getSignalServiceDataStore() {
1091 return new SignalServiceDataStore() {
1092 @Override
1093 public SignalServiceAccountDataStore get(final ServiceId accountIdentifier) {
1094 if (accountIdentifier.equals(aci)) {
1095 return aci();
1096 } else if (accountIdentifier.equals(pni)) {
1097 return pni();
1098 } else {
1099 throw new IllegalArgumentException("No matching store found for " + accountIdentifier);
1100 }
1101 }
1102
1103 @Override
1104 public SignalServiceAccountDataStore aci() {
1105 return getAciSignalServiceAccountDataStore();
1106 }
1107
1108 @Override
1109 public SignalServiceAccountDataStore pni() {
1110 return getPniSignalServiceAccountDataStore();
1111 }
1112
1113 @Override
1114 public boolean isMultiDevice() {
1115 return SignalAccount.this.isMultiDevice();
1116 }
1117 };
1118 }
1119
1120 private SignalServiceAccountDataStore getAciSignalServiceAccountDataStore() {
1121 return getOrCreate(() -> aciSignalProtocolStore,
1122 () -> aciSignalProtocolStore = new SignalProtocolStore(getAciPreKeyStore(),
1123 getAciSignedPreKeyStore(),
1124 getAciSessionStore(),
1125 getAciIdentityKeyStore(),
1126 getSenderKeyStore(),
1127 this::isMultiDevice));
1128 }
1129
1130 private SignalServiceAccountDataStore getPniSignalServiceAccountDataStore() {
1131 return getOrCreate(() -> pniSignalProtocolStore,
1132 () -> pniSignalProtocolStore = new SignalProtocolStore(getPniPreKeyStore(),
1133 getPniSignedPreKeyStore(),
1134 getPniSessionStore(),
1135 getPniIdentityKeyStore(),
1136 getSenderKeyStore(),
1137 this::isMultiDevice));
1138 }
1139
1140 private PreKeyStore getAciPreKeyStore() {
1141 return getOrCreate(() -> aciPreKeyStore,
1142 () -> aciPreKeyStore = new PreKeyStore(getAccountDatabase(), ServiceIdType.ACI));
1143 }
1144
1145 private SignedPreKeyStore getAciSignedPreKeyStore() {
1146 return getOrCreate(() -> aciSignedPreKeyStore,
1147 () -> aciSignedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), ServiceIdType.ACI));
1148 }
1149
1150 private PreKeyStore getPniPreKeyStore() {
1151 return getOrCreate(() -> pniPreKeyStore,
1152 () -> pniPreKeyStore = new PreKeyStore(getAccountDatabase(), ServiceIdType.PNI));
1153 }
1154
1155 private SignedPreKeyStore getPniSignedPreKeyStore() {
1156 return getOrCreate(() -> pniSignedPreKeyStore,
1157 () -> pniSignedPreKeyStore = new SignedPreKeyStore(getAccountDatabase(), ServiceIdType.PNI));
1158 }
1159
1160 public SessionStore getAciSessionStore() {
1161 return getOrCreate(() -> aciSessionStore,
1162 () -> aciSessionStore = new SessionStore(getAccountDatabase(), ServiceIdType.ACI));
1163 }
1164
1165 public SessionStore getPniSessionStore() {
1166 return getOrCreate(() -> pniSessionStore,
1167 () -> pniSessionStore = new SessionStore(getAccountDatabase(), ServiceIdType.PNI));
1168 }
1169
1170 public IdentityKeyStore getIdentityKeyStore() {
1171 return getOrCreate(() -> identityKeyStore,
1172 () -> identityKeyStore = new IdentityKeyStore(getAccountDatabase(), settings.trustNewIdentity()));
1173 }
1174
1175 public SignalIdentityKeyStore getAciIdentityKeyStore() {
1176 return getOrCreate(() -> aciIdentityKeyStore,
1177 () -> aciIdentityKeyStore = new SignalIdentityKeyStore(getRecipientResolver(),
1178 () -> aciIdentityKeyPair,
1179 localRegistrationId,
1180 getIdentityKeyStore()));
1181 }
1182
1183 public SignalIdentityKeyStore getPniIdentityKeyStore() {
1184 return getOrCreate(() -> pniIdentityKeyStore,
1185 () -> pniIdentityKeyStore = new SignalIdentityKeyStore(getRecipientResolver(),
1186 () -> pniIdentityKeyPair,
1187 localRegistrationId,
1188 getIdentityKeyStore()));
1189 }
1190
1191 public GroupStore getGroupStore() {
1192 return getOrCreate(() -> groupStore,
1193 () -> groupStore = new GroupStore(getAccountDatabase(),
1194 getRecipientResolver(),
1195 getRecipientIdCreator()));
1196 }
1197
1198 public ContactsStore getContactStore() {
1199 return getRecipientStore();
1200 }
1201
1202 private RecipientIdCreator getRecipientIdCreator() {
1203 return recipientId -> getRecipientStore().create(recipientId);
1204 }
1205
1206 public RecipientResolver getRecipientResolver() {
1207 return new RecipientResolver.RecipientResolverWrapper(this::getRecipientStore);
1208 }
1209
1210 public RecipientTrustedResolver getRecipientTrustedResolver() {
1211 return new RecipientTrustedResolver.RecipientTrustedResolverWrapper(this::getRecipientStore);
1212 }
1213
1214 public RecipientAddressResolver getRecipientAddressResolver() {
1215 return recipientId -> getRecipientStore().resolveRecipientAddress(recipientId);
1216 }
1217
1218 public RecipientStore getRecipientStore() {
1219 return getOrCreate(() -> recipientStore,
1220 () -> recipientStore = new RecipientStore(this::mergeRecipients,
1221 this::getSelfRecipientAddress,
1222 getAccountDatabase()));
1223 }
1224
1225 public ProfileStore getProfileStore() {
1226 return getRecipientStore();
1227 }
1228
1229 public StickerStore getStickerStore() {
1230 return getOrCreate(() -> stickerStore, () -> stickerStore = new StickerStore(getAccountDatabase()));
1231 }
1232
1233 public SenderKeyStore getSenderKeyStore() {
1234 return getOrCreate(() -> senderKeyStore, () -> senderKeyStore = new SenderKeyStore(getAccountDatabase()));
1235 }
1236
1237 public ConfigurationStore getConfigurationStore() {
1238 return configurationStore;
1239 }
1240
1241 public MessageCache getMessageCache() {
1242 return getOrCreate(() -> messageCache,
1243 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
1244 }
1245
1246 public AccountDatabase getAccountDatabase() {
1247 return getOrCreate(() -> accountDatabase, () -> {
1248 try {
1249 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
1250 } catch (SQLException e) {
1251 throw new RuntimeException(e);
1252 }
1253 });
1254 }
1255
1256 public MessageSendLogStore getMessageSendLogStore() {
1257 return getOrCreate(() -> messageSendLogStore,
1258 () -> messageSendLogStore = new MessageSendLogStore(getAccountDatabase(),
1259 settings.disableMessageSendLog()));
1260 }
1261
1262 public CredentialsProvider getCredentialsProvider() {
1263 return new CredentialsProvider() {
1264 @Override
1265 public ACI getAci() {
1266 return aci;
1267 }
1268
1269 @Override
1270 public PNI getPni() {
1271 return pni;
1272 }
1273
1274 @Override
1275 public String getE164() {
1276 return number;
1277 }
1278
1279 @Override
1280 public String getPassword() {
1281 return password;
1282 }
1283
1284 @Override
1285 public int getDeviceId() {
1286 return deviceId;
1287 }
1288 };
1289 }
1290
1291 public String getNumber() {
1292 return number;
1293 }
1294
1295 public void setNumber(final String number) {
1296 this.number = number;
1297 save();
1298 }
1299
1300 public ServiceEnvironment getServiceEnvironment() {
1301 return serviceEnvironment;
1302 }
1303
1304 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1305 this.serviceEnvironment = serviceEnvironment;
1306 save();
1307 }
1308
1309 public AccountAttributes getAccountAttributes(String registrationLock) {
1310 return new AccountAttributes(null,
1311 getLocalRegistrationId(),
1312 true,
1313 null,
1314 registrationLock != null ? registrationLock : getRegistrationLock(),
1315 getSelfUnidentifiedAccessKey(),
1316 isUnrestrictedUnidentifiedAccess(),
1317 capabilities,
1318 isDiscoverableByPhoneNumber(),
1319 encryptedDeviceName,
1320 getLocalPniRegistrationId(),
1321 null); // TODO recoveryPassword?
1322 }
1323
1324 public ServiceId getAccountId(ServiceIdType serviceIdType) {
1325 return serviceIdType.equals(ServiceIdType.ACI) ? aci : pni;
1326 }
1327
1328 public ACI getAci() {
1329 return aci;
1330 }
1331
1332 public void setAci(final ACI aci) {
1333 this.aci = aci;
1334 save();
1335 }
1336
1337 public PNI getPni() {
1338 return pni;
1339 }
1340
1341 public void setPni(final PNI updatedPni) {
1342 if (this.pni != null && !this.pni.equals(updatedPni)) {
1343 // Clear data for old PNI
1344 identityKeyStore.deleteIdentity(this.pni);
1345 getPniPreKeyStore().removeAllPreKeys();
1346 getPniSignedPreKeyStore().removeAllSignedPreKeys();
1347 }
1348
1349 this.pni = updatedPni;
1350 save();
1351 }
1352
1353 public void setPni(
1354 final PNI updatedPni,
1355 final IdentityKeyPair pniIdentityKeyPair,
1356 final SignedPreKeyRecord pniSignedPreKey,
1357 final int localPniRegistrationId
1358 ) {
1359 setPni(updatedPni);
1360
1361 setPniIdentityKeyPair(pniIdentityKeyPair);
1362 addPniSignedPreKey(pniSignedPreKey);
1363 setLocalPniRegistrationId(localPniRegistrationId);
1364 }
1365
1366 public SignalServiceAddress getSelfAddress() {
1367 return new SignalServiceAddress(aci, number);
1368 }
1369
1370 public RecipientAddress getSelfRecipientAddress() {
1371 return new RecipientAddress(aci, pni, number);
1372 }
1373
1374 public RecipientId getSelfRecipientId() {
1375 return getRecipientResolver().resolveRecipient(getSelfRecipientAddress());
1376 }
1377
1378 public String getSessionId(final String forNumber) {
1379 if (!forNumber.equals(sessionNumber)) {
1380 return null;
1381 }
1382 return sessionId;
1383 }
1384
1385 public void setSessionId(final String sessionNumber, final String sessionId) {
1386 this.sessionNumber = sessionNumber;
1387 this.sessionId = sessionId;
1388 save();
1389 }
1390
1391 public byte[] getEncryptedDeviceName() {
1392 return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
1393 }
1394
1395 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1396 this.encryptedDeviceName = encryptedDeviceName;
1397 save();
1398 }
1399
1400 public int getDeviceId() {
1401 return deviceId;
1402 }
1403
1404 public boolean isPrimaryDevice() {
1405 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1406 }
1407
1408 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1409 return serviceIdType.equals(ServiceIdType.ACI) ? aciIdentityKeyPair : pniIdentityKeyPair;
1410 }
1411
1412 public IdentityKeyPair getAciIdentityKeyPair() {
1413 return aciIdentityKeyPair;
1414 }
1415
1416 public IdentityKeyPair getPniIdentityKeyPair() {
1417 return pniIdentityKeyPair;
1418 }
1419
1420 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1421 pniIdentityKeyPair = identityKeyPair;
1422 final var pniPublicKey = identityKeyPair.getPublicKey();
1423 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1424 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1425 save();
1426 }
1427
1428 public int getLocalRegistrationId() {
1429 return localRegistrationId;
1430 }
1431
1432 public int getLocalPniRegistrationId() {
1433 return localPniRegistrationId;
1434 }
1435
1436 public void setLocalPniRegistrationId(final int localPniRegistrationId) {
1437 this.localPniRegistrationId = localPniRegistrationId;
1438 save();
1439 }
1440
1441 public String getPassword() {
1442 return password;
1443 }
1444
1445 private void setPassword(final String password) {
1446 this.password = password;
1447 save();
1448 }
1449
1450 public void setRegistrationLockPin(final String registrationLockPin) {
1451 this.registrationLockPin = registrationLockPin;
1452 save();
1453 }
1454
1455 public String getRegistrationLockPin() {
1456 return registrationLockPin;
1457 }
1458
1459 public String getRegistrationLock() {
1460 final var masterKey = getPinBackedMasterKey();
1461 if (masterKey == null) {
1462 return null;
1463 }
1464 return masterKey.deriveRegistrationLock();
1465 }
1466
1467 public MasterKey getPinBackedMasterKey() {
1468 if (registrationLockPin == null) {
1469 return null;
1470 }
1471 return pinMasterKey;
1472 }
1473
1474 public MasterKey getOrCreatePinMasterKey() {
1475 if (pinMasterKey == null) {
1476 pinMasterKey = KeyUtils.createMasterKey();
1477 save();
1478 }
1479 return pinMasterKey;
1480 }
1481
1482 public StorageKey getStorageKey() {
1483 if (pinMasterKey != null) {
1484 return pinMasterKey.deriveStorageServiceKey();
1485 }
1486 return storageKey;
1487 }
1488
1489 public StorageKey getOrCreateStorageKey() {
1490 if (isPrimaryDevice()) {
1491 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1492 }
1493 return storageKey;
1494 }
1495
1496 public void setStorageKey(final StorageKey storageKey) {
1497 if (storageKey.equals(this.storageKey)) {
1498 return;
1499 }
1500 this.storageKey = storageKey;
1501 save();
1502 }
1503
1504 public long getStorageManifestVersion() {
1505 return this.storageManifestVersion;
1506 }
1507
1508 public void setStorageManifestVersion(final long storageManifestVersion) {
1509 if (storageManifestVersion == this.storageManifestVersion) {
1510 return;
1511 }
1512 this.storageManifestVersion = storageManifestVersion;
1513 save();
1514 }
1515
1516 public Optional<SignalStorageManifest> getStorageManifest() {
1517 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1518 if (!storageManifestFile.exists()) {
1519 return Optional.empty();
1520 }
1521 try (var inputStream = new FileInputStream(storageManifestFile)) {
1522 return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
1523 } catch (IOException e) {
1524 logger.warn("Failed to read local storage manifest.", e);
1525 return Optional.empty();
1526 }
1527 }
1528
1529 public void setStorageManifest(SignalStorageManifest manifest) {
1530 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1531 if (manifest == null) {
1532 if (storageManifestFile.exists()) {
1533 try {
1534 Files.delete(storageManifestFile.toPath());
1535 } catch (IOException e) {
1536 logger.error("Failed to delete local storage manifest.", e);
1537 }
1538 }
1539 return;
1540 }
1541
1542 final var manifestBytes = manifest.serialize();
1543 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1544 outputStream.write(manifestBytes);
1545 } catch (IOException e) {
1546 logger.error("Failed to store local storage manifest.", e);
1547 }
1548 }
1549
1550 public ProfileKey getProfileKey() {
1551 return profileKey;
1552 }
1553
1554 public void setProfileKey(final ProfileKey profileKey) {
1555 if (profileKey.equals(this.profileKey)) {
1556 return;
1557 }
1558 this.profileKey = profileKey;
1559 save();
1560 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
1561 }
1562
1563 public byte[] getSelfUnidentifiedAccessKey() {
1564 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1565 }
1566
1567 public int getPreKeyIdOffset(ServiceIdType serviceIdType) {
1568 return serviceIdType.equals(ServiceIdType.ACI) ? aciPreKeyIdOffset : pniPreKeyIdOffset;
1569 }
1570
1571 public int getNextSignedPreKeyId(ServiceIdType serviceIdType) {
1572 return serviceIdType.equals(ServiceIdType.ACI) ? aciNextSignedPreKeyId : pniNextSignedPreKeyId;
1573 }
1574
1575 public boolean isRegistered() {
1576 return registered;
1577 }
1578
1579 public void setRegistered(final boolean registered) {
1580 this.registered = registered;
1581 save();
1582 }
1583
1584 public boolean isMultiDevice() {
1585 return isMultiDevice;
1586 }
1587
1588 public void setMultiDevice(final boolean multiDevice) {
1589 if (isMultiDevice == multiDevice) {
1590 return;
1591 }
1592 isMultiDevice = multiDevice;
1593 save();
1594 }
1595
1596 public long getLastReceiveTimestamp() {
1597 return lastReceiveTimestamp;
1598 }
1599
1600 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1601 this.lastReceiveTimestamp = lastReceiveTimestamp;
1602 save();
1603 }
1604
1605 public boolean isUnrestrictedUnidentifiedAccess() {
1606 final var profile = getProfileStore().getProfile(getSelfRecipientId());
1607 return profile != null && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED;
1608 }
1609
1610 public boolean isDiscoverableByPhoneNumber() {
1611 final var phoneNumberUnlisted = configurationStore.getPhoneNumberUnlisted();
1612 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1613 }
1614
1615 public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
1616 this.pinMasterKey = masterKey;
1617 this.storageManifestVersion = -1;
1618 this.setStorageManifest(null);
1619 this.storageKey = null;
1620 this.encryptedDeviceName = null;
1621 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1622 this.isMultiDevice = false;
1623 this.registered = true;
1624 this.aci = aci;
1625 this.pni = pni;
1626 this.registrationLockPin = pin;
1627 this.lastReceiveTimestamp = 0;
1628 save();
1629
1630 clearAllPreKeys();
1631 getAciSessionStore().archiveAllSessions();
1632 getPniSessionStore().archiveAllSessions();
1633 getSenderKeyStore().deleteAll();
1634 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1635 final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
1636 getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
1637 getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
1638 if (getPniIdentityKeyPair() == null) {
1639 setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
1640 } else {
1641 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
1642 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1643 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1644 }
1645 }
1646
1647 public void deleteAccountData() throws IOException {
1648 close();
1649 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1650 .sorted(Comparator.reverseOrder())) {
1651 for (final var file = files.iterator(); file.hasNext(); ) {
1652 Files.delete(file.next());
1653 }
1654 }
1655 Files.delete(getFileName(dataPath, accountPath).toPath());
1656 }
1657
1658 @Override
1659 public void close() {
1660 synchronized (fileChannel) {
1661 if (accountDatabase != null) {
1662 try {
1663 accountDatabase.close();
1664 } catch (SQLException e) {
1665 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1666 }
1667 }
1668 if (messageSendLogStore != null) {
1669 messageSendLogStore.close();
1670 }
1671 try {
1672 try {
1673 lock.close();
1674 } catch (ClosedChannelException ignored) {
1675 }
1676 fileChannel.close();
1677 } catch (IOException e) {
1678 logger.warn("Failed to close account: {}", e.getMessage(), e);
1679 }
1680 }
1681 }
1682
1683 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1684 var value = supplier.get();
1685 if (value != null) {
1686 return value;
1687 }
1688
1689 synchronized (LOCK) {
1690 value = supplier.get();
1691 if (value != null) {
1692 return value;
1693 }
1694 creator.call();
1695 return supplier.get();
1696 }
1697 }
1698
1699 private interface Callable {
1700
1701 void call();
1702 }
1703 }