Attest¶
Register this device's signing key with DEA. The SDK creates the key the first time it is needed, inside the Secure Enclave where the device has one, so the private half cannot be read by your app or by the SDK. Where the device supports it, the registration also carries an Apple App Attest token, so DEA can check that the device and the app are genuine. It also carries, as short codes, the signs of a jailbroken or modified device the SDK found, such as a jailbreak's files or a debugger; never a file path or an app name.
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 started. See the Quick Start.
- The
appKeyDEA issued for this iOS app, passed asappKey. Without it,attest()answers.unsupportedand contacts nobody. See Accounts and keys. - The platform setup below.
Platform setup¶
App Attest¶
Add the App Attest capability to your app:
- Open your project in Xcode.
- Select your app target, then Signing & Capabilities.
- Click + Capability and add App Attest.
This adds the com.apple.developer.devicecheck.appattest-environment entitlement. App Attest needs
iOS 14 or later and is not available on the iOS Simulator. There, on older devices, and on devices
without App Attest, the SDK sends the key without an App Attest token.
Usage¶
Attest¶
attest() makes network requests and blocks the calling thread until they finish (up to about
15 seconds per request, and as long again for App Attest). Never call it on the main thread:
import DeaSDK
Task.detached {
guard let attestation = Dea.sdk?.attest() else { return }
switch attestation.verdict {
case .passed:
// DEA accepted this device's key.
break
case .unsupported:
// No key could be created on this device, or no appKey was given.
print("DEA attestation unsupported: \(attestation.detail ?? "no device key")")
case .failed:
// DEA refused, could not be reached, or App Attest refused. See attestation.detail.
print("DEA attestation failed: \(attestation.detail ?? "")")
}
}
attest()never throws.- A passed result is cached, across launches too, until shortly before it expires, so calling it again costs nothing.
- After the first
attest(), the SDK renews the registration by itself shortly before it expires, including when the app returns to the foreground. An app that never callsattest()never registers a key and makes no attestation requests. - After DEA refuses a registration that used an App Attest token,
attest()answers.failedwithout contacting DEA for a while — one hour at first, doubling with each refusal in a row, up to seven days — anddetailsays until when. Apple counts every App Attest attestation for this app on the device, so the SDK does not spend them on a registration DEA keeps refusing.
Check whether the platform vouched for the device¶
A passed verdict means DEA accepted the key. It is a statement about the device only when
platformAttested is true:
if attestation.verdict == .passed && attestation.platformAttested {
// Apple confirmed the device and app.
}
platformAttested is false when the registration went up without an App Attest token: on the
simulator, on devices older than iOS 14 or without App Attest, and when App Attest did not answer in
time.
Read the last result without attesting again¶
Check for a registration without contacting DEA¶
Task.detached {
if let stored = Dea.sdk?.storedAttestation() {
print("Already registered until \(stored.expiresAt ?? 0)")
}
}
storedAttestation() returns the registration this install already holds, if it is still valid,
without any network request and without using App Attest; nil means attest() would have to
register again. It reads the keychain, so call it off the main thread. A registration found this
way is renewed automatically, as after attest().
Renew now¶
Task.detached {
if let renewed = Dea.sdk?.renewAttestation() {
print("DEA attestation renewed: \(renewed.verdict)")
}
}
renewAttestation() registers again even while the current result is still valid — for example
right before a sensitive step. If the renewal fails, the previous valid result is kept.
Like attest(), it blocks; call it off the main thread.
Start over¶
forgetAttestation() forgets this install's registration and any wait after refused attempts, so
the next attest() registers from the start. The device key is kept. The automatic renewal stops
until the next attest(). It waits for an attest() already in progress, so call it off the main
thread.
Examples¶
Attest before a payment step (SwiftUI)¶
import DeaSDK
import SwiftUI
struct CheckoutButton: View {
@State private var showPayment = false
var body: some View {
Button("Pay") {
Task {
let attestation = await Task.detached { Dea.sdk?.attest() }.value
if attestation?.verdict != .passed {
print("DEA attestation: \(String(describing: attestation))")
}
showPayment = true
}
}
.sheet(isPresented: $showPayment) {
PaymentView()
}
}
}
Attest at launch (UIKit)¶
import DeaSDK
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
Dea.start()
DispatchQueue.global(qos: .utility).async {
_ = Dea.sdk?.attest()
}
return true
}
}
API¶
| Member | Returns | Description |
|---|---|---|
attest() |
Attestation |
Registers the device key and returns the result. Blocking |
renewAttestation() |
Attestation |
Registers again now, keeping the previous result on failure. Blocking |
storedAttestation() |
Attestation? |
The valid registration this install holds, without a network request |
forgetAttestation() |
Void |
Forgets the registration, so the next attest() registers from the start |
lastAttestation |
Attestation? |
The last result, without attesting again |
nextAttestationRefresh |
Date? |
When the automatic renewal will next run |
appKey |
String? |
The app key the SDK sends; nil when none was given |
Attestation:
| Field | Type | Description |
|---|---|---|
supported |
Bool |
Whether a key could be created on this device and an appKey was given |
verdict |
Attestation.Verdict |
.passed, .failed or .unsupported |
platformAttested |
Bool |
Whether an App Attest token went up with the key and DEA accepted it |
platformTokenSpent |
Bool |
Whether this attempt used an App Attest attestation, whatever the result |
expiresAt |
Int? |
Expiry, in epoch seconds |
kid |
String? |
The key's identifier |
tier |
String? |
The trust tier DEA assigned |
integrity |
String? |
DEA's reading of the device's integrity, when its answer carries one |
backing |
String? |
Where the key is kept: secure_enclave or software |
detail |
String? |
Why attestation did not pass, when it did not |