Ikey backup restore works v1.0

This commit is contained in:
Paul Schaub 2020-11-19 23:36:49 +01:00
parent 067ea4b65b
commit b8185b30ae
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
19 changed files with 444 additions and 190 deletions

View File

@ -6,7 +6,11 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import org.bouncycastle.openpgp.PGPException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel;
import org.mercury_im.messenger.android.util.QrCodeGenerator;
@ -15,6 +19,7 @@ import org.mercury_im.messenger.core.crypto.OpenPgpSecretKeyBackupPassphraseGene
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.ikey.IkeySecretKeyBackupCreationViewModel;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -81,4 +86,11 @@ public class AndroidIkeyBackupCreationViewModel extends ViewModel implements Mer
public IkeySecretKeyBackupCreationViewModel getCommonViewModel() {
return commonViewModel;
}
public void uploadBackup() {
addDisposable(getCommonViewModel().createBackup()
.compose(schedulers.executeUiSafeCompletable())
.subscribe(() -> LOGGER.log(Level.INFO, "Successfully uploaded ikey backup."),
e -> LOGGER.log(Level.INFO, "Error uploading ikey backup:", e)));
}
}

View File

@ -14,8 +14,13 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.bouncycastle.openpgp.PGPException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.mercury_im.messenger.R;
import java.io.IOException;
import java.util.UUID;
import butterknife.BindView;
@ -55,6 +60,8 @@ public class IkeyBackupCreationFragment extends Fragment {
viewModel = new ViewModelProvider(this).get(AndroidIkeyBackupCreationViewModel.class);
viewModel.initialize(accountId);
viewModel.uploadBackup();
viewModel.getPassphrase().observe(getViewLifecycleOwner(), passphrase -> backupCode.setText(passphrase));
viewModel.getPassphraseAsQrCode().observe(getViewLifecycleOwner(), bitmap -> qrCode.setImageBitmap(bitmap));
}

View File

@ -4,9 +4,9 @@ import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.di.module.AndroidDatabaseModule;
import org.mercury_im.messenger.android.di.module.AndroidSchedulersModule;
import org.mercury_im.messenger.android.ui.account.detail.AndroidAccountDetailsViewModel;
import org.mercury_im.messenger.android.ui.account.login.AddAccountActivity;
import org.mercury_im.messenger.android.ui.account.login.AndroidIkeyInfoViewModel;
import org.mercury_im.messenger.android.ui.account.login.EnterAccountDetailsFragment;
import org.mercury_im.messenger.android.ui.account.login.IkeySetupViewModel;
import org.mercury_im.messenger.android.ui.account.login.AndroidIkeySetupViewModel;
import org.mercury_im.messenger.android.ui.contacts.AndroidContactListViewModel;
import org.mercury_im.messenger.android.crypto.ikey.AndroidIkeyBackupCreationViewModel;
import org.mercury_im.messenger.core.di.module.IkeyModule;
@ -101,9 +101,11 @@ public interface AppComponent {
void inject(AndroidIkeyBackupCreationViewModel androidIkeyBackupCreationViewModel);
void inject(IkeySetupViewModel ikeySetupViewModel);
void inject(AndroidIkeySetupViewModel androidIkeySetupViewModel);
// void inject(AndroidOxSecretKeyBackupRestoreViewModel androidOxSecretKeyBackupRestoreViewModel);
void inject(AndroidIkeyInfoViewModel androidIkeyInfoViewModel);
// Common VMs
void inject(LoginViewModel loginViewModel);

View File

@ -1,6 +1,7 @@
package org.mercury_im.messenger.android.ui.account.detail;
import android.os.Bundle;
import android.view.Menu;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

View File

@ -34,7 +34,7 @@ public class AddAccountActivity extends AppCompatActivity {
private SetupPagerAdapter pagerAdapter;
private AppComponent appComponent;
private IkeySetupViewModel ikeyViewModel;
private AndroidIkeySetupViewModel ikeyViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -47,7 +47,7 @@ public class AddAccountActivity extends AppCompatActivity {
viewPager.setAdapter(pagerAdapter);
viewPager.setUserInputEnabled(false); // disable swiping
ikeyViewModel = new ViewModelProvider(this).get(IkeySetupViewModel.class); // shared between fragments
ikeyViewModel = new ViewModelProvider(this).get(AndroidIkeySetupViewModel.class); // shared between fragments
}
public void loginFinished(Optional<Account> optionalAccount) {
@ -58,29 +58,32 @@ public class AddAccountActivity extends AppCompatActivity {
}
}
public void skipIkeySetup() {
finish();
}
public void setupIkey(UUID accountId) {
int pos = pagerAdapter.getItemCount();
pagerAdapter.getFragments().put(pos, IkeyBackupRestoreOrSkipFragment.newInstance(accountId));
int nextPos = pagerAdapter.getItemCount();
pagerAdapter.getFragments().put(nextPos, IkeyBackupRestoreOrSkipFragment.newInstance(accountId));
pagerAdapter.notifyDataSetChanged();
viewPager.setCurrentItem(pos);
viewPager.setCurrentItem(nextPos);
}
public void restoreIkeyBackup(UUID accountId) {
int pos = pagerAdapter.getItemCount();
pagerAdapter.getFragments().put(pos, IkeyBackupRestoreSuccessfulFragment.newInstance(accountId));
public void restoreSuccessful(UUID accountId) {
int nextPos = pagerAdapter.getItemCount();
pagerAdapter.getFragments().put(nextPos, IkeyKeyInfoFragment.newInstance(accountId));
pagerAdapter.notifyDataSetChanged();
viewPager.setCurrentItem(pos);
viewPager.setCurrentItem(nextPos);
}
public void generateIkeyBackup(UUID accountId) {
int pos = pagerAdapter.getItemCount();
pagerAdapter.getFragments().put(pos, IkeyBackupCreationFragment.newInstance(accountId));
int nextPos = pagerAdapter.getItemCount();
pagerAdapter.getFragments().put(nextPos, IkeyBackupCreationFragment.newInstance(accountId));
pagerAdapter.notifyDataSetChanged();
viewPager.setCurrentItem(pos);
viewPager.setCurrentItem(nextPos);
}
public void displayInfo(UUID accountId) {
int nextPos = pagerAdapter.getItemCount();
pagerAdapter.getFragments().put(nextPos, IkeyKeyInfoFragment.newInstance(accountId));
pagerAdapter.notifyDataSetChanged();
viewPager.setCurrentItem(nextPos);
}
private class SetupPagerAdapter extends FragmentStateAdapter {

View File

@ -0,0 +1,51 @@
package org.mercury_im.messenger.android.ui.account.login;
import android.app.Application;
import android.text.Spannable;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel;
import org.mercury_im.messenger.android.ui.openpgp.OpenPgpV4FingerprintFormatter;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.ikey.IkeyInfoViewModel;
import java.util.UUID;
import javax.inject.Inject;
public class AndroidIkeyInfoViewModel extends AndroidViewModel implements MercuryAndroidViewModel<IkeyInfoViewModel> {
private MutableLiveData<Spannable> fingerprint = new MutableLiveData<>();
@Inject
IkeyInfoViewModel commonViewModel;
public AndroidIkeyInfoViewModel(@NonNull Application application) {
super(application);
((MercuryImApplication) application).getAppComponent().inject(this);
}
public void init(UUID accountId) {
getCommonViewModel().init(accountId);
addDisposable(getCommonViewModel().getFingerprint()
.filter(Optional::isPresent)
.map(Optional::getItem)
.map(OpenPgpV4FingerprintFormatter::formatOpenPgpV4Fingerprint)
.subscribe(fingerprint::postValue));
}
public LiveData<Spannable> getFingerprint() {
return fingerprint;
}
@Override
public IkeyInfoViewModel getCommonViewModel() {
return commonViewModel;
}
}

View File

@ -0,0 +1,85 @@
package org.mercury_im.messenger.android.ui.account.login;
import android.app.Application;
import android.graphics.Path;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import org.bouncycastle.openpgp.PGPException;
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.ikey.IkeySetupViewModel;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.UUID;
import javax.inject.Inject;
import io.reactivex.Single;
public class AndroidIkeySetupViewModel extends AndroidViewModel implements MercuryAndroidViewModel<IkeySetupViewModel> {
@Inject
IkeySetupViewModel commonViewModel;
private MutableLiveData<Optional<String>> passphraseError = new MutableLiveData<>();
public AndroidIkeySetupViewModel(@NonNull Application application) {
super(application);
MercuryImApplication.getApplication().getAppComponent().inject(this);
}
public void init(UUID accountId) {
getCommonViewModel().init(accountId);
}
public Single<Optional<SecretkeyElement>> fetchBackupElement() {
return getCommonViewModel().fetchBackupElement();
}
@Override
public IkeySetupViewModel getCommonViewModel() {
return commonViewModel;
}
public void generateIdentityKey() throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
getCommonViewModel().generateIdentityKey();
}
public boolean restoreBackup(String backupCode) {
OpenPgpSecretKeyBackupPassphrase passphrase;
try {
passphrase = new OpenPgpSecretKeyBackupPassphrase(backupCode);
} catch (IllegalArgumentException e) {
passphraseError.setValue(new Optional<>("Malformed Passphrase"));
return false;
}
try {
getCommonViewModel().restoreBackup(passphrase);
passphraseError.setValue(new Optional<>());
return true;
} catch (InvalidBackupCodeException e) {
passphraseError.setValue(new Optional<>("Wrong Passphrase"));
} catch (IOException | PGPException e) {
passphraseError.setValue(new Optional<>("Error: " + e.getMessage()));
}
return false;
}
public LiveData<Optional<String>> getPassphraseError() {
return passphraseError;
}
}

View File

@ -1,6 +1,5 @@
package org.mercury_im.messenger.android.ui.account.login;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -8,6 +7,7 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -37,7 +37,7 @@ public class IkeyBackupRestoreOrSkipFragment extends Fragment {
private final UUID accountId;
private IkeySetupViewModel viewModel;
private AndroidIkeySetupViewModel viewModel;
IkeyBackupRestoreOrSkipFragment(UUID accountId) {
this.accountId = accountId;
@ -58,11 +58,25 @@ public class IkeyBackupRestoreOrSkipFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(requireActivity()).get(IkeySetupViewModel.class);
viewModel = new ViewModelProvider(requireActivity()).get(AndroidIkeySetupViewModel.class);
restoreButton.setOnClickListener(v ->
((AddAccountActivity) getActivity()).restoreIkeyBackup(accountId));
restoreButton.setOnClickListener(v -> onRestore());
regenerateButton.setOnClickListener(v ->
((AddAccountActivity) getActivity()).generateIkeyBackup(accountId));
viewModel.getPassphraseError().observe(this, opt -> {
if (opt.isPresent()) {
Toast.makeText(getContext(), opt.getItem(), Toast.LENGTH_SHORT).show();
}
});
}
public void onRestore() {
String backupCode = backupCodeEditText.getText().toString();
if (viewModel.restoreBackup(backupCode)) {
((AddAccountActivity) getActivity()).restoreSuccessful(accountId);
}
}
}

View File

@ -1,5 +1,6 @@
package org.mercury_im.messenger.android.ui.account.login;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -19,29 +20,33 @@ import java.util.UUID;
import butterknife.BindView;
import butterknife.ButterKnife;
public class IkeyBackupRestoreSuccessfulFragment extends Fragment {
public class IkeyKeyInfoFragment extends Fragment {
@BindView(R.id.btn_done)
Button doneButton;
@BindView(R.id.text)
@BindView(R.id.fingerprint)
TextView fingerprint;
private final UUID accountId;
private IkeySetupViewModel viewModel;
@BindView(R.id.btn_backup_ikey)
Button buttonBackupIkey;
IkeyBackupRestoreSuccessfulFragment(UUID accountId) {
@BindView(R.id.btn_done)
Button buttonDone;
private final UUID accountId;
private AndroidIkeyInfoViewModel viewModel;
public IkeyKeyInfoFragment(UUID accountId) {
this.accountId = accountId;
}
public static IkeyBackupRestoreSuccessfulFragment newInstance(UUID accountId) {
return new IkeyBackupRestoreSuccessfulFragment(accountId);
public static Fragment newInstance(UUID accountId) {
return new IkeyKeyInfoFragment(accountId);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_ikey_backup_restore_success, container);
View view = inflater.inflate(R.layout.fragment_ikey_key_info, container, false);
ButterKnife.bind(this, view);
return view;
}
@ -49,8 +54,12 @@ public class IkeyBackupRestoreSuccessfulFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(requireActivity()).get(IkeySetupViewModel.class);
doneButton.setOnClickListener(v -> getActivity().finish());
this.viewModel = new ViewModelProvider(this).get(AndroidIkeyInfoViewModel.class);
viewModel.init(accountId);
viewModel.getFingerprint().observe(this, f -> fingerprint.setText(f));
buttonBackupIkey.setOnClickListener(v -> ((AddAccountActivity)getActivity()).generateIkeyBackup(accountId));
buttonDone.setOnClickListener(v -> getActivity().finish());
}
}

View File

@ -1,6 +1,5 @@
package org.mercury_im.messenger.android.ui.account.login;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -30,7 +29,7 @@ public class IkeySetupFragment extends Fragment {
Button continueButton;
private final UUID accountId;
private IkeySetupViewModel viewModel;
private AndroidIkeySetupViewModel viewModel;
public IkeySetupFragment(UUID accountId) {
this.accountId = accountId;
@ -51,7 +50,7 @@ public class IkeySetupFragment extends Fragment {
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
viewModel = new ViewModelProvider(requireActivity()).get(IkeySetupViewModel.class);
viewModel = new ViewModelProvider(requireActivity()).get(AndroidIkeySetupViewModel.class);
viewModel.init(accountId);
skipButton.setOnClickListener(v ->
@ -65,7 +64,8 @@ public class IkeySetupFragment extends Fragment {
if (opt.isPresent()) {
((AddAccountActivity) getActivity()).setupIkey(accountId);
} else {
((AddAccountActivity) getActivity()).generateIkeyBackup(accountId);
viewModel.generateIdentityKey();
((AddAccountActivity) getActivity()).displayInfo(accountId);
}
})
.subscribe();

View File

@ -1,73 +0,0 @@
package org.mercury_im.messenger.android.ui.account.login;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.ikey.IkeyManager;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.mercury_im.messenger.android.MercuryImApplication;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer;
import org.mercury_im.messenger.core.util.Optional;
import java.util.UUID;
import javax.inject.Inject;
import io.reactivex.Maybe;
import io.reactivex.MaybeObserver;
import io.reactivex.Single;
public class IkeySetupViewModel extends AndroidViewModel {
@Inject
MercuryConnectionManager connectionManager;
@Inject
IkeyInitializer ikeyInitializer;
IkeyManager ikeyManager;
private SecretkeyElement secretkeyElement;
public IkeySetupViewModel(@NonNull Application application) {
super(application);
MercuryImApplication.getApplication().getAppComponent().inject(this);
}
public void init(UUID accountId) {
MercuryConnection connection = connectionManager.getConnection(accountId);
ikeyManager = ikeyInitializer.initFor(connection);
}
public Single<Optional<SecretkeyElement>> fetchBackupElement() {
return fetchMaybeBackupElement()
.map(Optional::new)
.toSingle(new Optional<>());
}
private Maybe<SecretkeyElement> fetchMaybeBackupElement() {
return new Maybe<SecretkeyElement>() {
@Override
protected void subscribeActual(MaybeObserver<? super SecretkeyElement> observer) {
try {
secretkeyElement = ikeyManager.fetchSecretIdentityKey();
if (secretkeyElement != null) {
observer.onSuccess(secretkeyElement);
} else {
observer.onComplete();
}
} catch (PubSubException.NotALeafNodeException e) {
observer.onComplete();
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
observer.onError(e);
}
}
};
}
}

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is your personal Identity Key:"
android:gravity="center"/>
<include android:id="@+id/fingerprint" layout="@layout/view_openpgp_4_fingerprint" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This fingerprint is intended to be shared with your contacts."
android:gravity="center"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="end">
<Button
android:id="@+id/btn_backup_ikey"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Upload Backup" />
<Button
android:id="@+id/btn_done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Done" />
</LinearLayout>
</LinearLayout>

View File

@ -64,8 +64,6 @@ public class RxIkeyRepository implements IkeyRepository {
m.setAccountId(accountId);
m.setKey(secretKey);
m.setFingerprint(new OpenPgpV4Fingerprint(secretKey.getPublicKey()));
m.setBackupPassphrase(m.getBackupPassphrase());
m.setTrusted(m.isTrusted());
return m;
})
.flatMap(data::upsert)
@ -86,8 +84,7 @@ public class RxIkeyRepository implements IkeyRepository {
.where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountID))
.get()
.observable()
.map(IkeySecretKeyModel::getBackupPassphrase)
.map(Optional::new)
.map(m -> new Optional<>(m.getBackupPassphrase()))
.single(new Optional<>());
}
@ -99,7 +96,7 @@ public class RxIkeyRepository implements IkeyRepository {
.single(new IkeySecretKeyModel())
.map(m -> {
m.setAccountId(accountId);
m.setBackupPassphrase(m.getBackupPassphrase());
m.setBackupPassphrase(passphrase);
return m;
})
.flatMap(data::upsert)

View File

@ -27,6 +27,7 @@ import org.jivesoftware.smackx.ikey_ox.OxIkeySignatureVerificationMechanism;
import org.jivesoftware.smackx.ox.OpenPgpManager;
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper;
import org.jivesoftware.smackx.pep.PepEventListener;
@ -39,8 +40,10 @@ import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.core.crypto.OpenPgpSecretKeyBackupPassphraseGenerator;
import org.mercury_im.messenger.core.crypto.SecureRandomSecretKeyBackupPassphraseGenerator;
import org.pgpainless.PGPainless;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.collection.PGPKeyRing;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.util.BCUtil;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
@ -280,4 +283,11 @@ public final class IkeyManager extends Manager {
private OpenPgpSecretKeyBackupPassphrase generateBackupPassphrase() {
return backupPassphraseGenerator.generateBackupPassphrase();
}
public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, OpenPgpSecretKeyBackupPassphrase passphrase)
throws PGPException, IOException, InvalidBackupCodeException {
PGPSecretKeyRing secretKeys = SecretKeyBackupHelper.restoreSecretKeyBackup(secretkeyElement, passphrase);
store.storeSecretKey(secretKeys);
store.storeBackupPassphrase(passphrase);
}
}

View File

@ -52,9 +52,7 @@ public class IkeyStoreAdapter implements IkeyStore {
@Override
public void storeSecretKey(PGPSecretKeyRing secretKey) {
repository.storeSecretKey(accountId, secretKey)
.compose(schedulers.executeUiSafeCompletable())
.subscribe();
repository.storeSecretKey(accountId, secretKey).blockingAwait();
}
@Override
@ -68,7 +66,6 @@ public class IkeyStoreAdapter implements IkeyStore {
@Override
public void storeBackupPassphrase(OpenPgpSecretKeyBackupPassphrase passphrase) {
repository.storeBackupPassphrase(accountId, passphrase)
.compose(schedulers.executeUiSafeCompletable())
.subscribe();
.blockingAwait();
}
}

View File

@ -0,0 +1,49 @@
package org.mercury_im.messenger.core.viewmodel.ikey;
import org.mercury_im.messenger.core.SchedulersFacade;
import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import java.util.UUID;
import javax.inject.Inject;
import io.reactivex.Observable;
import io.reactivex.subjects.BehaviorSubject;
public class IkeyInfoViewModel implements MercuryViewModel {
private final SchedulersFacade schedulers;
private final IkeyRepository ikeyRepository;
private final BehaviorSubject<Optional<OpenPgpV4Fingerprint>> fingerprint = BehaviorSubject.createDefault(new Optional<>());
private UUID accountId;
@Inject
public IkeyInfoViewModel(IkeyRepository ikeyRepository, SchedulersFacade schedulersFacade) {
this.ikeyRepository = ikeyRepository;
this.schedulers = schedulersFacade;
}
public void init(UUID accountId) {
if (this.accountId != null) {
throw new IllegalStateException("Already initialized.");
}
this.accountId = accountId;
addDisposable(ikeyRepository.loadSecretKey(accountId)
.filter(Optional::isPresent)
.map(Optional::getItem)
.firstOrError()
.map(OpenPgpV4Fingerprint::new)
.compose(schedulers.executeUiSafeSingle())
.map(Optional::new)
.subscribe(this.fingerprint::onNext));
}
public Observable<Optional<OpenPgpV4Fingerprint>> getFingerprint() {
return fingerprint;
}
}

View File

@ -1,61 +0,0 @@
package org.mercury_im.messenger.core.viewmodel.ikey;
import org.bouncycastle.openpgp.PGPException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.ikey.IkeyManager;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import org.mercury_im.messenger.entity.Account;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
public class IkeyInitializationViewModel implements MercuryViewModel {
private final MercuryConnectionManager connectionManager;
private final IkeyInitializer ikeyInitializer;
public IkeyInitializationViewModel(MercuryConnectionManager connectionManager,
IkeyInitializer ikeyInitializer) {
this.connectionManager = connectionManager;
this.ikeyInitializer = ikeyInitializer;
}
public void initialize(Account account) throws PGPException, NoSuchAlgorithmException,
NoSuchProviderException, InvalidAlgorithmParameterException, InterruptedException,
SmackException.NoResponseException, SmackException.NotConnectedException,
SmackException.FeatureNotSupportedException, XMPPException.XMPPErrorException,
PubSubException.NotALeafNodeException, IOException {
MercuryConnection connection = connectionManager.getConnection(account);
IkeyManager ikeyManager = ikeyInitializer.initFor(connection);
final SecretkeyElement backup = ikeyManager.fetchSecretIdentityKey();
if (!ikeyManager.hasLocalKey()) {
if (backup == null) {
ikeyManager.generateIdentityKey();
ikeyManager.depositIdentityKeyBackup();
} else {
//ikeyManager.restore(backup);
}
} else {
if (backup == null) {
ikeyManager.depositIdentityKeyBackup();
} else {
//if (!ikeyManager.keyBackupIsSameAsLocal()) {
// displayNewKeyNotification();
//} else {
// ikeyManager.depositIdentityKeyBackup();
// }
}
}
}
}

View File

@ -1,7 +1,11 @@
package org.mercury_im.messenger.core.viewmodel.ikey;
import org.bouncycastle.openpgp.PGPException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.ikey.IkeyManager;
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer;
@ -10,10 +14,12 @@ import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import org.mercury_im.messenger.entity.Account;
import java.io.IOException;
import java.util.UUID;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.Observable;
public class IkeySecretKeyBackupCreationViewModel implements MercuryViewModel {
@ -34,12 +40,21 @@ public class IkeySecretKeyBackupCreationViewModel implements MercuryViewModel {
this.accountId = accountId;
}
public void createIkeySecretKeyBackup(Account account) {
MercuryConnection connection = connectionManager.getConnection(account);
IkeyManager ikeyManager = ikeyInitializer.initFor(connection);
}
public Observable<Optional<OpenPgpSecretKeyBackupPassphrase>> getPassphrase() {
return ikeyRepository.loadBackupPassphrase(accountId).toObservable();
}
public void doCreateBackup() throws XMPPException.XMPPErrorException, InterruptedException,
SmackException.NoResponseException, SmackException.NotConnectedException,
SmackException.FeatureNotSupportedException, PGPException,
PubSubException.NotALeafNodeException, IOException {
MercuryConnection connection = connectionManager.getConnection(accountId);
IkeyManager ikeyManager = ikeyInitializer.initFor(connection);
ikeyManager.depositIdentityKeyBackup();
}
public Completable createBackup() {
return Completable.fromAction(this::doCreateBackup);
}
}

View File

@ -0,0 +1,96 @@
package org.mercury_im.messenger.core.viewmodel.ikey;
import org.bouncycastle.openpgp.PGPException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.ikey.IkeyManager;
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
import org.jivesoftware.smackx.pubsub.PubSubException;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
import org.mercury_im.messenger.core.crypto.OpenPgpSecretKeyBackupPassphraseGenerator;
import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer;
import org.mercury_im.messenger.core.util.Optional;
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import io.reactivex.Maybe;
import io.reactivex.MaybeObserver;
import io.reactivex.Single;
import lombok.Getter;
public class IkeySetupViewModel implements MercuryViewModel {
private static final Logger LOGGER = Logger.getLogger(IkeySetupViewModel.class.getName());
private final MercuryConnectionManager connectionManager;
private final IkeyInitializer ikeyInitializer;
private IkeyManager ikeyManager;
private SecretkeyElement secretkeyElement;
@Getter
private final OpenPgpSecretKeyBackupPassphrase backupCreationPassphrase;
@Inject
public IkeySetupViewModel(MercuryConnectionManager connectionManager, IkeyInitializer ikeyInitializer, OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator) {
this.connectionManager = connectionManager;
this.ikeyInitializer = ikeyInitializer;
this.backupCreationPassphrase = passphraseGenerator.generateBackupPassphrase();
}
public void init(UUID accountId) {
MercuryConnection connection = connectionManager.getConnection(accountId);
ikeyManager = ikeyInitializer.initFor(connection);
}
public Single<Optional<SecretkeyElement>> fetchBackupElement() {
return fetchMaybeBackupElement()
.map(Optional::new)
.toSingle(new Optional<>());
}
private Maybe<SecretkeyElement> fetchMaybeBackupElement() {
return new Maybe<SecretkeyElement>() {
@Override
protected void subscribeActual(MaybeObserver<? super SecretkeyElement> observer) {
try {
secretkeyElement = ikeyManager.fetchSecretIdentityKey();
if (secretkeyElement != null) {
LOGGER.log(Level.INFO, "Found IKey backup element.");
observer.onSuccess(secretkeyElement);
} else {
LOGGER.log(Level.INFO, "Ikey backup element is null.");
observer.onComplete();
}
} catch (PubSubException.NotALeafNodeException e) {
LOGGER.log(Level.INFO, "Ikey backup node is not a LeafNode.");
observer.onComplete();
} catch (InterruptedException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NoResponseException e) {
LOGGER.log(Level.INFO, "Error restoring Ikey backup", e);
observer.onError(e);
}
}
};
}
public void generateIdentityKey() throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
ikeyManager.generateIdentityKey();
}
public void restoreBackup(OpenPgpSecretKeyBackupPassphrase passphrase) throws IOException, PGPException, InvalidBackupCodeException {
ikeyManager.restoreSecretKeyBackup(secretkeyElement, passphrase);
}
}