From 4b9e9f9ae95543e43e6a4f44a9bb86e1a00819b0 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 10 Jan 2021 13:41:42 +0100 Subject: [PATCH] Ikey decisions are synced after reconnect --- .../AndroidAccountDetailsViewModel.java | 4 +- .../detail/AndroidContactDetailViewModel.java | 2 + .../ToggleableFingerprintsAdapter.java | 6 +- .../messenger/data/di/RepositoryModule.java | 10 ++ .../data/model/AbstractIkeyRecordModel.java | 14 +- .../data/model/AbstractIkeyTrustModel.java | 41 ------ .../data/repository/RxIkeyRepository.java | 55 +++++--- .../repository/RxIkeyTrustRepository.java | 42 ++++++ .../data/repository/RxOpenPgpRepository.java | 26 ++-- .../component/InMemoryDatabaseComponent.java | 5 + .../data/repository/IkeyRepositoryTest.java | 83 +++++++++++ .../jivesoftware/smackx/ikey/IkeyManager.java | 21 ++- .../smackx/ikey/record/IkeyRecord.java | 25 +++- .../ikey/record/IkeySubordinateRecord.java | 3 + .../ikey/record/OxSubordinateRecord.java | 8 +- .../smackx/ikey/util/IkeyTrust.java | 13 -- .../connection/MercuryConnectionManager.java | 5 + .../core/crypto/MercuryOpenPgpManager.java | 16 ++- .../data/repository/IkeyKeyRepository.java | 11 +- .../data/repository/OpenPgpRepository.java | 8 -- .../repository/OpenPgpTrustRepository.java | 18 +++ .../messenger/core/di/module/IkeyModule.java | 1 + .../core/di/module/ViewModelModule.java | 4 +- .../store/crypto/IkeyAwareOpenPgpStore.java | 133 ++++++++++++++++++ .../store/crypto/MercuryOpenPgpStore.java | 24 ++-- .../detail/AccountDetailsViewModel.java | 25 +++- .../contact/ContactDetailViewModel.java | 42 ++++-- 27 files changed, 499 insertions(+), 146 deletions(-) delete mode 100644 data/src/main/java/org/mercury_im/messenger/data/model/AbstractIkeyTrustModel.java create mode 100644 data/src/main/java/org/mercury_im/messenger/data/repository/RxIkeyTrustRepository.java create mode 100644 data/src/test/java/org/mercury_im/messenger/data/repository/IkeyRepositoryTest.java delete mode 100644 domain/src/main/java/org/jivesoftware/smackx/ikey/util/IkeyTrust.java create mode 100644 domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpTrustRepository.java create mode 100644 domain/src/main/java/org/mercury_im/messenger/core/store/crypto/IkeyAwareOpenPgpStore.java diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java index f8bab10..41b4762 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/account/detail/AndroidAccountDetailsViewModel.java @@ -53,7 +53,7 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements LOGGER.log(Level.INFO, "Creating AndroidAccountDetailsViewModel"); ((MercuryImApplication) application).getAppComponent().inject(this); - addDisposable(getCommonViewModel().observeLocalFingerprint(accountId) + addDisposable(getCommonViewModel().observeLocalDeviceFingerprint(accountId) .compose(schedulers.executeUiSafeObservable()) .filter(Optional::isPresent) .map(Optional::getItem) @@ -64,7 +64,7 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements .subscribe(ikeyFingerprint::postValue, e -> LOGGER.log(Level.SEVERE, "Error displaying ikey fingerprint", e))); - addDisposable(getCommonViewModel().observeRemoteFingerprints(accountId) + addDisposable(getCommonViewModel().observeRemoteDeviceFingerprints(accountId) .compose(schedulers.executeUiSafeObservable()) .subscribe(list -> { LOGGER.log(Level.INFO, "Set remote fingerprints to list: " + Arrays.toString(list.toArray())); diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/AndroidContactDetailViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/AndroidContactDetailViewModel.java index 8ce109c..5cc4027 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/AndroidContactDetailViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/contacts/detail/AndroidContactDetailViewModel.java @@ -27,6 +27,7 @@ import javax.inject.Inject; import io.reactivex.Completable; import io.reactivex.Single; +import lombok.SneakyThrows; public class AndroidContactDetailViewModel extends ViewModel implements MercuryAndroidViewModel { @@ -122,6 +123,7 @@ public class AndroidContactDetailViewModel extends ViewModel implements MercuryA } + @SneakyThrows public void markDeviceFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) { commonViewModel.markDeviceFingerprintTrusted(fingerprint, checked); } diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/ToggleableFingerprintsAdapter.java b/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/ToggleableFingerprintsAdapter.java index 7b1692f..e4727b1 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/ToggleableFingerprintsAdapter.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/openpgp/ToggleableFingerprintsAdapter.java @@ -57,8 +57,10 @@ public class ToggleableFingerprintsAdapter extends RecyclerView.Adapter toggleListener.onFingerprintToggled(fingerprint, isChecked)); + holder.trustSwitch.setOnClickListener(v -> { + boolean checked = ((Switch) v).isChecked(); + toggleListener.onFingerprintToggled(fingerprint, checked); + }); holder.divider.setVisibility(position == fingerprints.size() - 1 ? View.GONE : View.VISIBLE); holder.itemView.setOnLongClickListener(v -> { diff --git a/data/src/main/java/org/mercury_im/messenger/data/di/RepositoryModule.java b/data/src/main/java/org/mercury_im/messenger/data/di/RepositoryModule.java index 9554170..1fa653b 100644 --- a/data/src/main/java/org/mercury_im/messenger/data/di/RepositoryModule.java +++ b/data/src/main/java/org/mercury_im/messenger/data/di/RepositoryModule.java @@ -1,5 +1,6 @@ package org.mercury_im.messenger.data.di; +import org.jivesoftware.smackx.ikey.util.IkeyOpenPgpTrustStore; import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; import org.mercury_im.messenger.core.data.repository.AccountRepository; import org.mercury_im.messenger.core.data.repository.DirectChatRepository; @@ -8,6 +9,7 @@ import org.mercury_im.messenger.core.data.repository.GroupChatRepository; import org.mercury_im.messenger.core.data.repository.IkeyRecordRepository; import org.mercury_im.messenger.core.data.repository.MessageRepository; import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; import org.mercury_im.messenger.core.data.repository.PeerRepository; import org.mercury_im.messenger.data.mapping.AccountMapping; import org.mercury_im.messenger.data.mapping.DirectChatMapping; @@ -20,6 +22,7 @@ import org.mercury_im.messenger.data.repository.RxDirectChatRepository; import org.mercury_im.messenger.data.repository.RxEntityCapsRepository; import org.mercury_im.messenger.data.repository.RxGroupChatRepository; import org.mercury_im.messenger.data.repository.RxIkeyRepository; +import org.mercury_im.messenger.data.repository.RxIkeyTrustRepository; import org.mercury_im.messenger.data.repository.RxMessageRepository; import org.mercury_im.messenger.data.repository.RxOpenPgpRepository; import org.mercury_im.messenger.data.repository.RxPeerRepository; @@ -100,4 +103,11 @@ public class RepositoryModule { static IkeyRepository provideIkeyRepository(ReactiveEntityStore data) { return new RxIkeyRepository(data); } + + @Provides + @Singleton + static OpenPgpTrustRepository provideOpenPgpTrustRepository(IkeyRepository recordRepository, OpenPgpRepository openPgpRepository) { + return (OpenPgpTrustRepository) openPgpRepository; + // return new RxIkeyTrustRepository(recordRepository); + } } diff --git a/data/src/main/java/org/mercury_im/messenger/data/model/AbstractIkeyRecordModel.java b/data/src/main/java/org/mercury_im/messenger/data/model/AbstractIkeyRecordModel.java index dc051cb..2855f18 100644 --- a/data/src/main/java/org/mercury_im/messenger/data/model/AbstractIkeyRecordModel.java +++ b/data/src/main/java/org/mercury_im/messenger/data/model/AbstractIkeyRecordModel.java @@ -1,6 +1,7 @@ package org.mercury_im.messenger.data.model; import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jxmpp.jid.EntityBareJid; import org.mercury_im.messenger.data.converter.Base64PGPPublicKeyRingConverter; import org.mercury_im.messenger.data.converter.EntityBareJidConverter; @@ -15,23 +16,28 @@ import io.requery.CascadeAction; import io.requery.Column; import io.requery.Convert; import io.requery.Entity; +import io.requery.Index; import io.requery.Key; import io.requery.OneToMany; import io.requery.Table; import io.requery.converter.UUIDConverter; +import lombok.ToString; -@Table(name = "ikey_record") +@Table(name = "ikey_record", uniqueIndexes = "unique_ikey_record") @Entity +@ToString public class AbstractIkeyRecordModel { @Key @Convert(UUIDConverter.class) UUID id; - @Column(name = "account") + @Index("unique_ikey_record") + @Column @Convert(UUIDConverter.class) UUID accountId; + @Index("unique_ikey_record") @Column(name = "jid") @Convert(EntityBareJidConverter.class) EntityBareJid jid; @@ -50,7 +56,11 @@ public class AbstractIkeyRecordModel { @Convert(Base64PGPPublicKeyRingConverter.class) PGPPublicKeyRing superordinate; + @Index("unique_ikey_record") @Column(name = "fingerprint") @Convert(OpenPgpV4FingerprintConverter.class) OpenPgpV4Fingerprint fingerprint; + + @Column + OpenPgpTrustStore.Trust trust; } diff --git a/data/src/main/java/org/mercury_im/messenger/data/model/AbstractIkeyTrustModel.java b/data/src/main/java/org/mercury_im/messenger/data/model/AbstractIkeyTrustModel.java deleted file mode 100644 index be68eda..0000000 --- a/data/src/main/java/org/mercury_im/messenger/data/model/AbstractIkeyTrustModel.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.mercury_im.messenger.data.model; - -import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; -import org.jxmpp.jid.EntityBareJid; -import org.mercury_im.messenger.data.converter.EntityBareJidConverter; -import org.mercury_im.messenger.data.converter.OpenPgpTrustConverter; -import org.mercury_im.messenger.data.converter.OpenPgpV4FingerprintConverter; -import org.pgpainless.key.OpenPgpV4Fingerprint; - -import java.util.UUID; - -import io.requery.Column; -import io.requery.Convert; -import io.requery.Entity; -import io.requery.ForeignKey; -import io.requery.Key; -import io.requery.ReferentialAction; -import io.requery.Table; -import io.requery.converter.UUIDConverter; - -@Table(name = "ikey_trust") -@Entity -public class AbstractIkeyTrustModel { - - @Key - @ForeignKey(references = AbstractAccountModel.class, delete = ReferentialAction.CASCADE) - @Convert(UUIDConverter.class) - UUID accountId; - - @Key - @Convert(EntityBareJidConverter.class) - EntityBareJid jid; - - @Key - @Convert(OpenPgpV4FingerprintConverter.class) - OpenPgpV4Fingerprint fingerprint; - - @Column(name = "trust") - @Convert(OpenPgpTrustConverter.class) - OpenPgpTrustStore.Trust trust; -} diff --git a/data/src/main/java/org/mercury_im/messenger/data/repository/RxIkeyRepository.java b/data/src/main/java/org/mercury_im/messenger/data/repository/RxIkeyRepository.java index 4811159..9396f6b 100644 --- a/data/src/main/java/org/mercury_im/messenger/data/repository/RxIkeyRepository.java +++ b/data/src/main/java/org/mercury_im/messenger/data/repository/RxIkeyRepository.java @@ -4,16 +4,15 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.jivesoftware.smackx.ikey.record.IkeyRecord; import org.jivesoftware.smackx.ikey.record.IkeySubordinateRecord; import org.jivesoftware.smackx.ikey.record.OxSubordinateRecord; -import org.jivesoftware.smackx.ikey.util.IkeyTrust; import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; import org.jivesoftware.smackx.ox.element.OpenPgpElement; +import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jxmpp.jid.EntityBareJid; import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.data.model.IkeyRecordModel; import org.mercury_im.messenger.data.model.IkeySecretKeyModel; import org.mercury_im.messenger.data.model.IkeySubordinateModel; -import org.mercury_im.messenger.data.model.IkeyTrustModel; import org.pgpainless.key.OpenPgpV4Fingerprint; import java.util.ArrayList; @@ -23,6 +22,8 @@ import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; +import javax.inject.Inject; + import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.Single; @@ -36,6 +37,7 @@ public class RxIkeyRepository implements IkeyRepository { private final ReactiveEntityStore data; + @Inject public RxIkeyRepository(ReactiveEntityStore data) { this.data = data; } @@ -102,30 +104,27 @@ public class RxIkeyRepository implements IkeyRepository { } @Override - public Observable> loadSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint) { - return data.select(IkeyTrustModel.class) - .where(IkeyTrustModel.ACCOUNT_ID.eq(accountId).and(IkeyTrustModel.JID.eq(jid).and(IkeyTrustModel.FINGERPRINT.eq(fingerprint)))) - .get() - .observableResult() - .map(r -> { - IkeyTrustModel m = r.firstOrNull(); - if (m == null) { + public Observable> loadSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint) { + return loadRecord(accountId, jid) + .map(model -> { + if (fingerprint.equals(new OpenPgpV4Fingerprint(model.getSuperordinate()))) { + return new Optional<>(model.getTrust()); + } else { return new Optional<>(); } - IkeyTrust e = new IkeyTrust(); - e.setTrust(m.getTrust()); - return new Optional<>(e); }); } @Override - public Completable storeSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint, IkeyTrust trust) { - IkeyTrustModel model = new IkeyTrustModel(); - model.setAccountId(accountId); - model.setJid(jid); - model.setFingerprint(fingerprint); - model.setTrust(trust.getTrust()); - return data.upsert(model).ignoreElement(); + public Completable storeSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust) { + return loadRecord(accountId, jid) + .map(record -> { + record.setTrust(trust); + return record; + }) + .firstOrError() + .doOnSuccess(m -> LOGGER.log(Level.INFO, "First Record: " + m)) + .flatMapCompletable(record -> storeRecord(accountId, jid, record)); } @Override @@ -136,11 +135,13 @@ public class RxIkeyRepository implements IkeyRepository { for (IkeySubordinateModel sub : m.getSubordinates()) { if (sub.getType().equals(OpenPgpElement.NAMESPACE)) { OxSubordinateRecord r = new OxSubordinateRecord(); + r.setId(sub.getId()); r.setOxFingerprint(new OpenPgpV4Fingerprint(sub.getFpr())); r.setUri(sub.getUri()); + subordinateRecords.add(r); } } - return new IkeyRecord(jid, m.getTimestamp(), m.getSuperordinate(), subordinateRecords); + return new IkeyRecord(jid, m.getTimestamp(), m.getSuperordinate(), subordinateRecords, m.getTrust()); }) .doOnError(e -> LOGGER.log(Level.SEVERE, "Error loading ikey record", e)); } @@ -149,8 +150,11 @@ public class RxIkeyRepository implements IkeyRepository { public Completable storeRecord(UUID accountId, EntityBareJid jid, IkeyRecord record) { assert jid.equals(record.getJid()); return getRecordModel(accountId, jid, false) + .doOnNext(m -> LOGGER.log(Level.INFO, "Loaded model: " + m)) .single(new IkeyRecordModel()) + .doOnSuccess(m -> LOGGER.log(Level.INFO, "Singled model: " + m)) .map(m -> { + LOGGER.log(Level.INFO, "Mapping model: " + m); if (m.getId() == null) m.setId(UUID.randomUUID()); m.setAccountId(accountId); m.setJid(jid); @@ -158,22 +162,26 @@ public class RxIkeyRepository implements IkeyRepository { m.setFingerprint(new OpenPgpV4Fingerprint(record.getSuperordinate())); m.setSuperordinate(record.getSuperordinate()); m.setTimestamp(record.getTimestamp()); + m.setTrust(record.getTrust()); m.getSubordinates().clear(); for (IkeySubordinateRecord s : record.getSubordinates()) { IkeySubordinateModel sm = new IkeySubordinateModel(); - sm.setId(UUID.randomUUID()); + sm.setId(s.getId()); sm.setRecord(m); sm.setFpr(s.getFingerprint()); sm.setType(s.getType()); sm.setUri(s.getUri()); m.getSubordinates().add(sm); } + LOGGER.log(Level.INFO, "Return model: " + m); return m; }) .flatMap(data::upsert) + .doOnSuccess(m -> LOGGER.log(Level.INFO, "Upserting model: " + m)) .ignoreElement() + .doOnComplete(() -> LOGGER.log(Level.INFO, "Updated model")) .doOnError(e -> LOGGER.log(Level.SEVERE, "Error storing ikey record", e)); } @@ -190,7 +198,7 @@ public class RxIkeyRepository implements IkeyRepository { subordinateRecords.add(sr); } } - return new IkeyRecord(m.getJid(), m.getTimestamp(), m.getSuperordinate(), subordinateRecords); + return new IkeyRecord(m.getJid(), m.getTimestamp(), m.getSuperordinate(), subordinateRecords, m.getTrust()); }) .doOnError(e -> LOGGER.log(Level.SEVERE, "Error loading contender ikey record", e)); } @@ -208,6 +216,7 @@ public class RxIkeyRepository implements IkeyRepository { m.setFingerprint(new OpenPgpV4Fingerprint(record.getSuperordinate())); m.setSuperordinate(record.getSuperordinate()); m.setTimestamp(record.getTimestamp()); + m.setTrust(record.getTrust()); for (IkeySubordinateRecord s : record.getSubordinates()) { IkeySubordinateModel sm = new IkeySubordinateModel(); diff --git a/data/src/main/java/org/mercury_im/messenger/data/repository/RxIkeyTrustRepository.java b/data/src/main/java/org/mercury_im/messenger/data/repository/RxIkeyTrustRepository.java new file mode 100644 index 0000000..9a50dc8 --- /dev/null +++ b/data/src/main/java/org/mercury_im/messenger/data/repository/RxIkeyTrustRepository.java @@ -0,0 +1,42 @@ +package org.mercury_im.messenger.data.repository; + +import org.jivesoftware.smackx.ikey.record.IkeyRecord; +import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; +import org.jxmpp.jid.EntityBareJid; +import org.mercury_im.messenger.core.data.repository.IkeyRecordRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; +import org.pgpainless.key.OpenPgpV4Fingerprint; + +import java.util.UUID; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import io.reactivex.Completable; +import io.reactivex.Single; + +public class RxIkeyTrustRepository implements OpenPgpTrustRepository { + + private static final Logger LOGGER = Logger.getLogger(RxIkeyTrustRepository.class.getName()); + + private final IkeyRecordRepository recordRepository; + + @Inject + public RxIkeyTrustRepository(IkeyRecordRepository ikeyRecordRepository) { + this.recordRepository = ikeyRecordRepository; + } + + @Override + public Single loadTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint) { + return recordRepository.loadRecord(accountId, owner) + .filter(r -> r.hasSubordinate(fingerprint)) + .map(IkeyRecord::getTrust) + .first(OpenPgpTrustStore.Trust.undecided); + } + + @Override + public Completable storeTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust) { + return Completable.complete(); + } + +} diff --git a/data/src/main/java/org/mercury_im/messenger/data/repository/RxOpenPgpRepository.java b/data/src/main/java/org/mercury_im/messenger/data/repository/RxOpenPgpRepository.java index 41e1721..4adf160 100644 --- a/data/src/main/java/org/mercury_im/messenger/data/repository/RxOpenPgpRepository.java +++ b/data/src/main/java/org/mercury_im/messenger/data/repository/RxOpenPgpRepository.java @@ -8,6 +8,7 @@ import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.impl.JidCreate; import org.mercury_im.messenger.core.data.repository.AccountRepository; import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; import org.mercury_im.messenger.data.model.AnnouncedOpenPgpContactKey; @@ -25,6 +26,7 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; @@ -40,7 +42,7 @@ import io.requery.query.ResultDelegate; import io.requery.query.Tuple; import io.requery.reactivex.ReactiveEntityStore; -public class RxOpenPgpRepository implements OpenPgpRepository { +public class RxOpenPgpRepository implements OpenPgpRepository, OpenPgpTrustRepository { private static final Logger LOGGER = Logger.getLogger(RxOpenPgpRepository.class.getName()); @@ -132,6 +134,12 @@ public class RxOpenPgpRepository implements OpenPgpRepository { @Override public Completable storeAnnouncedFingerprints(UUID accountId, EntityBareJid owner, Map metadata) { + Set fingerprints = metadata.keySet(); + Completable deleteAll = data.delete(AnnouncedOpenPgpContactKey.class).where( + AnnouncedOpenPgpContactKey.ACCOUNT_ID.eq(accountId) + .and(AnnouncedOpenPgpContactKey.OWNER.eq(owner)) + .and(AnnouncedOpenPgpContactKey.FINGERPRINT.notIn(fingerprints))) + .get().single().ignoreElement(); List entities = new LinkedList<>(); for (Map.Entry entry : metadata.entrySet()) { AnnouncedOpenPgpContactKey entity = new AnnouncedOpenPgpContactKey(); @@ -141,9 +149,11 @@ public class RxOpenPgpRepository implements OpenPgpRepository { entity.setModificationDate(entry.getValue()); entities.add(entity); } - return data.upsert(entities).ignoreElement() + Completable upsertNew = data.upsert(entities).ignoreElement() .doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored announced fingerprints of " + owner + " for account " + accountId + ": " + Arrays.toString(metadata.keySet().toArray()))); + + return deleteAll.andThen(upsertNew); } @Override @@ -196,12 +206,6 @@ public class RxOpenPgpRepository implements OpenPgpRepository { .map(entity -> entity.getTrust() != null ? entity.getTrust() : OpenPgpTrustStore.Trust.undecided); } - @Override - public Single loadTrust(UUID accountId, OpenPgpV4Fingerprint fingerprint) { - return accountRepository.getAccount(accountId).toSingle() - .flatMap(account -> loadTrust(accountId, account.getJid(), fingerprint)); - } - @Override public Completable storeTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust) { OpenPgpKeyTrust entity = new OpenPgpKeyTrust(); @@ -212,12 +216,6 @@ public class RxOpenPgpRepository implements OpenPgpRepository { return data.upsert(entity).ignoreElement(); } - @Override - public Completable storeTrust(UUID accountId, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust) { - return accountRepository.getAccount(accountId).toSingle() - .flatMapCompletable(account -> storeTrust(accountId, account.getJid(), fingerprint, trust)); - } - @Override public Observable> observeFingerprintsOf(UUID accountId, String peerAddress) { return data.select(OpenPgpPublicKeyRing.class) diff --git a/data/src/test/java/org/mercury_im/messenger/data/di/component/InMemoryDatabaseComponent.java b/data/src/test/java/org/mercury_im/messenger/data/di/component/InMemoryDatabaseComponent.java index eaa33d5..9b2ab3f 100644 --- a/data/src/test/java/org/mercury_im/messenger/data/di/component/InMemoryDatabaseComponent.java +++ b/data/src/test/java/org/mercury_im/messenger/data/di/component/InMemoryDatabaseComponent.java @@ -1,9 +1,11 @@ package org.mercury_im.messenger.data.di.component; +import org.mercury_im.messenger.core.di.module.IkeyModule; import org.mercury_im.messenger.data.di.RepositoryModule; import org.mercury_im.messenger.data.di.module.SqliteTestDatabaseModule; import org.mercury_im.messenger.data.di.module.TestingSchedulerModule; import org.mercury_im.messenger.data.repository.AccountRepositoryTest; +import org.mercury_im.messenger.data.repository.IkeyRepositoryTest; import javax.inject.Singleton; @@ -11,6 +13,7 @@ import dagger.Component; @Component(modules = { RepositoryModule.class, + IkeyModule.class, SqliteTestDatabaseModule.class, TestingSchedulerModule.class }) @@ -18,4 +21,6 @@ import dagger.Component; public interface InMemoryDatabaseComponent { void inject(AccountRepositoryTest test); + + void inject(IkeyRepositoryTest test); } diff --git a/data/src/test/java/org/mercury_im/messenger/data/repository/IkeyRepositoryTest.java b/data/src/test/java/org/mercury_im/messenger/data/repository/IkeyRepositoryTest.java new file mode 100644 index 0000000..4b4bab3 --- /dev/null +++ b/data/src/test/java/org/mercury_im/messenger/data/repository/IkeyRepositoryTest.java @@ -0,0 +1,83 @@ +package org.mercury_im.messenger.data.repository; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.jivesoftware.smackx.ikey.record.IkeyRecord; +import org.jivesoftware.smackx.ikey.record.OxSubordinateRecord; +import org.junit.jupiter.api.Test; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.impl.JidCreate; +import org.mercury_im.messenger.data.di.component.DaggerInMemoryDatabaseComponent; +import org.mercury_im.messenger.data.di.component.InMemoryDatabaseComponent; +import org.pgpainless.PGPainless; +import org.pgpainless.key.OpenPgpV4Fingerprint; + +import java.net.URI; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.util.Date; +import java.util.UUID; + +import javax.inject.Inject; + +import io.requery.Persistable; +import io.requery.reactivex.ReactiveEntityStore; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class IkeyRepositoryTest { + + @Inject + ReactiveEntityStore dataStore; + + @Inject + RxIkeyRepository ikeyRepository; + + @Inject + public IkeyRepositoryTest() { + InMemoryDatabaseComponent testComponent = DaggerInMemoryDatabaseComponent.builder() + .build(); + testComponent.inject(this); + } + + @Test + public void test() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + UUID accountId = UUID.randomUUID(); + EntityBareJid jid = JidCreate.entityBareFromOrThrowUnchecked("test@test.test"); + PGPPublicKeyRing publicKey = PGPainless.generateKeyRing().simpleEcKeyRing(jid.asEntityBareJidString()).getPublicKeys(); + Date date = new Date(); + + IkeyRecord firstRecord = new IkeyRecord(); + firstRecord.setSuperordinate(publicKey); + firstRecord.setTimestamp(date); + firstRecord.setJid(jid); + + OxSubordinateRecord firstSub = new OxSubordinateRecord(); + firstSub.setUri(URI.create("xmpp.pubsub:pubsub.shakespeare.lit")); + firstSub.setOxFingerprint(new OpenPgpV4Fingerprint("1357B01865B2503C18453D208CAC2A9678548E35")); + firstRecord.getSubordinates().add(firstSub); + + ikeyRepository.storeRecord(accountId, jid, firstRecord) + .blockingAwait(); + IkeyRecord loaded = ikeyRepository.loadRecord(accountId, jid).blockingFirst(); + assertEquals(1, loaded.getSubordinates().size()); + assertEquals(firstSub, loaded.getSubordinates().get(0)); + + + IkeyRecord secondRecord = new IkeyRecord(); + secondRecord.setJid(jid); + secondRecord.setTimestamp(new Date()); + secondRecord.setSuperordinate(publicKey); + + OxSubordinateRecord secondSub = new OxSubordinateRecord(); + secondSub.setOxFingerprint(new OpenPgpV4Fingerprint("1357B01865B2503C18453D208CAC2A9678448E15")); + secondSub.setUri(URI.create("xmpp.pubsub:pubsub.shakespeare.lot")); + secondRecord.getSubordinates().add(secondSub); + + ikeyRepository.storeRecord(accountId, jid, secondRecord) + .blockingAwait(); + IkeyRecord loaded1 = ikeyRepository.loadRecord(accountId, jid).blockingFirst(); + assertEquals(1, loaded1.getSubordinates().size(), "Second record must only contain one subordinate."); + assertEquals(secondSub, loaded1.getSubordinates().get(0)); + } +} diff --git a/domain/src/main/java/org/jivesoftware/smackx/ikey/IkeyManager.java b/domain/src/main/java/org/jivesoftware/smackx/ikey/IkeyManager.java index 1df51d1..b349a13 100644 --- a/domain/src/main/java/org/jivesoftware/smackx/ikey/IkeyManager.java +++ b/domain/src/main/java/org/jivesoftware/smackx/ikey/IkeyManager.java @@ -33,6 +33,7 @@ import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; +import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil; import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper; import org.jivesoftware.smackx.pep.PepEventListener; @@ -166,7 +167,9 @@ public final class IkeyManager extends Manager { public void storeAndPublishElement(IkeyElement ikeyElement) throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, IOException, PGPException { - store.storeIkeyRecord(connection().getUser().asEntityBareJid(), elementToRecord(ikeyElement)); + IkeyRecord record = elementToRecord(ikeyElement); + record.setTrust(OpenPgpTrustStore.Trust.trusted); + store.storeIkeyRecord(connection().getUser().asEntityBareJid(), record); publishIkeyElement(ikeyElement); } @@ -214,7 +217,9 @@ public final class IkeyManager extends Manager { return; } - if (existsSameOrNewerRecord(newRecord)) { + IkeyRecord existingRecord = store.loadIkeyRecord(newRecord.getJid()); + + if (existsSameOrNewerRecord(newRecord, existingRecord)) { LOGGER.log(Level.WARNING, "There exists this exact, or a newer ikey record in the database for " + from); return; } @@ -225,8 +230,17 @@ public final class IkeyManager extends Manager { } if (isContenderElement(newRecord, store.loadIkeyRecord(from))) { + LOGGER.log(Level.INFO, "Storing contender element for " + from); store.storeContenderIkeyRecord(from, newRecord); } else { + if (existingRecord != null) { + newRecord.setTrust(existingRecord.getTrust()); + } + PGPSecretKeyRing secretKeys = store.loadSecretKey(); + if (secretKeys != null && new OpenPgpV4Fingerprint(secretKeys).equals(new OpenPgpV4Fingerprint(newRecord.getSuperordinate()))) { + newRecord.setTrust(OpenPgpTrustStore.Trust.trusted); + } + LOGGER.log(Level.INFO, "Storing ikey record " + newRecord); store.storeIkeyRecord(from, newRecord); } } @@ -321,8 +335,7 @@ public final class IkeyManager extends Manager { return timestamp.after(now); } - private boolean existsSameOrNewerRecord(IkeyRecord record) throws IOException { - IkeyRecord existingRecord = store.loadIkeyRecord(record.getJid()); + private boolean existsSameOrNewerRecord(IkeyRecord record, IkeyRecord existingRecord) throws IOException { if (existingRecord == null) { return false; } diff --git a/domain/src/main/java/org/jivesoftware/smackx/ikey/record/IkeyRecord.java b/domain/src/main/java/org/jivesoftware/smackx/ikey/record/IkeyRecord.java index 05b9c4a..251a100 100644 --- a/domain/src/main/java/org/jivesoftware/smackx/ikey/record/IkeyRecord.java +++ b/domain/src/main/java/org/jivesoftware/smackx/ikey/record/IkeyRecord.java @@ -1,7 +1,10 @@ package org.jivesoftware.smackx.ikey.record; import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.jivesoftware.smackx.ox.element.OpenPgpElement; +import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jxmpp.jid.EntityBareJid; +import org.pgpainless.key.OpenPgpV4Fingerprint; import java.io.Serializable; import java.util.ArrayList; @@ -10,7 +13,9 @@ import java.util.List; import lombok.Getter; import lombok.Setter; +import lombok.ToString; +@ToString public class IkeyRecord implements Serializable { @Getter @@ -18,7 +23,6 @@ public class IkeyRecord implements Serializable { private PGPPublicKeyRing superordinate; @Getter - @Setter private final List subordinates = new ArrayList<>(); @Getter @@ -29,14 +33,31 @@ public class IkeyRecord implements Serializable { @Setter private EntityBareJid jid; + @Getter + @Setter + private OpenPgpTrustStore.Trust trust = OpenPgpTrustStore.Trust.undecided; + public IkeyRecord() { } - public IkeyRecord(EntityBareJid jid, Date timestamp, PGPPublicKeyRing superordinate, List subordinates) { + public IkeyRecord(EntityBareJid jid, Date timestamp, PGPPublicKeyRing superordinate, List subordinates, OpenPgpTrustStore.Trust trust) { this.jid = jid; this.timestamp = timestamp; this.superordinate = superordinate; this.subordinates.addAll(subordinates); + this.trust = trust; + } + + public boolean hasSubordinate(OpenPgpV4Fingerprint fingerprint) { + for (IkeySubordinateRecord subordinateRecord : subordinates) { + if (subordinateRecord.getType().equals(OxSubordinateRecord.TYPE)) { + OxSubordinateRecord record = (OxSubordinateRecord) subordinateRecord; + if (record.getOxFingerprint().equals(fingerprint)) { + return true; + } + } + } + return false; } } diff --git a/domain/src/main/java/org/jivesoftware/smackx/ikey/record/IkeySubordinateRecord.java b/domain/src/main/java/org/jivesoftware/smackx/ikey/record/IkeySubordinateRecord.java index 7dab170..2309aac 100644 --- a/domain/src/main/java/org/jivesoftware/smackx/ikey/record/IkeySubordinateRecord.java +++ b/domain/src/main/java/org/jivesoftware/smackx/ikey/record/IkeySubordinateRecord.java @@ -1,9 +1,12 @@ package org.jivesoftware.smackx.ikey.record; import java.net.URI; +import java.util.UUID; public interface IkeySubordinateRecord { + UUID getId(); + String getType(); String getFingerprint(); diff --git a/domain/src/main/java/org/jivesoftware/smackx/ikey/record/OxSubordinateRecord.java b/domain/src/main/java/org/jivesoftware/smackx/ikey/record/OxSubordinateRecord.java index c0664f7..f0f5525 100644 --- a/domain/src/main/java/org/jivesoftware/smackx/ikey/record/OxSubordinateRecord.java +++ b/domain/src/main/java/org/jivesoftware/smackx/ikey/record/OxSubordinateRecord.java @@ -6,11 +6,17 @@ import org.pgpainless.key.OpenPgpV4Fingerprint; import java.net.URI; import java.util.UUID; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +@EqualsAndHashCode public class OxSubordinateRecord implements IkeySubordinateRecord { + public static final String TYPE = OpenPgpElement.NAMESPACE; + + @Getter + @Setter private UUID id; public OxSubordinateRecord() { @@ -33,7 +39,7 @@ public class OxSubordinateRecord implements IkeySubordinateRecord { @Override public String getType() { - return OpenPgpElement.NAMESPACE; + return TYPE; } @Override diff --git a/domain/src/main/java/org/jivesoftware/smackx/ikey/util/IkeyTrust.java b/domain/src/main/java/org/jivesoftware/smackx/ikey/util/IkeyTrust.java deleted file mode 100644 index 50f53a2..0000000 --- a/domain/src/main/java/org/jivesoftware/smackx/ikey/util/IkeyTrust.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.jivesoftware.smackx.ikey.util; - -import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; - -import lombok.Getter; -import lombok.Setter; - -public class IkeyTrust { - - @Getter - @Setter - OpenPgpTrustStore.Trust trust; -} diff --git a/domain/src/main/java/org/mercury_im/messenger/core/connection/MercuryConnectionManager.java b/domain/src/main/java/org/mercury_im/messenger/core/connection/MercuryConnectionManager.java index 66089ff..3870226 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/connection/MercuryConnectionManager.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/connection/MercuryConnectionManager.java @@ -6,6 +6,7 @@ import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.connection.state.ConnectionPoolState; import org.mercury_im.messenger.core.connection.state.ConnectionState; import org.mercury_im.messenger.core.crypto.MercuryOpenPgpManager; +import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer; import org.mercury_im.messenger.core.data.repository.AccountRepository; import org.mercury_im.messenger.core.store.caps.MercuryEntityCapsStore; import org.mercury_im.messenger.core.store.message.MercuryMessageStore; @@ -43,6 +44,7 @@ public class MercuryConnectionManager { private final AccountRepository accountRepository; private final RosterStoreBinder rosterStoreBinder; private final MercuryOpenPgpManager cryptoManager; + private final IkeyInitializer ikeyInitializer; private final SchedulersFacade schedulers; private final Map connectionsMap = new ConcurrentHashMap<>(); @@ -63,12 +65,14 @@ public class MercuryConnectionManager { MercuryMessageStoreFactory messageStoreFactory, XmppConnectionFactory connectionFactory, MercuryOpenPgpManager cryptoManager, + IkeyInitializer ikeyInitializer, SchedulersFacade schedulers) { this.accountRepository = accountRepository; this.rosterStoreBinder = rosterStoreBinder; this.connectionFactory = connectionFactory; this.messageStoreFactory = messageStoreFactory; this.cryptoManager = cryptoManager; + this.ikeyInitializer = ikeyInitializer; this.schedulers = schedulers; EntityCapsManager.setPersistentCache(entityCapsStore); @@ -170,6 +174,7 @@ public class MercuryConnectionManager { chatManager.addIncomingListener(mercuryMessageStore); })); cryptoManager.initialize(connection); + ikeyInitializer.initFor(connection); } private synchronized void handleOptionalAccountChangedEvent(MercuryConnection connection, Optional event) { diff --git a/domain/src/main/java/org/mercury_im/messenger/core/crypto/MercuryOpenPgpManager.java b/domain/src/main/java/org/mercury_im/messenger/core/crypto/MercuryOpenPgpManager.java index 6ed5428..4f5d990 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/crypto/MercuryOpenPgpManager.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/crypto/MercuryOpenPgpManager.java @@ -2,6 +2,7 @@ package org.mercury_im.messenger.core.crypto; import org.jivesoftware.smack.AbstractConnectionListener; import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.ikey.record.IkeyRecord; import org.jivesoftware.smackx.ox.OpenPgpManager; import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; import org.jivesoftware.smackx.ox.callback.SecretKeyPassphraseCallback; @@ -14,10 +15,15 @@ import org.jivesoftware.smackx.ox_im.OXInstantMessagingManager; import org.jivesoftware.smackx.pubsub.PubSubException; import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.connection.MercuryConnection; +import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; import org.mercury_im.messenger.core.data.repository.DirectChatRepository; +import org.mercury_im.messenger.core.data.repository.IkeyKeyRepository; +import org.mercury_im.messenger.core.data.repository.IkeyRecordRepository; import org.mercury_im.messenger.core.data.repository.MessageRepository; import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; import org.mercury_im.messenger.core.data.repository.PeerRepository; +import org.mercury_im.messenger.core.store.crypto.IkeyAwareOpenPgpStore; import org.mercury_im.messenger.core.store.crypto.MercuryOpenPgpStore; import org.mercury_im.messenger.core.store.message.MercuryMessageStore; @@ -34,6 +40,8 @@ public class MercuryOpenPgpManager { private final DirectChatRepository directChatRepository; private final MessageRepository messageRepository; private final OpenPgpRepository openPgpRepository; + private final OpenPgpTrustRepository openPgpTrustRepository; + private final IkeyRepository ikeyRepository; private final SchedulersFacade schedulers; private final OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator; private final LocalOxKeyGenerationStrategy keyGenerationStrategy; @@ -43,16 +51,20 @@ public class MercuryOpenPgpManager { DirectChatRepository directChatRepository, MessageRepository messageRepository, OpenPgpRepository openPgpRepository, + OpenPgpTrustRepository openPgpTrustRepository, OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator, LocalOxKeyGenerationStrategy keyGenerationStrategy, + IkeyRepository ikeyRepository, SchedulersFacade schedulers) { this.peerRepository = peerRepository; this.directChatRepository = directChatRepository; this.messageRepository = messageRepository; this.openPgpRepository = openPgpRepository; + this.openPgpTrustRepository = openPgpTrustRepository; this.schedulers = schedulers; this.keyGenerationStrategy = keyGenerationStrategy; this.passphraseGenerator = passphraseGenerator; + this.ikeyRepository = ikeyRepository; } public void initialize(MercuryConnection connection) { @@ -71,13 +83,11 @@ public class MercuryOpenPgpManager { } private void setup(MercuryConnection connection) { - OpenPgpStore store = new MercuryOpenPgpStore(connection.getAccountId(), openPgpRepository, schedulers); + OpenPgpStore store = new IkeyAwareOpenPgpStore(connection.getAccountId(), openPgpRepository, openPgpTrustRepository, ikeyRepository, schedulers); OpenPgpProvider provider = new PainlessOpenPgpProvider(store); OpenPgpManager oxManager = OpenPgpManager.getInstanceFor(connection.getConnection()); oxManager.setOpenPgpProvider(provider); OpenPgpSecretKeyBackupPassphrase passphrase = passphraseGenerator.generateBackupPassphrase(); - - generateAndPublish(connection, oxManager, passphrase); } diff --git a/domain/src/main/java/org/mercury_im/messenger/core/data/repository/IkeyKeyRepository.java b/domain/src/main/java/org/mercury_im/messenger/core/data/repository/IkeyKeyRepository.java index eb6918c..53be04d 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/data/repository/IkeyKeyRepository.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/data/repository/IkeyKeyRepository.java @@ -1,8 +1,8 @@ package org.mercury_im.messenger.core.data.repository; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.jivesoftware.smackx.ikey.util.IkeyTrust; import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; +import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jxmpp.jid.EntityBareJid; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.entity.Account; @@ -11,7 +11,6 @@ import org.pgpainless.key.OpenPgpV4Fingerprint; import java.util.UUID; import io.reactivex.Completable; -import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; @@ -47,15 +46,15 @@ public interface IkeyKeyRepository { Completable storeBackupPassphrase(UUID accountID, OpenPgpSecretKeyBackupPassphrase passphrase); - default Observable> loadSuperordinateTrust(Account account, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint) { + default Observable> loadSuperordinateTrust(Account account, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint) { return loadSuperordinateTrust(account.getId(), jid, fingerprint); } - Observable> loadSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint); + Observable> loadSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint); - default Completable storeSuperordinateTrust(Account account, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint, IkeyTrust trust) { + default Completable storeSuperordinateTrust(Account account, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust) { return storeSuperordinateTrust(account.getId(), jid, fingerprint, trust); } - Completable storeSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint, IkeyTrust trust); + Completable storeSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust); } diff --git a/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpRepository.java b/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpRepository.java index 0d4681d..0bd1492 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpRepository.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpRepository.java @@ -43,14 +43,6 @@ public interface OpenPgpRepository { Single loadAnnouncementDate(UUID accountId, OpenPgpV4Fingerprint fingerprint); - Single loadTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint); - - Single loadTrust(UUID accountId, OpenPgpV4Fingerprint fingerprint); - - Completable storeTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust); - - Completable storeTrust(UUID accountId, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust); - Observable> observeFingerprintsOf(UUID accountId, String peerAddress); Observable> observeLocalFingerprintOf(UUID accountId); diff --git a/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpTrustRepository.java b/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpTrustRepository.java new file mode 100644 index 0000000..d4f6266 --- /dev/null +++ b/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpTrustRepository.java @@ -0,0 +1,18 @@ +package org.mercury_im.messenger.core.data.repository; + +import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; +import org.jxmpp.jid.EntityBareJid; +import org.pgpainless.key.OpenPgpV4Fingerprint; + +import java.util.UUID; + +import io.reactivex.Completable; +import io.reactivex.Single; + +public interface OpenPgpTrustRepository { + + Single loadTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint); + + Completable storeTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust); + +} diff --git a/domain/src/main/java/org/mercury_im/messenger/core/di/module/IkeyModule.java b/domain/src/main/java/org/mercury_im/messenger/core/di/module/IkeyModule.java index 9788a3d..418a788 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/di/module/IkeyModule.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/di/module/IkeyModule.java @@ -2,6 +2,7 @@ package org.mercury_im.messenger.core.di.module; import org.mercury_im.messenger.core.crypto.LocalOxKeyGenerationStrategy; import org.mercury_im.messenger.core.crypto.OxPlusIkeyKeyGenerationStrategy; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; import javax.inject.Singleton; diff --git a/domain/src/main/java/org/mercury_im/messenger/core/di/module/ViewModelModule.java b/domain/src/main/java/org/mercury_im/messenger/core/di/module/ViewModelModule.java index 67245c3..6b8d76e 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/di/module/ViewModelModule.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/di/module/ViewModelModule.java @@ -9,6 +9,7 @@ import org.mercury_im.messenger.core.data.repository.AccountRepository; import org.mercury_im.messenger.core.data.repository.DirectChatRepository; import org.mercury_im.messenger.core.data.repository.MessageRepository; import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; import org.mercury_im.messenger.core.data.repository.PeerRepository; import org.mercury_im.messenger.core.viewmodel.account.LoginViewModel; import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel; @@ -45,11 +46,12 @@ public class ViewModelModule { @Singleton static AccountDetailsViewModel provideAccountDetailsViewModel(MercuryConnectionManager connectionManager, OpenPgpRepository openPgpRepository, + OpenPgpTrustRepository trustRepository, IkeyRepository ikeyRepository, AccountRepository accountRepository, SchedulersFacade schedulers, IkeyInitializer ikeyInitializer) { - return new AccountDetailsViewModel(connectionManager, openPgpRepository, ikeyRepository, accountRepository, schedulers, ikeyInitializer); + return new AccountDetailsViewModel(connectionManager, openPgpRepository, trustRepository, ikeyRepository, accountRepository, schedulers, ikeyInitializer); } @Provides diff --git a/domain/src/main/java/org/mercury_im/messenger/core/store/crypto/IkeyAwareOpenPgpStore.java b/domain/src/main/java/org/mercury_im/messenger/core/store/crypto/IkeyAwareOpenPgpStore.java new file mode 100644 index 0000000..0278f6a --- /dev/null +++ b/domain/src/main/java/org/mercury_im/messenger/core/store/crypto/IkeyAwareOpenPgpStore.java @@ -0,0 +1,133 @@ +package org.mercury_im.messenger.core.store.crypto; + +import org.jivesoftware.smackx.ikey.mechanism.IkeyType; +import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpTrustStore; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; +import org.mercury_im.messenger.core.SchedulersFacade; +import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; +import org.mercury_im.messenger.core.data.repository.IkeyKeyRepository; +import org.mercury_im.messenger.core.data.repository.IkeyRecordRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; +import org.mercury_im.messenger.core.util.Optional; +import org.pgpainless.key.OpenPgpV4Fingerprint; + +import java.io.IOException; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.subjects.BehaviorSubject; + +public class IkeyAwareOpenPgpStore extends MercuryOpenPgpStore { + + public IkeyAwareOpenPgpStore(UUID accountId, + OpenPgpRepository openPgpRepository, + OpenPgpTrustRepository openPgpTrustRepository, + IkeyRepository ikeyRepository, + SchedulersFacade schedulersFacade) { + this(accountId, openPgpRepository, openPgpTrustRepository, ikeyRepository, ikeyRepository, schedulersFacade); + } + + public IkeyAwareOpenPgpStore(UUID accountId, + OpenPgpRepository openPgpRepository, + OpenPgpTrustRepository openPgpTrustRepository, + IkeyKeyRepository ikeyKeyRepository, + IkeyRecordRepository ikeyRecordRepository, + SchedulersFacade schedulers) { + super(new KeyStore(accountId, openPgpRepository, schedulers), + new MetadataStore(accountId, openPgpRepository, schedulers), + new TrustStore(accountId, openPgpTrustRepository, ikeyKeyRepository, ikeyRecordRepository, schedulers)); + } + + public static class TrustStore extends AbstractOpenPgpTrustStore { + + private static final Logger LOGGER = Logger.getLogger(TrustStore.class.getName()); + + private final UUID accountId; + private final OpenPgpTrustRepository openPgpTrustRepository; + private final IkeyKeyRepository ikeyKeyRepository; + private final IkeyRecordRepository ikeyRecordRepository; + private final SchedulersFacade schedulers; + + private final CompositeDisposable disposable = new CompositeDisposable(); + private final BehaviorSubject accountIsIkeyAware = BehaviorSubject.createDefault(false); + + public TrustStore(UUID accountId, OpenPgpTrustRepository openPgpTrustRepository, IkeyKeyRepository ikeyKeyRepository, IkeyRecordRepository ikeyRecordRepository, SchedulersFacade schedulersFacade) { + this.accountId = accountId; + this.openPgpTrustRepository = openPgpTrustRepository; + this.ikeyKeyRepository = ikeyKeyRepository; + this.ikeyRecordRepository = ikeyRecordRepository; + this.schedulers = schedulersFacade; + + accountIsIkeyAware().subscribe(accountIsIkeyAware); + } + + @Override + protected Trust readTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException { + EntityBareJid jid = owner.asEntityBareJidOrThrow(); + Trust trust = accountIsIkeyAware.firstOrError() + .flatMap(isAware -> isAware ? + readIkeyTrust(jid, fingerprint) : + readManualTrust(jid, fingerprint)) + .blockingGet(); + return trust == null ? Trust.undecided : trust; + } + + private Single readIkeyTrust(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint) { + return ikeyRecordRepository.loadRecord(accountId, owner) + .map(record -> record.hasSubordinate(fingerprint) ? + record.getTrust() : Trust.undecided) + .doOnNext(trust -> LOGGER.log(Level.INFO, "Read ikey trust " + trust + " for device key " + fingerprint + " of contact " + owner)) + .firstElement() + .switchIfEmpty(readManualTrust(owner, fingerprint)); + } + + private Single readManualTrust(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint) { + return openPgpTrustRepository.loadTrust(accountId, owner, fingerprint) + .doOnSuccess(trust -> LOGGER.log(Level.INFO, "Read manual trust " + trust + " for device key " + fingerprint + " of contact " + owner)); + } + + private Completable writeManualTrust(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) { + return openPgpTrustRepository.storeTrust(accountId, owner.asEntityBareJidIfPossible(), fingerprint, trust) + .doOnComplete(() -> LOGGER.log(Level.INFO, + "Successfully marked device key " + fingerprint + " of " + owner + " as " + trust)); + } + + @Override + protected void writeTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) throws IOException { + EntityBareJid jid = owner.asEntityBareJidOrThrow(); + disposable.add( + contactHasIkeyRecord(owner.asEntityBareJidIfPossible()) + .flatMapCompletable(hasRecord -> hasRecord && accountIsIkeyAware.getValue() ? + skipManualTrustForIkeyContact(jid, fingerprint, trust) : + writeManualTrust(jid, fingerprint, trust)) + .compose(schedulers.executeUiSafeCompletable()) + .subscribe(() -> {}, + e -> LOGGER.log(Level.SEVERE, "An error happened while marking device key " + fingerprint + " of " + jid + " as " + trust, e))); + } + + private Completable skipManualTrustForIkeyContact(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) { + return Completable.complete() + .doOnComplete(() -> LOGGER.log(Level.INFO, + "Contact " + owner + " has an Ikey Record, so do not mark " + fingerprint + " as " + trust)); + } + + private Single contactHasIkeyRecord(EntityBareJid owner) { + return ikeyRecordRepository.loadRecord(accountId, owner.asEntityBareJidOrThrow()) + .isEmpty() + .map(resultEmpty -> !resultEmpty); // negate + } + + protected Observable accountIsIkeyAware() { + return ikeyKeyRepository.loadSecretKey(accountId) + .map(Optional::isPresent) + .compose(schedulers.executeUiSafeObservable()); + } + } +} diff --git a/domain/src/main/java/org/mercury_im/messenger/core/store/crypto/MercuryOpenPgpStore.java b/domain/src/main/java/org/mercury_im/messenger/core/store/crypto/MercuryOpenPgpStore.java index 928c85f..da80810 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/store/crypto/MercuryOpenPgpStore.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/store/crypto/MercuryOpenPgpStore.java @@ -10,6 +10,7 @@ import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpTrustStore; import org.jxmpp.jid.BareJid; import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; import org.pgpainless.key.OpenPgpV4Fingerprint; import java.io.IOException; @@ -26,14 +27,20 @@ public class MercuryOpenPgpStore extends AbstractOpenPgpStore { protected static final Logger LOGGER = Logger.getLogger(MercuryOpenPgpStore.class.getName()); - public MercuryOpenPgpStore(UUID accountId, OpenPgpRepository repository, SchedulersFacade schedulers) { - super( + public MercuryOpenPgpStore(UUID accountId, OpenPgpRepository repository, OpenPgpTrustRepository trustRepository, SchedulersFacade schedulers) { + this( new KeyStore(accountId, repository, schedulers), new MetadataStore(accountId, repository, schedulers), - new TrustStore(accountId, repository, schedulers)); + new TrustStore(accountId, trustRepository, schedulers)); } - private static class KeyStore extends AbstractOpenPgpKeyStore { + public MercuryOpenPgpStore(AbstractOpenPgpKeyStore keyStore, + AbstractOpenPgpMetadataStore metadataStore, + AbstractOpenPgpTrustStore trustStore) { + super(keyStore, metadataStore, trustStore); + } + + public static class KeyStore extends AbstractOpenPgpKeyStore { private final CompositeDisposable disposable = new CompositeDisposable(); private final OpenPgpRepository repository; @@ -117,7 +124,7 @@ public class MercuryOpenPgpStore extends AbstractOpenPgpStore { } } - private static class MetadataStore extends AbstractOpenPgpMetadataStore { + public static class MetadataStore extends AbstractOpenPgpMetadataStore { private final CompositeDisposable disposable = new CompositeDisposable(); private final OpenPgpRepository repository; @@ -141,7 +148,8 @@ public class MercuryOpenPgpStore extends AbstractOpenPgpStore { @Override protected void writeAnnouncedFingerprintsOf(BareJid contact, Map metadata) throws IOException { - disposable.add(repository.storeAnnouncedFingerprints(accountId, contact.asEntityBareJidIfPossible(), metadata) + disposable.add( + repository.storeAnnouncedFingerprints(accountId, contact.asEntityBareJidIfPossible(), metadata) .subscribeOn(schedulers.getIoScheduler()) .subscribe( () -> MercuryOpenPgpStore.LOGGER.log(Level.FINER, "Successfully updated announced OX fingerprints for " + contact + " (accountId=" + accountId + ")"), @@ -159,12 +167,12 @@ public class MercuryOpenPgpStore extends AbstractOpenPgpStore { public static class TrustStore extends AbstractOpenPgpTrustStore { private final CompositeDisposable disposable = new CompositeDisposable(); - private final OpenPgpRepository repository; + private final OpenPgpTrustRepository repository; private final SchedulersFacade schedulers; private final UUID accountId; - public TrustStore(UUID accountId, OpenPgpRepository repository, SchedulersFacade schedulers) { + public TrustStore(UUID accountId, OpenPgpTrustRepository repository, SchedulersFacade schedulers) { this.accountId = accountId; this.repository = repository; this.schedulers = schedulers; diff --git a/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/account/detail/AccountDetailsViewModel.java b/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/account/detail/AccountDetailsViewModel.java index ba422b6..12425d8 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/account/detail/AccountDetailsViewModel.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/account/detail/AccountDetailsViewModel.java @@ -5,8 +5,10 @@ import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smackx.ikey.IkeyManager; import org.jivesoftware.smackx.ikey.element.IkeyElement; import org.jivesoftware.smackx.ikey.element.SubordinateElement; +import org.jivesoftware.smackx.ox.OpenPgpManager; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement; +import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore; import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil; import org.jivesoftware.smackx.pep.PepManager; @@ -25,6 +27,8 @@ import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer; import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; import org.mercury_im.messenger.core.data.repository.AccountRepository; import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; +import org.mercury_im.messenger.core.store.crypto.IkeyAwareOpenPgpStore; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.viewmodel.MercuryViewModel; import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; @@ -55,6 +59,7 @@ public class AccountDetailsViewModel implements MercuryViewModel { private MercuryConnectionManager connectionManager; private final OpenPgpRepository openPgpRepository; + private final OpenPgpTrustRepository deviceKeyTrustRepository; private final IkeyRepository ikeyRepository; private final AccountRepository accountRepository; private final SchedulersFacade schedulers; @@ -63,12 +68,14 @@ public class AccountDetailsViewModel implements MercuryViewModel { @Inject public AccountDetailsViewModel(MercuryConnectionManager connectionManager, OpenPgpRepository openPgpRepository, + OpenPgpTrustRepository deviceKeyTrustRepository, IkeyRepository ikeyRepository, AccountRepository accountRepository, SchedulersFacade schedulers, IkeyInitializer ikeyInitializer) { this.connectionManager = connectionManager; this.openPgpRepository = openPgpRepository; + this.deviceKeyTrustRepository = deviceKeyTrustRepository; this.ikeyRepository = ikeyRepository; this.accountRepository = accountRepository; this.schedulers = schedulers; @@ -76,9 +83,14 @@ public class AccountDetailsViewModel implements MercuryViewModel { } public void markFingerprintTrusted(UUID accountId, OpenPgpV4Fingerprint fingerprint, boolean trusted) { - addDisposable(openPgpRepository.storeTrust(accountId, fingerprint, trusted ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted) + OpenPgpTrustStore.Trust trust = trusted ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted; + addDisposable(accountRepository.getAccount(accountId) + .toSingle() + .flatMapCompletable(account -> deviceKeyTrustRepository + .storeTrust(account.getId(), account.getJid(), fingerprint, trust)) .compose(schedulers.executeUiSafeCompletable()) - .subscribe()); + .subscribe(() -> LOGGER.log(Level.INFO, "Fingerprint " + fingerprint + " marked " + trust), + e -> LOGGER.log(Level.SEVERE, "An exception occurred marking fingerprint " + fingerprint + " as " + trusted, e))); } public void sendIkeyElement(UUID accountId) { @@ -150,12 +162,14 @@ public class AccountDetailsViewModel implements MercuryViewModel { .map(key -> key.isPresent() ? new Optional<>(new OpenPgpV4Fingerprint(key.getItem().getPublicKey())) : new Optional<>()); } - public Observable> observeLocalFingerprint(UUID accountId) { + public Observable> observeLocalDeviceFingerprint(UUID accountId) { return openPgpRepository.observeLocalFingerprintOf(accountId); } - public Observable> observeRemoteFingerprints(UUID accountId) { - return observeLocalFingerprint(accountId) + public Observable> observeRemoteDeviceFingerprints(UUID accountId) { + OpenPgpStore store = OpenPgpManager.getInstanceFor(connectionManager.getConnection(accountId).getConnection()) + .getOpenPgpProvider().getStore(); + return observeLocalDeviceFingerprint(accountId) .flatMap(optional -> accountRepository.getAccount(accountId).toSingle() .flatMapObservable(account -> openPgpRepository.observeFingerprints(accountId, account.getJid()) .map(list -> { @@ -165,6 +179,7 @@ public class AccountDetailsViewModel implements MercuryViewModel { List remoteFingerprints = new ArrayList<>(); for(FingerprintViewItem f : list) { + f.setTrusted(store.getTrust(f.getOwner(), f.getFingerprint())); if (!f.getFingerprint().equals(optional.getItem())) { remoteFingerprints.add(f); } diff --git a/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/contact/ContactDetailViewModel.java b/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/contact/ContactDetailViewModel.java index b220aa1..f0a246b 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/contact/ContactDetailViewModel.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/contact/ContactDetailViewModel.java @@ -6,17 +6,18 @@ import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.roster.Roster; import org.jivesoftware.smack.roster.RosterEntry; import org.jivesoftware.smack.roster.RosterGroup; -import org.jivesoftware.smackx.ikey.util.IkeyTrust; +import org.jivesoftware.smackx.ox.OpenPgpManager; +import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore; import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.connection.MercuryConnection; import org.mercury_im.messenger.core.connection.MercuryConnectionManager; import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; import org.mercury_im.messenger.core.data.repository.DirectChatRepository; import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; +import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository; import org.mercury_im.messenger.core.data.repository.PeerRepository; import org.mercury_im.messenger.core.util.CombinedPresenceListener; import org.mercury_im.messenger.core.util.Optional; @@ -27,10 +28,13 @@ import org.mercury_im.messenger.entity.chat.Chat; import org.mercury_im.messenger.entity.contact.Peer; import org.pgpainless.key.OpenPgpV4Fingerprint; +import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.inject.Inject; @@ -42,10 +46,13 @@ import lombok.Getter; public class ContactDetailViewModel implements MercuryViewModel { + private static final Logger LOGGER = Logger.getLogger(ContactDetailViewModel.class.getName()); + private final MercuryConnectionManager connectionManager; private final PeerRepository peerRepository; private final DirectChatRepository directChatRepository; private final OpenPgpRepository openPgpRepository; + private final OpenPgpTrustRepository trustRepository; private final IkeyRepository ikeyRepository; private final SchedulersFacade schedulers; @@ -62,6 +69,8 @@ public class ContactDetailViewModel implements MercuryViewModel { private BehaviorSubject> contactDeviceFingerprints = BehaviorSubject.createDefault(Collections.emptyList()); private BehaviorSubject> contactAvatarBase = BehaviorSubject.create(); + private OpenPgpStore openPgpStore; + @Getter private UUID peerId; @@ -73,12 +82,14 @@ public class ContactDetailViewModel implements MercuryViewModel { PeerRepository peerRepository, DirectChatRepository directChatRepository, OpenPgpRepository openPgpRepository, + OpenPgpTrustRepository trustRepository, IkeyRepository ikeyRepository, SchedulersFacade schedulers) { this.connectionManager = connectionManager; this.peerRepository = peerRepository; this.directChatRepository = directChatRepository; this.openPgpRepository = openPgpRepository; + this.trustRepository = trustRepository; this.ikeyRepository = ikeyRepository; this.schedulers = schedulers; } @@ -89,6 +100,9 @@ public class ContactDetailViewModel implements MercuryViewModel { } public Completable init(Peer peer) { + this.openPgpStore = OpenPgpManager.getInstanceFor(connectionManager.getConnection(peer.getAccount().getId()).getConnection()) + .getOpenPgpProvider().getStore(); + return Completable.fromAction(() -> { this.peerId = peer.getId(); this.accountId = peer.getAccount().getId(); @@ -109,11 +123,19 @@ public class ContactDetailViewModel implements MercuryViewModel { contactAvatarBase.onNext(new Tuple<>(peer.getDisplayName(), peer.getJid())); })); + // Device keys addDisposable(openPgpRepository .observeFingerprints(peer.getAccount().getId(), peer.getJid()) + .map(viewItems -> { + for (FingerprintViewItem item : viewItems) { + item.setTrusted(openPgpStore.getTrust(item.getOwner(), item.getFingerprint())); + } + return viewItems; + }) .compose(schedulers.executeUiSafeObservable()) .subscribe(fingerprints -> contactDeviceFingerprints.onNext(fingerprints))); + // Ikey addDisposable(ikeyRepository .loadRecord(peer.getAccount().getId(), peer.getJid()) .compose(schedulers.executeUiSafeObservable()) @@ -121,7 +143,7 @@ public class ContactDetailViewModel implements MercuryViewModel { peer.getAccount().getId(), record.getJid(), new OpenPgpV4Fingerprint(record.getSuperordinate()), record.getTimestamp(), record.getTimestamp(), - OpenPgpTrustStore.Trust.trusted) // TODO + record.getTrust()) ) .map(Optional::new) .subscribe(contactIdentityFingerprint::onNext)); @@ -219,17 +241,15 @@ public class ContactDetailViewModel implements MercuryViewModel { } } - public void markDeviceFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) { - openPgpRepository.storeTrust(accountId, contactAddress.getValue(), fingerprint, - checked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted) - .subscribe(); + public void markDeviceFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) throws IOException { + openPgpStore.setTrust(getContactAddress().blockingFirst(), fingerprint, checked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted); } public void markIkeyFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, EntityBareJid owner, boolean isChecked) { - IkeyTrust trust = new IkeyTrust(); - trust.setTrust(isChecked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted); - ikeyRepository.storeSuperordinateTrust(accountId, owner, fingerprint, trust) - .subscribe(); + OpenPgpTrustStore.Trust trust = isChecked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted; + addDisposable(ikeyRepository.storeSuperordinateTrust(accountId, owner, fingerprint, trust) + .subscribe(() -> LOGGER.log(Level.INFO, "Marked Ikey " + fingerprint + " of " + owner + " as " + trust), + e -> LOGGER.log(Level.SEVERE, "Error marking ikey " + fingerprint + " of " + owner + " as " + trust, e))); } public static abstract class FilteredPresenceEventListener extends CombinedPresenceListener {