From 4738c38a009f53d38a8c19ee1661e964ccc3711d Mon Sep 17 00:00:00 2001 From: polstianka Date: Mon, 4 Nov 2024 06:10:20 -0800 Subject: [PATCH] 5.0.9 --- .../java/com/tonapps/wallet/api/Extensions.kt | 4 +- .../wallet/data/account/AccountRepository.kt | 8 ++-- .../wallet/data/account/source/VaultSource.kt | 23 ++++++++++- .../wallet/data/core/ScreenCacheSource.kt | 4 +- .../wallet/data/passcode/PasscodeManager.kt | 22 ++++++++++- .../wallet/data/rn/expo/SecureStoreModule.kt | 4 +- .../tonkeeper/core/history/HistoryHelper.kt | 2 +- .../ui/component/MainRecyclerView.kt | 4 +- .../ui/screen/send/main/SendViewModel.kt | 3 +- .../tonkeeper/usecase/sign/SignProof.kt | 4 +- .../tonkeeper/usecase/sign/SignTransaction.kt | 3 +- apps/wallet/instance/main/build.gradle.kts | 2 +- lib/blockchain/build.gradle.kts | 3 ++ .../ton/extensions/SharedPreferences.kt | 34 ++++++++++------ lib/security/build.gradle.kts | 3 ++ .../java/com/tonapps/security/Security.kt | 39 +++++++++++++------ ui/blur/src/main/java/blur/BlurCompat.kt | 1 + 17 files changed, 119 insertions(+), 44 deletions(-) diff --git a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Extensions.kt b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Extensions.kt index 308281ada..75b15b6db 100644 --- a/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Extensions.kt +++ b/apps/wallet/api/src/main/java/com/tonapps/wallet/api/Extensions.kt @@ -47,11 +47,11 @@ fun withRetry( return null } catch (e: Throwable) { val statusCode = e.getHttpStatusCode() - if (statusCode == 429) { + if (statusCode == 429 || statusCode == 401 || statusCode == 502) { SystemClock.sleep(delay) continue } - if (statusCode >= 500 || statusCode == 404) { + if (statusCode >= 500 || statusCode == 404 || statusCode == 400) { return null } FirebaseCrashlytics.getInstance().recordException(e) diff --git a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/AccountRepository.kt b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/AccountRepository.kt index 690d1d991..58f52d315 100644 --- a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/AccountRepository.kt +++ b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/AccountRepository.kt @@ -226,9 +226,9 @@ class AccountRepository( return vaultSource.getMnemonic(wallet.publicKey) } - suspend fun getPrivateKey(id: String): PrivateKeyEd25519 { - val wallet = database.getAccount(id) ?: return EmptyPrivateKeyEd25519.invoke() - return vaultSource.getPrivateKey(wallet.publicKey) ?: EmptyPrivateKeyEd25519.invoke() + suspend fun getPrivateKey(id: String): PrivateKeyEd25519? { + val wallet = database.getAccount(id) ?: return null + return vaultSource.getPrivateKey(wallet.publicKey) } suspend fun pairLedger( @@ -367,7 +367,7 @@ class AccountRepository( } } - private fun createTonProofToken(wallet: WalletEntity): String? { + private suspend fun createTonProofToken(wallet: WalletEntity): String? { val payload = api.tonconnectPayload() ?: return null return try { val publicKey = wallet.publicKey diff --git a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/source/VaultSource.kt b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/source/VaultSource.kt index c58a71a4a..1f68fa6eb 100644 --- a/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/source/VaultSource.kt +++ b/apps/wallet/data/account/src/main/java/com/tonapps/wallet/data/account/source/VaultSource.kt @@ -9,6 +9,8 @@ import com.tonapps.extensions.getByteArray import com.tonapps.extensions.putByteArray import com.tonapps.security.Security import com.tonapps.security.clear +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import org.ton.api.pk.PrivateKeyEd25519 import org.ton.api.pub.PublicKeyEd25519 import org.ton.mnemonic.Mnemonic @@ -47,8 +49,25 @@ internal class VaultSource(context: Context) { return publicKey } - fun getPrivateKey(publicKey: PublicKeyEd25519): PrivateKeyEd25519? { - return prefs.getPrivateKey(privateKey(publicKey)) + suspend fun getPrivateKey(publicKey: PublicKeyEd25519): PrivateKeyEd25519? = withContext(Dispatchers.IO) { + val privateKey = prefs.getPrivateKey(privateKey(publicKey)) + if (privateKey == null) { + val fromMnemonic = getPrivateKeyFromMnemonic(publicKey) ?: return@withContext null + prefs.edit { + putByteArray(privateKey(publicKey), fromMnemonic.key.toByteArray()) + } + fromMnemonic + } else { + privateKey + } + } + + private fun getPrivateKeyFromMnemonic(publicKey: PublicKeyEd25519): PrivateKeyEd25519? { + val mnemonic = getMnemonic(publicKey) ?: return null + val seed = Mnemonic.toSeed(mnemonic.toList()) + val privateKey = PrivateKeyEd25519(seed) + seed.clear() + return privateKey } private fun privateKey(publicKey: PublicKeyEd25519) = key(PRIVATE_KEY_PREFIX, publicKey) diff --git a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/ScreenCacheSource.kt b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/ScreenCacheSource.kt index eb99b0fee..340954125 100644 --- a/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/ScreenCacheSource.kt +++ b/apps/wallet/data/core/src/main/java/com/tonapps/wallet/data/core/ScreenCacheSource.kt @@ -38,8 +38,8 @@ class ScreenCacheSource( ): ByteArray { try { val file = getFile(name, walletId) - if (!file.exists() || file.length() == 0L) { - throw IllegalStateException("File not found: ${file.absolutePath}") + if (!file.exists() || file.length() == 0L || !file.canRead()) { + return byteArrayOf() } return file.readBytes() } catch (e: Throwable) { diff --git a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeManager.kt b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeManager.kt index d6231e45f..244089370 100644 --- a/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeManager.kt +++ b/apps/wallet/data/passcode/src/main/java/com/tonapps/wallet/data/passcode/PasscodeManager.kt @@ -62,6 +62,7 @@ class PasscodeManager( rnLegacy.hasPinCode() } } catch (e: Throwable) { + FirebaseCrashlytics.getInstance().recordException(e) false } } @@ -185,12 +186,29 @@ class PasscodeManager( private suspend fun confirmationMigration( context: Context, ): Boolean = withContext(Dispatchers.Main) { - try { - val passcode = if (settingsRepository.biometric) { + val passcodeByBiometric: String? = try { + if (settingsRepository.biometric) { rnLegacy.exportPasscodeWithBiometry() } else { + null + } + } catch (e: Throwable) { + FirebaseCrashlytics.getInstance().recordException(e) + null + } + + val passcodeByDialog: String? = try { + if (passcodeByBiometric.isNullOrEmpty()) { PasscodeDialog.request(context) + } else { + null } + } catch (e: Throwable) { + FirebaseCrashlytics.getInstance().recordException(e) + null + } + try { + val passcode = passcodeByBiometric ?: passcodeByDialog if (passcode.isNullOrBlank()) { throw Exception("failed to request passcode") } diff --git a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/SecureStoreModule.kt b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/SecureStoreModule.kt index a79af704b..a331c3eec 100644 --- a/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/SecureStoreModule.kt +++ b/apps/wallet/data/rn/src/main/java/com/tonapps/wallet/data/rn/expo/SecureStoreModule.kt @@ -249,7 +249,7 @@ internal class SecureStoreModule( return keyStoreEntryClass.cast(entry) } - private fun getKeyEntry( + private suspend fun getKeyEntry( keyStoreEntryClass: Class, encryptor: KeyBasedEncryptor, options: SecureStoreOptions, @@ -273,7 +273,7 @@ internal class SecureStoreModule( return keyStoreEntry } - private fun getPreferredKeyEntry( + private suspend fun getPreferredKeyEntry( keyStoreEntryClass: Class, encryptor: KeyBasedEncryptor, options: SecureStoreOptions, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt index 5eb471ab0..423b4f50a 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/core/history/HistoryHelper.kt @@ -168,7 +168,7 @@ class HistoryHelper( } it }.map { wallet -> - val privateKey = accountRepository.getPrivateKey(wallet.id) + val privateKey = accountRepository.getPrivateKey(wallet.id) ?: throw Exception("Private key not found") val decrypted = CommentEncryption.decryptComment( wallet.publicKey, privateKey, diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/MainRecyclerView.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/MainRecyclerView.kt index d2bef83fd..ce3b15276 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/MainRecyclerView.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/component/MainRecyclerView.kt @@ -66,7 +66,7 @@ class MainRecyclerView @JvmOverloads constructor( } override fun dispatchDraw(canvas: Canvas) { - if (topBlur == null) { + if (topBlur == null || !topBlur.hasBlur) { super.dispatchDraw(canvas) } else { topBlur.draw(canvas) { @@ -76,7 +76,7 @@ class MainRecyclerView @JvmOverloads constructor( } override fun draw(canvas: Canvas) { - if (bottomBlur == null) { + if (bottomBlur == null || !bottomBlur.hasBlur) { super.draw(canvas) } else { bottomBlur.draw(canvas) { diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt index 443a744a6..ec5c9e578 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/ui/screen/send/main/SendViewModel.kt @@ -1,6 +1,7 @@ package com.tonapps.tonkeeper.ui.screen.send.main import android.app.Application +import android.util.Log import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.blockchain.ton.contract.WalletFeature @@ -939,7 +940,7 @@ class SendViewModel( _uiEventFlow.tryEmit(SendEvent.Loading) Triple(boc, transfer.wallet, internalMessage) }.catch { - FirebaseCrashlytics.getInstance().recordException(it) + FirebaseCrashlytics.getInstance().recordException(Throwable("SendViewModel sign failed", it)) if (it !is CancellationException) { _uiEventFlow.tryEmit(SendEvent.Failed(it)) } diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/sign/SignProof.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/sign/SignProof.kt index d44b7d548..3f9f51c95 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/sign/SignProof.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/sign/SignProof.kt @@ -91,9 +91,11 @@ class SignProof( throw CancellationException("Passcode cancelled") } + val privateKey = accountRepository.getPrivateKey(wallet.id) ?: throw Throwable("Private key not found") + return TONProof.sign( address = wallet.contract.address, - secretKey = accountRepository.getPrivateKey(wallet.id), + secretKey = privateKey, payload = payload, domain = domain ) diff --git a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/sign/SignTransaction.kt b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/sign/SignTransaction.kt index 964d120c0..dab021ff6 100644 --- a/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/sign/SignTransaction.kt +++ b/apps/wallet/instance/app/src/main/java/com/tonapps/tonkeeper/usecase/sign/SignTransaction.kt @@ -1,5 +1,6 @@ package com.tonapps.tonkeeper.usecase.sign +import android.util.Log import com.tonapps.blockchain.ton.extensions.EmptyPrivateKeyEd25519.sign import com.tonapps.blockchain.ton.extensions.hex import com.tonapps.ledger.ton.Transaction @@ -111,7 +112,7 @@ class SignTransaction( if (!isValidPasscode) { throw SendException.WrongPasscode() } - val privateKey = accountRepository.getPrivateKey(wallet.id) + val privateKey = accountRepository.getPrivateKey(wallet.id) ?: throw SendException.UnableSendTransaction() val hash = privateKey.sign(unsignedBody.hash()) BitString(hash) } diff --git a/apps/wallet/instance/main/build.gradle.kts b/apps/wallet/instance/main/build.gradle.kts index e09abb9fe..9d4b7be84 100644 --- a/apps/wallet/instance/main/build.gradle.kts +++ b/apps/wallet/instance/main/build.gradle.kts @@ -24,7 +24,7 @@ android { targetSdk = 34 versionCode = 600 - versionName = "5.0.8" // Format is "major.minor.patch" (e.g. "1.0.0") and only numbers are allowed + versionName = "5.0.9" // Format is "major.minor.patch" (e.g. "1.0.0") and only numbers are allowed testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/lib/blockchain/build.gradle.kts b/lib/blockchain/build.gradle.kts index 484f02016..4baaf07ec 100644 --- a/lib/blockchain/build.gradle.kts +++ b/lib/blockchain/build.gradle.kts @@ -23,6 +23,9 @@ android { } dependencies { + api(platform(Dependence.Firebase.bom)) + api(Dependence.Firebase.crashlytics) + api(Dependence.TON.tvm) api(Dependence.TON.crypto) api(Dependence.TON.tlb) diff --git a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/SharedPreferences.kt b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/SharedPreferences.kt index eba0ce89a..d3932a8ed 100644 --- a/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/SharedPreferences.kt +++ b/lib/blockchain/src/main/java/com/tonapps/blockchain/ton/extensions/SharedPreferences.kt @@ -2,33 +2,45 @@ package com.tonapps.blockchain.ton.extensions import android.content.SharedPreferences import android.util.Base64 +import android.util.Log +import com.google.firebase.crashlytics.FirebaseCrashlytics import com.tonapps.base64.decodeBase64 +import io.ktor.util.decodeBase64Bytes import org.ton.api.pk.PrivateKeyEd25519 fun SharedPreferences.getPrivateKey(key: String): PrivateKeyEd25519? { - return getPrivateKey2(key) ?: getPrivateKey1(key) + val base64 = getString(key, null) + if (base64.isNullOrEmpty()) { + return null + } + return decodePrivateKey2(key) ?: decodePrivateKey1(key) ?: decodePrivateKey3(key) } -private fun SharedPreferences.getPrivateKey1(key: String): PrivateKeyEd25519? { +// Bad hack to decode private key.... + +private fun decodePrivateKey1(base64: String): PrivateKeyEd25519? { try { - val base64 = getString(key, null) - if (base64.isNullOrEmpty()) { - return null - } return PrivateKeyEd25519(base64.decodeBase64()) } catch (e: Throwable) { + FirebaseCrashlytics.getInstance().recordException(e) return null } } -private fun SharedPreferences.getPrivateKey2(key: String): PrivateKeyEd25519? { +private fun decodePrivateKey2(base64: String): PrivateKeyEd25519? { try { - val base64 = getString(key, null) - if (base64.isNullOrEmpty()) { - return null - } return PrivateKeyEd25519(Base64.decode(base64, Base64.DEFAULT)) } catch (e: Throwable) { + FirebaseCrashlytics.getInstance().recordException(e) + return null + } +} + +private fun decodePrivateKey3(base64: String): PrivateKeyEd25519? { + try { + return PrivateKeyEd25519(base64.decodeBase64Bytes()) + } catch (e: Throwable) { + FirebaseCrashlytics.getInstance().recordException(e) return null } } \ No newline at end of file diff --git a/lib/security/build.gradle.kts b/lib/security/build.gradle.kts index 7e9bd1a8e..be37559a7 100644 --- a/lib/security/build.gradle.kts +++ b/lib/security/build.gradle.kts @@ -40,6 +40,9 @@ android { } dependencies { + api(platform(Dependence.Firebase.bom)) + api(Dependence.Firebase.crashlytics) + implementation(Dependence.KotlinX.coroutines) implementation(Dependence.AndroidX.security) implementation(project(Dependence.Lib.extensions)) diff --git a/lib/security/src/main/java/com/tonapps/security/Security.kt b/lib/security/src/main/java/com/tonapps/security/Security.kt index e3ead155d..adfbd947f 100644 --- a/lib/security/src/main/java/com/tonapps/security/Security.kt +++ b/lib/security/src/main/java/com/tonapps/security/Security.kt @@ -1,5 +1,6 @@ package com.tonapps.security +import android.app.KeyguardManager import android.content.Context import android.content.Intent import android.content.IntentFilter @@ -10,6 +11,7 @@ import android.os.Build import android.provider.Settings import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties +import android.security.keystore.UserNotAuthenticatedException import android.util.Log import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKeys @@ -42,18 +44,31 @@ object Security { @Synchronized fun pref(context: Context, keyAlias: String, name: String): SharedPreferences { - Log.d("SecurityPrefLog", "pref: $keyAlias") - KeyHelper.createIfNotExists(keyAlias) - - return EncryptedSharedPreferences.create( - name, - keyAlias, - context, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) - - // UserNotAuthenticatedException + try { + KeyHelper.createIfNotExists(keyAlias) + + return EncryptedSharedPreferences.create( + name, + keyAlias, + context, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } catch (e: UserNotAuthenticatedException) { + openUserAuthentication(context) + throw e + } catch (e: Throwable) { + throw e + } + } + + private fun openUserAuthentication(context: Context) { + try { + val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager + val intent = keyguardManager.createConfirmDeviceCredentialIntent("Tonkeeper", "Auth") ?: return + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } catch (ignored: Throwable) { } } fun generatePrivateKey(keySize: Int): SecretKey { diff --git a/ui/blur/src/main/java/blur/BlurCompat.kt b/ui/blur/src/main/java/blur/BlurCompat.kt index 151ccafca..7042fc37d 100644 --- a/ui/blur/src/main/java/blur/BlurCompat.kt +++ b/ui/blur/src/main/java/blur/BlurCompat.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Canvas import android.graphics.RectF import android.os.Build +import android.util.Log import androidx.annotation.RequiresApi import blur.node.api31.BlurNode import blur.node.api31.ContentNode