Approov Direct SDK Integration

This section provides a detailed reference for calling the SDK directly. If you are able to use one of our Mobile App Quickstarts then you will not need to use these interfaces, only the highly simplified interface of the quickstart package itself. Since the source code is open for these quickstarts you will be able to see how they use the underlying SDK.

Android SDK Integration

This section shows you how to get the Android Approov SDK and integrate it into your project in Android Studio.

Getting the Android SDK

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

approov sdk -getLibrary approov_sdk.aar

This writes the latest available SDK package to the approov_sdk.aar file (or any path that you specify).

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.

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

approov sdk -getLibrary approov_sdk.aar -libraryID 2772

Importing the SDK into Android Studio

The Approov SDK minimum requirement is Android 5 (API level 21). You cannot use Approov in apps that support versions older than this.

The Approov SDK can be added to an existing app project in Android Studio using the following steps. They are based on Add your AAR or JAR as a dependency.

  1. Navigate to File > Project Structure.

  2. Ensure that Dependencies is selected in the left hand side of the dialog that pops up.

    Android Studio: Add dependency

  3. Click the + and then .JAR/.AAR Dependency in the dropdown.

    Android Studio: Add AAR

  4. In the Add Jar/Aar Dependency dialog enter the path of the .aar file previously downloaded. Note that it is not possible to navigate to the file so you will need to copy and paste its pathname. Then click OK.

    Android Studio: Add AAR Dependency

  5. Click OK in the Project Structure dialog.

  6. A Gradle sync will then run. If it fails check that you specified the .aar pathname correctly.

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:4.12.0'

Please also add the following code to your gradle file:

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

The following app permissions need to be available in the manifest (AndroidManifest.xml) to use the Approov SDK, inside the top level manifest tag:

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

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 similar to the following:

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

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

iOS SDK Integration

This section shows you how to get the iOS Approov SDK and integrate it into your project in Xcode.

This section is written to obtain an iOS SDK. Since version 3.2.0 watchOS is also supported, but this requires a different SDK. You can use the commands listed in this section but append -watchOS to force the download of that version.

Getting the iOS SDK as an XCFramework

The iOS Approov SDK can be downloaded using the approov command line tool. By default the SDK is provided as a dynamic XCFramework to allow usage with both physical devices and simulators. Use the following command to download the latest SDK package:

approov sdk -getLibrary Approov.xcframework

This writes the SDK into the directory Approov.xcframework (or any path that is specified). Ensure that this is empty before you perform this operation. Note that the raw iOS package is quite large so it may take some seconds to download depending upon your connection speed. Download progress is shown.

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.

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

approov sdk -getLibrary Approov.xcframework -libraryID 5643

Obtaining the SDK as a .xcframework is only supported from version 2.6.0.

Getting a Bitcode Version

Apple deprecated bitcode in Xcode 14 and its subsequent versions. This change is reflected in Approov SDK releases from version 3.2.0 which no longer include bitcode support.

It is still possible to obtain a bitcode version of the SDK library for older versions, although this is not recommended unless you have a specific requirement to do this. The bitcode version can be obtained as follows:

approov sdk -getLibrary Approov.xcframework -bitcode

Obtaining the bitcode version of an SDK automatically marks that SDK has being used in bitcode mode for your account. If you didn’t obtain the SDK via the Approov CLI then it is possible to mark bitcode mode using the -bitcode option during app registration. There are bitcode mode management commands available.

Importing the SDK into Xcode

The Approov SDK minimum requirement is iOS 12. You cannot use Approov in apps that support versions older than this.

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 the Approov.xcframework 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.xcframework 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>

Getting the iOS SDK as a Framework

It is also possible to obtain the SDK in the classic .framework format as follows:

approov sdk -getLibrary Approov.framework

This writes the SDK into the directory Approov.framework (or any path that is specified). Ensure that this is empty before you perform this operation. Note that the raw iOS package is quite large (of the order of 10MB) so it may take some seconds to download depending upon your connection speed. Download progress is shown.

By default the SDK provided is only suitable for running on physical devices. You can use the -simulator option to obtain a different SDK that only includes simulator archiectures.

Note that it is also possible to obtain an SDK in the legacy .zip format by using this extension. This provides the SDK in a zipped file of Approov.framework.

If you download a version of the SDK prior to 2.6.0 using this method then it will contain architectures for both physical devices and simulators. This is not compatible with Xcode version 12.3 or later. You must remove the simulator architectures using lipo -remove x86_64 Approov.framework/Approov -o Approov.framework/Approov and lipo -remove i386 Approov.framework/Approov -o Approov.framework/Approov prior to importing into Xcode.

SDK Initialization

The SDK initial configuration consists of a fixed short string. Substitute initialConfig for that string. The SDK can then be initialized as follows:


try {
    Approov.initialize(getApplicationContext(), initialConfig, "auto", null);
} catch (IllegalArgumentException e) {
    Log.e(TAG, "Approov initialization failed: " + e.getMessage());
}

do {
    try Approov.initialize(initialConfig!, updateConfig:"auto", comment: nil)
} catch {
    NSLog("Approov initialization failed: \(error.localizedDescription)")
}

On Android, Approov initialization requires the app Context. The parameter auto to the update configuration indicates that the SDK should automatically handle any dynamic configuration updates, storing them in the peristent storage for the app. The final comment string parameter should be set to null unless you are reinitializing the SDK.

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.

Note that when the auto parameter is used, the SDK will attempt to fetch the latest dynamic configuration from the Approov servers on the very first initialization after the app is installed. This means that the initialization process could take up to a maximum of 2 seconds the first time. The initialization will not fail if network connectivity is not available.

The use of auto for the update configuration is only supported in 3.0.0 and later SDKs.

Reinitializing the SDK

Under normal circumstances it is only permissible to initialize the SDK once, and any attempt to initialize it a second time will result in an error.

There is one particular use case for Approov where a single app might be associated with several different Approov accounts, that might be selected as the app runs. Note though that the Approov SDK can only be associated with one account at a time.

Reinitialization is explicitly marked by providing reinit to the comment parameter of the initialization call.


try {
    Approov.initialize(getApplicationContext(), initialConfig, "auto", "reinit");
} catch (IllegalArgumentException e) {
    Log.e(TAG, "Approov reinitialization failed: " + e.getMessage());
}

do {
    try Approov.initialize(initialConfig, updateConfig:"auto", comment:"reinit")
} catch {
    NSLog("Approov reinitialization failed: \(error.localizedDescription)")
}

Reinitialization is only supported in version 2.4.0 and later SDKs.

The SDK should not be reinitialized too frequently. The typical use case is that the reinitialization should only occur if the user of the app switches their account in some way, requiring access to a different set of backend APIs. Reinitialization should never be performed if there are in-flight asynchronous Approov fetches.

SDK Fetch Operations

The Approov SDK supports a number of different types of “fetch” operation. These are fetchApproovToken, fetchApproovTokenAndWait, fetchSecureString, fetchSecureStringAndWait, fetchCustomJWT or fetchCustomJWTAndWait. This supports the fetching of Approov tokens, secure strings and custom JWTs with variants for synchronous (blocking) and asynchronous (non-blocking) calls.

All fetches return a standard Fetch Result, either as the return result or as a parameter to a callback function. This provides the results of the fetch operation.

Fetch Status Handling

Every fetch provides an Approov Fetch Status, providing information about the reason for any fetch failure to determine how the app should react. This status can be obtained as follows, assuming approovResult is the instance of the Fetch Result obtained.


String status = approovResult.getStatus();

let status = approovResult.status

The possible status results 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..

Status (Android / iOS) Recommendation
SUCCESS / success Continue
UNPROTECTED_URL / unprotectedURL Continue
UNKNOWN_URL / unknownURL Continue
NO_APPROOV_SERVICE / noApproovService Continue / Retry
NO_NETWORK / noNetwork Retry
POOR_NETWORK / poorNetwork Retry
MITM_DETECTED / mitmDetected Retry
REJECTED / rejected Message
BAD_URL / badURL Error
DISABLED / disabled Error
UNKNOWN_KEY / unknownKey Error
NA / badKey Error
NA / badPayload Error
NO_NETWORK_PERMISSION / NA Error
MISSING_LIB_DEPENDENCY / NA Error
INTERNAL_ERROR / internalError Error
NA / notInitialized Error

The Recommendation column shows how the app logic should proceed if it receives the given error state. There are four possible options:

  • Continue: Indicates that the app should go ahead and make the API call as expected.
  • Retry: Indicates that the app should not attempt the API call. This is because it has not been possible to complete the fetch 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.
  • Message: Indicates that a message needs to be presented to the user about why it is not possible to proceed.
  • Error: This indicates an error condition that 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 any Approov token. This will of course be rejected by the backend, but will provide loggable information there.

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.

Once your app is able to fetch Approov tokens, we strongly recommend that you also implement dynamic pinning. As well as protecting your users’ data, this will also protect Approov tokens from being stolen with a Man-in-the-Middle attack.

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.

You must never make this call directly from the main or UI thread of your application. This is a potentially long running blocking call, and there may be unpredictable delays and effects on your app if it blocks UI processing. Use an asynchronous call instead.

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


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

let approovResult = Approov.fetchTokenAndWait("api.myservice.io")

The method takes a single parameter specifying the 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. Check the fetch status portion of the returned result and react appropriately:


if (approovResult.getStatus() == Approov.TokenFetchStatus.SUCCESS) {
    String token = approovResult.getToken();
    // proceed with the API request, adding the Approov token to the request
} else if ((approovResult.getStatus() == Approov.TokenFetchStatus.UNPROTECTED_URL) ||
           (approovResult.getStatus() == Approov.TokenFetchStatus.UNKNOWN_URL) ||
           (approovResult.getStatus() == Approov.TokenFetchStatus.NO_APPROOV_SERVICE)) {
    // proceed with the API request, but without adding an Approov token to the request
} else if ((approovResult.getStatus() == Approov.TokenFetchStatus.NO_NETWORK) ||
           (approovResult.getStatus() == Approov.TokenFetchStatus.POOR_NETWORK) ||
           (approovResult.getStatus() == Approov.TokenFetchStatus.MITM_DETECTED)) (
    // network conditions do not allow the token fetch so allow a user initiated retry
} else {
    // unexpected error handling
}

if (approovResult.status == ApproovTokenFetchStatus.success) {
    let token = approovResult.token
    // proceed with the API request, adding the Approov token to the request
} else if (approovResult.status == ApproovTokenFetchStatus.unprotectedURL) ||
          (approovResult.status == ApproovTokenFetchStatus.unknownURL) ||
          (approovResult.status == ApproovTokenFetchStatus.noApproovService) {
    // proceed with the API request, but without adding an Approov token to the request
} else if (approovResult.status == ApproovTokenFetchStatus.noNetwork) ||
          (approovResult.status == ApproovTokenFetchStatus.poorNetwork) ||
          (approovResult.status == ApproovTokenFetchStatus.mitmDetected) {
    // network conditions do not allow the token fetch so allow a user initiated retry
} else {
   // unexpected error handling
}

Asynchronous Token Fetching

An asynchronous token fetching approach is also provided. This accepts a callback method parameter alongside the target domain. The callback is called either when an Approov token is available or there is an error and is always called from a different thread to the one that requests the callback. The fetchApproovToken method allows token fetches to be initiated from threads that cannot be blocked, such as UI threads. When invoked, the callback must check the fetch status of the provided result and react appropriately:


class ApproovCallbackHandler implements Approov.TokenFetchCallback {
    @Override
    public void approovCallback(Approov.TokenFetchResult pResult) {
        switch (pResult.getStatus()) {
            case Approov.TokenFetchStatus.SUCCESS:
                String token = pResult.getToken();
                // proceed with the API request, adding the Approov token to the request
            case Approov.TokenFetchStatus.UNPROTECTED_URL:
            case Approov.TokenFetchStatus.UNKNOWN_URL:
            case Approov.TokenFetchStatus.NO_APPROOV_SERVICE:
                // proceed with the API request, but without adding an Approov token to the request
            case Approov.TokenFetchStatus.NO_NETWORK:
            case Approov.TokenFetchStatus.POOR_NETWORK:
            case Approov.TokenFetchStatus.MITM_DETECTED:
                // network conditions do not allow the token fetch so allow a user initiated retry
            default:
                // unexpected error handling
        }
    }
}
ApproovCallbackHandler approovCallback = new ApproovCallbackHandler();
Approov.fetchApproovToken(approovCallback, "api.myservice.io");

Approov.fetchToken({ (approovResult: ApproovTokenFetchResult) in
    switch approovResult.status {
    case ApproovTokenFetchStatus.success:
         let token = approovResult.token
          // proceed with the API request, adding the Approov token to the request
    case ApproovTokenFetchStatus.unprotectedURL,
         ApproovTokenFetchStatus.unknownURL,
         ApproovTokenFetchStatus.noApproovService:
         // proceed with the API request, but without adding an Approov token to the request
    case ApproovTokenFetchStatus.noNetwork,
         ApproovTokenFetchStatus.poorNetwork,
         ApproovTokenFetchStatus.mitmDetected:
         // network conditions do not allow the token fetch so allow a user initiated retry
    default:
         // unexpected error handling
    }
}, "api.myservice.io")

Getting an Attestation Response Code

The Attestation Response Code is provided that is available once an Approov token is fetched. This is available even if the Approov token is a JWE and thus encrypted. Use the following call:


String arc = approovResult.getARC();

let arc = approovResult.ARC

If the Approov fetch was unsuccessful, or the ARC capability is not enabled, then an empty string is returned. Otherwise the short base32 encoded ARC string is provided. It is safe to include this in logging, since without access to the Approov backend it is not possible to decode its contents.

This method is only available in version 2.5.0 and later SDKs.

Getting the Rejection Reasons

The Rejection Reasons are provided that is available once an Approov token is fetched. Use the following to access it:


String reasons = approovResult.getRejectionReasons();

let reasons = approovResult.rejectionReasons

If the fetch was unsuccessful, or resulted in a pass result, then an empty string is provided. Otherwise a comma separated list of device properties causing a rejection are provided.

This information may be presented to the user as an explanation of why they are unable to proceed in the app.

You can rely on the device property names not changing, so you may wish to split the string and check against individual commonly occurring properties (e.g. rooted or jailbroken) and provide a more detailed explanation to the user.

This rejection reasons capability is only available in version 3.0.0 and later SDKs.

Public Key Pinning Implementation

Managing Dynamic Pinning describes 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 in the app at runtime 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.

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. The getPins method should be called with the parameter public-key-sha256, indicating the pinning type being performed. This specifies that the pinning type is the SHA256 hash of the Subject Public Key Information (SPKI) of the certificate. This is currenly the only type of pinning supported. A list of acceptable pins is returned, and the certificate chain presented by the connection to that domain must contain at least one of those pins.

When managed trust roots are enabled, additional pins are provided from getPins under the * domain name. This allows the trusted root set to be provided only once, even if they are to be used for multiple API domains.

This pinning implementation should operate in the following way:

  1. The pinning implementation should check for pins associated with the specific domain; if they are defined then they are used as the only accepted set of pins.
  2. If there are no pins defined for a domain, then the implementation should check if there are any pins associated with the * domain; if there are then they are should be used as the only accepted set of pins, representing trusted root certificates.
  3. Otherwise the connection is not subject to pinning.

This approach allows a mixture of API domains that use specific pins and domains using the managed trust roots.

The SDK also provides a getPinsJSON method that provides the pins in marshaled JSON, for cases where the type return from getPins cannot be easily bridged between language runtimes.

Reacting to Configuration Changes

Whenever an Approov token is fetched there is a possibility that a new dynamic SDK configuration will be received. This will happen for apps whenever Dynamic Pinning Changes have been made in the account. The getPins method always returns the latest set of pins received by the SDK. If your implementation calls this method dynamically on all new TLS connections then the update will happen completely automatically.

In some cases, however, your implementation may have set pins as part of the construction of a networking stack. This is notably the case for OkHttp where the CertificatePinner needs to be set during construction. In this case your implementation should detect configuration updates after Approov token fetches and then arrange to build a revised stack using the new pins.

A isConfigChanged boolean property is included in the Fetch Result from the SDK. The property is set to true if there is an unconsummed update to the SDK configuration. The update is consummed by the first call to fetchConfig after the new configuration is received; thereafter the isConfigChanged property of new fetch results will be false until the next update occurs.

Reacting to Force Apply Pins

In some cases it is not possible for the app to immediately react to pinning changes. In this case the app may need a restart to apply the new pin configuration, or prompt the user to restart the app. Generally though we do not wish to restart the app every time there is a configuration change, as this could result in a poor user experience. Moreover, we may wish to push new pins to the configuration ahead of time so that they are more likely to be available when needed and thus avoid the need for an invasive restart altogether, since there is a higher chance of the app being restarted anyway.

One approach is to postpone any restart until there has been both a pending configuration update and a pinning failure exception has been caught by the app. That way a restart only occurs when absolutely necessary. Of course this may complicate the code by requiring special checks and logic on pinning failures.

Another mechanism is provided that is initiated by the Forcing Pin Application command. A isForceApplyPins property is provided in the Fetch Result. If this flag is set then it indicates that the last call to getPins made by the app obtained a version of the pins that is older than that which is being forced. Thus if an app has been naturally restarted since the last pin configuration update then it will already have a sufficiently recent version and the flag is not asserted,

The fact that these pins have been forced indicates that it is important that the new version is being used, perhaps because a certificate has been compromised or new pins are required to access an endpoint. If the flag is set then the app should take action to ensure that the pins are updated to the latest version, even if this means initiating an app restart. Calling getPins will reset the flag for the next time an Approov fetch is made.

A mechanism is provided to signal the need to force an immediate update where this is necessary:

approov pin -forceApplyPins

You will be asked for confirmation as follows:

WARNING: forcing pin application will have an immediate impact on your apps in production
ATTENTION: If you wish to continue then please type YES and return: YES
pin update has been forced

In the typical use case, new pins should be updated a couple of weeks before they go into service and then sometime later they can be forced. This procedure only results in a restart in the minority of apps that have not been relaunched for that period. In an emergency the forcing can be issued immediately. Note that this behavior only impacts app integrations that are not able to dynamically update their pins.

SDK Interface Reference

The overall SDK interfaces may be accessed as follows:


import com.criticalblue.approovsdk.Approov;


#import <Approov/Approov.h>


import Approov

Note that the iOS SDK is actually written in Objective-C and the method references are described in this way. Other code examples in the documentation are in Swift, using the automatically generated bindings to the SDK methods.

Initialization


/**
 * Initialize the Approov SDK. This must be called prior to any other methods on the Approov
 * SDK. The SDK is initialized with an application context, an initial configuration and an
 * optional update configuration and comment. The initial configuration is either a short
 * initialization string or a signed JWT token string that is obtained from the Approov
 * CLI and contains all necessary parameters to initialize the SDK. An updated
 * configuration may be transmitted while the SDK is in use and this must be stored in the local
 * storage of the app. If "auto" is provided for the update then the SDK manages its own
 * update configuration persistent storage. If "auto" is used then on the very first initialization
 * a short network fetch will be attempted to get the latest update configuration if possible.
 * If "reinit" is provided for the comment parameter then the SDK can be reinitialized with a
 * different account.
 *
 * @param appContext is the application context to use
 * @param initialConfig is the initial configuration short init string or JWT and must be present
 * @param updateConfig is any update configuration JWT, "auto" or null if there is none
 * @param comment is an optional comment or "reinit" to enable SDK reinitializtion, or null otherwise
 * @throws IllegalArgumentException if provided configuration is not valid
 */
public static void initialize(Context appContext, String initialConfig, String updateConfig, String comment) throws IllegalArgumentException;

/**
 * Initialize the Approov SDK. This must be called prior to any other methods on the Approov
 * SDK. The SDK is initialized with an initial configuration and an optional update configuration
 * and comment. The initial configuration is either a short init string or a signed JWT token
 * that is obtained from the Approov CLI tool and contains all necessary parameters to initialize the SDK.
 * An updated configuration may be transmitted while the SDK is in use and this must be stored in the
 * local storage of the app. If "auto" is provided as the update configuration then the SDK will manage
 * its own configuration update storage. If "auto" is used then on the very first initialization
 * a short network fetch will be attempted to get the latest update configuration if possible. If
 * "reinit" is provided for the comment then it allows the SDK to be reinitialized to change Approov
 * accounts.
 *
 * @param initialConfig is the initial configuration which is either a short init string or full JWT and must be present
 * @param updateConfig is any update configuration JWT, "auto" or nil if there is none
 * @param comment is an optional comment that may be "reinit" to force SDK reinitialization, or nil otherwise
 * @param error the reference to an error object which will be set if an error occurred
 * @return YES if the Approov framework was successfully initialized or NO otherwise
 */
+ (BOOL)initialize:(nonnull NSString *)initialConfig updateConfig:(nullable NSString *)updateConfig
    comment:(nullable NSString * )comment error:(NSError *_Nullable *_Nullable)error;

/**
 * Initialize the Approov SDK. This must be called prior to any other methods on the Approov
 * SDK. The SDK is initialized with an initial configuration and an optional update configuration
 * and comment. The initial configuration is either a short init string or a signed JWT token
 * that is obtained from the Approov CLI tool and contains all necessary parameters to initialize the SDK.
 * An updated configuration may be transmitted while the SDK is in use and this must be stored in the
 * local storage of the app. If "auto" is provided as the update configuration then the SDK will manage
 * its own configuration update storage. If "auto" is used then on the very first initialization
 * a short network fetch will be attempted to get the latest update configuration if possible. If
 * "reinit" is provided for the comment then it allows the SDK to be reinitialized to change Approov
 * accounts.
 *
 * @param initialConfig is the initial configuration which is either a short init string or full JWT and must be present
 * @param updateConfig is any update configuration JWT, "auto" or nil if there is none
 * @param comment is an optional comment that may be "reinit" to force SDK reinitialization, or nil otherwise
 * @param error the reference to an error object which will be set if an error occurred
 * @return YES if the Approov framework was successfully initialized or NO otherwise
 */
class func initialize(_ initialConfig: String, updateConfig: String?, comment: String?) throws

Configuration Fetching


/**
  * Fetches the current configuration for the SDK. This may be the initial configuration or may
  * be a new updated configuration returned from the Approov cloud service. Such updates of the
  * configuration allow new sets of certificate pins and other configuration to be passed to
  * an app instance that is running in the field.
  *
  * Normally this method returns the latest configuration that is available and is cached in the
  * SDK. Thus the method will return quickly. However, if this method is called when there has
  * been no prior call to fetch an Approov token then a network request to the Approov cloud
  * service will be made to obtain any latest configuration update. The maximum timeout period
  * is set to be quite short but the caller must be aware that this delay may occur.
  *
  * Note that the returned configuration should generally be kept in local storage for the app
  * so that it can be made available on initialization of the Approov SDK next time the app
  * is started.
  *
  * It is possible to see if a new configuration becomes available from the isConfigChanged()
  * method of the TokenFetchResult. This changed flag is only cleared for future token fetches
  * if a call to this method is made.
  *
  * @return String representation of the configuration
  */
public static final String fetchConfig();

/**
 * Fetches the current configuration for the SDK. This may be the initial configuration or may
 * be a new updated configuration returned from the Approov cloud service. Such updates of the
 * configuration allow new sets of certificate pins and other configuration to be passed to
 * an app instance that is running in the field.
 *
 * Normally this method returns the latest configuration that is available and is cached in the
 * SDK. Thus the method will return quickly. However, if this method is called when there has
 * been no prior call to fetch an Approov token then a network request to the Approov cloud
 * service will be made to obtain any latest configuration update. The maximum timeout period
 * is set to be quite short but the caller must be aware that this delay may occur.
 *
 * Note that the returned configuration should generally be kept in local storage for the app
 * so that it can be made available on initialization of the Approov SDK next time the app
 * is started.
 *
 * It is possible to see if a new configuration becomes available from the isConfigChanged
 * property of the TokenFetchResult. This changed flag is only cleared for future token fetches
 * if a call to this method is made.
 *
 * @return String representation of the configuration or nil if SDK not initialized
 */
+ (nullable NSString *)fetchConfig;

/**
 * Fetches the current configuration for the SDK. This may be the initial configuration or may
 * be a new updated configuration returned from the Approov cloud service. Such updates of the
 * configuration allow new sets of certificate pins and other configuration to be passed to
 * an app instance that is running in the field.
 *
 * Normally this method returns the latest configuration that is available and is cached in the
 * SDK. Thus the method will return quickly. However, if this method is called when there has
 * been no prior call to fetch an Approov token then a network request to the Approov cloud
 * service will be made to obtain any latest configuration update. The maximum timeout period
 * is set to be quite short but the caller must be aware that this delay may occur.
 *
 * Note that the returned configuration should generally be kept in local storage for the app
 * so that it can be made available on initialization of the Approov SDK next time the app
 * is started.
 *
 * It is possible to see if a new configuration becomes available from the isConfigChanged
 * property of the TokenFetchResult. This changed flag is only cleared for future token fetches
 * if a call to this method is made.
 *
 * @return String representation of the configuration or nil if SDK not initialized
 */
class func fetchConfig() -> String?

Getting the Device ID


/**
 * Gets the device ID used by Approov to identify the particular device that the SDK is running on. Note
 * that different Approov apps on the same device will return a different ID. Moreover, the ID may be
 * changed by an uninstall and reinstall of the app.
 *
 * @return String representation of the deviceID.
 */
public static final String getDeviceID();

/**
 * Gets the device ID used by Approov to identify the particular device that the SDK is running on. Note
 * that different Approov apps on the same device will return a different ID. Moreover, the ID may be
 * changed by an uninstall and reinstall of the app.
 *
 * @return String of device ID or nil if SDK not initialized.
 */
+ (nullable NSString *)getDeviceID;

/**
 * Gets the device ID used by Approov to identify the particular device that the SDK is running on. Note
 * that different Approov apps on the same device will return a different ID. Moreover, the ID may be
 * changed by an uninstall and reinstall of the app.
 *
 * @return String of device ID or nil if SDK not initialized.
 */
class func getDeviceID() -> String?

Pins Extraction


/**
 * Fetches the pins from the current configuration of the SDK. This is returned as
 * as map from URL domain (hostname only) to the possible pins for that domain. If there is
 * no map entry for a domain then that indicates that the connection is not specifically
 * pinned. The type of pin requested determines the data in each of the pins. This is typically
 * the base64 encoding of the hash of some aspect of the certificate. A connection is considered
 * to be valid if any certificate in the chain presented is one with the same hash as one in
 * the array of hashes. Note that if the isForceApplyPins flag was set on the last Approov
 * token fetch then this clears the flag for future fetches as it indicates that the latest
 * pin information has been read. Pins may be returned with "*" as the domain as these represent
 * the trusted root pins of all the acceptable certificate authorities.
 *
 * @param pinType is the type of pinning information that is required
 * @return Map from domain to the list of strings providing the pins
 */
public static final Map<String, List<String>> getPins(String pinType);

/**
 * Provide access to the API-Pins information held in the current app configuration. This is a helper method that avoids
 * the need for the app to access the configuration directly. Pins of a particular type are retrieved. If the
 * SDK has not be initialized then nil is returned. A connection is considered to be valid if any certificate
 * in the chain presented has the same hash as one in the list of hashes for the domain. Note that if the isForceApplyPins
 * flag was set on the last Approov token fetch then this clears the flag for future fetches as it indicates that the
 * latest pin information has been read. Pins may be returned with "*" as the domain as these represent the trusted
 * root pins of all the acceptable certificate authorities.
 *
 * @param pinType is the format of the pins that should be retrieved
 * @result NSDictionary mapping from domain names to a list of pins in base64 format for that domain
*/
+ (nullable NSDictionary<NSString *, NSArray<NSString *> *> *)getPins:(nonnull NSString *)pinType;

/**
 * Provide access to the API-Pins information held in the current app configuration. This is a helper method that avoids
 * the need for the app to access the configuration directly. Pins of a particular type are retrieved. If the
 * SDK has not be initialized then nil is returned. A connection is considered to be valid if any certificate
 * in the chain presented has the same hash as one in the list of hashes for the domain. Note that if the isForceApplyPins
 * flag was set on the last Approov token fetch then this clears the flag for future fetches as it indicates that the
 * latest pin information has been read. Pins may be returned with "*" as the domain as these represent the trusted
 * root pins of all the acceptable certificate authorities.
 *
 * @param pinType is the format of the pins that should be retrieved
 * @result NSDictionary mapping from domain names to a list of pins in base64 format for that domain
*/
class func getPins(_ pinType: String) -> [String : [String]]?

Pins JSON Extraction


/**
 * Fetches the pins from the current configuration of the SDK. This is returned as
 * JSON which defines a map from URL domain (hostname only) to the possible pins for
 * that domain. If there is no map entry for a domain then that indicates that the connection
 * is not specifically pinned. The type of pin requested determines the data in each of the
 * pins. This is typically the base64 encoding of a hash of some aspect of the certificate.
 * A connection is considered to be valid if any certificate in the presented chain has a hash
 * that matches one in the array of hashes for the domain. Note that if the isForceApplyPins flag
 * was set on the last Approov token fetch then this clears the flag for future fetches as it
 * indicates that the latest pin information has been read. The special domain "*" may be present
 * to provide the full list of trusted root pins for all accepted certificate authorities.
 *
 * @param pinType is the type of pinning information that is required
 * @return JSON representation of the pins
 */
public static final String getPinsJSON(String pinType);

/**
 * Provide access to the API pin information held in the current app configuration. This method provides the information in
 * marsahlled JSON form. Pins of a particular type are retrieved. If the SDK has not be initialized then nil is returned. Note that
 * if the isForceApplyPins flag was set on the last Approov token fetch then this clears the flag for future fetches as it indicates
 * that the latest pin information has been read. Pins may be returned with "*" as the domain as these represent the trusted
 * root pins of all the acceptable certificate authorities.
 *
 * @param pinType is the format of the pins that should be retrieved
 * @result String representation of the JSON representing the pins, or nil if the SDK has not been initialized
 */
+ (nullable NSString *)getPinsJSON:(nonnull NSString *)pinType;

/**
 * Provide access to the API pin information held in the current app configuration. This method provides the information in
 * marsahlled JSON form.Pins of a particular type are retrieved. If the SDK has not be initialized then nil is returned. Note that
 * if the isForceApplyPins flag was set on the last Approov token fetch then this clears the flag for future fetches as it indicates
 * that the latest pin information has been read. Pins may be returned with "*" as the domain as these represent the trusted
 * root pins of all the acceptable certificate authorities.
 *
 * @param pinType is the format of the pins that should be retrieved
 * @result String representation of the JSON representing the pins, or nil if the SDK has not been initialized
 */
class func getPinsJSON(_ pinType: String) -> String?

Fetch Result


/**
 * Potential status results from an attempt to fetch from the SDK
 */
public enum TokenFetchStatus {
        SUCCESS,                // fetch was completed successfully
        NO_NETWORK,             // fetch failed because there is no network connectivity currently
        MITM_DETECTED,          // fetch failed because there is a Man-In-The-Middle (MITM) to the Approov cloud service
        POOR_NETWORK,           // fetch failed due to poor network connectivity
        NO_APPROOV_SERVICE,     // fetch failed, perhaps because Approov services are down
        BAD_URL,                // provided URL was not https or otherwise in the correct format
        UNKNOWN_URL,            // provided URL is not one that one configured for Approov
        UNPROTECTED_URL,        // provided URL does not need an Approov token
        NO_NETWORK_PERMISSION,  // app does not have ACCESS_NETWORK_STATE or INTERNET permission
        MISSING_LIB_DEPENDENCY, // app is missing a needed library dependency
        INTERNAL_ERROR,         // there has been an internal error in the SDK
        REJECTED,               // indicates a custom JWT or secure string fetch has been rejected because Approov attestation fails
        DISABLED,               // indicates that a custom JWT or secure string fetch fails because the feature is not enabled
        UNKNOWN_KEY             // indicates an attempt to fetch a secure string that has not been defined
    }

/**
 * Results from a fetch operation, including custom JWT and secure string fetches, as
 * well as standard Approov token fetches.
 */
public class TokenFetchResult {
  /**
   * Gets the status of the last fetch operation.
   *
   * @return Approov fetch status
   */
  public TokenFetchStatus getStatus();

  /**
   * Gets the token string of the last Approov token fetch. This may be an empty string
   * if the fetch did not succeed. This value should not not be cached by the app client
   * code.
   *
   * @return Approov token string
   */
  public String getToken();

  /**
   * Gets the secure string of the last secure string fetch. This may be null if the
   * string is not available. This value should not not be cached by the app client
   * code.
   *
   * @return any secure string or null if not available
   */
  public String getSecureString();

  /**
   * Gets any Attestation Response Code (ARC) associated with the last fetch. This may be an empty string
   * if the fetch did not succeed or if ARC is not enabled.
   *
   * @return ARC string
   */
  public String getARC();

  /**
   * Gets any rejection reasons describing why Approov attestation has failed. This is
   * a comma separated list of device properties, or an empty string for a pass or if the
   * feature is not enabled.
   *
   * @return comma separated rejection reasons, or empty string otherwise
   */
  public String getRejectionReasons();

  /**
   * Determines if a new configuration is available from fetchConfig().
   *
   * @return true if an updated configuration is available
   */
  public boolean isConfigChanged();

  /**
   * Determines if current user APIs must be updated to reflect a new version
   * available from getPins() or getPinsJSON(). Calling getPins() or getPinsJSON() will
   * clear this flag for the next Approov fetch.
   *
   * @return true if pins should be updated
   */
  public boolean isForceApplyPins();

  /**
   * Gets a measurement configuration if the last token fetch was to perform an
   * integrity measurement and was successful.
   *
   * @return Approov measurement configuration or null if no measurement made
   */
  public byte[] getMeasurementConfig();

  /**
   * Gets a loggable version of the result Approov token. This provides the decoded JSON payload
   * along with the first six characters of the base64 encoded signature as an additional
   * "sip" claim. This can be safely logged as it cannot be transformed into a valid token
   * since the full signature is not provided, but it can be subsequently checked for validity
   * if the shared secret is known, with a very high probability. The loggable token is always
   * valid JSON. If there is an error then the type is given with the key "error". Note that
   * this is not applicable to JWE tokens.
   *
   * @return Loggable Approov token string
   */
  public String getLoggableToken();
}

/*
 * Enumeration of results that may be generated as a result of an Approov fetch operation.
 */
typedef NS_ENUM(NSUInteger, ApproovTokenFetchStatus)
{
    // Indicates that a fetch successfully completed.
    ApproovTokenFetchStatusSuccess,

    // Indicates that the fetch failed because there is currently no network connectivity.
    ApproovTokenFetchStatusNoNetwork,

    // Indicates that the fetch failed because the certificate presented on the Approov endpoint was not one
    // that was expected. This might indicate the device is running on a network with a firewall that terminates TLS.
    ApproovTokenFetchStatusMITMDetected,

    // Indicates that the fetch failed due to poor network connectivity.
    ApproovTokenFetchStatusPoorNetwork,

     // Indicates that the fetch failed, perhaps because Approov services (primary and failover) are down.
    ApproovTokenFetchStatusNoApproovService,

    // Indicates that the provided domain is not in the correct format or is not of the https scheme.
    ApproovTokenFetchStatusBadURL,

    // Indicates that the URL provided is a for a domain that has not be specified in the Approov administration portal.
    // This may be an incorrect URL or may indicate that no Approov token is required for protecting this endpoint.
    ApproovTokenFetchStatusUnknownURL,

    // Indicates that no Approov token is needed for the domain. This is returned as a result of a configuration
    // that is set server side, indicating that the domain is pinned but no Approov token is required.
    ApproovTokenFetchStatusUnprotectedURL,

    // Indicates that the Approov SDK has not been initialized.
    ApproovTokenFetchStatusNotInitialized,

    // Indicates a custom JWT or secure string fetch has been rejected because Approov attestation fails.
    ApproovTokenFetchStatusRejected,

    // Indicates that a custom JWT or secure string fetch fails because the feature is not enabled.
    ApproovTokenFetchStatusDisabled,

    // Indicates an attempt to fetch a secure string that has not been defined.
    ApproovTokenFetchStatusUnknownKey,

    // Indicates an attempt to fetch a secure string with a bad key.
    ApproovTokenFetchStatusBadKey,

    // Indicates an attempt to fetch a custom JWT with a bad payload.
    ApproovTokenFetchStatusBadPayload,

    // Indicates an internal SDK error.
    ApproovTokenFetchStatusInternalError
};

/**
 * This interface is used for the result value of the fetch methods.
 */
__attribute__((visibility("default"))) @interface ApproovTokenFetchResult: NSObject

// The result code generated by a fetch operation
@property (readonly) ApproovTokenFetchStatus status;

// The last fetched token as a string which should not be cached by the app client code. This is the empty string if
// no token could be obtained.
@property (readonly, nonnull) NSString *token;

// The secure string of the last secure string fetch. This value should not be cached by the app client code. This
// may be nil if the string is not available.
@property (readonly, nullable) NSString *secureString;

// Any Attestation Response Code (ARC) providing details of the device properties. This is the empty string if
// no ARC was obtained.
@property (readonly, nonnull) NSString *ARC;

// Any rejection reasons describing why Approov attestation has failed. This is a comma separated list of
// device properties, or an empty string for a pass or if the feature is not enabled.
@property (readonly, nonnull) NSString *rejectionReasons;

// Provides a flag indicating if a new updated configuration JWT has been obtained from the server. Once set this
// remains true for subsequent token fetches until a fetchConfig() call is made.
@property (readonly) BOOL isConfigChanged;

// Provides a flag indicating if current user APIs must be updated to reflect a new version
// available from getPins(). Calling getPins() will clear this flag for the
// next Approov token fetch.
@property (readonly) BOOL isForceApplyPins;

// The measurement configuration is only provided if the last fetch operation for the domain initiated an integrity
// measurement, and the fetch was successful. This provides a binary buffer that must be provided to the
// getIntegrityMeasurementProof() or getDeviceMeasurementProof() to obtain a new proof.
@property (readonly, nullable) NSData *measurementConfig;

// ApproovTokenFetchCallback type definition
typedef void (^ApproovTokenFetchCallback)(ApproovTokenFetchResult *_Nonnull result);

/**
 * Gets a loggable version of the result Approov token. This provides the decoded JSON payload along with the first six
 * characters of the base64 encoded signature as an additional "sip" claim. This can be safely logged as it cannot be
 * transformed into a valid token since the full signature is not provided, but it can be subsequently checked for
 * validity if the shared secret is known, with a very high probability. The loggable token is always valid JSON. If
 * there is an error then the type is given with the key "error".
 * Note that this is not applicable to JWE tokens.
 *
 * @return Loggable Approov token string
 */
- (nonnull NSString *)loggableToken;

@end

/**
 * Gets a human readable string from an Approov token fetch status.
 *
 * @param approovTokenFetchStatus is the ApproovTokenFetchStatus
 * @return the string for the ApproovTokenFetchStatusq
 */
+ (nonnull NSString *)stringFromApproovTokenFetchStatus:(ApproovTokenFetchStatus)approovTokenFetchStatus;

/*
 * // Enumeration of results that may be generated as a result of an Approov fetch operation.
 */  
enum ApproovTokenFetchStatus {
    // Indicates that a fetch successfully completed.
    case success  

    // Indicates that the fetch failed because there is currently no network connectivity.
    case noNetwork

    // Indicates that the fetch failed because the certificate presented on the Approov endpoint was not one
    // that was expected. This might indicate the device is running on a network with a firewall that terminates TLS.
    case mitmDetected

    // Indicates that no token could be obtained due to poor network connectivity
    case poorNetwork

    // Indicates that the fetch failed, perhaps because Approov services (primary and failover) are down.
    case noApproovService

    // Indicates that the provided domain is not in the correct format or is not of the https scheme.
    case badURL

    // Indicates that the URL provided is a for a domain that has not be specified in the Approov administration portal.
    // This may be an incorrect URL or may indicate that no Approov token is required for protecting this endpoint.
    case unknownURL

    // Indicates that no Approov token is needed for the domain. This is returned as a result of a configuration
    // that is set server side, indicating that the domain is pinned but no Approov token is required.
    case unprotectedURL

    // Indicates that the Approov SDK has not been initialized.
    case notInitialized

    // Indicates a custom JWT or secure string fetch has been rejected because Approov attestation fails.
    case rejected

    // Indicates that a custom JWT or secure string fetch fails because the feature is not enabled.
    case disabled

    // Indicates an attempt to fetch a secure string that has not been defined.
    case unknownKey

    // Indicates an attempt to fetch a secure string with a bad key.
    case badKey

    // Indicates an attempt to fetch a custom JWT with a bad payload.
    case badPayload

    // Indicates an internal SDK error.
    case InternalError
}

/**
 * This interface is used for the result value of the fetch methods.
 */
class ApproovTokenFetchResult: NSObject {

    // The result code generated by a fetch operation
    private(set) var status: ApproovTokenFetchStatus?

    // The last fetched token as a string which should not be cached by the app client code. This is the empty string if
    // no token could be obtained.
    private(set) var token = ""

    // The secure string of the last secure string fetch.  This value should not be cached by the app client code. This may
    // be nil if the string is not available.
    private(set) var secureString: String?

    // Any Attestation Response Code (ARC) provided details of the device properties. This is the empty string if
    // no ARC was obtained.
    private(set) var arc = ""

    // Any rejection reasons describing why Approov attestation has failed. This is a comma separated list of
    // device properties, or an empty string for a pass or if the feature is not enabled.
    private(set) var rejectionReasons = ""

    // Provides a flag indicating if a new updated configuration JWT has been obtained from the server. Once set this
    // remains true for subsequent token fetches until a fetchConfig() call is made.
    private(set) var isConfigChanged = false

    // Provides a flag indicating if current user APIs must be updated to reflect a new version
    // available from getPins() or getPinsJSON(). Calling getPins() or getPinsJSON() will clear this flag for the
    // next Approov token fetch.
    private(set) var isForceApplyPins = false

    // The measurement configuration is only provided if the last fetch operation for the domain initiated an integrity
    // measurement, and was successful. This provides a binary buffer that must be provided to the
    // getIntegrityMeasurementProof() or getDeviceMeasurementProof() to obtain a new proof.
    private(set) var measurementConfig: Data?

    /**
     * Gets a loggable version of the result Approov token. This provides the decoded JSON payload along with the first six
     * characters of the base64 encoded signature as an additional "sip" claim. This can be safely logged as it cannot be
     * transformed into a valid token since the full signature is not provided, but it can be subsequently checked for
     * validity if the shared secret is known, with a very high probability. The loggable token is always valid JSON. If
     * there is an error then the type is given with the key "error".
     * Note that this is not applicable to JWE tokens.
     *
     * @return Loggable Approov token string
     */
    func loggableToken() -> String
}

/**
 * Gets a human readable string from an Approov token fetch status.
 *
 * @param approovTokenFetchStatus is the ApproovTokenFetchStatus
 * @return the string for the ApproovTokenFetchStatus
 */
class func string(from approovTokenFetchStatus: ApproovTokenFetchStatus) -> String

Synchronous Token Fetch


/**
 * Initiates a synchronous request to obtain an Approov token and other results. If an Approov token fetch
 * has been completed previously and the tokens are unexpired then this may return the same one
 * without a need to perform a network transaction. Note though that the caller should never cache the
 * Approov token as it may become invalidated at any point.
 *
 * If a new Approov token is required then a more extensive app measurement is performed that involves
 * communicating with the Approov cloud service. Thus this method may take up to several seconds to
 * return and should not be called from a UI thread. There is also a chance that due to poor network
 * connectivity or other factors an Approov token cannot be obtained, and this is reflected in the
 * returned status.
 *
 * All calls must provide a URL which provides the high level domain of the API to which the Approov
 * token is going to be sent. Different API domains will have different Approov tokens associated with
 * them so it is important that the Approov token is only sent to requests for that domain. If the
 * domain has not been configured in the admin portal then an error is obtained.
 *
 * @param url provides the top level domain URL for which a token is being fetched
 * @return results of fetching a token
 */
public static final TokenFetchResult fetchApproovTokenAndWait(String url);

/**
 * Initiates a synchronous request to obtain an Approov token and other results. If an Approov token fetch
 * has been completed previously and the tokens are unexpired then this may return the same one
 * without a need to perform a network transaction. Note though that the caller should never cache the
 * Approov token as it may become invalidated at any point.
 *
 * If a new Approov token is required then a more extensive app measurement is performed that involves
 * communicating with the Approov cloud service. Thus this method may take up to several seconds to
 * return and should not be called from a UI thread. There is also a chance that due to poor network
 * connectivity or other factors an Approov token cannot be obtained, and this is reflected in the
 * returned status.
 *
 * All calls must provide a URL which provides the high level domain of the API to which the Approov
 * token is going to be sent. Different API domains will have different Approov tokens associated with
 * them so it is important that the Approov token is only sent to requests for that domain. If the
 * domain has not been configured in the admin portal then an error is obtained. Note that the provided
 * URL may be suffixed by "?measurement" to initiate a measurement process for use with integrity and
 * device proofs.
 *
 * @param url provides the top level domain URL for which a token is being fetched
 * @return results of fetching a token
 */
+ (nonnull ApproovTokenFetchResult *)fetchApproovTokenAndWait:(nonnull NSString *)url;

/**
 * Initiates a synchronous request to obtain an Approov token and other results. If an Approov token fetch
 * has been completed previously and the tokens are unexpired then this may return the same one
 * without a need to perform a network transaction. Note though that the caller should never cache the
 * Approov token as it may become invalidated at any point.
 *
 * If a new Approov token is required then a more extensive app measurement is performed that involves
 * communicating with the Approov cloud service. Thus this method may take up to several seconds to
 * return and should not be called from a UI thread. There is also a chance that due to poor network
 * connectivity or other factors an Approov token cannot be obtained, and this is reflected in the
 * returned status.
 *
 * All calls must provide a URL which provides the high level domain of the API to which the Approov
 * token is going to be sent. Different API domains will have different Approov tokens associated with
 * them so it is important that the Approov token is only sent to requests for that domain. If the
 * domain has not been configured in the admin portal then an error is obtained. Note that the provided
 * URL may be suffixed by "?measurement" to initiate a measurement process for use with integrity and
 * device proofs.
 *
 * @param url provides the top level domain URL for which a token is being fetched
 * @return results of fetching a token
 */
class func fetchTokenAndWait(_ url: String) -> ApproovTokenFetchResult

Asynchronous Token Fetch


/**
 * Interface that must be implemented to receive token fetch callbacks for the
 * fetchApproovToken() method.
 */
public interface TokenFetchCallback {
   /**
    * Callback function to be implemented in the business logic.
    *
    * @param result is the TokenFetchResult
    */
   void approovCallback(TokenFetchResult result);
}

/**
 * Initiates an asynchronous request to obtain an Approov token and other results. The call returns
 * immediately and when the token is fetched (or if the request times out due to an error) then
 * a callback method is called on the supplied interface instance object. This callback is made
 * on a different thread. If an Approov token fetch has been completed previously and the tokens
 * are unexpired then this callback may be very rapid without a need to perform a network transaction.
 * Note though that the caller should never cache the Approov token as it may become invalidated at
 * any point.
 *
 * If a new Approov token is required then a more extensive app measurement is performed that involves
 * communicating with the Approov cloud service.There is a chance that due to poor network
 * connectivity or other factors an Approov token cannot be obtained, and this is reflected in the
 * returned status.
 *
 * All calls must provide a url which provides the high level domain of the API to which the Approov
 * token is going to be sent. Different API domains will have different Approov tokens associated with
 * them so it is important that the Approov token is only sent to requests for that domain. If the
 * domain has not been configured in the admin portal then an error is obtained.
 *
 * @param callback is an instance that implements TokenFetchCallback whose callback
 * @param url provides the top level domain url for which a token is being fetched
 */
public static final void fetchApproovToken(TokenFetchCallback callback, String url);

typedef void (^ApproovTokenFetchCallback)(ApproovTokenFetchResult *_Nonnull result);

/**
 * Initiates an asynchronous request to obtain an Approov token and other results. The call returns
 * immediately and when the token is fetched (or if the request times out due to an error) then
 * a callback function is called with the result. This callback is made on a different thread. If
 * an Approov token fetch has been completed previously and the token are unexpired then this callback
 * may be very rapid without a need to perform a network transaction. Note though that the caller
 * should never cache the Approov token as it may become invalidated at any point.
 *
 * If a new Approov token is required then a more extensive app measurement is performed that involves
 * communicating with the Approov cloud service. There is a chance that due to poor network
 * connectivity or other factors an Approov token cannot be obtained, and this is reflected in the
 * returned status.
 *
 * All calls must provide a url which provides the high level domain of the API to which the Approov
 * token is going to be sent. Different API domains will have different Approov tokens associated with
 * them so it is important that the Approov token is only sent to requests for that domain. If the
 * domain has not been configured in the admin portal then an error is obtained. Note that the provided
 * URL may be suffixed by "?measurement" to initiate a measurement process for use with integrity and
 * device proofs.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param url provides the top level domain url for which a token is being fetched
 */
+ (void)fetchApproovToken:(nonnull ApproovTokenFetchCallback)callbackHandler :(nonnull NSString *)url;

typealias ApproovTokenFetchCallback = (ApproovTokenFetchResult) -> Void

/**
 * Initiates an asynchronous request to obtain an Approov token and other results. The call returns
 * immediately and when the token is fetched (or if the request times out due to an error) then
 * a callback function is called with the result. This callback is made on a different thread. If
 * an Approov token fetch has been completed previously and the token are unexpired then this callback
 * may be very rapid without a need to perform a network transaction. Note though that the caller
 * should never cache the Approov token as it may become invalidated at any point.
 *
 * If a new Approov token is required then a more extensive app measurement is performed that involves
 * communicating with the Approov cloud service. There is a chance that due to poor network
 * connectivity or other factors an Approov token cannot be obtained, and this is reflected in the
 * returned status.
 *
 * All calls must provide a url which provides the high level domain of the API to which the Approov
 * token is going to be sent. Different API domains will have different Approov tokens associated with
 * them so it is important that the Approov token is only sent to requests for that domain. If the
 * domain has not been configured in the admin portal then an error is obtained. Note that the provided
 * URL may be suffixed by "?measurement" to initiate a measurement process for use with integrity and
 * device proofs.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param url provides the top level domain url for which a token is being fetched
 */
class func fetchToken(_ callbackHandler: ApproovTokenFetchCallback, _ url: String)

Synchronous Secure String Fetch


/**
 * Initiates a synchronous request to obtain, or update, a string held securely by the SDK. The
 * call only returns when the secure string has been obtained (or if the request times out due to
 * an error, or if the request is rejected). This provides the secure string value. It is also
 * possible to update the secure string value by providing a new definition. In this case the
 * new value is returned as the secure string. Use of an empty string for newDef removes the
 * string entry. Note that after an initial fetch secure strings will be available for some period
 * until they expire, with no latency for a network operation. If the string is not defined then
 * the UNKNOWN_KEY error status is returned. If the attestation process fails then the error
 * status REJECTED is returned. Finally, if the feature is not enabled then the DISABLED status
 * is returned.
 *
 * @param key is the name of the key (max 64 characters) that the secure string is held against
 * @param newDef is any new definition for the secure string, or null otherwise
 * @return results of fetching the secure string
 * @throws IllegalArgumentException if the key is empty or too long
 */
public static final TokenFetchResult fetchSecureStringAndWait(String key, String newDef);

/**
 * Initiates a synchronous request to obtain, or update, a string held securely by the SDK. The
 * call only returns when the secure string has been obtained (or if the request times out due to
 * an error, or if the request is rejected). This provides the secure string value. It is also
 * possible to update the secure string value by providing a new definition. In this case the
 * new value is returned as the secure string. Use of an empty string for newDef removes the
 * string entry. Note that after an initial fetch secure strings will be available for some period
 * until they expire, with no latency for a network operation. If the string is not defined then
 * the UnknownKey error status is returned. If the attestation process fails then the error status
 * Rejected is returned. If the provided key is invalid then a BadKey status is returned. Finally,
 * if the feature is not enabled then the Disabled status is returned.
 *
 * @param key is the name of the key (max 64 characters) that the secure string is held against
 * @param newDef is any new definition for the secure string, or nil otherwise
 * @return results of fetching the secure string
 */
+ (nonnull ApproovTokenFetchResult *)fetchSecureStringAndWait:(nonnull NSString *)key :(nullable NSString *)newDef;

/**
 * Initiates a synchronous request to obtain, or update, a string held securely by the SDK. The
 * call only returns when the secure string has been obtained (or if the request times out due to
 * an error, or if the request is rejected). This provides the secure string value. It is also
 * possible to update the secure string value by providing a new definition. In this case the
 * new value is returned as the secure string. Use of an empty string for newDef removes the
 * string entry. Note that after an initial fetch secure strings will be available for some period
 * until they expire, with no latency for a network operation. If the string is not defined then
 * the UnknownKey error status is returned. If the attestation process fails then the error status
 * Rejected is returned. If the provided key is invalid then a BadKey status is returned. Finally,
 * if the feature is not enabled then the Disabled status is returned.
 *
 * @param key is the name of the key (max 64 characters) that the secure string is held against
 * @param newDef is any new definition for the secure string, or nil otherwise
 * @return results of fetching the secure string
 */
class func fetchSecureStringAndWait(_ key: String, _ newDef: string) -> ApproovTokenFetchResult

Asynchronous Secure String Fetch


/**
 * Interface that must be implemented to receive token fetch callbacks for the
 * fetchSecureString() method.
 */
public interface TokenFetchCallback {
   /**
    * Callback function to be implemented in the business logic.
    *
    * @param result is the TokenFetchResult
    */
   void approovCallback(TokenFetchResult result);
}

/**
 * Initiates an asynchronous request to obtain, or update, a string held securely by the SDK. The call
 * returns immediately. After the secure string is fetched (or if the request times out due to an
 * error, or if the request is rejected), then a callback method is called on the supplied interface
 * instance object. This callback is made on a different thread. This provides the secure string value.
 * It is also possible to update the secure string value by providing a new definition. In this case the
 * new value is returned as the secure string. Use of an empty string for newDef removes the string entry.
 * Note that after an initial fetch secure strings will be available for some period until they expire, with
 * no latency for a network operation. If the string is not defined then the UNKNOWN_KEY error status is
 * returned. If the attestation process fails then the error status REJECTED is returned. Finally, if the
 * feature is not enabled then the DISABLED status is returned.
 *
 * @param callback is an instance that implements TokenFetchCallback
 * @param key is the name of the key (max 64 characters) that the secure string is held against
 * @param newDef is any new definition for the secure string, or null otherwise
 * @throws IllegalArgumentException if the key is empty or too long
 */
public static final void fetchSecureString(TokenFetchCallback callback, String key, String newDef);

typedef void (^ApproovTokenFetchCallback)(ApproovTokenFetchResult *_Nonnull result);

/**
 * Initiates an asynchronous request to obtain, or update, a string held securely by the SDK. The call
 * returns immediately. After the secure string is fetched (or if the request times out due to an
 * error, or if the request is rejected), then a callback method is called on the supplied interface
 * instance object. This callback is made on a different thread. This provides the secure string value.
 * It is also possible to update the secure string value by providing a new definition. In this case the
 * new value is returned as the secure string. Use of an empty string for newDef removes the string entry.
 * Note that after an initial fetch secure strings will be available for some period until they expire, with
 * no latency for a network operation. If the string is not defined then the UnknownKey error status
 * is returned. If the attestation process fails then the error status Rejected is returned. If the provided
 * key is invalid then a BadKey status is returned. Finally, if the feature is not enabled then the Disabled
 * status is returned.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param key is the name of the key (max 64 characters) that the secure string is held against
 * @param newDef is any new definition for the secure string, or nil otherwise
 */
+ (void)fetchSecureString:(nonnull ApproovTokenFetchCallback)callbackHandler :(nonnull NSString *)key :(nullable NSString *)newDef;

typealias ApproovTokenFetchCallback = (ApproovTokenFetchResult) -> Void

/**
 * Initiates an asynchronous request to obtain, or update, a string held securely by the SDK. The call
 * returns immediately. After the secure string is fetched (or if the request times out due to an
 * error, or if the request is rejected), then a callback method is called on the supplied interface
 * instance object. This callback is made on a different thread. This provides the secure string value.
 * It is also possible to update the secure string value by providing a new definition. In this case the
 * new value is returned as the secure string. Use of an empty string for newDef removes the string entry.
 * Note that after an initial fetch secure strings will be available for some period until they expire, with
 * no latency for a network operation. If the string is not defined then the UnknownKey error status
 * is returned. If the attestation process fails then the error status Rejected is returned. If the provided
 * key is invalid then a BadKey status is returned. Finally, if the feature is not enabled then the Disabled
 * status is returned.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param key is the name of the key (max 64 characters) that the secure string is held against
 * @param newDef is any new definition for the secure string, or nil otherwise
 */
class func fetchSecureString(_ callbackHandler: ApproovTokenFetchCallback, _ key: String, _ newDef: String)

Synchronous Custom JWT Fetch


/**
 * Initiates a synchronous request to obtain a custom JWT with the given payload. The call only
 * returns when the custom JWT has been obtained (or if the request times out due to an error, or
 * if the request is rejected). The payload must be valid JSON. If the attestation process fails
 * then the error status REJECTED is returned. Finally, if the feature is not enabled then the
 * DISABLED status is returned.
 *
 * @param payload provide marshaled JSON to be included in the custom JWT to be fetched
 * @return results of fetching a token
 * @throws IllegalArgumentException if the payload is not valid JSON
 */
public static final TokenFetchResult fetchCustomJWTAndWait(String payload);

/**
 * Initiates a synchronous request to obtain a custom JWT with the given payload. The call only
 * returns when the custom JWT has been obtained (or if the request times out due to an error, or
 * if the request is rejected). The payload must be valid JSON or else a BadPayload error status
 * is returned. If the attestation process fails then the error status Rejected is returned.
 * Finally, if the feature is not enabled then the Disabled status is returned.
 *
 * @param payload provide marshaled JSON to be included in the custom JWT to be fetched
 * @return results of fetching a token
 */
+ (nonnull ApproovTokenFetchResult *)fetchCustomJWTAndWait:(nonnull NSString *)payload;

/**
 * Initiates a synchronous request to obtain a custom JWT with the given payload. The call only
 * returns when the custom JWT has been obtained (or if the request times out due to an error, or
 * if the request is rejected). The payload must be valid JSON or else a BadPayload error status
 * is returned. If the attestation process fails then the error status Rejected is returned. Finally,
 * if the feature is not enabled then the Disabled status is returned.
 *
 * @param payload provide marshaled JSON to be included in the custom JWT to be fetched
 * @return results of fetching a token
 */
class func fetchCustomJWTAndWait(_ payload: String) -> ApproovTokenFetchResult

Asynchronous Custom JWT Fetch


/**
 * Interface that must be implemented to receive token fetch callbacks for the
 * fetchCustomJWT() method.
 */
public interface TokenFetchCallback {
   /**
    * Callback function to be implemented in the business logic.
    *
    * @param result is the TokenFetchResult
    */
   void approovCallback(TokenFetchResult result);
}

/**
 * Initiates an asynchronous request to obtain a custom JWT with the given payload. The call returns
 * immediately. After the token is fetched (or if the request times out due to an error, or if the
 * request is rejected) then a callback method is called on the supplied interface instance object.
 * This callback is made on a different thread. The payload must be valid JSON. If the attestation
 * process fails then the error status REJECTED is returned. Finally, if the feature is not enabled
 * then the DISABLED status is returned.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param payload provide marshaled JSON to be included in the custom JWT to be fetched
 * @throws IllegalArgumentException if the payload is not valid JSON
 */
public static final void fetchCustomJWT(TokenFetchCallback callback, String payload);

typedef void (^ApproovTokenFetchCallback)(ApproovTokenFetchResult *_Nonnull result);

/**
 * Initiates an asynchronous request to obtain a custom JWT with the given payload. The call returns
 * immediately. After the token is fetched (or if the request times out due to an error, or if the
 * request is rejected) then a callback method is called on the supplied interface instance object.
 * This callback is made on a different thread. The payload must be valid JSON or else a BadPayload
 * error status is returned. If the attestation process fails then the error status Rejected is
 * returned. Finally, if the feature is not enabled then the Disabled status is returned.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param payload provide marshaled JSON to be included in the custom JWT to be fetched
 */
+ (void)fetchCustomJWT:(nonnull ApproovTokenFetchCallback)callbackHandler :(nonnull NSString *)payload;

typealias ApproovTokenFetchCallback = (ApproovTokenFetchResult) -> Void

/**
 * Initiates an asynchronous request to obtain a custom JWT with the given payload. The call returns
 * immediately. After the token is fetched (or if the request times out due to an error, or if the
 * request is rejected) then a callback method is called on the supplied interface instance object.
 * This callback is made on a different thread. The payload must be valid JSON or else a BadPayload
 * error status is returned. If the attestation process fails then the error status Rejected is
 * returned. Finally, if the feature is not enabled then the Disabled status is returned.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param payload provide marshaled JSON to be included in the custom JWT to be fetched
 */
class func fetchCustomJWT(_ callbackHandler: ApproovTokenFetchCallback, _ payload: String)

Development Key


/**
 * Sets a development key on the SDK. This may provide a key indicating that
 * the app is a development version and it should pass attestation even
 * if the app is not registered or it is running on an emulator. The development
 * key value can be rotated at any point in the account if a version of the app
 * containing the development key is accidentally released. This is primarily
 * used for situations where the app package must be modified or resigned in
 * some way as part of the testing process.
 *
 * @param key is the development key value to be set, which may be null
 */
public static final void setDevKey(String key);

/**
 * Sets a development key on the SDK. This may be used to force a build of the app
 * to always pass attestation, even if the app itself is not registered or it is
 * being run on a simulator. This is to allow testing when the app might have to
 * be resigned to run on a particular test environment and the signing certificate
 * is either not known or not easily accessible. The development key is defined at
 * the account level and can be changed at any point.
 *
 * @param key is the development key to be set, which may be nil
 */
+ (void)setDevKey:(nullable NSString *)key;

/**
 * Sets a development key on the SDK. This may be used to force a build of the app
 * to always pass attestation, even if the app itself is not registered or it is
 * being run on a simulator. This is to allow testing when the app might have to
 * be resigned to run on a particular test environment and the signing certificate
 * is either not known or not easily accessible. The development key is defined at
 * the account level and can be changed at any point.
 *
 * @param key is the development key to be set, which may be nil
 */
class func setDevKey(_ key: String)

Token Binding


/**
 * Sets a hash of the given data value in the 'pay' claim of any subsequent Approov token
 * fetch. If the data value is also transmitted to your API backend, along with the Approov
 * token, then this allows the backend to check that the data value was indeed known to the
 * app at the time of the token fetch and hasn't been spoofed. If the provided data is the
 * same as the one from the previous call to this method, then the current tokens held by
 * the SDK do not need to be updated. Otherwise the next token fetch call will cause a new
 * attestation to fetch a new token. Note that this should not be done frequently due to
 * the additional latency on token fetching that will be caused. The hash appears in the
 * 'pay' claim of the Approov token as a base64 encoded string of the SHA256 hash of the
 * data. Note that the data is hashed locally and never sent to the Approov cloud service.
 *
 * @param data is the data whose SHA256 hash is to be included in future Approov tokens
 */
public static final void setDataHashInToken(String data);

/**
 * Sets a hash of the given data value in the 'pay' claim of any subsequent Approov token
 * fetch. If the data value is also transmitted to your API backend, along with the Approov
 * token, then this allows the backend to check that the data value was indeed known to the
 * app at the time of the token fetch and hasn't been spoofed. If the provided data is the
 * same as the one from the previous call to this method, then the current tokens held by
 * the SDK do not need to be updated. Otherwise the next token fetch call will cause a new
 * attestation to fetch a new token. Note that this should not be done frequently due to
 * the additional latency on token fetching that will be caused. The hash appears in the
 * 'pay' claim of the Approov token as a base64 encoded string of the SHA256 hash of the
 * data. Note that the data is hashed locally and never sent to the Approov cloud service.
 *
 * @param data is the data whose SHA256 hash is to be included in future Approov tokens
 */
+ (void)setDataHashInToken:(nonnull NSString *)data;

/**
 * Sets a hash of the given data value in the 'pay' claim of any subsequent Approov token
 * fetch. If the data value is also transmitted to your API backend, along with the Approov
 * token, then this allows the backend to check that the data value was indeed known to the
 * app at the time of the token fetch and hasn't been spoofed. If the provided data is the
 * same as the one from the previous call to this method, then the current tokens held by
 * the SDK do not need to be updated. Otherwise the next token fetch call will cause a new
 * attestation to fetch a new token. Note that this should not be done frequently due to
 * the additional latency on token fetching that will be caused. The hash appears in the
 * 'pay' claim of the Approov token as a base64 encoded string of the SHA256 hash of the
 * data. Note that the data is hashed locally and never sent to the Approov cloud service.
 *
 * @param data is the data whose SHA256 hash is to be included in future Approov tokens
 */
class func setDataHashInToken(_ data: String)

Integrity Measurement Proof


/**
 * Obtains an integrity measurement proof that is used to show that the app and its
 * environment have not changed since the time of the original integrity measurement.
 * The proof does an HMAC calculation over the secret integrity measurement value which
 * is salted by a provided nonce. This proves that the SDK is able to reproduce the
 * integrity measurement value.
 *
 * @param nonce is a 16-byte (128-bit) nonce value used to salt the proof HMAC
 * @param measurementConfig is the measurement configuration obtained from a previous token fetch results
 * @return 32-byte (256-bit) measurement proof value
 */
public static final byte[] getIntegrityMeasurementProof(byte[] nonce, byte[] measurementConfig);

/**
 * Obtains an integrity measurement proof that is used to show that the app and its
 * environment have not changed since the time of the original integrity measurement.
 * The proof does an HMAC calculation over the secret integrity measurement value which
 * is salted by a provided nonce. This proves that the SDK is able to reproduce the
 * integrity measurement value. This may return nil if the SDK has not been initialized
 * or if the parameters are in invalid.
 *
 * @param nonce is a 16-byte (128-bit) nonce value used to salt the proof HMAC
 * @param measurementConfig is the measurement configuration obtained from a previous token fetch results
 * @return 32-byte (256-bit) measurement proof value or nil if there was an error
 */
+ (nullable NSData *)getIntegrityMeasurementProof:(nonnull NSData *)nonce :(nonnull NSData *)measurementConfig

/**
 * Obtains an integrity measurement proof that is used to show that the app and its
 * environment have not changed since the time of the original integrity measurement.
 * The proof does an HMAC calculation over the secret integrity measurement value which
 * is salted by a provided nonce. This proves that the SDK is able to reproduce the
 * integrity measurement value. This may return nil if the SDK has not been initialized
 * or if the parameters are in invalid.
 *
 * @param nonce is a 16-byte (128-bit) nonce value used to salt the proof HMAC
 * @param measurementConfig is the measurement configuration obtained from a previous token fetch results
 * @return 32-byte (256-bit) measurement proof value or nil if there was an error
 */
class func getIntegrityMeasurementProof(_ nonce: Data, _ measurementConfig: Data) -> Data?

Device Measurement Proof

   
/**
 * Obtains a device measurement proof that is used to show that the device environment
 * has not changed since the time of the original integrity measurement. This allows the
 * app version, including the Approov SDK, to be updated while preserving the device
 * measurement. The proof does an HMAC calculation over the secret device measurement
 * value which is salted by a provided nonce. This proves that the SDK is able to reproduce
 * the device measurement value.
 *
 * @param nonce is a 16-byte (128-bit) nonce value used to salt the proof HMAC
 * @param measurementConfig is the measurement configuration obtained from a previous token fetch results
 * @return 32-byte (256-bit) measurement proof value
 */
public static final byte[] getDeviceMeasurementProof(byte[] nonce, byte[] measurementConfig);

/**
 * Obtains a device measurement proof that is used to show that the device environment
 * has not changed since the time of the original integrity measurement. This allows the
 * app version, including the Approov SDK, to be updated while preserving the device
 * measurement. The proof does an HMAC calculation over the secret device measurement
 * value which is salted by a provided nonce. This proves that the SDK is able to reproduce
 * the device measurement value. This may return nil if the SDK has not been initialized
 * or if the parameters are in invalid.
 *
 * @param nonce is a 16-byte (128-bit) nonce value used to salt the proof HMAC
 * @param measurementConfig is the measurement configuration obtained from a previous token fetch results
 * @return 32-byte (256-bit) measurement proof value or nil if there was an error
 */
+ (nullable NSData *)getDeviceMeasurementProof:(nonnull NSData *)nonce :(nonnull NSData *)measurementConfig

/**
 * Obtains a device measurement proof that is used to show that the device environment
 * has not changed since the time of the original integrity measurement. This allows the
 * app version, including the Approov SDK, to be updated while preserving the device
 * measurement. The proof does an HMAC calculation over the secret device measurement
 * value which is salted by a provided nonce. This proves that the SDK is able to reproduce
 * the device measurement value. This may return nil if the SDK has not been initialized
 * or if the parameters are in invalid.
 *
 * @param nonce is a 16-byte (128-bit) nonce value used to salt the proof HMAC
 * @param measurementConfig is the measurement configuration obtained from a previous token fetch results
 * @return 32-byte (256-bit) measurement proof value or nil if there was an error
 */
class func getDeviceMeasurementProof(_ nonce: Data, _ measurementConfig: Data) -> Data?

Message Signing


/**
 * Gets the signature for the given message. This uses an account specific message signing key that is
 * transmitted to the SDK after a successful token fetch if the facility is enabled for the account. Note
 * that if the attestation failed then the signing key provided is actually random so that the
 * signature will be incorrect. An Approov token should always be included in the message
 * being signed and sent alongside this signature to prevent replay attacks.
 *
 * @param message is the message whose content is to be signed
 * @return base64 encoded signature of the message, or null if no signing key is available
 */
public static final String getMessageSignature(String message);

/**
 * Gets the signature for the given message. This uses an account specific message signing key that is
 * transmitted to the SDK after a successful token fetch if the facility is enabled for the account. Note
 * that if the attestation failed then the signing key provided is actually random so that the
 * signature will be incorrect. An Approov token should always be included in the message
 * being signed and sent alongside this signature to prevent replay attacks.
 *
 * @param message is the message whose content is to be signed
 * @return base64 encoded signature of the message, or nil if no signing key is available
 */
+ (nullable NSString *)getMessageSignature:(nonnull NSString *)message;

/**
 * Gets the signature for the given message. This uses an account specific message signing key that is
 * transmitted to the SDK after a successful token fetch if the facility is enabled for the account. Note
 * that if the attestation failed then the signing key provided is actually random so that the
 * signature will be incorrect. An Approov token should always be included in the message
 * being signed and sent alongside this signature to prevent replay attacks.
 *
 * @param message is the message whose content is to be signed
 * @return base64 encoded signature of the message, or nil if no signing key is available
 */
class func getMessageSignature(_ message: String) -> String?

Set User Property


/**
 * Sets a user defined property on the SDK. This may provide information about the
 * app state or aspects of the environment it is running in. This has no direct
 * impact on Approov except it is visible as a property on attesting devices and
 * can be analyzed using device filters. Note that properties longer than 128
 * characters are ignored and all non ASCII characters are removed. The special
 * value "$error" may be used to mark an error condition for offline measurement
 * mismatches.
 *
 * @param property to be set, which may be null
 */
public static final void setUserProperty(String property);

/**
 * Sets a user defined property on the SDK. This may provide information about the
 * app state or aspects of the environment it is running in. This has no direct
 * impact on Approov except it is visible as a property on attesting devices and
 * can be analyzed using device filters. Note that properties longer than 128
 * characters are ignored and all non ASCII characters are removed. The special
 * value "$error" may be used to mark an error condition for offline measurement
 * mismatches.
 *
 * @param property to be set, which may be nil
 */
+ (void)setUserProperty:(nullable NSString *)property;

/**
 * Sets a user defined property on the SDK. This may provide information about the
 * app state or aspects of the environment it is running in. This has no direct
 * impact on Approov except it is visible as a property on attesting devices and
 * can be analyzed using device filters. Note that properties longer than 128
 * characters are ignored and all non ASCII characters are removed. The special
 * value "$error" may be used to mark an error condition for offline measurement
 * mismatches.
 *
 * @param property to be set, which may be nil
 */
class func setUserProperty(_ property: String?)

Set Activity

   
/**
 * Sets the information about a current activity. This may be set for an expected app
 * launch activity so that analysis can be performed to determine if the activity may have
 * been launched in an automatic way. A flag indicating this can then be included as an
 * annotation in the Approov token.
 *
 * @param activity is the current activity that is being run
 */
public static final void setActivity(Activity activity);

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. The methods used here are not supported in our frontend integration quickstarts so if you wish to use these features then you must call the SDK directly.

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 remote 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:

  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. The domain must 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:

  1. Methods (getIntegrityMeasurementProof and 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 remote 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.

Encrypted JWEs must 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");

let approovResult = Approov.fetchTokenAndWait("api.myservice.io?measurement");

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:


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
}

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:


byte[] integrityProof = Approov.getIntegrityMeasurementProof(proofNonce, measurementConfig);
if (integrityProof == null) {
    // error handling
}

let integrityProof = Approov.getIntegrityMeasurementProof(proofNonce, measurementConfig!)
if integrityProof == nil {
    // error handling
}

This generates a 32 byte result in the integrityProof variable. 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. The measurement may also change if the operating system version of the device on which the app is installed is upgraded.

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 (or device’s OS) that is being attested.

We recommend you include a mechanism to report unexpected changes in measurements to Approov, where these can be investigated and the exact cause of the difference determined. To do this we suggest your app remembers the measurement for a given proof nonce at the time of the initial measurement. If this changes unexpectedly then call the SDK method setUserProperty with the string parameter $error. Then perform an additional (non-measurement) Approov token fetch. This will require Internet access. This combination will cause additional logging to be collected by Approov’s server side to aid investigation.