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