From 528bc7c7c5cf09a0507cf8618bb26f8d2e5999b0 Mon Sep 17 00:00:00 2001 From: pokkst Date: Wed, 7 Sep 2022 23:04:28 -0500 Subject: [PATCH] Basic onboarding flow, allows for wallet passwords. NOTE: This commit still logs seeds to files for dev purposes, as there is no UI for it yet. --- .../com/m2049r/xmrwallet/MainActivity.java | 60 +++++++++-- .../dialog/PasswordBottomSheetDialog.java | 73 +++++++++++++ .../dialog/SendBottomSheetDialog.java | 16 +-- .../xmrwallet/fragment/home/HomeFragment.java | 64 +++++++---- .../onboarding/OnboardingFragment.java | 78 ++++++++++++++ .../onboarding/OnboardingViewModel.java | 7 ++ .../m2049r/xmrwallet/service/PrefService.java | 19 ++++ .../m2049r/xmrwallet/service/TxService.java | 10 +- .../com/m2049r/xmrwallet/util/Constants.java | 7 ++ .../xmrwallet/util/FingerprintHelper.java | 61 ----------- .../com/m2049r/xmrwallet/util/Helper.java | 100 ------------------ .../m2049r/xmrwallet/util/KeyStoreHelper.java | 81 -------------- app/src/main/res/layout/fragment_settings.xml | 40 +++++-- .../layout/password_bottom_sheet_dialog.xml | 49 +++++++++ app/src/main/res/navigation/main_nav.xml | 13 +++ app/src/main/res/values/strings.xml | 3 + 16 files changed, 384 insertions(+), 297 deletions(-) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/PasswordBottomSheetDialog.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingFragment.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingViewModel.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/service/PrefService.java create mode 100644 app/src/main/java/com/m2049r/xmrwallet/util/Constants.java delete mode 100644 app/src/main/java/com/m2049r/xmrwallet/util/FingerprintHelper.java create mode 100644 app/src/main/res/layout/password_bottom_sheet_dialog.xml diff --git a/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java b/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java index 32e45db..e325c3f 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/MainActivity.java @@ -1,20 +1,30 @@ package com.m2049r.xmrwallet; import android.os.Bundle; +import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.navigation.fragment.NavHostFragment; +import com.m2049r.xmrwallet.fragment.dialog.PasswordBottomSheetDialog; +import com.m2049r.xmrwallet.fragment.dialog.SendBottomSheetDialog; +import com.m2049r.xmrwallet.livedata.SingleLiveEvent; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.service.AddressService; import com.m2049r.xmrwallet.service.BalanceService; import com.m2049r.xmrwallet.service.HistoryService; import com.m2049r.xmrwallet.service.MoneroHandlerThread; +import com.m2049r.xmrwallet.service.PrefService; import com.m2049r.xmrwallet.service.TxService; +import com.m2049r.xmrwallet.util.Constants; import java.io.File; -public class MainActivity extends AppCompatActivity implements MoneroHandlerThread.Listener { +public class MainActivity extends AppCompatActivity implements MoneroHandlerThread.Listener, PasswordBottomSheetDialog.PasswordListener { + public final SingleLiveEvent restartEvents = new SingleLiveEvent(); private MoneroHandlerThread thread = null; private TxService txService = null; private BalanceService balanceService = null; @@ -25,21 +35,39 @@ public class MainActivity extends AppCompatActivity implements MoneroHandlerThre protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - init(); + File walletFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME); + new PrefService(this); + + if(walletFile.exists()) { + boolean promptPassword = PrefService.getInstance().getBoolean(Constants.PREF_USES_PASSWORD, false); + if(!promptPassword) { + init(walletFile, ""); + } else { + PasswordBottomSheetDialog passwordDialog = new PasswordBottomSheetDialog(); + passwordDialog.listener = this; + passwordDialog.show(getSupportFragmentManager(), null); + } + } else { + navigate(R.id.onboarding_fragment); + } + } + + private void navigate(int destination) { + FragmentActivity activity = this; + FragmentManager fm = activity.getSupportFragmentManager(); + NavHostFragment navHostFragment = + (NavHostFragment) fm.findFragmentById(R.id.nav_host_fragment); + if (navHostFragment != null) { + navHostFragment.getNavController().navigate(destination); + } } public MoneroHandlerThread getThread() { return thread; } - private void init() { - File walletFile = new File(getApplicationInfo().dataDir, "xmr_wallet"); - Wallet wallet = null; - if (walletFile.exists()) { - wallet = WalletManager.getInstance().openWallet(walletFile.getAbsolutePath(), ""); - } else { - wallet = WalletManager.getInstance().createWallet(walletFile, "", "English", 0); - } + public void init(File walletFile, String password) { + Wallet wallet = WalletManager.getInstance().openWallet(walletFile.getAbsolutePath(), password); WalletManager.getInstance().setProxy("127.0.0.1:9050"); thread = new MoneroHandlerThread("WalletService", wallet, this); this.txService = new TxService(this, thread); @@ -55,4 +83,16 @@ public class MainActivity extends AppCompatActivity implements MoneroHandlerThre this.balanceService.refreshBalance(); this.addressService.refreshAddress(); } + + @Override + public void onPasswordSuccess(String password) { + File walletFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME); + init(walletFile, password); + restartEvents.call(); + } + + @Override + public void onPasswordFail() { + Toast.makeText(this, R.string.bad_password, Toast.LENGTH_SHORT).show(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/PasswordBottomSheetDialog.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/PasswordBottomSheetDialog.java new file mode 100644 index 0000000..cad71e0 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/PasswordBottomSheetDialog.java @@ -0,0 +1,73 @@ +package com.m2049r.xmrwallet.fragment.dialog; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.service.BalanceService; +import com.m2049r.xmrwallet.service.TxService; +import com.m2049r.xmrwallet.util.Constants; +import com.m2049r.xmrwallet.util.Helper; + +import java.io.File; + +public class PasswordBottomSheetDialog extends BottomSheetDialogFragment { + public PasswordListener listener = null; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.password_bottom_sheet_dialog, null); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + File walletFile = new File(getActivity().getApplicationInfo().dataDir, Constants.WALLET_NAME); + + ImageButton pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton); + EditText passwordEditText = view.findViewById(R.id.wallet_password_edittext); + Button unlockWalletButton = view.findViewById(R.id.unlock_wallet_button); + + pastePasswordImageButton.setOnClickListener(view1 -> { + passwordEditText.setText(Helper.getClipBoardText(view.getContext())); + }); + + unlockWalletButton.setOnClickListener(view1 -> { + String password = passwordEditText.getText().toString(); + boolean success = checkPassword(walletFile, password); + if(success) { + listener.onPasswordSuccess(password); + dismiss(); + } else { + listener.onPasswordFail(); + } + }); + } + + private boolean checkPassword(File walletFile, String password) { + Wallet wallet = WalletManager.getInstance().openWallet(walletFile.getAbsolutePath(), password); + boolean ok = wallet.getStatus().isOk(); + wallet.close(); + return ok; + } + + public interface PasswordListener { + void onPasswordSuccess(String password); + void onPasswordFail(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/SendBottomSheetDialog.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/SendBottomSheetDialog.java index 4a05da0..e64b9a9 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/SendBottomSheetDialog.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/dialog/SendBottomSheetDialog.java @@ -1,12 +1,12 @@ package com.m2049r.xmrwallet.fragment.dialog; -import android.content.ClipboardManager; import android.os.Bundle; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.service.BalanceService; import com.m2049r.xmrwallet.service.TxService; +import com.m2049r.xmrwallet.util.Helper; import android.view.LayoutInflater; import android.view.View; @@ -21,7 +21,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; public class SendBottomSheetDialog extends BottomSheetDialogFragment { private MutableLiveData _sendingMax = new MutableLiveData<>(false); @@ -42,12 +41,8 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { Button sendButton = view.findViewById(R.id.send_button); TextView sendAllTextView = view.findViewById(R.id.sending_all_textview); - TxService.getInstance().clearSendEvent.observe(getViewLifecycleOwner(), o -> { - dismiss(); - }); - pasteAddressImageButton.setOnClickListener(view1 -> { - + addressEditText.setText(Helper.getClipBoardText(view.getContext())); }); sendMaxButton.setOnClickListener(view1 -> { @@ -68,7 +63,12 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { return; } sendButton.setEnabled(false); - TxService.getInstance().sendTx(address, amount, sendAll); + boolean success = TxService.getInstance().sendTx(address, amount, sendAll); + if(success) { + dismiss(); + } else { + Toast.makeText(getActivity(), getString(R.string.error_sending_tx), Toast.LENGTH_SHORT).show(); + } } else if (!validAddress) { Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show(); } else { diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java index 5514643..932b02d 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/home/HomeFragment.java @@ -16,6 +16,7 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; @@ -32,7 +33,9 @@ import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.service.AddressService; import com.m2049r.xmrwallet.service.BalanceService; import com.m2049r.xmrwallet.service.HistoryService; +import com.m2049r.xmrwallet.service.PrefService; import com.m2049r.xmrwallet.service.TxService; +import com.m2049r.xmrwallet.util.Constants; import java.util.Collections; @@ -49,9 +52,19 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + MainActivity mainActivity = (MainActivity)getActivity(); mViewModel = new ViewModelProvider(this).get(HomeViewModel.class); - bindObservers(view); - bindListeners(view); + + boolean usesPassword = PrefService.getInstance().getBoolean(Constants.PREF_USES_PASSWORD, false); + if(!usesPassword) { + bindObservers(view); + bindListeners(view); + } else { + mainActivity.restartEvents.observe(getViewLifecycleOwner(), o -> { + bindObservers(view); + bindListeners(view); + }); + } } private void bindListeners(View view) { @@ -79,31 +92,38 @@ public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxI TextView unlockedBalanceTextView = view.findViewById(R.id.balance_unlocked_textview); TextView lockedBalanceTextView = view.findViewById(R.id.balance_locked_textview); - BalanceService.getInstance().balance.observe(getViewLifecycleOwner(), balance -> { - unlockedBalanceTextView.setText(getString(R.string.wallet_balance_text, Wallet.getDisplayAmount(balance))); - }); + BalanceService balanceService = BalanceService.getInstance(); + HistoryService historyService = HistoryService.getInstance(); - BalanceService.getInstance().lockedBalance.observe(getViewLifecycleOwner(), lockedBalance -> { - if(lockedBalance == 0) { - lockedBalanceTextView.setVisibility(View.INVISIBLE); - } else { - lockedBalanceTextView.setText(getString(R.string.wallet_locked_balance_text, Wallet.getDisplayAmount(lockedBalance))); - lockedBalanceTextView.setVisibility(View.VISIBLE); - } - }); + if(balanceService != null) { + balanceService.balance.observe(getViewLifecycleOwner(), balance -> { + unlockedBalanceTextView.setText(getString(R.string.wallet_balance_text, Wallet.getDisplayAmount(balance))); + }); + + balanceService.lockedBalance.observe(getViewLifecycleOwner(), lockedBalance -> { + if (lockedBalance == 0) { + lockedBalanceTextView.setVisibility(View.INVISIBLE); + } else { + lockedBalanceTextView.setText(getString(R.string.wallet_locked_balance_text, Wallet.getDisplayAmount(lockedBalance))); + lockedBalanceTextView.setVisibility(View.VISIBLE); + } + }); + } TransactionInfoAdapter adapter = new TransactionInfoAdapter(this); txHistoryRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); txHistoryRecyclerView.setAdapter(adapter); - HistoryService.getInstance().history.observe(getViewLifecycleOwner(), history -> { - if(history.isEmpty()) { - txHistoryRecyclerView.setVisibility(View.GONE); - } else { - Collections.sort(history); - adapter.submitList(history); - txHistoryRecyclerView.setVisibility(View.VISIBLE); - } - }); + if(historyService != null) { + historyService.history.observe(getViewLifecycleOwner(), history -> { + if (history.isEmpty()) { + txHistoryRecyclerView.setVisibility(View.GONE); + } else { + Collections.sort(history); + adapter.submitList(history); + txHistoryRecyclerView.setVisibility(View.VISIBLE); + } + }); + } } @Override diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingFragment.java new file mode 100644 index 0000000..11be7d5 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingFragment.java @@ -0,0 +1,78 @@ +package com.m2049r.xmrwallet.fragment.onboarding; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; + +import com.m2049r.xmrwallet.MainActivity; +import com.m2049r.xmrwallet.R; +import com.m2049r.xmrwallet.model.Wallet; +import com.m2049r.xmrwallet.model.WalletManager; +import com.m2049r.xmrwallet.service.PrefService; +import com.m2049r.xmrwallet.util.Constants; + +import java.io.File; + +public class OnboardingFragment extends Fragment { + + private OnboardingViewModel mViewModel; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_settings, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mViewModel = new ViewModelProvider(this).get(OnboardingViewModel.class); + EditText walletPasswordEditText = view.findViewById(R.id.wallet_password_edittext); + EditText walletSeedEditText = view.findViewById(R.id.wallet_seed_edittext); + Button createWalletButton = view.findViewById(R.id.create_wallet_button); + createWalletButton.setOnClickListener(view1 -> { + String walletPassword = walletPasswordEditText.getText().toString(); + if(!walletPassword.isEmpty()) { + PrefService.getInstance().edit().putBoolean(Constants.PREF_USES_PASSWORD, true).apply(); + } + String walletSeed = walletSeedEditText.getText().toString().trim(); + File walletFile = new File(getActivity().getApplicationInfo().dataDir, Constants.WALLET_NAME); + Wallet wallet = null; + if(walletSeed.isEmpty()) { + wallet = WalletManager.getInstance().createWallet(walletFile, walletPassword, Constants.MNEMONIC_LANGUAGE, 0); + } else { + wallet = WalletManager.getInstance().recoveryWallet(walletFile, walletPassword, walletSeed, "", 0); + } + wallet.close(); + ((MainActivity)getActivity()).init(walletFile, walletPassword); + getActivity().onBackPressed(); + }); + walletSeedEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {} + + @Override + public void afterTextChanged(Editable editable) { + String text = editable.toString(); + if(text.isEmpty()) { + createWalletButton.setText(R.string.create_wallet); + } else { + createWalletButton.setText(R.string.menu_restore); + } + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingViewModel.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingViewModel.java new file mode 100644 index 0000000..34a9260 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/onboarding/OnboardingViewModel.java @@ -0,0 +1,7 @@ +package com.m2049r.xmrwallet.fragment.onboarding; + +import androidx.lifecycle.ViewModel; + +public class OnboardingViewModel extends ViewModel { + +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/PrefService.java b/app/src/main/java/com/m2049r/xmrwallet/service/PrefService.java new file mode 100644 index 0000000..e54d9fd --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/service/PrefService.java @@ -0,0 +1,19 @@ +package com.m2049r.xmrwallet.service; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.m2049r.xmrwallet.MainActivity; + +public class PrefService extends ServiceBase { + public static SharedPreferences instance = null; + + public static SharedPreferences getInstance() { + return instance; + } + + public PrefService(MainActivity mainActivity) { + super(mainActivity, null); + instance = mainActivity.getSharedPreferences(mainActivity.getApplicationInfo().packageName, Context.MODE_PRIVATE); + } +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java b/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java index 5ebc85e..8a2dd95 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/TxService.java @@ -10,18 +10,12 @@ public class TxService extends ServiceBase { return instance; } - private final SingleLiveEvent _clearSendEvent = new SingleLiveEvent(); - public SingleLiveEvent clearSendEvent = _clearSendEvent; - public TxService(MainActivity mainActivity, MoneroHandlerThread thread) { super(mainActivity, thread); instance = this; } - public void sendTx(String address, String amount, boolean sendAll) { - boolean success = this.getThread().sendTx(address, amount, sendAll); - if (success) { - _clearSendEvent.call(); - } + public boolean sendTx(String address, String amount, boolean sendAll) { + return this.getThread().sendTx(address, amount, sendAll); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Constants.java b/app/src/main/java/com/m2049r/xmrwallet/util/Constants.java new file mode 100644 index 0000000..859ec8e --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Constants.java @@ -0,0 +1,7 @@ +package com.m2049r.xmrwallet.util; + +public class Constants { + public static final String WALLET_NAME = "xmr_wallet"; + public static final String MNEMONIC_LANGUAGE = "English"; + public static final String PREF_USES_PASSWORD = "pref_uses_password"; +} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/FingerprintHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/FingerprintHelper.java deleted file mode 100644 index 906dba8..0000000 --- a/app/src/main/java/com/m2049r/xmrwallet/util/FingerprintHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2018-2020 m2049r et al. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.m2049r.xmrwallet.util; - -import android.app.KeyguardManager; -import android.content.Context; -import android.hardware.fingerprint.FingerprintManager; -import android.os.Build; -import android.os.CancellationSignal; - -public class FingerprintHelper { - - public static boolean isDeviceSupported(Context context) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return false; - } - - FingerprintManager fingerprintManager = context.getSystemService(FingerprintManager.class); - KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class); - - return (keyguardManager != null) && (fingerprintManager != null) && - keyguardManager.isKeyguardSecure() && - fingerprintManager.isHardwareDetected() && - fingerprintManager.hasEnrolledFingerprints(); - } - - public static boolean isFingerPassValid(Context context, String wallet) { - try { - KeyStoreHelper.loadWalletUserPass(context, wallet); - return true; - } catch (KeyStoreHelper.BrokenPasswordStoreException ex) { - return false; - } - } - - public static void authenticate(Context context, CancellationSignal cancelSignal, - FingerprintManager.AuthenticationCallback callback) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return; - } - - FingerprintManager manager = context.getSystemService(FingerprintManager.class); - if (manager != null) { - manager.authenticate(null, cancelSignal, 0, callback, null); - } - } -} diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java index a5d1508..a1b2078 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java @@ -295,22 +295,6 @@ public class Helper { return data; } - static public void setMoneroHome(Context context) { - try { - String home = getStorage(context, MONERO_DIR).getAbsolutePath(); - Os.setenv("HOME", home, true); - } catch (ErrnoException ex) { - throw new IllegalStateException(ex); - } - } - - static public void initLogger(Context context) { - if (BuildConfig.DEBUG) { - initLogger(context, WalletManager.LOGLEVEL_DEBUG); - } - // no logger if not debug - } - // TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ? static public void initLogger(Context context, int level) { String home = getStorage(context, MONERO_DIR).getAbsolutePath(); @@ -318,88 +302,4 @@ public class Helper { if (level >= WalletManager.LOGLEVEL_SILENT) WalletManager.setLogLevel(level); } - - static public boolean useCrazyPass(Context context) { - File flagFile = new File(getWalletRoot(context), NOCRAZYPASS_FLAGFILE); - return !flagFile.exists(); - } - - // try to figure out what the real wallet password is given the user password - // which could be the actual wallet password or a (maybe malformed) CrAzYpass - // or the password used to derive the CrAzYpass for the wallet - static public String getWalletPassword(Context context, String walletName, String password) { - String walletPath = new File(getWalletRoot(context), walletName + ".keys").getAbsolutePath(); - - // try with entered password (which could be a legacy password or a CrAzYpass) - if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, password)) { - return password; - } - - // maybe this is a malformed CrAzYpass? - String possibleCrazyPass = CrazyPassEncoder.reformat(password); - if (possibleCrazyPass != null) { // looks like a CrAzYpass - if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, possibleCrazyPass)) { - return possibleCrazyPass; - } - } - - // generate & try with CrAzYpass - String crazyPass = KeyStoreHelper.getCrazyPass(context, password); - if (WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, crazyPass)) { - return crazyPass; - } - - // or maybe it is a broken CrAzYpass? (of which we have two variants) - String brokenCrazyPass2 = KeyStoreHelper.getBrokenCrazyPass(context, password, 2); - if ((brokenCrazyPass2 != null) - && WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, brokenCrazyPass2)) { - return brokenCrazyPass2; - } - String brokenCrazyPass1 = KeyStoreHelper.getBrokenCrazyPass(context, password, 1); - if ((brokenCrazyPass1 != null) - && WalletManager.getInstance().verifyWalletPasswordOnly(walletPath, brokenCrazyPass1)) { - return brokenCrazyPass1; - } - - return null; - } - - static AlertDialog openDialog = null; // for preventing opening of multiple dialogs - static AsyncTask passwordTask = null; - - public interface PasswordAction { - void act(String walletName, String password, boolean fingerprintUsed); - - void fail(String walletName); - } - - static private boolean processPasswordEntry(Context context, String walletName, String pass, boolean fingerprintUsed, PasswordAction action) { - String walletPassword = Helper.getWalletPassword(context, walletName, pass); - if (walletPassword != null) { - action.act(walletName, walletPassword, fingerprintUsed); - return true; - } else { - action.fail(walletName); - return false; - } - } - - public interface Action { - boolean run(); - } - - static public boolean runWithNetwork(Action action) { - StrictMode.ThreadPolicy currentPolicy = StrictMode.getThreadPolicy(); - StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build(); - StrictMode.setThreadPolicy(policy); - try { - return action.run(); - } finally { - StrictMode.setThreadPolicy(currentPolicy); - } - } - - static public boolean preventScreenshot() { - return !(BuildConfig.DEBUG || BuildConfig.FLAVOR_type.equals("alpha")); - } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java index 896d2d0..984c171 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java @@ -62,55 +62,6 @@ public class KeyStoreHelper { System.loadLibrary("monerujo"); } - public static native byte[] slowHash(byte[] data, int brokenVariant); - - static final private String RSA_ALIAS = "MonerujoRSA"; - - private static String getCrazyPass(Context context, String password, int brokenVariant) { - byte[] data = password.getBytes(StandardCharsets.UTF_8); - byte[] sig = null; - try { - KeyStoreHelper.createKeys(context, RSA_ALIAS); - sig = KeyStoreHelper.signData(RSA_ALIAS, data); - byte[] hash = slowHash(sig, brokenVariant); - if (hash == null) { - throw new IllegalStateException("Slow Hash is null!"); - } - return CrazyPassEncoder.encode(hash); - } catch (NoSuchProviderException | NoSuchAlgorithmException | - InvalidAlgorithmParameterException | KeyStoreException | - InvalidKeyException | SignatureException ex) { - throw new IllegalStateException(ex); - } - } - - public static String getCrazyPass(Context context, String password) { - if (Helper.useCrazyPass(context)) - return getCrazyPass(context, password, 0); - else - return password; - } - - public static String getBrokenCrazyPass(Context context, String password, int brokenVariant) { - // due to a link bug in the initial implementation, some crazypasses were built with - // prehash & variant == 1 - // since there are wallets out there, we need to keep this here - // yes, it's a mess - if (isArm32() && (brokenVariant != 2)) return null; - return getCrazyPass(context, password, brokenVariant); - } - - private static Boolean isArm32 = null; - - public static boolean isArm32() { - if (isArm32 != null) return isArm32; - synchronized (KeyStoreException.class) { - if (isArm32 != null) return isArm32; - isArm32 = Build.SUPPORTED_ABIS[0].equals("armeabi-v7a"); - return isArm32; - } - } - public static boolean saveWalletUserPass(@NonNull Context context, String wallet, String password) { String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet; byte[] data = password.getBytes(StandardCharsets.UTF_8); @@ -141,38 +92,6 @@ public class KeyStoreHelper { } } - public static boolean hasStoredPasswords(@NonNull Context context) { - SharedPreferences prefs = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE); - return prefs.getAll().size() > 0; - } - - public static String loadWalletUserPass(@NonNull Context context, String wallet) throws BrokenPasswordStoreException { - String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet; - String encoded = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE) - .getString(wallet, ""); - if (encoded.isEmpty()) throw new BrokenPasswordStoreException(); - byte[] data = Base64.decode(encoded, Base64.DEFAULT); - byte[] decrypted = KeyStoreHelper.decrypt(walletKeyAlias, data); - if (decrypted == null) throw new BrokenPasswordStoreException(); - return new String(decrypted, StandardCharsets.UTF_8); - } - - public static void removeWalletUserPass(Context context, String wallet) { - String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet; - try { - KeyStoreHelper.deleteKeys(walletKeyAlias); - } catch (KeyStoreException ex) { - Timber.w(ex); - } - context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE).edit() - .remove(wallet).apply(); - } - - public static void copyWalletUserPass(Context context, String srcWallet, String dstWallet) throws BrokenPasswordStoreException { - final String pass = loadWalletUserPass(context, srcWallet); - saveWalletUserPass(context, dstWallet, pass); - } - /** * Creates a public and private key and stores it using the Android Key * Store, so that only this application will be able to access the keys. diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 59d4a98..dec3e0f 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -6,14 +6,40 @@ android:layout_height="match_parent" tools:context=".fragment.settings.SettingsFragment"> - + +