diff --git a/app/build.gradle b/app/build.gradle index 589330fb..88bbc75d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,7 +65,7 @@ android { applicationId = 'com.loafwallet' minSdkVersion 31 targetSdkVersion 33 - versionCode 20240424 + versionCode 20240521 versionName "v2.10.0" multiDexEnabled true archivesBaseName = "${versionName}(${versionCode})" diff --git a/app/src/main/java/com/breadwallet/presenter/activities/PaperKeyActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/PaperKeyActivity.java index 87d12b0e..918e8b84 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/PaperKeyActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/PaperKeyActivity.java @@ -115,7 +115,7 @@ public void onClick(BRDialogView brDialogView) { brDialogView.dismissWithAnimation(); } }, null, null, 0); - IllegalArgumentException ex = new IllegalArgumentException("Paper Key error, please contact support at contact@loafwallet.org: " + wordArray.length); + IllegalArgumentException ex = new IllegalArgumentException("Paper Key error, please contact support at support.litewallet.io: " + wordArray.length); Timber.e(ex); throw ex; } else { diff --git a/app/src/main/java/com/breadwallet/presenter/activities/settings/AboutActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/settings/AboutActivity.java index 39abeac5..7df7c604 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/settings/AboutActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/settings/AboutActivity.java @@ -55,6 +55,8 @@ protected void onCreate(Bundle savedInstanceState) { blogShare = (ImageView) findViewById(R.id.blog_share_button); versionText.setText(BRConstants.APP_VERSION_NAME_CODE); + versionText.setText(BRConstants.APP_VERSION_NAME_CODE); + instagramShare.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/app/src/main/java/com/breadwallet/presenter/activities/settings/SettingsActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/settings/SettingsActivity.java index fba4f2b6..4c8d9d22 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/settings/SettingsActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/settings/SettingsActivity.java @@ -128,14 +128,6 @@ private void populateItems() { /*Wallet Title*/ items.add(new BRSettingsItem(getString(R.string.Settings_wallet), "", null, true)); - /*Import Title*/ - items.add(new BRSettingsItem(getString(R.string.Settings_importTitle), "", v -> { - Intent intent = new Intent(SettingsActivity.this, ImportActivity.class); - startActivity(intent); - overridePendingTransition(R.anim.enter_from_bottom, R.anim.empty_300); - - }, false)); - /*Show Seed Phrase*/ items.add(new BRSettingsItem(getString(R.string.settings_show_seed), "", v -> { BRAnimator.showBalanceSeedFragment(this); diff --git a/app/src/main/java/com/breadwallet/presenter/activities/settings/SyncBlockchainActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/settings/SyncBlockchainActivity.java index 941f5be6..bcb5b10b 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/settings/SyncBlockchainActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/settings/SyncBlockchainActivity.java @@ -145,6 +145,14 @@ public void onClick(View v) { } }); + closeButton = (ImageButton) findViewById(R.id.close_button); + closeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onBackPressed(); + } + }); + } diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentSend.kt b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentSend.kt index 081971e7..6cfe35a4 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentSend.kt +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentSend.kt @@ -114,6 +114,7 @@ class FragmentSend : Fragment() { // Hiding until layouts are built. showKeyboard(false) signalLayout.layoutTransition = BRAnimator.getDefaultTransition() + return rootView } diff --git a/app/src/main/java/com/breadwallet/tools/security/KeyStoreManager.java b/app/src/main/java/com/breadwallet/tools/security/KeyStoreManager.java new file mode 100644 index 00000000..798f980a --- /dev/null +++ b/app/src/main/java/com/breadwallet/tools/security/KeyStoreManager.java @@ -0,0 +1,677 @@ +package com.breadwallet.tools.security; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.security.keystore.UserNotAuthenticatedException; +import android.util.Log; + +import com.breadwallet.R; +import com.breadwallet.exceptions.BRKeystoreErrorException; +import com.breadwallet.presenter.activities.IntroActivity; +import com.breadwallet.presenter.activities.MainActivity; +import com.breadwallet.tools.animation.BRAnimator; +import com.breadwallet.tools.util.ByteReader; +import com.breadwallet.tools.manager.SharedPreferencesManager; +import com.breadwallet.tools.util.TypesConverter; +import com.breadwallet.wallet.BRWalletManager; + +import junit.framework.Assert; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.text.Normalizer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.SynchronousQueue; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +/** + * BreadWallet + *

+ * Created by Mihail Gutan on 9/29/15. + * Copyright (c) 2016 breadwallet LLC + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + *

+ * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + *

+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +public class KeyStoreManager { + private static final String TAG = KeyStoreManager.class.getName(); + + public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding"; + public static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; + public static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; + public static final String ANDROID_KEY_STORE = "AndroidKeyStore"; + + public static Map aliasObjectMap; + + private static final String PHRASE_IV = "ivphrase"; + private static final String CANARY_IV = "ivcanary"; + private static final String PUB_KEY_IV = "ivpubkey"; + private static final String WALLET_CREATION_TIME_IV = "ivtime"; + private static final String PASS_CODE_IV = "ivpasscode"; + private static final String FAIL_COUNT_IV = "ivfailcount"; + private static final String SPENT_LIMIT_IV = "ivspendlimit"; + private static final String FAIL_TIMESTAMP_IV = "ivfailtimestamp"; + private static final String AUTH_KEY_IV = "ivauthkey"; + private static final String TOKEN_IV = "ivtoken"; + private static final String PASS_TIME_IV = "passtimetoken"; + + public static final String PHRASE_ALIAS = "phrase"; + public static final String CANARY_ALIAS = "canary"; + public static final String PUB_KEY_ALIAS = "pubKey"; + public static final String WALLET_CREATION_TIME_ALIAS = "creationTime"; + public static final String PASS_CODE_ALIAS = "passCode"; + public static final String FAIL_COUNT_ALIAS = "failCount"; + public static final String SPEND_LIMIT_ALIAS = "spendlimit"; + public static final String FAIL_TIMESTAMP_ALIAS = "failTimeStamp"; + public static final String AUTH_KEY_ALIAS = "authKey"; + public static final String TOKEN_ALIAS = "token"; + public static final String PASS_TIME_ALIAS = "passTime"; + + private static final String PHRASE_FILENAME = "my_phrase"; + private static final String CANARY_FILENAME = "my_canary"; + private static final String PUB_KEY_FILENAME = "my_pub_key"; + private static final String WALLET_CREATION_TIME_FILENAME = "my_creation_time"; + private static final String PASS_CODE_FILENAME = "my_pass_code"; + private static final String FAIL_COUNT_FILENAME = "my_fail_count"; + private static final String SPEND_LIMIT_FILENAME = "my_spend_limit"; + private static final String FAIL_TIMESTAMP_FILENAME = "my_fail_timestamp"; + private static final String AUTH_KEY_FILENAME = "my_auth_key"; + private static final String TOKEN_FILENAME = "my_token"; + private static final String PASS_TIME_FILENAME = "my_pass_time"; + + public static final int AUTH_DURATION_SEC = 300; + + static { + aliasObjectMap = new HashMap<>(); + aliasObjectMap.put(PHRASE_ALIAS, new AliasObject(PHRASE_ALIAS, PHRASE_FILENAME, PHRASE_IV)); + aliasObjectMap.put(CANARY_ALIAS, new AliasObject(CANARY_ALIAS, CANARY_FILENAME, CANARY_IV)); + aliasObjectMap.put(PUB_KEY_ALIAS, new AliasObject(PUB_KEY_ALIAS, PUB_KEY_FILENAME, PUB_KEY_IV)); + aliasObjectMap.put(WALLET_CREATION_TIME_ALIAS, new AliasObject(WALLET_CREATION_TIME_ALIAS, WALLET_CREATION_TIME_FILENAME, WALLET_CREATION_TIME_IV)); + aliasObjectMap.put(PASS_CODE_ALIAS, new AliasObject(PASS_CODE_ALIAS, PASS_CODE_FILENAME, PASS_CODE_IV)); + aliasObjectMap.put(FAIL_COUNT_ALIAS, new AliasObject(FAIL_COUNT_ALIAS, FAIL_COUNT_FILENAME, FAIL_COUNT_IV)); + aliasObjectMap.put(FAIL_COUNT_ALIAS, new AliasObject(FAIL_COUNT_ALIAS, FAIL_COUNT_FILENAME, FAIL_COUNT_IV)); + aliasObjectMap.put(SPEND_LIMIT_ALIAS, new AliasObject(SPEND_LIMIT_ALIAS, SPEND_LIMIT_FILENAME, SPENT_LIMIT_IV)); + aliasObjectMap.put(FAIL_TIMESTAMP_ALIAS, new AliasObject(FAIL_TIMESTAMP_ALIAS, FAIL_TIMESTAMP_FILENAME, FAIL_TIMESTAMP_IV)); + aliasObjectMap.put(AUTH_KEY_ALIAS, new AliasObject(AUTH_KEY_ALIAS, AUTH_KEY_FILENAME, AUTH_KEY_IV)); + aliasObjectMap.put(TOKEN_ALIAS, new AliasObject(TOKEN_ALIAS, TOKEN_FILENAME, TOKEN_IV)); + aliasObjectMap.put(PASS_TIME_ALIAS, new AliasObject(PASS_TIME_ALIAS, PASS_TIME_FILENAME, PASS_TIME_IV)); + + Assert.assertEquals(aliasObjectMap.size(), 11); + Assert.assertEquals(AUTH_DURATION_SEC, 300); + } + + private static android.app.AlertDialog dialog; + + private static boolean _setData(Activity context, byte[] data, String alias, String alias_file, String alias_iv, int request_code, boolean auth_required) { + if (alias.equals(alias_file) || alias.equals(alias_iv) || alias_file.equals(alias_iv)) + throw new IllegalArgumentException("mistake in parameters!"); + try { + KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); + keyStore.load(null); + // Create the keys if necessary + if (!keyStore.containsAlias(alias)) { + KeyGenerator keyGenerator = KeyGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); + + // Set the alias of the entry in Android KeyStore where the key will appear + // and the constrains (purposes) in the constructor of the Builder + keyGenerator.init(new KeyGenParameterSpec.Builder(alias, + KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(BLOCK_MODE) + .setKeySize(256) + .setUserAuthenticationRequired(auth_required) + .setUserAuthenticationValidityDurationSeconds(AUTH_DURATION_SEC) + .setRandomizedEncryptionRequired(false) + .setEncryptionPaddings(PADDING) + .build()); + SecretKey key = keyGenerator.generateKey(); + + } + + String encryptedDataFilePath = getEncryptedDataFilePath(alias_file, context); + + SecretKey secret = (SecretKey) keyStore.getKey(alias, null); + if (secret == null) return false; + Cipher inCipher = Cipher.getInstance(CIPHER_ALGORITHM); + inCipher.init(Cipher.ENCRYPT_MODE, secret); + byte[] iv = inCipher.getIV(); + String path = getEncryptedDataFilePath(alias_iv, context); + boolean success = writeBytesToFile(path, iv); + if (!success) throw new NullPointerException("FAILED TO WRITE BYTES TO FILE"); + CipherOutputStream cipherOutputStream = new CipherOutputStream( + new FileOutputStream(encryptedDataFilePath), inCipher); + cipherOutputStream.write(data); + try { + cipherOutputStream.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return true; + } catch (UserNotAuthenticatedException e) { + Log.e(TAG, Log.getStackTraceString(e)); + showAuthenticationScreen(context, request_code); + } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NullPointerException + | NoSuchPaddingException | KeyStoreException | UnrecoverableKeyException | + InvalidAlgorithmParameterException | NoSuchProviderException | IOException e) { + e.printStackTrace(); + } + return false; + } + + private static byte[] _getData(final Activity context, String alias, String alias_file, String alias_iv, int request_code) + throws BRKeystoreErrorException { + + if (alias.equals(alias_file) || alias.equals(alias_iv) || alias_file.equals(alias_iv)) + throw new RuntimeException("mistake in parameters!"); + Log.e(TAG, "_getData: " + alias); + KeyStore keyStore; + + String encryptedDataFilePath = getEncryptedDataFilePath(alias_file, context); + byte[] result = new byte[0]; + try { + keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); + keyStore.load(null); + SecretKey secretKey = (SecretKey) + keyStore.getKey(alias, null); + if (secretKey == null) { + /** no such key, the key is just simply not there */ + boolean fileExists = new File(encryptedDataFilePath).exists(); + Log.e(TAG, "_getData: " + alias + " file exist: " + fileExists); + if (!fileExists) return result; /** file also not there, fine then */ + showKeyStoreFailedToLoad(context); + throw new BRKeystoreErrorException("no key but the phrase is there"); + } + + if (!new File(getEncryptedDataFilePath(alias_iv, context)).exists() || + !new File(getEncryptedDataFilePath(alias_file, context)).exists()) { + removeAliasAndFiles(alias, context); + return result; + } + + byte[] iv = readBytesFromFile(getEncryptedDataFilePath(alias_iv, context)); + Cipher outCipher; + outCipher = Cipher.getInstance(CIPHER_ALGORITHM); + outCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); + + CipherInputStream cipherInputStream = new CipherInputStream( + new FileInputStream(encryptedDataFilePath), outCipher); + return ByteReader.readBytesFromStream(cipherInputStream); + } catch (InvalidKeyException e) { + Log.e(TAG, "_getData: InvalidKeyException"); + if (e instanceof UserNotAuthenticatedException) { + /**user not authenticated, ask the system for authentication*/ + Log.e(TAG, Log.getStackTraceString(e)); + showAuthenticationScreen(context, request_code); + throw new BRKeystoreErrorException(e.getMessage()); + } else if (e instanceof KeyPermanentlyInvalidatedException) { + showKeyStoreDialog("KeyStore Error", "Your Breadwallet encrypted data was recently invalidated because you " + + "disabled your Android lock screen. Please input your phrase to recover your Breadwallet now.", context.getString(R.string.ok), null, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }, null, new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialogInterface) { + if (context instanceof IntroActivity) { + if (BRAnimator.checkTheMultipressingAvailability()) { + ((IntroActivity) context).showRecoverWalletFragment(); + } + } + } + }); + throw new BRKeystoreErrorException("KeyPermanentlyInvalidatedException"); + } else { + Log.e(TAG, "_getData: InvalidKeyException", e); + showKeyStoreFailedToLoad(context); + throw new BRKeystoreErrorException("Key store error"); + } + } catch (IOException | CertificateException | KeyStoreException e) { + /** keyStore.load(null) threw the Exception, meaning the keystore is unavailable */ + Log.e(TAG, "_getData: keyStore.load(null) threw the Exception, meaning the keystore is unavailable", e); + if (e instanceof FileNotFoundException) { + Log.e(TAG, "_getData: File not found exception", e); + throw new RuntimeException("the key is present but the phrase on the disk no???"); + } else { + showKeyStoreFailedToLoad(context); + throw new BRKeystoreErrorException("Failed to load KeyStore"); + } + + } catch (UnrecoverableKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException e) { + /** if for any other reason the keystore fails, crash! */ + Log.e(TAG, "getData: error: " + e.getClass().getSuperclass().getName()); + throw new RuntimeException(e.getMessage()); + } + } + + private static String getEncryptedDataFilePath(String fileName, Context context) { + String filesDirectory = context.getFilesDir().getAbsolutePath(); + return filesDirectory + File.separator + fileName; + } + + private static void showKeyStoreFailedToLoad(final Activity context) { + showKeyStoreDialog("KeyStore Error", "Failed to load KeyStore. Please try again later or enter your phrase to recover your Breadwallet now.", "recover now", "try later", + context instanceof IntroActivity ? + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (BRAnimator.checkTheMultipressingAvailability()) { + ((IntroActivity) context).showRecoverWalletFragment(); + } + } + } : null, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + context.finish(); + } + }, + null); + } + + public static boolean putKeyStorePhrase(byte[] strToStore, Activity context, int requestCode) { + AliasObject obj = aliasObjectMap.get(PHRASE_ALIAS); + return !(strToStore == null || strToStore.length == 0) && _setData(context, strToStore, obj.alias, obj.datafileName, obj.ivFileName, requestCode, true); + } + + public static byte[] getKeyStorePhrase(final Activity context, int requestCode) + throws BRKeystoreErrorException { + AliasObject obj = aliasObjectMap.get(PHRASE_ALIAS); + return _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); + } + + public static boolean putKeyStoreCanary(String strToStore, Activity context, int requestCode) { + if (strToStore == null || strToStore.isEmpty()) return false; + AliasObject obj = aliasObjectMap.get(CANARY_ALIAS); + byte[] strBytes = new byte[0]; + try { + strBytes = strToStore.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return strBytes.length != 0 && _setData(context, strBytes, obj.alias, obj.datafileName, obj.ivFileName, requestCode, true); + } + + public static String getKeyStoreCanary(final Activity context, int requestCode) + throws BRKeystoreErrorException { + AliasObject obj = aliasObjectMap.get(CANARY_ALIAS); + byte[] data = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); + String result = null; + try { + result = new String(data, "UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return result; + } + + public static boolean putMasterPublicKey(byte[] masterPubKey, Activity context) { + AliasObject obj = aliasObjectMap.get(PUB_KEY_ALIAS); + return masterPubKey != null && masterPubKey.length != 0 && _setData(context, masterPubKey, obj.alias, obj.datafileName, obj.ivFileName, 0, false); + } + + public static byte[] getMasterPublicKey(final Activity context) { + byte[] result = new byte[0]; + AliasObject obj = aliasObjectMap.get(PUB_KEY_ALIAS); + try { + result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); + } catch (BRKeystoreErrorException e) { + e.printStackTrace(); + } + return result; + } + + public static boolean putAuthKey(byte[] authKey, Activity context) { + AliasObject obj = aliasObjectMap.get(AUTH_KEY_ALIAS); + return authKey != null && authKey.length != 0 && _setData(context, authKey, obj.alias, obj.datafileName, obj.ivFileName, 0, false); + } + + public static byte[] getAuthKey(final Activity context) { + AliasObject obj = aliasObjectMap.get(AUTH_KEY_ALIAS); + byte[] result = new byte[0]; + try { + result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); + } catch (BRKeystoreErrorException e) { + e.printStackTrace(); + } + return result; + } + + public static boolean putToken(byte[] token, Activity context) { + AliasObject obj = aliasObjectMap.get(TOKEN_ALIAS); + return token != null && token.length != 0 && _setData(context, token, obj.alias, obj.datafileName, obj.ivFileName, 0, false); + } + + public static byte[] getToken(final Activity context) { + AliasObject obj = aliasObjectMap.get(TOKEN_ALIAS); + byte[] result = new byte[0]; + try { + result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); + } catch (BRKeystoreErrorException e) { + e.printStackTrace(); + } + return result; + } + + public static boolean putWalletCreationTime(int creationTime, Activity context) { + Log.e(TAG, "putWalletCreationTime: " + creationTime); + AliasObject obj = aliasObjectMap.get(WALLET_CREATION_TIME_ALIAS); + byte[] bytesToStore = TypesConverter.intToBytes(creationTime); + return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); + } + + public static int getWalletCreationTime(final Activity context) { + AliasObject obj = aliasObjectMap.get(WALLET_CREATION_TIME_ALIAS); + byte[] result = new byte[0]; + try { + result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); + } catch (BRKeystoreErrorException e) { + e.printStackTrace(); + } + return result.length > 0 ? TypesConverter.bytesToInt(result) : 0; + } + + public static boolean putPassCode(String passcode, Activity context) { + AliasObject obj = aliasObjectMap.get(PASS_CODE_ALIAS); + byte[] bytesToStore = passcode.getBytes(); + return _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); + } + + public static String getPassCode(final Activity context) { + AliasObject obj = aliasObjectMap.get(PASS_CODE_ALIAS); + byte[] result = new byte[0]; + try { + result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); + } catch (BRKeystoreErrorException e) { + e.printStackTrace(); + } + String passCode = new String(result); + try { + int test = Integer.parseInt(passCode); + } catch (Exception e) { + Log.e(TAG, "getPassCode: " + e.getMessage()); + passCode = ""; + putPassCode(passCode, context); + KeyStoreManager.putFailCount(0, context); + KeyStoreManager.putFailTimeStamp(0, context); + return passCode; + } + if (passCode.length() != 4) { + passCode = ""; + putPassCode(passCode, context); + } + return passCode; + } + + public static boolean putFailCount(int failCount, Activity context) { + AliasObject obj = aliasObjectMap.get(FAIL_COUNT_ALIAS); + if (failCount >= 3) { + long time = SharedPreferencesManager.getSecureTime(context); + putFailTimeStamp(time, context); + } + byte[] bytesToStore = TypesConverter.intToBytes(failCount); + return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); + } + + public static int getFailCount(final Activity context) { + AliasObject obj = aliasObjectMap.get(FAIL_COUNT_ALIAS); + byte[] result = new byte[0]; + try { + result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); + } catch (BRKeystoreErrorException e) { + e.printStackTrace(); + } + + return result.length > 0 ? TypesConverter.bytesToInt(result) : 0; + } + + public static boolean putSpendLimit(long spendLimit, Activity context) { + AliasObject obj = aliasObjectMap.get(SPEND_LIMIT_ALIAS); + byte[] bytesToStore = TypesConverter.long2byteArray(spendLimit); + return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); + } + + public static long getSpendLimit(final Activity context) { + AliasObject obj = aliasObjectMap.get(SPEND_LIMIT_ALIAS); + byte[] result = new byte[0]; + try { + result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); + } catch (BRKeystoreErrorException e) { + e.printStackTrace(); + } + + return result.length > 0 ? TypesConverter.byteArray2long(result) : 0; + } + + public static boolean putFailTimeStamp(long spendLimit, Activity context) { + AliasObject obj = aliasObjectMap.get(FAIL_TIMESTAMP_ALIAS); + byte[] bytesToStore = TypesConverter.long2byteArray(spendLimit); + return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); + } + + public static long getFailTimeStamp(final Activity context) { + AliasObject obj = aliasObjectMap.get(FAIL_TIMESTAMP_ALIAS); + byte[] result = new byte[0]; + try { + result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); + } catch (BRKeystoreErrorException e) { + e.printStackTrace(); + } + + return result.length > 0 ? TypesConverter.byteArray2long(result) : 0; + } + + public static boolean putLastPasscodeUsedTime(long time, Activity context) { + AliasObject obj = aliasObjectMap.get(PASS_TIME_ALIAS); + byte[] bytesToStore = TypesConverter.long2byteArray(time); + return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); + } + + public static long getLastPasscodeUsedTime(final Activity context) { + AliasObject obj = aliasObjectMap.get(PASS_TIME_ALIAS); + byte[] result = new byte[0]; + try { + result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); + } catch (BRKeystoreErrorException e) { + e.printStackTrace(); + } + return result.length > 0 ? TypesConverter.byteArray2long(result) : 0; + } + + public static boolean phraseIsValid(String insertedPhrase, Activity activity) { + String normalizedPhrase = Normalizer.normalize(insertedPhrase.trim(), Normalizer.Form.NFKD); + if (!BRWalletManager.getInstance(activity).validatePhrase(activity, normalizedPhrase)) + return false; + BRWalletManager m = BRWalletManager.getInstance(activity); + byte[] rawPhrase = normalizedPhrase.getBytes(); + byte[] bytePhrase = TypesConverter.getNullTerminatedPhrase(rawPhrase); + byte[] pubKey = m.getMasterPubKey(bytePhrase); + byte[] pubKeyFromKeyStore = KeyStoreManager.getMasterPublicKey(activity); + Arrays.fill(bytePhrase, (byte) 0); + return Arrays.equals(pubKey, pubKeyFromKeyStore); + } + + public static boolean resetWalletKeyStore(Context context) { + KeyStore keyStore; + try { + keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); + keyStore.load(null); + int count = 0; + for (String a : aliasObjectMap.keySet()) { + removeAliasAndFiles(a, context); + count++; + } + Assert.assertEquals(count, 11); + + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return false; + } catch (KeyStoreException e) { + e.printStackTrace(); + return false; + } catch (IOException e) { + e.printStackTrace(); + return false; + } catch (java.security.cert.CertificateException e) { + e.printStackTrace(); + } + return true; + } + + public static void removeAliasAndFiles(String alias, Context context) { + KeyStore keyStore; + try { + boolean b1 = new File(getEncryptedDataFilePath(aliasObjectMap.get(alias).datafileName, context)).delete(); + boolean b2 = new File(getEncryptedDataFilePath(aliasObjectMap.get(alias).ivFileName, context)).delete(); + keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); + keyStore.load(null); + keyStore.deleteEntry(alias); + } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + + + } + + public static void showAuthenticationScreen(Activity context, int requestCode) { + // Create the Confirm Credentials screen. You can customize the title and description. Or + // we will provide a generic one for you if you leave it null + Log.e(TAG, "showAuthenticationScreen: " + requestCode); + KeyguardManager mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(context.getString(R.string.auth_required), context.getString(R.string.auth_message)); + if (intent != null) { + context.startActivityForResult(intent, requestCode); + } else { + throw new NullPointerException("no passcode is set"); + } + } + + public static byte[] readBytesFromFile(String path) { + byte[] bytes = null; + try { + File file = new File(path); + FileInputStream fin = new FileInputStream(file); + bytes = ByteReader.readBytesFromStream(fin); + } catch (IOException e) { + e.printStackTrace(); + } + return bytes; + } + + public static boolean writeBytesToFile(String path, byte[] data) { + + FileOutputStream fos = null; + + try { + File file = new File(path); + fos = new FileOutputStream(file); + + // Writes bytes from the specified byte array to this file output stream + fos.write(data); + return true; + } catch (FileNotFoundException e) { + System.out.println("File not found" + e); + } catch (IOException ioe) { + System.out.println("Exception while writing file " + ioe); + } finally { + // close the streams using close method + try { + if (fos != null) { + fos.close(); + } + } catch (IOException ioe) { + System.out.println("Error while closing stream: " + ioe); + } + + } + return false; + } + + private static void showKeyStoreDialog(final String title, final String message, final String posButton, final String negButton, + final DialogInterface.OnClickListener posButtonListener, + final DialogInterface.OnClickListener negButtonListener, + final DialogInterface.OnDismissListener dismissListener) { + Log.e(TAG, "showKeyStoreDialog"); + Activity app = MainActivity.app; + if (app == null) app = IntroActivity.app; + if (app == null) { + Log.e(TAG, "showCustomDialog: FAILED, context is null"); + return; + } + final Activity finalApp = app; + finalApp.runOnUiThread(new Runnable() { + @Override + public void run() { + if (dialog != null && dialog.isShowing()) { + System.out.println("some"); + if (dialog.getOwnerActivity() != null && !dialog.getOwnerActivity().isDestroyed()) + dialog.dismiss(); + else + return; + } + dialog = new android.app.AlertDialog.Builder(finalApp). + setTitle(title) + .setMessage(message) + .setPositiveButton(posButton, posButtonListener) + .setNegativeButton(negButton, negButtonListener) + .setOnDismissListener(dismissListener) + .setIcon(android.R.drawable.ic_dialog_alert) + .show(); + } + }); + } + + private static class AliasObject { + String alias; + String datafileName; + String ivFileName; + + AliasObject(String alias, String datafileName, String ivFileName) { + this.alias = alias; + this.datafileName = datafileName; + this.ivFileName = ivFileName; + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/breadwallet/tools/util/BRConstants.java b/app/src/main/java/com/breadwallet/tools/util/BRConstants.java index fb8b52eb..2b05efe1 100644 --- a/app/src/main/java/com/breadwallet/tools/util/BRConstants.java +++ b/app/src/main/java/com/breadwallet/tools/util/BRConstants.java @@ -30,6 +30,7 @@ private BRConstants() { * Permissions */ public static final int CAMERA_REQUEST_ID = 34; + public static final int GEO_REQUEST_ID = 35; /** * Request codes for auth @@ -102,7 +103,7 @@ private BRConstants() { public static final String WEB_LINK = "https://litewallet.io"; public static final String TOS_LINK = "https://litewallet.io/privacy"; - public static String CUSTOMER_SUPPORT_LINK = "https://support.litewallet.io"; + public static String CUSTOMER_SUPPORT_LINK = "https://support.litewallet.io/hc/en-us/requests/new"; public static String BITREFILL_AFFILIATE_LINK = "https://www.bitrefill.com/"; /** diff --git a/app/src/main/java/com/breadwallet/tools/util/Utils.java b/app/src/main/java/com/breadwallet/tools/util/Utils.java index 38f2c6b2..4018fb75 100644 --- a/app/src/main/java/com/breadwallet/tools/util/Utils.java +++ b/app/src/main/java/com/breadwallet/tools/util/Utils.java @@ -285,7 +285,7 @@ else if (name == PartnerNames.OPSALL) { AnalyticsManager.logCustomEventWithParams(BRConstants._20200112_ERR,params); return ""; } - /// Description: 1713522152 + /// Description: 1715876807 public static long tieredOpsFee(Context app, long sendAmount) { double sendAmountDouble = new Double(String.valueOf(sendAmount)); @@ -293,13 +293,17 @@ public static long tieredOpsFee(Context app, long sendAmount) { CurrencyEntity currency = CurrencyDataSource.getInstance(app).getCurrencyByIso(usIso); double doubleRate = currency.rate; double usdInLTC = sendAmountDouble * doubleRate / 100_000_000.0; + usdInLTC = Math.floor(usdInLTC * 100) / 100; - if (isBetween(usdInLTC, 0.00, 20.00)) { - return (long) ((0.20 / doubleRate) * 100_000_000.0); + if (isBetween(usdInLTC, 0.00, 20.00)) { + double lowRate = usdInLTC * 0.01; + return (long) ((lowRate / doubleRate) * 100_000_000.0); } else if (isBetween(usdInLTC, 20.00, 50.00)) { - return (long) ((0.25 / doubleRate) * 100_000_000.0); - } + Timber.d("timber: usdInLTC 2: %s", usdInLTC); + + return (long) ((0.30 / doubleRate) * 100_000_000.0); + } else if (isBetween(usdInLTC, 50.00, 100.00)) { return (long) ((1.00 / doubleRate) * 100_000_000.0); } diff --git a/app/src/main/jni/transition/PeerManager.c b/app/src/main/jni/transition/PeerManager.c index 79117cef..f512cd9e 100644 --- a/app/src/main/jni/transition/PeerManager.c +++ b/app/src/main/jni/transition/PeerManager.c @@ -277,11 +277,6 @@ Java_com_breadwallet_wallet_BRPeerManager_create(JNIEnv *env, jobject thiz, __android_log_print(ANDROID_LOG_DEBUG, "Message from C: ", "earliestKeyTime: %d", earliestKeyTime); _peerManager = BRPeerManagerNew(&BR_CHAIN_PARAMS, _wallet, (uint32_t) earliestKeyTime, _blocks, - (size_t) blocksCount, - _peers, (size_t) peersCount, (double) fpRate); - BRPeerManagerSetCallbacks(_peerManager, NULL, syncStarted, syncStopped, - txStatusUpdate, - saveBlocks, savePeers, networkIsReachable, threadCleanup); } if (_peerManager == NULL) { diff --git a/app/src/main/res/layout/button_buy_bitcoin.xml b/app/src/main/res/layout/button_buy_bitcoin.xml new file mode 100644 index 00000000..8f2f02f3 --- /dev/null +++ b/app/src/main/res/layout/button_buy_bitcoin.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index a2efd85a..daa2552a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -33,7 +33,7 @@ Fehler - Es liegt ein Problem mit dem KeyStore Ihres Android-Betriebssystems vor, bitte wenden Sie sich an contact@loafwallet.org + Es liegt ein Problem mit dem KeyStore Ihres Android-Betriebssystems vor, bitte wenden Sie sich an support.litewallet.io Ihre mit Loaf verschlüsselten Daten wurden vor Kurzem für ungültig erklärt, da Ihre Android-Bildschirmsperre deaktiviert war. @@ -197,7 +197,7 @@ Wallet verwalten - Litecoin kaufen + Kaufen Wallet sperren diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b01411b9..8134a833 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -33,7 +33,7 @@ Error - Hay un problema con el almacén de claves de tu sistema operativo Android. Ponte en contacto con contact@loafwallet.org. + Hay un problema con el almacén de claves de tu sistema operativo Android. Ponte en contacto con support.litewallet.io. Tus datos codificados en Loaf fueron anulados recientemente debido a que no estaba habilitada tu pantalla de bloqueo de Android. @@ -196,7 +196,7 @@ Gestionar cartera - Comprar Litecoins + Comprar Bloquear cartera diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c49b5218..f17bf6ee 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -33,7 +33,7 @@ Erreur - Un problème est survenu avec votre keystore Android OS, veuillez contacter contact@loafwallet.org + Un problème est survenu avec votre keystore Android OS, veuillez contacter support.litewallet.io Vos données cryptées de Loaf ont été récemment invalidées, car le verrouillage de l\'écran de votre appareil Android a été désactivé. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 35193f7c..89b08a24 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -33,7 +33,7 @@ Errore - C’è un problema con il portachiavi del tuo sistema operativo Android, per favore, contatta contact@loafwallet.org + C’è un problema con il portachiavi del tuo sistema operativo Android, per favore, contatta support.litewallet.io I tuoi dati criptati di Loaf sono stati invalidati recentemente perché la schermata di blocco del tuo Android era disabilitata. @@ -197,7 +197,7 @@ Gestisci Portafoglio - Acquista Litecoin + Acquista Blocca Portafoglio diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 3c450028..9693f557 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -33,7 +33,7 @@ エラー - Android OSのKeystoreに問題があります。contact@loafwallet.org にお問い合わせください。 + Android OSのKeystoreに問題があります。support.litewallet.io にお問い合わせください。 お使いのAndroidロック画面が無効にされたため、暗号化されたブレッドのデータはつい最近無効になりました。 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index f6e4e73b..7c424729 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -33,7 +33,7 @@ 오류 - 귀하의 안드로이드 OS 키스토어에 문제가 있습니다, contact@loafwallet.org로 연락해 주세요 + 귀하의 안드로이드 OS 키스토어에 문제가 있습니다, support.litewallet.io로 연락해 주세요 귀하의 안드로이드 잠금 화면이 꺼져서 귀하의 암호화 된 데이터가 최근 무효화 되었습니다. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 088a4c22..d93dde82 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -33,7 +33,7 @@ Erro - Há um problema com a KeyStore do seu SO Android. Por favor, contacte contact@loafwallet.org + Há um problema com a KeyStore do seu SO Android. Por favor, contacte support.litewallet.io Os seus dados encriptados pelo Loaf foram recentemente invalidados porque o seu ecrã de bloqueio do Android estava desativado. @@ -197,7 +197,7 @@ Gerir Carteira - Comprar Litecoins + Comprar Bloquear Carteira diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f7edf7ea..84f67968 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -33,7 +33,7 @@ Ошибка - Имеется проблема с хранилищем ключей в вашей ОС Android, обратитесь по адресу: contact@loafwallet.org + Имеется проблема с хранилищем ключей в вашей ОС Android, обратитесь по адресу: support.litewallet.io Ваши зашифрованные данные приложения Loaf были недавно аннулированы, так как была отключена блокировка экрана вашего устройства Android. @@ -197,7 +197,7 @@ Управление кошельком - Купить Litecoin + Купить Заблокировать кошелек diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index c05a5cf0..010834f9 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -709,7 +709,7 @@ - "• Купуйте LTC багатьма фіатними парами\n• Сплачуйте кількома способами\n• Глобальний постачальник платежів" + "• Купуйте LTC багатьма фіатними парами\n• Сплачуйте кількома способами\n• Глобальний постачальник платежів" Історія diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 9599fa54..61eb5b89 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -33,7 +33,7 @@ 错误 - 您的安卓操作系统密钥库存在问题,请联系contact@loafwallet.org + 您的安卓操作系统密钥库存在问题,请联系support.litewallet.io 您的 Loaf 加密数据最近因为您的安卓系统屏幕锁定被禁用而失效。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index de0f218c..0c5548d7 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -33,7 +33,7 @@ 錯誤 - 您的 Android OS 金鑰儲存區有問題,請聯絡 contact@loafwallet.org + 您的 Android OS 金鑰儲存區有問題,請聯絡 support.litewallet.io 您的 Loaf 加密資料最近已被判定為無效,因爲您的 Android 螢幕鎖定已被停用。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e9a11854..3353817e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -30,7 +30,7 @@ Error - There is a problem with your Android OS keystore, please contact litewallet@litecoinfoundation.net + There is a problem with your Android OS keystore, please contact support.litewallet.io Your Litewallet encrypted data was recently invalidated because your Android lock screen was disabled.