Skip to content

Commit

Permalink
Some identity key handling changes
Browse files Browse the repository at this point in the history
1) Prefetch identity keys when possible

2) Always accept prefetched keys or keys from incoming messages

3) Block sending only if it's a recent change, or if always
   block is enabled

// FREEBIE
  • Loading branch information
moxie0 committed May 31, 2017
1 parent ca701df commit d507756
Show file tree
Hide file tree
Showing 19 changed files with 469 additions and 241 deletions.
10 changes: 5 additions & 5 deletions build.gradle
Expand Up @@ -61,7 +61,7 @@ dependencies {

compile 'org.whispersystems:jobmanager:1.0.2'
compile 'org.whispersystems:libpastelog:1.0.7'
compile 'org.whispersystems:signal-service-android:2.5.6'
compile 'org.whispersystems:signal-service-android:2.5.7'
compile 'org.whispersystems:webrtc-android:M57-S2'

compile "me.leolin:ShortcutBadger:1.1.16"
Expand Down Expand Up @@ -135,7 +135,7 @@ dependencyVerification {
'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718',
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88',
'org.whispersystems:signal-service-android:d19edb9faaa59cf9b3550942a030c8fd73d939003cda16955ed6c71209dd4d29',
'org.whispersystems:signal-service-android:ef8e97ceef05909713dd5247f52d114cb0a30c3f48e79486d2e583ee4dbb89d5',
'org.whispersystems:webrtc-android:9d11e39d4b3823713e5b1486226e0ce09f989d6f47f52da1815e406c186701d5',
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
Expand Down Expand Up @@ -169,8 +169,8 @@ dependencyVerification {
'com.google.android.gms:play-services-base:0ca636a8fc9a5af45e607cdcd61783bf5d561cbbb0f862021ce69606eee5ad49',
'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d',
'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70',
'org.whispersystems:signal-service-java:a410adf969fc80119f0e04b2c0d4fcec0f9fcca11a098b3782c02925b61dfbad',
'org.whispersystems:signal-protocol-android:1b4b9d557c8eaf861797ff683990d482d4aa8e9f23d9b17ff0cc67a02f38cb19',
'org.whispersystems:signal-service-java:640e374e8bc5d4d2c33f0e0e51b1a88283c0f97a5ea89f8c81aaa957afd78f5c',
'org.whispersystems:signal-protocol-android:b6921cd5e2f237eb523cc8e0c301b022fb5109e4c8e4dca7bb873da6efbaa939',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
Expand All @@ -181,7 +181,7 @@ dependencyVerification {
'com.fasterxml.jackson.core:jackson-databind:835097bcdd11f5bc8a08378c70d4c8054dfa4b911691cc2752063c75534d198d',
'com.squareup.okhttp3:okhttp:a992938d7203ca557cd7a116f002e8c427ec9cdae7ea852441abb8aec891f948',
'org.whispersystems:curve25519-android:bf6c34223d45d2f2813a8efcab9923caf99115115c760c9acea680bcb42d23c0',
'org.whispersystems:signal-protocol-java:a835cd0609cf116a74651bd0aa748db9392bba48c2d2af787757b8a1b50d131c',
'org.whispersystems:signal-protocol-java:e184dee4c8c1900ce152f2cc9d539c97a0e42dd5f06663cd8e26b069a289ff61',
'com.fasterxml.jackson.core:jackson-annotations:0ca408c24202a7626ec8b861e99d85eca5e38b73311dd6dd12e3e9deecc3fe94',
'com.fasterxml.jackson.core:jackson-core:cbf4604784b4de226262845447a1ad3bb38a6728cebe86562e2c5afada8be2c0',
'com.squareup.okio:okio:8c5436cadfab36bbd97db5f5c43b7bfdb5bf2f5f894ec8709b1929f14bdd010c',
Expand Down
4 changes: 2 additions & 2 deletions res/xml/preferences_app_protection.xml
Expand Up @@ -35,8 +35,8 @@

<PreferenceCategory android:title="@string/preferences_app_protection__communication">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_blocking_identity_changes"
android:defaultValue="false"
android:key="pref_approve_identity_changes"
android:title="@string/preferences_app_protection__safety_numbers_approval"
android:summary="@string/preferences_app_protecting__require_approval_of_new_safety_numbers_when_they_change"/>

Expand Down
22 changes: 11 additions & 11 deletions src/org/thoughtcrime/securesms/ConfirmIdentityDialog.java
Expand Up @@ -8,20 +8,20 @@
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.widget.TextView;

import org.thoughtcrime.securesms.crypto.MasterSecret;
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.IdentityDatabase;
import org.thoughtcrime.securesms.database.MmsAddressDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.IdentityUpdateJob;
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
Expand All @@ -30,12 +30,15 @@
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VerifySpan;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;

import java.io.IOException;

import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;

public class ConfirmIdentityDialog extends AlertDialog {

private static final String TAG = ConfirmIdentityDialog.class.getSimpleName();
Expand Down Expand Up @@ -102,20 +105,17 @@ public void onClick(DialogInterface dialog, int which) {
{
@Override
protected Void doInBackground(Void... params) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());

identityDatabase.saveIdentity(mismatch.getRecipientId(),
mismatch.getIdentityKey());
synchronized (SESSION_LOCK) {
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(number, 1);

new TextSecureSessionStore(getContext()).deleteAllSessions(number);
if (new TextSecureIdentityKeyStore(getContext()).saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true, true)) {
new TextSecureSessionStore(getContext()).deleteAllSessions(number);
}
}

processMessageRecord(messageRecord);
processPendingMessageRecords(messageRecord.getThreadId(), mismatch);

ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new IdentityUpdateJob(getContext(), mismatch.getRecipientId()));

return null;
}

Expand Down
20 changes: 20 additions & 0 deletions src/org/thoughtcrime/securesms/ConversationActivity.java
Expand Up @@ -105,6 +105,7 @@
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
import org.thoughtcrime.securesms.mms.AudioSlide;
Expand Down Expand Up @@ -260,6 +261,7 @@ public void onSuccess(Boolean result) {
initializeDraft();
}
});
initializeProfiles();
}

@Override
Expand Down Expand Up @@ -315,6 +317,7 @@ protected void onResume() {

MessageNotifier.setVisibleThread(threadId);
markThreadAsRead();
markIdentitySeen();

Log.w(TAG, "onResume() Finished: " + (System.currentTimeMillis() - getIntent().getLongExtra(TIMING_EXTRA, 0)));
}
Expand Down Expand Up @@ -1142,6 +1145,12 @@ private void initializeResources() {
recipients.addListener(this);
}

private void initializeProfiles() {
ApplicationContext.getInstance(this)
.getJobManager()
.add(new RetrieveProfileJob(this, recipients));
}

@Override
public void onModified(final Recipients recipients) {
titleView.post(new Runnable() {
Expand Down Expand Up @@ -1434,6 +1443,17 @@ protected Void doInBackground(Long... params) {
}.execute(threadId);
}

private void markIdentitySeen() {
new AsyncTask<Recipient, Void, Void>() {
@Override
protected Void doInBackground(Recipient... params) {
DatabaseFactory.getIdentityDatabase(ConversationActivity.this)
.setSeen(params[0].getRecipientId());
return null;
}
}.execute(recipients.getPrimaryRecipient());
}

protected void sendComplete(long threadId) {
boolean refreshFragment = (threadId != this.threadId);
this.threadId = threadId;
Expand Down
14 changes: 10 additions & 4 deletions src/org/thoughtcrime/securesms/WebRtcCallActivity.java
Expand Up @@ -38,8 +38,8 @@
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallControls;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallScreen;
import org.thoughtcrime.securesms.components.webrtc.WebRtcIncomingCallOverlay;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.recipients.Recipient;
Expand All @@ -49,6 +49,9 @@
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.SignalProtocolAddress;

import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;

public class WebRtcCallActivity extends Activity {

Expand Down Expand Up @@ -254,8 +257,11 @@ private void handleUntrustedIdentity(@NonNull WebRtcViewModel event) {
callScreen.setAcceptIdentityListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(WebRtcCallActivity.this);
identityDatabase.saveIdentity(recipient.getRecipientId(), theirIdentity);
synchronized (SESSION_LOCK) {
if (new TextSecureIdentityKeyStore(WebRtcCallActivity.this).saveIdentity(new SignalProtocolAddress(recipient.getNumber(), 1), theirIdentity, true, true)) {
new TextSecureSessionStore(WebRtcCallActivity.this).deleteAllSessions(recipient.getNumber());
}
}

Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_NUMBER, recipient.getNumber());
Expand Down
Expand Up @@ -42,13 +42,13 @@ public int getLocalRegistrationId() {
}

@Override
public void saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
identityKeyStore.saveIdentity(address, identityKey);
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
return identityKeyStore.saveIdentity(address, identityKey);
}

@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
return identityKeyStore.isTrustedIdentity(address, identityKey);
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
}

@Override
Expand Down
@@ -1,21 +1,31 @@
package org.thoughtcrime.securesms.crypto.storage;

import android.content.Context;
import android.util.Log;

import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.jobs.IdentityUpdateJob;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.IdentityUtil;
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.concurrent.TimeUnit;

public class TextSecureIdentityKeyStore implements IdentityKeyStore {

private static final int TIMESTAMP_THRESHOLD_SECONDS = 5;

private static final String TAG = TextSecureIdentityKeyStore.class.getSimpleName();
private static final Object LOCK = new Object();

private final Context context;

public TextSecureIdentityKeyStore(Context context) {
Expand All @@ -32,31 +42,96 @@ public int getLocalRegistrationId() {
return TextSecurePreferences.getLocalRegistrationId(context);
}

public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey,
boolean blockingApproval, boolean nonBlockingApproval)
{
synchronized (LOCK) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address.getName(), true);
long recipientId = recipients.getPrimaryRecipient().getRecipientId();
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(recipientId);

if (!identityRecord.isPresent()) {
Log.w(TAG, "Saving new identity...");
identityDatabase.saveIdentity(recipientId, identityKey, true, System.currentTimeMillis(), blockingApproval, nonBlockingApproval);
return false;
}

if (!identityRecord.get().getIdentityKey().equals(identityKey)) {
Log.w(TAG, "Replacing existing identity...");
identityDatabase.saveIdentity(recipientId, identityKey, false, System.currentTimeMillis(), blockingApproval, nonBlockingApproval);
IdentityUtil.markIdentityUpdate(context, recipients.getPrimaryRecipient());
return true;
}

if (isBlockingApprovalRequired(identityRecord.get()) || isNonBlockingApprovalRequired(identityRecord.get())) {
Log.w(TAG, "Setting approval status...");
identityDatabase.setApproval(recipientId, blockingApproval, nonBlockingApproval);
return false;
}

return false;
}
}

@Override
public void saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
long recipientId = RecipientFactory.getRecipientsFromString(context, address.getName(), true).getPrimaryRecipient().getRecipientId();
DatabaseFactory.getIdentityDatabase(context).saveIdentity(recipientId, identityKey);
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
return saveIdentity(address, identityKey, !TextSecurePreferences.isSendingIdentityApprovalRequired(context), false);
}

@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
long recipientId = RecipientFactory.getRecipientsFromString(context, address.getName(), true).getPrimaryRecipient().getRecipientId();
boolean trusted = DatabaseFactory.getIdentityDatabase(context)
.isValidIdentity(recipientId, identityKey);
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
synchronized (LOCK) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
long recipientId = RecipientFactory.getRecipientsFromString(context, address.getName(), true).getPrimaryRecipient().getRecipientId();
String ourNumber = TextSecurePreferences.getLocalNumber(context);
long ourRecipientId = RecipientFactory.getRecipientsFromString(context, ourNumber, true).getPrimaryRecipient().getRecipientId();

if (trusted) {
return true;
} else if (!TextSecurePreferences.isBlockingIdentityUpdates(context)) {
saveIdentity(address, identityKey);
new TextSecureSessionStore(context).deleteAllSessions(address.getName());
if (ourRecipientId == recipientId || ourNumber.equals(address.getName())) {
return identityKey.equals(IdentityKeyUtil.getIdentityKey(context));
}

ApplicationContext.getInstance(context)
.getJobManager()
.add(new IdentityUpdateJob(context, recipientId));
switch (direction) {
case SENDING: return isTrustedForSending(identityKey, identityDatabase.getIdentity(recipientId));
case RECEIVING: return true;
default: throw new AssertionError("Unknown direction: " + direction);
}
}
}

private boolean isTrustedForSending(IdentityKey identityKey, Optional<IdentityRecord> identityRecord) {
if (!identityRecord.isPresent()) {
Log.w(TAG, "Nothing here, returning true...");
return true;
} else {
}

if (!identityKey.equals(identityRecord.get().getIdentityKey())) {
Log.w(TAG, "Identity keys don't match...");
return false;
}

if (isBlockingApprovalRequired(identityRecord.get())) {
Log.w(TAG, "Needs blocking approval!");
return false;
}

if (isNonBlockingApprovalRequired(identityRecord.get())) {
Log.w(TAG, "Needs non-blocking approval!");
return false;
}

return true;
}

private boolean isBlockingApprovalRequired(IdentityRecord identityRecord) {
return !identityRecord.isFirstUse() &&
TextSecurePreferences.isSendingIdentityApprovalRequired(context) &&
!identityRecord.isApprovedBlocking();
}

private boolean isNonBlockingApprovalRequired(IdentityRecord identityRecord) {
return !identityRecord.isFirstUse() &&
System.currentTimeMillis() - identityRecord.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS) &&
!identityRecord.isApprovedNonBlocking();
}
}
14 changes: 13 additions & 1 deletion src/org/thoughtcrime/securesms/database/DatabaseFactory.java
Expand Up @@ -78,7 +78,8 @@ public class DatabaseFactory {
private static final int INTRODUCED_DOCUMENTS = 32;
private static final int INTRODUCED_FAST_PREFLIGHT = 33;
private static final int INTRODUCED_VOICE_NOTES = 34;
private static final int DATABASE_VERSION = 34;
private static final int INTRODUCED_IDENTITY_TIMESTAMP = 35;
private static final int DATABASE_VERSION = 35;

private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
Expand Down Expand Up @@ -867,6 +868,17 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("ALTER TABLE part ADD COLUMN voice_note INTEGER DEFAULT 0");
}

if (oldVersion < INTRODUCED_IDENTITY_TIMESTAMP) {
db.execSQL("ALTER TABLE identities ADD COLUMN timestamp INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE identities ADD COLUMN first_use INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE identities ADD COLUMN seen INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE identities ADD COLUMN blocking_approval INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE identities ADD COLUMN nonblocking_approval INTEGER DEFAULT 0");

db.execSQL("DROP INDEX archived_count_index");
db.execSQL("CREATE INDEX IF NOT EXISTS archived_count_index ON thread (archived, message_count)");
}

db.setTransactionSuccessful();
db.endTransaction();
}
Expand Down

0 comments on commit d507756

Please sign in to comment.