Mercury-IM/domain/src/main/java/org/mercury_im/messenger/core/store/crypto/IkeyAwareOpenPgpStore.java

134 lines
6.9 KiB
Java

package org.mercury_im.messenger.core.store.crypto;
import org.jivesoftware.smackx.ikey.mechanism.IkeyType;
import org.jivesoftware.smackx.ox.store.abstr.AbstractOpenPgpTrustStore;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository;
import org.mercury_im.messenger.core.data.repository.IkeyKeyRepository;
import org.mercury_im.messenger.core.data.repository.IkeyRecordRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
import org.mercury_im.messenger.core.data.repository.OpenPgpTrustRepository;
import org.mercury_im.messenger.core.util.Optional;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.subjects.BehaviorSubject;
public class IkeyAwareOpenPgpStore extends MercuryOpenPgpStore {
public IkeyAwareOpenPgpStore(UUID accountId,
OpenPgpRepository openPgpRepository,
OpenPgpTrustRepository openPgpTrustRepository,
IkeyRepository ikeyRepository,
SchedulersFacade schedulersFacade) {
this(accountId, openPgpRepository, openPgpTrustRepository, ikeyRepository, ikeyRepository, schedulersFacade);
}
public IkeyAwareOpenPgpStore(UUID accountId,
OpenPgpRepository openPgpRepository,
OpenPgpTrustRepository openPgpTrustRepository,
IkeyKeyRepository ikeyKeyRepository,
IkeyRecordRepository ikeyRecordRepository,
SchedulersFacade schedulers) {
super(new KeyStore(accountId, openPgpRepository, schedulers),
new MetadataStore(accountId, openPgpRepository, schedulers),
new TrustStore(accountId, openPgpTrustRepository, ikeyKeyRepository, ikeyRecordRepository, schedulers));
}
public static class TrustStore extends AbstractOpenPgpTrustStore {
private static final Logger LOGGER = Logger.getLogger(TrustStore.class.getName());
private final UUID accountId;
private final OpenPgpTrustRepository openPgpTrustRepository;
private final IkeyKeyRepository ikeyKeyRepository;
private final IkeyRecordRepository ikeyRecordRepository;
private final SchedulersFacade schedulers;
private final CompositeDisposable disposable = new CompositeDisposable();
private final BehaviorSubject<Boolean> accountIsIkeyAware = BehaviorSubject.createDefault(false);
public TrustStore(UUID accountId, OpenPgpTrustRepository openPgpTrustRepository, IkeyKeyRepository ikeyKeyRepository, IkeyRecordRepository ikeyRecordRepository, SchedulersFacade schedulersFacade) {
this.accountId = accountId;
this.openPgpTrustRepository = openPgpTrustRepository;
this.ikeyKeyRepository = ikeyKeyRepository;
this.ikeyRecordRepository = ikeyRecordRepository;
this.schedulers = schedulersFacade;
accountIsIkeyAware().subscribe(accountIsIkeyAware);
}
@Override
protected Trust readTrust(BareJid owner, OpenPgpV4Fingerprint fingerprint) throws IOException {
EntityBareJid jid = owner.asEntityBareJidOrThrow();
Trust trust = accountIsIkeyAware.firstOrError()
.flatMap(isAware -> isAware ?
readIkeyTrust(jid, fingerprint) :
readManualTrust(jid, fingerprint))
.blockingGet();
return trust == null ? Trust.undecided : trust;
}
private Single<Trust> readIkeyTrust(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint) {
return ikeyRecordRepository.loadRecord(accountId, owner)
.map(record -> record.hasSubordinate(fingerprint) ?
record.getTrust() : Trust.undecided)
.doOnNext(trust -> LOGGER.log(Level.INFO, "Read ikey trust " + trust + " for device key " + fingerprint + " of contact " + owner))
.firstElement()
.switchIfEmpty(readManualTrust(owner, fingerprint));
}
private Single<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(
contactHasIkeyRecord(owner.asEntityBareJidIfPossible())
.flatMapCompletable(hasRecord -> hasRecord && accountIsIkeyAware.getValue() ?
skipManualTrustForIkeyContact(jid, fingerprint, trust) :
writeManualTrust(jid, fingerprint, trust))
.compose(schedulers.executeUiSafeCompletable())
.subscribe(() -> {},
e -> LOGGER.log(Level.SEVERE, "An error happened while marking device key " + fingerprint + " of " + jid + " as " + trust, e)));
}
private Completable skipManualTrustForIkeyContact(EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, Trust trust) {
return Completable.complete()
.doOnComplete(() -> LOGGER.log(Level.INFO,
"Contact " + owner + " has an Ikey Record, so do not mark " + fingerprint + " as " + trust));
}
private Single<Boolean> contactHasIkeyRecord(EntityBareJid owner) {
return ikeyRecordRepository.loadRecord(accountId, owner.asEntityBareJidOrThrow())
.isEmpty()
.map(resultEmpty -> !resultEmpty); // negate
}
protected Observable<Boolean> accountIsIkeyAware() {
return ikeyKeyRepository.loadSecretKey(accountId)
.map(Optional::isPresent)
.compose(schedulers.executeUiSafeObservable());
}
}
}