persist certificate trust to disk
This commit is contained in:
parent
177320d8fe
commit
0c4771e2a8
|
@ -2,7 +2,7 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "5152f8eab684376f6f4076cf392e22d7",
|
"identityHash": "bc04f3d0c58f7e50f5c7973a7a06c9eb",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "account",
|
"tableName": "account",
|
||||||
|
@ -946,6 +946,67 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"tableName": "certificate_trust",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `scope` TEXT NOT NULL, `fingerprint` BLOB NOT NULL, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "accountId",
|
||||||
|
"columnName": "accountId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "scope",
|
||||||
|
"columnName": "scope",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "fingerprint",
|
||||||
|
"columnName": "fingerprint",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_certificate_trust_accountId_scope",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"accountId",
|
||||||
|
"scope"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_certificate_trust_accountId_scope` ON `${TABLE_NAME}` (`accountId`, `scope`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "account",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"accountId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"tableName": "chat",
|
"tableName": "chat",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `type` TEXT, `archived` INTEGER NOT NULL, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `type` TEXT, `archived` INTEGER NOT NULL, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
@ -2376,7 +2437,7 @@
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5152f8eab684376f6f4076cf392e22d7')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bc04f3d0c58f7e50f5c7973a7a06c9eb')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,6 +10,7 @@ import im.conversations.android.database.dao.AvatarDao;
|
||||||
import im.conversations.android.database.dao.AxolotlDao;
|
import im.conversations.android.database.dao.AxolotlDao;
|
||||||
import im.conversations.android.database.dao.BlockingDao;
|
import im.conversations.android.database.dao.BlockingDao;
|
||||||
import im.conversations.android.database.dao.BookmarkDao;
|
import im.conversations.android.database.dao.BookmarkDao;
|
||||||
|
import im.conversations.android.database.dao.CertificateTrustDao;
|
||||||
import im.conversations.android.database.dao.ChatDao;
|
import im.conversations.android.database.dao.ChatDao;
|
||||||
import im.conversations.android.database.dao.DiscoDao;
|
import im.conversations.android.database.dao.DiscoDao;
|
||||||
import im.conversations.android.database.dao.MessageDao;
|
import im.conversations.android.database.dao.MessageDao;
|
||||||
|
@ -29,6 +30,7 @@ import im.conversations.android.database.entity.AxolotlSignedPreKeyEntity;
|
||||||
import im.conversations.android.database.entity.BlockedItemEntity;
|
import im.conversations.android.database.entity.BlockedItemEntity;
|
||||||
import im.conversations.android.database.entity.BookmarkEntity;
|
import im.conversations.android.database.entity.BookmarkEntity;
|
||||||
import im.conversations.android.database.entity.BookmarkGroupEntity;
|
import im.conversations.android.database.entity.BookmarkGroupEntity;
|
||||||
|
import im.conversations.android.database.entity.CertificateTrustEntity;
|
||||||
import im.conversations.android.database.entity.ChatEntity;
|
import im.conversations.android.database.entity.ChatEntity;
|
||||||
import im.conversations.android.database.entity.DiscoEntity;
|
import im.conversations.android.database.entity.DiscoEntity;
|
||||||
import im.conversations.android.database.entity.DiscoExtensionEntity;
|
import im.conversations.android.database.entity.DiscoExtensionEntity;
|
||||||
|
@ -63,6 +65,7 @@ import im.conversations.android.database.entity.RosterItemGroupEntity;
|
||||||
BlockedItemEntity.class,
|
BlockedItemEntity.class,
|
||||||
BookmarkEntity.class,
|
BookmarkEntity.class,
|
||||||
BookmarkGroupEntity.class,
|
BookmarkGroupEntity.class,
|
||||||
|
CertificateTrustEntity.class,
|
||||||
ChatEntity.class,
|
ChatEntity.class,
|
||||||
DiscoEntity.class,
|
DiscoEntity.class,
|
||||||
DiscoExtensionEntity.class,
|
DiscoExtensionEntity.class,
|
||||||
|
@ -114,6 +117,8 @@ public abstract class ConversationsDatabase extends RoomDatabase {
|
||||||
|
|
||||||
public abstract BookmarkDao bookmarkDao();
|
public abstract BookmarkDao bookmarkDao();
|
||||||
|
|
||||||
|
public abstract CertificateTrustDao certificateTrustDao();
|
||||||
|
|
||||||
public abstract ChatDao chatDao();
|
public abstract ChatDao chatDao();
|
||||||
|
|
||||||
public abstract DiscoDao discoDao();
|
public abstract DiscoDao discoDao();
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package im.conversations.android.database.dao;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.OnConflictStrategy;
|
||||||
|
import androidx.room.Query;
|
||||||
|
import im.conversations.android.database.entity.CertificateTrustEntity;
|
||||||
|
import im.conversations.android.database.model.Account;
|
||||||
|
import im.conversations.android.tls.ScopeFingerprint;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class CertificateTrustDao {
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
public abstract void insert(final CertificateTrustEntity certificateTrustEntity);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT EXISTS (SELECT id FROM certificate_trust WHERE accountId=:account AND"
|
||||||
|
+ " scope=:scope AND fingerprint=:fingerprint)")
|
||||||
|
protected abstract boolean isTrusted(
|
||||||
|
final long account, final String scope, final byte[] fingerprint);
|
||||||
|
|
||||||
|
public boolean isTrusted(final Account account, final ScopeFingerprint scopeFingerprint) {
|
||||||
|
return isTrusted(account.id, scopeFingerprint.scope, scopeFingerprint.fingerprint.array());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package im.conversations.android.database.entity;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.ForeignKey;
|
||||||
|
import androidx.room.Index;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
|
import im.conversations.android.tls.ScopeFingerprint;
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "certificate_trust",
|
||||||
|
foreignKeys =
|
||||||
|
@ForeignKey(
|
||||||
|
entity = AccountEntity.class,
|
||||||
|
parentColumns = {"id"},
|
||||||
|
childColumns = {"accountId"},
|
||||||
|
onDelete = ForeignKey.CASCADE),
|
||||||
|
indices = {
|
||||||
|
@Index(
|
||||||
|
value = {"accountId", "scope"},
|
||||||
|
unique = true)
|
||||||
|
})
|
||||||
|
public class CertificateTrustEntity {
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@NonNull public Long accountId;
|
||||||
|
|
||||||
|
@NonNull public String scope;
|
||||||
|
|
||||||
|
@NonNull public byte[] fingerprint;
|
||||||
|
|
||||||
|
public static CertificateTrustEntity of(
|
||||||
|
final long accountId, final ScopeFingerprint scopeFingerprint) {
|
||||||
|
final var entity = new CertificateTrustEntity();
|
||||||
|
entity.accountId = accountId;
|
||||||
|
entity.scope = scopeFingerprint.scope;
|
||||||
|
entity.fingerprint = scopeFingerprint.fingerprint.array();
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,9 +9,11 @@ import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import im.conversations.android.IDs;
|
import im.conversations.android.IDs;
|
||||||
import im.conversations.android.database.CredentialStore;
|
import im.conversations.android.database.CredentialStore;
|
||||||
import im.conversations.android.database.entity.AccountEntity;
|
import im.conversations.android.database.entity.AccountEntity;
|
||||||
|
import im.conversations.android.database.entity.CertificateTrustEntity;
|
||||||
import im.conversations.android.database.model.Account;
|
import im.conversations.android.database.model.Account;
|
||||||
import im.conversations.android.database.model.AccountIdentifier;
|
import im.conversations.android.database.model.AccountIdentifier;
|
||||||
import im.conversations.android.database.model.Connection;
|
import im.conversations.android.database.model.Connection;
|
||||||
|
import im.conversations.android.tls.ScopeFingerprint;
|
||||||
import im.conversations.android.xmpp.ConnectionPool;
|
import im.conversations.android.xmpp.ConnectionPool;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import im.conversations.android.xmpp.manager.RegistrationManager;
|
import im.conversations.android.xmpp.manager.RegistrationManager;
|
||||||
|
@ -131,6 +133,20 @@ public class AccountRepository extends AbstractRepository {
|
||||||
return database.accountDao().hasNoAccounts();
|
return database.accountDao().hasNoAccounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<Void> setCertificateTrustedAsync(
|
||||||
|
final Account account, final ScopeFingerprint scopeFingerprint) {
|
||||||
|
return Futures.submit(
|
||||||
|
() -> setCertificateTrusted(account, scopeFingerprint),
|
||||||
|
database.getQueryExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCertificateTrusted(
|
||||||
|
final Account account, final ScopeFingerprint scopeFingerprint) {
|
||||||
|
this.database
|
||||||
|
.certificateTrustDao()
|
||||||
|
.insert(CertificateTrustEntity.of(account.id, scopeFingerprint));
|
||||||
|
}
|
||||||
|
|
||||||
public static class AccountAlreadyExistsException extends IllegalStateException {
|
public static class AccountAlreadyExistsException extends IllegalStateException {
|
||||||
public AccountAlreadyExistsException(BareJid address) {
|
public AccountAlreadyExistsException(BareJid address) {
|
||||||
super(String.format("The account %s has already been setup", address));
|
super(String.format("The account %s has already been setup", address));
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package im.conversations.android.tls;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import im.conversations.android.xmpp.manager.TrustManager;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class ScopeFingerprint {
|
||||||
|
public final String scope;
|
||||||
|
public final ByteBuffer fingerprint;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("scope", scope)
|
||||||
|
.add("fingerprint", TrustManager.fingerprint(fingerprint.array()))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopeFingerprint(final String scope, final byte[] fingerprint) {
|
||||||
|
this(scope, ByteBuffer.wrap(fingerprint));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScopeFingerprint(final String scope, final ByteBuffer fingerprint) {
|
||||||
|
this.scope = scope;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ScopeFingerprint that = (ScopeFingerprint) o;
|
||||||
|
return Objects.equal(scope, that.scope) && Objects.equal(fingerprint, that.fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(scope, fingerprint);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import im.conversations.android.R;
|
||||||
import im.conversations.android.database.model.Account;
|
import im.conversations.android.database.model.Account;
|
||||||
import im.conversations.android.database.model.Connection;
|
import im.conversations.android.database.model.Connection;
|
||||||
import im.conversations.android.repository.AccountRepository;
|
import im.conversations.android.repository.AccountRepository;
|
||||||
|
import im.conversations.android.tls.ScopeFingerprint;
|
||||||
import im.conversations.android.ui.Event;
|
import im.conversations.android.ui.Event;
|
||||||
import im.conversations.android.util.ConnectionStates;
|
import im.conversations.android.util.ConnectionStates;
|
||||||
import im.conversations.android.xmpp.ConnectionException;
|
import im.conversations.android.xmpp.ConnectionException;
|
||||||
|
@ -58,28 +59,26 @@ public class SetupViewModel extends AndroidViewModel {
|
||||||
private final MutableLiveData<Event<Target>> redirection = new MutableLiveData<>();
|
private final MutableLiveData<Event<Target>> redirection = new MutableLiveData<>();
|
||||||
|
|
||||||
private final MutableLiveData<TrustDecision> trustDecision = new MutableLiveData<>();
|
private final MutableLiveData<TrustDecision> trustDecision = new MutableLiveData<>();
|
||||||
private final HashMap<TrustManager.ScopeFingerprint, Boolean> trustDecisions = new HashMap<>();
|
private final HashMap<ScopeFingerprint, Boolean> trustDecisions = new HashMap<>();
|
||||||
|
|
||||||
private final Function<TrustManager.ScopeFingerprint, ListenableFuture<Boolean>>
|
private final Function<ScopeFingerprint, ListenableFuture<Boolean>> trustDecisionCallback =
|
||||||
trustDecisionCallback =
|
scopeFingerprint -> {
|
||||||
scopeFingerprint -> {
|
final var decision = this.trustDecisions.get(scopeFingerprint);
|
||||||
final var decision = this.trustDecisions.get(scopeFingerprint);
|
if (decision != null) {
|
||||||
if (decision != null) {
|
LOGGER.info("Using previous trust decision ({})", decision);
|
||||||
LOGGER.info("Using previous trust decision ({})", decision);
|
return Futures.immediateFuture(decision);
|
||||||
return Futures.immediateFuture(decision);
|
}
|
||||||
}
|
LOGGER.info("Trust decision arrived in UI");
|
||||||
LOGGER.info("Trust decision arrived in UI");
|
final SettableFuture<Boolean> settableFuture = SettableFuture.create();
|
||||||
final SettableFuture<Boolean> settableFuture = SettableFuture.create();
|
final var trustDecision = new TrustDecision(scopeFingerprint, settableFuture);
|
||||||
final var trustDecision =
|
final var currentOperation = this.currentOperation;
|
||||||
new TrustDecision(scopeFingerprint, settableFuture);
|
if (currentOperation != null) {
|
||||||
final var currentOperation = this.currentOperation;
|
currentOperation.cancel(false);
|
||||||
if (currentOperation != null) {
|
}
|
||||||
currentOperation.cancel(false);
|
this.trustDecision.postValue(trustDecision);
|
||||||
}
|
this.redirection.postValue(new Event<>(Target.TRUST_CERTIFICATE));
|
||||||
this.trustDecision.postValue(trustDecision);
|
return settableFuture;
|
||||||
this.redirection.postValue(new Event<>(Target.TRUST_CERTIFICATE));
|
};
|
||||||
return settableFuture;
|
|
||||||
};
|
|
||||||
|
|
||||||
private final AccountRepository accountRepository;
|
private final AccountRepository accountRepository;
|
||||||
|
|
||||||
|
@ -189,13 +188,12 @@ public class SetupViewModel extends AndroidViewModel {
|
||||||
// TODO navigate back to sign in or show error?
|
// TODO navigate back to sign in or show error?
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
LOGGER.info(
|
LOGGER.info("committing trust for {}", trustDecision.scopeFingerprint);
|
||||||
"trying to commit trust for fingerprint {}",
|
this.accountRepository.setCertificateTrustedAsync(account, trustDecision.scopeFingerprint);
|
||||||
TrustManager.fingerprint(trustDecision.scopeFingerprint.fingerprint.array()));
|
|
||||||
// in case the UI interface hook gets called again before this gets written to DB
|
// in case the UI interface hook gets called again before this gets written to DB
|
||||||
this.trustDecisions.put(trustDecision.scopeFingerprint, true);
|
this.trustDecisions.put(trustDecision.scopeFingerprint, true);
|
||||||
if (trustDecision.decision.isDone()) {
|
if (trustDecision.decision.isDone()) {
|
||||||
ConnectionPool.getInstance(getApplication()).reconnect(account);
|
this.accountRepository.reconnect(account);
|
||||||
LOGGER.info("it was already done. we should reconnect");
|
LOGGER.info("it was already done. we should reconnect");
|
||||||
}
|
}
|
||||||
trustDecision.decision.set(true);
|
trustDecision.decision.set(true);
|
||||||
|
@ -265,6 +263,7 @@ public class SetupViewModel extends AndroidViewModel {
|
||||||
private void setAccount(@NonNull final Account account) {
|
private void setAccount(@NonNull final Account account) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.registerTrustDecisionCallback();
|
this.registerTrustDecisionCallback();
|
||||||
|
// TODO if the connection is already TLS_ERROR then do a quick reconnect
|
||||||
this.decideNextStep(Target.ENTER_ADDRESS, account);
|
this.decideNextStep(Target.ENTER_ADDRESS, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,6 +281,7 @@ public class SetupViewModel extends AndroidViewModel {
|
||||||
final var optionalTrustManager = getTrustManager();
|
final var optionalTrustManager = getTrustManager();
|
||||||
if (optionalTrustManager.isPresent()) {
|
if (optionalTrustManager.isPresent()) {
|
||||||
optionalTrustManager.get().setUserInterfaceCallback(this.trustDecisionCallback);
|
optionalTrustManager.get().setUserInterfaceCallback(this.trustDecisionCallback);
|
||||||
|
LOGGER.info("Registered user interface callback");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,11 +473,10 @@ public class SetupViewModel extends AndroidViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class TrustDecision {
|
public static class TrustDecision {
|
||||||
public final TrustManager.ScopeFingerprint scopeFingerprint;
|
public final ScopeFingerprint scopeFingerprint;
|
||||||
public final SettableFuture<Boolean> decision;
|
public final SettableFuture<Boolean> decision;
|
||||||
|
|
||||||
public TrustDecision(
|
public TrustDecision(ScopeFingerprint scopeFingerprint, SettableFuture<Boolean> decision) {
|
||||||
TrustManager.ScopeFingerprint scopeFingerprint, SettableFuture<Boolean> decision) {
|
|
||||||
this.scopeFingerprint = scopeFingerprint;
|
this.scopeFingerprint = scopeFingerprint;
|
||||||
this.decision = decision;
|
this.decision = decision;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,15 @@ package im.conversations.android.xmpp.manager;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.Objects;
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import im.conversations.android.AppSettings;
|
import im.conversations.android.AppSettings;
|
||||||
|
import im.conversations.android.tls.ScopeFingerprint;
|
||||||
import im.conversations.android.tls.TrustManagers;
|
import im.conversations.android.tls.TrustManagers;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
@ -73,7 +72,6 @@ public class TrustManager extends AbstractManager {
|
||||||
public void removeUserInterfaceCallback(
|
public void removeUserInterfaceCallback(
|
||||||
final Function<ScopeFingerprint, ListenableFuture<Boolean>> callback) {
|
final Function<ScopeFingerprint, ListenableFuture<Boolean>> callback) {
|
||||||
if (this.userInterfaceCallback == callback) {
|
if (this.userInterfaceCallback == callback) {
|
||||||
LOGGER.info("Remove user interface callback");
|
|
||||||
this.userInterfaceCallback = null;
|
this.userInterfaceCallback = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,33 +80,6 @@ public class TrustManager extends AbstractManager {
|
||||||
return new ScopedTrustManager(scope);
|
return new ScopedTrustManager(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ScopeFingerprint {
|
|
||||||
public final String scope;
|
|
||||||
public final ByteBuffer fingerprint;
|
|
||||||
|
|
||||||
public ScopeFingerprint(final String scope, final byte[] fingerprint) {
|
|
||||||
this(scope, ByteBuffer.wrap(fingerprint));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ScopeFingerprint(final String scope, final ByteBuffer fingerprint) {
|
|
||||||
this.scope = scope;
|
|
||||||
this.fingerprint = fingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
ScopeFingerprint that = (ScopeFingerprint) o;
|
|
||||||
return Objects.equal(scope, that.scope) && Objects.equal(fingerprint, that.fingerprint);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode(scope, fingerprint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ScopedTrustManager implements X509TrustManager {
|
private class ScopedTrustManager implements X509TrustManager {
|
||||||
|
|
||||||
private final String scope;
|
private final String scope;
|
||||||
|
@ -141,7 +112,10 @@ public class TrustManager extends AbstractManager {
|
||||||
final byte[] fingerprint =
|
final byte[] fingerprint =
|
||||||
Hashing.sha256().hashBytes(certificate.getEncoded()).asBytes();
|
Hashing.sha256().hashBytes(certificate.getEncoded()).asBytes();
|
||||||
final var scopeFingerprint = new ScopeFingerprint(scope, fingerprint);
|
final var scopeFingerprint = new ScopeFingerprint(scope, fingerprint);
|
||||||
LOGGER.info("Looking up {} in database", fingerprint(fingerprint));
|
if (getDatabase().certificateTrustDao().isTrusted(getAccount(), scopeFingerprint)) {
|
||||||
|
LOGGER.info("Found {} in database", scopeFingerprint);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final var callback = TrustManager.this.userInterfaceCallback;
|
final var callback = TrustManager.this.userInterfaceCallback;
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
throw new CertificateException(
|
throw new CertificateException(
|
||||||
|
|
Loading…
Reference in a new issue