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.
This section shows you how to get the Android Approov SDK and integrate it into your project in Android Studio.
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
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.
Navigate to File > Project Structure
.
Ensure that Dependencies
is selected in the left hand side of the dialog that pops up.
Click the +
and then .JAR/.AAR Dependency
in the dropdown.
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
.
Click OK
in the Project Structure
dialog.
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.
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.
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.
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.
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:
In the Xcode project editor, select the target to which you want to add the Approov SDK framework.
Select the General
tab.
Select the + (plus) icon under the Embedded Binaries
section.
Select Add Other...
in the file dialog and browse to the Approov.xcframework
obtained using the approov
command line tool.
Select Open
in the file dialog.
Ensure the Copy items if needed
destination option is checked, then select Finish.
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.
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>
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.
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.
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.
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.
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:
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.
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
}
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")
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.
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.
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.
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:
*
domain; if there are then they are should be used as the only accepted set of pins, representing trusted root certificates.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.
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.
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.
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.
/**
* 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
/**
* 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?
/**
* 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?
/**
* 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]]?
/**
* 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?
/**
* 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
/**
* 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
/**
* 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)
/**
* 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
/**
* 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)
/**
* 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
/**
* 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)
/**
* 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)
/**
* 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)
/**
* 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?
/**
* 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?
/**
* 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?
/**
* 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?)
/**
* 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);
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.
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.
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:
?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.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:
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.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.
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:
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.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
.
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.
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.