diff --git a/app/build.gradle b/app/build.gradle index ba38a7e10..d3c92f448 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -335,7 +335,7 @@ dependencies { implementation project(':video') implementation 'org.signal:zkgroup-android:0.7.0' - implementation 'org.whispersystems:signal-client-android:0.1.6' + implementation 'org.whispersystems:signal-client-android:0.1.7' implementation 'com.google.protobuf:protobuf-javalite:3.10.0' implementation 'org.signal:argon2:13.1@aar' diff --git a/app/src/main/java/org/thoughtcrime/securesms/ConfirmIdentityDialog.java b/app/src/main/java/org/thoughtcrime/securesms/ConfirmIdentityDialog.java index 555aa43c0..fc5a4be22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ConfirmIdentityDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ConfirmIdentityDialog.java @@ -11,6 +11,7 @@ import android.widget.TextView; import androidx.appcompat.app.AlertDialog; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessageDatabase; @@ -27,13 +28,12 @@ import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.VerifySpan; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import java.io.IOException; -import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; - public class ConfirmIdentityDialog extends AlertDialog { @SuppressWarnings("unused") @@ -94,7 +94,7 @@ public class ConfirmIdentityDialog extends AlertDialog { { @Override protected Void doInBackground(Void... params) { - synchronized (SESSION_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireServiceId(), 1); TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java index f4cb23113..02404f7d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/VerifyIdentityActivity.java @@ -63,6 +63,7 @@ import androidx.fragment.app.FragmentTransaction; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.components.camera.CameraView; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -91,14 +92,13 @@ import org.whispersystems.libsignal.fingerprint.Fingerprint; import org.whispersystems.libsignal.fingerprint.FingerprintParsingException; import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException; import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator; +import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.Locale; -import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; - /** * Activity for verifying identity keys. * @@ -620,7 +620,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement new AsyncTask() { @Override protected Void doInBackground(Recipient... params) { - synchronized (SESSION_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { if (isChecked) { Log.i(TAG, "Saving identity: " + params[0].getId()); DatabaseFactory.getIdentityDatabase(getActivity()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java index 4b10694e3..93faef379 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UntrustedSendDialog.java @@ -8,15 +8,15 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; +import org.whispersystems.signalservice.api.SignalSessionLock; import java.util.List; -import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; - public class UntrustedSendDialog extends AlertDialog.Builder implements DialogInterface.OnClickListener { private final List untrustedRecords; @@ -43,7 +43,7 @@ public class UntrustedSendDialog extends AlertDialog.Builder implements DialogIn final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext()); SimpleTask.run(() -> { - synchronized (SESSION_LOCK) { + try(SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { for (IdentityRecord identityRecord : untrustedRecords) { identityDatabase.setApproval(identityRecord.getRecipientId(), true); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java index 3312926f1..d1e981c3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/identity/UnverifiedSendDialog.java @@ -8,14 +8,14 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; +import org.whispersystems.signalservice.api.SignalSessionLock; import java.util.List; -import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; - public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogInterface.OnClickListener { private final List untrustedRecords; @@ -44,7 +44,7 @@ public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogI new AsyncTask() { @Override protected Void doInBackground(Void... params) { - synchronized (SESSION_LOCK) { + try(SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { for (IdentityRecord identityRecord : untrustedRecords) { identityDatabase.setVerified(identityRecord.getRecipientId(), identityRecord.getIdentityKey(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index d86777149..a98d0c68f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -141,6 +141,7 @@ import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog import org.thoughtcrime.securesms.conversation.ui.groupcall.GroupCallViewModel; import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerViewModel; import org.thoughtcrime.securesms.conversationlist.model.MessageResult; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.crypto.SecurityEvent; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DraftDatabase; @@ -279,6 +280,7 @@ import org.thoughtcrime.securesms.wallpaper.ChatWallpaperDimLevelUtil; import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalSessionLock; import java.io.IOException; import java.util.ArrayList; @@ -295,7 +297,6 @@ import java.util.concurrent.atomic.AtomicInteger; import static org.thoughtcrime.securesms.TransportOption.Type; import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; -import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; /** * Activity for displaying a message thread, as well as @@ -3620,7 +3621,7 @@ public class ConversationActivity extends PassphraseRequiredActivity new AsyncTask() { @Override protected Void doInBackground(Void... params) { - synchronized (SESSION_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { for (IdentityRecord identityRecord : unverifiedIdentities) { identityDatabase.setVerified(identityRecord.getRecipientId(), identityRecord.getIdentityKey(), 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 ac8b1a9a2..8669b3d15 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 @@ -12,6 +12,7 @@ import com.annimon.stream.Stream; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; @@ -25,12 +26,11 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.MessageSender; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.SignalProtocolAddress; +import org.whispersystems.signalservice.api.SignalSessionLock; import java.util.Collection; import java.util.List; -import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; - final class SafetyNumberChangeRepository { private static final String TAG = SafetyNumberChangeRepository.class.getSimpleName(); @@ -90,7 +90,7 @@ final class SafetyNumberChangeRepository { private TrustAndVerifyResult trustOrVerifyChangedRecipientsInternal(@NonNull List changedRecipients) { IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); - synchronized (SESSION_LOCK) { + try(SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { for (ChangedRecipient changedRecipient : changedRecipients) { IdentityRecord identityRecord = changedRecipient.getIdentityRecord(); @@ -110,7 +110,7 @@ final class SafetyNumberChangeRepository { @WorkerThread private TrustAndVerifyResult trustOrVerifyChangedRecipientsAndResendInternal(@NonNull List changedRecipients, @NonNull MessageRecord messageRecord) { - synchronized (SESSION_LOCK) { + try(SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { for (ChangedRecipient changedRecipient : changedRecipients) { SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(changedRecipient.getRecipient().requireServiceId(), 1); TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context); diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/DatabaseSessionLock.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/DatabaseSessionLock.java new file mode 100644 index 000000000..baa2b1a31 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/DatabaseSessionLock.java @@ -0,0 +1,31 @@ +package org.thoughtcrime.securesms.crypto; + +import net.sqlcipher.database.SQLiteDatabase; + +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.whispersystems.signalservice.api.SignalSessionLock; + +/** + * An implementation of {@link SignalSessionLock} that effectively re-uses our database lock. + */ +public enum DatabaseSessionLock implements SignalSessionLock { + + INSTANCE; + + @Override + public Lock acquire() { + SQLiteDatabase db = DatabaseFactory.getInstance(ApplicationDependencies.getApplication()).getRawDatabase(); + + if (db.isDbLockedByCurrentThread()) { + return () -> {}; + } + + db.beginTransaction(); + + return () -> { + db.setTransactionSuccessful(); + db.endTransaction(); + }; + } +} 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 6577231c4..dd3a7f629 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 @@ -5,6 +5,7 @@ import android.content.Context; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.SessionUtil; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; @@ -18,6 +19,7 @@ import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.state.IdentityKeyStore; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalSessionLock; import java.util.concurrent.TimeUnit; @@ -45,7 +47,7 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { } public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) { - synchronized (LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); Recipient recipient = Recipient.external(context, address.getName()); Optional identityRecord = identityDatabase.getIdentity(recipient.getId()); @@ -91,7 +93,7 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { @Override public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { - synchronized (LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) { IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); RecipientId ourRecipientId = Recipient.self().getId(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java index 3408be670..e0adb1319 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecurePreKeyStore.java @@ -4,12 +4,14 @@ import android.content.Context; import androidx.annotation.NonNull; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.whispersystems.libsignal.InvalidKeyIdException; import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.PreKeyStore; import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyStore; +import org.whispersystems.signalservice.api.SignalSessionLock; import java.util.List; @@ -18,8 +20,6 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore { @SuppressWarnings("unused") private static final String TAG = TextSecurePreKeyStore.class.getSimpleName(); - private static final Object FILE_LOCK = new Object(); - @NonNull private final Context context; @@ -29,7 +29,7 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore { @Override public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { PreKeyRecord preKeyRecord = DatabaseFactory.getPreKeyDatabase(context).getPreKey(preKeyId); if (preKeyRecord == null) throw new InvalidKeyIdException("No such key: " + preKeyId); @@ -39,7 +39,7 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore { @Override public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { SignedPreKeyRecord signedPreKeyRecord = DatabaseFactory.getSignedPreKeyDatabase(context).getSignedPreKey(signedPreKeyId); if (signedPreKeyRecord == null) throw new InvalidKeyIdException("No such signed prekey: " + signedPreKeyId); @@ -49,21 +49,21 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore { @Override public List loadSignedPreKeys() { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { return DatabaseFactory.getSignedPreKeyDatabase(context).getAllSignedPreKeys(); } } @Override public void storePreKey(int preKeyId, PreKeyRecord record) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { DatabaseFactory.getPreKeyDatabase(context).insertPreKey(preKeyId, record); } } @Override public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { DatabaseFactory.getSignedPreKeyDatabase(context).insertSignedPreKey(signedPreKeyId, record); } } @@ -87,5 +87,4 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore { public void removeSignedPreKey(int signedPreKeyId) { DatabaseFactory.getSignedPreKeyDatabase(context).removeSignedPreKey(signedPreKeyId); } - } diff --git a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java index f06edd332..d56f43eba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java +++ b/app/src/main/java/org/thoughtcrime/securesms/crypto/storage/TextSecureSessionStore.java @@ -5,6 +5,7 @@ import android.content.Context; import androidx.annotation.NonNull; import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.SessionDatabase; import org.thoughtcrime.securesms.recipients.Recipient; @@ -13,6 +14,7 @@ import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.protocol.CiphertextMessage; import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.signalservice.api.SignalServiceSessionStore; +import org.whispersystems.signalservice.api.SignalSessionLock; import java.util.Collections; import java.util.List; @@ -21,8 +23,6 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { private static final String TAG = TextSecureSessionStore.class.getSimpleName(); - private static final Object FILE_LOCK = new Object(); - @NonNull private final Context context; public TextSecureSessionStore(@NonNull Context context) { @@ -31,7 +31,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { @Override public SessionRecord loadSession(@NonNull SignalProtocolAddress address) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { RecipientId recipientId = Recipient.external(context, address.getName()).getId(); SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(recipientId, address.getDeviceId()); @@ -46,7 +46,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { @Override public void storeSession(@NonNull SignalProtocolAddress address, @NonNull SessionRecord record) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { RecipientId id = Recipient.external(context, address.getName()).getId(); DatabaseFactory.getSessionDatabase(context).store(id, address.getDeviceId(), record); } @@ -54,7 +54,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { @Override public boolean containsSession(SignalProtocolAddress address) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) { RecipientId recipientId = Recipient.external(context, address.getName()).getId(); SessionRecord sessionRecord = DatabaseFactory.getSessionDatabase(context).load(recipientId, address.getDeviceId()); @@ -70,7 +70,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { @Override public void deleteSession(SignalProtocolAddress address) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) { RecipientId recipientId = Recipient.external(context, address.getName()).getId(); DatabaseFactory.getSessionDatabase(context).delete(recipientId, address.getDeviceId()); @@ -82,7 +82,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { @Override public void deleteAllSessions(String name) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(name)) { RecipientId recipientId = Recipient.external(context, name).getId(); DatabaseFactory.getSessionDatabase(context).deleteAllFor(recipientId); @@ -92,7 +92,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { @Override public List getSubDeviceSessions(String name) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(name)) { RecipientId recipientId = Recipient.external(context, name).getId(); return DatabaseFactory.getSessionDatabase(context).getSubDevices(recipientId); @@ -105,7 +105,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { @Override public void archiveSession(SignalProtocolAddress address) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) { RecipientId recipientId = Recipient.external(context, address.getName()).getId(); archiveSession(recipientId, address.getDeviceId()); @@ -114,7 +114,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { } public void archiveSession(@NonNull RecipientId recipientId, int deviceId) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { SessionRecord session = DatabaseFactory.getSessionDatabase(context).load(recipientId, deviceId); if (session != null) { session.archiveCurrentState(); @@ -124,7 +124,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { } public void archiveSiblingSessions(@NonNull SignalProtocolAddress address) { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) { RecipientId recipientId = Recipient.external(context, address.getName()).getId(); List sessions = DatabaseFactory.getSessionDatabase(context).getAllFor(recipientId); @@ -142,7 +142,7 @@ public class TextSecureSessionStore implements SignalServiceSessionStore { } public void archiveAllSessions() { - synchronized (FILE_LOCK) { + try (SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { List sessions = DatabaseFactory.getSessionDatabase(context).getAll(); for (SessionDatabase.SessionRow row : sessions) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseLock.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseLock.java new file mode 100644 index 000000000..00c655afc --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseLock.java @@ -0,0 +1,32 @@ +package org.thoughtcrime.securesms.database; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import net.sqlcipher.database.SQLiteDatabase; + +import java.io.Closeable; + +public final class DatabaseLock { + + public static @NonNull Lock acquire(@NonNull Context context) { + SQLiteDatabase db = DatabaseFactory.getInstance(context).getRawDatabase(); + + if (db.isDbLockedByCurrentThread()) { + return () -> {}; + } + + db.beginTransaction(); + + return () -> { + db.setTransactionSuccessful(); + db.endTransaction(); + }; + } + + public interface Lock extends Closeable { + @Override + void close(); + } +} 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 6a76cfc86..44ca781a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java @@ -4,27 +4,20 @@ import android.app.Application; import android.content.Context; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import org.greenrobot.eventbus.EventBus; import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.BuildConfig; import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; -import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.database.DatabaseObserver; import org.thoughtcrime.securesms.database.JobDatabase; -import org.thoughtcrime.securesms.events.ReminderUpdateEvent; -import org.thoughtcrime.securesms.jobmanager.Constraint; -import org.thoughtcrime.securesms.jobmanager.ConstraintObserver; -import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.JobMigrator; import org.thoughtcrime.securesms.jobmanager.impl.FactoryJobPredicate; import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; -import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage; import org.thoughtcrime.securesms.jobs.FastJobStorage; import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob; import org.thoughtcrime.securesms.jobs.JobManagerFactories; @@ -36,7 +29,6 @@ import org.thoughtcrime.securesms.jobs.PushProcessMessageJob; import org.thoughtcrime.securesms.jobs.PushTextSendJob; import org.thoughtcrime.securesms.jobs.ReactionSendJob; import org.thoughtcrime.securesms.jobs.TypingSendJob; -import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.megaphone.MegaphoneRepository; import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever; import org.thoughtcrime.securesms.messages.IncomingMessageObserver; @@ -57,23 +49,18 @@ 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.thoughtcrime.securesms.util.concurrent.SettableFuture; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceMessageReceiver; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations; import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations; -import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException; import org.whispersystems.signalservice.api.util.CredentialsProvider; import org.whispersystems.signalservice.api.util.SleepTimer; import org.whispersystems.signalservice.api.util.UptimeSleepTimer; -import org.whispersystems.signalservice.api.websocket.ConnectivityListener; import java.util.UUID; -import okhttp3.Response; - /** * Implementation of {@link ApplicationDependencies.Provider} that provides real app dependencies. */ @@ -117,6 +104,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr return new SignalServiceMessageSender(provideSignalServiceNetworkAccess().getConfiguration(context), new DynamicCredentialsProvider(context), new SignalProtocolStoreImpl(context), + DatabaseSessionLock.INSTANCE, BuildConfig.SIGNAL_AGENT, TextSecurePreferences.isMultiDevice(context), Optional.fromNullable(IncomingMessageObserver.getPipe()), diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java index 88d337d97..3234aba15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.jobs; import android.app.PendingIntent; import android.content.Context; -import android.content.Intent; import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; @@ -26,6 +25,7 @@ import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.PushDatabase; @@ -111,7 +111,11 @@ public final class PushDecryptMessageJob extends BaseJob { JobManager jobManager = ApplicationDependencies.getJobManager(); try { - List jobs = handleMessage(envelope); + List jobs; + + try (DatabaseSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { + jobs = handleMessage(envelope); + } for (Job job: jobs) { jobManager.add(job); @@ -156,7 +160,7 @@ public final class PushDecryptMessageJob extends BaseJob { try { SignalProtocolStore axolotlStore = new SignalProtocolStoreImpl(context); SignalServiceAddress localAddress = new SignalServiceAddress(Optional.of(TextSecurePreferences.getLocalUuid(context)), Optional.of(TextSecurePreferences.getLocalNumber(context))); - SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, UnidentifiedAccessUtil.getCertificateValidator()); + SignalServiceCipher cipher = new SignalServiceCipher(localAddress, axolotlStore, DatabaseSessionLock.INSTANCE, UnidentifiedAccessUtil.getCertificateValidator()); SignalServiceContent content = cipher.decrypt(envelope); 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 fb17ad13a..9c0d1c597 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -9,6 +9,7 @@ import androidx.annotation.StringRes; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.crypto.DatabaseSessionLock; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -35,12 +36,11 @@ 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; +import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage; import java.util.List; -import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK; - public class IdentityUtil { private static final String TAG = IdentityUtil.class.getSimpleName(); @@ -147,7 +147,7 @@ public class IdentityUtil { } public static void saveIdentity(Context context, String user, IdentityKey identityKey) { - synchronized (SESSION_LOCK) { + try(SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context); SessionStore sessionStore = new TextSecureSessionStore(context); SignalProtocolAddress address = new SignalProtocolAddress(user, 1); @@ -164,7 +164,7 @@ public class IdentityUtil { } public static void processVerifiedMessage(Context context, VerifiedMessage verifiedMessage) { - synchronized (SESSION_LOCK) { + try(SignalSessionLock.Lock unused = DatabaseSessionLock.INSTANCE.acquire()) { IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); Recipient recipient = Recipient.externalPush(context, verifiedMessage.getDestination()); Optional identityRecord = identityDatabase.getIdentity(recipient.getId()); diff --git a/app/witness-verifications.gradle b/app/witness-verifications.gradle index 011c5bed9..7ed8cc44e 100644 --- a/app/witness-verifications.gradle +++ b/app/witness-verifications.gradle @@ -426,11 +426,11 @@ dependencyVerification { ['org.threeten:threetenbp:1.3.6', 'f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7'], - ['org.whispersystems:signal-client-android:0.1.6', + ['org.whispersystems:signal-client-android:0.1.7', '1fade2c159934cd34782474fc4a1010b822e7cd22026ac5da1b25098c99ad6f6'], - ['org.whispersystems:signal-client-java:0.1.6', - '34f79d5dd063c70d93570734b6e5649ad6176d9288e6beb382b2d62692e8952c'], + ['org.whispersystems:signal-client-java:0.1.7', + '59dd701f9564c2130177ddaca374d14ce54927075955288f85ff8f55565a78f0'], ['pl.tajchert:waitingdots:0.1.0', '2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c'], diff --git a/libsignal/service/build.gradle b/libsignal/service/build.gradle index 197544e49..a8ced4383 100644 --- a/libsignal/service/build.gradle +++ b/libsignal/service/build.gradle @@ -21,7 +21,7 @@ dependencies { api 'com.googlecode.libphonenumber:libphonenumber:8.12.17' api 'com.fasterxml.jackson.core:jackson-databind:2.9.9.2' - api 'org.whispersystems:signal-client-java:0.1.6' + api 'org.whispersystems:signal-client-java:0.1.7' api 'com.squareup.okhttp3:okhttp:3.12.10' implementation 'org.threeten:threetenbp:1.3.6' diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java index eaf625974..f695d6005 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceMessageSender.java @@ -14,11 +14,11 @@ import org.whispersystems.libsignal.SessionBuilder; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.logging.Log; import org.whispersystems.libsignal.state.PreKeyBundle; -import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.Pair; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.crypto.AttachmentCipherOutputStream; import org.whispersystems.signalservice.api.crypto.SignalServiceCipher; +import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; @@ -120,6 +120,7 @@ public class SignalServiceMessageSender { private final PushServiceSocket socket; private final SignalServiceProtocolStore store; + private final SignalSessionLock sessionLock; private final SignalServiceAddress localAddress; private final Optional eventListener; @@ -144,6 +145,7 @@ public class SignalServiceMessageSender { public SignalServiceMessageSender(SignalServiceConfiguration urls, UUID uuid, String e164, String password, SignalServiceProtocolStore store, + SignalSessionLock sessionLock, String signalAgent, boolean isMultiDevice, Optional pipe, @@ -153,12 +155,13 @@ public class SignalServiceMessageSender { ExecutorService executor, boolean automaticNetworkRetry) { - this(urls, new StaticCredentialsProvider(uuid, e164, password), store, signalAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, clientZkProfileOperations, executor, 0, automaticNetworkRetry); + this(urls, new StaticCredentialsProvider(uuid, e164, password), store, sessionLock, signalAgent, isMultiDevice, pipe, unidentifiedPipe, eventListener, clientZkProfileOperations, executor, 0, automaticNetworkRetry); } public SignalServiceMessageSender(SignalServiceConfiguration urls, CredentialsProvider credentialsProvider, SignalServiceProtocolStore store, + SignalSessionLock sessionLock, String signalAgent, boolean isMultiDevice, Optional pipe, @@ -171,6 +174,7 @@ public class SignalServiceMessageSender { { this.socket = new PushServiceSocket(urls, credentialsProvider, signalAgent, clientZkProfileOperations, automaticNetworkRetry); this.store = store; + this.sessionLock = sessionLock; this.localAddress = new SignalServiceAddress(credentialsProvider.getUuid(), credentialsProvider.getE164()); this.pipe = new AtomicReference<>(pipe); this.unidentifiedPipe = new AtomicReference<>(unidentifiedPipe); @@ -1549,7 +1553,7 @@ public class SignalServiceMessageSender { throws IOException, InvalidKeyException, UntrustedIdentityException { SignalProtocolAddress signalProtocolAddress = new SignalProtocolAddress(recipient.getIdentifier(), deviceId); - SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, null); + SignalServiceCipher cipher = new SignalServiceCipher(localAddress, store, sessionLock, null); if (!store.containsSession(signalProtocolAddress)) { try { @@ -1558,7 +1562,7 @@ public class SignalServiceMessageSender { for (PreKeyBundle preKey : preKeys) { try { SignalProtocolAddress preKeyAddress = new SignalProtocolAddress(recipient.getIdentifier(), preKey.getDeviceId()); - SessionBuilder sessionBuilder = new SessionBuilder(store, preKeyAddress); + SignalSessionBuilder sessionBuilder = new SignalSessionBuilder(sessionLock, new SessionBuilder(store, preKeyAddress)); sessionBuilder.process(preKey); } catch (org.whispersystems.libsignal.UntrustedIdentityException e) { throw new UntrustedIdentityException("Untrusted identity key!", recipient.getIdentifier(), preKey.getIdentityKey()); @@ -1598,7 +1602,7 @@ public class SignalServiceMessageSender { PreKeyBundle preKey = socket.getPreKey(recipient, missingDeviceId); try { - SessionBuilder sessionBuilder = new SessionBuilder(store, new SignalProtocolAddress(recipient.getIdentifier(), missingDeviceId)); + SignalSessionBuilder sessionBuilder = new SignalSessionBuilder(sessionLock, new SessionBuilder(store, new SignalProtocolAddress(recipient.getIdentifier(), missingDeviceId))); sessionBuilder.process(preKey); } catch (org.whispersystems.libsignal.UntrustedIdentityException e) { throw new UntrustedIdentityException("Untrusted identity key!", recipient.getIdentifier(), preKey.getIdentityKey()); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalSessionLock.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalSessionLock.java new file mode 100644 index 000000000..b974954a9 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalSessionLock.java @@ -0,0 +1,17 @@ +package org.whispersystems.signalservice.api; + +import java.io.Closeable; + +/** + * An interface to allow the injection of a lock that will be used to keep interactions with + * ecryptions/decryptions thread-safe. + */ +public interface SignalSessionLock { + + Lock acquire(); + + interface Lock extends Closeable { + @Override + void close(); + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSealedSessionCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSealedSessionCipher.java new file mode 100644 index 000000000..89361647b --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSealedSessionCipher.java @@ -0,0 +1,57 @@ +package org.whispersystems.signalservice.api.crypto; + +import org.signal.libsignal.metadata.InvalidMetadataMessageException; +import org.signal.libsignal.metadata.InvalidMetadataVersionException; +import org.signal.libsignal.metadata.ProtocolDuplicateMessageException; +import org.signal.libsignal.metadata.ProtocolInvalidKeyException; +import org.signal.libsignal.metadata.ProtocolInvalidKeyIdException; +import org.signal.libsignal.metadata.ProtocolInvalidMessageException; +import org.signal.libsignal.metadata.ProtocolInvalidVersionException; +import org.signal.libsignal.metadata.ProtocolLegacyMessageException; +import org.signal.libsignal.metadata.ProtocolNoSessionException; +import org.signal.libsignal.metadata.ProtocolUntrustedIdentityException; +import org.signal.libsignal.metadata.SealedSessionCipher; +import org.signal.libsignal.metadata.SelfSendException; +import org.signal.libsignal.metadata.certificate.CertificateValidator; +import org.signal.libsignal.metadata.certificate.SenderCertificate; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.SignalProtocolAddress; +import org.whispersystems.signalservice.api.SignalSessionLock; + +/** + * A thread-safe wrapper around {@link SealedSessionCipher}. + */ +public class SignalSealedSessionCipher { + + private final SignalSessionLock lock; + private final SealedSessionCipher cipher; + + public SignalSealedSessionCipher(SignalSessionLock lock, SealedSessionCipher cipher) { + this.lock = lock; + this.cipher = cipher; + } + + public byte[] encrypt(SignalProtocolAddress destinationAddress, SenderCertificate senderCertificate, byte[] paddedPlaintext) throws InvalidKeyException, org.whispersystems.libsignal.UntrustedIdentityException { + try (SignalSessionLock.Lock unused = lock.acquire()) { + return cipher.encrypt(destinationAddress, senderCertificate, paddedPlaintext); + } + } + + public SealedSessionCipher.DecryptionResult decrypt(CertificateValidator validator, byte[] ciphertext, long timestamp) throws InvalidMetadataMessageException, InvalidMetadataVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyException, ProtocolNoSessionException, ProtocolLegacyMessageException, ProtocolInvalidVersionException, ProtocolDuplicateMessageException, ProtocolInvalidKeyIdException, ProtocolUntrustedIdentityException, SelfSendException { + try (SignalSessionLock.Lock unused = lock.acquire()) { + return cipher.decrypt(validator, ciphertext, timestamp); + } + } + + public int getSessionVersion(SignalProtocolAddress remoteAddress) { + try (SignalSessionLock.Lock unused = lock.acquire()) { + return cipher.getSessionVersion(remoteAddress); + } + } + + public int getRemoteRegistrationId(SignalProtocolAddress remoteAddress) { + try (SignalSessionLock.Lock unused = lock.acquire()) { + return cipher.getRemoteRegistrationId(remoteAddress); + } + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java index c4e4ae6f2..27a9252eb 100644 --- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalServiceCipher.java @@ -37,6 +37,7 @@ import org.whispersystems.libsignal.protocol.PreKeySignalMessage; import org.whispersystems.libsignal.protocol.SignalMessage; import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.guava.Optional; +import org.whispersystems.signalservice.api.SignalSessionLock; import org.whispersystems.signalservice.api.messages.SignalServiceContent; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceMetadata; @@ -63,14 +64,17 @@ public class SignalServiceCipher { private static final String TAG = SignalServiceCipher.class.getSimpleName(); private final SignalProtocolStore signalProtocolStore; + private final SignalSessionLock sessionLock; private final SignalServiceAddress localAddress; private final CertificateValidator certificateValidator; public SignalServiceCipher(SignalServiceAddress localAddress, SignalProtocolStore signalProtocolStore, + SignalSessionLock sessionLock, CertificateValidator certificateValidator) { this.signalProtocolStore = signalProtocolStore; + this.sessionLock = sessionLock; this.localAddress = localAddress; this.certificateValidator = certificateValidator; } @@ -81,15 +85,15 @@ public class SignalServiceCipher { throws UntrustedIdentityException, InvalidKeyException { if (unidentifiedAccess.isPresent()) { - SealedSessionCipher sessionCipher = new SealedSessionCipher(signalProtocolStore, localAddress.getUuid().orNull(), localAddress.getNumber().orNull(), 1); - PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination)); - byte[] ciphertext = sessionCipher.encrypt(destination, unidentifiedAccess.get().getUnidentifiedCertificate(), transportDetails.getPaddedMessageBody(unpaddedMessage)); - String body = Base64.encodeBytes(ciphertext); - int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination); + SignalSealedSessionCipher sessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getUuid().orNull(), localAddress.getNumber().orNull(), 1)); + PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion(destination)); + byte[] ciphertext = sessionCipher.encrypt(destination, unidentifiedAccess.get().getUnidentifiedCertificate(), transportDetails.getPaddedMessageBody(unpaddedMessage)); + String body = Base64.encodeBytes(ciphertext); + int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(destination); return new OutgoingPushMessage(Type.UNIDENTIFIED_SENDER_VALUE, destination.getDeviceId(), remoteRegistrationId, body); } else { - SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, destination); + SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, destination)); PushTransportDetails transportDetails = new PushTransportDetails(sessionCipher.getSessionVersion()); CiphertextMessage message = sessionCipher.encrypt(transportDetails.getPaddedMessageBody(unpaddedMessage)); int remoteRegistrationId = sessionCipher.getRemoteRegistrationId(); @@ -173,23 +177,23 @@ public class SignalServiceCipher { if (envelope.isPreKeySignalMessage()) { SignalProtocolAddress sourceAddress = getPreferredProtocolAddress(signalProtocolStore, envelope.getSourceAddress(), envelope.getSourceDevice()); - SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, sourceAddress); + SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress)); paddedMessage = sessionCipher.decrypt(new PreKeySignalMessage(ciphertext)); metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false); sessionVersion = sessionCipher.getSessionVersion(); } else if (envelope.isSignalMessage()) { SignalProtocolAddress sourceAddress = getPreferredProtocolAddress(signalProtocolStore, envelope.getSourceAddress(), envelope.getSourceDevice()); - SessionCipher sessionCipher = new SessionCipher(signalProtocolStore, sourceAddress); + SignalSessionCipher sessionCipher = new SignalSessionCipher(sessionLock, new SessionCipher(signalProtocolStore, sourceAddress)); paddedMessage = sessionCipher.decrypt(new SignalMessage(ciphertext)); metadata = new SignalServiceMetadata(envelope.getSourceAddress(), envelope.getSourceDevice(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), false); sessionVersion = sessionCipher.getSessionVersion(); } else if (envelope.isUnidentifiedSender()) { - SealedSessionCipher sealedSessionCipher = new SealedSessionCipher(signalProtocolStore, localAddress.getUuid().orNull(), localAddress.getNumber().orNull(), 1); - DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp()); - SignalServiceAddress resultAddress = new SignalServiceAddress(UuidUtil.parse(result.getSenderUuid().orNull()), result.getSenderE164()); - SignalProtocolAddress protocolAddress = getPreferredProtocolAddress(signalProtocolStore, resultAddress, result.getDeviceId()); + SignalSealedSessionCipher sealedSessionCipher = new SignalSealedSessionCipher(sessionLock, new SealedSessionCipher(signalProtocolStore, localAddress.getUuid().orNull(), localAddress.getNumber().orNull(), 1)); + DecryptionResult result = sealedSessionCipher.decrypt(certificateValidator, ciphertext, envelope.getServerReceivedTimestamp()); + SignalServiceAddress resultAddress = new SignalServiceAddress(UuidUtil.parse(result.getSenderUuid().orNull()), result.getSenderE164()); + SignalProtocolAddress protocolAddress = getPreferredProtocolAddress(signalProtocolStore, resultAddress, result.getDeviceId()); paddedMessage = result.getPaddedMessage(); metadata = new SignalServiceMetadata(resultAddress, result.getDeviceId(), envelope.getTimestamp(), envelope.getServerReceivedTimestamp(), envelope.getServerDeliveredTimestamp(), true); diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionBuilder.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionBuilder.java new file mode 100644 index 000000000..76e207d19 --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionBuilder.java @@ -0,0 +1,27 @@ +package org.whispersystems.signalservice.api.crypto; + +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.SessionBuilder; +import org.whispersystems.libsignal.UntrustedIdentityException; +import org.whispersystems.libsignal.state.PreKeyBundle; +import org.whispersystems.signalservice.api.SignalSessionLock; + +/** + * A thread-safe wrapper around {@link SessionBuilder}. + */ +public class SignalSessionBuilder { + + private final SignalSessionLock lock; + private final SessionBuilder builder; + + public SignalSessionBuilder(SignalSessionLock lock, SessionBuilder builder) { + this.lock = lock; + this.builder = builder; + } + + public void process(PreKeyBundle preKey) throws InvalidKeyException, UntrustedIdentityException { + try (SignalSessionLock.Lock unused = lock.acquire()) { + builder.process(preKey); + } + } +} diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionCipher.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionCipher.java new file mode 100644 index 000000000..3c339c18a --- /dev/null +++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/crypto/SignalSessionCipher.java @@ -0,0 +1,58 @@ +package org.whispersystems.signalservice.api.crypto; + +import org.whispersystems.libsignal.DuplicateMessageException; +import org.whispersystems.libsignal.InvalidKeyException; +import org.whispersystems.libsignal.InvalidKeyIdException; +import org.whispersystems.libsignal.InvalidMessageException; +import org.whispersystems.libsignal.LegacyMessageException; +import org.whispersystems.libsignal.NoSessionException; +import org.whispersystems.libsignal.SessionCipher; +import org.whispersystems.libsignal.UntrustedIdentityException; +import org.whispersystems.libsignal.protocol.CiphertextMessage; +import org.whispersystems.libsignal.protocol.PreKeySignalMessage; +import org.whispersystems.libsignal.protocol.SignalMessage; +import org.whispersystems.signalservice.api.SignalSessionLock; + +/** + * A thread-safe wrapper around {@link SessionCipher}. + */ +public class SignalSessionCipher { + + private final SignalSessionLock lock; + private final SessionCipher cipher; + + public SignalSessionCipher(SignalSessionLock lock, SessionCipher cipher) { + this.lock = lock; + this.cipher = cipher; + } + + public CiphertextMessage encrypt(byte[] paddedMessage) throws org.whispersystems.libsignal.UntrustedIdentityException { + try (SignalSessionLock.Lock unused = lock.acquire()) { + return cipher.encrypt(paddedMessage); + } + } + + public byte[] decrypt(PreKeySignalMessage ciphertext) throws DuplicateMessageException, LegacyMessageException, InvalidMessageException, InvalidKeyIdException, InvalidKeyException, org.whispersystems.libsignal.UntrustedIdentityException { + try (SignalSessionLock.Lock unused = lock.acquire()) { + return cipher.decrypt(ciphertext); + } + } + + public byte[] decrypt(SignalMessage ciphertext) throws InvalidMessageException, DuplicateMessageException, LegacyMessageException, NoSessionException, UntrustedIdentityException { + try (SignalSessionLock.Lock unused = lock.acquire()) { + return cipher.decrypt(ciphertext); + } + } + + public int getRemoteRegistrationId() { + try (SignalSessionLock.Lock unused = lock.acquire()) { + return cipher.getRemoteRegistrationId(); + } + } + + public int getSessionVersion() { + try (SignalSessionLock.Lock unused = lock.acquire()) { + return cipher.getSessionVersion(); + } + } +} diff --git a/libsignal/service/witness-verifications.gradle b/libsignal/service/witness-verifications.gradle index f64a74135..45d83f20c 100644 --- a/libsignal/service/witness-verifications.gradle +++ b/libsignal/service/witness-verifications.gradle @@ -30,7 +30,7 @@ dependencyVerification { ['org.threeten:threetenbp:1.3.6', 'f4c23ffaaed717c3b99c003e0ee02d6d66377fd47d866fec7d971bd8644fc1a7'], - ['org.whispersystems:signal-client-java:0.1.6', - '34f79d5dd063c70d93570734b6e5649ad6176d9288e6beb382b2d62692e8952c'], + ['org.whispersystems:signal-client-java:0.1.7', + '59dd701f9564c2130177ddaca374d14ce54927075955288f85ff8f55565a78f0'], ] }