248 lines
12 KiB
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);
|
|
}
|
|
}
|