diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java index 241dc84e8..a57ceb6b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/DirectoryHelper.java @@ -469,8 +469,8 @@ public class DirectoryHelper { for (RecipientId newUser: newUsers) { Recipient recipient = Recipient.resolved(newUser); - if (!SessionUtil.hasSession(context, recipient.getId()) && - !recipient.isSelf() && + if (!SessionUtil.hasSession(recipient.getId()) && + !recipient.isSelf() && recipient.hasAUserSetDisplayName(context)) { IncomingJoinedMessage message = new IncomingJoinedMessage(recipient.getId()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java index 5cc455e03..7d481a692 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.database.MessageDatabase; import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.MessageSender; @@ -125,15 +126,16 @@ final class SafetyNumberChangeRepository { try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { for (ChangedRecipient changedRecipient : changedRecipients) { - SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(changedRecipient.getRecipient().requireServiceId(), SignalServiceAddress.DEFAULT_DEVICE_ID); - TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context); + SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(changedRecipient.getRecipient().requireServiceId(), SignalServiceAddress.DEFAULT_DEVICE_ID); + Log.d(TAG, "Saving identity for: " + changedRecipient.getRecipient().getId() + " " + changedRecipient.getIdentityRecord().getIdentityKey().hashCode()); - TextSecureIdentityKeyStore.SaveResult result = identityKeyStore.saveIdentity(mismatchAddress, changedRecipient.getIdentityRecord().getIdentityKey(), true); + TextSecureIdentityKeyStore.SaveResult result = ApplicationDependencies.getIdentityStore().saveIdentity(mismatchAddress, changedRecipient.getIdentityRecord().getIdentityKey(), true); + Log.d(TAG, "Saving identity result: " + result); if (result == TextSecureIdentityKeyStore.SaveResult.NO_CHANGE) { Log.i(TAG, "Archiving sessions explicitly as they appear to be out of sync."); - SessionUtil.archiveSession(context, changedRecipient.getRecipient().getId(), SignalServiceAddress.DEFAULT_DEVICE_ID); - SessionUtil.archiveSiblingSessions(context, mismatchAddress); + SessionUtil.archiveSession(changedRecipient.getRecipient().getId(), SignalServiceAddress.DEFAULT_DEVICE_ID); + SessionUtil.archiveSiblingSessions(mismatchAddress); DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(changedRecipient.getRecipient().getId()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java index 4ec767bca..2e0653695 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/PreKeyUtil.java @@ -21,6 +21,7 @@ import android.content.Context; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.InvalidKeyException; @@ -43,7 +44,7 @@ public class PreKeyUtil { private static final int BATCH_SIZE = 100; public synchronized static List generatePreKeys(Context context) { - PreKeyStore preKeyStore = new TextSecurePreKeyStore(context); + PreKeyStore preKeyStore = ApplicationDependencies.getPreKeyStore(); List records = new LinkedList<>(); int preKeyIdOffset = TextSecurePreferences.getNextPreKeyId(context); @@ -63,7 +64,7 @@ public class PreKeyUtil { public synchronized static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair, boolean active) { try { - SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context); + SignedPreKeyStore signedPreKeyStore = ApplicationDependencies.getPreKeyStore(); int signedPreKeyId = TextSecurePreferences.getNextSignedPreKeyId(context); ECKeyPair keyPair = Curve.generateKeyPair(); byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/SenderKeyUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/SenderKeyUtil.java index 5a1e64ea3..d0a1a0fc8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/SenderKeyUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/SenderKeyUtil.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import org.thoughtcrime.securesms.crypto.storage.SignalSenderKeyStore; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.recipients.Recipient; import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.push.DistributionId; @@ -19,7 +20,7 @@ public final class SenderKeyUtil { */ public static void rotateOurKey(@NonNull Context context, @NonNull DistributionId distributionId) { try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { - new SignalSenderKeyStore(context).deleteAllFor(Recipient.self().getId(), distributionId); + ApplicationDependencies.getSenderKeyStore().deleteAllFor(Recipient.self().getId(), distributionId); DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(distributionId); } } @@ -36,7 +37,7 @@ public final class SenderKeyUtil { */ public static void clearAllState(@NonNull Context context) { try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { - new SignalSenderKeyStore(context).deleteAll(); + ApplicationDependencies.getSenderKeyStore().deleteAll(); DatabaseFactory.getSenderKeySharedDatabase(context).deleteAll(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/SessionUtil.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/SessionUtil.java index 1702b6c3e..51618e5b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/SessionUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/SessionUtil.java @@ -1,44 +1,38 @@ package org.thoughtcrime.securesms.crypto; -import android.content.Context; - import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.ecc.ECPublicKey; import org.whispersystems.libsignal.state.SessionRecord; -import org.whispersystems.libsignal.state.SessionStore; import org.whispersystems.signalservice.api.push.SignalServiceAddress; public class SessionUtil { - public static boolean hasSession(@NonNull Context context, @NonNull RecipientId id) { - SessionStore sessionStore = new TextSecureSessionStore(context); + public static boolean hasSession(@NonNull RecipientId id) { SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(Recipient.resolved(id).requireServiceId(), SignalServiceAddress.DEFAULT_DEVICE_ID); - return sessionStore.containsSession(axolotlAddress); + return ApplicationDependencies.getSessionStore().containsSession(axolotlAddress); } - public static void archiveSiblingSessions(Context context, SignalProtocolAddress address) { - TextSecureSessionStore sessionStore = new TextSecureSessionStore(context); - sessionStore.archiveSiblingSessions(address); + public static void archiveSiblingSessions(SignalProtocolAddress address) { + ApplicationDependencies.getSessionStore().archiveSiblingSessions(address); } - public static void archiveAllSessions(Context context) { - new TextSecureSessionStore(context).archiveAllSessions(); + public static void archiveAllSessions() { + ApplicationDependencies.getSessionStore().archiveAllSessions(); } - public static void archiveSession(Context context, RecipientId recipientId, int deviceId) { - new TextSecureSessionStore(context).archiveSession(recipientId, deviceId); + public static void archiveSession(RecipientId recipientId, int deviceId) { + ApplicationDependencies.getSessionStore().archiveSession(recipientId, deviceId); } - public static boolean ratchetKeyMatches(@NonNull Context context, @NonNull Recipient recipient, int deviceId, @NonNull ECPublicKey ratchetKey) { - TextSecureSessionStore sessionStore = new TextSecureSessionStore(context); - SignalProtocolAddress address = new SignalProtocolAddress(recipient.resolve().requireServiceId(), deviceId); - SessionRecord session = sessionStore.loadSession(address); + public static boolean ratchetKeyMatches(@NonNull Recipient recipient, int deviceId, @NonNull ECPublicKey ratchetKey) { + SignalProtocolAddress address = new SignalProtocolAddress(recipient.resolve().requireServiceId(), deviceId); + SessionRecord session = ApplicationDependencies.getSessionStore().loadSession(address); return session.currentRatchetKeyMatches(ratchetKey); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java index a533d0458..5fb05e84e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/SignalProtocolStoreImpl.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; @@ -36,11 +37,11 @@ public class SignalProtocolStoreImpl implements SignalServiceDataStore { public SignalProtocolStoreImpl(Context context) { this.context = context; - this.preKeyStore = new TextSecurePreKeyStore(context); - this.signedPreKeyStore = new TextSecurePreKeyStore(context); - this.identityKeyStore = new TextSecureIdentityKeyStore(context); - this.sessionStore = new TextSecureSessionStore(context); - this.senderKeyStore = new SignalSenderKeyStore(context); + this.preKeyStore = ApplicationDependencies.getPreKeyStore(); + this.signedPreKeyStore = ApplicationDependencies.getPreKeyStore(); + this.identityKeyStore = ApplicationDependencies.getIdentityStore(); + this.sessionStore = ApplicationDependencies.getSessionStore(); + this.senderKeyStore = ApplicationDependencies.getSenderKeyStore(); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java index 5ea98d21c..919568961 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureIdentityKeyStore.java @@ -10,32 +10,33 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.SessionUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; -import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.database.model.IdentityStoreRecord; -import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.IdentityUtil; +import org.thoughtcrime.securesms.util.LRUCache; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.state.IdentityKeyStore; -import org.whispersystems.libsignal.util.guava.Optional; +import java.util.Map; import java.util.concurrent.TimeUnit; public class TextSecureIdentityKeyStore implements IdentityKeyStore { - private static final int TIMESTAMP_THRESHOLD_SECONDS = 5; - private static final String TAG = Log.tag(TextSecureIdentityKeyStore.class); - private static final Object LOCK = new Object(); + + private static final Object LOCK = new Object(); + private static final int TIMESTAMP_THRESHOLD_SECONDS = 5; private final Context context; + private final Cache cache; public TextSecureIdentityKeyStore(Context context) { this.context = context; + this.cache = new Cache(context); } @Override @@ -48,41 +49,44 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { return TextSecurePreferences.getLocalRegistrationId(context); } + @Override + public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { + return saveIdentity(address, identityKey, false) == SaveResult.UPDATE; + } + public @NonNull SaveResult saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) { synchronized (LOCK) { - IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); - Optional identityRecord = identityDatabase.getIdentity(address.getName()); - RecipientId recipientId = RecipientId.fromExternalPush(address.getName()); + IdentityStoreRecord identityRecord = cache.get(address.getName()); + RecipientId recipientId = RecipientId.fromExternalPush(address.getName()); - if (!identityRecord.isPresent()) { + if (identityRecord == null) { Log.i(TAG, "Saving new identity..."); - identityDatabase.saveIdentity(address.getName(), recipientId, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval); + cache.save(address.getName(), recipientId, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval); return SaveResult.NEW; } - if (!identityRecord.get().getIdentityKey().equals(identityKey)) { - Log.i(TAG, "Replacing existing identity... Existing: " + identityRecord.get().getIdentityKey().hashCode() + " New: " + identityKey.hashCode()); + if (!identityRecord.getIdentityKey().equals(identityKey)) { + Log.i(TAG, "Replacing existing identity... Existing: " + identityRecord.getIdentityKey().hashCode() + " New: " + identityKey.hashCode()); VerifiedStatus verifiedStatus; - if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED || - identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED) + if (identityRecord.getVerifiedStatus() == VerifiedStatus.VERIFIED || + identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) { verifiedStatus = VerifiedStatus.UNVERIFIED; } else { verifiedStatus = VerifiedStatus.DEFAULT; } - - identityDatabase.saveIdentity(address.getName(), recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval); + cache.save(address.getName(), recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval); IdentityUtil.markIdentityUpdate(context, recipientId); - SessionUtil.archiveSiblingSessions(context, address); + SessionUtil.archiveSiblingSessions(address); DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(recipientId); return SaveResult.UPDATE; } - if (isNonBlockingApprovalRequired(identityRecord.get())) { + if (isNonBlockingApprovalRequired(identityRecord)) { Log.i(TAG, "Setting approval status..."); - identityDatabase.setApproval(recipientId, nonBlockingApproval); + cache.setApproval(identityRecord, recipientId, nonBlockingApproval); return SaveResult.NON_BLOCKING_APPROVAL_REQUIRED; } @@ -90,37 +94,30 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { } } - @Override - public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { - return saveIdentity(address, identityKey, false) == SaveResult.UPDATE; - } - @Override public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { - synchronized (LOCK) { - boolean isSelf = address.getName().equals(TextSecurePreferences.getLocalUuid(context).toString()) || - address.getName().equals(TextSecurePreferences.getLocalNumber(context)); + boolean isSelf = address.getName().equals(TextSecurePreferences.getLocalUuid(context).toString()) || + address.getName().equals(TextSecurePreferences.getLocalNumber(context)); - if (isSelf) { - return identityKey.equals(IdentityKeyUtil.getIdentityKey(context)); - } + if (isSelf) { + return identityKey.equals(IdentityKeyUtil.getIdentityKey(context)); + } - IdentityStoreRecord record = DatabaseFactory.getIdentityDatabase(context).getIdentityStoreRecord(address.getName()); + IdentityStoreRecord record = cache.get(address.getName()); - switch (direction) { - case SENDING: - return isTrustedForSending(identityKey, record); - case RECEIVING: - return true; - default: - throw new AssertionError("Unknown direction: " + direction); - } + switch (direction) { + case SENDING: + return isTrustedForSending(identityKey, record); + case RECEIVING: + return true; + default: + throw new AssertionError("Unknown direction: " + direction); } } @Override public IdentityKey getIdentity(SignalProtocolAddress address) { - IdentityStoreRecord record = DatabaseFactory.getIdentityDatabase(context).getIdentityStoreRecord(address.getName()); + IdentityStoreRecord record = cache.get(address.getName()); return record != null ? record.getIdentityKey() : null; } @@ -148,18 +145,47 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { return true; } - private boolean isNonBlockingApprovalRequired(IdentityRecord identityRecord) { - return isNonBlockingApprovalRequired(identityRecord.isFirstUse(), identityRecord.getTimestamp(), identityRecord.isApprovedNonBlocking()); + private boolean isNonBlockingApprovalRequired(IdentityStoreRecord record) { + return !record.getFirstUse() && + !record.getNonblockingApproval() && + System.currentTimeMillis() - record.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS); } - private boolean isNonBlockingApprovalRequired(IdentityStoreRecord identityRecord) { - return isNonBlockingApprovalRequired(identityRecord.getFirstUse(), identityRecord.getTimestamp(), identityRecord.getNonblockingApproval()); - } + private static final class Cache { - private boolean isNonBlockingApprovalRequired(boolean firstUse, long timestamp, boolean nonblockingApproval) { - return !firstUse && - !nonblockingApproval && - System.currentTimeMillis() - timestamp < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS); + private final Context context; + private final Map cache; + + Cache(@NonNull Context context) { + this.context = context; + this.cache = new LRUCache<>(200); + } + + public synchronized @Nullable IdentityStoreRecord get(@NonNull String addressName) { + if (cache.containsKey(addressName)) { + return cache.get(addressName); + } else { + IdentityStoreRecord record = DatabaseFactory.getIdentityDatabase(context).getIdentityStoreRecord(addressName); + cache.put(addressName, record); + return record; + } + } + + public synchronized void save(@NonNull String addressName, @NonNull RecipientId recipientId, @NonNull IdentityKey identityKey, @NonNull VerifiedStatus verifiedStatus, boolean firstUse, long timestamp, boolean nonBlockingApproval) { + DatabaseFactory.getIdentityDatabase(context).saveIdentity(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval); + cache.put(addressName, new IdentityStoreRecord(addressName, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval)); + } + + public synchronized void setApproval(@NonNull IdentityStoreRecord record, @NonNull RecipientId recipientId, boolean nonblockingApproval) { + DatabaseFactory.getIdentityDatabase(context).setApproval(record.getAddressName(), recipientId, nonblockingApproval); + cache.put(record.getAddressName(), + new IdentityStoreRecord(record.getAddressName(), + record.getIdentityKey(), + record.getVerifiedStatus(), + record.getFirstUse(), + record.getTimestamp(), + nonblockingApproval)); + } } public enum SaveResult { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java index 01ddf50e0..c8c94a118 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/IdentityDatabase.java @@ -222,12 +222,16 @@ public class IdentityDatabase extends Database { } public void setApproval(@NonNull RecipientId recipientId, boolean nonBlockingApproval) { + setApproval(Recipient.resolved(recipientId).requireServiceId(), recipientId, nonBlockingApproval); + } + + public void setApproval(@NonNull String addressName, @NonNull RecipientId recipientId, boolean nonBlockingApproval) { SQLiteDatabase database = databaseHelper.getSignalWritableDatabase(); ContentValues contentValues = new ContentValues(2); contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval); - database.update(TABLE_NAME, contentValues, ADDRESS + " = ?", SqlUtil.buildArgs(Recipient.resolved(recipientId).requireServiceId())); + database.update(TABLE_NAME, contentValues, ADDRESS + " = ?", SqlUtil.buildArgs(addressName)); DatabaseFactory.getRecipientDatabase(context).markNeedsSync(recipientId); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index 0cb8dcb8c..eb9ac4407 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -8,6 +8,10 @@ import androidx.annotation.NonNull; import org.thoughtcrime.securesms.KbsEnclave; import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusSender; +import org.thoughtcrime.securesms.crypto.storage.SignalSenderKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.DatabaseObserver; import org.thoughtcrime.securesms.database.PendingRetryReceiptCache; import org.thoughtcrime.securesms.groups.GroupsV2Authorization; @@ -90,6 +94,10 @@ public class ApplicationDependencies { private static volatile PendingRetryReceiptCache pendingRetryReceiptCache; private static volatile SignalWebSocket signalWebSocket; private static volatile MessageNotifier messageNotifier; + private static volatile TextSecureIdentityKeyStore identityStore; + private static volatile TextSecureSessionStore sessionStore; + private static volatile TextSecurePreKeyStore preKeyStore; + private static volatile SignalSenderKeyStore senderKeyStore; @MainThread public static void init(@NonNull Application application, @NonNull Provider provider) { @@ -499,6 +507,50 @@ public class ApplicationDependencies { return signalWebSocket; } + public static @NonNull TextSecureIdentityKeyStore getIdentityStore() { + if (identityStore == null) { + synchronized (LOCK) { + if (identityStore == null) { + identityStore = provider.provideIdentityStore(); + } + } + } + return identityStore; + } + + public static @NonNull TextSecureSessionStore getSessionStore() { + if (sessionStore == null) { + synchronized (LOCK) { + if (sessionStore == null) { + sessionStore = provider.provideSessionStore(); + } + } + } + return sessionStore; + } + + public static @NonNull TextSecurePreKeyStore getPreKeyStore() { + if (preKeyStore == null) { + synchronized (LOCK) { + if (preKeyStore == null) { + preKeyStore = provider.providePreKeyStore(); + } + } + } + return preKeyStore; + } + + public static @NonNull SignalSenderKeyStore getSenderKeyStore() { + if (senderKeyStore == null) { + synchronized (LOCK) { + if (senderKeyStore == null) { + senderKeyStore = provider.provideSenderKeyStore(); + } + } + } + return senderKeyStore; + } + public interface Provider { @NonNull GroupsV2Operations provideGroupsV2Operations(); @NonNull SignalServiceAccountManager provideSignalServiceAccountManager(); @@ -527,5 +579,9 @@ public class ApplicationDependencies { @NonNull PendingRetryReceiptManager providePendingRetryReceiptManager(); @NonNull PendingRetryReceiptCache providePendingRetryReceiptCache(); @NonNull SignalWebSocket provideSignalWebSocket(); + @NonNull TextSecureIdentityKeyStore provideIdentityStore(); + @NonNull TextSecureSessionStore provideSessionStore(); + @NonNull TextSecurePreKeyStore providePreKeyStore(); + @NonNull SignalSenderKeyStore provideSenderKeyStore(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java index e0103f4e3..3156f0417 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -11,6 +11,10 @@ import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.crypto.ReentrantSessionLock; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; +import org.thoughtcrime.securesms.crypto.storage.SignalSenderKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore; +import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.DatabaseObserver; import org.thoughtcrime.securesms.database.JobDatabase; import org.thoughtcrime.securesms.database.PendingRetryReceiptCache; @@ -54,6 +58,7 @@ import org.thoughtcrime.securesms.util.EarlyMessageCache; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.FrameRateTracker; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; @@ -259,6 +264,26 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr return signalWebSocket; } + @Override + public @NonNull TextSecureIdentityKeyStore provideIdentityStore() { + return new TextSecureIdentityKeyStore(context); + } + + @Override + public @NonNull TextSecureSessionStore provideSessionStore() { + return new TextSecureSessionStore(context); + } + + @Override + public @NonNull TextSecurePreKeyStore providePreKeyStore() { + return new TextSecurePreKeyStore(context); + } + + @Override + public @NonNull SignalSenderKeyStore provideSenderKeyStore() { + return new SignalSenderKeyStore(context); + } + private @NonNull WebSocketFactory provideWebSocketFactory(@NonNull SignalWebSocketHealthMonitor healthMonitor) { return new WebSocketFactory() { @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java index 729bbe312..69b5fabb3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java @@ -84,7 +84,7 @@ public class AutomaticSessionResetJob extends BaseJob { @Override protected void onRun() throws Exception { - SessionUtil.archiveSession(context, recipientId, deviceId); + SessionUtil.archiveSession(recipientId, deviceId); DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(recipientId); insertLocalMessage(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java index bb74b40f1..195a40d95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ResendMessageJob.java @@ -163,7 +163,7 @@ public class ResendMessageJob extends BaseJob { .map(device -> new SignalProtocolAddress(recipient.requireServiceId(), device)) .collect(Collectors.toList()); - new SignalSenderKeyStore(context).markSenderKeySharedWith(distributionId, addresses); + ApplicationDependencies.getSenderKeyStore().markSenderKeySharedWith(distributionId, addresses); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java index 6e77e6ce4..f90eef4fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileJob.java @@ -373,7 +373,7 @@ public class RetrieveProfileJob extends BaseJob { return; } - IdentityUtil.saveIdentity(context, recipient.requireServiceId(), identityKey); + IdentityUtil.saveIdentity(recipient.requireServiceId(), identityKey); } catch (InvalidKeyException | IOException e) { Log.w(TAG, e); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java index 367f9f8ff..3bac97bad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SenderKeyDistributionSendJob.java @@ -111,7 +111,7 @@ public final class SenderKeyDistributionSendJob extends BaseJob { .map(device -> new SignalProtocolAddress(recipient.requireServiceId(), device)) .collect(Collectors.toList()); - new SignalSenderKeyStore(context).markSenderKeySharedWith(distributionId, addresses); + ApplicationDependencies.getSenderKeyStore().markSenderKeySharedWith(distributionId, addresses); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index eb5d084c0..cd6c87f33 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -694,8 +694,7 @@ public final class MessageContentProcessor { } if (insertResult.isPresent()) { - SessionStore sessionStore = new TextSecureSessionStore(context); - sessionStore.deleteAllSessions(content.getSender().getIdentifier()); + ApplicationDependencies.getSessionStore().deleteAllSessions(content.getSender().getIdentifier()); SecurityEvent.broadcastSecurityUpdateEvent(context); ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); @@ -717,8 +716,7 @@ public final class MessageContentProcessor { long threadId = DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient); if (!recipient.isGroup()) { - SessionStore sessionStore = new TextSecureSessionStore(context); - sessionStore.deleteAllSessions(recipient.requireServiceId()); + ApplicationDependencies.getSessionStore().deleteAllSessions(recipient.requireServiceId()); SecurityEvent.broadcastSecurityUpdateEvent(context); @@ -1884,10 +1882,10 @@ public final class MessageContentProcessor { if (decryptionErrorMessage.getDeviceId() == SignalServiceAddress.DEFAULT_DEVICE_ID && decryptionErrorMessage.getRatchetKey().isPresent() && - SessionUtil.ratchetKeyMatches(context, requester, content.getSenderDevice(), decryptionErrorMessage.getRatchetKey().get())) + SessionUtil.ratchetKeyMatches(requester, content.getSenderDevice(), decryptionErrorMessage.getRatchetKey().get())) { warn(content.getTimestamp(), "[RetryReceipt-I] Ratchet key matches. Archiving the session."); - SessionUtil.archiveSession(context, requester.getId(), content.getSenderDevice()); + SessionUtil.archiveSession(requester.getId(), content.getSenderDevice()); archivedSession = true; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java b/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java index 3442fdbad..2d40ec991 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/service/CodeVerificationRequest.java @@ -203,7 +203,7 @@ public final class CodeVerificationRequest { byte[] unidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(profileKey); TextSecurePreferences.setLocalRegistrationId(context, registrationId); - SessionUtil.archiveAllSessions(context); + SessionUtil.archiveAllSessions(); SenderKeyUtil.clearAllState(context); SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java index 8b30ae341..576341633 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -10,7 +10,6 @@ import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.ReentrantSessionLock; -import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -34,7 +33,6 @@ import org.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.SignalProtocolAddress; -import org.whispersystems.libsignal.state.IdentityKeyStore; import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.libsignal.state.SessionStore; import org.whispersystems.libsignal.util.guava.Optional; @@ -145,13 +143,12 @@ public final class IdentityUtil { } } - public static void saveIdentity(Context context, String user, IdentityKey identityKey) { + public static void saveIdentity(String user, IdentityKey identityKey) { try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { - IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context); - SessionStore sessionStore = new TextSecureSessionStore(context); + SessionStore sessionStore = ApplicationDependencies.getSessionStore(); SignalProtocolAddress address = new SignalProtocolAddress(user, 1); - if (identityKeyStore.saveIdentity(address, identityKey)) { + if (ApplicationDependencies.getIdentityStore().saveIdentity(address, identityKey)) { if (sessionStore.containsSession(address)) { SessionRecord sessionRecord = sessionStore.loadSession(address); sessionRecord.archiveCurrentState(); @@ -187,7 +184,7 @@ public final class IdentityUtil { (identityRecord.isPresent() && !identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey())) || (identityRecord.isPresent() && identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED))) { - saveIdentity(context, verifiedMessage.getDestination().getIdentifier(), verifiedMessage.getIdentityKey()); + saveIdentity(verifiedMessage.getDestination().getIdentifier(), verifiedMessage.getIdentityKey()); identityDatabase.setVerified(recipient.getId(), verifiedMessage.getIdentityKey(), IdentityDatabase.VerifiedStatus.VERIFIED); markIdentityVerified(context, recipient, true, true); }