Compare commits

...

2 Commits

31 changed files with 489 additions and 150 deletions

View File

@ -106,7 +106,7 @@ public class AccountDetailsFragment extends Fragment {
viewModel = new ViewModelProvider(this, factory)
.get(AndroidAccountDetailsViewModel.class);
this.otherFingerprintsAdapter = new ToggleableFingerprintsAdapter(this::markFingerprintTrusted);
this.otherFingerprintsAdapter = new ToggleableFingerprintsAdapter(context, this::markFingerprintTrusted);
this.otherFingerprintsAdapter.setItemLongClickListener(fingerprint -> viewModel.unpublishPublicKey(fingerprint));
}

View File

@ -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()));

View File

@ -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<ContactDetailViewModel> {
@ -122,6 +123,7 @@ public class AndroidContactDetailViewModel extends ViewModel implements MercuryA
}
@SneakyThrows
public void markDeviceFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) {
commonViewModel.markDeviceFingerprintTrusted(fingerprint, checked);
}

View File

@ -114,7 +114,7 @@ public class ContactDetailFragment extends Fragment {
contactName.setOnClickListener(v -> displayChangeContactNameDialog());
fingerprintsAdapter = new ToggleableFingerprintsAdapter(
fingerprintsAdapter = new ToggleableFingerprintsAdapter(getContext(),
(fingerprint, checked) -> viewModel.markDeviceFingerprintTrusted(fingerprint, checked));
fingerprintRecyclerView.setAdapter(fingerprintsAdapter);

View File

@ -1,5 +1,7 @@
package org.mercury_im.messenger.android.ui.openpgp;
import android.content.Context;
import android.os.Build;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -9,6 +11,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
import org.pgpainless.key.OpenPgpV4Fingerprint;
@ -25,8 +28,10 @@ public class ToggleableFingerprintsAdapter extends RecyclerView.Adapter<Toggleab
private OnFingerprintItemLongClickListener longClickListener = null;
private static final DateFormat dateFormat = SimpleDateFormat.getDateInstance();
private Context context;
public ToggleableFingerprintsAdapter(OnFingerprintItemToggleListener toggleListener) {
public ToggleableFingerprintsAdapter(Context context, OnFingerprintItemToggleListener toggleListener) {
this.context = context;
this.toggleListener = toggleListener;
}
@ -57,8 +62,13 @@ public class ToggleableFingerprintsAdapter extends RecyclerView.Adapter<Toggleab
holder.fingerprintTimestamp.setText(dateFormat.format(f.getModificationDate()));
holder.trustSwitch.setChecked(f.isTrusted());
holder.trustSwitch.setOnCheckedChangeListener(
(buttonView, isChecked) -> toggleListener.onFingerprintToggled(fingerprint, isChecked));
holder.trustSwitch.setOnClickListener(v -> {
boolean checked = ((Switch) v).isChecked();
toggleListener.onFingerprintToggled(fingerprint, checked);
});
if (f.getTrusted() == OpenPgpTrustStore.Trust.ikey_trusted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
holder.trustSwitch.setThumbDrawable(context.getDrawable(R.drawable.ic_lock_black_24dp));
}
holder.divider.setVisibility(position == fingerprints.size() - 1 ? View.GONE : View.VISIBLE);
holder.itemView.setOnLongClickListener(v -> {

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
</vector>

View File

@ -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<Persistable> data) {
return new RxIkeyRepository(data);
}
@Provides
@Singleton
static OpenPgpTrustRepository provideOpenPgpTrustRepository(IkeyRepository recordRepository, OpenPgpRepository openPgpRepository) {
return (OpenPgpTrustRepository) openPgpRepository;
// return new RxIkeyTrustRepository(recordRepository);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<Persistable> data;
@Inject
public RxIkeyRepository(ReactiveEntityStore<Persistable> data) {
this.data = data;
}
@ -102,30 +104,27 @@ public class RxIkeyRepository implements IkeyRepository {
}
@Override
public Observable<Optional<IkeyTrust>> 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<Optional<OpenPgpTrustStore.Trust>> 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));
}
@ -158,11 +159,12 @@ 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());
@ -190,7 +192,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 +210,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();

View File

@ -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<OpenPgpTrustStore.Trust> 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();
}
}

View File

@ -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<OpenPgpV4Fingerprint, Date> metadata) {
Set<OpenPgpV4Fingerprint> 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<AnnouncedOpenPgpContactKey> entities = new LinkedList<>();
for (Map.Entry<OpenPgpV4Fingerprint, Date> 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<OpenPgpTrustStore.Trust> 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<List<OpenPgpV4Fingerprint>> observeFingerprintsOf(UUID accountId, String peerAddress) {
return data.select(OpenPgpPublicKeyRing.class)

View File

@ -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);
}

View File

@ -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<Persistable> 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));
}
}

View File

@ -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;
}

View File

@ -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<IkeySubordinateRecord> 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<IkeySubordinateRecord> subordinates) {
public IkeyRecord(EntityBareJid jid, Date timestamp, PGPPublicKeyRing superordinate, List<IkeySubordinateRecord> 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;
}
}

View File

@ -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();

View File

@ -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

View File

@ -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;
}

View File

@ -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<UUID, MercuryConnection> 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<Account> event) {

View File

@ -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);
}

View File

@ -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<Optional<IkeyTrust>> loadSuperordinateTrust(Account account, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint) {
default Observable<Optional<OpenPgpTrustStore.Trust>> loadSuperordinateTrust(Account account, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint) {
return loadSuperordinateTrust(account.getId(), jid, fingerprint);
}
Observable<Optional<IkeyTrust>> loadSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint);
Observable<Optional<OpenPgpTrustStore.Trust>> 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);
}

View File

@ -43,14 +43,6 @@ public interface OpenPgpRepository {
Single<Date> loadAnnouncementDate(UUID accountId, OpenPgpV4Fingerprint fingerprint);
Single<OpenPgpTrustStore.Trust> loadTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint);
Single<OpenPgpTrustStore.Trust> 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<List<OpenPgpV4Fingerprint>> observeFingerprintsOf(UUID accountId, String peerAddress);
Observable<Optional<OpenPgpV4Fingerprint>> observeLocalFingerprintOf(UUID accountId);

View File

@ -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<OpenPgpTrustStore.Trust> loadTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint);
Completable storeTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust);
}

View File

@ -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;

View File

@ -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

View File

@ -0,0 +1,106 @@
package org.mercury_im.messenger.core.store.crypto;
import org.jivesoftware.smackx.ikey.record.IkeyRecord;
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.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.Maybe;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
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();
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;
}
@Override
protected Trust readTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException {
EntityBareJid jid = owner.asEntityBareJidOrThrow();
Trust trust = readIkeyTrust(jid, fingerprint)
.blockingGet();
return trust == null ? Trust.undecided : trust;
}
private Single<Trust> readIkeyTrust(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint) {
return ikeyRecordRepository.loadRecord(accountId, owner)
.filter(record -> record.getTrust() == Trust.trusted)
.filter(record -> record.hasSubordinate(fingerprint))
.map(IkeyRecord::getTrust)
.map(trust -> trust == Trust.trusted ? Trust.ikey_trusted : trust)
.firstElement()
.flatMap(t -> t == Trust.undecided ? readManualTrust(owner, fingerprint).toMaybe() : Maybe.just(t))
.switchIfEmpty(readManualTrust(owner, fingerprint));
}
private Single<Trust> 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(
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)));
}
}
}

View File

@ -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<OpenPgpV4Fingerprint, Date> 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;

View File

@ -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<Optional<OpenPgpV4Fingerprint>> observeLocalFingerprint(UUID accountId) {
public Observable<Optional<OpenPgpV4Fingerprint>> observeLocalDeviceFingerprint(UUID accountId) {
return openPgpRepository.observeLocalFingerprintOf(accountId);
}
public Observable<List<FingerprintViewItem>> observeRemoteFingerprints(UUID accountId) {
return observeLocalFingerprint(accountId)
public Observable<List<FingerprintViewItem>> 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<FingerprintViewItem> remoteFingerprints = new ArrayList<>();
for(FingerprintViewItem f : list) {
f.setTrusted(store.getTrust(f.getOwner(), f.getFingerprint()));
if (!f.getFingerprint().equals(optional.getItem())) {
remoteFingerprints.add(f);
}

View File

@ -6,17 +6,19 @@ 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.crypto.OpenPgpProvider;
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 +29,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 +47,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 +70,8 @@ public class ContactDetailViewModel implements MercuryViewModel {
private BehaviorSubject<List<FingerprintViewItem>> contactDeviceFingerprints = BehaviorSubject.createDefault(Collections.emptyList());
private BehaviorSubject<Tuple<String, EntityBareJid>> contactAvatarBase = BehaviorSubject.create();
private OpenPgpStore openPgpStore;
@Getter
private UUID peerId;
@ -73,12 +83,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 +101,10 @@ public class ContactDetailViewModel implements MercuryViewModel {
}
public Completable init(Peer peer) {
OpenPgpManager openPgpManager = OpenPgpManager.getInstanceFor(connectionManager.getConnection(peer.getAccount().getId()).getConnection());
OpenPgpProvider provider = openPgpManager.getOpenPgpProvider();
this.openPgpStore = provider.getStore();
return Completable.fromAction(() -> {
this.peerId = peer.getId();
this.accountId = peer.getAccount().getId();
@ -109,11 +125,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 +145,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 +243,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 {

View File

@ -23,6 +23,6 @@ public class FingerprintViewItem {
OpenPgpTrustStore.Trust trusted;
public boolean isTrusted() {
return trusted == OpenPgpTrustStore.Trust.trusted;
return trusted == OpenPgpTrustStore.Trust.trusted || trusted == OpenPgpTrustStore.Trust.ikey_trusted;
}
}