Attest¶
Register this device's signing key with DEA. The SDK creates the key the first time it is needed, inside the Android Keystore — in StrongBox where the device has it — where the private half cannot be read by your app or by the SDK. Where possible, the registration also carries a Google Play Integrity token, so DEA can check that the device and the app are genuine.
DEA files the registration under the app your appKey belongs to. See
Accounts and keys.
When to use it¶
- Before a sensitive step, to make sure the device is registered
- To show attestation status in a diagnostics screen
Prerequisites¶
- The SDK is installed and initialized with your
appId,appKeyandplayIntegrityProject. See the Quick Start and Accounts and keys. - Android 6.0 (API 23) or later. Below that,
attest()answersUNSUPPORTED. - The Play Integrity setup below.
Platform setup¶
Play Integrity¶
Play only issues a genuine token to an app it recognizes: installed from Google Play, or signed with
a key registered in your app's Play Console entry. When Play gives no token — on a device without
Google Play services, or when Play refuses the request — the SDK sends the registration without
one, and DEA may refuse it. attest() then answers FAILED, with DEA's answer in detail.
Pass the Google Cloud project number DEA issued for your app as playIntegrityProject. It must
be the project your app is linked to on the App integrity page of Play Console. With any other
number, attest() answers FAILED. See Accounts and keys.
DeaSdk.init(
this,
DeaConfig(appId = "YOUR_APP_ID", appKey = "YOUR_APP_KEY", playIntegrityProject = 123456789012L),
)
Usage¶
Attest¶
attest() makes network requests and blocks the calling thread, so call it off the main
thread. These samples use Kotlin coroutines:
import ai.dea.sdk.DeaSdk
import ai.dea.sdk.attest.Verdict
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend fun attestDevice() {
val attestation = withContext(Dispatchers.IO) { DeaSdk.instance?.attest() } ?: return
when (attestation.verdict) {
Verdict.PASSED -> {
// DEA accepted this device's key.
}
Verdict.UNSUPPORTED -> {
// No key could be created on this device, or no appKey was given. See attestation.detail.
}
Verdict.FAILED -> {
// DEA refused, or could not be reached. See attestation.detail.
}
}
}
attest()never throws.- The result is cached on the device until shortly before it expires, including across app restarts, so calling it again usually costs nothing.
- The first call turns on automatic renewal. From then on the SDK renews the registration
shortly before it expires, and re-checks whenever the app returns to the foreground. An app that
never calls
attest()never registers a key. - After a registration that used a Play Integrity token fails, the SDK waits before it tries
again: one hour, doubling with each further failure up to seven days, also across app restarts.
Until then
attest()answersFAILEDwithout a request, anddetailsays until when.
The registration sends DEA the key's public half, the Play Integrity token when there is one, and codes for any signs of a rooted, hooked, debugged or emulated device that the SDK found. See Privacy and network.
Check whether Play Integrity was part of it¶
A PASSED verdict means DEA accepted the key. platformAttested is true when a Play Integrity
token went with the accepted registration:
if (attestation.verdict == Verdict.PASSED && attestation.platformAttested) {
// DEA accepted the key, with Google's token about the device and app.
}
integrity carries DEA's own reading of the device, when DEA returns one.
Read the last result without attesting again¶
To read the registration this install already holds, including one from an earlier launch, without any request:
val stored = withContext(Dispatchers.IO) { DeaSdk.instance?.storedAttestation() } // null if none is fresh
storedAttestation() reads the device's storage. When it finds a fresh registration, it turns on
automatic renewal as attest() would.
Renew now¶
renewAttestation() registers again right away, even if the cached result is still valid. If the
renewal fails, the registration already held is kept. While the SDK is waiting after refused
registrations, it answers FAILED without a request, like attest(). Blocking, like attest().
Start over¶
Forgets the stored registration and any wait after refused attempts, and stops automatic renewal.
The device key is kept, so the next attest() registers the same key again.
Watch automatic renewal¶
import ai.dea.sdk.DeaSdk
import ai.dea.sdk.attest.AttestRefresher
import android.util.Log
val dea = DeaSdk.instance!!
// Set before the first attest(), which reports the first schedule.
dea.onAttestationEvent = { event ->
when (event) {
is AttestRefresher.Event.Scheduled -> Log.d("MyApp", "renewal in ${event.afterSeconds} s")
is AttestRefresher.Event.Fired -> Log.d("MyApp", "renewed: ${event.verdict}")
AttestRefresher.Event.Idle -> Log.d("MyApp", "nothing to renew")
}
}
Log.d("MyApp", "next renewal at ${dea.nextAttestationRefresh}") // epoch seconds, or null
The callback runs on the thread that made the decision, which is not always the main thread.
Examples¶
Attest before opening a payment screen¶
import ai.dea.sdk.DeaSdk
import ai.dea.sdk.attest.Verdict
import android.content.Intent
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
fun AppCompatActivity.openPayment() {
lifecycleScope.launch {
val attestation = withContext(Dispatchers.IO) { DeaSdk.instance?.attest() }
if (attestation?.verdict != Verdict.PASSED) {
Log.w("MyApp", "DEA attestation: $attestation")
}
startActivity(Intent(this@openPayment, PaymentActivity::class.java))
}
}
API¶
| Member | Returns | Description |
|---|---|---|
attest() |
Attestation |
Registers the device key and returns the result. Blocking |
renewAttestation() |
Attestation |
Registers again now. Blocking |
lastAttestation |
Attestation? |
The last result, without attesting again |
storedAttestation() |
Attestation? |
The fresh registration this install holds, read from storage without a request |
forgetAttestation() |
Unit |
Forgets the registration and stops automatic renewal; keeps the key |
nextAttestationRefresh |
Long? |
When the next automatic renewal runs, epoch seconds |
onAttestationEvent |
((AttestRefresher.Event) -> Unit)? |
Called on each automatic renewal decision |
Attestation:
| Field | Type | Description |
|---|---|---|
supported |
Boolean |
Whether the SDK could register: false when no key could be created, or no appKey was given |
verdict |
Verdict |
PASSED, FAILED or UNSUPPORTED |
platformAttested |
Boolean |
Whether a Play Integrity token went with an accepted registration |
platformTokenSpent |
Boolean |
Whether this attempt obtained a Play Integrity token, whatever DEA answered |
integrity |
String? |
DEA's reading of the device's integrity, when DEA returned one |
expiresAt |
Long? |
Expiry, in epoch seconds |
kid |
String? |
The key's identifier |
tier |
String? |
The trust tier DEA assigned |
backing |
String? |
Where the key is kept: strongbox, tee or software |
detail |
String? |
Why the SDK did not register, or what DEA or the network answered when it failed |