paint-brush
Strengthen TLS in React Native through Certificate Pinningby@skiph
17,167 reads
17,167 reads

Strengthen TLS in React Native through Certificate Pinning

by Skip HovsmithAugust 13th, 2018
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Beginning in July 2018 with the 68 release, Chrome began marking all sites not running HTTPS (TLS over HTTP) as “not secure”. TLS uses site certificates to establish a chain of trust and encrypt communication at the transport layer.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Strengthen TLS in React Native through Certificate Pinning
Skip Hovsmith HackerNoon profile picture

Enhance React Native’s networking API protection without touching your Javascript code or manually editing the native code projects.

Beginning in July 2018 with the 68 release, Chrome began marking all sites not running HTTPS (TLS over HTTP) as “not secure”. TLS uses site certificates to establish a chain of trust and encrypt communication at the transport layer.

Source: Google Security Blog

This is a significant boost in networking and API security, but especially on mobile devices, it may not be enough. Unfortunately, it is too easy to spoof mobile devices into trusting certificates signed by unexpected certificate authorities. Certificate Pinning should be used to limit trust to website leaf certificates or only those intermediate or root authorities trusted by the app itself.

Certificate pinning is not as popular as it should be because of the perceived implementation and maintenance difficulties. With React Native, it is even more challenging, because the networking interface required to implement pinning is not exposed at the javascript layer.

Currently available packages supporting certificate pinning in React Native require replacement of the built in networking package or manual changes to native code. This npm package, react-native-cert-pinner, pins network fetches without requiring any changes to the javascript code. Underlying native code used to pin the connections is fully generated from a developer-specified JSON configuration file. This is a work in progress, currently available on Android, with iOS to follow, along with additional package automation and security.

Preventing Man in the Middle Attacks

A Year of React Native: SSL Pinning does a nice job of describing risks to a mobile connection, even when using TLS. Compromises to a certificate authority or mobile device can cause an app to improperly trust a spoofed server certificate and allow an attacker to insert itself in the middle of the connection, silently decrypting, observing, possibly modifying, and re-encrypting supposedly secure communications.

Certificate Pinning builds on existing HTTPS (SSL or TLS over HTTP) techniques. With TLS, the mobile device follows the chain of certificates until it reaches a certificate signed by an authority it trusts.

Certificate pinning is used to identify specific certificates or limit the number of certificate authorities trusted to sign for a target website. By pinning a limited list of trusted server certificates within the app, fraudulently signed certificates, even if their certificate authorities are trusted by the device, will be rejected by the app. The app can pin a server’s leaf and intermediate certificates.

It is generally recommended to pin multiple certificate’s public keys so that the app can still trust one key if other keys are compromised.

SSL pinning is a mitigation method designed to reduce the effectiveness of MiTM attacks enabled by spoofing a back-end server’s SSL certificate. Pinning on intermediate keys eases certificate rotation and renewals. Checking the hash of a public key is convenient and hides certificate information from any attackers.

React Native Example App

The npm react-native-cert-pinner module contains an example app which we will use to demonstrate certificate pinning. The app checks the HTTPS connection to thedemo-server.approovr.io server:

$ curl https://demo-server.approovr.io

Hello World!

Because TLS is not exposed through React Native networking calls such as fetch(), a native module must be introduced, and the Expo environment cannot be easily used for development.

Start by initializing a React Native project using react-native-cli:


$ react-native init exampleInstalling react-native...

Next install the [react-native-cert-pinner](https://www.npmjs.com/package/react-native-cert-pinner) package:





$ cd example$ npm install -S react-native-cert-pinner+ [email protected] 4 packages from 2 contributors and audited packages in 6.689sfound 0 vulnerabilities

Use react-native to automatically link the cert pinner native module:










$ react-native linkScanning folders for symlinks in /Users/skiph/Projects/rn-pinning/rncp-test/example/node_modules (13ms)rnpm-install info Linking react-native-cert-pinner ios dependencyrnpm-install info Platform 'ios' module react-native-cert-pinner hasbeen successfully linkedrnpm-install info Linking react-native-cert-pinner androiddependencyrnpm-install info Platform 'android' module react-native-cert-pinnerhas been successfully linked

Delete the default index.js and App.js files and install index.js and src/ files from the example directory in the cert pinner package:



$ rm ./index.js ./App.js$ cp ./node_modules/react-native-cert-pinner/example/index.js ./$ cp -r ./node_modules/react-native-cert-pinner/example/src ./

You should be ready to build and run the app. Ensure an Android emulator is running or an Android device is connected and launch the app:




$ react-native run-android...installing APK 'app-debug.apk' on 'Pixel_2_API_25' for app:debugInstalled on 1 device.

BUILD SUCCESSFUL





Total time: 15.768 secsRunning adb -s emulator-5554 reverse tcp:8081 tcp:8081Starting the app on emulator-5554 (adb -s emulator-5554 shell am start -n com.example/com.example.MainActivity)...Starting: Intent { cmp=com.example/.MainActivity }

From the opening screen, push the TEST HELLO button. A successful connection will show a smiley face:

Although the connection was made successfully over TLS, certificate pinning was not used.

Pinning a Trusted Certificate

To add certificate pinning, start by initializing a pinset configuration file in the home directory of the example project:


$ npx pinset initFile './pinset.json' initialized.

Next, determine several public key hashes from the chain of certificates used for demo-server.approovr.io. Report URI has a convenient look up service at https://report-uri.com/home/pkp_hash. As this was being written, the available public key hashes are:

Edit pinset.json to pin a few of these key hashes:










{"domains": {"*.approovr.io": {"pins": ["sha256/oq+Uj+2TYMg13txh1pXW0/VLAkonU3TnoPr5hfxPZVc=","sha256/8Rw90Ej3Ttt8RRkrg+WYDS9n7IS03bk5bjP/UXPtaY8="]}}}

In a production app, you would add pins for each server domain your app communicates with. If you connect to many servers, consider using an API proxy gateway to improve API protection and reduce the number of pin sets you need to manage.

Generate the required native project files by running pinset gen:

$ npx pinset genReading config file './pinset.json'.Writing java file './android/app/src/main/java/com/criticalblue/reactnative/  GeneratedCertificatePinner.java'.

If you consider publishing hashes of public key certificates to be a security breach, you may want to remove or ignore the pinset configuration and generated files from your repository. In your root `.gitignore` file, add:


# default configuration file./pinset.json


# default generated android source./android/app/src/main/java/com/criticalblue/reactnative/GeneratedCertificatePinner.java

Rebuild and launch the modified app. You should again see a successful connection, but this time the connection is pinned by at least one of the public key hashes.

Rejecting an Unrecognized Certificate

To test certificate pinning, change the *.approovr.io public key hashes in pinset.json so they do not match any of the expected values:









{"domains": {"*.approovr.io": {"pins": ["sha256/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"]}}}

Regenerate the native project files by running pinset gen:

$ npx pinset genReading config file './pinset.json'.Writing java file './android/app/src/main/java/com/criticalblue/reactnative/  GeneratedCertificatePinner.java'.

Rebuild and launch the modified app. This time you should see a connection failure, because the app could not find a public key hash which matched any of its expected pins.

Wrapping Up For Now

You have successfully demonstrated a pinning utility for React Native on Android which uses the built-in fetch() API without requiring any manual edits of native Android code.

Future enhancements include:

  • Adding equivalent iOS support.
  • Automatically regenerating native source files whenever the pin set configuration changes.
  • Adding source regeneration and git ignores to the mostly automatic react-native linking step.
  • Adding certificate public key hash lookup to the pinset utility.
  • Strengthening security of pinset information within the app.

By simplifying certificate pinning for React Native apps, the [react-native-cert-pinner](https://www.npmjs.com/package/react-native-cert-pinner) package should help more developers use these techniques to strengthen the integrity of their mobile API connections.

To learn more about API security and related topics, visit approov.io or follow @critblue on twitter.