How to implement SSL Pinning in Ionic 5?

When we develop ionic or HTML5 hybrid application often we ignore some of the goofy security stuff, however, when a client wants to do security testing checkup, we need to implement many things which we really don’t care at the time of development, one of such thing is SSL Pinning

The pinning is an optional mechanism that can be used to improve the security of a service or site that relies on SSL Certificates. Pinning allows you to specify a cryptographic identity that should be accepted by users visiting your website

It’s a two-part process, first, we need to get a certificate from our API server where we host our backend services. second, we will need to implement SSL Pinning in the ionic app. Without further ado, let’s start the first part:

Step 1: Get the SSL Certificate from API Server.

Let’s pretend that our API server is otricks.com we can get an SSL certificate for the same, I will show you a very easy way to get it via Firefox browser.

Go to otricks.com or your API endpoint with https

When you click on PEM(cert), it will download otricks-com.pem file which you can convert it to filename.cer file extension. You need to have OpenSSL tool install in your machine, I have it already in my Macbook Pro. You can google it and install it.

Open a terminal window and run below command which will convert .pem a file to .cer file, change “nameOfPemFile.pem ” with your file and adjust the path accordingly. Once you run below command it will convert your pem file to cer file which we need for our next step.

openssl x509 -inform PEM -in /Users/anjum/Downloads/nameOfPemFile.pem -outform DER -out /Users/anjum/Downloads/certificate.cer

Step 2: Create a new Ionic 5 project.

Let’s create a new empty project with the Ionic CLI tool

ionic start sslpinning tabs

Open the project and edit the angular.json file add certificates block something like below. check app => architect => build => assets section. This way Angular will copy our certificates inside www folder of ionic when we build project

{
    "glob": "**/*",
    "input": "src/certificates",
    "output": "certificates"
},
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "defaultProject": "app",
  "newProjectRoot": "projects",
  "projects": {
    "app": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "www",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "assets": [
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "assets"
              },
              {
                "glob": "**/*",
                "input": "src/certificates",
                "output": "certificates"
              },
              {
                "glob": "**/*.svg",
                "input": "node_modules/ionicons/dist/ionicons/svg",
                "output": "./svg"
              }
            ],
            "styles": [
              {
                "input": "src/theme/variables.scss"
              },
              {
                "input": "src/global.scss"
              }
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            },
            "ci": {
              "progress": false
            }
          }
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "app:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "app:build:production"
            },
            "ci": {
              "progress": false
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "app:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "styles": [],
            "scripts": [],
            "assets": [
              {
                "glob": "favicon.ico",
                "input": "src/",
                "output": "/"
              },
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "/assets"
              }
            ]
          },
          "configurations": {
            "ci": {
              "progress": false,
              "watch": false
            }
          }
        },
        "lint": {
          "builder": "@angular-devkit/build-angular:tslint",
          "options": {
            "tsConfig": [
              "tsconfig.app.json",
              "tsconfig.spec.json",
              "e2e/tsconfig.json"
            ],
            "exclude": ["**/node_modules/**"]
          }
        },
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "app:serve"
          },
          "configurations": {
            "production": {
              "devServerTarget": "app:serve:production"
            },
            "ci": {
              "devServerTarget": "app:serve:ci"
            }
          }
        },
        "ionic-cordova-build": {
          "builder": "@ionic/angular-toolkit:cordova-build",
          "options": {
            "browserTarget": "app:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "app:build:production"
            }
          }
        },
        "ionic-cordova-serve": {
          "builder": "@ionic/angular-toolkit:cordova-serve",
          "options": {
            "cordovaBuildTarget": "app:ionic-cordova-build",
            "devServerTarget": "app:serve"
          },
          "configurations": {
            "production": {
              "cordovaBuildTarget": "app:ionic-cordova-build:production",
              "devServerTarget": "app:serve:production"
            }
          }
        }
      }
    }
  },
  "cli": {
    "defaultCollection": "@ionic/angular-toolkit"
  },
  "schematics": {
    "@ionic/angular-toolkit:component": {
      "styleext": "scss"
    },
    "@ionic/angular-toolkit:page": {
      "styleext": "scss"
    }
  }
}

Let’s make certificates folder inside src folder and copy your .cer file inside it. please check the screenshot below for the folder structure.

Now open the terminal and run ionic cordova platform add android to add an android platform. It will generate all related files and folders for android. Open the file config.xml and add below code inside the platform block.

<platform name="android">
     <!-- other tags will be here  -->
    <resource-file src="src/certificates/certificate.cer" target="src/certificates/certificate.cer" />
</platform>

Next, we need to include the Cordova plugin for SSL Pinning, for that we will use https://github.com/silkimen/cordova-plugin-advanced-http plugin which is great. Run below commands in the terminal to install the plugin in the project and we will also need ionic native too.

ionic cordova plugin add cordova-plugin-advanced-http

npm install @ionic-native/http

Once done open app.component.ts file and add code inside initializeApp() function check below file

import { Component } from '@angular/core';

import { Platform } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

 
import { HTTP } from '@ionic-native/http/ngx'; //<=== Import this 

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss']
})
export class AppComponent {
  constructor(
    private platform: Platform,
    private splashScreen: SplashScreen,
    private statusBar: StatusBar,
    private httpSSL: HTTP //<=== define this too
  ) {
    this.initializeApp();
  }

  initializeApp() {
    this.platform.ready().then(() => {
      this.statusBar.styleDefault();
      this.splashScreen.hide();

      this.platform.ready() 
      .then(() => {
          
          
        this.httpSSL.setServerTrustMode("pinned") //<=== Add this function 
          .then(() => {
              console.log("Congratulaions, you have set up SSL Pinning.")
          })
          .catch(() => {
              console.error("Opss, SSL pinning failed.")
          });
          
          
      })

    });
  }
}

Now create new service folder to keep all our http service at one place something like below and create api.service.ts file

api.service.ts

import { Injectable } from '@angular/core';
import { HTTP } from '@ionic-native/http/ngx';

@Injectable({
  providedIn: 'root'
})
export class ApiService {
    
    readonly  apiEndPoint = 'http://otricks.com/api/user';
    readonly  fakeEndPoint = 'http://www.google.com/api/xyz'
    
    // This will get work with SSL Pinning becuase API endpoint is same as our certificate 
    getDataFromActualAPi(){
        return this.http.get(`apiEndPoint`,{}, {});
    }
    
    
    // This call will be fail as the domain is not correct according to our certificate.
    getDataFromFakeApi(){
        return this.http.get(`fakeEndPoint`,{}, {});
    }

}

When you call this API service in the component getDataFromActualAPi() will return your correct response, however, if you try to call getDataFromFakeApi() the function will fail as the provided certificate is not from this domain.

The system will throw below error message, you can check in the debugger console.

{status: -2, error: "TLS connection could not be established: javax.net…n: Trust anchor for certification path not found."}
error: "TLS connection could not be established: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found."
status: -2
}

Hope this will help someone 🙂 if you like this article you may want to check how to protect your server

Anjum Nawab

I'm dreamer, I'm a software engineer, I'm an architect, I'm father, I'm a 10-year-old boy. Love technology

You may also like...