Open powilliam opened 3 weeks ago
Hello @powilliam,
Thank you for reporting the bug. I've seen a couple of issues recently that were caused by R8 in newer versions of RN, and this one seems to be very similar. I'll try to reproduce it tomorrow.
By the way, would you mind sharing your modified bridge with us? It could save me some time debugging the issue and the fix could be potentially released sooner.
Thanks, Tomas from Talsec
opa bão? @tompsota,
that's interesting to hear. I can try different/lower versions of the R8 to see if this issue would stop. btw I totally can share the bridge:
TLDR: it's literally the same implementation as freerasp-react-native/android
, just with some syntax differences in the threat handler and the bridge itself, so I can just keep using the library in the react native side without having to do something crazy for the iOS
// [react-native.config.js]
module.exports = {
dependencies: {
'freerasp-react-native': {
platforms: {
android: null,
},
},
},
}
// [Threat.kt]
package mine
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableArray
sealed class Threat(val value: Int) {
data object AppIntegrity : Threat((10000..999999999).random())
data object PrivilegedAccess : Threat((10000..999999999).random())
data object Debug : Threat((10000..999999999).random())
data object Hooks : Threat((10000..999999999).random())
data object Passcode : Threat((10000..999999999).random())
data object Simulator : Threat((10000..999999999).random())
data object SecureHardwareNotAvailable : Threat((10000..999999999).random())
data object SystemVPN : Threat((10000..999999999).random())
data object DeviceBinding : Threat((10000..999999999).random())
data object UnofficialStore : Threat((10000..999999999).random())
data object ObfuscationIssues : Threat((10000..999999999).random())
data object DevMode : Threat((10000..999999999).random())
companion object {
internal fun getThreatValues(): WritableArray {
return Arguments.fromList(
listOf(
AppIntegrity.value,
PrivilegedAccess.value,
Debug.value,
Hooks.value,
Passcode.value,
Simulator.value,
SecureHardwareNotAvailable.value,
SystemVPN.value,
DeviceBinding.value,
UnofficialStore.value,
ObfuscationIssues.value,
DevMode.value,
)
)
}
}
}
// [FreeraspThreatHandler.kt]
package mine
import com.aheaditec.talsec_security.security.api.ThreatListener
class FreeraspThreatHandler(
val onHandle: (Threat) -> Unit
) : ThreatListener.ThreatDetected,
ThreatListener.DeviceState {
override fun onRootDetected() = onHandle(Threat.PrivilegedAccess)
override fun onDebuggerDetected() = onHandle(Threat.Debug)
override fun onEmulatorDetected() = onHandle(Threat.Simulator)
override fun onTamperDetected() = onHandle(Threat.AppIntegrity)
override fun onUntrustedInstallationSourceDetected() = onHandle(Threat.UnofficialStore)
override fun onHookDetected() = onHandle(Threat.Hooks)
override fun onDeviceBindingDetected() = onHandle(Threat.DeviceBinding)
override fun onObfuscationIssuesDetected() = onHandle(Threat.ObfuscationIssues)
override fun onUnlockedDeviceDetected() = onHandle(Threat.Passcode)
override fun onHardwareBackedKeystoreNotAvailableDetected() = onHandle(Threat.SecureHardwareNotAvailable)
override fun onDeveloperModeDetected() = onHandle(Threat.DevMode)
override fun onSystemVPNDetected() = onHandle(Threat.SystemVPN)
}
// [FreeraspNativeModule.kt]
package mine
import com.aheaditec.talsec_security.security.api.Talsec
import com.aheaditec.talsec_security.security.api.TalsecConfig
import com.aheaditec.talsec_security.security.api.ThreatListener
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableArray
import com.facebook.react.modules.core.DeviceEventManagerModule
class FreeraspNativeModule(
private val reactContext: ReactApplicationContext
) : ReactContextBaseJavaModule(reactContext) {
override fun getName() = NAME
private val handler = FreeraspThreatHandler { threat ->
notifyListeners(threat)
}
private val listener = ThreatListener(handler)
@ReactMethod
fun addListener(eventName: String) {
}
@ReactMethod
fun removeListeners(count: Int) {
}
@ReactMethod
fun talsecStart(options: ReadableMap, promise: Promise) {
try {
val config = options.config()
listener.registerListener(reactContext)
Talsec.start(reactContext, config)
promise.resolve("freeRASP started")
} catch (exception: Exception) {
val params = Arguments.createMap().apply {
putString("message", exception.message)
}
promise.reject("initializationError", params)
}
}
@ReactMethod
fun getThreatIdentifiers(promise: Promise) {
promise.resolve(Threat.getThreatValues())
}
@ReactMethod
fun getThreatChannelData(promise: Promise) {
val channelData: WritableArray = Arguments.createArray().apply {
pushString(THREAT_CHANNEL_NAME)
pushString(THREAT_CHANNEL_KEY)
}
promise.resolve(channelData)
}
@ReactMethod
fun onInvalidCallback() {
android.os.Process.killProcess(android.os.Process.myPid())
}
private fun notifyListeners(threat: Threat) {
val params = Arguments.createMap().apply {
putInt(THREAT_CHANNEL_KEY, threat.value)
}
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(THREAT_CHANNEL_NAME, params)
}
companion object {
const val NAME = "FreeraspReactNative"
val THREAT_CHANNEL_NAME = (10000..999999999).random()
.toString()
val THREAT_CHANNEL_KEY = (10000..999999999).random()
.toString()
}
}
private fun ReadableMap.config(): TalsecConfig {
val androidConfig = getMap("androidConfig")
val packageName = androidConfig?.getString("packageName")!!
val certificateHashes = mutableListOf<String>()
val hashes = androidConfig.getArray("certificateHashes")!!
for (i in 0 until hashes.size()) {
@Suppress("UNNECESSARY_SAFE_CALL")
hashes.getString(i)?.let {
certificateHashes.add(it)
}
}
val watcherMail = getString("watcherMail")
val alternativeStores = mutableListOf<String>()
if (androidConfig.hasKey("supportedAlternativeStores")) {
val stores = androidConfig.getArray("supportedAlternativeStores")!!
for (i in 0 until stores.size()) {
@Suppress("UNNECESSARY_SAFE_CALL")
stores.getString(i)?.let {
alternativeStores.add(it)
}
}
}
var isProd = true
if (hasKey("isProd")) {
isProd = getBoolean("isProd")
}
return TalsecConfig(
packageName,
certificateHashes.toTypedArray(),
watcherMail,
alternativeStores.toTypedArray(),
isProd
)
}
Hi @powilliam,
thanks for sharing the bridge.
Did you modify also FreeraspReactNativePackage
class in any way? Because that's the package which is somehow missing, based on the error message you get.
I tried to set up project with Expo 51, but did not get the error in release build. So I suspect that maybe something went wrong during the upgrade of Expo. Did you try to reinstall the plugin (i.e. remove it and add again)? If not, please try it.
Cheers, Tomas from Talsec
Describe the bug Started right after upgrading from expo SDK 48 to SDK 51, only in Android and in the release build.
Already tried:
react-native config
output is pointing to valid entries atnode_modules/freerasp-react-native
My workaround until now was to wrap the android client myself into a bridge, but that's not ideal as I just don't want to maintain it while there's an official react native library.