]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/SignalAccount.java
6b2d063b78530d1411c9f864504fe7dba5903f8b
[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 false));
720
721 // Store profile keys only in profile store
722 var profileKeyString = contact.profileKey;
723 if (profileKeyString != null) {
724 final ProfileKey profileKey;
725 try {
726 profileKey = new ProfileKey(Base64.getDecoder().decode(profileKeyString));
727 getProfileStore().storeProfileKey(recipientId, profileKey);
728 } catch (InvalidInputException e) {
729 logger.warn("Failed to parse legacy contact profile key: {}", e.getMessage());
730 }
731 }
732 }
733 migrated = true;
734 }
735
736 if (rootNode.hasNonNull("profileStore")) {
737 logger.debug("Migrating legacy profile store.");
738 var profileStoreNode = rootNode.get("profileStore");
739 final var legacyProfileStore = jsonProcessor.convertValue(profileStoreNode, LegacyProfileStore.class);
740 for (var profileEntry : legacyProfileStore.getProfileEntries()) {
741 var recipientId = getRecipientStore().resolveRecipient(profileEntry.getAddress());
742 getRecipientStore().storeProfileKeyCredential(recipientId, profileEntry.getProfileKeyCredential());
743 getRecipientStore().storeProfileKey(recipientId, profileEntry.getProfileKey());
744 final var profile = profileEntry.getProfile();
745 if (profile != null) {
746 final var capabilities = new HashSet<Profile.Capability>();
747 if (profile.getCapabilities() != null) {
748 if (profile.getCapabilities().gv1Migration) {
749 capabilities.add(Profile.Capability.gv1Migration);
750 }
751 if (profile.getCapabilities().storage) {
752 capabilities.add(Profile.Capability.storage);
753 }
754 }
755 final var newProfile = new Profile(profileEntry.getLastUpdateTimestamp(),
756 profile.getGivenName(),
757 profile.getFamilyName(),
758 profile.getAbout(),
759 profile.getAboutEmoji(),
760 null,
761 null,
762 profile.isUnrestrictedUnidentifiedAccess()
763 ? Profile.UnidentifiedAccessMode.UNRESTRICTED
764 : profile.getUnidentifiedAccess() != null
765 ? Profile.UnidentifiedAccessMode.ENABLED
766 : Profile.UnidentifiedAccessMode.DISABLED,
767 capabilities);
768 getRecipientStore().storeProfile(recipientId, newProfile);
769 }
770 }
771 }
772
773 return migrated;
774 }
775
776 private boolean loadLegacyThreadStore(final JsonNode rootNode) {
777 var threadStoreNode = rootNode.get("threadStore");
778 if (threadStoreNode != null && !threadStoreNode.isNull()) {
779 var threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
780 // Migrate thread info to group and contact store
781 for (var thread : threadStore.getThreads()) {
782 if (thread.id == null || thread.id.isEmpty()) {
783 continue;
784 }
785 try {
786 if (UuidUtil.isUuid(thread.id) || thread.id.startsWith("+")) {
787 final var recipientId = getRecipientStore().resolveRecipient(thread.id);
788 var contact = getRecipientStore().getContact(recipientId);
789 if (contact != null) {
790 getRecipientStore().storeContact(recipientId,
791 Contact.newBuilder(contact)
792 .withMessageExpirationTime(thread.messageExpirationTime)
793 .build());
794 }
795 } else {
796 var groupInfo = groupStore.getGroup(GroupId.fromBase64(thread.id));
797 if (groupInfo instanceof GroupInfoV1) {
798 ((GroupInfoV1) groupInfo).messageExpirationTime = thread.messageExpirationTime;
799 groupStore.updateGroup(groupInfo);
800 }
801 }
802 } catch (Exception e) {
803 logger.warn("Failed to read legacy thread info: {}", e.getMessage());
804 }
805 }
806 return true;
807 }
808
809 return false;
810 }
811
812 private void saveStickerStore(StickerStore.Storage storage) {
813 this.stickerStoreStorage = storage;
814 save();
815 }
816
817 private void saveGroupStore(GroupStore.Storage storage) {
818 this.groupStoreStorage = storage;
819 save();
820 }
821
822 private void saveConfigurationStore(ConfigurationStore.Storage storage) {
823 this.configurationStoreStorage = storage;
824 save();
825 }
826
827 private void save() {
828 synchronized (fileChannel) {
829 var rootNode = jsonProcessor.createObjectNode();
830 rootNode.put("version", CURRENT_STORAGE_VERSION)
831 .put("username", number)
832 .put("serviceEnvironment", serviceEnvironment == null ? null : serviceEnvironment.name())
833 .put("uuid", aci == null ? null : aci.toString())
834 .put("pni", pni == null ? null : pni.toString())
835 .put("deviceName", encryptedDeviceName)
836 .put("deviceId", deviceId)
837 .put("isMultiDevice", isMultiDevice)
838 .put("lastReceiveTimestamp", lastReceiveTimestamp)
839 .put("password", password)
840 .put("registrationId", localRegistrationId)
841 .put("identityPrivateKey",
842 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPrivateKey().serialize()))
843 .put("identityKey",
844 Base64.getEncoder().encodeToString(aciIdentityKeyPair.getPublicKey().serialize()))
845 .put("pniIdentityPrivateKey",
846 pniIdentityKeyPair == null
847 ? null
848 : Base64.getEncoder()
849 .encodeToString(pniIdentityKeyPair.getPrivateKey().serialize()))
850 .put("pniIdentityKey",
851 pniIdentityKeyPair == null
852 ? null
853 : Base64.getEncoder().encodeToString(pniIdentityKeyPair.getPublicKey().serialize()))
854 .put("registrationLockPin", registrationLockPin)
855 .put("pinMasterKey",
856 pinMasterKey == null ? null : Base64.getEncoder().encodeToString(pinMasterKey.serialize()))
857 .put("storageKey",
858 storageKey == null ? null : Base64.getEncoder().encodeToString(storageKey.serialize()))
859 .put("storageManifestVersion", storageManifestVersion == -1 ? null : storageManifestVersion)
860 .put("preKeyIdOffset", aciPreKeyIdOffset)
861 .put("nextSignedPreKeyId", aciNextSignedPreKeyId)
862 .put("pniPreKeyIdOffset", pniPreKeyIdOffset)
863 .put("pniNextSignedPreKeyId", pniNextSignedPreKeyId)
864 .put("profileKey",
865 profileKey == null ? null : Base64.getEncoder().encodeToString(profileKey.serialize()))
866 .put("registered", registered)
867 .putPOJO("groupStore", groupStoreStorage)
868 .putPOJO("stickerStore", stickerStoreStorage)
869 .putPOJO("configurationStore", configurationStoreStorage);
870 try {
871 try (var output = new ByteArrayOutputStream()) {
872 // Write to memory first to prevent corrupting the file in case of serialization errors
873 jsonProcessor.writeValue(output, rootNode);
874 var input = new ByteArrayInputStream(output.toByteArray());
875 fileChannel.position(0);
876 input.transferTo(Channels.newOutputStream(fileChannel));
877 fileChannel.truncate(fileChannel.position());
878 fileChannel.force(false);
879 }
880 } catch (Exception e) {
881 logger.error("Error saving file: {}", e.getMessage(), e);
882 }
883 }
884 }
885
886 private static Pair<FileChannel, FileLock> openFileChannel(File fileName, boolean waitForLock) throws IOException {
887 var fileChannel = new RandomAccessFile(fileName, "rw").getChannel();
888 try {
889 var lock = fileChannel.tryLock();
890 if (lock == null) {
891 if (!waitForLock) {
892 logger.debug("Config file is in use by another instance.");
893 throw new IOException("Config file is in use by another instance.");
894 }
895 logger.info("Config file is in use by another instance, waiting…");
896 lock = fileChannel.lock();
897 logger.info("Config file lock acquired.");
898 }
899 final var result = new Pair<>(fileChannel, lock);
900 fileChannel = null;
901 return result;
902 } finally {
903 if (fileChannel != null) {
904 fileChannel.close();
905 }
906 }
907 }
908
909 public void addPreKeys(ServiceIdType serviceIdType, List<PreKeyRecord> records) {
910 if (serviceIdType.equals(ServiceIdType.ACI)) {
911 addAciPreKeys(records);
912 } else {
913 addPniPreKeys(records);
914 }
915 }
916
917 public void addAciPreKeys(List<PreKeyRecord> records) {
918 for (var record : records) {
919 if (aciPreKeyIdOffset != record.getId()) {
920 logger.error("Invalid pre key id {}, expected {}", record.getId(), aciPreKeyIdOffset);
921 throw new AssertionError("Invalid pre key id");
922 }
923 getAciPreKeyStore().storePreKey(record.getId(), record);
924 aciPreKeyIdOffset = (aciPreKeyIdOffset + 1) % Medium.MAX_VALUE;
925 }
926 save();
927 }
928
929 public void addPniPreKeys(List<PreKeyRecord> records) {
930 for (var record : records) {
931 if (pniPreKeyIdOffset != record.getId()) {
932 logger.error("Invalid pre key id {}, expected {}", record.getId(), pniPreKeyIdOffset);
933 throw new AssertionError("Invalid pre key id");
934 }
935 getPniPreKeyStore().storePreKey(record.getId(), record);
936 pniPreKeyIdOffset = (pniPreKeyIdOffset + 1) % Medium.MAX_VALUE;
937 }
938 save();
939 }
940
941 public void addSignedPreKey(ServiceIdType serviceIdType, SignedPreKeyRecord record) {
942 if (serviceIdType.equals(ServiceIdType.ACI)) {
943 addAciSignedPreKey(record);
944 } else {
945 addPniSignedPreKey(record);
946 }
947 }
948
949 public void addAciSignedPreKey(SignedPreKeyRecord record) {
950 if (aciNextSignedPreKeyId != record.getId()) {
951 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), aciNextSignedPreKeyId);
952 throw new AssertionError("Invalid signed pre key id");
953 }
954 getAciSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
955 aciNextSignedPreKeyId = (aciNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
956 save();
957 }
958
959 public void addPniSignedPreKey(SignedPreKeyRecord record) {
960 if (pniNextSignedPreKeyId != record.getId()) {
961 logger.error("Invalid signed pre key id {}, expected {}", record.getId(), pniNextSignedPreKeyId);
962 throw new AssertionError("Invalid signed pre key id");
963 }
964 getPniSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
965 pniNextSignedPreKeyId = (pniNextSignedPreKeyId + 1) % Medium.MAX_VALUE;
966 save();
967 }
968
969 public SignalServiceDataStore getSignalServiceDataStore() {
970 return new SignalServiceDataStore() {
971 @Override
972 public SignalServiceAccountDataStore get(final ServiceId accountIdentifier) {
973 if (accountIdentifier.equals(aci)) {
974 return getSignalServiceAccountDataStore();
975 } else if (accountIdentifier.equals(pni)) {
976 throw new AssertionError("PNI not to be used yet!");
977 } else {
978 throw new IllegalArgumentException("No matching store found for " + accountIdentifier);
979 }
980 }
981
982 @Override
983 public SignalServiceAccountDataStore aci() {
984 return getSignalServiceAccountDataStore();
985 }
986
987 @Override
988 public SignalServiceAccountDataStore pni() {
989 throw new AssertionError("PNI not to be used yet!");
990 }
991
992 @Override
993 public boolean isMultiDevice() {
994 return SignalAccount.this.isMultiDevice();
995 }
996 };
997 }
998
999 public SignalServiceAccountDataStore getSignalServiceAccountDataStore() {
1000 return getOrCreate(() -> signalProtocolStore,
1001 () -> signalProtocolStore = new SignalProtocolStore(getAciPreKeyStore(),
1002 getAciSignedPreKeyStore(),
1003 getSessionStore(),
1004 getIdentityKeyStore(),
1005 getSenderKeyStore(),
1006 this::isMultiDevice));
1007 }
1008
1009 private PreKeyStore getAciPreKeyStore() {
1010 return getOrCreate(() -> aciPreKeyStore,
1011 () -> aciPreKeyStore = new PreKeyStore(getAciPreKeysPath(dataPath, accountPath)));
1012 }
1013
1014 private SignedPreKeyStore getAciSignedPreKeyStore() {
1015 return getOrCreate(() -> aciSignedPreKeyStore,
1016 () -> aciSignedPreKeyStore = new SignedPreKeyStore(getAciSignedPreKeysPath(dataPath, accountPath)));
1017 }
1018
1019 private PreKeyStore getPniPreKeyStore() {
1020 return getOrCreate(() -> pniPreKeyStore,
1021 () -> pniPreKeyStore = new PreKeyStore(getPniPreKeysPath(dataPath, accountPath)));
1022 }
1023
1024 private SignedPreKeyStore getPniSignedPreKeyStore() {
1025 return getOrCreate(() -> pniSignedPreKeyStore,
1026 () -> pniSignedPreKeyStore = new SignedPreKeyStore(getPniSignedPreKeysPath(dataPath, accountPath)));
1027 }
1028
1029 public SessionStore getSessionStore() {
1030 return getOrCreate(() -> sessionStore,
1031 () -> sessionStore = new SessionStore(getSessionsPath(dataPath, accountPath), getRecipientStore()));
1032 }
1033
1034 public IdentityKeyStore getIdentityKeyStore() {
1035 return getOrCreate(() -> identityKeyStore,
1036 () -> identityKeyStore = new IdentityKeyStore(getIdentitiesPath(dataPath, accountPath),
1037 getRecipientStore(),
1038 aciIdentityKeyPair,
1039 localRegistrationId,
1040 trustNewIdentity));
1041 }
1042
1043 public GroupStore getGroupStore() {
1044 return groupStore;
1045 }
1046
1047 public ContactsStore getContactStore() {
1048 return getRecipientStore();
1049 }
1050
1051 public RecipientStore getRecipientStore() {
1052 return getOrCreate(() -> recipientStore,
1053 () -> recipientStore = RecipientStore.load(getRecipientsStoreFile(dataPath, accountPath),
1054 this::mergeRecipients,
1055 this::getSelfRecipientAddress));
1056 }
1057
1058 public ProfileStore getProfileStore() {
1059 return getRecipientStore();
1060 }
1061
1062 public StickerStore getStickerStore() {
1063 return stickerStore;
1064 }
1065
1066 public SenderKeyStore getSenderKeyStore() {
1067 return getOrCreate(() -> senderKeyStore,
1068 () -> senderKeyStore = new SenderKeyStore(getSharedSenderKeysFile(dataPath, accountPath),
1069 getSenderKeysPath(dataPath, accountPath),
1070 getRecipientStore()::resolveRecipientAddress,
1071 getRecipientStore()));
1072 }
1073
1074 public ConfigurationStore getConfigurationStore() {
1075 return configurationStore;
1076 }
1077
1078 public MessageCache getMessageCache() {
1079 return getOrCreate(() -> messageCache,
1080 () -> messageCache = new MessageCache(getMessageCachePath(dataPath, accountPath)));
1081 }
1082
1083 public AccountDatabase getAccountDatabase() {
1084 return getOrCreate(() -> accountDatabase, () -> {
1085 try {
1086 accountDatabase = AccountDatabase.init(getDatabaseFile(dataPath, accountPath));
1087 } catch (SQLException e) {
1088 throw new RuntimeException(e);
1089 }
1090 });
1091 }
1092
1093 public MessageSendLogStore getMessageSendLogStore() {
1094 return getOrCreate(() -> messageSendLogStore,
1095 () -> messageSendLogStore = new MessageSendLogStore(getRecipientStore(), getAccountDatabase()));
1096 }
1097
1098 public CredentialsProvider getCredentialsProvider() {
1099 return new CredentialsProvider() {
1100 @Override
1101 public ACI getAci() {
1102 return aci;
1103 }
1104
1105 @Override
1106 public PNI getPni() {
1107 return pni;
1108 }
1109
1110 @Override
1111 public String getE164() {
1112 return number;
1113 }
1114
1115 @Override
1116 public String getPassword() {
1117 return password;
1118 }
1119
1120 @Override
1121 public int getDeviceId() {
1122 return deviceId;
1123 }
1124 };
1125 }
1126
1127 public String getNumber() {
1128 return number;
1129 }
1130
1131 public void setNumber(final String number) {
1132 this.number = number;
1133 save();
1134 }
1135
1136 public ServiceEnvironment getServiceEnvironment() {
1137 return serviceEnvironment;
1138 }
1139
1140 public void setServiceEnvironment(final ServiceEnvironment serviceEnvironment) {
1141 this.serviceEnvironment = serviceEnvironment;
1142 save();
1143 }
1144
1145 public ACI getAci() {
1146 return aci;
1147 }
1148
1149 public void setAci(final ACI aci) {
1150 this.aci = aci;
1151 save();
1152 }
1153
1154 public PNI getPni() {
1155 return pni;
1156 }
1157
1158 public void setPni(final PNI pni) {
1159 this.pni = pni;
1160 save();
1161 }
1162
1163 public SignalServiceAddress getSelfAddress() {
1164 return new SignalServiceAddress(aci, number);
1165 }
1166
1167 public RecipientAddress getSelfRecipientAddress() {
1168 return new RecipientAddress(aci == null ? null : aci.uuid(), number);
1169 }
1170
1171 public RecipientId getSelfRecipientId() {
1172 return getRecipientStore().resolveRecipient(getSelfRecipientAddress());
1173 }
1174
1175 public byte[] getEncryptedDeviceName() {
1176 return encryptedDeviceName == null ? null : Base64.getDecoder().decode(encryptedDeviceName);
1177 }
1178
1179 public void setEncryptedDeviceName(final String encryptedDeviceName) {
1180 this.encryptedDeviceName = encryptedDeviceName;
1181 save();
1182 }
1183
1184 public int getDeviceId() {
1185 return deviceId;
1186 }
1187
1188 public boolean isMasterDevice() {
1189 return deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID;
1190 }
1191
1192 public IdentityKeyPair getIdentityKeyPair(ServiceIdType serviceIdType) {
1193 return serviceIdType.equals(ServiceIdType.ACI) ? aciIdentityKeyPair : pniIdentityKeyPair;
1194 }
1195
1196 public IdentityKeyPair getAciIdentityKeyPair() {
1197 return aciIdentityKeyPair;
1198 }
1199
1200 public IdentityKeyPair getPniIdentityKeyPair() {
1201 return pniIdentityKeyPair;
1202 }
1203
1204 public void setPniIdentityKeyPair(final IdentityKeyPair identityKeyPair) {
1205 pniIdentityKeyPair = identityKeyPair;
1206 save();
1207 }
1208
1209 public int getLocalRegistrationId() {
1210 return localRegistrationId;
1211 }
1212
1213 public String getPassword() {
1214 return password;
1215 }
1216
1217 private void setPassword(final String password) {
1218 this.password = password;
1219 save();
1220 }
1221
1222 public void setRegistrationLockPin(final String registrationLockPin, final MasterKey pinMasterKey) {
1223 this.registrationLockPin = registrationLockPin;
1224 this.pinMasterKey = pinMasterKey;
1225 save();
1226 }
1227
1228 public MasterKey getPinMasterKey() {
1229 return pinMasterKey;
1230 }
1231
1232 public StorageKey getStorageKey() {
1233 if (pinMasterKey != null) {
1234 return pinMasterKey.deriveStorageServiceKey();
1235 }
1236 return storageKey;
1237 }
1238
1239 public void setStorageKey(final StorageKey storageKey) {
1240 if (storageKey.equals(this.storageKey)) {
1241 return;
1242 }
1243 this.storageKey = storageKey;
1244 save();
1245 }
1246
1247 public long getStorageManifestVersion() {
1248 return this.storageManifestVersion;
1249 }
1250
1251 public void setStorageManifestVersion(final long storageManifestVersion) {
1252 if (storageManifestVersion == this.storageManifestVersion) {
1253 return;
1254 }
1255 this.storageManifestVersion = storageManifestVersion;
1256 save();
1257 }
1258
1259 public ProfileKey getProfileKey() {
1260 return profileKey;
1261 }
1262
1263 public void setProfileKey(final ProfileKey profileKey) {
1264 if (profileKey.equals(this.profileKey)) {
1265 return;
1266 }
1267 this.profileKey = profileKey;
1268 save();
1269 }
1270
1271 public byte[] getSelfUnidentifiedAccessKey() {
1272 return UnidentifiedAccess.deriveAccessKeyFrom(getProfileKey());
1273 }
1274
1275 public int getPreKeyIdOffset(ServiceIdType serviceIdType) {
1276 return serviceIdType.equals(ServiceIdType.ACI) ? aciPreKeyIdOffset : pniPreKeyIdOffset;
1277 }
1278
1279 public int getNextSignedPreKeyId(ServiceIdType serviceIdType) {
1280 return serviceIdType.equals(ServiceIdType.ACI) ? aciNextSignedPreKeyId : pniNextSignedPreKeyId;
1281 }
1282
1283 public boolean isRegistered() {
1284 return registered;
1285 }
1286
1287 public void setRegistered(final boolean registered) {
1288 this.registered = registered;
1289 save();
1290 }
1291
1292 public boolean isMultiDevice() {
1293 return isMultiDevice;
1294 }
1295
1296 public void setMultiDevice(final boolean multiDevice) {
1297 if (isMultiDevice == multiDevice) {
1298 return;
1299 }
1300 isMultiDevice = multiDevice;
1301 save();
1302 }
1303
1304 public long getLastReceiveTimestamp() {
1305 return lastReceiveTimestamp;
1306 }
1307
1308 public void setLastReceiveTimestamp(final long lastReceiveTimestamp) {
1309 this.lastReceiveTimestamp = lastReceiveTimestamp;
1310 save();
1311 }
1312
1313 public boolean isUnrestrictedUnidentifiedAccess() {
1314 final var profile = getProfileStore().getProfile(getSelfRecipientId());
1315 return profile != null && profile.getUnidentifiedAccessMode() == Profile.UnidentifiedAccessMode.UNRESTRICTED;
1316 }
1317
1318 public boolean isDiscoverableByPhoneNumber() {
1319 return configurationStore.getPhoneNumberUnlisted() == null || !configurationStore.getPhoneNumberUnlisted();
1320 }
1321
1322 public void finishRegistration(final ACI aci, final PNI pni, final MasterKey masterKey, final String pin) {
1323 this.pinMasterKey = masterKey;
1324 this.storageManifestVersion = -1;
1325 this.storageKey = null;
1326 this.encryptedDeviceName = null;
1327 this.deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
1328 this.isMultiDevice = false;
1329 this.registered = true;
1330 this.aci = aci;
1331 this.pni = pni;
1332 this.registrationLockPin = pin;
1333 this.lastReceiveTimestamp = 0;
1334 save();
1335
1336 clearAllPreKeys();
1337 getSessionStore().archiveAllSessions();
1338 getSenderKeyStore().deleteAll();
1339 final var recipientId = getRecipientStore().resolveSelfRecipientTrusted(getSelfRecipientAddress());
1340 final var publicKey = getAciIdentityKeyPair().getPublicKey();
1341 getIdentityKeyStore().saveIdentity(recipientId, publicKey, new Date());
1342 getIdentityKeyStore().setIdentityTrustLevel(recipientId, publicKey, TrustLevel.TRUSTED_VERIFIED);
1343 }
1344
1345 public void deleteAccountData() throws IOException {
1346 close();
1347 try (final var files = Files.walk(getUserPath(dataPath, accountPath).toPath())
1348 .sorted(Comparator.reverseOrder())) {
1349 for (final var file = files.iterator(); file.hasNext(); ) {
1350 Files.delete(file.next());
1351 }
1352 }
1353 Files.delete(getFileName(dataPath, accountPath).toPath());
1354 }
1355
1356 @Override
1357 public void close() {
1358 synchronized (fileChannel) {
1359 if (accountDatabase != null) {
1360 try {
1361 accountDatabase.close();
1362 } catch (SQLException e) {
1363 logger.warn("Failed to close account database: {}", e.getMessage(), e);
1364 }
1365 }
1366 if (messageSendLogStore != null) {
1367 messageSendLogStore.close();
1368 }
1369 try {
1370 try {
1371 lock.close();
1372 } catch (ClosedChannelException ignored) {
1373 }
1374 fileChannel.close();
1375 } catch (IOException e) {
1376 logger.warn("Failed to close account: {}", e.getMessage(), e);
1377 }
1378 }
1379 }
1380
1381 private <T> T getOrCreate(Supplier<T> supplier, Callable creator) {
1382 var value = supplier.get();
1383 if (value != null) {
1384 return value;
1385 }
1386
1387 synchronized (LOCK) {
1388 value = supplier.get();
1389 if (value != null) {
1390 return value;
1391 }
1392 creator.call();
1393 return supplier.get();
1394 }
1395 }
1396
1397 private interface Callable {
1398
1399 void call();
1400 }
1401 }