Research on native-only communication with React Native components for security

experiment

#1

Background

Exposing sensitive data to RN is inherently insecure due to the difficulty in controlling hundreds of NPM dependencies that RN (and our app) depends upon. The most obvious target is the TextEdit class which is used to allow the user to enter sensitive text such as passwords. The alternative to the pure RN approach is to cut out the text update notifications that contain the text being typed and reduce the attack surface area as much as possible. The research-phase project does this by using a custom-made ReactSecureEditText class which is used in the same way as RN’s ReactEditText (along with other supporting classes), except for setting and retrieving text contents, which are made available only to native code.

Assumptions

  1. JS code can’t call methods on the native ReactSecureEditText control (where it would e.g. be able to call getText). Some methods have nonetheless been fortified to prevent unknown listeners from registering (e.g., in addTextChangedListener), and we could envision doing the same for getText.
  2. JS code doesn’t need to know the contents of a secure edit box, and native code doesn’t need to be notified of changes to the edit box contents (although this particular functionality wouldn’t be hard to implement if needed).

1. Overriding RN classes

The idea is to still take advantage of ReactSecureTextInputManager/ReactEditText (overriding insecure methods and removing the insecure behavior or making the method throw an exception), and a new JS helper class SecureTextInput (to replace TextInput). The JS code creates <SecureTextInput> with a registrationID property which is used to register the native edit with ReactSecureTextInputManager. Native code can manipulate the contents of the input fields by calling ReactSecureTextInputManager.getText and ReactSecureTextInputManager.setText with the respective values used in registrationID. Other JS code can’t retrieve the text from the edit field or calculate it, because updateExtraData, addEventEmitters and setOnKeyPress have been overridden (and sealed with the final keyword).

2. Custom classes with no reuse of RN superclasses

This approach derives SecureTextInputManager from BaseViewManager<SecureEditText, LayoutShadowNode> and SecureEditText from Android’s EditText. It still reuses some RN helper classes such as ReactViewBackgroundManager, ReactTextInputShadowNode or ContentSizeWatcher. They are not huge classes so we could envision copying them as well, but some might contain dependencies that are impossible to extract.

Pros/cons

  • With method 1, the maintenance burden is much smaller, but more attention must be paid to RN upgrades, to ensure that new insecure public methods aren’t being added in the new release. The risk seems low, as that core functionality is not bound to be frequently changing, but it is still there;
  • With method 2, for maintainability sake, we limit the functionality of the edit control available to JS (e.g. support for setting multi-line, placeholders, images, max length, etc.) to reduce the amount of code that must be copied over from RN and later maintained. It can therefore easily happen that a <SecureTextInput> looks or behaves slightly different from other <TextInput> controls, as it might be lacking some support code from RN.

#2

Great job, Pedro!

I would upvote method 2. @petty what do you think?


#3

Thanks. To me, the real difference between both methods is that method #2 eliminates the chance of a new insecure method being introduced in one of the base classes automatically on an npm update (in exchange for more work upfront keeping the code in sync where needed). Other than that, between a locked-down #1 and a #2 there shouldn’t be much difference security-wise because both still share the base EditText Android class that exposes getText(), meaning that whoever manages to somehow get access to the native control will not see much of a practical difference between approach #1 or #2.


#4

Is it even possible to get a reference to a native subview in JS in React? Because it also differs between platforms, so you should even know which native method to call and I’m not sure that w/o exposing it to JS with @ReactMethod it is possible to JS to even see these methods. I might be wrong though…


#5

I don’t see a way, especially since React-Native doesn’t have a DOM, but I don’t have expert-level knowledge in the RN libraries, so can’t say for sure.