1 package org
.asamk
.signal
.manager
.storage
;
3 import com
.fasterxml
.jackson
.databind
.JsonNode
;
4 import com
.fasterxml
.jackson
.databind
.ObjectMapper
;
6 import org
.asamk
.signal
.manager
.TrustLevel
;
7 import org
.asamk
.signal
.manager
.api
.Pair
;
8 import org
.asamk
.signal
.manager
.groups
.GroupId
;
9 import org
.asamk
.signal
.manager
.storage
.configuration
.ConfigurationStore
;
10 import org
.asamk
.signal
.manager
.storage
.contacts
.ContactsStore
;
11 import org
.asamk
.signal
.manager
.storage
.contacts
.LegacyJsonContactsStore
;
12 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV1
;
13 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
14 import org
.asamk
.signal
.manager
.storage
.groups
.GroupStore
;
15 import org
.asamk
.signal
.manager
.storage
.identities
.IdentityKeyStore
;
16 import org
.asamk
.signal
.manager
.storage
.identities
.TrustNewIdentity
;
17 import org
.asamk
.signal
.manager
.storage
.messageCache
.MessageCache
;
18 import org
.asamk
.signal
.manager
.storage
.prekeys
.PreKeyStore
;
19 import org
.asamk
.signal
.manager
.storage
.prekeys
.SignedPreKeyStore
;
20 import org
.asamk
.signal
.manager
.storage
.profiles
.LegacyProfileStore
;
21 import org
.asamk
.signal
.manager
.storage
.profiles
.ProfileStore
;
22 import org
.asamk
.signal
.manager
.storage
.protocol
.LegacyJsonSignalProtocolStore
;
23 import org
.asamk
.signal
.manager
.storage
.protocol
.SignalProtocolStore
;
24 import org
.asamk
.signal
.manager
.storage
.recipients
.Contact
;
25 import org
.asamk
.signal
.manager
.storage
.recipients
.LegacyRecipientStore
;
26 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
27 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientAddress
;
28 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
29 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientStore
;
30 import org
.asamk
.signal
.manager
.storage
.senderKeys
.SenderKeyStore
;
31 import org
.asamk
.signal
.manager
.storage
.sessions
.SessionStore
;
32 import org
.asamk
.signal
.manager
.storage
.stickers
.StickerStore
;
33 import org
.asamk
.signal
.manager
.storage
.threads
.LegacyJsonThreadStore
;
34 import org
.asamk
.signal
.manager
.util
.IOUtils
;
35 import org
.asamk
.signal
.manager
.util
.KeyUtils
;
36 import org
.signal
.zkgroup
.InvalidInputException
;
37 import org
.signal
.zkgroup
.profiles
.ProfileKey
;
38 import org
.slf4j
.Logger
;
39 import org
.slf4j
.LoggerFactory
;
40 import org
.whispersystems
.libsignal
.IdentityKeyPair
;
41 import org
.whispersystems
.libsignal
.SignalProtocolAddress
;
42 import org
.whispersystems
.libsignal
.state
.PreKeyRecord
;
43 import org
.whispersystems
.libsignal
.state
.SessionRecord
;
44 import org
.whispersystems
.libsignal
.state
.SignedPreKeyRecord
;
45 import org
.whispersystems
.libsignal
.util
.Medium
;
46 import org
.whispersystems
.signalservice
.api
.crypto
.UnidentifiedAccess
;
47 import org
.whispersystems
.signalservice
.api
.kbs
.MasterKey
;
48 import org
.whispersystems
.signalservice
.api
.push
.ACI
;
49 import org
.whispersystems
.signalservice
.api
.push
.DistributionId
;
50 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
51 import org
.whispersystems
.signalservice
.api
.storage
.StorageKey
;
52 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
54 import java
.io
.ByteArrayInputStream
;
55 import java
.io
.ByteArrayOutputStream
;
56 import java
.io
.Closeable
;
58 import java
.io
.IOException
;
59 import java
.io
.RandomAccessFile
;
60 import java
.nio
.channels
.Channels
;
61 import java
.nio
.channels
.ClosedChannelException
;
62 import java
.nio
.channels
.FileChannel
;
63 import java
.nio
.channels
.FileLock
;
64 import java
.security
.SecureRandom
;
65 import java
.util
.Base64
;
66 import java
.util
.Date
;
67 import java
.util
.HashSet
;
68 import java
.util
.List
;
69 import java
.util
.function
.Supplier
;
71 public class SignalAccount
implements Closeable
{
73 private final static Logger logger
= LoggerFactory
.getLogger(SignalAccount
.class);
75 private static final int MINIMUM_STORAGE_VERSION
= 1;
76 private static final int CURRENT_STORAGE_VERSION
= 3;
78 private final Object LOCK
= new Object();
80 private final ObjectMapper jsonProcessor
= Utils
.createStorageObjectMapper();
82 private final FileChannel fileChannel
;
83 private final FileLock lock
;
85 private int previousStorageVersion
;
87 private File dataPath
;
88 private String account
;
90 private String encryptedDeviceName
;
91 private int deviceId
= SignalServiceAddress
.DEFAULT_DEVICE_ID
;
92 private boolean isMultiDevice
= false;
93 private String password
;
94 private String registrationLockPin
;
95 private MasterKey pinMasterKey
;
96 private StorageKey storageKey
;
97 private long storageManifestVersion
= -1;
98 private ProfileKey profileKey
;
99 private int preKeyIdOffset
;
100 private int nextSignedPreKeyId
;
101 private IdentityKeyPair identityKeyPair
;
102 private int localRegistrationId
;
103 private TrustNewIdentity trustNewIdentity
;
104 private long lastReceiveTimestamp
= 0;
106 private boolean registered
= false;
108 private SignalProtocolStore signalProtocolStore
;
109 private PreKeyStore preKeyStore
;
110 private SignedPreKeyStore signedPreKeyStore
;
111 private SessionStore sessionStore
;
112 private IdentityKeyStore identityKeyStore
;
113 private SenderKeyStore senderKeyStore
;
114 private GroupStore groupStore
;
115 private GroupStore
.Storage groupStoreStorage
;
116 private RecipientStore recipientStore
;
117 private StickerStore stickerStore
;
118 private StickerStore
.Storage stickerStoreStorage
;
119 private ConfigurationStore configurationStore
;
120 private ConfigurationStore
.Storage configurationStoreStorage
;
122 private MessageCache messageCache
;
124 private SignalAccount(final FileChannel fileChannel
, final FileLock lock
) {
125 this.fileChannel
= fileChannel
;
129 public static SignalAccount
load(
130 File dataPath
, String account
, boolean waitForLock
, final TrustNewIdentity trustNewIdentity
131 ) throws IOException
{
132 logger
.trace("Opening account file");
133 final var fileName
= getFileName(dataPath
, account
);
134 final var pair
= openFileChannel(fileName
, waitForLock
);
136 var signalAccount
= new SignalAccount(pair
.first(), pair
.second());
137 logger
.trace("Loading account file");
138 signalAccount
.load(dataPath
, trustNewIdentity
);
139 logger
.trace("Migrating legacy parts of account file");
140 signalAccount
.migrateLegacyConfigs();
142 if (!account
.equals(signalAccount
.getAccount())) {
143 throw new IOException("Number in account file doesn't match expected number: "
144 + signalAccount
.getAccount());
147 return signalAccount
;
148 } catch (Throwable e
) {
149 pair
.second().close();
150 pair
.first().close();
155 public static SignalAccount
create(
158 IdentityKeyPair identityKey
,
160 ProfileKey profileKey
,
161 final TrustNewIdentity trustNewIdentity
162 ) throws IOException
{
163 IOUtils
.createPrivateDirectories(dataPath
);
164 var fileName
= getFileName(dataPath
, account
);
165 if (!fileName
.exists()) {
166 IOUtils
.createPrivateFile(fileName
);
169 final var pair
= openFileChannel(fileName
, true);
170 var signalAccount
= new SignalAccount(pair
.first(), pair
.second());
172 signalAccount
.account
= account
;
173 signalAccount
.profileKey
= profileKey
;
175 signalAccount
.dataPath
= dataPath
;
176 signalAccount
.identityKeyPair
= identityKey
;
177 signalAccount
.localRegistrationId
= registrationId
;
178 signalAccount
.trustNewIdentity
= trustNewIdentity
;
179 signalAccount
.groupStore
= new GroupStore(getGroupCachePath(dataPath
, account
),
180 signalAccount
.getRecipientStore(),
181 signalAccount
::saveGroupStore
);
182 signalAccount
.stickerStore
= new StickerStore(signalAccount
::saveStickerStore
);
183 signalAccount
.configurationStore
= new ConfigurationStore(signalAccount
::saveConfigurationStore
);
185 signalAccount
.registered
= false;
187 signalAccount
.previousStorageVersion
= CURRENT_STORAGE_VERSION
;
188 signalAccount
.migrateLegacyConfigs();
189 signalAccount
.save();
191 return signalAccount
;
194 public static SignalAccount
createOrUpdateLinkedAccount(
199 String encryptedDeviceName
,
201 IdentityKeyPair identityKey
,
203 ProfileKey profileKey
,
204 final TrustNewIdentity trustNewIdentity
205 ) throws IOException
{
206 IOUtils
.createPrivateDirectories(dataPath
);
207 var fileName
= getFileName(dataPath
, account
);
208 if (!fileName
.exists()) {
209 return createLinkedAccount(dataPath
,
221 final var signalAccount
= load(dataPath
, account
, true, trustNewIdentity
);
222 signalAccount
.setProvisioningData(account
, aci
, password
, encryptedDeviceName
, deviceId
, profileKey
);
223 signalAccount
.getRecipientStore().resolveRecipientTrusted(signalAccount
.getSelfAddress());
224 signalAccount
.getSessionStore().archiveAllSessions();
225 signalAccount
.getSenderKeyStore().deleteAll();
226 signalAccount
.clearAllPreKeys();
227 return signalAccount
;
230 private void clearAllPreKeys() {
231 this.preKeyIdOffset
= new SecureRandom().nextInt(Medium
.MAX_VALUE
);
232 this.nextSignedPreKeyId
= new SecureRandom().nextInt(Medium
.MAX_VALUE
);
233 this.getPreKeyStore().removeAllPreKeys();
234 this.getSignedPreKeyStore().removeAllSignedPreKeys();
238 private static SignalAccount
createLinkedAccount(
243 String encryptedDeviceName
,
245 IdentityKeyPair identityKey
,
247 ProfileKey profileKey
,
248 final TrustNewIdentity trustNewIdentity
249 ) throws IOException
{
250 var fileName
= getFileName(dataPath
, account
);
251 IOUtils
.createPrivateFile(fileName
);
253 final var pair
= openFileChannel(fileName
, true);
254 var signalAccount
= new SignalAccount(pair
.first(), pair
.second());
256 signalAccount
.setProvisioningData(account
, aci
, password
, encryptedDeviceName
, deviceId
, profileKey
);
258 signalAccount
.dataPath
= dataPath
;
259 signalAccount
.identityKeyPair
= identityKey
;
260 signalAccount
.localRegistrationId
= registrationId
;
261 signalAccount
.trustNewIdentity
= trustNewIdentity
;
262 signalAccount
.groupStore
= new GroupStore(getGroupCachePath(dataPath
, account
),
263 signalAccount
.getRecipientStore(),
264 signalAccount
::saveGroupStore
);
265 signalAccount
.stickerStore
= new StickerStore(signalAccount
::saveStickerStore
);
266 signalAccount
.configurationStore
= new ConfigurationStore(signalAccount
::saveConfigurationStore
);
268 signalAccount
.getRecipientStore().resolveRecipientTrusted(signalAccount
.getSelfAddress());
269 signalAccount
.previousStorageVersion
= CURRENT_STORAGE_VERSION
;
270 signalAccount
.migrateLegacyConfigs();
271 signalAccount
.save();
273 return signalAccount
;
276 private void setProvisioningData(
277 final String account
,
279 final String password
,
280 final String encryptedDeviceName
,
282 final ProfileKey profileKey
284 this.account
= account
;
286 this.password
= password
;
287 this.profileKey
= profileKey
;
288 this.encryptedDeviceName
= encryptedDeviceName
;
289 this.deviceId
= deviceId
;
290 this.registered
= true;
291 this.isMultiDevice
= true;
292 this.lastReceiveTimestamp
= 0;
293 this.pinMasterKey
= null;
294 this.storageManifestVersion
= -1;
295 this.storageKey
= null;
298 private void migrateLegacyConfigs() {
299 if (getPassword() == null) {
300 setPassword(KeyUtils
.createPassword());
303 if (getProfileKey() == null) {
304 // Old config file, creating new profile key
305 setProfileKey(KeyUtils
.createProfileKey());
307 // Ensure our profile key is stored in profile store
308 getProfileStore().storeProfileKey(getSelfRecipientId(), getProfileKey());
309 if (previousStorageVersion
< 3) {
310 for (final var group
: groupStore
.getGroups()) {
311 if (group
instanceof GroupInfoV2
&& group
.getDistributionId() == null) {
312 ((GroupInfoV2
) group
).setDistributionId(DistributionId
.create());
313 groupStore
.updateGroup(group
);
320 private void mergeRecipients(RecipientId recipientId
, RecipientId toBeMergedRecipientId
) {
321 getSessionStore().mergeRecipients(recipientId
, toBeMergedRecipientId
);
322 getIdentityKeyStore().mergeRecipients(recipientId
, toBeMergedRecipientId
);
323 getMessageCache().mergeRecipients(recipientId
, toBeMergedRecipientId
);
324 getGroupStore().mergeRecipients(recipientId
, toBeMergedRecipientId
);
325 getSenderKeyStore().mergeRecipients(recipientId
, toBeMergedRecipientId
);
328 public void removeRecipient(final RecipientId recipientId
) {
329 getSessionStore().deleteAllSessions(recipientId
);
330 getIdentityKeyStore().deleteIdentity(recipientId
);
331 getMessageCache().deleteMessages(recipientId
);
332 getSenderKeyStore().deleteAll(recipientId
);
333 getRecipientStore().deleteRecipientData(recipientId
);
336 public static File
getFileName(File dataPath
, String account
) {
337 return new File(dataPath
, account
);
340 private static File
getUserPath(final File dataPath
, final String account
) {
341 final var path
= new File(dataPath
, account
+ ".d");
343 IOUtils
.createPrivateDirectories(path
);
344 } catch (IOException e
) {
345 throw new AssertionError("Failed to create user path", e
);
350 private static File
getMessageCachePath(File dataPath
, String account
) {
351 return new File(getUserPath(dataPath
, account
), "msg-cache");
354 private static File
getGroupCachePath(File dataPath
, String account
) {
355 return new File(getUserPath(dataPath
, account
), "group-cache");
358 private static File
getPreKeysPath(File dataPath
, String account
) {
359 return new File(getUserPath(dataPath
, account
), "pre-keys");
362 private static File
getSignedPreKeysPath(File dataPath
, String account
) {
363 return new File(getUserPath(dataPath
, account
), "signed-pre-keys");
366 private static File
getIdentitiesPath(File dataPath
, String account
) {
367 return new File(getUserPath(dataPath
, account
), "identities");
370 private static File
getSessionsPath(File dataPath
, String account
) {
371 return new File(getUserPath(dataPath
, account
), "sessions");
374 private static File
getSenderKeysPath(File dataPath
, String account
) {
375 return new File(getUserPath(dataPath
, account
), "sender-keys");
378 private static File
getSharedSenderKeysFile(File dataPath
, String account
) {
379 return new File(getUserPath(dataPath
, account
), "shared-sender-keys-store");
382 private static File
getRecipientsStoreFile(File dataPath
, String account
) {
383 return new File(getUserPath(dataPath
, account
), "recipients-store");
386 public static boolean userExists(File dataPath
, String account
) {
387 if (account
== null) {
390 var f
= getFileName(dataPath
, account
);
391 return !(!f
.exists() || f
.isDirectory());
395 File dataPath
, final TrustNewIdentity trustNewIdentity
396 ) throws IOException
{
398 synchronized (fileChannel
) {
399 fileChannel
.position(0);
400 rootNode
= jsonProcessor
.readTree(Channels
.newInputStream(fileChannel
));
403 if (rootNode
.hasNonNull("version")) {
404 var accountVersion
= rootNode
.get("version").asInt(1);
405 if (accountVersion
> CURRENT_STORAGE_VERSION
) {
406 throw new IOException("Config file was created by a more recent version!");
407 } else if (accountVersion
< MINIMUM_STORAGE_VERSION
) {
408 throw new IOException("Config file was created by a no longer supported older version!");
410 previousStorageVersion
= accountVersion
;
413 account
= Utils
.getNotNullNode(rootNode
, "username").asText();
414 if (rootNode
.hasNonNull("password")) {
415 password
= rootNode
.get("password").asText();
417 registered
= Utils
.getNotNullNode(rootNode
, "registered").asBoolean();
418 if (rootNode
.hasNonNull("uuid")) {
420 aci
= ACI
.parseOrThrow(rootNode
.get("uuid").asText());
421 } catch (IllegalArgumentException e
) {
422 throw new IOException("Config file contains an invalid uuid, needs to be a valid UUID", e
);
425 if (rootNode
.hasNonNull("deviceName")) {
426 encryptedDeviceName
= rootNode
.get("deviceName").asText();
428 if (rootNode
.hasNonNull("deviceId")) {
429 deviceId
= rootNode
.get("deviceId").asInt();
431 if (rootNode
.hasNonNull("isMultiDevice")) {
432 isMultiDevice
= rootNode
.get("isMultiDevice").asBoolean();
434 if (rootNode
.hasNonNull("lastReceiveTimestamp")) {
435 lastReceiveTimestamp
= rootNode
.get("lastReceiveTimestamp").asLong();
437 int registrationId
= 0;
438 if (rootNode
.hasNonNull("registrationId")) {
439 registrationId
= rootNode
.get("registrationId").asInt();
441 IdentityKeyPair identityKeyPair
= null;
442 if (rootNode
.hasNonNull("identityPrivateKey") && rootNode
.hasNonNull("identityKey")) {
443 final var publicKeyBytes
= Base64
.getDecoder().decode(rootNode
.get("identityKey").asText());
444 final var privateKeyBytes
= Base64
.getDecoder().decode(rootNode
.get("identityPrivateKey").asText());
445 identityKeyPair
= KeyUtils
.getIdentityKeyPair(publicKeyBytes
, privateKeyBytes
);
448 if (rootNode
.hasNonNull("registrationLockPin")) {
449 registrationLockPin
= rootNode
.get("registrationLockPin").asText();
451 if (rootNode
.hasNonNull("pinMasterKey")) {
452 pinMasterKey
= new MasterKey(Base64
.getDecoder().decode(rootNode
.get("pinMasterKey").asText()));
454 if (rootNode
.hasNonNull("storageKey")) {
455 storageKey
= new StorageKey(Base64
.getDecoder().decode(rootNode
.get("storageKey").asText()));
457 if (rootNode
.hasNonNull("storageManifestVersion")) {
458 storageManifestVersion
= rootNode
.get("storageManifestVersion").asLong();
460 if (rootNode
.hasNonNull("preKeyIdOffset")) {
461 preKeyIdOffset
= rootNode
.get("preKeyIdOffset").asInt(0);
465 if (rootNode
.hasNonNull("nextSignedPreKeyId")) {
466 nextSignedPreKeyId
= rootNode
.get("nextSignedPreKeyId").asInt();
468 nextSignedPreKeyId
= 0;
470 if (rootNode
.hasNonNull("profileKey")) {
472 profileKey
= new ProfileKey(Base64
.getDecoder().decode(rootNode
.get("profileKey").asText()));
473 } catch (InvalidInputException e
) {
474 throw new IOException(
475 "Config file contains an invalid profileKey, needs to be base64 encoded array of 32 bytes",
480 var migratedLegacyConfig
= false;
481 final var legacySignalProtocolStore
= rootNode
.hasNonNull("axolotlStore")
482 ? jsonProcessor
.convertValue(Utils
.getNotNullNode(rootNode
, "axolotlStore"),
483 LegacyJsonSignalProtocolStore
.class)
485 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacyIdentityKeyStore() != null) {
486 identityKeyPair
= legacySignalProtocolStore
.getLegacyIdentityKeyStore().getIdentityKeyPair();
487 registrationId
= legacySignalProtocolStore
.getLegacyIdentityKeyStore().getLocalRegistrationId();
488 migratedLegacyConfig
= true;
491 this.dataPath
= dataPath
;
492 this.identityKeyPair
= identityKeyPair
;
493 this.localRegistrationId
= registrationId
;
494 this.trustNewIdentity
= trustNewIdentity
;
496 migratedLegacyConfig
= loadLegacyStores(rootNode
, legacySignalProtocolStore
) || migratedLegacyConfig
;
498 if (rootNode
.hasNonNull("groupStore")) {
499 groupStoreStorage
= jsonProcessor
.convertValue(rootNode
.get("groupStore"), GroupStore
.Storage
.class);
500 groupStore
= GroupStore
.fromStorage(groupStoreStorage
,
501 getGroupCachePath(dataPath
, account
),
503 this::saveGroupStore
);
505 groupStore
= new GroupStore(getGroupCachePath(dataPath
, account
),
507 this::saveGroupStore
);
510 if (rootNode
.hasNonNull("stickerStore")) {
511 stickerStoreStorage
= jsonProcessor
.convertValue(rootNode
.get("stickerStore"), StickerStore
.Storage
.class);
512 stickerStore
= StickerStore
.fromStorage(stickerStoreStorage
, this::saveStickerStore
);
514 stickerStore
= new StickerStore(this::saveStickerStore
);
517 if (rootNode
.hasNonNull("configurationStore")) {
518 configurationStoreStorage
= jsonProcessor
.convertValue(rootNode
.get("configurationStore"),
519 ConfigurationStore
.Storage
.class);
520 configurationStore
= ConfigurationStore
.fromStorage(configurationStoreStorage
,
521 this::saveConfigurationStore
);
523 configurationStore
= new ConfigurationStore(this::saveConfigurationStore
);
526 migratedLegacyConfig
= loadLegacyThreadStore(rootNode
) || migratedLegacyConfig
;
528 if (migratedLegacyConfig
) {
533 private boolean loadLegacyStores(
534 final JsonNode rootNode
, final LegacyJsonSignalProtocolStore legacySignalProtocolStore
536 var migrated
= false;
537 var legacyRecipientStoreNode
= rootNode
.get("recipientStore");
538 if (legacyRecipientStoreNode
!= null) {
539 logger
.debug("Migrating legacy recipient store.");
540 var legacyRecipientStore
= jsonProcessor
.convertValue(legacyRecipientStoreNode
, LegacyRecipientStore
.class);
541 if (legacyRecipientStore
!= null) {
542 getRecipientStore().resolveRecipientsTrusted(legacyRecipientStore
.getAddresses());
544 getSelfRecipientId();
548 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacyPreKeyStore() != null) {
549 logger
.debug("Migrating legacy pre key store.");
550 for (var entry
: legacySignalProtocolStore
.getLegacyPreKeyStore().getPreKeys().entrySet()) {
552 getPreKeyStore().storePreKey(entry
.getKey(), new PreKeyRecord(entry
.getValue()));
553 } catch (IOException e
) {
554 logger
.warn("Failed to migrate pre key, ignoring", e
);
560 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacySignedPreKeyStore() != null) {
561 logger
.debug("Migrating legacy signed pre key store.");
562 for (var entry
: legacySignalProtocolStore
.getLegacySignedPreKeyStore().getSignedPreKeys().entrySet()) {
564 getSignedPreKeyStore().storeSignedPreKey(entry
.getKey(), new SignedPreKeyRecord(entry
.getValue()));
565 } catch (IOException e
) {
566 logger
.warn("Failed to migrate signed pre key, ignoring", e
);
572 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacySessionStore() != null) {
573 logger
.debug("Migrating legacy session store.");
574 for (var session
: legacySignalProtocolStore
.getLegacySessionStore().getSessions()) {
576 getSessionStore().storeSession(new SignalProtocolAddress(session
.address
.getIdentifier(),
577 session
.deviceId
), new SessionRecord(session
.sessionRecord
));
578 } catch (Exception e
) {
579 logger
.warn("Failed to migrate session, ignoring", e
);
585 if (legacySignalProtocolStore
!= null && legacySignalProtocolStore
.getLegacyIdentityKeyStore() != null) {
586 logger
.debug("Migrating legacy identity session store.");
587 for (var identity
: legacySignalProtocolStore
.getLegacyIdentityKeyStore().getIdentities()) {
588 RecipientId recipientId
= getRecipientStore().resolveRecipientTrusted(identity
.getAddress());
589 getIdentityKeyStore().saveIdentity(recipientId
, identity
.getIdentityKey(), identity
.getDateAdded());
590 getIdentityKeyStore().setIdentityTrustLevel(recipientId
,
591 identity
.getIdentityKey(),
592 identity
.getTrustLevel());
597 if (rootNode
.hasNonNull("contactStore")) {
598 logger
.debug("Migrating legacy contact store.");
599 final var contactStoreNode
= rootNode
.get("contactStore");
600 final var contactStore
= jsonProcessor
.convertValue(contactStoreNode
, LegacyJsonContactsStore
.class);
601 for (var contact
: contactStore
.getContacts()) {
602 final var recipientId
= getRecipientStore().resolveRecipientTrusted(contact
.getAddress());
603 getRecipientStore().storeContact(recipientId
,
604 new Contact(contact
.name
,
606 contact
.messageExpirationTime
,
610 // Store profile keys only in profile store
611 var profileKeyString
= contact
.profileKey
;
612 if (profileKeyString
!= null) {
613 final ProfileKey profileKey
;
615 profileKey
= new ProfileKey(Base64
.getDecoder().decode(profileKeyString
));
616 getProfileStore().storeProfileKey(recipientId
, profileKey
);
617 } catch (InvalidInputException e
) {
618 logger
.warn("Failed to parse legacy contact profile key: {}", e
.getMessage());
625 if (rootNode
.hasNonNull("profileStore")) {
626 logger
.debug("Migrating legacy profile store.");
627 var profileStoreNode
= rootNode
.get("profileStore");
628 final var legacyProfileStore
= jsonProcessor
.convertValue(profileStoreNode
, LegacyProfileStore
.class);
629 for (var profileEntry
: legacyProfileStore
.getProfileEntries()) {
630 var recipientId
= getRecipientStore().resolveRecipient(profileEntry
.getAddress());
631 getRecipientStore().storeProfileKeyCredential(recipientId
, profileEntry
.getProfileKeyCredential());
632 getRecipientStore().storeProfileKey(recipientId
, profileEntry
.getProfileKey());
633 final var profile
= profileEntry
.getProfile();
634 if (profile
!= null) {
635 final var capabilities
= new HashSet
<Profile
.Capability
>();
636 if (profile
.getCapabilities() != null) {
637 if (profile
.getCapabilities().gv1Migration
) {
638 capabilities
.add(Profile
.Capability
.gv1Migration
);
640 if (profile
.getCapabilities().gv2
) {
641 capabilities
.add(Profile
.Capability
.gv2
);
643 if (profile
.getCapabilities().storage
) {
644 capabilities
.add(Profile
.Capability
.storage
);
647 final var newProfile
= new Profile(profileEntry
.getLastUpdateTimestamp(),
648 profile
.getGivenName(),
649 profile
.getFamilyName(),
651 profile
.getAboutEmoji(),
653 profile
.isUnrestrictedUnidentifiedAccess()
654 ? Profile
.UnidentifiedAccessMode
.UNRESTRICTED
655 : profile
.getUnidentifiedAccess() != null
656 ? Profile
.UnidentifiedAccessMode
.ENABLED
657 : Profile
.UnidentifiedAccessMode
.DISABLED
,
659 getRecipientStore().storeProfile(recipientId
, newProfile
);
667 private boolean loadLegacyThreadStore(final JsonNode rootNode
) {
668 var threadStoreNode
= rootNode
.get("threadStore");
669 if (threadStoreNode
!= null && !threadStoreNode
.isNull()) {
670 var threadStore
= jsonProcessor
.convertValue(threadStoreNode
, LegacyJsonThreadStore
.class);
671 // Migrate thread info to group and contact store
672 for (var thread
: threadStore
.getThreads()) {
673 if (thread
.id
== null || thread
.id
.isEmpty()) {
677 if (UuidUtil
.isUuid(thread
.id
) || thread
.id
.startsWith("+")) {
678 final var recipientId
= getRecipientStore().resolveRecipient(thread
.id
);
679 var contact
= getRecipientStore().getContact(recipientId
);
680 if (contact
!= null) {
681 getRecipientStore().storeContact(recipientId
,
682 Contact
.newBuilder(contact
)
683 .withMessageExpirationTime(thread
.messageExpirationTime
)
687 var groupInfo
= groupStore
.getGroup(GroupId
.fromBase64(thread
.id
));
688 if (groupInfo
instanceof GroupInfoV1
) {
689 ((GroupInfoV1
) groupInfo
).messageExpirationTime
= thread
.messageExpirationTime
;
690 groupStore
.updateGroup(groupInfo
);
693 } catch (Exception e
) {
694 logger
.warn("Failed to read legacy thread info: {}", e
.getMessage());
703 private void saveStickerStore(StickerStore
.Storage storage
) {
704 this.stickerStoreStorage
= storage
;
708 private void saveGroupStore(GroupStore
.Storage storage
) {
709 this.groupStoreStorage
= storage
;
713 private void saveConfigurationStore(ConfigurationStore
.Storage storage
) {
714 this.configurationStoreStorage
= storage
;
718 private void save() {
719 synchronized (fileChannel
) {
720 var rootNode
= jsonProcessor
.createObjectNode();
721 rootNode
.put("version", CURRENT_STORAGE_VERSION
)
722 .put("username", account
)
723 .put("uuid", aci
== null ?
null : aci
.toString())
724 .put("deviceName", encryptedDeviceName
)
725 .put("deviceId", deviceId
)
726 .put("isMultiDevice", isMultiDevice
)
727 .put("lastReceiveTimestamp", lastReceiveTimestamp
)
728 .put("password", password
)
729 .put("registrationId", localRegistrationId
)
730 .put("identityPrivateKey",
731 Base64
.getEncoder().encodeToString(identityKeyPair
.getPrivateKey().serialize()))
732 .put("identityKey", Base64
.getEncoder().encodeToString(identityKeyPair
.getPublicKey().serialize()))
733 .put("registrationLockPin", registrationLockPin
)
735 pinMasterKey
== null ?
null : Base64
.getEncoder().encodeToString(pinMasterKey
.serialize()))
737 storageKey
== null ?
null : Base64
.getEncoder().encodeToString(storageKey
.serialize()))
738 .put("storageManifestVersion", storageManifestVersion
== -1 ?
null : storageManifestVersion
)
739 .put("preKeyIdOffset", preKeyIdOffset
)
740 .put("nextSignedPreKeyId", nextSignedPreKeyId
)
742 profileKey
== null ?
null : Base64
.getEncoder().encodeToString(profileKey
.serialize()))
743 .put("registered", registered
)
744 .putPOJO("groupStore", groupStoreStorage
)
745 .putPOJO("stickerStore", stickerStoreStorage
)
746 .putPOJO("configurationStore", configurationStoreStorage
);
748 try (var output
= new ByteArrayOutputStream()) {
749 // Write to memory first to prevent corrupting the file in case of serialization errors
750 jsonProcessor
.writeValue(output
, rootNode
);
751 var input
= new ByteArrayInputStream(output
.toByteArray());
752 fileChannel
.position(0);
753 input
.transferTo(Channels
.newOutputStream(fileChannel
));
754 fileChannel
.truncate(fileChannel
.position());
755 fileChannel
.force(false);
757 } catch (Exception e
) {
758 logger
.error("Error saving file: {}", e
.getMessage());
763 private static Pair
<FileChannel
, FileLock
> openFileChannel(File fileName
, boolean waitForLock
) throws IOException
{
764 var fileChannel
= new RandomAccessFile(fileName
, "rw").getChannel();
765 var lock
= fileChannel
.tryLock();
768 logger
.debug("Config file is in use by another instance.");
769 throw new IOException("Config file is in use by another instance.");
771 logger
.info("Config file is in use by another instance, waiting…");
772 lock
= fileChannel
.lock();
773 logger
.info("Config file lock acquired.");
775 return new Pair
<>(fileChannel
, lock
);
778 public void addPreKeys(List
<PreKeyRecord
> records
) {
779 for (var record : records
) {
780 if (preKeyIdOffset
!= record.getId()) {
781 logger
.error("Invalid pre key id {}, expected {}", record.getId(), preKeyIdOffset
);
782 throw new AssertionError("Invalid pre key id");
784 getPreKeyStore().storePreKey(record.getId(), record);
785 preKeyIdOffset
= (preKeyIdOffset
+ 1) % Medium
.MAX_VALUE
;
790 public void addSignedPreKey(SignedPreKeyRecord
record) {
791 if (nextSignedPreKeyId
!= record.getId()) {
792 logger
.error("Invalid signed pre key id {}, expected {}", record.getId(), nextSignedPreKeyId
);
793 throw new AssertionError("Invalid signed pre key id");
795 getSignedPreKeyStore().storeSignedPreKey(record.getId(), record);
796 nextSignedPreKeyId
= (nextSignedPreKeyId
+ 1) % Medium
.MAX_VALUE
;
800 public SignalProtocolStore
getSignalProtocolStore() {
801 return getOrCreate(() -> signalProtocolStore
,
802 () -> signalProtocolStore
= new SignalProtocolStore(getPreKeyStore(),
803 getSignedPreKeyStore(),
805 getIdentityKeyStore(),
807 this::isMultiDevice
));
810 private PreKeyStore
getPreKeyStore() {
811 return getOrCreate(() -> preKeyStore
, () -> preKeyStore
= new PreKeyStore(getPreKeysPath(dataPath
, account
)));
814 private SignedPreKeyStore
getSignedPreKeyStore() {
815 return getOrCreate(() -> signedPreKeyStore
,
816 () -> signedPreKeyStore
= new SignedPreKeyStore(getSignedPreKeysPath(dataPath
, account
)));
819 public SessionStore
getSessionStore() {
820 return getOrCreate(() -> sessionStore
,
821 () -> sessionStore
= new SessionStore(getSessionsPath(dataPath
, account
), getRecipientStore()));
824 public IdentityKeyStore
getIdentityKeyStore() {
825 return getOrCreate(() -> identityKeyStore
,
826 () -> identityKeyStore
= new IdentityKeyStore(getIdentitiesPath(dataPath
, account
),
833 public GroupStore
getGroupStore() {
837 public ContactsStore
getContactStore() {
838 return getRecipientStore();
841 public RecipientStore
getRecipientStore() {
842 return getOrCreate(() -> recipientStore
,
843 () -> recipientStore
= RecipientStore
.load(getRecipientsStoreFile(dataPath
, account
),
844 this::mergeRecipients
));
847 public ProfileStore
getProfileStore() {
848 return getRecipientStore();
851 public StickerStore
getStickerStore() {
855 public SenderKeyStore
getSenderKeyStore() {
856 return getOrCreate(() -> senderKeyStore
,
857 () -> senderKeyStore
= new SenderKeyStore(getSharedSenderKeysFile(dataPath
, account
),
858 getSenderKeysPath(dataPath
, account
),
859 getRecipientStore()::resolveRecipientAddress
,
860 getRecipientStore()));
863 public ConfigurationStore
getConfigurationStore() {
864 return configurationStore
;
867 public MessageCache
getMessageCache() {
868 return getOrCreate(() -> messageCache
,
869 () -> messageCache
= new MessageCache(getMessageCachePath(dataPath
, account
)));
872 public String
getAccount() {
876 public ACI
getAci() {
880 public void setAci(final ACI aci
) {
885 public SignalServiceAddress
getSelfAddress() {
886 return new SignalServiceAddress(aci
, account
);
889 public RecipientId
getSelfRecipientId() {
890 return getRecipientStore().resolveRecipientTrusted(new RecipientAddress(aci
== null ?
null : aci
.uuid(),
894 public String
getEncryptedDeviceName() {
895 return encryptedDeviceName
;
898 public void setEncryptedDeviceName(final String encryptedDeviceName
) {
899 this.encryptedDeviceName
= encryptedDeviceName
;
903 public int getDeviceId() {
907 public boolean isMasterDevice() {
908 return deviceId
== SignalServiceAddress
.DEFAULT_DEVICE_ID
;
911 public IdentityKeyPair
getIdentityKeyPair() {
912 return identityKeyPair
;
915 public int getLocalRegistrationId() {
916 return localRegistrationId
;
919 public String
getPassword() {
923 private void setPassword(final String password
) {
924 this.password
= password
;
928 public void setRegistrationLockPin(final String registrationLockPin
, final MasterKey pinMasterKey
) {
929 this.registrationLockPin
= registrationLockPin
;
930 this.pinMasterKey
= pinMasterKey
;
934 public MasterKey
getPinMasterKey() {
938 public StorageKey
getStorageKey() {
939 if (pinMasterKey
!= null) {
940 return pinMasterKey
.deriveStorageServiceKey();
945 public void setStorageKey(final StorageKey storageKey
) {
946 if (storageKey
.equals(this.storageKey
)) {
949 this.storageKey
= storageKey
;
953 public long getStorageManifestVersion() {
954 return this.storageManifestVersion
;
957 public void setStorageManifestVersion(final long storageManifestVersion
) {
958 if (storageManifestVersion
== this.storageManifestVersion
) {
961 this.storageManifestVersion
= storageManifestVersion
;
965 public ProfileKey
getProfileKey() {
969 public void setProfileKey(final ProfileKey profileKey
) {
970 if (profileKey
.equals(this.profileKey
)) {
973 this.profileKey
= profileKey
;
977 public byte[] getSelfUnidentifiedAccessKey() {
978 return UnidentifiedAccess
.deriveAccessKeyFrom(getProfileKey());
981 public int getPreKeyIdOffset() {
982 return preKeyIdOffset
;
985 public int getNextSignedPreKeyId() {
986 return nextSignedPreKeyId
;
989 public boolean isRegistered() {
993 public void setRegistered(final boolean registered
) {
994 this.registered
= registered
;
998 public boolean isMultiDevice() {
999 return isMultiDevice
;
1002 public void setMultiDevice(final boolean multiDevice
) {
1003 if (isMultiDevice
== multiDevice
) {
1006 isMultiDevice
= multiDevice
;
1010 public long getLastReceiveTimestamp() {
1011 return lastReceiveTimestamp
;
1014 public void setLastReceiveTimestamp(final long lastReceiveTimestamp
) {
1015 this.lastReceiveTimestamp
= lastReceiveTimestamp
;
1019 public boolean isUnrestrictedUnidentifiedAccess() {
1020 // TODO make configurable
1024 public boolean isDiscoverableByPhoneNumber() {
1025 return configurationStore
.getPhoneNumberUnlisted() == null || !configurationStore
.getPhoneNumberUnlisted();
1028 public void finishRegistration(final ACI aci
, final MasterKey masterKey
, final String pin
) {
1029 this.pinMasterKey
= masterKey
;
1030 this.storageManifestVersion
= -1;
1031 this.storageKey
= null;
1032 this.encryptedDeviceName
= null;
1033 this.deviceId
= SignalServiceAddress
.DEFAULT_DEVICE_ID
;
1034 this.isMultiDevice
= false;
1035 this.registered
= true;
1037 this.registrationLockPin
= pin
;
1038 this.lastReceiveTimestamp
= 0;
1042 getSessionStore().archiveAllSessions();
1043 getSenderKeyStore().deleteAll();
1044 final var recipientId
= getRecipientStore().resolveRecipientTrusted(getSelfAddress());
1045 final var publicKey
= getIdentityKeyPair().getPublicKey();
1046 getIdentityKeyStore().saveIdentity(recipientId
, publicKey
, new Date());
1047 getIdentityKeyStore().setIdentityTrustLevel(recipientId
, publicKey
, TrustLevel
.TRUSTED_VERIFIED
);
1051 public void close() {
1052 synchronized (fileChannel
) {
1056 } catch (ClosedChannelException ignored
) {
1058 fileChannel
.close();
1059 } catch (IOException e
) {
1060 logger
.warn("Failed to close account: {}", e
.getMessage(), e
);
1065 private <T
> T
getOrCreate(Supplier
<T
> supplier
, Callable creator
) {
1066 var value
= supplier
.get();
1067 if (value
!= null) {
1071 synchronized (LOCK
) {
1072 value
= supplier
.get();
1073 if (value
!= null) {
1077 return supplier
.get();
1081 private interface Callable
{