frw / react-native-ssl-public-key-pinning

Simple and secure SSL public key pinning for React Native. No native configuration needed, set up in <5 minutes.
MIT License
163 stars 13 forks source link
android ios network pki react-native ssl ssl-pinning

react-native-ssl-public-key-pinning

MIT License Package Version CI GitHub Repo stars

Simple and secure SSL public key pinning for React Native. Uses OkHttp CertificatePinner on Android and TrustKit on iOS.

🔍Overview

🧰Installation

React Native

npm install react-native-ssl-public-key-pinning

OR for Yarn use:

yarn add react-native-ssl-public-key-pinning

Before building for iOS, make sure to run the following commands:

cd ios && pod install && cd ..

Expo Managed Workflow

npx expo install react-native-ssl-public-key-pinning

and then follow the steps to create a development build or production build

Disable expo-dev-client Network Inspector on iOS (Optional)

If you are building an iOS Expo development build and want to test out your pinning configuration, you will need to disable expo-dev-client's network inspector as it interferes with the pinning setup. Note that the network inspector is automatically disabled on production builds and so this library would function properly on production builds without needing to go through the following steps.

  1. Install expo-build-properties
    npx expo install expo-build-properties
  2. Add the following plugin configuration to your app.json
    
    {
    "expo": {
    "plugins": [
      [
        "expo-build-properties",
        {
          "ios": {
            "networkInspector": false
          } 
        }
      ]
    ]
    }
    }
3. Run prebuild to update native files

npx expo prebuild


## 🚀Usage

1. Retrieve the base64-encoded SHA-256 public key hash of the certificates you want to pin. [More details on how to do this](#public-key-hash)
2. Call `initializeSslPinning` as early as possible in your App entry point with your SSL pinning configuration.
3. All network requests in your app should now have SSL pinning enabled!

### Pinning Example

```js
import { initializeSslPinning } from 'react-native-ssl-public-key-pinning';

await initializeSslPinning({
  'google.com': {
    includeSubdomains: true,
    publicKeyHashes: [
      'CLOmM1/OXvSPjw5UOYbAf9GKOxImEp9hhku9W90fHMk=',
      'hxqRlPTu1bMS/0DITB1SSu0vd4u/8l8TjPgfaAp63Gc=',
      'Vfd95BwDeSQo+NUYxVEEIlvkOlWY2SalKK1lPhzOx78=',
      'QXnt2YHvdHR3tJYmQIr0Paosp6t/nggsEGD4QJZ3Q0g=',
      'mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=',
    ],
  },
});

// ...

// This request will have public key pinning enabled
const response = await fetch('https://www.google.com');

Listener Example

import { addSslPinningErrorListener } from 'react-native-ssl-public-key-pinning';

useEffect(() => {
  const subscription = addSslPinningErrorListener((error) => {
    // Triggered when an SSL pinning error occurs due to pin mismatch
    console.log(error.serverHostname);
  });
  return () => {
    subscription.remove();
  };
}, []);

💡API Reference

API Description
isSslPinningAvailable(): boolean Returns whether the SslPublicKeyPinning NativeModule is available on the current app installation. Useful if you're using Expo Go and want to avoid initializing pinning if it's not available.
initializeSslPinning(options: PinningOptions): Promise<void> Initializes and enables SSL public key pinning for the domains and options you specify.
disableSslPinning(): Promise<void> Disables SSL public key pinning.
addSslPinningErrorListener(callback: ErrorListenerCallback): EmitterSubscription Subscribes to SSL pinning errors due to pin mismatch. Useful if you would like to report errors or inform the user of security issues.

⚙️Options

Option Type Mandatory Description
includeSubdomains boolean No Whether all subdomains of the specified domain should also be pinned. Defaults to false.
publicKeyHashes string[] Yes An array of SSL pins, where each pin is the base64-encoded SHA-256 hash of a certificate's Subject Public Key Info. Note that at least two pins are needed per domain on iOS.
expirationDate string No A string containing the date, in yyyy-MM-dd format, on which the domain’s configured SSL pins expire, thus disabling pinning validation. If this is not set, then the pins do not expire. Expiration helps prevent connectivity issues in Apps which do not get updates to their pin set, such as when the user disables App updates.

📝Additional Notes

Known Issues

Best Practices

🤔FAQ

How do I retrieve the base64-encoded SHA-256 public key hash of my certificates? ### OpenSSL CLI #### Server Run the following command, replacing `` with your server's hostname. ```sh echo | openssl s_client -servername -connect :443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | openssl enc -base64 ``` #### Certificate file ```sh openssl x509 -in certificate.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64 ``` #### Private SSL key 1. Convert private key to public pem key: ``` openssl rsa -in private.key -pubout -out public_key.pem ``` 2. generate hash from public key ``` openssl rsa -in public_key.pem -pubin -outform DER | openssl dgst -sha256 -binary | openssl enc -base64 ``` ### SSL Labs If your site is accessible publicly, you can use https://www.ssllabs.com/ssltest/index.html to retrieve the public key hash of your certificates. ![ssllabs](https://user-images.githubusercontent.com/1888212/224491992-f315c9b0-1cd5-4ad1-a02a-b32a9fc52493.jpg)
How do I ensure that everything is set up correctly? An easy way to test you've set everything up correctly is by temporarily providing the wrong public key hashes to `initializeSslPinning`. For example: ``` await initializeSslPinning({ 'google.com': { includeSubdomains: true, publicKeyHashes: [ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', 'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=', ], }, }); // ... // This request should fail with an error const response = await fetch('https://www.google.com'); ``` Any requests you make to the pinned domain should fail since the server is not providing certificates that match your hashes. You can then switch back to the correct public key hashes while leaving everything else the same, and once you ensure the requests succeed again you'll know you've set it all up correctly!

📚References

🤝Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.