From 85d35d564ba110620cb74bc95002fa66f6c6ca8f Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 11 May 2018 12:10:23 +0800 Subject: [PATCH] Add Input NFC Password --- app/src/main/AndroidManifest.xml | 1 + .../m2049r/xmrwallet/GenerateFragment.java | 35 +++++ .../com/m2049r/xmrwallet/LoginActivity.java | 97 ++++++++++++- .../dialog/InputNfcPasswordFragment.java | 130 ++++++++++++++++++ app/src/main/res/drawable/nfc1.png | Bin 0 -> 17447 bytes app/src/main/res/drawable/nfc2.png | Bin 0 -> 17414 bytes app/src/main/res/drawable/nfc3.png | Bin 0 -> 17280 bytes app/src/main/res/drawable/nfc_signal.xml | 7 + app/src/main/res/layout/fragment_generate.xml | 67 +++++++++ .../layout/fragment_input_nfc_password.xml | 40 ++++++ app/src/main/res/values-de/strings.xml | 3 + app/src/main/res/values-es/strings.xml | 3 + app/src/main/res/values-fr/strings.xml | 3 + app/src/main/res/values-it/strings.xml | 3 + app/src/main/res/values-nb/strings.xml | 3 + app/src/main/res/values-zh-rTW/strings.xml | 3 + app/src/main/res/values/strings.xml | 4 + 17 files changed, 395 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/m2049r/xmrwallet/dialog/InputNfcPasswordFragment.java create mode 100644 app/src/main/res/drawable/nfc1.png create mode 100644 app/src/main/res/drawable/nfc2.png create mode 100644 app/src/main/res/drawable/nfc3.png create mode 100644 app/src/main/res/drawable/nfc_signal.xml create mode 100644 app/src/main/res/layout/fragment_input_nfc_password.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aa708d26dc..7f10e51de7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java index 82bc40a822..3edb1ae49d 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java @@ -19,6 +19,7 @@ import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.graphics.drawable.AnimationDrawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.TextInputLayout; @@ -35,6 +36,7 @@ import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.Button; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Switch; import android.widget.TextView; @@ -71,6 +73,15 @@ public class GenerateFragment extends Fragment { private TextInputLayout etWalletViewKey; private TextInputLayout etWalletSpendKey; private TextInputLayout etWalletRestoreHeight; + + private LinearLayout llNfcPasswordSeed; + private TextInputLayout etNfcPasswordSeed; + private ImageView ivNfcPasswordSeed; + + private LinearLayout llNfcPasswordkey; + private TextInputLayout etNfcPasswordkey; + private ImageView ivNfcPasswordkey; + private Button bGenerate; private String type = null; @@ -92,8 +103,26 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey); etWalletSpendKey = (TextInputLayout) view.findViewById(R.id.etWalletSpendKey); etWalletRestoreHeight = (TextInputLayout) view.findViewById(R.id.etWalletRestoreHeight); + + llNfcPasswordSeed = (LinearLayout) view.findViewById(R.id.llNfcPasswordSeed); + etNfcPasswordSeed = (TextInputLayout) view.findViewById(R.id.etNfcPasswordSeed); + ivNfcPasswordSeed = (ImageView) view.findViewById(R.id.ivNfcPasswordSeed); + llNfcPasswordkey = (LinearLayout) view.findViewById(R.id.llNfcPasswordkey); + etNfcPasswordkey = (TextInputLayout) view.findViewById(R.id.etNfcPasswordkey); + ivNfcPasswordkey = (ImageView) view.findViewById(R.id.ivNfcPasswordkey); + bGenerate = (Button) view.findViewById(R.id.bGenerate); + ivNfcPasswordkey.setImageResource(R.drawable.nfc_signal); + ivNfcPasswordSeed.setImageResource(R.drawable.nfc_signal); + AnimationDrawable nfcAnimationkey = (AnimationDrawable) ivNfcPasswordkey.getDrawable(); + nfcAnimationkey.setOneShot(false); + nfcAnimationkey.start(); + + AnimationDrawable nfcAnimationSeed = (AnimationDrawable) ivNfcPasswordSeed.getDrawable(); + nfcAnimationSeed.setOneShot(false); + nfcAnimationSeed.start(); + etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT); etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etWalletViewKey.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); @@ -212,6 +241,8 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); + llNfcPasswordSeed.setVisibility(View.VISIBLE); + } else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) { etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { @@ -252,6 +283,8 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); + + llNfcPasswordkey.setVisibility(View.VISIBLE); } if (type.equals(TYPE_KEY)) { etWalletSpendKey.setVisibility(View.VISIBLE); @@ -268,6 +301,7 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); + llNfcPasswordkey.setVisibility(View.VISIBLE); } if (!type.equals(TYPE_NEW)) { etWalletRestoreHeight.setVisibility(View.VISIBLE); @@ -281,6 +315,7 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); + } bGenerate.setOnClickListener(new View.OnClickListener() diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index 782afba6e2..771106b2cd 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -18,6 +18,7 @@ import android.app.Activity; import android.app.AlertDialog; +import android.app.PendingIntent; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; @@ -25,6 +26,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.media.MediaScannerConnection; +import android.nfc.NfcAdapter; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -45,6 +47,7 @@ import com.m2049r.xmrwallet.dialog.AboutFragment; import com.m2049r.xmrwallet.dialog.CreditsFragment; import com.m2049r.xmrwallet.dialog.HelpFragment; +import com.m2049r.xmrwallet.dialog.InputNfcPasswordFragment; import com.m2049r.xmrwallet.dialog.PrivacyFragment; import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.Wallet; @@ -82,6 +85,10 @@ public class LoginActivity extends SecureActivity private Toolbar toolbar; + private TagUtil tagUtil ; + + private InputNfcPasswordFragment inputNfcPassword; + @Override public void setToolbarButton(int type) { toolbar.setButton(type); @@ -323,7 +330,6 @@ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { return false; } }); - dialog.show(); } @@ -379,6 +385,36 @@ protected void onPostExecute(Boolean result) { } } + private class AsyncBackupToNFC2 extends AsyncTask { + @Override + protected void onPreExecute() { + super.onPreExecute(); + + } + + @Override + protected Boolean doInBackground(Object... params) { + if (params.length != 2) return false; + Intent intent = (Intent) params[0]; + String walletName = (String) params[1]; + return backupWalletToNFC(intent,walletName); + } + + @Override + protected void onPostExecute(Boolean result) { + super.onPostExecute(result); + if (isDestroyed()) { + return; + } + dismissProgressDialog(); + if (!result) { + Toast.makeText(LoginActivity.this, getString(R.string.backup_failed), Toast.LENGTH_LONG).show(); + }else{ + InputNfcPasswordFragment.getInstance().dismiss(); + } + } + } + private boolean backupWallet(String walletName) { File backupFolder = new File(getStorageRoot(), "backups"); if (!backupFolder.exists()) { @@ -400,6 +436,42 @@ private boolean backupWallet(String walletName) { return success; } + private boolean backupWalletToNFC(Intent intent,String walletName) { + boolean success=false; + File walletFile = Helper.getWalletFile(LoginActivity.this, walletName); + //Timber.d("backup " + walletFile.getAbsolutePath() + " to " + backupFile.getAbsolutePath()); + + boolean authenticated=false; + try { + tagUtil = TagUtil.selectTag(intent, false); + authenticated = tagUtil.authentication(intent, getKey(), false); + }catch (Exception e) { + e.printStackTrace(); + } + + if(authenticated) + { + Toast toast = Toast.makeText(LoginActivity.this, "Authentication Successful", Toast.LENGTH_LONG); + try { + + byte[] bytes = new byte[(int)walletFile.length()]; + new FileInputStream(walletFile).read(bytes); + tagUtil.writeTag(intent,(byte)4,bytes,false); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (AuthenticationException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + else + return false; + return success; + } + private boolean backupWalletToNFC(String walletName) { boolean success=false; File walletFile = Helper.getWalletFile(LoginActivity.this, walletName); @@ -432,8 +504,8 @@ private boolean backupWalletToNFC(String walletName) { e.printStackTrace(); } } - else - return false; + else + return false; return success; } private byte[] getKey() @@ -475,7 +547,9 @@ public void onWalletBackupToFile(String walletName) { @Override public void onWalletBackupToNFC(String walletName) { Timber.d("backup to NFC hard wallet for wallet ." + walletName + "."); - new AsyncBackupToNFC().execute(walletName); + InputNfcPasswordFragment.display(getSupportFragmentManager()); + InputNfcPasswordFragment.getInstance().setWalletName(walletName); + } private class AsyncArchive extends AsyncTask { @@ -659,6 +733,21 @@ protected void onResume() { } } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(intent.getAction()) + || NfcAdapter.ACTION_TECH_DISCOVERED.equals(intent.getAction()) + || NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())) { + try { + tagUtil = TagUtil.selectTag(intent, false); + new AsyncBackupToNFC2().execute(intent,InputNfcPasswordFragment.getInstance().getWalletName()); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + private class MyProgressDialog extends ProgressDialog { Activity activity; diff --git a/app/src/main/java/com/m2049r/xmrwallet/dialog/InputNfcPasswordFragment.java b/app/src/main/java/com/m2049r/xmrwallet/dialog/InputNfcPasswordFragment.java new file mode 100644 index 0000000000..16ff8933e1 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/dialog/InputNfcPasswordFragment.java @@ -0,0 +1,130 @@ +package com.m2049r.xmrwallet.dialog; + + +import android.app.AlertDialog; +import android.app.Dialog; +import android.graphics.drawable.AnimationDrawable; +import android.os.Bundle; +import android.support.design.widget.TextInputLayout; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; + +import com.m2049r.xmrwallet.R; +import com.nulabinc.zxcvbn.Strength; +import com.nulabinc.zxcvbn.Zxcvbn; + +public class InputNfcPasswordFragment extends DialogFragment { + + private String walletName = null; + + + static final String TAG = "InputNfcPasswordFragment"; + + private TextInputLayout etWalletPassword; + + private static InputNfcPasswordFragment fragment = null; + + + public static InputNfcPasswordFragment getInstance() { + if(fragment ==null){ + fragment = new InputNfcPasswordFragment(); + } + + return fragment; + } + + public static void display(FragmentManager fm) { + FragmentTransaction ft = fm.beginTransaction(); + Fragment prev = fm.findFragmentByTag(TAG); + if (prev != null) { + ft.remove(prev); + } + InputNfcPasswordFragment.getInstance().show(ft, TAG); + } + + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_input_nfc_password, null); + etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword); + etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + checkPassword(); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + }); + etWalletPassword.requestFocus(); + ImageView nfcAnimationImage = (ImageView) view.findViewById(R.id.NfcAnimation); + nfcAnimationImage.setImageResource(R.drawable.nfc_signal); + AnimationDrawable nfcAnimation = (AnimationDrawable) nfcAnimationImage.getDrawable(); + nfcAnimation.setOneShot(false); + nfcAnimation.start(); + + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setView(view); + initZxcvbn(); + return builder.create(); + } + Zxcvbn zxcvbn = new Zxcvbn(); + // initialize zxcvbn engine in background thread + private void initZxcvbn() { + new Thread(new Runnable() { + @Override + public void run() { + zxcvbn.measure(""); + } + }).start(); + } + + private void checkPassword() { + String password = etWalletPassword.getEditText().getText().toString(); + if (!password.isEmpty()) { + Strength strength = zxcvbn.measure(password); + int msg; + double guessesLog10 = strength.getGuessesLog10(); + if (guessesLog10 < 10) + msg = R.string.password_weak; + else if (guessesLog10 < 11) + msg = R.string.password_fair; + else if (guessesLog10 < 12) + msg = R.string.password_good; + else if (guessesLog10 < 13) + msg = R.string.password_strong; + else + msg = R.string.password_very_strong; + etWalletPassword.setError(getResources().getString(msg)); + } else { + etWalletPassword.setError(null); + } + } + + //get input nfc password + public String getPassword(){ + String password = etWalletPassword.getEditText().getText().toString(); + return password; + } + + public String getWalletName() { + return walletName; + } + + public void setWalletName(String walletName) { + this.walletName = walletName; + } +} diff --git a/app/src/main/res/drawable/nfc1.png b/app/src/main/res/drawable/nfc1.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf6f9253f3d349cd23fd87d637ca3cc54ad6890 GIT binary patch literal 17447 zcmeI4dpJ~iAHYv0S(ox|qY|?7j4oP@xtcMPK~ZQIvq+>?GiS~)%!Rp_q1sUPecMeN zrCLQ~+a{Z=l`bf3Td6Evq*6-JD%7gC7Mt>(L7b#L`#ihv^FHtMoOzh}`u)C_^ZR_i zzwhtNAICNAca(Xf>tV#DLGm$alkJJmi5DAB4I#9 z70o3s&<7-h`0@xIas@D zJeH9V9JYy3D8Bl@hk2j%gPYDObsrk@9zh9G-q>U^pkfzQeJ{yk4nv z%+N~JqBt1A5YjO%l|k__5IF!+%GC-GS`-H_#cr(CtHh#_g5+|E>tL1fULz!Ucnnk(!QPh-ie-APb6v=zjYcv);t%rV zs~}gnBArPTClZwsL}ha5R1THmKyl_!Cvsc|qzT{0KpgNUo6bRjy(wKLR4h$Rb?GBZCOd;kx2@$R%UtrgRtwksYVfQ1KMW;~MLb{MeVlxB+5|zplkoeAQI!Q!xWgy0=xOpp-w^#|+YdoTk;c6b}ZGlkio1?EMB;tV{6~k93A^i^I5?^n@;ifj+v-CCt zJDI}=_1(=C)Nc?-=t6#DbmX#zEr-m+Z>RImAsF^{9j0gk{|ci5&w|%>ly6A5&IB*w+I|6^8`@yaGt+A1+8PpHacTCvVmmP=(~l`Cy1+NfcKmHr-R@OTb{Z`UDLrjrxh$%Q<6c(esitc4^Yo?`eM zRqD?J_>dej8a*?nW$1_{`wc_H#E{`&t`K+D1F0Mwh40(puMFhj?+kBddH4+an}v}T z{mqV%?yw+88mKac3l$Mk0mFr)fhuFTP!S;&FkDC)s4|8N6%kSa!-b@QDr2}%5g`>Y zTu2(IGKLEk5mEueg`|NhW4KTeAr&xONE)azh6@!DQUSw-q=719xKI%x6);>#8mKac z3l$Mk0mFr)fhuFTP!S;&FkDC)s4|8N6%kSa!-b@QDr2}%5g`>YTu2(IGKLEk5mEue zg`|NhW4KTeAr&xONE)azh6@!DQUSw-q=719xKI%x6);>#8mKac3l$Mk0mFr)fhuFT zP!S;&FkDC)s4|8N6%kSa!-b@QD*qualaZ%zAsPJWtp5Ke-xkD}_PwBO>DB*$^bQQ0pRHdeaK zpW}W%C?uICE`6eSX`52_EI#^K+~O)Fu4I9Eag%mprTmQj&2Ia?PratD7*|26?yqu} z9t#DU>t8?g zh5K!f=eNJObH8<+JcntX5!+?1G&y+%P}|MvKakW&sW(pi(hQnErTSm9Dk##=f5-^v zx$G{r{jjC^*ES26fC7UlU7sy@WrtHcuF7M2JP-eLpYHvlw7@ROzT8H?{wTu^d}ZT! zz_jJ&+!XUn;HJ9ef?@AhQ&_}?V(>(B#`?zCy>+DK@3y)XCO%5;Tl>-{@LBNUs=9pZ z_rYyD9)HrKx}VeNw{GjHQuFVB?(cqKQbVe?YxiM7I_5?5axfvnTTIkkYgEZ1XsAcTy*Fxu!!t-)$cM zxKV3UC}3>+i$&*>?SAt!{gEMw8%8F;3uB!+QYN8Tbhl4SA?C0;oEy(%$QTUZO-kAw(-vw zZm*F=)h8{V!5cIE#w$?lKOB*b8CLK4j@v%5{ZjSBg8`S(T zHQxS-S;)!aAfq3}tCqdjo_3YXXiyXHUf+{eXBI?V9g_5j*0s`bV}|NZh(#3sX&Hm} z)ee)%`zMP|-w4}lye%nzdiysgi*H7zMedJS-e;YsSak1TQX^3BbD$`+%4&Ck-K^i% zG`MH1Pk67HJ5Cq4C-LLj2a`M-*oJ2abKG3!2leKWJ~CQ*IFE4wH(OC|UKO$DhuGk9 zzl>;ngW>lUyhZJ9OBHLL_8t^^&8<22!9nNx$jVBCjOTl-*sq@Vc6K^gR%F%$W)@3c z?e&=A=r3e`oME%fZ1vQ$ww01xbH~{H7;$dgt+X!uKW@mjSh2U$zm{FTys$}Ow9AH+ zi%n|(f*L`4`(p`<;H@h|DH^R~OhUSF}xlbFed?jd@k$q`{&4o{o zhfLRb;Xhff`PTcM$LE)W*Yd*BY{(m)ofn;C8pY;87+2&mj zy7Pa#d}#g;CQtJh8^y!L5@)&{_q~VdEGPA)W1ri6@vo%;_f z>x;2O2Y;8`Gi$D;hQ`*sm{>mnPROM)qt=(-fYO!&%EYL$CyQrOTDlKjj?Q6@zZ%yP zY`DUqy8oXtIKr;=jpwS#D*MDFj?wo=toyTZS-blxHXhV8JuF^N_byDzf4{ReT>5Gw z?t?J<{>*@$Z;bWV)u`UTF4r(RE2#i??8hW?Hg9U{y1erpi@I;PbI;C7Pm0R1(tSB; z>D4I{aq0y*wV4m3IgVSN?|u-#0e_FIOD)*lx8&=y*1Cj(4$pPMN{#ql>i+ujV8bKu z$#>bEyKL1znYb4bnOc8;K&tGzz&7j;u-Y}TUUaaB0@&KK`hI?Ol$e%o>H tyQ1E&^$`b7*)wKA0|#%X%}p{6m`sS({TQxZt^ZdZK8yT34=r4k{2y@%iShsd literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/nfc2.png b/app/src/main/res/drawable/nfc2.png new file mode 100644 index 0000000000000000000000000000000000000000..f971f19e6b2f2d074e6cda30cacda6ef3f2ef8f7 GIT binary patch literal 17414 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/nfc3.png b/app/src/main/res/drawable/nfc3.png new file mode 100644 index 0000000000000000000000000000000000000000..50d8d85811bacebfd05a5c9892c9be58072dffcf GIT binary patch literal 17280 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/nfc_signal.xml b/app/src/main/res/drawable/nfc_signal.xml new file mode 100644 index 0000000000..aeb282c9a7 --- /dev/null +++ b/app/src/main/res/drawable/nfc_signal.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_generate.xml b/app/src/main/res/layout/fragment_generate.xml index 7a8df46466..9df86d946a 100644 --- a/app/src/main/res/layout/fragment_generate.xml +++ b/app/src/main/res/layout/fragment_generate.xml @@ -169,6 +169,73 @@ android:textAlignment="textStart" /> + + + + + + + + + + + + + + + + + + + + +