Skip to content

Commit

Permalink
feat: add better error handling to prevent crashes when creating shar…
Browse files Browse the repository at this point in the history
…edPreferences (#1267)

* feat: update sharedPreferences creation to properly handle creation errors gracefully

* feat: add extra error handling while using getSharedPreferences() method

* feat: add better error handling to prevent app crashes

* fix: update linting

* fix: update getSharedPreferences() implementation to create the masterKey internally

* fix: remove generated gradele files

* fix: refactor TEKStamper logic to reduce try-catch nesting

* fix: refactor TEKStamper logic to reduce try-catch nesting

* fix: fix linting

* fix: change exception variables
  • Loading branch information
iykazrji authored Jan 9, 2025
1 parent 06d9ded commit 2d5dae9
Show file tree
Hide file tree
Showing 15 changed files with 684 additions and 34 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.accountkit.reactnativesigner.core

import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.accountkit.reactnativesigner.core.errors.NoInjectedBundleException
Expand All @@ -19,6 +20,8 @@ import java.nio.ByteBuffer
import java.security.KeyFactory
import java.security.Security
import java.security.Signature
import java.security.KeyStore
import java.security.KeyStoreException

@Serializable
data class ApiStamp(val publicKey: String, val scheme: String, val signature: String)
Expand All @@ -27,6 +30,8 @@ data class Stamp(val stampHeaderName: String, val stampHeaderValue: String)

private const val BUNDLE_PRIVATE_KEY = "BUNDLE_PRIVATE_KEY"
private const val BUNDLE_PUBLIC_KEY = "BUNDLE_PUBLIC_KEY"
private const val MASTER_KEY_ALIAS = "tek_master_key"
private const val ENCRYPTED_SHARED_PREFERENCES_FILENAME = "tek_stamper_shared_prefs"

class TEKStamper(context: Context) {
// This is how the docs for EncryptedSharedPreferences recommend creating this setup
Expand All @@ -36,12 +41,6 @@ class TEKStamper(context: Context) {
//
// we should explore the best practices on how to do this once we reach a phase of further
// cleanup
private val masterKey =
MasterKey.Builder(context.applicationContext)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
// requires that the phone be unlocked
.setUserAuthenticationRequired(false)
.build()

/**
* We are using EncryptedSharedPreferences to store 2 pieces of data
Expand All @@ -64,29 +63,35 @@ class TEKStamper(context: Context) {
*
* The open question is if the storage of the decrypted private key is secure enough though
*/
private val sharedPreferences =
EncryptedSharedPreferences.create(
context,
"tek_stamper_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)


private val tekManager = HpkeTEKManager(sharedPreferences)

init {
TinkConfig.register()



if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME).javaClass !=
BouncyCastleProvider::class.java
) {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
}

if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(BouncyCastleProvider())
private lateinit var tekManager: HpkeTEKManager
private lateinit var sharedPreferences: SharedPreferences

init {
try {
TinkConfig.register()

sharedPreferences = getSharedPreferences(context)
tekManager = HpkeTEKManager(sharedPreferences)

if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME).javaClass !=
BouncyCastleProvider::class.java
) {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
}

if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(BouncyCastleProvider())
}
} catch (e: Exception){
throw RuntimeException("Error creating master key", e)
}

}

fun init(): String {
Expand Down Expand Up @@ -196,4 +201,58 @@ class TEKStamper(context: Context) {
)
return Pair(compressedPublicKey, privateKey)
}

private fun createSharedPreferences(masterKey: MasterKey, context: Context): SharedPreferences {
return EncryptedSharedPreferences.create(
context,
ENCRYPTED_SHARED_PREFERENCES_FILENAME,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}

private fun createMasterKey(context: Context): MasterKey {
return MasterKey.Builder(context.applicationContext, MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.setUserAuthenticationRequired(false)
.build()
}


private fun getSharedPreferences(context: Context): SharedPreferences {
try {
// Attempt to create or load the EncryptedSharedPreferences file
val masterKey = createMasterKey(context)

return createSharedPreferences(masterKey, context)
} catch(e: Exception) {
// Log the Exception
e.printStackTrace()
}

// An error occured creating or retrieving the Shared Preferences file.
// Delete the existing master key and EncryptedSharedPreferences

// first delete the MasterKey
try {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
keyStore.deleteEntry(MASTER_KEY_ALIAS)
} catch (keyStoreDeletionException: Exception) {
throw RuntimeException("An error occured deleting the Master Key", keyStoreDeletionException)
}

// attempt to recreate a new EncryptedSharedPreferences file
try {
// Create a new MasterKey
val newMasterKey = createMasterKey(context)
context.getSharedPreferences(ENCRYPTED_SHARED_PREFERENCES_FILENAME, Context.MODE_PRIVATE).edit().clear().apply()
context.deleteSharedPreferences(ENCRYPTED_SHARED_PREFERENCES_FILENAME)

return createSharedPreferences(newMasterKey, context)
} catch(retryException: Exception) {
throw RuntimeException("Couldn't create the required shared preferences file. Ensure you are properly authenticated on this device.", retryException)
}
}
}
44 changes: 35 additions & 9 deletions account-kit/rn-signer/example/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,46 @@ const { getDefaultConfig } = require("@react-native/metro-config");
const { getConfig } = require("react-native-builder-bob/metro-config");
const pkg = require("../package.json");

const root = path.resolve(__dirname, "..");
const rnSignerRoot = path.resolve(__dirname, "..");
const projectRoot = __dirname;
// handles the hoisted modules
const repoRoot = path.resolve(__dirname, "../../..");

const config = getDefaultConfig(projectRoot);

const monorepoPackages = {
"@account-kit/signer": path.resolve(repoRoot, "account-kit/signer"),
"@aa-sdk/core": path.resolve(repoRoot, "aa-sdk/core"),
"@account-kit/logging": path.resolve(repoRoot, "account-kit/logging"),
};

config.watchFolders = [
projectRoot,
rnSignerRoot,
...Object.values(monorepoPackages),
];

// Let Metro know where to resolve packages and in what order
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(rnSignerRoot, "node_modules"),
path.resolve(repoRoot, "node_modules"),
];

// Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
config.resolver.disableHierarchicalLookup = true;

config.resolver.extraNodeModules = {
...config.resolver.extraNodeModules,
...require("node-libs-react-native"),
...monorepoPackages,
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
};

/**
* Metro configuration
* https://facebook.github.io/metro/docs/configuration
*
*/
module.exports = {
...getConfig(getDefaultConfig(__dirname), {
root,
pkg,
project: __dirname,
}),
watchFolders: [root, repoRoot],
};
module.exports = config;

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 2d5dae9

Please sign in to comment.