Progress on generating IKeys
This commit is contained in:
parent
733f9684c7
commit
c09dc77859
|
@ -82,7 +82,10 @@ dependencies {
|
||||||
|
|
||||||
// Dagger 2 for dependency injection
|
// Dagger 2 for dependency injection
|
||||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
||||||
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||||
|
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||||
|
|
||||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||||
|
@ -91,6 +94,9 @@ dependencies {
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
|
||||||
annotationProcessor "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
annotationProcessor "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
|
||||||
|
|
||||||
|
implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycleVersion"
|
||||||
|
|
||||||
|
|
||||||
// Android extension for rxJava
|
// Android extension for rxJava
|
||||||
api "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
|
api "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
|
||||||
|
|
||||||
|
@ -110,6 +116,9 @@ dependencies {
|
||||||
// circular image viewer for avatars
|
// circular image viewer for avatars
|
||||||
implementation 'de.hdodenhof:circleimageview:3.0.1'
|
implementation 'de.hdodenhof:circleimageview:3.0.1'
|
||||||
|
|
||||||
|
implementation 'com.google.zxing:core:3.2.1'
|
||||||
|
implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar'
|
||||||
|
|
||||||
// Android specific classes of Smacks API
|
// Android specific classes of Smacks API
|
||||||
implementation "org.igniterealtime.smack:smack-android-extensions:$smackAndroidExtensionsVersion"
|
implementation "org.igniterealtime.smack:smack-android-extensions:$smackAndroidExtensionsVersion"
|
||||||
|
|
||||||
|
|
|
@ -1,149 +1,66 @@
|
||||||
package org.mercury_im.messenger;
|
package org.mercury_im.messenger;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.InstrumentationRegistry;
|
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.BeforeClass;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.jxmpp.jid.impl.JidCreate;
|
import org.mercury_im.messenger.core.data.repository.AccountRepository;
|
||||||
import org.mercury_im.messenger.xmpp.requery.entity.AccountModel;
|
import org.mercury_im.messenger.di.DaggerAndroidTestComponent;
|
||||||
import org.mercury_im.messenger.xmpp.requery.entity.ChatModel;
|
import org.mercury_im.messenger.entity.Account;
|
||||||
import org.mercury_im.messenger.xmpp.requery.entity.ContactModel;
|
|
||||||
import org.mercury_im.messenger.xmpp.requery.entity.EntityModel;
|
|
||||||
import org.mercury_im.messenger.xmpp.requery.entity.LastReadChatMessageRelation;
|
|
||||||
import org.mercury_im.messenger.xmpp.requery.entity.MessageModel;
|
|
||||||
import org.mercury_im.messenger.xmpp.requery.entity.Models;
|
|
||||||
import org.mercury_im.messenger.core.requery.enums.SubscriptionDirection;
|
|
||||||
|
|
||||||
import io.reactivex.disposables.Disposable;
|
import javax.inject.Inject;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import io.requery.Persistable;
|
import io.reactivex.observers.TestObserver;
|
||||||
import io.requery.android.sqlite.DatabaseSource;
|
|
||||||
import io.requery.reactivex.ReactiveEntityStore;
|
|
||||||
import io.requery.reactivex.ReactiveSupport;
|
|
||||||
import io.requery.sql.Configuration;
|
|
||||||
import io.requery.sql.EntityDataStore;
|
|
||||||
import io.requery.sql.TableCreationMode;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class RequeryDatabaseTest {
|
public class RequeryDatabaseTest {
|
||||||
|
|
||||||
static ReactiveEntityStore<Persistable> dataStore;
|
@Inject
|
||||||
|
AccountRepository accountRepository;
|
||||||
|
|
||||||
@BeforeClass
|
@Before
|
||||||
public static void setup() {
|
public void setup() {
|
||||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
DaggerAndroidTestComponent.builder().build().inject(this);
|
||||||
DatabaseSource source = new DatabaseSource(appContext, Models.DEFAULT,
|
|
||||||
"mercury_test_db", 1);
|
|
||||||
// use this in development mode to drop and recreate the tables on every upgrade
|
|
||||||
source.setTableCreationMode(TableCreationMode.DROP_CREATE);
|
|
||||||
source.setLoggingEnabled(true);
|
|
||||||
Configuration configuration = source.getConfiguration();
|
|
||||||
dataStore = ReactiveSupport.toReactiveStore(
|
|
||||||
new EntityDataStore<>(configuration));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void databaseTest() throws InterruptedException {
|
public void insertQueryDeleteAccountTest() {
|
||||||
AccountModel accountModel = new AccountModel();
|
Account account = new Account();
|
||||||
accountModel.setJid(JidCreate.entityBareFromOrThrowUnchecked("juliet@capulet.lit"));
|
|
||||||
accountModel.setPassword("romeo0romeo");
|
|
||||||
accountModel.setRosterVersion(null);
|
|
||||||
accountModel.setEnabled(true);
|
|
||||||
|
|
||||||
Disposable accounts = dataStore.select(AccountModel.class).get().observableResult().subscribe(
|
|
||||||
accountModels -> accountModels.forEach(System.out::println));
|
|
||||||
|
|
||||||
Disposable entities = dataStore.select(EntityModel.class).get().observableResult()
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.subscribe(entityModels -> entityModels.forEach(System.out::println));
|
|
||||||
|
|
||||||
Disposable contacts = dataStore.select(ContactModel.class).get()
|
|
||||||
.observableResult().subscribeOn(Schedulers.io()).subscribe(
|
|
||||||
contactModels -> contactModels.forEach(System.out::println));
|
|
||||||
|
|
||||||
Thread.sleep(100);
|
|
||||||
|
|
||||||
dataStore.upsert(accountModel).subscribeOn(Schedulers.io())
|
|
||||||
.subscribe();
|
|
||||||
|
|
||||||
ContactModel contactModel = new ContactModel();
|
|
||||||
EntityModel entityModel = new EntityModel();
|
|
||||||
entityModel.setAccount(accountModel);
|
|
||||||
entityModel.setJid(JidCreate.entityBareFromOrThrowUnchecked("romeo@capulet.lit"));
|
|
||||||
contactModel.setEntity(entityModel);
|
|
||||||
contactModel.setRostername("Romeo");
|
|
||||||
contactModel.setSub_direction(SubscriptionDirection.both);
|
|
||||||
dataStore.insert(contactModel).blockingGet();
|
|
||||||
|
|
||||||
dataStore.select(AccountModel.ENABLED, ContactModel.ROSTERNAME)
|
|
||||||
.from(AccountModel.class)
|
|
||||||
.join(EntityModel.class).on(AccountModel.ID.eq(EntityModel.ACCOUNT_ID))
|
|
||||||
.join(ContactModel.class).on(EntityModel.ID.eq(ContactModel.ENTITY_ID))
|
|
||||||
.get().observableResult().blockingForEach(e -> e.forEach(System.out::println));
|
|
||||||
|
|
||||||
Thread.sleep(10000);
|
|
||||||
accounts.dispose();
|
|
||||||
entities.dispose();
|
|
||||||
contacts.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ContactDetail {
|
|
||||||
private boolean enabled;
|
|
||||||
private String rostername;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test2() {
|
|
||||||
AccountModel account = new AccountModel();
|
|
||||||
account.setJid(JidCreate.entityBareFromOrThrowUnchecked("omemouser@jabberhead.tk"));
|
|
||||||
account.setEnabled(true);
|
account.setEnabled(true);
|
||||||
account.setPassword("nöö");
|
account.setPassword("sw0rdf1sh");
|
||||||
|
account.setAddress("alice@wonderland.lit");
|
||||||
|
account.setRosterVersion("");
|
||||||
|
account.setHost("wonderland.lit");
|
||||||
|
account.setPort(5222);
|
||||||
|
|
||||||
EntityModel entity = new EntityModel();
|
// Insert account into database
|
||||||
entity.setJid(JidCreate.entityBareFromOrThrowUnchecked("jabbertest@test.test"));
|
TestObserver<Account> testObserver = new TestObserver<>();
|
||||||
entity.setAccount(account);
|
accountRepository.upsertAccount(account)
|
||||||
|
.subscribe(testObserver);
|
||||||
|
|
||||||
ContactModel contact = new ContactModel();
|
testObserver.assertComplete();
|
||||||
contact.setEntity(entity);
|
testObserver.assertNoErrors();
|
||||||
contact.setRostername("Olaf");
|
testObserver.assertNoTimeout();
|
||||||
contact.setSub_direction(SubscriptionDirection.both);
|
testObserver.assertValue(account);
|
||||||
contact.setSub_approved(false);
|
|
||||||
contact.setSub_pending(true);
|
|
||||||
|
|
||||||
dataStore.upsert(contact).blockingGet();
|
// delete account from database
|
||||||
|
testObserver = new TestObserver<>();
|
||||||
|
accountRepository.deleteAccount(account)
|
||||||
|
.subscribe(testObserver);
|
||||||
|
|
||||||
ChatModel chat = new ChatModel();
|
testObserver.assertComplete();
|
||||||
chat.setPeer(entity);
|
testObserver.assertNoErrors();
|
||||||
chat.setDisplayed(true);
|
testObserver.assertNoTimeout();
|
||||||
|
|
||||||
dataStore.upsert(chat).blockingGet();
|
// assert no account in database
|
||||||
|
testObserver = new TestObserver<>();
|
||||||
|
accountRepository.getAccount(account.getId()).subscribe(testObserver);
|
||||||
|
|
||||||
MessageModel message = new MessageModel();
|
testObserver.assertComplete();
|
||||||
message.setBody("Hallo Welt!");
|
testObserver.assertNoErrors();
|
||||||
message.setChat(chat);
|
testObserver.assertNoValues();
|
||||||
|
|
||||||
dataStore.upsert(message).blockingGet();
|
|
||||||
|
|
||||||
LastReadChatMessageRelation lastRead = new LastReadChatMessageRelation();
|
|
||||||
lastRead.setChat(chat);
|
|
||||||
lastRead.setMessage(message);
|
|
||||||
|
|
||||||
dataStore.upsert(lastRead).blockingGet();
|
|
||||||
|
|
||||||
MessageModel message2 = new MessageModel();
|
|
||||||
message2.setChat(chat);
|
|
||||||
message2.setBody("How are you?");
|
|
||||||
|
|
||||||
dataStore.upsert(message2).blockingGet();
|
|
||||||
|
|
||||||
LastReadChatMessageRelation lastRead2 = new LastReadChatMessageRelation();
|
|
||||||
lastRead2.setChat(chat);
|
|
||||||
lastRead2.setMessage(message2);
|
|
||||||
|
|
||||||
dataStore.upsert(lastRead2).blockingGet();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.mercury_im.messenger.di;
|
||||||
|
|
||||||
|
import org.mercury_im.messenger.RequeryDatabaseTest;
|
||||||
|
import org.mercury_im.messenger.data.di.MappingModule;
|
||||||
|
import org.mercury_im.messenger.data.di.RepositoryModule;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Component(modules = {
|
||||||
|
TestDatabaseModule.class,
|
||||||
|
RepositoryModule.class,
|
||||||
|
MappingModule.class
|
||||||
|
})
|
||||||
|
public interface AndroidTestComponent {
|
||||||
|
|
||||||
|
void inject(RequeryDatabaseTest test);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.mercury_im.messenger.di;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
|
||||||
|
import org.mercury_im.messenger.data.model.Models;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import io.requery.Persistable;
|
||||||
|
import io.requery.android.sqlite.DatabaseSource;
|
||||||
|
import io.requery.reactivex.ReactiveEntityStore;
|
||||||
|
import io.requery.reactivex.ReactiveSupport;
|
||||||
|
import io.requery.sql.Configuration;
|
||||||
|
import io.requery.sql.EntityDataStore;
|
||||||
|
import io.requery.sql.TableCreationMode;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class TestDatabaseModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public static ReactiveEntityStore<Persistable> provideDataStore() {
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
DatabaseSource source = new DatabaseSource(appContext, Models.DEFAULT,
|
||||||
|
"mercury_test_db", 1);
|
||||||
|
// use this in development mode to drop and recreate the tables on every upgrade
|
||||||
|
source.setTableCreationMode(TableCreationMode.DROP_CREATE);
|
||||||
|
source.setLoggingEnabled(true);
|
||||||
|
Configuration configuration = source.getConfiguration();
|
||||||
|
return ReactiveSupport.toReactiveStore(new EntityDataStore<>(configuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.mercury_im.messenger">
|
package="org.mercury_im.messenger">
|
||||||
|
|
||||||
<!-- To auto-complete the email text field in the login form with the user's emails -->
|
<!-- To auto-complete the email text field in the login form with the user's emails -->
|
||||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||||
<uses-permission android:name="android.permission.READ_PROFILE" />
|
<uses-permission android:name="android.permission.READ_PROFILE" />
|
||||||
|
@ -18,9 +17,10 @@
|
||||||
android:roundIcon="@drawable/ic_mercury_icon"
|
android:roundIcon="@drawable/ic_mercury_icon"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Mercury">
|
android:theme="@style/Theme.Mercury">
|
||||||
|
<activity android:name=".android.ui.ikey.IkeyBackupRestoreActivity"></activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".android.ui.chat.ChatActivity"
|
android:name=".android.ui.chat.ChatActivity"
|
||||||
android:label="Chat"/>
|
android:label="Chat" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".android.ui.MainActivity"
|
android:name=".android.ui.MainActivity"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.mercury_im.messenger.android.di.module.AndroidDatabaseModule;
|
||||||
import org.mercury_im.messenger.android.di.module.AndroidSchedulersModule;
|
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.detail.AndroidAccountDetailsViewModel;
|
||||||
import org.mercury_im.messenger.android.ui.contacts.AndroidContactListViewModel;
|
import org.mercury_im.messenger.android.ui.contacts.AndroidContactListViewModel;
|
||||||
|
import org.mercury_im.messenger.android.ui.ikey.IkeyBackupCreationAndroidViewModel;
|
||||||
import org.mercury_im.messenger.core.di.module.OpenPgpModule;
|
import org.mercury_im.messenger.core.di.module.OpenPgpModule;
|
||||||
import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule;
|
import org.mercury_im.messenger.core.di.module.RxMercuryMessageStoreFactoryModule;
|
||||||
import org.mercury_im.messenger.core.di.module.RxMercuryRosterStoreFactoryModule;
|
import org.mercury_im.messenger.core.di.module.RxMercuryRosterStoreFactoryModule;
|
||||||
|
@ -93,7 +94,7 @@ public interface AppComponent {
|
||||||
|
|
||||||
void inject(AndroidAccountDetailsViewModel accountDetailsViewModel);
|
void inject(AndroidAccountDetailsViewModel accountDetailsViewModel);
|
||||||
|
|
||||||
//void inject(AndroidOxSecretKeyBackupRestoreViewModel viewModel);
|
void inject(IkeyBackupCreationAndroidViewModel ikeyBackupCreationAndroidViewModel);
|
||||||
|
|
||||||
|
|
||||||
// Common VMs
|
// Common VMs
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
|
||||||
import org.mercury_im.messenger.R;
|
import org.mercury_im.messenger.R;
|
||||||
import org.mercury_im.messenger.android.ui.MercuryActivity;
|
import org.mercury_im.messenger.android.ui.MercuryActivity;
|
||||||
|
@ -11,6 +12,7 @@ import org.mercury_im.messenger.android.util.ArgumentUtils;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
|
@ -20,6 +22,9 @@ public class AccountDetailsActivity extends AppCompatActivity implements Mercury
|
||||||
|
|
||||||
private UUID accountId;
|
private UUID accountId;
|
||||||
|
|
||||||
|
@BindView(R.id.toolbar)
|
||||||
|
Toolbar toolbar;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -32,12 +37,14 @@ public class AccountDetailsActivity extends AppCompatActivity implements Mercury
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindUiComponents() {
|
private void bindUiComponents() {
|
||||||
setContentView(R.layout.activity_fragment_container);
|
setContentView(R.layout.layout_top_toolbar);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.fragment, new AccountDetailsFragment(accountId), "account_details")
|
.replace(R.id.fragment, new AccountDetailsFragment(accountId), "account_details")
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Arguments getArguments(Bundle bundle) {
|
private Arguments getArguments(Bundle bundle) {
|
||||||
|
|
|
@ -4,6 +4,9 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
@ -11,6 +14,8 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
@ -19,10 +24,14 @@ import com.google.android.material.card.MaterialCardView;
|
||||||
|
|
||||||
import org.mercury_im.messenger.R;
|
import org.mercury_im.messenger.R;
|
||||||
import org.mercury_im.messenger.android.MercuryImApplication;
|
import org.mercury_im.messenger.android.MercuryImApplication;
|
||||||
|
import org.mercury_im.messenger.android.ui.ikey.IkeyBackupCreationFragment;
|
||||||
import org.mercury_im.messenger.android.ui.openpgp.ToggleableFingerprintsAdapter;
|
import org.mercury_im.messenger.android.ui.openpgp.ToggleableFingerprintsAdapter;
|
||||||
import org.mercury_im.messenger.android.ui.openpgp.OpenPgpV4FingerprintFormatter;
|
import org.mercury_im.messenger.android.ui.openpgp.OpenPgpV4FingerprintFormatter;
|
||||||
|
import org.mercury_im.messenger.core.util.Optional;
|
||||||
|
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
|
@ -40,6 +49,15 @@ public class AccountDetailsFragment extends Fragment {
|
||||||
@BindView(R.id.btn_share)
|
@BindView(R.id.btn_share)
|
||||||
Button localFingerprintShareButton;
|
Button localFingerprintShareButton;
|
||||||
|
|
||||||
|
@BindView(R.id.btn_share_ikey)
|
||||||
|
Button ikeyFingerprintShareButton;
|
||||||
|
|
||||||
|
@BindView(R.id.btn_backup_ikey)
|
||||||
|
Button ikeyCreateBackupButton;
|
||||||
|
|
||||||
|
@BindView(R.id.ikey_fingerprint)
|
||||||
|
TextView ikeyFingerprint;
|
||||||
|
|
||||||
@BindView(R.id.local_fingerprint)
|
@BindView(R.id.local_fingerprint)
|
||||||
TextView localFingerprint;
|
TextView localFingerprint;
|
||||||
|
|
||||||
|
@ -49,8 +67,11 @@ public class AccountDetailsFragment extends Fragment {
|
||||||
@BindView(R.id.other_fingerprints_card)
|
@BindView(R.id.other_fingerprints_card)
|
||||||
MaterialCardView otherFingerprintsLayout;
|
MaterialCardView otherFingerprintsLayout;
|
||||||
|
|
||||||
|
@BindView(R.id.layout_ikey)
|
||||||
|
MaterialCardView ikeyLayout;
|
||||||
|
|
||||||
private final UUID accountId;
|
private final UUID accountId;
|
||||||
private ToggleableFingerprintsAdapter adapter;
|
private ToggleableFingerprintsAdapter otherFingerprintsAdapter;
|
||||||
|
|
||||||
private AndroidAccountDetailsViewModel viewModel;
|
private AndroidAccountDetailsViewModel viewModel;
|
||||||
|
|
||||||
|
@ -58,19 +79,6 @@ public class AccountDetailsFragment extends Fragment {
|
||||||
this.accountId = accountId;
|
this.accountId = accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.fragment_account_details, container, false);
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
externalFingerprintRecyclerView.setAdapter(adapter);
|
|
||||||
|
|
||||||
observe();
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(@NonNull Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
|
@ -79,33 +87,110 @@ public class AccountDetailsFragment extends Fragment {
|
||||||
viewModel = new ViewModelProvider(this, factory)
|
viewModel = new ViewModelProvider(this, factory)
|
||||||
.get(AndroidAccountDetailsViewModel.class);
|
.get(AndroidAccountDetailsViewModel.class);
|
||||||
|
|
||||||
this.adapter = new ToggleableFingerprintsAdapter(
|
this.otherFingerprintsAdapter = new ToggleableFingerprintsAdapter(
|
||||||
(fingerprint, checked) -> viewModel.markFingerprintTrusted(fingerprint, checked));
|
(fingerprint, checked) -> viewModel.markFingerprintTrusted(fingerprint, checked));
|
||||||
this.adapter.setItemLongClickListener(fingerprint -> viewModel.unpublishPublicKey(fingerprint));
|
this.otherFingerprintsAdapter.setItemLongClickListener(fingerprint -> viewModel.unpublishPublicKey(fingerprint));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void observe() {
|
@Override
|
||||||
viewModel.getLocalFingerprint().observe(getViewLifecycleOwner(),
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
f -> localFingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(f)));
|
inflater.inflate(R.menu.menu_account_details, menu);
|
||||||
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.getRemoteFingerprints().observe(getViewLifecycleOwner(), items -> {
|
@Override
|
||||||
otherFingerprintsLayout.setVisibility(items.isEmpty() ? View.GONE : View.VISIBLE);
|
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||||
adapter.setItems(items);
|
switch (item.getItemId()) {
|
||||||
});
|
|
||||||
|
case R.id.action_generate_ikey:
|
||||||
|
viewModel.onGenerateIkey();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.action_delete_ikey:
|
||||||
|
viewModel.onDeleteIkey();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case R.id.action_restore_ikey_backup:
|
||||||
|
viewModel.onRestoreIkeyBackup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_account_details, container, false);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
|
externalFingerprintRecyclerView.setAdapter(otherFingerprintsAdapter);
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getIkeyFingerprint().observe(getViewLifecycleOwner(), this::displayIkeyFingerprint);
|
||||||
|
viewModel.getLocalFingerprint().observe(getViewLifecycleOwner(), this::displayLocalOxFingerprint);
|
||||||
|
viewModel.getRemoteFingerprints().observe(getViewLifecycleOwner(), this::displayOtherOxFingerprints);
|
||||||
|
|
||||||
viewModel.getJid().observe(getViewLifecycleOwner(), accountJid -> jid.setText(accountJid.toString()));
|
viewModel.getJid().observe(getViewLifecycleOwner(), accountJid -> jid.setText(accountJid.toString()));
|
||||||
|
|
||||||
localFingerprintShareButton.setOnClickListener(v -> {
|
localFingerprintShareButton.setOnClickListener(v -> {
|
||||||
OpenPgpV4Fingerprint fingerprint = viewModel.getLocalFingerprint().getValue();
|
OpenPgpV4Fingerprint fingerprint = viewModel.getLocalFingerprint().getValue();
|
||||||
Intent sendIntent = new Intent();
|
startShareFingerprintIntent(fingerprint);
|
||||||
sendIntent.setAction(Intent.ACTION_SEND);
|
});
|
||||||
sendIntent.putExtra(Intent.EXTRA_TEXT, "openpgp4fpr:" + fingerprint);
|
|
||||||
sendIntent.setType("text/plain");
|
|
||||||
|
|
||||||
Intent shareIntent = Intent.createChooser(sendIntent, "Share OpenPGP Fingerprint");
|
ikeyFingerprintShareButton.setOnClickListener(v -> {
|
||||||
startActivity(shareIntent);
|
Optional<OpenPgpV4Fingerprint> fingerprint = viewModel.getIkeyFingerprint().getValue();
|
||||||
|
if (fingerprint == null || !fingerprint.isPresent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startShareFingerprintIntent(fingerprint.getItem());
|
||||||
|
});
|
||||||
|
|
||||||
|
ikeyCreateBackupButton.setOnClickListener(v -> {
|
||||||
|
getParentFragmentManager().beginTransaction()
|
||||||
|
.replace(R.id.fragment, IkeyBackupCreationFragment.newInstance(accountId)).commit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startShareFingerprintIntent(OpenPgpV4Fingerprint fingerprint) {
|
||||||
|
Intent sendIntent = new Intent();
|
||||||
|
sendIntent.setAction(Intent.ACTION_SEND);
|
||||||
|
sendIntent.putExtra(Intent.EXTRA_TEXT, "openpgp4fpr:" + fingerprint);
|
||||||
|
sendIntent.setType("text/plain");
|
||||||
|
|
||||||
|
Intent shareIntent = Intent.createChooser(sendIntent, "Share OpenPGP Fingerprint");
|
||||||
|
startActivity(shareIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayIkeyFingerprint(Optional<OpenPgpV4Fingerprint> fingerprint) {
|
||||||
|
if (fingerprint.isPresent()) {
|
||||||
|
ikeyLayout.setVisibility(View.VISIBLE);
|
||||||
|
ikeyFingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(fingerprint.getItem()));
|
||||||
|
} else {
|
||||||
|
ikeyLayout.setVisibility(View.GONE);
|
||||||
|
ikeyFingerprint.setText(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayLocalOxFingerprint(OpenPgpV4Fingerprint fingerprint) {
|
||||||
|
if (fingerprint != null) {
|
||||||
|
localFingerprint.setText(OpenPgpV4FingerprintFormatter.formatOpenPgpV4Fingerprint(fingerprint));
|
||||||
|
} else {
|
||||||
|
localFingerprint.setText(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayOtherOxFingerprints(List<FingerprintViewItem> fingerprints) {
|
||||||
|
otherFingerprintsAdapter.setItems(fingerprints);
|
||||||
|
if (fingerprints.isEmpty()) {
|
||||||
|
otherFingerprintsLayout.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
otherFingerprintsLayout.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.jxmpp.jid.impl.JidCreate;
|
||||||
import org.mercury_im.messenger.android.MercuryImApplication;
|
import org.mercury_im.messenger.android.MercuryImApplication;
|
||||||
import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel;
|
import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel;
|
||||||
import org.mercury_im.messenger.core.SchedulersFacade;
|
import org.mercury_im.messenger.core.SchedulersFacade;
|
||||||
|
import org.mercury_im.messenger.core.util.DefaultUtil;
|
||||||
import org.mercury_im.messenger.core.util.Optional;
|
import org.mercury_im.messenger.core.util.Optional;
|
||||||
import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel;
|
import org.mercury_im.messenger.core.viewmodel.account.detail.AccountDetailsViewModel;
|
||||||
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
|
import org.mercury_im.messenger.core.viewmodel.openpgp.FingerprintViewItem;
|
||||||
|
@ -41,9 +42,10 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements
|
||||||
AccountDetailsViewModel commonViewModel;
|
AccountDetailsViewModel commonViewModel;
|
||||||
|
|
||||||
private final UUID accountId;
|
private final UUID accountId;
|
||||||
private MutableLiveData<OpenPgpV4Fingerprint> localFingerprint = new MutableLiveData<>(new OpenPgpV4Fingerprint("09858F60046289311743B90F3152226EB43287C5"));
|
private MutableLiveData<Optional<OpenPgpV4Fingerprint>> ikeyFingerprint = new MutableLiveData<>();
|
||||||
|
private MutableLiveData<OpenPgpV4Fingerprint> localFingerprint = new MutableLiveData<>();
|
||||||
private MutableLiveData<List<FingerprintViewItem>> remoteFingerprints = new MutableLiveData<>(new ArrayList<>());
|
private MutableLiveData<List<FingerprintViewItem>> remoteFingerprints = new MutableLiveData<>(new ArrayList<>());
|
||||||
private MutableLiveData<EntityBareJid> jid = new MutableLiveData<>(JidCreate.entityBareFromOrThrowUnchecked("placeholder@place.holder"));
|
private MutableLiveData<EntityBareJid> jid = new MutableLiveData<>(DefaultUtil.defaultJid());
|
||||||
|
|
||||||
public AndroidAccountDetailsViewModel(@NonNull Application application, UUID accountId) {
|
public AndroidAccountDetailsViewModel(@NonNull Application application, UUID accountId) {
|
||||||
super(application);
|
super(application);
|
||||||
|
@ -58,6 +60,11 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements
|
||||||
.map(Optional::getItem)
|
.map(Optional::getItem)
|
||||||
.subscribe(localFingerprint::postValue));
|
.subscribe(localFingerprint::postValue));
|
||||||
|
|
||||||
|
addDisposable(getCommonViewModel().observeIkeyFingerprint(accountId)
|
||||||
|
.compose(schedulers.executeUiSafeObservable())
|
||||||
|
.subscribe(ikeyFingerprint::postValue,
|
||||||
|
e -> LOGGER.log(Level.SEVERE, "Error displaying ikey fingerprint", e)));
|
||||||
|
|
||||||
addDisposable(getCommonViewModel().observeRemoteFingerprints(accountId)
|
addDisposable(getCommonViewModel().observeRemoteFingerprints(accountId)
|
||||||
.compose(schedulers.executeUiSafeObservable())
|
.compose(schedulers.executeUiSafeObservable())
|
||||||
.subscribe(list -> {
|
.subscribe(list -> {
|
||||||
|
@ -83,6 +90,10 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements
|
||||||
getCommonViewModel().markFingerprintTrusted(accountId, fingerprint, trusted);
|
getCommonViewModel().markFingerprintTrusted(accountId, fingerprint, trusted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<Optional<OpenPgpV4Fingerprint>> getIkeyFingerprint() {
|
||||||
|
return ikeyFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
public LiveData<OpenPgpV4Fingerprint> getLocalFingerprint() {
|
public LiveData<OpenPgpV4Fingerprint> getLocalFingerprint() {
|
||||||
return localFingerprint;
|
return localFingerprint;
|
||||||
}
|
}
|
||||||
|
@ -99,6 +110,30 @@ public class AndroidAccountDetailsViewModel extends AndroidViewModel implements
|
||||||
e -> LOGGER.log(Level.SEVERE, "Error unpublishing fingerprint " + fingerprint, e)));
|
e -> LOGGER.log(Level.SEVERE, "Error unpublishing fingerprint " + fingerprint, e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onGenerateIkey() {
|
||||||
|
addDisposable(getCommonViewModel().generateIkey(accountId)
|
||||||
|
.subscribeOn(schedulers.getNewThread())
|
||||||
|
.observeOn(schedulers.getUiScheduler())
|
||||||
|
.subscribe(() -> LOGGER.log(Level.INFO, "IKey generated for account " + accountId),
|
||||||
|
e -> LOGGER.log(Level.SEVERE, "Could not generate Ikey", e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onDeleteIkey() {
|
||||||
|
addDisposable(getCommonViewModel().deleteIkey(accountId)
|
||||||
|
.subscribeOn(schedulers.getNewThread())
|
||||||
|
.observeOn(schedulers.getUiScheduler())
|
||||||
|
.subscribe(() -> LOGGER.log(Level.INFO, "IKey deleted for account " + accountId),
|
||||||
|
e -> LOGGER.log(Level.SEVERE, "Could not delete Ikey", e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRestoreIkeyBackup() {
|
||||||
|
addDisposable(getCommonViewModel().restoreIkeyBackup(accountId)
|
||||||
|
.subscribeOn(schedulers.getNewThread())
|
||||||
|
.observeOn(schedulers.getUiScheduler())
|
||||||
|
.subscribe(() -> LOGGER.log(Level.INFO, "IKey restored for account " + accountId),
|
||||||
|
e -> LOGGER.log(Level.SEVERE, "Could not restore Ikey backup", e)));
|
||||||
|
}
|
||||||
|
|
||||||
public static class AndroidAccountDetailsViewModelFactory implements ViewModelProvider.Factory {
|
public static class AndroidAccountDetailsViewModelFactory implements ViewModelProvider.Factory {
|
||||||
|
|
||||||
private final Application application;
|
private final Application application;
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class ContactDetailActivity extends AppCompatActivity implements MercuryA
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindUiComponents() {
|
private void bindUiComponents() {
|
||||||
setContentView(R.layout.activity_fragment_container);
|
setContentView(R.layout.layout_top_toolbar);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.mercury_im.messenger.android.ui.ikey;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
||||||
|
import org.mercury_im.messenger.android.MercuryImApplication;
|
||||||
|
import org.mercury_im.messenger.android.ui.MercuryAndroidViewModel;
|
||||||
|
import org.mercury_im.messenger.android.util.QrCodeGenerator;
|
||||||
|
import org.mercury_im.messenger.core.SchedulersFacade;
|
||||||
|
import org.mercury_im.messenger.core.util.Optional;
|
||||||
|
import org.mercury_im.messenger.core.viewmodel.ikey.IkeySecretKeyBackupCreationViewModel;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
|
||||||
|
public class IkeyBackupCreationAndroidViewModel extends ViewModel implements MercuryAndroidViewModel<IkeySecretKeyBackupCreationViewModel> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(IkeyBackupCreationAndroidViewModel.class.getName());
|
||||||
|
|
||||||
|
MutableLiveData<OpenPgpSecretKeyBackupPassphrase> passphrase = new MutableLiveData<>();
|
||||||
|
MutableLiveData<Bitmap> passphraseAsQrCode = new MutableLiveData<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
IkeySecretKeyBackupCreationViewModel commonViewModel;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
SchedulersFacade schedulers;
|
||||||
|
|
||||||
|
public IkeyBackupCreationAndroidViewModel() {
|
||||||
|
MercuryImApplication.getApplication().getAppComponent().inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize(UUID accountId) {
|
||||||
|
getCommonViewModel().setAccountId(accountId);
|
||||||
|
|
||||||
|
Observable<Optional<OpenPgpSecretKeyBackupPassphrase>> passphraseObservable =
|
||||||
|
//getCommonViewModel().getPassphrase()
|
||||||
|
Observable.just(new Optional<>(new OpenPgpSecretKeyBackupPassphrase("71ZA-Y416-UA7A-7NCE-3SNM-88EF")));
|
||||||
|
|
||||||
|
addDisposable(passphraseObservable
|
||||||
|
.subscribeOn(schedulers.getIoScheduler())
|
||||||
|
.observeOn(schedulers.getUiScheduler())
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::getItem)
|
||||||
|
.subscribe(
|
||||||
|
passphrase::setValue,
|
||||||
|
e -> LOGGER.log(Level.SEVERE, "Error subscribing to passphrase", e)));
|
||||||
|
|
||||||
|
addDisposable(passphraseObservable
|
||||||
|
.filter(Optional::isPresent)
|
||||||
|
.map(Optional::getItem)
|
||||||
|
.map(pass -> QrCodeGenerator.generateBarcode(pass.toString()))
|
||||||
|
.subscribeOn(schedulers.getIoScheduler())
|
||||||
|
.observeOn(schedulers.getUiScheduler())
|
||||||
|
.subscribe(passphraseAsQrCode::setValue,
|
||||||
|
e -> LOGGER.log(Level.SEVERE, "Error subscribing to passphrase QR code", e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<OpenPgpSecretKeyBackupPassphrase> getPassphrase() {
|
||||||
|
return passphrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Bitmap> getPassphraseAsQrCode() {
|
||||||
|
return passphraseAsQrCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IkeySecretKeyBackupCreationViewModel getCommonViewModel() {
|
||||||
|
return commonViewModel;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package org.mercury_im.messenger.android.ui.ikey;
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.mercury_im.messenger.R;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
|
public class IkeyBackupCreationFragment extends Fragment {
|
||||||
|
|
||||||
|
private IkeyBackupCreationAndroidViewModel viewModel;
|
||||||
|
|
||||||
|
@BindView(R.id.backup_code)
|
||||||
|
TextView backupCode;
|
||||||
|
|
||||||
|
@BindView(R.id.qr_code)
|
||||||
|
ImageView qrCode;
|
||||||
|
|
||||||
|
private final UUID accountId;
|
||||||
|
|
||||||
|
private IkeyBackupCreationFragment(UUID accountId) {
|
||||||
|
this.accountId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IkeyBackupCreationFragment newInstance(UUID accountId) {
|
||||||
|
return new IkeyBackupCreationFragment(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_ikey_backup_creation, container, false);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
viewModel = new ViewModelProvider(this).get(IkeyBackupCreationAndroidViewModel.class);
|
||||||
|
viewModel.initialize(accountId);
|
||||||
|
|
||||||
|
viewModel.getPassphrase().observe(getViewLifecycleOwner(), passphrase -> backupCode.setText(passphrase));
|
||||||
|
viewModel.getPassphraseAsQrCode().observe(getViewLifecycleOwner(), bitmap -> qrCode.setImageBitmap(bitmap));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.mercury_im.messenger.android.ui.ikey;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import org.mercury_im.messenger.R;
|
||||||
|
|
||||||
|
public class IkeyBackupRestoreActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_ikey_backup_restore);
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ public class ToggleableFingerprintsAdapter extends RecyclerView.Adapter<Toggleab
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.view_fingerprint_toggleable, parent, false);
|
.inflate(R.layout.view_openpgp_4_fingerprint_toggleable, parent, false);
|
||||||
return new ViewHolder(view);
|
return new ViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.mercury_im.messenger.android.util;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.MultiFormatWriter;
|
||||||
|
import com.google.zxing.WriterException;
|
||||||
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
import com.journeyapps.barcodescanner.BarcodeEncoder;
|
||||||
|
|
||||||
|
public class QrCodeGenerator {
|
||||||
|
|
||||||
|
public static Bitmap generateBarcode(String content) throws WriterException {
|
||||||
|
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
|
||||||
|
BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE,200,200);
|
||||||
|
BarcodeEncoder barcodeEncoder = new BarcodeEncoder();
|
||||||
|
Bitmap bitmap = barcodeEncoder.createBitmap(bitMatrix);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
android:id="@+id/fragment"
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/fragment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/appbar_layout"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/bottom_navigation">
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".android.ui.ikey.IkeyBackupRestoreActivity">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notice"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="An iKey backup has been found!"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linearLayout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/notice">
|
||||||
|
|
||||||
|
<include
|
||||||
|
layout="@layout/view_openpgp_4_fingerprint"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:layout_editor_absoluteY="19dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="In order to restore the backup, please enter the backup code below."
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/linearLayout" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textView" >
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Backup Code"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/imageButton"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:layout_marginEnd="8dp"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/imageButton"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:padding="4dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:srcCompat="@drawable/ic_qr_code_scanner_black_24dp" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -14,31 +14,18 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
<include layout="@layout/view_toolbar_top" />
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
tools:menu="@menu/menu_main"/>
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<include layout="@layout/activity_fragment_container"
|
||||||
android:id="@+id/fragment"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/appbar_layout"
|
app:layout_constraintTop_toBottomOf="@id/appbar_layout"
|
||||||
app:layout_constraintBottom_toTopOf="@id/bottom_navigation">
|
app:layout_constraintBottom_toTopOf="@id/bottom_navigation" />
|
||||||
|
|
||||||
</FrameLayout>
|
<include layout="@layout/view_toolbar_bottom" />
|
||||||
|
|
||||||
|
|
||||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
|
||||||
android:id="@+id/bottom_navigation"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:menu="@menu/bottom_menu_main" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,120 +1,188 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<de.hdodenhof.circleimageview.CircleImageView
|
|
||||||
android:id="@+id/avatar"
|
|
||||||
android:layout_width="196dp"
|
|
||||||
android:layout_height="196dp"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:src="@drawable/aldrin"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/jid"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Title"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_bias="0.497"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/avatar"
|
|
||||||
tools:text="aldrin@mercury.im" />
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content">
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="12dp"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/jid">
|
|
||||||
|
|
||||||
<TextView
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.SearchResult.Title"
|
|
||||||
android:text="Encryption Keys" />
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="8dp"
|
android:orientation="vertical">
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
app:cardCornerRadius="4dp"
|
|
||||||
app:cardElevation="4dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
|
android:id="@+id/avatar"
|
||||||
|
android:layout_width="196dp"
|
||||||
|
android:layout_height="196dp"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:src="@drawable/aldrin"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/jid"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Title"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.497"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/avatar"
|
||||||
|
tools:text="aldrin@mercury.im" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingStart="12dp"
|
android:padding="12dp"
|
||||||
android:paddingEnd="12dp"
|
android:clipToPadding="false"
|
||||||
android:paddingTop="12dp">
|
app:layout_constraintTop_toBottomOf="@id/jid">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/title"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Local Fingerprint"
|
android:layout_gravity="center"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:textAppearance="@style/TextAppearance.AppCompat.SearchResult.Title"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
android:text="Encryption Keys" />
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
<include
|
android:id="@+id/layout_ikey"
|
||||||
android:id="@+id/local_fingerprint"
|
android:layout_width="match_parent"
|
||||||
layout="@layout/view_fingerprint"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintHorizontal_bias="0.5"
|
android:visibility="gone"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
tools:visibility="visible"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/title" />
|
app:cardCornerRadius="4dp"
|
||||||
|
app:cardElevation="4dp">
|
||||||
|
|
||||||
<Button
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/btn_backup"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title_ikey"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Account Identity Key"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/ikey_fingerprint"
|
||||||
|
layout="@layout/view_openpgp_4_fingerprint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/title_ikey" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_backup_ikey"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/btn_server_backup"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btn_share_ikey"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ikey_fingerprint" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_share_ikey"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="share"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/ikey_fingerprint" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:cardCornerRadius="4dp"
|
||||||
|
app:cardElevation="4dp">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Local Fingerprint"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/local_fingerprint"
|
||||||
|
layout="@layout/view_openpgp_4_fingerprint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/title" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_backup"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/btn_server_backup"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/btn_share"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/local_fingerprint" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_share"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="share"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/local_fingerprint" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<include layout="@layout/view_fingerprints_card_toggleable"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:id="@+id/other_fingerprints_card"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/btn_server_backup"
|
android:layout_width="match_parent"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/btn_share"
|
android:layout_marginTop="8dp"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/local_fingerprint" />
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
<Button
|
</LinearLayout>
|
||||||
android:id="@+id/btn_share"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="share"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/local_fingerprint" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<include layout="@layout/view_fingerprints_card_toggleable"
|
|
||||||
android:id="@+id/other_fingerprints_card"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".android.ui.ikey.IkeyBackupCreationFragment">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notice"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.SearchResult.Title"
|
||||||
|
android:text="Copy Identity Key to new Device"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="You should now write down the code below and store it somewhere safe.\nAlternatively you can scan the code below with another device to copy the identity key over."
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/notice" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/qr_code"
|
||||||
|
android:layout_width="200dp"
|
||||||
|
android:layout_height="200dp"
|
||||||
|
android:layout_marginTop="100dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.497"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/notice"
|
||||||
|
app:srcCompat="@drawable/ic_qr_code_scanner_black_24dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/backup_code"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat"
|
||||||
|
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/qr_code"
|
||||||
|
tools:text="TWNK-KD5Y-MT3T-E1GS-DRDB-KVTW" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".android.ui.MainActivity">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<include layout="@layout/view_toolbar_top" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/fragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/appbar_layout"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/bottom_navigation">
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<include layout="@layout/view_toolbar_bottom" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<Switch
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_centerVertical="true" />
|
||||||
|
</RelativeLayout>
|
|
@ -29,7 +29,7 @@
|
||||||
android:id="@+id/fingerprint_list"
|
android:id="@+id/fingerprint_list"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:listitem="@layout/view_fingerprint_toggleable"
|
tools:listitem="@layout/view_openpgp_4_fingerprint_toggleable"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:typeface="monospace"
|
android:typeface="monospace"
|
||||||
android:text="@string/test_fingerprint"
|
|
||||||
tools:text="@string/test_fingerprint" />
|
tools:text="@string/test_fingerprint" />
|
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/local_fingerprint"
|
android:id="@+id/local_fingerprint"
|
||||||
layout="@layout/view_fingerprint"
|
layout="@layout/view_openpgp_4_fingerprint"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<include
|
<include
|
||||||
android:id="@+id/fingerprint"
|
android:id="@+id/fingerprint"
|
||||||
layout="@layout/view_fingerprint"
|
layout="@layout/view_openpgp_4_fingerprint"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
|
@ -0,0 +1,9 @@
|
||||||
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/bottom_navigation"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:menu="@menu/bottom_menu_main" />
|
|
@ -0,0 +1,8 @@
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
tools:menu="@menu/menu_main" />
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/toggle_account_enabled"
|
||||||
|
android:title="Enable Account"
|
||||||
|
app:actionLayout="@layout/switch_item"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_generate_ikey"
|
||||||
|
android:title="Generate Identity Key"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_delete_ikey"
|
||||||
|
android:title="Delete Identity Key"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_restore_ikey_backup"
|
||||||
|
android:title="Restore Identity Key Backup"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
</menu>
|
|
@ -16,4 +16,5 @@
|
||||||
android:orderInCategory="100"
|
android:orderInCategory="100"
|
||||||
android:title="@string/action_settings"
|
android:title="@string/action_settings"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
|
|
@ -159,6 +159,6 @@
|
||||||
<string name="content_description_avatar_image">Avatar Image</string>
|
<string name="content_description_avatar_image">Avatar Image</string>
|
||||||
<string name="button_delete">Delete</string>
|
<string name="button_delete">Delete</string>
|
||||||
|
|
||||||
<string name="test_fingerprint">1357 B018 65B2 503C 1845\n3D20 8CAC 2A96 7854 8E35</string>
|
<string name="test_fingerprint">0123 4567 89AB CDEF 0123\n4567 89AB CDEF 0123 4567</string>
|
||||||
<string name="btn_server_backup">Create Server Backup</string>
|
<string name="btn_server_backup">Create Server Backup</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
package org.mercury_im.messenger;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
public class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
public void addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,18 +14,21 @@ dependencies {
|
||||||
|
|
||||||
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
compileOnly "org.projectlombok:lombok:$lombokVersion"
|
||||||
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
annotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||||
|
testAnnotationProcessor "org.projectlombok:lombok:$lombokVersion"
|
||||||
|
|
||||||
// RxJava2
|
// RxJava2
|
||||||
implementation "io.reactivex.rxjava2:rxjava:$rxJava2Version"
|
implementation "io.reactivex.rxjava2:rxjava:$rxJava2Version"
|
||||||
|
|
||||||
// Dagger 2 for dependency injection
|
// Dagger 2 for dependency injection
|
||||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||||
|
testImplementation "com.google.dagger:dagger:$daggerVersion"
|
||||||
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||||
|
|
||||||
// Requery ORM
|
// Requery ORM
|
||||||
api "io.requery:requery:$requeryVersion"
|
api "io.requery:requery:$requeryVersion"
|
||||||
annotationProcessor "io.requery:requery-processor:$requeryVersion"
|
annotationProcessor "io.requery:requery-processor:$requeryVersion"
|
||||||
|
testAnnotationProcessor "io.requery:requery-processor:$requeryVersion"
|
||||||
|
|
||||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,6 @@ import io.requery.converter.UUIDConverter;
|
||||||
public class AbstractIkeyRecordModel {
|
public class AbstractIkeyRecordModel {
|
||||||
|
|
||||||
@Key
|
@Key
|
||||||
@Generated
|
|
||||||
UUID id;
|
UUID id;
|
||||||
|
|
||||||
@Column(name = "account", unique = true)
|
@Column(name = "account", unique = true)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
||||||
import org.mercury_im.messenger.data.converter.Base64PGPSecretKeyRingConverter;
|
import org.mercury_im.messenger.data.converter.Base64PGPSecretKeyRingConverter;
|
||||||
import org.mercury_im.messenger.data.converter.OpenPGPSecretKeyBackupPassphraseConverter;
|
import org.mercury_im.messenger.data.converter.OpenPGPSecretKeyBackupPassphraseConverter;
|
||||||
|
import org.mercury_im.messenger.data.converter.OpenPgpV4FingerprintConverter;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -25,6 +26,7 @@ public class AbstractIkeySecretKeyModel {
|
||||||
UUID accountId;
|
UUID accountId;
|
||||||
|
|
||||||
@Column(name = "fingerprint")
|
@Column(name = "fingerprint")
|
||||||
|
@Convert(OpenPgpV4FingerprintConverter.class)
|
||||||
OpenPgpV4Fingerprint fingerprint;
|
OpenPgpV4Fingerprint fingerprint;
|
||||||
|
|
||||||
@Column(name = "backup_passphrase")
|
@Column(name = "backup_passphrase")
|
||||||
|
|
|
@ -17,7 +17,6 @@ import io.requery.converter.URIConverter;
|
||||||
public class AbstractIkeySubordinateModel {
|
public class AbstractIkeySubordinateModel {
|
||||||
|
|
||||||
@Key
|
@Key
|
||||||
@Generated
|
|
||||||
UUID id;
|
UUID id;
|
||||||
|
|
||||||
@Column(name = "type")
|
@Column(name = "type")
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.jivesoftware.smackx.ikey.mechanism.IkeyType;
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository;
|
import org.mercury_im.messenger.core.crypto.ikey.IkeyRepository;
|
||||||
|
import org.mercury_im.messenger.core.util.Optional;
|
||||||
import org.mercury_im.messenger.data.model.IkeyRecordModel;
|
import org.mercury_im.messenger.data.model.IkeyRecordModel;
|
||||||
import org.mercury_im.messenger.data.model.IkeySecretKeyModel;
|
import org.mercury_im.messenger.data.model.IkeySecretKeyModel;
|
||||||
import org.mercury_im.messenger.data.model.IkeySubordinateModel;
|
import org.mercury_im.messenger.data.model.IkeySubordinateModel;
|
||||||
|
@ -18,17 +19,23 @@ import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
import io.reactivex.Completable;
|
||||||
import io.reactivex.Maybe;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.requery.Persistable;
|
import io.requery.Persistable;
|
||||||
|
import io.requery.query.ResultDelegate;
|
||||||
import io.requery.reactivex.ReactiveEntityStore;
|
import io.requery.reactivex.ReactiveEntityStore;
|
||||||
|
|
||||||
public class RxIkeyRepository implements IkeyRepository {
|
public class RxIkeyRepository implements IkeyRepository {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(RxIkeyRepository.class.getName());
|
||||||
|
|
||||||
private final ReactiveEntityStore<Persistable> data;
|
private final ReactiveEntityStore<Persistable> data;
|
||||||
|
|
||||||
public RxIkeyRepository(ReactiveEntityStore<Persistable> data) {
|
public RxIkeyRepository(ReactiveEntityStore<Persistable> data) {
|
||||||
|
@ -36,12 +43,14 @@ public class RxIkeyRepository implements IkeyRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Maybe<PGPSecretKeyRing> loadSecretKey(UUID accountId) {
|
public Observable<Optional<PGPSecretKeyRing>> loadSecretKey(UUID accountId) {
|
||||||
return data.select(IkeySecretKeyModel.class)
|
return data.select(IkeySecretKeyModel.class)
|
||||||
.where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountId))
|
.where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountId))
|
||||||
.get()
|
.get()
|
||||||
.maybe()
|
.observableResult()
|
||||||
.map(IkeySecretKeyModel::getKey);
|
.map(ResultDelegate::toList)
|
||||||
|
.doOnNext(l -> LOGGER.log(Level.INFO, "new Result: " + Arrays.toString(l.toArray())))
|
||||||
|
.map(l -> l.isEmpty() ? new Optional<>() : new Optional<>(l.get(0).getKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,13 +81,14 @@ public class RxIkeyRepository implements IkeyRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Single<OpenPgpSecretKeyBackupPassphrase> loadBackupPassphrase(UUID accountID) {
|
public Single<Optional<OpenPgpSecretKeyBackupPassphrase>> loadBackupPassphrase(UUID accountID) {
|
||||||
return data.select(IkeySecretKeyModel.class)
|
return data.select(IkeySecretKeyModel.class)
|
||||||
.where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountID))
|
.where(IkeySecretKeyModel.ACCOUNT_ID.eq(accountID))
|
||||||
.get()
|
.get()
|
||||||
.observable()
|
.observable()
|
||||||
.singleOrError()
|
.map(IkeySecretKeyModel::getBackupPassphrase)
|
||||||
.map(IkeySecretKeyModel::getBackupPassphrase);
|
.map(Optional::new)
|
||||||
|
.single(new Optional<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -97,10 +107,10 @@ public class RxIkeyRepository implements IkeyRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Maybe<IkeyElement> loadRecord(UUID accountId, EntityBareJid jid) {
|
public Observable<IkeyElement> loadRecord(UUID accountId, EntityBareJid jid) {
|
||||||
return data.select(IkeyRecordModel.class)
|
return data.select(IkeyRecordModel.class)
|
||||||
.where(IkeyRecordModel.ACCOUNT_ID.eq(accountId).and(IkeyRecordModel.JID.eq(jid)))
|
.where(IkeyRecordModel.ACCOUNT_ID.eq(accountId).and(IkeyRecordModel.JID.eq(jid)))
|
||||||
.get().maybe()
|
.get().observable()
|
||||||
.map(m -> {
|
.map(m -> {
|
||||||
SuperordinateElement superordinateElement = new SuperordinateElement(m.getSuperordinate().getEncoded());
|
SuperordinateElement superordinateElement = new SuperordinateElement(m.getSuperordinate().getEncoded());
|
||||||
List<SubordinateElement> subList = new ArrayList<>();
|
List<SubordinateElement> subList = new ArrayList<>();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.mercury_im.messenger.data.di.component;
|
package org.mercury_im.messenger.data.di.component;
|
||||||
|
|
||||||
import org.mercury_im.messenger.data.di.RepositoryModule;
|
import org.mercury_im.messenger.data.di.RepositoryModule;
|
||||||
import org.mercury_im.messenger.data.di.module.TestDatabaseModule;
|
import org.mercury_im.messenger.data.di.module.SqliteTestDatabaseModule;
|
||||||
import org.mercury_im.messenger.data.di.module.TestingSchedulerModule;
|
import org.mercury_im.messenger.data.di.module.TestingSchedulerModule;
|
||||||
import org.mercury_im.messenger.data.repository.AccountRepositoryTest;
|
import org.mercury_im.messenger.data.repository.AccountRepositoryTest;
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import dagger.Component;
|
||||||
|
|
||||||
@Component(modules = {
|
@Component(modules = {
|
||||||
RepositoryModule.class,
|
RepositoryModule.class,
|
||||||
TestDatabaseModule.class,
|
SqliteTestDatabaseModule.class,
|
||||||
TestingSchedulerModule.class
|
TestingSchedulerModule.class
|
||||||
})
|
})
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
|
@ -23,7 +23,7 @@ import io.requery.sql.SchemaModifier;
|
||||||
import io.requery.sql.TableCreationMode;
|
import io.requery.sql.TableCreationMode;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class TestDatabaseModule {
|
public class SqliteTestDatabaseModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
|
@ -1,9 +1,9 @@
|
||||||
apply plugin: 'java-library'
|
apply plugin: 'java-library'
|
||||||
|
|
||||||
sourceSets {
|
//sourceSets {
|
||||||
//main.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/main/"
|
//main.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/main/"
|
||||||
test.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/test/"
|
// test.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/test/"
|
||||||
}
|
//}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.jivesoftware.smackx.ikey;
|
package org.jivesoftware.smackx.ikey;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.jivesoftware.smack.Manager;
|
import org.jivesoftware.smack.Manager;
|
||||||
import org.jivesoftware.smack.SmackException;
|
import org.jivesoftware.smack.SmackException;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
@ -10,6 +12,7 @@ import org.jivesoftware.smack.provider.ProviderManager;
|
||||||
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
||||||
import org.jivesoftware.smackx.ikey.element.ProofElement;
|
import org.jivesoftware.smackx.ikey.element.ProofElement;
|
||||||
import org.jivesoftware.smackx.ikey.element.SignedElement;
|
import org.jivesoftware.smackx.ikey.element.SignedElement;
|
||||||
|
import org.jivesoftware.smackx.ikey.element.SubordinateElement;
|
||||||
import org.jivesoftware.smackx.ikey.element.SubordinateListElement;
|
import org.jivesoftware.smackx.ikey.element.SubordinateListElement;
|
||||||
import org.jivesoftware.smackx.ikey.element.SuperordinateElement;
|
import org.jivesoftware.smackx.ikey.element.SuperordinateElement;
|
||||||
import org.jivesoftware.smackx.ikey.mechanism.IkeySignatureCreationMechanism;
|
import org.jivesoftware.smackx.ikey.mechanism.IkeySignatureCreationMechanism;
|
||||||
|
@ -19,7 +22,13 @@ import org.jivesoftware.smackx.ikey.provider.SubordinateListElementProvider;
|
||||||
import org.jivesoftware.smackx.ikey.record.IkeyStore;
|
import org.jivesoftware.smackx.ikey.record.IkeyStore;
|
||||||
import org.jivesoftware.smackx.ikey.util.IkeyConstants;
|
import org.jivesoftware.smackx.ikey.util.IkeyConstants;
|
||||||
import org.jivesoftware.smackx.ikey.util.UnsupportedSignatureAlgorithmException;
|
import org.jivesoftware.smackx.ikey.util.UnsupportedSignatureAlgorithmException;
|
||||||
|
import org.jivesoftware.smackx.ikey_ox.OxIkeySignatureCreationMechanism;
|
||||||
import org.jivesoftware.smackx.ikey_ox.OxIkeySignatureVerificationMechanism;
|
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.util.OpenPgpPubSubUtil;
|
||||||
|
import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper;
|
||||||
import org.jivesoftware.smackx.pep.PepEventListener;
|
import org.jivesoftware.smackx.pep.PepEventListener;
|
||||||
import org.jivesoftware.smackx.pep.PepManager;
|
import org.jivesoftware.smackx.pep.PepManager;
|
||||||
import org.jivesoftware.smackx.pubsub.LeafNode;
|
import org.jivesoftware.smackx.pubsub.LeafNode;
|
||||||
|
@ -27,9 +36,17 @@ import org.jivesoftware.smackx.pubsub.PayloadItem;
|
||||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
import org.jivesoftware.smackx.pubsub.PubSubException;
|
||||||
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
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.PGPainless;
|
||||||
|
import org.pgpainless.key.collection.PGPKeyRing;
|
||||||
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.NoSuchProviderException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -37,11 +54,15 @@ import java.util.WeakHashMap;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import static org.jivesoftware.smackx.ikey.util.IkeyConstants.SUPERORDINATE_NODE;
|
||||||
|
|
||||||
public final class IkeyManager extends Manager {
|
public final class IkeyManager extends Manager {
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(IkeyManager.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(IkeyManager.class.getName());
|
||||||
private static final Map<XMPPConnection, IkeyManager> INSTANCES = new WeakHashMap<>();
|
private static final Map<XMPPConnection, IkeyManager> INSTANCES = new WeakHashMap<>();
|
||||||
|
|
||||||
|
private final OpenPgpSecretKeyBackupPassphraseGenerator backupPassphraseGenerator;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// TODO: Replace with .providers file once merged into Smack
|
// TODO: Replace with .providers file once merged into Smack
|
||||||
ProviderManager.addExtensionProvider(IkeyElement.ELEMENT, IkeyElement.NAMESPACE, new IkeyElementProvider());
|
ProviderManager.addExtensionProvider(IkeyElement.ELEMENT, IkeyElement.NAMESPACE, new IkeyElementProvider());
|
||||||
|
@ -52,6 +73,7 @@ public final class IkeyManager extends Manager {
|
||||||
|
|
||||||
private IkeyManager(XMPPConnection connection) {
|
private IkeyManager(XMPPConnection connection) {
|
||||||
super(connection);
|
super(connection);
|
||||||
|
this.backupPassphraseGenerator = new SecureRandomSecretKeyBackupPassphraseGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized IkeyManager getInstanceFor(XMPPConnection connection) {
|
public static synchronized IkeyManager getInstanceFor(XMPPConnection connection) {
|
||||||
|
@ -63,6 +85,49 @@ public final class IkeyManager extends Manager {
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SecretkeyElement fetchSecretIdentityKey()
|
||||||
|
throws InterruptedException, PubSubException.NotALeafNodeException,
|
||||||
|
XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
||||||
|
SmackException.NoResponseException {
|
||||||
|
return OpenPgpPubSubUtil.fetchSecretKey(PepManager.getInstanceFor(connection()), SUPERORDINATE_NODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenPgpSecretKeyBackupPassphrase depositIdentityKeyBackup()
|
||||||
|
throws PGPException, InterruptedException, SmackException.NoResponseException,
|
||||||
|
SmackException.NotConnectedException, SmackException.FeatureNotSupportedException,
|
||||||
|
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, IOException {
|
||||||
|
PGPSecretKeyRing secretKeys = store.loadSecretKey();
|
||||||
|
OpenPgpSecretKeyBackupPassphrase passphrase = store.loadBackupPassphrase();
|
||||||
|
|
||||||
|
return depositIdentityKeyBackup(secretKeys, passphrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenPgpSecretKeyBackupPassphrase depositIdentityKeyBackup(PGPSecretKeyRing secretKey, OpenPgpSecretKeyBackupPassphrase passphrase)
|
||||||
|
throws InterruptedException, SmackException.NoResponseException,
|
||||||
|
SmackException.NotConnectedException, SmackException.FeatureNotSupportedException,
|
||||||
|
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, IOException, PGPException {
|
||||||
|
SecretkeyElement secretkeyElement = SecretKeyBackupHelper.createSecretkeyElement(secretKey.getEncoded(), passphrase);
|
||||||
|
OpenPgpPubSubUtil.depositSecretKey(connection(), secretkeyElement, SUPERORDINATE_NODE);
|
||||||
|
|
||||||
|
return passphrase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IkeyElement createOxIkeyElement(PGPSecretKeyRing secretKeys,
|
||||||
|
SecretKeyRingProtector keyRingProtector,
|
||||||
|
SubordinateElement... subordinateElements) throws IOException {
|
||||||
|
IkeySignatureCreationMechanism mechanism = new OxIkeySignatureCreationMechanism(secretKeys, keyRingProtector);
|
||||||
|
SuperordinateElement superordinateElement = new SuperordinateElement(secretKeys.getPublicKey().getEncoded());
|
||||||
|
SubordinateListElement subordinateListElement = new SubordinateListElement(connection().getUser().asEntityBareJid(),
|
||||||
|
new Date(), Arrays.asList(subordinateElements));
|
||||||
|
return createIkeyElement(mechanism, superordinateElement, subordinateListElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean deleteSecretIdentityKeyNode()
|
||||||
|
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
||||||
|
InterruptedException, SmackException.NoResponseException {
|
||||||
|
return OpenPgpPubSubUtil.deleteSecretKeyNode(PepManager.getInstanceFor(connection()), SUPERORDINATE_NODE);
|
||||||
|
}
|
||||||
|
|
||||||
public void startListeners() {
|
public void startListeners() {
|
||||||
PepManager.getInstanceFor(connection())
|
PepManager.getInstanceFor(connection())
|
||||||
.addPepEventListener(IkeyConstants.SUBORDINATES_NODE, IkeyElement.class, ikeyPepEventListener);
|
.addPepEventListener(IkeyConstants.SUBORDINATES_NODE, IkeyElement.class, ikeyPepEventListener);
|
||||||
|
@ -163,10 +228,9 @@ public final class IkeyManager extends Manager {
|
||||||
PGPPublicKeyRing ikey = PGPainless.readKeyRing().publicKeyRing(ikeyElement.getSuperordinate().getPubKeyBytes());
|
PGPPublicKeyRing ikey = PGPainless.readKeyRing().publicKeyRing(ikeyElement.getSuperordinate().getPubKeyBytes());
|
||||||
return new OxIkeySignatureVerificationMechanism(ikey);
|
return new OxIkeySignatureVerificationMechanism(ikey);
|
||||||
|
|
||||||
case X509:
|
default:
|
||||||
throw new UnsupportedSignatureAlgorithmException("X.509");
|
throw new UnsupportedSignatureAlgorithmException(ikeyElement.getType().name());
|
||||||
}
|
}
|
||||||
throw new AssertionError("Unknown verification algorithm encountered: " + ikeyElement.getType());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isFromTheFuture(IkeyElement element) {
|
private static boolean isFromTheFuture(IkeyElement element) {
|
||||||
|
@ -197,4 +261,23 @@ public final class IkeyManager extends Manager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public boolean hasStore() {
|
||||||
|
return store != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasLocalKey() {
|
||||||
|
return store.loadSecretKey() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void generateIdentityKey() throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
|
||||||
|
PGPKeyRing key = OpenPgpManager.getInstanceFor(connection())
|
||||||
|
.generateKeyRing(connection().getUser().asBareJid());
|
||||||
|
store.storeSecretKey(key.getSecretKeys());
|
||||||
|
store.storeBackupPassphrase(generateBackupPassphrase());
|
||||||
|
}
|
||||||
|
|
||||||
|
private OpenPgpSecretKeyBackupPassphrase generateBackupPassphrase() {
|
||||||
|
return backupPassphraseGenerator.generateBackupPassphrase();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package org.jivesoftware.smackx.ikey.record;
|
package org.jivesoftware.smackx.ikey.record;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.jivesoftware.smack.parsing.SmackParsingException;
|
import org.jivesoftware.smack.parsing.SmackParsingException;
|
||||||
import org.jivesoftware.smack.util.Objects;
|
import org.jivesoftware.smack.util.Objects;
|
||||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||||
import org.jivesoftware.smack.xml.XmlPullParserException;
|
import org.jivesoftware.smack.xml.XmlPullParserException;
|
||||||
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
||||||
import org.jivesoftware.smackx.ikey.provider.IkeyElementProvider;
|
import org.jivesoftware.smackx.ikey.provider.IkeyElementProvider;
|
||||||
|
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
@ -49,6 +51,26 @@ public class FileBasedIkeyStore implements IkeyStore {
|
||||||
writeToFile(new FileOutputStream(file), record.toXML().toString());
|
writeToFile(new FileOutputStream(file), record.toXML().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PGPSecretKeyRing loadSecretKey() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeSecretKey(PGPSecretKeyRing secretKey) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpenPgpSecretKeyBackupPassphrase loadBackupPassphrase() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeBackupPassphrase(OpenPgpSecretKeyBackupPassphrase passphrase) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private static String getFileContent(FileInputStream fis) throws IOException {
|
private static String getFileContent(FileInputStream fis) throws IOException {
|
||||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8))) {
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8))) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.jivesoftware.smackx.ikey.record;
|
package org.jivesoftware.smackx.ikey.record;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
||||||
|
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -10,4 +12,12 @@ public interface IkeyStore {
|
||||||
IkeyElement loadIkeyRecord(EntityBareJid jid) throws IOException;
|
IkeyElement loadIkeyRecord(EntityBareJid jid) throws IOException;
|
||||||
|
|
||||||
void storeIkeyRecord(EntityBareJid jid, IkeyElement record) throws IOException;
|
void storeIkeyRecord(EntityBareJid jid, IkeyElement record) throws IOException;
|
||||||
|
|
||||||
|
PGPSecretKeyRing loadSecretKey();
|
||||||
|
|
||||||
|
void storeSecretKey(PGPSecretKeyRing secretKey);
|
||||||
|
|
||||||
|
OpenPgpSecretKeyBackupPassphrase loadBackupPassphrase();
|
||||||
|
|
||||||
|
void storeBackupPassphrase(OpenPgpSecretKeyBackupPassphrase passphrase);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
package org.jivesoftware.smackx.ikey_ox;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
||||||
import org.jivesoftware.smack.Manager;
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
|
||||||
import org.jivesoftware.smack.XMPPException;
|
|
||||||
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.element.SubordinateListElement;
|
|
||||||
import org.jivesoftware.smackx.ikey.element.SuperordinateElement;
|
|
||||||
import org.jivesoftware.smackx.ikey.mechanism.IkeySignatureCreationMechanism;
|
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
|
||||||
import org.jivesoftware.smackx.ox.element.SecretkeyElement;
|
|
||||||
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
|
|
||||||
import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper;
|
|
||||||
import org.jivesoftware.smackx.pep.PepManager;
|
|
||||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
|
||||||
import org.mercury_im.messenger.core.crypto.OpenPgpSecretKeyBackupPassphraseGenerator;
|
|
||||||
import org.mercury_im.messenger.core.crypto.SecureRandomSecretKeyBackupPassphraseGenerator;
|
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
import static org.jivesoftware.smackx.ikey.util.IkeyConstants.SUPERORDINATE_NODE;
|
|
||||||
|
|
||||||
public final class OxIkeyManager extends Manager {
|
|
||||||
|
|
||||||
private static final Map<XMPPConnection, OxIkeyManager> INSTANCES = new WeakHashMap<>();
|
|
||||||
|
|
||||||
private final IkeyManager ikeyManager;
|
|
||||||
|
|
||||||
private OxIkeyManager(XMPPConnection connection) {
|
|
||||||
super(connection);
|
|
||||||
this.ikeyManager = IkeyManager.getInstanceFor(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static OxIkeyManager getInstanceFor(XMPPConnection connection) {
|
|
||||||
OxIkeyManager manager = INSTANCES.get(connection);
|
|
||||||
if (manager == null) {
|
|
||||||
manager = new OxIkeyManager(connection);
|
|
||||||
INSTANCES.put(connection, manager);
|
|
||||||
}
|
|
||||||
return manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SecretkeyElement fetchSecretIdentityKey()
|
|
||||||
throws InterruptedException, PubSubException.NotALeafNodeException,
|
|
||||||
XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
|
||||||
SmackException.NoResponseException {
|
|
||||||
return OpenPgpPubSubUtil.fetchSecretKey(PepManager.getInstanceFor(connection()), SUPERORDINATE_NODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenPgpSecretKeyBackupPassphrase depositSecretIdentityKey(PGPSecretKeyRing secretKey)
|
|
||||||
throws PGPException, InterruptedException, SmackException.NoResponseException,
|
|
||||||
SmackException.NotConnectedException, SmackException.FeatureNotSupportedException,
|
|
||||||
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, IOException {
|
|
||||||
OpenPgpSecretKeyBackupPassphraseGenerator passphraseGenerator = new SecureRandomSecretKeyBackupPassphraseGenerator();
|
|
||||||
OpenPgpSecretKeyBackupPassphrase passphrase = passphraseGenerator.generateBackupPassphrase();
|
|
||||||
|
|
||||||
depositSecretIdentityKey(secretKey, passphrase);
|
|
||||||
|
|
||||||
return passphrase;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void depositSecretIdentityKey(PGPSecretKeyRing secretKey, OpenPgpSecretKeyBackupPassphrase passphrase)
|
|
||||||
throws InterruptedException, SmackException.NoResponseException,
|
|
||||||
SmackException.NotConnectedException, SmackException.FeatureNotSupportedException,
|
|
||||||
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, IOException, PGPException {
|
|
||||||
SecretkeyElement secretkeyElement = SecretKeyBackupHelper.createSecretkeyElement(secretKey.getEncoded(), passphrase);
|
|
||||||
OpenPgpPubSubUtil.depositSecretKey(connection(), secretkeyElement, SUPERORDINATE_NODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IkeyElement createOxIkeyElement(PGPSecretKeyRing secretKeys,
|
|
||||||
SecretKeyRingProtector keyRingProtector,
|
|
||||||
SubordinateElement... subordinateElements) throws IOException {
|
|
||||||
IkeySignatureCreationMechanism mechanism = new OxIkeySignatureCreationMechanism(secretKeys, keyRingProtector);
|
|
||||||
SuperordinateElement superordinateElement = new SuperordinateElement(secretKeys.getPublicKey().getEncoded());
|
|
||||||
SubordinateListElement subordinateListElement = new SubordinateListElement(connection().getUser().asEntityBareJid(),
|
|
||||||
new Date(), Arrays.asList(subordinateElements));
|
|
||||||
return ikeyManager.createIkeyElement(mechanism, superordinateElement, subordinateListElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean deleteSecretIdentityKeyNode()
|
|
||||||
throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
|
|
||||||
InterruptedException, SmackException.NoResponseException {
|
|
||||||
return OpenPgpPubSubUtil.deleteSecretKeyNode(PepManager.getInstanceFor(connection()), SUPERORDINATE_NODE);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package org.jivesoftware.smackx.ikey_ox;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
|
||||||
|
|
||||||
public interface OxIkeyStore {
|
|
||||||
|
|
||||||
PGPSecretKeyRing loadSecretKey();
|
|
||||||
|
|
||||||
void storeSecretKey(PGPSecretKeyRing secretKey);
|
|
||||||
|
|
||||||
OpenPgpSecretKeyBackupPassphrase loadBackupPassphrase();
|
|
||||||
|
|
||||||
void storeBackupPassphrase(OpenPgpSecretKeyBackupPassphrase passphrase);
|
|
||||||
}
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.mercury_im.messenger.core.crypto.ikey;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.ikey.IkeyManager;
|
||||||
|
import org.mercury_im.messenger.core.SchedulersFacade;
|
||||||
|
import org.mercury_im.messenger.core.connection.MercuryConnection;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
public class IkeyInitializer {
|
||||||
|
|
||||||
|
private final IkeyRepository ikeyRepository;
|
||||||
|
private final SchedulersFacade schedulers;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public IkeyInitializer(IkeyRepository ikeyRepository, SchedulersFacade schedulers) {
|
||||||
|
this.ikeyRepository = ikeyRepository;
|
||||||
|
this.schedulers = schedulers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IkeyManager initFor(MercuryConnection connection) {
|
||||||
|
IkeyManager ikeyManager = IkeyManager.getInstanceFor(connection.getConnection());
|
||||||
|
if (ikeyManager.hasStore()) {
|
||||||
|
return ikeyManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind repo to store
|
||||||
|
IkeyStoreAdapter store = new IkeyStoreAdapter(connection.getAccountId(), ikeyRepository, schedulers);
|
||||||
|
ikeyManager.setStore(store);
|
||||||
|
|
||||||
|
return ikeyManager;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,19 +3,18 @@ package org.mercury_im.messenger.core.crypto.ikey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
import org.jivesoftware.smackx.ikey.element.IkeyElement;
|
||||||
import org.jivesoftware.smackx.ikey.record.IkeyStore;
|
import org.jivesoftware.smackx.ikey.record.IkeyStore;
|
||||||
import org.jivesoftware.smackx.ikey_ox.OxIkeyStore;
|
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
import org.mercury_im.messenger.core.SchedulersFacade;
|
import org.mercury_im.messenger.core.SchedulersFacade;
|
||||||
|
import org.mercury_im.messenger.core.util.Optional;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter that serves as a {@link IkeyStore} and {@link OxIkeyStore} implementation
|
* Adapter that serves as a {@link IkeyStore} implementation wrapping a {@link IkeyRepository}.
|
||||||
* wrapping a {@link IkeyRepository}.
|
|
||||||
*/
|
*/
|
||||||
public class IkeyStoreAdapter implements IkeyStore, OxIkeyStore {
|
public class IkeyStoreAdapter implements IkeyStore {
|
||||||
|
|
||||||
private final UUID accountId;
|
private final UUID accountId;
|
||||||
private final IkeyRepository repository;
|
private final IkeyRepository repository;
|
||||||
|
@ -30,7 +29,8 @@ public class IkeyStoreAdapter implements IkeyStore, OxIkeyStore {
|
||||||
@Override
|
@Override
|
||||||
public IkeyElement loadIkeyRecord(EntityBareJid jid) throws IOException {
|
public IkeyElement loadIkeyRecord(EntityBareJid jid) throws IOException {
|
||||||
return repository.loadRecord(accountId, jid)
|
return repository.loadRecord(accountId, jid)
|
||||||
.compose(schedulers.executeUiSafeMaybe())
|
.compose(schedulers.executeUiSafeObservable())
|
||||||
|
.firstElement()
|
||||||
.blockingGet();
|
.blockingGet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +44,10 @@ public class IkeyStoreAdapter implements IkeyStore, OxIkeyStore {
|
||||||
@Override
|
@Override
|
||||||
public PGPSecretKeyRing loadSecretKey() {
|
public PGPSecretKeyRing loadSecretKey() {
|
||||||
return repository.loadSecretKey(accountId)
|
return repository.loadSecretKey(accountId)
|
||||||
.compose(schedulers.executeUiSafeMaybe())
|
.compose(schedulers.executeUiSafeObservable())
|
||||||
.blockingGet();
|
.firstElement()
|
||||||
|
.blockingGet()
|
||||||
|
.getItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -57,9 +59,10 @@ public class IkeyStoreAdapter implements IkeyStore, OxIkeyStore {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OpenPgpSecretKeyBackupPassphrase loadBackupPassphrase() {
|
public OpenPgpSecretKeyBackupPassphrase loadBackupPassphrase() {
|
||||||
return repository.loadBackupPassphrase(accountId)
|
Optional<OpenPgpSecretKeyBackupPassphrase> passphrase = repository.loadBackupPassphrase(accountId)
|
||||||
.compose(schedulers.executeUiSafeSingle())
|
.compose(schedulers.executeUiSafeSingle())
|
||||||
.blockingGet();
|
.blockingGet();
|
||||||
|
return passphrase.isPresent() ? passphrase.getItem() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
package org.mercury_im.messenger.core.crypto.ikey;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
||||||
import org.jivesoftware.smack.SmackException;
|
|
||||||
import org.jivesoftware.smack.XMPPException;
|
|
||||||
import org.jivesoftware.smackx.ikey.IkeyManager;
|
|
||||||
import org.jivesoftware.smackx.ikey_ox.OxIkeyManager;
|
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
|
||||||
import org.jivesoftware.smackx.pubsub.PubSubException;
|
|
||||||
import org.mercury_im.messenger.core.SchedulersFacade;
|
|
||||||
import org.mercury_im.messenger.core.connection.MercuryConnection;
|
|
||||||
import org.pgpainless.PGPainless;
|
|
||||||
import org.pgpainless.key.collection.PGPKeyRing;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
public class MercuryIkeyManager {
|
|
||||||
|
|
||||||
private final IkeyRepository ikeyRepository;
|
|
||||||
private final SchedulersFacade schedulers;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public MercuryIkeyManager(IkeyRepository ikeyRepository, SchedulersFacade schedulers) {
|
|
||||||
this.ikeyRepository = ikeyRepository;
|
|
||||||
this.schedulers = schedulers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initFor(MercuryConnection connection)
|
|
||||||
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException,
|
|
||||||
InterruptedException, SmackException.NoResponseException,
|
|
||||||
SmackException.NotConnectedException, SmackException.FeatureNotSupportedException,
|
|
||||||
XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, IOException {
|
|
||||||
IkeyManager ikeyManager = IkeyManager.getInstanceFor(connection.getConnection());
|
|
||||||
|
|
||||||
// bind repo to store
|
|
||||||
IkeyStoreAdapter store = new IkeyStoreAdapter(connection.getAccountId(), ikeyRepository, schedulers);
|
|
||||||
ikeyManager.setStore(store);
|
|
||||||
|
|
||||||
PGPSecretKeyRing ikey = store.loadSecretKey();
|
|
||||||
if (ikey == null) {
|
|
||||||
PGPKeyRing keyRing = PGPainless.generateKeyRing().simpleEcKeyRing("xmpp:" + connection.getAccount().getAddress());
|
|
||||||
store.storeSecretKey(keyRing.getSecretKeys());
|
|
||||||
}
|
|
||||||
|
|
||||||
OxIkeyManager oxIkeyManager = OxIkeyManager.getInstanceFor(connection.getConnection());
|
|
||||||
OpenPgpSecretKeyBackupPassphrase passphrase = oxIkeyManager.depositSecretIdentityKey(ikey);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +1,24 @@
|
||||||
package org.mercury_im.messenger.core.data.repository;
|
package org.mercury_im.messenger.core.data.repository;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.mercury_im.messenger.core.util.Optional;
|
||||||
import org.mercury_im.messenger.entity.Account;
|
import org.mercury_im.messenger.entity.Account;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
import io.reactivex.Completable;
|
||||||
import io.reactivex.Maybe;
|
import io.reactivex.Maybe;
|
||||||
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
|
||||||
public interface IkeyKeyRepository {
|
public interface IkeyKeyRepository {
|
||||||
|
|
||||||
default Maybe<PGPSecretKeyRing> loadSecretKey(Account account) {
|
default Observable<Optional<PGPSecretKeyRing>> loadSecretKey(Account account) {
|
||||||
return loadSecretKey(account.getId());
|
return loadSecretKey(account.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
Maybe<PGPSecretKeyRing> loadSecretKey(UUID accountId);
|
Observable<Optional<PGPSecretKeyRing>> loadSecretKey(UUID accountId);
|
||||||
|
|
||||||
default Completable storeSecretKey(Account account, PGPSecretKeyRing secretKey) {
|
default Completable storeSecretKey(Account account, PGPSecretKeyRing secretKey) {
|
||||||
return storeSecretKey(account.getId(), secretKey);
|
return storeSecretKey(account.getId(), secretKey);
|
||||||
|
@ -32,11 +32,11 @@ public interface IkeyKeyRepository {
|
||||||
|
|
||||||
Single<Integer> deleteSecretKey(UUID accountId);
|
Single<Integer> deleteSecretKey(UUID accountId);
|
||||||
|
|
||||||
default Single<OpenPgpSecretKeyBackupPassphrase> loadBackupPassphrase(Account account) {
|
default Single<Optional<OpenPgpSecretKeyBackupPassphrase>> loadBackupPassphrase(Account account) {
|
||||||
return loadBackupPassphrase(account.getId());
|
return loadBackupPassphrase(account.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
Single<OpenPgpSecretKeyBackupPassphrase> loadBackupPassphrase(UUID accountID);
|
Single<Optional<OpenPgpSecretKeyBackupPassphrase>> loadBackupPassphrase(UUID accountID);
|
||||||
|
|
||||||
default Completable storeBackupPassphrase(Account account, OpenPgpSecretKeyBackupPassphrase passphrase) {
|
default Completable storeBackupPassphrase(Account account, OpenPgpSecretKeyBackupPassphrase passphrase) {
|
||||||
return storeBackupPassphrase(account.getId(), passphrase);
|
return storeBackupPassphrase(account.getId(), passphrase);
|
||||||
|
|
|
@ -8,14 +8,15 @@ import java.util.UUID;
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
import io.reactivex.Completable;
|
||||||
import io.reactivex.Maybe;
|
import io.reactivex.Maybe;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
|
||||||
public interface IkeyRecordRepository {
|
public interface IkeyRecordRepository {
|
||||||
|
|
||||||
default Maybe<IkeyElement> loadRecord(Account account, EntityBareJid jid) {
|
default Observable<IkeyElement> loadRecord(Account account, EntityBareJid jid) {
|
||||||
return loadRecord(account.getId(), jid);
|
return loadRecord(account.getId(), jid);
|
||||||
}
|
}
|
||||||
|
|
||||||
Maybe<IkeyElement> loadRecord(UUID accountId, EntityBareJid jid);
|
Observable<IkeyElement> loadRecord(UUID accountId, EntityBareJid jid);
|
||||||
|
|
||||||
default Completable storeRecord(Account account, EntityBareJid jid, IkeyElement record) {
|
default Completable storeRecord(Account account, EntityBareJid jid, IkeyElement record) {
|
||||||
return storeRecord(account.getId(), jid, record);
|
return storeRecord(account.getId(), jid, record);
|
||||||
|
|
|
@ -3,6 +3,8 @@ package org.mercury_im.messenger.core.di.module;
|
||||||
import org.mercury_im.messenger.core.Messenger;
|
import org.mercury_im.messenger.core.Messenger;
|
||||||
import org.mercury_im.messenger.core.SchedulersFacade;
|
import org.mercury_im.messenger.core.SchedulersFacade;
|
||||||
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
|
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
|
||||||
|
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.AccountRepository;
|
||||||
import org.mercury_im.messenger.core.data.repository.DirectChatRepository;
|
import org.mercury_im.messenger.core.data.repository.DirectChatRepository;
|
||||||
import org.mercury_im.messenger.core.data.repository.MessageRepository;
|
import org.mercury_im.messenger.core.data.repository.MessageRepository;
|
||||||
|
@ -43,9 +45,11 @@ public class ViewModelModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
static AccountDetailsViewModel provideAccountDetailsViewModel(MercuryConnectionManager connectionManager,
|
static AccountDetailsViewModel provideAccountDetailsViewModel(MercuryConnectionManager connectionManager,
|
||||||
OpenPgpRepository openPgpRepository,
|
OpenPgpRepository openPgpRepository,
|
||||||
|
IkeyRepository ikeyRepository,
|
||||||
AccountRepository accountRepository,
|
AccountRepository accountRepository,
|
||||||
SchedulersFacade schedulers) {
|
SchedulersFacade schedulers,
|
||||||
return new AccountDetailsViewModel(connectionManager, openPgpRepository, accountRepository, schedulers);
|
IkeyInitializer ikeyInitializer) {
|
||||||
|
return new AccountDetailsViewModel(connectionManager, openPgpRepository, ikeyRepository, accountRepository, schedulers, ikeyInitializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.mercury_im.messenger.core.util;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
|
import org.jxmpp.jid.impl.JidCreate;
|
||||||
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
|
|
||||||
|
public class DefaultUtil {
|
||||||
|
|
||||||
|
public static OpenPgpV4Fingerprint defaultFingerprint() {
|
||||||
|
return new OpenPgpV4Fingerprint("0123456789ABCDEF0123456789ABCDEF01234567");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntityBareJid defaultJid() {
|
||||||
|
return JidCreate.entityBareFromOrThrowUnchecked("placeholder@example.org");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
package org.mercury_im.messenger.core.viewmodel.account.detail;
|
package org.mercury_im.messenger.core.viewmodel.account.detail;
|
||||||
|
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
import org.jivesoftware.smackx.ikey.IkeyManager;
|
||||||
import org.jivesoftware.smackx.ox.element.PublicKeysListElement;
|
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.store.definition.OpenPgpTrustStore;
|
||||||
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
|
import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil;
|
||||||
import org.jivesoftware.smackx.pep.PepManager;
|
import org.jivesoftware.smackx.pep.PepManager;
|
||||||
|
@ -10,7 +12,10 @@ import org.jivesoftware.smackx.pubsub.PayloadItem;
|
||||||
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
import org.jivesoftware.smackx.pubsub.PubSubManager;
|
||||||
import org.jxmpp.jid.EntityBareJid;
|
import org.jxmpp.jid.EntityBareJid;
|
||||||
import org.mercury_im.messenger.core.SchedulersFacade;
|
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.MercuryConnectionManager;
|
||||||
|
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.AccountRepository;
|
||||||
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
|
import org.mercury_im.messenger.core.data.repository.OpenPgpRepository;
|
||||||
import org.mercury_im.messenger.core.util.Optional;
|
import org.mercury_im.messenger.core.util.Optional;
|
||||||
|
@ -22,12 +27,15 @@ import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import io.reactivex.Completable;
|
import io.reactivex.Completable;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
|
||||||
import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS;
|
import static org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil.PEP_NODE_PUBLIC_KEYS;
|
||||||
|
|
||||||
|
@ -35,15 +43,24 @@ public class AccountDetailsViewModel implements MercuryViewModel {
|
||||||
|
|
||||||
private MercuryConnectionManager connectionManager;
|
private MercuryConnectionManager connectionManager;
|
||||||
private final OpenPgpRepository openPgpRepository;
|
private final OpenPgpRepository openPgpRepository;
|
||||||
|
private final IkeyRepository ikeyRepository;
|
||||||
private final AccountRepository accountRepository;
|
private final AccountRepository accountRepository;
|
||||||
private final SchedulersFacade schedulers;
|
private final SchedulersFacade schedulers;
|
||||||
|
private final IkeyInitializer ikeyInitializer;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AccountDetailsViewModel(MercuryConnectionManager connectionManager, OpenPgpRepository openPgpRepository, AccountRepository accountRepository, SchedulersFacade schedulers) {
|
public AccountDetailsViewModel(MercuryConnectionManager connectionManager,
|
||||||
|
OpenPgpRepository openPgpRepository,
|
||||||
|
IkeyRepository ikeyRepository,
|
||||||
|
AccountRepository accountRepository,
|
||||||
|
SchedulersFacade schedulers,
|
||||||
|
IkeyInitializer ikeyInitializer) {
|
||||||
this.connectionManager = connectionManager;
|
this.connectionManager = connectionManager;
|
||||||
this.openPgpRepository = openPgpRepository;
|
this.openPgpRepository = openPgpRepository;
|
||||||
|
this.ikeyRepository = ikeyRepository;
|
||||||
this.accountRepository = accountRepository;
|
this.accountRepository = accountRepository;
|
||||||
this.schedulers = schedulers;
|
this.schedulers = schedulers;
|
||||||
|
this.ikeyInitializer = ikeyInitializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void markFingerprintTrusted(UUID accountId, OpenPgpV4Fingerprint fingerprint, boolean trusted) {
|
public void markFingerprintTrusted(UUID accountId, OpenPgpV4Fingerprint fingerprint, boolean trusted) {
|
||||||
|
@ -58,6 +75,11 @@ public class AccountDetailsViewModel implements MercuryViewModel {
|
||||||
.map(Account::getJid);
|
.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) {
|
public Observable<Optional<OpenPgpV4Fingerprint>> observeLocalFingerprint(UUID accountId) {
|
||||||
return openPgpRepository.observeLocalFingerprintOf(accountId);
|
return openPgpRepository.observeLocalFingerprintOf(accountId);
|
||||||
}
|
}
|
||||||
|
@ -113,4 +135,24 @@ public class AccountDetailsViewModel implements MercuryViewModel {
|
||||||
OpenPgpPubSubUtil.deletePublicKeyNode(pepManager, fingerprint);
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
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();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,45 @@
|
||||||
package org.mercury_im.messenger.core.viewmodel.ikey;
|
package org.mercury_im.messenger.core.viewmodel.ikey;
|
||||||
|
|
||||||
import org.jivesoftware.smackx.ikey.IkeyManager;
|
import org.jivesoftware.smackx.ikey.IkeyManager;
|
||||||
import org.jivesoftware.smackx.ikey_ox.OxIkeyManager;
|
import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
|
||||||
import org.mercury_im.messenger.core.connection.MercuryConnection;
|
import org.mercury_im.messenger.core.connection.MercuryConnection;
|
||||||
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
|
import org.mercury_im.messenger.core.connection.MercuryConnectionManager;
|
||||||
|
import org.mercury_im.messenger.core.crypto.ikey.IkeyInitializer;
|
||||||
|
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.mercury_im.messenger.core.viewmodel.MercuryViewModel;
|
||||||
import org.mercury_im.messenger.entity.Account;
|
import org.mercury_im.messenger.entity.Account;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
|
||||||
public class IkeySecretKeyBackupCreationViewModel implements MercuryViewModel {
|
public class IkeySecretKeyBackupCreationViewModel implements MercuryViewModel {
|
||||||
|
|
||||||
private final MercuryConnectionManager connectionManager;
|
private final MercuryConnectionManager connectionManager;
|
||||||
|
private final IkeyInitializer ikeyInitializer;
|
||||||
|
private UUID accountId;
|
||||||
|
private final IkeyRepository ikeyRepository;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public IkeySecretKeyBackupCreationViewModel(MercuryConnectionManager connectionManager) {
|
public IkeySecretKeyBackupCreationViewModel(MercuryConnectionManager connectionManager, IkeyInitializer ikeyInitializer, IkeyRepository ikeyRepository) {
|
||||||
this.connectionManager = connectionManager;
|
this.connectionManager = connectionManager;
|
||||||
|
this.ikeyInitializer = ikeyInitializer;
|
||||||
|
this.ikeyRepository = ikeyRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccountId(UUID accountId) {
|
||||||
|
this.accountId = accountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createIkeySecretKeyBackup(Account account) {
|
public void createIkeySecretKeyBackup(Account account) {
|
||||||
MercuryConnection connection = connectionManager.getConnection(account);
|
MercuryConnection connection = connectionManager.getConnection(account);
|
||||||
IkeyManager ikeyManager = IkeyManager.getInstanceFor(connection.getConnection());
|
IkeyManager ikeyManager = ikeyInitializer.initFor(connection);
|
||||||
OxIkeyManager oxIkeyManager = OxIkeyManager.getInstanceFor(connection.getConnection());
|
}
|
||||||
|
|
||||||
|
public Observable<Optional<OpenPgpSecretKeyBackupPassphrase>> getPassphrase() {
|
||||||
|
return ikeyRepository.loadBackupPassphrase(accountId).toObservable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.mercury_im.messenger.core.viewmodel.ikey;
|
||||||
|
|
||||||
|
import org.mercury_im.messenger.core.viewmodel.MercuryViewModel;
|
||||||
|
|
||||||
|
public class IkeySecretKeyBackupRestoreViewModel implements MercuryViewModel {
|
||||||
|
|
||||||
|
}
|
|
@ -33,9 +33,11 @@ public class IkeyElementTest extends MercurySmackTestSuite {
|
||||||
JidCreate.entityBareFromOrThrowUnchecked("hamlet@denmark.lit"),
|
JidCreate.entityBareFromOrThrowUnchecked("hamlet@denmark.lit"),
|
||||||
date,
|
date,
|
||||||
new SubordinateElement(
|
new SubordinateElement(
|
||||||
|
"urn:xmpp:openpgp:0",
|
||||||
new URI("xmpp:hamlet@denmark.lit?;node=urn:xmpp:openpgp:0:public-keys:1357B01865B2503C18453D208CAC2A9678548E35;item=2020-01-21T10:46:21Z"),
|
new URI("xmpp:hamlet@denmark.lit?;node=urn:xmpp:openpgp:0:public-keys:1357B01865B2503C18453D208CAC2A9678548E35;item=2020-01-21T10:46:21Z"),
|
||||||
"1357B01865B2503C18453D208CAC2A9678548E35"),
|
"1357B01865B2503C18453D208CAC2A9678548E35"),
|
||||||
new SubordinateElement(
|
new SubordinateElement(
|
||||||
|
"urn:xmpp:omemo:1",
|
||||||
new URI("xmpp:hamlet@denmark.lit?;node=urn:xmpp:omemo:1:bundles;item=123456"),
|
new URI("xmpp:hamlet@denmark.lit?;node=urn:xmpp:omemo:1:bundles;item=123456"),
|
||||||
"e64dc9166dd34db64c9247bd502c5969e365a98f3aa41c87247d120487fdd32f")
|
"e64dc9166dd34db64c9247bd502c5969e365a98f3aa41c87247d120487fdd32f")
|
||||||
);
|
);
|
||||||
|
|
|
@ -57,6 +57,7 @@ public class IkeySignatureCreatorAndVerifierTest extends MercurySmackTestSuite {
|
||||||
SuperordinateElement superordinate = new SuperordinateElement(Base64.encodeToString(keyRing.getMasterKey().getEncoded()));
|
SuperordinateElement superordinate = new SuperordinateElement(Base64.encodeToString(keyRing.getMasterKey().getEncoded()));
|
||||||
List<SubordinateElement> subList = new ArrayList<>();
|
List<SubordinateElement> subList = new ArrayList<>();
|
||||||
subList.add(new SubordinateElement(
|
subList.add(new SubordinateElement(
|
||||||
|
"urn:xmpp:openpgp:0",
|
||||||
new URI("xmpp:" + jid + "?;node=urn:xmpp:openpgp:0:public-keys:" + fingerprint + ";item=2020-01-21T10:46:21Z"),
|
new URI("xmpp:" + jid + "?;node=urn:xmpp:openpgp:0:public-keys:" + fingerprint + ";item=2020-01-21T10:46:21Z"),
|
||||||
fingerprint.toString()));
|
fingerprint.toString()));
|
||||||
SubordinateListElement subs = new SubordinateListElement(jid, new Date(), subList);
|
SubordinateListElement subs = new SubordinateListElement(jid, new Date(), subList);
|
||||||
|
|
|
@ -55,10 +55,17 @@ public class XepTest extends MercurySmackTestSuite {
|
||||||
|
|
||||||
List<SubordinateElement> subList = new ArrayList<>();
|
List<SubordinateElement> subList = new ArrayList<>();
|
||||||
subList.add(new SubordinateElement(
|
subList.add(new SubordinateElement(
|
||||||
|
"urn:xmpp:openpgp:0",
|
||||||
new URI("xmpp:" + jid + "?;node=urn:xmpp:openpgp:0:public-keys:1357B01865B2503C18453D208CAC2A9678548E35;item=2020-01-21T10:46:21Z"),
|
new URI("xmpp:" + jid + "?;node=urn:xmpp:openpgp:0:public-keys:1357B01865B2503C18453D208CAC2A9678548E35;item=2020-01-21T10:46:21Z"),
|
||||||
"1357B01865B2503C18453D208CAC2A9678548E35"));
|
"1357B01865B2503C18453D208CAC2A9678548E35"));
|
||||||
subList.add(new SubordinateElement(new URI("xmpp:" + jid + "?;node=urn:xmpp:omemo:1:bundles;item=31415"), "e64dc9166dd34db64c9247bd502c5969e365a98f3aa41c87247d120487fdd32f"));
|
subList.add(new SubordinateElement(
|
||||||
subList.add(new SubordinateElement(new URI("xmpp:" + jid + "?;node=eu.siacs.conversations.axolotl.bundles:31415"), "e64dc9166dd34db64c9247bd502c5969e365a98f3aa41c87247d120487fdd32f"));
|
"urn:xmpp:omemo:1",
|
||||||
|
new URI("xmpp:" + jid + "?;node=urn:xmpp:omemo:1:bundles;item=31415"),
|
||||||
|
"e64dc9166dd34db64c9247bd502c5969e365a98f3aa41c87247d120487fdd32f"));
|
||||||
|
subList.add(new SubordinateElement(
|
||||||
|
"eu.siacs.conversations.axolotl",
|
||||||
|
new URI("xmpp:" + jid + "?;node=eu.siacs.conversations.axolotl.bundles:31415"),
|
||||||
|
"e64dc9166dd34db64c9247bd502c5969e365a98f3aa41c87247d120487fdd32f"));
|
||||||
|
|
||||||
SubordinateListElement subs = new SubordinateListElement(jid, new Date(), subList);
|
SubordinateListElement subs = new SubordinateListElement(jid, new Date(), subList);
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Account entity.
|
* User Account entity.
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class Account {
|
public class Account {
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 5c40669a73ad2daabd3c0a303e97f02dea62f8a6
|
Subproject commit 36997c5835e8ed72b927a5543d2dc55c12e8d28a
|
|
@ -73,7 +73,7 @@ ext {
|
||||||
// Other libraries
|
// Other libraries
|
||||||
|
|
||||||
// Architecture Components
|
// Architecture Components
|
||||||
lifecycleVersion = '2.2.0-rc03'
|
lifecycleVersion = '2.2.0'
|
||||||
pagingVersion = "2.1.0"
|
pagingVersion = "2.1.0"
|
||||||
appCompatVersion = '1.1.0'
|
appCompatVersion = '1.1.0'
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue