Android SDK User Guide

Introduction

The Approov SDK provides an API to control the attestation process and retrieve tokens from our servers. The tokens are then used to verify the authenticity of the app on your servers.

The SDK is provided as an .aar and can be simply dropped in to an existing Android Studio project.

Importing the Approov SDK into Android Studio

The following instructions are accurate for Android Studio 2.1.2 and above

  1. Make sure the Project View is open (if it is not, click on “1: 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 “1: Project”), the Project View’s tabs are either visible side-by-side (a), or grouped together in a drop-down menu (b):
  1. select the “Android” tab (at the top of the Project View). If the “Android” tab is not visible, left clicking on the two arrows at the end of the list of tabs brings up a selection of check-boxes. Check the box labeled “Android” to make the “Android” tab visible.
  2. from the Project View’s tab drop-down menu (at the top of the view) select “Android”.
  1. Right-click on the “app” folder in the “Android” tab and select “New” → “Module”.
  2. Select the “Import .JAR/.ARR Package” option and click on “Next”.
  3. Enter the path of your Approov SDK .aar 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 (by default: cb_approov_<client library id>; you can also choose a different sub-project name). The library can be downloaded from the The Approov Admin Portal.
  4. Click “Finish”. The new subproject folder should appear in the “Android” tab.
  5. Right-click on the “app” folder in the “Android” tab and select “Open Module Settings”.
  6. Ensure that the “app” module is selected in the sidebar on the bottom left.
  7. Click on the “Dependencies” tab and click on the green “+” button (labeled “Add”) in the top right corner.
  8. Select “3 Module Dependency”.
  9. Select the Approov SDK module that you just imported (by default: cb_approov_<client library id>), click “OK” and then click “OK” again to close the window.
  10. A Gradle sync will then run.

Once you have successfully completed these steps you are ready to start making use of the Approov SDK.

Note

Before a valid token will be generated by our SDK, the app must be registered (see Android Registration).

Approov API Overview

The Approov SDK provides a simple API for authenticating your app and requesting a time and IP-limited Approov token from the Approov Cloud Service to represent the app’s authenticity which you can then transmit securely to your web services for validation.

Warning

The Approov SDK securely delivers the Token to your app but it is your responsibility to ensure the token is securely transmitted to your API. The connection between app and API must use HTTPS and be Pinned to your API host to prevent theft of valid tokens.

You can use the Approov SDK to implement Dynamic Pinning with MITM Detection if you do not already have Certificate Pinning in place. This avoids the operational issues associated with deploying Certificate Pinning while providing a similar level of protection and will prevent theft of valid tokens.

The API is based around an ApproovAttestation Java class. This class enables you to authenticate your app and fetch Approov tokens either synchronously or asynchronously using the methods fetchApproovTokenAndWait(String pUrl) or fetchApproovToken(TokenInterface callback, String pUrl) depending on your requirements. The TokenInterface should be implemented by the app to respond to the outcome of fetching an Approov token.

An overview of the ApproovAttestation API is shown below:

Examples

Android
/**
 * Accessor for the ApproovAttestation singleton
 *
 * @return - The singleton attestation object (may be null)
 */
static ApproovAttestation shared();

/**
 * Initialize the attestation library using the customer name. 
 * The initializer generates all the appropriate URLs for communication 
 * with the Approov servers
 *
 * @param pConfig the Approov configuration object to use in the initialization process
 * @throws IllegalArgumentException
 * @throws MalformedURLException
 */
static void initialize(ApproovConfig pConfig) throws IllegalArgumentException, MalformedURLException;


/**
 * Initiates an asynchronous request to perform an attestation of the running 
 * app and fetch an Approov token from the Approov cloud service.
 * The token request is asynchronously, meaning this will return immediately.
 * Once a token has been fetched, the given interface will be called in a new thread.
 *
 *
 * @param pTokenInterface instance of TokenInterface to call once token is obtained
 * @param pUrl the URL of the connection to be protected by Approov, this may be null but this
 *             is not recommended as the connection will not be protected from MITM attack
 */
void fetchApproovToken(TokenInterface pTokenInterface, String pUrl);

/**
 * Initiates a synchronous request to perform an attestation of the running 
 * app and fetch an Approov token from the Approov cloud service.
 *
 * This method blocks the caller until completion or timeout. Since this 
 * method may make internet conections it should never be called from the 
 * main UI thread.
 *
 * When the synchronous fetch token operation is complete, the result and 
 * Approov token is returned and the caller is unblocked.
 *
 * @param pUrl the URL of the connection to be protected by Approov, this may be null but this
 *             is not recommended as the connection will not be protected from MITM attack
 */
TokenInterface.ApproovResults fetchApproovTokenAndWait(String pUrl);

/**
 * Retrieve DER binary format data of the X.509 TLS leaf certificate 
 * for the given URL retrieved by Approov.
 *
 * @param pUrl URL of the connection protected by Approov
 * @result the certificate data, or null if the connection to the given URL has not been retrieved by Approov
 *
 * The fetchApproovToken (or fetchApproovTokenAndWait variant) method 
 * should be invoked with the URL prior to  invoking this method (or after 
 * invoking the clearCerts method). If this is the case and getCert returns null,  
 * it is possible that the connection has been intercepted and is therefore not secure.
 *
 */
byte[] getCert(String pUrl);

/**
 * Clear the internal cache of X.509 TLS leaf certificates retrieved by Approov.
 * This should be called if you suspect that the certificate information stored is incorrect,
 * either as a result of communication with your server or a miss-match in the certificates
 * obtained by calling getCert and comparing the answer to your connection's certificate.
 */
void clearCerts();

// ASYNCHRONOUS TOKEN FETCH CALLBACK INTERFACE:

// Interface that all callbacks provided to Approov must implement
public interface TokenInterface {

    /** Main working function, to be implemented with relevant business logic */
    void approovTokenFetchResult(ApproovResults pResults);

    /**
     * Plain old data class for returning results
     */
    class ApproovResults {

        /** Approov token string getter
         * @return Approov token string, will be empty string on failure, will not be null
         */
        public String getToken();

        /** Approov token fetch result getter
         * @return Approov token fetch result
         */
        public AttestationResult getResult();
    }
}

The API also provides support for Dynamic Pinning of your app to your server via the getCert(String pUrl) and clearCerts() methods. Please see Approov MITM Detection for detailed information.

Initializing the Approov SDK

In order to attest you will first need to initialize the Attestation library. We recommend that initialization should be performed at application start-up so the Approov SDK is available to perform attestations straight away. There is only a single instance of the ApproovAttestation object which is managed by the SDK and is accessed via the ApproovAttestation.shared() static method once initialization is completed.

We recommend that you initialize the library in the OnCreate() method of your Application or derived object.

Here is an example of the recommended way to setup the Approov library. First, either create a new Class that expands the Application class (see the official Android documentation ) or extend your existing one, then add a call to the Approov initialize function:

Examples

Android
public void onCreate() { 
    super.onCreate();
    
    // Initialize the Approov SDK
    try {
        // Creates the configuration object for the Approov SDK based
        // on the Android application context
        ApproovConfig config = 
            ApproovConfig.getDefaultConfig(this.getApplicationContext());
        ApproovAttestation.initialize(config);
    } catch (IllegalArgumentException ex) {
        Log.e(TAG, ex.getMessage());
    } catch (MalformedURLException ex) {
        Log.e(TAG, ex.getMessage());
    }

    // Other initialization code
}

Once the SDK is initialized, you should always use the shared() singleton accessor to call methods of the ApproovAttestation object.

Examples

Android
// Use static accessor to call Attestation methods
ApproovAttestation.shared().fetchApproovTokenAndWait("api.myservice.io");

Calling initialize multiple times has no effect. If initialization fails (it may throw IllegalArgumentException or MalformedURLException) then calling ApproovAttestation.shared() will return null.

Note

It is important to use the Application context to initialize the library. The use of a short lived context, such as an Activity context, is likely to create memory leaks by holding a reference to the context after the Activity life-cycle has ended.

The TokenInterface

The TokenInterface enables Approov to provide a callback mechanism into your code so that you can be notified when an Approov token fetch operation has completed. A concrete class should be created in your code which implements this interface and handles the response from Approov appropriately.

The TokenInterface is shown below:

// Interface that all callbacks provided to Approov must implement
public interface TokenInterface {

    void approovTokenFetchResult(ApproovResults pResults);

    /**
     * Plain old data class for returning results
     */
    class ApproovResults {
        private AttestationResult mEnum;
        private String mToken;

        public ApproovResults(AttestationResult pEnum, String pToken){
            mEnum = pEnum;
            mToken = pToken;
        }

        public String getToken(){
            return mToken;
        }

        public AttestationResult getResult(){
            return mEnum;
        }
    }
}

When using the asynchronous fetchApproovToken API call the object passed in must implement this interface, specifically the approovTokenFetchResult(ApproovResults pResults, String pURL) method. This method is called in its own thread and is given the results object detailing the results of the latest token fetch attempt. If the attempt was not successful, the token String will be the empty string (it will never be null).

If the token fetch attempt was successful then the token String will contain an Approov token. This token is cached inside the Approov library, as a result it is strongly recommended that the token should not be cached by the app (see Token Lifespan).

Requesting an Approov Token

There are two attestation functions available on the Approov library’s public API, one asynchronous and one synchronous. Both populate an ApproovResults object which contains information about the success of the operation and the resulting token.

Note

The result’s success and failure refer to the Approov library’s attempt to fetch a token and not the validity of the token returned. Failure is generally caused by poor connectivity, resulting in the connection to the Approov server timing out or being unavailable.

Synchronous Fetch

TokenInterface.ApproovResults aApproovResults = ApproovAttestation.shared().fetchApproovTokenAndWait("api.myservice.io"));

This call takes no arguments and upon completion of this blocking call an ApproovResults object is returned which can be used to obtain the results of the token fetch. Further details of the ApproovResults can also be found in The TokenInterface. It is strongly recommended that the token should not be cached by the app (see Token Lifespan).

Examples

Android
...

final Button button = (Button) findViewById(R.id.login);
button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        ApproovResults aApproovResults = mAttestation.fetchApproovTokenAndWait("api.myservice.io");
        if (aApproovResults.getResult() == AttestationResult.SUCCESS) {
            loginFunction(aApproovResults.getToken());
        } else {
            displayErrorMessageToUser();
        }
    }
});

...

final void loginFunction(String pToken){
    /* Logic to put the token into your API login call */
}

Note

This blocking request may involve network traffic. As a result the request may take some time and should not be called on the UI thread.

Asynchronous Fetch

void fetchApproovToken(TokenInterface pTokenInterface, String pUrl);
This call takes two arguments:
  • An instance of a class implementing the Approov TokenInterface (discussed in The TokenInterface).
  • The URL of the domain being accessed as a String.

The callback function passed in will be called once the token fetch request has been completed and takes as its only argument an ApproovResults object which can be used to obtain the results of the token fetch. Further details of the ApproovResults can be found in The TokenInterface. Note that each callback runs in its own thread to ensure that callbacks from multiple places can be served in parallel. It is strongly recommended that the token should not be cached by the app (see Token Lifespan).

Examples

Android
...

final Button button = (Button) findViewById(R.id.login);
final TokenInterface callback = new TokenInterfaceImpl();
button.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        ApproovAttestation.shared().fetchApproovToken(callback, "api.myservice.io");
    }
});

...

public class TokenInterfaceImpl implements TokenInterface {


    TokenInterfaceImpl() { /* EMPTY */ }

    /**
     * Interface method, the actual callback functionality
     *
     * @param approovResults results of fetching approov token
     */
    @Override
    public void approovTokenFetchResult(final ApproovResults approovResults) {

        ApproovAttestation.AttestationResult result = pApproovResults.getResult();
        String token = approovResults.getToken();

        if (result == ApproovAttestation.AttestationResult.SUCCESS){
            loginFunction(token);
        } else if (result == ApproovAttestation.AttestationResult.FAILURE){
            displayErrorMessageToUser();
        }
    }
}

final void loginFunction(String token){
    /* Logic to put the token into your API login call */
}

Using Approov from a WebView

WebView Setup

If you are using a WebView to access your server then you can also get your token via JavaScript. To do this you must first ensure that your WebView allows JavaScript. If your WebView does not already make use of JavaScript you will need to enable it:

Android
WebSettings aWebSettings = aWebview.getSettings();
aWebSettings.setJavaScriptEnabled(true);

In order to pass the attestation token to your server inside a WebView you will need to register your application’s WebView with our library. This is done by a simple function call to an AndroidPlatformSpecifics object.

There are two ways of adding a WebView, directly or via the root view. If you know the exact WebView that you want to register then you can directly register your WebView, like this:

Android
WebView webview = (WebView) findViewById(R.id.webview);
ApproovAttestation.shared().register(webview);

Otherwise, if you do not know the exact WebView you can register the root View. Here is an example:

Android
ViewGroup rootView = (ViewGroup)view.getRootView();
ApproovAttestation.shared().register(rootView);

Any ViewGroup that is registered with a PlatformSpecifics object will recurse through the view tree to set the JavaScript interfaces on all WebViews that it encounters. This is why it is possible to register just the root View. It is more efficient to add the WebViews directly as it avoids this search.

Note

The Approov JavaScript API will not be visible until you reload the page. This is automatically done on registration of the WebView.

Note

The Approov JavaScript API is only available for devices running Android 4.2 and above. This is because of a security change that was brought into the Android JavaScript interface at Android 4.2.

WebView Use

Once your WebView is set to allow JavaScript and has been registered, all you have to do is add either two or three JavaScript functions to call the Approov SDK’s public API. The first function is the one that requests a token from the Approov API.

This takes either three arguments as shown here:

Android WebView
// Send a request for a token and cope with
// success and failure in separate functions
function requestTokenAndFilter() {
    Approov.fetchApproovToken("fetchSuccess", "fetchFailure", "api.myservice.io");
}

// Callback for success
function fetchSuccess(token) {
    // Use new token, token
    ...
}

// Callback for failure
function fetchFailure(token) {
    // Show error dialogue
    ...
}

Or alternatively, can take two arguments:

Android WebView
 // Send a request for a token
function requestToken() {
    Approov.fetchApproovToken("callbackFunction", "api.myservice.io");
}

// Callback on success or failure
function callbackFunction(token) {
    // if token is empty string then was failure
    ...
}

Additionally, it is possible to make a synchronous Javascript request:

Android WebView
// Send a request for a token synchronously
function requestTokenSync() {
    // token will either be "" or a string containing an Approov token
    token = Approov.fetchApproovTokenAndWait("api.myservice.io");
    // do work with token
    ...
}

WebView Clean Up

As with any asynchronous use of JavaScript, care must be taken to cleanup properly if transitioning to a different View or Activity. For example, if the Activity with the Approov registered WebView is to login to see some protected content that is dealt with in a different Activity. To do this, the WebView should be de-registered from the Approov SDK and it may be necessary to prevent any further JavaScript from firing on the WebView.

One way to achieve this would be to expand the onStop method of the Activity:

Android
@Override
protected void onStop() {
    ...
    WebView webview = (WebView) findViewById(R.id.<WebView ID>);
    ApproovAttestation.shared().unregister(webview);
    WebSettings webSettings = webview.getSettings();
    webSettings.setJavaScriptEnabled(false);
    ...
}

Using the Approov Java API with the Approov JavaScript API

It is possible to use the Approov Java API and the Approov JavaScript API at the same time. To do so you must follow both the instructions for using the JavaScript API in a WebView and the instructions for using the Java API and implementing the Approov TokenInterface.

Including a custom claim

In order to extend the security of the Approov token through a strength in depth approach that ties a particular Approov token to an arbitrary string it is possible to include a hash of arbitrary data as one of the Approov token’s claims. This is intended to be used for long lived data, such as an OAuth token or a user session id, 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 puts the bytes through a SHA-256 hash and then base64 encodes the result. Details of how the claim appears in the token can be found in the server side documentation, see Customer Payload Data for more details.

Note

The value supplied cannot be null, otherwise an IllegalArgumentException is thrown.

The setTokenPayloadValue(value) function should be called immediately after startup to ensure that a value is always set so that there is no call to fetch an Approov token before this value is set. The value of payload can be changed at any time, but doing so will cause a new token to be fetched, rather than returned a cache token, the next time a call to fetch an Approov token is made. As such it is recommended that this value should not be changed often. In the case of wanting to fetch an Approov token when no suitable data is available (such as before the user is logged in), it is necessary to call setTokenPayloadValue(...) with a sentinel value that should not be a valid value that will be used in later operation. Values such as the empty String or “NONE” are likely to be suitable.

Note

The use of this Custom Payload feature is applicable for either the synchronous fetchApproovToken(...) or asynchronous fetchApproovTokenAndWait(...) methods.

Examples

Android
try {
    ApproovAttestation.shared().setTokenPayloadValue(customValue);
} catch (IllegalArgumentException e) {
    logNullCustomValueError();
}

JavaScript

if (!approov.setTokenPayloadValue(value)){
    logNullCustomValueError();
}

Token Lifespan

Approov tokens have a limited lifespan and are cached to avoid unnecessary network traffic. When a call to fetch an Approov token is made, the library checks if the current token is still valid. This allows the call to return a token very quickly in the general case. If the SDK is notified of a network connectivity change this will automatically invalidate the token and cause a new token to be fetched on the next request. In this way the Approov SDK will take care of token expiry behind the scenes.

As a result of this you should not cache the token in any application logic; instead call our SDK to fetch one whenever it is required. This will also help improve security as it minimizes the number of copies of the Approov token that are held in memory for any length of time.

Using a VPN

There are known issues with trying to use a VPN connection on Android 4.4 (Kitkat). This may result in the Approov SDK having incorrect information on the connectivity status of the device. This would in turn result in the tokens obtained from the Approov SDK being rejected as invalid by the application servers and prevent the user from being able to use the application. If this behavior is observed the suggested action is to either restart the application or to enable then disable flight mode; either of which should force a connection update.