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.data.repository.OpenPgpTrustRepository; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; import org.mercury_im.messenger.data.model.AnnouncedOpenPgpContactKey; 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.Set; 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, OpenPgpTrustRepository { private static final Logger LOGGER = Logger.getLogger(RxOpenPgpRepository.class.getName()); private final ReactiveEntityStore data; private final AccountRepository accountRepository; @Inject public RxOpenPgpRepository(ReactiveEntityStore 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 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 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 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 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 dates) { List entities = new LinkedList<>(); for (Map.Entry 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 metadata) { Set 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 entities = new LinkedList<>(); for (Map.Entry entry : metadata.entrySet()) { AnnouncedOpenPgpContactKey entity = new AnnouncedOpenPgpContactKey(); entity.setAccountId(accountId); entity.setOwner(owner); entity.setFingerprint(entry.getKey()); entity.setModificationDate(entry.getValue()); entities.add(entity); } Completable upsertNew = data.upsert(entities).ignoreElement() .doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored announced fingerprints of " + owner + " for account " + accountId + ": " + Arrays.toString(metadata.keySet().toArray()))); return deleteAll.andThen(upsertNew); } @Override public Single> 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 map = new ConcurrentHashMap<>(); for (AnnouncedOpenPgpContactKey key : list) { map.put(key.getFingerprint(), key.getModificationDate()); } return map; }); } @Override public Single 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 loadAnnouncementDate(UUID accountId, OpenPgpV4Fingerprint fingerprint) { return accountRepository.getAccount(accountId).toSingle() .flatMap(account -> loadAnnouncementDate(accountId, account.getJid(), fingerprint)); } @Override public Single 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 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 Observable> 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 fingerprints = new ArrayList<>(); PGPPublicKeyRingCollection keys = PGPainless.readKeyRing().publicKeyRingCollection(model.getBytes()); for (PGPPublicKeyRing key : keys) { fingerprints.add(new OpenPgpV4Fingerprint(key)); } return fingerprints; }); } @Override public Observable> 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> 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))) .orderBy(AnnouncedOpenPgpContactKey.MODIFICATION_DATE.desc()) .get().observableResult() .map(ResultDelegate::toList) .map(list -> { ArrayList 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> loadPublicKeyFetchDates(UUID accountId, EntityBareJid owner) { List fetchDates = data.select(OpenPgpKeyFetchDate.class) .where(OpenPgpKeyFetchDate.ACCOUNT_ID.eq(accountId)) .and(OpenPgpKeyFetchDate.OWNER.eq(owner)) .get() .toList(); Map map = new ConcurrentHashMap<>(); for (OpenPgpKeyFetchDate date : fetchDates) { map.put(date.getFingerprint(), date.getFetchDate()); } return Single.just(map); } }