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) viewModel = new ViewModelProvider(this, factory)
.get(AndroidAccountDetailsViewModel.class); .get(AndroidAccountDetailsViewModel.class);
this.otherFingerprintsAdapter = new ToggleableFingerprintsAdapter(this::markFingerprintTrusted); this.otherFingerprintsAdapter = new ToggleableFingerprintsAdapter(context, this::markFingerprintTrusted);
this.otherFingerprintsAdapter.setItemLongClickListener(fingerprint -> viewModel.unpublishPublicKey(fingerprint)); 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"); LOGGER.log(Level.INFO, "Creating AndroidAccountDetailsViewModel");
((MercuryImApplication) application).getAppComponent().inject(this); ((MercuryImApplication) application).getAppComponent().inject(this);
addDisposable(getCommonViewModel().observeLocalFingerprint(accountId) addDisposable(getCommonViewModel().observeLocalDeviceFingerprint(accountId)
.compose(schedulers.executeUiSafeObservable()) .compose(schedulers.executeUiSafeObservable())
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::getItem) .map(Optional::getItem)
@ -64,7 +64,7 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements
.subscribe(ikeyFingerprint::postValue, .subscribe(ikeyFingerprint::postValue,
e -> LOGGER.log(Level.SEVERE, "Error displaying ikey fingerprint", e))); e -> LOGGER.log(Level.SEVERE, "Error displaying ikey fingerprint", e)));
addDisposable(getCommonViewModel().observeRemoteFingerprints(accountId) addDisposable(getCommonViewModel().observeRemoteDeviceFingerprints(accountId)
.compose(schedulers.executeUiSafeObservable()) .compose(schedulers.executeUiSafeObservable())
.subscribe(list -> { .subscribe(list -> {
LOGGER.log(Level.INFO, "Set remote fingerprints to list: " + Arrays.toString(list.toArray())); 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.Completable;
import io.reactivex.Single; import io.reactivex.Single;
import lombok.SneakyThrows;
public class AndroidContactDetailViewModel extends ViewModel implements MercuryAndroidViewModel<ContactDetailViewModel> { 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) { public void markDeviceFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) {
commonViewModel.markDeviceFingerprintTrusted(fingerprint, checked); commonViewModel.markDeviceFingerprintTrusted(fingerprint, checked);
} }

View File

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

View File

@ -1,5 +1,7 @@
package org.mercury_im.messenger.android.ui.openpgp; package org.mercury_im.messenger.android.ui.openpgp;
import android.content.Context;
import android.os.Build;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -9,6 +11,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.mercury_im.messenger.R; import org.mercury_im.messenger.R;
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
@ -25,8 +28,10 @@ public class ToggleableFingerprintsAdapter extends RecyclerView.Adapter<Toggleab
private OnFingerprintItemLongClickListener longClickListener = null; private OnFingerprintItemLongClickListener longClickListener = null;
private static final DateFormat dateFormat = SimpleDateFormat.getDateInstance(); 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; this.toggleListener = toggleListener;
} }
@ -57,8 +62,13 @@ public class ToggleableFingerprintsAdapter extends RecyclerView.Adapter<Toggleab
holder.fingerprintTimestamp.setText(dateFormat.format(f.getModificationDate())); holder.fingerprintTimestamp.setText(dateFormat.format(f.getModificationDate()));
holder.trustSwitch.setChecked(f.isTrusted()); holder.trustSwitch.setChecked(f.isTrusted());
holder.trustSwitch.setOnCheckedChangeListener( holder.trustSwitch.setOnClickListener(v -> {
(buttonView, isChecked) -> toggleListener.onFingerprintToggled(fingerprint, isChecked)); 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.divider.setVisibility(position == fingerprints.size() - 1 ? View.GONE : View.VISIBLE);
holder.itemView.setOnLongClickListener(v -> { 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; 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.crypto.ikey.IkeyRepository;
import org.mercury_im.messenger.core.data.repository.AccountRepository; 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.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.IkeyRecordRepository;
import org.mercury_im.messenger.core.data.repository.MessageRepository; 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.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.data.repository.PeerRepository;
import org.mercury_im.messenger.data.mapping.AccountMapping; import org.mercury_im.messenger.data.mapping.AccountMapping;
import org.mercury_im.messenger.data.mapping.DirectChatMapping; 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.RxEntityCapsRepository;
import org.mercury_im.messenger.data.repository.RxGroupChatRepository; import org.mercury_im.messenger.data.repository.RxGroupChatRepository;
import org.mercury_im.messenger.data.repository.RxIkeyRepository; 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.RxMessageRepository;
import org.mercury_im.messenger.data.repository.RxOpenPgpRepository; import org.mercury_im.messenger.data.repository.RxOpenPgpRepository;
import org.mercury_im.messenger.data.repository.RxPeerRepository; import org.mercury_im.messenger.data.repository.RxPeerRepository;
@ -100,4 +103,11 @@ public class RepositoryModule {
static IkeyRepository provideIkeyRepository(ReactiveEntityStore<Persistable> data) { static IkeyRepository provideIkeyRepository(ReactiveEntityStore<Persistable> data) {
return new RxIkeyRepository(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; package org.mercury_im.messenger.data.model;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.data.converter.Base64PGPPublicKeyRingConverter; import org.mercury_im.messenger.data.converter.Base64PGPPublicKeyRingConverter;
import org.mercury_im.messenger.data.converter.EntityBareJidConverter; import org.mercury_im.messenger.data.converter.EntityBareJidConverter;
@ -15,23 +16,28 @@ import io.requery.CascadeAction;
import io.requery.Column; import io.requery.Column;
import io.requery.Convert; import io.requery.Convert;
import io.requery.Entity; import io.requery.Entity;
import io.requery.Index;
import io.requery.Key; import io.requery.Key;
import io.requery.OneToMany; import io.requery.OneToMany;
import io.requery.Table; import io.requery.Table;
import io.requery.converter.UUIDConverter; import io.requery.converter.UUIDConverter;
import lombok.ToString;
@Table(name = "ikey_record") @Table(name = "ikey_record", uniqueIndexes = "unique_ikey_record")
@Entity @Entity
@ToString
public class AbstractIkeyRecordModel { public class AbstractIkeyRecordModel {
@Key @Key
@Convert(UUIDConverter.class) @Convert(UUIDConverter.class)
UUID id; UUID id;
@Column(name = "account") @Index("unique_ikey_record")
@Column
@Convert(UUIDConverter.class) @Convert(UUIDConverter.class)
UUID accountId; UUID accountId;
@Index("unique_ikey_record")
@Column(name = "jid") @Column(name = "jid")
@Convert(EntityBareJidConverter.class) @Convert(EntityBareJidConverter.class)
EntityBareJid jid; EntityBareJid jid;
@ -50,7 +56,11 @@ public class AbstractIkeyRecordModel {
@Convert(Base64PGPPublicKeyRingConverter.class) @Convert(Base64PGPPublicKeyRingConverter.class)
PGPPublicKeyRing superordinate; PGPPublicKeyRing superordinate;
@Index("unique_ikey_record")
@Column(name = "fingerprint") @Column(name = "fingerprint")
@Convert(OpenPgpV4FingerprintConverter.class) @Convert(OpenPgpV4FingerprintConverter.class)
OpenPgpV4Fingerprint fingerprint; 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.IkeyRecord;
import org.jivesoftware.smackx.ikey.record.IkeySubordinateRecord; import org.jivesoftware.smackx.ikey.record.IkeySubordinateRecord;
import org.jivesoftware.smackx.ikey.record.OxSubordinateRecord; 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.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.OpenPgpElement;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository;
import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.data.model.IkeyRecordModel; import org.mercury_im.messenger.data.model.IkeyRecordModel;
import org.mercury_im.messenger.data.model.IkeySecretKeyModel; import org.mercury_im.messenger.data.model.IkeySecretKeyModel;
import org.mercury_im.messenger.data.model.IkeySubordinateModel; import org.mercury_im.messenger.data.model.IkeySubordinateModel;
import org.mercury_im.messenger.data.model.IkeyTrustModel;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.util.ArrayList; import java.util.ArrayList;
@ -23,6 +22,8 @@ import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject;
import io.reactivex.Completable; import io.reactivex.Completable;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
@ -36,6 +37,7 @@ public class RxIkeyRepository implements IkeyRepository {
private final ReactiveEntityStore<Persistable> data; private final ReactiveEntityStore<Persistable> data;
@Inject
public RxIkeyRepository(ReactiveEntityStore<Persistable> data) { public RxIkeyRepository(ReactiveEntityStore<Persistable> data) {
this.data = data; this.data = data;
} }
@ -102,30 +104,27 @@ public class RxIkeyRepository implements IkeyRepository {
} }
@Override @Override
public Observable<Optional<IkeyTrust>> loadSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint) { public Observable<Optional<OpenPgpTrustStore.Trust>> loadSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint) {
return data.select(IkeyTrustModel.class) return loadRecord(accountId, jid)
.where(IkeyTrustModel.ACCOUNT_ID.eq(accountId).and(IkeyTrustModel.JID.eq(jid).and(IkeyTrustModel.FINGERPRINT.eq(fingerprint)))) .map(model -> {
.get() if (fingerprint.equals(new OpenPgpV4Fingerprint(model.getSuperordinate()))) {
.observableResult() return new Optional<>(model.getTrust());
.map(r -> { } else {
IkeyTrustModel m = r.firstOrNull();
if (m == null) {
return new Optional<>(); return new Optional<>();
} }
IkeyTrust e = new IkeyTrust();
e.setTrust(m.getTrust());
return new Optional<>(e);
}); });
} }
@Override @Override
public Completable storeSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint, IkeyTrust trust) { public Completable storeSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust) {
IkeyTrustModel model = new IkeyTrustModel(); return loadRecord(accountId, jid)
model.setAccountId(accountId); .map(record -> {
model.setJid(jid); record.setTrust(trust);
model.setFingerprint(fingerprint); return record;
model.setTrust(trust.getTrust()); })
return data.upsert(model).ignoreElement(); .firstOrError()
.doOnSuccess(m -> LOGGER.log(Level.INFO, "First Record: " + m))
.flatMapCompletable(record -> storeRecord(accountId, jid, record));
} }
@Override @Override
@ -136,11 +135,13 @@ public class RxIkeyRepository implements IkeyRepository {
for (IkeySubordinateModel sub : m.getSubordinates()) { for (IkeySubordinateModel sub : m.getSubordinates()) {
if (sub.getType().equals(OpenPgpElement.NAMESPACE)) { if (sub.getType().equals(OpenPgpElement.NAMESPACE)) {
OxSubordinateRecord r = new OxSubordinateRecord(); OxSubordinateRecord r = new OxSubordinateRecord();
r.setId(sub.getId());
r.setOxFingerprint(new OpenPgpV4Fingerprint(sub.getFpr())); r.setOxFingerprint(new OpenPgpV4Fingerprint(sub.getFpr()));
r.setUri(sub.getUri()); 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)); .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.setFingerprint(new OpenPgpV4Fingerprint(record.getSuperordinate()));
m.setSuperordinate(record.getSuperordinate()); m.setSuperordinate(record.getSuperordinate());
m.setTimestamp(record.getTimestamp()); m.setTimestamp(record.getTimestamp());
m.setTrust(record.getTrust());
m.getSubordinates().clear(); m.getSubordinates().clear();
for (IkeySubordinateRecord s : record.getSubordinates()) { for (IkeySubordinateRecord s : record.getSubordinates()) {
IkeySubordinateModel sm = new IkeySubordinateModel(); IkeySubordinateModel sm = new IkeySubordinateModel();
sm.setId(UUID.randomUUID()); sm.setId(s.getId());
sm.setRecord(m); sm.setRecord(m);
sm.setFpr(s.getFingerprint()); sm.setFpr(s.getFingerprint());
sm.setType(s.getType()); sm.setType(s.getType());
@ -190,7 +192,7 @@ public class RxIkeyRepository implements IkeyRepository {
subordinateRecords.add(sr); 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)); .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.setFingerprint(new OpenPgpV4Fingerprint(record.getSuperordinate()));
m.setSuperordinate(record.getSuperordinate()); m.setSuperordinate(record.getSuperordinate());
m.setTimestamp(record.getTimestamp()); m.setTimestamp(record.getTimestamp());
m.setTrust(record.getTrust());
for (IkeySubordinateRecord s : record.getSubordinates()) { for (IkeySubordinateRecord s : record.getSubordinates()) {
IkeySubordinateModel sm = new IkeySubordinateModel(); 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.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.core.data.repository.AccountRepository; 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.OpenPgpRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository;
import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
import org.mercury_im.messenger.data.model.AnnouncedOpenPgpContactKey; import org.mercury_im.messenger.data.model.AnnouncedOpenPgpContactKey;
@ -25,6 +26,7 @@ import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level; import java.util.logging.Level;
@ -40,7 +42,7 @@ import io.requery.query.ResultDelegate;
import io.requery.query.Tuple; import io.requery.query.Tuple;
import io.requery.reactivex.ReactiveEntityStore; 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()); private static final Logger LOGGER = Logger.getLogger(RxOpenPgpRepository.class.getName());
@ -132,6 +134,12 @@ public class RxOpenPgpRepository implements OpenPgpRepository {
@Override @Override
public Completable storeAnnouncedFingerprints(UUID accountId, EntityBareJid owner, Map<OpenPgpV4Fingerprint, Date> metadata) { 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<>(); List<AnnouncedOpenPgpContactKey> entities = new LinkedList<>();
for (Map.Entry<OpenPgpV4Fingerprint, Date> entry : metadata.entrySet()) { for (Map.Entry<OpenPgpV4Fingerprint, Date> entry : metadata.entrySet()) {
AnnouncedOpenPgpContactKey entity = new AnnouncedOpenPgpContactKey(); AnnouncedOpenPgpContactKey entity = new AnnouncedOpenPgpContactKey();
@ -141,9 +149,11 @@ public class RxOpenPgpRepository implements OpenPgpRepository {
entity.setModificationDate(entry.getValue()); entity.setModificationDate(entry.getValue());
entities.add(entity); entities.add(entity);
} }
return data.upsert(entities).ignoreElement() Completable upsertNew = data.upsert(entities).ignoreElement()
.doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored announced fingerprints of " + .doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored announced fingerprints of " +
owner + " for account " + accountId + ": " + Arrays.toString(metadata.keySet().toArray()))); owner + " for account " + accountId + ": " + Arrays.toString(metadata.keySet().toArray())));
return deleteAll.andThen(upsertNew);
} }
@Override @Override
@ -196,12 +206,6 @@ public class RxOpenPgpRepository implements OpenPgpRepository {
.map(entity -> entity.getTrust() != null ? entity.getTrust() : OpenPgpTrustStore.Trust.undecided); .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 @Override
public Completable storeTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust) { public Completable storeTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust) {
OpenPgpKeyTrust entity = new OpenPgpKeyTrust(); OpenPgpKeyTrust entity = new OpenPgpKeyTrust();
@ -212,12 +216,6 @@ public class RxOpenPgpRepository implements OpenPgpRepository {
return data.upsert(entity).ignoreElement(); 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 @Override
public Observable<List<OpenPgpV4Fingerprint>> observeFingerprintsOf(UUID accountId, String peerAddress) { public Observable<List<OpenPgpV4Fingerprint>> observeFingerprintsOf(UUID accountId, String peerAddress) {
return data.select(OpenPgpPublicKeyRing.class) return data.select(OpenPgpPublicKeyRing.class)

View File

@ -1,9 +1,11 @@
package org.mercury_im.messenger.data.di.component; 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.RepositoryModule;
import org.mercury_im.messenger.data.di.module.SqliteTestDatabaseModule; import org.mercury_im.messenger.data.di.module.SqliteTestDatabaseModule;
import org.mercury_im.messenger.data.di.module.TestingSchedulerModule; import org.mercury_im.messenger.data.di.module.TestingSchedulerModule;
import org.mercury_im.messenger.data.repository.AccountRepositoryTest; import org.mercury_im.messenger.data.repository.AccountRepositoryTest;
import org.mercury_im.messenger.data.repository.IkeyRepositoryTest;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -11,6 +13,7 @@ import dagger.Component;
@Component(modules = { @Component(modules = {
RepositoryModule.class, RepositoryModule.class,
IkeyModule.class,
SqliteTestDatabaseModule.class, SqliteTestDatabaseModule.class,
TestingSchedulerModule.class TestingSchedulerModule.class
}) })
@ -18,4 +21,6 @@ import dagger.Component;
public interface InMemoryDatabaseComponent { public interface InMemoryDatabaseComponent {
void inject(AccountRepositoryTest test); 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.OpenPgpElement;
import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; 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.OpenPgpPubSubUtil;
import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper; import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper;
import org.jivesoftware.smackx.pep.PepEventListener; import org.jivesoftware.smackx.pep.PepEventListener;
@ -166,7 +167,9 @@ public final class IkeyManager extends Manager {
public void storeAndPublishElement(IkeyElement ikeyElement) public void storeAndPublishElement(IkeyElement ikeyElement)
throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, SmackException.NoResponseException, IOException, PGPException { 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); publishIkeyElement(ikeyElement);
} }
@ -214,7 +217,9 @@ public final class IkeyManager extends Manager {
return; 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); LOGGER.log(Level.WARNING, "There exists this exact, or a newer ikey record in the database for " + from);
return; return;
} }
@ -225,8 +230,17 @@ public final class IkeyManager extends Manager {
} }
if (isContenderElement(newRecord, store.loadIkeyRecord(from))) { if (isContenderElement(newRecord, store.loadIkeyRecord(from))) {
LOGGER.log(Level.INFO, "Storing contender element for " + from);
store.storeContenderIkeyRecord(from, newRecord); store.storeContenderIkeyRecord(from, newRecord);
} else { } 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); store.storeIkeyRecord(from, newRecord);
} }
} }
@ -321,8 +335,7 @@ public final class IkeyManager extends Manager {
return timestamp.after(now); return timestamp.after(now);
} }
private boolean existsSameOrNewerRecord(IkeyRecord record) throws IOException { private boolean existsSameOrNewerRecord(IkeyRecord record, IkeyRecord existingRecord) throws IOException {
IkeyRecord existingRecord = store.loadIkeyRecord(record.getJid());
if (existingRecord == null) { if (existingRecord == null) {
return false; return false;
} }

View File

@ -1,7 +1,10 @@
package org.jivesoftware.smackx.ikey.record; package org.jivesoftware.smackx.ikey.record;
import org.bouncycastle.openpgp.PGPPublicKeyRing; 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.jxmpp.jid.EntityBareJid;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
@ -10,7 +13,9 @@ import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
@ToString
public class IkeyRecord implements Serializable { public class IkeyRecord implements Serializable {
@Getter @Getter
@ -18,7 +23,6 @@ public class IkeyRecord implements Serializable {
private PGPPublicKeyRing superordinate; private PGPPublicKeyRing superordinate;
@Getter @Getter
@Setter
private final List<IkeySubordinateRecord> subordinates = new ArrayList<>(); private final List<IkeySubordinateRecord> subordinates = new ArrayList<>();
@Getter @Getter
@ -29,14 +33,31 @@ public class IkeyRecord implements Serializable {
@Setter @Setter
private EntityBareJid jid; private EntityBareJid jid;
@Getter
@Setter
private OpenPgpTrustStore.Trust trust = OpenPgpTrustStore.Trust.undecided;
public IkeyRecord() { 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.jid = jid;
this.timestamp = timestamp; this.timestamp = timestamp;
this.superordinate = superordinate; this.superordinate = superordinate;
this.subordinates.addAll(subordinates); 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; package org.jivesoftware.smackx.ikey.record;
import java.net.URI; import java.net.URI;
import java.util.UUID;
public interface IkeySubordinateRecord { public interface IkeySubordinateRecord {
UUID getId();
String getType(); String getType();
String getFingerprint(); String getFingerprint();

View File

@ -6,11 +6,17 @@ import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.net.URI; import java.net.URI;
import java.util.UUID; import java.util.UUID;
import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@EqualsAndHashCode
public class OxSubordinateRecord implements IkeySubordinateRecord { public class OxSubordinateRecord implements IkeySubordinateRecord {
public static final String TYPE = OpenPgpElement.NAMESPACE;
@Getter
@Setter
private UUID id; private UUID id;
public OxSubordinateRecord() { public OxSubordinateRecord() {
@ -33,7 +39,7 @@ public class OxSubordinateRecord implements IkeySubordinateRecord {
@Override @Override
public String getType() { public String getType() {
return OpenPgpElement.NAMESPACE; return TYPE;
} }
@Override @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.ConnectionPoolState;
import org.mercury_im.messenger.core.connection.state.ConnectionState; import org.mercury_im.messenger.core.connection.state.ConnectionState;
import org.mercury_im.messenger.core.crypto.MercuryOpenPgpManager; 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.data.repository.AccountRepository;
import org.mercury_im.messenger.core.store.caps.MercuryEntityCapsStore; import org.mercury_im.messenger.core.store.caps.MercuryEntityCapsStore;
import org.mercury_im.messenger.core.store.message.MercuryMessageStore; import org.mercury_im.messenger.core.store.message.MercuryMessageStore;
@ -43,6 +44,7 @@ public class MercuryConnectionManager {
private final AccountRepository accountRepository; private final AccountRepository accountRepository;
private final RosterStoreBinder rosterStoreBinder; private final RosterStoreBinder rosterStoreBinder;
private final MercuryOpenPgpManager cryptoManager; private final MercuryOpenPgpManager cryptoManager;
private final IkeyInitializer ikeyInitializer;
private final SchedulersFacade schedulers; private final SchedulersFacade schedulers;
private final Map<UUID, MercuryConnection> connectionsMap = new ConcurrentHashMap<>(); private final Map<UUID, MercuryConnection> connectionsMap = new ConcurrentHashMap<>();
@ -63,12 +65,14 @@ public class MercuryConnectionManager {
MercuryMessageStoreFactory messageStoreFactory, MercuryMessageStoreFactory messageStoreFactory,
XmppConnectionFactory connectionFactory, XmppConnectionFactory connectionFactory,
MercuryOpenPgpManager cryptoManager, MercuryOpenPgpManager cryptoManager,
IkeyInitializer ikeyInitializer,
SchedulersFacade schedulers) { SchedulersFacade schedulers) {
this.accountRepository = accountRepository; this.accountRepository = accountRepository;
this.rosterStoreBinder = rosterStoreBinder; this.rosterStoreBinder = rosterStoreBinder;
this.connectionFactory = connectionFactory; this.connectionFactory = connectionFactory;
this.messageStoreFactory = messageStoreFactory; this.messageStoreFactory = messageStoreFactory;
this.cryptoManager = cryptoManager; this.cryptoManager = cryptoManager;
this.ikeyInitializer = ikeyInitializer;
this.schedulers = schedulers; this.schedulers = schedulers;
EntityCapsManager.setPersistentCache(entityCapsStore); EntityCapsManager.setPersistentCache(entityCapsStore);
@ -170,6 +174,7 @@ public class MercuryConnectionManager {
chatManager.addIncomingListener(mercuryMessageStore); chatManager.addIncomingListener(mercuryMessageStore);
})); }));
cryptoManager.initialize(connection); cryptoManager.initialize(connection);
ikeyInitializer.initFor(connection);
} }
private synchronized void handleOptionalAccountChangedEvent(MercuryConnection connection, Optional<Account> event) { 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.AbstractConnectionListener;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.ikey.record.IkeyRecord;
import org.jivesoftware.smackx.ox.OpenPgpManager; import org.jivesoftware.smackx.ox.OpenPgpManager;
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.ox.callback.SecretKeyPassphraseCallback; 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.jivesoftware.smackx.pubsub.PubSubException;
import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.connection.MercuryConnection; 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.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.MessageRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; 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.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.crypto.MercuryOpenPgpStore;
import org.mercury_im.messenger.core.store.message.MercuryMessageStore; import org.mercury_im.messenger.core.store.message.MercuryMessageStore;
@ -34,6 +40,8 @@ public class MercuryOpenPgpManager {
private final DirectChatRepository directChatRepository; private final DirectChatRepository directChatRepository;
private final MessageRepository messageRepository; private final MessageRepository messageRepository;
private final OpenPgpRepository openPgpRepository; private final OpenPgpRepository openPgpRepository;
private final OpenPgpTrustRepository openPgpTrustRepository;
private final IkeyRepository ikeyRepository;
private final SchedulersFacade schedulers; private final SchedulersFacade schedulers;
private final OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator; private final OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator;
private final LocalOxKeyGenerationStrategy keyGenerationStrategy; private final LocalOxKeyGenerationStrategy keyGenerationStrategy;
@ -43,16 +51,20 @@ public class MercuryOpenPgpManager {
DirectChatRepository directChatRepository, DirectChatRepository directChatRepository,
MessageRepository messageRepository, MessageRepository messageRepository,
OpenPgpRepository openPgpRepository, OpenPgpRepository openPgpRepository,
OpenPgpTrustRepository openPgpTrustRepository,
OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator, OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator,
LocalOxKeyGenerationStrategy keyGenerationStrategy, LocalOxKeyGenerationStrategy keyGenerationStrategy,
IkeyRepository ikeyRepository,
SchedulersFacade schedulers) { SchedulersFacade schedulers) {
this.peerRepository = peerRepository; this.peerRepository = peerRepository;
this.directChatRepository = directChatRepository; this.directChatRepository = directChatRepository;
this.messageRepository = messageRepository; this.messageRepository = messageRepository;
this.openPgpRepository = openPgpRepository; this.openPgpRepository = openPgpRepository;
this.openPgpTrustRepository = openPgpTrustRepository;
this.schedulers = schedulers; this.schedulers = schedulers;
this.keyGenerationStrategy = keyGenerationStrategy; this.keyGenerationStrategy = keyGenerationStrategy;
this.passphraseGenerator = passphraseGenerator; this.passphraseGenerator = passphraseGenerator;
this.ikeyRepository = ikeyRepository;
} }
public void initialize(MercuryConnection connection) { public void initialize(MercuryConnection connection) {
@ -71,13 +83,11 @@ public class MercuryOpenPgpManager {
} }
private void setup(MercuryConnection connection) { 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); OpenPgpProvider provider = new PainlessOpenPgpProvider(store);
OpenPgpManager oxManager = OpenPgpManager.getInstanceFor(connection.getConnection()); OpenPgpManager oxManager = OpenPgpManager.getInstanceFor(connection.getConnection());
oxManager.setOpenPgpProvider(provider); oxManager.setOpenPgpProvider(provider);
OpenPgpSecretKeyBackupPassphrase passphrase = passphraseGenerator.generateBackupPassphrase(); OpenPgpSecretKeyBackupPassphrase passphrase = passphraseGenerator.generateBackupPassphrase();
generateAndPublish(connection, oxManager, passphrase); generateAndPublish(connection, oxManager, passphrase);
} }

View File

@ -1,8 +1,8 @@
package org.mercury_im.messenger.core.data.repository; package org.mercury_im.messenger.core.data.repository;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.jivesoftware.smackx.ikey.util.IkeyTrust;
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.entity.Account; import org.mercury_im.messenger.entity.Account;
@ -11,7 +11,6 @@ import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.util.UUID; import java.util.UUID;
import io.reactivex.Completable; import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
@ -47,15 +46,15 @@ public interface IkeyKeyRepository {
Completable storeBackupPassphrase(UUID accountID, OpenPgpSecretKeyBackupPassphrase passphrase); 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); 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); 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<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<List<OpenPgpV4Fingerprint>> observeFingerprintsOf(UUID accountId, String peerAddress);
Observable<Optional<OpenPgpV4Fingerprint>> observeLocalFingerprintOf(UUID accountId); 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.LocalOxKeyGenerationStrategy;
import org.mercury_im.messenger.core.crypto.OxPlusIkeyKeyGenerationStrategy; import org.mercury_im.messenger.core.crypto.OxPlusIkeyKeyGenerationStrategy;
import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository;
import javax.inject.Singleton; 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.DirectChatRepository;
import org.mercury_im.messenger.core.data.repository.MessageRepository; 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.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.data.repository.PeerRepository;
import org.mercury_im.messenger.core.viewmodel.account.LoginViewModel; import org.mercury_im.messenger.core.viewmodel.account.LoginViewModel;
import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel; import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel;
@ -45,11 +46,12 @@ public class ViewModelModule {
@Singleton @Singleton
static AccountDetailsViewModel provideAccountDetailsViewModel(MercuryConnectionManager connectionManager, static AccountDetailsViewModel provideAccountDetailsViewModel(MercuryConnectionManager connectionManager,
OpenPgpRepository openPgpRepository, OpenPgpRepository openPgpRepository,
OpenPgpTrustRepository trustRepository,
IkeyRepository ikeyRepository, IkeyRepository ikeyRepository,
AccountRepository accountRepository, AccountRepository accountRepository,
SchedulersFacade schedulers, SchedulersFacade schedulers,
IkeyInitializer ikeyInitializer) { IkeyInitializer ikeyInitializer) {
return new AccountDetailsViewModel(connectionManager, openPgpRepository, ikeyRepository, accountRepository, schedulers, ikeyInitializer); return new AccountDetailsViewModel(connectionManager, openPgpRepository, trustRepository, ikeyRepository, accountRepository, schedulers, ikeyInitializer);
} }
@Provides @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.jxmpp.jid.BareJid;
import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.io.IOException; import java.io.IOException;
@ -26,14 +27,20 @@ public class MercuryOpenPgpStore extends AbstractOpenPgpStore {
protected static final Logger LOGGER = Logger.getLogger(MercuryOpenPgpStore.class.getName()); protected static final Logger LOGGER = Logger.getLogger(MercuryOpenPgpStore.class.getName());
public MercuryOpenPgpStore(UUID accountId, OpenPgpRepository repository, SchedulersFacade schedulers) { public MercuryOpenPgpStore(UUID accountId, OpenPgpRepository repository, OpenPgpTrustRepository trustRepository, SchedulersFacade schedulers) {
super( this(
new KeyStore(accountId, repository, schedulers), new KeyStore(accountId, repository, schedulers),
new MetadataStore(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 CompositeDisposable disposable = new CompositeDisposable();
private final OpenPgpRepository repository; 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 CompositeDisposable disposable = new CompositeDisposable();
private final OpenPgpRepository repository; private final OpenPgpRepository repository;
@ -141,7 +148,8 @@ public class MercuryOpenPgpStore extends AbstractOpenPgpStore {
@Override @Override
protected void writeAnnouncedFingerprintsOf(BareJid contact, Map<OpenPgpV4Fingerprint, Date> metadata) protected void writeAnnouncedFingerprintsOf(BareJid contact, Map<OpenPgpV4Fingerprint, Date> metadata)
throws IOException { throws IOException {
disposable.add(repository.storeAnnouncedFingerprints(accountId, contact.asEntityBareJidIfPossible(), metadata) disposable.add(
repository.storeAnnouncedFingerprints(accountId, contact.asEntityBareJidIfPossible(), metadata)
.subscribeOn(schedulers.getIoScheduler()) .subscribeOn(schedulers.getIoScheduler())
.subscribe( .subscribe(
() -> MercuryOpenPgpStore.LOGGER.log(Level.FINER, "Successfully updated announced OX fingerprints for " + contact + " (accountId=" + accountId + ")"), () -> 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 { public static class TrustStore extends AbstractOpenPgpTrustStore {
private final CompositeDisposable disposable = new CompositeDisposable(); private final CompositeDisposable disposable = new CompositeDisposable();
private final OpenPgpRepository repository; private final OpenPgpTrustRepository repository;
private final SchedulersFacade schedulers; private final SchedulersFacade schedulers;
private final UUID accountId; private final UUID accountId;
public TrustStore(UUID accountId, OpenPgpRepository repository, SchedulersFacade schedulers) { public TrustStore(UUID accountId, OpenPgpTrustRepository repository, SchedulersFacade schedulers) {
this.accountId = accountId; this.accountId = accountId;
this.repository = repository; this.repository = repository;
this.schedulers = schedulers; 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.IkeyManager;
import org.jivesoftware.smackx.ikey.element.IkeyElement; import org.jivesoftware.smackx.ikey.element.IkeyElement;
import org.jivesoftware.smackx.ikey.element.SubordinateElement; 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.PublicKeysListElement;
import org.jivesoftware.smackx.ox.element.SecretkeyElement; 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.store.definition.OpenPgpTrustStore;
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil; import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
import org.jivesoftware.smackx.pep.PepManager; 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.crypto.ikey.IkeyRepository;
import org.mercury_im.messenger.core.data.repository.AccountRepository; 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.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.util.Optional;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel; import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
@ -55,6 +59,7 @@ public class AccountDetailsViewModel implements MercuryViewModel {
private MercuryConnectionManager connectionManager; private MercuryConnectionManager connectionManager;
private final OpenPgpRepository openPgpRepository; private final OpenPgpRepository openPgpRepository;
private final OpenPgpTrustRepository deviceKeyTrustRepository;
private final IkeyRepository ikeyRepository; private final IkeyRepository ikeyRepository;
private final AccountRepository accountRepository; private final AccountRepository accountRepository;
private final SchedulersFacade schedulers; private final SchedulersFacade schedulers;
@ -63,12 +68,14 @@ public class AccountDetailsViewModel implements MercuryViewModel {
@Inject @Inject
public AccountDetailsViewModel(MercuryConnectionManager connectionManager, public AccountDetailsViewModel(MercuryConnectionManager connectionManager,
OpenPgpRepository openPgpRepository, OpenPgpRepository openPgpRepository,
OpenPgpTrustRepository deviceKeyTrustRepository,
IkeyRepository ikeyRepository, IkeyRepository ikeyRepository,
AccountRepository accountRepository, AccountRepository accountRepository,
SchedulersFacade schedulers, SchedulersFacade schedulers,
IkeyInitializer ikeyInitializer) { IkeyInitializer ikeyInitializer) {
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.openPgpRepository = openPgpRepository; this.openPgpRepository = openPgpRepository;
this.deviceKeyTrustRepository = deviceKeyTrustRepository;
this.ikeyRepository = ikeyRepository; this.ikeyRepository = ikeyRepository;
this.accountRepository = accountRepository; this.accountRepository = accountRepository;
this.schedulers = schedulers; this.schedulers = schedulers;
@ -76,9 +83,14 @@ public class AccountDetailsViewModel implements MercuryViewModel {
} }
public void markFingerprintTrusted(UUID accountId, OpenPgpV4Fingerprint fingerprint, boolean trusted) { 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()) .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) { 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<>()); .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); return openPgpRepository.observeLocalFingerprintOf(accountId);
} }
public Observable<List<FingerprintViewItem>> observeRemoteFingerprints(UUID accountId) { public Observable<List<FingerprintViewItem>> observeRemoteDeviceFingerprints(UUID accountId) {
return observeLocalFingerprint(accountId) OpenPgpStore store = OpenPgpManager.getInstanceFor(connectionManager.getConnection(accountId).getConnection())
.getOpenPgpProvider().getStore();
return observeLocalDeviceFingerprint(accountId)
.flatMap(optional -> accountRepository.getAccount(accountId).toSingle() .flatMap(optional -> accountRepository.getAccount(accountId).toSingle()
.flatMapObservable(account -> openPgpRepository.observeFingerprints(accountId, account.getJid()) .flatMapObservable(account -> openPgpRepository.observeFingerprints(accountId, account.getJid())
.map(list -> { .map(list -> {
@ -165,6 +179,7 @@ public class AccountDetailsViewModel implements MercuryViewModel {
List<FingerprintViewItem> remoteFingerprints = new ArrayList<>(); List<FingerprintViewItem> remoteFingerprints = new ArrayList<>();
for(FingerprintViewItem f : list) { for(FingerprintViewItem f : list) {
f.setTrusted(store.getTrust(f.getOwner(), f.getFingerprint()));
if (!f.getFingerprint().equals(optional.getItem())) { if (!f.getFingerprint().equals(optional.getItem())) {
remoteFingerprints.add(f); 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.Roster;
import org.jivesoftware.smack.roster.RosterEntry; import org.jivesoftware.smack.roster.RosterEntry;
import org.jivesoftware.smack.roster.RosterGroup; 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.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.connection.MercuryConnection; import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager; import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; 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.DirectChatRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; 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.data.repository.PeerRepository;
import org.mercury_im.messenger.core.util.CombinedPresenceListener; import org.mercury_im.messenger.core.util.CombinedPresenceListener;
import org.mercury_im.messenger.core.util.Optional; 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.mercury_im.messenger.entity.contact.Peer;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@ -42,10 +47,13 @@ import lombok.Getter;
public class ContactDetailViewModel implements MercuryViewModel { public class ContactDetailViewModel implements MercuryViewModel {
private static final Logger LOGGER = Logger.getLogger(ContactDetailViewModel.class.getName());
private final MercuryConnectionManager connectionManager; private final MercuryConnectionManager connectionManager;
private final PeerRepository peerRepository; private final PeerRepository peerRepository;
private final DirectChatRepository directChatRepository; private final DirectChatRepository directChatRepository;
private final OpenPgpRepository openPgpRepository; private final OpenPgpRepository openPgpRepository;
private final OpenPgpTrustRepository trustRepository;
private final IkeyRepository ikeyRepository; private final IkeyRepository ikeyRepository;
private final SchedulersFacade schedulers; private final SchedulersFacade schedulers;
@ -62,6 +70,8 @@ public class ContactDetailViewModel implements MercuryViewModel {
private BehaviorSubject<List<FingerprintViewItem>> contactDeviceFingerprints = BehaviorSubject.createDefault(Collections.emptyList()); private BehaviorSubject<List<FingerprintViewItem>> contactDeviceFingerprints = BehaviorSubject.createDefault(Collections.emptyList());
private BehaviorSubject<Tuple<String, EntityBareJid>> contactAvatarBase = BehaviorSubject.create(); private BehaviorSubject<Tuple<String, EntityBareJid>> contactAvatarBase = BehaviorSubject.create();
private OpenPgpStore openPgpStore;
@Getter @Getter
private UUID peerId; private UUID peerId;
@ -73,12 +83,14 @@ public class ContactDetailViewModel implements MercuryViewModel {
PeerRepository peerRepository, PeerRepository peerRepository,
DirectChatRepository directChatRepository, DirectChatRepository directChatRepository,
OpenPgpRepository openPgpRepository, OpenPgpRepository openPgpRepository,
OpenPgpTrustRepository trustRepository,
IkeyRepository ikeyRepository, IkeyRepository ikeyRepository,
SchedulersFacade schedulers) { SchedulersFacade schedulers) {
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.peerRepository = peerRepository; this.peerRepository = peerRepository;
this.directChatRepository = directChatRepository; this.directChatRepository = directChatRepository;
this.openPgpRepository = openPgpRepository; this.openPgpRepository = openPgpRepository;
this.trustRepository = trustRepository;
this.ikeyRepository = ikeyRepository; this.ikeyRepository = ikeyRepository;
this.schedulers = schedulers; this.schedulers = schedulers;
} }
@ -89,6 +101,10 @@ public class ContactDetailViewModel implements MercuryViewModel {
} }
public Completable init(Peer peer) { 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(() -> { return Completable.fromAction(() -> {
this.peerId = peer.getId(); this.peerId = peer.getId();
this.accountId = peer.getAccount().getId(); this.accountId = peer.getAccount().getId();
@ -109,11 +125,19 @@ public class ContactDetailViewModel implements MercuryViewModel {
contactAvatarBase.onNext(new Tuple<>(peer.getDisplayName(), peer.getJid())); contactAvatarBase.onNext(new Tuple<>(peer.getDisplayName(), peer.getJid()));
})); }));
// Device keys
addDisposable(openPgpRepository addDisposable(openPgpRepository
.observeFingerprints(peer.getAccount().getId(), peer.getJid()) .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()) .compose(schedulers.executeUiSafeObservable())
.subscribe(fingerprints -> contactDeviceFingerprints.onNext(fingerprints))); .subscribe(fingerprints -> contactDeviceFingerprints.onNext(fingerprints)));
// Ikey
addDisposable(ikeyRepository addDisposable(ikeyRepository
.loadRecord(peer.getAccount().getId(), peer.getJid()) .loadRecord(peer.getAccount().getId(), peer.getJid())
.compose(schedulers.executeUiSafeObservable()) .compose(schedulers.executeUiSafeObservable())
@ -121,7 +145,7 @@ public class ContactDetailViewModel implements MercuryViewModel {
peer.getAccount().getId(), record.getJid(), peer.getAccount().getId(), record.getJid(),
new OpenPgpV4Fingerprint(record.getSuperordinate()), new OpenPgpV4Fingerprint(record.getSuperordinate()),
record.getTimestamp(), record.getTimestamp(), record.getTimestamp(), record.getTimestamp(),
OpenPgpTrustStore.Trust.trusted) // TODO record.getTrust())
) )
.map(Optional::new) .map(Optional::new)
.subscribe(contactIdentityFingerprint::onNext)); .subscribe(contactIdentityFingerprint::onNext));
@ -219,17 +243,15 @@ public class ContactDetailViewModel implements MercuryViewModel {
} }
} }
public void markDeviceFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) { public void markDeviceFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, boolean checked) throws IOException {
openPgpRepository.storeTrust(accountId, contactAddress.getValue(), fingerprint, openPgpStore.setTrust(getContactAddress().blockingFirst(), fingerprint, checked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted);
checked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted)
.subscribe();
} }
public void markIkeyFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, EntityBareJid owner, boolean isChecked) { public void markIkeyFingerprintTrusted(OpenPgpV4Fingerprint fingerprint, EntityBareJid owner, boolean isChecked) {
IkeyTrust trust = new IkeyTrust(); OpenPgpTrustStore.Trust trust = isChecked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted;
trust.setTrust(isChecked ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted); addDisposable(ikeyRepository.storeSuperordinateTrust(accountId, owner, fingerprint, trust)
ikeyRepository.storeSuperordinateTrust(accountId, owner, fingerprint, trust) .subscribe(() -> LOGGER.log(Level.INFO, "Marked Ikey " + fingerprint + " of " + owner + " as " + trust),
.subscribe(); e -> LOGGER.log(Level.SEVERE, "Error marking ikey " + fingerprint + " of " + owner + " as " + trust, e)));
} }
public static abstract class FilteredPresenceEventListener extends CombinedPresenceListener { public static abstract class FilteredPresenceEventListener extends CombinedPresenceListener {

View File

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