2016-08-30 00:49:49 +00:00
|
|
|
package org.thoughtcrime.securesms.util;
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
import android.os.AsyncTask;
|
2017-06-07 01:03:09 +00:00
|
|
|
import android.support.annotation.NonNull;
|
|
|
|
import android.support.annotation.Nullable;
|
|
|
|
import android.support.annotation.StringRes;
|
2016-08-30 00:49:49 +00:00
|
|
|
|
2017-06-07 01:03:09 +00:00
|
|
|
import org.thoughtcrime.securesms.R;
|
2017-06-23 20:57:38 +00:00
|
|
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
|
|
|
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
2017-07-26 16:59:15 +00:00
|
|
|
import org.thoughtcrime.securesms.database.Address;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|
|
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
2017-06-07 01:03:09 +00:00
|
|
|
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
|
|
|
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
|
|
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
2018-05-22 09:13:10 +00:00
|
|
|
import org.thoughtcrime.securesms.logging.Log;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
2016-08-30 00:49:49 +00:00
|
|
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
2017-06-07 01:03:09 +00:00
|
|
|
import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
|
2017-06-07 01:03:09 +00:00
|
|
|
import org.thoughtcrime.securesms.sms.IncomingIdentityVerifiedMessage;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
2017-06-07 01:03:09 +00:00
|
|
|
import org.thoughtcrime.securesms.sms.OutgoingIdentityDefaultMessage;
|
|
|
|
import org.thoughtcrime.securesms.sms.OutgoingIdentityVerifiedMessage;
|
|
|
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
2016-08-30 00:49:49 +00:00
|
|
|
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
|
|
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
2017-06-23 20:57:38 +00:00
|
|
|
import org.whispersystems.libsignal.IdentityKey;
|
|
|
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
|
|
|
import org.whispersystems.libsignal.state.IdentityKeyStore;
|
|
|
|
import org.whispersystems.libsignal.state.SessionRecord;
|
|
|
|
import org.whispersystems.libsignal.state.SessionStore;
|
2016-08-30 00:49:49 +00:00
|
|
|
import org.whispersystems.libsignal.util.guava.Optional;
|
2017-05-20 01:01:40 +00:00
|
|
|
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
2017-06-07 01:03:09 +00:00
|
|
|
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
2016-08-30 00:49:49 +00:00
|
|
|
|
2017-06-07 01:03:09 +00:00
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
|
|
|
|
2016-08-30 00:49:49 +00:00
|
|
|
public class IdentityUtil {
|
|
|
|
|
2017-05-20 01:01:40 +00:00
|
|
|
private static final String TAG = IdentityUtil.class.getSimpleName();
|
|
|
|
|
2017-06-07 01:03:09 +00:00
|
|
|
public static ListenableFuture<Optional<IdentityRecord>> getRemoteIdentityKey(final Context context, final Recipient recipient) {
|
|
|
|
final SettableFuture<Optional<IdentityRecord>> future = new SettableFuture<>();
|
2016-08-30 00:49:49 +00:00
|
|
|
|
2017-06-07 01:03:09 +00:00
|
|
|
new AsyncTask<Recipient, Void, Optional<IdentityRecord>>() {
|
2016-08-30 00:49:49 +00:00
|
|
|
@Override
|
2017-06-07 01:03:09 +00:00
|
|
|
protected Optional<IdentityRecord> doInBackground(Recipient... recipient) {
|
|
|
|
return DatabaseFactory.getIdentityDatabase(context)
|
2017-07-26 16:59:15 +00:00
|
|
|
.getIdentity(recipient[0].getAddress());
|
2016-08-30 00:49:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2017-06-07 01:03:09 +00:00
|
|
|
protected void onPostExecute(Optional<IdentityRecord> result) {
|
2016-08-30 00:49:49 +00:00
|
|
|
future.set(result);
|
|
|
|
}
|
2017-10-23 20:03:32 +00:00
|
|
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient);
|
2016-08-30 00:49:49 +00:00
|
|
|
|
|
|
|
return future;
|
|
|
|
}
|
|
|
|
|
2018-01-25 03:17:44 +00:00
|
|
|
public static void markIdentityVerified(Context context, Recipient recipient, boolean verified, boolean remote)
|
2017-06-07 01:03:09 +00:00
|
|
|
{
|
|
|
|
long time = System.currentTimeMillis();
|
|
|
|
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
|
|
|
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
|
|
|
GroupDatabase.Reader reader = groupDatabase.getGroups();
|
|
|
|
|
|
|
|
GroupDatabase.GroupRecord groupRecord;
|
|
|
|
|
|
|
|
while ((groupRecord = reader.getNext()) != null) {
|
2017-08-01 15:56:00 +00:00
|
|
|
if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive() && !groupRecord.isMms()) {
|
2017-06-07 01:03:09 +00:00
|
|
|
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
|
|
|
|
|
|
|
if (remote) {
|
2018-05-22 09:13:10 +00:00
|
|
|
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
|
2017-06-07 01:03:09 +00:00
|
|
|
|
|
|
|
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
|
|
|
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
|
|
|
|
|
|
|
smsDatabase.insertMessageInbox(incoming);
|
|
|
|
} else {
|
2017-08-22 01:32:38 +00:00
|
|
|
Recipient groupRecipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedId(group.getGroupId(), false)), true);
|
2017-08-01 15:56:00 +00:00
|
|
|
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
|
2017-06-07 01:03:09 +00:00
|
|
|
OutgoingTextMessage outgoing ;
|
|
|
|
|
2017-08-01 15:56:00 +00:00
|
|
|
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipient);
|
|
|
|
else outgoing = new OutgoingIdentityDefaultMessage(recipient);
|
2017-06-07 01:03:09 +00:00
|
|
|
|
2018-01-25 03:17:44 +00:00
|
|
|
DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoing, false, time, null);
|
2017-06-07 01:03:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remote) {
|
2018-05-22 09:13:10 +00:00
|
|
|
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false);
|
2017-06-07 01:03:09 +00:00
|
|
|
|
|
|
|
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
|
|
|
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
|
|
|
|
|
|
|
smsDatabase.insertMessageInbox(incoming);
|
|
|
|
} else {
|
|
|
|
OutgoingTextMessage outgoing;
|
|
|
|
|
2017-08-01 15:56:00 +00:00
|
|
|
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipient);
|
|
|
|
else outgoing = new OutgoingIdentityDefaultMessage(recipient);
|
2017-06-07 01:03:09 +00:00
|
|
|
|
2017-08-01 15:56:00 +00:00
|
|
|
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
|
2017-06-07 01:03:09 +00:00
|
|
|
|
2018-08-02 13:25:33 +00:00
|
|
|
Log.i(TAG, "Inserting verified outbox...");
|
2018-01-25 03:17:44 +00:00
|
|
|
DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(threadId, outgoing, false, time, null);
|
2017-06-07 01:03:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-20 01:01:40 +00:00
|
|
|
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();
|
|
|
|
|
|
|
|
GroupDatabase.GroupRecord groupRecord;
|
|
|
|
|
|
|
|
while ((groupRecord = reader.getNext()) != null) {
|
2017-07-26 16:59:15 +00:00
|
|
|
if (groupRecord.getMembers().contains(recipient.getAddress()) && groupRecord.isActive()) {
|
2017-05-20 01:01:40 +00:00
|
|
|
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
2018-05-22 09:13:10 +00:00
|
|
|
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.of(group), 0, false);
|
2017-05-20 01:01:40 +00:00
|
|
|
IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming);
|
|
|
|
|
|
|
|
smsDatabase.insertMessageInbox(groupUpdate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-22 09:13:10 +00:00
|
|
|
IncomingTextMessage incoming = new IncomingTextMessage(recipient.getAddress(), 1, time, null, Optional.absent(), 0, false);
|
2017-05-20 01:01:40 +00:00
|
|
|
IncomingIdentityUpdateMessage individualUpdate = new IncomingIdentityUpdateMessage(incoming);
|
|
|
|
Optional<InsertResult> insertResult = smsDatabase.insertMessageInbox(individualUpdate);
|
|
|
|
|
|
|
|
if (insertResult.isPresent()) {
|
2018-01-25 03:17:44 +00:00
|
|
|
MessageNotifier.updateNotification(context, insertResult.get().getThreadId());
|
2017-05-20 01:01:40 +00:00
|
|
|
}
|
|
|
|
}
|
2017-06-07 01:03:09 +00:00
|
|
|
|
2017-06-23 20:57:38 +00:00
|
|
|
public static void saveIdentity(Context context, String number, IdentityKey identityKey) {
|
|
|
|
synchronized (SESSION_LOCK) {
|
|
|
|
IdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context);
|
|
|
|
SessionStore sessionStore = new TextSecureSessionStore(context);
|
|
|
|
SignalProtocolAddress address = new SignalProtocolAddress(number, 1);
|
|
|
|
|
|
|
|
if (identityKeyStore.saveIdentity(address, identityKey)) {
|
|
|
|
if (sessionStore.containsSession(address)) {
|
|
|
|
SessionRecord sessionRecord = sessionStore.loadSession(address);
|
|
|
|
sessionRecord.archiveCurrentState();
|
|
|
|
|
|
|
|
sessionStore.storeSession(address, sessionRecord);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-25 03:17:44 +00:00
|
|
|
public static void processVerifiedMessage(Context context, VerifiedMessage verifiedMessage) {
|
2017-06-23 20:57:38 +00:00
|
|
|
synchronized (SESSION_LOCK) {
|
|
|
|
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
2017-08-22 01:32:38 +00:00
|
|
|
Recipient recipient = Recipient.from(context, Address.fromExternal(context, verifiedMessage.getDestination()), true);
|
2017-07-26 16:59:15 +00:00
|
|
|
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(recipient.getAddress());
|
2017-06-23 20:57:38 +00:00
|
|
|
|
|
|
|
if (!identityRecord.isPresent() && verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT) {
|
|
|
|
Log.w(TAG, "No existing record for default status");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT &&
|
|
|
|
identityRecord.isPresent() &&
|
|
|
|
identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey()) &&
|
|
|
|
identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.DEFAULT)
|
|
|
|
{
|
2017-07-26 16:59:15 +00:00
|
|
|
identityDatabase.setVerified(recipient.getAddress(), identityRecord.get().getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT);
|
2018-01-25 03:17:44 +00:00
|
|
|
markIdentityVerified(context, recipient, false, true);
|
2017-06-23 20:57:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.VERIFIED &&
|
|
|
|
(!identityRecord.isPresent() ||
|
|
|
|
(identityRecord.isPresent() && !identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey())) ||
|
|
|
|
(identityRecord.isPresent() && identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED)))
|
|
|
|
{
|
|
|
|
saveIdentity(context, verifiedMessage.getDestination(), verifiedMessage.getIdentityKey());
|
2017-07-26 16:59:15 +00:00
|
|
|
identityDatabase.setVerified(recipient.getAddress(), verifiedMessage.getIdentityKey(), IdentityDatabase.VerifiedStatus.VERIFIED);
|
2018-01-25 03:17:44 +00:00
|
|
|
markIdentityVerified(context, recipient, true, true);
|
2017-06-23 20:57:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-06-07 01:03:09 +00:00
|
|
|
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 {
|
2019-05-07 15:26:01 +00:00
|
|
|
int othersCount = recipients.size() - 2;
|
|
|
|
String nMore = context.getResources().getQuantityString(R.plurals.identity_others, othersCount, othersCount);
|
2017-06-07 01:03:09 +00:00
|
|
|
|
|
|
|
return context.getString(resourceMany, firstName, secondName, nMore);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-30 00:49:49 +00:00
|
|
|
}
|