session-android/src/org/thoughtcrime/securesms/util/IdentityUtil.java
Moxie Marlinspike 76c28cfa7a Add support for SN verification
// FREEBIE
2017-06-09 19:58:01 -07:00

254 lines
12 KiB
Java

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.R;
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
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.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.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
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<IdentityRecord>> getRemoteIdentityKey(final Context context, final Recipient recipient) {
final SettableFuture<Optional<IdentityRecord>> future = new SettableFuture<>();
new AsyncTask<Recipient, Void, Optional<IdentityRecord>>() {
@Override
protected Optional<IdentityRecord> doInBackground(Recipient... recipient) {
return DatabaseFactory.getIdentityDatabase(context)
.getIdentity(recipient[0].getRecipientId());
}
@Override
protected void onPostExecute(Optional<IdentityRecord> result) {
future.set(result);
}
}.execute(recipient);
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);
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
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());
IncomingTextMessage incoming = new IncomingTextMessage(number, 1, time, null, Optional.of(group), 0);
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);
smsDatabase.insertMessageInbox(groupUpdate);
}
}
IncomingTextMessage incoming = new IncomingTextMessage(number, 1, time, null, Optional.<SignalServiceGroup>absent(), 0);
IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming);
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate);
if (insertResult.isPresent()) {
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);
}
}
}
}