From 89695c617f43bebdf2685df0f09c0f3c5b5edfbe Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 26 Jun 2020 16:00:47 +0200 Subject: [PATCH] OX key experiments --- .../detail/ContactDetailFragment.java | 14 ++++ .../detail/ContactDetailViewModel.java | 25 ++++++- .../util/OpenPgpFingerprintColorizer.java | 73 +++++++++++++++++++ .../res/layout/fragment_contact_details.xml | 29 ++++++++ .../data/repository/RxOpenPgpRepository.java | 49 +++++++++++-- .../core/crypto/MercuryOpenPgpManager.java | 5 +- .../data/repository/OpenPgpRepository.java | 4 + .../core/xmpp/MercuryConnectionManager.java | 2 +- 8 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/org/mercury_im/messenger/android/util/OpenPgpFingerprintColorizer.java diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/roster/contacts/detail/ContactDetailFragment.java b/app/src/main/java/org/mercury_im/messenger/android/ui/roster/contacts/detail/ContactDetailFragment.java index 458e8ae..0a1a890 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/roster/contacts/detail/ContactDetailFragment.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/roster/contacts/detail/ContactDetailFragment.java @@ -23,10 +23,13 @@ import com.google.android.material.chip.Chip; import com.google.android.material.chip.ChipGroup; import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; +import org.jivesoftware.smackx.colors.ConsistentColor; +import org.mercury_im.messenger.android.util.OpenPgpFingerprintColorizer; import org.mercury_im.messenger.core.Messenger; import org.mercury_im.messenger.R; import org.mercury_im.messenger.android.ui.chat.ChatActivity; import org.mercury_im.messenger.android.util.ColorUtil; +import org.pgpainless.key.OpenPgpV4Fingerprint; import java.util.List; @@ -65,6 +68,9 @@ public class ContactDetailFragment extends Fragment { @BindView(R.id.button_add_to_group) Button button; + @BindView(R.id.fingerprint) + TextView fingerprint; + private ContactDetailViewModel viewModel; @Nullable @@ -145,6 +151,7 @@ public class ContactDetailFragment extends Fragment { viewModel.getContactPresenceStatus().observe(this, presenceText -> contactPresence.setText(presenceText)); viewModel.getContactAccountAddress().observe(this, address -> contactAccount.setText(address)); viewModel.getContactGroups().observe(this, this::setRosterGroups); + viewModel.getContactFingerprints().observe(this, this::setFingerprints); } private void setRosterGroups(List groups) { @@ -164,4 +171,11 @@ public class ContactDetailFragment extends Fragment { contactGroups.addView(chip); } } + + private void setFingerprints(List fingerprints) { + if (fingerprints.isEmpty()) { + return; + } + fingerprint.setText(OpenPgpFingerprintColorizer.formatOpenPgpV4Fingerprint(fingerprints.get(0))); + } } diff --git a/app/src/main/java/org/mercury_im/messenger/android/ui/roster/contacts/detail/ContactDetailViewModel.java b/app/src/main/java/org/mercury_im/messenger/android/ui/roster/contacts/detail/ContactDetailViewModel.java index d4ca519..334d0d8 100644 --- a/app/src/main/java/org/mercury_im/messenger/android/ui/roster/contacts/detail/ContactDetailViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/android/ui/roster/contacts/detail/ContactDetailViewModel.java @@ -19,10 +19,13 @@ import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; import org.mercury_im.messenger.android.MercuryImApplication; import org.mercury_im.messenger.core.Messenger; +import org.mercury_im.messenger.core.SchedulersFacade; +import org.mercury_im.messenger.core.data.repository.OpenPgpRepository; import org.mercury_im.messenger.core.data.repository.PeerRepository; import org.mercury_im.messenger.entity.contact.Peer; import org.mercury_im.messenger.android.ui.avatar.AvatarDrawable; import org.mercury_im.messenger.core.util.CombinedPresenceListener; +import org.pgpainless.key.OpenPgpV4Fingerprint; import java.util.ArrayList; import java.util.Collections; @@ -41,6 +44,12 @@ public class ContactDetailViewModel extends ViewModel { @Inject PeerRepository peerRepository; + @Inject + OpenPgpRepository openPgpRepository; + + @Inject + SchedulersFacade schedulers; + @Inject Messenger messenger; @@ -52,6 +61,7 @@ public class ContactDetailViewModel extends ViewModel { private MutableLiveData contactName = new MutableLiveData<>("Alice Wonderland"); private MutableLiveData contactAccountAddress = new MutableLiveData<>("mad@hatter.lit"); private MutableLiveData> contactGroups = new MutableLiveData<>(Collections.emptyList()); + private MutableLiveData> contactFingerprints = new MutableLiveData<>(Collections.singletonList(new OpenPgpV4Fingerprint("1357B01865B2503C18453D208CAC2A9678548E35"))); private Roster roster; @@ -69,8 +79,8 @@ public class ContactDetailViewModel extends ViewModel { contactAddress.setValue(peerAddress); contactAccountId.setValue(accountId); disposable.add(peerRepository.observePeerByAddress(accountId, peerAddress) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) + .subscribeOn(schedulers.getIoScheduler()) + .observeOn(schedulers.getUiScheduler()) .subscribe(peerOptional -> { if (!peerOptional.isPresent()) { return; @@ -98,6 +108,11 @@ public class ContactDetailViewModel extends ViewModel { contactPresenceMode.postValue(presence.getMode()); contactPresenceStatus.postValue(presence.getStatus()); } + + disposable.add(openPgpRepository.observeFingerprintsOf(accountId, peerAddress) + .subscribeOn(schedulers.getIoScheduler()) + .observeOn(schedulers.getUiScheduler()) + .subscribe(list -> contactFingerprints.setValue(list))); } public LiveData getContactAddress() { @@ -156,7 +171,7 @@ public class ContactDetailViewModel extends ViewModel { InterruptedException, SmackException.NoResponseException { if (!newName.trim().isEmpty()) { RosterEntry entry = roster.getEntry(JidCreate.entityBareFromOrThrowUnchecked(getContactAddress().getValue())); - entry.setName(newName); + entry.setName(newName); } } @@ -183,4 +198,8 @@ public class ContactDetailViewModel extends ViewModel { return Completable.fromAction(() -> roster.getGroup(group).removeEntry(roster.getEntry(JidCreate.entityBareFromOrThrowUnchecked(getContactAddress().getValue())))); } + + public LiveData> getContactFingerprints() { + return contactFingerprints; + } } diff --git a/app/src/main/java/org/mercury_im/messenger/android/util/OpenPgpFingerprintColorizer.java b/app/src/main/java/org/mercury_im/messenger/android/util/OpenPgpFingerprintColorizer.java new file mode 100644 index 0000000..cf4990f --- /dev/null +++ b/app/src/main/java/org/mercury_im/messenger/android/util/OpenPgpFingerprintColorizer.java @@ -0,0 +1,73 @@ +package org.mercury_im.messenger.android.util; + +import android.text.Spannable; +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; + +import org.jivesoftware.smackx.colors.ConsistentColor; +import org.pgpainless.key.OpenPgpV4Fingerprint; + +public class OpenPgpFingerprintColorizer { + + /** + * Split an OpenPGP fingerprint into 10 blocks of length 4. + * + * @param fingerprint fingerprint + * @return blocks + */ + public static String[] getFingerprintBlocks(OpenPgpV4Fingerprint fingerprint) { + String[] blocks = new String[10]; + for (int i = 0; i < 10; i++) { + blocks[i] = fingerprint.subSequence(i*4, (i+1)*4).toString(); + } + return blocks; + } + + /** + * Calculate consistent colors for 10 blocks of length 4 of the fingerprint. + * + * @param blocks Array of 10 OpenPGP fingerprint blocks of length 4 + * @param consistentColorSettings settings for color generation + * @return array of generated colors + */ + public static int[] getColorsForFingerprintBlocks(String[] blocks, + ConsistentColor.ConsistentColorSettings consistentColorSettings) { + int[] colors = new int[10]; + for (int i = 0; i < 10; i++) { + if (consistentColorSettings != null) { + colors[i] = ColorUtil.consistentColor(blocks[i], consistentColorSettings); + } else { + colors[i] = ColorUtil.consistentColor(blocks[i]); + } + } + return colors; + } + + public static Spannable formatOpenPgpV4Fingerprint(OpenPgpV4Fingerprint fingerprint) { + return formatOpenPgpV4Fingerprint(fingerprint, null); + } + + public static Spannable formatOpenPgpV4Fingerprint(OpenPgpV4Fingerprint fingerprint, + ConsistentColor.ConsistentColorSettings settings) { + String[] blocks = getFingerprintBlocks(fingerprint); + int[] colors = getColorsForFingerprintBlocks(blocks, settings); + + StringBuilder formattedFingerprint = new StringBuilder(); + for (int i = 0; i < 10; i++) { + formattedFingerprint.append(blocks[i]); + if (i == 4) { + formattedFingerprint.append('\n'); + continue; + } + if (i != 9) { + formattedFingerprint.append(' '); + } + } + + Spannable spannable = new SpannableString(formattedFingerprint.toString()); + for (int i = 0; i < 10; i++) { + spannable.setSpan(new ForegroundColorSpan(colors[i]), i*5, (i+1)*5-1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + return spannable; + } +} diff --git a/app/src/main/res/layout/fragment_contact_details.xml b/app/src/main/res/layout/fragment_contact_details.xml index f4110fd..94083eb 100644 --- a/app/src/main/res/layout/fragment_contact_details.xml +++ b/app/src/main/res/layout/fragment_contact_details.xml @@ -153,6 +153,35 @@ + + + + + + + + + + + + data; @Inject @@ -46,7 +56,8 @@ public class RxOpenPgpRepository implements OpenPgpRepository { keyRing.setOwner(owner); keyRing.setBytes(keys.getEncoded()); return keyRing; - }).flatMap(data::upsert).ignoreElement(); + }).flatMap(data::upsert).ignoreElement() + .doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored public keys of " + owner + " for account " + accountId)); } @Override @@ -64,7 +75,8 @@ public class RxOpenPgpRepository implements OpenPgpRepository { return data.delete(OpenPgpPublicKeyRing.class) .where(OpenPgpPublicKeyRing.ACCOUNT_ID.eq(accountId)) .and(OpenPgpPublicKeyRing.OWNER.eq(owner)) - .get().single(); + .get().single() + .doOnSuccess(count -> LOGGER.log(Level.INFO, "Successfully deleted " + count + " public keys of " + owner + " for account " + accountId)); } @Override @@ -75,7 +87,8 @@ public class RxOpenPgpRepository implements OpenPgpRepository { keyRing.setOwner(owner); keyRing.setBytes(keys.getEncoded()); return keyRing; - }).flatMap(data::upsert).ignoreElement(); + }).flatMap(data::upsert).ignoreElement() + .doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored secret keys of " + owner + " for account " + accountId)); } @Override @@ -93,7 +106,8 @@ public class RxOpenPgpRepository implements OpenPgpRepository { return data.delete(OpenPgpSecretKeyRing.class) .where(OpenPgpSecretKeyRing.ACCOUNT_ID.eq(accountId)) .and(OpenPgpSecretKeyRing.OWNER.eq(owner)) - .get().single(); + .get().single() + .doOnSuccess(count -> LOGGER.log(Level.INFO, "Successfully deleted " + count + " secret keys of " + owner + " for account " + accountId)); } @Override @@ -121,7 +135,9 @@ public class RxOpenPgpRepository implements OpenPgpRepository { entity.setModificationDate(entry.getValue()); entities.add(entity); } - return data.upsert(entities).ignoreElement(); + return data.upsert(entities).ignoreElement() + .doOnComplete(() -> LOGGER.log(Level.INFO, "Successfully stored announced fingerprints of " + + owner + " for account " + accountId + ": " + Arrays.toString(metadata.keySet().toArray()))); } @Override @@ -165,6 +181,26 @@ public class RxOpenPgpRepository implements OpenPgpRepository { return data.upsert(entity).ignoreElement(); } + @Override + public Observable> observeFingerprintsOf(UUID accountId, String peerAddress) { + return data.select(OpenPgpPublicKeyRing.class) + //.where(OpenPgpPublicKeyRing.ACCOUNT_ID.eq(accountId)) + .where(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 Single> loadPublicKeyFetchDates(UUID accountId, EntityBareJid owner) { return data.select(OpenPgpKeyFetchDate.class) @@ -180,6 +216,7 @@ public class RxOpenPgpRepository implements OpenPgpRepository { map.put(date.getFingerprint(), date.getFetchDate()); } return map; - }); + }) + .doOnError(e -> Logger.getLogger(RxOpenPgpRepository.class.getName()).log(Level.SEVERE, "Error observing public keys", e)); } } diff --git a/domain/src/main/java/org/mercury_im/messenger/core/crypto/MercuryOpenPgpManager.java b/domain/src/main/java/org/mercury_im/messenger/core/crypto/MercuryOpenPgpManager.java index db84d04..ef91ed8 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/crypto/MercuryOpenPgpManager.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/crypto/MercuryOpenPgpManager.java @@ -81,9 +81,10 @@ public class MercuryOpenPgpManager { } oxManager.announceSupportAndPublish(); OXInstantMessagingManager oximManager = OXInstantMessagingManager.getInstanceFor(connection.getConnection()); - oximManager.addOxMessageListener(new MercuryMessageStore(connection.getAccount(), repositories.getPeerRepository(), repositories.getDirectChatRepository(), repositories.getMessageRepository(), schedulers)); + oximManager.addOxMessageListener(new MercuryMessageStore(connection.getAccount(), + repositories.getPeerRepository(), repositories.getDirectChatRepository(), + repositories.getMessageRepository(), schedulers)); oximManager.announceSupportForOxInstantMessaging(); - oxManager.stopMetadataListener(); } catch (Exception e) { e.printStackTrace(); } diff --git a/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpRepository.java b/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpRepository.java index 277fcf8..1991d8f 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpRepository.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/data/repository/OpenPgpRepository.java @@ -7,10 +7,12 @@ import org.jxmpp.jid.EntityBareJid; import org.pgpainless.key.OpenPgpV4Fingerprint; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.UUID; import io.reactivex.Completable; +import io.reactivex.Observable; import io.reactivex.Single; public interface OpenPgpRepository { @@ -38,4 +40,6 @@ public interface OpenPgpRepository { Single loadTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint); Completable storeTrust(UUID accountId, EntityBareJid owner, OpenPgpV4Fingerprint fingerprint, OpenPgpTrustStore.Trust trust); + + Observable> observeFingerprintsOf(UUID accountId, String peerAddress); } diff --git a/domain/src/main/java/org/mercury_im/messenger/core/xmpp/MercuryConnectionManager.java b/domain/src/main/java/org/mercury_im/messenger/core/xmpp/MercuryConnectionManager.java index a917961..506ee5d 100644 --- a/domain/src/main/java/org/mercury_im/messenger/core/xmpp/MercuryConnectionManager.java +++ b/domain/src/main/java/org/mercury_im/messenger/core/xmpp/MercuryConnectionManager.java @@ -82,7 +82,7 @@ public class MercuryConnectionManager { this.cryptoManager = cryptoManager; this.schedulers = schedulers; - EntityCapsManager.setPersistentCache(entityCapsStore); + //EntityCapsManager.setPersistentCache(entityCapsStore); start(); }