The Digi.me SDK for Android is a multi-module library that allows seamless authentication with Consent Access service, making content requests and core decryption services. For details on the API and general CA architecture, visit Dev Support Docs.
Digi.me SDK depends on digi.me app being installed to enable user initiated authorization of requests. For detailed explanation of the Consent Access architecture please visit Dev Support Docs.
- Manual Installation
- Proguard setup
- Configuring SDK usage
- Providing contract private key
- Callbacks and responses
- Authorization
- Fetching data
NOTE: For testing and initial integration,DEVELOPMENT builds should be used, since production usage might be subject to usage billing. For instructions how to set up use of Dev version see the section below.
- Add the repository path to your root build.gradle
allprojects {
repositories {
maven { url "https://repository.sysdigi.net/m2/libs-release"}
}
}
-
If creating a new project set Minimum SDK to 21.
-
For existing projects set minSdkVersion to 21 in build.gradle
-
In your project build.gradle (for example app.build.gradle) add the digime-core dependency
dependencies {
compile 'me.digi.sdk:digime-core:1.3.2'
}
- You should be able to import
me.digi.sdk.core.DigiMeClient
now.
For testing purposes or initial integration dev version of the library should always be used. To use development version, following dependency should be imported.
dependencies {
compile '...:digime-core:1.3.2-dev'
}
Snapshot builds can be retrieved from the SNAPSHOTS repository. Snapshots can be used to try out new in-development features. It is discouraged to use snapshots in production releases. To use snapshot builds use the following dependency:
dependencies {
compile '...:digime-core:1.3.3-SNAPSHOT'
}
-
Download source code
-
If creating a new project set Minimum SDK to 21.
-
For existing projects set minSdkVersion to 21 in build.gradle
-
In Android Studio, go to File > New > New Module, select "Import Existing Project as Module"
-
Specify location of the downloaded code
-
Go to File > Project Structure, add the SDK module as a dependency for your project
-
You should be able to import
me.digi.sdk.core.DigiMeClient
now.
If proguard is enabled in the project you might need to add following parameters to proguard configuration:
-dontwarn retrofit2.**
-dontwarn javax.naming.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn`` okhttp3.**
-dontwarn okio.**
-dontwarn com.google.gson.**
-dontwarn org.spongycastle.**
Before accessing the public APIs, a valid Contract ID needs to be registered for an App ID. The Contract ID uniquely identifies a contract with the user that spells out what type of data you want, what you will and won't do with it, how long you will retain it and if you will implement the right to be forgotten. It also specifies how the data is encrypted in transit.
To register a Consent Access contract check out Digi.me Dev Support. There you can request a Contract ID and App ID to which it is bound.
DigiMeClient is the main hub for all the interaction with the API. You access it through it's singleton accessor:
DigiMeClient.getInstance()
DigiMeClient is automatically bootstrapped so there is no need to initialize it onCreate. However before you start interacting with it in your app, you will need to configure it with your contractId and appId:
Add your contractId
and appId
to a project resource file (for example strings.xml) and reference them in your Android manifest:
-
Add a string to your strings.xml with the value of your
contractId
(example name digime_contract_id) -
In AndroidManifest.xml add a
meta-data
element specifying yourcontractId
:
<application ...>
...
<meta-data android:name="me.digi.sdk.Contracts" android:resource="@string/digime_contract_id" />
...
</application>
- Include you
appId
by adding ameta-data
element:
<application ...>
...
<meta-data android:name="me.digi.sdk.AppId" android:value="@string/DIGIME_APP_ID" />
...
</application>
- SDK needs internet permission so you need to add it with uses-permission element:
<uses-permission android:name="android.permission.INTERNET" />
- Since DigiMeClient calls out to Digi.me app to let the user authorize request for data, you need to add the following
intent-filter
:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
- Optionally you can include a custom App Name by specifying
meta-data
element:
<application ...>
...
<meta-data android:name="me.digi.sdk.AppName" android:value="@string/app_name" />
...
</application>
All content retrieved by the SDK is encrypted in transit using the public key bound to the certificate that was created when the Consent Access contract was created. For SDK to be able to decrypt content transparently matching private key must be provided (ie. from the keypair created for contract).
Digi.me SDK accepts PKCS #12 encoded files as the default key storage format.
API exposes multiple input vectors for p12 files.
Digi.me SDK provides multiple helper methods to read and extract keys from p12 files.
KeyLoaderProvider
is the object that manages all the keys. Invoking DigiMeClient.getDefaultKeyLoader()
returns the default provider.
From there we have an option to add a p12 file content via InputStream
:
DigiMeClient.getDefaultKeyLoader().addKeyFromPKCS12Stream(stream, keystore_passphrase);
or manually from assets/resources:
//From assets
DigiMeClient.getDefaultKeyLoader().getStore().addPKCS12KeyFromAssets(context, assetPath, null, store_passphrase, null);
//or from resources
DigiMeClient.getDefaultKeyLoader().getStore().addPKCS12KeyFromResources(context, resourceID, null, store_passphrase, null);
Utility class PKCS12Utils
also provides additional input vectors (it is used internally):
getPKCS12KeysFromByteArray
getPKCS12KeysFromBase64
Utility methods return a List<PrivateKey>
which in turn can be sent to the default loader:
DigiMeClient.getDefaultKeyLoader().getStore().addFromList(List_of_private_keys);
If the p12 file is located in application assets, SDK can extract it with a valid asset path:
<application>
<meta-data android:name="me.digi.sdk.Keys" android:value="path_to_file_in_assets" />
</application>
In case the p12 file is part of app resources providing the resource ID will be enough to extract the file:
<application>
<meta-data android:name="me.digi.sdk.Keys" android:value="integer_id_of_the_resource" />
</application>
Since it is recommended for p12 files to be locked with a passphrase, provide the passphrase through meta-data:
<application>
<meta-data android:name="me.digi.sdk.KeysPassphrase" android:value="passphrase" />
</application>
In rare cases (usage not recommended) app might need to provide raw private keys either encoded with PEM or raw PKCS#8 format:
/*
* If key is hexadecimal string, extract it
*/
byte[] key = ByteUtils.hexToBytes(hexCodedKey);
/*
* Extract PrivateKey from byte array
*/
PrivateKey privateKey = CryptoUtils.getPrivateKey(key);
/*
* Add the key to the SDK key provider
*/
DigiMeClient.getDefaultKeyLoader().getStore().addKey(privateKey);
Digi.me SDK is built to be asynchronous and thread-safe and as such it provides a couple of mechanisms of redirecting results back to the application. For that purpose the SDK provides SDKCallback interface and SDKListener interface.
Both of them are interchangeable and can be used depending on preference. They can be used both at the same time, although such usage would result in very verbose code.
SDKCallback
is provided per call and it contains a SDKResponse object which encapsulates all of the objects you can get for the request and actual raw response data.
To use a callback you pass it's reference to the request:
import me.digi.sdk.core.DigiMeClient;
import me.digi.sdk.core.SDKCallback;
import me.digi.sdk.core.SDKException;
import me.digi.sdk.core.SDKResponse;
DigiMeClient.getInstance().getFileList(new SDKCallback<CAFiles>() {
@Override
public void succeeded(SDKResponse<CAFiles> result) {
CAFiles files = result.body;
}
@Override
public void failed(SDKException exception) {
//Handle exception or error response
}
});
Alternatively if you prefer the SDKListener
interface, callbacks can be omitted by passing null
.
DigiMeClient.getInstance().getFileList(null);
SDKListener
provides a central listening pipe for all the relevant SDK events.
To start listening you must implement the SDKListener
interface (most frequently in your Launch Activity) and register it with the DigiMeClient (for example in the onCreate method of your Launch Activity).
public class MainActivity extends ... implements SDKListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
DigiMeClient.getInstance().addListener(this);
}
}
To start getting data into application, you'll need to authorize a session. Authorization flow is separated into two phases:
-
Initialize a session with digi.me API (returns a CASession object)
-
Authorize session with the digi.me app and prepare data if user accepts.
SDK starts and handles these steps automatically by calling the authorize(Activity, SDKCallback)
method.
This method expects a reference to the calling activity and optionally a callback.
DigiMeClient.getInstance().authorize(this, new SDKCallback<CASession>() {
@Override
public void succeeded(SDKResponse<CASession> result) {
}
@Override
public void failed(SDKException exception) {
}
});
On success it returns a CASession
in your callback, which encapsulates session data required for further calls.
Since authorize()
automatically calls into digi.me app, you'll need some way of handling the switch back to your app.
You will accomplish this by overriding onActivityResult
for your Activity.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
DigiMeClient.getInstance().getAuthManager().onActivityResult(requestCode, resultCode, data);
}
Method authorize() also returns an instance of DigiMeAuthorizationManager
, which in this case is the default one you can access by calling:
DigiMeClient.getInstance().getAuthManager()
If using the SDKListener interface instead of callbacks, it will trigger these events:
void sessionCreated(CASession session);
void sessionCreateFailed(SDKException reason);
/*
* User approved in the digi.me app and data ready
*/
void authorizeSucceeded(CASession session);
/*
* User declined
*/
void authorizeDenied(AuthorizationException reason);
/*
* Activity passed a wrong request code, most likely from another application
*/
void authorizeFailedWithWrongRequestCode();
Furthermore you don't have to keep a reference to the returned CASession object internally, since DigiMeClient already track that object. You can always reference it later if need arises, but such scenarios are very rare:
DigiMeClient.getInstance().getSessionManager().getCurrentSession()
Upon successful authorization you can request user's files. To fetch the list of available files for your contract:
/* @param callback reference to the SDKCallback<CAFiles> or null if using SDKListener
*
*/
DigiMeClient.getInstance().getFileList(callback)
Upon success DigiMeClient returns a CAFiles
object which contains a single field fileIds
, a list of file IDs.
Finally you can use the returned file IDs to fetch their data:
/* @param fileId ID of the file to retrieve
* @param callback reference to the SDKCallback<CAFileResponse> or null if using SDKListener
*/
DigiMeClient.getInstance().getFileContent(fileId, callback)
Upon success DigiMeClient returns a CAFileResponse
which contains a list of deserialized content objects (CAContent
)
For detailed content item structure look at Dev Docs.
Due to asynchronous nature of Consent Access architecture, it is possible for the CA services to return the 404 HTTP response. 404 errors in this context indicate that "File is not ready". In other words CA services have yet to finish copying and encrypting the content for your created session.
Digi.me SDK handles those errors internally and retries those requests with exponential backoff policy. The defaults are set to 3 retries with base lower interval of 500ms.
In the event that content is not ready even after retrying, SDK will return an exception to appropriate callback/listener.
All of those parameters can be adjusted globally including toggling the backoff policy on/off.
Connection timeout in seconds:
int globalConnectTimeout;
Connection read/write IO timeout in seconds:
int globalReadWriteTimeout;
Controls retries globally; toggle automatic retries on/off:
boolean retryOnFail;
Minimal base delay for retries:
long minRetryPeriod;
Toggle exponential backoff policy on/off:
boolean retryWithExponentialBackoff;
Maximum number of times to retry before failing. 0 uses per call defaults, >0 sets a global hard limit. Defaults to 0:
int maxRetryCount;
These configuration options are set statically on DigiMeClient:
//Set base delay to 1000 ms
DigiMeClient.minRetryPeriod = 1000;
In some cases it is beneficial to have access to the complete underlying json response. As with regular fetch you can retrieve the data once you have the list of file IDs with:
/* @param fileId ID of the file to retrieve
* @param callback reference to the SDKCallback<JsonElement> or null if using SDKListener
*/
DigiMeClient.getInstance().getFileJSON(fileId, callback)
Upon success DigiMeClient returns a JsonElement
which contains complete file content.
For detailed content item structure look at Dev Docs.
DigiMeSDK also provides relevant metadata about service accounts linked to returned file content. You can fetch account details after obtaining a valid authorized session key with:
/*
* @param callback reference to the SDKCallback<CAAccounts> or null if using SDKListener
*/
DigiMeClient.getInstance().getAccounts(callback)
Upon success DigiMeClient returns a CAAccounts
object which contains List<>
of CAAccount
objects.
Among others, most notable properties ofCAAccount
object are service.name
- name of the underlying service,
accountId
- account identifier which can be used to link returned entities to specific account.
There are no additional steps necessary to decrypt the data, the SDK handles the decryption and cryptography management behind the scenes.
In cases where you don't want to use the SDK for requests, the security module can be used independently.
Just import me.digi.sdk.crypto
package.
For details on such implementation check out the examples/consent-access-no-sdk example app.