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 9195689618..18550d4117 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 @@ -3,40 +3,37 @@ package org.thoughtcrime.securesms.crypto.storage; import android.content.Context; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.SessionUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.IdentityDatabase; +import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; -import org.thoughtcrime.securesms.database.model.IdentityStoreRecord; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.IdentityUtil; -import org.thoughtcrime.securesms.util.LRUCache; 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.Map; import java.util.concurrent.TimeUnit; public class TextSecureIdentityKeyStore implements IdentityKeyStore { - private static final String TAG = Log.tag(TextSecureIdentityKeyStore.class); + private static final int TIMESTAMP_THRESHOLD_SECONDS = 5; - private static final Object LOCK = new Object(); - private static final int TIMESTAMP_THRESHOLD_SECONDS = 5; + private static final String TAG = Log.tag(TextSecureIdentityKeyStore.class); + private static final Object LOCK = new Object(); private final Context context; - private final Cache cache; public TextSecureIdentityKeyStore(Context context) { this.context = context; - this.cache = new Cache(context); } @Override @@ -49,44 +46,40 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { return TextSecurePreferences.getLocalRegistrationId(context); } - @Override - public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { - return saveIdentity(address, identityKey, false) == SaveResult.UPDATE; - } - public @NonNull SaveResult saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) { synchronized (LOCK) { - IdentityStoreRecord identityRecord = cache.get(address.getName()); - RecipientId recipientId = RecipientId.fromExternalPush(address.getName()); + IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); + RecipientId recipientId = RecipientId.fromExternalPush(address.getName()); + Optional identityRecord = identityDatabase.getIdentity(recipientId); - if (identityRecord == null) { + if (!identityRecord.isPresent()) { Log.i(TAG, "Saving new identity..."); - cache.save(address.getName(), recipientId, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval); + identityDatabase.saveIdentity(recipientId, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval); return SaveResult.NEW; } - if (!identityRecord.getIdentityKey().equals(identityKey)) { - Log.i(TAG, "Replacing existing identity... Existing: " + identityRecord.getIdentityKey().hashCode() + " New: " + identityKey.hashCode()); + if (!identityRecord.get().getIdentityKey().equals(identityKey)) { + Log.i(TAG, "Replacing existing identity... Existing: " + identityRecord.get().getIdentityKey().hashCode() + " New: " + identityKey.hashCode()); VerifiedStatus verifiedStatus; - if (identityRecord.getVerifiedStatus() == VerifiedStatus.VERIFIED || - identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) + if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED || + identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED) { verifiedStatus = VerifiedStatus.UNVERIFIED; } else { verifiedStatus = VerifiedStatus.DEFAULT; } - cache.save(address.getName(), recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval); + identityDatabase.saveIdentity(recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval); IdentityUtil.markIdentityUpdate(context, recipientId); SessionUtil.archiveSiblingSessions(address); DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(recipientId); return SaveResult.UPDATE; } - if (isNonBlockingApprovalRequired(identityRecord)) { + if (isNonBlockingApprovalRequired(identityRecord.get())) { Log.i(TAG, "Setting approval status..."); - cache.setApproval(identityRecord, recipientId, nonBlockingApproval); + identityDatabase.setApproval(recipientId, nonBlockingApproval); return SaveResult.NON_BLOCKING_APPROVAL_REQUIRED; } @@ -94,50 +87,73 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { } } + @Override + public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { + return saveIdentity(address, identityKey, false) == SaveResult.UPDATE; + } + @Override public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) { - boolean isSelf = address.getName().equals(TextSecurePreferences.getLocalUuid(context).toString()) || - address.getName().equals(TextSecurePreferences.getLocalNumber(context)); + synchronized (LOCK) { + if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) { + IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context); + RecipientId ourRecipientId = Recipient.self().getId(); + RecipientId theirRecipientId = RecipientId.fromExternalPush(address.getName()); - if (isSelf) { - return identityKey.equals(IdentityKeyUtil.getIdentityKey(context)); - } + if (ourRecipientId.equals(theirRecipientId)) { + return identityKey.equals(IdentityKeyUtil.getIdentityKey(context)); + } - IdentityStoreRecord record = cache.get(address.getName()); - - switch (direction) { - case SENDING: - return isTrustedForSending(identityKey, record); - case RECEIVING: - return true; - default: - throw new AssertionError("Unknown direction: " + direction); + switch (direction) { + case SENDING: return isTrustedForSending(identityKey, identityDatabase.getIdentity(theirRecipientId)); + case RECEIVING: return true; + default: throw new AssertionError("Unknown direction: " + direction); + } + } else { + Log.w(TAG, "Tried to check if identity is trusted for " + address.getName() + ", but no matching recipient existed!"); + switch (direction) { + case SENDING: return false; + case RECEIVING: return true; + default: throw new AssertionError("Unknown direction: " + direction); + } + } } } @Override public IdentityKey getIdentity(SignalProtocolAddress address) { - IdentityStoreRecord record = cache.get(address.getName()); - return record != null ? record.getIdentityKey() : null; + if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) { + RecipientId recipientId = RecipientId.fromExternalPush(address.getName()); + Optional record = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipientId); + + if (record.isPresent()) { + return record.get().getIdentityKey(); + } else { + return null; + } + } else { + Log.w(TAG, "Tried to get identity for " + address.getName() + ", but no matching recipient existed!"); + return null; + } } - private boolean isTrustedForSending(@NonNull IdentityKey identityKey, @Nullable IdentityStoreRecord identityRecord) { - if (identityRecord == null) { + private boolean isTrustedForSending(IdentityKey identityKey, Optional identityRecord) { + if (!identityRecord.isPresent()) { Log.w(TAG, "Nothing here, returning true..."); return true; } - if (!identityKey.equals(identityRecord.getIdentityKey())) { - Log.w(TAG, "Identity keys don't match... service: " + identityKey.hashCode() + " database: " + identityRecord.getIdentityKey().hashCode()); + if (!identityKey.equals(identityRecord.get().getIdentityKey())) { + Log.w(TAG, "Identity keys don't match... service: " + identityKey.hashCode() + " database: " + identityRecord.get().getIdentityKey().hashCode()); return false; } - if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) { + if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED) { Log.w(TAG, "Needs unverified approval!"); return false; } - if (isNonBlockingApprovalRequired(identityRecord)) { + if (isNonBlockingApprovalRequired(identityRecord.get())) { Log.w(TAG, "Needs non-blocking approval!"); return false; } @@ -145,47 +161,10 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore { return true; } - private boolean isNonBlockingApprovalRequired(IdentityStoreRecord record) { - return !record.getFirstUse() && - !record.getNonblockingApproval() && - System.currentTimeMillis() - record.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS); - } - - private static final class Cache { - - private final Context context; - private final Map cache; - - Cache(@NonNull Context context) { - this.context = context; - this.cache = new LRUCache<>(200); - } - - public synchronized @Nullable IdentityStoreRecord get(@NonNull String addressName) { - if (cache.containsKey(addressName)) { - return cache.get(addressName); - } else { - IdentityStoreRecord record = DatabaseFactory.getIdentityDatabase(context).getIdentityStoreRecord(addressName); - cache.put(addressName, record); - return record; - } - } - - public synchronized void save(@NonNull String addressName, @NonNull RecipientId recipientId, @NonNull IdentityKey identityKey, @NonNull VerifiedStatus verifiedStatus, boolean firstUse, long timestamp, boolean nonBlockingApproval) { - DatabaseFactory.getIdentityDatabase(context).saveIdentity(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval); - cache.put(addressName, new IdentityStoreRecord(addressName, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval)); - } - - public synchronized void setApproval(@NonNull IdentityStoreRecord record, @NonNull RecipientId recipientId, boolean nonblockingApproval) { - DatabaseFactory.getIdentityDatabase(context).setApproval(record.getAddressName(), recipientId, nonblockingApproval); - cache.put(record.getAddressName(), - new IdentityStoreRecord(record.getAddressName(), - record.getIdentityKey(), - record.getVerifiedStatus(), - record.getFirstUse(), - record.getTimestamp(), - nonblockingApproval)); - } + private boolean isNonBlockingApprovalRequired(IdentityRecord identityRecord) { + return !identityRecord.isFirstUse() && + System.currentTimeMillis() - identityRecord.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS) && + !identityRecord.isApprovedNonBlocking(); } public enum SaveResult {