npm package: @quanle94/react-native-cert-pinner
Description: For React Native, pins TLS connections to specific trusted certificates' public keys with configuration for enforcePinning
Author: SKip Hovsmith
Latest version: 0.0.6

# react-native-cert-pinner

This package manages TLS certificate pinning in react-native for Android and iOS.

## Getting started

`$ npm install react-native-cert-pinner --save`

### Mostly automatic installation

`$ react-native link react-native-cert-pinner`

### Manual installation

#### iOS

Add the following line to the project targets in your `Podfile`:

```
pod 'TrustKit', '~> 1.4.2'
```

Then run pod install.

#### Android

1. Open up `android/app/src/main/java/[...]/MainApplication.java`

- Add `import com.criticalblue.reactnative.CertPinnerPackage;` to the imports at the top of the file
- Add `new CertPinnerPackage()` to the list returned by the `getPackages()` method

2. Append the following lines to `android/settings.gradle`:
   ```
   include ':react-native-cert-pinner'
   project(':react-native-cert-pinner').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-cert-pinner/android')
   ```
3. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
   ```
     compile project(':react-native-cert-pinner')
   ```

## Usage

To use the react-native networking utilities, like `fetch()`, certificate pinning must be done in the native app before the app's react-native javascript is run.

Unlike typical installed packages, there is no need to require any modules in the javascript. Everything is setup and enforced inside the native module.

### Certificate Pinning Configuration

A `pinset` utility is provided to help configure the native modules for pinning.

The default setup assumes you are running in your project's home directory. The default configuration file is `./pinset.json`, and the default native android project is assumed to be located at `./android`. Both these locations may be overriden on the command line.

#### Command Help

To get help:

```
$ npx pinset -h

    pinset [command] [options]

      init ..... initialize pinset configuration
      gen ...... generate pinset configuration
      version .. show package version
      help ..... show help menu for a command
```

or for a sub-command:

```
$ npx pinset help gen

    pinset gen [options] [config]

      --android, -a <path> .. path to Android project (defaults to './android')
      --ios, -i <path> ...... path to iOS project (defaults to './ios')
      --force, -f ........... always overwrite existing configuration

      config ................ configuration file - defaults to 'pinset.json'
```

#### Initialization

The first step is to generate a starter configuration:

```
$ npx pinset init
```

This command will not overwrite an existing configuration file unless the `--force` flag is used.

#### Lookup

Next, determine which URLs you want to pin, and determine each certificate's public key hash. A convenient utility is provided by **Report URI** at [https://report-uri.com/home/pkp_hash](https://report-uri.com/home/pkp_hash). Enter a URL to see the current chain of certificate hashes.

Enter the desired public key hashes into the `pinset.json` file:

Note:

- `enforcePinning` is added into the domain config to enable the diabling of PIN validation.
- `selfSignedCerts` is added on the same level as the domains (see example), to allow the setting of public keys of SELF-SIGNED certificates. Ensure that these self-signed certificates are available publicly. If they are not, they should not be added here. If not, the app will crash.
  - Adding these certs will help the server to validate the PINs. Server cannot validate PINs of self-signed certs as these certs are not publicly trusted. Thus, if the public keys of these certs are added here, the server can decode them to obtain the necessary information to validate the PIN. Beware that this only works if the self-signed servers are pubicly accessible. If they are local secured servers, it will not work and cause the app to crash.

```json
{
  "domains": {
    "*.approov.io": {
      "pins": [
        "sha256/0000000000000000000000000000000000000000000",
        "sha256/1111111111111111111111111111111111111111111"
      ]
    },
    "*.criticalblue.com": {
      "pins": [
        "sha256/2222222222222222222222222222222222222222222",
        "sha256/3333333333333333333333333333333333333333333"
      ]
    },
    "*.southeastasia.cloudapp.azure.com": {
      "pins": ["sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"],
      "enforcePinning": false // Disable pinning. Use this when you do not want to validate the server certificate, especially in the case of self-signed or unsecured servers.
    }
  },
  "selfSignedCerts": [] // Base-64 PEM key of the cert. This should only be used for self-signed servers. To obtain this key, use the command `openssl s_client -showcerts -connect <server_name>:<port>` (server_name can be `dev.workforceoptimizer.com`, port should be 443, the default port for secured servvers). The key lies between the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----
}
```

Domains starting with`*.` will include all subdomains.

It is recommended to select multiple hashes with at least one of them being from an intermediate certificate.

#### Generation

Once the configuration is set, generate the native project sources:

```
$ npx pinset gen
Reading config file './pinset.json'.
Updating java file './android/app/src/main/java/com/criticalblue/reactnative/GeneratedCertificatePinner.java'.
Updating plist file './ios/example/info.plist'.
```

Build and run the react-native app, for example:

```
$ react-native run-ios
```

#### Updates

To update the certificate pins, edit the configuration file, regenerate the native sources, and rebuild the app.

Note, there is no way to update the pin sets from javascript while the app is running.

### Certificate Security

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 fines from your repository.

To ignore the default files in a git repository, add to `.gitignore`:

```
# default configuration file
./pinset.json

# default generated android source
./android/app/src/main/java/com/criticalblue/reactnative/GeneratedCertificatePinner.java
./ios/<your project here>/info.plist
```

## Debugging

- Open a git bash console
- Type command `adb logcat -s ADInterceptor`. Log messages under the tag name `ADInterceptor` will appear.
- The log will commonly start with some initialisation messages:

`Getting All Issuers, Count = <number_of_trusted_issuers>` (`number_of_trusted_issuers` is usually 137)

`Generated Certficate Pinner in use`

`Self signed certs: <number_of_registered_self_signed_certs>` (`number_of_registered_self_signed_certs` is the number of entries in the `selfSignedCerts` attribute of the `pinset.json` file.)

- Interceptor will start with the log message `Verifying hostname`, followed by the log message `Intercepting`.
- If the server is properly signed with a trusted CA, the log message `Pass validation - HOST: <host_name>` will be shown, e..g `Pass validation - HOST: qa.workforceoptimizer.com`.
- If the server is self-signed, an unsecured one, or is not pinned, an error log message with the error will be displayed `Failed validation - HOST: <host_name>, <error_message>`.
- If the validation fails, but the server has the enablePinning set to false, this log message `Pinning is disabled for this PIN: <sha256_pin>` will show up. If not, this error log message `Pinning is enabled for this host. Connection aborted.` will show up.