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