package org.mercury_im.messenger.data.repository; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.jivesoftware.smackx.ikey.record.IkeyRecord; import org.jivesoftware.smackx.ikey.record.IkeySubordinateRecord; import org.jivesoftware.smackx.ikey.record.OxSubordinateRecord; import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jxmpp.jid.EntityBareJid; import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; import org.mercury_im.messenger.core.util.Optional; import org.mercury_im.messenger.data.model.IkeyRecordModel; import org.mercury_im.messenger.data.model.IkeySecretKeyModel; import org.mercury_im.messenger.data.model.IkeySubordinateModel; import org.pgpainless.key.OpenPgpV4Fingerprint; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; 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.reactivex.ReactiveEntityStore; public class RxIkeyRepository implements IkeyRepository { private static final Logger LOGGER = Logger.getLogger(RxIkeyRepository.class.getName()); private final ReactiveEntityStore data; @Inject public RxIkeyRepository(ReactiveEntityStore data) { this.data = data; } @Override public Observable> loadSecretKey(UUID accountId) { return data.select(IkeySecretKeyModel.class) .where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountId)) .get() .observableResult() .map(ResultDelegate::toList) .doOnNext(l -> LOGGER.log(Level.INFO, "new Result: " + Arrays.toString(l.toArray()))) .map(l -> l.isEmpty() ? new Optional<>() : new Optional<>(l.get(0).getKey())); } @Override public Completable storeSecretKey(UUID accountId, PGPSecretKeyRing secretKey) { return data.select(IkeySecretKeyModel.class) .where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountId)) .get().observable() .single(new IkeySecretKeyModel()) .map(m -> { m.setAccountId(accountId); m.setKey(secretKey); m.setFingerprint(new OpenPgpV4Fingerprint(secretKey.getPublicKey())); return m; }) .flatMap(data::upsert) .ignoreElement(); } @Override public Single deleteSecretKey(UUID accountId) { return data.delete().from(IkeySecretKeyModel.class) .where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountId)) .get() .single(); } @Override public Single> loadBackupPassphrase(UUID accountID) { return data.select(IkeySecretKeyModel.class) .where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountID)) .get() .observable() .map(m -> new Optional<>(m.getBackupPassphrase())) .single(new Optional<>()); } @Override public Completable storeBackupPassphrase(UUID accountId, OpenPgpSecretKeyBackupPassphrase passphrase) { return data.select(IkeySecretKeyModel.class) .where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountId)) .get().observable() .single(new IkeySecretKeyModel()) .map(m -> { m.setAccountId(accountId); m.setBackupPassphrase(passphrase); return m; }) .flatMap(data::upsert) .ignoreElement(); } @Override public Observable> loadSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint) { return loadRecord(accountId, jid) .map(model -> { if (fingerprint.equals(new OpenPgpV4Fingerprint(model.getSuperordinate()))) { return new Optional<>(model.getTrust()); } else { return new Optional<>(); } }); } @Override public Completable storeSuperordinateTrust(UUID accountId, EntityBareJid jid, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust) { return loadRecord(accountId, jid) .map(record -> { record.setTrust(trust); return record; }) .firstOrError() .doOnSuccess(m -> LOGGER.log(Level.INFO, "First Record: " + m)) .flatMapCompletable(record -> storeRecord(accountId, jid, record)); } @Override public Observable loadRecord(UUID accountId, EntityBareJid jid) { return getRecordModel(accountId, jid, false) .map(m -> { List subordinateRecords = new ArrayList<>(); for (IkeySubordinateModel sub : m.getSubordinates()) { if (sub.getType().equals(OpenPgpElement.NAMESPACE)) { OxSubordinateRecord r = new OxSubordinateRecord(); r.setId(sub.getId()); r.setOxFingerprint(new OpenPgpV4Fingerprint(sub.getFpr())); r.setUri(sub.getUri()); subordinateRecords.add(r); } } return new IkeyRecord(jid, m.getTimestamp(), m.getSuperordinate(), subordinateRecords, m.getTrust()); }) .doOnError(e -> LOGGER.log(Level.SEVERE, "Error loading ikey record", e)); } @Override public Completable storeRecord(UUID accountId, EntityBareJid jid, IkeyRecord record) { assert jid.equals(record.getJid()); return getRecordModel(accountId, jid, false) .single(new IkeyRecordModel()) .map(m -> { if (m.getId() == null) m.setId(UUID.randomUUID()); m.setAccountId(accountId); m.setJid(jid); m.setContender(false); m.setFingerprint(new OpenPgpV4Fingerprint(record.getSuperordinate())); m.setSuperordinate(record.getSuperordinate()); m.setTimestamp(record.getTimestamp()); m.setTrust(record.getTrust()); m.getSubordinates().clear(); for (IkeySubordinateRecord s : record.getSubordinates()) { IkeySubordinateModel sm = new IkeySubordinateModel(); sm.setId(s.getId()); sm.setRecord(m); sm.setFpr(s.getFingerprint()); sm.setType(s.getType()); sm.setUri(s.getUri()); m.getSubordinates().add(sm); } return m; }) .flatMap(data::upsert) .ignoreElement() .doOnError(e -> LOGGER.log(Level.SEVERE, "Error storing ikey record", e)); } @Override public Observable loadContenderRecord(UUID accountId, EntityBareJid jid) { return getRecordModel(accountId, jid, false) .map(m -> { List subordinateRecords = new ArrayList<>(); for (IkeySubordinateModel s : m.getSubordinates()) { if (s.getType().equals(OpenPgpElement.NAMESPACE)) { OxSubordinateRecord sr = new OxSubordinateRecord(); sr.setUri(s.getUri()); sr.setOxFingerprint(new OpenPgpV4Fingerprint(s.getFpr())); subordinateRecords.add(sr); } } return new IkeyRecord(m.getJid(), m.getTimestamp(), m.getSuperordinate(), subordinateRecords, m.getTrust()); }) .doOnError(e -> LOGGER.log(Level.SEVERE, "Error loading contender ikey record", e)); } @Override public Completable storeContenderRecord(UUID accountId, EntityBareJid jid, IkeyRecord record) { assert jid.equals(record.getJid()); return getRecordModel(accountId, jid, true) .single(new IkeyRecordModel()) .map(m -> { if (m.getId() == null) m.setId(UUID.randomUUID()); m.setAccountId(accountId); m.setJid(jid); m.setContender(true); m.setFingerprint(new OpenPgpV4Fingerprint(record.getSuperordinate())); m.setSuperordinate(record.getSuperordinate()); m.setTimestamp(record.getTimestamp()); m.setTrust(record.getTrust()); for (IkeySubordinateRecord s : record.getSubordinates()) { IkeySubordinateModel sm = new IkeySubordinateModel(); sm.setId(UUID.randomUUID()); sm.setRecord(m); sm.setFpr(s.getFingerprint()); sm.setType(s.getType()); sm.setUri(s.getUri()); m.getSubordinates().add(sm); } return m; }) .flatMap(data::upsert) .ignoreElement() .doOnError(e -> LOGGER.log(Level.SEVERE, "Error storing contender ikey record", e)); } @Override public Completable clearContenderRecord(UUID accountId, EntityBareJid jid) { return data.delete(IkeyRecordModel.class) .where(IkeyRecordModel.ACCOUNT_ID.eq(accountId) .and(IkeyRecordModel.JID.eq(jid)) .and(IkeyRecordModel.CONTENDER.eq(true))) .get().single().ignoreElement(); } private Observable getRecordModel(UUID accountId, EntityBareJid jid, boolean isContender) { return data.select(IkeyRecordModel.class) .where(IkeyRecordModel.ACCOUNT_ID.eq(accountId) .and(IkeyRecordModel.JID.eq(jid)) .and(IkeyRecordModel.CONTENDER.eq(isContender))) .get().observable(); } }