package org.mercury_im.messenger.core.viewmodel.account.detail; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smackx.ikey.IkeyManager; import org.jivesoftware.smackx.ikey.element.IkeyElement; import org.jivesoftware.smackx.ikey.element.SubordinateElement; import org.jivesoftware.smackx.ikey.mechanism.IkeyType; import org.jivesoftware.smackx.ikey_ox.OxIkeySignatureCreationMechanism; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil; import org.jivesoftware.smackx.pep.PepManager; import org.jivesoftware.smackx.pubsub.LeafNode; import org.jivesoftware.smackx.pubsub.PayloadItem; import org.jivesoftware.smackx.pubsub.PubSubManager; import org.jivesoftware.smackx.pubsub.PubSubUri; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; import org.mercury_im.messenger.core.SchedulersFacade; import org.mercury_im.messenger.core.connection.MercuryConnection; import org.mercury_im.messenger.core.connection.MercuryConnectionManager; import org.mercury_im.messenger.core.connection.state.ConnectionState; import org.mercury_im.messenger.core.connection.state.ConnectivityState; import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer; import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository; 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.MercuryViewModel; import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem; import org.mercury_im.messenger.entity.Account; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.protection.UnprotectedKeysProtector; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; 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.Maybe; import io.reactivex.Observable; import io.reactivex.Single; import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS; public class AccountDetailsViewModel implements MercuryViewModel { private static final Logger LOGGER = Logger.getLogger(AccountDetailsViewModel.class.getName()); private MercuryConnectionManager connectionManager; private final OpenPgpRepository openPgpRepository; private final IkeyRepository ikeyRepository; private final AccountRepository accountRepository; private final SchedulersFacade schedulers; private final IkeyInitializer ikeyInitializer; @Inject public AccountDetailsViewModel(MercuryConnectionManager connectionManager, OpenPgpRepository openPgpRepository, IkeyRepository ikeyRepository, AccountRepository accountRepository, SchedulersFacade schedulers, IkeyInitializer ikeyInitializer) { this.connectionManager = connectionManager; this.openPgpRepository = openPgpRepository; this.ikeyRepository = ikeyRepository; this.accountRepository = accountRepository; this.schedulers = schedulers; this.ikeyInitializer = ikeyInitializer; } public void markFingerprintTrusted(UUID accountId, OpenPgpV4Fingerprint fingerprint, boolean trusted) { addDisposable(openPgpRepository.storeTrust(accountId, fingerprint, trusted ? OpenPgpTrustStore.Trust.trusted : OpenPgpTrustStore.Trust.untrusted) .compose(schedulers.executeUiSafeCompletable()) .subscribe()); } public void sendIkeyElement(UUID accountId) { addDisposable(Completable .fromAction(() -> { IkeyElement ikeyElement = syncCreateIkeyElement(accountId); IkeyManager ikeyManager = IkeyManager.getInstanceFor(connectionManager.getConnection(accountId).getConnection()); ikeyManager.publishIkeyElement(ikeyElement); }) .compose(schedulers.executeUiSafeCompletable()) .subscribe( () -> LOGGER.log(Level.INFO, "Successfully published ikey element."), e -> LOGGER.log(Level.SEVERE, "Error publishing ikey element:", e))); } private IkeyElement syncCreateIkeyElement(UUID accountId) throws URISyntaxException, PGPException, IOException { MercuryConnection connection = connectionManager.getConnection(accountId); IkeyManager ikeyManager = ikeyInitializer.initFor(connection); Account account = accountRepository.getAccount(accountId).blockingGet(); OpenPgpV4Fingerprint localFp = openPgpRepository.observeLocalFingerprintOf(accountId).blockingFirst().getItem(); // Why does openPgpRepository.loadAnnouncedFingerprints() never return? List fingerprintList = openPgpRepository.observeFingerprints(accountId, account.getJid()).first(new ArrayList<>()) .map(l -> { List fps = new ArrayList<>(); for (FingerprintViewItem vi : l) { fps.add(vi.getFingerprint()); } return fps; }).blockingGet(); List subordinateElements = new ArrayList<>(); URI uri = new URI(getOxKeyNodeUri(connection, localFp).toString()); subordinateElements.add(new SubordinateElement("urn:xmpp:openpgp:0", uri, localFp.toString())); for (OpenPgpV4Fingerprint fp : fingerprintList) { URI u = new URI(getOxKeyNodeUri(connection, fp).toString()); subordinateElements.add(new SubordinateElement("urn:xmpp:openpgp:0", u, fp.toString())); } PGPSecretKeyRing secretKeys = openPgpRepository.loadSecretKeysOf(accountId, account.getJid()) .blockingGet().getSecretKeyRing(localFp.getKeyId()); IkeyElement ikeyElement = ikeyManager.createOxIkeyElement(secretKeys, new UnprotectedKeysProtector(), subordinateElements.toArray(new SubordinateElement[]{})); return ikeyElement; } private PubSubUri getOxKeyNodeUri(MercuryConnection connection, OpenPgpV4Fingerprint fingerprint) { PubSubManager pubSubManager = PubSubManager.getInstanceFor(connection.getConnection()); BareJid serviceJid = pubSubManager.getServiceJid(); return new PubSubUri(serviceJid, OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEY(fingerprint), null, null); } public Single getJid(UUID accountId) { return accountRepository.getAccount(accountId).toSingle() .compose(schedulers.executeUiSafeSingle()) .map(Account::getJid); } public Observable> observeIkeyFingerprint(UUID accountId) { return ikeyRepository.loadSecretKey(accountId) .map(key -> key.isPresent() ? new Optional<>(new OpenPgpV4Fingerprint(key.getItem().getPublicKey())) : new Optional<>()); } public Observable> observeLocalFingerprint(UUID accountId) { return openPgpRepository.observeLocalFingerprintOf(accountId); } public Observable> observeRemoteFingerprints(UUID accountId) { return observeLocalFingerprint(accountId) .flatMap(optional -> accountRepository.getAccount(accountId).toSingle() .flatMapObservable(account -> openPgpRepository.observeFingerprints(accountId, account.getJid()) .map(list -> { if (!optional.isPresent()) { return list; } List remoteFingerprints = new ArrayList<>(); for(FingerprintViewItem f : list) { if (!f.getFingerprint().equals(optional.getItem())) { remoteFingerprints.add(f); } } return remoteFingerprints; }))); } public Completable unpublishPublicKey(UUID accountId, OpenPgpV4Fingerprint fingerprint) { return deletePublicKeyNode(accountId, fingerprint) .andThen(removePublicKeyFromPubKeyList(accountId, fingerprint)); } private Completable removePublicKeyFromPubKeyList(UUID accountId, OpenPgpV4Fingerprint fingerprint) { return Completable.fromAction(() -> { XMPPConnection xmppConnection = connectionManager.getConnection(accountId).getConnection(); PepManager pepManager = PepManager.getInstanceFor(xmppConnection); PubSubManager pubSubManager = pepManager.getPepPubSubManager(); PublicKeysListElement publishedKeys = OpenPgpPubSubUtil.fetchPubkeysList(xmppConnection); PublicKeysListElement.Builder builder = PublicKeysListElement.builder(); for (PublicKeysListElement.PubkeyMetadataElement meta : publishedKeys.getMetadata().values()) { if (meta.getV4Fingerprint().equals(fingerprint)) { continue; } builder.addMetadata(meta); } publishedKeys = builder.build(); LeafNode metadataNode = pubSubManager.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS); metadataNode.publish(new PayloadItem<>(publishedKeys)); }); } private Completable deletePublicKeyNode(UUID accountId, OpenPgpV4Fingerprint fingerprint) { return Completable.fromAction(() -> { XMPPConnection xmppConnection = connectionManager.getConnection(accountId).getConnection(); PepManager pepManager = PepManager.getInstanceFor(xmppConnection); OpenPgpPubSubUtil.deletePublicKeyNode(pepManager, fingerprint); }); } public Completable generateIkey(UUID accountId) { return Completable.fromAction(() -> { MercuryConnection connection = connectionManager.getConnection(accountId); IkeyManager ikeyManager = ikeyInitializer.initFor(connection); ikeyManager.generateIdentityKey(); }); } public Completable deleteIkey(UUID accountId) { return ikeyRepository.deleteSecretKey(accountId).ignoreElement(); } public Completable restoreIkeyBackup(UUID accountId) { return Completable.fromAction(() -> { MercuryConnection connection = connectionManager.getConnection(accountId); IkeyManager ikeyManager = ikeyInitializer.initFor(connection); SecretkeyElement secretkeyElement = ikeyManager.fetchSecretIdentityKey(); }); } public Observable isAccountEnabled(UUID accountId) { return accountRepository.observeAccount(accountId) .filter(Optional::isPresent) .map(Optional::getItem) .map(Account::isEnabled); } public Observable isAccountAuthenticated(UUID accountId) { return connectionManager.getConnection(accountId) .observeConnection() .map(ConnectionState::getConnectivity) .map(connectivity -> connectivity == ConnectivityState.authenticated); } }