]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
5014ab6d99ea26eee41a377bf90c142cbe3d9083
[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.getCapabilities;
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().serviceId().get();
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 getAccountCapabilities(),
1332 isDiscoverableByPhoneNumber(),
1333 encryptedDeviceName,
1334 getLocalPniRegistrationId(),
1335 null); // TODO recoveryPassword?
1336 }
1337
1338 public AccountAttributes.Capabilities getAccountCapabilities() {
1339 return getCapabilities(isPrimaryDevice());
1340 }
1341
1342 public ServiceId getAccountId(ServiceIdType serviceIdType) {
1343 return serviceIdType.equals(ServiceIdType.ACI) ? aci : pni;
1344 }
1345
1346 public ACI getAci() {
1347 return aci;
1348 }
1349
1350 public void setAci(final ACI aci) {
1351 this.aci = aci;
1352 save();
1353 }
1354
1355 public PNI getPni() {
1356 return pni;
1357 }
1358
1359 public void setPni(final PNI updatedPni) {
1360 if (this.pni != null && !this.pni.equals(updatedPni)) {
1361 // Clear data for old PNI
1362 identityKeyStore.deleteIdentity(this.pni);
1363 getPniPreKeyStore().removeAllPreKeys();
1364 getPniSignedPreKeyStore().removeAllSignedPreKeys();
1365 }
1366
1367 this.pni = updatedPni;
1368 save();
1369 }
1370
1371 public void setPni(
1372 final PNI updatedPni,
1373 final IdentityKeyPair pniIdentityKeyPair,
1374 final SignedPreKeyRecord pniSignedPreKey,
1375 final int localPniRegistrationId
1376 ) {
1377 setPni(updatedPni);
1378
1379 setPniIdentityKeyPair(pniIdentityKeyPair);
1380 addPniSignedPreKey(pniSignedPreKey);
1381 setLocalPniRegistrationId(localPniRegistrationId);
1382 }
1383
1384 public SignalServiceAddress getSelfAddress() {
1385 return new SignalServiceAddress(aci, number);
1386 }
1387
1388 public RecipientAddress getSelfRecipientAddress() {
1389 return new RecipientAddress(aci, pni, number, username);
1390 }
1391
1392 public RecipientId getSelfRecipientId() {
1393 return getRecipientResolver().resolveRecipient(getSelfRecipientAddress());
1394 }
1395
1396 public String getSessionId(final String forNumber) {
1397 if (!forNumber.equals(sessionNumber)) {
1398 return null;
1399 }
1400 return sessionId;
1401 }
1402
1403 public void setSessionId(final String sessionNumber, final String sessionId) {
1404 this.sessionNumber = sessionNumber;
1405 this.sessionId = sessionId;
1406 save();
1407 }
1408
1409 public byte[] getEncryptedDeviceName() {
1410 return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
1411 }
1412
1413 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1414 this.encryptedDeviceName = encryptedDeviceName;
1415 save();
1416 }
1417
1418 public int getDeviceId() {
1419 return deviceId;
1420 }
1421
1422 public boolean isPrimaryDevice() {
1423 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1424 }
1425
1426 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1427 return serviceIdType.equals(ServiceIdType.ACI) ? aciIdentityKeyPair : pniIdentityKeyPair;
1428 }
1429
1430 public IdentityKeyPair getAciIdentityKeyPair() {
1431 return aciIdentityKeyPair;
1432 }
1433
1434 public IdentityKeyPair getPniIdentityKeyPair() {
1435 return pniIdentityKeyPair;
1436 }
1437
1438 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1439 pniIdentityKeyPair = identityKeyPair;
1440 final var pniPublicKey = identityKeyPair.getPublicKey();
1441 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1442 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1443 save();
1444 }
1445
1446 public int getLocalRegistrationId() {
1447 return localRegistrationId;
1448 }
1449
1450 public int getLocalPniRegistrationId() {
1451 return localPniRegistrationId;
1452 }
1453
1454 public void setLocalPniRegistrationId(final int localPniRegistrationId) {
1455 this.localPniRegistrationId = localPniRegistrationId;
1456 save();
1457 }
1458
1459 public String getPassword() {
1460 return password;
1461 }
1462
1463 private void setPassword(final String password) {
1464 this.password = password;
1465 save();
1466 }
1467
1468 public void setRegistrationLockPin(final String registrationLockPin) {
1469 this.registrationLockPin = registrationLockPin;
1470 save();
1471 }
1472
1473 public String getRegistrationLockPin() {
1474 return registrationLockPin;
1475 }
1476
1477 public String getRegistrationLock() {
1478 final var masterKey = getPinBackedMasterKey();
1479 if (masterKey == null) {
1480 return null;
1481 }
1482 return masterKey.deriveRegistrationLock();
1483 }
1484
1485 public MasterKey getPinBackedMasterKey() {
1486 if (registrationLockPin == null) {
1487 return null;
1488 }
1489 return pinMasterKey;
1490 }
1491
1492 public MasterKey getOrCreatePinMasterKey() {
1493 if (pinMasterKey == null) {
1494 pinMasterKey = KeyUtils.createMasterKey();
1495 save();
1496 }
1497 return pinMasterKey;
1498 }
1499
1500 public StorageKey getStorageKey() {
1501 if (pinMasterKey != null) {
1502 return pinMasterKey.deriveStorageServiceKey();
1503 }
1504 return storageKey;
1505 }
1506
1507 public StorageKey getOrCreateStorageKey() {
1508 if (isPrimaryDevice()) {
1509 return getOrCreatePinMasterKey().deriveStorageServiceKey();
1510 }
1511 return storageKey;
1512 }
1513
1514 public void setStorageKey(final StorageKey storageKey) {
1515 if (storageKey.equals(this.storageKey)) {
1516 return;
1517 }
1518 this.storageKey = storageKey;
1519 save();
1520 }
1521
1522 public long getStorageManifestVersion() {
1523 return this.storageManifestVersion;
1524 }
1525
1526 public void setStorageManifestVersion(final long storageManifestVersion) {
1527 if (storageManifestVersion == this.storageManifestVersion) {
1528 return;
1529 }
1530 this.storageManifestVersion = storageManifestVersion;
1531 save();
1532 }
1533
1534 public Optional<SignalStorageManifest> getStorageManifest() {
1535 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1536 if (!storageManifestFile.exists()) {
1537 return Optional.empty();
1538 }
1539 try (var inputStream = new FileInputStream(storageManifestFile)) {
1540 return Optional.of(SignalStorageManifest.deserialize(inputStream.readAllBytes()));
1541 } catch (IOException e) {
1542 logger.warn("Failed to read local storage manifest.", e);
1543 return Optional.empty();
1544 }
1545 }
1546
1547 public void setStorageManifest(SignalStorageManifest manifest) {
1548 final var storageManifestFile = getStorageManifestFile(dataPath, accountPath);
1549 if (manifest == null) {
1550 if (storageManifestFile.exists()) {
1551 try {
1552 Files.delete(storageManifestFile.toPath());
1553 } catch (IOException e) {
1554 logger.error("Failed to delete local storage manifest.", e);
1555 }
1556 }
1557 return;
1558 }
1559
1560 final var manifestBytes = manifest.serialize();
1561 try (var outputStream = new FileOutputStream(storageManifestFile)) {
1562 outputStream.write(manifestBytes);
1563 } catch (IOException e) {
1564 logger.error("Failed to store local storage manifest.", e);
1565 }
1566 }
1567
1568 public ProfileKey getProfileKey() {
1569 return profileKey;
1570 }
1571
1572 public void setProfileKey(final ProfileKey profileKey) {
1573 if (profileKey.equals(this.profileKey)) {
1574 return;
1575 }
1576 this.profileKey = profileKey;
1577 save();
1578 getProfileStore().storeSelfProfileKey(getSelfRecipientId(), getProfileKey());
1579 }
1580
1581 public byte[] getSelfUnidentifiedAccessKey() {
1582 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1583 }
1584
1585 public int getPreKeyIdOffset(ServiceIdType serviceIdType) {
1586 return serviceIdType.equals(ServiceIdType.ACI) ? aciPreKeyIdOffset : pniPreKeyIdOffset;
1587 }
1588
1589 public int getNextSignedPreKeyId(ServiceIdType serviceIdType) {
1590 return serviceIdType.equals(ServiceIdType.ACI) ? aciNextSignedPreKeyId : pniNextSignedPreKeyId;
1591 }
1592
1593 public boolean isRegistered() {
1594 return registered;
1595 }
1596
1597 public void setRegistered(final boolean registered) {
1598 this.registered = registered;
1599 save();
1600 }
1601
1602 public boolean isMultiDevice() {
1603 return isMultiDevice;
1604 }
1605
1606 public void setMultiDevice(final boolean multiDevice) {
1607 if (isMultiDevice == multiDevice) {
1608 return;
1609 }
1610 isMultiDevice = multiDevice;
1611 save();
1612 }
1613
1614 public long getLastReceiveTimestamp() {
1615 return lastReceiveTimestamp;
1616 }
1617
1618 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1619 this.lastReceiveTimestamp = lastReceiveTimestamp;
1620 save();
1621 }
1622
1623 public boolean isUnrestrictedUnidentifiedAccess() {
1624 final var profile = getProfileStore().getProfile(getSelfRecipientId());
1625 return profile != null && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED;
1626 }
1627
1628 public boolean isDiscoverableByPhoneNumber() {
1629 final var phoneNumberUnlisted = configurationStore.getPhoneNumberUnlisted();
1630 return phoneNumberUnlisted == null || !phoneNumberUnlisted;
1631 }
1632
1633 public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
1634 this.pinMasterKey = masterKey;
1635 this.storageManifestVersion = -1;
1636 this.setStorageManifest(null);
1637 this.storageKey = null;
1638 this.encryptedDeviceName = null;
1639 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1640 this.isMultiDevice = false;
1641 this.registered = true;
1642 this.aci = aci;
1643 this.pni = pni;
1644 this.registrationLockPin = pin;
1645 this.lastReceiveTimestamp = 0;
1646 save();
1647
1648 clearAllPreKeys();
1649 getAciSessionStore().archiveAllSessions();
1650 getPniSessionStore().archiveAllSessions();
1651 getSenderKeyStore().deleteAll();
1652 getRecipientTrustedResolver().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1653 final var aciPublicKey = getAciIdentityKeyPair().getPublicKey();
1654 getIdentityKeyStore().saveIdentity(getAci(), aciPublicKey);
1655 getIdentityKeyStore().setIdentityTrustLevel(getAci(), aciPublicKey, TrustLevel.TRUSTED_VERIFIED);
1656 if (getPniIdentityKeyPair() == null) {
1657 setPniIdentityKeyPair(KeyUtils.generateIdentityKeyPair());
1658 } else {
1659 final var pniPublicKey = getPniIdentityKeyPair().getPublicKey();
1660 getIdentityKeyStore().saveIdentity(getPni(), pniPublicKey);
1661 getIdentityKeyStore().setIdentityTrustLevel(getPni(), pniPublicKey, TrustLevel.TRUSTED_VERIFIED);
1662 }
1663 }
1664
1665 public void deleteAccountData() throws IOException {
1666 close();
1667 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1668 .sorted(Comparator.reverseOrder())) {
1669 for (final var file = files.iterator(); file.hasNext(); ) {
1670 Files.delete(file.next());
1671 }
1672 }
1673 Files.delete(getFileName(dataPath, accountPath).toPath());
1674 }
1675
1676 @Override
1677 public void close() {
1678 synchronized (fileChannel) {
1679 if (accountDatabase != null) {
1680 try {
1681 accountDatabase.close();
1682 } catch (SQLException e) {
1683 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1684 }
1685 }
1686 if (messageSendLogStore != null) {
1687 messageSendLogStore.close();
1688 }
1689 try {
1690 try {
1691 lock.close();
1692 } catch (ClosedChannelException ignored) {
1693 }
1694 fileChannel.close();
1695 } catch (IOException e) {
1696 logger.warn("Failed to close account: {}", e.getMessage(), e);
1697 }
1698 }
1699 }
1700
1701 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1702 var value = supplier.get();
1703 if (value != null) {
1704 return value;
1705 }
1706
1707 synchronized (LOCK) {
1708 value = supplier.get();
1709 if (value != null) {
1710 return value;
1711 }
1712 creator.call();
1713 return supplier.get();
1714 }
1715 }
1716
1717 private interface Callable {
1718
1719 void call();
1720 }
1721 }