mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-24 02:00:40 +00:00
Add support for SN verification
// FREEBIE
This commit is contained in:
@@ -2,61 +2,59 @@ package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingIdentityVerifiedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingIdentityDefaultMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingIdentityVerifiedMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SessionStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
public class IdentityUtil {
|
||||
|
||||
private static final String TAG = IdentityUtil.class.getSimpleName();
|
||||
|
||||
@UiThread
|
||||
public static ListenableFuture<Optional<IdentityKey>> getRemoteIdentityKey(final Context context,
|
||||
final MasterSecret masterSecret,
|
||||
final Recipient recipient)
|
||||
{
|
||||
final SettableFuture<Optional<IdentityKey>> future = new SettableFuture<>();
|
||||
public static ListenableFuture<Optional<IdentityRecord>> getRemoteIdentityKey(final Context context, final Recipient recipient) {
|
||||
final SettableFuture<Optional<IdentityRecord>> future = new SettableFuture<>();
|
||||
|
||||
new AsyncTask<Recipient, Void, Optional<IdentityKey>>() {
|
||||
new AsyncTask<Recipient, Void, Optional<IdentityRecord>>() {
|
||||
@Override
|
||||
protected Optional<IdentityKey> doInBackground(Recipient... recipient) {
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(recipient[0].getNumber(), SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||
SessionRecord record = sessionStore.loadSession(axolotlAddress);
|
||||
|
||||
if (record == null) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
return Optional.fromNullable(record.getSessionState().getRemoteIdentityKey());
|
||||
protected Optional<IdentityRecord> doInBackground(Recipient... recipient) {
|
||||
return DatabaseFactory.getIdentityDatabase(context)
|
||||
.getIdentity(recipient[0].getRecipientId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Optional<IdentityKey> result) {
|
||||
protected void onPostExecute(Optional<IdentityRecord> result) {
|
||||
future.set(result);
|
||||
}
|
||||
}.execute(recipient);
|
||||
@@ -64,6 +62,102 @@ public class IdentityUtil {
|
||||
return future;
|
||||
}
|
||||
|
||||
public static void markIdentityVerified(Context context, MasterSecretUnion masterSecret,
|
||||
Recipient recipient, boolean verified, boolean remote)
|
||||
{
|
||||
long time = System.currentTimeMillis();
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
Recipients recipients = RecipientFactory.getRecipientsFor(context, recipient, true);
|
||||
GroupDatabase.Reader reader = groupDatabase.getGroups();
|
||||
|
||||
String number = recipient.getNumber();
|
||||
|
||||
try {
|
||||
number = Util.canonicalizeNumber(context, number);
|
||||
} catch (InvalidNumberException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
GroupDatabase.GroupRecord groupRecord;
|
||||
|
||||
while ((groupRecord = reader.getNext()) != null) {
|
||||
if (groupRecord.getMembers().contains(number) && groupRecord.isActive()) {
|
||||
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
||||
|
||||
if (remote) {
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(number, 1, time, null, Optional.of(group), 0);
|
||||
|
||||
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
||||
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
||||
|
||||
smsDatabase.insertMessageInbox(incoming);
|
||||
} else {
|
||||
Recipients groupRecipients = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(group.getGroupId()), true);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients);
|
||||
OutgoingTextMessage outgoing ;
|
||||
|
||||
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipients);
|
||||
else outgoing = new OutgoingIdentityDefaultMessage(recipients);
|
||||
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageOutbox(masterSecret, threadId, outgoing, false, time, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(number, 1, time, null, Optional.<SignalServiceGroup>absent(), 0);
|
||||
|
||||
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
||||
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
||||
|
||||
smsDatabase.insertMessageInbox(incoming);
|
||||
} else {
|
||||
OutgoingTextMessage outgoing;
|
||||
|
||||
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipients);
|
||||
else outgoing = new OutgoingIdentityDefaultMessage(recipients);
|
||||
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
|
||||
Log.w(TAG, "Inserting verified outbox...");
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context)
|
||||
.insertMessageOutbox(masterSecret, threadId, outgoing, false, time, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void processVerifiedMessage(Context context, MasterSecretUnion masterSecret, VerifiedMessage verifiedMessage) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
Recipient recipient = RecipientFactory.getRecipientsFromString(context, verifiedMessage.getDestination(), true).getPrimaryRecipient();
|
||||
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(recipient.getRecipientId());
|
||||
|
||||
if (!identityRecord.isPresent() && verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT) {
|
||||
Log.w(TAG, "No existing record for default status");
|
||||
return;
|
||||
}
|
||||
|
||||
if (identityRecord.isPresent() &&
|
||||
identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey()) &&
|
||||
identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.DEFAULT &&
|
||||
verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT)
|
||||
{
|
||||
identityDatabase.setVerified(recipient.getRecipientId(), identityRecord.get().getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
markIdentityVerified(context, masterSecret, recipient, false, true);
|
||||
}
|
||||
|
||||
if (verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.VERIFIED &&
|
||||
(!identityRecord.isPresent() ||
|
||||
(identityRecord.isPresent() && !identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey())) ||
|
||||
(identityRecord.isPresent() && identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED)))
|
||||
{
|
||||
identityDatabase.saveIdentity(recipient.getRecipientId(), verifiedMessage.getIdentityKey(),
|
||||
IdentityDatabase.VerifiedStatus.VERIFIED, false, System.currentTimeMillis(), true);
|
||||
markIdentityVerified(context, masterSecret, recipient, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void markIdentityUpdate(Context context, Recipient recipient) {
|
||||
long time = System.currentTimeMillis();
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
@@ -98,4 +192,62 @@ public class IdentityUtil {
|
||||
MessageNotifier.updateNotification(context, null, insertResult.get().getThreadId());
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable String getUnverifiedBannerDescription(@NonNull Context context,
|
||||
@NonNull List<Recipient> unverified)
|
||||
{
|
||||
return getPluralizedIdentityDescription(context, unverified,
|
||||
R.string.IdentityUtil_unverified_banner_one,
|
||||
R.string.IdentityUtil_unverified_banner_two,
|
||||
R.string.IdentityUtil_unverified_banner_many);
|
||||
}
|
||||
|
||||
public static @Nullable String getUnverifiedSendDialogDescription(@NonNull Context context,
|
||||
@NonNull List<Recipient> unverified)
|
||||
{
|
||||
return getPluralizedIdentityDescription(context, unverified,
|
||||
R.string.IdentityUtil_unverified_dialog_one,
|
||||
R.string.IdentityUtil_unverified_dialog_two,
|
||||
R.string.IdentityUtil_unverified_dialog_many);
|
||||
}
|
||||
|
||||
public static @Nullable String getUntrustedSendDialogDescription(@NonNull Context context,
|
||||
@NonNull List<Recipient> untrusted)
|
||||
{
|
||||
return getPluralizedIdentityDescription(context, untrusted,
|
||||
R.string.IdentityUtil_untrusted_dialog_one,
|
||||
R.string.IdentityUtil_untrusted_dialog_two,
|
||||
R.string.IdentityUtil_untrusted_dialog_many);
|
||||
}
|
||||
|
||||
private static @Nullable String getPluralizedIdentityDescription(@NonNull Context context,
|
||||
@NonNull List<Recipient> recipients,
|
||||
@StringRes int resourceOne,
|
||||
@StringRes int resourceTwo,
|
||||
@StringRes int resourceMany)
|
||||
{
|
||||
if (recipients.isEmpty()) return null;
|
||||
|
||||
if (recipients.size() == 1) {
|
||||
String name = recipients.get(0).toShortString();
|
||||
return context.getString(resourceOne, name);
|
||||
} else {
|
||||
String firstName = recipients.get(0).toShortString();
|
||||
String secondName = recipients.get(1).toShortString();
|
||||
|
||||
if (recipients.size() == 2) {
|
||||
return context.getString(resourceTwo, firstName, secondName);
|
||||
} else {
|
||||
String nMore;
|
||||
|
||||
if (recipients.size() == 3) {
|
||||
nMore = context.getResources().getQuantityString(R.plurals.identity_others, 1);
|
||||
} else {
|
||||
nMore = context.getResources().getQuantityString(R.plurals.identity_others, recipients.size() - 2);
|
||||
}
|
||||
|
||||
return context.getString(resourceMany, firstName, secondName, nMore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,9 @@ public class VerifySpan extends ClickableSpan {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
Intent intent = new Intent(context, VerifyIdentityActivity.class);
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_ID, recipientId);
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_IDENTITY, new IdentityKeyParcelable(identityKey));
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_ID_EXTRA, recipientId);
|
||||
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey));
|
||||
intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, false);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user