Mercury-IM/data/src/main/java/org/mercury_im/messenger/data/repository/RxOpenPgpRepository.java

300 lines
14 KiB
Java

package org.mercury_im.messenger.data.repository;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore;
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.util.Optional;
import org.mercury_im.messenger.data.model.AnnouncedOpenPgpContactKey;
import org.mercury_im.messenger.data.model.OpenPgpKeyFetchDate;
import org.mercury_im.messenger.data.model.OpenPgpKeyTrust;
import org.mercury_im.messenger.data.model.OpenPgpPublicKeyRing;
import org.mercury_im.messenger.data.model.OpenPgpSecretKeyRing;
import org.pgpainless.PGPainless;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.requery.Persistable;
import io.requery.query.ResultDelegate;
import io.requery.reactivex.ReactiveEntityStore;
public class RxOpenPgpRepository implements OpenPgpRepository {
private static final Logger LOGGER = Logger.getLogger(RxOpenPgpRepository.class.getName());
private final ReactiveEntityStore<Persistable> data;
private final AccountRepository accountRepository;
@Inject
public RxOpenPgpRepository(ReactiveEntityStore<Persistable> data, AccountRepository accountRepository) {
this.data = data;
this.accountRepository = accountRepository;
}
@Override
public Completable storePublicKeysOf(UUID accountId, EntityBareJid owner, PGPPublicKeyRingCollection keys) {
return Single.fromCallable(() -> {
OpenPgpPublicKeyRing keyRing = new OpenPgpPublicKeyRing();
keyRing.setAccountId(accountId);
keyRing.setOwner(owner);
keyRing.setBytes(keys.getEncoded());
return keyRing;
}).flatMap(data::upsert).ignoreElement()
.doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored public keys of " + owner + " for account " + accountId));
}
@Override
public Single<PGPPublicKeyRingCollection> loadPublicKeysOf(UUID accountId, EntityBareJid owner) {
return data.select(OpenPgpPublicKeyRing.class)
.where(OpenPgpPublicKeyRing.ACCOUNT_ID.eq(accountId))
.and(OpenPgpPublicKeyRing.OWNER.eq(owner))
.limit(1).get()
.maybe().toSingle()
.map(keyring -> PGPainless.readKeyRing().publicKeyRingCollection(keyring.getBytes()));
}
@Override
public Single<Integer> deletePublicKeysOf(UUID accountId, EntityBareJid owner) {
return data.delete(OpenPgpPublicKeyRing.class)
.where(OpenPgpPublicKeyRing.ACCOUNT_ID.eq(accountId))
.and(OpenPgpPublicKeyRing.OWNER.eq(owner))
.get().single()
.doOnSuccess(count -> LOGGER.log(Level.INFO, "Successfully deleted " + count + " public keys of " + owner + " for account " + accountId));
}
@Override
public Completable storeSecretKeysOf(UUID accountId, EntityBareJid owner, PGPSecretKeyRingCollection keys) {
return Single.fromCallable(() -> {
OpenPgpSecretKeyRing keyRing = new OpenPgpSecretKeyRing();
keyRing.setAccountId(accountId);
keyRing.setOwner(owner);
keyRing.setBytes(keys.getEncoded());
return keyRing;
}).flatMap(data::upsert).ignoreElement()
.doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored secret keys of " + owner + " for account " + accountId));
}
@Override
public Single<PGPSecretKeyRingCollection> loadSecretKeysOf(UUID accountId, EntityBareJid owner) {
return data.select(OpenPgpSecretKeyRing.class)
.where(OpenPgpSecretKeyRing.ACCOUNT_ID.eq(accountId))
.and(OpenPgpSecretKeyRing.OWNER.eq(owner))
.limit(1).get()
.maybe().toSingle()
.map(keyring -> PGPainless.readKeyRing().secretKeyRingCollection(keyring.getBytes()));
}
@Override
public Single<Integer> deleteSecretKeysOf(UUID accountId, EntityBareJid owner) {
return data.delete(OpenPgpSecretKeyRing.class)
.where(OpenPgpSecretKeyRing.ACCOUNT_ID.eq(accountId))
.and(OpenPgpSecretKeyRing.OWNER.eq(owner))
.get().single()
.doOnSuccess(count -> LOGGER.log(Level.INFO, "Successfully deleted " + count + " secret keys of " + owner + " for account " + accountId));
}
@Override
public Completable storePublicKeyFetchDates(UUID accountId, EntityBareJid owner, Map<OpenPgpV4Fingerprint, Date> dates) {
List<OpenPgpKeyFetchDate> entities = new LinkedList<>();
for (Map.Entry<OpenPgpV4Fingerprint, Date> entry : dates.entrySet()) {
OpenPgpKeyFetchDate entity = new OpenPgpKeyFetchDate();
entity.setAccountId(accountId);
entity.setOwner(owner);
entity.setFingerprint(entry.getKey());
entity.setFetchDate(entry.getValue());
entities.add(entity);
}
return data.upsert(entities).ignoreElement();
}
@Override
public Completable storeAnnouncedFingerprints(UUID accountId, EntityBareJid owner, Map<OpenPgpV4Fingerprint, Date> metadata) {
List<AnnouncedOpenPgpContactKey> entities = new LinkedList<>();
for (Map.Entry<OpenPgpV4Fingerprint, Date> entry : metadata.entrySet()) {
AnnouncedOpenPgpContactKey entity = new AnnouncedOpenPgpContactKey();
entity.setAccountId(accountId);
entity.setOwner(owner);
entity.setFingerprint(entry.getKey());
entity.setModificationDate(entry.getValue());
entities.add(entity);
}
return data.upsert(entities).ignoreElement()
.doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored announced fingerprints of " +
owner + " for account " + accountId + ": " + Arrays.toString(metadata.keySet().toArray())));
}
@Override
public Single<Map<OpenPgpV4Fingerprint, Date>> loadAnnouncedFingerprints(UUID accountId, EntityBareJid contact) {
return data.select(AnnouncedOpenPgpContactKey.class)
.where(AnnouncedOpenPgpContactKey.ACCOUNT_ID.eq(accountId))
.and(AnnouncedOpenPgpContactKey.OWNER.eq(contact))
.get()
.observableResult()
.map(ResultDelegate::toList)
.single(Collections.emptyList())
.map(list -> {
Map<OpenPgpV4Fingerprint, Date> map = new ConcurrentHashMap<>();
for (AnnouncedOpenPgpContactKey key : list) {
map.put(key.getFingerprint(), key.getModificationDate());
}
return map;
});
}
@Override
public Single<Date> loadAnnouncementDate(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint) {
return data.select(AnnouncedOpenPgpContactKey.class)
.where(AnnouncedOpenPgpContactKey.ACCOUNT_ID.eq(accountId)
.and(AnnouncedOpenPgpContactKey.OWNER.eq(owner))
.and(AnnouncedOpenPgpContactKey.FINGERPRINT.eq(fingerprint)))
.get().observableResult()
.map(ResultDelegate::first)
.map(AnnouncedOpenPgpContactKey::getModificationDate)
.singleOrError();
}
@Override
public Single<Date> loadAnnouncementDate(UUID accountId, OpenPgpV4Fingerprint fingerprint) {
return accountRepository.getAccount(accountId).toSingle()
.flatMap(account -> loadAnnouncementDate(accountId, account.getJid(), fingerprint));
}
@Override
public Single<OpenPgpTrustStore.Trust> loadTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint) {
return data.select(OpenPgpKeyTrust.class)
.where(OpenPgpKeyTrust.ACCOUNT_ID.eq(accountId))
.and(OpenPgpKeyTrust.OWNER.eq(owner))
.and(OpenPgpKeyTrust.FINGERPRINT.eq(fingerprint))
.limit(1)
.get()
.maybe()
.toSingle(new OpenPgpKeyTrust())
.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();
entity.setAccountId(accountId);
entity.setOwner(owner);
entity.setFingerprint(fingerprint);
entity.setTrust(trust);
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)
.where(OpenPgpPublicKeyRing.ACCOUNT_ID.eq(accountId))
.and(OpenPgpPublicKeyRing.OWNER.eq(JidCreate.entityBareFromOrThrowUnchecked(peerAddress)))
.get().observableResult()
.map(result -> {
OpenPgpPublicKeyRing model = new ResultDelegate<>(result).firstOrNull();
if (model == null) {
return Collections.emptyList();
}
List<OpenPgpV4Fingerprint> fingerprints = new ArrayList<>();
PGPPublicKeyRingCollection keys = PGPainless.readKeyRing().publicKeyRingCollection(model.getBytes());
for (PGPPublicKeyRing key : keys) {
fingerprints.add(new OpenPgpV4Fingerprint(key));
}
return fingerprints;
});
}
@Override
public Observable<Optional<OpenPgpV4Fingerprint>> observeLocalFingerprintOf(UUID accountId) {
return data.select(OpenPgpSecretKeyRing.class)
.where(OpenPgpSecretKeyRing.ACCOUNT_ID.eq(accountId))
.get().observableResult()
.map(result -> {
OpenPgpSecretKeyRing ring = new ResultDelegate<>(result).firstOrNull();
if (ring == null) {
return new Optional<>();
} else {
return new Optional<>(new OpenPgpV4Fingerprint(
PGPainless.readKeyRing().secretKeyRing(ring.getBytes()))
);
}
});
}
@Override
public Observable<List<OpenPgpV4Fingerprint>> observeRemoteFingerprintsOfAccount(UUID accountId) {
return observeLocalFingerprintOf(accountId)
.flatMap(localFingerprint -> data.select(OpenPgpPublicKeyRing.class)
.where(OpenPgpPublicKeyRing.ACCOUNT_ID.eq(accountId))
.get().observableResult()
.map(result -> {
OpenPgpPublicKeyRing ring = new ResultDelegate<>(result).firstOrNull();
if (ring == null) {
return Collections.emptyList();
} else {
Iterator<PGPPublicKeyRing> iterator = PGPainless.readKeyRing().publicKeyRingCollection(ring.getBytes()).iterator();
List<OpenPgpV4Fingerprint> fingerprints = new ArrayList<>();
while (iterator.hasNext()) {
PGPPublicKeyRing r = iterator.next();
OpenPgpV4Fingerprint f = new OpenPgpV4Fingerprint(r);
fingerprints.add(f);
}
if (localFingerprint.isPresent()) {
fingerprints.remove(localFingerprint.getItem());
}
return fingerprints;
}
}));
}
@Override
public Single<Map<OpenPgpV4Fingerprint, Date>> loadPublicKeyFetchDates(UUID accountId, EntityBareJid owner) {
List<OpenPgpKeyFetchDate> fetchDates =
data.select(OpenPgpKeyFetchDate.class)
.where(OpenPgpKeyFetchDate.ACCOUNT_ID.eq(accountId))
.and(OpenPgpKeyFetchDate.OWNER.eq(owner))
.get()
.toList();
Map<OpenPgpV4Fingerprint, Date> map = new ConcurrentHashMap<>();
for (OpenPgpKeyFetchDate date : fetchDates) {
map.put(date.getFingerprint(), date.getFetchDate());
}
return Single.just(map);
}
}