Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add better error handling to prevent crashes when creating sharedPreferences #1267

Merged
merged 13 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main bulk of changes in this PR are autoGenerated. This file contains the main crux of the work on here. 🙏🏾

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
@@ -1,22 +1,48 @@
const path = require("path");
const { getDefaultConfig } = require("@react-native/metro-config");
const { getConfig } = require("react-native-builder-bob/metro-config");

Check warning on line 3 in account-kit/rn-signer/example/metro.config.js

View workflow job for this annotation

GitHub Actions / Lint

'getConfig' is assigned a value but never used
const pkg = require("../package.json");

Check warning on line 4 in account-kit/rn-signer/example/metro.config.js

View workflow job for this annotation

GitHub Actions / Lint

'pkg' is assigned a value but never used

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
Loading