324 lines
15 KiB
Java
324 lines
15 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.core.viewmodel.openpgp.FingerprintViewItem;
|
|
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.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 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.query.Tuple;
|
|
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)))
|
|
.limit(1)
|
|
.get()
|
|
.maybe()
|
|
.toSingle()
|
|
.map(AnnouncedOpenPgpContactKey::getModificationDate);
|
|
}
|
|
|
|
@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 accountRepository.getAccount(accountId).toSingle()
|
|
.flatMapObservable(account -> data.select(AnnouncedOpenPgpContactKey.class)
|
|
.where(AnnouncedOpenPgpContactKey.ACCOUNT_ID.eq(accountId)
|
|
.and(AnnouncedOpenPgpContactKey.OWNER.eq(account.getJid())))
|
|
.get().observableResult()
|
|
.map(ResultDelegate::toList)
|
|
.map(list -> {
|
|
ArrayList<OpenPgpV4Fingerprint> fingerprints = new ArrayList<>();
|
|
for (AnnouncedOpenPgpContactKey key : list) {
|
|
fingerprints.add(key.getFingerprint());
|
|
}
|
|
return fingerprints;
|
|
}));
|
|
}
|
|
|
|
@Override
|
|
public Observable<List<FingerprintViewItem>> observeFingerprints(UUID accountId, EntityBareJid owner) {
|
|
return data.select(AnnouncedOpenPgpContactKey.ACCOUNT_ID,
|
|
AnnouncedOpenPgpContactKey.OWNER,
|
|
AnnouncedOpenPgpContactKey.FINGERPRINT,
|
|
AnnouncedOpenPgpContactKey.MODIFICATION_DATE,
|
|
OpenPgpKeyFetchDate.FETCH_DATE,
|
|
OpenPgpKeyTrust.TRUST)
|
|
|
|
.from(AnnouncedOpenPgpContactKey.class)
|
|
.leftJoin(OpenPgpKeyFetchDate.class)
|
|
.on(AnnouncedOpenPgpContactKey.ACCOUNT_ID.eq(OpenPgpKeyFetchDate.ACCOUNT_ID)
|
|
.and(AnnouncedOpenPgpContactKey.OWNER.eq(OpenPgpKeyFetchDate.OWNER)
|
|
.and(AnnouncedOpenPgpContactKey.FINGERPRINT.eq(OpenPgpKeyFetchDate.FINGERPRINT))))
|
|
|
|
.leftJoin(OpenPgpKeyTrust.class)
|
|
.on(OpenPgpKeyTrust.ACCOUNT_ID.eq(AnnouncedOpenPgpContactKey.ACCOUNT_ID)
|
|
.and(OpenPgpKeyTrust.OWNER.eq(AnnouncedOpenPgpContactKey.OWNER)
|
|
.and(OpenPgpKeyTrust.FINGERPRINT.eq(AnnouncedOpenPgpContactKey.FINGERPRINT))))
|
|
|
|
.where(AnnouncedOpenPgpContactKey.ACCOUNT_ID.eq(accountId).and(AnnouncedOpenPgpContactKey.OWNER.eq(owner)))
|
|
|
|
.get().observableResult()
|
|
.map(ResultDelegate::toList)
|
|
.map(list -> {
|
|
ArrayList<FingerprintViewItem> result = new ArrayList<>(list.size());
|
|
for (Tuple tuple : list) {
|
|
result.add(new FingerprintViewItem(tuple.get(0), tuple.get(1), tuple.get(2), tuple.get(3), tuple.get(4), tuple.get(5)));
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
|
|
@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);
|
|
}
|
|
}
|