Approov User Manual

Getting Started With Approov

Once you have installed the Approov CLI Tool there are a few more steps to get your Approov integrated app running. These are set out in roughly the expected order over the next few sections of this manual:

  1. Integrate the SDK into your app
  2. Get the SDK Configuration
  3. Write the code to initialize the SDK in your app
  4. Add the API domain(s) you want to get tokens for in your app
  5. Integrate some code in your app to fetch Approov tokens and send them in API requests
  6. Build your app and register it with the Approov cloud service
  7. Modify your API backend to check the Approov tokens

After following these steps you should have a working Approov integration, although there are of course other features you can try. In particular, we strongly recommend that you configure pins and implement pinning in the app.

Other aspects you might want to try are:

  • Update your Security Policy that determines the conditions under which an app will be given a valid Approov token.
  • Learn how to Manage Devices that allows you to change the policies on specific devices.
  • Understand how to issue and revoke your own Management Tokens to control access to your Approov account.
  • Use the Metrics Graphs to see live and accumulated metrics of devices using your account and any reasons for devices being rejected and not being provided with valid Approov tokens. You can also see your billing usage which is based on the total number of unique devices using your account each month.
  • Investigate advanced features, such as Token Binding and Offline Security Mode.

You can reach the support portal at https://approov.zendesk.com. Sign up for an account or use an existing Google account. This portal is only for technical enquiries.

You can reach your subscription the portal at https://approov.chargebeeportal.com. If you are a new account holder, you can request a new sign-up link at the bottom of the portal page, otherwise use your credentials to log in. The portal allows you to change your subscription plan, update your payment details, terminate your subscription, etc. For further sales-related enquiries, please contact sales@approov.io

If you are currently using an Approov 1.x version, then look at Migrating from Approov 1 for tips on how to proceed.

Approov Architecture

This describes the overall architecture of Approov and familiarizes you with the key concepts required to understand the steps in the Approov integration.

Key Components

The key components of Approov are detailed in the following diagram:

Overall Architecture

The steps required to obtain an Approov token are as follows:

  1. You will be able to administer the properties of your account using the approov command line tool and the management tokens you were issued upon sign up. A key aspect of this administration is the registration of new apps that are to be released to the app store. The approov tool analyzes the app (in either .apk or .ipa format) and adds its signature to a database in the Approov cloud service for your account. The particular build of the app then becomes recognized as being official, allowing valid Approov tokens to be generated for calls from that app.
  2. Your app itself must make a call to either the fetchApproovToken or fetchApproovTokenAndWait methods in the Approov SDK that must be integrated into your app. Note that these calls must only be made after having initialized the Approov SDK. If this is the first call to obtain an Approov token, then it will initiate an integrity measurement process inside the SDK that requires communication with the Approov cloud service. Once an Approov token has been obtained it is cached by the SDK for up to 5 minutes so that subsequent fetch calls do not require additional network communication.
  3. The integrity check process occurs in combination between the SDK and the Approov cloud service. The SDK analyzes the runtime environment of the app and the authenticity of the app that is being measured. These checks are implemented in hardened code and communications are protected both by TLS and also by a secondary level of request integrity signing. The Approov cloud service performs analysis on the data provided by the SDK and makes a decision based on this and the security policy criteria you set for your account. If the criteria are met then the Approov cloud provides a short lived token signed with a symmetric allocated randomly during your account sign up. If the criteria are not met then a token is still issued, but it is not signed with the correct secret.
  4. The obtained Approov token should be transmitted with a backend API request. We recommend you add the token as an additional header, such as Approov-Token, but the approach will depend on your particular API. It is important that all communications made by these APIs are pinned so that no Man-in-the-Middle (MitM) interception is possible that could make a copy of the short lived token. Pinning TLS connections is good security hygiene anyway, as it prevents customer data being intercepted in the same way.
  5. Your backend API is able to check the validity of the Approov token by checking it has been signed with the symmetric secret correctly. If it is then you know that the API request is really coming from an official registered version of your app, and not being spoofed by some other entity. Moreover, depending upon the security policy you have set in your account, a valid Approov token also provides guarantees about the runtime environment of the device the app is running on. Since the shared symmetric key is never put inside the app itself an attacker cannot reverse engineer it in order to create their own signed Approov tokens without going through the integrity measurement process.

Security Rules Updates

The Approov SDK is able to execute security analysis rules that are supplied dynamically by the Approov cloud service as illustrated below:

Rules Updates

You may set your security policy and Approov researchers are continually updating the set of rules and security signatures that are being detected to indicate malicious intent inside the app’s runtime environment. When the first Approov token fetch is made in the app, the latest set of security rules are transmitted from the Approov cloud to the SDK. These rules determine the data that is gathered inside the SDK and the checks that are performed on it against particular threat signatures. The security rules are automatically updated for running apps if they are changed on the server.

Whenever an Approov token fetch is performed, a predetermined subset of the gathered data and the results of signature analysis are transmitted securely to the Approov cloud. Data analysis is then performed before determining if the particular app instance should be issued with a valid or an invalid Approov token.

This mechanism is also used by Approov researchers to gather intelligence on specific devices that are associated with malicious behaviour. It enables a highly reactive security stance without the requirement for SDKs within apps to be updated.

Cloud Server Redundancy

To enable a highly reliable Approov service, the backend is implemented in two different cloud service providers, as illustrated below:

Failover System

When an Approov token fetch is requested the initial transmission is sent to a domain name hosted in the AWS cloud. This is the primary token service, with at least three frontend servers available for your account instantiated in different availability zones within the overall data center. The particular geographic data center utilized is allocated upon sign up. Some accounts may also have support in multiple different geographic data centers to lower latencies in different parts of the world and to offer further enhanced redundancy.

If communication cannot be established with the primary service (or errors are continually returned) then the SDK attempts to make contact with the secondary (aka failover) system. This is available on a different domain name using a different TLD (Top Level Domain) to further enhance redundancy. The secondary failover system is implemented in Google Cloud to provide complete isolation from large scale failures that may occur in AWS. The failover system only provides a subset of the full analysis capability of the primary system, but will ensure your apps should continue to receive valid Approov tokens in the event of a catastrophic primary system failure.

The failover system only serves Approov tokens if it is enabled. It continually checks the primary system on a minute-by-minute basis and automatically enables itself if a primary failure is detected, with no need for any manual intervention in the switch over process.

SDK Integration

This section describes how you can download the latest Approov SDK and integrate it into the build process of your app project.

Getting the Approov SDK

The Approov SDK can be downloaded using the approov command line tool. Use the following command to download the latest SDK package for Android:

$ approov sdk -getLibrary approov_sdk.aar
Android SDK library 2.0.4(1050) written to approov_sdk.aar

This writes the latest available SDK package to the approov_sdk.aar file. You can download the iOS package by simply using a .zip rather than .aar extension.

You will be informed if a new SDK version is available when you register an app that contains an older version. If this happens you should consider upgrading using this command, which will always provide the latest version.

On iOS it is also possible to obtain a bitcode version of the iOS SDK library. We recommend that you use the native version unless you have to use bitcode for other reasons in your release process. The bitcode version can be obtained as follows:

$ approov sdk -getLibrary approov_sdk.zip -bitcode
iOS SDK library 2.0.4-bitcode(1060) written to approov_sdk.zip

In some cases you may have been instructed to use a specific version of the SDK with a specific numeric identifier. You can select a specific one as follows, 1039 in this example:

$ approov sdk -getLibrary approov_sdk.zip -libraryID 1039
iOS SDK library 2.0.4(1039) written to approov_sdk.zip

Importing the Approov SDK into Android Studio

The Approov SDK can be added to an existing app project in Android Studio using the following steps (instructions are accurate for Android Studio 3.4.1 and above):

  1. Make sure the Project View is open (if it is not, click on “Project” located on the left hand side near the top).
  2. Depending on your Project View’s “Group Tabs” setting (accessible by right-clicking on “Project”), the Project View’s tabs are either visible side-by-side, or grouped together in a drop-down menu
  3. Select the “Android” tab (at the top of the Project View).
  4. Right-click on the “app” folder in the “Android” tab and select “New” → “Module”:

    Android Studio: Add new module

  5. Select the “Import .JAR/.AAR Package” option and click on “Next”.

  6. Enter the path for your Approov SDK .aar that you previously downloaded into the “File name” field (or navigate to the .aar and click “OK”). The “Subproject name” field will be populated with the .aar’s name you can also choose a different sub-project name if desired.

  7. Click “Finish”. The new subproject folder should appear in the “Android” tab.

  8. Right-click on the “app” folder in the “Android” tab and select “Open Module Settings”.

  9. Ensure that the “app” module is selected in the sidebar on the bottom left.

  10. Click on the “Dependencies” tab and click on the green “+” button (labeled “Add”) in the top right corner.

  11. Select “3 Module Dependency”.

    Android Studio: Select module dependency

  12. Select the Approov SDK module that you just imported , click “OK” and then click “OK” again to close the window.

    Android Studio: Select the Approov SDK module

  13. A Gradle sync will then run.

Once you have successfully completed these steps you are ready to start making use of the Approov SDK. The SDK methods are available by importing the package:

import com.criticalblue.approovsdk.Approov;

The Approov SDK uses the OkHttp stack for making its requests, so the following dependency must be added to your Gradle file:

implementation 'com.squareup.okhttp3:okhttp:3.14.2'

If using version 3.14 or above of the OkHttp library, then please add the following code to your gradle file:

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

Note that the OkHttp library works on Android 5.0+ (API level 21+). The Approov SDK will work with versions as old as Android 4.4 (API level 19+), but to do this you must use version 3.12.3 of OkHttp which is not recommended (see the OkHttp repository). The version shown here (3.14.2) is the latest at the time of writing.

The following app permissions also need to be available in the manifest to use the Approov SDK:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />

If proguard is being used to protect an application that includes an Approov SDK, the SafetyNet dependency has to be included in your gradle dependency section:

implementation 'com.google.android.gms:play-services-safetynet:16.0.0'

Logging output from the Approov SDK can be seen under the Approov tag in logcat. Normally the SDK is silent except during initialization or if there is a problem with connectivity. Approov support may require this logging to answer any issue you may raise.

The initial logging on startup will be something like the following:

2019-05-29 11:53:06.637 4997-4997/? I/Approov: test-account, com.criticalblue.demo, 2.0.4(1039), h4gubfCFzJu81j/U2BJsdg==

It provides information about the account, app, SDK being used and device ID.

Importing the Approov SDK into iOS Xcode

The Approov framework can be added to an existing app Xcode project using the following steps:

  1. In the Xcode project editor, select the target to which you want to add the Approov SDK framework.
  2. Select the “General” tab.
  3. Select the + (plus) icon under the “Embedded Binaries” section.
  4. Select “Add Other…” in the file dialog and browse to theApproov.framework bundle location. The Approov.framework is the unzipped content of the approov_sdk.zip file obtained using the approov command line tool.
  5. Select “Open” in the file dialog.
  6. Ensure the “Copy items if needed” destination option is checked, then select Finish.

    xCode: Add Approov SDK

  7. The Approov.framework entry should now be present in the “Embedded Binaries” and “Linked Frameworks and Libraries” sections, indicating that this will be included and linked with your app.

    xCode: Verify Approov SDK

To access the Approov framework in source code, the public header must be imported. This can be achieved by adding the following to the top of your source or header files:


import Approov

import <Approov/Approov.h>

Logging

Logging is output by the iOS SDK into the document directory for the app in a file named Approov.log. The SDK only outputs to this during initialization or if there is a connectivity issue with the SDK. You may be asked to supply this file to Approov support if you log a support request and we are unable to diagnose the issue from the server side analysis. Note that you may also need to consult this file to obtain the device ID.

In order to access the log file produced by Approov, the property label “Application supports iTunes file sharing” has to be added to the Information property list in Xcode and the Boolean value has to be set to Yes. If modifying the Information property list with an external editor, the equivalent property key is UIFileSharingEnabled and the value must be set to true. It is then possible to use iTunes, Apple Configurator or ios-deploy to add and retrieve files to the Documents folder of an application. If using iTunes to access the Documents folder of an application, please remember to synchronise the device for the changes to take effect.

Architectures

Please note that the Approov SDK contains four CPU architectures combined in a single binary, two architectures for actual devices and two more targeting simulators. Submitting an application to the Apple App Store that contains simulator architectures will not succeed. You should remove the simulator architectures by using the lipo command before submitting your application to Apple:

$ lipo -remove x86_64 Approov.framework/Approov -o Approov.framework/Approov
$ lipo -remove i386   Approov.framework/Approov -o Approov.framework/Approov

The simulator architectures are only made available in order to facilitate development. The Approov service will not generate a valid Approov token for a simulator device. The Approov service inserts an ios-simulator annotation if an all annotation policy has been set. The only way to obtain a valid Approov token on a simulator is to use the approov tool to whitelist the simulator by obtaining the device ID, either from the console logs or a previous token fetch in the did claim. Please note, using the command line to install or remove applications on the simulator may produce different device ID. This is also the case if resetting the simulator by erasing all settings and applications. We recommend using a physical device during development if obtaining a valid Approov token is a requirement.

SDK Configuration

The SDK configuration is an encoded string that is used to initialize the Approov SDK in your app.

How SDK Configuration Works

The SDK configuration is a base64 encoded JSON Web Token (JWT) providing information about the account holder. It contains the following information:

  • Name of the account
  • A public key that has been allocated to the account
  • Various configurable network access rules for the SDK
  • The set of API domains to protect along with any public key pinning information for them.

The JWT is signed using asymmetric Elliptic Curve Cryptography (ECC). The public key is in the configuration, but the private key is held securely in the Approov cloud service. This means that only the Approov cloud service is able to generate valid configurations. They cannot be manually modified as any tampering will be detected. The public key is transmitted by the SDK to the Approov cloud service, so any attempt to modify the public key and re-sign the configuration will also be detected.

By making the API domain pinning information available from the configuration, it is accessible upon startup of the app without the need for network access. Some app development frameworks require the pinning to be set at this point. More information about the pinning approach is provided in Public Key Pinning.

In order to support over-the-air dynamic updates to the configuration, the Approov cloud service can send an update if API domains or their pins (or some other parameter) is changed. Updates are also signed using ECC and the signature is checked against the public key provided in the initial base configuration. This prevents any tampering of the configuration in the communication channel.

Getting the Initial SDK Configuration

In order to initialize the SDK a configuration must be downloaded from the Approov cloud service using the approov tool.

$ approov sdk -getConfig initial.config
initial SDK configuration written to initial.config

In this case the configuration is written to the file initial.config. This file must be included inside the app to provide this information at runtime. This is described in SDK Initialization.

If the set of APIs and/or their pins are modified in the future, then the configuration becomes out of date. The app will continue to work, since it will receive an updated configuration automatically over the air. However, it is better for the initial configuration to be as up to date as possible, so that it is available on the initial launch of an app even when it doesn’t have Internet access.

The approov tool issues app registration warnings whenever there is a pending download for a configuration change.

Handling Multiple Accounts

You may have access to multiple Approov accounts. Each account will have a different set of management tokens, so care must be taken to keep these tokens separate.

Furthermore, each account will have a different initial configuration for the SDK. This configuration will embed a different account name and also a different public key, since each account is allocated a different public/private keypair. This prevents a configuration for one Approov account being valid for another one.

These different initial configurations will need to be carefully managed in your app development and build environments. Note that, the account name that is read from the configuration is always logged as part of the Approov SDK initialization, e.g. an example from Android logcat:

2019-05-23 16:41:43.139 32592-32592/com.criticalblue.demo I/Approov: test-account, com.criticalblue.demo, 2.0.4(1033), h4gubfCFzJu81j/U2BJsdg==

SDK Initialization

The steps required to initialize the Approov SDK are covered here. Configuration must be supplied to the SDK upon initialization. It is in the form of a string that is described in SDK Configuration. Saving and retrieving the dynamic configuration is not built into the SDK so that you can persist the configuration data in a way that best fits with the other data in your app.

Reading Initial SDK Configuration

This loads the initial configuration approov-initial.config from the assets of the app. In this example code we load it from a file. The name and folder location of this file may be adjusted depending upon the conventions of any particular app.


// read the initial configuration for the Approov SDK
String initialConfig = null;
try {
    InputStream stream = getAssets().open("approov-initial.config");
    BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
    initialConfig = reader.readLine();
    reader.close();
} catch (IOException e) {
    // this should be fatal if the SDK cannot read an initial configuration
    Log.e(TAG, "Approov initial configuration read failed: " + e.getMessage());
}

// read the initial configuration
var initialConfig : String? = nil;
if let initialConfigURL = Bundle.main.url(forResource: "approov-initial", withExtension: "config") {
    do {
        initialConfig = try String(contentsOf: initialConfigURL)
    } catch {
        // it should be fatal if the SDK cannot read an initial configuration
        NSLog("Approov initial configuration read failed: \(error.localizedDescription)")
    }
} else {
    // it should be fatal if the SDK cannot read an initial configuration
    NSLog("Approov initial configuration not found")
}

Reading Dynamic SDK Configuration

The next step of the initialization is responsible for loading any dynamic configuration. In our example code we load it from a local file:


// read any dynamic configuration for the SDK from local storage
String dynamicConfig = null;
try {
    FileInputStream stream = getApplicationContext().openFileInput("approov-dynamic.config");
    BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
    dynamicConfig = reader.readLine();
    reader.close();
} catch (IOException e) {
    // we can log this but it is not fatal as the app will receive a new update if the
    // stored one is corrupted in some way
    Log.i(TAG, "Approov dynamic configuration read failed: " + e.getMessage());
}

// read any dynamic configuration for the SDK from local storage
var dynamicConfig : String? = nil;
let URLs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let dynamicConfigURL = URLs[0].appendingPathComponent("approov-dynamic.config")
do {
    dynamicConfig = try String(contentsOf: dynamicConfigURL)
} catch {
    // log this but it is not fatal as the app will receive a new update if the
    // stored one is corrupted in some way
    NSLog("Approov dynamic configuration read failed: \(error.localizedDescription)")
}

This dynamic configuration approov-dynamic.config is held in the local file storage of the app. This configuration is optional, and when an app is launched for the first time it will not be present. The idea is that this allows a dynamically updated configuration for the app to be transmitted from the Approov cloud service to the app where it will be stored locally, and be available immediately the next time the app is started with no need to fetch it again over the network. This mechanism allows various aspects of the SDK behavior to be modified in the field, but crucially this is used to transmit updated public key pins to an app as is explained Public Key Pinning.

Starting the SDK

The next step is to actually start the SDK itself:


// initialize the Approov SDK
try {
    Approov.initialize(getApplicationContext(), initialConfig, dynamicConfig, null);
} catch (IllegalArgumentException e) {
    // this should be fatal if the SDK cannot be initialized as all subsequent attempts
    // to use the SDK will fail
    Log.e(TAG, "Approov initialization failed: " + e.getMessage());
}

// initialize the Approov SDK
do {
    try Approov.initialize(initialConfig!, updateConfig: dynamicConfig, comment: nil)
} catch {
    // it should be fatal if the SDK cannot be initialized as all subsequent attempts
    // to use the SDK will fail
    NSLog("Approov initialization failed: \(error.localizedDescription)")
}

On Android, Approov initialization requires the app Context. The initial and dynamic configurations are also provided. Remember the initialConfig must be non-null but the dynamicConfig may be null. The final string parameter is reserved for future use and should be set to null. If there is a problem with the initialization then an exception will be thrown and it will not be possible to execute further methods in the SDK.

Writing Dynamic SDK Configuration

There is one final step for the overall SDK initialization:


// if we didn't have a dynamic configuration (which happens after the first launch of the app) then
// we write it to local storage now
if (dynamicConfig == null)
    saveApproovConfigUpdate();


// if we didn't have a dynamic configuration (which happens after the first launch of the app) then
// we fetch one and write it to local storage now
if dynamicConfig == nil {
    saveApproovConfigUpdate()
}

This ensures that on the first execution of the app after installation an updated configuration is obtained from the Approov cloud service. If there is no update then the initial configuration is written as the updated configuration, indicating it is the latest available.

This pattern implies that on the first invocation of your app after installation a network request will be made to determine if there is any updated dynamic configuration available (this will not count towards your device billing). This approach will immediately bring a fresh app install up to date with the latest configuration. This does mean, however, there there may be a small network delay at this time if there is poor network connectivity. No update attempt is made at all if there is no network connectivity.

This calls a user defined example method saveApproovConfigUpdate to save an updated configuration that is defined below. It should be defined as a separate method as it will need to be called whenever the SDK receives a new configuration from the Approov cloud service. This is provided as a code example outside of the Approov SDK so that you can modify it to use whatever file persistence makes the most sense for your particular app’s structure.


/**
 * Saves an update to the Approov configuration to local configuration of the app. This should
 * be called after every Approov token fetch where isConfigChanged() is set. It saves a new
 * configuration received from the Approov server to the local app storage so that it is
 * available on app startup on the next launch.
 */
public void saveApproovConfigUpdate() {
    String updateConfig = Approov.fetchConfig();
    if (updateConfig == null)
        Log.e(TAG, "Could not get dynamic Approov configuration");
    else {
        try {
            FileOutputStream outputStream = getApplicationContext().openFileOutput("approov-dynamic.config", Context.MODE_PRIVATE);
            PrintStream printStream = new PrintStream(outputStream);
            printStream.print(updateConfig);
            printStream.close();
        } catch (IOException e) {
            // we can log this but it is not fatal as the app will receive a new update if the
            // stored one is corrupted in some way
            Log.e(TAG, "Cannot write Approov dynamic configuration: " + e.getMessage());
            return;
        }
        Log.i(TAG, "Wrote dynamic Approov configuration");
    }
}

/**
 * Saves an update to the Approov configuration to local configuration of the app. This should
 * be called after every Approov token fetch where isConfigChanged is set. It saves a new
 * configuration received from the Approov server to the local app storage so that it is
 * available on app startup on the next launch.
 */
func saveApproovConfigUpdate() {
    let updateConfig = Approov.fetchConfig()
    if (updateConfig == nil) {
        NSLog("Could not get dynamic Approov configuration to save")
    } else {
        let URLs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let updateConfigURL = URLs[0].appendingPathComponent("approov-dynamic.config")
        do {
            try updateConfig!.write(to: updateConfigURL, atomically: true, encoding: String.Encoding.utf8)
        } catch {
            NSLog("Approov dynamic configuration write failed: \(error.localizedDescription)")
        }
    }
}

This calls the newfetchConfig() method and obtains an updated configuration and writes it to local storage as approov-dynamic.config. This will then be read the next time the app is started without having to do a network fetch.

Managing API Domains

This provides an overview of the management of API domains in the account. These correspond to the domains for which Approov tokens can be fetched.

Overview

Each Approov account has a set of API domains, for which an app can fetch Approov tokens. These will typically correspond to the domains of the APIs that are to be protected with Approov. An account may have a maximum of 25 different domains. API domains may be added and removed from the account as required.

In the app code, the fetchApproovToken or fetchApproovTokenAndWait calls are used to obtain a token. These calls have a required parameter that specifies the target domain to be accessed, and a custom token for the specified domain is what’s provided by the SDK. Under the hood, each Approov attestation retrieves tokens for all registered domains and these tokens are cached securely until a new attestation replaces them.

There are a couple of motivations for specifying Approov token fetches with particular domains:

  • APIs that are protected with Approov tokens need to be pinned to prevent a Man-in-the-Middle (MitM) attack from stealing valid, albeit short lived, Approov tokens. Pinning needs to be based on the domain for the API since this determines the certificate that will be presented by the server.
  • It is possible that Approov tokens fetched for different domains are in a different format, or are signed using different token secrets. For instance, a particular domain may be setup to use encrypted (JWE) rather than symmetrically signed (JWS) tokens.

Domains are restricted to lower and upper case letters, digits, a dash and a period. This means that the API domain should not (and cannot) contain the full URI path to a particular resource.

There is no special handling of sub-domains so, for instance, my.domain.com and sub.my.domain.com are considered to be entirely distinct. Approov tokens should not be fetched for my.domain.com that are then sent to sub.my.domain.com. If required you can define a star.my.domain.com domain and fetch Approov tokens for sub domains from that. However, you may have to define some special logic for the Public Key Pinning Implementation if using Approov dynamic pinning to convert the domain name into a suitable wildcard supporting pin domain.

Adding API Domains

A new API domain can be added to the account as follows:

$ approov api -add my.domain.com
WARNING: adding the API will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
added new API domain my.domain.com with pin JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU=

Confirmation is required whenever a new domain is added. This is because it has an immediate impact in production, with tokens being served for the new domain. As soon as a domain is added, tokens for the API domain can be obtained from fetchApproovToken or fetchApproovTokenAndWait calls, using the new domain as a parameter. You should ensure that a pinning implementation is present in your app to actually pin the connection as explained in Public Key Pinning Implementation.

New domains have a public key pin added derived from the public key of the leaf certificate presented on the domain. This pin can be used to protect the communication channel between the app and API domain to prevent any Man-in-the-Middle (MITM) attack between the app and the API backend. This is explained in detail in Public Key Pinning and is not covered further here. In some cases using a leaf pin might not be appropriate, so you should consult the team responsible for maintaining the certificates for the backend API.

By default, Approov tokens for a particular API will be issued as signed JWS tokens. Approov also provides an option to issue encrypted JWE tokens for a domain. This is enabled by using the -jweoption when adding the new API domain. The Approov token format is covered in more detail here.

Listing API Domains

A list of all the domains that are configured for the account can be obtained with:

$ approov api -list
2 API domains:
 my.domain.com
 shapes.approov.io

The API domains listed are the only ones for which Approov tokens are served for the account. Any attempt to get a token for another domain results in an UNKNOWN_URL error from the Approov token fetch call.

If any API domain has been setup to require encrypted JWE Approov tokens then this is denoted with (jwe) shown after the domain in the listing.

Removing API Domains

An API domain that has been previously added can be removed again with:

$ approov api myadmin.tok -remove my.domain.com
WARNING: removing the API will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
removed API domain my.domain.com

Note that removal of an API domain requires an administration level management token and, as per our recommendation, this must be specified explicitly on the command line. Further confirmation is required before the domain is removed. Note that this means that Approov tokens will no longer be served for that domain. You obviously must be careful not to remove a domain that is being used by production apps as this will lead to a production outage.

Adding Demonstration Shapes API

A special shapes domain can be added to the account that is provided by Approov:

$ approov api -add shapes.approov.io
WARNING: adding the API will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
added new API domain shapes.approov.io with pin JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU=

This domain serves a very simple API that provides random shapes and it is used by the Approov demo app. Any account can add this domain to generate Approov tokens for the API. If added, the domain has the special property that the associated Approov tokens will be signed with the token secret expected by that API rather than the standard one for the account.

Fetching Approov Tokens

This section describes the methods that should be used to obtain an Approov token from the SDK. This is the token that is required to pass to a subsequent backend API call.

You should never cache an Approov token in your app code. Always make a call to fetch a token immediately prior to making an API request that needs it. The Approov SDK automatically caches the Approov token and only performs a network request if a new one is required. Moreover, the SDK performs additional fast checks on app integrity every time an Approov token is fetched.

Token Fetch Errors

Whenever an Approov token fetch is performed there are a set of different success or error results that may occur. These provide more information about the reason for any token fetch failure, either for debug analysis or to determine how the app should react.

These potential errors are enumerated in the table below along with the recommended action and a description of the associated meaning. Note that all iOS errors are prefixed by ApproovTokenFetchStatus..

Error (Android / iOS) Rcmd Description
SUCCESS / success Continue Indicates that an Approov token was successfully retrieved.
NO_APPROOV_SERVICE / noApproovService Continue Indicates that no token could be obtained in the unlikely event that Approov cloud services are completely down (including the failover service) or, more likely, that the account has been cancelled. The error might also occur if an app is using an old version of the Approov SDK that has no active registrations and has been deprecated and removed from the Approov cloud systems In this case the error should be propagated to the actual API call to allow Approov token checking to be disabled on the API backend.
NO_NETWORK / noNetwork Retry Indicates no token was received because there is no network connectivity. The SDK checks for network connectivity before making a token fetch attempt. This allows a rapid return from the token fetch if there is no connectivity. If this error is received then there is no point in proceeding with any following API call which will also require network access. A timed, or user initiated, retry is required.
POOR_NETWORK / poorNetwork Retry Indicates that no token could be obtained due to poor network connectivity. If this error is received then there is no point in proceeding with any following API call which will also require network access. A timed, or user initiated, retry is required.
MITM_DETECTED / mitmDetected Retry Indicates that there is a Man-In-The-Middle (MITM) in the communication with the Approov cloud service. This may be malicious or may simply indicate that the end user is using a network with a firewall that intercepts all traffic for inspection. If this error is received then there is no point in proceeding with any following API call which will also require network access. A timed, or user initiated, retry is required.
BAD_URL / badURL Error Indicates that the provided URL is not in the correct format. The URL should just be provided as a domain, although a full URL path may be specified from which the domain is extracted. This error can occur if such a URL is provided with a http:// rather than the https:// scheme.
UNKNOWN_URL / unknownURL Error Indicates that the provided URL is not an API domain configured for use.
UNPROTECTED_URL / unprotectedURL Error Indicates that the provided URL is valid but is not one that needs to be provided with an Approov token. This error should never occur under normal usage.
NO_NETWORK_PERMISSION / NA Error For Android, indicates that the app does not have ACCESS_NETWORK_STATE or INTERNET permission.
MISSING_LIB_DEPENDENCY / NA Error For Android, indicates that the Approov SDK dependency on the OkHttp library has not been satisfied.
INTERNAL_ERROR / NA Error For Android, indicates there has been an internal error within the SDK. Please contact Approov support if this ever happens.
NA / notInitialized Error For iOS, indicates that an attempt is being made to use an SDK method before it has been initialized. Note that the Android SDK generates an exception in this case.

The Rcmd column shows our recommendation for how the app logic should proceed if it receives the given error state. There are three possible options:

  • Continue: Indicates that the app should go ahead and make the API call as expected. Together with the success case, we also include the case where no Approov service is detected. In this case, the API call will be made with an empty token, so should be rejected by the Approov check in the API backend. Our recommendation, to continue, is provided to guard against some catastrophic failure or cancellation of your Approov service. This will ensure that your app will continue to operate as normal without the Approov service if you choose to disable the backend API check for the Approov token. If you integrate Approov in the recommended way then this circumstance should be the only legitimate reason to receive empty tokens on your API backend. Another, illegitimate, reason would be because an attacker is trying to spoof API requests but without a valid Approov token. If you unexpectedly receive large numbers of empty tokens on the backend and you suspect that this is caused by an Approov service failure then please contact our support immediately.
  • Retry: Indicates that the app should not attempt the API call. This is because it has not been possible to fetch an Approov token due to some network conditions. The Approov SDK already makes various retries, so there is probably no point in performing a further automated retry. Instead, the retry should require a further user initiated event. The typical case will be when the device has no (or very poor) network connectivity, so some user initiated event is required to retry when connectivity has been restored. Note that a special message might be appropriate if MITM is detected on the Approov channel, as the network being used might be inappropriate as it intercepts TLS. The user will need to connect to a different network to use the app.
  • Error: This indicates an error condition that really should not occur in a production app. We recommend that this condition is logged with any crash reporting SDK that you may be using. The next step will depend on your general strategy for error handling. You could go ahead and make the API call, but use the loggable token as the Approov token. This will of course be rejected by the backend, but will provide loggable information there.

Note that if an API domain, in active usage, is accidentally removed from your Approov account then this will lead to an error indication as the token fetch attempt will receive an Unknown URL or Unprotected URL error.

Note in cases where you are using an interceptor pattern to add your Approov token you may wish to treat the Unknown URL or Unprotected URL cases differently. If you get these then this may just indicate that the interceptor is being used on a domain that does not need Approov tokens at all. In this case you should just continue with the request, but without adding the token.

Synchronous Token Fetching

This describes how to fetch an Approov token with the synchronous form of the call. This method does not return until a token has been fetched. If a cached token may be used, then the call will return promptly. If a new token is required, then the call will include the time to complete the network requests with the Approov cloud service and so there may be some delay before returning. The exact delay will depend on many factors, especially network connectivity quality. This should not be called from a UI thread since the OS might force stop your application.

The code to make the call is very simple, as follows:


Approov.TokenFetchResult approovResult = Approov.fetchApproovTokenAndWait("api.myservice.io");
if (approovResult.isConfigChanged())
    saveApproovConfigUpdate();

let approovResult = Approov.fetchTokenAndWait("api.myservice.io")
if approovResult.isConfigChanged {
    saveApproovConfigUpdate()
}

The method takes a parameter of the particular API domain for which a token is being fetched. This must be one that has been configured to provide Approov tokens or else an error will result.

Whenever a token fetch is performed, the recommended pattern is to check whether any new configuration has been transmitted to the app and, if so, use the saveApproovConfigUpdate method to save it locally. This means that this configuration will be available immediately on the next app startup, even if there is no network connectivity at the time. The availability of the new configuration is checked using the isConfigChanged() getter in the returned fetch result. Note that this flag remains set until a fetchConfig() method, as used in saveApproovConfigUpdate, is called. The method saveApproovConfigUpdate is defined here. If it is not convenient or appropriate to update the configuration at that point (perhaps if the token fetching is in an interceptor) then a flag can be set to cause the update to occur at some later, more appropriate point in the code flow.

Next, check the status returned from the fetch, and react appropriately:


String token = approovResult.getToken();
if ((approovResult.getStatus() == Approov.TokenFetchStatus.SUCCESS) ||
    (approovResult.getStatus() == Approov.TokenFetchStatus.NO_APPROOV_SERVICE)) {
    <pass handling>
} else if ((approovResult.getStatus() == Approov.TokenFetchStatus.NO_NETWORK) ||
           (approovResult.getStatus() == Approov.TokenFetchStatus.POOR_NETWORK) ||
           (approovResult.getStatus() == Approov.TokenFetchStatus.MITM_DETECTED)) (
    <retry handling>
} else {
    <error handling>
}

let token = approovResult.token
if (approovResult.status == ApproovTokenFetchStatus.success) ||
   (approovResult.status == ApproovTokenFetchStatus.noApproovService) {
    <pass handling>
} else if (approovResult.status == ApproovTokenFetchStatus.noNetwork) ||
          (approovResult.status == ApproovTokenFetchStatus.poorNetwork) ||
          (approovResult.status == ApproovTokenFetchStatus.mitmDetected) {
    <retry handling>
} else {
    <error handling>
}

These represent the three classes of response, as discussed in the previous section. The exact implementation will depend on the structure of your app:

  • Pass Handling: Should go ahead and make the API call for the domain for which the Approov token was fetched.
  • Retry Handling: The API call should not be made, and instead another attempt should be made to fetch an Approov token after a user initiated retry.
  • Error Handling: This is for error conditions that should not normally occur. Logging and/or crash analytics should be used. Although the app may continue, it should not proceed with the API call as the token string will be empty.

Note that the token fetching process itself may make various retries of the communication with the Approov server. Within the SDK, the absolute worst case timeout before the method returns is typically configured to be 30 seconds.

Asynchronous Token Fetching

An alternative Approov token fetch calling mechanism is provided. This uses an asynchronous approach whereby a callback method is provided. This is called either when an Approov token is available or there is an error. This method allows token fetches to be initiated from threads that cannot be blocked, such as UI threads.


class ApproovCallbackHandler implements Approov.TokenFetchCallback {
    @Override
    public void approovCallback(Approov.TokenFetchResult pResult) {
        if (pResult.isConfigChanged())
            saveApproovConfigUpdate();
        switch (pResult.getStatus()) {
            case Approov.TokenFetchStatus.SUCCESS:
            case Approov.TokenFetchStatus.NO_APPROOV_SERVICE:
                // Go ahead and make the API call with the 'pResult.getToken()'
            case Approov.TokenFetchStatus.NO_NETWORK:
            case Approov.TokenFetchStatus.POOR_NETWORK:
            case Approov.TokenFetchStatus.MITM_DETECTED:
                // We should retry doing a fetch after a user driven event
            default:
                // There has been some error event that should be reported
        }
    }
}

// ommitted code for brevity

ApproovCallbackHandler approovCallback = new ApproovCallbackHandler();
Approov.fetchApproovToken(approovCallback, "api.myservice.io");

Approov.fetchToken({ (approovResult: ApproovTokenFetchResult) in
    if approovResult.isConfigChanged {
        saveApproovConfigUpdate()
    }
    switch approovResult.status {
    case ApproovTokenFetchStatus.success,
         ApproovTokenFetchStatus.noApproovService:
         // Go ahead and make the API call with the 'approovResult.token'
         <pass handling>
    case ApproovTokenFetchStatus.noNetwork,
         ApproovTokenFetchStatus.poorNetwork,
         ApproovTokenFetchStatus.mitmDetected:
          // We should retry doing a fetch after a user driven event
          <retry handling>
    default:
        // There has been some error event that should be reported
        <error handling>
    }
}, "api.myservice.io")

For Android, a class must be defined that implements the Approov.TokenFetchCallbackinterface. For iOS, a closure may be defined within the call. All other handling should follow the same logic as described in the preceding sections.

Token Fetch Latency

The integrity check operation provided by Approov requires the SDK to perform a CPU computation and possibly one or more network requests. If the SDK has to perform more than one network request (i.e. typically during App launch) as part of an integrity check operation, we can describe this as a Cold Fetch. A Cold Fetch can also happen if no token has been requested for a significant amount of time, causing the currently available token to expire. A Warm Fetch requires only a single network request, and might have different CPU computation requirements. A Cached Fetch as its name implies, returns an already available token and thus, does not require additional computation or any network connection. The table below provides a range of values obtained by measuring the performance of a fetch operation on a range of 32 and 64 bit ARM devices for both Android and iOS with optimal network conditions. The type of token fetch mechanism (synchronous or asynchronous) offers exaclty the same performance results.

Device/Platform Cold Fetch Warm Fetch Cached Fetch
Android ARM32 950 - 1050 ms 500 - 600 ms 50 - 70 ms
Android ARM64 715 - 930 ms 530 - 700 ms 50 - 70 ms
iOS ARM32 900 - 1090 ms 150 - 200 ms 5 - 10 ms
iOS ARM64 830 - 900 ms 150 - 200 ms 5 - 10 ms

Managing Registrations

App registration management determines the set of apps that the Approov service will consider valid for the account.

Registering an App

The Approov service is designed to only send a valid Approov token to apps that have been registered. Registration indicates that Approov has captured the fingerprint of a good instance of your app and should, subject to various other checks and conditions, deliver valid Approov tokens to any identical app instance. Any other app cannot receive valid Approov tokens and will therefore be unable to access the backend API services that you have protected with Approov.

When you release a new version of your app to the app store, you will need to register the final version that will be published. Multiple different versions of the app registration may be live simultaneously to support end users who are running different versions of the app. In general it may take some time for users’ apps to be updated to the latest version. You may also wish to register various versions of the app during the development and test process.

In order to register an app you need the app package that is to be run. This is either an .apk file (Android) or an .ipa file (iOS). An app being registered must have the Approov SDK integrated within it or else the registration will fail.

$ approov registration -add my_app.apk
registering app
 Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=my.app.com-2.0[3]-1041  SDK:Android(2.0.4)
registration successful

The message indicates that an overall signature of the app, its name and its version have been extracted and are available for viewing in the list of all registrations for the account. The overall signature consists of a signature hash of aspects of the application, the application package name, application version information, and the ID of the Approov SDK that is integrated within it. Finally, information about the type and version number of the Approov SDK used is also provided.

Once an app is registered, it should be possible to get valid Approov tokens for it, assuming there are no other issues with the device or its runtime environment (for instance, a debugger being active) that prevent it from receiving a valid token.

By default, an app registration is permanent and will remain in the Approov cloud database until it is explicitly removed. Permanent app registrations should be used to identify apps that are being published to production.

It is only possible to have a maximum of 250 registrations at any one time.

Registration Upgrade Messages

When an app is registered, you may receive two different types of upgrade advisory messages. These are generated to assist you with keeping Approov usage up to date. The possible messages are as follows:

  • SDK Library Upgrade: If you receive a message such as note: newer version 2.1.0 of the Approov Android SDK is available or similar, then this indicates that the Approov SDK integrated in the app is not the latest one available. You should consider upgrading before making a new release of the app. The latest SDK library package can be obtained via the approov tool and then integrated in the app to replace the old version. Release notes are available here.
  • SDK Configuration Upgrade: You may receive a message such as WARNING: SDK initial configuration must be out of date since a new one is available. This means that there have been some changes to the SDK initial configuration that are probably related to changes to the API domains served and their pins. It might also be related to some change that the Approov service itself has made. If you see this, then you should upgrade your initial SDK configuration inside the app to ensure it has the most up to date version when released. The app can of course get a dynamic update over-the-air, but it is always best to ship new apps with the latest version.

Temporary Registrations

By default an app registration is permanent. This also means that an administration level management token is required to remove it. Removal needs to be restricted since an accidental removal will stop apps in production from receiving valid tokens, which may effectively bring an app service down. Note that in such a circumstance the Approov failover system will not be enabled, since this is not an Approov cloud system failure but explicit user action.

A temporary registration is performed just like a permanent registration, but with the addition of the -expireAfter option.

$ approov registration -add my_app.ipa -expireAfter 3d
registering app MyCoolApp
 P6nTmI3fhftxUr740ZnUMGKuAyTYjxP5dxKzJGd9yOk=my.app.com-2.0[3]-1039  SDK:iOS(2.0.2)
registration successful, expires 2019-06-01 16:27:01

This creates a registration that is active for 3 days. The expiry time is given in local time in the output. Note though, if the registration already exists and is permanent or has a longer lived temporary expiration time, then that later time is retained.

The -expireAfter parameter takes a duration which may be specified in y (years), d (days), h (hours), m (minutes) or s (seconds). Multiple time units may be used as long as they are specified in this order, e.g. 3d12h to register for three and a half days.

Listing Registrations

All of the registrations that are active for the account can be listed as follows:

$ approov registration -list
3 app registrations:
 Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=com.criticalblue.demo-2.0[3]-1041                   SDK:Android(2.0.3)  registered:2019-05-15 18:03:17
 P6nTmI3fhftxUr740ZnUMGKuAyTYjxP5dxKzJGd9yOk=approov.io.client.swift.shapes-client-1.0[1]-1039   SDK:iOS(2.0.2)  registered:2019-05-15 18:03:31
 Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=com.criticalblue.demo-2.0[3]-1042                   SDK:Android(2.0.4)  registered:2019-05-29 14:49:36 expiry:2019-06-01 14:49:36

Each registration entry provides its overall signature as a single text block at the start of each line. This is the signature that must be copied in order to remove a registration (see next section). It contains a signature hash of aspects of the application, the application package name, application version information, and the ID of the Approov SDK that is integrated within it.

More information is also provided about the type and version number of the Approov SDK used. The registration time of the app is also provided (in local time). This is updated if a new registration of an app is made that happens to have the same signature. Finally, the expiry time of the registration is shown if it was registered with -expireAfter. Once a registration expires it is automatically removed from the list.

Note that if you are looking for a particular registration you are able to find it by simply piping the output through grep, for instance on Linux/MacOS:

$ approov registration -list | grep iOS
 P6nTmI3fhftxUr740ZnUMGKuAyTYjxP5dxKzJGd9yOk=approov.io.client.swift.shapes-client-1.0[1]-1039  SDK:iOS(2.0.2)  registered:2019-05-15 18:03:31

Using this method it is possible to find registrations for specific platforms, SDK versions, app names and versions.

Removing Registrations

Any individual registration can be removed using its overall signature provided from the registration -listoption. For instance, to delete a temporary app registration:

$ approov registration -remove Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=com.criticalblue.demo-2.0[3]-1039
app registration Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=com.criticalblue.demo-2.0[3]-1039 has been removed

You will be asked for confirmation if the app has a permanent registration, but not for a temporary registration. When a registration is removed no further valid Approov tokens will be issued for the app, with a delay of up to 30 seconds for this deletion operation to propagate through the Approov servers. Note that running apps may already hold a cached Approov token so it may take up to a further 5 minutes before all valid token usage by those apps ceases.

Permanent app registrations are removed in the same way but users are also asked to provide confirmation before the deletion occurs. They also require use of an administration token or a development token issue to the same user name that originally registered the app. In other words, a developer can remove their own permanent registrations but not those added by other users. Furthermore, apps registered with an adminstration level token can only be removed with an adminstration level token. These rules are to provide additional safeguards for apps that may be in active production.

$ approov registration myadmin.tok -remove P6nTmI3fhftxUr740ZnUMGKuAyTYjxP5dxKzJGd9yOk=approov.io.client.swift.shapes-client-1.0[1]-1040
WARNING: removing app registration will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
app registration P6nTmI3fhftxUr740ZnUMGKuAyTYjxP5dxKzJGd9yOk=approov.io.client.swift.shapes-client-1.0[1]-1040 has been removed

Removing Multiple Registrations

A facility is provided to remove multiple registrations using a single command. This is useful for maintaining the hygiene of your app registrations. You should actively remove registrations that are no longer in use, either because they are registrations for old testing apps (that have been accidentally permanently registered), or for previously released production apps that no end users are still using. The command is invoked as follows:

$ approov registration myadmin.tok -removeMatching com.criticalblue.demo,30d
2 matched registrations:
 Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=com.criticalblue.demo-2.0[3]-1039  registered:2019-05-05 16:01:46
 A4rcdXpfEOg0wse4snbAURnjRZidPvY63EULzUrZeFE=com.criticalblue.demo-3.0[4]-1039  registered:2019-05-15 18:02:08
WARNING: removing app registrations will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
removing Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=com.criticalblue.demo-2.0[3]-1039
removing A4rcdXpfEOg0wse4snbAURnjRZidPvY63EULzUrZeFE=com.criticalblue.demo-3.0[4]-1039

This needs an administration level management token as this is a risky operation that could cause current production app registrations to be removed if care is not taken. The parameters provide the exact package name of apps to be matched and the minimum age of last registration of the app. In this case it is 30 days i.e. the command will only remove apps last registered more than this time ago. A list of matching registrations is provided. You should review this carefully before allowing the operation to proceed.

Android App Bundles

Google has rolled out a new application packaging format for Android applications. The Android Application Bundle (AAB) includes platform, screen density, and language variants needed for application installation on any device. After a bundle has been uploaded to the playstore, each device installation will include only the specific platform, density, and language features needed by that device. The installation is delivered using Android’s split-APKs feature, and post-installation dynamic feature modules can be provided on demand using supplemental APKs.

When using bundles, the content of an application is uploaded to Google in AAB format and the individual APKs are generated automatically. An app’s Android app bundle generates a range of split-APKs including a master base APK (base-master.apk), a set of base configuration APKs (split across different platforms, densities, and languages), and a set of dynamic feature APKs (similarly split into master, platform, density, and language APKs for each feature). This is represented as follows, reproduced from About Dynamic Delivery.

Android APK dynamic delivery

During app installation, the base-master.apk is the one APK which will always be downloaded for any device supporting split-APKs (Android 5.0 and above). You must register this base-master.apk with the Approov service for attestation. All other APKs associated with your application share the same application ID and signing configuration with the registered base master APK. Note that we do not recommended using bundles with Approov if you wish to target below Android 5.0 (note, however, that there are limitations on pre-Android 5.0 targeting anyway due to OkHttp usage).

During development you can use whitelisting on particular devices to avoid the need to register individual builds.

Registering a playstore app bundle with Approov requires a manual step. Start by logging into the Playstore console. Select the application which you uploaded the app bundle for, and select Release Management > Artifact Library" from the left-hand menu:

Android: Google Play console artifact library

Next, select the EXPLORE button to see a list of split-apk bundles per configuration:

Android: Google Play console app bundle explorer

In the rightmost column, download any one of the supported device APK archives, for example, the Acer Iconia One 10 acer_jetfirelte device. Extract the base.apk from the downloaded archive.

You are then able to register this APK in the normal way. Once registered, install and run your application from the playstore to check the registration is working as expected.

Note that we have longer term plans to support bundles natively with the registration tool to obviate the need to go through this download process.

Annotating a Registration

A facility is provided to add an arbitrary string instead of the version name for a particular registration. This allows a particular registration to be annotated to show that it is for some specific purpose (perhaps a special testing version, for instance) and this allows it to be easily located in the list of all app registrations. We advise you not to use this facility on apps which are to be retained for production purposes, so that the real version number of app registration is available.

The annotation is added as follows:

$ approov registration -add my_app.apk -versionName "special"
registering app
 Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=my.app.com-special-1041  SDK:Android(2.0.4)
registration successful

This particular registration can then be easily found again:

$ approov registration -list | grep special
Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=com.criticalblue.demo-special-1041  SDK:Android(2.0.4)  registered:2019-05-29 17:07:22

Special Library Registration

By default, the SDK library ID for an app is determined by the approov tool that analyses the app to be registered. In some cases though an Approov SDK may itself be embedded inside another SDK within the app. In this case the tool cannot automatically locate the SDK and, in any case, it is possible for there to be more than one Approov SDK in the app.

In such a case you must use the specific ID of the Approov SDK that you want to register with. This can be specified with an additional command line option as follows:

$ approov registration -add my_app.apk -libraryID 1056
registering app
 Ac15BRFWqxn79dGsjOdVJXVqBQQ64ZWTAuKdrzRC9hc=my.app.com-2.0[3]-1056  SDK:Android(2.0.2)
registration successful

Approov Tokens

The result of performing a token fetch request with the Approov SDK is an Approov token held by the app. The app then adds the token to the requests made to the backend API to be used in the authorization flow. This section describes the format and content of the tokens.

Token Format

The Approov service uses JSON Web Tokens (JWTs) to represent the authenticity of client apps. This in an open and standard mechanism for representing claims in a tamper proof form and your web service will need to decode and verify these tokens as part of the Approov flow. An Approov token is typically a JWS (a type of JWT) which consists of three parts; the header, payload and signature where each part is base64url encoded and the parts are separated by periods:

header.payload.signature

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

The aboove JWS example is only for illustrations purposes, and is not an Approov Token. For a more in depth explanation off JWT tokens, please read this introduction.

To work with Approov Tokens, encode and decode, you need to use the Approov CLI tool.

JWT libraries take responsibility for generating the header and the signature. We just need to specify the signing algorithm we want to use, the secret key for the algorithm, and the un-encoded payload part. For JWTs, the payload is always a JSON object with the entries referred to as claims.

Note that Approov can also support another type of JWT, encrypted JWE tokens. Typically these only need to be used in conjunction with the Offline Security Mode. However, they might also be used if you wish to provide extended information within the anno claim (see below) and does not want to reveal the contents to any party that is able to read the tokens. JWE tokens use compact serialization which has 5 parts separated by ‘.’, where the first part is unencrypted and holds the base-64 encoded information about the algorithms used to encrypt the Content Encryption Key (CEK) and the payload. JWE Approov tokens may be enabled for a particular API domain by specifying jwe as a pin value when setting APIs, or using the -jwe option when adding it initially.

Token Signing

This section describes the approach used to sign the JWTs. This differs depending on whether a particular API domain is using JWS or JWE tokens as follows:

  • JWS: These tokens are signed using the HS256 profile, which is an HMAC signing with a secret. The secret is a 512-bit symmetric secret that is made available (in base64 format) via theapproov command line tool. See the section on extracting the secret.
  • JWE: The Approov service uses AES256GCKW for encrypting the CEK and AES256GC for content encryption. We continue to use the same symmetric key secret that is used for signing a JWS for the encryption of the JWE. As the symmetric secret key is 512 bits, but the key encryption algorithm only requires 256 bits, the Approov service uses the SHA256 hash of the signing key as the encryption key.

Note that all API domains added to an account typically share the same signing secret. However, in some cases, specific domains (such as the demonstration Shapes domain) may use a different secret. Tokens issued for those domains will therefore be signed differently.

Token Lifespan

The normal lifetime for an Approov token is 5 minutes, plus a grace period, from the point of issue by the Approov cloud service. The grace period of a few seconds is added to allow a valid token to be propagated and checked within a backend API system. If a particular device is identified as being risky in some way (such as it being rooted or jailbroken), then, independently of the overall security rejection policy that is set, it will also receive a shorter lived 2 minute token instead of a 5 minute one. This forces the device to make more frequent checks as it receives tokens.

The app itself should never cache Approov tokens that it gets from the Approov SDK. Instead, a fresh call to fetchApproovToken or fetchApproovTokenAndWait should be made each time an Approov token is required. If a token has already been fetched, and is not yet expired, then it will be returned immediately. However, each fetch call does some basic app environment checking and, if issues are discovered, performs a complete attestation check to fetch a new token. Thus an Approov token lifetime should be considered to be up to 5 minutes, since any cached token may be discarded at any point.

Token Claims

Depending on the source of an Approov token (the main Approov service or the Failover) the claims it contains may vary. However, the expiry (exp) claim is always present. The details of the claims are provided in the table below:

Key Name Type Description
exp Expiry Number The only mandatory claim for Approov tokens. It specifies the expiry time for the token as a Unix timestamp.
did Device ID String This claim identifies the device for which the token was issued. This is a base64 encoded string representing a 128-bit device identifier. Note that this is not, strictly speaking, a device identifier as it is also influenced by the app identifier and may change if the same app is uninstalled and then reinstalled on the same device. This claim is not included by tokens from the failover.
ip IP Address String This holds the IP address of the device as seen by the Approov cloud service. It is provided in a human readable IP address format (in either IPv4 or IPv6 format). In practice this value can often change between the time a token is issued and the time it is sent to your backend, so you should never block if it differs, but you may include it as a signal that tokens have somehow been stolen and are being replayed. This claim is not included by tokens from the failover or if the IP Tracking Policy for the account has been set to none.
iss Issuer String This is an optional claim that is only provided in certain types of Approov tokens. It may be present in tokens issued by the failover system. It is also present as a user-settable value for long lived Approov tokens designed for use with server-to-server communication.
anno Annotation Embedded JSON This is an embedded JSON array of strings showing the list of flags that are set and are in the annotation set for the security policy that is selected. This allows additional information to be collected about the state of a particular device without necessarily causing an attestation failure. Note that if there are no possible annotations then this claim is not present at all. This claim is not included by tokens from the failover.
pay Payload Hash String An optional claim that is added if the protected app passes a token binding argument to the setDataHashInToken method. The claim value is set to the base64 encoded SHA256 hash of the provided payload string. This is typically used to bind an Approov token to some other data used by your app to enhance security (like a user auth token). This claim is not included by tokens from the failover.
mpk Measurement Proof Key String An optional claim to provide the measurement proof key if a measurement has been requested by the SDK on the domain for which the token is issued. This is a base64 encoded 128-bit proof key value. Note that if measurement is being used, then typically JWE tokens will be used to keep this claim secret. This claim is not included by tokens from the failover.
imh Integrity Measurement Hash String An optional claim to provide the integrity measurement hash if a measurement has been requested by the SDK on the domain for which the token is issued. This is a base64 encoded 256-bit SHA256 measurement value. Note that if measurement is being used, then typically JWE tokens will be used to keep this claim secret. This claim is not included by tokens from the failover.
dmh Device Measurement Hash String An optional claim to provide the device measurement hash if a measurement has been requested by the SDK on the domain for which the token is issued. This is a base64 encoded 256-bit SHA256 measurement value. Note that if measurement is being used, then typically JWE tokens will be used to keep this claim secret. This claim is not included by tokens from the failover.

Checking Token Validity

A checking option is provided that allows any Approov token to be checked. This is useful for debug purposes during your integration with Approov. Simply use:

$ approov token -check eyJhbGciOiJIUzI1Ni…
invalid: expired JWS {"anno":["app-not-registered"],"did":"h4gubfCFzJu81j/U2BJsdg==","exp":1558626228,"ip":"1.2.3.4","pay":"6ZIvH1YelZyc2hx1iRRqD3dWNcFgjVtCon+7921XDjg="}

The Approov token content is decoded, and information is provided about whether the token was valid or invalid. It also shows if the token has expired or not. Remember that expiry and signature validity are independent. The validity of a token (i.e. whether it is signed with the valid Approov token secret) depends on whether the request came from a valid app instance and also the security policy in place in the account. The expiry is purely related to how long ago the token was issued. Tokens normally only have a 5 minute lifetime.

The content of the decoded token will depend on other Approov features that are being used. This particular token is issued from an account with a non-default annotation policy, that provides some reasons why a token may be invalid, in the anno claim. It also has a pay claim as a result of using setDataHashInToken.

Note that the token check is also able to process encrypted JWE tokens. It shows if they are valid and can decode their content.

Loggable Tokens

It is generally not recommended to log Approov tokens within an app. This is because it might be possible for an attacker to turn logging on in a production app and use this as a mechanism to steal valid Approov tokens that have been received. Furthermore, the raw Approov tokens are also very opaque since they are just base64url encoded JWT strings and therefore not very useful for debugging.

A facility is provided in the SDK to get a “loggable token” from the result data returned from fetchApproovToken or fetchApproovTokenAndWait. This provides a decoded string form of the Approov token, and is therefore much more useful for debug purposes. The returned string holds a JSON object containing the payload contents with an additional sip claim, as demonstrated by the following example:

{
  "anno": [
    "app-not-registered"
  ],
  "did": "h4gubfCFzJu81j/U2BJsdg==",
  "exp": 1558626228,
  "ip": "1.2.3.4",
  "pay": "6ZIvH1YelZyc2hx1iRRqD3dWNcFgjVtCon+7921XDjg=",
  "sip": "HggK-u"
}

Note that if no valid token was returned from the Approov token fetch request, or the returned token was a JWE, then a simple token giving the error is provided, e.g.:


{
  "error": "POOR_NETWORK"
}

{
  "error": "JWE"
}

As mentioned, a loggable token includes a special sip claim. This contains the first few characters of the signature of the token. Six characters are provided in base64, giving 36 bits out of the 256 bits provided in the complete signature. Thus it is not sufficient to create a validly signed token, but, if the signing key is known, it is sufficient to check, with a very high degree of accuracy, whether an Approov token is valid. Note that app itself never knows the symmetric secret used for signing tokens. Its possible to check a loggable token in the same way as a JWT as follows:

$ approov token -check '{"anno":["app-not-registered"...'
valid: loggable JWS

Note that the loggable token must be enclosed in single quotes so that it is treated properly as a single command line parameter. A statement is provided about whether the token was valid or not. It does this by reproducing the complete Approov token on the cloud service with the correct signature and then checking this against the prefix part of the signature that it knows from the sip.

Long Lived Approov Tokens

It is possible to generate Approov tokens that last much longer than the typical 5 minutes when they are issued to mobile apps. The facility allows tokens for an arbitrary duration to be created. It requires an administration level management token. To create one use:

$ approov token my-admin.tok -genLongLived server-token,90d
token will expire at 2019-06-03 13:24:38
WARNING: Long lived token should never be integrated into public clients and their security must be carefully managed
eyJhbGciOiJIUzI…

This generates an Approov token that is valid for 90 days. The server-token parameter is included in the iss claim of the token. It can be used to identify particular long lived tokens, or even to revoke certain ones by backend API checking. In effect this claim could be used as a type of API key within the overall signed Approov token.

The generated Approov token can be checked and decoded as follows:

$ approov token -check eyJhbGciOiJIUzI…
valid: JWS {"exp":1567078473,"iss":"server-token"}

Long lived tokens can be used to authorize communication to the protected backend API for server-to-server communication and test environments. This allows a particular API to be accessible both from mobile apps and also via other servers without having to create a different authorization mechanism.

Long lived tokens must never be included in public clients, such as web pages or mobile apps. They should only be used in circumstances where the token can be protected in a server environment.

Long lived tokens cannot be generated for the shapes.approov.io demo endpoint.

IP Tracking Policy

The IP tracking policy determines how Internet Protocol addresses are dealt with in your Approov account. When your account is setup the policy will be token. This means that the IP address of the device requesting an Approov token will be included in the token itself in the ip claim. However, in accordance with Approov’s privacy notice, no record will be kept of the IP address in internal logs or records. This is because IP addresses can be considered to be Personally Identifiable Information (PII) and the Approov service does not collect this by default for an account holder’s end customers. IP addresses are only stored after transformation through a one-way hash function from which the original IP address cannot be recovered.

If desired the IP tracking policy can be changed to none so that the IP address is not even included in the issued Approov token.

$ approov policy -setIPTracking none
WARNING: updating the IP tracking policy will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
IP tracking policy was set successfully

An adminstration level management token is required to make an IP tracking policy change.

A policy of full is also available whereby permission is given for Approov to record IP addresses. This may allow more sophisticated correlation analysis between IP addresses and device characteristics. This may be carried out in conjunction with Approov support.

The current IP tracking policy can always be retrieved as follows:

$ approov policy -getIPTracking
IP tracking policy is none

Backend Integration

This section describes the changes that are needed in the backend API to check that an Approov token is valid.

Requirements

To protect a backend API with Approov it is necessary to check that incoming requests have a valid Approov token. This might be required on all endpoints or just on some that demand additional security. Typically, token checking will be performed in some middleware layer of the backend rather than be implemented in the business logic.

Approov token checking is often performed in addition to user authorization, as an orthogonal activity. User authentication and authorization ensure that a known user is invoking the software making the request. Approov checking ensures that the software making the request is actually what it claims to be.

Approov uses standard JWTs as discussed in the previous section. This makes the backend API checking straightforward since almost all backend languages and technologies support JWT checking. To check an Approov JWS, the token secret must be made available to the backend code. The token will be signed with the HS256 profile, and the shared symmetric token secret is needed to perform the check. Libraries for working with JWTs are available in most common languages, with many listed on the JWT homepage.

There are essentially three cases which should cause a request to be rejected:

  • Invalid Token: If the Approov token is simply missing or is not a correctly formatted JWT then this indicates some spoofed request and should be rejected.
  • Incorrectly Signed Token: If the token is not correctly signed then it must be rejected. Obviously an attacker can synthesize an incorrectly signed token. An app is also provided with an incorrectly signed token if the attestation has been rejected. Note it is important to ensure that the signing method of the JWT is HS256.
  • Expired Token: Any token that has expired must be rejected, as under normal operation this should never happen. The app should never cache the Approov token, and the Approov SDK never provides a token that has expired. A small grace period is built into the token lifetime to allow for transmission and propagation delays in the backend systems. It is important to make sure that the server checking the tokens is correctly time synchronized.

Note that the actual behavior for an invalid token does not have to be rejection, although this is the typical approach. An invalid token event could simply be used for logging or as a signal to require some additional authentication and security checks on a particular user.

We recommend that the backend integration has a dynamic facility to enable or disable the Approov token check. This allows traffic to be passed through without the check in case of some emergency situation. Moreover, you may wish to collect logging for events where invalid Approov tokens are being provided.

Token Secret Extraction

The current Approov token secret can be obtained with the following command:

$ approov secret myadmin.tok -get
dEitzKUYJLQ…

An administration token (myadmin.tok in this case) must be specified to gain access to the Approov token secret. It is provided as a base64 encoded string, representing a 512-bit value that is randomly assigned to the account during signup. The value of the secret must be protected with extreme care. The number of individuals with exposure to the value of the secret should be minimized and it should never be checked into a source control system. Ideally, it should only be made available to the runtime environment of the backend servers.

Example API Integration

The first step in integrating Approov is to configure the server to check for the presence of a valid token. We recommend you add the token as a header in requests to the servers that you want to protect. The best way to integrate the token checking logic is dependent on your server architecture, but the steps you have to take are common to any implementation:

  • Check for the presence of the header containing the Approov token.
  • Extract the token.
  • Verify the token is correctly signed with your secret and not expired.

Here we have a simple server that we want to protect. To demonstrate the integration procedure, this guide extends a very simple web service, built in the Python language and using the Flask framework. The examples use the pyjwt library for working with JWT tokens:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
   return "Hello World!\n"

if __name__ == "__main__":
   app.run()

All the server does, is to return Hello World!. To check that the client app that is being used to access the API is genuine, we need to extract a token from a header and verify it is valid:

from flask import Flask, request, abort
import jwt
import json
import base64

app = Flask(__name__)

# Token secret value obtained with the Approov CLI tool: approov secret <admin.tok> -get
SECRET = bytes("VcXl2IgVUgzYZTHloVJ/gmkHyUuvXIRrN+aCjk7/4ZPwq24/XSF1DaTOcHCX6j3eUNUX3i4GBfcpNZKSeUefqA==","ascii")

# Function to check the validity of the token
def verifyToken(token):
  try:
    # Decode our token, allowing only the HS256 algorithm, using our base64
    # encoded SECRET
    tokenContents = jwt.decode(token, base64.b64decode(SECRET), algorithms=['HS256'])
    return tokenContents
  except jwt.ExpiredSignatureError as e:
    # Signature has expired, token is bad
    return None
  except jwt.InvalidTokenError as e:
    # Token could not be decoded, token is bad
    return None

@app.route("/")
def hello():
  # Get the Approov Token from header
  token = request.headers.get("Approov-Token")
  # If we didn't find a token, then reject the request
  if token == "":
    abort(401)
  tokenContents = verifyToken(token)
  if (tokenContents == None):
    abort(401)
  return "Hello World!\n"

if __name__ == "__main__":
   app.run()

We could also implement more complex checks or behaviors, but this simple example just blocks all invalid traffic. The API will now only respond to requests which contain an Approov-Token header which contains a valid JWT from our SDK.

This example embeds the SECRET directly into the code. This is purely for the purposes of explanation. We do not recommend putting the secret directly into the code, due to the risk of it being exposed through source control systems. We suggest that the secret is only added into the environment of the server, and then read through some environment variable. There are various technologies available to help with the management of backend secrets.

This example assumes that a signed JWS token is provided in the header. Approov also provides support for encrypted JWE tokens. This must be checked and decoded in a slightly different manner. Examples for these will be published in a future version of the documentation. If you wish to implement JWE checking before then, please get in touch with Approov support.

Generating Example Tokens

It is possible to generate example Approov tokens that have the same format as those generated for a mobile app. For example:

$ approov token -genExample my.api.io
eyJhbGciOiJIUzI1N…

The API domain, for which the token is being generated, must be provided in the same way that the app provides it for a call to fetchApproovToken. The result is a valid token that lasts for one hour. The extended expiry time of these tokens provides for longer debug sessions using a single token. For example, the token can be used in synthetic queries made to a backend API to check that it is working correctly. There is no need to know the actual token secret to use this facility as the token is automatically signed with the correct secret.

If an API domain is provided that has not been added for the account then no token can be generated. If the API domain is associated with encrypted JWE tokens then one is generated, to allow verification of the JWE decryption logic in the backend.

The example tokens contain a fake IP address and device ID that are in the correct format. A -setDataHashInToken option is also provided for testing the Token Binding feature. You can decode the example Approov token to see what it contains:

$ approov token -check eyJhbGciOiJIUzI1N…
valid: JWS {"did":"ExampleApproovTokenDID==","exp":1558980988,"ip":"1.2.3.4"}

By default a valid Approov token is generated. But it is also possible to generate an invalid or failover token to test the backend API handling with such tokens. For instance:

$ approov token -genExample my.api.io -type failover
eyJhbGciOiJIUzI1N…
$ approov token -check eyJhbGciOiJIUzI1N…
valid: JWS {"exp":1558981918}

A failover token is only guaranteed to contain an expiry time and may have no other claims.

Token Secret Update

The Approov token secret can be changed with the following command:

$ approov secret myadmin.tok -update
WARNING: updating the token secret will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return:

This requires an administration token (myadmin.tok in this case) and a confirmation of the operation is required. If confirmed then the new token secret value is output as follows:

new Approov token secret is VcXl2IgVUgz…

If the Approov token secret is changed then this has an impact in production within 30 seconds for newly issued tokens. This means that for a period of up to 5 minutes the backend API may see a combination of tokens signed with the old secret (if they have been cached and reused by the app SDKs) and new ones signed with the new secret (for freshly obtained tokens).

In general it is hard for a backend integration to deal with two different secret values, so we recommend the following procedure if rotating the token secret:

  1. Disable Approov token checking on the backend API. This will let any traffic through for a short period of time,
  2. Execute the command to update the token secret and copy the new token secret value.
  3. Integrate the new Approov token secret into the backend API.
  4. Re-enable Approov token checking. Assuming this process takes more than 5 minutes, all tokens in use will be using the new token secret.

Public Key Pinning Configuration

This section describes how certificate public key pins can be defined so that they can be made available in the initial and dynamic configuration of the apps using Approov. The next section deals with how the app can use this pinning information.

Background

It is essential that all of the channels over which Approov tokens may be transmitted use pinned connections. This means that the app should only be willing to establish a TLS connection where the presented certificates are both valid and where at least one public key of a certificate in the chain is specifically white listed (aka pinned), by the app itself, as being valid.

Pinning is necessitated by the fact that an attacker may be able to control both the device and also the channel over which it communicates. In the absence of pinning attackers are able to install additional certificates on the device as being trusted, and then use a proxy to decrypt any traffic from the mobile app to the API endpoints. This method can then be used to steal valid Approov tokens from the communication channel in order to make spoofed requests, as though they were coming from the app.

Pinning represents good security hygiene. It prevents real users from having their traffic intercepted if an attacker is able to trick them into installing an additional certificate on their device that then becomes trusted for TLS communication. Pinning ensures that the app avoids completely delegating its trust to the device.

There are various existing mechanisms to implement pinning in an app. Although conceptually identical, there are significant implementation differences between Android and iOS:

  • Android: There is a good overview on TLS more generally here. This article provides specific information on how pinning can be implemented for various different HTTP stacks that might be used in an app. Recent versions of Android have also added a network security configuration feature that allows pins to be specified at the manifest level for an app. Pinning with the OkHttp stack is straightforward using the CertificatePinner class.
  • iOS: There are fewer resources targeting iOS, but this is a good place to start. TrustKit is a popular library used to implement pinning, and there is also a version for Android.

These standard methods can be used to pin an app’s connections with the backend API server.

Note that the Approov SDK uses its own methods to pin the connections it makes with the Approov cloud service, so you can be sure that this channel is defended without any further work.

Static Pinning Issues

Approov provides a dynamic pinning mechanism, discussed in the next section. This section discusses some of the limitations of traditional static pinning.

Under normal circumstances public key pins do not have to be changed very often. Approov uses public key pinning rather than certificate pinning. This means that the pin is actually to the public key of the certificate rather than a hash of the whole certificate contents. The advantage of this is that, if certificates are changed simply because they are expiring and need to be renewed, then the same public/private key pair may be used to generate the new certificate without invalidating the pins.

The difficulty arises when a certificate has to be revoked and replaced if there is a concern that the private key has been compromised (or lost). This could allow an attacker to generate fake certificates and spoof the endpoint, intercepting traffic from the app if they are able to insert themselves as a Man-in-the-Middle (MitM) in the network.

Such an event necessitates a change in the public key pin for the certificate, as a new private key needs to be generated. If this is simply changed immediately then it would prevent apps with the public key pin from connecting to the API. The app would no longer work. It is recommended practice to also include a backup pin inside in the app that could be used in such an emergency. But this requires careful management and also relies on the backup’s private key not being compromised at the same time.

Thus the disadvantage of the static pinning methods described in the previous section is that they fix the set of valid pins into the app itself as part of its configuration. This means that for an app to get a new set of pins a new version of the app must be released and be installed on a user’s device. In reality this can take many days, or even weeks, for most of the apps to update and there may always be a stubborn cohort whose apps are never updated. These would be denied access once the pins are changed, and may end up as either permanently lost users or ones which increase user support load.

Static pinning causes problems both with the speed of incident response and user retention. Ideally what is required is a means to transmit the updated pins over-the-air immediately to invocations of the app without any need for an app update. Moreover, this needs to be done in a secure manner to prevent an attacker using this as a back door to inject their own pins to undermine the pinning protection. Approov offers a dynamic pinning solution that fulfils these requirements and is described in the next section.

Approov Dynamic Pinning

Approov holds the set of public key pins for API domains being protected, inside the SDK configuration file. The initial SDK configuration is obtained and added into the app content. This means that the pins are available from this configuration as soon as the app starts with no need for network configuration. This is convenient for apps developed using frameworks that require any public key pins to be presented very early during the initialisation of the app. If the pins are changed then subsequent app registrations cause a warning message to be issued to update the initial configuration with the latest information.

The overall architecture is as follows:

Pinning Architecture

The SDK configuration is signed using Elliptic Curve Cryptography (ECC), with the public key held in the initial SDK configuration and the private key held securely in Approov’s servers. This allows over-the-air dynamic updates to the configuration to be sent to running apps that can be verified as being untampered and authentically issued by the Approov servers. This is guaranteed even if the update is transmitted over a channel already compromised by a MitM attack. An attacker cannot know the private key to tamper with an update. They could block an update of course, but only while they have control of all communication, so any other use of the app will cause an update to be received.

If any change is made to the API domains and their pins using the approov commands described here, then an updated dynamic configuration will be transmitted to all any app that requests a new Approov token. The dynamic configuration is signed with the ECC private key, preventing any possibility of tampering and proving that the update has been issued by the Approov servers. This updated dynamic configuration will be written to the local storage of the app, and will either have an impact next time the app is started or immediately, depending upon the implementation of the pinning in the app. This verified updated configuration usurps the settings previously available from the initial SDK configuration.

This dynamic pinning is primarily designed to protect the channels over which Approov tokens are being transmitted. However, there is no reason why it can’t be used to protect any domain that the app communicates with, even if no Approov tokens are transmitted. The only restriction is that there must be sufficient knowledge about how certificates will be presented on that endpoint so that there is notice if there is an upcoming change that may impact the public key.

The dynamic configuration update can also be used to perform over-the-air updates of the networking access rules (in terms of domain names and timeouts) that are used by the Approov SDK to access the Approov cloud servers.

Managing Pins

Any domain can have a list of pins expressed as subject public key info hashes in base64. A pin is considered to have passed if any part of the certificate chain includes one of the pins provided. Thus intermediate public key pins are supported. Backup pin(s) can also be included if desired.

Facilities are provided for extracting the public keys of certificates directly, or live from existing endpoints. Certificate extraction allows pins to known prior to certificates going into service, so that the pins can be added to the Approov server configuration ahead of time as additional pins. Ideally this should happen some time before the new certificate is about to go live. This ensures that there is absolutely no interruption of service to the app since it will have already received the configuration update prior to the certificate changing.

If the app is able to receive a new configuration update and immediately change the pins being used, then the update only needs to occur early enough that any prior Approov token fetch will have received the configuration revision. This only needs to be 5 minutes. In other cases, where the app cannot immediately update its pins on receipt of a new configuration, then a longer time period is required. This will depend on the circumstances in the app but it is generally advisable to push out a new configuration some days ahead of the actual change.

Note, once a change has been completed, any redundant pins can be removed in a new configuration. If they were rotated due to a breach then this is especially important, since we no longer want the app to trust the pin.

Pinning For New API Domains

When a new API domain is added to the set of APIs, a pin for accessing that domain is automatically added. This is the pin extracted from the leaf certificate presented by the domain’s server:

$ approov api -add my.domain.com
WARNING: adding the API will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
added new API domain my.domain.com with pin JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU=

We strongly advise you to implement pinning for the API domains you access in your app. This means that any communication with this particular domain will be pinned to the value shown. In some cases it might not be appropriate to pin to the leaf certificate value, perhaps because different certificates may be presented by different servers on the endpoint. You must verify the stability of the provided pin with the backend API implementation team before going into production. If the pin changes, then your app will no longer work until a new set of pins can be pushed out, using the dynamic configuration update mechanism.

It is possible to change the pin type so that no pin is added at all with the new API domain:

$ approov api -add my.domain.com -pinType none
WARNING: adding the API will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
added new API domain my.domain.com without pin

When a leaf pin is being collected, it is obtained from both a local network request and via a network request from the Approov cloud servers. The pin obtained must match, or else an error is generated, for example:

$ approov api -add my.domain.com
domain my.domain.com local pin 1O0wDRM/roe6UTctDVQ5aN/ASNYsGQFVzXYhO34t5GE= differs from remote pin OfxtkIyzmWFXb028KrIr3VqP0XHW41mbunxtNnS3eb4= (may be due to local firewall)

This means that the observations differ. This might indicate that the leaf certificate obtained is not deterministic, or may depend on the geographic location from where the request is made. Alternatively, it might just mean that the local network is subject to interception via a firewall (this can be checked with the -getCertChainPins command which obtains the full certificate chain obtained from a local request).

If you are confident that either the localor remote public key pin is the one required then the appropriate one can be selected using the -pinType option. E.g.

$ approov api -add my.domain.com -pinType remote
leaf pin for my.domain.com is OfxtkIyzmWFXb028KrIr3VqP0XHW41mbunxtNnS3eb4=

This selects the remote public key pin. Note that of course this means that the mobile app will not be able to run on the local network.

It is also possible to set an explicit pin value when adding a new API domain. This might be a domain that has been extracted using one of the methods discussed in the following sections. To do this the explicit option must be used for the pin type. For example:

$ approov api -add my.domain.com -pinType explicit -explicitPin Q62DnTGJsy1h+ude8HB5ZjKy0Vhg2pvTzjplWSD3hkk=
WARNING: adding the API will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
added new API domain my.domain.com with pin Q62DnTGJsy1h+ude8HB5ZjKy0Vhg2pvTzjplWSD3hkk=

Note that only a single pin may be set per domain. If you wish to set multiple pins then see Setting Pin Configuration.

Leaf Public Key Pin Extraction

The public key pin for a leaf certificate presented on a particular domain may be obtained as follows:

$ approov api -getLeafPin shapes.approov.io
leaf pin for shapes.approov.io is JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU=

This obtains the same pin that would be obtained by the -addoption described above. However, this doesn’t add the API domain; it only obtains the pin value that may be included manually in the JSON used to configure the API domains. Note that the -pinType option may be used and operates in the same manner as it does with the -add option.

Intermediate Public Key Pin Extraction

In some cases you might not want to pin against a leaf certificate but, instead, choose to pin against an intermediate certificate within the whole chain presented on a domain. A command is provided to extract this information:

$ approov api -getCertChainPins shapes.approov.io
pin JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU=, expiry 2020-05-22, CN=shapes.approov.io
pin JSMzqOOrtyOT1kmau6zKhgT676hGgczD5VMdRMyJZFA=, expiry 2025-10-19, CN=Amazon,OU=Server CA 1B,O=Amazon,C=US
pin ++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=, expiry 2037-12-31, CN=Amazon Root CA 1,O=Amazon,C=US
pin KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6+oP5I=, expiry 2034-06-28, CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\, Inc.,L=Scottsdale,ST=Arizona,C=US

This shows the full chain of certificates. In this example we use our demo endpoint shapes.approov.io that is hosted with AWS and uses an AWS issued certificate. You can see the chain right up to Amazon’s root certificate. Notice that certificates higher in the chain tend to have longer expiry times. Depending upon your particular backend setup it may be better to pin to one of these certificates rather than the leaf, which is more subject to change. Of course you need to ensure that any certificate you pin to cannot itself be used to sign certificates that an attacker might use. So, in general, try and pin as low in the overall chain as is practicable.

Note that the certificate chain is determined by making a local request. If the local network has a firewall that intercepts TLS traffic, then the resulting chain will not be the same as one observed by a device outside that network. Also be aware that, depending upon the backend API architecture, the observed chain may be influenced by the specific server that responds to the request. For example, different geographical regions may supply different certificates.

Certificate File Pin Extraction

As well as getting the public key pin from a live endpoint, it is also possible to get a pin from a certificate file. This is useful when you want to add a pin to a certificate that is not yet in service. Use the following to get the pin:

$ approov api -getCertPin my_cert.crt
pin jZetC3373f9dmwxg5YE9TCZzl4MYvp0eYTiEsbcTU34=, expiry 2020-05-10, CN=mydomain.com

This provides the public key pin along with the expiry time and subject of the certificate. Note that, to use this option it is necessary to have a PEM encoded certificate file. This is normally associated with a .cer, .crt or .pem file extension.

Getting Pin Configuration

The complete set of API domains that have been added to the account can be extracted into a JSON format file as follows:

$ approov api -getAll apis.json
API domains and pins written to JSON file apis.json

This command gets all of the API and pin information and writes it to the apis.json file. In this example the content is as follows:

$ less apis.json
{
    "my.domain.com": [],
    "shapes.approov.io": [
      "JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU="
    ]
}

This shows that Approov tokens can be issued either for my.domain.com or shapes.approov.io. The first domain has no pins (not recommended, unless pinning is being done in some other way) and the second has the given pin.

Note that if any API domain should generate JWE rather than JWS tokens then this is represented by having jwe as one of the pins for the domain. This entry is not transmitted to the SDK and is not part of its configuration.

Setting Pin Configuration

Pins may be set in the same format as that obtained with the API -getAll option. New domains may be added, removed or edited. For instance, we may edit the JSON received in the previous section with a different domain and two different potential pins.

$ less apis.json
{
    "another.domain.com": [
      "/ABoW73P1I3gWumpugB6zzHtqjl0+yFKRBEvJW8I3sQ=",
      "Wp/PdTn4cw7/jwsS9+DnheEm5AbtTEsmVPAGOGY3tHk="
    ],
    "shapes.approov.io": [
      "JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU="
    ]
}

We can then update the Approov server configuration as follows:

$ approov api myadmin.tok -setAll apis.json
another.domain.com: matched pin Wp/PdTn4cw7/jwsS9+DnheEm5AbtTEsmVPAGOGY3tHk=
shapes.approov.io: matched pin JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU=
WARNING: updating the APIs will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return: YES
APIs set successfully

An error is generated if the input is not well formed JSON. Each of the domains provided are checked to see if they are reachable and if any one of the pins provided match. This ensures that the configuration is valid prior to updating it in the Approov cloud and distributing it to apps. If there is a problem then the follow message is seen:

WARNING: problem detected with APIs, we do not recommend you proceed (unless caused by known local firewall issue)

You can still choose to proceed if the issue may be caused by your local connectivity, since the check is made via the local network, but you should be convinced that this is the only issue or else it would be unwise to proceed.

An administration level management token is required and further confirmation is expected. This is because these changes will have an impact on production within 30 seconds and as such are dangerous if domains have been accidentally removed or if pins are incorrect.

Any attempt to change the pins for the demo endpoint shapes.approov.io are ignored.

Checking API Configuration

An option is provided that checks that all of the specified API domains are accessible and that, if pins are provided, then they do actually match a certificate being presented on the domain. This can be used as a quick check that the API configuration that is currently set is valid. It is invoked as follows:

$ approov api -check
my.domain.com: no pins defined
shapes.approov.io: matched pin JG29gD8vWiEanTCVPjYLQ5yiYMKQqR05OH38yFf0kBU=

The current API configuration is obtained from the Approov cloud service, and then each of the domains is checked from your local machine. If the API domain is not accessible to obtain the certificates then that is considered an error. The command has a return code of 0 if it succeeded or 1 if it failed. Note that if the management token provided is invalid, or the Approov cloud service cannot be contacted, then this is not considered a failure since the explicit purpose of the command is to check the API endpoints themselves.

You can use this command to perform local continuous monitoring of your API domains and the pin validity. This means that if the configuration is invalid, either because the API configuration has been incorrectly modified, or because a certificate being presented has changed, then you will be able to get an immediate alert. The exact process for alerting will depend upon your internal processes, but there is a basic bash script for running the command in a continuous checking loop:

#!/bin/bash

while true; do
  eval "approov api -check"
  if [[ $? -eq 0 ]]; then
     sleep 2m
  else
    break
  fi
done

This checks the validity of the API configuration every 2 minutes. We suggest you do not check more frequently than this, as you may be rate limited or rejected if you make too many continuous calls to the Approov cloud service. The script exits if a problem is found. From there you should be able to automate raising an alert, perhaps via automatically sending an email. Note that this needs to be run in an environment with a solid Internet connection or else there may be false positives simply due to a lack of local connectivity.

If the configuration is invalid then your app users may be unable to use the app due to the pinning restrictions. In this case, you will need to modify the pinning configuration to allow app users to make connections to the API. The changed configuration is automatically pushed to running apps, although the speed of reaction to the pin change will depend on your apps’ implementation.

Public Key Pinning Implementation

The previous section described how the configuration can be managed to make use of the Approov dynamic pinning feature. This section provides examples of how the pins may be set using different languages and HTTP stacks.

Setting Pins

Pins can be set immediately after the Approov SDK has been initialized and any dynamic SDK configuration has been written. At this stage some pins will be available, either through the initial SDK configuration or through dynamic SDK configuration updates that have been received since the app was first installed.

This early access to the pins allows the pinning to be set up even for app development frameworks that require the information very early during the app startup. However, for these pins to actually have any impact in the app it is necessary to add code to communicate the pins to the TLS stack.

Reacting to Configuration Changes

Whenever an Approov token is fetched, a check should be made to see if a new dynamic SDK configuration has been received. This will happen for apps when a Pinning Configuration Change has been made in the account. The following code snippet shows how to perform this check and update, assuming that the approovResults variable holds the result of a token fetch:


if (approovResults.isConfigChanged()) {
    saveApproovConfigUpdate()
}


if approovResults.isConfigChanged {
    saveApproovConfigUpdate()
}

If a configuration update is received then this calls the saveApproovConfigUpdate method as defined here, or some equivalent for your app. This causes the new dynamic SDK configuration to be written to persistent storage so that it will be available before any network operations the next time the app is started.

A change to the SDK configuration is typically the result of some changes to the API domains and/or the pins that are set on them. Ideally, the app should react to those changes immediately by calling code to reset the pins associated with the APIs used by the app. Whether this is possible, or architecturally easy to achieve, will depend on your app and the framework in which it is developed. Resetting pins typically requires a new pin configuration to be provided while constructing a new instance of the HTTP client used for making API calls. We recommend this as the best approach where possible, since it makes your app very reactive to any pin changes.

If this is not possible, then the app will need a restart to apply the new pin configuration. In this case, we recommend building force restart functionality into the app which is triggered at a convenient point in the user flow after the pin configuration change. Alternatively, the app restart functionality may be postponed until a pinning failure exception is caught by the app. That way a restart only occurs when absolutely necessary.

Pinning Android OkHttp

This discusses how pinning can be implemented using the OkHttp stack which is popular on Android. The SDK provides a getPins method that can be used by the app to obtain the currently configured set of pins to be used. The pins will be set up immediately after the initialization of the Approov SDK.

// build the pinning configuration
CertificatePinner.Builder pinBuilder = new CertificatePinner.Builder();
Map<String, List<String>> pins = Approov.getPins("public-key-sha256");
for (Map.Entry<String, List<String>> entry: pins.entrySet()) {
    for (String pin: entry.getValue()) {
        pinBuilder = pinBuilder.add(entry.getKey(), "sha256/" + pin);
        Log.i(TAG, "Adding OkHttp pin " + entry.getKey() + ":sha256/" + pin);
    }
}

// now we can construct the OkHttpClient with the correct pins preset
okHttpClient = new OkHttpClient.Builder().certificatePinner(pinBuilder.build()).build();

This constructs a newokHttpClientobject that should be used for subsequent API requests. This object is suitably pinned using CertificatePinner so that connections are only possible if the pinned certificates are served. Note the public-key-sha256 parameter to getPins indicates the type of pinning being performed. In this case the pin is to the SHA256 hash of the subject public key information of the certificate.

To make your app reactive to pinning changes we recommend that you rebuild the pin set whenever a new dynamic configuration is received, as discussed in Reacting to Configuration Changes.

If your app uses an Network (as opposed to an Application) interceptor to obtain the Approov token, then it is important that you catch any SSLPeerUnverfiedException and fetch an Approov token to allow a dynamic configuration update to be received, as discussed below. Failure to do this may result in your app being unable to react at all to pinning changes, requiring a new app to be released to deal with an updated set of pins. The differences between Application and Network interceptors is discussed here.

When you are using an network interceptor to get the Approov token you must add additional code to ensure that a dynamic configuration update can be received if there is a pinning exception. This is because otherwise no update can be received, since the TLS connection to the endpoint is established before calling a network interceptor. So if the interceptor is never called there is no opportunity for the app to get any updated pins via a configuration update.

You simply need to add some code to check for a SSLPeerUnverifiedException in any failure callback, for instance:

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        if (e.toString().contains("SSLPeerUnverifiedException")) {
            Approov.TokenFetchResult approovResults = Approov.fetchApproovTokenAndWait(call.request().url().host());
            if (approovResults.isConfigChanged()) {
                saveApproovConfigUpdate();
            }
        }
        ...
    }
}

This tries to get a new Approov token in this exceptional case and then provide an update if a new dynamic configuration is received. The Approov token itself isn’t needed and simply isn’t read. Ideally your code should also rebuild the http client with the new pins from the getPins() Approov call at this point. This means that the next API call will use the updated pins and should therefore be able to complete.

Pinning Android HttpsUrlConnection

This discusses how pinning can be implemented for the HttpsUrlConnection class used for HTTP transactions on Android. Unfortunately this does not have a direct method that allows pins to be set. Instead, a custom hostname verifier must be created that checks the pins, before delegating to the standard verifier. Since this is done for each new TLS connection the pins used are entirely dynamic, so there is no need to have special code for handling SDK configuration changes.

The definition for the custom hostname verifier is as follows:

public final class PinningHostnameVerifier implements HostnameVerifier {

    // The HostnameVerifier you would normally be using
    private final HostnameVerifier delegate;

    /**
     * Construct a PinningHostnameVerifier which delegates
     * the initial verify to a user defined HostnameVerifier before
     * applying pinning on top.
     *
     * @param delegate is the HostnameVerifier to apply before the custom pinning
     */
    public PinningHostnameVerifier(HostnameVerifier delegate) {
        this.delegate = delegate;
    }

    @Override
    public boolean verify(String hostname, SSLSession session) {
        // check the delegate function first and only proceed if it passes
        if (delegate.verify(hostname, session)) try {
            // extract the set of valid pins for the hostname
            Set<String> hostPins = new HashSet<>();
            Map<String, List<String>> pins = Approov.getPins("public-key-sha256");
            for (Map.Entry<String, List<String>> entry: pins.entrySet()) {
                if (entry.getKey().equals(hostname)) {
                    for (String pin: entry.getValue())
                        hostPins.add(pin);
                }
            }

            // if there are no pins then we accept any certificate
            if (hostPins.isEmpty())
                return true;

            // check to see if any of the pins are in the certificate chain
            for (Certificate cert: session.getPeerCertificates()) {
                if (cert instanceof X509Certificate) {
                    X509Certificate x509Cert = (X509Certificate) cert;
                    ByteString digest = ByteString.of(x509Cert.getPublicKey().getEncoded()).sha256();
                    String hash = digest.base64();
                    if (hostPins.contains(hash))
                        return true;
                }
            }

            // the connection is rejected
            return false;
        } catch (SSLException e) {
            throw new RuntimeException(e);
        }
        return false;
    }
}

This may then be constructed for subsequent usage in https connections.

PinningHostnameVerifier pinningHostnameVerifier = new PinningHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());

Individual connections can then override their hostname verifier as follows, to ensure that the connections are correctly pinned:

connection = (HttpsURLConnection) url.openConnection();
connection.setHostnameVerifier(pinningHostnameVerifier);

Pinning iOS URLSession

An implementation for this is provided in Swift. It creates an implementation of urlSession(_:didReceive:completionHandler:) that may be used for the pinned connections. The method getPins from the Approov SDK is used to obtain the latest pins and dynamically match them against the certificate chain being authorized.

func urlSession(_ session: URLSession,
    didReceive challenge: URLAuthenticationChallenge,
    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

    let protectionSpace = challenge.protectionSpace

    // only handle requests that are related to server trust
    if protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
        // determine the host whose connection is pinned (this requires that the session delegate keeps a reference
        // to the URLSessionTask when the task is created)
        guard let urlSessionTask = sessionTask,
            let host = urlSessionTaskHost(urlSessionTask: urlSessionTask),
            let pins = Approov.getPins("public-key-sha256")?[host]
        else {
            completionHandler(.cancelAuthenticationChallenge, nil);
            return
        }

        // check the validity of the server trust
        let sslPolicy = SecPolicyCreateSSL(true, host as CFString)
        var secResult = SecTrustResultType.invalid
        guard let serverTrust = protectionSpace.serverTrust,
            // Ensure a sensible SSL policy is used when evaluating the server trust
            SecTrustSetPolicies(serverTrust, sslPolicy) == errSecSuccess,
            SecTrustEvaluate(serverTrust, &secResult) == errSecSuccess,
            secResult == .unspecified || secResult == .proceed
        else {
            completionHandler(.cancelAuthenticationChallenge, nil);
            return
        }

        // remember the hashes of all public key infos for logging in case of pinning failure
        var spkiHashesBase64 = [String](repeating: "", count: SecTrustGetCertificateCount(serverTrust))

        // check public key hash of all certificates in the chain, leaf certificate first
        for i in 0 ..< SecTrustGetCertificateCount(serverTrust) {

            guard let serverCert = SecTrustGetCertificateAtIndex(serverTrust, i),
                let spkiHashBase64 = publicKeyInfoSHA256Base64(certificate: serverCert)
            else {
                continue
            }

            spkiHashesBase64[i] = spkiHashBase64
            NSLog("URLSessionDelegate: host %@ public key hash %@", host, spkiHashesBase64[i])

            // check that the hash is the same as at least one of the pins
            for pin in pins {
                if spkiHashesBase64[i].elementsEqual(pin) {
                    NSLog("URLSessionDelegate: pinning valid")
                    completionHandler(.useCredential, URLCredential(trust: serverTrust))
                    // Successful match
                    return
                }
            }
        }

        // the certificates did not match any of the pins
        completionHandler(.cancelAuthenticationChallenge, nil);

        // log the hashes of all certificates in the certificate chain for the host - this helps with choosing the
        // correct hash(es) to put into the Approov configuration
        for i in 0 ..< SecTrustGetCertificateCount(serverTrust) {
            NSLog("URLSessionDelegate: pinning invalid for host %@ and certificate %d's public key info hash %@",
                  host, i, spkiHashesBase64[i])
        }
    }
}

These are helper functions to get the host from a URLSessionTask and to compute the SHA256 hash of the subject public key for a certificate. Much of the code complexity is for supporting the calculation of the subject public key hash for certificates prior to iOS 12, when there was no operating system support for it:

/*
 * Gets the host from a URLSessionTask's URL
 *
 * @param urlSessionTask  the URLSessionTask for which to determine the host
 * @return  the host, or nil if the host could not be determined
 */
func urlSessionTaskHost(urlSessionTask : URLSessionTask) -> String? {
    if let url = urlSessionTask.currentRequest?.url,
        let baseURLComponents = NSURLComponents.init(url: url, resolvingAgainstBaseURL: false),
        let host = baseURLComponents.host {

        return host
    }
    return nil
}

/*
 * Gets the subject public key info (SPKI) header for a public key's type and size. Only RSA-2048, RSA-4096, EC-256
 * and EC-384 are supported.
 *
 * @param type  key type, one of kSecAttrKeyTypeRSA or kSecAttrKeyTypeECSECPrimeRandom
 * @param size  key length in bits, one of 2048 or 4096 for RSA, one of 256 or 384 for EC
 * @return the SPKI header, or nil if no SPKI header is available for the requested combination of type and key size
 */
func publicKeyInfoHeader(publicKey: SecKey) -> [UInt8]? {
    let spkiHeader: Dictionary<CFString,Dictionary<Int,[UInt8]>> = [
        kSecAttrKeyTypeRSA: [
            2048: [
                0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
                0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00
            ],
            4096: [
                0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01,
                0x01, 0x05, 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00
            ]
        ],
        kSecAttrKeyTypeECSECPrimeRandom: [
            256: [
                0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a,
                0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00
            ],
            384: [
                0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b,
                0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00
            ]
        ]
    ]

    // get the SPKI header depending on the key's type and size
    guard let aPublicKeyAttributes = SecKeyCopyAttributes(publicKey) as NSDictionary?,
        let aPublicKeyType = aPublicKeyAttributes[kSecAttrKeyType] as! CFString?,
        let aPublicKeySize = aPublicKeyAttributes[kSecAttrKeySizeInBits] as! Int?
    else {
        return nil
    }

    return spkiHeader[aPublicKeyType]?[aPublicKeySize]
}

/*
 * Gets the public key from a certificate, using SecCertificateCopyKey if available.
 *
 * @param certificate  certificate from which to retrieve the public key
 * @return the public key, or nil if the key could not be retrieved
 */
func certificatePublicKey(certificate: SecCertificate) -> SecKey? {
    if #available(iOS 12.0, *) {
        // get the public key from the certificate
        return SecCertificateCopyKey(certificate)
    }
    else {
        // see TrustKit https://github.com/datatheorem/TrustKit/blob/master/TrustKit/Pinning/TSKSPKIHashCache.m
        // lines 221-234:
        // create an X509 trust using the certificate
        let policy = SecPolicyCreateBasicX509()
        var aOptionalTrust : SecTrust?
        guard SecTrustCreateWithCertificates(certificate, policy, &aOptionalTrust) == errSecSuccess,
            let aTrust = aOptionalTrust
        else {
            return nil
        }
        // get a public key reference for the certificate from the trust
        var aResult : SecTrustResultType = SecTrustResultType.invalid;
        guard SecTrustEvaluate(aTrust, &aResult) == errSecSuccess else {
            return nil
        }
        return SecTrustCopyPublicKey(aTrust)
    }
}

/*
 * Computes the SHA-256 hash of the certificate's public key info and encodes it as Base-64
 */
func publicKeyInfoSHA256Base64(certificate: SecCertificate) -> String? {
    // get the public key from the certificate
    guard let publicKey : SecKey = certificatePublicKey(certificate: certificate),
        let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil)
    else {
        return nil
    }

    // get the subject public key info (SPKI) header depending on the key's type and size
    guard let aSPKIHeader = publicKeyInfoHeader(publicKey: publicKey) else {
        return nil
    }

    // Compute the hash of the SPKI header and the key data, and base-64 encode
    let shaContext = UnsafeMutablePointer<CC_SHA256_CTX>.allocate(capacity:1)
    CC_SHA256_Init(shaContext)
    CC_SHA256_Update(shaContext, aSPKIHeader, CC_LONG(aSPKIHeader.count))
    CC_SHA256_Update(shaContext, CFDataGetBytePtr(publicKeyData), CC_LONG(CFDataGetLength(publicKeyData)))
    var keyHash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    CC_SHA256_Final(&keyHash, shaContext);
    let keyHashBase64 = NSData.init(bytes: keyHash, length: Int(CC_SHA256_DIGEST_LENGTH)).base64EncodedString(options: [])
    return keyHashBase64
}

Testing the Pinning Implementation

It is important that any pinning implementation is tested, by forcing the app into situations where the pins are incorrect with respect to the certificates being presented on the API endpoints. It is necessary to show that this is handled correctly and, importantly, that dynamic configuration updates issued from the Approov cloud update the app’s pins correctly.

In production it would be undesirable to change the certificates on the real API endpoints to do the testing as this would impact all app users. To avoid the need to do this, Approov provides facilities to modify the set of pins provided to a particular device being tested. These facilities are discussed in detail in the Managing Devices section. The idea is that for a particular device the pins can be set to a known bad value that will trigger the pinning exception so that it can be tested, i.e. Approov changes what the app is pinning against rather than what is being presented by the API endpoint. This is an equivalently good test.

The following is a recipe of commands to perform a test on your pinning implementation. Firstly you will need to get the ID of your device, see Extracting the Device ID. You should then check that the app is operating normally and that you have pins defined for the endpoints you are trying to protect with pinning. You can check what pins are set using Getting Pin Configuration.

Next, you can block all of the communication by forcing bad pins to be downloaded as a dynamic configuration for your app. For example:

approov device -add h4gubfCFzJu81j/U2BJsdg== -pinMode block

Note that nothing will happen until the next Approov token fetch is made. An existing token may have a lifetime of up to 5 minutes. A new dynamic configuration should then be received. You may be able to see this in the app’s logging. How quickly your app can respond to this update will depend upon the app’s architecture. Once the update has been actioned, you should see an appropriate error from your app when it attempts to make API calls. We suggest you go through this whole process without exiting the app.

You can then simulate what would happen to your running app if a new set of valid pins are transmitted to your app via a dynamic configuration update:

approov device -add h4gubfCFzJu81j/U2BJsdg== -pinMode pin

This sets the pins back to the default for the account. Once any currently fetched Approov token has expired (up to 5 minutes) a dynamic configuration update is transmitted to the app. Depending on the pinning implementation, this might have an immediate impact or after some time if the pins can only be are only rebuilt then. If your app’s pinning implementation cannot rebuild the pins until the app is restarted then it should provide a user message to that effect.

It is vitally important that you test that your app is able to recover from having incorrect pins to correct ones via a dynamic configuration update. This is the process that your app must successfully follow should there be a need to supply new pins to your app over-the-air.

It is also possible to block all app communication, including that between the Approov SDK and the Approov cloud as follows:

approov device -add h4gubfCFzJu81j/U2BJsdg== -pinMode blockAll

In this case you will receive a MITM_DETECTED error from token fetch attempts. This will allow you to check the handling of this in your app.

Security Policies

A security policy is set at the account level and determines which apps may be issued with a valid Approov token.

What is a Security Policy

Approov provides fundamental checks regarding the integrity of the app itself, so that valid Approov tokens are only issued to valid app instances. Additionally, various other runtime integrity checks are also performed and the results transmitted to the Approov cloud server in a secure manner. Whether these checks should result in an invalid Approov token or not is determined by the security policies that are selected. This gives you the flexibility to permit accesses or block them according to your risk assessments and the characteristics of your user base. For instance, if you develop a banking app, you may take the view that no rooted device should receive a valid Approov token. However, other consumer apps, deployed to certain markets or demographics, may require rooted devices to be accepted. Thus the security stance must be informed by the characteristics of the app’s user base and the particular threats that Approov is being used to defend against.

A security policy is shown and specified as three comma separated individual selections as follows:

  • Rules Policy: The overall policy rules that should be executed. Currently the only valid value is default.
  • Rejection Policy: Policy to be used to determine if a particular request should be rejected and an invalid Approov token issued.
  • Annotation Policy: Policy for including flag information in the Approov token itself regarding characteristics of the device or runtime environment that have been determined by the Approov analysis.

When a new account is created it is allocated a security policy of default,default,default. This provides a restricted policy of only issuing valid Approov tokens where no threat has been detected.

Note that security policies may also be set for individual devices as well as the whole account as described in Managing Devices. For this reason blanket policies such as whitelist and blacklist are provided that would not normally be applied to the whole account.

Getting Current Security Policy

It is possible to determine the current security policy for an account using the following command:

$ approov policy -get
security policy is default,allow-root,all

This indicates that the default rules policy is being used, with a rejection policy of allow-root and an annotation policy of all.

Changing Security Policy

The security policy may be changed for the account using the following command:

$ approov policy myadmin.tok -set default,allow-root-and-jailbroken,xposed
WARNING: updating the security policy will have an immediate impact on your apps in production. If you wish to continue then please enter YES and return:

This requires an admin level management token available in myadmin.tok. This particular command will change the security policy to allow jailbroken devices as well and to only show the xposed flag as an annotation. The user must confirm the confirmation prompt in order to apply the new security policy.

If adding the new security policy is confirmed then it will have an effect on the account within 30 seconds. Care must be taken when making changes like this to a live account in case a new policy has a detrimental impact on legitimate users of your app.

Rejection Policies

The rejection policy is the second parameter in the comma separated list for setting a security policy. It is used to determine the set of characteristics that are used to reject a particular Approov token request and to generate an invalid Approov token. The options are as follows:

Policy Description
default Reject all rooted/jailbroken devices, emulators and frameworks.
allow-xposed Allow Android Xposed, but otherwise reject all rooted/jailbroken devices and emulators. This option is provided because for some markets Xposed is a popular modding framework for devices, without an intent to attack a given app. If an Android device is rooted without using Xposed, or if an Xposed module being used is implicated in data stealing, then a rejection is made.
allow-root Accept rooted Android devices but reject emulators/simulators and Cydia/Cycript/Frida. Detected jailbroken iOS devices are rejected.
allow-root-and-jailbroken Accept rooted Android, iOS jailbroken devices, emulator/simulators but reject Cydia/Cycript/Frida.
whitelist A highly permissive policy that generates valid Approov tokens except if there is direct evidence of tampering of the Approov SDK. This generates valid tokens even if the app is not registered. This will typically only ever be applied to individual devices.
blacklist Always rejects, thus generating invalid Approov tokens. Typically this will only ever be applied to individual devices.

Annotation Policies

The annotation policy is the third parameter in the comma separated list for setting a security policy. The following represent valid options. This determines what content any anno claim in the Approov token may have.

Policy Description
default No annotations are made, so it is not possible to determine the reason for rejection by analysing the Approov token itself.
xposed Only the xposed flag should be included in the annotation. This is useful for cases where Xposed is being allowed but other backend authorization mechanisms may put additional measures in place for devices that have checked positively for this framework.
all Indicates that all available annotations should be included in the Approov token.

Annotation Results

These are the potential strings, and their meanings, that may appear in the anno claim if the all annotation policy is used. This allows the potential reasons for an invalid Approov token issue to be seen within the token. Note that in general, this annotation option should never be enabled for the whole account, other than during early development, since this information may provide an attacker with information about the reasons for any Approov rejection. If constant monitoring of these properties is required in the backend API then we suggest that JWE Approov tokens are used so that the content is encrypted. Note also, that the list below may change at any time as new threats are recognized or old, more specific, threats are subsumed into wider ranging categories.

Annotation Meaning
debug The app is currently being debugged
scan-fail One or more of the checks that are used to scan the status of the app and device have failed. This is normally considered suspicious and causes an invalid Approov token to be issued.
tampered One or more of the checks used to test the runtime integrity of the app have failed. This is considered suspicious and causes a invalid Approov token to be issued.
bad-nonce Indicates that the nonce value used to salt the HMAC integrity of the data transfer with the Approov cloud service may have been replayed. This is considered suspicious.
bad-hmac Indicates that the HMAC calculation used to ensure the integrity of the data transfer between the SDK and the Approov cloud service has failed, which may indicate a tampering that normally causes an invalid Approov token to be issued.
devices-exceeded Indicates, that for an account with a restricted number of devices allowed, that limit was exceeded.
device-changed Indicates that there is evidence that the device ID is being tampered with.
app-not-registered The app being run has not been registered, which may indicate that a fake or tampered app is being used.
rooted The Android device, that the app is running on, has been rooted.
jailbroken The iOS device, that the app is running on, has been jailbroken.
root-risk Analysis indicates that the device may be rooted but this cannot be categorically proved.
emulator The app is running on an Android emulator (such as QEMU). Generally, if the SDK is running on x86, then it is more likely to be an emulator, so only an internal whitelist of known real x86 devices are allowed through.
ios-simulator The app is running on the iOS simulator.
xposed The Android app has the Xposed framework installed
xposed-unsafe The Android app has the Xposed framework installed and analysis shows that it is using a module implicated in the extraction of data from an app.
cydia The iOS app has the Cydia framework or app installed
frida The app has the Frida framework installed.
cycript The iOS app has the Cycript framework installed.
magisk-man-hidden Indicates that the Android Magisk root manager is installed and is being actively cloaked.
init-config-outdated Indicates that the initial configuration for the SDK is not the latest available, and thus had to be dynamically updated.
devicecheck-ban Indicates that the physical iOS device has been permanently banned using the Approov DeviceCheck integration capability.

Managing Devices

Approov provides facilities to apply specific policies to particular devices that are being used. This affords flexibility in work practices during development, in continuous build systems, and to allow pentesting and analysis of an Approov protected app.

Extracting the Device ID

In order to modify the policy of a particular device, its ID must be extracted. There are two methods by which this extraction may be done.

The first method is via the logging that is output by Approov when the SDK is first initialized. On Android this can be seen by looking in logcat under the Approov tag. For instance, this is logging from a demo app starting:

2019-05-20 13:56:57.789 29546-29546/com.criticalblue.demo I/Approov: test-account, com.criticalblue.demo, 2.0.4(1021), h4gubfCFzJu81j/U2BJsdg==

The base64 string h4gubfCFzJu81j/U2BJsdg== at the end of the line is the device ID. It represents a 128-bit ID for the app and device combination. On iOS this logging is available in the documents folder for the app in the file Approov.log.

The second method is via extraction from an Approov token that has been generated on the device. A loggable token contains the device ID directly in the did claim. It can be extracted from an ordinary Approov token using the command line tool, for example:

$ approov token -check eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkaWQiO…
invalid: {"did":"h4gubfCFzJu81j/U2BJsdg==","exp":1558626254,"ip":"85.233.105.183"}

Device ID Stability

To respect an individual’s right to privacy, both Apple and Google limit the ability to uniquely identify a device. The identifiers that are truly fixed for a device are hidden and device IDs are generated on-the-fly for new app installations. These, on-the-fly, platform generated device IDs are consistent for the same app but vary across iOS and Android as described below.

Approov device IDs are partly derived from the platform provided device ID, but also include information derived from the app identifier, so device IDs are always per-app device IDs. Thus if you are developing two different apps on the same device you will find that they will be allocated different device IDs. Moreover, any change to the package name of the app will also change the device ID.

Thus you must be aware that the ID for a particular device may change, and this will cause the applied policy for that physical device to be lost. The new device ID must be extracted and the device added again, with the old redundant one removed.

The underlying identifiers used to derive the Approov device ID are as follows:

  • iOS: The identifierForVendor is used. This ID remains constant per app vendor as long as there is one or more apps from the vendor installed. If all the apps are uninstalled then a new ID will be allocated. All apps from the same vendor share the same ID. Thus a good strategy is to always have another app signed by the same developer installed on the device to anchor the device ID for all of them.
  • Android: The ANDROID_ID is used. Since Android O, this is unique to an app installation and registered device user and is stable across app uninstalls and reinstalls. It will change if your app signing key changes or if the device user performs a factory reset.

Adding a Device Security Policy

An individual device may be assigned a specific security policy as follows:

approov device -add h4gubfCFzJu81j/U2BJsdg== -policy default,whitelist,all

This causes the given device to have the particular policy applied without applying it to the rest of the account. This change should be actioned for new Approov token requests within 30 seconds. Remember though that if your app is already running on the device and has fetched a token then it might not need to fetch a new one for up to 5 minutes, in which case the revised policy will only be apparent when that new fetch occurs.

The example shown above is a common one for development. By whitelisting the device, it is possible to always get a valid Approov token to access endpoints without needing to re-register modified apps. This policy also provides valid Approov tokens when debugging the app.

Note the following option can be used to blacklist a particular device to ensure it never receives a valid Approov token, either for testing or to block access for a particular device:

approov device -add h4gubfCFzJu81j/U2BJsdg== -policy default,blacklist,default

Note that there is a maximum of 25 devices that may be added to an account at one time.

Setting Pinning Mode

It is also possible to set the pinning mode for a particular device. This changes the way pins are handled for the device and can cause a new dynamic configuration to be downloaded to the app. These options are covered in more detail in Testing the Pinning Implementation. This command removes all pinning for the app, by causing an empty set of pins to be transmitted as the dynamic configuration:

approov device -add h4gubfCFzJu81j/U2BJsdg== -pinMode unpin

This has the further impact of sending a special dynamic configuration update to the app on the particular device that has all the certificate public key pins removed. This causes all pinning protection to be removed from the app, allowing a proxy (such as Charles Proxy or MITM Proxy) to be used to intercept traffic between the app and the API endpoints. This can be useful for debug or pentesting. Note that some apps may require a relaunch to unpin after receiving the update if they are not written to be immediately reactive to pinning changes.

Other options may be provided for the pinning mode as follows:

  • block: This transmits an fixed but invalid pin for in the dynamic configuration for all domains that have had pins added. This will force the app to experience a pinning failure in the same way it would if the API endpoint’s certificates had chnaged. This can be used for testing what happens in the app when such a certificate mismatch occurs.
  • blockAll: This works like block but it also causes the Approov channel to detect a MITM. THis simulates what happens if all TLS traffic is being intercepted. In this case any Approov token fetch (that needs to fetch a new token) will receive a MITM_DETECTED error.
  • pin: This is the default and returns the device to the default pinning for the account.

Note that since no security policy is explicitly set, the device is assigned the current security policy for the account. You may also set an explicit security policy for the device in the same command.

Listing Your Devices

A full list of devices that have had a special override security status can be obtained with:

approov device -list

This produces a list, for example:

1 device:
 h4gubfCFzJu81j/U2BJsdg== default,whitelist,all  unpin  A N Other

It shows the device ID, the security policy currently being applied and any pinning mode that has been set (or pin by default). The name of the management token holder that added the device is also included. This makes it easier to determine the user of a particular device.

Removing Devices

A particular device can be removed as follows:

approov device -remove h4gubfCFzJu81j/U2BJsdg==

Within 30 seconds the standard account policy will be applied to the device for new Approov token fetches. Note that, if the app is already running then a new token fetch will be necessary before the new policy is enforced (up to 5 minutes).

Device removal is not restricted to the user that added the device.

Token Binding

This section covers the optional token binding feature in Approov.

Token Binding Concept

Token binding extends the security of the Approov token through a strength in depth approach. The mechanism ties an arbitrary string to an Approov token by including its hash in one of the claims. The facility is intended for long lived data, such as an OAuth token or a user session identifier, that can be used to uniquely identify a user. The data is supplied to the Approov SDK as an ASCII encoded string. The SDK takes the string and calculates its SHA256 hash. This is then provided inside the Approov token in the pay claim as a base64 encoded string.

If the binding data changes then this invalidates any currently cached Approov token and a new one is requested on the next token fetch. Thus it is important not to use rapidly changing data for binding. Ideally long lived user session tokens should be used that will likely not change during typical app usage.

Note that the hashing of the data sent in the pay claim is always done in the SDK itself. Only the SHA256 hash of the data is sent to the Approov cloud service, so there is no need to be concerned about additional Personal Identifiable Information (PII) being sent to the Approov cloud service.

Setting the Binding

The data value may be set with the following method, where data is a string representing the data to be bound:


Approov.setDataHashInToken(data);

Approov.setDataHashInToken(data);

This checks the SHA256 hash calculated for data value against any current value that is held. If the value is different, then any cached Approov token is cleared. Any subsequent Approov token fetch call will obtain a new Approov token. The maximum rate at which new tokens can be fetched may be rate limited to prevent excessive usage if the binding data is continually modified in a tight loop of token fetching.

The data binding is held in the pay claim of an Approov token that is generated. The same value is retained in all subsequently generated Approov tokens until it is changed with another setDataHashInToken call.

Note that once a pay claim has been added it is not possible to remove its inclusion in the running app, only change its value.

Generating Example Token with Binding

The example token generation also supports the ability to generate a token with a pay claim. This can be used to test backend integrations that must perform the same hashing calculation to compare the claim. For example we can use:

$ approov token -genExample your.api.com -setDataHashInToken custom-data
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJl…

The generated Approov token string that can be then be decoded as follows:

$ approov token -check eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJl…
valid: JWS {"did":"ExampleApproovTokenDID==","exp":1558640470,"ip":"1.2.3.4","pay":"tih+xRFV8PMsDhKthuFdvqWtQpKdT+K8X5W3258EJnU="}

This shows the pay claim hash derived from the string custom-data:

{
  "did": "ExampleApproovTokenDID==",
  "exp": 1558640470,
  "ip": "1.2.3.4",
  "pay": "tih+xRFV8PMsDhKthuFdvqWtQpKdT+K8X5W3258EJnU="
}

Backend Integration Impact

If the token binding features are used then the pay claim must be checked on the backend to ensure the hash is consistent with the source data being presented. This data must be present in some other part of the request so that it can be extracted for comparison with the hash in the Approov token.

Note that the claim will, under normal circumstances, be present, but if the Approov Failover system is enabled, then no claim will be present. Your code must be written to deal with this case otherwise you will not be able to benefit from the redundancy afforded by the Failover system. You should check your implementation using Approov tokens generated as examples from the failover to be sure the implementation is working as expected.

The recommended process for checking the pay claim against the data provided in the request is as follows:

  1. Do a basic check of the Approov Token to see if it is valid.
  2. Check whether the token has a pay claim. If claim is not present then the token may be from the Failover service.
  3. If the claim is present then the data should be compared with the SHA256 hash of the data you provided to Approov. In some cases, such as when you are providing an OAuth token and you have yet to retrieve this in the application code, you should use a known none value which you both pass into the token fetch call in your app and in your server side payload check. This is because your backend code must cope with Failover tokens. Thus you must ensure normal tokens always have some content in the pay claim or else the logic for handling failover tokens (which will only ever be issued in the unlikely case of an Approov primary outage) could provide a back door for avoiding a matching pay claim.

The following python example shows the expected data being passed into a function. The source of this data will depend on your application, it may have been extracted from a header sent from your application or it may be the known none value. If the Approov Token is valid this is then hashed, base64 encoded, and then compared with the value extracted from the Approov Token.

def advanced_verify_token_payload(token, expected_payload):
    '''
        Verify token and check if payload data matches with the payload from a
        custom header.  Return token validity.

        Args:
            token string:            An Approov Token
            expected_payload string: Data which has been sent to Approov to match
                                     with the 'pay' claim in the Approov Token

        Returns:
            boolean: True if token is ok
    '''
    token_contents = basic_verify_token(token)
    if token_contents is None:
        return False

    if 'pay' in token_contents:
        # Compare payload from token with expected_payload
        encoded_header = base64.b64encode(hashlib.sha256(expected_payload).digest())
        if token_contents['pay'] != encoded_header:
            return False

    return True

Apple DeviceCheck Integration

This section covers the integration between Approov and Apple’s DeviceCheck feature. This feature is useful if there is suspicion of malicious or fraudulent activity occurring via the continual re-installation of an app on the same physical device. The app itself may be unmodified and running in a clean environment so would receive a valid Approov token. The integration allows a specific physical iOS device to be banned once fraudulent activity has been detected on it so it is not able to receive valid Approov tokens again.

DeviceCheck Concept

As discussed in Device ID Stability, the device ID reported on iOS automatically changes if an app is removed and then reinstalled. This is to preserve the privacy of the end user, as otherwise it would be possible to track an individual indefinitely via the device ID reported on their device. However, the inability to track specific devices creates some challenges with blocking specific malicious or fraudulent behaviour emanating from a particular device. If blocking is based on a device ID then the malicious actor can always circumvent any banning via reinstallation of an app. IP based blocking can also be evaded by the use of proxies.

Apple have recognized this as an issue and have introduced the DeviceCheck feature to provide some very limited tracking of specific physical devices that maintains user privacy. The facility allows up to two bits to be recorded for each physical device for each Apple developer account. These bits can be used to record information such as the detection of some fraudulent behaviour on the device.

Obtaining the bit status values and updates to them is performed by calling an Apple server API. In order to identify the device to this API it is necessary to obtain a Device Token. This is an ephemeral token (approximately 3KB in size) that identifies the device. Of course to prevent this itself being used to identify the device, and breaking the privacy policy, it is derived from a randomly generated nonce value so is different every time even on the same device. Since accessing the Apple API requires an authorization key this cannot be done in the app itself, since if this key was extracted it could be used to make arbitrary bit changes for other devices. Thus the API calls must be performed server side, necessitating that a protocol be established to communicate the DeviceCheck token from the mobile app to the server.

The Approov integration of DeviceCheck handles all of this integration complexity. If a DeviceCheck key is added to the Approov account, then the Approov SDK automatically collects and transmits the DeviceCheck tokens to the Approov servers. This token is then used to get and update the status of the device physical bits and prevent banned devices from being issued with valid Approov tokens. Commands are provided to easily ban and and unban devices, and an optional automatic banning facility is available to automatically detect and ban devices associated with repeated app uninstalls and reinstalls.

Setting the DeviceCheck Key

In order to use the Approov DeviceCheck integration you need to add an Apple authentication key to your Approov account. This key only needs permissions to access and update the two bits provided for physical device. This is used by the Approov servers to dynamically query the Apple servers when new devices are added, and to update the settings of the bits if a particular device is being banned (or subsequently unbanned).

Log into your Apple Developer Account, click on Account on the top menus, login and then navigate to the “Certificates, IDs & Profiles page”. Here you will see the list of keys and certificates that have been issued for your account, such as this below.

Apple Certificates Page

Select the “Keys” menu on the left hand side to navigate to the keys that have been issued for the account.

Apple Keys Page

You need to click on the blue plus button to add a new key. Type in the name of that you want to call the key (you an choose anything that is meaningful to you) and click the option for DeviceCheck.

Register New Key

You then press the “Continue” button and a dialog will be shown asking you to confirm the key addition.

New Key Confirmation

Click Register. You will then be provided with a dialog that gives you an option to download the private key that has been generated. Click this button. You will only get once chance to download this key so make sure the file is saved somewhere that is accessible for the next steps.

New Key Download

When the key is downloaded you will get a dialog such as the below with details of the key. The .p8 file itself should have been written to the Downloads folder that your browser uses by default. Also make a note of the Team ID value shown on the top right of the screen next to your company name. You will also need that.

Downloaded Key

Now that you have a private key you need to make it accessible to Approov using the approov command line tool.

$ approov devicecheck myadmin.tok -addAuthKey AuthKey_XXXXXXXXXX.p8 -teamID YYYYYYYYY -bits bit0
device check information added

Where AuthKey_XXXXXXXXXX.p8 is the private key file you downloaded and YYYYYYYYY is your team ID. In this case bit0 is selected so that the Approov service can read and update bit 0 of the two bits available per physical iOS device. You can change this to bit1 if you would rather use that bit instead.

Now that this information is added to your Approov account, the Approov cloud servers are able to contact the Apple servers to read and update the state. You can confirm that the information has been recorded with:

$ approov devicecheck -get
TeamID: YYYYYYYYY
KeyID: XXXXXXXXXX
Expiry: 12m
Bits: both
Apple Endpoint: Production

Approov sets the bit in order to indicate that a particular device has been banned, and should no longer receive valid Approov tokens. If you have never used the Apple DeviceCheck facility previously then the bits will not be set and so the default will be to allow the allow valid token fetching. If you have used the facility before and some bits may be set then this will cause banning of those devices. In this case you may use the -expiry option when setting the authorization key to ignore settings made more than a certain number of months ago. If the bit setting has been more recent than one month then you will not be able to use this Approov facility on your apps until sufficient time has elapsed.

Banning a Device

Once the DeviceCheck key has been added to the account you can ban a particular iOS device if you have its device ID. If you wish to ban a customer device due to malicious activity then the first step is to determine this by obtaining an Approov token that has been issued to that device. Each Approov token contains a did claim which holds the device ID. You can extract the claims from the token using the Checking Token Validity command. So, assuming a device ID of h4gubfCFzJu81j/U2BJsdg== was extracted, the device can then be banned with the following command:

$ approov device -add h4gubfCFzJu81j/U2BJsdg== -banMode ban
successfully set device h4gubfCFzJu81j/U2BJsdg==

The next time this device tries to fetch an Approov token it will be added to the permanently banned list and will not receive a valid Approov token. The actual physical device is banned, so even if the app containing the Approov SDK is uninstalled and reinstalled in order to get a different device ID, the ban will be permanent.

If you have the all Annotation Policy selected then tokens rejected due to a DeviceCheck ban have devicecheck-ban listed as a rejection reason.

Once the ban has been actioned there is no need to retain the device in the custom list for Approov, so you can remove it again with:

$ approov device -remove h4gubfCFzJu81j/U2BJsdg==
successfully removed device h4gubfCFzJu81j/U2BJsdg==

When a particular device is banned it is across all apps associated with the same Apple developer account, even if these apps are associated with different Approov accounts. This is because Apple stores the bit states on a per-developer account basis. If you are using multiple Approov accounts associated with a single Apple developer then you may select different bits on different accounts to avoid such interference.

Removing a Device Ban

A device ban can be removed if the current device ID can be obtained. As discussed in Banning a Device, this may be obtained from an example Approov token issued to the device. Initiate the unban as follows:

$ approov device -add h4gubfCFzJu81j/U2BJsdg== -banMode unban
successfully set device h4gubfCFzJu81j/U2BJsdg==

The next time this device tries to fetch an Approov token its ban status will be cleared, and it will be issued with a valid Approov token (presuming it meets all of the other criteria). Once the unban is actioned, remove the device from the custom list.

Removing the DeviceCheck Key

The DeviceCheck authorization key can be removed from the account at any point, if an adminstration level management token is available, as follows:

$ approov devicecheck myadmin.tok  -remove
device check information removed

This will disable all lookups of DeviceCheck, so that any device that is currently banned will start working again, presuming that all other criteria are met to issue a valid Approov token. If the device check key is subsequently set again then the banning of the same devices will be reinstated since the Apple bit state will not have been changed.

Automatic Device Banning

An automatic device banning feature is enabled if both bits are made available when setting the DeviceCheck key, such as the following:

$ approov devicecheck myadmin.tok -addAuthKey AuthKey_XXXXXXXXXX.p8 -teamID YYYYYYYYY -bits both
device check information added

In this case the two available bits are used as a counter of the number of times an app has been removed and reinstalled on a device. Each reinstallation will generate a new device ID. However the DeviceCheck bits implement a counter and when this reaches the maximum value of 3 the physical device will be automatically banned. This is because such continual re-installation behaviour is typically associated with some kind of malicious activity. Once banned the abuser will no longer be able to use the device to obtain valid Approov tokens. Non-malicious users would have to uninstall and reinstall the app at least three times before receiving a ban and this would represent very unusual behaviour.

Since the two bits are interpreted as a counter, we do not advise you to use this feature if the DeviceCheck bits have ever been previously used on the account for other purposes more recently than the value of -expiry. Moreover, you should never use the automatic banning feature if sharing a single Apple developer account across multiple Approov accounts or if you have multiple different iOS apps associated with even a single Approov account.

Note that once a device has been banned, the only way to unban it is via the manual Removing Device Ban method.

Metrics Graphs

Metrics graphs provide both live and longer term summary information about your account usage. They can be used to see the total number of devices that have requested Approov tokens over a time period (and therefore what the usage related costs will be) and also show the failures where particular devices have been denied valid Approov tokens. The graphs provide information about the reasons for any such failures.

The metrics portal can be reached via an approov command. This automatically opens the metrics information in your browser:

$ approov metrics
opening https://metrics.approovr.com/redirect/test-account/usage?access_token=eyJhbGciOiJ…

The URL being opened is shown in the command line and a new browser window is opened with the relevant information. If the browser does not open for some reason (perhaps because there is no default browser is setup) then the given URL can be pasted in another browser window to view the information.

Note that the browser request contains an access token to authorize viewing of the information. Since this is pasted into a browser, it should be handled with care and additional security measures are put in place by Approov around this. It is a specially constructed token that is generated which only provides access to the metrics information and no other aspects of the account. It has a maximum lifetime of 24 hours, with a minimum guaranteed lifetime of 6 hours.

Grafana

The metrics graphs are rendered using Grafana. Please refer to Grafana Getting Started to understand how to use the basic features of Grafana. Note that you will be provided with a fixed set of dashboards showing information from your accont and will not be able to edit these dashboards or add new data sources.

A number of different individual dashboards are available for your account, each showing different information and described in subsequent sections. Each dashboard may contain a number of different graphs showing active data from your account. Individual dashboards may be selected using the pull-down menu on the top left hand side of the screen next to the Grafana logo.

Graphs are rendered over a timescale defined in the text box at the top right of the screen. If you click on this then various timescale options are provided. Moreover you can click the refresh button to the right hand side of this initiate a refresh to show the latest data. Options for automatic refresh are also available. Note that times are always shown in UTC, regardless of your local timezone setting. The timescale settings impact all graphs shown on the same dashboard. It is possible to zoom into a graph simply by left clicking on the graph and zooming into the area of interest.

Each graph shows a number of time series, depending upon the graph and the exact circumstances of your account. On many graphs there are numerous time series so it may be difficult to see an individual one. You can restrict the graph to only show a single time series by clicking on it in the legend on the right hand side of the graph. Clicking on it again enables all time series. You can use the control key to perform a multi-select to show an arbitrary set of time series together.

The legend shows the maximum value in the time range being shown. For some graphs an average is also available. Hovering over the graph allows the values to be seen on each selected time series at an arbitrary time.

The authentication method used for Approov Grafana dashboards does not allow the “Share” features to be used. If you wish to share the dashboards you can copy and share the URL generated by the approov metrics command. Note though that the token embedded within this URL has a limited lifetime.

Metrics Naming

The Approov cloud service generates a number of individual metrics for your account. These represent different activities that are occurring in your account and help you see an overview of usage and any potential issues. There are a number of different types of metrics that are described in the following sections.

Summary Metrics

Summary metrics are used to provide an overview of the status of a particular attestation request received from an app associated with your Approov account.

Metric Name Description
pass Indicates that a valid Approov token was issued.
fail Indicates that a invalid Approov token was issued because a criteria was not met, depending upon the rejection policy selected. Other metrics will provide information about the reason for the rejection.
error Indicates that a request was rejected outright because it was incorrectly formed. Under normal circumstances this should not occur, but could indicate an account misconfiguration or a failed attempt to spoof requests for the account to the Approov servers.

Flag Metrics

Flag metrics provide information about the presence of particular characteristics in apps attesting using your account. Whenever these metrics are shown they are either prefixed with pass- or fail- depending on whether the overall request was passed or failed.

Whether a given requet is rejected or not will depend on the Rejection Policy associated with the account or requesting device. This means that if the security policy is changed then flags previously associated with passes may become fails (or vice versa).

Metric Name Description
flag-tampered Indicates some attempt to tamper with the execution of the Approov SDK. This always results in an invalid Approov token being issued.
flag-scan-fail Indicates that some of the checks that the Approov SDK does could not be completed. This may be a result of tampering of the app runtime environment. This always results in an invalid Approov token being issued.
flag-bad-nonce Indicates some attempt to tamper with the nonce value that must be part of the request. This always results in an invalid Approov token being issued.
flag-bad-hmac Indicates some attempt to tamper with the requests being made by the Approov SDK. This always results in an invalid Approov token being issued.
flag-rooted Indicates that an Android device appears to be rooted.
flag-root-risk Indicates that an Android device has various packages installed or other indications that it may be rooted but this is not definitely proven.
flag-jailbroken Indicates that an iOS device appears to be jailbroken.
flag-app-not-registered Indicates that an app has not been registered with the Approov service.
flag-debug Indicates that an app instance is being actively debugged.
flag-emulator Indicates that an app appears to be running on an emulator rather than on a real physical device.
flag-ios-simulator Indicates that an iOS app is running on the simulator rather than a real physical device.
flag-frida Indicates an app has the Frida framework installed.
flag-frida-hook Indicates an app is running Frida and some of the functions that the Approov SDK uses are being actively hooked to try and evade detection.
flag-devicecheck-ban Indicates that a physical iOS device has been banned using the Approov DeviceCheck integration.
flag-devicecheck-apple-err Indicates that there are issues connecting to the Apple DeviceCheck servers, which may indicate that the authorization key provided in your account has been revoked.
flag-cydia Indicates that an iOS app has the Cydia framework or app installed.
flag-cycript Indicates that an iOS app has the Cycript framework installed.
flag-xposed Indicates that an Android app has the Xposed framework installed.
flag-xposed-unsafe Indicates that an Android app has the Xposed framework installed and that it is running a plugin within it which is known to be associated with reverse engineering attempts.
flag-magisk-man-hidden Indicates that an Android app has the Magisk root manager is installed and is being actively cloaked.
flag-device-changed Indicates that an app has had its device ID changed while it is running. This is almost certainly related to a hacking attempt.
flag-devices-exceeded Indicates that, for an account with a limit of the number of unique devices that can be used, that this limit has been exceeded.
flag-init-config-outdated Indicates than an app instance has a base configuration that is not up to date, which is being updated by an over-the-air transfer.
flag-new-did Indicates that an app instance has a new device ID that has not been previously seen for the account. This provides an indication of brand new users.

App Metrics

These are metrics generated depending on the app making the request. This allows an easy assessment of the population of apps, or those associated with a particular problematic behaviour. Whenever these metrics are shown they are either prefixed with pass- or fail- depending on whether the overall request was passed or failed. Whether a given request is rejected or not will depend on the Rejection Policy associated with the account or requesting device.

Metric Name Description
app-<name> The package name of the app that is requesting an Approov token.
appver-<name>-<version> The package name of the app with its corresponding app version that is requesting an Approov token. This allows tracking of specific versions and the transition to a new version when it is pushed out into the app store.

SDK Metrics

These are metrics generated from statistics collected in each running Approov SDK, which are then aggregated across your entire account. These allow an assessment to be made of the rate of usage of the SDK methods and the prevalence of any errors generated.

Metric Name Description
sdk-calls-fetch-sync Number of calls to made to the synchronous fetchApproovTokenAndWait method in the SDK.
sdk-calls-fetch-async Number of calls to made to the asynchronous fetchApproovToken method in the SDK.
sdk-calls-fetch-config Number of calls to made to the fetchConfig method in the SDK.
sdk-calls-get-pins Number of calls to made to the getPins method in the SDK.
sdk-calls-set-data-hash Number of calls to made to the setDatahashInToken binding method in the SDK.
sdk-calls-integrity-proof Number of calls to made to the getIntegrityMeasurementProof method in the SDK.
sdk-calls-device-proof Number of calls to made to the getDeviceMeasurementProof method in the SDK.
sdk-result-success Number of token fetch calls resulting in a SUCCESS / success status.
sdk-result-no-network Number of token fetch calls resulting in a NO_NETWORK / noNetwork status (assuming the app could subsequently get network access to report this).
sdk-result-poor-network Number of token fetch calls resulting in a POOR_NETWORK / poorNetwork status.
sdk-result-mitm-detected Number of token fetch calls resulting in a MITM_DETECTED / mitmDetected status.

Live Metrics

The Live Metrics dashboard allow you to see what is happening in an account on a near real time basis. Information shown in the graphs is updated within a maximum of 2 minutes from the event occurring. New data is made available every minute.

Live Metrics

The dashboard consists of two graphs showing Summary, Flag and App metrics. The upper graph shows the metrics captured from requests that pass and for which a valid Approov token was provided. Metrics are prefixed with pass- indicating this status. The pass summary metric show the total number of passes. Conversely, the lower graph shows failing requests with metric prefixed with fail-.

The y axis for the graphs are in terms of the volume of requests (requests per minute). The same device making multiple requests in a time period will be counted multiple times.

During initial development, when there are no production apps in the account also providing data, the graph can be used for quickly determining if requests are reaching the Approov servers and assessing the response made.

Billing Usage

This dashboard provides an overview of the usage of an account over time showing Summary Pass and non-versioned App metrics. A new entry is generated on each hour. The y axis scale is based on the number of unique device IDs that have been seen since the start of your account’s billing period in the month.

Billing Usage

This figure will grow over the month until it is reset on the “billing day” anniversary each month. The maximum value that the monthly-pass figure reaches during the month is used to calculate the account’s usage invoice for the month. Since billing is calculated only on the basis of passing attestation requests all metrics shown are in fact prefixed monthly-pass.

In addition to the overall figure, metrics are also provided for each app package name that has been successfully used on the account. This allows an assessment to be made between iOS and Android usage, or for different apps associated with the account.

Hourly Metrics

This dashboard provides an overview of the hourly usage of the account, in terms of the number of different device IDs that used the account in an hour period. A new sample is output every hour. It shows Summary, Flag and App metrics. This allows assessment on the level of account usage from hour to hour in terms of the count of different devices.

Hourly Metrics

The upper graph shows devices that were granted a valid Approov token in the last hour, and metrics are prefixed with hourly-pass. THe lower graph shows the devices that had at lest one failure to receive a valid Approov token in the last hour, and metrics are prefixed with hourly-fail. Note that if a device both passed and failed in the last hour then it will be counted in both graphs.

Daily Metrics

This dashboard provides an overview of the daily usage of the account. The number of devices are counted throughout the day, until reset at midnight UTC. A new sample is output every hour. It shows Summary, Flag and App metrics. This allows assessment on the level of account usage from day to day in terms of the count of different devices.

Daily Metrics

The upper graph shows devices that were granted a valid Approov token in the last day, and metrics are prefixed with daily-pass. THe lower graph shows the devices that had at lest one failure to receive a valid Approov token in the last day, and metrics are prefixed with daily-fail. Note that if a device both passed and failed in the last day then it will be counted in both graphs.

Monthly Metrics

This dashboard provids an overview of the monthly usage of the account. The number of devices are counted throughout the month, until reset at midnight UTC on the billing day of the account. A new sample is output every hour. It shows Summary, Flag and App metrics.

Monthly Metrics

The upper graph shows devices that were granted a valid Approov token in the month, and metrics are prefixed with monthly-pass. THe lower graph shows the devices that had at lest one failure to receive a valid Approov token in the month, and metrics are prefixed with monthly-fail. Note that if a device both passed and failed in the month then it will be counted in both graphs.

SDK Metrics

This dashboard shows SDK metrics that have been collected from running Approov SDKs in apps associated with the account. These are aggregated across all SDKs with a new sample being generated every minute. These metrics are not directly associated with a pass or fail. The sdk-result metrics in particular can be used to determine if there are any widespread issues affecting running SDKs.

SDK Metrics

Exporting Data

It is possible to export raw graph data into a CSV file using the menu available on the right click button on each graph, as follows:

Export CSV

Management Tokens

Management tokens are used to access your account in the Approov cloud using the approov command line tool.

Best Practice

When a new account is created the account holder is issued with new development and administration management tokens. These are encoded strings that provide management access to the Approov cloud service. The differences between the development and administration tokens are covered in the next section.

In a larger organization where multiple personnel need to interact with the Approov service, we expect that there will be some internal control of the access to the management tokens. We envisage that only a single individual, or small restricted group, will have access to the management tokens issued when the Approov account was first created. The administration token can be used to add further management tokens to provide to individuals within the organization.

We suggest that development management tokens are created for each of the individuals that are involved in the development of the apps that use the Approov service. These tokens allow access to all of the facilities required for app development, without more dangerous privileges that might allow an accidental detrimental impact to apps that are live in production in the account. Development tokens can be issued to named individuals, preferably for a timescale that represents their likely involvement. Tokens can be revoked by the administrator at any time.

It may also be necessary to issue additional administration management tokens. These have the facilities for accessing token secrets and making certain global changes to the account. Creating a new administration token allows these capabilities to be delegated to a particular individual.

For larger and more complex teams and projects we would suggest having multiple Approov accounts to provide enhanced insulation between development and production operations. If this is a requirement then please contact Approov support.

Types of Management Token

There are two different types of management token that may be used with the approov command line tool. These are development and administration tokens. An administration token has additional privileges that are not available for development tokens. These privileges are as follows:

  • Removing app registrations that do not have an expiry date. Typically, this type of registration should be reserved for apps in production and so the removal of such registrations is protected.
  • Removing multiple app registrations in single command invocation using the -removeMatching option.
  • Modifying the security policy applied to the whole account. If this is changed then it could cause invalid Approov tokens to be sent to a range of different users that might cause a partial outage event.
  • Removing or modifying the set of API domains and pins for the account. If these are changed incorrectly then connection may be denied in production apps via an incorrect pin, or if an active API domain is accidentally removed then it will no longer receive valid tokens. All these actions could result in an outage event. Note that new API domains can be added with a development token as this does not carry a risk of outage.
  • The creation, revocation and listing of management tokens as discussed in this section.
  • Creating long lived Approov tokens for server-to-server communication or testing.
  • The extraction and update of the Approov token secret. This secret must be considered to be extremely sensitive since its possession allows arbitrary valid Approov tokens to be generated.

Creating New Management Tokens

New management tokens may be issued using the administration management token issued when the account was onboarded. This assumes that the administration token is held in the myadmin.tok file. As discussed, we would discourage the practice of setting an administration token in the APPROOV_MANAGEMENT_TOKEN environment variable to ensure that uses of the administration token are explicit.

Here is an example of creating a new development token valid for a day:

$ approov management myadmin.tok -add "joe@yourdomain.com,J Bloggs,1d" -file joe_dev.tok
new management token written to joe_dev.tok

The -add option takes three parameters of the user email, user’s name and the duration. Note that the user email is purely for information purposes and is not verified. Approov never uses this email address to send emails to the token holder. Quotes are used around the parameter to allow the use of spaces in the user name. The final parameter is the duration of the management token. Normally this will be specified in terms of the number of days that the token will be valid for. Note that it is not possible to generate a new management token that has an expiry time after that of the administration token used to create it.

The new token is output to the file specified in with the -file option (if this is not present then it is output to the console). Once the intended user of the token has the file, they can use it on command line calls to the approov tool, or place its contents into the APPROOV_MANAGEMENT_TOKEN environment variable as mentioned previously.

By default a new development management token will be created. This has restricted privileges as discussed in the previous section. A new administration token may be created as follows:

$ approov management myadmin.tok -add "boss@yourdomain.com,Boss Bloggs,7d" -admin -file boss_admin.tok
new management token written to boss_admin.tok

Administration management tokens should be very carefully managed and controlled since they have elevated privileges.

Note that there is currently a maximum limit of 25 management tokens that may be live for any individual Approov account. If this limit is reached then old unused management tokens should be revoked to allow the creation of new ones.

Listing Management Tokens

The available management tokens can be listed as follows:

$ approov management myadmin.tok -list
another@yourdomain.com, A N Other, admin, expires 2029-05-08 15:01:05, eyJhbGciO…
joe@yourdomain.com, J Bloggs, dev, expires 2019-05-25 18:34:50, eyJhbGciO…

The output of this command contains very sensitive information, thus should be dealt securely and MUST not be saved in a file, unless the file will be encrypted.

This command output provides the information about the active management tokens, including the email addresses, user names, and expiry dates for the management tokens. This also shows if the management token has dev (development) or admin (administration) level privileges. The full management token string is also provided.

If a token has been lost and needs to be reissued, the token string MUST be sent to the relevant party only in a secure manner.

Note that the listed management tokens only include those that have the same or earlier expiry times than the administration management token used to obtain this information. The initial administration token issued by Approov upon initial onboarding has the longest expiry time and additional tokens with shorter expiry times may be created from this. This may include other administration tokens with earlier expiry times. These then provide a more restricted view of the available management tokens.

Revoking Management Tokens

In some cases it is desirable to be able to revoke the use of a specific management token before its expiry time. This may be necessary if there is a concern that the token has been compromised or the individual that was allocated the token has left the project. A management token can be revoked as follows:

$ approov management myadmin.tok -revoke eyJhbGciO…
management token revoked

The full string of the management token can be obtained using the -list option. Revocation can take up to 30s. After it completes, attempts to use it will result in an authorization error, e.g.

$ approov registration -list
your management token is invalid, revoked or expired - contact your Approov administrator
error Forbidden while getting libraries

Note that an administration management token can only be used to revoke tokens that have earlier expiry times. In other words it is only possible to revoke management tokens that may have been created with that administration management token. Thus it is not possible to accidentally revoke management tokens issued to you by Approov. If you feel that these tokens may have been compromised, then please contact Approov support who are able to revoke these and then provide new ones.

Offline Security Mode

This section discusses the Approov feature that allows the SDK to prove to a remote hardware device, not directly connected to the Internet, that it is a genuine instance of a particular app.

Use Case

This feature is useful for specific use cases where the mobile device and/or some other remote hardware being commanded by an app may not have a continuous Internet connection. Approov attests itself in the normal way and accesses an API, but additional step for this flow causes a measurement of the app integrity to be taken. This is included in some communication from the backend API to the remote hardware. This will include an encrypted form of the measurement that only the remote hardware is able to decrypt. The remote hardware is then able to know the value of the prior measurement made on the app. An offline attestation process between the app and the remote hardware then proves that the app is able to reproduce the measurement without actually transmitting it. This gives assurity to the remote hardware that it is being commanded by the same app that was earlier measured. This prevents an attacker from commanding the remove hardware using any other client software.

Note that this feature must be enabled in the Approov cloud service before it can be used from an app. Contact Approov support if you are not sure of the status or wish to perform a trial of the feature. It is not provided in our standard Approov commercial package.

Operational Flow

The following provides an overview of the steps required to obtain and use a measurement proof using the offline security feature. The first stage is the app obtaining a measurement:

Offline Security Initial Measurement

  1. A special Approov token fetch must be performed that also performs a measurement. This takes the API domain to which the measurement is to be sent with the suffix ?measurement. The fetched Approov token will contain additional information for the collected measurements. Generally the domain should have encrypted JWE tokens enabled, so that the measurements cannot be read by any 3rd party if they are somehow able to intercept the request.
  2. During the Approov token fetch, a full app Integrity Measurement (IM) and a Device Measurement (DM) are made by the SDK. These are both derived from very similar analysis and data as the main measurements required to obtain a token. The IM is a 256-bit HMAC result that encompasses all aspects of the app and mobile device, but is designed to always produce a stable result from the same app on the same mobile device, even if the app or the whole mobile device is rebooted. The DM is also a 256-bit result but is specifically designed to only include data sources that are device specific, or specific to data stored by the app in internal storage. This means that this hash should be stable even if the app is updated (including an update to the version of the Approov SDK that is embedded within it). A property of both the IM and DM is that they are salted by a nonce value. This means that the actual hash values are different for each Approov attestation, even if the app and its environment have not changed.
  3. The Approov cloud evaluates the attestation request as normal. The only difference is that the generated Approov token will have an additional claim providing encrypted forms of the IM, DM and also the Measurement Proof Key (MPK). The MPK is a 128-bit key that is dissolved into the code of a particular version of the SDK and is also known by the Approov backend.
  4. The app will pass the Approov token containing the measurements to the backend API. The backend evaluates the request, checking the validity of the Approov token. If the verification checks are passed, then some form of measurement token is created to pass to the remote hardware, containing the measurements from the Approov token. This will be encrypted by a public key known to the backend, with the decrypting private key held securely in the remote hardware. This means that only the remote hardware is able to know the contents.
  5. As an additional result of performing a measurement during an Approov token fetch, a Measurement Configuration (MC) is also provided as part of the result. This is a binary data blob that must be held in local storage. It contains the configuration information that will be used by the app to reproduce integrity and device measurements in the future. The contents of this data are signed (in the sense that any modification will impact the measurement results) so an attacker cannot tamper with it. This is saved into persistent storage of the app. Furthermore, the encrypted measurement token provided by the API is also persisted in the local storage of the app.

The next step (which may be repeated an arbitrary number of times) is to perform a measurement proof between the app and a remote hardware device:

Offline Security Measurement Proof

  1. Methods (getIntegrityMeasurementProofand getDeviceMeasurementProof) are provided in the Approov SDK to allow a measurement proof to be made, salted by a nonce value provided by the app. This should be different each time, to prevent replay attacks. The measurement proof methods require the MC file that was saved earlier. Since this is held in persistent local storage the app can be closed, and indeed the mobile device rebooted, between measurements and the same result will be obtained if no tampering of the app has occurred. Crucially, this entire measurement proof process does not require any Internet access.
  2. The measurement proof result is transmitted to the remote hardware device over some local communication link, such as Bluetooth.
  3. The measurement token, that was stored earlier, and contains the encrypted measurement is also communicated to the remote hardware device over the local communication link.
  4. The remove hardware is able to check any measurement proofs since it has access to the known good measurement values decrypted from the measureent token. Thus it can determine if the same app instance is making the request as was originally validated by the API backend. Only the remote device is able to decrypt the measurement token since only it is in possession of the required private key.

Backend Integration

In order to support offline mode there are some requirements on the backend integration for the API domain that is responsible for issuing measurement tokens to the remote hardware. A key requirement is that some Public Key Infrastructure (PKI) needs to be in place with the remote hardware. This means that the backend API can encrypt the measurement token and only the specific remote hardware (or class of remote hardware) should be able to decrypt the contents. Of course this depends on keeping the private key in the remote hardware secure, but this is beyond the scope of Approov.

The backend API must check the Approov token in the normal way, but then extract the imh (Integrity Measurement Hash), dmh (Device Measurement Hash) and mpk (Measurement Proof Key) out of the Approov token and put the values into the measurement token.

We strongly recommend that encrypted JWEs will be used on endpoints to which measurements are transmitted for additional security.

Remote Hardware Integration

It is envisaged that the app will communicate with a remote hardware in order to command it in some manner. This will require a combination of a valid measurement proof and likely other user credentials to be accepted by the remote hardware. In other words, it is checking that it is being commanded by the right software and by the right user. The exact channel of communication is independent of the Approov feature, but a typical choice is Bluetooth.

A valid measurement proof indicates that the Approov SDK is able to reproduce the integrity measurement value that was calculated earlier when the measurement was first obtained. Each measurement is salted with a 128-bit nonce value to prevent replays of prior results. A measurement is actually an HMAC of the measurement salted by the nonce. The method of choosing this nonce is not defined by Approov.

The remote hardware implementation must do the following:

  1. Decoding Measurement: The measurement token transmitted to the remote hardware should be decrypted by the hardware using its private key. This gives it access to the Integrity Measurement (IM) and potentially also the Device Measurement (DM). The Measurement Proof Key (MPK) also needs to be decrypted as it is used in the measurement proofs.
  2. Checking Measurement Proofs: The Approov SDK calculates the integrity measurement proof HMAC result. It does not need Internet access to be able to do this and the overall calculation should not take more than approximately 100ms. The integrity measurement is recalculated on the fly during this call and used to calculate the result of HMAC(MPK, nonce || IM). The Measurement Proof Key (MPK) is dissolved in the obfuscated code of Approov and provides a further layer of security. The result is a 256-bit value called the Integrity Measurement Proof (IMP). This IMP value is transmitted to the remote hardware and it must perform exactly the same HMAC calculation. Note that a Device Measurement Proof (DMP) can be calculated in exactly the same manner.
  3. Restricting Access: Once the IMP (and/or DMP) have been calculated they can be compared with that provided by the SDK. If they do not match then some aspect of the app or its runtime environment has changed since the original measurement. This would normally result in an access denial. Some useful error state should be returned to the app, so that it can suggest to the user that they will need to redo the original measurement process and verification by the API backend.

Requesting a Baseline Measurement

As discussed previously, the first stage in making an Approov measurement is to request a token in measurement mode. Example code to do this is as follows:


Approov.TokenFetchResult approovResult = Approov.fetchApproovTokenAndWait("api.myservice.io?measurement");
if (approovResult.isConfigChanged())
    saveApproovConfigUpdate();



let approovResult = Approov.fetchTokenAndWait("api.myservice.io?measurement");
if approovResult.isConfigChanged {
    saveApproovConfigUpdate()
}

This is a standard token fetch call, except for the ?measurement suffix of the provided domain. This forces a measurement to occur, even if there is a previously cached and valid Approov token. The expectation is the special measurement token will be sent to the API domain api.myservice.io.

Persisting the Measurement Configuration

Next, the measurement configuration obtained from this measurement must be saved to local app storage. This code provides an example approach:


if (approovResults.getStatus() == Approov.TokenFetchStatus.SUCCESS)
    // check that we obtained a configuration measurement (this should always be the case
    // but we check in case there has been an error)
    byte[] measurementConfig = approovResults.getMeasurementConfig();
    if (measurementConfig == null) {
        <error handling>
    }

    // save the measurement configuration to local storage
    try {
        FileOutputStream outputStream = getApplicationContext().openFileOutput("measurement_config.bin", Context.MODE_PRIVATE);
        outputStream.write(measurementConfig);
        outputStream.close();
    } catch (IOException e) {
        <error handling>
    }
}

if approovResult.status == ApproovTokenFetchStatus.success {
    // check that we obtained a configuration measurement (this should always be the case
    // but we check in case there has been an error)
    if approovResult.measurementConfig == nil {
        <error handling>
    }

    // save the measurement configuration to local storage
    let URLs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let measurementConfigURL = URLs[0].appendingPathComponent("measurement_config.bin")
    do {
        try approovResult.measurementConfig!.write(to: measurementConfigURL)
    } catch {
        <error handling>
    }
}

This saves the configuration to the local file measurement_config.bin. A saved measurement configuration is required in order to perform subsequent proofs that the integrity measurement has not changed. By saving the measurement configuration the app is able to perform such checks even if the app is restarted.

Getting a Measurement Proof

At some point later, a measurement proof may be required to communicate the app’s authenticity to a remote hardware. First, the measurement configuration must be loaded from local app storage again. Example code is as follows:


// read the measurement configuration from local storage
byte[] measurementConfig = null;
try {
    InputStream stream = getApplicationContext().openFileInput("measurement_config.bin");
    DataInputStream reader = new DataInputStream(stream);
    measurementConfig = new byte[stream.available()];
    reader.readFully(measurementConfig);
    reader.close();
} catch (IOException e) {
    <error handling>
}

// read the measurement configuration from local storage
let URLs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let measurementConfigURL = URLs[0].appendingPathComponent("measurement_config.bin")
var measurementConfig : Data? = nil;
do {
    measurementConfig = try Data.init(contentsOf: measurementConfigURL)
} catch {
    <error handling>
}

Once the measurement configuration is available, a measurement proof calculation can be performed as follows:


// perform the integrity measurement proof calculation
byte[] integrityProof = Approov.getIntegrityMeasurementProof(proofNonce, measurementConfig);
if (integrityProof == null) {
    <error handling>
}

// perform the integrity measurement proof calculation
let integrityProof = Approov.getIntegrityMeasurementProof(proofNonce, measurementConfig!)
if integrityProof == nil {
    <error handling>
}

This generates a 32 byte result in the integrityProofvariable. The result of this is a combination of the measurement taken earlier when the Approov token was fetched, and a 16-byte proofNonce value provided by the application. The choice of the nonce is beyond the scope of this document, but it should be varied on each measurement proof.

The measurement proof value can then be transmitted to the remote hardware, along with any other relevant credentials as part of any command from the app to the remote hardware The hardware is able to evaluate this to determine if the app has calculated the same integrity measurement value as previously. Since a non-replayable HMAC proof value is being transmitted, any attacker able to steal data from the channel will not be able to infer the actual integrity measurement value or replay the data later.

If any of the characteristics of the app’s integrity changes, then the computed value will differ and access will be denied. This occurs if there is any change to the app itself, or if the runtime environment has changed. For instance if the app is now under debug, or subject to analysis by a framework, then the measurement will be altered.

Note that the Approov SDK also includes a getDeviceMeasurementProof method. This is used in exactly the same way, but only measures attributes of the mobile device on which the app is running. Thus this calculation will not be impacted by any update to the app that is being attested.

Migrating from Approov 1

This section describes the process of porting mobile apps, that have previously used the Approov 1 SDK, to use the Approov 2 product.

Key Differences

In the transition to the new version we have taken the opportunity to improve a number of aspects of the interfaces and also brought greater consistency between the Android and iOS versions.

Approov 1 SDKs were downloaded using the admin portal and have your account details embedded within them automatically. Approov 2 uses a configuration file generated from the new approov command line tool to initialize the SDK with your account information.

The most significant change is in the implementation of pinning. If you are not using the Approov dynamic certificate pinning features in Approov 1, then this will not have any impact. If you are, then there is a requirement to employ the new approach implemented in Approov 2. This continues to provide a way to dynamically transmit pinning information from the Approov services to live apps, but no longer requires the direct implementation of a custom trust manager or hostname verifier in most cases. Overall, the new scheme is simpler and applicable to a wider range of client side frameworks. More details on porting from the current scheme are provided in the Migrating Pinning section.

Performing the Transition

When we create your new Approov 2 account we will ensure that the Approov token secret allocated is identical to the one used in your Approov 1 instance. This means that there need be no changes to your backend API integration. The only exception might be if you are reading the IP claim in Approov tokens, as this is now in a different format. However, we do not normally recommend using this claim. You can test the backend compatibility using the Approov token example generation facilities.

Your new app releases should integrate the new Approov 2 SDK and be registered with the Approov 2 tool and management tools. You cannot register an app containing an Approov 1 SDK with Approov 2. During this transition period you will have some apps using Approov 1 and some apps on Approov 2. Over some period of time we would expect all of your users to transition to the newer version of the app. We will monitor this situation over time and eventually, with your agreement, we will decommission your Approov 1 account.

The usage and billing information will show the combined usage for both Approov 1 and Approov 2.

Legacy Registration Tool Mode

Whilst you make the transition to using Approov 2, your Approov 1 account will continue to operate. You will be issued new management tokens to control your new Approov 2 account. You must use the new approov command line tool for Approov 2.

Since you have been using Approov 1, you may have integrated its registration tool into your continuous build systems for automatically registering new versions of app builds. You might also be familiar with the command option syntax for registering apps.

In order to use Approov 2 you will need to replace the existing registration tool and use the new management tokens issued to you. However, the new approov command line tool features a compatibility mode that accepts exactly the same options as the previous Approov 1 registration tool. To access this compatibility mode you must invoke it with the name of the old tool. This is automatically detected and the legacy mode is enabled.

For instance, on Linux or MacOS:

$ ln -s approov registration
$ ./registration -v
Approov Legacy Registration Tool 2.0.0
no token path specified

This assumes that the approov command is in the current directory. It creates a symbolic link to allow the tool to be invoked in legacy mode.

On Windows you should make a copy of the approov.exe file as follows:

$ copy approov.exe registration.exe
       1 file(s) copied.
$ registration.exe -v
Approov Legacy Registration Tool 2.0.0
no token path specified

Legacy Admin Portal

Approov 1 uses a web based administration tool (located here) to list apps that have been registered for your account and allow them to be deleted. This portal is also used for downloading new SDK libraries, accessing the token secret and the service usage information. This information can now be reached using the new command line tool and the admin portal is no longer required.

During the transition period, and if you wish, you can continue to use the admin portal in the same way as before. Note that the library access service is no longer available and thus no library access token is provided. All SDK libraries are automatically populated in the portal as they become available and can be downloaded directly from the portal.

Legacy Usage Portal

The legacy usage portal can be reached via an approov command. This automatically opens the usage information in your browser:

$ approov usage
opening https://analytics.approovr.com/redirect/test-account/usage?access_token=eyJhbGciOiJ…

The URL being opened is shown in the command line and a new browser window is opened with the relevant information. If the browser does not open for some reason (perhaps because there is no default browser is setup) then the given URL can be pasted in another browser window to view the information.

Note that the browser request contains an access token to authorize viewing of the information. Since this is pasted into a browser, it should be handled with care and additional security measures are put in place by Approov around this. It is a specially constructed token that is generated which only provides access to the usage information and no other aspects of the account. It has a maximum lifetime of 24 hours, with a minimum guaranteed lifetime of 6 hours.

The usage portal allows access to statistics and charts for your Approov enabled apps. All graphs are updated daily, shortly after midnight UTC. The graphs show the combined usage on both Approov 1 and Approov 2 versions.

Usage Portal Example

The main usage panel provides the following information:

  • Device ID Counts Summary: The total number of unique devices seen throughout the past three months of usage. Note that your monthly billing will be based on these totals when the end of your billing month is reached. This is day anniversary of the day in the month that you signed up to Approov.
  • Unique Devices: The number of unique devices seen on the account, cumulatively since the start of he billing month. The data for the last three months is shown side-by-side to allow easy comparison from month to month.
  • Active Devices by Platform and App Name: For each of the previous three months this provides a breakdown of the number of unique devices using each of your registered apps (by OS platform and app package name). Note that different apps running on the same device count independently in these usage graphs.

The Daily Monitoring tab provides more detailed information about what is happening in your account over the past month, on a day by day basis. For example:

Monitoring Portal Example

The individual graphs are as follows, all showing data on a daily basis:

  • Unique Devices:Number of unique devices seen each day.
  • Attestations:Total number of attestation attempts made on the account each day, showing those that are passing and those that are rejected.
  • Attestations Rejected:Total number of rejected attestations on the account each day. This is shown on a different graph so that the scale can show smaller fluctuations on a daily basis.
  • Rooted Attestations:Total number of attestations performed per day from rooted or jailbroken devices. This may either result in passed or rejection attestations depending upon the security policy selected.
  • Emulator Attestations:Total number of attestations coming from a known or suspected emulator device per day. Again, whether these are passed or rejected depends on the security policy selected.
  • Attestations with Instrumentation Frameworks:Total number of attestations coming from devices that have a framework installed. Again, whether these are passed or rejected depends on the security policy selected.
  • Instrumentation Framework Types:Provides a breakdown of the different instrumentation frameworks that have been detected on devices.

The usage graphs and Unique Devices in the daily monitoring have a y axis that count devices. Other graphs show the number of attestation attempts, not the number of unique devices.

SDK Usage Differences

A significant difference between Approov 1 and Approov 2 SDKs is the manner in which the SDK is configured. Approov 1 SDKs are customized with account information is embedded within them. This means that the developer only needs to use generic code to initialize the SDK and when it subsequently contacts the Approov cloud service, it is able to identify itself as associated with a particular account.

In terms of interfaces, the manner in which Approov tokens are fetched in the Approov 2 SDK is very similar to Approov 1. The differences are primarily syntactic in terms of the package name and the names of the returned result structures. There is also a richer diversity of result codes provided by the SDK to make it easier to diagnose why an Approov token cannot be fetched.

Use of the parameter to provide the domain for Approov token fetches is now mandatory. In Approov 1 a null/nil value could be supplied if dynamic pinning was not in use.

The bigger difference is the way in which the SDK is initialized. In Approov 1 it was not mandatory to initialise the SDK at all for iOS. Step by step instructions are provided for the initialisation of the Approov 2 SDK.

Note that the custom claim feature continues to be available and operates in essentially the same manner, but the method to set it is now called setDataHashInToken.

Javascript bindings for calling the SDK directly from a Webview are no longer directly supported in the SDK. If you need these, then we are able to provide code for a wrapper library that will allow you to make compatible calls. Please contact Approov support.

Migrating Pinning

Approov 1 provided a dynamic pinning capability that was used by some Approov users. This pinning mechanism probed particular API endpoints dynamically, and transmitted hashes of the leaf certificates that were found to the Approov cloud server. These could then be compared against the expected result so that apps that were subject to a Man-in-the-Middle (MitM) attack could be protected.

To support this flow, the SDK included the methods getCert()and clearCerts(). These are no longer available in the Approov 2 SDK and the Approov 1 dynamic pinning method is deprecated. There are a number of different reasons for this:

  • The scheme is quite complex to implement in the app and is particularly difficult to test.
  • For Android, in order to use the dynamic pinning features it was necessary to hook the underlying HTTP socket communication with custom trust verifiers and hostname verifiers. Custom hooking of the trust management layers is not ideal practice and has led to some false positives in app security scans and compatibility issues with some versions of Android.
  • The scheme is limited to pinning against leaf certificates only, and some Approov users require a more flexible approach allowing pinning to intermediate certificates to support their backend API requirements.
  • The scheme only prevents valid Approov tokens being issued if the TLS channel is compromised, it does not prevent app user data interception. Apps are increasingly using pinning to protect user data as well as authorization tokens. Thus an approach is needed that fully prevents any communication on the channel if MitM is detected.
  • We wish to leverage the various pinning support libraries that exist for pinning in different languages and on different HTTP stacks. We also wanted to move to using the de-facto standard of subject public key info hash pins, which were established by HPKP (HTTP Public Key Pinning).

To migrate, the custom verifiers should be removed and replaced with those discussed in the Public Key Pinning section. The pins then need to be managed from the SDK configuration side. They are not automatically updated when the certificates change, so more careful coordination of certificate updates is required.