Mercury-IM/domain/src/main/java/org/mercury_im/messenger/core/viewmodel/account/detail/AccountDetailsViewModel.java

248 lines
12 KiB
Java

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<OpenPgpV4Fingerprint> fingerprintList =
openPgpRepository.observeFingerprints(accountId, account.getJid()).first(new ArrayList<>())
.map(l -> {
List<OpenPgpV4Fingerprint> fps = new ArrayList<>();
for (FingerprintViewItem vi : l) {
fps.add(vi.getFingerprint());
}
return fps;
}).blockingGet();
List<SubordinateElement> 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<EntityBareJid> getJid(UUID accountId) {
return accountRepository.getAccount(accountId).toSingle()
.compose(schedulers.executeUiSafeSingle())
.map(Account::getJid);
}
public Observable<Optional<OpenPgpV4Fingerprint>> observeIkeyFingerprint(UUID accountId) {
return ikeyRepository.loadSecretKey(accountId)
.map(key -> key.isPresent() ? new Optional<>(new OpenPgpV4Fingerprint(key.getItem().getPublicKey())) : new Optional<>());
}
public Observable<Optional<OpenPgpV4Fingerprint>> observeLocalFingerprint(UUID accountId) {
return openPgpRepository.observeLocalFingerprintOf(accountId);
}
public Observable<List<FingerprintViewItem>> 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<FingerprintViewItem> 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<Boolean> isAccountEnabled(UUID accountId) {
return accountRepository.observeAccount(accountId)
.filter(Optional::isPresent)
.map(Optional::getItem)
.map(Account::isEnabled);
}
public Observable<Boolean> isAccountAuthenticated(UUID accountId) {
return connectionManager.getConnection(accountId)
.observeConnection()
.map(ConnectionState::getConnectivity)
.map(connectivity -> connectivity == ConnectivityState.authenticated);
}
}