The first edition of this article implemented TLS certificate pinning for React Native apps on Android. Since then, the react-native-cert-pinner package has been enhanced to support pinning on iOS devices, and this edition of the post walks through the previous example for iOS.
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, API and mobile 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 and iOS, with additional package automation and security to follow.
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.
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 the demo-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 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 ./
For iOS, copy in the example podfile
and install the pod manually:
$ cp ./node_modules/react-native-cert-pinner/example/ios/podfile ./ios/$ cd ios && pod install
You should be ready to build and run the app. Ensure an iOS simulator is running or an iOS device is connected, and launch the app:
$ react-native run-ios…
** BUILD SUCCEEDED **
The following commands produced analyzer issues:Analyze …/RCTModuleMethod.mm normal x86_64Analyze …/RCTImageCache.m normal x86_64Analyze …/RCTNetInfo.m normal x86_64(3 commands with analyzer issues)
Installing build/Build/Products/Debug-iphonesimulator/example.appLaunching org.reactjs.native.example.exampleorg.reactjs.native.example.example: 9252
From the opening screen, push the Test Hello button at the bottom of the screen. A successful connection will show a smiley face:
Although the connection was made successfully over TLS, certificate pinning was not used.
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’.Updating plist file ‘./ios/example/info.plist’.
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 source files./ios/example/info.plist
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.
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’.Updating plist file ‘./ios/example/info.plist’.
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.
You have successfully demonstrated a pinning utility for React Native on iOS which uses the built-in fetch() API without requiring any manual edits of native iOS code. See the first edition of this article to follow the same example in Android.
Future package enhancements include:
By simplifying certificate pinning for React Native apps, the 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.