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